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

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

1,348 lines 89.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Enqueue CSS and JS files
3 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), '1.0.0');
4 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), '1.0.0', true);
5
6 // Initialize the form manager
7 $form_manager = new EasyInvoice\Forms\Invoice\InvoiceFormManager();
8
9 // Get field configuration for JavaScript
10 $invoice_field_config = $form_manager->getFieldConfigForJavaScript();
11
12 // Trigger form initialization to allow pro version to register new elements
13 do_action('easy_invoice_form_init');
14
15 $tabs = $form_manager->getTabs();
16 $templates = $form_manager->getTemplates();
17
18 // Create a separate array for browse templates modal with all available templates
19 $all_templates = [
20 'standard' => [
21 'name' => __('Standard', 'easy-invoice'),
22 'icon' => 'fas fa-file-invoice',
23 'description' => __('Clean and professional standard template', 'easy-invoice'),
24 'order' => 1,
25 'is_free' => true
26 ],
27 'legacy' => [
28 'name' => __('Legacy', 'easy-invoice'),
29 'icon' => 'fas fa-file-alt',
30 'description' => __('Clean, minimalist design with traditional business layout', 'easy-invoice'),
31 'order' => 2,
32 'is_free' => true
33 ],
34 'professional' => [
35 'name' => __('Professional', 'easy-invoice'),
36 'icon' => 'fas fa-briefcase',
37 'description' => __('Professional business template with modern design', 'easy-invoice'),
38 'order' => 3,
39 'is_free' => false
40 ],
41 'modern' => [
42 'name' => __('Modern', 'easy-invoice'),
43 'icon' => 'fas fa-rocket',
44 'description' => __('Modern and sleek design template', 'easy-invoice'),
45 'order' => 4,
46 'is_free' => false
47 ],
48 'classic' => [
49 'name' => __('Classic', 'easy-invoice'),
50 'icon' => 'fas fa-landmark',
51 'description' => __('Traditional business template', 'easy-invoice'),
52 'order' => 5,
53 'is_free' => false
54 ],
55 'minimal' => [
56 'name' => __('Minimal', 'easy-invoice'),
57 'icon' => 'fas fa-minus',
58 'description' => __('Simple and clean minimal template', 'easy-invoice'),
59 'order' => 6,
60 'is_free' => false
61 ],
62
63 'corporate' => [
64 'name' => __('Corporate', 'easy-invoice'),
65 'icon' => 'fas fa-building',
66 'description' => __('Professional corporate template', 'easy-invoice'),
67 'order' => 7,
68 'is_free' => false
69 ],
70 'elegant' => [
71 'name' => __('Elegant', 'easy-invoice'),
72 'icon' => 'fas fa-gem',
73 'description' => __('Elegant and sophisticated design', 'easy-invoice'),
74 'order' => 8,
75 'is_free' => false
76 ],
77 'creative' => [
78 'name' => __('Creative', 'easy-invoice'),
79 'icon' => 'fas fa-palette',
80 'description' => __('Creative and artistic design template', 'easy-invoice'),
81 'order' => 9,
82 'is_free' => false
83 ]
84 ];
85
86
87
88 // Load client data directly in PHP if invoice has a client
89 $client_data = null;
90 if ($invoice && $invoice->getClientId()) {
91 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
92 if ($client) {
93 $client_data = array(
94 'id' => $client->getId(),
95 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
96 'email' => $client->getEmail() ?: '',
97 'phone' => $client->getExtraInfo() ?: '',
98 'company' => $client->getBusinessClientName() ?: '',
99 'address' => $client->getAddress() ?: '',
100 'website' => $client->getWebsite() ?: '',
101 );
102 }
103 }
104 ?>
105
106 <style>
107 /* Premium Template Styling */
108 .premium-template-blurred {
109 position: relative;
110 opacity: 0.8;
111 transition: all 0.4s ease;
112 cursor: pointer;
113 border: 2px solid #e5e7eb;
114 background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
115 transform: scale(0.98);
116 }
117
118 .premium-template-blurred::before {
119 content: '';
120 position: absolute;
121 top: 0;
122 left: 0;
123 right: 0;
124 bottom: 0;
125 background: linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%);
126 border-radius: 0.5rem;
127 z-index: 1;
128 pointer-events: none;
129 }
130
131 .premium-template-blurred::after {
132 content: 'PRO';
133 position: absolute;
134 top: 8px;
135 right: 8px;
136 background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
137 color: white;
138 font-size: 10px;
139 font-weight: 700;
140 padding: 2px 6px;
141 border-radius: 4px;
142 z-index: 2;
143 box-shadow: 0 2px 4px rgba(139, 92, 246, 0.3);
144 letter-spacing: 0.5px;
145 }
146
147 .premium-template-blurred:hover {
148 opacity: 0.95;
149 transform: scale(1.02);
150 border-color: #8b5cf6;
151 box-shadow: 0 8px 25px rgba(139, 92, 246, 0.15);
152 }
153
154 .premium-template-blurred:hover::after {
155 background: linear-gradient(135deg, #7c3aed 0%, #9333ea 100%);
156 box-shadow: 0 4px 8px rgba(139, 92, 246, 0.4);
157 }
158
159 .premium-template-blurred .template-radio {
160 pointer-events: none;
161 }
162
163 .premium-template-blurred > div {
164 position: relative;
165 z-index: 3;
166 }
167 </style>
168
169 <div class="bg-white shadow rounded-lg">
170
171 <div class="border-b border-gray-200">
172 <nav class="flex -mb-px">
173 <?php foreach ($tabs as $tab): ?>
174 <button type="button"
175 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"
176 data-tab="<?php echo esc_attr($tab['id']); ?>">
177 <i class="<?php echo esc_attr($tab['icon']); ?> mb-1 text-lg"></i>
178 <span><?php echo esc_html($tab['label']); ?></span>
179 </button>
180 <?php endforeach; ?>
181 </nav>
182 </div>
183 <form id="invoice-form">
184 <!-- Hidden invoice ID field for edit mode -->
185 <input type="hidden" id="invoice-id" name="invoice_id" value="<?php echo $invoice_id ? esc_attr($invoice_id) : ''; ?>">
186
187 <!-- Security nonce -->
188 <input type="hidden" id="invoice_nonce" name="invoice_nonce" value="<?php echo wp_create_nonce('easy_invoice_nonce'); ?>">
189
190 <?php foreach ($tabs as $tab): ?>
191 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
192 <?php if ($tab['id'] === 'invoice-tab'): ?>
193 <!-- Template Selection Section -->
194 <div class="mb-8 pb-6 border-b border-gray-200">
195 <div class="flex justify-between items-center mb-4">
196 <h3 class="text-lg font-medium text-gray-900"><?php _e('Invoice Template', 'easy-invoice'); ?></h3>
197 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
198 <span class="mr-1"><?php _e('Browse Invoice Templates', 'easy-invoice'); ?></span>
199 <i class="fas fa-chevron-right"></i>
200 </button>
201 </div>
202
203 <?php
204 // Get the current template (default to 'standard')
205 $current_template = $invoice ? $invoice->getTemplate() : 'standard';
206
207 if (empty($current_template)) {
208 $current_template = 'standard';
209 }
210
211 // Always include the hidden input for the template field
212 ?>
213 <input type="hidden" name="invoice_template" value="<?php echo esc_attr($current_template); ?>">
214 <?php
215
216 // Check if this is the free version
217 $is_free_version = !easy_invoice_has_pro();
218
219
220
221 // Show all templates grid
222 ?>
223 <div class="grid grid-cols-2 md:grid-cols-3 gap-4" id="main-templates-grid">
224 <?php
225 // Build the main grid templates array - always include current template plus 2 others
226 $main_grid_templates = [];
227
228 // First, add the current template if it exists in all_templates
229 if (isset($all_templates[$current_template])) {
230 // For free users, if current template is premium, default to standard
231 if ($is_free_version && isset($all_templates[$current_template]['is_free']) && !$all_templates[$current_template]['is_free']) {
232 $current_template = 'standard';
233 $main_grid_templates['standard'] = $all_templates['standard'];
234 } else {
235 $main_grid_templates[$current_template] = $all_templates[$current_template];
236 }
237 }
238
239 // Add other templates to fill up to 3, prioritizing standard templates
240 $default_templates = ['standard', 'legacy', 'professional'];
241 foreach ($default_templates as $template_id) {
242 if (count($main_grid_templates) >= 3) break;
243 if ($template_id !== $current_template && isset($all_templates[$template_id])) {
244 $main_grid_templates[$template_id] = $all_templates[$template_id];
245 }
246 }
247
248 // If we still don't have 3 templates, add any remaining templates
249 foreach ($all_templates as $template_id => $template) {
250 if (count($main_grid_templates) >= 3) break;
251 if (!isset($main_grid_templates[$template_id])) {
252 $main_grid_templates[$template_id] = $template;
253 }
254 }
255
256 // Display the main grid templates
257 foreach ($main_grid_templates as $template_id => $template) :
258 $checked = ($current_template === $template_id) ? 'checked' : '';
259 $selected_class = ($current_template === $template_id) ? 'selected' : '';
260
261 // Check if this template is premium (not free)
262 $is_premium = isset($template['is_free']) && !$template['is_free'];
263
264 // Add premium overlay classes
265 $premium_classes = '';
266 if ($is_free_version && $is_premium) {
267 $premium_classes = 'premium-template-blurred';
268 }
269 ?>
270 <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'; ?>">
271 <input type="radio" id="template-<?php echo esc_attr($template_id); ?>" name="invoice_template" value="<?php echo esc_attr($template_id); ?>" class="template-radio" <?php echo $checked; ?> <?php echo ($is_free_version && $is_premium) ? 'disabled' : ''; ?>>
272 <div class="p-4 border rounded-lg">
273 <div class="flex items-center mb-2">
274 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
275 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
276 </div>
277 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
278 <div class="mt-2 flex-grow">
279 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
280 </div>
281 </div>
282 </div>
283 </div>
284 <?php endforeach; ?>
285 </div>
286 </div>
287
288 <!-- Dynamic Fields Section -->
289 <div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
290 <?php
291 $main_fields = $form_manager->getFields('invoice-tab');
292 foreach ($main_fields as $field):
293 // Skip fields that have a section (like notes) - they're rendered separately
294 // Also skip the template field as it's rendered manually in the template selection section
295 if (!isset($field['section']) && $field['name'] !== 'invoice_template') {
296 echo $form_manager->renderField($field, $invoice);
297 }
298 endforeach;
299 ?>
300 </div>
301
302 <!-- Auto-generation notice for new invoices -->
303 <?php if (!$invoice): ?>
304 <div class="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
305 <div class="flex">
306 <div class="flex-shrink-0">
307 <i class="fas fa-info-circle text-blue-400"></i>
308 </div>
309 <div class="ml-3">
310 <p class="text-sm text-blue-700">
311 <?php _e('Invoice number will be automatically generated when you save the invoice.', 'easy-invoice'); ?>
312 </p>
313 </div>
314 </div>
315 </div>
316 <?php endif; ?>
317
318 <!-- Recurring Invoice Section -->
319 <?php if (easy_invoice_has_pro()): ?>
320 <?php
321 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
322 if (!empty($recurring_fields)):
323 ?>
324 <div class="mt-8 border-t border-gray-200 pt-6">
325 <h3 class="text-lg font-medium text-gray-900"><?php _e('Recurring Invoice Settings', 'easy-invoice-pro'); ?></h3>
326 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
327 <?php
328 foreach ($recurring_fields as $field):
329 echo $form_manager->renderField($field, $invoice);
330 endforeach;
331 ?>
332 </div>
333 </div>
334 <?php endif; ?>
335 <?php else: ?>
336 <!-- Recurring Invoice Promotion for Free Users -->
337 <div class="mt-6 border-t border-gray-200 pt-4">
338 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
339 <div class="flex items-start">
340 <div class="flex-shrink-0">
341 <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
342 <i class="fas fa-sync-alt text-white text-sm"></i>
343 </div>
344 </div>
345 <div class="ml-3 flex-1">
346 <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Recurring Invoices', 'easy-invoice'); ?></h3>
347 <p class="text-sm text-gray-600 mb-3"><?php _e('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice'); ?></p>
348 <div class="flex items-center space-x-3 mb-3">
349 <div class="flex items-center text-xs text-gray-500">
350 <i class="fas fa-check text-green-500 mr-1"></i>
351 <span><?php _e('Automated billing cycles', 'easy-invoice'); ?></span>
352 </div>
353 <div class="flex items-center text-xs text-gray-500">
354 <i class="fas fa-check text-green-500 mr-1"></i>
355 <span><?php _e('Custom intervals', 'easy-invoice'); ?></span>
356 </div>
357 <div class="flex items-center text-xs text-gray-500">
358 <i class="fas fa-check text-green-500 mr-1"></i>
359 <span><?php _e('Auto-send notifications', 'easy-invoice'); ?></span>
360 </div>
361 </div>
362 <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Recurring Invoices', 'easy-invoice'); ?>', '<?php _e('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice'); ?>'); } else { alert('<?php _e('This is a premium feature. Please upgrade to Pro to access recurring invoices.', 'easy-invoice'); ?>'); }">
363 <i class="fas fa-crown mr-1.5"></i>
364 <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
365 </button>
366 </div>
367 </div>
368 </div>
369 </div>
370 <?php endif; ?>
371
372 <!-- Subscription Invoice Section -->
373 <?php if (easy_invoice_has_pro()): ?>
374 <?php
375 // Check if subscription is actually enabled
376 $subscription_enabled = get_option('easy_invoice_pro_subscription_enabled', '0');
377 if ($subscription_enabled):
378 $subscription_fields = $form_manager->getFieldsBySection('invoice-tab', 'subscription');
379 if (!empty($subscription_fields)):
380 ?>
381 <div class="mt-8 border-t border-gray-200 pt-6">
382 <h3 class="text-lg font-medium text-gray-900"><?php _e('Subscription Settings', 'easy-invoice-pro'); ?></h3>
383 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
384 <?php
385 foreach ($subscription_fields as $field):
386 echo $form_manager->renderField($field, $invoice);
387 endforeach;
388 ?>
389 </div>
390 </div>
391 <?php endif; ?>
392 <?php endif; ?>
393 <?php else: ?>
394 <!-- Subscription Invoice Promotion for Free Users -->
395 <div class="mt-6 border-t border-gray-200 pt-4">
396 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
397 <div class="flex items-start">
398 <div class="flex-shrink-0">
399 <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
400 <i class="fas fa-calendar-alt text-white text-sm"></i>
401 </div>
402 </div>
403 <div class="ml-3 flex-1">
404 <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Partial Payment', 'easy-invoice'); ?></h3>
405 <p class="text-sm text-gray-600 mb-3"><?php _e('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice'); ?></p>
406 <div class="flex items-center space-x-3 mb-3">
407 <div class="flex items-center text-xs text-gray-500">
408 <i class="fas fa-check text-green-500 mr-1"></i>
409 <span><?php _e('Flexible payment schedules', 'easy-invoice'); ?></span>
410 </div>
411 <div class="flex items-center text-xs text-gray-500">
412 <i class="fas fa-check text-green-500 mr-1"></i>
413 <span><?php _e('Payment tracking', 'easy-invoice'); ?></span>
414 </div>
415 <div class="flex items-center text-xs text-gray-500">
416 <i class="fas fa-check text-green-500 mr-1"></i>
417 <span><?php _e('Balance management', 'easy-invoice'); ?></span>
418 </div>
419 </div>
420 <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Partial Payment', 'easy-invoice'); ?>', '<?php _e('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice'); ?>'); } else { alert('<?php _e('This is a premium feature. Please upgrade to Pro to access partial payment functionality.', 'easy-invoice'); ?>'); }">
421 <i class="fas fa-crown mr-1.5"></i>
422 <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
423 </button>
424 </div>
425 </div>
426 </div>
427 </div>
428 <?php endif; ?>
429
430 <!-- Notes & Terms Section -->
431 <div class="mt-8 border-t border-gray-200 pt-6">
432 <h3 class="text-lg font-medium text-gray-900"><?php _e('Notes & Terms', 'easy-invoice'); ?></h3>
433 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
434 <?php
435 // Only render notes and terms fields, not all invoice tab fields
436 $notes_fields = $form_manager->getFieldsBySection('invoice-tab', 'notes');
437 foreach ($notes_fields as $field):
438 echo $form_manager->renderField($field, $invoice);
439 endforeach;
440 ?>
441 </div>
442 </div>
443
444 <?php elseif ($tab['id'] === 'items-tab'): ?>
445 <!-- Items Tab Content -->
446 <div class="flex justify-between items-center mb-6">
447 <h2 class="text-lg font-medium text-gray-900"><?php _e('Invoice Items', 'easy-invoice'); ?></h2>
448 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
449 <i class="fas fa-chevron-down mr-1"></i>
450 <span><?php _e('Collapse All', 'easy-invoice'); ?></span>
451 </button>
452 </div>
453
454 <div class="space-y-4 invoice-items-container">
455 <?php
456 // If editing an existing invoice, directly output invoice items
457 if ($invoice && $invoice->getItems()) :
458 $index = 0;
459 foreach ($invoice->getItems() as $item) :
460 // Generate a unique ID for the item
461 $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999);
462
463 // Get display title for header using dynamic field access
464 $title = $item->getName();
465
466
467 if (empty($title) && method_exists($item, 'getData')) {
468 $item_data = $item->getData();
469 if (is_array($item_data) && isset($item_data['title'])) {
470 $title = $item_data['title'];
471 }
472 }
473
474 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
475 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
476 ?>
477 <div class="invoice-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; ?>">
478 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
479 <div class="flex items-center">
480 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
481 <i class="fas fa-chevron-down text-sm"></i>
482 </button>
483 <h3 class="text-base font-normal text-gray-800 flex-1">
484 <?php
485 echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
486 ?>
487 </h3>
488 </div>
489 <div class="flex items-center gap-3">
490 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
491 <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>
492 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
493 </div>
494 <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'); ?>">
495 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
496 <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"/>
497 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
498 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
499 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
500 </svg>
501 </button>
502 <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">
503 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
504 </button>
505 </div>
506 </div>
507 <div class="item-content">
508 <?php echo $form_manager->generateExistingItemHtml($item, $index); ?>
509 </div>
510 </div>
511 <?php
512 $index++;
513 endforeach;
514 else:
515 // For new invoices, render one empty item by default
516 $index = 0;
517 // Get the item fields config
518 $item_fields = $form_manager->getItemFields();
519 $title_placeholder = '';
520 foreach ($item_fields as $field) {
521 if ($field['name'] === 'title') {
522 $title_placeholder = $field['placeholder'] ?? __('Item', 'easy-invoice');
523 break;
524 }
525 }
526 // Try to get the value from POST (if form was submitted and errored)
527 $item_title = isset($_POST['items'][0]['title']) ? trim($_POST['items'][0]['title']) : '';
528 ?>
529 <div class="invoice-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;">
530 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
531 <div class="flex items-center">
532 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
533 <i class="fas fa-chevron-down text-sm"></i>
534 </button>
535 <h3 class="text-base font-normal text-gray-800 flex-1">
536 <?php
537 echo !empty($item_title) ? esc_html($item_title) : esc_html($title_placeholder);
538 ?>
539 </h3>
540 </div>
541 <div class="flex items-center gap-3">
542 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
543 <span class="quantity-summary">1</span> × $<span class="price-summary">0.00</span>
544 = $<span class="total-summary">0.00</span>
545 </div>
546 <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'); ?>">
547 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
548 <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"/>
549 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
550 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
551 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
552 </svg>
553 </button>
554 <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">
555 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
556 </button>
557 </div>
558 </div>
559 <div class="item-content">
560 <?php echo $form_manager->generateItemTemplateHtml(); ?>
561 </div>
562 </div>
563 <?php endif; ?>
564 </div>
565
566 <div class="mt-6">
567 <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">
568 <div class="flex items-center">
569 <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">
570 <i class="fas fa-plus text-indigo-600 text-lg"></i>
571 </div>
572 <div class="text-left">
573 <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span>
574 <p class="text-sm text-indigo-600 mt-0.5"><?php _e('Click to add a new line item', 'easy-invoice'); ?></p>
575 </div>
576 </div>
577 </button>
578 </div>
579 <?php elseif ($tab['id'] === 'client-tab'): ?>
580 <!-- Client Tab Content -->
581 <div class="mb-6">
582 <label class="block text-sm font-medium text-gray-700 mb-3"><?php _e('Select Existing Client', 'easy-invoice'); ?></label>
583
584 <!-- Client Search and Selection -->
585 <div class="relative">
586 <div class="flex items-center space-x-3">
587 <div class="relative flex-grow">
588 <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
589 <i class="fas fa-search text-gray-400"></i>
590 </div>
591 <input
592 type="text"
593 id="client-search-input"
594 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"
595 placeholder="<?php _e('Search clients by name, email, or company...', 'easy-invoice'); ?>"
596 autocomplete="off"
597 >
598 </div>
599 <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">
600 <i class="fas fa-plus -ml-1 mr-2"></i>
601 <?php _e('New Client', 'easy-invoice'); ?>
602 </button>
603 </div>
604
605 <!-- Search Results Dropdown -->
606 <div id="client-search-results" class="absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto" style="display: none;">
607 <div id="client-search-loading" class="p-6 text-center text-gray-500" style="display: none;">
608 <i class="fas fa-spinner fa-spin mr-2"></i>
609 <?php _e('Searching...', 'easy-invoice'); ?>
610 </div>
611 <div id="client-search-empty" class="p-6 text-center text-gray-500" style="display: none;">
612 <i class="fas fa-search mr-2"></i>
613 <?php _e('No clients found', 'easy-invoice'); ?>
614 </div>
615 <div id="client-search-list" class="py-2">
616 <!-- Client options will be populated here -->
617 </div>
618 </div>
619
620 <!-- Selected Client Display -->
621 <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 ($invoice && $invoice->getClientId()) ? '' : 'hidden'; ?>">
622 <div class="flex items-center justify-between">
623 <div class="flex items-center">
624 <div class="flex-shrink-0">
625 <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">
626 <i class="fas fa-user text-white text-sm"></i>
627 </div>
628 </div>
629 <div class="ml-4">
630 <p id="selected-client-name" class="text-sm font-semibold text-gray-900">
631 <?php
632 if ($invoice && $invoice->getClientId()) {
633 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
634 if ($client) {
635 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
636 } else {
637 echo esc_html__('—', 'easy-invoice');
638 }
639 } else {
640 echo esc_html__('—', 'easy-invoice');
641 }
642 ?>
643 </p>
644 <p id="selected-client-email" class="text-sm text-gray-600">
645 <?php
646 if ($invoice && $invoice->getClientId()) {
647 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
648 if ($client) {
649 echo esc_html($client->getEmail() ?: esc_html__('—', 'easy-invoice'));
650 } else {
651 echo esc_html__('—', 'easy-invoice');
652 }
653 } else {
654 echo esc_html__('—', 'easy-invoice');
655 }
656 ?>
657 </p>
658 </div>
659 </div>
660 <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">
661 <i class="fas fa-times"></i>
662 </button>
663 </div>
664 </div>
665 </div>
666
667 <!-- Hidden select for form submission -->
668 <select id="select-client" class="hidden">
669 <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option>
670 <?php
671 // Ensure $clients is defined
672 if (!isset($clients)) {
673 $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
674 $clients = $client_repository->all();
675 }
676
677 // Output client options
678 if (!empty($clients)) {
679 foreach ($clients as $client) {
680 // Get the WordPress user data directly
681 $user = get_user_by('id', $client->getId());
682 if (!$user) {
683 continue;
684 }
685
686 // Use Client model properties first, fallback to WordPress user fields
687 $business_name = $client->business_client_name ?: '';
688 $first_name = $client->first_name ?: $user->first_name ?: '';
689 $last_name = $client->last_name ?: $user->last_name ?: '';
690 $client_email = $client->email ?: $user->user_email ?: '';
691
692 // Create display name
693 $client_name = $business_name ?: ($first_name . ' ' . $last_name);
694 if (empty(trim($client_name))) {
695 $client_name = $user->display_name ?: 'User ' . $client->getId();
696 }
697
698 $selected = $invoice && $invoice->getClientId() == $client->getId() ? 'selected' : '';
699 printf(
700 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
701 esc_attr($client->getId()),
702 $selected,
703 esc_attr($client_name),
704 esc_attr($client_email),
705 esc_html($client_name),
706 esc_html($client_email)
707 );
708 }
709 }
710 ?>
711 </select>
712 </div>
713
714 <div class="border-t border-gray-200 pt-4 mt-4">
715 <input type="hidden" name="client_id" id="client-id" value="<?php echo $invoice ? esc_attr($invoice->getClientId()) : ''; ?>">
716
717 <!-- Client Information Display -->
718 <div id="client-info-display" class="mt-6 <?php echo $invoice && $invoice->getClientId() ? '' : 'hidden'; ?>">
719 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
720 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
721 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
722 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
723 <span><?php _e('Client Information', 'easy-invoice'); ?></span>
724 </h4>
725 <a href="<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($invoice && $invoice->getClientId() ? $invoice->getClientId() : '')); ?>"
726 id="edit-selected-client"
727 class="text-sm text-indigo-600 hover:text-indigo-800 font-medium"
728 target="_blank">
729 <i class="fas fa-pen mr-1"></i>
730 <?php _e('Edit', 'easy-invoice'); ?>
731 </a>
732 </div>
733
734 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
735 <!-- Client Name -->
736 <div class="flex items-start group">
737 <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>
738 <div class="ml-3">
739 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Name', 'easy-invoice'); ?></label>
740 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
741 <?php
742 if ($invoice && $invoice->getClientId()) {
743 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
744 if ($client) {
745 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
746 } else {
747 echo esc_html__('—', 'easy-invoice');
748 }
749 } else {
750 echo esc_html__('—', 'easy-invoice');
751 }
752 ?>
753 </p>
754 </div>
755 </div>
756
757 <!-- Company Name -->
758 <div class="flex items-start group">
759 <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>
760 <div class="ml-3">
761 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Company', 'easy-invoice'); ?></label>
762 <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1">
763 <?php
764 if ($invoice && $invoice->getClientId()) {
765 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
766 if ($client) {
767 echo esc_html($client->getBusinessClientName() ?: esc_html__('—', 'easy-invoice'));
768 } else {
769 echo esc_html__('—', 'easy-invoice');
770 }
771 } else {
772 echo esc_html__('—', 'easy-invoice');
773 }
774 ?>
775 </p>
776 </div>
777 </div>
778
779 <!-- Email -->
780 <div class="flex items-start group">
781 <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>
782 <div class="ml-3">
783 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Email', 'easy-invoice'); ?></label>
784 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
785 <?php
786 if ($invoice && $invoice->getClientId()) {
787 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
788 if ($client) {
789 echo esc_html($client->getEmail() ?: esc_html__('—', 'easy-invoice'));
790 } else {
791 echo esc_html__('—', 'easy-invoice');
792 }
793 } else {
794 echo esc_html__('—', 'easy-invoice');
795 }
796 ?>
797 </p>
798 </div>
799 </div>
800
801 <!-- Phone -->
802 <div class="flex items-start group">
803 <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>
804 <div class="ml-3">
805 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Phone', 'easy-invoice'); ?></label>
806 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
807 <?php
808 if ($invoice && $invoice->getClientId()) {
809 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
810 if ($client) {
811 echo esc_html($client->getExtraInfo() ?: esc_html__('—', 'easy-invoice'));
812 } else {
813 echo esc_html__('—', 'easy-invoice');
814 }
815 } else {
816 echo esc_html__('—', 'easy-invoice');
817 }
818 ?>
819 </p>
820 </div>
821 </div>
822
823 <!-- Website -->
824 <div class="flex items-start group md:col-span-2">
825 <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>
826 <div class="ml-3">
827 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Website', 'easy-invoice'); ?></label>
828 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
829 <?php
830 if ($invoice && $invoice->getClientId()) {
831 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
832 if ($client) {
833 echo esc_html($client->getWebsite() ?: esc_html__('—', 'easy-invoice'));
834 } else {
835 echo esc_html__('—', 'easy-invoice');
836 }
837 } else {
838 echo esc_html__('—', 'easy-invoice');
839 }
840 ?>
841 </p>
842 </div>
843 </div>
844
845 <!-- Address -->
846 <div class="flex items-start group md:col-span-2">
847 <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>
848 <div class="ml-3">
849 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Address', 'easy-invoice'); ?></label>
850 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
851 <?php
852 if ($invoice && $invoice->getClientId()) {
853 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
854 if ($client) {
855 echo esc_html($client->getAddress() ?: esc_html__('—', 'easy-invoice'));
856 } else {
857 echo esc_html__('—', 'easy-invoice');
858 }
859 } else {
860 echo esc_html__('—', 'easy-invoice');
861 }
862 ?>
863 </p>
864 </div>
865 </div>
866 </div>
867 </div>
868 </div>
869
870 <!-- No Client Selected Message -->
871 <div id="no-client-message" class="mt-4 text-center py-6 border-2 border-dashed border-gray-300 rounded-lg <?php echo $invoice && $invoice->getClientId() ? 'hidden' : ''; ?>">
872 <p class="text-gray-600"><?php _e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p>
873 </div>
874 </div>
875 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
876 <!-- Discount & Tax Tab Content -->
877 <div>
878 <h3 class="text-lg font-medium text-gray-900"><?php _e('Discount & Tax Settings', 'easy-invoice'); ?></h3>
879 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
880 <?php
881 // Get all discount and tax fields
882 $discount_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
883 $tax_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'tax');
884 $additional_tax_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'additional_tax');
885
886 // Combine and sort by order
887 $all_fields = array_merge($discount_fields, $tax_fields, $additional_tax_fields);
888 usort($all_fields, function($a, $b) {
889 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
890 });
891
892 foreach ($all_fields as $field):
893 echo $form_manager->renderField($field, $invoice);
894 endforeach;
895 ?>
896 </div>
897 </div>
898 <?php elseif ($tab['id'] === 'payments-tab'): ?>
899 <!-- Payment Tab Content -->
900 <div>
901 <h3 class="text-lg font-medium text-gray-900"><?php _e('Payment Information', 'easy-invoice'); ?></h3>
902 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
903 <?php
904 $payment_fields = $form_manager->getFieldsBySection('payments-tab', 'payment');
905 foreach ($payment_fields as $field):
906 echo $form_manager->renderField($field, $invoice);
907 endforeach;
908 ?>
909 </div>
910
911 <!-- Currency Section -->
912 <div class="mt-8 border-t border-gray-200 pt-6">
913 <h3 class="text-lg font-medium text-gray-900"><?php _e('Currency', 'easy-invoice'); ?></h3>
914 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
915 <?php
916 $currency_fields = $form_manager->getFieldsBySection('payments-tab', 'currency');
917 foreach ($currency_fields as $field):
918 echo $form_manager->renderField($field, $invoice);
919 endforeach;
920 ?>
921 </div>
922 </div>
923 </div>
924 <?php endif; ?>
925 </div>
926 <?php endforeach; ?>
927 </form>
928 </div>
929
930 <!-- Template Gallery Modal -->
931 <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">
932 <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">
933 <div class="flex justify-between items-center mb-4">
934 <h3 class="text-xl font-medium text-gray-900"><?php _e('Browse Invoice Templates', 'easy-invoice'); ?></h3>
935 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
936 <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span>
937 <i class="fas fa-times text-xl"></i>
938 </button>
939 </div>
940
941 <div class="templates-grid grid grid-cols-2 md:grid-cols-3 gap-4 mb-4 max-h-[45vh] overflow-y-auto p-2">
942 <?php
943 // Display all template options in the modal
944 foreach ($all_templates as $template_id => $template) :
945 $is_current = ($current_template === $template_id);
946 $selected_class = $is_current ? 'selected' : '';
947 ?>
948 <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'; ?>">
949 <div class="p-4 border rounded-lg">
950 <div class="flex items-center mb-2">
951 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
952 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
953 </div>
954 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
955 <div class="mt-2 flex-grow">
956 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
957 </div>
958 </div>
959 </div>
960 </div>
961 <?php endforeach; ?>
962 </div>
963
964 <div class="flex justify-end mt-4 space-x-3">
965 <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">
966 <?php _e('Apply', 'easy-invoice'); ?>
967 </button>
968 <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">
969 <?php _e('Close', 'easy-invoice'); ?>
970 </button>
971 </div>
972 </div>
973 </div>
974
975 <!-- Hidden Template for Invoice Items -->
976 <template id="invoice-item-template" style="display: none;">
977 <div class="invoice-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;">
978 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
979 <div class="flex items-center">
980 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
981 <i class="fas fa-chevron-down text-sm"></i>
982 </button>
983 <h3 class="text-base font-normal text-gray-800 flex-1">
984 <?php
985 echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
986 ?>
987 </h3>
988 </div>
989 <div class="flex items-center gap-3">
990 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
991 <span class="quantity-summary">1</span> × $<span class="price-summary">0.00</span>
992 = $<span class="total-summary">0.00</span>
993 </div>
994 <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'); ?>">
995 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
996 <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"/>
997 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
998 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
999 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
1000 </svg>
1001 </button>
1002 <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">
1003 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
1004 </button>
1005 </div>
1006 </div>
1007 <div class="item-content">
1008 <?php echo $form_manager->generateItemTemplateHtml(); ?>
1009 </div>
1010 </div>
1011 </template>
1012
1013 <!-- Add Client Modal -->
1014 <div id="add_client_modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50">
1015 <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-2/3 lg:w-1/2 shadow-lg rounded-md bg-white">
1016 <div class="flex justify-between items-center mb-4">
1017 <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
1018 <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
1019 <span class="sr-only">Close</span>
1020 <i class="fas fa-times"></i>
1021 </button>
1022 </div>
1023
1024 <?php
1025 // Unset the $client variable from the foreach loop to ensure clean add form
1026 unset($client);
1027 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
1028 ?>
1029 </div>
1030 </div>
1031
1032 <?php
1033
1034 // Always localize script with AJAX URL and nonce
1035 wp_localize_script('easy-invoice-form', 'easyInvoice', array(
1036 'ajaxUrl' => admin_url('admin-ajax.php'),
1037 'nonce' => wp_create_nonce('easy_invoice_nonce'),
1038 'clientData' => $client_data ?: array()
1039 ));
1040 ?>
1041
1042 <script>
1043 jQuery(document).ready(function($) {
1044
1045
1046 // Client search functionality for invoice form
1047 var searchTimeout;
1048
1049 // Initialize existing client data
1050 var existingClientId = '<?php echo $invoice ? esc_js($invoice->getClientId()) : ''; ?>';
1051 var existingClientName = '<?php
1052 if ($invoice && $invoice->getClientId()) {
1053 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
1054 if ($client) {
1055 echo esc_js($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
1056 }
1057 }
1058 ?>';
1059 var existingClientEmail = '<?php
1060 if ($invoice && $invoice->getClientId()) {
1061 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
1062 if ($client) {
1063 echo esc_js($client->getEmail() ?: '');
1064 }
1065 }
1066 ?>';
1067 var existingClientData = <?php echo json_encode($client_data); ?>;
1068
1069 if (existingClientId && existingClientName && existingClientName !== '-') {
1070 // Show the selected client display and client info display
1071 $('#selected-client-display').show();
1072 $('#client-info-display').show();
1073 $('#no-client-message').hide();
1074
1075 // Set the select client dropdown value
1076 $('#select-client').val(existingClientId);
1077
1078 // Populate the search input with the existing client name
1079 $('#client-search-input').val(existingClientName);
1080
1081
1082 }
1083
1084 // Client search input functionality
1085 $('#client-search-input').on('input', function() {
1086 const query = $(this).val().trim();
1087 clearTimeout(searchTimeout);
1088
1089 // Don't hide results immediately for short queries - let the search function handle it
1090 if (query.length === 0) {
1091 // Show all clients when input is cleared
1092 searchClients('');
1093 return;
1094 }
1095
1096 searchTimeout = setTimeout(function() {
1097 searchClients(query);
1098 }, 300);
1099 });
1100
1101 // Focus on search input - ensure client tab is active
1102 $('#client-search-input').on('focus', function() {
1103 // Switch to client tab first to ensure the search results are visible
1104 if (window.switchToTab) {
1105 window.switchToTab('client-tab');
1106 } else {
1107 // Fallback: manually switch tabs
1108 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
1109 $('.tab-content').addClass('hidden');
1110 $('.tab-button[data-tab="client-tab"]').addClass('border-indigo-500 text-indigo-600');
1111 $('#client-tab').removeClass('hidden');
1112 }
1113 });
1114
1115 // Click on search input to show all clients
1116 $("#client-search-input").on("click", function() {
1117 // Show the dropdown and call AJAX to populate it
1118 $('#client-search-results').show();
1119 $('#client-search-loading').show();
1120 $('#client-search-empty').hide();
1121
1122 // Call the AJAX method to populate the dropdown
1123 if (typeof easyInvoice !== 'undefined' && easyInvoice.ajaxUrl) {
1124 searchClients('');
1125 } else {
1126 $('#client-search-loading').hide();
1127 $('#client-search-empty').show();
1128 }
1129 });
1130
1131 // Client selection
1132 $(document).on('click', '.client-option', function(e) {
1133 e.preventDefault();
1134 e.stopPropagation();
1135
1136 const clientId = $(this).data('client-id');
1137 const clientName = $(this).data('client-name');
1138 const clientEmail = $(this).data('client-email');
1139 const clientCompany = $(this).data('client-company');
1140 const clientPhone = $(this).data('client-phone');
1141 const clientWebsite = $(this).data('client-website');
1142 const clientAddress = $(this).data('client-address');
1143
1144 // Create client data object with all available information
1145 const clientData = {
1146 name: clientName,
1147 email: clientEmail,
1148 company: clientCompany,
1149 phone: clientPhone,
1150 website: clientWebsite,
1151 address: clientAddress
1152 };
1153
1154 selectClient(clientId, clientName, clientEmail, clientData);
1155 $('#client-search-results').hide();
1156 $('#client-search-input').val('');
1157 });
1158
1159 // Alternative click handler for client options
1160 $('#client-search-list').on('click', '.client-option', function(e) {
1161 e.preventDefault();
1162 e.stopPropagation();
1163
1164 const clientId = $(this).data('client-id');
1165 const clientName = $(this).data('client-name');
1166 const clientEmail = $(this).data('client-email');
1167 const clientCompany = $(this).data('client-company');
1168 const clientPhone = $(this).data('client-phone');
1169 const clientWebsite = $(this).data('client-website');
1170 const clientAddress = $(this).data('client-address');
1171
1172 // Create client data object with all available information
1173 const clientData = {
1174 name: clientName,
1175 email: clientEmail,
1176 company: clientCompany,
1177 phone: clientPhone,
1178 website: clientWebsite,
1179 address: clientAddress
1180 };
1181
1182 selectClient(clientId, clientName, clientEmail, clientData);
1183 $('#client-search-results').hide();
1184 $('#client-search-input').val('');
1185 });
1186
1187 // Clear client selection
1188 $('#clear-client-selection').on('click', function() {
1189 clearClientSelection();
1190 });
1191
1192 // Add new client button
1193 $('#add-new-client-btn').on('click', function() {
1194 $('#add_client_modal').show();
1195 });
1196
1197 // Close modal
1198 $('.close-modal').on('click', function() {
1199 $('#add_client_modal').hide();
1200 });
1201
1202 // Functions
1203 function searchClients(query) {
1204 $('#client-search-loading').show();
1205 $('#client-search-empty').hide();
1206 $('#client-search-list').empty();
1207
1208 // If query is empty or less than 2 characters, show all clients
1209 if (!query || query.trim().length < 2) {
1210 query = ''; // Set to empty string to get all clients
1211 }
1212
1213 $.ajax({
1214 url: easyInvoice.ajaxUrl,
1215 type: 'POST',
1216 data: {
1217 action: 'easy_invoice_search_clients',
1218 query: query,
1219 nonce: easyInvoice.nonce
1220 },
1221 success: function(response) {
1222 $('#client-search-loading').hide();
1223
1224 if (response.success && response.data.length > 0) {
1225 response.data.forEach(function(client) {
1226 const option = $(`
1227 <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
1228 data-client-id="${client.id}"
1229 data-client-name="${client.name.replace(/"/g, '&quot;')}"
1230 data-client-email="${client.email.replace(/"/g, '&quot;')}"
1231 data-client-company="${(client.company || '').replace(/"/g, '&quot;')}"
1232 data-client-phone="${(client.phone || '').replace(/"/g, '&quot;')}"
1233 data-client-website="${(client.website || '').replace(/"/g, '&quot;')}"
1234 data-client-address="${(client.address || '').replace(/"/g, '&quot;')}"
1235 onclick="selectClientFromOption(this)">
1236 <div class="font-medium text-gray-900">${client.name}</div>
1237 <div class="text-sm text-gray-600">${client.email}</div>
1238 </div>
1239 `);
1240
1241 $('#client-search-list').append(option);
1242 });
1243 $('#client-search-results').show();
1244 } else {
1245 $('#client-search-empty').show();
1246 $('#client-search-results').show();
1247 }
1248 },
1249 error: function(xhr, status, error) {
1250 $('#client-search-loading').hide();
1251 $('#client-search-empty').show();
1252 $('#client-search-results').show();
1253 }
1254 });
1255 }
1256
1257 function selectClient(clientId, clientName, clientEmail, clientData = null) {
1258
1259 $('#select-client').val(clientId);
1260 $('#client-id').val(clientId);
1261 $('#selected-client-name').text(clientName);
1262 $('#selected-client-email').text(clientEmail);
1263
1264 $('#selected-client-display').show().removeClass('hidden');
1265 $('#client-info-display').show().removeClass('hidden');
1266 $('#no-client-message').hide().removeClass('hidden');
1267
1268 // Update the edit client button URL
1269 $('#edit-selected-client').attr('href', '<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id='); ?>' + clientId);
1270
1271 // If we have full client data, populate all fields directly
1272 if (clientData) {
1273 $('#display-client-name').text(clientData.name || clientName || '-');
1274 $('#display-client-company').text(clientData.company || '-');
1275 $('#display-client-email').text(clientData.email || clientEmail || '-');
1276 $('#display-client-phone').text(clientData.phone || '-');
1277 $('#display-client-website').text(clientData.website || '-');
1278 $('#display-client-address').text(clientData.address || '-');
1279 } else {
1280 // Fallback to just the basic info we have
1281 $('#display-client-name').text(clientName);
1282 $('#display-client-email').text(clientEmail);
1283 $('#display-client-company').text('-');
1284 $('#display-client-phone').text('-');
1285 $('#display-client-website').text('-');
1286 $('#display-client-address').text('-');
1287 }
1288 }
1289
1290 function clearClientSelection() {
1291 $('#select-client').val('');
1292 $('#client-id').val('');
1293 $('#selected-client-display').hide();
1294 $('#client-info-display').hide();
1295 $('#no-client-message').show();
1296
1297 // Clear display fields
1298 $('#display-client-name, #display-client-email, #display-client-company, #display-client-phone, #display-client-website, #display-client-address').text('-');
1299 }
1300
1301 // Global function for inline onclick
1302 window.selectClientFromOption = function(element) {
1303 const $element = $(element);
1304 const clientId = $element.data('client-id');
1305 const clientName = $element.data('client-name');
1306 const clientEmail = $element.data('client-email');
1307 const clientCompany = $element.data('client-company');
1308 const clientPhone = $element.data('client-phone');
1309 const clientWebsite = $element.data('client-website');
1310 const clientAddress = $element.data('client-address');
1311
1312 // Create client data object with all available information
1313 const clientData = {
1314 name: clientName,
1315 email: clientEmail,
1316 company: clientCompany,
1317 phone: clientPhone,
1318 website: clientWebsite,
1319 address: clientAddress
1320 };
1321
1322 selectClient(clientId, clientName, clientEmail, clientData);
1323 $('#client-search-results').hide();
1324 $('#client-search-input').val('');
1325 };
1326 });
1327 </script>
1328
1329 <script>
1330 // Invoice field configuration and pre-loaded data
1331 window.easyInvoice = window.easyInvoice || {};
1332 <?php
1333 $json_field_config = json_encode($invoice_field_config);
1334 ?>
1335 window.easyInvoice.fieldConfig = <?php echo $json_field_config; ?>;
1336 window.easyInvoice.adminUrl = '<?php echo admin_url(); ?>';
1337 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
1338 window.easyInvoice.translations = {
1339 premiumTemplates: '<?php _e('Premium Templates', 'easy-invoice'); ?>',
1340 premiumTemplatesDescription: '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice'); ?>',
1341 premiumFeatureMessage: '<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>',
1342 selectTemplateFirst: '<?php _e('Please select a template first', 'easy-invoice'); ?>'
1343 };
1344 <?php if ($client_data): ?>
1345 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
1346 <?php endif; ?>
1347 </script>
1348