PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.2.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.2.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / templates / invoices / single.php

single.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.2.0, at templates/invoices/single.php

1,291 lines 54.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Single Invoice Template (Bare)
4 *
5 * @package Easy_Invoice
6 * @subpackage Templates
7 */
8
9
10 // Prevent direct access
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 // Get text settings for invoice actions
16 $text_settings = \EasyInvoice\Helpers\TemplateTextHelper::getInvoiceTextSettings();
17
18
19 // Hide the admin bar for this view
20 add_filter('show_admin_bar', '__return_false');
21
22 // Get invoice from WordPress query
23 global $post;
24 $invoice = null;
25
26 if ($post && $post->post_type === \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE) {
27 // Load invoice from post using the correct service provider
28 $invoice = \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository()->find($post->ID);
29 }
30
31 if (!$invoice) {
32 wp_die(__('Invoice not found.', 'easy-invoice'));
33 }
34
35 // Initialize formatter for currency formatting
36 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
37
38 // Stripe is handled by the Pro plugin
39
40 // Get the selected template for this invoice
41 $current_template = $invoice->getTemplate();
42 if (empty($current_template)) {
43 $current_template = 'standard';
44 }
45
46 // Check if the template file exists
47 $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/' . $current_template . '.php';
48
49 $template_file = file_exists($template_file) ? $template_file : EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/default.php';
50
51 ?><!DOCTYPE html>
52 <html <?php language_attributes(); ?>>
53 <head>
54 <meta charset="<?php bloginfo('charset'); ?>">
55 <meta name="viewport" content="width=device-width, initial-scale=1.0">
56 <title><?php echo esc_html($invoice->getTitle() ?: __('Invoice', 'easy-invoice')); ?></title>
57 <!-- Load jQuery and toast system for better user experience -->
58 <script src="<?php echo esc_url(includes_url('js/jquery/jquery.min.js')); ?>"></script>
59 <script src="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/js/easy-invoice-toast.js'); ?>"></script>
60 <script src="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/js/confirmation-modal.js'); ?>"></script>
61
62 <!-- Load jsPDF for PDF generation -->
63
64 <script src="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/js/document-pdf.js?ver=' . rawurlencode(EASY_INVOICE_VERSION)); ?>"></script>
65
66 <!-- Stripe.js is loaded by the Pro plugin when needed -->
67
68 <!-- Add Font Awesome for payment icons -->
69 <link rel="stylesheet" href="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css'); ?>">
70
71 <!-- Payment panel JavaScript functions -->
72 <?php do_action('easy_invoice_head'); ?>
73
74 <script>
75 // Simple payment panel toggle functionality
76 function togglePaymentPanel() {
77 const paymentPanel = document.getElementById('payment-panel');
78 const toggleButton = document.getElementById('toggle-payment-panel');
79
80 if (paymentPanel && toggleButton) {
81 if (paymentPanel.style.display === 'none' || !paymentPanel.style.display) {
82 // Show panel
83 paymentPanel.style.display = 'block';
84 setTimeout(() => {
85 paymentPanel.style.transform = 'translateX(0)';
86 }, 10);
87 toggleButton.textContent = '<?php _e('Hide Payment', 'easy-invoice'); ?>';
88 toggleButton.style.background = '#6c757d';
89 } else {
90 // Hide panel
91 paymentPanel.style.transform = 'translateX(100%)';
92 setTimeout(() => {
93 paymentPanel.style.display = 'none';
94 }, 300);
95 toggleButton.textContent = '<?php echo esc_js($text_settings['pay_now']); ?>';
96 toggleButton.style.background = '#27ae60';
97 }
98 }
99 }
100
101 function closePaymentPanel() {
102 const paymentPanel = document.getElementById('payment-panel');
103 const toggleButton = document.getElementById('toggle-payment-panel');
104
105 if (paymentPanel && toggleButton) {
106 paymentPanel.style.transform = 'translateX(100%)';
107 setTimeout(() => {
108 paymentPanel.style.display = 'none';
109 }, 300);
110 toggleButton.textContent = '<?php _e('Pay Now', 'easy-invoice'); ?>';
111 toggleButton.style.background = '#27ae60';
112 }
113 }
114
115 // Initialize payment panel on page load
116 document.addEventListener('DOMContentLoaded', function() {
117 const paymentPanel = document.getElementById('payment-panel');
118 if (paymentPanel) {
119 paymentPanel.style.transform = 'translateX(100%)';
120 }
121 });
122 </script>
123
124 <style>
125 body { margin: 0; padding: 0; font-family: sans-serif; background: #eaeaea; color: #222; }
126 .easy-invoice-invoice-container { max-width: 800px; margin: 40px auto; }
127 h1, h2, h3 { margin-top: 0; }
128 .invoice-actions { margin-top: 32px; text-align: center; }
129 .invoice-actions button, .invoice-actions a { margin: 0 8px; padding: 10px 24px; border: none; border-radius: 4px; background: #6366f1; color: #fff; font-size: 1rem; cursor: pointer; text-decoration: none; }
130 .invoice-actions button.pay { background: #10b981; }
131 .invoice-actions button.decline { background: #f87171; }
132 .template-not-found { text-align: center; padding: 40px; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
133 .template-not-found-icon { font-size: 48px; color: #dc3545; margin-bottom: 20px; }
134 .template-not-found h3 { font-size: 24px; color: #32325d; margin-bottom: 10px; }
135 .template-not-found p { color: #8898aa; margin-bottom: 10px; }
136
137 /* Modal System Styles */
138 .ei-modal-overlay {
139 position: fixed;
140 top: 0;
141 left: 0;
142 width: 100%;
143 height: 100%;
144 background: rgba(0, 0, 0, 0.5);
145 display: flex;
146 justify-content: center;
147 align-items: center;
148 z-index: 9999;
149 opacity: 0;
150 transition: opacity 0.3s ease;
151 }
152
153 .ei-modal-overlay.show {
154 opacity: 1;
155 }
156
157 .ei-modal {
158 background: white;
159 border-radius: 8px;
160 box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
161 max-width: 500px;
162 width: 90%;
163 max-height: 80vh;
164 overflow-y: auto;
165 transform: scale(0.9);
166 transition: transform 0.3s ease;
167 }
168
169 .ei-modal.show {
170 transform: scale(1);
171 }
172
173 .ei-modal-header {
174 padding: 20px 24px 0;
175 display: flex;
176 align-items: center;
177 gap: 12px;
178 }
179
180 .ei-modal-icon {
181 width: 40px;
182 height: 40px;
183 border-radius: 50%;
184 display: flex;
185 align-items: center;
186 justify-content: center;
187 flex-shrink: 0;
188 }
189
190 .ei-modal-icon.info {
191 background: #dbeafe;
192 }
193
194 .ei-modal-icon.danger {
195 background: #fee2e2;
196 }
197
198 .ei-modal-icon svg {
199 width: 20px;
200 height: 20px;
201 }
202
203 .ei-modal-title {
204 margin: 0;
205 font-size: 18px;
206 font-weight: 600;
207 color: #1f2937;
208 }
209
210 .ei-modal-body {
211 padding: 16px 24px 20px;
212 }
213
214 .ei-modal-message {
215 color: #6b7280;
216 line-height: 1.5;
217 white-space: pre-line;
218 }
219
220 .ei-modal-actions {
221 padding: 0 24px 20px;
222 display: flex;
223 gap: 12px;
224 justify-content: flex-end;
225 }
226
227 .ei-modal-btn {
228 padding: 8px 16px;
229 border: none;
230 border-radius: 6px;
231 font-size: 14px;
232 font-weight: 500;
233 cursor: pointer;
234 transition: background-color 0.2s ease;
235 }
236
237 .ei-modal-btn-primary {
238 background: #3b82f6;
239 color: white;
240 }
241
242 .ei-modal-btn-primary:hover {
243 background: #2563eb;
244 }
245
246 .ei-modal-btn-secondary {
247 background: #f3f4f6;
248 color: #374151;
249 }
250
251 .ei-modal-btn-secondary:hover {
252 background: #e5e7eb;
253 }
254
255 .ei-modal-btn-danger {
256 background: #ef4444;
257 color: white;
258 }
259
260 .ei-modal-btn-danger:hover {
261 background: #dc2626;
262 }
263
264 .ei-modal-reason-field {
265 margin-top: 16px;
266 }
267
268 .ei-modal-label {
269 display: block;
270 margin-bottom: 8px;
271 font-weight: 500;
272 color: #374151;
273 }
274
275 .ei-modal-textarea {
276 width: 100%;
277 padding: 12px;
278 border: 1px solid #d1d5db;
279 border-radius: 6px;
280 font-family: inherit;
281 font-size: 14px;
282 line-height: 1.5;
283 resize: vertical;
284 min-height: 80px;
285 }
286
287 .ei-modal-textarea:focus {
288 outline: none;
289 border-color: #3b82f6;
290 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
291 }
292
293 .ei-modal-textarea.error {
294 border-color: #ef4444;
295 }
296 </style>
297 </head>
298 <body>
299 <div class="easy-invoice-invoice-container">
300 <!-- Invoice Actions - Always show at the top regardless of template -->
301 <div class="invoice-actions" style="margin-bottom: 32px;">
302 <?php if ($invoice->getStatus() === 'unpaid'): ?>
303 <form method="post" style="display:inline;">
304 <?php wp_nonce_field('easy_invoice_invoice_action', 'invoice_nonce'); ?>
305 <input type="hidden" name="invoice_id" value="<?php echo esc_attr($invoice->getId()); ?>">
306 <button type="submit" name="pay_invoice" class="pay"><?php echo esc_html($text_settings['pay_now']); ?></button>
307 </form>
308 <?php endif; ?>
309
310 <!-- Additional action buttons -->
311 <button type="button" onclick="printInvoiceContent()" style="background: #10b981;"><?php echo esc_html($text_settings['print']); ?></button>
312
313 <button type="button" onclick="downloadInvoicePdf(<?php echo esc_js($invoice->getId()); ?>)" style="background: #f59e0b;">
314 <?php echo esc_html($text_settings['download_pdf']); ?>
315 </button>
316
317 <button class="send-email-button" type="button" style="background: #8b5cf6; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">
318 <?php echo esc_html($text_settings['send_email']); ?>
319 </button>
320
321 <!-- Pay Now button to toggle payment panel -->
322 <?php
323 $invoice_status = $invoice->getStatus();
324
325 $show_pay_button = $invoice && in_array($invoice_status, ['unpaid', 'available']);
326 ?>
327 <?php if ($show_pay_button): ?>
328 <button type="button" id="toggle-payment-panel" onclick="togglePaymentPanel()" style="background: #27ae60; color: white; padding: 10px 24px; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer;">
329 <?php echo esc_html($text_settings['pay_now']); ?>
330 </button>
331 <?php endif; ?>
332 </div>
333 <?php do_action('easy_invoice_before_invoice_content_top', $invoice); ?>
334
335 <!-- Main content area with invoice and payment panel -->
336 <div class="invoice-layout" style="display: flex; gap: 32px; max-width: 1200px; margin: 0 auto;">
337 <!-- Invoice content (left side) -->
338 <div class="invoice-content" style="flex: 1; min-width: 0; position: relative;">
339 <?php
340 // Add Additional CSS button for admin users
341 if (is_user_logged_in() && current_user_can('manage_options')):
342 $additional_css = get_post_meta($invoice->getId(), '_easy_invoice_additional_css', true) ?: '';
343 $has_css = !empty($additional_css);
344 ?>
345 <button type="button" id="additional-css-btn" style="position: absolute; top: 0; left: -40px; background: <?php echo $has_css ? '#10b981' : '#3b82f6'; ?>; color: white; border: none; padding: 6px 10px; border-radius: 3px; cursor: pointer; font-weight: 500; font-size: 11px; line-height: 1; z-index: 1000;" title="Additional CSS<?php echo $has_css ? ' (CSS already added)' : ''; ?>">
346 <i class="fas fa-code"></i>
347 <?php if ($has_css): ?>
348 <span style="position: absolute; top: -4px; right: -4px; background: #ef4444; color: white; border-radius: 50%; width: 8px; height: 8px; font-size: 6px; display: flex; align-items: center; justify-content: center;"></span>
349 <?php endif; ?>
350 </button>
351 <?php endif; ?>
352
353 <?php do_action('easy_invoice_invoice_view_content_top', $invoice); ?>
354 <?php if (file_exists($template_file)): ?>
355 <?php
356 // Include the selected template file
357 include $template_file;
358 ?>
359 <?php else: ?>
360 <!-- Template not found - fallback to basic display -->
361 <div class="template-not-found">
362 <div class="template-not-found-icon">
363 <i class="fas fa-exclamation-circle"></i>
364 </div>
365 <h3><?php _e('Template Not Found', 'easy-invoice'); ?></h3>
366 <p><?php printf(__('The selected template "%s" does not exist.', 'easy-invoice'), esc_html($current_template)); ?></p>
367 <p><?php _e('Please contact support if you believe this is an error.', 'easy-invoice'); ?></p>
368 </div>
369
370 <!-- Fallback basic invoice display -->
371 <div class="invoice-header" style="position: relative;">
372 <h1><?php echo esc_html($invoice->getTitle() ?: __('Invoice', 'easy-invoice')); ?>
373 <span class="invoice-status status-<?php echo esc_attr($invoice->getStatus() ?: 'draft'); ?>"><?php echo esc_html(ucfirst($invoice->getStatus() ?: 'draft')); ?></span>
374 </h1>
375 <div><?php echo esc_html($invoice->getNumber()); ?> &bull; <?php echo esc_html(\EasyInvoice\Controllers\SettingsController::formatDate($invoice->getIssueDate())); ?></div>
376 <?php if ($invoice->getCustomerName()): ?>
377 <div style="margin-top: 12px;">
378 <strong><?php echo esc_html($invoice->getCustomerName()); ?></strong><br>
379 <?php echo esc_html($invoice->getCustomerEmail()); ?><br>
380 <?php echo esc_html($invoice->getCustomerAddress()); ?>
381 </div>
382 <?php endif; ?>
383 </div>
384
385 <?php if ($invoice->getNotes()): ?>
386 <div style="margin-bottom: 24px; color: #555;">
387 <?php echo nl2br(esc_html($invoice->getNotes())); ?>
388 </div>
389 <?php endif; ?>
390
391 <table class="invoice-items">
392 <thead>
393 <tr>
394 <th><?php _e('Item', 'easy-invoice'); ?></th>
395 <th><?php _e('Description', 'easy-invoice'); ?></th>
396 <th><?php _e('Qty', 'easy-invoice'); ?></th>
397 <th><?php _e('Unit Price', 'easy-invoice'); ?></th>
398 <?php if (\EasyInvoice\Controllers\SettingsController::shouldShowQuoteAdjustField()): ?>
399 <th><?php _e('Adjust (%)', 'easy-invoice'); ?></th>
400 <?php endif; ?>
401 <th><?php _e('Total', 'easy-invoice'); ?></th>
402 </tr>
403 </thead>
404 <tbody>
405 <?php foreach ($invoice->getItems() as $item): ?>
406 <tr>
407 <td><?php echo esc_html($item->getName()); ?></td>
408 <td><?php echo esc_html($item->getDescription()); ?></td>
409 <td><?php echo esc_html($item->getQuantity()); ?></td>
410 <td><?php echo esc_html($formatter->format($item->getPrice())); ?></td>
411 <?php if (\EasyInvoice\Controllers\SettingsController::shouldShowQuoteAdjustField()): ?>
412 <td><?php
413 $adjust_percentage = $item->getAdjustPercentage();
414 if ($adjust_percentage != 0) {
415 echo esc_html($adjust_percentage > 0 ? '+' : '') . esc_html($adjust_percentage) . '%';
416 } else {
417 echo esc_html__('', 'easy-invoice');
418 }
419 ?></td>
420 <?php endif; ?>
421 <td><?php echo esc_html($formatter->format($item->getAmount())); ?></td>
422 </tr>
423 <?php endforeach; ?>
424 </tbody>
425 </table>
426
427 <div class="invoice-summary">
428 <div><strong><?php _e('Subtotal', 'easy-invoice'); ?>:</strong> <?php echo esc_html($formatter->format($invoice->getSubtotal())); ?></div>
429 <?php if ($invoice->getDiscountAmount() > 0): ?>
430 <div><strong><?php _e('Discount', 'easy-invoice'); ?>:</strong> -<?php echo esc_html($formatter->format($invoice->getDiscountAmount())); ?></div>
431 <?php endif; ?>
432 <?php if ($invoice->getTaxAmount() > 0): ?>
433 <div><strong><?php _e('Tax', 'easy-invoice'); ?>:</strong> <?php echo esc_html($formatter->format($invoice->getTaxAmount())); ?></div>
434 <?php endif; ?>
435 <?php do_action('easy_invoice_invoice_totals_after_tax', $invoice); ?>
436 <div style="font-size: 1.2em; margin-top: 8px;"><strong><?php _e('Total', 'easy-invoice'); ?>:</strong> <?php echo esc_html($formatter->format($invoice->getTotal())); ?></div>
437 </div>
438 <?php endif; ?>
439 </div>
440
441 <!-- Payment panel (right side) - hidden by default -->
442 <?php if ($show_pay_button): ?>
443 <div id="payment-panel" class="payment-panel" style="width: 450px; flex-shrink: 0; display: none; transition: transform 0.3s ease; position: fixed; top: 0; right: 0; height: 100vh; z-index: 1000; background: white; box-shadow: -2px 0 10px rgba(0,0,0,0.1);">
444 <div style="position: relative; height: 100%; overflow-y: auto;">
445 <button onclick="closePaymentPanel()" style="position: absolute; top: 15px; right: 15px; background: rgba(0,0,0,0.1); border: none; color: #333; width: 35px; height: 35px; border-radius: 50%; cursor: pointer; font-size: 18px; z-index: 10; display: flex; align-items: center; justify-content: center;">×</button>
446 <?php include EASY_INVOICE_PLUGIN_DIR . 'templates/payment-section.php'; ?>
447 </div>
448 </div>
449 <?php endif; ?>
450 </div>
451 </div>
452
453 <script>
454 function printInvoiceContent() {
455 // Get the invoice content
456 const invoiceContent = document.querySelector('.invoice-content');
457
458 if (!invoiceContent) {
459 alert('<?php _e('Invoice content not found', 'easy-invoice'); ?>');
460 return;
461 }
462
463 // Create a new window for printing
464 const printWindow = window.open('', '_blank', 'width=800,height=600');
465
466 // Create the print HTML with comprehensive styles
467 const printHTML = `
468 <!DOCTYPE html>
469 <html>
470 <head>
471 <title><?php echo esc_js($invoice->getNumber() ?: __('Invoice', 'easy-invoice')); ?></title>
472 <meta charset="utf-8">
473 <style>
474 /* Reset and base styles */
475 * {
476 margin: 0;
477 padding: 0;
478 box-sizing: border-box;
479 }
480
481 body {
482 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
483 line-height: 1.6;
484 color: #333;
485 background: white;
486 padding: 20px;
487 }
488
489 /* Invoice container styles */
490 .invoice-container {
491 max-width: 800px;
492 margin: 0 auto;
493 background: white;
494 padding: 30px;
495 border: 1px solid #e5e7eb;
496 border-radius: 8px;
497 }
498
499 /* Header styles */
500 .invoice-header {
501 text-align: center;
502 margin-bottom: 40px;
503 padding-bottom: 20px;
504 border-bottom: 2px solid #e5e7eb;
505 }
506
507 .invoice-header h1 {
508 font-size: 32px;
509 font-weight: 700;
510 color: #1f2937;
511 margin-bottom: 10px;
512 }
513
514 .invoice-number {
515 font-size: 18px;
516 color: #6b7280;
517 font-weight: 500;
518 }
519
520 .invoice-date {
521 font-size: 14px;
522 color: #9ca3af;
523 margin-top: 5px;
524 }
525
526 /* Company and client info */
527 .invoice-info {
528 display: flex;
529 justify-content: space-between;
530 margin-bottom: 40px;
531 gap: 40px;
532 }
533
534 .company-info, .client-info {
535 flex: 1;
536 }
537
538 .company-info h2, .client-info h2 {
539 font-size: 16px;
540 font-weight: 600;
541 color: #374151;
542 margin-bottom: 10px;
543 text-transform: uppercase;
544 letter-spacing: 0.5px;
545 }
546
547 .company-info p, .client-info p {
548 margin-bottom: 5px;
549 color: #6b7280;
550 font-size: 14px;
551 }
552
553 .client-name {
554 font-weight: 600;
555 color: #1f2937;
556 font-size: 16px;
557 margin-bottom: 8px;
558 }
559
560 /* Items table */
561 .invoice-items {
562 width: 100%;
563 border-collapse: collapse;
564 margin-bottom: 30px;
565 }
566
567 .invoice-items th {
568 background: #f9fafb;
569 padding: 12px 15px;
570 text-align: left;
571 font-weight: 600;
572 color: #374151;
573 border-bottom: 2px solid #e5e7eb;
574 font-size: 14px;
575 text-transform: uppercase;
576 letter-spacing: 0.5px;
577 }
578
579 .invoice-items td {
580 padding: 15px;
581 border-bottom: 1px solid #f3f4f6;
582 vertical-align: top;
583 }
584
585 .invoice-items tr:hover {
586 background: #f9fafb;
587 }
588
589 .item-name {
590 font-weight: 500;
591 color: #1f2937;
592 }
593
594 .item-description {
595 font-size: 13px;
596 color: #6b7280;
597 margin-top: 5px;
598 }
599
600 .item-quantity, .item-price, .item-total {
601 text-align: right;
602 font-weight: 500;
603 }
604
605 /* Totals section */
606 .invoice-summary {
607 margin-left: auto;
608 width: 300px;
609 border-top: 2px solid #e5e7eb;
610 padding-top: 20px;
611 }
612
613 .summary-row {
614 display: flex;
615 justify-content: space-between;
616 padding: 8px 0;
617 font-size: 14px;
618 }
619
620 .summary-row.total {
621 font-size: 18px;
622 font-weight: 700;
623 color: #1f2937;
624 border-top: 1px solid #e5e7eb;
625 padding-top: 15px;
626 margin-top: 10px;
627 }
628
629 .summary-label {
630 color: #6b7280;
631 }
632
633 .summary-value {
634 font-weight: 500;
635 color: #1f2937;
636 }
637
638 /* Notes section */
639 .invoice-notes {
640 margin-top: 40px;
641 padding: 20px;
642 background: #f9fafb;
643 border-radius: 6px;
644 border-left: 4px solid #3b82f6;
645 }
646
647 .invoice-notes h3 {
648 font-size: 16px;
649 font-weight: 600;
650 color: #374151;
651 margin-bottom: 10px;
652 }
653
654 .invoice-notes p {
655 color: #6b7280;
656 line-height: 1.6;
657 }
658
659 /* Status badge */
660 .invoice-status {
661 display: inline-block;
662 padding: 4px 12px;
663 border-radius: 20px;
664 font-size: 12px;
665 font-weight: 600;
666 text-transform: uppercase;
667 letter-spacing: 0.5px;
668 margin-left: 10px;
669 }
670
671 .status-draft { background: #e5e7eb; color: #374151; }
672 .status-unpaid { background: #fef2f2; color: #dc2626; }
673 .status-paid { background: #f0fdf4; color: #166534; }
674 .status-overdue { background: #fffbeb; color: #d97706; }
675 .status-available { background: #eff6ff; color: #1d4ed8; }
676
677 /* Print-specific styles */
678 @media print {
679 body {
680 margin: 0;
681 padding: 0;
682 }
683
684 .invoice-container {
685 border: none;
686 padding: 0;
687 max-width: none;
688 }
689
690 .invoice-items th {
691 background: #f9fafb !important;
692 -webkit-print-color-adjust: exact;
693 color-adjust: exact;
694 }
695
696 .invoice-notes {
697 background: #f9fafb !important;
698 -webkit-print-color-adjust: exact;
699 color-adjust: exact;
700 }
701
702 .status-draft { background: #e5e7eb !important; }
703 .status-unpaid { background: #fef2f2 !important; }
704 .status-paid { background: #f0fdf4 !important; }
705 .status-overdue { background: #fffbeb !important; }
706 .status-available { background: #eff6ff !important; }
707
708 /* Ensure proper page breaks */
709 .invoice-content {
710 page-break-inside: avoid;
711 }
712
713 table {
714 page-break-inside: avoid;
715 }
716
717 tr {
718 page-break-inside: avoid;
719 }
720 }
721 </style>
722 </head>
723 <body>
724 ${invoiceContent.outerHTML}
725 </body>
726 </html>
727 `;
728
729 // Write the content to the new window
730 printWindow.document.write(printHTML);
731 printWindow.document.close();
732
733 // Wait for content to load, then print
734 printWindow.onload = function() {
735 setTimeout(function() {
736 printWindow.print();
737 printWindow.close();
738 }, 500);
739 };
740 }
741
742 function downloadInvoicePdf(invoiceId) {
743 // Show loading state
744 const button = event.target;
745 const originalText = button.innerHTML;
746 button.innerHTML = '<?php _e('Generating PDF...', 'easy-invoice'); ?>';
747 button.disabled = true;
748
749 try {
750 // Use the unified PDF generator
751 if (typeof DocumentPdfGenerator !== 'undefined') {
752 const pdfGenerator = new DocumentPdfGenerator('invoice');
753 pdfGenerator.generatePDF();
754
755 } else {
756 // Fallback if PDF generator not available
757 if (typeof EasyInvoiceToast !== 'undefined') {
758 EasyInvoiceToast.error('<?php _e('PDF generator not available', 'easy-invoice'); ?>');
759 } else {
760 alert('<?php _e('PDF generator not available', 'easy-invoice'); ?>');
761 }
762 }
763 } catch (error) {
764 console.error('PDF generation error:', error);
765 if (typeof EasyInvoiceToast !== 'undefined') {
766 EasyInvoiceToast.error('<?php _e('Error generating PDF', 'easy-invoice'); ?>');
767 } else {
768 alert('<?php _e('Error generating PDF', 'easy-invoice'); ?>');
769 }
770 }
771
772 // Reset button state
773 setTimeout(function() {
774 button.innerHTML = originalText;
775 button.disabled = false;
776 }, 1000);
777 }
778
779 function sendInvoiceEmail(invoiceId) {
780 // Show loading state
781 const button = event.target;
782 const originalText = button.innerHTML;
783 button.innerHTML = '<?php _e('Sending...', 'easy-invoice'); ?>';
784 button.disabled = true;
785
786 // Make AJAX request
787 jQuery.ajax({
788 url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
789 type: 'POST',
790 data: {
791 action: 'easy_invoice_send_invoice_email',
792 invoice_id: invoiceId,
793 nonce: '<?php echo wp_create_nonce('send_invoice_email'); ?>'
794 },
795 success: function(response) {
796 if (response.success) {
797 if (typeof EasyInvoiceToast !== 'undefined') {
798 EasyInvoiceToast.success('<?php _e('Invoice sent successfully', 'easy-invoice'); ?>');
799 } else {
800 alert('<?php _e('Invoice sent successfully', 'easy-invoice'); ?>');
801 }
802 } else {
803 if (typeof EasyInvoiceToast !== 'undefined') {
804 EasyInvoiceToast.error(response.data || '<?php _e('Error sending invoice', 'easy-invoice'); ?>');
805 } else {
806 alert(response.data || '<?php _e('Error sending invoice', 'easy-invoice'); ?>');
807 }
808 }
809 },
810 error: function() {
811 if (typeof EasyInvoiceToast !== 'undefined') {
812 EasyInvoiceToast.error('<?php _e('Network error occurred', 'easy-invoice'); ?>');
813 } else {
814 alert('<?php _e('Network error occurred', 'easy-invoice'); ?>');
815 }
816 },
817 complete: function() {
818 // Reset button state
819 button.innerHTML = originalText;
820 button.disabled = false;
821 }
822 });
823 }
824
825 // Modal System and Event Listeners
826 document.addEventListener('DOMContentLoaded', function() {
827 const invoiceId = '<?php echo esc_js($invoice->getId()); ?>';
828
829 // Utility: Show loading spinner in a button
830 function setButtonLoading(btn, loadingText) {
831 btn.disabled = true;
832 btn.innerHTML = `<span class='ei-btn-spinner' style='display:inline-block;vertical-align:middle;width:18px;height:18px;margin-right:8px;'>
833 <svg width='18' height='18' viewBox='0 0 50 50'><circle cx='25' cy='25' r='20' fill='none' stroke='#fff' stroke-width='5' stroke-linecap='round' stroke-dasharray='31.415, 31.415' transform='rotate(0 25 25)'><animateTransform attributeName='transform' type='rotate' from='0 25 25' to='360 25 25' dur='0.8s' repeatCount='indefinite'/></circle></svg>
834 </span>${loadingText}`;
835 }
836 function resetButtonLoading(btn, originalText) {
837 btn.disabled = false;
838 btn.innerHTML = originalText;
839 }
840
841 // Send Email Button
842 const emailButton = document.querySelector('.send-email-button');
843 if (emailButton) {
844 emailButton.addEventListener('click', function() {
845 const message = '<?php echo esc_js(__('Send this invoice via email?', 'easy-invoice')); ?>\n\n<?php echo esc_js(__('This will send the invoice to the client\'s email address.', 'easy-invoice')); ?>';
846
847 showConfirmationModal(
848 '<?php echo esc_js($text_settings['send_email']); ?>',
849 message,
850 '<?php echo esc_js($text_settings['send_email']); ?>',
851 '<?php echo esc_js(__('Cancel', 'easy-invoice')); ?>',
852 'primary',
853 function(modal, confirmBtn, originalText) {
854 handleSendEmail(invoiceId, confirmBtn, originalText);
855 }
856 );
857 });
858 }
859
860 // Custom Modal System
861 function showConfirmationModal(title, message, confirmText, cancelText, confirmType, onConfirm) {
862 // Remove existing modal if any
863 const existingModal = document.querySelector('.ei-modal-overlay');
864 if (existingModal) {
865 existingModal.remove();
866 }
867
868 // Create modal overlay
869 const overlay = document.createElement('div');
870 overlay.className = 'ei-modal-overlay';
871
872 // Create modal content
873 const modal = document.createElement('div');
874 modal.className = 'ei-modal';
875
876 const confirmBtnClass = confirmType === 'danger' ? 'ei-modal-btn-danger' : 'ei-modal-btn-primary';
877 const iconClass = confirmType === 'danger' ? 'danger' : 'info';
878 const icon = confirmType === 'danger' ?
879 '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="none"/><path d="M12 7v4.5M12 16h.01" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>' :
880 '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="none"/><path d="M12 16v-4M12 8h.01" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
881
882 modal.innerHTML = `
883 <div class="ei-modal-header">
884 <div class="ei-modal-icon ${iconClass}">
885 ${icon}
886 </div>
887 <h3 class="ei-modal-title">${title}</h3>
888 </div>
889 <div class="ei-modal-body">
890 <div class="ei-modal-message">${message}</div>
891 </div>
892 <div class="ei-modal-actions">
893 <button type="button" class="ei-modal-btn ei-modal-btn-secondary" id="modal-cancel">${cancelText}</button>
894 <button type="button" class="ei-modal-btn ${confirmBtnClass}" id="modal-confirm">${confirmText}</button>
895 </div>
896 `;
897
898 overlay.appendChild(modal);
899 document.body.appendChild(overlay);
900
901 // Show modal with animation
902 setTimeout(() => {
903 overlay.classList.add('show');
904 modal.classList.add('show');
905 }, 10);
906
907 // Handle button clicks
908 const confirmBtn = modal.querySelector('#modal-confirm');
909 const cancelBtn = modal.querySelector('#modal-cancel');
910
911 confirmBtn.addEventListener('click', function() {
912 const originalText = confirmBtn.innerHTML;
913 setButtonLoading(confirmBtn, confirmBtn.textContent.trim());
914 onConfirm(modal, confirmBtn, originalText);
915 });
916
917 cancelBtn.addEventListener('click', function() {
918 hideModal(overlay);
919 });
920
921 // Handle overlay click to close
922 overlay.addEventListener('click', function(e) {
923 if (e.target === overlay) {
924 hideModal(overlay);
925 }
926 });
927
928 // Handle escape key
929 const handleEscape = function(e) {
930 if (e.key === 'Escape') {
931 hideModal(overlay);
932 document.removeEventListener('keydown', handleEscape);
933 }
934 };
935 document.addEventListener('keydown', handleEscape);
936
937 // Focus on confirm button
938 setTimeout(() => {
939 confirmBtn.focus();
940 }, 100);
941 }
942
943 function hideModal(overlay) {
944 const modal = overlay.querySelector('.ei-modal');
945 modal.classList.remove('show');
946 overlay.classList.remove('show');
947
948 setTimeout(() => {
949 if (overlay.parentNode) {
950 overlay.remove();
951 }
952 }, 300);
953 }
954
955 // Show message in modal (used for AJAX responses)
956 function showModalMessage(type, message, onClose) {
957 // Remove existing modal if any
958 const existingModal = document.querySelector('.ei-modal-overlay');
959 if (existingModal) {
960 existingModal.remove();
961 }
962 // Create modal overlay
963 const overlay = document.createElement('div');
964 overlay.className = 'ei-modal-overlay';
965 // Create modal content
966 const modal = document.createElement('div');
967 modal.className = 'ei-modal';
968 const iconClass = type === 'success' ? 'info' : 'danger';
969 const icon = type === 'success'
970 ? '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="#10b981"/><path d="M7 13l3 3 7-7" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
971 : '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="#ef4444"/><path d="M12 7v4.5M12 16h.01" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
972 modal.innerHTML = `
973 <div class="ei-modal-header">
974 <div class="ei-modal-icon ${iconClass}">${icon}</div>
975 <h3 class="ei-modal-title">${type === 'success' ? '<?php echo esc_js(__('Success', 'easy-invoice')); ?>' : '<?php echo esc_js(__('Error', 'easy-invoice')); ?>'}</h3>
976 </div>
977 <div class="ei-modal-body">
978 <div class="ei-modal-message">${message}</div>
979 </div>
980 <div class="ei-modal-actions">
981 <button type="button" class="ei-modal-btn ei-modal-btn-primary" id="modal-close"><?php echo esc_js(__('Close', 'easy-invoice')); ?></button>
982 </div>
983 `;
984 overlay.appendChild(modal);
985 document.body.appendChild(overlay);
986 setTimeout(() => {
987 overlay.classList.add('show');
988 modal.classList.add('show');
989 }, 10);
990 const closeBtn = modal.querySelector('#modal-close');
991 closeBtn.addEventListener('click', function() {
992 hideModal(overlay);
993 if (onClose) onClose();
994 });
995 overlay.addEventListener('click', function(e) {
996 if (e.target === overlay) {
997 hideModal(overlay);
998 if (onClose) onClose();
999 }
1000 });
1001 const handleEscape = function(e) {
1002 if (e.key === 'Escape') {
1003 hideModal(overlay);
1004 document.removeEventListener('keydown', handleEscape);
1005 if (onClose) onClose();
1006 }
1007 };
1008 document.addEventListener('keydown', handleEscape);
1009 setTimeout(() => {
1010 closeBtn.focus();
1011 }, 100);
1012 }
1013
1014 // Show loading state in modal
1015 function showModalLoading(message) {
1016 // Remove existing modal if any
1017 const existingModal = document.querySelector('.ei-modal-overlay');
1018 if (existingModal) {
1019 existingModal.remove();
1020 }
1021 // Create modal overlay
1022 const overlay = document.createElement('div');
1023 overlay.className = 'ei-modal-overlay';
1024 // Create modal content
1025 const modal = document.createElement('div');
1026 modal.className = 'ei-modal';
1027 modal.innerHTML = `
1028 <div class="ei-modal-header">
1029 <div class="ei-modal-icon info">
1030 <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="#3b82f6"/><circle cx="12" cy="12" r="6" fill="#fff"/><animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/></svg>
1031 </div>
1032 <h3 class="ei-modal-title"><?php echo esc_js(__('Please wait...', 'easy-invoice')); ?></h3>
1033 </div>
1034 <div class="ei-modal-body">
1035 <div class="ei-modal-message">${message}</div>
1036 </div>
1037 `;
1038 overlay.appendChild(modal);
1039 document.body.appendChild(overlay);
1040 setTimeout(() => {
1041 overlay.classList.add('show');
1042 modal.classList.add('show');
1043 }, 10);
1044 }
1045
1046 // Handle Send Email
1047 function handleSendEmail(invoiceId, confirmBtn, originalText) {
1048 showModalLoading('<?php echo esc_js(__('Sending email...', 'easy-invoice')); ?>');
1049 jQuery.ajax({
1050 url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
1051 type: 'POST',
1052 data: {
1053 action: 'easy_invoice_send_invoice_email',
1054 invoice_id: invoiceId,
1055 nonce: '<?php echo wp_create_nonce('easy_invoice_send_invoice_email'); ?>'
1056 },
1057 success: function(response) {
1058 if (response.success) {
1059 var msg = (response && response.data && (response.data.message || (response.data.toast && response.data.toast.message))) || '<?php _e('Email sent successfully', 'easy-invoice'); ?>';
1060 showModalMessage('success', msg);
1061 } else {
1062 // Handle both response.data.message and response.data (direct string)
1063 var msg = '';
1064 if (response && response.data) {
1065 if (response.data.message) {
1066 msg = response.data.message;
1067 } else if (typeof response.data === 'string') {
1068 msg = response.data;
1069 } else if (response.data.toast && response.data.toast.message) {
1070 msg = response.data.toast.message;
1071 }
1072 }
1073 msg = msg || '<?php _e('Error sending email', 'easy-invoice'); ?>';
1074
1075 resetButtonLoading(confirmBtn, originalText);
1076 showModalMessage('error', msg);
1077 }
1078 },
1079 error: function() {
1080 resetButtonLoading(confirmBtn, originalText);
1081 showModalMessage('error', '<?php _e('Error connecting to server', 'easy-invoice'); ?>');
1082 }
1083 });
1084 }
1085 });
1086 </script>
1087
1088 <!-- Payment variables for the payment form -->
1089 <script>
1090 // Global variables needed by the payment form (basic payment functionality)
1091 window.easy_invoice_vars = {
1092 ajax_url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
1093 nonce: '<?php echo wp_create_nonce('easy_invoice_payment'); ?>',
1094 currency_symbol: '<?php echo esc_js(\EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($invoice->getCurrencyCode() ?: 'USD')); ?>',
1095 currency_code: '<?php echo esc_js($invoice->getCurrencyCode() ?: 'USD'); ?>'
1096 };
1097
1098 // Invoice data for PDF generation
1099 window.invoiceData = <?php
1100 $invoice_data = \EasyInvoice\Includes\Helpers\PdfHelper::getInvoiceDataForPdf($invoice);
1101 $invoice_data['status'] = $invoice->getStatus();
1102 echo json_encode($invoice_data);
1103 ?>;
1104 </script>
1105 <script>
1106 document.addEventListener('DOMContentLoaded', function() {
1107 // Auto-download PDF if ?auto_download_pdf=1 is present
1108 if (window.location.search.indexOf('auto_download_pdf=1') !== -1) {
1109 setTimeout(function() {
1110 if (typeof DocumentPdfGenerator !== 'undefined') {
1111 const pdfGenerator = new DocumentPdfGenerator('invoice');
1112 pdfGenerator.generatePDF();
1113 }
1114 }, 800);
1115 }
1116 });
1117 </script>
1118
1119 <?php
1120 // Load additional CSS for all users (not just admin)
1121 $additional_css = get_post_meta($invoice->getId(), '_easy_invoice_additional_css', true) ?: '';
1122 if (!empty($additional_css)):
1123 ?>
1124 <style id="custom-invoice-css">
1125 <?php echo esc_html($additional_css); ?>
1126 </style>
1127 <?php endif; ?>
1128
1129 <?php
1130 // Add Additional CSS feature for admin users
1131 if (is_user_logged_in() && current_user_can('manage_options')):
1132 ?>
1133 <!-- CSS Panel -->
1134 <div id="css-panel" style="display: none; position: fixed; top: 0; left: 0; width: 400px; height: 100vh; background: white; box-shadow: 2px 0 10px rgba(0,0,0,0.1); z-index: 1001; overflow-y: auto;">
1135 <div style="padding: 20px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center;">
1136 <h3 style="margin: 0; font-size: 18px; font-weight: 600;">Additional CSS</h3>
1137 <button type="button" id="close-css-panel" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #6b7280;">&times;</button>
1138 </div>
1139 <div style="padding: 20px;">
1140 <div style="margin-bottom: 15px;">
1141 <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #374151;">Custom CSS for this invoice:</label>
1142 <textarea id="additional-css-textarea" style="width: calc(100% - 24px); height: 300px; padding: 12px; border: 1px solid #d1d5db; border-radius: 6px; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; resize: vertical; box-sizing: border-box;" placeholder="Enter your custom CSS here..."><?php echo esc_textarea($additional_css); ?></textarea>
1143 </div>
1144 <div style="display: flex; gap: 10px;">
1145 <button type="button" id="save-css-btn" style="background: #10b981; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">Save CSS</button>
1146 <button type="button" id="preview-css-btn" style="background: #6366f1; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">Preview</button>
1147 <button type="button" id="clear-css-btn" style="background: #ef4444; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">Clear</button>
1148 </div>
1149 <div id="css-status" style="margin-top: 15px; padding: 10px; border-radius: 4px; display: none;"></div>
1150 </div>
1151 </div>
1152
1153 <!-- Overlay -->
1154 <div id="css-overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;"></div>
1155
1156 <script>
1157 // Additional CSS functionality
1158 document.addEventListener('DOMContentLoaded', function() {
1159 const cssBtn = document.getElementById('additional-css-btn');
1160 const cssPanel = document.getElementById('css-panel');
1161 const closeBtn = document.getElementById('close-css-panel');
1162 const overlay = document.getElementById('css-overlay');
1163 const saveBtn = document.getElementById('save-css-btn');
1164 const previewBtn = document.getElementById('preview-css-btn');
1165 const clearBtn = document.getElementById('clear-css-btn');
1166 const textarea = document.getElementById('additional-css-textarea');
1167 const status = document.getElementById('css-status');
1168
1169 if (!cssBtn) return; // Exit if CSS button doesn't exist
1170
1171 let originalCSS = textarea.value;
1172
1173 // Toggle panel
1174 cssBtn.addEventListener('click', function() {
1175 cssPanel.style.display = 'block';
1176 overlay.style.display = 'block';
1177 });
1178
1179 // Close panel
1180 function closePanel() {
1181 cssPanel.style.display = 'none';
1182 overlay.style.display = 'none';
1183 }
1184
1185 closeBtn.addEventListener('click', closePanel);
1186 overlay.addEventListener('click', closePanel);
1187
1188 // Save CSS
1189 saveBtn.addEventListener('click', function() {
1190 const css = textarea.value;
1191
1192 // AJAX request to save CSS
1193 fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
1194 method: 'POST',
1195 headers: {
1196 'Content-Type': 'application/x-www-form-urlencoded',
1197 },
1198 body: new URLSearchParams({
1199 'action': 'save_additional_css',
1200 'post_id': <?php echo $invoice->getId(); ?>,
1201 'css': css,
1202 'nonce': '<?php echo wp_create_nonce('save_additional_css_nonce'); ?>'
1203 })
1204 })
1205 .then(response => response.json())
1206 .then(data => {
1207 if (data.success) {
1208 originalCSS = css;
1209 status.textContent = 'CSS saved successfully!';
1210 status.style.background = '#d1fae5';
1211 status.style.color = '#065f46';
1212 status.style.display = 'block';
1213
1214 // Apply the saved CSS
1215 applyCustomCSS(css);
1216
1217 setTimeout(() => {
1218 status.style.display = 'none';
1219 }, 3000);
1220 } else {
1221 status.textContent = 'Error saving CSS: ' + data.message;
1222 status.style.background = '#fee2e2';
1223 status.style.color = '#991b1b';
1224 status.style.display = 'block';
1225 }
1226 })
1227 .catch(error => {
1228 status.textContent = 'Error saving CSS: ' + error.message;
1229 status.style.background = '#fee2e2';
1230 status.style.color = '#b1b';
1231 status.style.display = 'block';
1232 });
1233 });
1234
1235 // Preview CSS
1236 previewBtn.addEventListener('click', function() {
1237 const css = textarea.value;
1238 applyCustomCSS(css);
1239
1240 status.textContent = 'CSS preview applied';
1241 status.style.background = '#dbeafe';
1242 status.style.color = '#1e40af';
1243 status.style.display = 'block';
1244
1245 setTimeout(() => {
1246 status.style.display = 'none';
1247 }, 2000);
1248 });
1249
1250 // Clear CSS
1251 clearBtn.addEventListener('click', function() {
1252 if (confirm('Are you sure you want to clear all custom CSS?')) {
1253 textarea.value = '';
1254 applyCustomCSS('');
1255
1256 status.textContent = 'CSS cleared';
1257 status.style.background = '#fee2e2';
1258 status.style.color = '#991b1b';
1259 status.style.display = 'block';
1260
1261 setTimeout(() => {
1262 status.style.display = 'none';
1263 }, 2000);
1264 }
1265 });
1266
1267 // Apply custom CSS function
1268 function applyCustomCSS(css) {
1269 // Remove existing custom CSS
1270 const existingStyle = document.getElementById('custom-invoice-css');
1271 if (existingStyle) {
1272 existingStyle.remove();
1273 }
1274
1275 // Add new custom CSS if not empty
1276 if (css.trim()) {
1277 const style = document.createElement('style');
1278 style.id = 'custom-invoice-css';
1279 style.textContent = css;
1280 document.head.appendChild(style);
1281 }
1282 }
1283
1284 // Apply saved CSS on page load
1285 applyCustomCSS(originalCSS);
1286 });
1287 </script>
1288 <?php endif; ?>
1289 </body>
1290 </html>
1291