PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.2.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.2.0
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.2.0, at templates/invoices/listing.php

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