| @@ -3,23 +3,62 @@ | ||
| 3 | 3 | if (!defined('ABSPATH')) { |
| 4 | 4 | exit; |
| 5 | 5 | } |
| 6 | 6 | |
| 7 | +use EasyInvoice\Constants\PagesSlugs; | |
| 7 | 8 | |
| 9 | +$monthly_revenue = isset($monthly_revenue) && is_array($monthly_revenue) ? $monthly_revenue : array(); | |
| 10 | +$easy_invoice_has_chart_data = false; | |
| 11 | +foreach ($monthly_revenue as $currencies) { | |
| 12 | + if (! is_array($currencies)) { | |
| 13 | + continue; | |
| 14 | + } | |
| 15 | + foreach ($currencies as $row) { | |
| 16 | + if (isset($row['amount']) && is_numeric($row['amount']) && (float) $row['amount'] > 0) { | |
| 17 | + $easy_invoice_has_chart_data = true; | |
| 18 | + break 2; | |
| 19 | + } | |
| 20 | + } | |
| 21 | +} | |
| 22 | + | |
| 23 | +if ($easy_invoice_has_chart_data) { | |
| 24 | + wp_enqueue_script( | |
| 25 | + 'easy-invoice-chart-vendor', | |
| 26 | + EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/chart.min.js', | |
| 27 | + array(), | |
| 28 | + EASY_INVOICE_VERSION, | |
| 29 | + true | |
| 30 | + ); | |
| 31 | + wp_enqueue_script( | |
| 32 | + 'easy-invoice-dashboard-chart', | |
| 33 | + EASY_INVOICE_PLUGIN_URL . 'assets/js/dashboard-chart.js', | |
| 34 | + array('easy-invoice-chart-vendor'), | |
| 35 | + EASY_INVOICE_VERSION, | |
| 36 | + true | |
| 37 | + ); | |
| 38 | + wp_localize_script( | |
| 39 | + 'easy-invoice-dashboard-chart', | |
| 40 | + 'easyInvoiceDashboard', | |
| 41 | + array( | |
| 42 | + 'monthlyRevenue' => $monthly_revenue, | |
| 43 | + 'hasData' => true, | |
| 44 | + ) | |
| 45 | + ); | |
| 46 | +} | |
| 47 | + | |
| 48 | +$invoices_list_url = admin_url('admin.php?page=' . PagesSlugs::ALL_INVOICES); | |
| 8 | 49 | ?> |
| 9 | 50 | |
| 10 | 51 | <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;"> | |
| 52 | + <div class="ei-app-page-header bg-white border-b border-gray-200 px-6 easy-invoice-admin-page-header"> | |
| 13 | 53 | <div class="flex items-center justify-between"> |
| 14 | 54 | <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> | |
| 55 | + <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e( 'Dashboard', 'easy-invoice' ); ?></h1> | |
| 56 | + <p class="mt-1 text-sm text-gray-500"><?php esc_html_e( 'Overview of your invoice management system', 'easy-invoice' ); ?></p> | |
| 17 | 57 | </div> |
| 18 | 58 | </div> |
| 19 | 59 | </div> |
| 20 | 60 | |
| 21 | - <!-- Stats Cards --> | |
| 22 | 61 | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-8"> |
| 23 | 62 | <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1"> |
| 24 | 63 | <div class="flex items-center"> |
| 25 | 64 | <div class="rounded-full bg-indigo-100 p-3 mr-4"> |
| @@ -27,9 +66,9 @@ | ||
| 27 | 66 | <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 | 67 | </svg> |
| 29 | 68 | </div> |
| 30 | 69 | <div> |
| 31 | - <h3 class="text-sm font-medium text-gray-500">Total Invoices</h3> | |
| 70 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e( 'Total Invoices', 'easy-invoice' ); ?></h3> | |
| 32 | 71 | <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($total_invoices); ?></p> |
| 33 | 72 | </div> |
| 34 | 73 | </div> |
| 35 | 74 | </div> |
| @@ -40,9 +79,9 @@ | ||
| 40 | 79 | <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 | 80 | </svg> |
| 42 | 81 | </div> |
| 43 | 82 | <div> |
| 44 | - <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3> | |
| 83 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e( 'Total Revenue', 'easy-invoice' ); ?></h3> | |
| 45 | 84 | <?php if (is_array($total_revenue) && !empty($total_revenue)): ?> |
| 46 | 85 | <div class="mt-1"> |
| 47 | 86 | <?php |
| 48 | 87 | $currency_count = count($total_revenue); |
| @@ -51,15 +90,30 @@ | ||
| 51 | 90 | $amount = isset($data['amount']) && is_numeric($data['amount']) ? $data['amount'] : 0; |
| 52 | 91 | $symbol = isset($data['symbol']) ? $data['symbol'] : ''; |
| 53 | 92 | ?> |
| 54 | 93 | <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); ?> | |
| 94 | + <?php echo esc_html($symbol . number_format($amount, 2)); ?> | |
| 56 | 95 | <span class="text-xs font-normal text-gray-500"><?php echo esc_html(strtoupper((string)($currency ?? 'USD'))); ?></span> |
| 57 | 96 | </div> |
| 58 | 97 | <?php endforeach; ?> |
| 59 | 98 | </div> |
| 60 | 99 | <?php else: ?> |
| 61 | - <p class="mt-1 text-lg font-bold text-gray-900">$0.00</p> | |
| 100 | + <?php | |
| 101 | + // Show a formatted zero, not a sentence. Every sibling tile on | |
| 102 | + // this row displays a number ("0"), so putting the prose | |
| 103 | + // "No revenue recorded" here broke the row's visual rhythm and | |
| 104 | + // wrapped onto two lines, making this card read as taller and | |
| 105 | + // more important than the others. The zero carries the same | |
| 106 | + // meaning and stays scannable alongside them. | |
| 107 | + $ei_zero_symbol = function_exists('easy_invoice_get_currency_symbol') ? easy_invoice_get_currency_symbol() : ''; | |
| 108 | + $ei_zero_code = function_exists('easy_invoice_get_currency_code') ? easy_invoice_get_currency_code() : ''; | |
| 109 | + ?> | |
| 110 | + <div class="text-lg font-bold text-gray-900"> | |
| 111 | + <?php echo esc_html($ei_zero_symbol . number_format(0, 2)); ?> | |
| 112 | + <?php if ($ei_zero_code) : ?> | |
| 113 | + <span class="text-xs font-normal text-gray-500"><?php echo esc_html(strtoupper((string) $ei_zero_code)); ?></span> | |
| 114 | + <?php endif; ?> | |
| 115 | + </div> | |
| 62 | 116 | <?php endif; ?> |
| 63 | 117 | </div> |
| 64 | 118 | </div> |
| 65 | 119 | </div> |
| @@ -70,10 +124,13 @@ | ||
| 70 | 124 | <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 | 125 | </svg> |
| 72 | 126 | </div> |
| 73 | 127 | <div> |
| 74 | - <h3 class="text-sm font-medium text-gray-500">Unpaid Invoices</h3> | |
| 128 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e( 'Unpaid Invoices', 'easy-invoice' ); ?></h3> | |
| 75 | 129 | <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html($unpaid_invoices); ?></p> |
| 130 | + <?php foreach ((array) ($unpaid_amount ?? []) as $ei_cur => $ei_amt) : ?> | |
| 131 | + <p class="text-xs text-gray-500" style="font-variant-numeric: tabular-nums;"><?php echo esc_html(easy_invoice_format_money((float) $ei_amt, null, (string) $ei_cur)); ?> <?php esc_html_e('outstanding', 'easy-invoice'); ?></p> | |
| 132 | + <?php endforeach; ?> | |
| 76 | 133 | </div> |
| 77 | 134 | </div> |
| 78 | 135 | </div> |
| 79 | 136 | <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1"> |
| @@ -83,17 +140,23 @@ | ||
| 83 | 140 | <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 | 141 | </svg> |
| 85 | 142 | </div> |
| 86 | 143 | <div> |
| 87 | - <h3 class="text-sm font-medium text-gray-500">Overdue Invoices</h3> | |
| 144 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e( 'Overdue Invoices', 'easy-invoice' ); ?></h3> | |
| 88 | 145 | <p class="mt-1 text-2xl font-bold text-red-600"><?php echo esc_html($overdue_invoices); ?></p> |
| 146 | + <?php foreach ((array) ($overdue_amount ?? []) as $ei_cur => $ei_amt) : ?> | |
| 147 | + <p class="text-xs text-gray-500" style="font-variant-numeric: tabular-nums;"><?php echo esc_html(easy_invoice_format_money((float) $ei_amt, null, (string) $ei_cur)); ?> <?php esc_html_e('past due', 'easy-invoice'); ?></p> | |
| 148 | + <?php endforeach; ?> | |
| 89 | 149 | </div> |
| 90 | 150 | </div> |
| 91 | 151 | </div> |
| 92 | 152 | </div> |
| 93 | 153 | |
| 94 | - <!-- Client Stats Cards --> | |
| 95 | - <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| 154 | + <?php // Same 4-column track as the stat row above (grid-cols-4 at lg), so the two | |
| 155 | + // client cards line up under the first four instead of stretching to half the | |
| 156 | + // page each. They are the same kind of tile and were rendering at roughly | |
| 157 | + // double the width of their siblings, which read as unintentional. ?> | |
| 158 | + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | |
| 96 | 159 | <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1"> |
| 97 | 160 | <div class="flex items-center"> |
| 98 | 161 | <div class="rounded-full bg-blue-100 p-3 mr-4"> |
| 99 | 162 | <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"> |
| @@ -103,9 +166,9 @@ | ||
| 103 | 166 | <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 | 167 | </svg> |
| 105 | 168 | </div> |
| 106 | 169 | <div> |
| 107 | - <h3 class="text-sm font-medium text-gray-500">Total Clients</h3> | |
| 170 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e( 'Total Clients', 'easy-invoice' ); ?></h3> | |
| 108 | 171 | <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($total_clients); ?></p> |
| 109 | 172 | </div> |
| 110 | 173 | </div> |
| 111 | 174 | </div> |
| @@ -116,9 +179,9 @@ | ||
| 116 | 179 | <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 | 180 | </svg> |
| 118 | 181 | </div> |
| 119 | 182 | <div> |
| 120 | - <h3 class="text-sm font-medium text-gray-500">Active Clients</h3> | |
| 183 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e( 'Active Clients', 'easy-invoice' ); ?></h3> | |
| 121 | 184 | <p class="mt-1 text-2xl font-bold text-indigo-600"><?php echo esc_html($active_clients); ?></p> |
| 122 | 185 | </div> |
| 123 | 186 | </div> |
| 124 | 187 | </div> |
| @@ -123,49 +186,61 @@ | ||
| 123 | 186 | </div> |
| 124 | 187 | </div> |
| 125 | 188 | </div> |
| 126 | 189 | |
| 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> | |
| 190 | + <div class="bg-white rounded-lg shadow p-6 mb-8 border border-gray-100"> | |
| 191 | + <h2 class="text-lg font-medium text-gray-900 mb-1"><?php esc_html_e( 'Monthly Revenue', 'easy-invoice' ); ?></h2> | |
| 192 | + <?php if ($easy_invoice_has_chart_data) : ?> | |
| 193 | + <p class="text-sm text-gray-500 mb-4"><?php esc_html_e( 'Paid amounts recorded over the last twelve months, by currency.', 'easy-invoice' ); ?></p> | |
| 194 | + <div class="easy-invoice-chart-wrap"> | |
| 195 | + <canvas id="revenue-chart" class="easy-invoice-chart-canvas" role="img" aria-label="<?php echo esc_attr__( 'Monthly revenue by month and currency', 'easy-invoice' ); ?>"></canvas> | |
| 196 | + </div> | |
| 197 | + <?php else : ?> | |
| 198 | + <p class="text-sm text-gray-500 mb-6"><?php esc_html_e( 'Trends appear here once completed payments exist in the selected period.', 'easy-invoice' ); ?></p> | |
| 199 | + <div class="rounded-xl border border-dashed border-gray-200 bg-gradient-to-b from-gray-50 to-white px-8 py-12 text-center"> | |
| 200 | + <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-indigo-50 text-indigo-600"> | |
| 201 | + <svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> | |
| 202 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path> | |
| 203 | + </svg> | |
| 204 | + </div> | |
| 205 | + <h3 class="mt-4 text-base font-semibold text-gray-900"><?php esc_html_e( 'No revenue trend to show yet', 'easy-invoice' ); ?></h3> | |
| 206 | + <p class="mt-2 max-w-md mx-auto text-sm text-gray-500 leading-relaxed"> | |
| 207 | + <?php esc_html_e( 'Record a payment on an invoice to see your monthly performance in this chart.', 'easy-invoice' ); ?> | |
| 208 | + </p> | |
| 209 | + <a href="<?php echo esc_url($invoices_list_url); ?>" class="mt-6 inline-flex items-center rounded-md bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"> | |
| 210 | + <?php esc_html_e( 'View all invoices', 'easy-invoice' ); ?> | |
| 211 | + </a> | |
| 212 | + </div> | |
| 213 | + <?php endif; ?> | |
| 133 | 214 | </div> |
| 134 | 215 | |
| 135 | - <!-- Recent Invoices --> | |
| 136 | 216 | <div class="bg-white rounded-lg shadow"> |
| 137 | 217 | <div class="px-6 py-4 border-b border-gray-200"> |
| 138 | - <h2 class="text-lg font-medium text-gray-900">Recent Invoices</h2> | |
| 218 | + <h2 class="text-lg font-medium text-gray-900"><?php esc_html_e( 'Recent Invoices', 'easy-invoice' ); ?></h2> | |
| 139 | 219 | </div> |
| 140 | 220 | <div class="overflow-x-auto"> |
| 141 | 221 | <table class="min-w-full divide-y divide-gray-200"> |
| 142 | 222 | <thead class="bg-gray-50"> |
| 143 | 223 | <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> | |
| 224 | + <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e( 'Invoice #', 'easy-invoice' ); ?></th> | |
| 225 | + <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e( 'Client', 'easy-invoice' ); ?></th> | |
| 226 | + <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e( 'Amount', 'easy-invoice' ); ?></th> | |
| 227 | + <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e( 'Status', 'easy-invoice' ); ?></th> | |
| 228 | + <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e( 'Due Date', 'easy-invoice' ); ?></th> | |
| 229 | + <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e( 'Actions', 'easy-invoice' ); ?></th> | |
| 150 | 230 | </tr> |
| 151 | 231 | </thead> |
| 152 | 232 | <tbody class="bg-white divide-y divide-gray-200"> |
| 153 | 233 | <?php if (!empty($recent_invoices)) : ?> |
| 154 | 234 | <?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 | 235 | $invoiceId = $invoice->getId(); |
| 163 | 236 | $invoiceNumber = $invoice->getNumber() ?: ('INV-' . $invoiceId); |
| 164 | 237 | $invoiceTotal = $invoice->getTotal(); |
| 165 | - $clientName = $invoice->getCustomerName() ?: 'Client'; | |
| 166 | - $dueDate = $invoice->getDueDate() ?: date('Y-m-d'); | |
| 167 | - $status = ucfirst($invoice->getStatus()); | |
| 238 | + $clientName = $invoice->getCustomerName() ?: __( 'Client', 'easy-invoice' ); | |
| 239 | + $dueDate = $invoice->getDueDate() ?: current_time('Y-m-d'); | |
| 240 | + $status_key = \EasyInvoice\Helpers\InvoiceStatus::displayKeyFor( $invoice ); | |
| 241 | + $statusClass = \EasyInvoice\Helpers\InvoiceStatus::badgeClass( $status_key ); | |
| 242 | + $status = \EasyInvoice\Helpers\InvoiceStatus::label( $status_key ); | |
| 168 | 243 | ?> |
| 169 | 244 | <tr> |
| 170 | 245 | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"><?php echo esc_html($invoiceNumber); ?></td> |
| 171 | 246 | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo esc_html($clientName); ?></td> |
| @@ -175,21 +250,31 @@ | ||
| 175 | 250 | echo esc_html($formatter->format($invoiceTotal)); |
| 176 | 251 | ?></td> |
| 177 | 252 | <td class="px-6 py-4 whitespace-nowrap"> |
| 178 | 253 | <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); ?> | |
| 254 | + <?php echo esc_html( $status ); ?> | |
| 180 | 255 | </span> |
| 181 | 256 | </td> |
| 182 | 257 | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><?php echo esc_html($dueDate); ?></td> |
| 183 | 258 | <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> | |
| 259 | + <?php | |
| 260 | + // Both links pointed at `page=easy-invoice-invoices`, which is | |
| 261 | + // not a registered admin page — the invoice list is | |
| 262 | + // `easy-invoice-all` and the editor is `easy-invoice-builder`. | |
| 263 | + // Every Edit and View link on the dashboard returned a 403 | |
| 264 | + // "you do not have permission" screen. | |
| 265 | + // | |
| 266 | + // Now matching what the invoice listing itself uses: Edit opens | |
| 267 | + // the builder, View opens the public invoice permalink. | |
| 268 | + ?> | |
| 269 | + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoiceId)); ?>" class="text-indigo-600 hover:text-indigo-900 mr-3"><?php esc_html_e( 'Edit', 'easy-invoice' ); ?></a> | |
| 270 | + <a href="<?php echo esc_url(get_permalink($invoiceId)); ?>" target="_blank" rel="noopener" class="text-indigo-600 hover:text-indigo-900"><?php esc_html_e( 'View', 'easy-invoice' ); ?></a> | |
| 186 | 271 | </td> |
| 187 | 272 | </tr> |
| 188 | 273 | <?php endforeach; ?> |
| 189 | 274 | <?php else: ?> |
| 190 | 275 | <tr> |
| 191 | - <td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">No invoices found</td> | |
| 276 | + <td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500"><?php esc_html_e( 'No invoices found', 'easy-invoice' ); ?></td> | |
| 192 | 277 | </tr> |
| 193 | 278 | <?php endif; ?> |
| 194 | 279 | </tbody> |
| 195 | 280 | </table> |
| @@ -194,183 +279,5 @@ | ||
| 194 | 279 | </tbody> |
| 195 | 280 | </table> |
| 196 | 281 | </div> |
| 197 | 282 | </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> | |
| 283 | +</div> | |