PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | templates/quotes/single.php +10 -1703 2.1.132.4.0 View file →
@@ -1,1714 +1,21 @@
1 1 <?php
2 2 /**
3 - * Single Quote 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 -
10 -
11 -// Prevent direct access
12 -if (!defined('ABSPATH')) {
14 +if ( ! defined( 'ABSPATH' ) ) {
13 15 exit;
14 16 }
15 17
16 -
17 -// Hide the admin bar for this view
18 -add_filter('show_admin_bar', '__return_false');
19 -
20 -// Get quote from WordPress query
21 -global $post;
22 -$quote = null;
23 -
24 -if ($post && $post->post_type === \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
25 - // Load quote from post using the correct service provider
26 - $quote = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository()->find($post->ID);
27 -
28 - // Ensure quote has proper slug for pretty URLs
29 - if ($quote) {
30 - $quote->ensureProperSlug();
31 - }
18 +$ei_document_template = easy_invoice_locate_template( 'document/single.php' );
19 +if ( '' !== $ei_document_template ) {
20 + include $ei_document_template;
32 21 }
33 -
34 -if (!$quote) {
35 - wp_die(__('Quote not found.', 'easy-invoice'));
36 -}
37 -
38 -// Get the selected template for this quote
39 -$current_template = $quote->getTemplate();
40 -if (empty($current_template)) {
41 - $current_template = 'standard';
42 -}
43 -
44 -// Initialize formatter for the quote templates
45 -$formatter = new \EasyInvoice\Helpers\QuoteFormatter($quote);
46 -
47 - // Check if the template file exists
48 -$template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/' . $current_template . '.php';
49 -
50 -$template_file = file_exists($template_file) ? $template_file : EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/default.php';
51 -
52 -?><!DOCTYPE html>
53 -<html <?php language_attributes(); ?>>
54 -<head>
55 - <meta charset="<?php bloginfo('charset'); ?>">
56 - <meta name="viewport" content="width=device-width, initial-scale=1.0">
57 - <title><?php echo esc_html($quote->getTitle() ?: __('Quote', 'easy-invoice')); ?></title>
58 -
59 - <!-- Load jQuery and toast system for better user experience -->
60 - <script src="<?php echo esc_url(includes_url('js/jquery/jquery.min.js')); ?>"></script>
61 - <script src="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/js/easy-invoice-toast.js'); ?>"></script>
62 - <script src="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/js/confirmation-modal.js'); ?>"></script>
63 -
64 - <!-- Load jsPDF for PDF generation -->
65 - <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
66 - <script src="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/js/document-pdf.js'); ?>"></script>
67 - <!-- Note: Pro version's pdf-watermark.js will be loaded automatically by the Pro plugin -->
68 - <!-- Add Font Awesome for payment icons -->
69 - <link rel="stylesheet" href="<?php echo esc_url(EASY_INVOICE_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css'); ?>">
70 - <?php do_action('easy_invoice_head'); ?>
71 - <!-- Define quote action functions -->
72 - <script>
73 - // Global functions for quote actions
74 - function resetButtonLoading(button, originalText) {
75 - if (button) {
76 - button.innerHTML = originalText;
77 - button.disabled = false;
78 - }
79 - }
80 -
81 - // Handle Download PDF
82 - function handleDownloadPDF(quoteId, button) {
83 - const originalText = button.innerHTML;
84 - button.innerHTML = '<?php _e('Generating PDF...', 'easy-invoice'); ?>';
85 - button.disabled = true;
86 -
87 - try {
88 - // Use the unified PDF generator
89 - if (typeof DocumentPdfGenerator !== 'undefined') {
90 - const pdfGenerator = new DocumentPdfGenerator('quote');
91 - pdfGenerator.generatePDF();
92 - } else {
93 - throw new Error('PDF generator not available');
94 - }
95 - } catch (error) {
96 - console.error('PDF generation error:', error);
97 - showMessage('<?php _e('Error generating PDF', 'easy-invoice'); ?>', 'error');
98 - } finally {
99 - button.innerHTML = originalText;
100 - button.disabled = false;
101 - }
102 - }
103 -
104 - // Handle Send Email
105 - function handleSendEmail(quoteId, confirmBtn, originalText) {
106 - // Fallback loading if modal helper is not yet defined
107 - if (typeof showModalLoading === 'function') {
108 - showModalLoading('<?php echo esc_js(__('Sending email...', 'easy-invoice')); ?>');
109 - } else if (confirmBtn) {
110 - confirmBtn.disabled = true;
111 - confirmBtn.dataset._oldText = confirmBtn.innerHTML;
112 - confirmBtn.innerHTML = '<?php echo esc_js(__('Sending...', 'easy-invoice')); ?>';
113 - }
114 - jQuery.ajax({
115 - url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
116 - type: 'POST',
117 - data: {
118 - action: 'easy_invoice_send_quote_email',
119 - quote_id: quoteId,
120 - nonce: '<?php echo wp_create_nonce('easy_invoice_send_quote_email'); ?>'
121 - },
122 - success: function(response) {
123 - var msg = '';
124 - if (response.success) {
125 - msg = (response && response.data && (response.data.message || (response.data.toast && response.data.toast.message))) || '<?php _e('Email sent successfully', 'easy-invoice'); ?>';
126 - showModalMessage('success', msg);
127 - } else {
128 - // Handle both response.data.message and response.data (direct string)
129 - if (response && response.data) {
130 - if (response.data.message) {
131 - msg = response.data.message;
132 - } else if (typeof response.data === 'string') {
133 - msg = response.data;
134 - } else if (response.data.toast && response.data.toast.message) {
135 - msg = response.data.toast.message;
136 - }
137 - }
138 - msg = msg || '<?php _e('Error sending email', 'easy-invoice'); ?>';
139 -
140 - if (confirmBtn && confirmBtn.dataset._oldText) {
141 - confirmBtn.innerHTML = confirmBtn.dataset._oldText;
142 - confirmBtn.disabled = false;
143 - delete confirmBtn.dataset._oldText;
144 - } else {
145 - resetButtonLoading(confirmBtn, originalText);
146 - }
147 - showModalMessage('error', msg);
148 - }
149 - },
150 - error: function(jqXHR) {
151 - var msg = (jqXHR && jqXHR.responseJSON && jqXHR.responseJSON.data && (jqXHR.responseJSON.data.message || (jqXHR.responseJSON.data.toast && jqXHR.responseJSON.data.toast.message))) || '';
152 - if (confirmBtn && confirmBtn.dataset._oldText) {
153 - confirmBtn.innerHTML = confirmBtn.dataset._oldText;
154 - confirmBtn.disabled = false;
155 - delete confirmBtn.dataset._oldText;
156 - } else {
157 - resetButtonLoading(confirmBtn, originalText);
158 - }
159 - showModalMessage('error', msg || '<?php _e('Error connecting to server', 'easy-invoice'); ?>');
160 - }
161 - });
162 - }
163 -
164 - // Show message function
165 - function showMessage(message, type) {
166 - if (typeof EasyInvoiceToast !== 'undefined') {
167 - if (type === 'success') {
168 - EasyInvoiceToast.success(message);
169 - } else {
170 - EasyInvoiceToast.error(message);
171 - }
172 - } else {
173 - alert(message);
174 - }
175 - }
176 -
177 - // Generic confirmation modal (global) - modern ES6 syntax
178 - function showConfirmationModal(title, message, confirmText, cancelText, confirmType, onConfirm) {
179 - // Remove existing modal if any
180 - const existingModal = document.querySelector('.ei-modal-overlay');
181 - if (existingModal) {
182 - existingModal.remove();
183 - }
184 -
185 - // Create modal overlay
186 - const overlay = document.createElement('div');
187 - overlay.className = 'ei-modal-overlay';
188 -
189 - // Create modal content
190 - const modal = document.createElement('div');
191 - modal.className = 'ei-modal';
192 -
193 - const confirmBtnClass = confirmType === 'danger' ? 'ei-modal-btn-danger' : 'ei-modal-btn-primary';
194 - const iconClass = confirmType === 'danger' ? 'danger' : 'info';
195 - const icon = confirmType === 'danger' ?
196 - '<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>' :
197 - '<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>';
198 -
199 - modal.innerHTML = `
200 - <div class="ei-modal-header">
201 - <div class="ei-modal-icon ${iconClass}">
202 - ${icon}
203 - </div>
204 - <h3 class="ei-modal-title">${title}</h3>
205 - </div>
206 - <div class="ei-modal-body">
207 - <div class="ei-modal-message">${message}</div>
208 - </div>
209 - <div class="ei-modal-actions">
210 - <button type="button" class="ei-modal-btn ei-modal-btn-secondary" id="modal-cancel">${cancelText}</button>
211 - <button type="button" class="ei-modal-btn ${confirmBtnClass}" id="modal-confirm">${confirmText}</button>
212 - </div>
213 - `;
214 -
215 - overlay.appendChild(modal);
216 - document.body.appendChild(overlay);
217 -
218 - // Show modal with animation
219 - setTimeout(() => {
220 - overlay.classList.add('show');
221 - modal.classList.add('show');
222 - }, 10);
223 -
224 - // Handle button clicks
225 - const confirmBtn = modal.querySelector('#modal-confirm');
226 - const cancelBtn = modal.querySelector('#modal-cancel');
227 -
228 - confirmBtn.addEventListener('click', function() {
229 - const originalText = confirmBtn.innerHTML;
230 - setButtonLoading(confirmBtn, confirmBtn.textContent.trim());
231 - onConfirm(modal, confirmBtn, originalText);
232 - });
233 -
234 - cancelBtn.addEventListener('click', function() {
235 - hideModal(overlay);
236 - });
237 -
238 - // Handle overlay click to close
239 - overlay.addEventListener('click', function(e) {
240 - if (e.target === overlay) {
241 - hideModal(overlay);
242 - }
243 - });
244 -
245 - // Handle escape key
246 - const handleEscape = function(e) {
247 - if (e.key === 'Escape') {
248 - hideModal(overlay);
249 - document.removeEventListener('keydown', handleEscape);
250 - }
251 - };
252 - document.addEventListener('keydown', handleEscape);
253 -
254 - // Focus on confirm button
255 - setTimeout(() => {
256 - confirmBtn.focus();
257 - }, 100);
258 - }
259 -
260 - // Wrapper to confirm sending quote email
261 - function confirmSendEmail(quoteId) {
262 - const title = '<?php echo esc_js(__('Send Email', 'easy-invoice')); ?>';
263 - const message = '<?php echo esc_js(__('Send this quote via email?', 'easy-invoice')); ?>\n\n<?php echo esc_js(__('This will send the quote to the client\'s email address.', 'easy-invoice')); ?>';
264 - const confirmText = '<?php echo esc_js(__('Send Email', 'easy-invoice')); ?>';
265 - const cancelText = '<?php echo esc_js(__('Cancel', 'easy-invoice')); ?>';
266 - showConfirmationModal(title, message, confirmText, cancelText, 'primary', function(modal, confirmBtn, originalText) {
267 - handleSendEmail(quoteId, confirmBtn, originalText);
268 - });
269 - }
270 -
271 - // Global modal helpers
272 - function hideModal(overlay) {
273 - const modal = overlay.querySelector('.ei-modal');
274 - modal.classList.remove('show');
275 - overlay.classList.remove('show');
276 -
277 - setTimeout(() => {
278 - if (overlay.parentNode) {
279 - overlay.remove();
280 - }
281 - }, 300);
282 - }
283 -
284 - // Utility: Show loading spinner in a button
285 - function setButtonLoading(btn, loadingText) {
286 - btn.disabled = true;
287 - btn.innerHTML = `<span class='ei-btn-spinner' style='display:inline-block;vertical-align:middle;width:18px;height:18px;margin-right:8px;'>
288 - <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>
289 - </span>${loadingText}`;
290 - }
291 -
292 - function resetButtonLoading(btn, originalText) {
293 - btn.disabled = false;
294 - btn.innerHTML = originalText;
295 - }
296 -
297 - // Show message in modal (used for AJAX responses)
298 - function showModalMessage(type, message, onClose) {
299 - // Remove existing modal if any
300 - const existingModal = document.querySelector('.ei-modal-overlay');
301 - if (existingModal) {
302 - existingModal.remove();
303 - }
304 - // Create modal overlay
305 - const overlay = document.createElement('div');
306 - overlay.className = 'ei-modal-overlay';
307 - // Create modal content
308 - const modal = document.createElement('div');
309 - modal.className = 'ei-modal';
310 - const iconClass = type === 'success' ? 'info' : 'danger';
311 - const icon = type === 'success'
312 - ? '<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>'
313 - : '<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>';
314 - modal.innerHTML = `
315 - <div class="ei-modal-header">
316 - <div class="ei-modal-icon ${iconClass}">${icon}</div>
317 - <h3 class="ei-modal-title">${type === 'success' ? '<?php echo esc_js(__('Success', 'easy-invoice')); ?>' : '<?php echo esc_js(__('Error', 'easy-invoice')); ?>'}</h3>
318 - </div>
319 - <div class="ei-modal-body">
320 - <div class="ei-modal-message">${message}</div>
321 - </div>
322 - <div class="ei-modal-actions">
323 - <button type="button" class="ei-modal-btn ei-modal-btn-primary" id="modal-close"><?php echo esc_js(__('Close', 'easy-invoice')); ?></button>
324 - </div>
325 - `;
326 - overlay.appendChild(modal);
327 - document.body.appendChild(overlay);
328 - setTimeout(() => {
329 - overlay.classList.add('show');
330 - modal.classList.add('show');
331 - }, 10);
332 - const closeBtn = modal.querySelector('#modal-close');
333 - closeBtn.addEventListener('click', function() {
334 - hideModal(overlay);
335 - if (onClose) onClose();
336 - });
337 - overlay.addEventListener('click', function(e) {
338 - if (e.target === overlay) {
339 - hideModal(overlay);
340 - if (onClose) onClose();
341 - }
342 - });
343 - const handleEscape = function(e) {
344 - if (e.key === 'Escape') {
345 - hideModal(overlay);
346 - document.removeEventListener('keydown', handleEscape);
347 - if (onClose) onClose();
348 - }
349 - };
350 - document.addEventListener('keydown', handleEscape);
351 - setTimeout(() => {
352 - closeBtn.focus();
353 - }, 100);
354 - }
355 -
356 - // Show loading state in modal
357 - function showModalLoading(message) {
358 - // Remove existing modal if any
359 - const existingModal = document.querySelector('.ei-modal-overlay');
360 - if (existingModal) {
361 - existingModal.remove();
362 - }
363 - // Create modal overlay
364 - const overlay = document.createElement('div');
365 - overlay.className = 'ei-modal-overlay';
366 - // Create modal content
367 - const modal = document.createElement('div');
368 - modal.className = 'ei-modal';
369 - modal.innerHTML = `
370 - <div class="ei-modal-header">
371 - <div class="ei-modal-icon info">
372 - <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>
373 - </div>
374 - <h3 class="ei-modal-title"><?php echo esc_js(__('Please wait...', 'easy-invoice')); ?></h3>
375 - </div>
376 - <div class="ei-modal-body">
377 - <div class="ei-modal-message">${message}</div>
378 - </div>
379 - `;
380 - overlay.appendChild(modal);
381 - document.body.appendChild(overlay);
382 - setTimeout(() => {
383 - overlay.classList.add('show');
384 - modal.classList.add('show');
385 - }, 10);
386 - }
387 -
388 -
389 - </script>
390 -
391 - <style>
392 - body { margin: 0; padding: 0; font-family: sans-serif; background: #eaeaea; color: #222; }
393 - .easy-invoice-quote-container { max-width: 800px; margin: 40px auto; }
394 - h1, h2, h3 { margin-top: 0; }
395 - .quote-actions { margin-top: 32px; text-align: center; }
396 - .quote-actions button, .quote-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; }
397 - .quote-actions button.decline { background: #f87171; }
398 - .quote-actions button:disabled { opacity: 0.6; cursor: not-allowed; }
399 - .template-not-found { text-align: center; padding: 40px; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
400 - .template-not-found-icon { font-size: 48px; color: #dc3545; margin-bottom: 20px; }
401 - .template-not-found h3 { font-size: 24px; color: #32325d; margin-bottom: 10px; }
402 - .template-not-found p { color: #8898aa; margin-bottom: 10px; }
403 - .quote-content { flex: 1; min-width: 0; position: relative; }
404 -
405 - /* Custom Modal Styles */
406 - .ei-modal-overlay {
407 - position: fixed;
408 - top: 0;
409 - left: 0;
410 - width: 100%;
411 - height: 100%;
412 - background: rgba(0, 0, 0, 0.6);
413 - display: flex;
414 - justify-content: center;
415 - align-items: center;
416 - z-index: 10000;
417 - opacity: 0;
418 - visibility: hidden;
419 - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
420 - backdrop-filter: blur(4px);
421 - }
422 -
423 - .ei-modal-overlay.show {
424 - opacity: 1;
425 - visibility: visible;
426 - }
427 -
428 - .ei-modal {
429 - background: white;
430 - border-radius: 16px;
431 - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
432 - max-width: 480px;
433 - width: 90%;
434 - max-height: 85vh;
435 - overflow: hidden;
436 - transform: scale(0.95) translateY(20px);
437 - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
438 - border: 1px solid rgba(0, 0, 0, 0.05);
439 - }
440 -
441 - .ei-modal.show {
442 - transform: scale(1) translateY(0);
443 - }
444 -
445 - .ei-modal-header {
446 - padding: 32px 32px 0 32px;
447 - text-align: center;
448 - position: relative;
449 - }
450 -
451 - .ei-modal-icon {
452 - width: 64px;
453 - height: 64px;
454 - border-radius: 50%;
455 - margin: 0 auto 20px auto;
456 - display: flex;
457 - align-items: center;
458 - justify-content: center;
459 - font-size: 28px;
460 - color: white;
461 - }
462 -
463 - .ei-modal-icon.success {
464 - background: linear-gradient(135deg, #10b981, #059669);
465 - }
466 -
467 - .ei-modal-icon.warning {
468 - background: linear-gradient(135deg, #f59e0b, #d97706);
469 - }
470 -
471 - .ei-modal-icon.danger {
472 - background: linear-gradient(135deg, #ef4444, #dc2626);
473 - }
474 -
475 - .ei-modal-icon.info {
476 - background: linear-gradient(135deg, #3b82f6, #2563eb);
477 - }
478 -
479 - .ei-modal-title {
480 - font-size: 24px;
481 - font-weight: 700;
482 - color: #111827;
483 - margin: 0 0 8px 0;
484 - line-height: 1.3;
485 - }
486 -
487 - .ei-modal-subtitle {
488 - font-size: 16px;
489 - color: #6b7280;
490 - margin: 0;
491 - line-height: 1.5;
492 - }
493 -
494 - .ei-modal-body {
495 - padding: 24px 32px 32px 32px;
496 - }
497 -
498 - .ei-modal-message {
499 - font-size: 16px;
500 - line-height: 1.6;
501 - color: #374151;
502 - margin: 0;
503 - white-space: pre-line;
504 - text-align: center;
505 - }
506 -
507 - .ei-modal-actions {
508 - display: flex;
509 - gap: 12px;
510 - justify-content: center;
511 - padding: 0 32px 32px 32px;
512 - }
513 -
514 - .ei-modal-btn {
515 - padding: 12px 24px;
516 - border: none;
517 - border-radius: 8px;
518 - font-size: 15px;
519 - font-weight: 600;
520 - cursor: pointer;
521 - transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
522 - min-width: 100px;
523 - position: relative;
524 - overflow: hidden;
525 - }
526 -
527 - .ei-modal-btn::before {
528 - content: '';
529 - position: absolute;
530 - top: 0;
531 - left: -100%;
532 - width: 100%;
533 - height: 100%;
534 - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
535 - transition: left 0.5s;
536 - }
537 -
538 - .ei-modal-btn:hover::before {
539 - left: 100%;
540 - }
541 -
542 - .ei-modal-btn-secondary {
543 - background: #f9fafb;
544 - color: #374151;
545 - border: 1px solid #e5e7eb;
546 - }
547 -
548 - .ei-modal-btn-secondary:hover {
549 - background: #f3f4f6;
550 - border-color: #d1d5db;
551 - transform: translateY(-1px);
552 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
553 - }
554 -
555 - .ei-modal-btn-primary {
556 - background: linear-gradient(135deg, #007cba, #005a87);
557 - color: white;
558 - box-shadow: 0 4px 12px rgba(0, 124, 186, 0.3);
559 - }
560 -
561 - .ei-modal-btn-primary:hover {
562 - background: linear-gradient(135deg, #005a87, #004a6f);
563 - transform: translateY(-1px);
564 - box-shadow: 0 6px 16px rgba(0, 124, 186, 0.4);
565 - }
566 -
567 - .ei-modal-btn-danger {
568 - background: linear-gradient(135deg, #ef4444, #dc2626);
569 - color: white;
570 - box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
571 - }
572 -
573 - .ei-modal-btn-danger:hover {
574 - background: linear-gradient(135deg, #dc2626, #b91c1c);
575 - transform: translateY(-1px);
576 - box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
577 - }
578 -
579 - .ei-modal-btn:active {
580 - transform: translateY(0);
581 - }
582 -
583 - .ei-modal-btn:disabled {
584 - opacity: 0.6;
585 - cursor: not-allowed;
586 - transform: none !important;
587 - }
588 -
589 - /* Reason Field Styles */
590 - .ei-modal-reason-field {
591 - margin-top: 28px;
592 - }
593 - .ei-modal-label {
594 - display: block;
595 - font-size: 15px;
596 - font-weight: 600;
597 - color: #374151;
598 - margin-bottom: 10px;
599 - text-align: left;
600 - }
601 - .ei-modal-textarea {
602 - width: 100%;
603 - max-width: 100%;
604 - box-sizing: border-box;
605 - padding: 14px 18px;
606 - border: 1.5px solid #d1d5db;
607 - border-radius: 10px;
608 - font-size: 15px;
609 - line-height: 1.6;
610 - color: #374151;
611 - background: #f8fafc;
612 - transition: border-color 0.2s, box-shadow 0.2s;
613 - resize: vertical;
614 - min-height: 90px;
615 - font-family: inherit;
616 - box-shadow: 0 1px 2px rgba(0,0,0,0.03);
617 - }
618 - .ei-modal-textarea:focus {
619 - outline: none;
620 - border-color: #ef4444;
621 - background: #fff;
622 - box-shadow: 0 0 0 2px #fee2e2;
623 - }
624 - .ei-modal-textarea::placeholder {
625 - color: #9ca3af;
626 - }
627 - .ei-modal-textarea.error {
628 - border-color: #ef4444;
629 - background: #fef2f2;
630 - }
631 - .ei-modal-header {
632 - padding: 36px 36px 0 36px;
633 - text-align: center;
634 - position: relative;
635 - }
636 - .ei-modal-icon.danger {
637 - background: linear-gradient(135deg, #f87171, #dc2626);
638 - width: 60px;
639 - height: 60px;
640 - border-radius: 50%;
641 - display: flex;
642 - align-items: center;
643 - justify-content: center;
644 - margin: 0 auto 18px auto;
645 - box-shadow: 0 2px 8px rgba(239,68,68,0.10);
646 - }
647 - .ei-modal-icon.danger svg {
648 - width: 32px;
649 - height: 32px;
650 - display: block;
651 - }
652 - .ei-modal-icon.info {
653 - background: linear-gradient(135deg, #60a5fa, #3b82f6);
654 - width: 60px;
655 - height: 60px;
656 - border-radius: 50%;
657 - display: flex;
658 - align-items: center;
659 - justify-content: center;
660 - margin: 0 auto 18px auto;
661 - box-shadow: 0 2px 8px rgba(59,130,246,0.10);
662 - }
663 - .ei-modal-icon.info svg {
664 - width: 32px;
665 - height: 32px;
666 - display: block;
667 - }
668 - .ei-modal-title {
669 - font-size: 23px;
670 - font-weight: 700;
671 - color: #111827;
672 - margin: 0 0 10px 0;
673 - line-height: 1.3;
674 - }
675 - .ei-modal-body {
676 - padding: 28px 36px 36px 36px;
677 - }
678 - .ei-modal-message {
679 - font-size: 16px;
680 - line-height: 1.7;
681 - color: #374151;
682 - margin: 0 0 18px 0;
683 - white-space: pre-line;
684 - text-align: center;
685 - }
686 - .ei-modal-actions {
687 - display: flex;
688 - gap: 16px;
689 - justify-content: center;
690 - padding: 0 36px 36px 36px;
691 - margin-top: 18px;
692 - }
693 - .ei-modal {
694 - background: white;
695 - border-radius: 18px;
696 - box-shadow: 0 30px 60px -10px rgba(0,0,0,0.22), 0 2px 8px rgba(0,0,0,0.08);
697 - max-width: 420px;
698 - width: 95%;
699 - max-height: 90vh;
700 - overflow: hidden;
701 - transform: scale(0.95) translateY(20px);
702 - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
703 - border: 1px solid rgba(0, 0, 0, 0.04);
704 - }
705 - @media (max-width: 640px) {
706 - .ei-modal {
707 - width: 99%;
708 - margin: 10px;
709 - }
710 - .ei-modal-header, .ei-modal-body, .ei-modal-actions {
711 - padding-left: 12px;
712 - padding-right: 12px;
713 - }
714 - }
715 -
716 - @media print {
717 - .quote-actions { display: none; }
718 - body { background: white; }
719 - .quote-content { box-shadow: none; }
720 - }
721 - </style>
722 -</head>
723 -<body>
724 - <?php
725 - // Get global quote settings
726 - $settings_controller = new \EasyInvoice\Controllers\SettingsController();
727 - $accept_button_text = $settings_controller::getQuoteAcceptText();
728 - $accept_action = $settings_controller::getQuoteAcceptAction();
729 - $declined_message = $settings_controller::getDeclinedQuoteMessage();
730 -
731 - // Determine what happens when quote is accepted based on global settings
732 - $accept_action_description = '';
733 - switch ($accept_action) {
734 - case 'convert':
735 - $accept_action_description = __('This will convert the quote to an invoice.', 'easy-invoice');
736 - break;
737 - case 'convert_send':
738 - $accept_action_description = __('This will convert the quote to an invoice and send it to the client.', 'easy-invoice');
739 - break;
740 - case 'duplicate':
741 - $accept_action_description = __('This will create a new invoice while keeping this quote unchanged.', 'easy-invoice');
742 - break;
743 - case 'duplicate_send':
744 - $accept_action_description = __('This will create a new invoice and send it to the client, while keeping this quote unchanged.', 'easy-invoice');
745 - break;
746 - case 'do_nothing':
747 - default:
748 - $accept_action_description = __('This will mark the quote as accepted.', 'easy-invoice');
749 - break;
750 - }
751 - ?>
752 -
753 - <div class="easy-invoice-quote-container">
754 - <!-- Accept Action Information - Show at the top -->
755 - <?php
756 - // Get text settings for quote actions
757 - $text_settings = \EasyInvoice\Helpers\TemplateTextHelper::getQuoteTextSettings();
758 - ?>
759 -
760 - <?php if (in_array($quote->getStatus(), ['available', 'sent', 'draft']) && $accept_action_description): ?>
761 - <div class="accept-action-info" style="margin-bottom: 16px; padding: 12px; background: #f8f9fa; border-left: 4px solid #007cba; border-radius: 4px; font-size: 0.9em; color: #666;">
762 - <i class="fas fa-info-circle" style="margin-right: 8px; color: #007cba;"></i>
763 - <strong><?php echo esc_html($text_settings['accept_quote']); ?>:</strong>
764 - <?php echo esc_html($accept_action_description); ?>
765 - </div>
766 - <?php endif; ?>
767 -
768 - <!-- Quote Actions - All buttons in one line -->
769 - <div class="quote-actions" style="margin-bottom: 32px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center;">
770 - <?php if (in_array($quote->getStatus(), ['available', 'sent', 'draft'])): ?>
771 - <button type="button" class="accept" style="background: #28a745; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500; min-width: 120px; white-space: nowrap; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
772 - <?php echo esc_html($text_settings['accept_quote']); ?>
773 - </button>
774 - <button type="button" class="decline" style="background: #dc3545; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">
775 - <?php echo esc_html($text_settings['decline_quote']); ?>
776 - </button>
777 - <?php endif; ?>
778 -
779 - <!-- Additional action buttons -->
780 - <button type="button" onclick="printQuoteContent()" style="background: #10b981; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">
781 - <?php echo esc_html($text_settings['print']); ?>
782 - </button>
783 -
784 - <button type="button" class="download-pdf-btn" style="background: #f59e0b; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">
785 - <?php echo esc_html($text_settings['download_pdf']); ?>
786 - </button>
787 -
788 - <button type="button" onclick="confirmSendEmail(<?php echo esc_js($quote->getId()); ?>)" class="send-email-button" style="background: #8b5cf6; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 500;">
789 - <?php echo esc_html($text_settings['send_email']); ?>
790 - </button>
791 - </div>
792 -
793 - <!-- Quote Content Container -->
794 - <div class="quote-content" id="quote-content" style="flex: 1; min-width: 0; position: relative;">
795 - <?php
796 - // Add Additional CSS button for admin users
797 - if (is_user_logged_in() && current_user_can('manage_options')):
798 - $additional_css = get_post_meta($quote->getId(), '_easy_invoice_additional_css', true) ?: '';
799 - $has_css = !empty($additional_css);
800 - ?>
801 - <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)' : ''; ?>">
802 - <i class="fas fa-code"></i>
803 - <?php if ($has_css): ?>
804 - <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>
805 - <?php endif; ?>
806 - </button>
807 - <?php endif; ?>
808 -
809 - <?php do_action('easy_invoice_quote_view_content_top', $quote); ?>
810 - <?php if (file_exists($template_file)): ?>
811 - <?php
812 - // Include the selected template file
813 - include $template_file;
814 - ?>
815 - <?php else: ?>
816 - <!-- Fallback basic quote display -->
817 - <div class="quote-header" style="position: relative;">
818 - <h1><?php echo esc_html($quote->getTitle() ?: __('Quote', 'easy-invoice')); ?>
819 - <span class="quote-status"><?php echo esc_html(ucfirst($quote->getStatus())); ?></span>
820 - </h1>
821 - <div><?php echo esc_html($quote->getNumber()); ?> &bull; <?php echo esc_html(\EasyInvoice\Controllers\SettingsController::formatDate($quote->getCreatedDate())); ?></div>
822 - <?php if ($quote->getCustomerName()): ?>
823 - <div style="margin-top: 12px;">
824 - <strong><?php echo esc_html($quote->getCustomerName()); ?></strong><br>
825 - <?php echo esc_html($quote->getCustomerEmail()); ?><br>
826 - <?php echo esc_html($quote->getCustomerAddress()); ?>
827 - </div>
828 - <?php endif; ?>
829 - </div>
830 -
831 - <?php if ($quote->getNotes()): ?>
832 - <div style="margin-bottom: 24px; color: #555;">
833 - <?php echo nl2br(esc_html($quote->getNotes())); ?>
834 - </div>
835 - <?php endif; ?>
836 -
837 - <table class="quote-items">
838 - <thead>
839 - <tr>
840 - <th><?php _e('Item', 'easy-invoice'); ?></th>
841 - <th><?php _e('Description', 'easy-invoice'); ?></th>
842 - <th><?php _e('Qty', 'easy-invoice'); ?></th>
843 - <th><?php _e('Unit Price', 'easy-invoice'); ?></th>
844 - <th><?php _e('Adjust (%)', 'easy-invoice'); ?></th>
845 - <th><?php _e('Total', 'easy-invoice'); ?></th>
846 - </tr>
847 - </thead>
848 - <tbody>
849 - <?php foreach ($quote->getItems() as $item): ?>
850 - <tr>
851 - <td><?php echo esc_html($item->getName()); ?></td>
852 - <td><?php echo esc_html($item->getDescription()); ?></td>
853 - <td><?php echo esc_html($item->getQuantity()); ?></td>
854 - <td><?php echo esc_html(number_format_i18n($item->getPrice(), 2)); ?></td>
855 - <td><?php
856 - $adjust_percentage = $item->getAdjustPercentage();
857 - if ($adjust_percentage != 0) {
858 - echo esc_html($adjust_percentage > 0 ? '+' : '') . esc_html($adjust_percentage) . '%';
859 - } else {
860 - echo esc_html__('—', 'easy-invoice');
861 - }
862 - ?></td>
863 - <td><?php echo esc_html(number_format_i18n($item->getAmount(), 2)); ?></td>
864 - </tr>
865 - <?php endforeach; ?>
866 - </tbody>
867 - </table>
868 -
869 - <div class="quote-summary">
870 - <div><strong><?php _e('Subtotal', 'easy-invoice'); ?>:</strong> <?php echo esc_html(number_format_i18n($quote->getSubtotal(), 2)); ?></div>
871 - <?php if ($quote->getDiscountValue() > 0): ?>
872 - <div><strong><?php _e('Discount', 'easy-invoice'); ?>:</strong> -<?php echo esc_html(number_format_i18n($quote->getDiscountValue(), 2)); ?></div>
873 - <?php endif; ?>
874 - <?php if ($quote->getTaxRate() > 0): ?>
875 - <div><strong><?php _e('Tax', 'easy-invoice'); ?> (<?php echo esc_html($quote->getTaxRate()); ?>%):</strong> <?php echo esc_html(number_format_i18n($quote->getTaxAmount(), 2)); ?></div>
876 - <?php endif; ?>
877 - <?php do_action('easy_invoice_quote_totals_after_tax', $quote); ?>
878 - <div style="font-size: 1.2em; margin-top: 8px;"><strong><?php _e('Total', 'easy-invoice'); ?>:</strong> <?php echo esc_html(number_format_i18n($quote->getTotal(), 2)); ?></div>
879 - </div>
880 - <?php endif; ?>
881 -
882 - <?php do_action('easy_invoice_quote_content_after', $quote); ?>
883 - </div>
884 - </div>
885 -
886 - <script type="text/javascript">
887 - // Print Quote Content - Global function
888 - function printQuoteContent() {
889 - // Get the quote content
890 - const quoteContent = document.querySelector('.quote-content');
891 -
892 - if (!quoteContent) {
893 - alert('<?php _e('Quote content not found', 'easy-invoice'); ?>');
894 - return;
895 - }
896 -
897 - // Create a new window for printing
898 - const printWindow = window.open('', '_blank', 'width=800,height=600');
899 -
900 - // Create the print HTML with comprehensive styles
901 - const printHTML = `
902 - <!DOCTYPE html>
903 - <html>
904 - <head>
905 - <title><?php echo esc_js($quote->getNumber() ?: __('Quote', 'easy-invoice')); ?></title>
906 - <meta charset="utf-8">
907 - <style>
908 - /* Reset and base styles */
909 - * {
910 - margin: 0;
911 - padding: 0;
912 - box-sizing: border-box;
913 - }
914 -
915 - body {
916 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
917 - line-height: 1.6;
918 - color: #333;
919 - background: white;
920 - padding: 20px;
921 - }
922 -
923 - /* Quote container styles */
924 - .quote-container {
925 - max-width: 800px;
926 - margin: 0 auto;
927 - background: white;
928 - padding: 30px;
929 - border: 1px solid #e5e7eb;
930 - border-radius: 8px;
931 - }
932 -
933 - /* Header styles */
934 - .quote-header {
935 - margin-bottom: 30px;
936 - padding-bottom: 20px;
937 - border-bottom: 2px solid #e5e7eb;
938 - }
939 -
940 - .quote-title {
941 - font-size: 2em;
942 - font-weight: bold;
943 - color: #1f2937;
944 - margin-bottom: 10px;
945 - }
946 -
947 - .quote-number {
948 - font-size: 1.1em;
949 - color: #6b7280;
950 - margin-bottom: 15px;
951 - }
952 -
953 - .quote-date {
954 - color: #6b7280;
955 - font-size: 0.9em;
956 - }
957 -
958 - /* Customer info styles */
959 - .customer-info {
960 - margin-bottom: 30px;
961 - padding: 15px;
962 - background: #f9fafb;
963 - border-radius: 6px;
964 - }
965 -
966 - .customer-name {
967 - font-weight: bold;
968 - font-size: 1.1em;
969 - margin-bottom: 5px;
970 - }
971 -
972 - .customer-email, .customer-address {
973 - color: #6b7280;
974 - margin-bottom: 3px;
975 - }
976 -
977 - /* Items table styles */
978 - .quote-items {
979 - width: 100%;
980 - border-collapse: collapse;
981 - margin-bottom: 30px;
982 - }
983 -
984 - .quote-items th {
985 - background: #f9fafb;
986 - padding: 12px;
987 - text-align: left;
988 - font-weight: 600;
989 - border-bottom: 2px solid #e5e7eb;
990 - color: #374151;
991 - }
992 -
993 - .quote-items td {
994 - padding: 12px;
995 - border-bottom: 1px solid #e5e7eb;
996 - vertical-align: top;
997 - }
998 -
999 - .quote-items tr:nth-child(even) {
1000 - background: #f9fafb;
1001 - }
1002 -
1003 - /* Summary styles */
1004 - .quote-summary {
1005 - margin-top: 30px;
1006 - padding: 20px;
1007 - background: #f9fafb;
1008 - border-radius: 6px;
1009 - text-align: right;
1010 - }
1011 -
1012 - .quote-summary div {
1013 - margin-bottom: 8px;
1014 - font-size: 1em;
1015 - }
1016 -
1017 - .quote-summary .total {
1018 - font-size: 1.2em;
1019 - font-weight: bold;
1020 - color: #1f2937;
1021 - border-top: 2px solid #e5e7eb;
1022 - padding-top: 10px;
1023 - margin-top: 10px;
1024 - }
1025 -
1026 - /* Notes styles */
1027 - .quote-notes {
1028 - margin-top: 30px;
1029 - padding: 15px;
1030 - background: #f9fafb;
1031 - border-left: 4px solid #3b82f6;
1032 - border-radius: 4px;
1033 - }
1034 -
1035 - .quote-notes h4 {
1036 - margin-bottom: 10px;
1037 - color: #1f2937;
1038 - }
1039 -
1040 - /* Status styles */
1041 - .quote-status {
1042 - display: inline-block;
1043 - padding: 4px 12px;
1044 - border-radius: 20px;
1045 - font-size: 0.8em;
1046 - font-weight: 600;
1047 - text-transform: uppercase;
1048 - margin-left: 10px;
1049 - }
1050 -
1051 - .status-draft { background: #e5e7eb; color: #374151; }
1052 - .status-sent { background: #dbeafe; color: #1e40af; }
1053 - .status-accepted { background: #d1fae5; color: #065f46; }
1054 - .status-declined { background: #fee2e2; color: #991b1b; }
1055 - .status-available { background: #eff6ff; color: #1e40af; }
1056 -
1057 - /* Print-specific styles */
1058 - @media print {
1059 - body {
1060 - margin: 0;
1061 - padding: 0;
1062 - }
1063 -
1064 - .quote-container {
1065 - border: none;
1066 - padding: 0;
1067 - max-width: none;
1068 - }
1069 -
1070 - .quote-items th {
1071 - background: #f9fafb !important;
1072 - -webkit-print-color-adjust: exact;
1073 - color-adjust: exact;
1074 - }
1075 -
1076 - .quote-notes {
1077 - background: #f9fafb !important;
1078 - -webkit-print-color-adjust: exact;
1079 - color-adjust: exact;
1080 - }
1081 -
1082 - .status-draft { background: #e5e7eb !important; }
1083 - .status-sent { background: #dbeafe !important; }
1084 - .status-accepted { background: #d1fae5 !important; }
1085 - .status-declined { background: #fee2e2 !important; }
1086 - .status-available { background: #eff6ff !important; }
1087 -
1088 - /* Ensure proper page breaks */
1089 - .quote-content {
1090 - page-break-inside: avoid;
1091 - }
1092 -
1093 - table {
1094 - page-break-inside: avoid;
1095 - }
1096 -
1097 - tr {
1098 - page-break-inside: avoid;
1099 - }
1100 - }
1101 - </style>
1102 - </head>
1103 - <body>
1104 - ${quoteContent.outerHTML}
1105 - </body>
1106 - </html>
1107 - `;
1108 -
1109 - // Write the content to the new window
1110 - printWindow.document.write(printHTML);
1111 - printWindow.document.close();
1112 -
1113 - // Wait for content to load, then print
1114 - printWindow.onload = function() {
1115 - setTimeout(function() {
1116 - printWindow.print();
1117 - printWindow.close();
1118 - }, 500);
1119 - };
1120 - }
1121 -
1122 - document.addEventListener('DOMContentLoaded', function() {
1123 - const quoteId = '<?php echo esc_js($quote->getId()); ?>';
1124 - const acceptActionDescription = '<?php echo esc_js($accept_action_description); ?>';
1125 - const declinedMessage = '<?php echo esc_js($declined_message); ?>';
1126 - const isDeclineReasonRequired = <?php echo json_encode($settings_controller::isDeclineReasonRequired()); ?>;
1127 -
1128 - // Utility: Show loading spinner in a button
1129 - function setButtonLoading(btn, loadingText) {
1130 - btn.disabled = true;
1131 - btn.innerHTML = `<span class='ei-btn-spinner' style='display:inline-block;vertical-align:middle;width:18px;height:18px;margin-right:8px;'>
1132 - <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>
1133 - </span>${loadingText}`;
1134 - }
1135 - // resetButtonLoading function is now defined globally above
1136 -
1137 - // Accept Quote Button
1138 - const acceptButton = document.querySelector('.quote-actions .accept');
1139 - if (acceptButton) {
1140 - acceptButton.addEventListener('click', function() {
1141 - let message = '<?php echo esc_js(__('Accept this quote?', 'easy-invoice')); ?>';
1142 - if (acceptActionDescription) {
1143 - message += '\n\n' + acceptActionDescription;
1144 - }
1145 - showConfirmationModal(
1146 - '<?php echo esc_js($text_settings['accept_quote']); ?>',
1147 - message,
1148 - '<?php echo esc_js($text_settings['accept_quote']); ?>',
1149 - '<?php echo esc_js(__('Cancel', 'easy-invoice')); ?>',
1150 - 'primary',
1151 - function(modal, confirmBtn, originalText) {
1152 - handleAcceptQuote(quoteId, confirmBtn, originalText);
1153 - }
1154 - );
1155 - });
1156 - }
1157 -
1158 - // Decline Quote Button
1159 - const declineButton = document.querySelector('.quote-actions .decline');
1160 - if (declineButton) {
1161 - declineButton.addEventListener('click', function() {
1162 - let message = '<?php echo esc_js(__('Decline this quote?', 'easy-invoice')); ?>';
1163 - if (declinedMessage) {
1164 - message += '\n\n' + declinedMessage;
1165 - }
1166 -
1167 - if (isDeclineReasonRequired) {
1168 - showDeclineModalWithReason();
1169 - } else {
1170 - showConfirmationModal(
1171 - '<?php echo esc_js($text_settings['decline_quote']); ?>',
1172 - message,
1173 - '<?php echo esc_js($text_settings['decline_quote']); ?>',
1174 - '<?php echo esc_js(__('Cancel', 'easy-invoice')); ?>',
1175 - 'danger',
1176 - function(modal, confirmBtn, originalText) {
1177 - handleDeclineQuote(quoteId, confirmBtn, originalText);
1178 - }
1179 - );
1180 - }
1181 - });
1182 - }
1183 -
1184 -
1185 -
1186 - // Note: Download and Send Email buttons are handled via inline onclick attributes to avoid duplicate bindings
1187 -
1188 - // Custom Modal System
1189 - function showConfirmationModal(title, message, confirmText, cancelText, confirmType, onConfirm) {
1190 - // Remove existing modal if any
1191 - const existingModal = document.querySelector('.ei-modal-overlay');
1192 - if (existingModal) {
1193 - existingModal.remove();
1194 - }
1195 -
1196 - // Create modal overlay
1197 - const overlay = document.createElement('div');
1198 - overlay.className = 'ei-modal-overlay';
1199 -
1200 - // Create modal content
1201 - const modal = document.createElement('div');
1202 - modal.className = 'ei-modal';
1203 -
1204 - const confirmBtnClass = confirmType === 'danger' ? 'ei-modal-btn-danger' : 'ei-modal-btn-primary';
1205 - const iconClass = confirmType === 'danger' ? 'danger' : 'info';
1206 - const icon = confirmType === 'danger' ?
1207 - '<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>' :
1208 - '<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>';
1209 -
1210 - modal.innerHTML = `
1211 - <div class="ei-modal-header">
1212 - <div class="ei-modal-icon ${iconClass}">
1213 - ${icon}
1214 - </div>
1215 - <h3 class="ei-modal-title">${title}</h3>
1216 - </div>
1217 - <div class="ei-modal-body">
1218 - <div class="ei-modal-message">${message}</div>
1219 - </div>
1220 - <div class="ei-modal-actions">
1221 - <button type="button" class="ei-modal-btn ei-modal-btn-secondary" id="modal-cancel">${cancelText}</button>
1222 - <button type="button" class="ei-modal-btn ${confirmBtnClass}" id="modal-confirm">${confirmText}</button>
1223 - </div>
1224 - `;
1225 -
1226 - overlay.appendChild(modal);
1227 - document.body.appendChild(overlay);
1228 -
1229 - // Show modal with animation
1230 - setTimeout(() => {
1231 - overlay.classList.add('show');
1232 - modal.classList.add('show');
1233 - }, 10);
1234 -
1235 - // Handle button clicks
1236 - const confirmBtn = modal.querySelector('#modal-confirm');
1237 - const cancelBtn = modal.querySelector('#modal-cancel');
1238 -
1239 - confirmBtn.addEventListener('click', function() {
1240 - const originalText = confirmBtn.innerHTML;
1241 - setButtonLoading(confirmBtn, confirmBtn.textContent.trim());
1242 - onConfirm(modal, confirmBtn, originalText);
1243 - });
1244 -
1245 - cancelBtn.addEventListener('click', function() {
1246 - hideModal(overlay);
1247 - });
1248 -
1249 - // Handle overlay click to close
1250 - overlay.addEventListener('click', function(e) {
1251 - if (e.target === overlay) {
1252 - hideModal(overlay);
1253 - }
1254 - });
1255 -
1256 - // Handle escape key
1257 - const handleEscape = function(e) {
1258 - if (e.key === 'Escape') {
1259 - hideModal(overlay);
1260 - document.removeEventListener('keydown', handleEscape);
1261 - }
1262 - };
1263 - document.addEventListener('keydown', handleEscape);
1264 -
1265 - // Focus on confirm button
1266 - setTimeout(() => {
1267 - confirmBtn.focus();
1268 - }, 100);
1269 - }
1270 -
1271 - // Decline Modal with Reason Field
1272 - function showDeclineModalWithReason() {
1273 - // Remove existing modal if any
1274 - const existingModal = document.querySelector('.ei-modal-overlay');
1275 - if (existingModal) {
1276 - existingModal.remove();
1277 - }
1278 - // Create modal overlay
1279 - const overlay = document.createElement('div');
1280 - overlay.className = 'ei-modal-overlay';
1281 - // Create modal content
1282 - const modal = document.createElement('div');
1283 - modal.className = 'ei-modal';
1284 - let message = '<?php echo esc_js(__('Decline this quote?', 'easy-invoice')); ?>';
1285 - if (declinedMessage) {
1286 - message += '\n\n' + declinedMessage;
1287 - }
1288 - modal.innerHTML = `
1289 - <div class="ei-modal-header">
1290 - <div class="ei-modal-icon danger">
1291 - <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>
1292 - </div>
1293 - <h3 class="ei-modal-title"><?php echo esc_js($text_settings['decline_quote']); ?></h3>
1294 - </div>
1295 - <div class="ei-modal-body">
1296 - <div class="ei-modal-message">${message}</div>
1297 - <div class="ei-modal-reason-field">
1298 - <label for="decline-reason" class="ei-modal-label"><?php echo esc_js($text_settings['decline_reason']); ?>:</label>
1299 - <textarea id="decline-reason" class="ei-modal-textarea" rows="4" placeholder="<?php echo esc_js(__('Please provide a reason for declining this quote...', 'easy-invoice')); ?>" required></textarea>
1300 - </div>
1301 - </div>
1302 - <div class="ei-modal-actions">
1303 - <button type="button" class="ei-modal-btn ei-modal-btn-secondary" id="modal-cancel"><?php echo esc_js(__('Cancel', 'easy-invoice')); ?></button>
1304 - <button type="button" class="ei-modal-btn ei-modal-btn-danger" id="modal-confirm"><?php echo esc_js($text_settings['decline_quote']); ?></button>
1305 - </div>
1306 - `;
1307 - overlay.appendChild(modal);
1308 - document.body.appendChild(overlay);
1309 - setTimeout(() => {
1310 - overlay.classList.add('show');
1311 - modal.classList.add('show');
1312 - }, 10);
1313 - // Handle button clicks
1314 - const confirmBtn = modal.querySelector('#modal-confirm');
1315 - const cancelBtn = modal.querySelector('#modal-cancel');
1316 - const reasonField = modal.querySelector('#decline-reason');
1317 - confirmBtn.addEventListener('click', function() {
1318 - const reason = reasonField.value.trim();
1319 - if (!reason) {
1320 - reasonField.focus();
1321 - reasonField.classList.add('error');
1322 - return;
1323 - }
1324 - const originalText = confirmBtn.innerHTML;
1325 - setButtonLoading(confirmBtn, confirmBtn.textContent.trim());
1326 - handleDeclineQuote(quoteId, confirmBtn, reason, originalText);
1327 - });
1328 - cancelBtn.addEventListener('click', function() {
1329 - hideModal(overlay);
1330 - });
1331 - overlay.addEventListener('click', function(e) {
1332 - if (e.target === overlay) {
1333 - hideModal(overlay);
1334 - }
1335 - });
1336 - const handleEscape = function(e) {
1337 - if (e.key === 'Escape') {
1338 - hideModal(overlay);
1339 - document.removeEventListener('keydown', handleEscape);
1340 - }
1341 - };
1342 - document.addEventListener('keydown', handleEscape);
1343 - setTimeout(() => {
1344 - reasonField.focus();
1345 - }, 100);
1346 - reasonField.addEventListener('input', function() {
1347 - this.classList.remove('error');
1348 - });
1349 - }
1350 -
1351 - function hideModal(overlay) {
1352 - const modal = overlay.querySelector('.ei-modal');
1353 - modal.classList.remove('show');
1354 - overlay.classList.remove('show');
1355 -
1356 - setTimeout(() => {
1357 - if (overlay.parentNode) {
1358 - overlay.remove();
1359 - }
1360 - }, 300);
1361 - }
1362 -
1363 - // Show message in modal (used for AJAX responses)
1364 - function showModalMessage(type, message, onClose) {
1365 - // Remove existing modal if any
1366 - const existingModal = document.querySelector('.ei-modal-overlay');
1367 - if (existingModal) {
1368 - existingModal.remove();
1369 - }
1370 - // Create modal overlay
1371 - const overlay = document.createElement('div');
1372 - overlay.className = 'ei-modal-overlay';
1373 - // Create modal content
1374 - const modal = document.createElement('div');
1375 - modal.className = 'ei-modal';
1376 - const iconClass = type === 'success' ? 'info' : 'danger';
1377 - const icon = type === 'success'
1378 - ? '<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>'
1379 - : '<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>';
1380 - modal.innerHTML = `
1381 - <div class="ei-modal-header">
1382 - <div class="ei-modal-icon ${iconClass}">${icon}</div>
1383 - <h3 class="ei-modal-title">${type === 'success' ? '<?php echo esc_js(__('Success', 'easy-invoice')); ?>' : '<?php echo esc_js(__('Error', 'easy-invoice')); ?>'}</h3>
1384 - </div>
1385 - <div class="ei-modal-body">
1386 - <div class="ei-modal-message">${message}</div>
1387 - </div>
1388 - <div class="ei-modal-actions">
1389 - <button type="button" class="ei-modal-btn ei-modal-btn-primary" id="modal-close"><?php echo esc_js(__('Close', 'easy-invoice')); ?></button>
1390 - </div>
1391 - `;
1392 - overlay.appendChild(modal);
1393 - document.body.appendChild(overlay);
1394 - setTimeout(() => {
1395 - overlay.classList.add('show');
1396 - modal.classList.add('show');
1397 - }, 10);
1398 - const closeBtn = modal.querySelector('#modal-close');
1399 - closeBtn.addEventListener('click', function() {
1400 - hideModal(overlay);
1401 - if (onClose) onClose();
1402 - });
1403 - overlay.addEventListener('click', function(e) {
1404 - if (e.target === overlay) {
1405 - hideModal(overlay);
1406 - if (onClose) onClose();
1407 - }
1408 - });
1409 - const handleEscape = function(e) {
1410 - if (e.key === 'Escape') {
1411 - hideModal(overlay);
1412 - document.removeEventListener('keydown', handleEscape);
1413 - if (onClose) onClose();
1414 - }
1415 - };
1416 - document.addEventListener('keydown', handleEscape);
1417 - setTimeout(() => {
1418 - closeBtn.focus();
1419 - }, 100);
1420 - }
1421 - // Show loading state in modal
1422 - function showModalLoading(message) {
1423 - // Remove existing modal if any
1424 - const existingModal = document.querySelector('.ei-modal-overlay');
1425 - if (existingModal) {
1426 - existingModal.remove();
1427 - }
1428 - // Create modal overlay
1429 - const overlay = document.createElement('div');
1430 - overlay.className = 'ei-modal-overlay';
1431 - // Create modal content
1432 - const modal = document.createElement('div');
1433 - modal.className = 'ei-modal';
1434 - modal.innerHTML = `
1435 - <div class="ei-modal-header">
1436 - <div class="ei-modal-icon info">
1437 - <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>
1438 - </div>
1439 - <h3 class="ei-modal-title"><?php echo esc_js(__('Please wait...', 'easy-invoice')); ?></h3>
1440 - </div>
1441 - <div class="ei-modal-body">
1442 - <div class="ei-modal-message">${message}</div>
1443 - </div>
1444 - `;
1445 - overlay.appendChild(modal);
1446 - document.body.appendChild(overlay);
1447 - setTimeout(() => {
1448 - overlay.classList.add('show');
1449 - modal.classList.add('show');
1450 - }, 10);
1451 - }
1452 -
1453 - // Handle Accept Quote
1454 - function handleAcceptQuote(quoteId, confirmBtn, originalText) {
1455 - showModalLoading('<?php echo esc_js(__('Accepting quote...', 'easy-invoice')); ?>');
1456 - jQuery.ajax({
1457 - url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
1458 - type: 'POST',
1459 - data: {
1460 - action: 'easy_invoice_accept_quote',
1461 - quote_id: quoteId,
1462 - nonce: '<?php echo wp_create_nonce('easy_invoice_quote_action'); ?>'
1463 - },
1464 - success: function(response) {
1465 - if (response.success) {
1466 - // Check if an invoice was created and redirect to it
1467 - if (response.data && response.data.invoice_id) {
1468 - // Use the PHP-generated URL (secure URL first, then regular URL)
1469 - let invoiceUrl = response.data.secure_url || response.data.invoice_url;
1470 -
1471 - if (invoiceUrl) {
1472 - showModalMessage('success', response.data.message || '<?php _e('Quote accepted successfully! Redirecting to invoice...', 'easy-invoice'); ?>', function() {
1473 - window.location.href = invoiceUrl;
1474 - });
1475 - } else {
1476 - // Fallback to reload if no URL available
1477 - showModalMessage('success', response.data.message || '<?php _e('Quote accepted successfully', 'easy-invoice'); ?>', function() { location.reload(); });
1478 - }
1479 - } else {
1480 - // No invoice created, just show success message
1481 - showModalMessage('success', response.data && response.data.message ? response.data.message : '<?php _e('Quote accepted successfully', 'easy-invoice'); ?>', function() { location.reload(); });
1482 - }
1483 - } else {
1484 - resetButtonLoading(confirmBtn, originalText);
1485 - showModalMessage('error', response.data || '<?php _e('Error accepting quote', 'easy-invoice'); ?>');
1486 - }
1487 - },
1488 - error: function() {
1489 - resetButtonLoading(confirmBtn, originalText);
1490 - showModalMessage('error', '<?php _e('Error connecting to server', 'easy-invoice'); ?>');
1491 - }
1492 - });
1493 - }
1494 -
1495 - // Handle Decline Quote
1496 - function handleDeclineQuote(quoteId, confirmBtn, reason = '', originalText) {
1497 - showModalLoading('<?php echo esc_js(__('Declining quote...', 'easy-invoice')); ?>');
1498 - const ajaxData = {
1499 - action: 'easy_invoice_decline_quote',
1500 - quote_id: quoteId,
1501 - nonce: '<?php echo wp_create_nonce('easy_invoice_quote_action'); ?>'
1502 - };
1503 - if (reason) {
1504 - ajaxData.decline_reason = reason;
1505 - }
1506 - jQuery.ajax({
1507 - url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
1508 - type: 'POST',
1509 - data: ajaxData,
1510 - success: function(response) {
1511 - if (response.success) {
1512 - showModalMessage('success', response.data && response.data.message ? response.data.message : '<?php _e('Quote declined successfully', 'easy-invoice'); ?>', function() { location.reload(); });
1513 - } else {
1514 - resetButtonLoading(confirmBtn, originalText);
1515 - showModalMessage('error', response.data || '<?php _e('Error declining quote', 'easy-invoice'); ?>');
1516 - }
1517 - },
1518 - error: function() {
1519 - resetButtonLoading(confirmBtn, originalText);
1520 - showModalMessage('error', '<?php _e('Error connecting to server', 'easy-invoice'); ?>');
1521 - }
1522 - });
1523 - }
1524 -
1525 - // Functions moved to global scope above
1526 - });
1527 - </script>
1528 -
1529 - <script>
1530 - document.addEventListener('DOMContentLoaded', function() {
1531 - // Auto-download PDF if ?auto_download_pdf=1 is present
1532 - if (window.location.search.indexOf('auto_download_pdf=1') !== -1) {
1533 - setTimeout(function() {
1534 - if (typeof DocumentPdfGenerator !== 'undefined') {
1535 - const pdfGenerator = new DocumentPdfGenerator('quote');
1536 - pdfGenerator.generatePDF();
1537 - }
1538 - }, 800);
1539 - }
1540 - });
1541 - </script>
1542 -
1543 - <?php
1544 - // Load additional CSS for all users (not just admin)
1545 - $additional_css = get_post_meta($quote->getId(), '_easy_invoice_additional_css', true) ?: '';
1546 - if (!empty($additional_css)):
1547 - ?>
1548 - <style id="custom-quote-css">
1549 - <?php echo esc_html($additional_css); ?>
1550 - </style>
1551 - <?php endif; ?>
1552 -
1553 - <?php
1554 - // Add Additional CSS feature for admin users
1555 - if (is_user_logged_in() && current_user_can('manage_options')):
1556 - ?>
1557 - <!-- CSS Panel -->
1558 - <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;">
1559 - <div style="padding: 20px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center;">
1560 - <h3 style="margin: 0; font-size: 18px; font-weight: 600;">Additional CSS</h3>
1561 - <button type="button" id="close-css-panel" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #6b7280;">&times;</button>
1562 - </div>
1563 - <div style="padding: 20px;">
1564 - <div style="margin-bottom: 15px;">
1565 - <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #374151;">Custom CSS for this quote:</label>
1566 - <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>
1567 - </div>
1568 - <div style="display: flex; gap: 10px;">
1569 - <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>
1570 - <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>
1571 - <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>
1572 - </div>
1573 - <div id="css-status" style="margin-top: 15px; padding: 10px; border-radius: 4px; display: none;"></div>
1574 - </div>
1575 - </div>
1576 -
1577 - <!-- Overlay -->
1578 - <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>
1579 -
1580 - <script>
1581 - // Additional CSS functionality
1582 - document.addEventListener('DOMContentLoaded', function() {
1583 - const cssBtn = document.getElementById('additional-css-btn');
1584 - const cssPanel = document.getElementById('css-panel');
1585 - const closeBtn = document.getElementById('close-css-panel');
1586 - const overlay = document.getElementById('css-overlay');
1587 - const saveBtn = document.getElementById('save-css-btn');
1588 - const previewBtn = document.getElementById('preview-css-btn');
1589 - const clearBtn = document.getElementById('clear-css-btn');
1590 - const textarea = document.getElementById('additional-css-textarea');
1591 - const status = document.getElementById('css-status');
1592 -
1593 - if (!cssBtn) return; // Exit if CSS button doesn't exist
1594 -
1595 - let originalCSS = textarea.value;
1596 -
1597 - // Toggle panel
1598 - cssBtn.addEventListener('click', function() {
1599 - cssPanel.style.display = 'block';
1600 - overlay.style.display = 'block';
1601 - });
1602 -
1603 - // Close panel
1604 - function closePanel() {
1605 - cssPanel.style.display = 'none';
1606 - overlay.style.display = 'none';
1607 - }
1608 -
1609 - closeBtn.addEventListener('click', closePanel);
1610 - overlay.addEventListener('click', closePanel);
1611 -
1612 - // Save CSS
1613 - saveBtn.addEventListener('click', function() {
1614 - const css = textarea.value;
1615 -
1616 - // AJAX request to save CSS
1617 - fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
1618 - method: 'POST',
1619 - headers: {
1620 - 'Content-Type': 'application/x-www-form-urlencoded',
1621 - },
1622 - body: new URLSearchParams({
1623 - 'action': 'save_additional_css',
1624 - 'post_id': <?php echo $quote->getId(); ?>,
1625 - 'css': css,
1626 - 'nonce': '<?php echo wp_create_nonce('save_additional_css_nonce'); ?>'
1627 - })
1628 - })
1629 - .then(response => response.json())
1630 - .then(data => {
1631 - if (data.success) {
1632 - originalCSS = css;
1633 - status.textContent = 'CSS saved successfully!';
1634 - status.style.background = '#d1fae5';
1635 - status.style.color = '#065f46';
1636 - status.style.display = 'block';
1637 -
1638 - // Apply the saved CSS
1639 - applyCustomCSS(css);
1640 -
1641 - setTimeout(() => {
1642 - status.style.display = 'none';
1643 - }, 3000);
1644 - } else {
1645 - status.textContent = 'Error saving CSS: ' + data.message;
1646 - status.style.background = '#fee2e2';
1647 - status.style.color = '#991b1b';
1648 - status.style.display = 'block';
1649 - }
1650 - })
1651 - .catch(error => {
1652 - status.textContent = 'Error saving CSS: ' + error.message;
1653 - status.style.background = '#fee2e2';
1654 - status.style.color = '#991b1b';
1655 - status.style.display = 'block';
1656 - });
1657 - });
1658 -
1659 - // Preview CSS
1660 - previewBtn.addEventListener('click', function() {
1661 - const css = textarea.value;
1662 - applyCustomCSS(css);
1663 -
1664 - status.textContent = 'CSS preview applied';
1665 - status.style.background = '#dbeafe';
1666 - status.style.color = '#1e40af';
1667 - status.style.display = 'block';
1668 -
1669 - setTimeout(() => {
1670 - status.style.display = 'none';
1671 - }, 2000);
1672 - });
1673 -
1674 - // Clear CSS
1675 - clearBtn.addEventListener('click', function() {
1676 - if (confirm('Are you sure you want to clear all custom CSS?')) {
1677 - textarea.value = '';
1678 - applyCustomCSS('');
1679 -
1680 - status.textContent = 'CSS cleared';
1681 - status.style.background = '#fee2e2';
1682 - status.style.color = '#991b1b';
1683 - status.style.display = 'block';
1684 -
1685 - setTimeout(() => {
1686 - status.style.display = 'none';
1687 - }, 2000);
1688 - }
1689 - });
1690 -
1691 - // Apply custom CSS function
1692 - function applyCustomCSS(css) {
1693 - // Remove existing custom CSS
1694 - const existingStyle = document.getElementById('custom-quote-css');
1695 - if (existingStyle) {
1696 - existingStyle.remove();
1697 - }
1698 -
1699 - // Add new custom CSS if not empty
1700 - if (css.trim()) {
1701 - const style = document.createElement('style');
1702 - style.id = 'custom-quote-css';
1703 - style.textContent = css;
1704 - document.head.appendChild(style);
1705 - }
1706 - }
1707 -
1708 - // Apply saved CSS on page load
1709 - applyCustomCSS(originalCSS);
1710 - });
1711 - </script>
1712 - <?php endif; ?>
1713 -</body>
1714 -</html>