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

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

1,393 lines 94.3 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 = apply_filters('easy_invoice_invoice_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" aria-label="<?php esc_attr_e( 'Invoice form sections', 'easy-invoice' ); ?>">
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 <?php
184 // Lifecycle-stage lock check. Addons (PartialPayments)
185 // can return false here to mark this invoice as
186 // immutable — currently used to freeze paid deposit
187 // invoices so admins don't accidentally invalidate
188 // the linked balance pair. Default: true (no listener).
189 $ei_can_edit = ! $invoice_id || apply_filters('easy_invoice_can_edit_invoice', true, (int) $invoice_id);
190 if (! $ei_can_edit && $invoice_id):
191 $ei_balance_id = (int) get_post_meta((int) $invoice_id, '_balance_invoice_id', true);
192 $ei_paid_date = (string) get_post_meta((int) $invoice_id, '_easy_invoice_payment_date', true);
193 ?>
194 <div class="mx-6 mt-4 mb-4 rounded-md border border-amber-200 bg-amber-50 p-4">
195 <div class="flex items-start">
196 <i class="fas fa-lock text-amber-500 mt-0.5 mr-3"></i>
197 <div class="flex-1 text-sm text-amber-800">
198 <p class="font-semibold mb-1"><?php esc_html_e('This deposit invoice is locked.', 'easy-invoice'); ?></p>
199 <p class="mb-2">
200 <?php
201 if ($ei_paid_date) {
202 printf(
203 /* translators: %s: payment date */
204 esc_html__('A payment was recorded on %s. Items and totals are frozen so the deposit receipt the customer already saw stays accurate. To add new charges, open the linked balance invoice instead.', 'easy-invoice'),
205 esc_html($ei_paid_date)
206 );
207 } else {
208 esc_html_e('A payment has been recorded against this deposit. Items and totals are frozen so the deposit receipt the customer already saw stays accurate. To add new charges, open the linked balance invoice instead.', 'easy-invoice');
209 }
210 ?>
211 </p>
212 <?php if ($ei_balance_id > 0): ?>
213 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $ei_balance_id)); ?>"
214 class="inline-flex items-center px-3 py-1.5 rounded text-xs font-medium bg-amber-600 text-white hover:bg-amber-700">
215 <i class="fas fa-arrow-right mr-1"></i>
216 <?php esc_html_e('Open the balance invoice', 'easy-invoice'); ?>
217 </a>
218 <?php endif; ?>
219 </div>
220 </div>
221 </div>
222 <?php endif; ?>
223 <form id="invoice-form"<?php echo $ei_can_edit ? '' : ' data-ei-locked="1"'; ?>>
224 <input type="hidden" id="invoice-id" name="invoice_id" value="<?php echo $invoice_id ? esc_attr($invoice_id) : ''; ?>">
225
226 <input type="hidden" id="invoice_nonce" name="invoice_nonce" value="<?php echo wp_create_nonce('easy_invoice_nonce'); ?>">
227
228 <?php foreach ($tabs as $tab): ?>
229 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
230 <?php if ($tab['id'] === 'invoice-tab'): ?>
231 <div class="mb-8 pb-6 border-b border-gray-200">
232 <div class="flex justify-between items-center mb-4">
233 <h3 class="text-lg font-medium text-gray-900"><?php _e('Invoice Template', 'easy-invoice'); ?></h3>
234 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
235 <span class="mr-1"><?php _e('Browse Invoice Templates', 'easy-invoice'); ?></span>
236 <i class="fas fa-chevron-right"></i>
237 </button>
238 </div>
239
240 <?php
241 // Get the current template (default to 'standard')
242 $current_template = $invoice ? $invoice->getTemplate() : 'standard';
243
244 if (empty($current_template)) {
245 $current_template = 'standard';
246 }
247
248 // Always include the hidden input for the template field
249 ?>
250 <input type="hidden" name="invoice_template" value="<?php echo esc_attr($current_template); ?>">
251 <?php
252
253 // Check if this is the free version
254 $is_free_version = !easy_invoice_has_pro();
255
256
257
258 // Show all templates grid
259 ?>
260 <div class="grid grid-cols-2 md:grid-cols-3 gap-4" id="main-templates-grid">
261 <?php
262 // Build the main grid templates array - always include current template plus 2 others
263 $main_grid_templates = [];
264
265 // First, add the current template if it exists in all_templates
266 if (isset($all_templates[$current_template])) {
267 // For free users, if current template is premium, default to standard
268 if ($is_free_version && isset($all_templates[$current_template]['is_free']) && !$all_templates[$current_template]['is_free']) {
269 $current_template = 'standard';
270 $main_grid_templates['standard'] = $all_templates['standard'];
271 } else {
272 $main_grid_templates[$current_template] = $all_templates[$current_template];
273 }
274 }
275
276 // Add other templates to fill up to 3, prioritizing standard templates
277 $default_templates = ['standard', 'legacy', 'professional'];
278 foreach ($default_templates as $template_id) {
279 if (count($main_grid_templates) >= 3) break;
280 if ($template_id !== $current_template && isset($all_templates[$template_id])) {
281 $main_grid_templates[$template_id] = $all_templates[$template_id];
282 }
283 }
284
285 // If we still don't have 3 templates, add any remaining templates
286 foreach ($all_templates as $template_id => $template) {
287 if (count($main_grid_templates) >= 3) break;
288 if (!isset($main_grid_templates[$template_id])) {
289 $main_grid_templates[$template_id] = $template;
290 }
291 }
292
293 // Display the main grid templates
294 foreach ($main_grid_templates as $template_id => $template) :
295 $checked = ($current_template === $template_id) ? 'checked' : '';
296 $selected_class = ($current_template === $template_id) ? 'selected' : '';
297
298 // Check if this template is premium (not free)
299 $is_premium = isset($template['is_free']) && !$template['is_free'];
300
301 // Add premium overlay classes
302 $premium_classes = '';
303 if ($is_free_version && $is_premium) {
304 $premium_classes = 'premium-template-blurred';
305 }
306 ?>
307 <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'; ?>">
308 <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' : ''; ?>>
309 <div class="p-4 border rounded-lg">
310 <div class="flex items-center mb-2">
311 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
312 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
313 </div>
314 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
315 <div class="mt-2 flex-grow">
316 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
317 </div>
318 </div>
319 </div>
320 </div>
321 <?php endforeach; ?>
322 </div>
323 </div>
324
325 <div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
326 <?php
327 $main_fields = $form_manager->getFields('invoice-tab');
328 foreach ($main_fields as $field):
329 // Skip fields that have a section (like notes) - they're rendered separately
330 // Also skip the template field as it's rendered manually in the template selection section
331 if (!isset($field['section']) && $field['name'] !== 'invoice_template') {
332 echo $form_manager->renderField($field, $invoice);
333 }
334 endforeach;
335 ?>
336 </div>
337
338 <?php if (!$invoice): ?>
339 <div class="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
340 <div class="flex">
341 <div class="flex-shrink-0">
342 <i class="fas fa-info-circle text-blue-400"></i>
343 </div>
344 <div class="ml-3">
345 <p class="text-sm text-blue-700">
346 <?php _e('Invoice number will be automatically generated when you save the invoice.', 'easy-invoice'); ?>
347 </p>
348 </div>
349 </div>
350 </div>
351 <?php endif; ?>
352
353 <?php if (easy_invoice_has_pro()): ?>
354 <?php
355 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
356 if (!empty($recurring_fields)):
357 ?>
358 <div class="mt-8 border-t border-gray-200 pt-6">
359 <h3 class="text-lg font-medium text-gray-900"><?php _e('Recurring Invoice Settings', 'easy-invoice-pro'); ?></h3>
360 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
361 <?php
362 foreach ($recurring_fields as $field):
363 echo $form_manager->renderField($field, $invoice);
364 endforeach;
365 ?>
366 </div>
367 </div>
368 <?php endif; ?>
369 <?php else: ?>
370 <div class="mt-6 border-t border-gray-200 pt-4">
371 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
372 <div class="flex items-start">
373 <div class="flex-shrink-0">
374 <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
375 <i class="fas fa-sync-alt text-white text-sm"></i>
376 </div>
377 </div>
378 <div class="ml-3 flex-1">
379 <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Recurring Invoices', 'easy-invoice'); ?></h3>
380 <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>
381 <div class="flex items-center space-x-3 mb-3">
382 <div class="flex items-center text-xs text-gray-500">
383 <i class="fas fa-check text-green-500 mr-1"></i>
384 <span><?php _e('Automated billing cycles', 'easy-invoice'); ?></span>
385 </div>
386 <div class="flex items-center text-xs text-gray-500">
387 <i class="fas fa-check text-green-500 mr-1"></i>
388 <span><?php _e('Custom intervals', 'easy-invoice'); ?></span>
389 </div>
390 <div class="flex items-center text-xs text-gray-500">
391 <i class="fas fa-check text-green-500 mr-1"></i>
392 <span><?php _e('Auto-send notifications', 'easy-invoice'); ?></span>
393 </div>
394 </div>
395 <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'); ?>'); }">
396 <i class="fas fa-crown mr-1.5"></i>
397 <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
398 </button>
399 </div>
400 </div>
401 </div>
402 </div>
403 <?php endif; ?>
404
405 <?php if (easy_invoice_has_pro()): ?>
406 <?php
407 // Check if subscription is actually enabled
408 $subscription_enabled = get_option('easy_invoice_pro_subscription_enabled', '0');
409 if ($subscription_enabled):
410 $subscription_fields = $form_manager->getFieldsBySection('invoice-tab', 'subscription');
411 if (!empty($subscription_fields)):
412 ?>
413 <div class="mt-8 border-t border-gray-200 pt-6">
414 <h3 class="text-lg font-medium text-gray-900"><?php _e('Subscription Settings', 'easy-invoice-pro'); ?></h3>
415 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
416 <?php
417 foreach ($subscription_fields as $field):
418 echo $form_manager->renderField($field, $invoice);
419 endforeach;
420 ?>
421 </div>
422 </div>
423 <?php endif; ?>
424 <?php endif; ?>
425 <?php else: ?>
426 <div class="mt-6 border-t border-gray-200 pt-4">
427 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
428 <div class="flex items-start">
429 <div class="flex-shrink-0">
430 <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
431 <i class="fas fa-calendar-alt text-white text-sm"></i>
432 </div>
433 </div>
434 <div class="ml-3 flex-1">
435 <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Partial Payment', 'easy-invoice'); ?></h3>
436 <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>
437 <div class="flex items-center space-x-3 mb-3">
438 <div class="flex items-center text-xs text-gray-500">
439 <i class="fas fa-check text-green-500 mr-1"></i>
440 <span><?php _e('Flexible payment schedules', 'easy-invoice'); ?></span>
441 </div>
442 <div class="flex items-center text-xs text-gray-500">
443 <i class="fas fa-check text-green-500 mr-1"></i>
444 <span><?php _e('Payment tracking', 'easy-invoice'); ?></span>
445 </div>
446 <div class="flex items-center text-xs text-gray-500">
447 <i class="fas fa-check text-green-500 mr-1"></i>
448 <span><?php _e('Balance management', 'easy-invoice'); ?></span>
449 </div>
450 </div>
451 <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'); ?>'); }">
452 <i class="fas fa-crown mr-1.5"></i>
453 <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
454 </button>
455 </div>
456 </div>
457 </div>
458 </div>
459 <?php endif; ?>
460
461 <div class="mt-8 border-t border-gray-200 pt-6">
462 <h3 class="text-lg font-medium text-gray-900"><?php _e('Notes & Terms', 'easy-invoice'); ?></h3>
463 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
464 <?php
465 // Only render notes and terms fields, not all invoice tab fields
466 $notes_fields = $form_manager->getFieldsBySection('invoice-tab', 'notes');
467 foreach ($notes_fields as $field):
468 echo $form_manager->renderField($field, $invoice);
469 endforeach;
470 ?>
471 </div>
472 </div>
473
474 <?php elseif ($tab['id'] === 'items-tab'): ?>
475 <div class="flex justify-between items-center mb-6">
476 <h2 class="text-lg font-medium text-gray-900"><?php _e('Invoice Items', 'easy-invoice'); ?></h2>
477 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
478 <i class="fas fa-chevron-down mr-1"></i>
479 <span><?php _e('Collapse All', 'easy-invoice'); ?></span>
480 </button>
481 </div>
482
483 <div class="space-y-4 invoice-items-container">
484 <?php
485 // If editing an existing invoice, directly output invoice items
486 if ($invoice && $invoice->getItems()) :
487 $index = 0;
488 foreach ($invoice->getItems() as $item) :
489 // Generate a unique ID for the item
490 $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999);
491
492 // Get display title for header using dynamic field access
493 $title = $item->getName();
494
495
496 if (empty($title) && method_exists($item, 'getData')) {
497 $item_data = $item->getData();
498 if (is_array($item_data) && isset($item_data['title'])) {
499 $title = $item_data['title'];
500 }
501 }
502
503 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
504 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
505 ?>
506 <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; ?>">
507 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
508 <div class="flex items-center">
509 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
510 <i class="fas fa-chevron-down text-sm"></i>
511 </button>
512 <h3 class="text-base font-normal text-gray-800 flex-1">
513 <?php
514 echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
515 ?>
516 </h3>
517 </div>
518 <div class="flex items-center gap-3">
519 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
520 <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>
521 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
522 </div>
523 <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'); ?>">
524 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
525 <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"/>
526 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
527 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
528 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
529 </svg>
530 </button>
531 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
532 <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php _e('Select from Item Library', 'easy-invoice'); ?>">
533 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
534 <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
535 </svg>
536 </button>
537 <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Save to Item Library', 'easy-invoice'); ?>" data-item-index="<?php echo $index; ?>" data-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>">
538 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
539 <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
540 </svg>
541 </button>
542 <?php endif; ?>
543 <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">
544 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
545 </button>
546 </div>
547 </div>
548 <div class="item-content">
549 <?php echo $form_manager->generateExistingItemHtml($item, $index); ?>
550 </div>
551 </div>
552 <?php
553 $index++;
554 endforeach;
555 else:
556 // For new invoices, render one empty item by default
557 $index = 0;
558 // Get the item fields config
559 $item_fields = $form_manager->getItemFields();
560 $title_placeholder = '';
561 foreach ($item_fields as $field) {
562 if ($field['name'] === 'title') {
563 $title_placeholder = $field['placeholder'] ?? __('Item', 'easy-invoice');
564 break;
565 }
566 }
567 // Try to get the value from POST (if form was submitted and errored)
568 $item_title = isset($_POST['items'][0]['title']) ? trim($_POST['items'][0]['title']) : '';
569 ?>
570 <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;">
571 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
572 <div class="flex items-center">
573 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
574 <i class="fas fa-chevron-down text-sm"></i>
575 </button>
576 <h3 class="text-base font-normal text-gray-800 flex-1">
577 <?php
578 echo !empty($item_title) ? esc_html($item_title) : esc_html($title_placeholder);
579 ?>
580 </h3>
581 </div>
582 <div class="flex items-center gap-3">
583 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
584 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
585 = $<span class="total-summary">0.00</span>
586 </div>
587 <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'); ?>">
588 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
589 <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"/>
590 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
591 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
592 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
593 </svg>
594 </button>
595 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
596 <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php _e('Select from Item Library', 'easy-invoice'); ?>">
597 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
598 <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
599 </svg>
600 </button>
601 <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Save to Item Library', 'easy-invoice'); ?>" data-item-index="0" data-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>">
602 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
603 <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
604 </svg>
605 </button>
606 <?php endif; ?>
607 <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">
608 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
609 </button>
610 </div>
611 </div>
612 <div class="item-content">
613 <?php echo $form_manager->generateItemTemplateHtml(); ?>
614 </div>
615 </div>
616 <?php endif; ?>
617 </div>
618
619 <div class="mt-6">
620 <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">
621 <div class="flex items-center">
622 <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">
623 <i class="fas fa-plus text-indigo-600 text-lg"></i>
624 </div>
625 <div class="text-left">
626 <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span>
627 <p class="text-sm text-indigo-600 mt-0.5"><?php _e('Click to add a new line item', 'easy-invoice'); ?></p>
628 </div>
629 </div>
630 </button>
631 </div>
632 <?php elseif ($tab['id'] === 'client-tab'): ?>
633 <div class="mb-6">
634 <label class="block text-sm font-medium text-gray-700 mb-3"><?php _e('Select Existing Client', 'easy-invoice'); ?></label>
635
636 <div class="relative">
637 <div class="flex items-center space-x-3">
638 <div class="relative flex-grow">
639 <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
640 <i class="fas fa-search text-gray-400"></i>
641 </div>
642 <input
643 type="text"
644 id="client-search-input"
645 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"
646 placeholder="<?php _e('Search clients by name, email, or company...', 'easy-invoice'); ?>"
647 autocomplete="off"
648 >
649 </div>
650 <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">
651 <i class="fas fa-plus -ml-1 mr-2"></i>
652 <?php _e('New Client', 'easy-invoice'); ?>
653 </button>
654 </div>
655
656 <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;">
657 <div id="client-search-loading" class="p-6 text-center text-gray-500" style="display: none;">
658 <i class="fas fa-spinner fa-spin mr-2"></i>
659 <?php _e('Searching...', 'easy-invoice'); ?>
660 </div>
661 <div id="client-search-empty" class="p-6 text-center text-gray-500" style="display: none;">
662 <i class="fas fa-search mr-2"></i>
663 <?php _e('No clients found', 'easy-invoice'); ?>
664 </div>
665 <div id="client-search-list" class="py-2">
666 </div>
667 </div>
668
669 <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'; ?>">
670 <div class="flex items-center justify-between">
671 <div class="flex items-center">
672 <div class="flex-shrink-0">
673 <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">
674 <i class="fas fa-user text-white text-sm"></i>
675 </div>
676 </div>
677 <div class="ml-4">
678 <p id="selected-client-name" class="text-sm font-semibold text-gray-900">
679 <?php
680 if ($invoice && $invoice->getClientId()) {
681 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
682 if ($client) {
683 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
684 } else {
685 echo esc_html__('—', 'easy-invoice');
686 }
687 } else {
688 echo esc_html__('—', 'easy-invoice');
689 }
690 ?>
691 </p>
692 <p id="selected-client-email" class="text-sm text-gray-600">
693 <?php
694 if ($invoice && $invoice->getClientId()) {
695 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
696 if ($client) {
697 echo esc_html($client->getEmail() ?: esc_html__('—', 'easy-invoice'));
698 } else {
699 echo esc_html__('—', 'easy-invoice');
700 }
701 } else {
702 echo esc_html__('—', 'easy-invoice');
703 }
704 ?>
705 </p>
706 </div>
707 </div>
708 <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">
709 <i class="fas fa-times"></i>
710 </button>
711 </div>
712 </div>
713 </div>
714
715 <select id="select-client" class="hidden">
716 <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option>
717 <?php
718 // Ensure $clients is defined
719 if (!isset($clients)) {
720 $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
721 $clients = $client_repository->all();
722 }
723
724 // Output client options
725 if (!empty($clients)) {
726 foreach ($clients as $client) {
727 // Get the WordPress user data directly
728 $user = get_user_by('id', $client->getId());
729 if (!$user) {
730 continue;
731 }
732
733 // Use Client model properties first, fallback to WordPress user fields
734 $business_name = $client->business_client_name ?: '';
735 $first_name = $client->first_name ?: $user->first_name ?: '';
736 $last_name = $client->last_name ?: $user->last_name ?: '';
737 $client_email = $client->email ?: $user->user_email ?: '';
738
739 // Create display name
740 $client_name = $business_name ?: ($first_name . ' ' . $last_name);
741 if (empty(trim($client_name))) {
742 $client_name = $user->display_name ?: 'User ' . $client->getId();
743 }
744
745 $selected = $invoice && $invoice->getClientId() == $client->getId() ? 'selected' : '';
746 printf(
747 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
748 esc_attr($client->getId()),
749 $selected,
750 esc_attr($client_name),
751 esc_attr($client_email),
752 esc_html($client_name),
753 esc_html($client_email)
754 );
755 }
756 }
757 ?>
758 </select>
759 </div>
760
761 <div class="border-t border-gray-200 pt-4 mt-4">
762 <input type="hidden" name="client_id" id="client-id" value="<?php echo $invoice ? esc_attr($invoice->getClientId()) : ''; ?>">
763
764 <div id="client-info-display" class="mt-6 <?php echo $invoice && $invoice->getClientId() ? '' : 'hidden'; ?>">
765 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
766 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
767 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
768 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
769 <span><?php _e('Client Information', 'easy-invoice'); ?></span>
770 </h4>
771 <a href="<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($invoice && $invoice->getClientId() ? $invoice->getClientId() : '')); ?>"
772 id="edit-selected-client"
773 class="text-sm text-indigo-600 hover:text-indigo-800 font-medium"
774 target="_blank">
775 <i class="fas fa-pen mr-1"></i>
776 <?php _e('Edit', 'easy-invoice'); ?>
777 </a>
778 </div>
779
780 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
781 <div class="flex items-start group">
782 <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>
783 <div class="ml-3">
784 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Name', 'easy-invoice'); ?></label>
785 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
786 <?php
787 if ($invoice && $invoice->getClientId()) {
788 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
789 if ($client) {
790 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
791 } else {
792 echo esc_html__('—', 'easy-invoice');
793 }
794 } else {
795 echo esc_html__('—', 'easy-invoice');
796 }
797 ?>
798 </p>
799 </div>
800 </div>
801
802 <div class="flex items-start group">
803 <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>
804 <div class="ml-3">
805 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Company', 'easy-invoice'); ?></label>
806 <p id="display-client-company" class="text-sm font-semibold 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->getBusinessClientName() ?: 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 <div class="flex items-start group">
824 <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>
825 <div class="ml-3">
826 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Email', 'easy-invoice'); ?></label>
827 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
828 <?php
829 if ($invoice && $invoice->getClientId()) {
830 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
831 if ($client) {
832 echo esc_html($client->getEmail() ?: esc_html__('—', 'easy-invoice'));
833 } else {
834 echo esc_html__('—', 'easy-invoice');
835 }
836 } else {
837 echo esc_html__('—', 'easy-invoice');
838 }
839 ?>
840 </p>
841 </div>
842 </div>
843
844 <div class="flex items-start group">
845 <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>
846 <div class="ml-3">
847 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Phone', 'easy-invoice'); ?></label>
848 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
849 <?php
850 if ($invoice && $invoice->getClientId()) {
851 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
852 if ($client) {
853 echo esc_html($client->getExtraInfo() ?: esc_html__('—', 'easy-invoice'));
854 } else {
855 echo esc_html__('—', 'easy-invoice');
856 }
857 } else {
858 echo esc_html__('—', 'easy-invoice');
859 }
860 ?>
861 </p>
862 </div>
863 </div>
864
865 <div class="flex items-start group md:col-span-2">
866 <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>
867 <div class="ml-3">
868 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Website', 'easy-invoice'); ?></label>
869 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
870 <?php
871 if ($invoice && $invoice->getClientId()) {
872 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
873 if ($client) {
874 echo esc_html($client->getWebsite() ?: esc_html__('—', 'easy-invoice'));
875 } else {
876 echo esc_html__('—', 'easy-invoice');
877 }
878 } else {
879 echo esc_html__('—', 'easy-invoice');
880 }
881 ?>
882 </p>
883 </div>
884 </div>
885
886 <div class="flex items-start group md:col-span-2">
887 <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>
888 <div class="ml-3">
889 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Address', 'easy-invoice'); ?></label>
890 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
891 <?php
892 if ($invoice && $invoice->getClientId()) {
893 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
894 if ($client) {
895 echo esc_html($client->getAddress() ?: esc_html__('—', 'easy-invoice'));
896 } else {
897 echo esc_html__('—', 'easy-invoice');
898 }
899 } else {
900 echo esc_html__('—', 'easy-invoice');
901 }
902 ?>
903 </p>
904 </div>
905 </div>
906 </div>
907 </div>
908 </div>
909
910 <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' : ''; ?>">
911 <p class="text-gray-600"><?php _e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p>
912 </div>
913 </div>
914 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
915 <div>
916 <h3 class="text-lg font-medium text-gray-900"><?php _e('Discount & Tax Settings', 'easy-invoice'); ?></h3>
917 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
918 <?php
919 // Get all discount and tax fields
920 $discount_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
921 $tax_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'tax');
922 $additional_tax_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'additional_tax');
923
924 // Combine and sort by order
925 $all_fields = array_merge($discount_fields, $tax_fields, $additional_tax_fields);
926 usort($all_fields, function($a, $b) {
927 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
928 });
929
930 foreach ($all_fields as $field):
931 echo $form_manager->renderField($field, $invoice);
932 endforeach;
933 ?>
934 </div>
935 </div>
936 <?php elseif ($tab['id'] === 'payments-tab'): ?>
937 <div>
938 <h3 class="text-lg font-medium text-gray-900"><?php _e('Payment Information', 'easy-invoice'); ?></h3>
939 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
940 <?php
941 $payment_fields = $form_manager->getFieldsBySection('payments-tab', 'payment');
942 foreach ($payment_fields as $field):
943 echo $form_manager->renderField($field, $invoice);
944 endforeach;
945 ?>
946 </div>
947
948 <div class="mt-8 border-t border-gray-200 pt-6">
949 <h3 class="text-lg font-medium text-gray-900"><?php _e('Currency', 'easy-invoice'); ?></h3>
950 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
951 <?php
952 $currency_fields = $form_manager->getFieldsBySection('payments-tab', 'currency');
953 foreach ($currency_fields as $field):
954 echo $form_manager->renderField($field, $invoice);
955 endforeach;
956 ?>
957 </div>
958 </div>
959 </div>
960 <?php endif; ?>
961 </div>
962 <?php endforeach; ?>
963 </form>
964 </div>
965
966 <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">
967 <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">
968 <div class="flex justify-between items-center mb-4">
969 <h3 class="text-xl font-medium text-gray-900"><?php _e('Browse Invoice Templates', 'easy-invoice'); ?></h3>
970 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
971 <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span>
972 <i class="fas fa-times text-xl"></i>
973 </button>
974 </div>
975
976 <div class="templates-grid grid grid-cols-2 md:grid-cols-3 gap-4 mb-4 max-h-[45vh] overflow-y-auto p-2">
977 <?php
978 // Display all template options in the modal
979 foreach ($all_templates as $template_id => $template) :
980 $is_current = ($current_template === $template_id);
981 $selected_class = $is_current ? 'selected' : '';
982 ?>
983 <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'; ?>">
984 <div class="p-4 border rounded-lg">
985 <div class="flex items-center mb-2">
986 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
987 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
988 </div>
989 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
990 <div class="mt-2 flex-grow">
991 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
992 </div>
993 </div>
994 </div>
995 </div>
996 <?php endforeach; ?>
997 </div>
998
999 <div class="flex justify-end mt-4 space-x-3">
1000 <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">
1001 <?php _e('Apply', 'easy-invoice'); ?>
1002 </button>
1003 <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">
1004 <?php _e('Close', 'easy-invoice'); ?>
1005 </button>
1006 </div>
1007 </div>
1008 </div>
1009
1010 <template id="invoice-item-template" style="display: none;">
1011 <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;">
1012 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
1013 <div class="flex items-center">
1014 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
1015 <i class="fas fa-chevron-down text-sm"></i>
1016 </button>
1017 <h3 class="text-base font-normal text-gray-800 flex-1">
1018 <?php
1019 echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
1020 ?>
1021 </h3>
1022 </div>
1023 <div class="flex items-center gap-3">
1024 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
1025 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
1026 = $<span class="total-summary">0.00</span>
1027 </div>
1028 <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'); ?>">
1029 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1030 <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"/>
1031 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
1032 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
1033 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
1034 </svg>
1035 </button>
1036 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
1037 <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php _e('Select from Item Library', 'easy-invoice'); ?>">
1038 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1039 <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
1040 </svg>
1041 </button>
1042 <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Save to Item Library', 'easy-invoice'); ?>" data-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>">
1043 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1044 <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
1045 </svg>
1046 </button>
1047 <?php endif; ?>
1048 <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">
1049 <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
1050 </button>
1051 </div>
1052 </div>
1053 <div class="item-content">
1054 <?php echo $form_manager->generateItemTemplateHtml(); ?>
1055 </div>
1056 </div>
1057 </template>
1058
1059 <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">
1060 <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">
1061 <div class="flex justify-between items-center mb-4">
1062 <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
1063 <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
1064 <span class="sr-only">Close</span>
1065 <i class="fas fa-times"></i>
1066 </button>
1067 </div>
1068
1069 <?php
1070 // Unset the $client variable from the foreach loop to ensure clean add form
1071 unset($client);
1072 include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
1073 ?>
1074 </div>
1075 </div>
1076
1077 <?php
1078
1079 // Always localize script with AJAX URL and nonce
1080 wp_localize_script('easy-invoice-form', 'easyInvoice', array(
1081 'ajaxUrl' => admin_url('admin-ajax.php'),
1082 'nonce' => wp_create_nonce('easy_invoice_nonce'),
1083 'clientData' => $client_data ?: array()
1084 ));
1085 ?>
1086
1087 <script>
1088 jQuery(document).ready(function($) {
1089
1090
1091 // Client search functionality for invoice form
1092 var searchTimeout;
1093
1094 // Initialize existing client data
1095 var existingClientId = '<?php echo $invoice ? esc_js($invoice->getClientId()) : ''; ?>';
1096 var existingClientName = '<?php
1097 if ($invoice && $invoice->getClientId()) {
1098 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
1099 if ($client) {
1100 echo esc_js($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
1101 }
1102 }
1103 ?>';
1104 var existingClientEmail = '<?php
1105 if ($invoice && $invoice->getClientId()) {
1106 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
1107 if ($client) {
1108 echo esc_js($client->getEmail() ?: '');
1109 }
1110 }
1111 ?>';
1112 var existingClientData = <?php echo json_encode($client_data); ?>;
1113
1114 if (existingClientId && existingClientName && existingClientName !== '-') {
1115 // Show the selected client display and client info display
1116 $('#selected-client-display').show();
1117 $('#client-info-display').show();
1118 $('#no-client-message').hide();
1119
1120 // Set the select client dropdown value
1121 $('#select-client').val(existingClientId);
1122
1123 // Populate the search input with the existing client name
1124 $('#client-search-input').val(existingClientName);
1125
1126
1127 }
1128
1129 // Client search input functionality
1130 $('#client-search-input').on('input', function() {
1131 const query = $(this).val().trim();
1132 clearTimeout(searchTimeout);
1133
1134 // Don't hide results immediately for short queries - let the search function handle it
1135 if (query.length === 0) {
1136 // Show all clients when input is cleared
1137 searchClients('');
1138 return;
1139 }
1140
1141 searchTimeout = setTimeout(function() {
1142 searchClients(query);
1143 }, 300);
1144 });
1145
1146 // Focus on search input - ensure client tab is active
1147 $('#client-search-input').on('focus', function() {
1148 // Switch to client tab first to ensure the search results are visible
1149 if (window.switchToTab) {
1150 window.switchToTab('client-tab');
1151 } else {
1152 // Fallback: manually switch tabs
1153 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
1154 $('.tab-content').addClass('hidden');
1155 $('.tab-button[data-tab="client-tab"]').addClass('border-indigo-500 text-indigo-600');
1156 $('#client-tab').removeClass('hidden');
1157 }
1158 });
1159
1160 // Click on search input to show all clients
1161 $("#client-search-input").on("click", function() {
1162 // Show the dropdown and call AJAX to populate it
1163 $('#client-search-results').show();
1164 $('#client-search-loading').show();
1165 $('#client-search-empty').hide();
1166
1167 // Call the AJAX method to populate the dropdown
1168 if (typeof easyInvoice !== 'undefined' && easyInvoice.ajaxUrl) {
1169 searchClients('');
1170 } else {
1171 $('#client-search-loading').hide();
1172 $('#client-search-empty').show();
1173 }
1174 });
1175
1176 // Client selection
1177 $(document).on('click', '.client-option', function(e) {
1178 e.preventDefault();
1179 e.stopPropagation();
1180
1181 const clientId = $(this).data('client-id');
1182 const clientName = $(this).data('client-name');
1183 const clientEmail = $(this).data('client-email');
1184 const clientCompany = $(this).data('client-company');
1185 const clientPhone = $(this).data('client-phone');
1186 const clientWebsite = $(this).data('client-website');
1187 const clientAddress = $(this).data('client-address');
1188
1189 // Create client data object with all available information
1190 const clientData = {
1191 name: clientName,
1192 email: clientEmail,
1193 company: clientCompany,
1194 phone: clientPhone,
1195 website: clientWebsite,
1196 address: clientAddress
1197 };
1198
1199 selectClient(clientId, clientName, clientEmail, clientData);
1200 $('#client-search-results').hide();
1201 $('#client-search-input').val('');
1202 });
1203
1204 // Alternative click handler for client options
1205 $('#client-search-list').on('click', '.client-option', function(e) {
1206 e.preventDefault();
1207 e.stopPropagation();
1208
1209 const clientId = $(this).data('client-id');
1210 const clientName = $(this).data('client-name');
1211 const clientEmail = $(this).data('client-email');
1212 const clientCompany = $(this).data('client-company');
1213 const clientPhone = $(this).data('client-phone');
1214 const clientWebsite = $(this).data('client-website');
1215 const clientAddress = $(this).data('client-address');
1216
1217 // Create client data object with all available information
1218 const clientData = {
1219 name: clientName,
1220 email: clientEmail,
1221 company: clientCompany,
1222 phone: clientPhone,
1223 website: clientWebsite,
1224 address: clientAddress
1225 };
1226
1227 selectClient(clientId, clientName, clientEmail, clientData);
1228 $('#client-search-results').hide();
1229 $('#client-search-input').val('');
1230 });
1231
1232 // Clear client selection
1233 $('#clear-client-selection').on('click', function() {
1234 clearClientSelection();
1235 });
1236
1237 // Add new client button
1238 $('#add-new-client-btn').on('click', function() {
1239 $('#add_client_modal').show();
1240 });
1241
1242 // Close modal
1243 $('.close-modal').on('click', function() {
1244 $('#add_client_modal').hide();
1245 });
1246
1247 // Functions
1248 function searchClients(query) {
1249 $('#client-search-loading').show();
1250 $('#client-search-empty').hide();
1251 $('#client-search-list').empty();
1252
1253 // If query is empty or less than 2 characters, show all clients
1254 if (!query || query.trim().length < 2) {
1255 query = ''; // Set to empty string to get all clients
1256 }
1257
1258 $.ajax({
1259 url: easyInvoice.ajaxUrl,
1260 type: 'POST',
1261 data: {
1262 action: 'easy_invoice_search_clients',
1263 query: query,
1264 nonce: easyInvoice.nonce
1265 },
1266 success: function(response) {
1267 $('#client-search-loading').hide();
1268
1269 if (response.success && response.data.length > 0) {
1270 response.data.forEach(function(client) {
1271 const option = $(`
1272 <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
1273 data-client-id="${client.id}"
1274 data-client-name="${client.name.replace(/"/g, '&quot;')}"
1275 data-client-email="${client.email.replace(/"/g, '&quot;')}"
1276 data-client-company="${(client.company || '').replace(/"/g, '&quot;')}"
1277 data-client-phone="${(client.phone || '').replace(/"/g, '&quot;')}"
1278 data-client-website="${(client.website || '').replace(/"/g, '&quot;')}"
1279 data-client-address="${(client.address || '').replace(/"/g, '&quot;')}"
1280 onclick="selectClientFromOption(this)">
1281 <div class="font-medium text-gray-900">${client.name}</div>
1282 <div class="text-sm text-gray-600">${client.email}</div>
1283 </div>
1284 `);
1285
1286 $('#client-search-list').append(option);
1287 });
1288 $('#client-search-results').show();
1289 } else {
1290 $('#client-search-empty').show();
1291 $('#client-search-results').show();
1292 }
1293 },
1294 error: function(xhr, status, error) {
1295 $('#client-search-loading').hide();
1296 $('#client-search-empty').show();
1297 $('#client-search-results').show();
1298 }
1299 });
1300 }
1301
1302 function selectClient(clientId, clientName, clientEmail, clientData = null) {
1303
1304 $('#select-client').val(clientId);
1305 $('#client-id').val(clientId);
1306 $('#selected-client-name').text(clientName);
1307 $('#selected-client-email').text(clientEmail);
1308
1309 $('#selected-client-display').show().removeClass('hidden');
1310 $('#client-info-display').show().removeClass('hidden');
1311 $('#no-client-message').hide().removeClass('hidden');
1312
1313 // Update the edit client button URL
1314 $('#edit-selected-client').attr('href', '<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id='); ?>' + clientId);
1315
1316 // If we have full client data, populate all fields directly
1317 if (clientData) {
1318 $('#display-client-name').text(clientData.name || clientName || '-');
1319 $('#display-client-company').text(clientData.company || '-');
1320 $('#display-client-email').text(clientData.email || clientEmail || '-');
1321 $('#display-client-phone').text(clientData.phone || '-');
1322 $('#display-client-website').text(clientData.website || '-');
1323 $('#display-client-address').text(clientData.address || '-');
1324 } else {
1325 // Fallback to just the basic info we have
1326 $('#display-client-name').text(clientName);
1327 $('#display-client-email').text(clientEmail);
1328 $('#display-client-company').text('-');
1329 $('#display-client-phone').text('-');
1330 $('#display-client-website').text('-');
1331 $('#display-client-address').text('-');
1332 }
1333 }
1334
1335 function clearClientSelection() {
1336 $('#select-client').val('');
1337 $('#client-id').val('');
1338 $('#selected-client-display').hide();
1339 $('#client-info-display').hide();
1340 $('#no-client-message').show();
1341
1342 // Clear display fields
1343 $('#display-client-name, #display-client-email, #display-client-company, #display-client-phone, #display-client-website, #display-client-address').text('-');
1344 }
1345
1346 // Global function for inline onclick
1347 window.selectClientFromOption = function(element) {
1348 const $element = $(element);
1349 const clientId = $element.data('client-id');
1350 const clientName = $element.data('client-name');
1351 const clientEmail = $element.data('client-email');
1352 const clientCompany = $element.data('client-company');
1353 const clientPhone = $element.data('client-phone');
1354 const clientWebsite = $element.data('client-website');
1355 const clientAddress = $element.data('client-address');
1356
1357 // Create client data object with all available information
1358 const clientData = {
1359 name: clientName,
1360 email: clientEmail,
1361 company: clientCompany,
1362 phone: clientPhone,
1363 website: clientWebsite,
1364 address: clientAddress
1365 };
1366
1367 selectClient(clientId, clientName, clientEmail, clientData);
1368 $('#client-search-results').hide();
1369 $('#client-search-input').val('');
1370 };
1371 });
1372 </script>
1373
1374 <script>
1375 // Invoice field configuration and pre-loaded data
1376 window.easyInvoice = window.easyInvoice || {};
1377 <?php
1378 $json_field_config = json_encode($invoice_field_config);
1379 ?>
1380 window.easyInvoice.fieldConfig = <?php echo $json_field_config; ?>;
1381 window.easyInvoice.adminUrl = '<?php echo admin_url(); ?>';
1382 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
1383 window.easyInvoice.translations = {
1384 premiumTemplates: '<?php _e('Premium Templates', 'easy-invoice'); ?>',
1385 premiumTemplatesDescription: '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice'); ?>',
1386 premiumFeatureMessage: '<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>',
1387 selectTemplateFirst: '<?php _e('Please select a template first', 'easy-invoice'); ?>'
1388 };
1389 <?php if ($client_data): ?>
1390 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
1391 <?php endif; ?>
1392 </script>
1393