PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.0
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
easy-invoice / templates / clients-page.php

clients-page.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.0, at templates/clients-page.php

643 lines 37.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Extract data passed from controller
3 $clients = $clients ?? [];
4 $stats = $stats ?? [];
5 $current_page = $current_page ?? 1;
6 $per_page = $per_page ?? 20;
7 $total_clients = $total_clients ?? 0;
8 $total_pages = $total_pages ?? 1;
9
10 // $clients variable contains Client objects
11
12 ?>
13 <div class="p-8">
14 <!-- Header with Actions -->
15 <div class="bg-white border-b border-gray-200 px-6 py-5" style="margin-left: -2rem; margin-top: -2rem; padding-left:2rem; padding-right:2rem; margin-right: -2rem;">
16 <div class="flex items-center justify-between">
17 <div>
18 <h1 class="text-2xl font-bold text-gray-900"><?php _e('Clients', 'easy-invoice'); ?></h1>
19 <p class="mt-1 text-sm text-gray-500"><?php _e('Manage all client records', 'easy-invoice'); ?></p>
20 </div>
21 <div class="flex items-center space-x-3">
22 <?php if (!easy_invoice_has_pro()): ?>
23 <button type="button" id="export-all-clients-btn" class="premium-export-btn inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200 relative">
24 <svg class="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
25 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
26 </svg>
27 <?php _e('Export All', 'easy-invoice'); ?>
28 <svg class="ml-2 w-5 h-5 text-purple-600 crown-icon" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
29 <path d="M12 8L15 13.2L18 10.5L17.3 14H6.7L6 10.5L9 13.2L12 8M12 4L8.5 10L3 5L5 16H19L21 5L15.5 10L12 4Z"/>
30 </svg>
31 </button>
32 <script>
33 jQuery(document).ready(function($) {
34 $('#export-all-clients-btn').on('click', function(e) {
35 e.preventDefault();
36 // Show premium popup using the proper confirmation modal
37 if (typeof EasyInvoiceConfirmation !== 'undefined') {
38 EasyInvoiceConfirmation.showFeatureUpgrade('Export All Clients', 'Export all your clients to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.');
39 } else {
40 // Fallback to toast if confirmation modal is not available
41 EasyInvoiceToast.error('<?php echo esc_js(__('Exporting all clients is a premium feature. Upgrade to Easy Invoice Pro to unlock this feature.', 'easy-invoice')); ?>');
42 }
43 });
44 });
45 </script>
46 <?php else: ?>
47 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
48 <input type="hidden" name="action" value="easy_invoice_export_all_clients" />
49 <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce('easy_invoice_export_all_clients'); ?>" />
50 <button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
51 <svg class="w-4 h-4 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
52 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
53 </svg>
54 <?php _e('Export All', 'easy-invoice'); ?>
55 </button>
56 </form>
57 <?php endif; ?>
58 <button id="add-new-client-btn" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
59 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
60 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
61 </svg>
62 <?php _e('Add New Client', 'easy-invoice'); ?>
63 </button>
64 </div>
65 </div>
66 </div>
67
68 <!-- Stats Cards -->
69 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8">
70 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
71 <div class="flex items-center">
72 <div class="rounded-full bg-blue-100 p-3 mr-4">
73 <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
74 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 21V19C17 17.9391 16.5786 16.9217 15.8284 16.1716C15.0783 15.4214 14.0609 15 13 15H5C3.93913 15 2.92172 15.4214 2.17157 16.1716C1.42143 16.9217 1 17.9391 1 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
75 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
76 <path d="M23 21V19C23 18.1137 22.6485 17.2628 22.0237 16.6425C21.3989 16.0221 20.5471 15.6722 19.66 15.6722C18.7729 15.6722 17.9211 16.0221 17.2963 16.6425C16.6715 17.2628 16.32 18.1137 16.32 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
77 <path d="M16.32 11.6722C18.36 11.6722 20 10.0322 20 7.99219C20 5.95219 18.36 4.31219 16.32 4.31219" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
78 </svg>
79 </div>
80 <div>
81 <h3 class="text-sm font-medium text-gray-500">Total Clients</h3>
82 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo isset($stats['total_clients']) ? $stats['total_clients'] : count($clients); ?></p>
83 </div>
84 </div>
85 </div>
86 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
87 <div class="flex items-center">
88 <div class="rounded-full bg-indigo-100 p-3 mr-4">
89 <svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
90 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
91 </svg>
92 </div>
93 <div>
94 <h3 class="text-sm font-medium text-gray-500">Total Invoices</h3>
95 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo isset($stats['total_invoices']) ? $stats['total_invoices'] : 0; ?></p>
96 </div>
97 </div>
98 </div>
99 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
100 <div class="flex items-center">
101 <div class="rounded-full bg-green-100 p-3 mr-4">
102 <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
103 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
104 </svg>
105 </div>
106 <div>
107 <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3>
108 <?php if (is_array($stats['total_revenue']) && !empty($stats['total_revenue'])): ?>
109 <div class="mt-1">
110 <?php
111 $currency_count = count($stats['total_revenue']);
112 foreach ($stats['total_revenue'] as $index => $data):
113 $is_last = $index === $currency_count - 1;
114 // Get currency code from the data array or use a fallback
115 $currency_code = isset($data['currency_code']) ? $data['currency_code'] : 'USD';
116 ?>
117 <div class="<?php echo $currency_count > 1 ? 'text-sm' : 'text-lg'; ?> font-bold text-gray-900 <?php echo !$is_last ? 'mb-1' : ''; ?>">
118 <?php echo $data['symbol'] . number_format($data['amount'], 2); ?>
119 <span class="text-xs font-normal text-gray-500"><?php echo strtoupper($currency_code ?? 'USD'); ?></span>
120 </div>
121 <?php endforeach; ?>
122 </div>
123 <?php else: ?>
124 <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p>
125 <?php endif; ?>
126 </div>
127 </div>
128 </div>
129 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
130 <div class="flex items-center">
131 <div class="rounded-full bg-yellow-100 p-3 mr-4">
132 <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
133 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
134 </svg>
135 </div>
136 <div>
137 <h3 class="text-sm font-medium text-gray-500">Pending Invoices</h3>
138 <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo isset($stats['pending_invoices']) ? $stats['pending_invoices'] : 0; ?></p>
139 </div>
140 </div>
141 </div>
142 </div>
143
144 <!-- Top Pagination -->
145 <?php if ($total_clients > 0): ?>
146 <div class="mb-4">
147 <div class="px-4 py-3 flex items-center justify-between">
148 <div class="flex items-center space-x-2">
149 <span class="text-sm text-gray-700">
150 Showing
151 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
152 to
153 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_clients)); ?></span>
154 of
155 <span class="font-medium"><?php echo number_format($total_clients); ?></span>
156 results
157 </span>
158 </div>
159 <?php if ($total_pages > 1): ?>
160 <div class="flex items-center space-x-2">
161 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
162 <?php
163 // Use WordPress's built-in pagination for top
164 $top_pagination_args = array(
165 'base' => add_query_arg('paged', '%#%'),
166 'format' => '',
167 'current' => $current_page,
168 'total' => $total_pages,
169 'prev_text' => '‹ Previous',
170 'next_text' => 'Next ›',
171 'type' => 'array',
172 'end_size' => 1,
173 'mid_size' => 2,
174 'add_args' => array(
175 'page' => 'easy-invoice-clients'
176 )
177 );
178
179 $top_pagination_links = paginate_links($top_pagination_args);
180
181 if ($top_pagination_links) {
182 echo '<div class="flex space-x-1">';
183 foreach ($top_pagination_links as $link) {
184 // Add null check to prevent passing null to easy_invoice_str_replace
185 if ($link === null) {
186 continue;
187 }
188
189 // Convert WordPress pagination to our styling
190 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
191 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
192 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
193 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
194 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
195
196 // Add default styling for regular page numbers
197 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
198 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
199 }
200
201 echo $link;
202 }
203 echo '</div>';
204 }
205 ?>
206 </div>
207 <?php endif; ?>
208 </div>
209 </div>
210 <?php endif; ?>
211
212 <!-- Clients Table -->
213 <div class="bg-white rounded-lg shadow">
214 <div class="overflow-x-auto">
215 <table class="min-w-full divide-y divide-gray-200 client-list-table">
216 <thead class="bg-gray-50">
217 <tr>
218 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="id">
219 <div class="flex items-center space-x-1">
220 <span>Client ID</span>
221 <div class="sort-indicator">
222 <i class="fas fa-sort text-gray-400"></i>
223 </div>
224 </div>
225 </th>
226 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="name">
227 <div class="flex items-center space-x-1">
228 <span>Client Name</span>
229 <div class="sort-indicator">
230 <i class="fas fa-sort text-gray-400"></i>
231 </div>
232 </div>
233 </th>
234 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="email">
235 <div class="flex items-center space-x-1">
236 <span>Email</span>
237 <div class="sort-indicator">
238 <i class="fas fa-sort text-gray-400"></i>
239 </div>
240 </div>
241 </th>
242 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="username">
243 <div class="flex items-center space-x-1">
244 <span>Username</span>
245 <div class="sort-indicator">
246 <i class="fas fa-sort text-gray-400"></i>
247 </div>
248 </div>
249 </th>
250 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="role">
251 <div class="flex items-center space-x-1">
252 <span>Role</span>
253 <div class="sort-indicator">
254 <i class="fas fa-sort text-gray-400"></i>
255 </div>
256 </div>
257 </th>
258 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
259 Actions
260 </th>
261 </tr>
262 </thead>
263 <tbody class="bg-white divide-y divide-gray-200">
264 <?php if (empty($clients)): ?>
265 <tr>
266 <td colspan="6" class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">
267 No clients found
268 </td>
269 </tr>
270 <?php endif; ?>
271 <?php foreach ($clients as $client): ?>
272 <?php
273 $clientName = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName());
274 if(empty(trim($clientName))) {
275 $clientName = $client->getUsername();
276 }
277 $clientId = $client->getId();
278
279 // Get client role
280 $user = get_user_by('ID', $clientId);
281 $client_role = $user ? $user->roles[0] ?? 'client' : 'client';
282
283 // Role styling
284 $role_class = 'bg-blue-100 text-blue-800';
285 $role_icon = 'fas fa-user';
286
287 switch ($client_role) {
288 case 'administrator':
289 $role_class = 'bg-red-100 text-red-800';
290 $role_icon = 'fas fa-crown';
291 break;
292 case 'editor':
293 $role_class = 'bg-purple-100 text-purple-800';
294 $role_icon = 'fas fa-user-edit';
295 break;
296 case 'author':
297 $role_class = 'bg-green-100 text-green-800';
298 $role_icon = 'fas fa-user-edit';
299 break;
300 case 'contributor':
301 $role_class = 'bg-yellow-100 text-yellow-800';
302 $role_icon = 'fas fa-user-plus';
303 break;
304 case 'subscriber':
305 $role_class = 'bg-gray-100 text-gray-800';
306 $role_icon = 'fas fa-user';
307 break;
308 default:
309 $role_class = 'bg-blue-100 text-blue-800';
310 $role_icon = 'fas fa-user';
311 }
312 ?>
313 <tr>
314 <td class="px-6 py-4 whitespace-nowrap">
315 <div class="text-sm text-gray-500 font-mono font-semibold"><?php echo esc_html($clientId); ?></div>
316 </td>
317 <td class="px-6 py-4 whitespace-nowrap">
318 <div class="text-sm font-medium text-gray-900"><?php echo esc_html($clientName); ?></div>
319 </td>
320 <td class="px-6 py-4 whitespace-nowrap">
321 <div class="text-sm text-gray-500"><?php echo esc_html($client->getEmail()); ?></div>
322 </td>
323 <td class="px-6 py-4 whitespace-nowrap">
324 <div class="text-sm text-gray-500"><?php echo esc_html($client->getUsername()); ?></div>
325 </td>
326 <td class="px-6 py-4 whitespace-nowrap">
327 <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <?php echo $role_class; ?>">
328 <i class="<?php echo $role_icon; ?> mr-1"></i>
329 <?php echo esc_html(ucfirst($client_role)); ?>
330 </span>
331 </td>
332 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
333 <div class="flex items-center space-x-2">
334 <a href="?page=easy-invoice-client-view&client_id=<?php echo esc_attr($clientId); ?>"
335 class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200"
336 title="View Client">
337 <i class="fas fa-eye mr-1"></i>
338 View
339 </a>
340 <a href="?page=easy-invoice-client-edit&client_id=<?php echo esc_attr($clientId); ?>"
341 class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
342 title="Edit Client">
343 <i class="fas fa-edit mr-1"></i>
344 Edit
345 </a>
346 <?php if ($client_role === 'administrator'): ?>
347 <button type="button"
348 class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-gray-400 bg-gray-100 cursor-not-allowed opacity-50"
349 title="Cannot delete administrator"
350 disabled>
351 <i class="fas fa-trash mr-1"></i>
352 Delete
353 </button>
354 <?php else: ?>
355 <button type="button"
356 class="delete-client inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200"
357 data-id="<?php echo esc_attr($clientId); ?>"
358 title="Delete Client">
359 <i class="fas fa-trash mr-1"></i>
360 Delete
361 </button>
362 <?php endif; ?>
363 </div>
364 </td>
365 </tr>
366 <?php endforeach; ?>
367 </tbody>
368 </table>
369 </div>
370 </div>
371
372 <!-- Bottom Pagination -->
373 <?php if ($total_clients > 0): ?>
374 <div class="mt-4">
375 <div class="px-4 py-3 flex items-center justify-between">
376 <div class="flex items-center space-x-2">
377 <span class="text-sm text-gray-700">
378 Showing
379 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
380 to
381 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_clients)); ?></span>
382 of
383 <span class="font-medium"><?php echo number_format($total_clients); ?></span>
384 results
385 </span>
386 </div>
387 <?php if ($total_pages > 1): ?>
388 <div class="flex items-center space-x-2">
389 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
390 <?php
391 // Use WordPress's built-in pagination for bottom
392 $bottom_pagination_args = array(
393 'base' => add_query_arg('paged', '%#%'),
394 'format' => '',
395 'current' => $current_page,
396 'total' => $total_pages,
397 'prev_text' => '‹ Previous',
398 'next_text' => 'Next ›',
399 'type' => 'array',
400 'end_size' => 1,
401 'mid_size' => 2,
402 'add_args' => array(
403 'page' => 'easy-invoice-clients'
404 )
405 );
406
407 $bottom_pagination_links = paginate_links($bottom_pagination_args);
408
409 if ($bottom_pagination_links) {
410 echo '<div class="flex space-x-1">';
411 foreach ($bottom_pagination_links as $link) {
412 // Add null check to prevent passing null to easy_invoice_str_replace
413 if ($link === null) {
414 continue;
415 }
416
417 // Convert WordPress pagination to our styling
418 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
419 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
420 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
421 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
422 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
423
424 // Add default styling for regular page numbers
425 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
426 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
427 }
428
429 echo $link;
430 }
431 echo '</div>';
432 }
433 ?>
434 </div>
435 <?php endif; ?>
436 </div>
437 </div>
438 <?php endif; ?>
439
440 <!-- Add New Client Modal -->
441 <div id="add_client_modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
442 <div class="bg-white rounded-lg shadow-xl max-w-3xl w-full p-6 relative">
443 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
444 <i class="fas fa-times"></i>
445 </button>
446 <h2 class="text-xl font-medium text-gray-900 mb-4">Add New Client</h2>
447
448 <?php
449 // Unset the $client variable from the foreach loop to ensure clean add form
450 unset($client);
451 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
452 ?>
453 </div>
454 </div>
455 </div>
456
457 <script type="text/javascript">
458 jQuery(document).ready(function($) {
459 // Client functionality is now handled by client-manager.js
460 // Only keep delete functionality here since it's specific to the clients listing page
461
462 // Table sorting functionality
463 let currentSort = { column: 'id', direction: 'desc' };
464
465 // Initialize sorting
466 function initSorting() {
467 // Set initial sort indicator
468 updateSortIndicator('id', 'desc');
469
470 // Add click handlers to sortable headers
471 $('th[data-sort]').on('click', function() {
472 const column = $(this).data('sort');
473 const currentDirection = currentSort.column === column ? currentSort.direction : 'desc';
474 const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
475
476 currentSort = { column: column, direction: newDirection };
477 updateSortIndicator(column, newDirection);
478 sortTable(column, newDirection);
479 });
480 }
481
482 // Update sort indicator
483 function updateSortIndicator(activeColumn, direction) {
484 // Reset all indicators
485 $('.sort-indicator i').removeClass('fa-sort-up fa-sort-down').addClass('fa-sort text-gray-400');
486
487 // Set active indicator
488 $(`th[data-sort="${activeColumn}"] .sort-indicator i`)
489 .removeClass('fa-sort text-gray-400')
490 .addClass(direction === 'asc' ? 'fa-sort-up text-indigo-600' : 'fa-sort-down text-indigo-600');
491 }
492
493 // Sort table function
494 function sortTable(column, direction) {
495 const tbody = $('tbody');
496 const rows = tbody.find('tr').toArray();
497
498 rows.sort(function(a, b) {
499 let aValue, bValue;
500
501 switch(column) {
502 case 'id':
503 aValue = parseInt($(a).find('td:first-child div').text());
504 bValue = parseInt($(b).find('td:first-child div').text());
505 break;
506 case 'name':
507 aValue = $(a).find('td:nth-child(2) div').text().toLowerCase();
508 bValue = $(b).find('td:nth-child(2) div').text().toLowerCase();
509 break;
510 case 'email':
511 aValue = $(a).find('td:nth-child(3) div').text().toLowerCase();
512 bValue = $(b).find('td:nth-child(3) div').text().toLowerCase();
513 break;
514 case 'username':
515 aValue = $(a).find('td:nth-child(4) div').text().toLowerCase();
516 bValue = $(b).find('td:nth-child(4) div').text().toLowerCase();
517 break;
518 case 'role':
519 aValue = $(a).find('td:nth-child(5) span').text().toLowerCase();
520 bValue = $(b).find('td:nth-child(5) span').text().toLowerCase();
521 break;
522 default:
523 return 0;
524 }
525
526 if (direction === 'asc') {
527 return aValue > bValue ? 1 : -1;
528 } else {
529 return aValue < bValue ? 1 : -1;
530 }
531 });
532
533 // Reorder rows with animation
534 tbody.find('tr').fadeOut(200, function() {
535 tbody.empty();
536 $.each(rows, function(index, row) {
537 tbody.append(row);
538 });
539 tbody.find('tr').fadeIn(200);
540 });
541 }
542
543 // Initialize sorting
544 initSorting();
545
546 // Delete client functionality
547 $(".delete-client").on("click", function(e) {
548 e.preventDefault();
549
550 const clientId = $(this).data("id");
551 const clientName = $(this).closest("tr").find("td:first-child div").text().trim();
552
553 // Use the new confirmation system instead of browser confirm
554 if (typeof EasyInvoiceConfirmation !== 'undefined') {
555 EasyInvoiceConfirmation.confirmAction('delete', 'client "' + clientName + '"', function() {
556 $.ajax({
557 url: easyInvoice.ajaxUrl,
558 type: "POST",
559 data: {
560 action: "easy_invoice_delete_client",
561 client_id: clientId,
562 nonce: easyInvoice.nonce
563 },
564 success: function(response) {
565 if (response.success) {
566 EasyInvoiceToast.success(response.data.message || 'Client deleted successfully');
567 $("[data-id='" + clientId + "']").closest("tr").fadeOut(400, function() {
568 $(this).remove();
569
570 // Update total clients count
571 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
572 $("h3:contains('Total Clients')").next().text(Math.max(0, currentCount - 1));
573
574 // Check if table is empty and show "No clients found"
575 if ($("tbody tr").length === 0) {
576 $("tbody").html('<tr><td colspan="6" class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">No clients found</td></tr>');
577 }
578 });
579 } else {
580 EasyInvoiceToast.error(response.data.message || 'Error deleting client');
581 }
582 },
583 error: function() {
584 EasyInvoiceToast.error('Error connecting to server');
585 }
586 });
587 });
588 } else {
589 // Fallback to browser confirm if toast system not available
590 if (confirm("Are you sure you want to delete the client \"" + clientName + "\"? This action cannot be undone.")) {
591 $.ajax({
592 url: easyInvoice.ajaxUrl,
593 type: "POST",
594 data: {
595 action: "easy_invoice_delete_client",
596 client_id: clientId,
597 nonce: easyInvoice.nonce
598 },
599 success: function(response) {
600 if (response.success) {
601 // Use unified toast system
602 if (typeof EasyInvoiceToast !== 'undefined') {
603 EasyInvoiceToast.success("Client \"" + clientName + "\" has been deleted");
604 } else {
605 alert("Client \"" + clientName + "\" has been deleted");
606 }
607
608 // Remove the row from the table
609 $("[data-id='" + clientId + "']").closest("tr").fadeOut(300, function() {
610 $(this).remove();
611
612 // Update total clients count
613 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
614 $("h3:contains('Total Clients')").next().text(Math.max(0, currentCount - 1));
615
616 // Check if table is empty and show "No clients found"
617 if ($("tbody tr").length === 0) {
618 $("tbody").html('<tr><td colspan="6" class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">No clients found</td></tr>');
619 }
620 });
621 } else {
622 // Use unified toast system
623 if (typeof EasyInvoiceToast !== 'undefined') {
624 EasyInvoiceToast.error(response.data.message || "Error deleting client");
625 } else {
626 alert(response.data.message || "Error deleting client");
627 }
628 }
629 },
630 error: function() {
631 // Use unified toast system
632 if (typeof EasyInvoiceToast !== 'undefined') {
633 EasyInvoiceToast.error("Error connecting to server");
634 } else {
635 alert("Error connecting to server");
636 }
637 }
638 });
639 }
640 }
641 });
642 });
643 </script>