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

676 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 <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;">
15 <div class="flex items-center justify-between">
16 <div>
17 <h1 class="text-2xl font-bold text-gray-900"><?php _e('Clients', 'easy-invoice'); ?></h1>
18 <p class="mt-1 text-sm text-gray-500"><?php _e('Manage all client records', 'easy-invoice'); ?></p>
19 </div>
20 <div class="flex items-center space-x-3">
21 <?php
22 // "Export All" is part of the bulk_operations addon
23 // (Personal tier). See templates/invoices/listing.php
24 // for the canonical three-state gate pattern.
25 $ei_bulk_ops_loaded = \EasyInvoice\Addons\AddonManager::shouldLoad('bulk_operations');
26 ?>
27 <?php if ($ei_bulk_ops_loaded): ?>
28 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
29 <input type="hidden" name="action" value="easy_invoice_export_all_clients" />
30 <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce('easy_invoice_export_all_clients'); ?>" />
31 <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">
32 <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">
33 <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>
34 </svg>
35 <?php _e('Export All', 'easy-invoice'); ?>
36 </button>
37 </form>
38 <?php elseif (!easy_invoice_has_pro()): ?>
39 <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">
40 <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">
41 <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>
42 </svg>
43 <?php _e('Export All', 'easy-invoice'); ?>
44 <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">
45 <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"/>
46 </svg>
47 </button>
48 <script>
49 jQuery(document).ready(function($) {
50 $('#export-all-clients-btn').on('click', function(e) {
51 e.preventDefault();
52 if (typeof EasyInvoiceConfirmation !== 'undefined') {
53 EasyInvoiceConfirmation.showFeatureUpgrade('Export All Clients', 'Export all your clients to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.');
54 } else {
55 EasyInvoiceToast.error('<?php echo esc_js(__('Exporting all clients is a premium feature. Upgrade to Easy Invoice Pro to unlock this feature.', 'easy-invoice')); ?>');
56 }
57 });
58 });
59 </script>
60 <?php endif; ?>
61 <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">
62 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
63 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
64 </svg>
65 <?php _e('Add New Client', 'easy-invoice'); ?>
66 </button>
67 </div>
68 </div>
69 </div>
70
71 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8">
72 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
73 <div class="flex items-center">
74 <div class="rounded-full bg-blue-100 p-3 mr-4">
75 <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">
76 <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"/>
77 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
78 <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"/>
79 <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"/>
80 </svg>
81 </div>
82 <div>
83 <h3 class="text-sm font-medium text-gray-500">Total Clients</h3>
84 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo isset($stats['total_clients']) ? $stats['total_clients'] : count($clients); ?></p>
85 </div>
86 </div>
87 </div>
88 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
89 <div class="flex items-center">
90 <div class="rounded-full bg-indigo-100 p-3 mr-4">
91 <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">
92 <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>
93 </svg>
94 </div>
95 <div>
96 <h3 class="text-sm font-medium text-gray-500">Total Invoices</h3>
97 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo isset($stats['total_invoices']) ? $stats['total_invoices'] : 0; ?></p>
98 </div>
99 </div>
100 </div>
101 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
102 <div class="flex items-center">
103 <div class="rounded-full bg-green-100 p-3 mr-4">
104 <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">
105 <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>
106 </svg>
107 </div>
108 <div>
109 <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3>
110 <?php if (is_array($stats['total_revenue']) && !empty($stats['total_revenue'])): ?>
111 <div class="mt-1">
112 <?php
113 $currency_count = count($stats['total_revenue']);
114 foreach ($stats['total_revenue'] as $index => $data):
115 $is_last = $index === $currency_count - 1;
116 // Get currency code from the data array or use a fallback
117 $currency_code = isset($data['currency_code']) ? $data['currency_code'] : 'USD';
118 ?>
119 <div class="<?php echo $currency_count > 1 ? 'text-sm' : 'text-lg'; ?> font-bold text-gray-900 <?php echo !$is_last ? 'mb-1' : ''; ?>">
120 <?php echo $data['symbol'] . number_format($data['amount'], 2); ?>
121 <span class="text-xs font-normal text-gray-500"><?php echo strtoupper($currency_code ?? 'USD'); ?></span>
122 </div>
123 <?php endforeach; ?>
124 </div>
125 <?php else: ?>
126 <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p>
127 <?php endif; ?>
128 </div>
129 </div>
130 </div>
131 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
132 <div class="flex items-center">
133 <div class="rounded-full bg-yellow-100 p-3 mr-4">
134 <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">
135 <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>
136 </svg>
137 </div>
138 <div>
139 <h3 class="text-sm font-medium text-gray-500">Pending Invoices</h3>
140 <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo isset($stats['pending_invoices']) ? $stats['pending_invoices'] : 0; ?></p>
141 </div>
142 </div>
143 </div>
144 </div>
145
146 <?php if ($total_clients > 0): ?>
147 <div class="mb-4">
148 <div class="px-4 py-3 flex items-center justify-between">
149 <div class="flex items-center space-x-2">
150 <span class="text-sm text-gray-700">
151 Showing
152 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
153 to
154 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_clients)); ?></span>
155 of
156 <span class="font-medium"><?php echo number_format($total_clients); ?></span>
157 results
158 </span>
159 </div>
160 <?php if ($total_pages > 1): ?>
161 <div class="flex items-center space-x-2">
162 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
163 <?php
164 // Use WordPress's built-in pagination for top
165 $top_pagination_args = array(
166 'base' => add_query_arg('paged', '%#%'),
167 'format' => '',
168 'current' => $current_page,
169 'total' => $total_pages,
170 'prev_text' => '‹ Previous',
171 'next_text' => 'Next ›',
172 'type' => 'array',
173 'end_size' => 1,
174 'mid_size' => 2,
175 'add_args' => array(
176 'page' => 'easy-invoice-clients'
177 )
178 );
179
180 $top_pagination_links = paginate_links($top_pagination_args);
181
182 if ($top_pagination_links) {
183 echo '<div class="flex space-x-1">';
184 foreach ($top_pagination_links as $link) {
185 // Add null check to prevent passing null to easy_invoice_str_replace
186 if ($link === null) {
187 continue;
188 }
189
190 // Convert WordPress pagination to our styling
191 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
192 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
193 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
194 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
195 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
196
197 // Add default styling for regular page numbers
198 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
199 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
200 }
201
202 echo $link;
203 }
204 echo '</div>';
205 }
206 ?>
207 </div>
208 <?php endif; ?>
209 </div>
210 </div>
211 <?php endif; ?>
212
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 <?php if ($total_clients > 0): ?>
400 <div class="mt-4">
401 <div class="px-4 py-3 flex items-center justify-between">
402 <div class="flex items-center space-x-2">
403 <span class="text-sm text-gray-700">
404 Showing
405 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
406 to
407 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_clients)); ?></span>
408 of
409 <span class="font-medium"><?php echo number_format($total_clients); ?></span>
410 results
411 </span>
412 </div>
413 <?php if ($total_pages > 1): ?>
414 <div class="flex items-center space-x-2">
415 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
416 <?php
417 // Use WordPress's built-in pagination for bottom
418 $bottom_pagination_args = array(
419 'base' => add_query_arg('paged', '%#%'),
420 'format' => '',
421 'current' => $current_page,
422 'total' => $total_pages,
423 'prev_text' => '‹ Previous',
424 'next_text' => 'Next ›',
425 'type' => 'array',
426 'end_size' => 1,
427 'mid_size' => 2,
428 'add_args' => array(
429 'page' => 'easy-invoice-clients'
430 )
431 );
432
433 $bottom_pagination_links = paginate_links($bottom_pagination_args);
434
435 if ($bottom_pagination_links) {
436 echo '<div class="flex space-x-1">';
437 foreach ($bottom_pagination_links as $link) {
438 // Add null check to prevent passing null to easy_invoice_str_replace
439 if ($link === null) {
440 continue;
441 }
442
443 // Convert WordPress pagination to our styling
444 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
445 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
446 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
447 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
448 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
449
450 // Add default styling for regular page numbers
451 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
452 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
453 }
454
455 echo $link;
456 }
457 echo '</div>';
458 }
459 ?>
460 </div>
461 <?php endif; ?>
462 </div>
463 </div>
464 <?php endif; ?>
465
466 <div id="add_client_modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
467 <div class="bg-white rounded-lg shadow-xl max-w-3xl w-full p-6 relative">
468 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
469 <i class="fas fa-times"></i>
470 </button>
471 <h2 class="text-xl font-medium text-gray-900 mb-4">Add New Client</h2>
472
473 <?php
474 // Unset the $client variable from the foreach loop to ensure clean add form
475 unset($client);
476 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
477 ?>
478 </div>
479 </div>
480 </div>
481
482 <script type="text/javascript">
483 jQuery(document).ready(function($) {
484 // Client functionality is now handled by client-manager.js
485 // Only keep delete functionality here since it's specific to the clients listing page
486
487 // Table sorting functionality
488 let currentSort = { column: 'id', direction: 'desc' };
489
490 // Initialize sorting
491 function initSorting() {
492 // Set initial sort indicator
493 updateSortIndicator('id', 'desc');
494
495 // Add click handlers to sortable headers
496 $('th[data-sort]').on('click', function() {
497 const column = $(this).data('sort');
498 const currentDirection = currentSort.column === column ? currentSort.direction : 'desc';
499 const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
500
501 currentSort = { column: column, direction: newDirection };
502 updateSortIndicator(column, newDirection);
503 sortTable(column, newDirection);
504 });
505 }
506
507 // Update sort indicator
508 function updateSortIndicator(activeColumn, direction) {
509 // Reset all indicators
510 $('.sort-indicator i').removeClass('fa-sort-up fa-sort-down').addClass('fa-sort text-gray-400');
511
512 // Set active indicator
513 $(`th[data-sort="${activeColumn}"] .sort-indicator i`)
514 .removeClass('fa-sort text-gray-400')
515 .addClass(direction === 'asc' ? 'fa-sort-up text-indigo-600' : 'fa-sort-down text-indigo-600');
516 }
517
518 // Sort table function
519 function sortTable(column, direction) {
520 const tbody = $('tbody');
521 const rows = tbody.find('tr').toArray();
522
523 rows.sort(function(a, b) {
524 let aValue, bValue;
525
526 switch(column) {
527 case 'id':
528 aValue = parseInt($(a).find('td:first-child div').text());
529 bValue = parseInt($(b).find('td:first-child div').text());
530 break;
531 case 'business_name':
532 aValue = $(a).find('td:nth-child(2) div').text().toLowerCase();
533 bValue = $(b).find('td:nth-child(2) div').text().toLowerCase();
534 break;
535 case 'name':
536 aValue = $(a).find('td:nth-child(3) div').text().toLowerCase();
537 bValue = $(b).find('td:nth-child(3) div').text().toLowerCase();
538 break;
539 case 'email':
540 aValue = $(a).find('td:nth-child(4) div').text().toLowerCase();
541 bValue = $(b).find('td:nth-child(4) div').text().toLowerCase();
542 break;
543 case 'phone':
544 aValue = $(a).find('td:nth-child(5) div').text().toLowerCase();
545 bValue = $(b).find('td:nth-child(5) div').text().toLowerCase();
546 break;
547 case 'username':
548 aValue = $(a).find('td:nth-child(6) div').text().toLowerCase();
549 bValue = $(b).find('td:nth-child(6) div').text().toLowerCase();
550 break;
551 case 'role':
552 aValue = $(a).find('td:nth-child(7) span').text().toLowerCase();
553 bValue = $(b).find('td:nth-child(7) span').text().toLowerCase();
554 break;
555 default:
556 return 0;
557 }
558
559 if (direction === 'asc') {
560 return aValue > bValue ? 1 : -1;
561 } else {
562 return aValue < bValue ? 1 : -1;
563 }
564 });
565
566 // Reorder rows with animation
567 tbody.find('tr').fadeOut(200, function() {
568 tbody.empty();
569 $.each(rows, function(index, row) {
570 tbody.append(row);
571 });
572 tbody.find('tr').fadeIn(200);
573 });
574 }
575
576 // Initialize sorting
577 initSorting();
578
579 // Delete client functionality
580 $(".delete-client").on("click", function(e) {
581 e.preventDefault();
582
583 const clientId = $(this).data("id");
584 const clientName = $(this).closest("tr").find("td:first-child div").text().trim();
585
586 // Use the new confirmation system instead of browser confirm
587 if (typeof EasyInvoiceConfirmation !== 'undefined') {
588 EasyInvoiceConfirmation.confirmAction('delete', 'client "' + clientName + '"', function() {
589 $.ajax({
590 url: easyInvoice.ajaxUrl,
591 type: "POST",
592 data: {
593 action: "easy_invoice_delete_client",
594 client_id: clientId,
595 nonce: easyInvoice.nonce
596 },
597 success: function(response) {
598 if (response.success) {
599 EasyInvoiceToast.success(response.data.message || 'Client deleted successfully');
600 $("[data-id='" + clientId + "']").closest("tr").fadeOut(400, function() {
601 $(this).remove();
602
603 // Update total clients count
604 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
605 $("h3:contains('Total Clients')").next().text(Math.max(0, currentCount - 1));
606
607 // Check if table is empty and show "No clients found"
608 if ($("tbody tr").length === 0) {
609 $("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>');
610 }
611 });
612 } else {
613 EasyInvoiceToast.error(response.data.message || 'Error deleting client');
614 }
615 },
616 error: function() {
617 EasyInvoiceToast.error('Error connecting to server');
618 }
619 });
620 });
621 } else {
622 // Fallback to browser confirm if toast system not available
623 if (confirm("Are you sure you want to delete the client \"" + clientName + "\"? This action cannot be undone.")) {
624 $.ajax({
625 url: easyInvoice.ajaxUrl,
626 type: "POST",
627 data: {
628 action: "easy_invoice_delete_client",
629 client_id: clientId,
630 nonce: easyInvoice.nonce
631 },
632 success: function(response) {
633 if (response.success) {
634 // Use unified toast system
635 if (typeof EasyInvoiceToast !== 'undefined') {
636 EasyInvoiceToast.success("Client \"" + clientName + "\" has been deleted");
637 } else {
638 alert("Client \"" + clientName + "\" has been deleted");
639 }
640
641 // Remove the row from the table
642 $("[data-id='" + clientId + "']").closest("tr").fadeOut(300, function() {
643 $(this).remove();
644
645 // Update total clients count
646 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
647 $("h3:contains('Total Clients')").next().text(Math.max(0, currentCount - 1));
648
649 // Check if table is empty and show "No clients found"
650 if ($("tbody tr").length === 0) {
651 $("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>');
652 }
653 });
654 } else {
655 // Use unified toast system
656 if (typeof EasyInvoiceToast !== 'undefined') {
657 EasyInvoiceToast.error(response.data.message || "Error deleting client");
658 } else {
659 alert(response.data.message || "Error deleting client");
660 }
661 }
662 },
663 error: function() {
664 // Use unified toast system
665 if (typeof EasyInvoiceToast !== 'undefined') {
666 EasyInvoiceToast.error("Error connecting to server");
667 } else {
668 alert("Error connecting to server");
669 }
670 }
671 });
672 }
673 }
674 });
675 });
676 </script>