| 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> |