PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.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
← All changes | templates/clients-page.php +119 -152 2.1.12.4.0 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +if ( ! defined( 'ABSPATH' ) ) {
3 + exit;
4 +}
2 5 // Extract data passed from controller
3 6 $clients = $clients ?? [];
4 7 $stats = $stats ?? [];
5 8 $current_page = $current_page ?? 1;
@@ -9,23 +12,48 @@
9 12
10 13 // $clients variable contains Client objects
11 14
12 15 ?>
16 +<style>
17 +<?php if (!easy_invoice_user_can('ei_manage_clients')) : ?>.client-list-table a[href*="client-edit"], .client-list-table .delete-client { display: none; }<?php endif; ?>
18 +/* Eight columns do not fit a laptop; the username and role are the least useful. */
19 +.client-list-table th, .client-list-table td { padding-left: 0.875rem; padding-right: 0.875rem; }
20 +@media (max-width: 1700px) {
21 + .client-list-table th:nth-child(6), .client-list-table td:nth-child(6),
22 + .client-list-table th:nth-child(7), .client-list-table td:nth-child(7) { display: none; }
23 +}
24 +</style>
13 25 <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;">
26 + <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 27 <div class="flex items-center justify-between">
17 28 <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>
29 + <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Clients', 'easy-invoice'); ?></h1>
30 + <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Manage all client records', 'easy-invoice'); ?></p>
20 31 </div>
21 32 <div class="flex items-center space-x-3">
22 - <?php if (!easy_invoice_has_pro()): ?>
33 + <?php
34 + // "Export All" is part of the bulk_operations addon
35 + // (Personal tier). See templates/invoices/listing.php
36 + // for the canonical three-state gate pattern.
37 + $ei_bulk_ops_loaded = \EasyInvoice\Addons\AddonManager::shouldLoad('bulk_operations');
38 + ?>
39 + <?php if ($ei_bulk_ops_loaded): ?>
40 + <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
41 + <input type="hidden" name="action" value="easy_invoice_export_all_clients" />
42 + <input type="hidden" name="_wpnonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_export_all_clients')); ?>" />
43 + <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">
44 + <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">
45 + <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>
46 + </svg>
47 + <?php esc_html_e('Export All', 'easy-invoice'); ?>
48 + </button>
49 + </form>
50 + <?php elseif (!easy_invoice_has_pro()): ?>
23 51 <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 52 <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 53 <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 54 </svg>
27 - <?php _e('Export All', 'easy-invoice'); ?>
55 + <?php esc_html_e('Export All', 'easy-invoice'); ?>
28 56 <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 57 <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 58 </svg>
31 59 </button>
@@ -32,41 +60,29 @@
32 60 <script>
33 61 jQuery(document).ready(function($) {
34 62 $('#export-all-clients-btn').on('click', function(e) {
35 63 e.preventDefault();
36 - // Show premium popup using the proper confirmation modal
37 64 if (typeof EasyInvoiceConfirmation !== 'undefined') {
38 65 EasyInvoiceConfirmation.showFeatureUpgrade('Export All Clients', 'Export all your clients to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.');
39 66 } else {
40 - // Fallback to toast if confirmation modal is not available
41 67 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 68 }
43 69 });
44 70 });
45 71 </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 72 <?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">
73 + <?php if (easy_invoice_user_can('ei_manage_clients')) : ?>
74 +<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 75 <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 76 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
61 77 </svg>
62 - <?php _e('Add New Client', 'easy-invoice'); ?>
78 + <?php esc_html_e('Add New Client', 'easy-invoice'); ?>
63 79 </button>
80 +<?php endif; ?>
64 81 </div>
65 82 </div>
66 83 </div>
67 84
68 - <!-- Stats Cards -->
69 85 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8">
70 86 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
71 87 <div class="flex items-center">
72 88 <div class="rounded-full bg-blue-100 p-3 mr-4">
@@ -77,10 +93,10 @@
77 93 <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 94 </svg>
79 95 </div>
80 96 <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>
97 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Clients', 'easy-invoice'); ?></h3>
98 + <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo (int) (isset($stats['total_clients']) ? $stats['total_clients'] : count($clients)); ?></p>
83 99 </div>
84 100 </div>
85 101 </div>
86 102 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
@@ -90,10 +106,10 @@
90 106 <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 107 </svg>
92 108 </div>
93 109 <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>
110 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Invoices', 'easy-invoice'); ?></h3>
111 + <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html(isset($stats['total_invoices']) ? $stats['total_invoices'] : 0); ?></p>
96 112 </div>
97 113 </div>
98 114 </div>
99 115 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
@@ -103,9 +119,9 @@
103 119 <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 120 </svg>
105 121 </div>
106 122 <div>
107 - <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3>
123 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Revenue', 'easy-invoice'); ?></h3>
108 124 <?php if (is_array($stats['total_revenue']) && !empty($stats['total_revenue'])): ?>
109 125 <div class="mt-1">
110 126 <?php
111 127 $currency_count = count($stats['total_revenue']);
@@ -114,10 +130,10 @@
114 130 // Get currency code from the data array or use a fallback
115 131 $currency_code = isset($data['currency_code']) ? $data['currency_code'] : 'USD';
116 132 ?>
117 133 <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>
134 + <?php echo esc_html($data['symbol'] . number_format($data['amount'], 2)); ?>
135 + <span class="text-xs font-normal text-gray-500"><?php echo esc_html(strtoupper($currency_code ?? 'USD')); ?></span>
120 136 </div>
121 137 <?php endforeach; ?>
122 138 </div>
123 139 <?php else: ?>
@@ -133,16 +149,15 @@
133 149 <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 150 </svg>
135 151 </div>
136 152 <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>
153 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Pending Invoices', 'easy-invoice'); ?></h3>
154 + <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html(isset($stats['pending_invoices']) ? $stats['pending_invoices'] : 0); ?></p>
139 155 </div>
140 156 </div>
141 157 </div>
142 158 </div>
143 159
144 - <!-- Top Pagination -->
145 160 <?php if ($total_clients > 0): ?>
146 161 <div class="mb-4">
147 162 <div class="px-4 py-3 flex items-center justify-between">
148 163 <div class="flex items-center space-x-2">
@@ -197,9 +212,9 @@
197 212 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
198 213 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
199 214 }
200 215
201 - echo $link;
216 + echo wp_kses_post($link);
202 217 }
203 218 echo '</div>';
204 219 }
205 220 ?>
@@ -208,10 +223,17 @@
208 223 </div>
209 224 </div>
210 225 <?php endif; ?>
211 226
212 - <!-- Clients Table -->
213 227 <div class="bg-white rounded-lg shadow">
228 + <div class="px-4 py-3 border-b border-gray-200 flex items-center justify-between gap-3 flex-wrap">
229 + <label for="client-search" class="sr-only"><?php esc_html_e('Search clients', 'easy-invoice'); ?></label>
230 + <div class="relative w-full sm:w-80">
231 + <svg class="absolute left-3 top-2.5 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M17 11A6 6 0 115 11a6 6 0 0112 0z"/></svg>
232 + <input type="search" id="client-search" placeholder="<?php esc_attr_e('Search by name, company, email or phone…', 'easy-invoice'); ?>" class="block w-full pl-9 pr-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500">
233 + </div>
234 + <span id="client-search-count" class="text-sm text-gray-500"></span>
235 + </div>
214 236 <div class="overflow-x-auto">
215 237 <table class="min-w-full divide-y divide-gray-200 client-list-table">
216 238 <thead class="bg-gray-50">
217 239 <tr>
@@ -216,17 +238,25 @@
216 238 <thead class="bg-gray-50">
217 239 <tr>
218 240 <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 241 <div class="flex items-center space-x-1">
220 - <span>Client ID</span>
242 + <span><?php esc_html_e('Client ID', 'easy-invoice'); ?></span>
221 243 <div class="sort-indicator">
222 244 <i class="fas fa-sort text-gray-400"></i>
223 245 </div>
224 246 </div>
225 247 </th>
248 + <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">
249 + <div class="flex items-center space-x-1">
250 + <span><?php esc_html_e('Business Name', 'easy-invoice'); ?></span>
251 + <div class="sort-indicator">
252 + <i class="fas fa-sort text-gray-400"></i>
253 + </div>
254 + </div>
255 + </th>
226 256 <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 257 <div class="flex items-center space-x-1">
228 - <span>Client Name</span>
258 + <span><?php esc_html_e('Client Name', 'easy-invoice'); ?></span>
229 259 <div class="sort-indicator">
230 260 <i class="fas fa-sort text-gray-400"></i>
231 261 </div>
232 262 </div>
@@ -232,9 +262,9 @@
232 262 </div>
233 263 </th>
234 264 <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 265 <div class="flex items-center space-x-1">
236 - <span>Email</span>
266 + <span><?php esc_html_e('Email', 'easy-invoice'); ?></span>
237 267 <div class="sort-indicator">
238 268 <i class="fas fa-sort text-gray-400"></i>
239 269 </div>
240 270 </div>
@@ -240,9 +270,9 @@
240 270 </div>
241 271 </th>
242 272 <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 273 <div class="flex items-center space-x-1">
244 - <span>Phone</span>
274 + <span><?php esc_html_e('Phone', 'easy-invoice'); ?></span>
245 275 <div class="sort-indicator">
246 276 <i class="fas fa-sort text-gray-400"></i>
247 277 </div>
248 278 </div>
@@ -248,9 +278,9 @@
248 278 </div>
249 279 </th>
250 280 <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 281 <div class="flex items-center space-x-1">
252 - <span>Username</span>
282 + <span><?php esc_html_e('Username', 'easy-invoice'); ?></span>
253 283 <div class="sort-indicator">
254 284 <i class="fas fa-sort text-gray-400"></i>
255 285 </div>
256 286 </div>
@@ -256,9 +286,9 @@
256 286 </div>
257 287 </th>
258 288 <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 289 <div class="flex items-center space-x-1">
260 - <span>Role</span>
290 + <span><?php esc_html_e('Role', 'easy-invoice'); ?></span>
261 291 <div class="sort-indicator">
262 292 <i class="fas fa-sort text-gray-400"></i>
263 293 </div>
264 294 </div>
@@ -270,9 +300,9 @@
270 300 </thead>
271 301 <tbody class="bg-white divide-y divide-gray-200">
272 302 <?php if (empty($clients)): ?>
273 303 <tr>
274 - <td colspan="6" class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">
304 + <td colspan="8" class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">
275 305 No clients found
276 306 </td>
277 307 </tr>
278 308 <?php endif; ?>
@@ -277,12 +307,17 @@
277 307 </tr>
278 308 <?php endif; ?>
279 309 <?php foreach ($clients as $client): ?>
280 310 <?php
281 - $clientName = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName());
282 - if(empty(trim($clientName))) {
283 - $clientName = $client->getUsername();
311 + // Get business name separately
312 + $businessName = $client->getBusinessClientName();
313 +
314 + // Get contact name (first + last name)
315 + $contactName = trim($client->getFirstName() . ' ' . $client->getLastName());
316 + if(empty($contactName)) {
317 + $contactName = $client->getUsername();
284 318 }
319 +
285 320 $clientId = $client->getId();
286 321
287 322 // Get client role
288 323 $user = get_user_by('ID', $clientId);
@@ -322,11 +357,14 @@
322 357 <td class="px-6 py-4 whitespace-nowrap">
323 358 <div class="text-sm text-gray-500 font-mono font-semibold"><?php echo esc_html($clientId); ?></div>
324 359 </td>
325 360 <td class="px-6 py-4 whitespace-nowrap">
326 - <div class="text-sm font-medium text-gray-900"><?php echo esc_html($clientName); ?></div>
361 + <div class="text-sm font-medium text-gray-900"><?php echo esc_html($businessName ?: '-'); ?></div>
327 362 </td>
328 363 <td class="px-6 py-4 whitespace-nowrap">
364 + <div class="text-sm font-medium text-gray-900"><?php echo esc_html($contactName); ?></div>
365 + </td>
366 + <td class="px-6 py-4 whitespace-nowrap">
329 367 <div class="text-sm text-gray-500"><?php echo esc_html($client->getEmail()); ?></div>
330 368 </td>
331 369 <td class="px-6 py-4 whitespace-nowrap">
332 370 <div class="text-sm text-gray-500"><?php echo esc_html($client->getPhone()); ?></div>
@@ -334,10 +372,10 @@
334 372 <td class="px-6 py-4 whitespace-nowrap">
335 373 <div class="text-sm text-gray-500"><?php echo esc_html($client->getUsername()); ?></div>
336 374 </td>
337 375 <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>
376 + <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <?php echo esc_attr($role_class); ?>">
377 + <i class="<?php echo esc_attr($role_icon); ?> mr-1"></i>
340 378 <?php echo esc_html(ucfirst($client_role)); ?>
341 379 </span>
342 380 </td>
343 381 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
@@ -347,14 +385,20 @@
347 385 title="View Client">
348 386 <i class="fas fa-eye mr-1"></i>
349 387 View
350 388 </a>
351 - <a href="?page=easy-invoice-client-edit&client_id=<?php echo esc_attr($clientId); ?>"
389 + <a href="?page=easy-invoice-client-edit&client_id=<?php echo esc_attr($clientId); ?>"
352 390 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 391 title="Edit Client">
354 392 <i class="fas fa-edit mr-1"></i>
355 393 Edit
356 394 </a>
395 + <a href="<?php echo esc_url(\EasyInvoice\Controllers\StatementController::pageUrl((int) $clientId)); ?>"
396 + class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-gray-700 bg-gray-100 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-colors duration-200"
397 + title="<?php esc_attr_e('Statement of account', 'easy-invoice'); ?>">
398 + <i class="fas fa-file-invoice-dollar mr-1"></i>
399 + <?php esc_html_e('Statement', 'easy-invoice'); ?>
400 + </a>
357 401 <?php if ($client_role === 'administrator'): ?>
358 402 <button type="button"
359 403 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 404 title="Cannot delete administrator"
@@ -379,9 +423,8 @@
379 423 </table>
380 424 </div>
381 425 </div>
382 426
383 - <!-- Bottom Pagination -->
384 427 <?php if ($total_clients > 0): ?>
385 428 <div class="mt-4">
386 429 <div class="px-4 py-3 flex items-center justify-between">
387 430 <div class="flex items-center space-x-2">
@@ -436,9 +479,9 @@
436 479 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
437 480 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
438 481 }
439 482
440 - echo $link;
483 + echo wp_kses_post($link);
441 484 }
442 485 echo '</div>';
443 486 }
444 487 ?>
@@ -447,15 +490,14 @@
447 490 </div>
448 491 </div>
449 492 <?php endif; ?>
450 493
451 - <!-- Add New Client Modal -->
452 494 <div id="add_client_modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
453 495 <div class="bg-white rounded-lg shadow-xl max-w-3xl w-full p-6 relative">
454 496 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
455 497 <i class="fas fa-times"></i>
456 498 </button>
457 - <h2 class="text-xl font-medium text-gray-900 mb-4">Add New Client</h2>
499 + <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Add New Client', 'easy-invoice'); ?></h2>
458 500
459 501 <?php
460 502 // Unset the $client variable from the foreach loop to ensure clean add form
461 503 unset($client);
@@ -513,27 +555,31 @@
513 555 case 'id':
514 556 aValue = parseInt($(a).find('td:first-child div').text());
515 557 bValue = parseInt($(b).find('td:first-child div').text());
516 558 break;
517 - case 'name':
559 + case 'business_name':
518 560 aValue = $(a).find('td:nth-child(2) div').text().toLowerCase();
519 561 bValue = $(b).find('td:nth-child(2) div').text().toLowerCase();
520 562 break;
521 - case 'email':
563 + case 'name':
522 564 aValue = $(a).find('td:nth-child(3) div').text().toLowerCase();
523 565 bValue = $(b).find('td:nth-child(3) div').text().toLowerCase();
524 566 break;
525 - case 'phone':
567 + case 'email':
526 568 aValue = $(a).find('td:nth-child(4) div').text().toLowerCase();
527 569 bValue = $(b).find('td:nth-child(4) div').text().toLowerCase();
528 570 break;
529 - case 'username':
571 + case 'phone':
530 572 aValue = $(a).find('td:nth-child(5) div').text().toLowerCase();
531 573 bValue = $(b).find('td:nth-child(5) div').text().toLowerCase();
532 574 break;
575 + case 'username':
576 + aValue = $(a).find('td:nth-child(6) div').text().toLowerCase();
577 + bValue = $(b).find('td:nth-child(6) div').text().toLowerCase();
578 + break;
533 579 case 'role':
534 - aValue = $(a).find('td:nth-child(6) span').text().toLowerCase();
535 - bValue = $(b).find('td:nth-child(6) span').text().toLowerCase();
580 + aValue = $(a).find('td:nth-child(7) span').text().toLowerCase();
581 + bValue = $(b).find('td:nth-child(7) span').text().toLowerCase();
536 582 break;
537 583 default:
538 584 return 0;
539 585 }
@@ -558,101 +604,22 @@
558 604 // Initialize sorting
559 605 initSorting();
560 606
561 607 // 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 - });
608 + // Filter the rows as you type; no request, the list is already here.
609 + (function () {
610 + var $input = $('#client-search'), $count = $('#client-search-count');
611 + if (!$input.length) { return; }
612 + $input.on('input', function () {
613 + var q = $.trim($(this).val()).toLowerCase(), shown = 0, total = 0;
614 + $('.client-list-table tbody tr').each(function () {
615 + var $tr = $(this); if ($tr.find('td').length < 3) { return; }
616 + total++;
617 + var hit = !q || $tr.text().toLowerCase().indexOf(q) !== -1;
618 + $tr.toggle(hit); if (hit) { shown++; }
602 619 });
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 - });
620 + $count.text(q ? shown + ' / ' + total : '');
621 + });
622 + })();
623 + // Delete is handled by assets/js/clients.js (choice of keeping or removing the client's documents).
657 624 });
658 625 </script>