PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.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 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / templates / quotes / form.php

form.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.2, at templates/quotes/form.php

1,739 lines 88.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Quote Form Template
4 *
5 * @package EasyInvoice
6 * @since 1.0.0
7 */
8
9 // Only enqueue scripts if not loaded via AJAX
10 if (!wp_doing_ajax()) {
11 // Enqueue CSS and JS files
12 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), '1.0.0');
13 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), '1.0.0', true);
14 wp_enqueue_script('easy-invoice-builder', plugin_dir_url(__FILE__) . '../../assets/js/invoice-builder.js', array('jquery', 'easy-invoice-form'), '1.0.0', true);
15 wp_enqueue_script('easy-invoice-payment', plugin_dir_url(__FILE__) . '../../assets/js/payment-manager.js', array('jquery'), '1.0.0', true);
16 wp_enqueue_script('easy-quote-save', plugin_dir_url(__FILE__) . '../../assets/js/quote-save.js', array('jquery', 'easy-invoice-form', 'easy-invoice-payment'), '1.0.0', true);
17 }
18
19 // Trigger form initialization to allow pro version to register new elements
20 do_action('easy_invoice_form_init');
21
22 // Initialize payment manager with quote settings
23 wp_localize_script('easy-invoice-payment', 'easyInvoicePayment', array(
24 'discountType' => $quote ? $quote->getDiscountType() : 'percentage',
25 'discountValue' => $quote ? $quote->getDiscountValue() : 0,
26 'taxRate' => $quote ? $quote->getTaxRate() : easy_invoice_get_tax_rate(),
27 'calculationMethod' => $quote ? $quote->getCalculationMethod() : 'before_tax',
28 'currency' => $quote ? $quote->getCurrencyCode() : 'USD',
29 'currencySymbol' => $quote ? $quote->getCurrencySymbol() : '$'
30 ));
31
32 // Get tabs from form manager and override labels for quotes
33 $tabs = $quote_form_manager->getTabs();
34
35 // Deduplicate tabs by ID to prevent duplicate tabs in UI
36 $unique_tabs = [];
37 foreach ($tabs as $tab) {
38 $unique_tabs[$tab['id']] = $tab;
39 }
40 $tabs = array_values($unique_tabs);
41
42 // Override tab labels for quotes
43 foreach ($tabs as &$tab) {
44 switch ($tab['id']) {
45 case 'quote-tab':
46 $tab['label'] = __('Quote Details', 'easy-invoice');
47 break;
48 case 'items-tab':
49 $tab['label'] = __('Items', 'easy-invoice');
50 break;
51 case 'client-tab':
52 $tab['label'] = __('Client', 'easy-invoice');
53 break;
54 case 'discounts-taxes-tab':
55 $tab['label'] = __('Discounts & Taxes', 'easy-invoice');
56 break;
57 case 'payments-tab':
58 $tab['label'] = __('Payments', 'easy-invoice');
59 break;
60 }
61 }
62 unset($tab);
63
64 $templates = $quote_form_manager->getTemplates();
65
66 // Use the actual templates from the quote form manager
67 $all_templates = $templates;
68
69
70
71 // Load client data directly in PHP if quote has a client
72 $client_data = null;
73 if ($quote && $quote->getClientId()) {
74 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
75 if ($client) {
76 $client_data = array(
77 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
78 'email' => $client->getEmail() ?: '',
79 'phone' => $client->getExtraInfo() ?: '',
80 'company' => $client->getBusinessClientName() ?: '',
81 'address' => $client->getAddress() ?: '',
82 'website' => $client->getWebsite() ?: '',
83 );
84 }
85 }
86 ?>
87
88 <style>
89 /* Quote Item Collapse/Expand Styling - Exact copy of invoice styling */
90 .quote-item.collapsed-item {
91 padding: 8px !important;
92 margin-bottom: 8px !important;
93 border: 1px solid #e5e7eb !important;
94 border-radius: 0.375rem;
95 background-color: #f9fafb;
96 transition: all 0.2s ease;
97 }
98
99 .quote-item.collapsed-item .flex.items-center.justify-between {
100 margin-bottom: 0 !important;
101 padding-bottom: 0 !important;
102 border-bottom: none !important;
103 }
104
105 .quote-item.collapsed-item .item-collapsed-summary {
106 display: inline-flex !important;
107 align-items: center;
108 font-size: 0.875rem;
109 color: #6b7280;
110 }
111
112 .quote-item .item-content {
113 transition: all 0.3s ease-in-out;
114 }
115
116 .quote-item.collapsed-item .item-content {
117 display: none;
118 }
119
120 /* Additional styling for quote items to match invoice behavior */
121 .quote-item .item-collapsed-summary {
122 display: none;
123 background-color: #f8fafc;
124 border: 1px solid #e2e8f0;
125 border-radius: 9999px;
126 padding: 0.25rem 0.75rem;
127 font-size: 0.875rem;
128 color: #64748b;
129 font-weight: 500;
130 }
131
132 .quote-item.collapsed-item .item-collapsed-summary {
133 display: inline-flex !important;
134 }
135
136 .quote-item .fill-sample-data-btn {
137 transition: opacity 0.2s ease-in-out;
138 }
139
140 .quote-item.collapsed-item .fill-sample-data-btn {
141 opacity: 0;
142 pointer-events: none;
143 }
144
145 /* Smooth transitions for collapse/expand */
146 .quote-item .item-collapse-toggle {
147 transition: all 0.2s ease-in-out;
148 }
149
150 .quote-item .item-collapse-toggle:hover {
151 color: #4f46e5 !important;
152 transform: scale(1.1);
153 }
154
155 /* Premium Template Styling */
156 .premium-template-blurred {
157 position: relative;
158 opacity: 0.8;
159 transition: all 0.4s ease;
160 cursor: pointer;
161 border: 2px solid #e5e7eb;
162 background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
163 transform: scale(0.98);
164 }
165
166 .premium-template-blurred::before {
167 content: '';
168 position: absolute;
169 top: 0;
170 left: 0;
171 right: 0;
172 bottom: 0;
173 background: linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%);
174 border-radius: 0.5rem;
175 z-index: 1;
176 pointer-events: none;
177 }
178
179 .premium-template-blurred::after {
180 content: 'PRO';
181 position: absolute;
182 top: 8px;
183 right: 8px;
184 background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
185 color: white;
186 font-size: 10px;
187 font-weight: 700;
188 padding: 2px 6px;
189 border-radius: 4px;
190 z-index: 2;
191 box-shadow: 0 2px 4px rgba(139, 92, 246, 0.3);
192 letter-spacing: 0.5px;
193 }
194
195 .premium-template-blurred:hover {
196 opacity: 0.95;
197 transform: scale(1.02);
198 border-color: #8b5cf6;
199 box-shadow: 0 8px 25px rgba(139, 92, 246, 0.15);
200 }
201
202 .premium-template-blurred:hover::after {
203 background: linear-gradient(135deg, #7c3aed 0%, #9333ea 100%);
204 box-shadow: 0 4px 8px rgba(139, 92, 246, 0.4);
205 }
206
207 .premium-template-blurred .template-radio {
208 pointer-events: none;
209 }
210
211 .premium-template-blurred > div {
212 position: relative;
213 z-index: 3;
214 }
215 </style>
216
217 <?php if (wp_doing_ajax()): ?>
218 <form id="quote-form" method="post">
219 <input type="hidden" id="quote-id" name="quote_id" value="<?php echo $quote_id ? esc_attr($quote_id) : ''; ?>">
220 <?php endif; ?>
221
222 <div class="bg-white shadow rounded-lg">
223 <!-- Tab Navigation -->
224 <div class="border-b border-gray-200">
225 <nav class="flex -mb-px">
226 <?php foreach ($tabs as $tab): ?>
227 <button type="button"
228 class="tab-button whitespace-nowrap py-3 px-6 border-b-2 <?php echo $tab['active'] ? 'border-indigo-500 font-medium text-sm text-indigo-600' : 'border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300'; ?> flex flex-col items-center"
229 data-tab="<?php echo esc_attr($tab['id']); ?>">
230 <i class="<?php echo esc_attr($tab['icon']); ?> mb-1 text-lg"></i>
231 <span><?php echo esc_html($tab['label']); ?></span>
232 </button>
233 <?php endforeach; ?>
234 </nav>
235 </div>
236
237 <!-- Form content starts here (form tag removed to avoid nesting) -->
238
239 <?php foreach ($tabs as $tab): ?>
240 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
241 <?php if ($tab['id'] === 'quote-tab'): ?>
242 <!-- Template Selection Section -->
243 <div class="mb-8 pb-6 border-b border-gray-200">
244 <div class="flex justify-between items-center mb-4">
245 <h3 class="text-lg font-medium text-gray-900"><?php _e('Quote Template', 'easy-invoice'); ?></h3>
246 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
247 <span class="mr-1"><?php _e('Browse Quote Templates', 'easy-invoice'); ?></span>
248 <i class="fas fa-chevron-right"></i>
249 </button>
250 </div>
251
252 <?php
253 // Get the current template (default to 'standard')
254 $current_template = $quote ? $quote->getTemplate() : 'standard';
255
256 if (empty($current_template)) {
257 $current_template = 'standard';
258 }
259
260 // Always include the hidden input for the template field
261 ?>
262 <input type="hidden" name="quote_template" value="<?php echo esc_attr($current_template); ?>">
263 <?php
264
265 // Check if this is the free version
266 $is_free_version = !easy_invoice_has_pro();
267
268
269
270 // Show all templates grid
271 ?>
272 <div class="grid grid-cols-2 md:grid-cols-3 gap-4" id="main-templates-grid">
273 <?php
274 // Build the main grid templates array - always include current template plus 2 others
275 $main_grid_templates = [];
276
277 // First, add the current template if it exists in all_templates
278 if (isset($all_templates[$current_template])) {
279 // For free users, if current template is premium, default to standard
280 if ($is_free_version && isset($all_templates[$current_template]['is_free']) && !$all_templates[$current_template]['is_free']) {
281 $current_template = 'standard';
282 $main_grid_templates['standard'] = $all_templates['standard'];
283 } else {
284 $main_grid_templates[$current_template] = $all_templates[$current_template];
285 }
286 }
287
288 // Add other templates to fill up to 3, prioritizing standard templates
289 $default_templates = ['standard', 'legacy', 'modern'];
290 foreach ($default_templates as $template_id) {
291 if (count($main_grid_templates) >= 3) break;
292 if ($template_id !== $current_template && isset($all_templates[$template_id])) {
293 $main_grid_templates[$template_id] = $all_templates[$template_id];
294 }
295 }
296
297 // If we still don't have 3 templates, add any remaining templates
298 foreach ($all_templates as $template_id => $template) {
299 if (count($main_grid_templates) >= 3) break;
300 if (!isset($main_grid_templates[$template_id])) {
301 $main_grid_templates[$template_id] = $template;
302 }
303 }
304
305 // Display the main grid templates
306 foreach ($main_grid_templates as $template_id => $template) :
307 $checked = ($current_template === $template_id) ? 'checked' : '';
308 $selected_class = ($current_template === $template_id) ? 'selected' : '';
309
310 // Check if this template is premium (not free)
311 $is_premium = isset($template['is_free']) && !$template['is_free'];
312
313 // Add premium overlay classes
314 $premium_classes = '';
315 if ($is_free_version && $is_premium) {
316 $premium_classes = 'premium-template-blurred';
317 }
318 ?>
319 <div class="template-card <?php echo $selected_class . ' ' . $premium_classes; ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo $is_premium ? 'true' : 'false'; ?>">
320 <input type="radio" id="template-<?php echo esc_attr($template_id); ?>" name="quote_template" value="<?php echo esc_attr($template_id); ?>" class="template-radio" <?php echo $checked; ?> <?php echo ($is_free_version && $is_premium) ? 'disabled' : ''; ?>>
321 <div class="p-4 border rounded-lg">
322 <div class="flex items-center mb-2">
323 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
324 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
325 </div>
326 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
327 <div class="mt-2 flex-grow">
328 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
329 </div>
330 </div>
331 </div>
332 </div>
333 <?php endforeach; ?>
334 </div>
335 </div>
336
337 <!-- Dynamic Fields Section -->
338 <div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
339 <?php
340 $main_fields = $quote_form_manager->getFields('quote-tab');
341
342
343
344 foreach ($main_fields as $field):
345 // Skip fields that have a section (like notes) - they're rendered separately
346 // Also skip the template field as it's rendered manually in the template selection section
347 if (!isset($field['section']) && $field['name'] !== 'quote_template') {
348
349 echo $quote_form_manager->renderField($field, $quote);
350 }
351 endforeach;
352 ?>
353 </div>
354
355 <!-- Auto-generation notice for new quotes -->
356 <?php if (!$quote): ?>
357 <div class="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
358 <div class="flex">
359 <div class="flex-shrink-0">
360 <i class="fas fa-info-circle text-blue-400"></i>
361 </div>
362 <div class="ml-3">
363 <p class="text-sm text-blue-700">
364 <?php _e('Quote number will be automatically generated when you save the quote.', 'easy-invoice'); ?>
365 </p>
366 </div>
367 </div>
368 </div>
369 <?php endif; ?>
370
371 <!-- Notes & Terms Section -->
372 <div class="mt-8 border-t border-gray-200 pt-6">
373 <h3 class="text-lg font-medium text-gray-900"><?php _e('Notes & Terms', 'easy-invoice'); ?></h3>
374 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
375 <?php
376 // Only render notes and terms fields, not all quote tab fields
377 $notes_fields = $quote_form_manager->getFieldsBySection('quote-tab', 'notes');
378 foreach ($notes_fields as $field):
379 echo $quote_form_manager->renderField($field, $quote);
380 endforeach;
381 ?>
382 </div>
383 </div>
384
385 <?php elseif ($tab['id'] === 'items-tab'): ?>
386 <!-- Items Tab Content -->
387 <div class="flex justify-between items-center mb-6">
388 <h2 class="text-lg font-medium text-gray-900"><?php _e('Quote Items', 'easy-invoice'); ?></h2>
389 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
390 <i class="fas fa-chevron-down mr-1"></i>
391 <span><?php _e('Collapse All', 'easy-invoice'); ?></span>
392 </button>
393 </div>
394
395 <div class="space-y-4 quote-items-container">
396 <?php
397 // If editing an existing quote, directly output quote items
398 if ($quote && $quote->getItems()) :
399 $index = 0;
400 foreach ($quote->getItems() as $item) :
401 // Generate a unique ID for the item
402 $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999);
403
404 // Get display title for header using dynamic field access
405 $title = $item->getName();
406
407
408 if (empty($title) && method_exists($item, 'getData')) {
409 $item_data = $item->getData();
410 if (is_array($item_data) && isset($item_data['title'])) {
411 $title = $item_data['title'];
412 }
413 }
414
415 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
416 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
417 ?>
418 <div class="quote-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;" id="<?php echo $item_id; ?>">
419 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
420 <div class="flex items-center">
421 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
422 <i class="fas fa-chevron-down text-sm"></i>
423 </button>
424 <h3 class="text-base font-normal text-gray-800 flex-1">
425 <?php
426 echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
427 ?>
428 </h3>
429 </div>
430 <div class="flex items-center gap-3">
431 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
432 <span class="quantity-summary"><?php echo esc_html($item->getQuantity()); ?></span> × $<span class="price-summary"><?php echo esc_html(number_format(floatval($item->getPrice()), 2)); ?></span>
433 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
434 </div>
435 <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Fill this item with sample data', 'easy-invoice'); ?>">
436 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
437 <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/>
438 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
439 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
440 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
441 </svg>
442 </button>
443 <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium">
444 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
445 </button>
446 </div>
447 </div>
448 <div class="item-content">
449 <?php echo $quote_form_manager->generateExistingItemHtml($item, $index); ?>
450 </div>
451 </div>
452 <?php
453 $index++;
454 endforeach;
455 else:
456 // For new quotes, render one empty item by default
457 $index = 0;
458 // Get the item fields config
459 $item_fields = $quote_form_manager->getItemFields();
460 $title_placeholder = '';
461 foreach ($item_fields as $field) {
462 if ($field['name'] === 'title') {
463 $title_placeholder = $field['placeholder'] ?? __('Item', 'easy-invoice');
464 break;
465 }
466 }
467 // Try to get the value from POST (if form was submitted and errored)
468 $item_title = isset($_POST['items'][0]['title']) ? trim($_POST['items'][0]['title']) : '';
469 ?>
470 <div class="quote-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;">
471 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
472 <div class="flex items-center">
473 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
474 <i class="fas fa-chevron-down text-sm"></i>
475 </button>
476 <h3 class="text-base font-normal text-gray-800 flex-1">
477 <?php
478 echo !empty($item_title) ? esc_html($item_title) : esc_html($title_placeholder);
479 ?>
480 </h3>
481 </div>
482 <div class="flex items-center gap-3">
483 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
484 <span class="quantity-summary">1</span> × $<span class="price-summary">0.00</span>
485 = $<span class="total-summary">0.00</span>
486 </div>
487 <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Fill this item with sample data', 'easy-invoice'); ?>">
488 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
489 <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/>
490 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
491 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
492 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
493 </svg>
494 </button>
495 <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium">
496 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
497 </button>
498 </div>
499 </div>
500 <div class="item-content">
501 <?php echo $quote_form_manager->generateItemTemplateHtml(); ?>
502 </div>
503 </div>
504 <?php endif; ?>
505 </div>
506
507 <div class="mt-6">
508 <button type="button" class="add-item-button w-full flex justify-center items-center px-6 py-4 bg-white border border-blue-100 rounded-xl text-indigo-700 hover:bg-blue-50 hover:border-blue-200 hover:shadow-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 group">
509 <div class="flex items-center">
510 <div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center mr-3 group-hover:bg-indigo-200 transition-colors">
511 <i class="fas fa-plus text-indigo-600 text-lg"></i>
512 </div>
513 <div class="text-left">
514 <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span>
515 <p class="text-sm text-indigo-600 mt-0.5"><?php _e('Click to add a new line item', 'easy-invoice'); ?></p>
516 </div>
517 </div>
518 </button>
519 </div>
520
521 <?php elseif ($tab['id'] === 'client-tab'): ?>
522 <!-- Client Tab Content -->
523 <div class="mb-6">
524 <label class="block text-sm font-medium text-gray-700 mb-3"><?php _e('Select Existing Client', 'easy-invoice'); ?></label>
525
526 <!-- Client Search and Selection -->
527 <div class="relative">
528 <div class="flex items-center space-x-3">
529 <div class="relative flex-grow">
530 <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
531 <i class="fas fa-search text-gray-400"></i>
532 </div>
533 <input
534 type="text"
535 id="client-search-input"
536 class="block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm py-2 px-3 pl-10"
537 placeholder="<?php _e('Search clients by name, email, or company...', 'easy-invoice'); ?>"
538 autocomplete="off"
539 >
540 </div>
541 <button type="button" id="add-new-client-btn" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
542 <i class="fas fa-plus -ml-1 mr-2"></i>
543 <?php _e('New Client', 'easy-invoice'); ?>
544 </button>
545 </div>
546
547 <!-- Search Results Dropdown -->
548 <div id="client-search-results" class="hidden absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto">
549 <div id="client-search-loading" class="hidden p-6 text-center text-gray-500">
550 <i class="fas fa-spinner fa-spin mr-2"></i>
551 <?php _e('Searching...', 'easy-invoice'); ?>
552 </div>
553 <div id="client-search-empty" class="hidden p-6 text-center text-gray-500">
554 <i class="fas fa-search mr-2"></i>
555 <?php _e('No clients found', 'easy-invoice'); ?>
556 </div>
557 <div id="client-search-list" class="py-2">
558 <!-- Client options will be populated here -->
559 </div>
560 </div>
561
562 <!-- Selected Client Display -->
563 <div id="selected-client-display" class="mt-4 p-4 bg-gradient-to-r from-indigo-50 to-blue-50 border border-indigo-200 rounded-lg shadow-sm <?php echo ($quote && $quote->getClientId()) ? '' : 'hidden'; ?>">
564 <div class="flex items-center justify-between">
565 <div class="flex items-center">
566 <div class="flex-shrink-0">
567 <div class="w-10 h-10 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-full flex items-center justify-center shadow-sm">
568 <i class="fas fa-user text-white text-sm"></i>
569 </div>
570 </div>
571 <div class="ml-4">
572 <p id="selected-client-name" class="text-sm font-semibold text-gray-900">
573 <?php
574 if ($quote && $quote->getClientId()) {
575 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
576 if ($client) {
577 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
578 } else {
579 echo '-';
580 }
581 } else {
582 echo '-';
583 }
584 ?>
585 </p>
586 <p id="selected-client-email" class="text-sm text-gray-600">
587 <?php
588 if ($quote && $quote->getClientId()) {
589 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
590 if ($client) {
591 echo esc_html($client->getEmail() ?: '-');
592 } else {
593 echo '-';
594 }
595 } else {
596 echo '-';
597 }
598 ?>
599 </p>
600 </div>
601 </div>
602 <button type="button" id="clear-client-selection" class="text-gray-400 hover:text-gray-600 transition-colors p-2 rounded-full hover:bg-white hover:shadow-sm">
603 <i class="fas fa-times"></i>
604 </button>
605 </div>
606 </div>
607 </div>
608
609 <!-- Hidden select for form submission -->
610 <select id="select-client" class="hidden">
611 <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option>
612 <?php
613 // Output client options
614 if (!empty($clients)) {
615 foreach ($clients as $client) {
616 // Get the WordPress user data directly
617 $user = get_user_by('id', $client->getId());
618 if (!$user) {
619 continue;
620 }
621
622 // Use standard WordPress user fields
623 $business_name = $client->business_client_name ?: '';
624 $first_name = $user->first_name ?: '';
625 $last_name = $user->last_name ?: '';
626 $client_email = $user->user_email ?: '';
627
628 // Create display name
629 $client_name = $business_name ?: ($first_name . ' ' . $last_name);
630 if (empty(trim($client_name))) {
631 $client_name = $user->display_name ?: 'User ' . $client->getId();
632 }
633
634 $selected = $quote && $quote->getClientId() == $client->getId() ? 'selected' : '';
635 printf(
636 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
637 esc_attr($client->getId()),
638 $selected,
639 esc_attr($client_name),
640 esc_attr($client_email),
641 esc_html($client_name),
642 esc_html($client_email)
643 );
644 }
645 }
646 ?>
647 </select>
648 </div>
649
650 <div class="border-t border-gray-200 pt-4 mt-4">
651 <input type="hidden" name="client_id" id="client-id" value="<?php echo $quote ? esc_attr($quote->getClientId()) : ''; ?>">
652
653 <!-- Client Information Display -->
654 <div id="client-info-display" class="mt-6 <?php echo $quote && $quote->getClientId() ? '' : 'hidden'; ?>">
655 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
656 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
657 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
658 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
659 <span><?php _e('Client Information', 'easy-invoice'); ?></span>
660 </h4>
661 <a href="<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($quote && $quote->getClientId() ? $quote->getClientId() : '')); ?>"
662 id="edit-selected-client"
663 class="text-sm text-indigo-600 hover:text-indigo-800 font-medium"
664 target="_blank">
665 <i class="fas fa-pen mr-1"></i>
666 <?php _e('Edit', 'easy-invoice'); ?>
667 </a>
668 </div>
669
670 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
671 <!-- Client Name -->
672 <div class="flex items-start group">
673 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-user-tie text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
674 <div class="ml-3">
675 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Name', 'easy-invoice'); ?></label>
676 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
677 <?php
678 if ($quote && $quote->getClientId()) {
679 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
680 if ($client) {
681 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
682 } else {
683 echo '-';
684 }
685 } else {
686 echo '-';
687 }
688 ?>
689 </p>
690 </div>
691 </div>
692
693 <!-- Company Name -->
694 <div class="flex items-start group">
695 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-building text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
696 <div class="ml-3">
697 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Company', 'easy-invoice'); ?></label>
698 <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1">
699 <?php
700 if ($quote && $quote->getClientId()) {
701 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
702 if ($client) {
703 echo esc_html($client->getBusinessClientName() ?: '-');
704 } else {
705 echo '-';
706 }
707 } else {
708 echo '-';
709 }
710 ?>
711 </p>
712 </div>
713 </div>
714
715 <!-- Email -->
716 <div class="flex items-start group">
717 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-envelope text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
718 <div class="ml-3">
719 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Email', 'easy-invoice'); ?></label>
720 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
721 <?php
722 if ($quote && $quote->getClientId()) {
723 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
724 if ($client) {
725 echo esc_html($client->getEmail() ?: '-');
726 } else {
727 echo '-';
728 }
729 } else {
730 echo '-';
731 }
732 ?>
733 </p>
734 </div>
735 </div>
736
737 <!-- Phone -->
738 <div class="flex items-start group">
739 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-phone-alt text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
740 <div class="ml-3">
741 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Phone', 'easy-invoice'); ?></label>
742 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
743 <?php
744 if ($quote && $quote->getClientId()) {
745 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
746 if ($client) {
747 echo esc_html($client->getExtraInfo() ?: esc_html__('', 'easy-invoice'));
748 } else {
749 echo esc_html__('', 'easy-invoice');
750 }
751 } else {
752 echo esc_html__('', 'easy-invoice');
753 }
754 ?>
755 </p>
756 </div>
757 </div>
758
759 <!-- Website -->
760 <div class="flex items-start group md:col-span-2">
761 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-globe-americas text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
762 <div class="ml-3">
763 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Website', 'easy-invoice'); ?></label>
764 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
765 <?php
766 if ($quote && $quote->getClientId()) {
767 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
768 if ($client) {
769 echo esc_html($client->getWebsite() ?: esc_html__('', 'easy-invoice'));
770 } else {
771 echo esc_html__('', 'easy-invoice');
772 }
773 } else {
774 echo esc_html__('', 'easy-invoice');
775 }
776 ?>
777 </p>
778 </div>
779 </div>
780
781 <!-- Address -->
782 <div class="flex items-start group md:col-span-2">
783 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-map-marker-alt text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
784 <div class="ml-3">
785 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Address', 'easy-invoice'); ?></label>
786 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
787 <?php
788 if ($quote && $quote->getClientId()) {
789 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId());
790 if ($client) {
791 echo esc_html($client->getAddress() ?: esc_html__('', 'easy-invoice'));
792 } else {
793 echo esc_html__('', 'easy-invoice');
794 }
795 } else {
796 echo esc_html__('', 'easy-invoice');
797 }
798 ?>
799 </p>
800 </div>
801 </div>
802 </div>
803 </div>
804 </div>
805
806 <!-- No Client Selected Message -->
807 <div id="no-client-message" class="mt-4 text-center py-6 border-2 border-dashed border-gray-300 rounded-lg <?php echo $quote && $quote->getClientId() ? 'hidden' : ''; ?>">
808 <p class="text-gray-600"><?php _e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p>
809 </div>
810 </div>
811
812 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
813 <!-- Discount & Tax Tab Content -->
814 <div>
815 <h3 class="text-lg font-medium text-gray-900"><?php _e('Discount & Tax Settings', 'easy-invoice'); ?></h3>
816
817 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
818 <?php
819 // Get all discount and tax fields
820 $discount_fields = $quote_form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
821 $tax_fields = $quote_form_manager->getFieldsBySection('discounts-taxes-tab', 'tax');
822 $additional_tax_fields = $quote_form_manager->getFieldsBySection('discounts-taxes-tab', 'additional_tax');
823
824 // Combine and sort by order
825 $all_fields = array_merge($discount_fields, $tax_fields, $additional_tax_fields);
826 usort($all_fields, function($a, $b) {
827 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
828 });
829
830 foreach ($all_fields as $field):
831 echo $quote_form_manager->renderField($field, $quote);
832 endforeach;
833 ?>
834 </div>
835 </div>
836
837 <?php elseif ($tab['id'] === 'payments-tab'): ?>
838 <!-- Payment Tab Content -->
839 <div>
840 <h3 class="text-lg font-medium text-gray-900"><?php _e('Payment Information', 'easy-invoice'); ?></h3>
841 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
842 <?php
843 $payment_fields = $quote_form_manager->getFieldsBySection('payments-tab', 'payment');
844 foreach ($payment_fields as $field):
845 echo $quote_form_manager->renderField($field, $quote);
846 endforeach;
847 ?>
848 </div>
849
850 <!-- Currency Section -->
851 <div class="mt-8 border-t border-gray-200 pt-6">
852 <h3 class="text-lg font-medium text-gray-900"><?php _e('Currency', 'easy-invoice'); ?></h3>
853 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
854 <?php
855 $currency_fields = $quote_form_manager->getFieldsBySection('payments-tab', 'currency');
856 foreach ($currency_fields as $field):
857 echo $quote_form_manager->renderField($field, $quote);
858 endforeach;
859 ?>
860 </div>
861 </div>
862 </div>
863
864 <?php endif; ?>
865 </div>
866 <?php endforeach; ?>
867 </div>
868
869 <!-- Template Gallery Modal -->
870 <div id="template-gallery-modal" style="display: none;" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50 flex items-center justify-center">
871 <div class="mx-auto p-6 border w-full md:w-4/5 max-w-4xl shadow-lg rounded-md bg-white max-h-[80vh] overflow-y-auto">
872 <div class="flex justify-between items-center mb-4">
873 <h3 class="text-xl font-medium text-gray-900"><?php _e('Browse Quote Templates', 'easy-invoice'); ?></h3>
874 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
875 <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span>
876 <i class="fas fa-times text-xl"></i>
877 </button>
878 </div>
879
880 <div class="templates-grid grid grid-cols-2 md:grid-cols-3 gap-4 mb-4 max-h-[45vh] overflow-y-auto p-2">
881 <?php
882 // Display all template options in the modal
883 foreach ($all_templates as $template_id => $template) :
884 $is_current = ($current_template === $template_id);
885 $selected_class = $is_current ? 'selected' : '';
886 ?>
887 <div class="gallery-template-card <?php echo $selected_class; ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo isset($template['is_free']) && !$template['is_free'] ? 'true' : 'false'; ?>">
888 <div class="p-4 border rounded-lg">
889 <div class="flex items-center mb-2">
890 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
891 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
892 </div>
893 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
894 <div class="mt-2 flex-grow">
895 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
896 </div>
897 </div>
898 </div>
899 </div>
900 <?php endforeach; ?>
901 </div>
902
903 <div class="flex justify-end mt-4 space-x-3">
904 <button type="button" id="apply-selected-template" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
905 <?php _e('Apply', 'easy-invoice'); ?>
906 </button>
907 <button type="button" class="gallery-close-btn inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
908 <?php _e('Close', 'easy-invoice'); ?>
909 </button>
910 </div>
911 </div>
912 </div>
913
914 <!-- Hidden Template for Quote Items -->
915 <template id="quote-item-template" style="display: none;">
916 <div class="quote-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;">
917 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
918 <div class="flex items-center">
919 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
920 <i class="fas fa-chevron-down text-sm"></i>
921 </button>
922 <h3 class="text-base font-normal text-gray-800 flex-1">
923 <?php _e('New Item', 'easy-invoice'); ?>
924 </h3>
925 </div>
926 <div class="flex items-center gap-3">
927 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
928 <span class="quantity-summary">1</span> × $<span class="price-summary">0.00</span>
929 = $<span class="total-summary">0.00</span>
930 </div>
931 <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Fill this item with sample data', 'easy-invoice'); ?>">
932 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
933 <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/>
934 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
935 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
936 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
937 </svg>
938 </button>
939 <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium">
940 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
941 </button>
942 </div>
943 </div>
944 <div class="item-content">
945 <?php echo $quote_form_manager->generateItemTemplateHtml(); ?>
946 </div>
947 </div>
948 </template>
949
950 <script>
951 // Set Pro detection early
952 window.easyInvoice = window.easyInvoice || {};
953 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
954
955 jQuery(document).ready(function($) {
956 // Set flag to indicate this is a quote form
957 window.isQuoteForm = true;
958
959 // Function to get URL parameter
960 function getUrlParameter(name) {
961 name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
962 var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
963 var results = regex.exec(location.search);
964 return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
965 }
966
967 // Function to switch to a specific tab
968 function switchToTab(tabId) {
969 if (tabId && $('#' + tabId).length) {
970 // Remove active state from all tabs
971 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
972 $('.tab-content').addClass('hidden');
973
974 // Activate the specified tab
975 $('.tab-button[data-tab="' + tabId + '"]').addClass('border-indigo-500 text-indigo-600');
976 $('#' + tabId).removeClass('hidden');
977
978 // Update URL without page reload
979 var url = new URL(window.location);
980 url.searchParams.set('tab', tabId);
981 window.history.replaceState({}, '', url);
982 }
983 }
984
985 // Check for tab parameter on page load
986 var initialTab = getUrlParameter('tab');
987 if (initialTab) {
988 switchToTab(initialTab);
989 }
990
991 // Tab switching
992 $('.tab-button').on('click', function() {
993 var tabId = $(this).data('tab');
994 switchToTab(tabId);
995 });
996
997 // Template gallery functionality
998 $('#browse-all-templates, #browse-templates-btn').on('click', function() {
999 // Check if this is the free version
1000 const isFreeVersion = !window.easyInvoice || !window.easyInvoice.isPro;
1001
1002 if (isFreeVersion) {
1003 // Show premium upgrade modal for free users
1004 if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) {
1005 EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Premium Templates', 'easy-invoice'); ?>', '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look. Browse through our complete template library and find the perfect design for your business.', 'easy-invoice'); ?>');
1006 } else {
1007 alert('<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>');
1008 }
1009 } else {
1010 // Pro users can browse all templates
1011 $('#template-gallery-modal').show();
1012 }
1013 });
1014
1015 $('.close-gallery-modal, .gallery-close-btn').on('click', function() {
1016 $('#template-gallery-modal').hide();
1017 });
1018
1019 // Gallery template card click handler
1020 $(document).on('click', '.gallery-template-card', function() {
1021 $('.gallery-template-card').removeClass('selected');
1022 $(this).addClass('selected');
1023 });
1024
1025 // Template card click handler
1026 $(document).on('click', '.template-card', function(e) {
1027 const templateId = $(this).data('template-id');
1028
1029 // Check if this is a premium template in free version
1030 const isPremium = $(this).attr('data-is-premium') === 'true';
1031 const isFreeVersion = !window.easyInvoice || !window.easyInvoice.isPro;
1032
1033 if (isPremium && isFreeVersion) {
1034 // Show premium upgrade modal instead of selecting the template
1035 if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) {
1036 EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Premium Templates', 'easy-invoice'); ?>', '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look.', 'easy-invoice'); ?>');
1037 } else {
1038 alert('<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>');
1039 }
1040 return;
1041 }
1042
1043 // Select the template
1044 $('.template-card').removeClass('selected');
1045 $(this).addClass('selected');
1046 $('input[name="quote_template"]').val(templateId);
1047 $('input[name="quote_template"][value="' + templateId + '"]').prop('checked', true);
1048
1049 // Update the live preview
1050 if (typeof updateLivePreview === 'function') {
1051 updateLivePreview(templateId);
1052 }
1053 });
1054
1055 // Apply selected template
1056 $('#apply-selected-template').on('click', function() {
1057 const selectedTemplate = $('.gallery-template-card.selected').data('template-id');
1058 if (selectedTemplate) {
1059 $('.template-card').removeClass('selected');
1060 $('.template-card[data-template-id="' + selectedTemplate + '"]').addClass('selected');
1061 $('input[name="quote_template"]').val(selectedTemplate);
1062 $('input[name="quote_template"][value="' + selectedTemplate + '"]').prop('checked', true);
1063 $('#template-gallery-modal').hide();
1064
1065 // Update the live preview
1066 if (typeof updateLivePreview === 'function') {
1067 updateLivePreview(selectedTemplate);
1068 }
1069 }
1070 });
1071
1072
1073
1074
1075
1076 // Quote Items functionality
1077 let itemIndex = $('.quote-item').length;
1078
1079 // Initialize existing items with correct indices
1080 $('.quote-item').each(function(index) {
1081 const $item = $(this);
1082
1083 // Update all input fields - match both items[0][fieldname] and items[-1][fieldname]
1084 $item.find('input[name*="items["]').each(function() {
1085 var oldName = $(this).attr('name');
1086 var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + index + '][');
1087 $(this).attr('name', newName);
1088 });
1089
1090 // Update all textarea fields - match both items[0][fieldname] and items[-1][fieldname]
1091 $item.find('textarea[name*="items["]').each(function() {
1092 var oldName = $(this).attr('name');
1093 var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + index + '][');
1094 $(this).attr('name', newName);
1095 });
1096
1097 // Update all select fields - match both items[0][fieldname] and items[-1][fieldname]
1098 $item.find('select[name*="items["]').each(function() {
1099 var oldName = $(this).attr('name');
1100 var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + index + '][');
1101 $(this).attr('name', newName);
1102 });
1103
1104 // Update field IDs to be unique (handle both _-1 and _0)
1105 $item.find('[id*="_-1"], [id*="_0"]').each(function() {
1106 var oldId = $(this).attr('id');
1107 var newId = oldId.replace(/_(?:-1|0)/, '_' + index);
1108 $(this).attr('id', newId);
1109 // Update corresponding label for attribute
1110 var $label = $item.find('label[for="' + oldId + '"]');
1111 if ($label.length) {
1112 $label.attr('for', newId);
1113 }
1114 });
1115 });
1116
1117 // Add new item
1118 $('.add-item-button').on('click', function() {
1119 addNewQuoteItem();
1120 });
1121
1122 // Remove item
1123 $(document).on('click', '.remove-item', function() {
1124 const itemContainer = $(this).closest('.quote-item');
1125 if ($('.quote-item').length > 1) {
1126 itemContainer.remove();
1127 updateItemNumbers();
1128 calculateTotals();
1129 } else {
1130 // If it's the last item, just clear the fields
1131 itemContainer.find('input, textarea').val('');
1132 itemContainer.find('select').prop('selectedIndex', 0);
1133 }
1134 });
1135
1136 // Item collapse toggle
1137 $(document).on('click', '.item-collapse-toggle', function() {
1138 const itemContainer = $(this).closest('.quote-item');
1139 const content = itemContainer.find('.item-content');
1140 const summary = itemContainer.find('.item-collapsed-summary');
1141 const sampleButton = itemContainer.find('.fill-sample-data-btn');
1142 const icon = $(this).find('i');
1143
1144 if (content.is(':visible')) {
1145 // Collapsing - update summary and change icon
1146 updateQuoteItemSummary(itemContainer);
1147 content.slideUp(200);
1148 summary.slideDown(200);
1149 sampleButton.hide(); // Hide sample button when collapsed
1150 icon.removeClass('fa-chevron-down').addClass('fa-chevron-right');
1151 // Add compact styling to the collapsed item
1152 itemContainer.addClass('collapsed-item');
1153 } else {
1154 // Expanding - hide summary and change icon
1155 content.slideDown(200);
1156 summary.slideUp(200);
1157 sampleButton.show(); // Show sample button when expanded
1158 icon.removeClass('fa-chevron-right').addClass('fa-chevron-down');
1159 // Remove compact styling from the expanded item
1160 itemContainer.removeClass('collapsed-item');
1161 }
1162
1163 // Update totals if payment manager is available
1164 if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
1165 window.EasyInvoicePayment.updateTotals();
1166 } else if (typeof updatePreview === 'function') {
1167 updatePreview();
1168 }
1169 });
1170
1171 // Fill sample data
1172 $(document).on('click', '.fill-sample-data-btn', function() {
1173 const itemContainer = $(this).closest('.quote-item');
1174 fillQuoteItemWithSampleData(itemContainer);
1175 });
1176
1177 // Collapse all items
1178 $('#collapse-all-items').on('click', function() {
1179 const $button = $(this);
1180 let allCollapsed = true;
1181
1182 // Check if all items are already collapsed
1183 $('.quote-item').each(function() {
1184 const $item = $(this);
1185 const itemContent = $item.find('.item-content');
1186 if (itemContent.is(':visible')) {
1187 allCollapsed = false;
1188 return false; // Break the loop if we find an expanded item
1189 }
1190 });
1191
1192 if (allCollapsed) {
1193 // If all items are collapsed, expand them
1194 $button.html('<i class="fas fa-chevron-down mr-1"></i> <?php _e('Collapse All', 'easy-invoice'); ?>');
1195 $('.quote-item').each(function() {
1196 const $item = $(this);
1197 const itemContent = $item.find('.item-content');
1198 const summaryElement = $item.find('.item-collapsed-summary');
1199 const sampleButton = $item.find('.fill-sample-data-btn');
1200 const icon = $item.find('.item-collapse-toggle i');
1201
1202 // Only toggle if it's currently collapsed
1203 if (!itemContent.is(':visible')) {
1204 itemContent.slideDown(200);
1205 summaryElement.slideUp(200);
1206 sampleButton.show(); // Show sample button when expanded
1207 icon.removeClass('fa-chevron-right').addClass('fa-chevron-down');
1208 // Remove collapsed item styling
1209 $item.removeClass('collapsed-item');
1210 }
1211 });
1212 } else {
1213 // If any items are expanded, collapse them all
1214 $button.html('<i class="fas fa-chevron-right mr-1"></i> <?php _e('Expand All', 'easy-invoice'); ?>');
1215 $('.quote-item').each(function() {
1216 const $item = $(this);
1217 const itemContent = $item.find('.item-content');
1218 const summaryElement = $item.find('.item-collapsed-summary');
1219 const sampleButton = $item.find('.fill-sample-data-btn');
1220 const icon = $item.find('.item-collapse-toggle i');
1221
1222 // Only toggle if it's currently expanded
1223 if (itemContent.is(':visible')) {
1224 // Update the summary before collapsing
1225 updateQuoteItemSummary($item);
1226 itemContent.slideUp(200);
1227 summaryElement.slideDown(200);
1228 sampleButton.hide(); // Hide sample button when collapsed
1229 icon.removeClass('fa-chevron-down').addClass('fa-chevron-right');
1230 // Add collapsed item styling
1231 $item.addClass('collapsed-item');
1232 }
1233 });
1234 }
1235
1236 // Update totals if payment manager is available
1237 if (window.EasyInvoicePayment && typeof window.EasyInvoicePayment.updateTotals === 'function') {
1238 window.EasyInvoicePayment.updateTotals();
1239 } else if (typeof updatePreview === 'function') {
1240 updatePreview();
1241 }
1242 });
1243
1244 // Client search functionality
1245 let searchTimeout;
1246 let isSearching = false;
1247 $('#client-search-input').on('input', function() {
1248 const query = $(this).val().trim();
1249 clearTimeout(searchTimeout);
1250
1251 // Don't hide results immediately for short queries - let the search function handle it
1252 if (query.length === 0) {
1253 // Don't call searchClients here to avoid duplicate calls with click event
1254 return;
1255 }
1256
1257 searchTimeout = setTimeout(function() {
1258 searchClients(query);
1259 }, 300);
1260 });
1261
1262 // Focus on search input - ensure client tab is active
1263 $('#client-search-input').on('focus', function() {
1264 // Switch to client tab first to ensure the search results are visible
1265 if (window.switchToTab) {
1266 window.switchToTab('client-tab');
1267 } else {
1268 // Fallback: manually switch tabs
1269 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
1270 $('.tab-content').addClass('hidden');
1271 $('.tab-button[data-tab="client-tab"]').addClass('border-indigo-500 text-indigo-600');
1272 $('#client-tab').removeClass('hidden');
1273 }
1274 });
1275
1276 // Click on search input to show all clients
1277 $("#client-search-input").on("click", function() {
1278
1279
1280 // Switch to client tab first to ensure the search results are visible
1281 if (window.switchToTab) {
1282 window.switchToTab('client-tab');
1283 } else {
1284 // Fallback: manually switch tabs
1285 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
1286 $('.tab-content').addClass('hidden');
1287 $('.tab-button[data-tab="client-tab"]').addClass('border-indigo-500 text-indigo-600');
1288 $('#client-tab').removeClass('hidden');
1289 }
1290
1291 // Always show all clients when clicking the search input
1292 searchClients('');
1293 });
1294
1295 // Client selection
1296 $(document).on('click', '.client-option', function(e) {
1297 e.preventDefault();
1298 e.stopPropagation();
1299
1300 const clientId = $(this).data('client-id');
1301 const clientName = $(this).data('client-name');
1302 const clientEmail = $(this).data('client-email');
1303 const clientCompany = $(this).data('client-company');
1304 const clientPhone = $(this).data('client-phone');
1305 const clientWebsite = $(this).data('client-website');
1306 const clientAddress = $(this).data('client-address');
1307
1308 // Create client data object with all available information
1309 const clientData = {
1310 name: clientName,
1311 email: clientEmail,
1312 company: clientCompany,
1313 phone: clientPhone,
1314 website: clientWebsite,
1315 address: clientAddress
1316 };
1317
1318 selectClient(clientId, clientName, clientEmail, clientData);
1319 $('#client-search-results').hide();
1320 $('#client-search-input').val('');
1321 });
1322
1323 // Alternative click handler for client options
1324 $('#client-search-list').on('click', '.client-option', function(e) {
1325 e.preventDefault();
1326 e.stopPropagation();
1327
1328 const clientId = $(this).data('client-id');
1329 const clientName = $(this).data('client-name');
1330 const clientEmail = $(this).data('client-email');
1331 const clientCompany = $(this).data('client-company');
1332 const clientPhone = $(this).data('client-phone');
1333 const clientWebsite = $(this).data('client-website');
1334 const clientAddress = $(this).data('client-address');
1335
1336 // Create client data object with all available information
1337 const clientData = {
1338 name: clientName,
1339 email: clientEmail,
1340 company: clientCompany,
1341 phone: clientPhone,
1342 website: clientWebsite,
1343 address: clientAddress
1344 };
1345
1346 selectClient(clientId, clientName, clientEmail, clientData);
1347 $('#client-search-results').hide();
1348 $('#client-search-input').val('');
1349 });
1350
1351 // Clear client selection
1352 $('#clear-client-selection').on('click', function() {
1353 clearClientSelection();
1354 });
1355
1356 // Add new client button - handled by client-manager.js
1357 // $('#add-new-client-btn').on('click', function() {
1358 // $('#add_client_modal').show();
1359 // });
1360
1361 // Close modal - handled by client-manager.js
1362 // $('.close-modal').on('click', function() {
1363 // $('#add_client_modal').hide();
1364 // });
1365
1366 // Functions
1367 function addNewQuoteItem() {
1368 const template = $('#quote-item-template').html();
1369 const newItem = $(template);
1370
1371 // Update field names with new index using the same pattern as invoice builder
1372 newItem.find('input[name*="items["]').each(function() {
1373 var oldName = $(this).attr('name');
1374 var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + itemIndex + '][');
1375 $(this).attr('name', newName);
1376 });
1377
1378 newItem.find('textarea[name*="items["]').each(function() {
1379 var oldName = $(this).attr('name');
1380 var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + itemIndex + '][');
1381 $(this).attr('name', newName);
1382 });
1383
1384 newItem.find('select[name*="items["]').each(function() {
1385 var oldName = $(this).attr('name');
1386 var newName = oldName.replace(/items\[(?:-1|0)\]\[/, 'items[' + itemIndex + '][');
1387 $(this).attr('name', newName);
1388 });
1389
1390 // Update field IDs to be unique (handle both _-1 and _0)
1391 newItem.find('[id*="_-1"], [id*="_0"]').each(function() {
1392 var oldId = $(this).attr('id');
1393 var newId = oldId.replace(/_(?:-1|0)/, '_' + itemIndex);
1394 $(this).attr('id', newId);
1395 // Update corresponding label for attribute
1396 var $label = newItem.find('label[for="' + oldId + '"]');
1397 if ($label.length) {
1398 $label.attr('for', newId);
1399 }
1400 });
1401
1402 $('.quote-items-container').append(newItem);
1403 itemIndex++;
1404 updateItemNumbers();
1405 }
1406
1407 function updateItemNumbers() {
1408 $('.quote-item').each(function(index) {
1409 const title = $(this).find('h3');
1410 if (title.text().includes('Item #')) {
1411 title.text('Item #' + (index + 1));
1412 }
1413 });
1414 }
1415
1416 function fillQuoteItemWithSampleData(itemContainer) {
1417 const samples = [
1418 {
1419 'title': 'Website Design',
1420 'description': 'Complete design and layout for a responsive business website.',
1421 'quantity': 1,
1422 'price': 1200.00,
1423 'taxable': true
1424 },
1425 {
1426 'title': 'Consultation Session',
1427 'description': 'One-hour business strategy consultation.',
1428 'quantity': 2,
1429 'price': 150.00,
1430 'taxable': true
1431 },
1432 {
1433 'title': 'Logo Design',
1434 'description': 'Custom logo design with 3 initial concepts and 2 revisions.',
1435 'quantity': 1,
1436 'price': 350.00,
1437 'taxable': true
1438 },
1439 {
1440 'title': 'SEO Optimization',
1441 'description': 'On-page and off-page SEO optimization for better search rankings.',
1442 'quantity': 1,
1443 'price': 500.00,
1444 'taxable': true
1445 },
1446 {
1447 'title': 'Content Writing',
1448 'description': 'Professional content writing for 5 web pages.',
1449 'quantity': 5,
1450 'price': 80.00,
1451 'taxable': true
1452 },
1453 {
1454 'title': 'Annual Hosting',
1455 'description': 'One year of premium website hosting.',
1456 'quantity': 1,
1457 'price': 200.00,
1458 'taxable': false
1459 }
1460 ];
1461 const sampleData = samples[Math.floor(Math.random() * samples.length)];
1462
1463 itemContainer.find('input[name*="[title]"]').val(sampleData.title);
1464 itemContainer.find('textarea[name*="[description]"]').val(sampleData.description);
1465 itemContainer.find('input[name*="[quantity]"]').val(sampleData.quantity);
1466 itemContainer.find('input[name*="[price]"]').val(sampleData.price);
1467 itemContainer.find('input[name*="[taxable]"]').prop('checked', sampleData.taxable);
1468
1469 // Update the item title in the header
1470 itemContainer.find('h3').text(sampleData.title);
1471
1472 // Trigger calculations
1473 calculateTotals();
1474 }
1475
1476 // Update the collapsed summary of a quote item
1477 function updateQuoteItemSummary(itemContainer) {
1478 const quantity = parseFloat(itemContainer.find('input[name*="[quantity]"]').val()) || 0;
1479 const price = parseFloat(itemContainer.find('input[name*="[price]"]').val()) || 0;
1480 const total = quantity * price;
1481 itemContainer.find('.quantity-summary').text(quantity);
1482 itemContainer.find('.price-summary').text(price.toFixed(2));
1483 itemContainer.find('.total-summary').text(total.toFixed(2));
1484 itemContainer.find('input[name*="[total]"]').val(total.toFixed(2));
1485 }
1486
1487 function searchClients(query) {
1488
1489
1490 // Prevent multiple simultaneous requests
1491 if (isSearching) {
1492 return;
1493 }
1494
1495 // Clear any existing timeout to prevent multiple requests
1496 if (searchTimeout) {
1497 clearTimeout(searchTimeout);
1498 }
1499
1500 isSearching = true;
1501
1502 $('#client-search-loading').show().removeClass('hidden');
1503 $('#client-search-empty').hide().addClass('hidden');
1504 $('#client-search-list').empty();
1505
1506 // If query is empty or less than 2 characters, show all clients
1507 if (!query || query.trim().length < 2) {
1508 query = ''; // Set to empty string to get all clients
1509 }
1510
1511
1512
1513 $.ajax({
1514 url: easyInvoice.ajaxUrl,
1515 type: 'POST',
1516 data: {
1517 action: 'easy_invoice_search_clients',
1518 query: query,
1519 nonce: easyInvoice.nonce
1520 },
1521
1522 success: function(response) {
1523
1524 isSearching = false;
1525 $('#client-search-loading').hide().addClass('hidden');
1526
1527 if (response.success && response.data.length > 0) {
1528
1529 response.data.forEach(function(client) {
1530 const option = $(`
1531 <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
1532 data-client-id="${client.id}"
1533 data-client-name="${(client.name || '').replace(/"/g, '&quot;')}"
1534 data-client-email="${(client.email || '').replace(/"/g, '&quot;')}"
1535 data-client-company="${(client.company || '').replace(/"/g, '&quot;')}"
1536 data-client-phone="${(client.phone || '').replace(/"/g, '&quot;')}"
1537 data-client-address="${(client.address || '').replace(/"/g, '&quot;')}"
1538 onclick="selectClientFromOption(this)">
1539 <div class="font-medium text-gray-900">${client.name || 'No Name'}</div>
1540 <div class="text-sm text-gray-600">${client.email || 'No Email'}</div>
1541 </div>
1542 `);
1543 $('#client-search-list').append(option);
1544 });
1545 $('#client-search-results').show().removeClass('hidden');
1546
1547 } else {
1548 $('#client-search-empty').show().removeClass('hidden');
1549 $('#client-search-results').show().removeClass('hidden');
1550 }
1551 },
1552 error: function(xhr, status, error) {
1553 console.error('Quote form: AJAX error:', error);
1554 console.error('Quote form: Status:', status);
1555 console.error('Quote form: Response:', xhr.responseText);
1556 isSearching = false;
1557 $('#client-search-loading').hide().addClass('hidden');
1558 $('#client-search-empty').show().removeClass('hidden');
1559 $('#client-search-results').show().removeClass('hidden');
1560 }
1561 });
1562 }
1563
1564 function selectClient(clientId, clientName, clientEmail, clientData = null) {
1565 $('#select-client').val(clientId);
1566 $('#client-id').val(clientId);
1567 $('#selected-client-name').text(clientName);
1568 $('#selected-client-email').text(clientEmail);
1569
1570 $('#selected-client-display').show().removeClass('hidden');
1571 $('#client-info-display').show().removeClass('hidden');
1572 $('#no-client-message').hide().addClass('hidden');
1573
1574 // Update the edit client button URL
1575 $('#edit-selected-client').attr('href', '<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id='); ?>' + clientId);
1576
1577 // If we have full client data, populate all fields directly
1578 if (clientData) {
1579 $('#display-client-name').text(clientData.name || clientName || '-');
1580 $('#display-client-company').text(clientData.company || '-');
1581 $('#display-client-email').text(clientData.email || clientEmail || '-');
1582 $('#display-client-phone').text(clientData.phone || '-');
1583 $('#display-client-website').text(clientData.website || '-');
1584 $('#display-client-address').text(clientData.address || '-');
1585 } else {
1586 // Fallback to just the basic info we have
1587 $('#display-client-name').text(clientName);
1588 $('#display-client-email').text(clientEmail);
1589 $('#display-client-company').text('-');
1590 $('#display-client-phone').text('-');
1591 $('#display-client-website').text('-');
1592 $('#display-client-address').text('-');
1593 }
1594 }
1595
1596 function clearClientSelection() {
1597 $('#select-client').val('');
1598 $('#client-id').val('');
1599 $('#selected-client-display').hide();
1600 $('#client-info-display').hide();
1601 $('#no-client-message').show();
1602
1603 // Clear display fields
1604 $('#display-client-name, #display-client-email, #display-client-company, #display-client-phone, #display-client-website, #display-client-address').text('-');
1605 }
1606
1607 function calculateTotals() {
1608 let subtotal = 0;
1609 $('.quote-item').each(function() {
1610 const quantity = parseFloat($(this).find('input[name*="[quantity]"]').val()) || 0;
1611 const price = parseFloat($(this).find('input[name*="[price]"]').val()) || 0;
1612 const total = quantity * price;
1613 $(this).find('.total-summary').text(total.toFixed(2));
1614 $(this).find('input[name*="[total]"]').val(total.toFixed(2));
1615 subtotal += total;
1616 });
1617 $('input[name="subtotal"]').val(subtotal.toFixed(2));
1618 calculateTaxAndDiscount();
1619 }
1620
1621 function calculateTaxAndDiscount() {
1622 const subtotal = parseFloat($('input[name="subtotal"]').val()) || 0;
1623 const discountValue = parseFloat($('input[name="discount_value"]').val()) || 0;
1624 const discountType = $('select[name="discount_type"]').val();
1625 const taxRate = parseFloat($('input[name="tax_rate"]').val()) || 0;
1626
1627 let discountAmount = 0;
1628 if (discountType === 'percentage') {
1629 discountAmount = subtotal * (discountValue / 100);
1630 } else {
1631 discountAmount = discountValue;
1632 }
1633
1634 const taxableAmount = subtotal - discountAmount;
1635 const taxAmount = taxableAmount * (taxRate / 100);
1636 const total = taxableAmount + taxAmount;
1637
1638 $('input[name="discount_amount"]').val(discountAmount.toFixed(2));
1639 $('input[name="tax_amount"]').val(taxAmount.toFixed(2));
1640 $('input[name="total"]').val(total.toFixed(2));
1641 }
1642
1643 // Initialize calculations on page load
1644 calculateTotals();
1645
1646 // Initialize existing client data on page load
1647 const existingClientId = $('#client-id').val();
1648 const existingClientName = $('#selected-client-name').text().trim();
1649 const existingClientEmail = $('#selected-client-email').text().trim();
1650
1651 // Get existing client data from PHP
1652 const existingClientData = <?php echo json_encode($client_data); ?>;
1653
1654 if (existingClientId && existingClientName && existingClientName !== '-') {
1655 // Show the selected client display and client info display
1656 $('#selected-client-display').show();
1657 $('#client-info-display').show();
1658 $('#no-client-message').hide();
1659
1660 // Set the select client dropdown value without triggering change event
1661 // Use a flag to prevent AJAX calls during initialization
1662 window.isInitializingClient = true;
1663 $('#select-client').val(existingClientId);
1664 setTimeout(function() {
1665 window.isInitializingClient = false;
1666 }, 1000);
1667
1668 // Populate the search input with the existing client name
1669 $('#client-search-input').val(existingClientName);
1670
1671 // Client initialized successfully
1672 }
1673
1674 // Bind calculation events
1675 $(document).on('input', 'input[name*="[quantity]"], input[name*="[price]"]', function() {
1676 calculateTotals();
1677 });
1678
1679 $(document).on('input', 'input[name="discount_value"], input[name="tax_rate"]', function() {
1680 calculateTaxAndDiscount();
1681 });
1682
1683 $(document).on('change', 'select[name="discount_type"]', function() {
1684 calculateTaxAndDiscount();
1685 });
1686
1687 // Click on search input to show all clients
1688 $("#client-search-input").on("click", function() {
1689 // Only show all clients if the search results are not already visible
1690 if (!$('#client-search-results').is(':visible')) {
1691 searchClients('');
1692 }
1693 });
1694
1695 // Global function for inline onclick
1696 window.selectClientFromOption = function(element) {
1697 const $element = $(element);
1698 const clientId = $element.data('client-id');
1699 const clientName = $element.data('client-name');
1700 const clientEmail = $element.data('client-email');
1701 const clientCompany = $element.data('client-company');
1702 const clientPhone = $element.data('client-phone');
1703 const clientWebsite = $element.data('client-website');
1704 const clientAddress = $element.data('client-address');
1705
1706 // Create client data object with all available information
1707 const clientData = {
1708 name: clientName,
1709 email: clientEmail,
1710 company: clientCompany,
1711 phone: clientPhone,
1712 website: clientWebsite,
1713 address: clientAddress
1714 };
1715
1716 selectClient(clientId, clientName, clientEmail, clientData);
1717 $('#client-search-results').hide();
1718 $('#client-search-input').val('');
1719 };
1720 });
1721
1722 // Quote field configuration and pre-loaded data
1723 </script>
1724
1725 <?php if (wp_doing_ajax()): ?>
1726 <!-- Save Button Section for Modal -->
1727 <div class="mt-8 pt-6 border-t border-gray-200">
1728 <div class="flex justify-end space-x-3">
1729 <button type="button" id="cancel-quote-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300">
1730 Cancel
1731 </button>
1732 <button type="submit" id="save-quote-btn" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1733 <i class="fas fa-save mr-2"></i>
1734 <span>Save Quote</span>
1735 </button>
1736 </div>
1737 </div>
1738 </form>
1739 <?php endif; ?>