PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
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
← All changes | templates/invoices/single.php +10 -1091 2.1.1 → 2.4.3 View file →
@@ -1,1102 +1,21 @@
1 1 <?php
2 2 /**
3 - * Single Invoice Template (Bare)
3 + * Superseded by templates/document/single.php.
4 4 *
5 + * Both public documents render through one page now. This file stays so
6 + * anything that resolved this path directly keeps working; override the
7 + * shared page at {theme}/easy-invoice/document/single.php instead.
8 + *
5 9 * @package Easy_Invoice
6 10 * @subpackage Templates
11 + * @deprecated 2.4.0
7 12 */
8 13
9 -// Prevent direct access
10 -if (!defined('ABSPATH')) {
14 +if ( ! defined( 'ABSPATH' ) ) {
11 15 exit;
12 16 }
13 17
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);
18 +$ei_document_template = easy_invoice_locate_template( 'document/single.php' );
19 +if ( '' !== $ei_document_template ) {
20 + include $ei_document_template;
28 21 }
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>