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

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

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