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 / dashboard-page.php

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

376 lines 19.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 ?>
9
10 <div class="p-8">
11 <!-- Page Header -->
12 <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;">
13 <div class="flex items-center justify-between">
14 <div>
15 <h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
16 <p class="mt-1 text-sm text-gray-500">Overview of your invoice management system</p>
17 </div>
18 </div>
19 </div>
20
21 <!-- Stats Cards -->
22 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8">
23 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
24 <div class="flex items-center">
25 <div class="rounded-full bg-indigo-100 p-3 mr-4">
26 <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">
27 <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>
28 </svg>
29 </div>
30 <div>
31 <h3 class="text-sm font-medium text-gray-500">Total Invoices</h3>
32 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($total_invoices); ?></p>
33 </div>
34 </div>
35 </div>
36 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
37 <div class="flex items-center">
38 <div class="rounded-full bg-green-100 p-3 mr-4">
39 <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">
40 <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>
41 </svg>
42 </div>
43 <div>
44 <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3>
45 <?php if (is_array($total_revenue) && !empty($total_revenue)): ?>
46 <div class="mt-1">
47 <?php
48 $currency_count = count($total_revenue);
49 foreach ($total_revenue as $currency => $data):
50 $is_last = --$currency_count === 0;
51 $amount = isset($data['amount']) && is_numeric($data['amount']) ? $data['amount'] : 0;
52 $symbol = isset($data['symbol']) ? $data['symbol'] : '';
53 ?>
54 <div class="<?php echo $currency_count > 0 ? 'text-sm' : 'text-lg'; ?> font-bold text-gray-900 <?php echo !$is_last ? 'mb-1' : ''; ?>">
55 <?php echo $symbol . number_format($amount, 2); ?>
56 <span class="text-xs font-normal text-gray-500"><?php echo esc_html(strtoupper((string)($currency ?? 'USD'))); ?></span>
57 </div>
58 <?php endforeach; ?>
59 </div>
60 <?php else: ?>
61 <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p>
62 <?php endif; ?>
63 </div>
64 </div>
65 </div>
66 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
67 <div class="flex items-center">
68 <div class="rounded-full bg-yellow-100 p-3 mr-4">
69 <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">
70 <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>
71 </svg>
72 </div>
73 <div>
74 <h3 class="text-sm font-medium text-gray-500">Unpaid Invoices</h3>
75 <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html($unpaid_invoices); ?></p>
76 </div>
77 </div>
78 </div>
79 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
80 <div class="flex items-center">
81 <div class="rounded-full bg-red-100 p-3 mr-4">
82 <svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
83 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
84 </svg>
85 </div>
86 <div>
87 <h3 class="text-sm font-medium text-gray-500">Overdue Invoices</h3>
88 <p class="mt-1 text-2xl font-bold text-red-600"><?php echo esc_html($overdue_invoices); ?></p>
89 </div>
90 </div>
91 </div>
92 </div>
93
94 <!-- Client Stats Cards -->
95 <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
96 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
97 <div class="flex items-center">
98 <div class="rounded-full bg-blue-100 p-3 mr-4">
99 <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">
100 <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"/>
101 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
102 <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"/>
103 <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"/>
104 </svg>
105 </div>
106 <div>
107 <h3 class="text-sm font-medium text-gray-500">Total Clients</h3>
108 <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($total_clients); ?></p>
109 </div>
110 </div>
111 </div>
112 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
113 <div class="flex items-center">
114 <div class="rounded-full bg-indigo-100 p-3 mr-4">
115 <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">
116 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
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-indigo-600"><?php echo esc_html($active_clients); ?></p>
122 </div>
123 </div>
124 </div>
125 </div>
126
127 <!-- Monthly Revenue Chart -->
128 <div class="bg-white rounded-lg shadow p-6 mb-8">
129 <h2 class="text-lg font-medium text-gray-900 mb-4">Monthly Revenue</h2>
130 <div style="height: 300px; position: relative;">
131 <canvas id="revenue-chart" style="width: 100% !important; height: 100% !important;"></canvas>
132 </div>
133 </div>
134
135 <!-- Recent Invoices -->
136 <div class="bg-white rounded-lg shadow">
137 <div class="px-6 py-4 border-b border-gray-200">
138 <h2 class="text-lg font-medium text-gray-900">Recent Invoices</h2>
139 </div>
140 <div class="overflow-x-auto">
141 <table class="min-w-full divide-y divide-gray-200">
142 <thead class="bg-gray-50">
143 <tr>
144 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice #</th>
145 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Client</th>
146 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
147 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
148 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due Date</th>
149 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
150 </tr>
151 </thead>
152 <tbody class="bg-white divide-y divide-gray-200">
153 <?php if (!empty($recent_invoices)) : ?>
154 <?php foreach ($recent_invoices as $invoice):
155 $statusClass = [
156 'paid' => 'bg-green-100 text-green-800',
157 'unpaid' => 'bg-yellow-100 text-yellow-800',
158 'overdue' => 'bg-red-100 text-red-800',
159 'draft' => 'bg-gray-100 text-gray-800'
160 ][$invoice->getStatus()] ?? 'bg-gray-100 text-gray-800';
161
162 $invoiceId = $invoice->getId();
163 $invoiceNumber = $invoice->getNumber() ?: ('INV-' . $invoiceId);
164 $invoiceTotal = $invoice->getTotal();
165 $clientName = $invoice->getCustomerName() ?: 'Client';
166 $dueDate = $invoice->getDueDate() ?: date('Y-m-d');
167 $status = ucfirst($invoice->getStatus());
168 ?>
169 <tr>
170 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"><?php echo esc_html($invoiceNumber); ?></td>
171 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo esc_html($clientName); ?></td>
172 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php
173 // Use InvoiceFormatter for proper currency formatting
174 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter();
175 echo esc_html($formatter->format($invoiceTotal));
176 ?></td>
177 <td class="px-6 py-4 whitespace-nowrap">
178 <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full <?php echo esc_attr($statusClass); ?>">
179 <?php echo esc_html($status); ?>
180 </span>
181 </td>
182 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo esc_html($dueDate); ?></td>
183 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
184 <a href="<?php echo admin_url('admin.php?page=easy-invoice-invoices&view=edit&invoice_id=' . $invoiceId); ?>" class="text-indigo-600 hover:text-indigo-900 mr-3">Edit</a>
185 <a href="<?php echo admin_url('admin.php?page=easy-invoice-invoices&view=view&invoice_id=' . $invoiceId); ?>" class="text-indigo-600 hover:text-indigo-900">View</a>
186 </td>
187 </tr>
188 <?php endforeach; ?>
189 <?php else: ?>
190 <tr>
191 <td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">No invoices found</td>
192 </tr>
193 <?php endif; ?>
194 </tbody>
195 </table>
196 </div>
197 </div>
198 </div>
199
200 <!-- Chart.js for Revenue Chart -->
201 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
202 <script>
203 // Add fallback to CDN if local file fails
204 if (typeof Chart === "undefined") {
205 var script = document.createElement("script");
206 script.src = "https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js";
207 script.onload = function() {
208 // Chart.js loaded from CDN
209 };
210 script.onerror = function() {
211 // Failed to load Chart.js from CDN
212 };
213 document.head.appendChild(script);
214 }
215
216 // Initialize dashboard revenue chart
217 document.addEventListener("DOMContentLoaded", function() {
218 const chartElement = document.getElementById("revenue-chart");
219
220 if (!chartElement) {
221 return;
222 }
223
224 // Check if Chart.js is available
225 if (typeof Chart === "undefined") {
226 setTimeout(function() {
227 initializeDashboardRevenueChart();
228 }, 500);
229 return;
230 }
231
232 initializeDashboardRevenueChart();
233 });
234
235 function initializeDashboardRevenueChart() {
236 const chartElement = document.getElementById("revenue-chart");
237
238 if (!chartElement) {
239 return;
240 }
241
242 // Destroy existing chart if it exists
243 if (window.dashboardRevenueChart) {
244 window.dashboardRevenueChart.destroy();
245 }
246
247 const ctx = chartElement.getContext("2d");
248 if (!ctx) {
249 return;
250 }
251
252 // Get data from PHP
253 const monthlyRevenueData = <?php echo json_encode($monthly_revenue); ?>;
254 const monthLabels = Object.keys(monthlyRevenueData);
255
256 // Check if we have real data
257 let hasRealData = false;
258 if (monthlyRevenueData && typeof monthlyRevenueData === "object") {
259 for (let month in monthlyRevenueData) {
260 if (monthlyRevenueData[month] && typeof monthlyRevenueData[month] === "object") {
261 for (let currency in monthlyRevenueData[month]) {
262 if (monthlyRevenueData[month][currency] &&
263 monthlyRevenueData[month][currency].amount &&
264 monthlyRevenueData[month][currency].amount > 0) {
265 hasRealData = true;
266 break;
267 }
268 }
269 }
270 }
271 }
272
273 if (hasRealData) {
274 // Create datasets for each currency
275 const datasets = [];
276 const colors = [
277 "rgba(79, 70, 229, 0.8)",
278 "rgba(34, 197, 94, 0.8)",
279 "rgba(239, 68, 68, 0.8)",
280 "rgba(245, 158, 11, 0.8)",
281 "rgba(139, 92, 246, 0.8)"
282 ];
283
284 let colorIndex = 0;
285 for (let currency in monthlyRevenueData) {
286 if (monthlyRevenueData[currency] && monthlyRevenueData[currency].amount > 0) {
287 datasets.push({
288 label: currency,
289 data: [monthlyRevenueData[currency].amount],
290 backgroundColor: colors[colorIndex % colors.length],
291 borderColor: colors[colorIndex % colors.length].replace("0.8", "1"),
292 borderWidth: 2
293 });
294 colorIndex++;
295 }
296 }
297
298 // Create chart with real data
299 window.dashboardRevenueChart = new Chart(ctx, {
300 type: "bar",
301 data: {
302 labels: monthLabels,
303 datasets: datasets
304 },
305 options: {
306 responsive: true,
307 maintainAspectRatio: false,
308 scales: {
309 y: {
310 beginAtZero: true,
311 ticks: {
312 callback: function(value) {
313 return value;
314 }
315 }
316 }
317 },
318 plugins: {
319 legend: {
320 display: true,
321 position: "top"
322 }
323 }
324 }
325 });
326 } else {
327 // Create sample chart
328 const sampleData = {
329 "Jan 2025": { USD: { amount: 1200, symbol: "$" } },
330 "Feb 2025": { USD: { amount: 1800, symbol: "$" } },
331 "Mar 2025": { USD: { amount: 1500, symbol: "$" } },
332 "Apr 2025": { USD: { amount: 2200, symbol: "$" } },
333 "May 2025": { USD: { amount: 1900, symbol: "$" } },
334 "Jun 2025": { USD: { amount: 2500, symbol: "$" } }
335 };
336
337 const sampleLabels = Object.keys(sampleData);
338 const sampleValues = sampleLabels.map(month => sampleData[month].USD.amount);
339
340 window.dashboardRevenueChart = new Chart(ctx, {
341 type: "bar",
342 data: {
343 labels: sampleLabels,
344 datasets: [{
345 label: "Sample Revenue",
346 data: sampleValues,
347 backgroundColor: "rgba(79, 70, 229, 0.8)",
348 borderColor: "rgba(79, 70, 229, 1)",
349 borderWidth: 2
350 }]
351 },
352 options: {
353 responsive: true,
354 maintainAspectRatio: false,
355 scales: {
356 y: {
357 beginAtZero: true,
358 ticks: {
359 callback: function(value) {
360 return value;
361 }
362 }
363 }
364 },
365 plugins: {
366 legend: {
367 display: true,
368 position: "top"
369 }
370 }
371 }
372 });
373 }
374 }
375 </script>
376 </script>