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

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

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