PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.10
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.10
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 / settings-page.php

settings-page.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.10, at templates/settings-page.php

1,221 lines 107.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings page template for Easy Invoice
4 *
5 * @package Easy_Invoice
6 * @since 1.0.0
7 */
8
9 // Exit if accessed directly
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 // Function to dequeue WordPress admin styles and load our custom styles
15 function easy_invoice_dequeue_admin_styles() {
16 // Get current screen
17 $screen = get_current_screen();
18
19 // Check if we're on an Easy Invoice page
20 if ($screen && property_exists($screen, 'id') && strpos($screen->id, 'easy-invoice') !== false) {
21 // Remove all WordPress admin styles
22 global $wp_styles;
23
24 $styles_to_remove = [
25 'admin-bar', 'colors', 'ie', 'common', 'forms', 'admin-menu',
26 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about',
27 'nav-menus', 'widgets', 'site-icon', 'l10n', 'code-editor', 'wp-auth-check',
28 'dashicons', 'wp-pointer', 'buttons', 'wp-jquery-ui-dialog', 'wp-color-picker',
29 'wp-components', 'wp-edit-post', 'wp-format-library', 'wp-block-library',
30 'wp-block-library-theme', 'wp-nux', 'wp-reusable-blocks', 'wp-editor',
31 'wp-edit-blocks', 'wp-block-editor',
32 ];
33 $styles_to_remove = array_unique($styles_to_remove); // Ensure no duplicates
34
35 foreach ($styles_to_remove as $style) {
36 if (isset($wp_styles->registered[$style])) {
37 wp_dequeue_style($style);
38 wp_deregister_style($style);
39 }
40 }
41 add_filter('show_admin_bar', '__return_false');
42
43 // Add custom CSS to hide screen-meta-links
44 echo '<style>
45 .screen-meta-links,
46 #screen-meta,
47 #screen-meta-links {
48 display: none !important;
49 }
50 </style>';
51 }
52 }
53 add_action('admin_enqueue_scripts', 'easy_invoice_dequeue_admin_styles', 9999);
54
55 /**
56 * Helper function to render a single settings field.
57 *
58 * @param string $option_key The option key.
59 * @param array $field_config The field configuration.
60 * @param mixed $current_value The current value of the field.
61 */
62 function easy_invoice_render_field($option_key, $field_config, $current_value) {
63 $label = esc_html($field_config['label'] ?? '');
64 $type = $field_config['type'] ?? 'text';
65 $default = $field_config['default'] ?? '';
66 $value = $current_value ?? $default;
67 $description_text = $field_config['description'] ?? '';
68 $field_id = esc_attr($option_key);
69 $field_name = 'settings[' . esc_attr($option_key) . ']';
70 $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6');
71 $placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : '';
72 $required = !empty($field_config['required']) ? 'required' : '';
73 $aria_describedby = !empty($description_text) ? 'aria-describedby="' . $field_id . '-description"' : '';
74
75 echo '<div class=" ' . $col_span_class . '">'; // Added 'form-group' class for JS show/hide functionality
76
77 // Special layout for checkboxes with descriptions (e.g., Invoice Numbering type)
78 if ($type === 'checkbox' && !empty($description_text) && ($option_key === 'easy_invoice_invoice_numbering')) {
79 echo '<div class="flex items-start space-x-3 p-4 bg-gray-50 rounded-lg border border-gray-200">';
80 echo ' <div class="flex items-center h-5 mt-0.5">';
81 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" ' . $required . '>';
82 echo ' </div>';
83 echo ' <div class="flex-1">';
84 echo ' <label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-1">' . $label . '</label>';
85 echo ' <p id="' . $field_id . '-description" class="text-sm text-gray-400 leading-relaxed">' . wp_kses($description_text, array(
86 'a' => array(
87 'href' => array(),
88 'target' => array(),
89 'class' => array()
90 )
91 )) . '</p>';
92 echo ' </div>';
93 echo '</div>';
94 } else {
95 // Standard label for other types, or checkboxes without the special description structure
96 if ($type !== 'checkbox') {
97 echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-2">' . $label . '</label>';
98 }
99
100 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
101 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
102
103
104 switch ($type) {
105 case 'text':
106 case 'email':
107 case 'url':
108 case 'tel':
109 echo '<input type="' . esc_attr($type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
110 break;
111 case 'number':
112 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
113 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
114 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
115 echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
116 break;
117 case 'textarea':
118 echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($value) . '</textarea>';
119 break;
120 case 'select':
121 echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>';
122 if (!empty($field_config['options']) && is_array($field_config['options'])) {
123 foreach ($field_config['options'] as $opt_val => $opt_label) {
124 echo '<option value="' . esc_attr($opt_val) . '" ' . selected($value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>';
125 }
126 }
127 echo '</select>';
128 break;
129 case 'multiselect':
130 echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($label) . '">';
131 if (!empty($field_config['options']) && is_array($field_config['options'])) {
132 foreach ($field_config['options'] as $opt_val => $opt_label) {
133 $selected = is_array($value) && in_array($opt_val, $value) ? 'selected="selected"' : '';
134 echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>';
135 }
136 }
137 echo '</select>';
138 break;
139 case 'readonly':
140 echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . ' bg-gray-50" readonly ' . $aria_describedby . '>';
141 echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($value) . '">';
142 break;
143 case 'checkbox': // Checkboxes that don't use the special description layout
144 echo '<div class="flex items-center">'; // Original 'tax_enabled' was just this simple structure
145 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" ' . $required . ' ' . $aria_describedby . '>';
146 echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $label . '</label>';
147 echo '</div>';
148 if (!empty($description_text)) { // Show description below the field
149 echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . wp_kses($description_text, array(
150 'a' => array(
151 'href' => array(),
152 'target' => array(),
153 'class' => array()
154 )
155 )) . '</p>';
156 }
157 break;
158 case 'image':
159 echo '<div class="mt-1 flex items-center">';
160 echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">';
161 echo ' <img id="' . $field_id . '-preview" src="' . esc_url($value) . '" alt="' . esc_attr($label) . '" class="h-full w-full object-cover' . (empty($value) ? ' hidden' : '') . '">';
162 echo ' </span>';
163 echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">';
164 echo esc_html__('Change', 'easy-invoice');
165 echo '</button>';
166 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" ' . $required . ' ' . $aria_describedby . '>';
167 echo '</div>';
168 break;
169 case 'wp_editor':
170 // For wp_editor, the label is usually handled before calling it.
171 // The col_span div wraps this.
172 wp_editor( $value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] );
173 // Handle description for wp_editor specifically
174 if (!empty($description_text)) {
175 echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($description_text, array(
176 'a' => array(
177 'href' => array(),
178 'target' => array(),
179 'class' => array()
180 )
181 )) . '</p>';
182 }
183 break;
184 case 'test_email':
185 echo '<div class="mt-1">';
186 echo ' <div class="flex items-center space-x-3">';
187 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block w-[200px] border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
188 echo ' <button type="button" id="' . $field_id . '_button" class="test-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 h-[38px]">';
189 echo ' <i class="fas fa-paper-plane mr-2" aria-hidden="true"></i>';
190 echo ' ' . esc_html__('Send Test Email', 'easy-invoice');
191 echo ' </button>';
192 echo ' </div>';
193 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
194 echo '</div>';
195 break;
196 case 'color':
197 echo '<input type="color" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="h-10 w-20 border border-gray-300 rounded">';
198 break;
199 case 'range':
200 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
201 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
202 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
203 echo '<input type="range" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="w-full">';
204 echo '<span class="text-sm text-gray-400">' . esc_html($value) . '%</span>';
205 break;
206 case 'button':
207 $button_text = $field_config['button_text'] ?? $label;
208 $button_class = $field_config['button_class'] ?? 'button button-secondary';
209 $button_class_name = 'regenerate-invoice-numbers-button';
210 if ($option_key === 'easy_invoice_regenerate_quote_numbers') {
211 $button_class_name = 'regenerate-quote-numbers-button';
212 }
213 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . $button_class_name . '">';
214 echo esc_html($button_text);
215 echo '</button>';
216 break;
217 case 'image_upload':
218 echo '<div class="image-upload-wrap">';
219 echo '<div class="flex items-center space-x-3">';
220 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '">';
221 echo ' <button type="button" class="upload-image-button bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-target="' . $field_id . '">';
222 echo esc_html__('Upload Image', 'easy-invoice');
223 echo '</button>';
224 echo '</div>';
225 echo '<div class="image-preview mt-2' . (empty($value) ? ' hidden' : '') . '">';
226 if (!empty($value)) {
227 echo '<img src="' . esc_url($value) . '" alt="Preview" style="max-width: 200px; max-height: 100px;">';
228 }
229 echo '</div>';
230 echo '</div>';
231 break;
232 default:
233 // Special handling for regenerate invoice numbers button
234 if ($option_key === 'easy_invoice_regenerate_invoice_numbers') {
235 $button_text = $field_config['button_text'] ?? $label;
236 $button_class = $field_config['button_class'] ?? 'button button-secondary';
237 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' regenerate-invoice-numbers-button">';
238 echo esc_html($button_text);
239 echo '</button>';
240 } elseif ($option_key === 'easy_invoice_regenerate_quote_numbers') {
241 $button_text = $field_config['button_text'] ?? $label;
242 $button_class = $field_config['button_class'] ?? 'button button-secondary';
243 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' regenerate-quote-numbers-button">';
244 echo esc_html($button_text);
245 echo '</button>';
246 } else {
247 echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
248 }
249 break;
250 }
251 // General description for non-checkbox and non-wp_editor fields (if it exists and not handled by special layouts)
252 if ($type !== 'checkbox' && $type !== 'wp_editor' && !empty($description_text)) {
253 echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($description_text, array(
254 'a' => array(
255 'href' => array(),
256 'target' => array(),
257 'class' => array()
258 )
259 )) . '</p>';
260 }
261 }
262 echo '</div>'; // End col_span div
263 }
264
265 /**
266 * Filter settings config to allow extensions to modify it
267 * Note: This filter is already applied in the controller, so this is redundant
268 */
269 // $settings_config = apply_filters('easy_invoice_settings_fields_config', $settings_config);
270
271 $active_section = $_GET['section'] ?? key($settings_config);
272 if (!isset($settings_config[$active_section])) {
273 $active_section = key($settings_config);
274 }
275 ?>
276
277 <div class="p-8">
278 <!-- Header with Actions -->
279 <div class="bg-white border-b border-gray-200 px-6 py-5" style="margin-left: -2rem; margin-top: -2rem; padding-left:2rem; padding-right:2rem; margin-right: -2rem;">
280 <div class="flex items-center justify-between">
281 <div>
282 <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Settings', 'easy-invoice'); ?></h1>
283 <p class="mt-1 text-sm text-gray-400"><?php esc_html_e('Configure your invoice management system', 'easy-invoice'); ?></p>
284 </div>
285 <div class="flex items-center space-x-3">
286 <button type="button" id="save-settings" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" aria-label="<?php esc_attr_e('Save Settings', 'easy-invoice'); ?>">
287 <i class="fas fa-save mr-2" aria-hidden="true"></i>
288 <?php esc_html_e('Save Changes', 'easy-invoice'); ?>
289 </button>
290 </div>
291 </div>
292 </div>
293
294 <!-- Main Content -->
295 <div class="max-w-7xl mx-auto">
296 <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8">
297 <!-- Left Sidebar - Navigation -->
298 <div class="lg:col-span-1">
299 <div class="bg-white shadow-lg rounded-lg sticky top-24 border border-gray-200">
300 <nav class="space-y-2 p-4" aria-label="<?php esc_attr_e('Settings Navigation', 'easy-invoice'); ?>">
301 <?php foreach ($settings_config as $section_id => $section_data) : ?>
302 <div class="space-y-1">
303 <a href="?page=easy-invoice-settings&section=<?php echo esc_attr($section_id); ?>"
304 class="nav-link group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 <?php echo ($section_id === $active_section) ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>"
305 aria-selected="<?php echo $section_id === $active_section ? 'true' : 'false'; ?>"
306 role="tab">
307
308 <?php if ($section_id === 'company'): ?>
309 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
310 <path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
311 <polyline points="9,22 9,12 15,12 15,22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
312 </svg>
313 <?php elseif ($section_id === 'invoice'): ?>
314 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
315 <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
316 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
317 <path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
318 <path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
319 <path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
320 </svg>
321 <?php elseif ($section_id === 'quote'): ?>
322 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
323 <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
324 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
325 <path d="M9 9H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
326 <path d="M9 13H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
327 <path d="M9 17H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
328 </svg>
329 <?php elseif ($section_id === 'currency'): ?>
330 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
331 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
332 <path d="M12 1V23" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
333 <path d="M17 5H9.5C8.57174 5 7.6815 5.36875 7.02513 6.02513C6.36875 6.6815 6 7.57174 6 8.5C6 9.42826 6.36875 10.3185 7.02513 10.9749C7.6815 11.6313 8.57174 12 9.5 12H14.5C15.4283 12 16.3185 12.3687 16.9749 13.0251C17.6313 13.6815 18 14.5717 18 15.5C18 16.4283 17.6313 17.3185 16.9749 17.9749C16.3185 18.6313 15.4283 19 14.5 19H6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
334 </svg>
335 <?php elseif ($section_id === 'tax'): ?>
336 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
337 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
338 <path d="M8 14L16 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
339 <path d="M8 8L16 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
340 <path d="M12 6V18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
341 </svg>
342 <?php elseif ($section_id === 'payment'): ?>
343 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
344 <rect x="1" y="4" width="22" height="16" rx="2" ry="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
345 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
346 </svg>
347 <?php elseif ($section_id === 'email'): ?>
348 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
349 <path d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
350 <polyline points="22,6 12,13 2,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
351 </svg>
352 <?php elseif ($section_id === 'advanced'): ?>
353 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
354 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
355 <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
356 </svg>
357 <?php elseif ($section_id === 'text_settings'): ?>
358 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
359 <path d="M4 7V4C4 3.44772 4.44772 3 5 3H19C19.5523 3 20 3.44772 20 4V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
360 <path d="M4 7V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
361 <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
362 <path d="M8 15H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
363 <path d="M8 19H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
364 <path d="M12 11L14 13L12 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
365 </svg>
366 <?php else: ?>
367 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
368 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
369 <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
370 </svg>
371 <?php endif; ?>
372
373 <span class="flex-1"><?php echo esc_html($section_data['title']); ?></span>
374 <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?>
375 <svg class="flex-shrink-0 h-4 w-4 text-gray-400 group-hover:text-gray-500 transition-transform duration-200" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
376 <polyline points="6,9 12,15 18,9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
377 </svg>
378 <?php endif; ?>
379 </a>
380
381 <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?>
382 <div class="ml-6 space-y-1 email-settings-submenu" style="display: none;">
383 <?php
384 // Only show active subsection if we're actually on an email settings page
385 $active_email_subsection = null;
386 if ($active_section === 'email') {
387 if (isset($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
388 $active_email_subsection = $_GET['subsection'];
389 } elseif (!empty($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
390 $active_email_subsection = $_GET['subsection'];
391 } else {
392 // Default to 'general' only if we're on email section but no specific subsection
393 $active_email_subsection = 'general';
394 }
395 }
396 ?>
397 <?php foreach ($section_data['subsections'] as $subsection_id => $subsection_data): ?>
398 <?php $is_active_subsection = ($active_email_subsection && $subsection_id === $active_email_subsection); ?>
399 <a href="?page=easy-invoice-settings&section=email&subsection=<?php echo esc_attr($subsection_id); ?>"
400 class="email-submenu-link group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-all duration-200 <?php echo $is_active_subsection ? 'text-indigo-700 bg-indigo-50 border-l-2 border-indigo-500' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'; ?>"
401 data-subsection="<?php echo esc_attr($subsection_id); ?>"
402 data-section="<?php echo esc_attr($section_id); ?>">
403 <svg class="mr-2 flex-shrink-0 h-4 w-4 <?php echo $is_active_subsection ? 'text-indigo-500' : 'text-gray-400 group-hover:text-gray-500'; ?>" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
404 <polyline points="9,18 15,12 9,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
405 </svg>
406 <?php
407 // Display shorter names for better UX
408 $display_name = '';
409 switch ($subsection_id) {
410 case 'general':
411 $display_name = 'General';
412 break;
413 case 'invoice_available':
414 $display_name = 'Invoice Available';
415 break;
416 case 'quote_available':
417 $display_name = 'Quote Available';
418 break;
419 case 'payment_received':
420 $display_name = 'Payment Received';
421 break;
422 default:
423 $display_name = $subsection_data['title'];
424 break;
425 }
426 echo esc_html($display_name);
427 ?>
428 </a>
429 <?php endforeach; ?>
430 </div>
431 <?php endif; ?>
432 </div>
433 <?php endforeach; ?>
434 </nav>
435 </div>
436 </div>
437
438 <!-- Right Content - Settings -->
439 <div class="lg:col-span-2">
440 <form id="settings-form" method="post" action="">
441 <?php wp_nonce_field('easy_invoice_settings', 'easy_invoice_settings_nonce'); ?>
442 <input type="hidden" name="action" value="easy_invoice_save_settings">
443
444 <?php foreach ($settings_config as $section_id => $section_data) : ?>
445 <div id="<?php echo esc_attr($section_id); ?>"
446 class="settings-section bg-white shadow rounded-lg <?php echo ($section_id !== $active_section) ? 'hidden' : ''; ?>"
447 role="tabpanel"
448 aria-labelledby="tab-<?php echo esc_attr($section_id); ?>">
449 <div class="px-6 py-5 border-b border-gray-200">
450 <div class="flex items-center">
451 <div class="flex-shrink-0 bg-indigo-100 rounded-lg p-3">
452 <?php if (isset($section_data['icon']) && !empty($section_data['icon'])): ?>
453 <?php if (strpos($section_data['icon'], '<svg') === 0): ?>
454 <?php echo $section_data['icon']; ?>
455 <?php else: ?>
456 <i class="<?php echo esc_attr($section_data['icon']); ?> text-indigo-600 text-xl"></i>
457 <?php endif; ?>
458 <?php else: ?>
459 <svg class="text-indigo-600 text-xl h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
460 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
461 <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
462 </svg>
463 <?php endif; ?>
464 </div>
465 <div class="ml-4">
466 <h2 class="text-lg font-medium text-gray-900"><?php echo esc_html($section_data['title']); ?></h2>
467 <p class="mt-1 text-sm text-gray-400"><?php echo esc_html($section_data['description']); ?></p>
468 </div>
469 </div>
470 </div>
471 <div class="px-6 py-5">
472 <div class="email-settings">
473 <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?>
474 <?php
475 // Determine the active subsection from the hash or default to 'general'
476 $active_email_subsection = 'general';
477 if (isset($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
478 $active_email_subsection = $_GET['subsection'];
479 } elseif (!empty($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
480 $active_email_subsection = $_GET['subsection'];
481 }
482 if (!isset($section_data['subsections'][$active_email_subsection])) {
483 $active_email_subsection = array_key_first($section_data['subsections']);
484 }
485 $subsection_data = $section_data['subsections'][$active_email_subsection];
486 ?>
487 <?php
488 // Render all email subsections so JS can show/hide them
489 foreach ($section_data['subsections'] as $subsection_key => $subsection_data) {
490 $is_active = ($subsection_key === $active_email_subsection) ? '' : 'hidden';
491 ?>
492 <div id="email--<?php echo esc_attr($subsection_key); ?>-content" class="email-subsection-content <?php echo $is_active; ?>">
493 <?php
494 // Display subsection title
495 $subsection_title = '';
496 switch ($subsection_key) {
497 case 'general':
498 $subsection_title = 'General Email Settings';
499 break;
500 case 'invoice_available':
501 $subsection_title = 'Invoice Available Email Settings';
502 break;
503 case 'quote_available':
504 $subsection_title = 'Quote Available Email Settings';
505 break;
506 case 'payment_received':
507 $subsection_title = 'Payment Received Email Settings';
508 break;
509 default:
510 $subsection_title = $subsection_data['title'];
511 break;
512 }
513 ?>
514 <div class="mb-6">
515 <h3 class="text-lg font-medium text-gray-900 mb-2"><?php echo esc_html($subsection_title); ?></h3>
516 <?php if (!empty($subsection_data['description'])): ?>
517 <p class="text-sm text-gray-400"><?php echo esc_html($subsection_data['description']); ?></p>
518 <?php endif; ?>
519 </div>
520 <div class="space-y-6">
521 <?php if (!empty($subsection_data['fields'])) {
522 foreach ($subsection_data['fields'] as $option_key => $field_config) {
523 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
524 $field_id = esc_attr($option_key);
525 $field_type = $field_config['type'] ?? 'text';
526 $field_label = esc_html($field_config['label'] ?? '');
527 $field_placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : '';
528 $field_description = $field_config['description'] ?? '';
529 $field_name = 'settings[' . esc_attr($option_key) . ']';
530 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
531 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
532 $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6');
533 echo '<div class="field-wrapper form-group">';
534 if ($field_type !== 'checkbox') {
535 echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . $field_label . '</label>';
536 }
537
538 // Define required and aria_describedby variables
539 $required = !empty($field_config['required']) ? 'required' : '';
540 $aria_describedby = !empty($field_description) ? 'aria-describedby="' . $field_id . '-description"' : '';
541
542 switch ($field_type) {
543 case 'text':
544 case 'email':
545 case 'url':
546 case 'tel':
547 echo '<input type="' . esc_attr($field_type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
548 break;
549 case 'number':
550 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
551 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
552 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
553 echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
554 break;
555 case 'textarea':
556 echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>';
557 break;
558 case 'select':
559 echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>';
560 if (!empty($field_config['options']) && is_array($field_config['options'])) {
561 foreach ($field_config['options'] as $opt_val => $opt_label) {
562 echo '<option value="' . esc_attr($opt_val) . '" ' . selected($current_value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>';
563 }
564 }
565 echo '</select>';
566 break;
567 case 'multiselect':
568 echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">';
569 if (!empty($field_config['options']) && is_array($field_config['options'])) {
570 foreach ($field_config['options'] as $opt_val => $opt_label) {
571 $selected = is_array($current_value) && in_array($opt_val, $current_value) ? 'selected="selected"' : '';
572 echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>';
573 }
574 }
575 echo '</select>';
576 break;
577 case 'readonly':
578 echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . ' bg-gray-50" readonly>';
579 echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($current_value) . '">';
580 break;
581 case 'checkbox':
582 echo '<div class="flex items-center">';
583 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">';
584 echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $field_label . '</label>';
585 echo '</div>';
586 if (!empty($field_description)) {
587 echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . esc_html($field_description) . '</p>';
588 }
589 break;
590 case 'image':
591 echo '<div class="mt-1 flex items-center">';
592 echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">';
593 echo ' <img id="' . $field_id . '-preview" src="' . esc_url($current_value) . '" alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">';
594 echo ' </span>';
595 echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">';
596 echo esc_html__('Change', 'easy-invoice');
597 echo '</button>';
598 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">';
599 echo '</div>';
600 break;
601 case 'wp_editor':
602 wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] );
603 // Handle description for wp_editor specifically
604 if (!empty($field_description)) {
605 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
606 }
607 break;
608 case 'test_email':
609 echo '<div class="mt-1">';
610 echo ' <div class="flex items-center space-x-3">';
611 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block w-[200px] border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
612 echo ' <button type="button" id="' . $field_id . '_button" class="test-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 h-[38px]">';
613 echo ' <i class="fas fa-paper-plane mr-2" aria-hidden="true"></i>';
614 echo ' ' . esc_html__('Send Test Email', 'easy-invoice');
615 echo ' </button>';
616 echo ' </div>';
617 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
618 echo '</div>';
619 break;
620 case 'test_template_email':
621 $template_type = $field_config['template_type'] ?? '';
622 $is_payment_test = ($field_id === 'easy_invoice_payment_test_template');
623 $width_class = $is_payment_test ? 'w-[200px]' : 'w-64';
624 echo '<div class="mt-1">';
625 echo ' <div class="flex items-center space-x-3">';
626 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block ' . $width_class . ' border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
627 echo ' <button type="button" id="' . $field_id . '_button" class="test-template-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 h-[38px]" data-template-type="' . esc_attr($template_type) . '">';
628 echo ' <i class="fas fa-envelope mr-2" aria-hidden="true"></i>';
629 echo ' ' . esc_html__('Test Template', 'easy-invoice');
630 echo ' </button>';
631 echo ' </div>';
632 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
633 echo '</div>';
634 break;
635 case 'test_payment_reminder_email':
636 echo '<div class="mt-1">';
637 echo ' <div class="flex items-center space-x-3">';
638 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block w-64 border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
639 echo ' <button type="button" id="' . $field_id . '_button" class="test-payment-reminder-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 h-[38px]">';
640 echo ' <i class="fas fa-bell mr-2" aria-hidden="true"></i>';
641 echo ' ' . esc_html__('Test Reminder', 'easy-invoice');
642 echo ' </button>';
643 echo ' </div>';
644 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
645 echo '</div>';
646 break;
647 default:
648 echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '">';
649 break;
650 }
651 if ($field_type !== 'checkbox' && $field_type !== 'wp_editor' && !empty($field_description)) {
652 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
653 }
654 echo '</div>'; // Close the field wrapper div
655 }
656 }
657 ?>
658
659 <?php
660 // Show placeholders only for specific email subsections (not General)
661 if (in_array($subsection_key, ['invoice_available', 'quote_available', 'payment_received', 'payment_reminder'])): ?>
662 <div class="mt-6">
663 <div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
664 <p class="text-sm font-medium text-blue-900 mb-3"><?php esc_html_e('Available Placeholders:', 'easy-invoice'); ?></p>
665 <div class="grid grid-cols-2 md:grid-cols-3 gap-2">
666 <?php if (in_array($subsection_key, ['invoice_available', 'payment_received', 'payment_reminder'])): ?>
667 <!-- Invoice-specific placeholders -->
668 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{invoice_number}}</div>
669 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{due_date}}</div>
670 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{invoice_url}}</div>
671 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_url}}</div>
672 <?php endif; ?>
673
674 <?php if (in_array($subsection_key, ['quote_available'])): ?>
675 <!-- Quote-specific placeholders -->
676 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{quote_number}}</div>
677 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{expiry_date}}</div>
678 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{quote_url}}</div>
679 <?php endif; ?>
680
681 <!-- Common placeholders for all templates -->
682 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_name}}</div>
683 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_first_name}}</div>
684 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_last_name}}</div>
685 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_email}}</div>
686 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_address}}</div>
687 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_name}}</div>
688 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{total_amount}}</div>
689 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{issue_date}}</div>
690 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_email}}</div>
691 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_phone}}</div>
692 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_terms}}</div>
693 <?php do_action('easy_invoice_settings_email_placeholders', $settings); ?>
694 </div>
695 </div>
696 </div>
697 <?php endif; ?>
698 </div>
699 </div>
700 <?php } ?>
701 <?php elseif ($section_id === 'payment' && !empty($all_gateways_sorted)): ?>
702 <div class="space-y-5">
703 <?php if (count($all_gateways_sorted) > 1): ?>
704 <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
705 <div class="flex items-center">
706 <i class="fas fa-info-circle text-blue-500 mr-2"></i>
707 <p class="text-sm text-blue-700">
708 <?php _e('Drag and drop payment methods to reorder them. The order will be reflected on payment forms.', 'easy-invoice'); ?>
709 </p>
710 </div>
711 </div>
712 <?php endif; ?>
713
714 <div id="sortable-gateways" class="space-y-5">
715 <?php foreach ($all_gateways_sorted as $gateway_id => $gateway) : ?>
716 <div class="gateway-item bg-gray-50 p-5 rounded-lg border border-gray-200 cursor-move" data-gateway-id="<?php echo esc_attr($gateway_id); ?>">
717 <div class="flex items-center mb-3 space-x-3">
718 <!-- Drag Handle -->
719 <div class="gateway-handle text-gray-400 hover:text-gray-600 cursor-move mr-2">
720 <i class="fas fa-grip-vertical"></i>
721 </div>
722
723 <input type="checkbox"
724 class="gateway-enable-checkbox h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
725 name="settings[easy_invoice_payment_methods][]"
726 id="gateway-enable-<?php echo esc_attr($gateway_id); ?>"
727 value="<?php echo esc_attr($gateway_id); ?>"
728 <?php checked(!empty($payment_methods_enabled) && in_array($gateway_id, $payment_methods_enabled)); ?>>
729
730 <div class="flex-1">
731 <label for="gateway-enable-<?php echo esc_attr($gateway_id); ?>" class="font-medium text-gray-900">
732 <?php echo esc_html(method_exists($gateway, 'getTitle') ? $gateway->getTitle() : $gateway_id); ?>
733 </label>
734 <?php if (method_exists($gateway, 'getDescription') && $gateway->getDescription()): ?>
735 <span class="block text-sm text-gray-400"><?php echo esc_html($gateway->getDescription()); ?></span>
736 <?php endif; ?>
737 </div>
738 </div>
739
740 <!-- Custom Display Name Field -->
741 <div class="gateway-settings <?php echo empty($payment_methods_enabled) || !in_array($gateway_id, $payment_methods_enabled) ? 'hidden' : ''; ?>">
742 <div class="mb-4">
743 <label for="gateway-display-name-<?php echo esc_attr($gateway_id); ?>" class="block text-sm font-medium text-gray-700">
744 <?php _e('Display Name', 'easy-invoice'); ?>
745 </label>
746 <input type="text"
747 id="gateway-display-name-<?php echo esc_attr($gateway_id); ?>"
748 name="settings[easy_invoice_gateway_display_name_<?php echo esc_attr($gateway_id); ?>]"
749 value="<?php echo esc_attr($settings['easy_invoice_gateway_display_name_' . $gateway_id] ?? (method_exists($gateway, 'getTitle') ? $gateway->getTitle() : ucfirst(str_replace('_', ' ', $gateway_id)))); ?>"
750 placeholder="<?php echo esc_attr(method_exists($gateway, 'getTitle') ? $gateway->getTitle() : ucfirst(str_replace('_', ' ', $gateway_id))); ?>"
751 class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
752 <p class="mt-1 text-xs text-gray-500">
753 <?php _e('Leave blank to use the default name. This name will appear on invoices and payment forms.', 'easy-invoice'); ?>
754 </p>
755 </div>
756
757 <?php
758 $gateway_config = method_exists($gateway, 'getSettingsConfig') ? $gateway->getSettingsConfig() : [];
759 if (!empty($gateway_config['fields'])): ?>
760 <div class="grid grid-cols-1 md:grid-cols-6 gap-4">
761 <?php foreach ($gateway_config['fields'] as $option_key => $field_config): ?>
762 <?php
763 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
764 easy_invoice_render_field($option_key, $field_config, $current_value);
765 ?>
766 <?php endforeach; ?>
767 </div>
768 <?php endif; ?>
769 </div>
770
771 <input type="hidden" class="gateway-order-input" name="settings[easy_invoice_gateway_order][]" value="<?php echo esc_attr($gateway_id); ?>">
772 </div>
773 <?php endforeach; ?>
774 </div>
775 </div>
776 <?php elseif (!empty($section_data['fields'])): ?>
777 <?php
778 // Check if this section has subsections
779 if (!empty($section_data['subsections'])) {
780 // Handle sections with subsections (like invoice, quote, etc.)
781 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
782 $subsection_title = $subsection_data['title'] ?? '';
783 $subsection_description = $subsection_data['description'] ?? '';
784 ?>
785 <div class="subsection-content mb-8">
786 <div class="mb-4">
787 <h3 class="text-lg font-medium text-gray-900"><?php echo esc_html($subsection_title); ?></h3>
788 <?php if (!empty($subsection_description)): ?>
789 <p class="text-sm text-gray-400"><?php echo esc_html($subsection_description); ?></p>
790 <?php endif; ?>
791 </div>
792 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
793 <?php if (!empty($subsection_data['fields'])) {
794 foreach ($subsection_data['fields'] as $option_key => $field_config) {
795 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
796 easy_invoice_render_field($option_key, $field_config, $current_value);
797 }
798 }
799 ?>
800 </div>
801 </div>
802 <?php
803 }
804 } else {
805 // Handle regular sections without subsections
806 if ($section_id === 'tax') {
807 // Render Enable Tax checkbox full width
808 $enable_tax_key = 'easy_invoice_tax_enabled';
809 $enable_tax_field = $section_data['fields'][$enable_tax_key];
810 $current_value = $settings[$enable_tax_key] ?? ($enable_tax_field['default'] ?? '');
811 echo '<div class="mb-4">';
812 echo '<div class="flex items-center">';
813 echo ' <input type="checkbox" name="settings[' . esc_attr($enable_tax_key) . ']" id="' . esc_attr($enable_tax_key) . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">';
814 echo ' <label for="' . esc_attr($enable_tax_key) . '" class="ml-2 block text-sm text-gray-900">' . esc_html($enable_tax_field['label']) . '</label>';
815 echo '</div>';
816 if (!empty($enable_tax_field['description'])) {
817 echo '<p id="' . $field_id . '-description" class="text-gray-400 mt-1 text-xs">' . esc_html($enable_tax_field['description']) . '</p>';
818 }
819 echo '</div>';
820 // Render the other three fields in a 3-column grid
821 echo '<div class="grid grid-cols-1 md:grid-cols-3 gap-6">';
822 $tax_keys = ['easy_invoice_tax_entry_method', 'easy_invoice_tax_rate', 'easy_invoice_tax_name'];
823 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
824 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
825 foreach ($tax_keys as $tax_key) {
826 $field_config = $section_data['fields'][$tax_key];
827 $current_value = $settings[$tax_key] ?? ($field_config['default'] ?? '');
828 echo '<div>';
829 if ($field_config['type'] !== 'checkbox') {
830 echo '<label for="' . esc_attr($tax_key) . '" class="block text-sm font-medium text-gray-700">' . esc_html($field_config['label']) . '</label>';
831 }
832 switch ($field_config['type']) {
833 case 'text':
834 case 'email':
835 case 'url':
836 case 'tel':
837 echo '<input type="' . esc_attr($field_config['type']) . '" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">';
838 break;
839 case 'number':
840 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
841 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
842 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
843 echo '<input type="number" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">';
844 break;
845 case 'select':
846 echo '<select id="' . esc_attr($tax_key) . '" name="settings[' . esc_attr($tax_key) . ']" class="' . $select_class . '">';
847 if (!empty($field_config['options']) && is_array($field_config['options'])) {
848 foreach ($field_config['options'] as $opt_val => $opt_label) {
849 echo '<option value="' . esc_attr($opt_val) . '" ' . selected($current_value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>';
850 }
851 }
852 echo '</select>';
853 break;
854 }
855 if (!empty($field_config['description'])) {
856 echo '<p class="mt-1 text-xs text-gray-400">' . esc_html($field_config['description']) . '</p>';
857 }
858 echo '</div>';
859 }
860 echo '</div>';
861
862 // Allow additional fields to be added to the tax section
863 $additional_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], $settings);
864 if (!empty($additional_tax_fields)) {
865 echo '<div class="mt-6 pt-6 border-t border-gray-200">';
866 echo '<h4 class="text-sm font-medium text-gray-900 mb-4">' . __('Additional Tax Settings', 'easy-invoice-pro') . '</h4>';
867 echo '<div class="grid grid-cols-1 md:grid-cols-2 gap-6">';
868 foreach ($additional_tax_fields as $field_key => $field_config) {
869 $current_value = $settings[$field_key] ?? ($field_config['default'] ?? '');
870 easy_invoice_render_field($field_key, $field_config, $current_value);
871 }
872 echo '</div>';
873 echo '</div>';
874 }
875 } else {
876 // Use a two-column grid for currency and company settings
877 $is_grid = ($section_id === 'currency' || $section_id === 'company');
878 // Use a 6-column grid for invoice and quote settings to allow specific field pairing
879 $is_invoice_grid = ($section_id === 'invoice');
880 $is_quote_grid = ($section_id === 'quote');
881 // Use a specific grid for advanced settings to improve spacing
882 $is_advanced_grid = ($section_id === 'advanced');
883 // Use a 3-column grid for text settings
884 $is_text_settings_grid = ($section_id === 'text_settings');
885 if ($is_grid) echo '<div class="grid grid-cols-1 md:grid-cols-2 gap-6">';
886 if ($is_invoice_grid) echo '<div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">';
887 if ($is_quote_grid) echo '<div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">';
888 if ($is_advanced_grid) echo '<div class="grid grid-cols-1 gap-6">';
889 if ($is_text_settings_grid) echo '<div class="grid grid-cols-1 md:grid-cols-3 gap-6">';
890 foreach ($section_data['fields'] as $option_key => $field_config) {
891 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
892 $field_id = esc_attr($option_key);
893 $field_type = $field_config['type'] ?? 'text';
894 $field_label = esc_html($field_config['label'] ?? '');
895 $field_placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : '';
896 $field_description = $field_config['description'] ?? '';
897 $field_name = 'settings[' . esc_attr($option_key) . ']';
898 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
899 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
900 $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6');
901 $form_group_class = '';
902 if ($is_grid) {
903 $form_group_class = '';
904 } elseif ($is_invoice_grid || $is_quote_grid || $is_advanced_grid || $is_text_settings_grid) {
905 $form_group_class = $col_span_class;
906 } else {
907 $form_group_class = $col_span_class;
908 }
909
910 // Handle conditional display
911 $conditional_class = '';
912 if (isset($field_config['depends_on']) && is_array($field_config['depends_on'])) {
913 $conditional_class = ' conditional-field';
914 foreach ($field_config['depends_on'] as $depends_key => $depends_value) {
915 $depends_field_name = 'settings[' . esc_attr($depends_key) . ']';
916 $conditional_class .= ' depends-on-' . esc_attr($depends_key) . '-' . esc_attr($depends_value);
917 }
918 }
919
920 echo '<div class="form-group ' . $form_group_class . $conditional_class . '">';
921 if ($field_type !== 'checkbox') {
922 echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . $field_label . '</label>';
923 }
924
925 // Define required and aria_describedby variables
926 $required = !empty($field_config['required']) ? 'required' : '';
927 $aria_describedby = !empty($field_description) ? 'aria-describedby="' . $field_id . '-description"' : '';
928
929 switch ($field_type) {
930 case 'text':
931 case 'email':
932 case 'url':
933 case 'tel':
934 echo '<input type="' . esc_attr($field_type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
935 break;
936 case 'number':
937 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
938 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
939 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
940 echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
941 break;
942 case 'textarea':
943 echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>';
944 break;
945 case 'select':
946 echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>';
947 if (!empty($field_config['options']) && is_array($field_config['options'])) {
948 foreach ($field_config['options'] as $opt_val => $opt_label) {
949 echo '<option value="' . esc_attr($opt_val) . '" ' . selected(strtolower($current_value), strtolower($opt_val), false) . '>' . esc_html($opt_label) . '</option>';
950 }
951 }
952 echo '</select>';
953 break;
954 case 'multiselect':
955 echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">';
956 if (!empty($field_config['options']) && is_array($field_config['options'])) {
957 foreach ($field_config['options'] as $opt_val => $opt_label) {
958 $selected = is_array($current_value) && in_array($opt_val, $current_value) ? 'selected="selected"' : '';
959 echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>';
960 }
961 }
962 echo '</select>';
963 break;
964 case 'readonly':
965 echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . ' bg-gray-50" readonly>';
966 echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($current_value) . '">';
967 break;
968 case 'checkbox':
969 echo '<div class="flex items-center">';
970 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">';
971 echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $field_label . '</label>';
972 echo '</div>';
973 if (!empty($field_description)) {
974 echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . esc_html($field_description) . '</p>';
975 }
976 break;
977 case 'image':
978 echo '<div class="mt-1 flex items-center">';
979 echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">';
980 echo ' <img id="' . $field_id . '-preview" src="' . esc_url($current_value) . '" alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">';
981 echo ' </span>';
982 echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">';
983 echo esc_html__('Change', 'easy-invoice');
984 echo '</button>';
985 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">';
986 echo '</div>';
987 break;
988 case 'wp_editor':
989 wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] );
990 // Handle description for wp_editor specifically
991 if (!empty($field_description)) {
992 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
993 }
994 break;
995 case 'color':
996 echo '<input type="color" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="h-10 w-20 border border-gray-300 rounded">';
997 break;
998 case 'range':
999 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
1000 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
1001 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
1002 echo '<input type="range" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="w-full">';
1003 echo '<span class="text-sm text-gray-400">' . esc_html($current_value) . '%</span>';
1004 break;
1005 case 'image_upload':
1006 echo '<div class="image-upload-wrap">';
1007 echo '<div class="flex items-center space-x-3">';
1008 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">';
1009 echo ' <button type="button" class="upload-image-button bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-target="' . $field_id . '">';
1010 echo esc_html__('Upload Image', 'easy-invoice');
1011 echo '</button>';
1012 echo '</div>';
1013 echo '<div class="image-preview mt-2' . (empty($current_value) ? ' hidden' : '') . '">';
1014 if (!empty($current_value)) {
1015 echo '<img src="' . esc_url($current_value) . '" alt="Preview" style="max-width: 200px; max-height: 100px;">';
1016 }
1017 echo '</div>';
1018 echo '</div>';
1019 break;
1020 case 'button':
1021 $button_text = $field_config['button_text'] ?? $field_label;
1022 $button_class = $field_config['button_class'] ?? 'button button-secondary';
1023 $button_class_name = 'regenerate-invoice-numbers-button';
1024 if ($option_key === 'easy_invoice_regenerate_quote_numbers') {
1025 $button_class_name = 'regenerate-quote-numbers-button';
1026 }
1027 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . $button_class_name . '">';
1028 echo esc_html($button_text);
1029 echo '</button>';
1030 break;
1031 default:
1032 echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '">';
1033 break;
1034 }
1035 if ($field_type !== 'checkbox' && $field_type !== 'wp_editor' && !empty($field_description)) {
1036 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
1037 }
1038 echo '</div>';
1039 }
1040 if ($is_grid) echo '</div>';
1041 if ($is_invoice_grid) echo '</div>';
1042 if ($is_quote_grid) echo '</div>';
1043 if ($is_advanced_grid) echo '</div>';
1044 if ($is_text_settings_grid) echo '</div>';
1045 }
1046 }
1047 ?>
1048 <?php endif; ?>
1049 </div>
1050 </div>
1051 </div>
1052 <?php endforeach; ?>
1053
1054 <!-- Bottom Save Button -->
1055 <div class="mt-8 pt-6 border-t border-gray-200">
1056 <div class="flex items-center justify-start space-x-3">
1057 <button type="button" id="save-settings-bottom" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200" aria-label="<?php esc_attr_e('Save Settings', 'easy-invoice'); ?>">
1058 <i class="fas fa-save mr-2" aria-hidden="true"></i>
1059 <?php esc_html_e('Save Changes', 'easy-invoice'); ?>
1060 </button>
1061 </div>
1062 </div>
1063
1064 </form>
1065 </div>
1066 </div>
1067 </div>
1068 </div>
1069
1070 <script>
1071 document.addEventListener('DOMContentLoaded', function() {
1072 // Enter key functionality to save settings
1073 const settingsForm = document.getElementById('settings-form');
1074 const saveButton = document.getElementById('save-settings-bottom');
1075
1076 if (settingsForm && saveButton) {
1077 // Get both save buttons
1078 const topSaveButton = document.getElementById('save-settings');
1079 const bottomSaveButton = document.getElementById('save-settings-bottom');
1080
1081 // Function to trigger save
1082 function triggerSave() {
1083 // Try bottom button first, then top button
1084 if (bottomSaveButton) {
1085 bottomSaveButton.click();
1086 } else if (topSaveButton) {
1087 topSaveButton.click();
1088 }
1089 }
1090
1091 // Listen for Enter key on form inputs
1092 settingsForm.addEventListener('keydown', function(e) {
1093 // Check if Enter key is pressed and not in a textarea or select
1094 if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') {
1095 e.preventDefault();
1096 triggerSave();
1097 }
1098 });
1099
1100 // Also listen for Enter key on the entire form
1101 settingsForm.addEventListener('keypress', function(e) {
1102 if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') {
1103 e.preventDefault();
1104 triggerSave();
1105 }
1106 });
1107 }
1108
1109 // Email Settings submenu hover behavior for settings page
1110 const emailNavLink = document.querySelector('.nav-link[href*="section=email"]');
1111 const emailSubmenu = document.querySelector('.email-settings-submenu');
1112
1113 if (emailNavLink && emailSubmenu) {
1114 // Check if any Email Settings subsection is active
1115 const activeSubmenuLink = document.querySelector('.email-submenu-link.text-indigo-700');
1116 const isEmailSectionActive = emailNavLink.classList.contains('text-indigo-700');
1117 const isAnySubsectionActive = activeSubmenuLink !== null;
1118
1119 // If any subsection is active, show submenu permanently and disable hover effects
1120 if (isAnySubsectionActive || isEmailSectionActive) {
1121 emailSubmenu.style.display = 'block';
1122 emailSubmenu.style.opacity = '1';
1123 emailSubmenu.style.visibility = 'visible';
1124 emailSubmenu.style.maxHeight = '200px';
1125 emailSubmenu.classList.add('show');
1126 return; // Exit early, no hover effects needed
1127 }
1128
1129 // Only add hover effects if no subsection is active
1130 // Show submenu on hover
1131 emailNavLink.addEventListener('mouseenter', function() {
1132 emailSubmenu.style.display = 'block';
1133 emailSubmenu.classList.add('show');
1134 setTimeout(() => {
1135 emailSubmenu.style.opacity = '1';
1136 emailSubmenu.style.visibility = 'visible';
1137 emailSubmenu.style.maxHeight = '200px';
1138 }, 10);
1139 });
1140
1141 // Hide submenu when mouse leaves
1142 emailNavLink.addEventListener('mouseleave', function() {
1143 // Only hide if not hovering over submenu
1144 setTimeout(() => {
1145 if (!emailSubmenu.matches(':hover')) {
1146 emailSubmenu.style.opacity = '0';
1147 emailSubmenu.style.visibility = 'hidden';
1148 emailSubmenu.style.maxHeight = '0';
1149 emailSubmenu.classList.remove('show');
1150 setTimeout(() => {
1151 emailSubmenu.style.display = 'none';
1152 }, 200);
1153 }
1154 }, 100);
1155 });
1156
1157 // Keep submenu open when hovering over submenu items
1158 emailSubmenu.addEventListener('mouseenter', function() {
1159 emailSubmenu.style.opacity = '1';
1160 emailSubmenu.style.visibility = 'visible';
1161 emailSubmenu.style.maxHeight = '200px';
1162 emailSubmenu.classList.add('show');
1163 });
1164
1165 emailSubmenu.addEventListener('mouseleave', function() {
1166 emailSubmenu.style.opacity = '0';
1167 emailSubmenu.style.visibility = 'hidden';
1168 emailSubmenu.style.maxHeight = '0';
1169 emailSubmenu.classList.remove('show');
1170 setTimeout(() => {
1171 emailSubmenu.style.display = 'none';
1172 }, 200);
1173 });
1174 }
1175 });
1176 </script>
1177
1178 <style>
1179 #screen-meta-links{
1180 display:none;
1181 }
1182 /* Email Settings Submenu Styles for Settings Page */
1183 .email-settings-submenu {
1184 transition: all 0.3s ease;
1185 overflow: hidden;
1186 }
1187
1188 .email-settings-submenu.show {
1189 display: block !important;
1190 }
1191
1192 .email-settings-submenu.show ~ .nav-link .fas.fa-chevron-down,
1193 .email-settings-submenu:hover ~ .nav-link .fas.fa-chevron-down {
1194 transform: rotate(180deg);
1195 }
1196
1197 /* Custom color for settings navigation icons */
1198 .nav-link:hover svg {
1199 color: rgb(79 70 229) !important;
1200 }
1201
1202 /* Active state icon color */
1203 .bg-indigo-50 svg {
1204 color: rgb(79 70 229) !important;
1205 }
1206
1207 /* Hover state for non-active items */
1208 .nav-link:hover:not(.bg-indigo-50) svg {
1209 color: rgb(79 70 229) !important;
1210 }
1211
1212 /* Email submenu icon colors */
1213 .email-submenu-link:hover svg {
1214 color: rgb(79 70 229) !important;
1215 }
1216
1217 .email-submenu-link.bg-indigo-50 svg {
1218 color: rgb(79 70 229) !important;
1219 }
1220 </style>
1221