PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.2
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
easy-invoice / templates / payments / list.php

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

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