PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.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 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.1.2, at templates/payments/list.php

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