PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.2.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.2.0
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.2.0, at templates/clients-page.php

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