PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.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 / reports-page.php

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

1,483 lines 85.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6 ?>
7
8 <script type="text/javascript">
9 // Run immediately to ensure payment tab is visible before anything else
10 (function() {
11 document.addEventListener('DOMContentLoaded', function() {
12 // Force payment tab to be visible as soon as DOM is ready
13 var paymentTab = document.getElementById('payment-report-content');
14 if (paymentTab) {
15 paymentTab.style.display = 'block';
16 paymentTab.classList.remove('hidden');
17 paymentTab.classList.add('block');
18 }
19
20 // Make sure payment button is active
21 var paymentButton = document.getElementById('tab-payment-report');
22 if (paymentButton) {
23 paymentButton.classList.add('active-tab', 'border-indigo-500', 'text-indigo-600');
24 paymentButton.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
25 }
26 });
27 })();
28 </script>
29
30 <div class="p-8">
31 <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;">
32 <div class="flex items-center justify-between">
33 <div>
34 <h1 class="text-2xl font-bold text-gray-900">Reports</h1>
35 <p class="mt-1 text-sm text-gray-500">Comprehensive insights and analytics</p>
36 </div>
37 <div class="flex items-center space-x-3">
38 <form method="get" action="<?php echo admin_url('admin.php'); ?>" class="flex space-x-2">
39 <input type="hidden" name="page" value="easy-invoice-reports">
40 <div class="flex items-center">
41 <label for="start_date" class="mr-2 text-sm font-medium text-gray-700">From:</label>
42 <input type="date" id="start_date" name="start_date" value="<?php echo esc_attr($start_date); ?>" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition-colors duration-200">
43 </div>
44 <div class="flex items-center">
45 <label for="end_date" class="mr-2 text-sm font-medium text-gray-700">To:</label>
46 <input type="date" id="end_date" name="end_date" value="<?php echo esc_attr($end_date); ?>" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition-colors duration-200">
47 </div>
48 <button type="submit" class="inline-flex items-center px-3 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">
49 <i class="fas fa-filter mr-1"></i> Filter
50 </button>
51 </form>
52 <button type="button" id="export-report" 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">
53 <i class="fas fa-download mr-2"></i>
54 Export Report
55 </button>
56 </div>
57 </div>
58 </div>
59
60 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8">
61 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
62 <div class="flex items-center">
63 <div class="rounded-full bg-indigo-100 p-3 mr-4">
64 <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">
65 <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>
66 </svg>
67 </div>
68 <div>
69 <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3>
70 <?php if (is_array($summary_stats['total_revenue']) && !empty($summary_stats['total_revenue'])): ?>
71 <div class="mt-1">
72 <?php
73 $currency_count = count($summary_stats['total_revenue']);
74 foreach ($summary_stats['total_revenue'] as $index => $data):
75 $is_last = $index === $currency_count - 1;
76 // Get currency code from the data array or use a fallback
77 $currency_code = isset($data['currency_code']) ? $data['currency_code'] : 'USD';
78 ?>
79 <div class="<?php echo $currency_count > 1 ? 'text-sm' : 'text-lg'; ?> font-bold text-gray-900 <?php echo !$is_last ? 'mb-1' : ''; ?>">
80 <?php echo $data['symbol'] . number_format($data['amount'], 2); ?>
81 <span class="text-xs font-normal text-gray-500"><?php echo strtoupper($currency_code ?? 'USD'); ?></span>
82 </div>
83 <?php endforeach; ?>
84 </div>
85 <?php else: ?>
86 <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p>
87 <?php endif; ?>
88 </div>
89 </div>
90 </div>
91
92 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
93 <div class="flex items-center">
94 <div class="rounded-full bg-green-100 p-3 mr-4">
95 <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">
96 <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>
97 </svg>
98 </div>
99 <div>
100 <h3 class="text-sm font-medium text-gray-500">Total Invoices</h3>
101 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($summary_stats['total_invoices']); ?></p>
102 </div>
103 </div>
104 </div>
105
106 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
107 <div class="flex items-center">
108 <div class="rounded-full bg-blue-100 p-3 mr-4">
109 <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">
110 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 21V19C17 17.9391 16.5786 16.9217 15.8284 16.1716C15.0783 15.4214 14.0609 15 13 15H5C3.93913 15 2.92172 15.4214 2.17157 16.1716C1.42143 16.9217 1 17.9391 1 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
111 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
112 <path d="M23 21V19C23 18.1137 22.6485 17.2628 22.0237 16.6425C21.3989 16.0221 20.5471 15.6722 19.66 15.6722C18.7729 15.6722 17.9211 16.0221 17.2963 16.6425C16.6715 17.2628 16.32 18.1137 16.32 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
113 <path d="M16.32 11.6722C18.36 11.6722 20 10.0322 20 7.99219C20 5.95219 18.36 4.31219 16.32 4.31219" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
114 </svg>
115 </div>
116 <div>
117 <h3 class="text-sm font-medium text-gray-500">Active Clients</h3>
118 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($summary_stats['active_clients']); ?></p>
119 </div>
120 </div>
121 </div>
122
123 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
124 <div class="flex items-center">
125 <div class="rounded-full bg-yellow-100 p-3 mr-4">
126 <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">
127 <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>
128 </svg>
129 </div>
130 <div>
131 <h3 class="text-sm font-medium text-gray-500">Average Payment Time</h3>
132 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($summary_stats['avg_payment_time']); ?> days</p>
133 </div>
134 </div>
135 </div>
136 </div>
137
138 <div class="grid grid-cols-1 gap-5 lg:grid-cols-2 mb-8">
139 <div class="bg-white shadow rounded-lg p-6">
140 <h2 class="text-lg font-medium text-gray-900 mb-4">Monthly Revenue</h2>
141 <div class="h-64">
142 <canvas id="revenue-chart"></canvas>
143 </div>
144 </div>
145
146 <div class="bg-white shadow rounded-lg p-6">
147 <h2 class="text-lg font-medium text-gray-900 mb-4">Invoice Status</h2>
148 <div class="h-64 flex items-center justify-center">
149 <div class="w-48 h-48 relative">
150 <canvas id="status-chart"></canvas>
151 </div>
152 <div class="ml-8">
153 <?php
154 $statusColors = [
155 'Paid' => 'bg-green-500',
156 'Unpaid' => 'bg-yellow-500',
157 'Overdue' => 'bg-red-500',
158 'Draft' => 'bg-gray-500'
159 ];
160
161 if (isset($invoice_status['percentages']) && isset($invoice_status['counts'])) {
162 foreach ($invoice_status['percentages'] as $status => $percentage) {
163 $color = isset($statusColors[ucfirst($status)]) ? $statusColors[ucfirst($status)] : 'bg-gray-500';
164 $count = isset($invoice_status['counts'][$status]) ? $invoice_status['counts'][$status] : 0;
165 echo '<div class="flex items-center mb-2">';
166 echo '<div class="w-4 h-4 rounded-full ' . esc_attr($color) . ' mr-2"></div>';
167 echo '<span class="text-sm text-gray-600">' . esc_html(ucfirst($status)) . ' (' . esc_html($percentage) . '% - ' . esc_html($count) . ')</span>';
168 echo '</div>';
169 }
170 }
171 ?>
172 </div>
173 </div>
174 </div>
175 </div>
176
177 <div class="bg-white shadow rounded-lg mb-8">
178 <div class="px-6 py-4 border-b border-gray-200">
179 <h2 class="text-lg font-medium text-gray-900">Top Clients by Revenue</h2>
180 </div>
181 <div class="overflow-x-auto">
182 <table class="min-w-full divide-y divide-gray-200">
183 <thead class="bg-gray-50">
184 <tr>
185 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
186 Client
187 </th>
188 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
189 Total Amount
190 </th>
191 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
192 Invoices
193 </th>
194 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
195 Last Invoice
196 </th>
197 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
198 Actions
199 </th>
200 </tr>
201 </thead>
202 <tbody class="bg-white divide-y divide-gray-200">
203 <?php if (!empty($top_clients)) : ?>
204 <?php foreach ($top_clients as $client): ?>
205 <tr class="hover:bg-gray-50">
206 <td class="px-6 py-4 whitespace-nowrap">
207 <div class="flex items-center">
208 <div class="flex-shrink-0 h-10 w-10">
209 <?php
210 $gravatar_url = '';
211 if (!empty($client['email'])) {
212 $gravatar_url = get_avatar_url($client['email'], ['size' => 40, 'default' => 'mp']);
213 }
214 if ($gravatar_url) {
215 echo '<img class="h-10 w-10 rounded-full object-cover" src="' . esc_url($gravatar_url) . '" alt="' . esc_attr($client['name']) . '" onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">';
216 }
217 ?>
218 <div class="h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center" <?php echo $gravatar_url ? 'style="display: none;"' : ''; ?>>
219 <span class="text-indigo-600 font-medium"><?php echo esc_html(substr($client['name'] ?? '', 0, 1)); ?></span>
220 </div>
221 </div>
222 <div class="ml-4">
223 <div class="text-sm font-medium text-gray-900"><?php echo esc_html($client['name']); ?></div>
224 <div class="text-sm text-gray-500"><?php echo esc_html($client['email']); ?></div>
225 </div>
226 </div>
227 </td>
228 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
229 <?php if (isset($client['total_amount']) && is_array($client['total_amount']) && !empty($client['total_amount'])): ?>
230 <?php foreach ($client['total_amount'] as $currency => $currency_data): ?>
231 <div>
232 <?php echo $currency_data['symbol'] . number_format($currency_data['amount'], 2); ?>
233 <span class="text-xs text-gray-500"><?php echo $currency; ?></span>
234 </div>
235 <?php endforeach; ?>
236 <?php else: ?>
237 $0.00
238 <?php endif; ?>
239 </td>
240 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
241 <?php echo esc_html($client['total_invoices']); ?> invoices
242 </td>
243 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
244 <?php echo !empty($client['last_invoice']) ? date('M d, Y', strtotime($client['last_invoice'])) : 'N/A'; ?>
245 </td>
246
247
248 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
249 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-view&client_id=' . $client['id'])); ?>" class="text-indigo-600 hover:text-indigo-900">View Client</a>
250 </td>
251 </tr>
252 <?php endforeach; ?>
253 <?php else: ?>
254 <tr>
255 <td colspan="5" class="px-6 py-4 text-center text-gray-500">No client data available</td>
256 </tr>
257 <?php endif; ?>
258 </tbody>
259 </table>
260 </div>
261 </div>
262
263 <div class="bg-white shadow rounded-lg">
264 <div class="border-b border-gray-200">
265 <div class="flex items-center justify-between px-6 py-3">
266 <h2 class="text-lg font-medium text-gray-900">Detailed Reports</h2>
267 <div>
268 <button id="export-detailed-report" class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
269 <i class="fas fa-download mr-2"></i> Export
270 </button>
271 </div>
272 </div>
273 <nav class="flex -mb-px">
274 <button id="tab-payment-report" class="tab-button active-tab w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm border-indigo-500 text-indigo-600">
275 Payment Report
276 </button>
277 <button id="tab-invoice-report" class="tab-button w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300">
278 Invoice Report
279 </button>
280 </nav>
281 </div>
282
283 <div id="payment-report-content" class="tab-content block" style="display: block !important;">
284 <div class="p-4 bg-gray-50 border-b">
285 <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
286 <div class="bg-white p-4 rounded-md shadow-sm">
287 <div class="text-sm text-gray-500">Total Payments</div>
288 <div class="text-xl font-bold"><?php echo esc_html($payment_report['count']); ?></div>
289 </div>
290 <div class="bg-white p-4 rounded-md shadow-sm">
291 <div class="text-sm text-gray-500">Total Amount</div>
292 <div class="text-lg font-bold">
293 <?php
294 // Calculate total amount from all payment methods and currencies
295 $total_amounts_by_currency = [];
296 if (!empty($payment_report['by_method'])) {
297 foreach ($payment_report['by_method'] as $method => $data) {
298 if (isset($data['amounts_by_currency'])) {
299 foreach ($data['amounts_by_currency'] as $currency => $currency_data) {
300 if (!isset($total_amounts_by_currency[$currency])) {
301 $total_amounts_by_currency[$currency] = [
302 'amount' => 0,
303 'symbol' => $currency_data['symbol']
304 ];
305 }
306 $total_amounts_by_currency[$currency]['amount'] += $currency_data['amount'];
307 }
308 }
309 }
310 }
311
312 if (!empty($total_amounts_by_currency)): ?>
313 <?php foreach ($total_amounts_by_currency as $currency => $data): ?>
314 <div class="text-sm">
315 <?php echo $data['symbol'] . number_format($data['amount'], 2); ?>
316 <span class="text-xs text-gray-500"><?php echo $currency; ?></span>
317 </div>
318 <?php endforeach; ?>
319 <?php else: ?>
320 $0.00
321 <?php endif; ?>
322 </div>
323 </div>
324 <div class="bg-white p-4 rounded-md shadow-sm">
325 <div class="text-sm text-gray-500">Average Payment</div>
326 <div class="text-lg font-bold">
327 <?php
328 // Calculate average payments by currency
329 $average_amounts_by_currency = [];
330 $payment_count = $payment_report['count'];
331
332 if (!empty($payment_report['by_method']) && $payment_count > 0) {
333 foreach ($payment_report['by_method'] as $method => $data) {
334 if (isset($data['amounts_by_currency'])) {
335 foreach ($data['amounts_by_currency'] as $currency => $currency_data) {
336 if (!isset($average_amounts_by_currency[$currency])) {
337 $average_amounts_by_currency[$currency] = [
338 'amount' => 0,
339 'symbol' => $currency_data['symbol']
340 ];
341 }
342 $average_amounts_by_currency[$currency]['amount'] += $currency_data['amount'];
343 }
344 }
345 }
346
347 // Calculate averages
348 foreach ($average_amounts_by_currency as $currency => $data) {
349 $average_amounts_by_currency[$currency]['amount'] = $data['amount'] / $payment_count;
350 }
351 }
352
353 if (!empty($average_amounts_by_currency)): ?>
354 <?php foreach ($average_amounts_by_currency as $currency => $data): ?>
355 <div class="text-sm">
356 <?php echo $data['symbol'] . number_format($data['amount'], 2); ?>
357 <span class="text-xs text-gray-500">
358 <?php
359 $display_currency = $currency;
360 if ($display_currency === 'GLOBAL') {
361 $display_currency = get_option('easy_invoice_currency_code', 'USD');
362 }
363 echo esc_html(strtoupper($display_currency));
364 ?>
365 </span>
366 </div>
367 <?php endforeach; ?>
368 <?php elseif ($payment_count > 0): ?>
369 <span class="text-sm text-gray-500">No currency data</span>
370 <?php else: ?>
371 $0.00
372 <?php endif; ?>
373 </div>
374 </div>
375 </div>
376 </div>
377
378 <div class="p-4">
379 <h3 class="text-md font-medium text-gray-700 mb-3">Payment Methods Distribution</h3>
380 <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
381 <div class="bg-white p-4 rounded-md shadow-sm">
382 <canvas id="payment-methods-chart" height="200"></canvas>
383 </div>
384 <div class="bg-white p-4 rounded-md shadow-sm">
385 <table class="min-w-full divide-y divide-gray-200">
386 <thead>
387 <tr>
388 <th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Method</th>
389 <th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
390 <th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
391 </tr>
392 </thead>
393 <tbody>
394 <?php
395 if (!empty($payment_report['by_method'])) :
396 foreach ($payment_report['by_method'] as $method => $data) :
397 ?>
398 <tr>
399 <td class="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
400 <?php echo esc_html(ucfirst($method)); ?>
401 </td>
402 <td class="px-3 py-2 whitespace-nowrap text-sm text-right text-gray-500">
403 <?php echo esc_html($data['count']); ?>
404 </td>
405 <td class="px-3 py-2 whitespace-nowrap text-sm text-right text-gray-900">
406 <?php if (isset($data['amounts_by_currency']) && !empty($data['amounts_by_currency'])): ?>
407 <?php foreach ($data['amounts_by_currency'] as $currency => $currency_data): ?>
408 <div class="text-xs">
409 <?php echo $currency_data['symbol'] . number_format($currency_data['amount'], 2); ?>
410 <span class="text-xs text-gray-500"><?php echo $currency; ?></span>
411 </div>
412 <?php endforeach; ?>
413 <?php else: ?>
414 $0.00
415 <?php endif; ?>
416 </td>
417 </tr>
418 <?php
419 endforeach;
420 else:
421 ?>
422 <tr>
423 <td colspan="3" class="px-3 py-4 text-center text-sm text-gray-500">No payment method data available</td>
424 </tr>
425 <?php endif; ?>
426 </tbody>
427 </table>
428 </div>
429 </div>
430 </div>
431
432 <div class="p-4">
433 <h3 class="text-md font-medium text-gray-700 mb-3">Recent Payments</h3>
434 <div class="overflow-x-auto">
435 <table class="min-w-full divide-y divide-gray-200">
436 <thead class="bg-gray-50">
437 <tr>
438 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
439 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice</th>
440 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
441 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Method</th>
442 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
443 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Transaction ID</th>
444 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
445 </tr>
446 </thead>
447 <tbody class="bg-white divide-y divide-gray-200">
448 <?php if (!empty($payment_report['payments'])) : ?>
449 <?php foreach ($payment_report['payments'] as $payment): ?>
450 <tr class="hover:bg-gray-50">
451 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
452 <?php echo date('M d, Y', strtotime($payment['date'])); ?>
453 </td>
454 <td class="px-4 py-3 whitespace-nowrap">
455 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice&action=edit&id=' . $payment['invoice_id'])); ?>" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium">
456 <?php echo esc_html($payment['invoice_number']); ?>
457 </a>
458 </td>
459 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
460 <?php echo esc_html($payment['currency_symbol'] . number_format($payment['amount'], 2)); ?>
461 </td>
462 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
463 <?php echo esc_html(ucfirst($payment['payment_method'])); ?>
464 </td>
465 <td class="px-4 py-3 whitespace-nowrap">
466 <?php
467 $status_class = 'bg-gray-100 text-gray-800';
468 if (in_array($payment['status'], ['completed', 'paid'])) {
469 $status_class = 'bg-green-100 text-green-800';
470 } elseif (in_array($payment['status'], ['pending', 'pending-bank', 'pending-cheque'])) {
471 $status_class = 'bg-yellow-100 text-yellow-800';
472 } elseif (in_array($payment['status'], ['failed', 'rejected'])) {
473 $status_class = 'bg-red-100 text-red-800';
474 }
475 ?>
476 <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full <?php echo esc_attr($status_class); ?>">
477 <?php echo esc_html(ucfirst($payment['status'])); ?>
478 </span>
479 </td>
480 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
481 <?php echo esc_html($payment['transaction_id'] ?: esc_html__('', 'easy-invoice')); ?>
482 </td>
483 <td class="px-4 py-3 whitespace-nowrap text-sm font-medium">
484 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-payments&action=view&id=' . $payment['id'])); ?>" class="text-indigo-600 hover:text-indigo-900">View</a>
485 </td>
486 </tr>
487 <?php endforeach; ?>
488 <?php else: ?>
489 <tr>
490 <td colspan="7" class="px-4 py-4 text-center text-gray-500">No payment records found for the selected period</td>
491 </tr>
492 <?php endif; ?>
493 </tbody>
494 </table>
495 </div>
496 </div>
497 </div>
498
499 <div id="invoice-report-content" class="tab-content hidden" style="display: none;">
500 <div class="p-4 bg-gray-50 border-b">
501 <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
502 <div class="bg-white p-4 rounded-md shadow-sm">
503 <div class="text-sm text-gray-500">Total Invoices</div>
504 <div class="text-xl font-bold"><?php echo esc_html($invoice_report['count']); ?></div>
505 </div>
506 <div class="bg-white p-4 rounded-md shadow-sm">
507 <div class="text-sm text-gray-500">Total Amount</div>
508 <div class="text-lg font-bold">
509 <?php
510 // Display total amounts by currency
511 if (isset($invoice_report['total_amounts_by_currency']) && !empty($invoice_report['total_amounts_by_currency'])): ?>
512 <?php foreach ($invoice_report['total_amounts_by_currency'] as $currency => $currency_data): ?>
513 <div class="text-sm">
514 <?php echo $currency_data['symbol'] . number_format($currency_data['amount'], 2); ?>
515 <span class="text-xs text-gray-500">
516 <?php
517 $display_currency = $currency;
518 if ($display_currency === 'GLOBAL') {
519 $display_currency = get_option('easy_invoice_currency_code', 'USD');
520 }
521 echo esc_html(strtoupper($display_currency));
522 ?>
523 </span>
524 </div>
525 <?php endforeach; ?>
526 <?php else: ?>
527 $0.00
528 <?php endif; ?>
529 </div>
530 </div>
531 <div class="bg-white p-4 rounded-md shadow-sm">
532 <div class="text-sm text-gray-500">Average Invoice</div>
533 <div class="text-lg font-bold">
534 <?php
535 // Calculate average amounts by currency
536 if (isset($invoice_report['total_amounts_by_currency']) && !empty($invoice_report['total_amounts_by_currency']) && $invoice_report['count'] > 0): ?>
537 <?php foreach ($invoice_report['total_amounts_by_currency'] as $currency => $currency_data): ?>
538 <div class="text-sm">
539 <?php
540 $avg_amount = $currency_data['amount'] / $invoice_report['count'];
541 echo $currency_data['symbol'] . number_format($avg_amount, 2);
542 ?>
543 <span class="text-xs text-gray-500">
544 <?php
545 $display_currency = $currency;
546 if ($display_currency === 'GLOBAL') {
547 $display_currency = get_option('easy_invoice_currency_code', 'USD');
548 }
549 echo esc_html(strtoupper($display_currency));
550 ?>
551 </span>
552 </div>
553 <?php endforeach; ?>
554 <?php else: ?>
555 $0.00
556 <?php endif; ?>
557 </div>
558 </div>
559 </div>
560 </div>
561
562 <div class="p-4">
563 <h3 class="text-md font-medium text-gray-700 mb-3">Invoice Status Distribution</h3>
564 <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
565 <div class="bg-white p-4 rounded-md shadow-sm">
566 <canvas id="invoice-status-chart" height="200"></canvas>
567 </div>
568 <div class="bg-white p-4 rounded-md shadow-sm">
569 <table class="min-w-full divide-y divide-gray-200">
570 <thead>
571 <tr>
572 <th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
573 <th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
574 <th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
575 </tr>
576 </thead>
577 <tbody>
578 <?php
579 $status_list = [
580 'paid' => 'Paid',
581 'unpaid' => 'Unpaid',
582 'overdue' => 'Overdue',
583 'draft' => 'Draft',
584 'canceled' => 'Canceled',
585 'other' => 'Other'
586 ];
587
588 foreach ($status_list as $status_key => $status_label) :
589 $status_data = isset($invoice_report['by_status'][$status_key]) ? $invoice_report['by_status'][$status_key] : ['amount' => 0, 'count' => 0];
590 ?>
591 <tr>
592 <td class="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
593 <?php echo esc_html($status_label); ?>
594 </td>
595 <td class="px-3 py-2 whitespace-nowrap text-sm text-right text-gray-500">
596 <?php echo esc_html($status_data['count']); ?>
597 </td>
598 <td class="px-3 py-2 whitespace-nowrap text-sm text-right text-gray-900">
599 <?php if (isset($status_data['amounts_by_currency']) && !empty($status_data['amounts_by_currency'])): ?>
600 <?php foreach ($status_data['amounts_by_currency'] as $currency => $currency_data): ?>
601 <div class="text-xs">
602 <?php echo $currency_data['symbol'] . number_format($currency_data['amount'], 2); ?>
603 <span class="text-xs text-gray-500">
604 <?php
605 $display_currency = $currency;
606 if ($display_currency === 'GLOBAL') {
607 $display_currency = get_option('easy_invoice_currency_code', 'USD');
608 }
609 echo esc_html(strtoupper($display_currency));
610 ?>
611 </span>
612 </div>
613 <?php endforeach; ?>
614 <?php else: ?>
615 $0.00
616 <?php endif; ?>
617 </td>
618 </tr>
619 <?php endforeach; ?>
620 </tbody>
621 </table>
622 </div>
623 </div>
624 </div>
625
626 <div class="p-4">
627 <h3 class="text-md font-medium text-gray-700 mb-3">Recent Invoices</h3>
628 <div class="overflow-x-auto">
629 <table class="min-w-full divide-y divide-gray-200">
630 <thead class="bg-gray-50">
631 <tr>
632 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
633 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due Date</th>
634 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice #</th>
635 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Client</th>
636 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
637 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
638 <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
639 </tr>
640 </thead>
641 <tbody class="bg-white divide-y divide-gray-200">
642 <?php if (!empty($invoice_report['invoices'])) : ?>
643 <?php foreach ($invoice_report['invoices'] as $invoice): ?>
644 <tr class="hover:bg-gray-50">
645 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
646 <?php echo date('M d, Y', strtotime($invoice['issue_date'])); ?>
647 </td>
648 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
649 <?php
650 if (!empty($invoice['due_date']) && $invoice['due_date'] !== '0000-00-00' && $invoice['due_date'] !== '1970-01-01') {
651 echo date('M d, Y', strtotime($invoice['due_date']));
652 } else {
653 echo '-';
654 }
655 ?>
656 </td>
657 <td class="px-4 py-3 whitespace-nowrap">
658 <a href="<?php echo esc_url(get_permalink($invoice['id'])); ?>" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium" target="_blank">
659 <?php echo esc_html($invoice['invoice_number']); ?>
660 </a>
661 </td>
662 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
663 <?php if (!empty($invoice['client_id']) && !empty($invoice['client_name'])): ?>
664 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-clients&view=view&client_id=' . $invoice['client_id'])); ?>" class="text-gray-900 hover:text-indigo-900">
665 <?php echo esc_html($invoice['client_name']); ?>
666 </a>
667 <?php elseif (!empty($invoice['client_name'])): ?>
668 <span class="text-gray-900"><?php echo esc_html($invoice['client_name']); ?></span>
669 <?php else: ?>
670 <span class="text-gray-500">-</span>
671 <?php endif; ?>
672 </td>
673 <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
674 <?php
675 $display_currency = $invoice['currency'];
676 if ($display_currency === 'GLOBAL') {
677 $display_currency = get_option('easy_invoice_currency_code', 'USD');
678 }
679 echo esc_html($invoice['currency_symbol'] . number_format($invoice['total'], 2)); ?>
680 <span class="text-xs text-gray-500"><?php echo esc_html(strtoupper($display_currency)); ?></span>
681 </td>
682 <td class="px-4 py-3 whitespace-nowrap">
683 <?php
684 $status_class = 'bg-gray-100 text-gray-800';
685 if (in_array($invoice['status'], ['paid', 'completed'])) {
686 $status_class = 'bg-green-100 text-green-800';
687 } elseif ($invoice['status'] === 'unpaid') {
688 $status_class = 'bg-yellow-100 text-yellow-800';
689 } elseif ($invoice['status'] === 'overdue') {
690 $status_class = 'bg-red-100 text-red-800';
691 } elseif ($invoice['status'] === 'draft') {
692 $status_class = 'bg-gray-100 text-gray-800';
693 }
694 ?>
695 <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full <?php echo esc_attr($status_class); ?>">
696 <?php echo esc_html(ucfirst($invoice['status'])); ?>
697 </span>
698 </td>
699 <td class="px-4 py-3 whitespace-nowrap text-sm font-medium">
700 <a href="<?php echo esc_url(get_permalink($invoice['id'])); ?>" class="text-indigo-600 hover:text-indigo-900" target="_blank">View</a>
701 </td>
702 </tr>
703 <?php endforeach; ?>
704 <?php else: ?>
705 <tr>
706 <td colspan="7" class="px-4 py-4 text-center text-gray-500">No invoice records found for the selected period</td>
707 </tr>
708 <?php endif; ?>
709 </tbody>
710 </table>
711 </div>
712 </div>
713 </div>
714 </div>
715 </div>
716
717 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
718 <script>
719
720
721 // Monthly revenue chart
722 function initializeRevenueChart() {
723 // Wait for Chart.js to be available
724 if (typeof Chart === 'undefined') {
725 setTimeout(initializeRevenueChart, 500);
726 return;
727 }
728
729 const chartElement = document.getElementById('revenue-chart');
730 if (chartElement) {
731 const monthlyRevenueLabels = <?php echo json_encode(array_keys($monthly_revenue)); ?>;
732
733
734
735 // Process multi-currency data for chart display
736 const monthlyRevenueData = <?php
737
738
739 // Get all unique currencies from monthly revenue data
740 $all_currencies = [];
741 if (is_array($monthly_revenue)) {
742 foreach ($monthly_revenue as $month => $currencies) {
743 if (is_array($currencies)) {
744 foreach ($currencies as $currency => $data) {
745 if (!in_array($currency, $all_currencies)) {
746 $all_currencies[] = $currency;
747 }
748 }
749 }
750 }
751 }
752 sort($all_currencies); // Sort currencies for consistent order
753
754 // Create datasets for each currency
755 $datasets = [];
756 $colors = [
757 'rgba(79, 70, 229, 0.8)', // Indigo for USD
758 'rgba(16, 185, 129, 0.8)', // Green for EUR
759 'rgba(245, 158, 11, 0.8)', // Yellow for other currencies
760 'rgba(239, 68, 68, 0.8)', // Red
761 'rgba(107, 114, 128, 0.8)' // Gray
762 ];
763
764 if (!empty($all_currencies)) {
765 foreach ($all_currencies as $index => $currency) {
766 $currency_data = [];
767 $symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency);
768
769 foreach ($monthly_revenue as $month => $currencies) {
770 $amount = isset($currencies[$currency]) ? $currencies[$currency]['amount'] : 0;
771 $currency_data[] = $amount;
772 }
773
774 $datasets[] = [
775 'label' => $currency . ' (' . $symbol . ')',
776 'data' => $currency_data,
777 'backgroundColor' => $colors[$index % count($colors)],
778 'borderColor' => easy_invoice_str_replace('0.8', '1', $colors[$index % count($colors)]),
779 'borderWidth' => 1
780 ];
781 }
782 }
783
784 echo json_encode($datasets);
785 ?>;
786
787
788
789 // Check if we have data to display
790 if (monthlyRevenueLabels.length > 0 && monthlyRevenueData.length > 0) {
791 const revenueCtx = document.getElementById('revenue-chart').getContext('2d');
792
793 // Destroy existing chart if it exists
794 if (window.revenueChart) {
795 window.revenueChart.destroy();
796 }
797
798 try {
799 window.revenueChart = new Chart(revenueCtx, {
800 type: 'bar',
801 data: {
802 labels: monthlyRevenueLabels,
803 datasets: monthlyRevenueData
804 },
805 options: {
806 responsive: true,
807 maintainAspectRatio: false,
808 scales: {
809 y: {
810 beginAtZero: true,
811 ticks: {
812 callback: function(value) {
813 return '$' + value.toLocaleString();
814 }
815 }
816 }
817 },
818 plugins: {
819 tooltip: {
820 callbacks: {
821 label: function(context) {
822 return context.dataset.label + ': ' + context.raw.toLocaleString();
823 }
824 }
825 },
826 legend: {
827 display: true,
828 position: 'top'
829 }
830 }
831 }
832 });
833 } catch (error) {
834 console.error('Error initializing Monthly Revenue Chart:', error);
835 const chartContainer = document.getElementById('revenue-chart');
836 if (chartContainer) {
837 chartContainer.style.display = 'flex';
838 chartContainer.style.alignItems = 'center';
839 chartContainer.style.justifyContent = 'center';
840 chartContainer.innerHTML = '<div class="text-red-500 text-center"><p>Error loading chart. Please refresh the page.</p></div>';
841 }
842 }
843 } else {
844 const chartContainer = document.getElementById('revenue-chart');
845 if (chartContainer) {
846 chartContainer.style.display = 'flex';
847 chartContainer.style.alignItems = 'center';
848 chartContainer.style.justifyContent = 'center';
849 chartContainer.innerHTML = '<div class="text-gray-500 text-center"><p>No revenue data available for the selected period.</p><p class="text-sm mt-2">Revenue data will appear here once payments are recorded.</p></div>';
850 }
851 return;
852 }
853 }
854 }
855
856 // Initialize chart when DOM is ready
857 document.addEventListener('DOMContentLoaded', function() {
858 try {
859 // Make sure payment report tab is visible by default
860 const paymentReportTab = document.getElementById('payment-report-content');
861 const invoiceReportTab = document.getElementById('invoice-report-content');
862
863 if (paymentReportTab && invoiceReportTab) {
864 // Set proper visibility immediately
865 paymentReportTab.classList.remove('hidden');
866 paymentReportTab.classList.add('block');
867 paymentReportTab.style.display = 'block';
868
869 invoiceReportTab.classList.add('hidden');
870 invoiceReportTab.classList.remove('block');
871 invoiceReportTab.style.display = 'none';
872
873 // Set proper button state
874 const paymentButton = document.getElementById('tab-payment-report');
875 const invoiceButton = document.getElementById('tab-invoice-report');
876
877 if (paymentButton && invoiceButton) {
878 paymentButton.classList.add('active-tab', 'border-indigo-500', 'text-indigo-600');
879 paymentButton.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
880
881 invoiceButton.classList.remove('active-tab', 'border-indigo-500', 'text-indigo-600');
882 invoiceButton.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
883 }
884
885 // Also set the visibility with a delay to override any other scripts
886 setTimeout(function() {
887 paymentReportTab.classList.remove('hidden');
888 paymentReportTab.classList.add('block');
889 paymentReportTab.style.display = 'block';
890
891 invoiceReportTab.classList.add('hidden');
892 invoiceReportTab.classList.remove('block');
893 invoiceReportTab.style.display = 'none';
894 }, 100);
895 }
896
897 // Initialize the revenue chart
898 initializeRevenueChart();
899
900
901
902 // Invoice status pie chart
903 if (document.getElementById('status-chart')) {
904
905 const statusLabels = <?php
906 $status_labels = [];
907 $status_data = [];
908 if (isset($invoice_status['percentages']) && is_array($invoice_status['percentages'])) {
909 foreach ($invoice_status['percentages'] as $status => $percentage) {
910 $status_labels[] = ucfirst($status);
911 $status_data[] = $percentage;
912 }
913 }
914 echo json_encode($status_labels);
915 ?>;
916
917 const statusData = <?php
918 echo json_encode($status_data);
919 ?>;
920
921
922
923 const statusColors = [
924 'rgba(34, 197, 94, 0.8)', // Green for Paid
925 'rgba(234, 179, 8, 0.8)', // Yellow for Unpaid
926 'rgba(239, 68, 68, 0.8)', // Red for Overdue
927 'rgba(209, 213, 219, 0.8)' // Gray for Draft
928 ];
929
930 try {
931 if (statusLabels.length === 0 || statusData.length === 0) {
932 return;
933 }
934
935 const statusCtx = document.getElementById('status-chart').getContext('2d');
936
937 new Chart(statusCtx, {
938 type: 'doughnut',
939 data: {
940 labels: statusLabels,
941 datasets: [{
942 data: statusData,
943 backgroundColor: statusColors,
944 borderWidth: 1
945 }]
946 },
947 options: {
948 responsive: true,
949 maintainAspectRatio: false,
950 plugins: {
951 legend: {
952 display: false
953 },
954 tooltip: {
955 callbacks: {
956 label: function(context) {
957 return context.label + ': ' + context.raw + '%';
958 }
959 }
960 }
961 },
962 cutout: '70%'
963 }
964 });
965 } catch (error) {
966 console.error('Error creating status chart:', error);
967 }
968 }
969
970 // Payment methods chart - use try-catch to prevent errors from breaking the page
971 try {
972 if (document.getElementById('payment-methods-chart')) {
973 const paymentMethodsData = <?php
974 $methods = [];
975 $counts = [];
976 $methodColors = [];
977 $colorPalette = ['rgba(79, 70, 229, 0.8)', 'rgba(59, 130, 246, 0.8)', 'rgba(16, 185, 129, 0.8)',
978 'rgba(245, 158, 11, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(107, 114, 128, 0.8)'];
979
980 $i = 0;
981 if (!empty($payment_report['by_method'])) {
982 foreach ($payment_report['by_method'] as $method => $data) {
983 $methods[] = ucfirst($method);
984 $counts[] = $data['count'];
985 $methodColors[] = $colorPalette[$i % count($colorPalette)];
986 $i++;
987 }
988 }
989
990 echo json_encode([
991 'labels' => $methods,
992 'data' => $counts,
993 'colors' => $methodColors
994 ]);
995 ?>;
996
997 if (paymentMethodsData.labels && paymentMethodsData.labels.length > 0) {
998 const paymentMethodsCtx = document.getElementById('payment-methods-chart').getContext('2d');
999 new Chart(paymentMethodsCtx, {
1000 type: 'pie',
1001 data: {
1002 labels: paymentMethodsData.labels,
1003 datasets: [{
1004 data: paymentMethodsData.data,
1005 backgroundColor: paymentMethodsData.colors,
1006 borderWidth: 1
1007 }]
1008 },
1009 options: {
1010 responsive: true,
1011 maintainAspectRatio: false,
1012 plugins: {
1013 legend: {
1014 position: 'right'
1015 },
1016 tooltip: {
1017 callbacks: {
1018 label: function(context) {
1019 const total = context.dataset.data.reduce((a, b) => a + b, 0);
1020 const percentage = Math.round((context.raw / total) * 100);
1021 return context.label + ': ' + context.raw + ' (' + percentage + '%)';
1022 }
1023 }
1024 }
1025 }
1026 }
1027 });
1028 } else {
1029 // If no data, display a message
1030 const canvas = document.getElementById('payment-methods-chart');
1031 const ctx = canvas.getContext('2d');
1032 ctx.font = '14px Arial';
1033 ctx.fillStyle = '#6B7280';
1034 ctx.textAlign = 'center';
1035 ctx.fillText('No payment method data available', canvas.width / 2, canvas.height / 2);
1036 }
1037 }
1038 } catch (error) {
1039 console.error('Error initializing payment methods chart:', error);
1040 }
1041
1042 // Invoice status detailed chart - use try-catch to prevent errors from breaking the page
1043 try {
1044 if (document.getElementById('invoice-status-chart')) {
1045 const invoiceStatusData = <?php
1046 $statuses = [];
1047 $statusCounts = [];
1048 $statusChartColors = [
1049 'rgba(34, 197, 94, 0.8)', // Green for Paid
1050 'rgba(234, 179, 8, 0.8)', // Yellow for Unpaid
1051 'rgba(239, 68, 68, 0.8)', // Red for Overdue
1052 'rgba(209, 213, 219, 0.8)', // Gray for Draft
1053 'rgba(107, 114, 128, 0.8)', // Dark Gray for Canceled
1054 'rgba(156, 163, 175, 0.8)' // Medium Gray for Other
1055 ];
1056
1057 $status_list = [
1058 'paid' => 'Paid',
1059 'unpaid' => 'Unpaid',
1060 'overdue' => 'Overdue',
1061 'draft' => 'Draft',
1062 'canceled' => 'Canceled',
1063 'other' => 'Other'
1064 ];
1065
1066 $i = 0;
1067 foreach ($status_list as $status_key => $status_label) {
1068 $status_data = $invoice_report['by_status'][$status_key] ?? ['count' => 0];
1069 if ($status_data['count'] > 0) {
1070 $statuses[] = $status_label;
1071 $statusCounts[] = $status_data['count'];
1072 $i++;
1073 }
1074 }
1075
1076 echo json_encode([
1077 'labels' => $statuses,
1078 'data' => $statusCounts,
1079 'colors' => array_slice($statusChartColors, 0, $i)
1080 ]);
1081 ?>;
1082
1083 if (invoiceStatusData.labels && invoiceStatusData.labels.length > 0) {
1084 const invoiceStatusCtx = document.getElementById('invoice-status-chart').getContext('2d');
1085 new Chart(invoiceStatusCtx, {
1086 type: 'pie',
1087 data: {
1088 labels: invoiceStatusData.labels,
1089 datasets: [{
1090 data: invoiceStatusData.data,
1091 backgroundColor: invoiceStatusData.colors,
1092 borderWidth: 1
1093 }]
1094 },
1095 options: {
1096 responsive: true,
1097 maintainAspectRatio: false,
1098 plugins: {
1099 legend: {
1100 position: 'right'
1101 },
1102 tooltip: {
1103 callbacks: {
1104 label: function(context) {
1105 const total = context.dataset.data.reduce((a, b) => a + b, 0);
1106 const percentage = Math.round((context.raw / total) * 100);
1107 return context.label + ': ' + context.raw + ' (' + percentage + '%)';
1108 }
1109 }
1110 }
1111 }
1112 }
1113 });
1114 } else {
1115 // If no data, display a message
1116 const canvas = document.getElementById('invoice-status-chart');
1117 const ctx = canvas.getContext('2d');
1118 ctx.font = '14px Arial';
1119 ctx.fillStyle = '#6B7280';
1120 ctx.textAlign = 'center';
1121 ctx.fillText('No invoice status data available', canvas.width / 2, canvas.height / 2);
1122 }
1123 }
1124 } catch (error) {
1125 console.error('Error initializing invoice status chart:', error);
1126 }
1127
1128 // Tab switching functionality
1129 const tabButtons = document.querySelectorAll('.tab-button');
1130
1131 tabButtons.forEach(button => {
1132 button.addEventListener('click', function(e) {
1133 // Prevent default behavior that might cause page reload
1134 e.preventDefault();
1135
1136 // Get button ID safely
1137 const buttonId = this.id || '';
1138
1139 // Store the target tab ID before modifying classes
1140 const targetId = buttonId.replace('tab-', '') + '-content';
1141
1142 // For troubleshooting
1143
1144
1145 // Remove active class from all buttons first
1146 tabButtons.forEach(btn => {
1147 btn.classList.remove('active-tab', 'border-indigo-500', 'text-indigo-600');
1148 btn.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
1149 });
1150
1151 // Add active class to clicked button
1152 this.classList.add('active-tab', 'border-indigo-500', 'text-indigo-600');
1153 this.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
1154
1155 // Get all tab contents
1156 const paymentReportTab = document.getElementById('payment-report-content');
1157 const invoiceReportTab = document.getElementById('invoice-report-content');
1158
1159 // Hide both tabs first
1160 if (paymentReportTab) {
1161 paymentReportTab.classList.add('hidden');
1162 paymentReportTab.classList.remove('block');
1163 paymentReportTab.style.display = 'none';
1164 }
1165
1166 if (invoiceReportTab) {
1167 invoiceReportTab.classList.add('hidden');
1168 invoiceReportTab.classList.remove('block');
1169 invoiceReportTab.style.display = 'none';
1170 }
1171
1172 // Show the target tab
1173 const targetTab = document.getElementById(targetId);
1174 if (targetTab) {
1175 // Short delay helps ensure DOM updates properly
1176 setTimeout(() => {
1177 targetTab.classList.remove('hidden');
1178 targetTab.classList.add('block');
1179 targetTab.style.display = 'block';
1180 }, 10);
1181 } else {
1182 // Fallback to payment report if target not found
1183 if (paymentReportTab) {
1184 setTimeout(() => {
1185 paymentReportTab.classList.remove('hidden');
1186 paymentReportTab.classList.add('block');
1187 paymentReportTab.style.display = 'block';
1188 }, 10);
1189 }
1190 }
1191 });
1192 });
1193
1194 // Export report functionality (disabled to prevent conflict with detailed export)
1195 /*
1196 if (document.getElementById('export-report')) {
1197 document.getElementById('export-report').addEventListener('click', function() {
1198 try {
1199 // Create a timestamp for the filename
1200 const date = new Date();
1201 const timestamp = date.getFullYear() + '-' +
1202 String(date.getMonth() + 1).padStart(2, '0') + '-' +
1203 String(date.getDate()).padStart(2, '0');
1204
1205 // Generate CSV content
1206 let csvContent = 'data:text/csv;charset=utf-8,';
1207
1208 // Add report title and date range
1209 csvContent += '<?php echo esc_html__('Easy Invoice Report: Reports', 'easy-invoice'); ?>\r\n';
1210 csvContent += 'Date Range: ' + (document.getElementById('start_date') ? document.getElementById('start_date').value : 'N/A') + ' to ' + (document.getElementById('end_date') ? document.getElementById('end_date').value : 'N/A') + '\r\n\r\n';
1211
1212 // Add summary stats
1213 csvContent += 'Summary Statistics\r\n';
1214 <?php if (isset($summary_stats['total_revenue']) && is_array($summary_stats['total_revenue']) && !empty($summary_stats['total_revenue'])): ?>
1215 <?php foreach ($summary_stats['total_revenue'] as $currency => $data): ?>
1216 csvContent += 'Total Revenue (<?php echo $currency; ?>),<?php echo $data['symbol'] . number_format($data['amount'], 2); ?>\r\n';
1217 <?php endforeach; ?>
1218 <?php else: ?>
1219 csvContent += 'Total Revenue,$0.00\r\n';
1220 <?php endif; ?>
1221 csvContent += 'Total Invoices,<?php echo isset($summary_stats['total_invoices']) ? $summary_stats['total_invoices'] : 0; ?>\r\n';
1222 csvContent += 'Active Clients,<?php echo isset($summary_stats['active_clients']) ? $summary_stats['active_clients'] : 0; ?>\r\n';
1223 csvContent += 'Average Payment Time,<?php echo isset($summary_stats['avg_payment_time']) ? $summary_stats['avg_payment_time'] : 0; ?> days\r\n\r\n';
1224
1225 // Add monthly revenue
1226 csvContent += 'Monthly Revenue\r\n';
1227 csvContent += 'Month,USD,EUR,Other Currencies\r\n';
1228 <?php if (isset($monthly_revenue) && is_array($monthly_revenue)): ?>
1229 <?php foreach ($monthly_revenue as $month => $currencies): ?>
1230 <?php
1231 $usd_amount = isset($currencies['USD']) ? $currencies['USD']['amount'] : 0;
1232 $eur_amount = isset($currencies['EUR']) ? $currencies['EUR']['amount'] : 0;
1233 $other_currencies = [];
1234 if (is_array($currencies)) {
1235 foreach ($currencies as $currency => $data) {
1236 if ($currency !== 'USD' && $currency !== 'EUR' && is_array($data)) {
1237 $other_currencies[] = $currency . ': ' . $data['symbol'] . number_format($data['amount'], 2);
1238 }
1239 }
1240 }
1241 $other_text = !empty($other_currencies) ? implode('; ', $other_currencies) : '';
1242 ?>
1243 csvContent += '<?php echo $month; ?>,$<?php echo number_format($usd_amount, 2); ?>,€<?php echo number_format($eur_amount, 2); ?>,"<?php echo addslashes($other_text); ?>"\r\n';
1244 <?php endforeach; ?>
1245 <?php endif; ?>
1246 csvContent += '\r\n';
1247
1248 // Add invoice status
1249 csvContent += 'Invoice Status\r\n';
1250 csvContent += 'Status,Percentage,Count\r\n';
1251 <?php if (isset($invoice_status['percentages']) && is_array($invoice_status['percentages'])): ?>
1252 <?php foreach ($invoice_status['percentages'] as $status => $percentage): ?>
1253 <?php $count = isset($invoice_status['counts'][$status]) ? $invoice_status['counts'][$status] : 0; ?>
1254 csvContent += '<?php echo ucfirst($status); ?>,<?php echo $percentage; ?>%,<?php echo $count; ?>\r\n';
1255 <?php endforeach; ?>
1256 <?php endif; ?>
1257 csvContent += '\r\n';
1258
1259 // Add top clients
1260 csvContent += 'Top Clients by Revenue\r\n';
1261 csvContent += 'Client,Email,Total Amount (USD),Total Amount (EUR),Other Currencies,Invoices,Last Invoice\r\n';
1262 <?php if (isset($top_clients) && is_array($top_clients)): ?>
1263 <?php foreach ($top_clients as $client): ?>
1264 <?php
1265 $usd_amount = isset($client['total_amount']['USD']) ? $client['total_amount']['USD']['amount'] : 0;
1266 $eur_amount = isset($client['total_amount']['EUR']) ? $client['total_amount']['EUR']['amount'] : 0;
1267 $other_currencies = [];
1268 if (isset($client['total_amount']) && is_array($client['total_amount'])) {
1269 foreach ($client['total_amount'] as $currency => $data) {
1270 if ($currency !== 'USD' && $currency !== 'EUR' && is_array($data)) {
1271 $other_currencies[] = $currency . ': ' . $data['symbol'] . number_format($data['amount'], 2);
1272 }
1273 }
1274 }
1275 $other_text = !empty($other_currencies) ? implode('; ', $other_currencies) : '';
1276 $client_name = isset($client['name']) ? $client['name'] : 'Unknown';
1277 $client_email = isset($client['email']) ? $client['email'] : '';
1278 $total_invoices = isset($client['total_invoices']) ? $client['total_invoices'] : 0;
1279 $last_invoice = isset($client['last_invoice']) && !empty($client['last_invoice']) ? date('Y-m-d', strtotime($client['last_invoice'])) : 'N/A';
1280 ?>
1281 csvContent += '<?php echo addslashes($client_name); ?>,<?php echo addslashes($client_email); ?>,$<?php echo number_format($usd_amount, 2); ?>,€<?php echo number_format($eur_amount, 2); ?>,"<?php echo addslashes($other_text); ?>",<?php echo $total_invoices; ?>,<?php echo $last_invoice; ?>\r\n';
1282 <?php endforeach; ?>
1283 <?php endif; ?>
1284
1285 // Create download link
1286 const encodedUri = encodeURI(csvContent);
1287 const link = document.createElement('a');
1288 link.setAttribute('href', encodedUri);
1289 link.setAttribute('download', 'easy-invoice-report-' + timestamp + '.csv');
1290 document.body.appendChild(link);
1291
1292 // Trigger download
1293 link.click();
1294 document.body.removeChild(link);
1295 } catch (error) {
1296 alert('Error generating export. Please try again.');
1297 }
1298 });
1299 }
1300 */
1301
1302 // Note: The above export functionality is disabled to prevent double downloads
1303 // with the detailed export button below. Only the detailed export is active.
1304
1305 // Re-enable the proper export functionality with a different approach
1306 if (document.getElementById('export-report')) {
1307 document.getElementById('export-report').addEventListener('click', function() {
1308 // Set a flag to prevent detailed export from running
1309 window.exportInProgress = true;
1310
1311 // Clear the flag after a short delay
1312 setTimeout(() => {
1313 window.exportInProgress = false;
1314 }, 1000);
1315 try {
1316 // Create a timestamp for the filename
1317 const date = new Date();
1318 const timestamp = date.getFullYear() + '-' +
1319 String(date.getMonth() + 1).padStart(2, '0') + '-' +
1320 String(date.getDate()).padStart(2, '0');
1321
1322 // Generate CSV content
1323 let csvContent = 'data:text/csv;charset=utf-8,';
1324
1325 // Add report title and date range
1326 csvContent += 'Easy Invoice Report: Reports\r\n';
1327 csvContent += 'Date Range: ' + (document.getElementById('start_date') ? document.getElementById('start_date').value : 'N/A') + ' to ' + (document.getElementById('end_date') ? document.getElementById('end_date').value : 'N/A') + '\r\n\r\n';
1328
1329 // Add summary stats
1330 csvContent += 'Summary Statistics\r\n';
1331 csvContent += 'Total Revenue,$0.00\r\n';
1332 csvContent += 'Total Invoices,0\r\n';
1333 csvContent += 'Active Clients,0\r\n';
1334 csvContent += 'Average Payment Time,0 days\r\n\r\n';
1335
1336 // Add monthly revenue
1337 csvContent += 'Monthly Revenue\r\n';
1338 csvContent += 'Month,Revenue\r\n';
1339 csvContent += 'No data available\r\n\r\n';
1340
1341 // Add invoice status
1342 csvContent += 'Invoice Status\r\n';
1343 csvContent += 'Status,Percentage,Count\r\n';
1344 csvContent += 'No data available\r\n\r\n';
1345
1346 // Add top clients
1347 csvContent += 'Top Clients by Revenue\r\n';
1348 csvContent += 'Client,Email,Total Amount,Invoices,Last Invoice\r\n';
1349 csvContent += 'No data available\r\n';
1350
1351 // Create download link
1352 const encodedUri = encodeURI(csvContent);
1353 const link = document.createElement('a');
1354 link.setAttribute('href', encodedUri);
1355 link.setAttribute('download', 'easy-invoice-report-' + timestamp + '.csv');
1356 document.body.appendChild(link);
1357
1358 // Trigger download
1359 link.click();
1360 document.body.removeChild(link);
1361 } catch (error) {
1362 alert('Error generating export. Please try again.');
1363 }
1364 });
1365 }
1366
1367 // Simple Export functionality
1368 if (document.getElementById('export-detailed-report')) {
1369 // Remove any existing event listeners to prevent duplicates
1370 const exportButton = document.getElementById('export-detailed-report');
1371 const newButton = exportButton.cloneNode(true);
1372 exportButton.parentNode.replaceChild(newButton, exportButton);
1373
1374 newButton.addEventListener('click', function(e) {
1375 // Prevent multiple clicks
1376 e.preventDefault();
1377 e.stopPropagation();
1378
1379 // Check if another export is in progress
1380 if (window.exportInProgress) {
1381 return;
1382 }
1383
1384 // Disable button to prevent multiple clicks
1385 this.disabled = true;
1386 this.style.opacity = '0.5';
1387
1388 // Re-enable after 2 seconds
1389 setTimeout(() => {
1390 this.disabled = false;
1391 this.style.opacity = '1';
1392 }, 2000);
1393 try {
1394 // Get active tab
1395 const activeTab = document.querySelector('.tab-button.active-tab');
1396 if (!activeTab) {
1397 alert('Please select a report tab first.');
1398 return;
1399 }
1400
1401 // Determine report type
1402 const isPaymentTab = activeTab.id === 'tab-payment-report';
1403 const reportType = isPaymentTab ? 'payment' : 'invoice';
1404
1405 // Get data based on report type
1406 let data, headers, fileName;
1407
1408 if (isPaymentTab) {
1409 data = <?php echo json_encode(isset($payment_report['payments']) ? $payment_report['payments'] : []); ?>;
1410 headers = ['Date', 'Invoice', 'Amount', 'Currency', 'Method', 'Status', 'Transaction ID'];
1411 fileName = 'easy-invoice-payment-report-' + new Date().toISOString().split('T')[0] + '.csv';
1412 } else {
1413 data = <?php echo json_encode(isset($invoice_report['invoices']) ? $invoice_report['invoices'] : []); ?>;
1414 headers = ['Date', 'Due Date', 'Invoice #', 'Client', 'Amount', 'Currency', 'Status'];
1415 fileName = 'easy-invoice-invoice-report-' + new Date().toISOString().split('T')[0] + '.csv';
1416 }
1417
1418 // Generate CSV with proper escaping
1419 let csv = headers.map(header => `"${header}"`).join(',') + '\r\n';
1420
1421 if (Array.isArray(data) && data.length > 0) {
1422 data.forEach(function(item, index) {
1423 if (isPaymentTab) {
1424 // Payment data
1425 const row = [
1426 item.date ? new Date(item.date).toLocaleDateString() : 'N/A',
1427 item.invoice_number || 'N/A',
1428 (item.currency_symbol || '$') + (parseFloat(item.amount || 0).toFixed(2)),
1429 item.currency || 'USD',
1430 item.payment_method || 'Unknown',
1431 item.status || 'Unknown',
1432 item.transaction_id || '-'
1433 ];
1434 csv += row.map(field => `"${field}"`).join(',') + '\r\n';
1435 } else {
1436 // Invoice data
1437 const row = [
1438 item.issue_date ? new Date(item.issue_date).toLocaleDateString() : 'N/A',
1439 item.due_date ? new Date(item.due_date).toLocaleDateString() : '-',
1440 item.invoice_number || 'N/A',
1441 item.client_name || 'Unknown',
1442 (item.currency_symbol || '$') + (parseFloat(item.total || 0).toFixed(2)),
1443 item.currency || 'USD',
1444 item.status || 'Unknown'
1445 ];
1446 csv += row.map(field => `"${field}"`).join(',') + '\r\n';
1447 }
1448 });
1449 } else {
1450 csv += 'No data available\r\n';
1451 }
1452
1453 // Create download using data URL approach
1454 const csvContent = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv);
1455 const link = document.createElement('a');
1456 link.href = csvContent;
1457 link.download = fileName;
1458 link.style.display = 'none';
1459 document.body.appendChild(link);
1460
1461 // Trigger download
1462 link.click();
1463 document.body.removeChild(link);
1464
1465 } catch (error) {
1466 console.error('Export error:', error);
1467 alert('Error generating export. Please try again.');
1468 }
1469 });
1470 }
1471 } catch (error) {
1472 console.error('Error in reports page initialization:', error);
1473
1474 // Make sure tabs are still visible even if there's an error
1475 const tabContents = document.querySelectorAll('.tab-content');
1476 if (tabContents.length > 0) {
1477 tabContents[0].classList.remove('hidden');
1478 tabContents[0].classList.add('block');
1479 }
1480 }
1481 });
1482 </script>
1483