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