PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.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 / payments / list.php

list.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.1, at templates/payments/list.php

1,069 lines 56.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Payments List Template
4 *
5 * @package Easy_Invoice
6 */
7
8 // Ensure we're in the correct namespace
9 namespace EasyInvoice\Templates\Payments;
10
11 // Exit if accessed directly
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 use EasyInvoice\Models\Payment;
17 use EasyInvoice\Models\Invoice;
18
19
20 // Extract data passed from controller
21 $payments = $payments ?? [];
22 $current_view = $current_view ?? 'all';
23 $status_filter = $status_filter ?? '';
24 $status_filters = $status_filters ?? [];
25 $trash_count = $trash_count ?? 0;
26 $stats = $stats ?? [];
27 $current_page = $current_page ?? 1;
28 $per_page = $per_page ?? 20;
29 $total_payments = $total_payments ?? 0;
30 $total_pages = $total_pages ?? 1;
31
32 // Ensure all required stats keys exist with default values
33 $stats = array_merge([
34 'total_payments' => 0,
35 'total_amount' => 0,
36 'completed_payments' => 0,
37 'pending_payments' => 0,
38 'failed_payments' => 0
39 ], $stats);
40
41 // Initialize amounts_by_currency array
42 $amounts_by_currency = [];
43 // Calculate stats from the payments array
44 foreach ($payments as $payment) {
45 $amount = floatval($payment->getAmount());
46 $status = $payment->getStatus();
47
48 $stats['total_amount'] += $amount;
49
50 switch ($status) {
51 case 'completed':
52 $stats['completed_payments']++;
53 break;
54 case 'pending':
55 $stats['pending_payments']++;
56 break;
57 case 'failed':
58 $stats['failed_payments']++;
59 break;
60 }
61
62 // Calculate currency breakdown
63 $payment_currency = $payment->getCurrency();
64
65 // If currency is empty or "global", get the actual currency that was used
66 if (empty($payment_currency) || $payment_currency === 'global') {
67 $actual_currency = get_post_meta($payment->getId(), '_currency', true);
68 $payment_currency = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
69 }
70
71 // If currency is still "global", use the global setting
72 if ($payment_currency === 'global') {
73 $payment_currency = get_option('easy_invoice_currency_code', 'USD');
74 }
75
76 $payment_currency_symbol = $payment->getCurrencySymbol() ?: \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($payment_currency);
77
78 // Normalize currency code to uppercase for consistent grouping
79 $payment_currency = strtoupper($payment_currency);
80
81 // Group amounts by currency
82 if (!isset($amounts_by_currency[$payment_currency])) {
83 $amounts_by_currency[$payment_currency] = [
84 'amount' => 0,
85 'symbol' => $payment_currency_symbol
86 ];
87 }
88 $amounts_by_currency[$payment_currency]['amount'] += $amount;
89 }
90
91 // Check for bulk action notifications
92 $notification = false;
93
94 if (isset($_GET['bulk_trashed']) && $_GET['bulk_trashed'] > 0) {
95 $count = intval($_GET['bulk_trashed']);
96 /* translators: %s: number of items. */
97 easy_invoice_display_notification('success', sprintf(_n('%s payment moved to trash.', '%s payments moved to trash.', $count, 'easy-invoice'), $count));
98 $notification = true;
99 }
100
101 if (isset($_GET['bulk_restored']) && $_GET['bulk_restored'] > 0) {
102 $count = intval($_GET['bulk_restored']);
103 /* translators: %s: number of items. */
104 easy_invoice_display_notification('success', sprintf(_n('%s payment restored from trash.', '%s payments restored from trash.', $count, 'easy-invoice'), $count));
105 $notification = true;
106 }
107
108 if (isset($_GET['bulk_deleted']) && $_GET['bulk_deleted'] > 0) {
109 $count = intval($_GET['bulk_deleted']);
110 /* translators: %s: number of items. */
111 easy_invoice_display_notification('success', sprintf(_n('%s payment permanently deleted.', '%s payments permanently deleted.', $count, 'easy-invoice'), $count));
112 $notification = true;
113 }
114
115 if (isset($_GET['bulk_error'])) {
116 $error = sanitize_text_field($_GET['bulk_error']);
117 $error_message = 'An error occurred while processing the bulk action.';
118
119 if ($error === 'no_selection') {
120 $error_message = 'Please select at least one payment to perform this action.';
121 } elseif ($error === 'invalid_action') {
122 $error_message = 'Please select a valid bulk action.';
123 }
124
125 easy_invoice_display_notification('error', $error_message);
126 $notification = true;
127 }
128
129 ?>
130 <div class="p-8">
131 <div class="ei-app-page-header bg-white border-b border-gray-200 px-6" style="margin-left: -2rem; margin-top: -2rem; margin-right: -2rem; padding-left: 2rem; padding-right: 2rem;">
132 <div class="flex items-center justify-between">
133 <div>
134 <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Payments', 'easy-invoice'); ?></h1>
135 <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Manage all payment records', 'easy-invoice'); ?></p>
136 </div>
137 <div class="flex items-center space-x-3">
138 <?php
139 // "Export All" is part of the bulk_operations addon
140 // (Personal tier). See templates/invoices/listing.php
141 // for the canonical three-state gate pattern.
142 $ei_bulk_ops_loaded = \EasyInvoice\Addons\AddonManager::shouldLoad('bulk_operations');
143 ?>
144 <?php if ($ei_bulk_ops_loaded): ?>
145 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
146 <input type="hidden" name="action" value="easy_invoice_export_all_payments" />
147 <input type="hidden" name="_wpnonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_export_all_payments')); ?>" />
148 <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">
149 <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">
150 <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>
151 </svg>
152 <?php esc_html_e('Export All', 'easy-invoice'); ?>
153 </button>
154 </form>
155 <?php elseif (!easy_invoice_has_pro()): ?>
156 <button type="button" id="export-all-payments-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">
157 <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">
158 <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>
159 </svg>
160 <?php esc_html_e('Export All', 'easy-invoice'); ?>
161 <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">
162 <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"/>
163 </svg>
164 </button>
165 <script>
166 jQuery(document).ready(function($) {
167 $('#export-all-payments-btn').on('click', function(e) {
168 e.preventDefault();
169 if (typeof EasyInvoiceConfirmation !== 'undefined') {
170 EasyInvoiceConfirmation.showFeatureUpgrade('Export All Payments', 'Export all your payments to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.');
171 } else {
172 EasyInvoiceToast.error('<?php echo esc_js(__('Exporting all payments is a premium feature. Upgrade to Easy Invoice Pro to unlock this feature.', 'easy-invoice')); ?>');
173 }
174 });
175 });
176 </script>
177 <?php endif; ?>
178
179 <?php if (easy_invoice_user_can('ei_record_payment')) : ?>
180 <a href="?page=easy-invoice-payment-new" 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">
181 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
182 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
183 </svg>
184 <?php esc_html_e('Add New Payment', 'easy-invoice'); ?>
185 </a>
186 <?php endif; ?>
187 </div>
188 </div>
189 </div>
190
191 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8">
192 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
193 <div class="flex items-center">
194 <div class="rounded-full bg-indigo-100 p-3 mr-4">
195 <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">
196 <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>
197 </svg>
198 </div>
199 <div>
200 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Payments', 'easy-invoice'); ?></h3>
201 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($stats['total_payments']); ?></p>
202 </div>
203 </div>
204 </div>
205 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
206 <div class="flex items-center">
207 <div class="rounded-full bg-green-100 p-3 mr-4">
208 <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">
209 <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>
210 </svg>
211 </div>
212 <div>
213 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Amount', 'easy-invoice'); ?></h3>
214 <?php if (!empty($amounts_by_currency)): ?>
215 <div class="mt-1">
216 <?php foreach ($amounts_by_currency as $currency => $data): ?>
217 <p class="text-lg font-bold text-gray-900">
218 <?php echo esc_html($data['symbol'] . number_format($data['amount'], 2)); ?>
219 <span class="text-sm font-normal text-gray-500"><?php echo esc_html(strtoupper($currency ?? 'USD')); ?></span>
220 </p>
221 <?php endforeach; ?>
222 </div>
223 <?php else: ?>
224 <p class="mt-1 text-lg font-bold text-gray-900"><?php echo esc_html(get_option('easy_invoice_currency_symbol', '$') . '0.00'); ?></p>
225 <?php endif; ?>
226 </div>
227 </div>
228 </div>
229 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
230 <div class="flex items-center">
231 <div class="rounded-full bg-green-100 p-3 mr-4">
232 <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">
233 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
234 </svg>
235 </div>
236 <div>
237 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Completed Payments', 'easy-invoice'); ?></h3>
238 <p class="mt-1 text-2xl font-bold text-green-600"><?php echo esc_html($stats['completed_payments']); ?></p>
239 </div>
240 </div>
241 </div>
242 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
243 <div class="flex items-center">
244 <div class="rounded-full bg-yellow-100 p-3 mr-4">
245 <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">
246 <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>
247 </svg>
248 </div>
249 <div>
250 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Pending Payments', 'easy-invoice'); ?></h3>
251 <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html($stats['pending_payments']); ?></p>
252 </div>
253 </div>
254 </div>
255 </div>
256
257 <?php if ($total_payments > 0): ?>
258 <div class="mb-4">
259 <div class="px-4 py-3 flex items-center justify-between">
260 <div class="flex items-center space-x-2">
261 <span class="text-sm text-gray-700">
262 Showing
263 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
264 to
265 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_payments)); ?></span>
266 of
267 <span class="font-medium"><?php echo number_format($total_payments); ?></span>
268 results
269 </span>
270 </div>
271 <?php if ($total_pages > 1): ?>
272 <div class="flex items-center space-x-2">
273 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
274 <?php
275 // Use WordPress's built-in pagination for top
276 $top_pagination_args = array(
277 'base' => add_query_arg('paged', '%#%'),
278 'format' => '',
279 'current' => $current_page,
280 'total' => $total_pages,
281 'prev_text' => '‹ Previous',
282 'next_text' => 'Next ›',
283 'type' => 'array',
284 'end_size' => 1,
285 'mid_size' => 2,
286 'add_args' => array(
287 'page' => 'easy-invoice-payments',
288 'view' => $current_view,
289 'status' => $status_filter
290 )
291 );
292
293 $top_pagination_links = paginate_links($top_pagination_args);
294
295 if ($top_pagination_links) {
296 echo '<div class="flex space-x-1">';
297 foreach ($top_pagination_links as $link) {
298 // Add null check to prevent passing null to easy_invoice_str_replace
299 if ($link === null) {
300 continue;
301 }
302
303 // Convert WordPress pagination to our styling
304 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
305 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
306 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
307 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
308 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
309
310 // Add default styling for regular page numbers
311 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
312 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
313 }
314
315 echo wp_kses_post($link);
316 }
317 echo '</div>';
318 }
319 ?>
320 </div>
321 <?php endif; ?>
322 </div>
323 </div>
324 <?php endif; ?>
325
326 <div class="bg-white rounded-lg shadow mb-6 overflow-hidden">
327 <div class="border-b border-gray-200 bg-gradient-to-r from-indigo-50 to-white">
328 <nav class="flex">
329 <?php
330 // View Tabs (All, Trash)
331 $views = array(
332 'all' => 'All (' . $stats['total_payments'] . ')',
333 'trash' => 'Trash (' . $trash_count . ')'
334 );
335
336 foreach ($views as $view => $label) {
337 $is_current = $current_view === $view;
338 $status_param = !empty($status_filter) && $view === 'all' ? '&status=' . $status_filter : '';
339 $class = $is_current
340 ? 'border-b-2 border-indigo-500 text-indigo-600 bg-white'
341 : 'border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300';
342 ?>
343 <a href="?page=easy-invoice-payments&view=<?php echo esc_attr($view . $status_param); ?>"
344 class="flex items-center whitespace-nowrap py-4 px-6 font-medium text-sm transition-colors duration-200 <?php echo esc_attr($class); ?>">
345 <?php echo esc_html($label); ?>
346 </a>
347 <?php } ?>
348 </nav>
349 </div>
350 </div>
351
352 <div class="bg-white shadow overflow-hidden sm:rounded-lg">
353 <div class="px-6 py-3 border-b border-gray-200 bg-gray-50">
354 <div class="flex items-center justify-between">
355 <div class="flex items-center space-x-4">
356 <form id="bulk-action-form" method="post" class="flex items-center gap-2">
357 <?php wp_nonce_field('easy_invoice_payment_bulk_action', 'easy_invoice_payment_bulk_nonce'); ?>
358 <input type="hidden" name="action" value="easy_invoice_payment_bulk_action">
359
360 <select name="bulk_action" aria-label="Bulk action for selected payments" class="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white text-gray-700 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
361 <option value=""><?php esc_html_e('Bulk Actions', 'easy-invoice'); ?></option>
362 <?php if ($current_view === 'trash'): ?>
363 <option value="restore"><?php esc_html_e('Restore', 'easy-invoice'); ?></option>
364 <option value="delete"><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></option>
365 <?php else: ?>
366 <option value="trash"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></option>
367 <option value="delete"><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></option>
368 <?php endif; ?>
369 </select>
370
371 <button type="submit" 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">
372 Apply
373 </button>
374 </form>
375 </div>
376 <div class="text-sm text-gray-500">
377 <span id="selected-count">0</span> payments selected
378 </div>
379 </div>
380 </div>
381
382 <div class="ei-table-responsive">
383 <table class="min-w-full divide-y divide-gray-200 ei-payments-table">
384 <thead class="bg-gray-50">
385 <tr>
386 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
387 <input type="checkbox" id="select-all-payments" aria-label="Select all payments on this page" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
388 </th>
389 <th scope="col" 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">
390 <div class="flex items-center space-x-1">
391 <span><?php esc_html_e('Payment ID', 'easy-invoice'); ?></span>
392 <div class="sort-indicator">
393 <i class="fas fa-sort text-gray-400"></i>
394 </div>
395 </div>
396 </th>
397 <th scope="col" 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="invoice">
398 <div class="flex items-center space-x-1">
399 <span><?php esc_html_e('Invoice', 'easy-invoice'); ?></span>
400 <div class="sort-indicator">
401 <i class="fas fa-sort text-gray-400"></i>
402 </div>
403 </div>
404 </th>
405 <th scope="col" 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="amount">
406 <div class="flex items-center space-x-1">
407 <span><?php esc_html_e('Amount', 'easy-invoice'); ?></span>
408 <div class="sort-indicator">
409 <i class="fas fa-sort text-gray-400"></i>
410 </div>
411 </div>
412 </th>
413 <th scope="col" 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="method">
414 <div class="flex items-center space-x-1">
415 <span><?php esc_html_e('Method', 'easy-invoice'); ?></span>
416 <div class="sort-indicator">
417 <i class="fas fa-sort text-gray-400"></i>
418 </div>
419 </div>
420 </th>
421 <th scope="col" 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="status">
422 <div class="flex items-center space-x-1">
423 <span><?php esc_html_e('Status', 'easy-invoice'); ?></span>
424 <div class="sort-indicator">
425 <i class="fas fa-sort text-gray-400"></i>
426 </div>
427 </div>
428 </th>
429 <th scope="col" 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="date">
430 <div class="flex items-center space-x-1">
431 <span><?php esc_html_e('Date', 'easy-invoice'); ?></span>
432 <div class="sort-indicator">
433 <i class="fas fa-sort text-gray-400"></i>
434 </div>
435 </div>
436 </th>
437 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 hidden sm:table-cell" data-sort="type">
438 <div class="flex items-center space-x-1">
439 <span><?php esc_html_e('Type', 'easy-invoice'); ?></span>
440 <div class="sort-indicator">
441 <i class="fas fa-sort text-gray-400"></i>
442 </div>
443 </div>
444 </th>
445 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
446 Actions
447 </th>
448 </tr>
449 </thead>
450 <tbody class="bg-white divide-y divide-gray-200">
451 <?php if ($payments): ?>
452 <?php foreach ($payments as $payment): ?>
453 <?php
454 // $payment is already a Payment object, no need to create new one
455
456 // Get invoice information
457 $invoice_id = $payment->getInvoiceId();
458
459 // Temporarily show all payments regardless of invoice
460 ?>
461 <tr>
462 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
463 <input type="checkbox" name="payment_ids[]" value="<?php echo (int) $payment->getId(); ?>" form="bulk-action-form" class="payment-checkbox rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
464 </td>
465 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
466 #<?php echo (int) $payment->getId(); ?>
467 </td>
468 <td class="px-6 py-4 text-sm text-gray-500">
469 <?php if ($invoice_id): ?>
470 <?php
471 $invoice_post = get_post($invoice_id);
472 if ($invoice_post && $invoice_post->post_type === 'easy_invoice'):
473 $invoice = new \EasyInvoice\Models\Invoice($invoice_id);
474 ?>
475 <div>
476 <a href="<?php echo esc_url(get_permalink($invoice->getId())); ?>" class="text-indigo-600 hover:text-indigo-900 font-medium" target="_blank">
477 <?php echo esc_html($invoice->getNumber()); ?>
478 </a>
479 <div class="text-xs text-gray-400 mt-1">
480 <?php echo esc_html($invoice->getTitle() ?: __('Untitled Invoice', 'easy-invoice')); ?>
481 </div>
482 </div>
483 <?php else: ?>
484 <span class="text-gray-400">
485 <?php esc_html_e('Invoice not found', 'easy-invoice'); ?>
486 <?php if (current_user_can('manage_options')): ?>
487 <br><small class="text-xs text-gray-500">ID: <?php echo (int) $invoice_id; ?> (Type: <?php echo esc_html($invoice_post ? $invoice_post->post_type : 'null'); ?>)</small>
488 <?php endif; ?>
489 </span>
490 <?php endif; ?>
491 <?php else: ?>
492 <span class="text-gray-400">
493 <?php esc_html_e('No invoice ID', 'easy-invoice'); ?>
494 </span>
495 <?php endif; ?>
496 </td>
497 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
498 <?php echo esc_html($payment->getCurrencySymbol() . number_format((float) $payment->getAmount(), 2)); ?>
499 </td>
500 <td class="px-6 py-4 text-sm text-gray-500">
501 <?php
502 // Get payment method label dynamically from registered gateways
503 $payment_method = $payment->getPaymentMethod();
504 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
505 $gateways = $gateway_manager->getGateways();
506
507 // Try to get the gateway title from registered gateways
508 $method_label = ucfirst(easy_invoice_str_replace('_', ' ', $payment_method)); // Default fallback
509
510 foreach ($gateways as $gateway) {
511 if ($gateway->getName() === $payment_method) {
512 $method_label = $gateway_manager->getGatewayDisplayName($gateway->getName());
513 break;
514 }
515 }
516
517 // Special case for manual payments
518 if ($payment_method === 'manual') {
519 $method_label = __('Manual', 'easy-invoice');
520 }
521
522 echo esc_html($method_label);
523 ?>
524 </td>
525 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
526 <?php
527 $status = $payment->getStatus();
528 $status_class = '';
529 $status_text = '';
530
531 switch ($status) {
532 case 'completed':
533 $status_class = 'bg-green-100 text-green-800';
534 $status_text = __('Completed', 'easy-invoice');
535 break;
536 case 'pending':
537 $status_class = 'bg-yellow-100 text-yellow-800';
538 $status_text = __('Pending', 'easy-invoice');
539 break;
540 case 'failed':
541 $status_class = 'bg-red-100 text-red-800';
542 $status_text = __('Failed', 'easy-invoice');
543 break;
544 default:
545 $status_class = 'bg-gray-100 text-gray-800';
546 $status_text = ucfirst($status);
547 }
548 ?>
549 <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <?php echo esc_attr($status_class); ?>">
550 <?php echo esc_html($status_text); ?>
551 </span>
552 </td>
553 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
554 <?php echo esc_html($payment->getPaymentDate() ? gmdate('M j, Y', strtotime($payment->getPaymentDate())) : gmdate('M j, Y', strtotime((string) get_post_field('post_date', $payment->getId())) ?: time())); ?>
555 </td>
556 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 hidden sm:table-cell">
557 <?php echo esc_html(ucfirst($payment->getPaymentType() ?: 'one-time')); ?>
558 </td>
559 <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
560 <div class="flex items-center space-x-2">
561 <a href="?page=easy-invoice-payments&action=view&id=<?php echo (int) $payment->getId(); ?>" class="text-indigo-600 hover:text-indigo-900">
562 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
563 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
564 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
565 </svg>
566 </a>
567 <a href="?page=easy-invoice-payments&action=edit&id=<?php echo (int) $payment->getId(); ?>" class="text-blue-600 hover:text-blue-900">
568 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
569 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
570 </svg>
571 </a>
572 <?php if (in_array($status, ['pending', 'pending-bank', 'pending-cheque'])): ?>
573 <button type="button" class="approve-payment-btn text-green-600 hover:text-green-900"
574 data-payment-id="<?php echo (int) $payment->getId(); ?>"
575 data-invoice-id="<?php echo esc_attr($payment->getInvoiceId()); ?>"
576 data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_approve_payment')); ?>">
577 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
578 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
579 </svg>
580 </button>
581 <?php endif; ?>
582
583 <?php
584 // Hook for additional payment actions (used by Pro plugin for receipts)
585 do_action('easy_invoice_payment_row_actions', $payment, $status);
586 ?>
587 </div>
588 </td>
589 </tr>
590 <?php endforeach; ?>
591 <?php else : ?>
592 <tr>
593 <?php
594 // Was a bare "No payments found." with no way forward. Every
595 // other empty state in the plugin offers the next action, so
596 // this one now points at Add New Payment too.
597 //
598 // `whitespace-nowrap` also had to go: it is meant for table
599 // cells holding short values, and on a 9-column empty row it
600 // stopped the message wrapping.
601 $ei_add_payment_link = '<a href="' . esc_url(admin_url('admin.php?page=easy-invoice-payment-new')) . '" class="text-indigo-600 hover:text-indigo-900">'
602 . esc_html__('Record your first payment', 'easy-invoice')
603 . '</a>';
604 ?>
605 <td colspan="9" class="px-6 py-4 text-sm text-gray-500 text-center">
606 <?php
607 /* translators: %s: link reading "Record your first payment". */
608 printf(esc_html__('No payments found. %s.', 'easy-invoice'), wp_kses_post($ei_add_payment_link));
609 ?>
610 </td>
611 </tr>
612 <?php endif; ?>
613 </tbody>
614 </table>
615 </div>
616 </div>
617
618 <?php if ($total_payments > 0): ?>
619 <div class="mt-4">
620 <div class="px-4 py-3 flex items-center justify-between">
621 <div class="flex items-center space-x-2">
622 <span class="text-sm text-gray-700">
623 Showing
624 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
625 to
626 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_payments)); ?></span>
627 of
628 <span class="font-medium"><?php echo number_format($total_payments); ?></span>
629 results
630 </span>
631 </div>
632 <?php if ($total_pages > 1): ?>
633 <div class="flex items-center space-x-2">
634 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
635 <?php
636 // Use WordPress's built-in pagination for bottom
637 $bottom_pagination_args = array(
638 'base' => add_query_arg('paged', '%#%'),
639 'format' => '',
640 'current' => $current_page,
641 'total' => $total_pages,
642 'prev_text' => '‹ Previous',
643 'next_text' => 'Next ›',
644 'type' => 'array',
645 'end_size' => 1,
646 'mid_size' => 2,
647 'add_args' => array(
648 'page' => 'easy-invoice-payments',
649 'view' => $current_view,
650 'status' => $status_filter
651 )
652 );
653
654 $bottom_pagination_links = paginate_links($bottom_pagination_args);
655
656 if ($bottom_pagination_links) {
657 echo '<div class="flex space-x-1">';
658 foreach ($bottom_pagination_links as $link) {
659 // Add null check to prevent passing null to easy_invoice_str_replace
660 if ($link === null) {
661 continue;
662 }
663
664 // Convert WordPress pagination to our styling
665 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
666 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
667 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
668 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
669 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
670
671 // Add default styling for regular page numbers
672 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
673 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
674 }
675
676 echo wp_kses_post($link);
677 }
678 echo '</div>';
679 }
680 ?>
681 </div>
682 <?php endif; ?>
683 </div>
684 </div>
685 <?php endif; ?>
686 </div>
687
688 <div id="payment-approval-modal" class="fixed inset-0 z-50 overflow-y-auto hidden" aria-labelledby="modal-title" role="dialog" aria-modal="true">
689 <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
690 <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
691
692 <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
693 <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
694 <div class="sm:flex sm:items-start">
695 <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10">
696 <svg class="h-6 w-6 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
697 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
698 </svg>
699 </div>
700 <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
701 <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
702 <?php esc_html_e('Approve Payment', 'easy-invoice'); ?>
703 </h3>
704 <div class="mt-2">
705 <p class="text-sm text-gray-500">
706 <?php esc_html_e('You are about to approve this payment. This will mark the invoice as paid and update the payment records.', 'easy-invoice'); ?>
707 </p>
708 <div class="mt-4">
709 <label for="approval-notes" class="block text-sm font-medium text-gray-700">
710 <?php esc_html_e('Notes (optional)', 'easy-invoice'); ?>
711 </label>
712 <textarea id="approval-notes" name="notes" rows="3" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="<?php echo esc_js(__('Add any notes about this payment approval...', 'easy-invoice')); ?>"></textarea>
713 </div>
714 </div>
715 </div>
716 </div>
717 </div>
718 <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
719 <button type="button" id="confirm-approve-btn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">
720 <?php esc_html_e('Approve Payment', 'easy-invoice'); ?>
721 </button>
722 <button type="button" id="cancel-approve-btn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
723 <?php esc_html_e('Cancel', 'easy-invoice'); ?>
724 </button>
725 </div>
726 </div>
727 </div>
728 </div>
729
730 <div id="payment-approve-notification" class="fixed top-0 right-0 m-4 hidden">
731 </div>
732
733 <style>
734 /* Add any additional custom styles here */
735 .ei-table-responsive{
736 width: 100%;
737 max-width: 100%;
738 overflow-x: auto;
739 -webkit-overflow-scrolling: touch;
740 }
741 .ei-payments-table{
742 width: 100%;
743 min-width: 900px;
744 }
745 @media (max-width: 640px) {
746 .ei-payments-table th,
747 .ei-payments-table td{
748 padding-left: 0.75rem !important;
749 padding-right: 0.75rem !important;
750 }
751 }
752 .payment-status {
753 display: inline-block;
754 padding: 3px 8px;
755 border-radius: 3px;
756 font-size: 12px;
757 font-weight: 600;
758 }
759 .status-completed {
760 background-color: #dff0d8;
761 color: #3c763d;
762 }
763 .status-pending {
764 background-color: #fcf8e3;
765 color: #8a6d3b;
766 }
767 .status-failed {
768 background-color: #f2dede;
769 color: #a94442;
770 }
771 </style>
772
773 <script>
774 // Define ajaxurl for AJAX requests
775 var ajaxurl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
776
777 jQuery(document).ready(function($) {
778 // Variables to store the current payment being approved
779 let currentInvoiceId = null;
780 let currentPaymentId = null;
781 let currentNonce = null;
782
783 // Handle Approve button click
784 $('.approve-payment-btn').on('click', function(e) {
785 e.preventDefault();
786
787 // Store the data for the current payment
788 currentInvoiceId = $(this).data('invoice-id');
789 currentPaymentId = $(this).data('payment-id');
790 currentNonce = $(this).data('nonce');
791
792 // Show the approval modal
793 $('#payment-approval-modal').removeClass('hidden');
794 });
795
796 // Handle Cancel button in modal
797 $('#cancel-approve-btn').on('click', function() {
798 // Hide the modal and reset the form
799 $('#payment-approval-modal').addClass('hidden');
800 $('#approval-notes').val('');
801 });
802
803 // Handle Confirm Approve button in modal
804 $('#confirm-approve-btn').on('click', function() {
805 // Get the notes from the textarea
806 const notes = $('#approval-notes').val();
807
808 // Disable the button to prevent double clicks
809 $(this).prop('disabled', true).text('Processing...');
810
811 // Make AJAX request to approve the payment
812 $.ajax({
813 url: ajaxurl,
814 type: 'POST',
815 data: {
816 action: 'easy_invoice_approve_payment',
817 invoice_id: currentInvoiceId,
818 nonce: currentNonce,
819 notes: notes
820 },
821 success: function(response) {
822 // Hide the modal
823 $('#payment-approval-modal').addClass('hidden');
824
825 if (response.success) {
826 // Show success toast
827 if (typeof EasyInvoiceToast !== 'undefined') {
828 EasyInvoiceToast.success('Payment approved successfully.');
829 }
830
831 // Refresh the page after a short delay
832 setTimeout(function() {
833 location.reload();
834 }, 1500);
835 } else {
836 // Show error toast
837 if (typeof EasyInvoiceToast !== 'undefined') {
838 EasyInvoiceToast.error('Failed to approve payment.');
839 }
840
841 // Re-enable the button
842 $('#confirm-approve-btn').prop('disabled', false).text('Approve Payment');
843 }
844 },
845 error: function() {
846 // Hide the modal
847 $('#payment-approval-modal').addClass('hidden');
848
849 // Show error toast
850 if (typeof EasyInvoiceToast !== 'undefined') {
851 EasyInvoiceToast.error('Server error occurred. Please try again.');
852 }
853
854 // Re-enable the button
855 $('#confirm-approve-btn').prop('disabled', false).text('Approve Payment');
856 }
857 });
858 });
859
860
861
862 // Close the modal if clicking outside of it
863 $(window).on('click', function(e) {
864 if ($(e.target).is('#payment-approval-modal')) {
865 $('#payment-approval-modal').addClass('hidden');
866 $('#approval-notes').val('');
867 }
868 });
869
870 // Close the modal with Escape key
871 $(document).on('keydown', function(e) {
872 if (e.key === "Escape" && !$('#payment-approval-modal').hasClass('hidden')) {
873 $('#payment-approval-modal').addClass('hidden');
874 $('#approval-notes').val('');
875 }
876 });
877
878 // Dismiss notification buttons
879 $('.notification-dismiss').on('click', function() {
880 $(this).closest('.rounded-md').remove();
881 });
882
883 // Payment Table Sorting and Filtering Functionality
884 let currentSort = { column: '', direction: '' };
885
886 // Initialize sorting and filtering
887 function initSortingAndFiltering() {
888 // Add click handlers to sortable headers
889 $('th[data-sort]').on('click', function() {
890 const column = $(this).data('sort');
891 const currentDirection = currentSort.column === column ? currentSort.direction : 'desc';
892 const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
893
894 currentSort = { column: column, direction: newDirection };
895 updateSortIndicator(column, newDirection);
896 sortTable(column, newDirection);
897 });
898 }
899
900 // Update sort indicator
901 function updateSortIndicator(activeColumn, direction) {
902 // Reset all indicators
903 $('.sort-indicator i').removeClass('fa-sort-up fa-sort-down').addClass('fa-sort text-gray-400');
904
905 // Set active indicator
906 $(`th[data-sort="${activeColumn}"] .sort-indicator i`)
907 .removeClass('fa-sort text-gray-400')
908 .addClass(direction === 'asc' ? 'fa-sort-up text-indigo-600' : 'fa-sort-down text-indigo-600');
909 }
910
911 // Sort table function
912 function sortTable(column, direction) {
913 const tbody = $('tbody');
914 const rows = tbody.find('tr').toArray();
915
916 rows.sort(function(a, b) {
917 let aValue, bValue;
918
919 switch(column) {
920 case 'id':
921 aValue = parseInt($(a).find('td:first-child').text().replace('#', ''));
922 bValue = parseInt($(b).find('td:first-child').text().replace('#', ''));
923 break;
924 case 'invoice':
925 aValue = $(a).find('td:nth-child(2) a').text().toLowerCase();
926 bValue = $(b).find('td:nth-child(2) a').text().toLowerCase();
927 break;
928 case 'amount':
929 aValue = parseFloat($(a).find('td:nth-child(3)').text().replace(/[^0-9.-]+/g, ''));
930 bValue = parseFloat($(b).find('td:nth-child(3)').text().replace(/[^0-9.-]+/g, ''));
931 break;
932 case 'method':
933 aValue = $(a).find('td:nth-child(4)').text().toLowerCase();
934 bValue = $(b).find('td:nth-child(4)').text().toLowerCase();
935 break;
936 case 'status':
937 aValue = $(a).find('td:nth-child(5) span').text().toLowerCase();
938 bValue = $(b).find('td:nth-child(5) span').text().toLowerCase();
939 break;
940 case 'date':
941 aValue = new Date($(a).find('td:nth-child(6)').text());
942 bValue = new Date($(b).find('td:nth-child(6)').text());
943 break;
944 case 'type':
945 aValue = $(a).find('td:nth-child(7)').text().toLowerCase();
946 bValue = $(b).find('td:nth-child(7)').text().toLowerCase();
947 break;
948 default:
949 return 0;
950 }
951
952 if (direction === 'asc') {
953 return aValue > bValue ? 1 : -1;
954 } else {
955 return aValue < bValue ? 1 : -1;
956 }
957 });
958
959 // Reorder rows with animation
960 tbody.find('tr').fadeOut(200, function() {
961 tbody.empty();
962 $.each(rows, function(index, row) {
963 tbody.append(row);
964 });
965 tbody.find('tr').fadeIn(200);
966 });
967 }
968
969 // Initialize sorting and filtering
970 initSortingAndFiltering();
971
972 // Payment Delete Functionality
973 let selectedPayments = [];
974
975 // Handle select all checkbox
976 $('#select-all-payments').on('change', function() {
977 const isChecked = $(this).is(':checked');
978 $('.payment-checkbox').prop('checked', isChecked);
979
980 if (isChecked) {
981 selectedPayments = $('.payment-checkbox').map(function() {
982 return $(this).val();
983 }).get();
984 } else {
985 selectedPayments = [];
986 }
987
988 updateBulkActionButton();
989 });
990
991 // Handle individual checkboxes
992 $(document).on('change', '.payment-checkbox', function() {
993 const paymentId = $(this).val();
994 const isChecked = $(this).is(':checked');
995
996 if (isChecked) {
997 if (!selectedPayments.includes(paymentId)) {
998 selectedPayments.push(paymentId);
999 }
1000 } else {
1001 selectedPayments = selectedPayments.filter(id => id !== paymentId);
1002 }
1003
1004 // Update select all checkbox
1005 const totalCheckboxes = $('.payment-checkbox').length;
1006 const checkedCheckboxes = $('.payment-checkbox:checked').length;
1007
1008 if (checkedCheckboxes === 0) {
1009 $('#select-all-payments').prop('checked', false).prop('indeterminate', false);
1010 } else if (checkedCheckboxes === totalCheckboxes) {
1011 $('#select-all-payments').prop('checked', true).prop('indeterminate', false);
1012 } else {
1013 $('#select-all-payments').prop('checked', false).prop('indeterminate', true);
1014 }
1015
1016 updateBulkActionButton();
1017 });
1018
1019 // Update bulk action button state
1020 function updateBulkActionButton() {
1021 const button = $('#bulk-action-form button[type="submit"]');
1022 const countSpan = $('#selected-count');
1023
1024 if (selectedPayments.length > 0) {
1025 button.prop('disabled', false);
1026 countSpan.text(selectedPayments.length);
1027 } else {
1028 button.prop('disabled', true);
1029 countSpan.text('0');
1030 }
1031 }
1032
1033 // Form submission validation for bulk actions
1034 $("#bulk-action-form").on("submit", function(e) {
1035 e.preventDefault();
1036
1037 var bulkAction = $("select[name='bulk_action']").val();
1038 if (!bulkAction) {
1039 EasyInvoiceToast.error('Please select a bulk action.');
1040 return;
1041 }
1042
1043 if (selectedPayments.length === 0) {
1044 EasyInvoiceToast.error('Please select at least one payment.');
1045 return;
1046 }
1047
1048 // Confirm bulk actions
1049 if (bulkAction === 'trash') {
1050 EasyInvoiceConfirmation.confirmAction('trash', selectedPayments.length + ' payment(s)', function() {
1051 $("#bulk-action-form")[0].submit();
1052 });
1053 return;
1054 } else if (bulkAction === 'delete') {
1055 EasyInvoiceConfirmation.confirmAction('delete', selectedPayments.length + ' payment(s)', function() {
1056 $("#bulk-action-form")[0].submit();
1057 });
1058 return;
1059 } else if (bulkAction === 'restore') {
1060 EasyInvoiceConfirmation.confirmAction('restore', selectedPayments.length + ' payment(s)', function() {
1061 $("#bulk-action-form")[0].submit();
1062 });
1063 return;
1064 } else {
1065 $("#bulk-action-form")[0].submit();
1066 }
1067 });
1068 });
1069 </script>