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

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

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