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

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