PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.4
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.4
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.3.4, at templates/invoices/single.php

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