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

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

1,746 lines 108.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Ensure we're in the correct namespace
3 namespace EasyInvoice\Templates\Invoices;
4
5 // Exit if accessed directly
6 if (!defined('ABSPATH')) {
7 exit;
8 }
9
10 use EasyInvoice\Models\Invoice;
11 use EasyInvoice\Models\Client;
12
13 // Prevent function redeclaration
14 if (!function_exists('easy_invoice_display_notification')) {
15 function easy_invoice_display_notification($type, $message) {
16 $bg_class = '';
17 $text_class = '';
18 $icon = '';
19
20 switch ($type) {
21 case 'success':
22 $bg_class = 'bg-green-50';
23 $text_class = 'text-green-800';
24 $icon = '<svg class="h-5 w-5 text-green-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg>';
25 break;
26 case 'error':
27 $bg_class = 'bg-red-50';
28 $text_class = 'text-red-800';
29 $icon = '<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg>';
30 break;
31 case 'warning':
32 $bg_class = 'bg-yellow-50';
33 $text_class = 'text-yellow-800';
34 $icon = '<svg class="h-5 w-5 text-yellow-400" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>';
35 break;
36 default:
37 $bg_class = 'bg-blue-50';
38 $text_class = 'text-blue-800';
39 $icon = '<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>';
40 }
41
42 echo '<div class="rounded-md ' . esc_html($bg_class) . ' p-4 mb-4">';
43 echo '<div class="flex">';
44 echo '<div class="flex-shrink-0">' . wp_kses_post($icon) . '</div>';
45 echo '<div class="ml-3">';
46 echo '<p class="text-sm font-medium ' . esc_html($text_class) . '">' . esc_html($message) . '</p>';
47 echo '</div>';
48 echo '<div class="ml-auto pl-3">';
49 echo '<div class="-mx-1.5 -my-1.5">';
50 echo '<button type="button" class="inline-flex rounded-md p-1.5 ' . esc_html($text_class) . ' hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 notification-dismiss">';
51 echo '<span class="sr-only">Dismiss</span>';
52 echo '<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">';
53 echo '<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />';
54 echo '</svg>';
55 echo '</button>';
56 echo '</div>';
57 echo '</div>';
58 echo '</div>';
59 echo '</div>';
60 }
61 }
62
63 // One-time fix: Update all existing draft invoices to published status for proper permalinks
64 if (!get_option('easy_invoice_draft_invoices_fixed', false)) {
65 $invoice_repository = \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository();
66 $updated_count = $invoice_repository->updateAllExistingInvoices();
67
68 if ($updated_count > 0) {
69 // Show a one-time notification
70 add_action('admin_notices', function() use ($updated_count) {
71 echo '<div class="notice notice-success is-dismissible">';
72 /* translators: %d: number updated. */
73 echo '<p>' . sprintf(esc_html__('%d draft invoices updated to published status for proper permalinks.', 'easy-invoice'), (int) $updated_count) . '</p>';
74 echo '</div>';
75 });
76 }
77
78 // Mark as fixed
79 update_option('easy_invoice_draft_invoices_fixed', true);
80 }
81
82 // Get current view (all, trash)
83 $current_view = isset($_GET['view']) ? sanitize_text_field($_GET['view']) : 'all';
84
85 // Get status filter
86 $status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
87
88 // Get recurring filter
89 $recurring_filter = isset($_GET['recurring']) ? sanitize_text_field($_GET['recurring']) : '';
90
91 // Get client filter (from template data, falls back to GET)
92 $client_filter = isset($client_filter) ? (int) $client_filter : (isset($_GET['client_id']) ? absint($_GET['client_id']) : 0);
93 $clients_list = isset($clients_list) && is_array($clients_list) ? $clients_list : [];
94
95 // Check for bulk action notifications
96 $notification = false;
97
98 if (isset($_GET['bulk_trashed']) && $_GET['bulk_trashed'] > 0) {
99 $count = intval($_GET['bulk_trashed']);
100 /* translators: %s: number of items. */
101 easy_invoice_display_notification('success', sprintf(_n('%s invoice moved to trash.', '%s invoices moved to trash.', $count, 'easy-invoice'), $count));
102 $notification = true;
103 }
104
105 if (isset($_GET['bulk_restored']) && $_GET['bulk_restored'] > 0) {
106 $count = intval($_GET['bulk_restored']);
107 /* translators: %s: number of items. */
108 easy_invoice_display_notification('success', sprintf(_n('%s invoice restored from trash.', '%s invoices restored from trash.', $count, 'easy-invoice'), $count));
109 $notification = true;
110 }
111
112 if (isset($_GET['bulk_deleted']) && $_GET['bulk_deleted'] > 0) {
113 $count = intval($_GET['bulk_deleted']);
114 /* translators: %s: number of items. */
115 easy_invoice_display_notification('success', sprintf(_n('%s invoice permanently deleted.', '%s invoices permanently deleted.', $count, 'easy-invoice'), $count));
116 $notification = true;
117 }
118
119 if (isset($_GET['bulk_drafted']) && $_GET['bulk_drafted'] > 0) {
120 $count = intval($_GET['bulk_drafted']);
121 /* translators: %s: number of items. */
122 easy_invoice_display_notification('success', sprintf(_n('%s invoice set to draft status.', '%s invoices set to draft status.', $count, 'easy-invoice'), $count));
123 $notification = true;
124 }
125
126 if (isset($_GET['bulk_published']) && $_GET['bulk_published'] > 0) {
127 $count = intval($_GET['bulk_published']);
128 /* translators: %s: number of items. */
129 easy_invoice_display_notification('success', sprintf(_n('%s invoice published successfully.', '%s invoices published successfully.', $count, 'easy-invoice'), $count));
130 $notification = true;
131 }
132
133 if (isset($_GET['bulk_error'])) {
134 $error = sanitize_text_field($_GET['bulk_error']);
135 $error_message = 'An error occurred while processing the bulk action.';
136
137 if ($error === 'no_selection') {
138 $error_message = 'Please select at least one invoice to perform this action.';
139 } elseif ($error === 'invalid_action') {
140 $error_message = 'Please select a valid bulk action.';
141 }
142
143 easy_invoice_display_notification('error', $error_message);
144 $notification = true;
145 }
146 // Extract invoice data for statistics
147 $formatted_invoices = array();
148 foreach ($invoices as $invoice) {
149 // For Invoice model objects
150 if (is_object($invoice)) {
151 $formatted_invoices[] = array(
152 'id' => $invoice->getId(),
153 'amount' => $invoice->getTotal(),
154 'status' => $invoice->getStatus(),
155 );
156 } else {
157 // For already formatted invoice arrays
158 $formatted_invoices[] = array(
159 'id' => $invoice['id'],
160 'amount' => isset($invoice['total_amount']) ? $invoice['total_amount'] : $invoice['amount'],
161 'status' => $invoice['status'],
162 );
163 }
164 }
165
166 // Get stats from all invoices, not just the current page
167 $invoice_controller = new \EasyInvoice\Controllers\InvoiceController();
168 $stats = $invoice_controller->getInvoiceStats();
169
170 // The controller passes the real counts (found_posts and SQL counts); the
171 // template used to overwrite the total with the size of the current page and
172 // load every trashed / draft invoice as a model just to count them.
173 $all_count = (int) ($stats['total_invoices'] ?? 0);
174 if (!isset($trash_count)) {
175 $trash_count = (int) $repository->count(['post_status' => 'trash']);
176 }
177 if (!isset($draft_count)) {
178 $draft_count = (int) $repository->count(['meta_key' => '_easy_invoice_status', 'meta_value' => 'draft']); // phpcs:ignore WordPress.DB.SlowDBQuery
179 }
180
181 // Define available status filters
182 // The statuses an invoice can actually hold — "Pending" was offered but no
183 // invoice is ever in that state, while "Available" (every new invoice) was not.
184 $status_filters = array(
185 'available' => __('Available', 'easy-invoice'),
186 'paid' => __('Paid', 'easy-invoice'),
187 'partial' => __('Partially paid', 'easy-invoice'),
188 'unpaid' => __('Unpaid', 'easy-invoice'),
189 'overdue' => __('Overdue', 'easy-invoice'),
190 'draft' => __('Draft', 'easy-invoice'),
191 'cancelled' => __('Cancelled', 'easy-invoice'),
192 );
193
194 ?>
195 <div class="p-8">
196 <div class="ei-app-page-header bg-white border-b border-gray-200 px-6" style="margin-left: -2rem; margin-top: -2rem; padding-left:2rem; padding-right:2rem; margin-right: -2rem;">
197 <div class="flex items-center justify-between">
198 <div>
199 <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Invoices', 'easy-invoice'); ?></h1>
200 <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Manage and track your invoices', 'easy-invoice'); ?></p>
201 </div>
202 <div class="flex items-center space-x-3">
203 <?php if (isset($_GET['rewrite_flushed'])): ?>
204 <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-2 rounded-md text-sm">
205 <?php esc_html_e('Rewrite rules flushed successfully!', 'easy-invoice'); ?>
206 </div>
207 <?php endif; ?>
208
209 <?php if (isset($_GET['post_types_registered'])): ?>
210 <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-2 rounded-md text-sm">
211 <?php esc_html_e('Post types registered successfully!', 'easy-invoice'); ?>
212 </div>
213 <?php endif; ?>
214
215 <?php
216 // "Flush Rewrite Rules" is a troubleshooting action, not something a
217 // user does as part of their job — but it rendered unconditionally as
218 // a prominent button right beside the primary "Create New Invoice" CTA
219 // on every admin's screen. It is now hidden by default and shown when
220 // WP_DEBUG is on, or when a site opts in via the filter.
221 //
222 // Kept rather than deleted because it is the manual escape hatch if
223 // invoice permalinks ever 404.
224 $ei_show_maintenance_tools = apply_filters(
225 'easy_invoice_show_maintenance_tools',
226 defined('WP_DEBUG') && WP_DEBUG
227 );
228 if ($ei_show_maintenance_tools) : ?>
229 <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin-post.php?action=flush_easy_invoice_rewrite_rules'), 'flush_easy_invoice_rewrite_rules')); ?>"
230 class="bg-yellow-100 text-yellow-700 hover:bg-yellow-200 px-4 py-2 rounded-md text-sm font-medium transition-colors duration-200">
231 <?php esc_html_e('Flush Rewrite Rules', 'easy-invoice'); ?>
232 </a>
233 <?php endif; ?>
234
235 <?php
236 // "Export All" is part of the bulk_operations addon
237 // (Personal tier). Gate three states:
238 // addon loaded → working POST form
239 // free user (no Pro) → upsell button + modal
240 // Pro w/o addon active → hidden (enable in Addons grid)
241 $ei_bulk_ops_loaded = \EasyInvoice\Addons\AddonManager::shouldLoad('bulk_operations');
242 ?>
243 <?php if ($ei_bulk_ops_loaded): ?>
244 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
245 <input type="hidden" name="action" value="easy_invoice_export_all_invoices" />
246 <input type="hidden" name="_wpnonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_export_all_invoices')); ?>" />
247 <button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
248 <svg class="w-4 h-4 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
249 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
250 </svg>
251 <?php esc_html_e('Export All', 'easy-invoice'); ?>
252 </button>
253 </form>
254 <?php elseif (!easy_invoice_has_pro()): ?>
255 <button type="button" id="export-all-invoices-btn" class="premium-export-btn inline-flex items-center px-4 py-2 border border-gray-300 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 transition-colors duration-200 relative">
256 <svg class="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
257 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
258 </svg>
259 <?php esc_html_e('Export All', 'easy-invoice'); ?>
260 <svg class="ml-2 w-5 h-5 text-purple-600 crown-icon" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
261 <path d="M12 8L15 13.2L18 10.5L17.3 14H6.7L6 10.5L9 13.2L12 8M12 4L8.5 10L3 5L5 16H19L21 5L15.5 10L12 4Z"/>
262 </svg>
263 </button>
264 <script>
265 jQuery(document).ready(function($) {
266 $('#export-all-invoices-btn').on('click', function(e) {
267 e.preventDefault();
268 if (typeof EasyInvoiceConfirmation !== 'undefined') {
269 EasyInvoiceConfirmation.showFeatureUpgrade('Export All Invoices', 'Export all your invoices to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.');
270 } else {
271 alert('<?php echo esc_js(__('Exporting all invoices is a premium feature. Upgrade to Easy Invoice Pro to unlock this feature.', 'easy-invoice')); ?>');
272 }
273 });
274 });
275 </script>
276 <?php endif; ?>
277 <?php if (easy_invoice_user_can('ei_create_invoice')) : ?>
278 <a href="#" id="create-new-invoice-btn" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
279 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
280 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
281 </svg>
282 <?php esc_html_e('Create New Invoice', 'easy-invoice'); ?>
283 </a>
284 <?php endif; ?>
285 </div>
286 </div>
287 </div>
288
289 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8 mt-8">
290 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
291 <div class="flex items-center">
292 <div class="rounded-full bg-indigo-100 p-3 mr-4">
293 <svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
294 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
295 </svg>
296 </div>
297 <div>
298 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Invoices', 'easy-invoice'); ?></h3>
299 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($stats['total_invoices']); ?></p>
300 </div>
301 </div>
302 </div>
303 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
304 <div class="flex items-center">
305 <div class="rounded-full bg-green-100 p-3 mr-4">
306 <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
307 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
308 </svg>
309 </div>
310 <div>
311 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Revenue', 'easy-invoice'); ?></h3>
312 <?php if (is_array($stats['total_revenue']) && !empty($stats['total_revenue'])): ?>
313 <div class="mt-1">
314 <?php foreach ($stats['total_revenue'] as $currency => $data): ?>
315 <?php
316 // Create a temporary invoice object with the currency for proper formatting
317 $temp_invoice = new \EasyInvoice\Models\Invoice();
318 $temp_invoice->setCurrencyCode($currency);
319 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($temp_invoice);
320 ?>
321 <p class="text-lg font-bold text-gray-900">
322 <?php echo esc_html($formatter->format($data['amount'])); ?>
323 <span class="text-sm font-normal text-gray-500"><?php echo esc_html($currency); ?></span>
324 </p>
325 <?php endforeach; ?>
326 </div>
327 <?php else: ?>
328 <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p>
329 <?php endif; ?>
330 </div>
331 </div>
332 </div>
333 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
334 <div class="flex items-center">
335 <div class="rounded-full bg-blue-100 p-3 mr-4">
336 <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
337 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
338 </svg>
339 </div>
340 <div>
341 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Value', 'easy-invoice'); ?></h3>
342 <?php if (is_array($stats['total_value']) && !empty($stats['total_value'])): ?>
343 <div class="mt-1">
344 <?php foreach ($stats['total_value'] as $currency => $data): ?>
345 <?php
346 $formatted_amount = easy_invoice_format_money((float) $data['amount'], null, (string) $currency);
347 ?>
348 <div class="text-lg font-bold text-gray-900">
349 <?php echo esc_html($formatted_amount); ?>
350 <span class="text-xs font-normal text-gray-500 ml-1">(<?php echo esc_html($data['invoices']); ?> <?php echo $data['invoices'] > 1 ? 'invoices' : 'invoice'; ?>)</span>
351 </div>
352 <?php endforeach; ?>
353 </div>
354 <?php else: ?>
355 <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p>
356 <?php endif; ?>
357 </div>
358 </div>
359 </div>
360 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
361 <div class="flex items-center">
362 <div class="rounded-full bg-green-100 p-3 mr-4">
363 <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
364 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
365 </svg>
366 </div>
367 <div>
368 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Paid Invoices', 'easy-invoice'); ?></h3>
369 <p class="mt-1 text-2xl font-bold text-green-600"><?php echo esc_html($stats['paid_invoices']); ?></p>
370 </div>
371 </div>
372 </div>
373 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
374 <div class="flex items-center">
375 <div class="rounded-full bg-yellow-100 p-3 mr-4">
376 <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
377 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
378 </svg>
379 </div>
380 <div>
381 <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Outstanding', 'easy-invoice'); ?></h3>
382 <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html($stats['pending_invoices']); ?></p>
383 </div>
384 </div>
385 </div>
386 </div>
387
388 <?php if ($total_invoices > 0): ?>
389 <div class="mb-4">
390 <div class="px-4 py-3 flex items-center justify-between">
391 <div class="flex items-center space-x-2">
392 <span class="text-sm text-gray-700">
393 Showing
394 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
395 to
396 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_invoices)); ?></span>
397 of
398 <span class="font-medium"><?php echo number_format($total_invoices); ?></span>
399 results
400 </span>
401 </div>
402 <?php if ($total_pages > 1): ?>
403 <div class="flex items-center space-x-2">
404 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
405 <?php
406 // Use WordPress's built-in pagination for top
407 $top_pagination_args = array(
408 'base' => add_query_arg('paged', '%#%'),
409 'format' => '',
410 'current' => $current_page,
411 'total' => $total_pages,
412 'prev_text' => '‹ Previous',
413 'next_text' => 'Next ›',
414 'type' => 'array',
415 'end_size' => 1,
416 'mid_size' => 2,
417 'add_args' => array(
418 'view' => $current_view,
419 'status' => $status_filter,
420 'recurring' => $recurring_filter,
421 'client_id' => $client_filter ?: false,
422 )
423 );
424
425 $top_pagination_links = paginate_links($top_pagination_args);
426
427 if ($top_pagination_links) {
428 echo '<div class="flex space-x-1">';
429 foreach ($top_pagination_links as $link) {
430 // Add null check to prevent passing null to easy_invoice_str_replace
431 if ($link === null) {
432 continue;
433 }
434
435 // Convert WordPress pagination to our styling
436 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
437 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
438 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
439 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
440 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
441
442 // Add default styling for regular page numbers
443 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
444 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
445 }
446
447 echo wp_kses_post($link);
448 }
449 echo '</div>';
450 }
451 ?>
452 </div>
453 <?php endif; ?>
454 </div>
455 </div>
456 <?php endif; ?>
457
458 <div class="bg-white rounded-lg shadow mb-6 overflow-hidden">
459 <div class="border-b border-gray-200 bg-gradient-to-r from-indigo-50 to-white">
460 <nav class="flex">
461 <?php
462 // View Tabs (All, Draft, Trash)
463 $views = array(
464 'all' => 'All (' . $all_count . ')',
465 'draft' => 'Draft (' . $draft_count . ')',
466 'trash' => 'Trash (' . $trash_count . ')'
467 );
468
469 foreach ($views as $view => $label) {
470 $is_current = $current_view === $view;
471 $status_param = !empty($status_filter) && $view === 'all' ? '&status=' . $status_filter : '';
472 if (!empty($client_filter)) {
473 $status_param .= '&client_id=' . (int) $client_filter;
474 }
475 $class = $is_current
476 ? 'border-b-2 border-indigo-500 text-indigo-600 bg-white'
477 : 'border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300';
478 ?>
479 <a href="?page=easy-invoice-all&view=<?php echo esc_attr($view . $status_param); ?>"
480 class="flex items-center whitespace-nowrap py-4 px-6 font-medium text-sm transition-colors duration-200 <?php echo esc_attr($class); ?>">
481 <?php if ($view === 'all'): ?>
482 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
483 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
484 </svg>
485 <?php elseif ($view === 'draft'): ?>
486 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
487 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
488 </svg>
489 <?php elseif ($view === 'trash'): ?>
490 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
491 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
492 </svg>
493 <?php endif; ?>
494 <?php echo esc_html($label); ?>
495 </a>
496 <?php
497 }
498 ?>
499 </nav>
500 </div>
501
502 <div class="p-4 flex flex-wrap items-center justify-between bg-white border-b border-gray-100">
503 <div class="flex items-center space-x-2 my-2">
504 <?php if (easy_invoice_user_can('ei_create_invoice')) : ?>
505 <form id="bulk-action-form" method="post" class="flex items-center gap-2">
506 <?php wp_nonce_field('easy_invoice_bulk_action', 'easy_invoice_bulk_nonce'); ?>
507 <input type="hidden" name="action" value="easy_invoice_bulk_action">
508
509 <select name="bulk_action" aria-label="Bulk action for selected invoices" class="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white text-gray-700 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
510 <option value=""><?php esc_html_e('Bulk Actions', 'easy-invoice'); ?></option>
511 <?php if ($current_view === 'trash'): ?>
512 <option value="restore"><?php esc_html_e('Restore', 'easy-invoice'); ?></option>
513 <option value="delete"><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></option>
514 <?php elseif ($current_view === 'draft'): ?>
515 <option value="publish"><?php esc_html_e('Publish', 'easy-invoice'); ?></option>
516 <option value="trash"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></option>
517 <?php else: ?>
518 <option value="trash"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></option>
519 <option value="draft"><?php esc_html_e('Set to Draft', 'easy-invoice'); ?></option>
520 <?php endif; ?>
521 <option value="export" data-requires-pro="1"><?php esc_html_e('Export Selected (Pro)', 'easy-invoice'); ?></option>
522 </select>
523
524 <button type="submit" 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 whitespace-nowrap">
525 Apply
526 </button>
527 </form>
528 <?php endif; ?>
529 </div>
530
531 <?php if ($current_view !== 'trash'): ?>
532 <div class="flex items-center gap-2">
533 <form method="get" class="flex items-center gap-2">
534 <input type="hidden" name="page" value="easy-invoice-all">
535 <input type="hidden" name="view" value="<?php echo esc_attr($current_view); ?>">
536 <?php if (!empty($status_filter)): ?>
537 <input type="hidden" name="status" value="<?php echo esc_attr($status_filter); ?>">
538 <?php endif; ?>
539 <?php if (!empty($recurring_filter)): ?>
540 <input type="hidden" name="recurring" value="<?php echo esc_attr($recurring_filter); ?>">
541 <?php endif; ?>
542
543 <?php if (!empty($clients_list)): ?>
544 <select name="client_id" aria-label="<?php esc_attr_e('Filter by client', 'easy-invoice'); ?>"
545 class="block px-3 py-2 border border-gray-300 rounded-md bg-white text-gray-700 sm:text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
546 onchange="this.form.submit()">
547 <option value=""><?php esc_html_e('All clients', 'easy-invoice'); ?></option>
548 <?php foreach ($clients_list as $client_option): ?>
549 <option value="<?php echo esc_attr($client_option['id']); ?>" <?php selected((int) $client_filter, (int) $client_option['id']); ?>>
550 <?php echo esc_html($client_option['name']); ?>
551 </option>
552 <?php endforeach; ?>
553 </select>
554 <?php endif; ?>
555
556 <div class="relative">
557 <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
558 <svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
559 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
560 </svg>
561 </div>
562 <input type="text"
563 name="search"
564 value="<?php echo esc_attr($search_query); ?>"
565 placeholder="<?php echo esc_js(__('Search invoices, numbers, or clients...', 'easy-invoice')); ?>"
566 class="block w-64 pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
567 </div>
568
569 <button type="submit" 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">
570 <?php esc_html_e('Search', 'easy-invoice'); ?>
571 </button>
572
573 <?php if (!empty($search_query)): ?>
574 <a href="<?php echo esc_url(remove_query_arg('search', $_SERVER['REQUEST_URI'])); ?>" class="inline-flex items-center px-4 py-2 border border-gray-300 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">
575 <?php esc_html_e('Clear', 'easy-invoice'); ?>
576 </a>
577 <?php endif; ?>
578 </form>
579 </div>
580 <?php endif; ?>
581 </div>
582
583 <?php if ($current_view !== 'trash'): ?>
584 <div class="p-4 bg-white border-b border-gray-100">
585 <div class="flex flex-wrap items-center gap-2 my-2">
586 <span class="text-sm text-gray-500 mr-2"><?php esc_html_e('Filter by status:', 'easy-invoice'); ?></span>
587 <div class="flex flex-wrap gap-1">
588 <?php
589 // Build base URL with current view, recurring filter, client filter and search query
590 $base_url = '?page=easy-invoice-all&view=' . $current_view;
591 if (!empty($recurring_filter)) {
592 $base_url .= '&recurring=' . urlencode($recurring_filter);
593 }
594 if (!empty($client_filter)) {
595 $base_url .= '&client_id=' . (int) $client_filter;
596 }
597 if (!empty($search_query)) {
598 $base_url .= '&search=' . urlencode($search_query);
599 }
600 ?>
601 <a href="<?php echo esc_url($base_url); ?>" class="<?php echo empty($status_filter) ? 'bg-indigo-100 text-indigo-800 ring-1 ring-indigo-300' : 'bg-gray-50 text-gray-700 hover:bg-gray-100'; ?> px-3 py-1 rounded-full text-xs font-medium transition-colors duration-200">
602 All
603 </a>
604
605 <?php
606 $status_colors = [
607 'available' => 'bg-blue-100 text-blue-800 ring-1 ring-blue-300',
608 'paid' => 'bg-green-100 text-green-800 ring-1 ring-green-300',
609 'partial' => 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300',
610 'unpaid' => 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300',
611 'pending' => 'bg-blue-100 text-blue-800 ring-1 ring-blue-300',
612 'draft' => 'bg-gray-100 text-gray-800 ring-1 ring-gray-300',
613 'overdue' => 'bg-red-100 text-red-800 ring-1 ring-red-300',
614 'cancelled' => 'bg-gray-100 text-gray-800 ring-1 ring-gray-300'
615 ];
616
617 foreach ($status_filters as $status_key => $status_label):
618 $is_current = $status_filter === $status_key;
619 $base_class = 'px-3 py-1 rounded-full text-xs font-medium transition-colors duration-200';
620
621 if ($is_current) {
622 $color_class = $status_colors[$status_key] ?? 'bg-indigo-100 text-indigo-800 ring-1 ring-indigo-300';
623 } else {
624 $color_class = 'bg-gray-50 text-gray-700 hover:bg-gray-100';
625 }
626
627 // Build URL with status filter
628 $status_url = $base_url . '&status=' . urlencode($status_key);
629 ?>
630 <a href="<?php echo esc_url($status_url); ?>"
631 class="<?php echo esc_html($base_class) . ' ' . esc_html($color_class); ?>">
632 <?php echo esc_html($status_label); ?>
633 </a>
634 <?php endforeach; ?>
635 </div>
636 <?php do_action('easy_invoice_inline_type_filter'); ?>
637 </div>
638
639 <?php do_action('easy_invoice_before_invoice_listing'); ?>
640 <?php endif; ?>
641 </div>
642 </div>
643
644 <div class="bg-white rounded-lg shadow overflow-hidden">
645 <div class="overflow-x-auto">
646 <table class="min-w-full divide-y divide-gray-200 ei-invoices-table">
647 <thead>
648 <tr class="bg-gray-50">
649 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-10 border-b border-gray-200">
650 <input type="checkbox" id="select-all" aria-label="Select all invoices on this page" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
651 </th>
652 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200">
653 <?php esc_html_e('Invoice', 'easy-invoice'); ?>
654 </th>
655 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200">
656 <?php esc_html_e('Client', 'easy-invoice'); ?>
657 </th>
658 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200">
659 <?php esc_html_e('Status', 'easy-invoice'); ?>
660 </th>
661 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200">
662 <?php esc_html_e('Amount', 'easy-invoice'); ?>
663 </th>
664 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200">
665 <?php esc_html_e('Due Date', 'easy-invoice'); ?>
666 </th>
667 <?php do_action('easy_invoice_after_invoice_listing_header'); ?>
668 </tr>
669 </thead>
670 <tbody class="bg-white divide-y divide-gray-100">
671 <?php foreach ($invoices as $invoice) :
672 // Handle both object and array formats
673 if (is_object($invoice)) {
674 $invoice_id = $invoice->getId();
675 $invoice_number = $invoice->getNumber();
676 $invoice_title = $invoice->getTitle();
677
678 // Get client name directly from client repository
679 $client_name = 'No Client';
680 if ($invoice->getClientId() > 0) {
681 $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
682 $client = $client_repository->find($invoice->getClientId());
683 if ($client) {
684 $client_name = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName());
685 if (empty(trim($client_name))) {
686 $client_name = $client->getDisplayName() ?: 'Client ' . $client->getId();
687 }
688 }
689 } elseif ($invoice->getCustomerName()) {
690 $client_name = $invoice->getCustomerName();
691 }
692
693 $client_email = $invoice->getCustomerEmail();
694 $status = $invoice->getStatus();
695
696 // Use formatter to display amount properly
697 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
698 $amount = $formatter->format($invoice->getTotal());
699 $due_date = $invoice->getDueDate();
700 } else {
701 $invoice_id = $invoice['id'];
702 $invoice_number = $invoice['invoice_number'];
703 $invoice_title = isset($invoice['title']) ? $invoice['title'] : $invoice_number;
704 $client_name = $invoice['client_name'];
705 $client_email = isset($invoice['client_email']) ? $invoice['client_email'] : '';
706 $status = $invoice['status'];
707 $amount = $invoice['total_amount'];
708 $due_date = $invoice['due_date'];
709 }
710
711 // Format the display title
712 $display_title = $invoice_title ?: 'Untitled Invoice';
713
714 // An invoice still owed after its due date reads as overdue
715 // whatever its stored status says (nothing flips the status
716 // by itself); the underlying status stays for the filters.
717 $status = \EasyInvoice\Helpers\InvoiceStatus::displayKey((string) $status, $due_date ? (string) $due_date : null);
718 $status_class = 'available' === $status
719 ? 'bg-gray-100 text-gray-800 ring-1 ring-gray-200'
720 : \EasyInvoice\Helpers\InvoiceStatus::badgeClass($status);
721 ?>
722 <?php do_action('easy_invoice_before_invoice_listing_row', $invoice); ?>
723 <tr class="hover:bg-gray-50 transition-colors duration-150" data-invoice-id="<?php echo (int) $invoice_id; ?>">
724 <td class="px-6 py-4 whitespace-nowrap">
725 <input type="checkbox" name="invoice_ids[]" value="<?php echo (int) $invoice_id; ?>" form="bulk-action-form" aria-label="<?php /* translators: %s: invoice number. */ echo esc_attr(sprintf(__('Select invoice %s', 'easy-invoice'), $invoice_number)); ?>" class="invoice-checkbox rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
726 </td>
727 <td class="px-6 py-4 whitespace-nowrap">
728 <?php if ($current_view === 'trash'): ?>
729 <div class="text-sm font-medium text-gray-900">
730 <?php echo esc_html($display_title); ?>
731 </div>
732 <div class="text-xs text-gray-500 mt-1">
733 <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice)); ?>
734 <?php if ('draft' !== get_post_meta($invoice_id, '_easy_invoice_status', true)) : ?>
735 <span class="ei-viewed ml-2 inline-flex items-center gap-1 <?php echo \EasyInvoice\Services\DocumentViews::summary($invoice_id)['count'] ? 'text-emerald-600' : 'text-gray-400'; ?>" title="<?php echo esc_attr(\EasyInvoice\Services\DocumentViews::summary($invoice_id)['from'] ? sprintf(/* translators: %s: anonymised IP. */ __('Last opened from %s', 'easy-invoice'), \EasyInvoice\Services\DocumentViews::summary($invoice_id)['from']) : ''); ?>">
736 <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
737 <?php echo esc_html(\EasyInvoice\Services\DocumentViews::label($invoice_id)); ?>
738 </span>
739 <?php endif; ?>
740 <?php
741 // Check if this invoice was converted from a quote
742 $quote_info = \EasyInvoice\Helpers\QuoteInvoiceHelper::getQuoteInfoFromInvoice($invoice_id);
743 if ($quote_info): ?>
744 <span class="ml-2 inline-flex items-center space-x-1">
745 <i class="<?php echo esc_attr($quote_info['icon_class']); ?> text-blue-500 text-xs" title="<?php echo esc_attr($quote_info['tooltip_text']); ?>"></i>
746 <a href="<?php echo esc_url($quote_info['url']); ?>"
747 target="_blank"
748 class="text-blue-600 hover:text-blue-800 hover:underline text-xs"
749 title="<?php echo esc_attr($quote_info['tooltip_text']); ?>">
750 <?php echo esc_html($quote_info['number'] ?: $quote_info['title']); ?>
751 </a>
752 </span>
753 <?php endif; ?>
754 </div>
755 <div class="row-actions mt-1">
756 <span class="restore"><a href="#" class="restore-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path></svg><?php esc_html_e('Restore', 'easy-invoice'); ?></a></span>
757 <span class="delete"><a href="#" class="delete-permanently text-red-600" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></a></span>
758 </div>
759 <?php elseif ($current_view === 'draft'): ?>
760 <div class="text-sm font-medium text-gray-900">
761 <a href="<?php echo esc_url(get_permalink($invoice_id)); ?>" class="text-indigo-600 hover:text-indigo-900">
762 <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_title', esc_html($display_title), $invoice)); ?>
763 </a>
764 </div>
765 <div class="text-xs text-gray-500 mt-1">
766 <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice)); ?>
767 <?php if ('draft' !== get_post_meta($invoice_id, '_easy_invoice_status', true)) : ?>
768 <span class="ei-viewed ml-2 inline-flex items-center gap-1 <?php echo \EasyInvoice\Services\DocumentViews::summary($invoice_id)['count'] ? 'text-emerald-600' : 'text-gray-400'; ?>" title="<?php echo esc_attr(\EasyInvoice\Services\DocumentViews::summary($invoice_id)['from'] ? sprintf(/* translators: %s: anonymised IP. */ __('Last opened from %s', 'easy-invoice'), \EasyInvoice\Services\DocumentViews::summary($invoice_id)['from']) : ''); ?>">
769 <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
770 <?php echo esc_html(\EasyInvoice\Services\DocumentViews::label($invoice_id)); ?>
771 </span>
772 <?php endif; ?>
773 <?php
774 // Check if this invoice was converted from a quote
775 $quote_info = \EasyInvoice\Helpers\QuoteInvoiceHelper::getQuoteInfoFromInvoice($invoice_id);
776 if ($quote_info): ?>
777 <span class="ml-2 inline-flex items-center space-x-1">
778 <i class="<?php echo esc_attr($quote_info['icon_class']); ?> text-blue-500 text-xs" title="<?php echo esc_attr($quote_info['tooltip_text']); ?>"></i>
779 <a href="<?php echo esc_url($quote_info['url']); ?>"
780 target="_blank"
781 class="text-blue-600 hover:text-blue-800 hover:underline text-xs"
782 title="<?php echo esc_attr($quote_info['tooltip_text']); ?>">
783 <?php echo esc_html($quote_info['number'] ?: $quote_info['title']); ?>
784 </a>
785 </span>
786 <?php endif; ?>
787 </div>
788 <div class="row-actions mt-1">
789 <span class="edit"><a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id)); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg><?php esc_html_e('Edit', 'easy-invoice'); ?></a></span>
790 <span class="publish"><a href="#" class="publish-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg><?php esc_html_e('Publish', 'easy-invoice'); ?></a></span>
791 <span class="trash"><a href="#" class="trash-invoice text-red-600" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php esc_html_e('Trash', 'easy-invoice'); ?></a></span>
792 <span class="email"><a href="#" class="email-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg><?php esc_html_e('Send Email', 'easy-invoice'); ?></a></span>
793 <span class="pdf"><a href="<?php echo esc_url(get_permalink($invoice_id)); ?>?auto_download_pdf=1" target="_blank" class="download-pdf" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg><?php esc_html_e('Download PDF', 'easy-invoice'); ?></a></span>
794 <?php if (!easy_invoice_has_pro()): ?>
795 <span class="export premium-feature"><a href="#" class="export-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>Export</a></span>
796 <?php endif; ?>
797 <?php if (!easy_invoice_has_pro()): ?>
798 <span class="duplicate premium-feature"><a href="#" class="duplicate-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7l2 4 4 .5-3 2.5.5 4-3.5-2-3.5 2 .5-4-3-2.5 4-.5 2-4z"/></svg>Duplicate</a></span>
799 <?php endif; ?>
800
801 <?php
802 // Apply filter for Pro plugin to add secure link actions
803 $secure_actions = apply_filters('easy_invoice_invoice_row_actions', [], $invoice);
804 foreach ($secure_actions as $action_key => $action_html) {
805 echo '<span class="' . esc_attr($action_key) . '">' . wp_kses_post($action_html) . '</span>';
806 }
807 ?>
808 </div>
809 <?php else: ?>
810 <div class="text-sm font-medium text-gray-900">
811 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id)); ?>" class="text-indigo-600 hover:text-indigo-900">
812 <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_title', esc_html($display_title), $invoice)); ?>
813 </a>
814 </div>
815 <div class="text-xs text-gray-500 mt-1">
816 <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice)); ?>
817 <?php if ('draft' !== get_post_meta($invoice_id, '_easy_invoice_status', true)) : ?>
818 <span class="ei-viewed ml-2 inline-flex items-center gap-1 <?php echo \EasyInvoice\Services\DocumentViews::summary($invoice_id)['count'] ? 'text-emerald-600' : 'text-gray-400'; ?>" title="<?php echo esc_attr(\EasyInvoice\Services\DocumentViews::summary($invoice_id)['from'] ? sprintf(/* translators: %s: anonymised IP. */ __('Last opened from %s', 'easy-invoice'), \EasyInvoice\Services\DocumentViews::summary($invoice_id)['from']) : ''); ?>">
819 <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
820 <?php echo esc_html(\EasyInvoice\Services\DocumentViews::label($invoice_id)); ?>
821 </span>
822 <?php endif; ?>
823 <?php
824 // Check if this invoice was converted from a quote
825 $quote_info = \EasyInvoice\Helpers\QuoteInvoiceHelper::getQuoteInfoFromInvoice($invoice_id);
826 if ($quote_info): ?>
827 <span class="ml-2 inline-flex items-center space-x-1">
828 <i class="<?php echo esc_attr($quote_info['icon_class']); ?> text-blue-500 text-xs" title="<?php echo esc_attr($quote_info['tooltip_text']); ?>"></i>
829 <a href="<?php echo esc_url($quote_info['url']); ?>"
830 target="_blank"
831 class="text-blue-600 hover:text-blue-800 hover:underline text-xs"
832 title="<?php echo esc_attr($quote_info['tooltip_text']); ?>">
833 <?php echo esc_html($quote_info['number'] ?: $quote_info['title']); ?>
834 </a>
835 </span>
836 <?php endif; ?>
837 </div>
838 <div class="row-actions mt-1">
839 <span class="view"><a href="<?php echo esc_url(apply_filters('easy_invoice_invoice_view_url', get_permalink($invoice_id), $invoice_id)); ?>" target="_blank"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg><?php esc_html_e('View', 'easy-invoice'); ?></a></span>
840 <span class="edit"><a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id)); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg><?php esc_html_e('Edit', 'easy-invoice'); ?></a></span>
841 <?php if (in_array((string) $invoice->getStatus(), ['available', 'unpaid', 'overdue'], true)) : // paid, part-paid and cancelled invoices are not un-issued ?>
842 <?php if (is_object($invoice) && in_array(strtolower((string) $invoice->getStatus()), ['available', 'unpaid', 'overdue'], true)) : // paid, part-paid and cancelled invoices are not un-issued ?>
843 <span class="draft"><a href="#" class="draft-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg><?php esc_html_e('Draft', 'easy-invoice'); ?></a></span>
844 <?php endif; ?>
845 <?php endif; ?>
846 <span class="trash"><a href="#" class="trash-invoice text-red-600" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php esc_html_e('Trash', 'easy-invoice'); ?></a></span>
847 <span class="email"><a href="#" class="email-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg><?php esc_html_e('Send Email', 'easy-invoice'); ?></a></span>
848 <span class="pdf"><a href="<?php echo esc_url(get_permalink($invoice_id)); ?>?auto_download_pdf=1" target="_blank" class="download-pdf" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg><?php esc_html_e('Download PDF', 'easy-invoice'); ?></a></span>
849 <?php if (!easy_invoice_has_pro()): ?>
850 <span class="export premium-feature"><a href="#" class="export-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>Export</a></span>
851 <?php endif; ?>
852 <?php if (!easy_invoice_has_pro()): ?>
853 <span class="duplicate premium-feature"><a href="#" class="duplicate-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7l2 4 4 .5-3 2.5.5 4-3.5-2-3.5 2 .5-4-3-2.5 4-.5 2-4z"/></svg>Duplicate</a></span>
854 <?php endif; ?>
855
856 <?php
857 // Apply filter for Pro plugin to add secure link actions
858 $secure_actions = apply_filters('easy_invoice_invoice_row_actions', [], $invoice);
859 foreach ($secure_actions as $action_key => $action_html) {
860 echo '<span class="' . esc_attr($action_key) . '">' . wp_kses_post($action_html) . '</span>';
861 }
862 ?>
863 </div>
864 <?php endif; ?>
865 </td>
866 <td class="px-6 py-4 whitespace-nowrap">
867 <div class="text-sm text-gray-900"><?php echo esc_html($client_name ?: 'No Client'); ?></div>
868 <div class="text-sm text-gray-500"><?php echo esc_html($client_email ?: ''); ?></div>
869 </td>
870 <td class="px-6 py-4 whitespace-nowrap">
871 <span class="px-3 py-1 inline-flex text-xs leading-5 font-medium rounded-full <?php echo esc_attr($status_class); ?>">
872 <?php echo esc_html(\EasyInvoice\Helpers\InvoiceStatus::label($status)); ?>
873 </span>
874 </td>
875 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 font-medium">
876 <?php echo esc_html($amount); ?>
877 </td>
878 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
879 <?php echo esc_html($due_date); ?>
880 </td>
881 <?php do_action('easy_invoice_after_invoice_listing_row', $invoice); ?>
882 </tr>
883 <?php endforeach; ?>
884
885 <?php if (empty($invoices)): ?>
886 <tr>
887 <td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500">
888 <?php
889 // These three strings were hardcoded English with no
890 // __() wrapper, so they could never be translated even
891 // though the rest of the screen is fully localised.
892 $ei_create_link = '<a href="#" class="text-indigo-600 hover:text-indigo-900 create-first-invoice">'
893 . esc_html__('Create your first invoice', 'easy-invoice')
894 . '</a>';
895 ?>
896 <?php if ($current_view === 'trash'): ?>
897 <?php esc_html_e('No invoices found in trash.', 'easy-invoice'); ?>
898 <?php elseif ($current_view === 'draft'): ?>
899 <?php
900 /* translators: %s: link reading "Create your first invoice". */
901 printf(esc_html__('No invoices found in draft. %s.', 'easy-invoice'), wp_kses_post($ei_create_link));
902 ?>
903 <?php else: ?>
904 <?php
905 /* translators: %s: link reading "Create your first invoice". */
906 printf(esc_html__('No invoices found. %s.', 'easy-invoice'), wp_kses_post($ei_create_link));
907 ?>
908 <?php endif; ?>
909 </td>
910 </tr>
911 <?php endif; ?>
912 </tbody>
913 </table>
914 </div>
915 </div>
916
917 <?php if ($total_invoices > 0): ?>
918 <div class="mt-4">
919 <div class="px-4 py-3 flex items-center justify-between">
920 <div class="flex items-center space-x-2">
921 <span class="text-sm text-gray-700">
922 Showing
923 <span class="font-medium"><?php echo number_format((($current_page - 1) * $per_page) + 1); ?></span>
924 to
925 <span class="font-medium"><?php echo number_format(min($current_page * $per_page, $total_invoices)); ?></span>
926 of
927 <span class="font-medium"><?php echo number_format($total_invoices); ?></span>
928 results
929 </span>
930 </div>
931 <?php if ($total_pages > 1): ?>
932 <div class="flex items-center space-x-2">
933 <span class="text-sm text-gray-500">Page <?php echo number_format($current_page); ?> of <?php echo number_format($total_pages); ?></span>
934 <?php
935 // Use WordPress's built-in pagination for bottom
936 $bottom_pagination_args = array(
937 'base' => add_query_arg('paged', '%#%'),
938 'format' => '',
939 'current' => $current_page,
940 'total' => $total_pages,
941 'prev_text' => '‹ Previous',
942 'next_text' => 'Next ›',
943 'type' => 'array',
944 'end_size' => 1,
945 'mid_size' => 2,
946 'add_args' => array(
947 'view' => $current_view,
948 'status' => $status_filter,
949 'recurring' => $recurring_filter
950 )
951 );
952
953 $bottom_pagination_links = paginate_links($bottom_pagination_args);
954
955 if ($bottom_pagination_links) {
956 echo '<div class="flex space-x-1">';
957 foreach ($bottom_pagination_links as $link) {
958 // Add null check to prevent passing null to easy_invoice_str_replace
959 if ($link === null) {
960 continue;
961 }
962
963 // Convert WordPress pagination to our styling
964 $link = easy_invoice_str_replace('page-numbers', 'px-3 py-2 text-sm font-medium border border-gray-300 rounded-md', $link);
965 $link = easy_invoice_str_replace('current', 'bg-indigo-50 border-indigo-500 text-indigo-600', $link);
966 $link = easy_invoice_str_replace('prev', 'bg-white text-gray-500 hover:bg-gray-50', $link);
967 $link = easy_invoice_str_replace('next', 'bg-white text-gray-500 hover:bg-gray-50', $link);
968 $link = easy_invoice_str_replace('dots', 'bg-white text-gray-700', $link);
969
970 // Add default styling for regular page numbers
971 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
972 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
973 }
974
975 echo wp_kses_post($link);
976 }
977 echo '</div>';
978 }
979 ?>
980 </div>
981 <?php endif; ?>
982 </div>
983 </div>
984 <?php endif; ?>
985 </div>
986
987 <div id="trash-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
988 <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative">
989 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
990 <i class="fas fa-times"></i>
991 </button>
992 <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></h2>
993 <p class="text-sm text-gray-500 mb-4">Are you sure you want to move invoice <span id="trash-invoice-number" class="font-medium"></span> to trash?</p>
994
995 <div class="mt-5 flex justify-end space-x-3">
996 <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
997 Cancel
998 </button>
999 <button type="button" id="confirm-trash" class="bg-red-600 py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
1000 Move to Trash
1001 </button>
1002 </div>
1003 </div>
1004 </div>
1005
1006 <div id="restore-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
1007 <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative">
1008 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
1009 <i class="fas fa-times"></i>
1010 </button>
1011 <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Restore Invoice', 'easy-invoice'); ?></h2>
1012 <p class="text-sm text-gray-500 mb-4">Are you sure you want to restore invoice <span id="restore-invoice-number" class="font-medium"></span>?</p>
1013
1014 <div class="mt-5 flex justify-end space-x-3">
1015 <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1016 Cancel
1017 </button>
1018 <button type="button" id="confirm-restore" class="bg-indigo-600 py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1019 Restore Invoice
1020 </button>
1021 </div>
1022 </div>
1023 </div>
1024
1025 <div id="delete-permanently-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
1026 <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative">
1027 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
1028 <i class="fas fa-times"></i>
1029 </button>
1030 <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Delete Invoice Permanently', 'easy-invoice'); ?></h2>
1031 <p class="text-sm text-gray-500 mb-4">Are you sure you want to permanently delete invoice <span id="delete-permanently-number" class="font-medium"></span>? This action cannot be undone.</p>
1032
1033 <div class="mt-5 flex justify-end space-x-3">
1034 <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1035 Cancel
1036 </button>
1037 <button type="button" id="confirm-delete-permanently" class="bg-red-600 py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
1038 Delete Permanently
1039 </button>
1040 </div>
1041 </div>
1042 </div>
1043
1044 <div id="publish-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
1045 <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative">
1046 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
1047 <i class="fas fa-times"></i>
1048 </button>
1049 <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Publish Invoice', 'easy-invoice'); ?></h2>
1050 <p class="text-sm text-gray-500 mb-4">Are you sure you want to publish invoice <span id="publish-invoice-number" class="font-medium"></span>?</p>
1051
1052 <div class="mt-5 flex justify-end space-x-3">
1053 <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1054 Cancel
1055 </button>
1056 <button type="button" id="confirm-publish" class="bg-green-600 py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
1057 Publish Invoice
1058 </button>
1059 </div>
1060 </div>
1061 </div>
1062
1063 <div id="draft-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50">
1064 <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative">
1065 <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500">
1066 <i class="fas fa-times"></i>
1067 </button>
1068 <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Set Invoice to Draft', 'easy-invoice'); ?></h2>
1069 <p class="text-sm text-gray-500 mb-4"><?php esc_html_e('Set invoice', 'easy-invoice'); ?> <span id="draft-invoice-number" class="font-medium"></span> <?php esc_html_e('back to draft? It stays in your list and keeps its number, but reads as not yet issued until you send it again.', 'easy-invoice'); ?></p>
1070
1071 <div class="mt-5 flex justify-end space-x-3">
1072 <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1073 Cancel
1074 </button>
1075 <button type="button" id="confirm-draft" class="bg-indigo-600 py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1076 Set to Draft
1077 </button>
1078 </div>
1079 </div>
1080 </div>
1081
1082 <style>
1083 /* Modern row actions styling */
1084 /* Team roles: controls a member may not use are not drawn (the endpoints check again). */
1085 <?php if (!easy_invoice_user_can('ei_create_invoice')) : ?>.row-actions .edit, .row-actions .publish, .row-actions .draft { display: none; }<?php endif; ?>
1086 <?php if (!easy_invoice_user_can('ei_delete_invoice')) : ?>.row-actions .trash, .row-actions .delete, .row-actions .restore { display: none; }<?php endif; ?>
1087 <?php if (!easy_invoice_user_can('ei_send_invoice')) : ?>.row-actions .email { display: none; }<?php endif; ?>
1088 .ei-invoices-table td:nth-child(2) { white-space: normal; }
1089 .ei-invoices-table td:nth-child(2) .ei-viewed { white-space: nowrap; }
1090 .row-actions {
1091 visibility: hidden;
1092 font-size: 12px;
1093 padding-top: 4px;
1094 display: flex;
1095 flex-wrap: wrap; /* a long action list must not widen the column past the screen */
1096 gap: 4px 8px;
1097 }
1098
1099 tr:hover .row-actions,
1100 tr:focus-within .row-actions {
1101 visibility: visible;
1102 }
1103
1104 /*
1105 * Row actions were revealed on `tr:hover` only, which made them unreachable for
1106 * two whole groups of users:
1107 *
1108 * - Keyboard users. `visibility: hidden` also removes elements from the tab
1109 * order, so View / Edit / Trash / Send Email / Download PDF / Duplicate could
1110 * never be focused at all. `:focus-within` above fixes that — tabbing into the
1111 * row now reveals them.
1112 * - Touch users. There is no hover on a phone or tablet, so every row action was
1113 * permanently invisible and there was no other way to reach them.
1114 *
1115 * On pointer-less devices, and on the narrow widths WordPress itself treats as
1116 * mobile admin, the actions are simply always visible.
1117 */
1118 @media (hover: none), (max-width: 782px) {
1119 .row-actions {
1120 visibility: visible;
1121 }
1122 }
1123
1124 .row-actions span a {
1125 color: #6366f1; /* indigo-500 */
1126 text-decoration: none;
1127 transition: all 0.2s;
1128 display: inline-flex;
1129 align-items: center;
1130 }
1131
1132 .row-actions span a:hover {
1133 color: #4f46e5; /* indigo-600 */
1134 text-decoration: none;
1135 }
1136
1137 .row-actions span a.text-red-600 {
1138 color: #dc2626; /* red-600 */
1139 }
1140
1141 .row-actions span a.text-red-600:hover {
1142 color: #b91c1c; /* red-700 */
1143 }
1144
1145 /* Add subtle separators between action links */
1146 .row-actions span:not(:last-child)::after {
1147 content: "";
1148 display: inline-block;
1149 width: 3px;
1150 height: 3px;
1151 border-radius: 50%;
1152 background-color: #d1d5db; /* gray-300 */
1153 margin-left: 8px;
1154 vertical-align: middle;
1155 }
1156
1157 /* Premium duplicate button styling */
1158 .premium-feature .premium-duplicate-btn {
1159 border: 1.5px solid #FFD700;
1160 background: linear-gradient(90deg, #fffbe6 0%, #fff8dc 100%);
1161 box-shadow: 0 0 6px 0 #ffe06633;
1162 position: relative;
1163 transition: box-shadow 0.2s, border-color 0.2s;
1164 }
1165 .premium-feature .premium-duplicate-btn:hover {
1166 box-shadow: 0 0 16px 2px #ffd70099, 0 0 0 2px #fffbe6;
1167 border-color: #FFA500;
1168 animation: shimmer 1.2s linear infinite;
1169 }
1170 @keyframes shimmer {
1171 0% { box-shadow: 0 0 6px 0 #ffe06633; }
1172 50% { box-shadow: 0 0 24px 4px #ffd700cc; }
1173 100% { box-shadow: 0 0 6px 0 #ffe06633; }
1174 }
1175 .premium-feature .premium-tooltip {
1176 opacity: 0;
1177 pointer-events: none;
1178 position: absolute;
1179 left: 50%;
1180 top: 120%;
1181 transform: translateX(-50%);
1182 background: #222;
1183 color: #fffbe6;
1184 padding: 3px 10px;
1185 border-radius: 4px;
1186 font-size: 11px;
1187 white-space: nowrap;
1188 z-index: 10;
1189 transition: opacity 0.2s;
1190 box-shadow: 0 2px 8px #0002;
1191 }
1192 .premium-feature .group:hover .premium-tooltip {
1193 opacity: 1;
1194 }
1195 .premium-feature .premium-duplicate-icon {
1196 filter: drop-shadow(0 0 2px #ffd700);
1197 }
1198
1199 /* Premium Export Button Styling - Minimal */
1200 .premium-export-btn {
1201 border-color: #d1d5db;
1202 transition: all 0.2s ease;
1203 }
1204
1205 .premium-export-btn:hover {
1206 border-color: #9ca3af;
1207 background-color: #f9fafb;
1208 }
1209
1210 .premium-export-btn .crown-icon {
1211 filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.2));
1212 color: #9333ea;
1213 transition: all 0.3s ease;
1214 }
1215
1216 .premium-export-btn:hover .crown-icon {
1217 filter: drop-shadow(0 2px 6px rgba(147, 51, 234, 0.3));
1218 transform: scale(1.1);
1219 color: #7c3aed;
1220 }
1221 </style>
1222
1223 <?php
1224 // Localize script data for AJAX operations
1225 $script_data = array(
1226 'ajaxUrl' => admin_url('admin-ajax.php'),
1227 'nonce' => wp_create_nonce('easy_invoice_nonce')
1228 );
1229 ?>
1230 <script type="text/javascript">
1231 // Make sure easyInvoice object is available for the AJAX operations
1232 var easyInvoice = <?php echo json_encode($script_data); ?>;
1233
1234 jQuery(document).ready(function($) {
1235 var selectedInvoiceId = null;
1236
1237 // Select All Checkboxes
1238 $("#select-all").on("click", function() {
1239 $(".invoice-checkbox").prop("checked", $(this).prop("checked"));
1240 });
1241
1242 // Check if any checkbox is unchecked and update select all
1243 $(".invoice-checkbox").on("click", function() {
1244 if (!$(this).prop("checked")) {
1245 $("#select-all").prop("checked", false);
1246 } else {
1247 // Check if all checkboxes are checked
1248 var allChecked = true;
1249 $(".invoice-checkbox").each(function() {
1250 if (!$(this).prop("checked")) {
1251 allChecked = false;
1252 return false; // Break the loop
1253 }
1254 });
1255 $("#select-all").prop("checked", allChecked);
1256 }
1257 });
1258
1259 // Open trash modal when Trash link is clicked
1260 $(".trash-invoice").on("click", function(e) {
1261 e.preventDefault();
1262 selectedInvoiceId = $(this).data("invoice-id");
1263 var invoiceNumber = $(this).data("invoice-number");
1264
1265 $("#trash-invoice-number").text(invoiceNumber);
1266 $("#trash-invoice-modal").removeClass("hidden");
1267 });
1268
1269 // Open restore modal when Restore link is clicked
1270 $(".restore-invoice").on("click", function(e) {
1271 e.preventDefault();
1272 selectedInvoiceId = $(this).data("invoice-id");
1273 var invoiceNumber = $(this).data("invoice-number");
1274
1275 $("#restore-invoice-number").text(invoiceNumber);
1276 $("#restore-invoice-modal").removeClass("hidden");
1277 });
1278
1279 // Open delete permanently modal
1280 $(".delete-permanently").on("click", function(e) {
1281 e.preventDefault();
1282 selectedInvoiceId = $(this).data("invoice-id");
1283 var invoiceNumber = $(this).data("invoice-number");
1284
1285 $("#delete-permanently-number").text(invoiceNumber);
1286 $("#delete-permanently-modal").removeClass("hidden");
1287 });
1288
1289 // Open publish invoice modal
1290 $(".publish-invoice").on("click", function(e) {
1291 e.preventDefault();
1292 selectedInvoiceId = $(this).data("invoice-id");
1293 var invoiceNumber = $(this).data("invoice-number");
1294
1295 $("#publish-invoice-number").text(invoiceNumber);
1296 $("#publish-invoice-modal").removeClass("hidden");
1297 });
1298
1299 // Open draft invoice modal
1300 $(".draft-invoice").on("click", function(e) {
1301 e.preventDefault();
1302 selectedInvoiceId = $(this).data("invoice-id");
1303 var invoiceNumber = $(this).data("invoice-number");
1304
1305 $("#draft-invoice-number").text(invoiceNumber);
1306 $("#draft-invoice-modal").removeClass("hidden");
1307 });
1308
1309 // Handle Email Invoice click
1310 $(".email-invoice").on("click", function(e) {
1311 e.preventDefault();
1312 var invoiceId = $(this).data("invoice-id");
1313 var invoiceNumber = $(this).data("invoice-number");
1314
1315 // Use the new confirmation system instead of browser confirm
1316 if (typeof EasyInvoiceConfirmation !== 'undefined') {
1317 EasyInvoiceConfirmation.confirmAction('send email', 'invoice ' + invoiceNumber, function() {
1318 // Show loading state
1319 var $link = $(this);
1320 var originalText = $link.text();
1321 $link.text("Sending...").css("opacity", "0.7");
1322
1323 // Send AJAX request
1324 $.ajax({
1325 url: easyInvoice.ajaxUrl,
1326 type: "POST",
1327 data: {
1328 action: "easy_invoice_send_email",
1329 invoice_id: invoiceId,
1330 nonce: easyInvoice.nonce
1331 },
1332 success: function(response) {
1333 if (response.success) {
1334 EasyInvoiceToast.success("Email sent successfully!");
1335 } else {
1336 EasyInvoiceToast.error(response.data.message || "Error sending email");
1337 }
1338 // Reset link text
1339 $link.text(originalText).css("opacity", "1");
1340 },
1341 error: function() {
1342 EasyInvoiceToast.error("Error connecting to server");
1343 // Reset link text
1344 $link.text(originalText).css("opacity", "1");
1345 }
1346 });
1347 });
1348 } else {
1349 // Fallback to browser confirm if toast system not available
1350 if (confirm("Are you sure you want to send invoice " + invoiceNumber + " via email?")) {
1351 // Show loading state
1352 var $link = $(this);
1353 var originalText = $link.text();
1354 $link.text("Sending...").css("opacity", "0.7");
1355
1356 // Send AJAX request
1357 $.ajax({
1358 url: easyInvoice.ajaxUrl,
1359 type: "POST",
1360 data: {
1361 action: "easy_invoice_send_email",
1362 invoice_id: invoiceId,
1363 nonce: easyInvoice.nonce
1364 },
1365 success: function(response) {
1366 if (response.success) {
1367 EasyInvoiceToast.success("Email sent successfully!");
1368 } else {
1369 EasyInvoiceToast.error(response.data.message || "Error sending email");
1370 }
1371 // Reset link text
1372 $link.text(originalText).css("opacity", "1");
1373 },
1374 error: function() {
1375 EasyInvoiceToast.error("Error connecting to server");
1376 // Reset link text
1377 $link.text(originalText).css("opacity", "1");
1378 }
1379 });
1380 }
1381 }
1382 });
1383
1384 // Download PDF: let the anchor's native href do the job.
1385 //
1386 // Historically this handler intercepted the click and did an
1387 // admin-ajax round-trip that minted a per-user nonce and opened
1388 // the resulting admin-ajax URL in a new tab. That extra hop was
1389 // fragile in production — page-cache layers, security plugins /
1390 // WAFs, cross-tab session cookie drops (Safari ITP, SameSite),
1391 // and PHP output-buffering interactions all caused the target
1392 // handler's redirect to be swallowed, leaving the user on a
1393 // blank admin-ajax page with no download.
1394 //
1395 // The anchor's href is already `<permalink>?auto_download_pdf=1`
1396 // (see line ~756). The single-invoice page JS picks up that
1397 // query arg and triggers the PDF render directly. Removing the
1398 // interceptor collapses three server round-trips into one and
1399 // sidesteps every failure mode above. The
1400 // `easy_invoice_download_invoice_pdf` /
1401 // `easy_invoice_generate_pdf` handlers are still registered on
1402 // the server for any external integration that used them (they
1403 // now have their own robust 3-path auth + JS redirect fallback
1404 // — see EasyInvoiceAjax::generateInvoicePdf), but this button
1405 // no longer depends on them.
1406
1407 // Close modals when Close button or Cancel is clicked
1408 $(".close-modal, .cancel-modal").on("click", function() {
1409 $(".fixed").addClass("hidden");
1410 selectedInvoiceId = null;
1411 });
1412
1413 // Handle trash confirmation
1414 $("#confirm-trash").on("click", function() {
1415 if (selectedInvoiceId) {
1416 // Send AJAX request to trash the invoice
1417 $.ajax({
1418 url: easyInvoice.ajaxUrl,
1419 type: "POST",
1420 data: {
1421 action: "easy_invoice_trash_invoice",
1422 invoice_id: selectedInvoiceId,
1423 nonce: easyInvoice.nonce
1424 },
1425 success: function(response) {
1426 if (response.success) {
1427 // Reload the page to show updated list
1428 window.location.reload();
1429 } else {
1430 EasyInvoiceToast.error(response.data.message || "Error trashing invoice");
1431 }
1432 },
1433 error: function() {
1434 EasyInvoiceToast.error("Error connecting to server");
1435 }
1436 });
1437 }
1438 });
1439
1440 // Handle restore confirmation
1441 $("#confirm-restore").on("click", function() {
1442 if (selectedInvoiceId) {
1443 // Send AJAX request to restore the invoice
1444 $.ajax({
1445 url: easyInvoice.ajaxUrl,
1446 type: "POST",
1447 data: {
1448 action: "easy_invoice_restore_invoice",
1449 invoice_id: selectedInvoiceId,
1450 nonce: easyInvoice.nonce
1451 },
1452 success: function(response) {
1453 if (response.success) {
1454 // Reload the page to show updated list
1455 window.location.reload();
1456 } else {
1457 EasyInvoiceToast.error(response.data.message || "Error restoring invoice");
1458 }
1459 },
1460 error: function() {
1461 EasyInvoiceToast.error("Error connecting to server");
1462 }
1463 });
1464 }
1465 });
1466
1467 // Handle publish confirmation
1468 $("#confirm-publish").on("click", function() {
1469 if (selectedInvoiceId) {
1470 // Send AJAX request to publish the invoice
1471 $.ajax({
1472 url: easyInvoice.ajaxUrl,
1473 type: "POST",
1474 data: {
1475 action: "easy_invoice_publish_invoice",
1476 invoice_id: selectedInvoiceId,
1477 nonce: easyInvoice.nonce
1478 },
1479 success: function(response) {
1480 if (response.success) {
1481 // Reload the page to show updated list
1482 window.location.reload();
1483 } else {
1484 EasyInvoiceToast.error(response.data.message || "Error publishing invoice");
1485 }
1486 },
1487 error: function() {
1488 EasyInvoiceToast.error("Error connecting to server");
1489 }
1490 });
1491 }
1492 });
1493
1494 // Handle draft confirmation
1495 $("#confirm-draft").on("click", function() {
1496 if (selectedInvoiceId) {
1497 // Send AJAX request to set the invoice to draft
1498 $.ajax({
1499 url: easyInvoice.ajaxUrl,
1500 type: "POST",
1501 data: {
1502 action: "easy_invoice_draft_invoice",
1503 invoice_id: selectedInvoiceId,
1504 nonce: easyInvoice.nonce
1505 },
1506 success: function(response) {
1507 if (response.success) {
1508 // Reload the page to show updated list
1509 window.location.reload();
1510 } else {
1511 EasyInvoiceToast.error(response.data.message || "Error setting invoice to draft");
1512 }
1513 },
1514 error: function() {
1515 EasyInvoiceToast.error("Error connecting to server");
1516 }
1517 });
1518 }
1519 });
1520
1521 // Handle delete permanently confirmation
1522 $("#confirm-delete-permanently").on("click", function() {
1523 if (selectedInvoiceId) {
1524 // Send AJAX request to delete the invoice permanently
1525 $.ajax({
1526 url: easyInvoice.ajaxUrl,
1527 type: "POST",
1528 data: {
1529 action: "easy_invoice_delete_invoice_permanently",
1530 invoice_id: selectedInvoiceId,
1531 nonce: easyInvoice.nonce
1532 },
1533 success: function(response) {
1534 if (response.success) {
1535 // Reload the page to show updated list
1536 window.location.reload();
1537 } else {
1538 EasyInvoiceToast.error(response.data.message || "Error deleting invoice");
1539 }
1540 },
1541 error: function() {
1542 EasyInvoiceToast.error("Error connecting to server");
1543 }
1544 });
1545 }
1546 });
1547
1548 // Form submission validation for bulk actions
1549 $("#bulk-action-form").on("submit", function(e) {
1550 // Check if an action is selected
1551 var bulkAction = $("select[name='bulk_action']").val();
1552 if (!bulkAction) {
1553 e.preventDefault();
1554 EasyInvoiceToast.warning("Please select an action");
1555 return false;
1556 }
1557
1558 // Check if at least one invoice is selected
1559 var checkedInvoices = $(".invoice-checkbox:checked").length;
1560 if (checkedInvoices < 1) {
1561 e.preventDefault();
1562 EasyInvoiceToast.warning("Please select at least one invoice");
1563 return false;
1564 }
1565
1566 // Confirm bulk actions using the new confirmation system
1567 if (bulkAction === 'trash') {
1568 e.preventDefault();
1569 if (typeof EasyInvoiceConfirmation !== 'undefined') {
1570 EasyInvoiceConfirmation.confirmAction('move to trash', 'the selected invoices', function() {
1571 $("#bulk-action-form")[0].submit();
1572 });
1573 } else {
1574 // Fallback to browser confirm
1575 if (!confirm('Are you sure you want to move the selected invoices to trash?')) {
1576 return false;
1577 }
1578 }
1579 return false;
1580 } else if (bulkAction === 'delete') {
1581 e.preventDefault();
1582 if (typeof EasyInvoiceConfirmation !== 'undefined') {
1583 EasyInvoiceConfirmation.confirmAction('permanently delete', 'the selected invoices', function() {
1584 $("#bulk-action-form")[0].submit();
1585 });
1586 } else {
1587 // Fallback to browser confirm
1588 if (!confirm('Are you sure you want to permanently delete the selected invoices? This cannot be undone.')) {
1589 return false;
1590 }
1591 }
1592 return false;
1593 }
1594
1595 return true;
1596 });
1597
1598 // Dismiss notifications
1599 $(".notification-dismiss").on("click", function() {
1600 $(this).closest(".rounded-md").fadeOut(300, function() {
1601 $(this).remove();
1602 });
1603 });
1604
1605 // Duplicate invoice functionality
1606 $(document).on('click', '.duplicate-invoice-pro-placeholder', function(e) {
1607 e.preventDefault();
1608 EasyInvoiceConfirmation.showFeatureUpgrade('Invoice Duplication');
1609 });
1610
1611 // Export invoice functionality
1612 $(document).on('click', '.export-invoice-pro-placeholder', function(e) {
1613 e.preventDefault();
1614 EasyInvoiceConfirmation.showFeatureUpgrade('Invoice Export', 'Export your invoices to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.');
1615 });
1616
1617 });
1618 </script>
1619
1620 <div id="new-invoice-modal" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50 hidden">
1621 <div class="relative mx-auto p-6 border w-96 shadow-xl rounded-lg bg-white my-20">
1622 <div class="flex justify-between items-center mb-6">
1623 <h3 class="text-xl font-semibold text-gray-900"><?php esc_html_e('Create New Invoice', 'easy-invoice'); ?></h3>
1624 <button type="button" id="close-new-invoice-modal" class="text-gray-400 hover:text-gray-600 transition-colors duration-200">
1625 <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1626 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
1627 </svg>
1628 </button>
1629 </div>
1630
1631 <form id="new-invoice-form" class="space-y-6">
1632 <div>
1633 <label for="new_invoice_title" class="block text-sm font-medium text-gray-700 mb-2">Invoice Title *</label>
1634 <input type="text"
1635 name="new_invoice_title"
1636 id="new_invoice_title"
1637 required
1638 placeholder="Enter invoice title..."
1639 class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-3 px-4 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition-colors duration-200">
1640 <p class="mt-2 text-sm text-gray-500"><?php esc_html_e('This will be the title of your invoice', 'easy-invoice'); ?></p>
1641 </div>
1642
1643 <div class="flex justify-end space-x-3 pt-4">
1644 <button type="button" id="cancel-new-invoice"
1645 class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
1646 Cancel
1647 </button>
1648 <button type="submit"
1649 class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium 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">
1650 Create Invoice
1651 </button>
1652 </div>
1653 </form>
1654
1655 <div id="new-invoice-message" class="mt-4 hidden"></div>
1656 </div>
1657 </div>
1658
1659 <script>
1660 jQuery(document).ready(function($) {
1661 // Initialize easyInvoice object for AJAX calls
1662 window.easyInvoice = {
1663 ajaxUrl: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
1664 nonce: '<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>',
1665 isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>
1666 };
1667
1668 // New Invoice Modal Functionality
1669 $('#create-new-invoice-btn').on('click', function(e) {
1670 e.preventDefault(); // Prevent the default link behavior
1671 $('#new-invoice-modal').removeClass('hidden');
1672 $('#new_invoice_title').focus();
1673 });
1674
1675 // Handle "Create your first invoice" links
1676 $(document).on('click', '.create-first-invoice', function(e) {
1677 e.preventDefault(); // Prevent the default link behavior
1678 $('#new-invoice-modal').removeClass('hidden');
1679 $('#new_invoice_title').focus();
1680 });
1681
1682 $('#close-new-invoice-modal, #cancel-new-invoice').on('click', function() {
1683 $('#new-invoice-modal').addClass('hidden');
1684 $('#new-invoice-form')[0].reset();
1685 $('#new-invoice-message').addClass('hidden').html('');
1686 });
1687
1688 // Close modal when clicking outside
1689 $('#new-invoice-modal').on('click', function(e) {
1690 if (e.target === this) {
1691 $(this).addClass('hidden');
1692 $('#new-invoice-form')[0].reset();
1693 $('#new-invoice-message').addClass('hidden').html('');
1694 }
1695 });
1696
1697 // Handle form submission
1698 $('#new-invoice-form').on('submit', function(e) {
1699 e.preventDefault();
1700
1701 var $form = $(this);
1702 var $submitBtn = $form.find('button[type="submit"]');
1703 var $message = $('#new-invoice-message');
1704 var title = $('#new_invoice_title').val().trim();
1705
1706 if (!title) {
1707 $message.removeClass('hidden').html('<div class="text-red-600 text-sm"><?php esc_html_e('Please enter an invoice title.', 'easy-invoice'); ?></div>');
1708 return;
1709 }
1710
1711 // Show loading state
1712 $submitBtn.prop('disabled', true).html('Creating...');
1713 $message.addClass('hidden');
1714
1715 $.ajax({
1716 url: easyInvoice.ajaxUrl,
1717 type: 'POST',
1718 data: {
1719 action: 'easy_invoice_create_new_invoice',
1720 title: title,
1721 nonce: easyInvoice.nonce
1722 },
1723 success: function(response) {
1724 if (response.success) {
1725 // Show success message
1726 $message.removeClass('hidden').html('<div class="text-green-600 text-sm"><?php esc_html_e('Invoice created successfully! Redirecting...', 'easy-invoice'); ?></div>');
1727
1728 // Redirect to the builder page with the new invoice ID
1729 setTimeout(function() {
1730 window.location.href = response.data.redirect_url;
1731 }, 1000);
1732 } else {
1733 $message.removeClass('hidden').html('<div class="text-red-600 text-sm">' + (response.data.message || 'Error creating invoice.') + '</div>');
1734 $submitBtn.prop('disabled', false).html('Create Invoice');
1735 }
1736 },
1737 error: function() {
1738 $message.removeClass('hidden').html('<div class="text-red-600 text-sm"><?php esc_html_e('Error connecting to server. Please try again.', 'easy-invoice'); ?></div>');
1739 $submitBtn.prop('disabled', false).html('Create Invoice');
1740 }
1741 });
1742 });
1743 });
1744 </script>
1745 <?php
1746