PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.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 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / templates / clients-page.php

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

658 lines 38.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="phone">
243 <div class="flex items-center space-x-1">
244 <span>Phone</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="username">
251 <div class="flex items-center space-x-1">
252 <span>Username</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 cursor-pointer hover:bg-gray-100" data-sort="role">
259 <div class="flex items-center space-x-1">
260 <span>Role</span>
261 <div class="sort-indicator">
262 <i class="fas fa-sort text-gray-400"></i>
263 </div>
264 </div>
265 </th>
266 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
267 Actions
268 </th>
269 </tr>
270 </thead>
271 <tbody class="bg-white divide-y divide-gray-200">
272 <?php if (empty($clients)): ?>
273 <tr>
274 <td colspan="6" class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">
275 No clients found
276 </td>
277 </tr>
278 <?php endif; ?>
279 <?php foreach ($clients as $client): ?>
280 <?php
281 $clientName = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName());
282 if(empty(trim($clientName))) {
283 $clientName = $client->getUsername();
284 }
285 $clientId = $client->getId();
286
287 // Get client role
288 $user = get_user_by('ID', $clientId);
289 $client_role = $user ? $user->roles[0] ?? 'client' : 'client';
290
291 // Role styling
292 $role_class = 'bg-blue-100 text-blue-800';
293 $role_icon = 'fas fa-user';
294
295 switch ($client_role) {
296 case 'administrator':
297 $role_class = 'bg-red-100 text-red-800';
298 $role_icon = 'fas fa-crown';
299 break;
300 case 'editor':
301 $role_class = 'bg-purple-100 text-purple-800';
302 $role_icon = 'fas fa-user-edit';
303 break;
304 case 'author':
305 $role_class = 'bg-green-100 text-green-800';
306 $role_icon = 'fas fa-user-edit';
307 break;
308 case 'contributor':
309 $role_class = 'bg-yellow-100 text-yellow-800';
310 $role_icon = 'fas fa-user-plus';
311 break;
312 case 'subscriber':
313 $role_class = 'bg-gray-100 text-gray-800';
314 $role_icon = 'fas fa-user';
315 break;
316 default:
317 $role_class = 'bg-blue-100 text-blue-800';
318 $role_icon = 'fas fa-user';
319 }
320 ?>
321 <tr>
322 <td class="px-6 py-4 whitespace-nowrap">
323 <div class="text-sm text-gray-500 font-mono font-semibold"><?php echo esc_html($clientId); ?></div>
324 </td>
325 <td class="px-6 py-4 whitespace-nowrap">
326 <div class="text-sm font-medium text-gray-900"><?php echo esc_html($clientName); ?></div>
327 </td>
328 <td class="px-6 py-4 whitespace-nowrap">
329 <div class="text-sm text-gray-500"><?php echo esc_html($client->getEmail()); ?></div>
330 </td>
331 <td class="px-6 py-4 whitespace-nowrap">
332 <div class="text-sm text-gray-500"><?php echo esc_html($client->getPhone()); ?></div>
333 </td>
334 <td class="px-6 py-4 whitespace-nowrap">
335 <div class="text-sm text-gray-500"><?php echo esc_html($client->getUsername()); ?></div>
336 </td>
337 <td class="px-6 py-4 whitespace-nowrap">
338 <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <?php echo $role_class; ?>">
339 <i class="<?php echo $role_icon; ?> mr-1"></i>
340 <?php echo esc_html(ucfirst($client_role)); ?>
341 </span>
342 </td>
343 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
344 <div class="flex items-center space-x-2">
345 <a href="?page=easy-invoice-client-view&client_id=<?php echo esc_attr($clientId); ?>"
346 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"
347 title="View Client">
348 <i class="fas fa-eye mr-1"></i>
349 View
350 </a>
351 <a href="?page=easy-invoice-client-edit&client_id=<?php echo esc_attr($clientId); ?>"
352 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"
353 title="Edit Client">
354 <i class="fas fa-edit mr-1"></i>
355 Edit
356 </a>
357 <?php if ($client_role === 'administrator'): ?>
358 <button type="button"
359 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"
360 title="Cannot delete administrator"
361 disabled>
362 <i class="fas fa-trash mr-1"></i>
363 Delete
364 </button>
365 <?php else: ?>
366 <button type="button"
367 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"
368 data-id="<?php echo esc_attr($clientId); ?>"
369 title="Delete Client">
370 <i class="fas fa-trash mr-1"></i>
371 Delete
372 </button>
373 <?php endif; ?>
374 </div>
375 </td>
376 </tr>
377 <?php endforeach; ?>
378 </tbody>
379 </table>
380 </div>
381 </div>
382
383 <!-- Bottom Pagination -->
384 <?php if ($total_clients > 0): ?>
385 <div class="mt-4">
386 <div class="px-4 py-3 flex items-center justify-between">
387 <div class="flex items-center space-x-2">
388 <span class="text-sm text-gray-700">
389 Showing
390 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
391 to
392 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_clients)); ?></span>
393 of
394 <span class="font-medium"><?php echo number_format($total_clients); ?></span>
395 results
396 </span>
397 </div>
398 <?php if ($total_pages > 1): ?>
399 <div class="flex items-center space-x-2">
400 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
401 <?php
402 // Use WordPress's built-in pagination for bottom
403 $bottom_pagination_args = array(
404 'base' => add_query_arg('paged', '%#%'),
405 'format' => '',
406 'current' => $current_page,
407 'total' => $total_pages,
408 'prev_text' => '‹ Previous',
409 'next_text' => 'Next ›',
410 'type' => 'array',
411 'end_size' => 1,
412 'mid_size' => 2,
413 'add_args' => array(
414 'page' => 'easy-invoice-clients'
415 )
416 );
417
418 $bottom_pagination_links = paginate_links($bottom_pagination_args);
419
420 if ($bottom_pagination_links) {
421 echo '<div class="flex space-x-1">';
422 foreach ($bottom_pagination_links as $link) {
423 // Add null check to prevent passing null to easy_invoice_str_replace
424 if ($link === null) {
425 continue;
426 }
427
428 // Convert WordPress pagination to our styling
429 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
430 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
431 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
432 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
433 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
434
435 // Add default styling for regular page numbers
436 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
437 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
438 }
439
440 echo $link;
441 }
442 echo '</div>';
443 }
444 ?>
445 </div>
446 <?php endif; ?>
447 </div>
448 </div>
449 <?php endif; ?>
450
451 <!-- Add New Client Modal -->
452 <div id="add_client_modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
453 <div class="bg-white rounded-lg shadow-xl max-w-3xl w-full p-6 relative">
454 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
455 <i class="fas fa-times"></i>
456 </button>
457 <h2 class="text-xl font-medium text-gray-900 mb-4">Add New Client</h2>
458
459 <?php
460 // Unset the $client variable from the foreach loop to ensure clean add form
461 unset($client);
462 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
463 ?>
464 </div>
465 </div>
466 </div>
467
468 <script type="text/javascript">
469 jQuery(document).ready(function($) {
470 // Client functionality is now handled by client-manager.js
471 // Only keep delete functionality here since it's specific to the clients listing page
472
473 // Table sorting functionality
474 let currentSort = { column: 'id', direction: 'desc' };
475
476 // Initialize sorting
477 function initSorting() {
478 // Set initial sort indicator
479 updateSortIndicator('id', 'desc');
480
481 // Add click handlers to sortable headers
482 $('th[data-sort]').on('click', function() {
483 const column = $(this).data('sort');
484 const currentDirection = currentSort.column === column ? currentSort.direction : 'desc';
485 const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
486
487 currentSort = { column: column, direction: newDirection };
488 updateSortIndicator(column, newDirection);
489 sortTable(column, newDirection);
490 });
491 }
492
493 // Update sort indicator
494 function updateSortIndicator(activeColumn, direction) {
495 // Reset all indicators
496 $('.sort-indicator i').removeClass('fa-sort-up fa-sort-down').addClass('fa-sort text-gray-400');
497
498 // Set active indicator
499 $(`th[data-sort="${activeColumn}"] .sort-indicator i`)
500 .removeClass('fa-sort text-gray-400')
501 .addClass(direction === 'asc' ? 'fa-sort-up text-indigo-600' : 'fa-sort-down text-indigo-600');
502 }
503
504 // Sort table function
505 function sortTable(column, direction) {
506 const tbody = $('tbody');
507 const rows = tbody.find('tr').toArray();
508
509 rows.sort(function(a, b) {
510 let aValue, bValue;
511
512 switch(column) {
513 case 'id':
514 aValue = parseInt($(a).find('td:first-child div').text());
515 bValue = parseInt($(b).find('td:first-child div').text());
516 break;
517 case 'name':
518 aValue = $(a).find('td:nth-child(2) div').text().toLowerCase();
519 bValue = $(b).find('td:nth-child(2) div').text().toLowerCase();
520 break;
521 case 'email':
522 aValue = $(a).find('td:nth-child(3) div').text().toLowerCase();
523 bValue = $(b).find('td:nth-child(3) div').text().toLowerCase();
524 break;
525 case 'phone':
526 aValue = $(a).find('td:nth-child(4) div').text().toLowerCase();
527 bValue = $(b).find('td:nth-child(4) div').text().toLowerCase();
528 break;
529 case 'username':
530 aValue = $(a).find('td:nth-child(5) div').text().toLowerCase();
531 bValue = $(b).find('td:nth-child(5) div').text().toLowerCase();
532 break;
533 case 'role':
534 aValue = $(a).find('td:nth-child(6) span').text().toLowerCase();
535 bValue = $(b).find('td:nth-child(6) span').text().toLowerCase();
536 break;
537 default:
538 return 0;
539 }
540
541 if (direction === 'asc') {
542 return aValue > bValue ? 1 : -1;
543 } else {
544 return aValue < bValue ? 1 : -1;
545 }
546 });
547
548 // Reorder rows with animation
549 tbody.find('tr').fadeOut(200, function() {
550 tbody.empty();
551 $.each(rows, function(index, row) {
552 tbody.append(row);
553 });
554 tbody.find('tr').fadeIn(200);
555 });
556 }
557
558 // Initialize sorting
559 initSorting();
560
561 // Delete client functionality
562 $(".delete-client").on("click", function(e) {
563 e.preventDefault();
564
565 const clientId = $(this).data("id");
566 const clientName = $(this).closest("tr").find("td:first-child div").text().trim();
567
568 // Use the new confirmation system instead of browser confirm
569 if (typeof EasyInvoiceConfirmation !== 'undefined') {
570 EasyInvoiceConfirmation.confirmAction('delete', 'client "' + clientName + '"', function() {
571 $.ajax({
572 url: easyInvoice.ajaxUrl,
573 type: "POST",
574 data: {
575 action: "easy_invoice_delete_client",
576 client_id: clientId,
577 nonce: easyInvoice.nonce
578 },
579 success: function(response) {
580 if (response.success) {
581 EasyInvoiceToast.success(response.data.message || 'Client deleted successfully');
582 $("[data-id='" + clientId + "']").closest("tr").fadeOut(400, function() {
583 $(this).remove();
584
585 // Update total clients count
586 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
587 $("h3:contains('Total Clients')").next().text(Math.max(0, currentCount - 1));
588
589 // Check if table is empty and show "No clients found"
590 if ($("tbody tr").length === 0) {
591 $("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>');
592 }
593 });
594 } else {
595 EasyInvoiceToast.error(response.data.message || 'Error deleting client');
596 }
597 },
598 error: function() {
599 EasyInvoiceToast.error('Error connecting to server');
600 }
601 });
602 });
603 } else {
604 // Fallback to browser confirm if toast system not available
605 if (confirm("Are you sure you want to delete the client \"" + clientName + "\"? This action cannot be undone.")) {
606 $.ajax({
607 url: easyInvoice.ajaxUrl,
608 type: "POST",
609 data: {
610 action: "easy_invoice_delete_client",
611 client_id: clientId,
612 nonce: easyInvoice.nonce
613 },
614 success: function(response) {
615 if (response.success) {
616 // Use unified toast system
617 if (typeof EasyInvoiceToast !== 'undefined') {
618 EasyInvoiceToast.success("Client \"" + clientName + "\" has been deleted");
619 } else {
620 alert("Client \"" + clientName + "\" has been deleted");
621 }
622
623 // Remove the row from the table
624 $("[data-id='" + clientId + "']").closest("tr").fadeOut(300, function() {
625 $(this).remove();
626
627 // Update total clients count
628 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
629 $("h3:contains('Total Clients')").next().text(Math.max(0, currentCount - 1));
630
631 // Check if table is empty and show "No clients found"
632 if ($("tbody tr").length === 0) {
633 $("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>');
634 }
635 });
636 } else {
637 // Use unified toast system
638 if (typeof EasyInvoiceToast !== 'undefined') {
639 EasyInvoiceToast.error(response.data.message || "Error deleting client");
640 } else {
641 alert(response.data.message || "Error deleting client");
642 }
643 }
644 },
645 error: function() {
646 // Use unified toast system
647 if (typeof EasyInvoiceToast !== 'undefined') {
648 EasyInvoiceToast.error("Error connecting to server");
649 } else {
650 alert("Error connecting to server");
651 }
652 }
653 });
654 }
655 }
656 });
657 });
658 </script>