946 lines
47 KiB
JavaScript
946 lines
47 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { Head } from '@inertiajs/react';
|
|
import EmployerLayout from '../../Layouts/EmployerLayout';
|
|
import { useTranslation } from '../../lib/LanguageContext';
|
|
import {
|
|
CreditCard,
|
|
CheckCircle2,
|
|
Search,
|
|
FileText,
|
|
Download,
|
|
Printer,
|
|
Copy,
|
|
FileSpreadsheet,
|
|
ArrowUpRight,
|
|
TrendingUp,
|
|
Filter,
|
|
Calendar,
|
|
ChevronRight,
|
|
DollarSign,
|
|
RefreshCw
|
|
} from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
|
|
export default function PaymentHistory({ payments = [], currentPlan, expiresAt, subscriptionStatus }) {
|
|
const { t } = useTranslation();
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
const [sortBy, setSortBy] = useState('date_desc');
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const itemsPerPage = 5;
|
|
|
|
// Reset to page 1 when filter/sorting parameters change
|
|
React.useEffect(() => {
|
|
setCurrentPage(1);
|
|
}, [searchTerm, statusFilter, sortBy]);
|
|
|
|
// Calculate dynamic stats
|
|
const totalSpent = payments.reduce((acc, curr) => acc + (curr.status === 'success' ? curr.amount : 0), 0);
|
|
const successfulCount = payments.filter(p => p.status === 'success').length;
|
|
const failedCount = payments.filter(p => p.status === 'failed').length;
|
|
|
|
// Filter and Sort Logic
|
|
const filteredPayments = payments
|
|
.filter(payment => {
|
|
const matchesSearch =
|
|
payment.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
payment.invoice_no.toLowerCase().includes(searchTerm.toLowerCase());
|
|
|
|
const matchesStatus = statusFilter === 'all' || payment.status === statusFilter;
|
|
|
|
return matchesSearch && matchesStatus;
|
|
})
|
|
.sort((a, b) => {
|
|
if (sortBy === 'date_desc') {
|
|
return new Date(b.date) - new Date(a.date);
|
|
}
|
|
if (sortBy === 'date_asc') {
|
|
return new Date(a.date) - new Date(b.date);
|
|
}
|
|
if (sortBy === 'amount_desc') {
|
|
return b.amount - a.amount;
|
|
}
|
|
if (sortBy === 'amount_asc') {
|
|
return a.amount - b.amount;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
const totalPages = Math.ceil(filteredPayments.length / itemsPerPage);
|
|
const paginatedPayments = filteredPayments.slice(
|
|
(currentPage - 1) * itemsPerPage,
|
|
currentPage * itemsPerPage
|
|
);
|
|
|
|
const downloadReceipt = (invoice) => {
|
|
// Calculate UAE VAT (5%)
|
|
const total = parseFloat(invoice.amount);
|
|
const subtotal = total / 1.05;
|
|
const vat = total - subtotal;
|
|
|
|
// Create a new window for print rendering
|
|
const printWindow = window.open('', '_blank', 'width=900,height=900');
|
|
if (!printWindow) {
|
|
toast.error(t('popup_blocked', 'Popup blocked! Please allow popups to download receipts.'));
|
|
return;
|
|
}
|
|
|
|
printWindow.document.write(`
|
|
<html>
|
|
<head>
|
|
<title>Invoice - ${invoice.invoice_no}</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
color: #1e293b;
|
|
background-color: #ffffff;
|
|
padding: 50px;
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
font-size: 13px;
|
|
-webkit-print-color-adjust: exact;
|
|
print-color-adjust: exact;
|
|
}
|
|
.invoice-container {
|
|
max-width: 850px;
|
|
margin: 0 auto;
|
|
background: #ffffff;
|
|
}
|
|
.header-banner {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 50px;
|
|
}
|
|
.logo-placeholder {
|
|
font-size: 24px;
|
|
font-weight: 950;
|
|
color: #185FA5;
|
|
letter-spacing: -1px;
|
|
}
|
|
.header-banner h1 {
|
|
font-size: 38px;
|
|
font-weight: 800;
|
|
color: #185FA5;
|
|
margin: 0;
|
|
letter-spacing: -1px;
|
|
text-transform: uppercase;
|
|
}
|
|
.address-grid {
|
|
display: grid;
|
|
grid-template-cols: 1fr 1fr 1.2fr;
|
|
gap: 30px;
|
|
margin-bottom: 50px;
|
|
}
|
|
.address-block h3 {
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
color: #185FA5;
|
|
margin: 0 0 10px 0;
|
|
border-bottom: 1.5px solid #e2e8f0;
|
|
padding-bottom: 6px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.address-block p {
|
|
margin: 0 0 5px 0;
|
|
color: #475569;
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
}
|
|
.address-block p strong {
|
|
color: #0f172a;
|
|
font-weight: 700;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 40px;
|
|
}
|
|
th {
|
|
background-color: #185FA5;
|
|
color: #ffffff;
|
|
padding: 12px 16px;
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
border: 1px solid #185FA5;
|
|
}
|
|
th:first-child {
|
|
border-top-left-radius: 4px;
|
|
}
|
|
th:last-child {
|
|
border-top-right-radius: 4px;
|
|
}
|
|
td {
|
|
padding: 16px;
|
|
color: #334155;
|
|
font-weight: 500;
|
|
border: 1px solid #e2e8f0;
|
|
background-color: #fafafa;
|
|
}
|
|
.text-center {
|
|
text-align: center;
|
|
}
|
|
.text-right {
|
|
text-align: right;
|
|
}
|
|
.totals-section {
|
|
width: 320px;
|
|
margin-left: auto;
|
|
margin-bottom: 50px;
|
|
}
|
|
.totals-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
color: #475569;
|
|
font-weight: 600;
|
|
}
|
|
.totals-row.grand-total {
|
|
border-top: 2px solid #185FA5;
|
|
font-size: 16px;
|
|
font-weight: 900;
|
|
color: #0f172a;
|
|
padding-top: 10px;
|
|
margin-top: 4px;
|
|
}
|
|
.details-section {
|
|
margin-bottom: 50px;
|
|
}
|
|
.details-title {
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
color: #185FA5;
|
|
margin: 0 0 10px 0;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
border-bottom: 1.5px solid #e2e8f0;
|
|
padding-bottom: 6px;
|
|
}
|
|
.details-content {
|
|
color: #475569;
|
|
font-weight: 500;
|
|
margin-bottom: 24px;
|
|
line-height: 1.4;
|
|
}
|
|
.signature-block {
|
|
display: grid;
|
|
grid-template-cols: 1fr 1fr;
|
|
gap: 80px;
|
|
margin-top: 80px;
|
|
margin-bottom: 60px;
|
|
}
|
|
.sig-line {
|
|
border-top: 1.5px solid #94a3b8;
|
|
text-align: center;
|
|
padding-top: 8px;
|
|
font-size: 10px;
|
|
font-weight: 800;
|
|
color: #475569;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
.footer-bar {
|
|
background-color: #185FA5;
|
|
color: #ffffff;
|
|
padding: 16px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
}
|
|
.footer-bar span {
|
|
opacity: 0.9;
|
|
}
|
|
.btn-print {
|
|
position: fixed;
|
|
bottom: 30px;
|
|
right: 30px;
|
|
background: #185FA5;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 12px;
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 12px rgba(24, 95, 165, 0.3);
|
|
transition: all 0.2s;
|
|
z-index: 999;
|
|
}
|
|
.btn-print:hover {
|
|
background: #144f8a;
|
|
transform: translateY(-1px);
|
|
}
|
|
@media print {
|
|
body {
|
|
padding: 0;
|
|
background: none;
|
|
}
|
|
.invoice-container {
|
|
border: none;
|
|
box-shadow: none;
|
|
padding: 0;
|
|
}
|
|
.btn-print {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="invoice-container">
|
|
<div class="header-banner">
|
|
<div class="logo-placeholder">MARKETPLACE</div>
|
|
<h1>Invoice</h1>
|
|
</div>
|
|
|
|
<div class="address-grid">
|
|
<div class="address-block">
|
|
<h3>Invoice From</h3>
|
|
<p><strong>MOHRE Domestic Labor Portal</strong></p>
|
|
<p>Domestic Labor Marketplace</p>
|
|
<p>billing@marketplace.ae</p>
|
|
<p>Abu Dhabi, UAE</p>
|
|
<p>Phone: 800-MOHRE</p>
|
|
<p>TRN: 100293810200003</p>
|
|
</div>
|
|
<div class="address-block">
|
|
<h3>Invoice To</h3>
|
|
<p><strong>Employer Account</strong></p>
|
|
<p>Domestic Sponsor Household</p>
|
|
<p>sponsor@marketplace.ae</p>
|
|
<p>Dubai, UAE</p>
|
|
<p>Phone: +971 50 123 4567</p>
|
|
</div>
|
|
<div class="address-block">
|
|
<h3>Invoice Details</h3>
|
|
<p><strong>Invoice Number:</strong> ${invoice.invoice_no}</p>
|
|
<p><strong>Invoice Date:</strong> ${invoice.date}</p>
|
|
<p><strong>Invoice Due Date:</strong> ${invoice.date}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 80px;">Items</th>
|
|
<th>Description</th>
|
|
<th class="text-center" style="width: 80px;">QTY</th>
|
|
<th class="text-right" style="width: 120px;">Price</th>
|
|
<th class="text-right" style="width: 120px;">Sales Tax</th>
|
|
<th class="text-right" style="width: 120px;">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="text-center">1</td>
|
|
<td>${invoice.description}</td>
|
|
<td class="text-center">1</td>
|
|
<td class="text-right">${subtotal.toFixed(2)} AED</td>
|
|
<td class="text-right">5% VAT</td>
|
|
<td class="text-right">${total.toFixed(2)} AED</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="totals-section">
|
|
<div class="totals-row">
|
|
<span>Subtotal</span>
|
|
<span>${subtotal.toFixed(2)} AED</span>
|
|
</div>
|
|
<div class="totals-row">
|
|
<span>Sales Tax (5% VAT)</span>
|
|
<span>${vat.toFixed(2)} AED</span>
|
|
</div>
|
|
<div class="totals-row grand-total">
|
|
<span>Total</span>
|
|
<span>${total.toFixed(2)} AED</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-title">Payment Details</div>
|
|
<div class="details-content">
|
|
<strong>Name:</strong> Domestic Labor Marketplace Portal<br>
|
|
<strong>Account Number:</strong> 1029384756<br>
|
|
<strong>Bank Name and Address:</strong> Emirates NBD Bank PJSC, Main Branch, Dubai<br>
|
|
<strong>Bank Swift Code:</strong> EBILAEADXXX<br>
|
|
<strong>IBAN Number:</strong> AE89 0030 0000 0010 2938 4756
|
|
</div>
|
|
|
|
<div class="details-title">Payment Terms</div>
|
|
<div class="details-content">
|
|
Payment processed successfully via PayTabs Secured Checkout. Plan access updated instantly.
|
|
</div>
|
|
|
|
<div class="details-title">Notes</div>
|
|
<div class="details-content">
|
|
Auto-renewal is enabled. Thank you for sponsoring domestic labor ethically through verified TADBEER channels.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="signature-block">
|
|
<div class="sig-line">Signature</div>
|
|
<div class="sig-line">Date</div>
|
|
</div>
|
|
|
|
<div class="footer-bar">
|
|
<span>This invoice was auto generated by Domestic Labor Marketplace.</span>
|
|
<span>Powered by PayTabs Secured Gateway</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="btn-print" onclick="window.print()">Print / Save PDF</button>
|
|
|
|
<script>
|
|
window.onload = function() {
|
|
setTimeout(function() {
|
|
window.print();
|
|
}, 500);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`);
|
|
printWindow.document.close();
|
|
|
|
toast.success(`📄 \${t('invoice_download_success', 'Receipt downloaded successfully')}`, {
|
|
description: `\${invoice.invoice_no} has been converted to an official tax invoice.`,
|
|
duration: 4000
|
|
});
|
|
};
|
|
|
|
const handlePrint = () => {
|
|
window.print();
|
|
};
|
|
|
|
const handleExportCSV = () => {
|
|
if (filteredPayments.length === 0) {
|
|
toast.error(t('no_data_to_export', 'No data to export'));
|
|
return;
|
|
}
|
|
|
|
const headers = ['Invoice ID', 'Date & Time', 'Description', 'Status', 'Amount', 'Currency'];
|
|
const rows = filteredPayments.map(p => [
|
|
p.invoice_no,
|
|
`${p.date} ${p.time}`,
|
|
p.description,
|
|
p.status.toUpperCase(),
|
|
p.amount.toFixed(2),
|
|
p.currency
|
|
]);
|
|
|
|
const csvContent = "data:text/csv;charset=utf-8,"
|
|
+ [headers.join(','), ...rows.map(e => e.map(val => `"${String(val).replace(/"/g, '""')}"`).join(','))].join('\n');
|
|
|
|
const encodedUri = encodeURI(csvContent);
|
|
const link = document.createElement("a");
|
|
link.setAttribute("href", encodedUri);
|
|
link.setAttribute("download", `payment_history_${new Date().toISOString().slice(0,10)}.csv`);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
toast.success(`📊 ${t('csv_exported', 'CSV Export Complete!')}`, {
|
|
description: `${filteredPayments.length} billing items downloaded successfully.`
|
|
});
|
|
};
|
|
|
|
const handleCopy = () => {
|
|
if (filteredPayments.length === 0) {
|
|
toast.error(t('no_data_to_copy', 'No data to copy'));
|
|
return;
|
|
}
|
|
const headers = ['Invoice ID', 'Date & Time', 'Description', 'Status', 'Amount', 'Currency'];
|
|
const rows = filteredPayments.map(p => [
|
|
p.invoice_no,
|
|
`${p.date} ${p.time}`,
|
|
p.description,
|
|
p.status.toUpperCase(),
|
|
p.amount.toFixed(2),
|
|
p.currency
|
|
]);
|
|
const text = [headers.join('\t'), ...rows.map(r => r.join('\t'))].join('\n');
|
|
navigator.clipboard.writeText(text);
|
|
toast.success(`📋 ${t('copied_to_clipboard', 'Copied to Clipboard!')}`);
|
|
};
|
|
|
|
const handleExportPDF = () => {
|
|
if (filteredPayments.length === 0) {
|
|
toast.error(t('no_data_to_export', 'No data to export'));
|
|
return;
|
|
}
|
|
|
|
const printWindow = window.open('', '_blank', 'width=900,height=900');
|
|
if (!printWindow) {
|
|
toast.error(t('popup_blocked', 'Popup blocked! Please allow popups.'));
|
|
return;
|
|
}
|
|
|
|
const totalAmount = filteredPayments.reduce((sum, p) => sum + p.amount, 0);
|
|
|
|
printWindow.document.write(`
|
|
<html>
|
|
<head>
|
|
<title>Payment History Statement</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
color: #1e293b;
|
|
background-color: #ffffff;
|
|
padding: 50px;
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
font-size: 13px;
|
|
}
|
|
.statement-container {
|
|
max-width: 850px;
|
|
margin: 0 auto;
|
|
}
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 40px;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
padding-bottom: 20px;
|
|
}
|
|
.title {
|
|
font-size: 24px;
|
|
font-weight: 800;
|
|
color: #185FA5;
|
|
}
|
|
.meta-info {
|
|
text-align: right;
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 30px;
|
|
}
|
|
th {
|
|
background-color: #185FA5;
|
|
color: #ffffff;
|
|
padding: 10px 12px;
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
text-align: left;
|
|
}
|
|
td {
|
|
padding: 12px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
font-size: 12px;
|
|
}
|
|
.text-right {
|
|
text-align: right;
|
|
}
|
|
.summary-card {
|
|
background-color: #f8fafc;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 40px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
.summary-item h4 {
|
|
margin: 0 0 5px 0;
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
color: #64748b;
|
|
}
|
|
.summary-item p {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 850;
|
|
color: #0f172a;
|
|
}
|
|
.btn-print {
|
|
position: fixed;
|
|
bottom: 30px;
|
|
right: 30px;
|
|
background: #185FA5;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 12px;
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 12px rgba(24, 95, 165, 0.3);
|
|
}
|
|
@media print {
|
|
.btn-print {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="statement-container">
|
|
<div class="header">
|
|
<div>
|
|
<div class="title">PAYMENT HISTORY STATEMENT</div>
|
|
<div style="font-size: 12px; margin-top: 5px; font-weight: 500;">Employer Account: ${currentPlan}</div>
|
|
</div>
|
|
<div class="meta-info">
|
|
<div><strong>Statement Date:</strong> ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</div>
|
|
<div><strong>Status:</strong> ${subscriptionStatus}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="summary-card">
|
|
<div class="summary-item">
|
|
<h4>Total Invoices</h4>
|
|
<p>${filteredPayments.length}</p>
|
|
</div>
|
|
<div class="summary-item">
|
|
<h4>Total Amount</h4>
|
|
<p>${totalAmount.toFixed(2)} AED</p>
|
|
</div>
|
|
<div class="summary-item">
|
|
<h4>Active Until</h4>
|
|
<p>${expiresAt}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Invoice ID</th>
|
|
<th>Date & Time</th>
|
|
<th>Description</th>
|
|
<th>Status</th>
|
|
<th class="text-right">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${filteredPayments.map(p => `
|
|
<tr>
|
|
<td style="font-family: monospace; font-weight: bold; color: #185FA5;">${p.invoice_no}</td>
|
|
<td>${p.date} - ${p.time}</td>
|
|
<td>${p.description}</td>
|
|
<td style="font-weight: 700; color: ${p.status === 'success' ? '#10b981' : '#f43f5e'};">${p.status.toUpperCase()}</td>
|
|
<td class="text-right" style="font-weight: bold;">${p.amount.toFixed(2)} AED</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<button class="btn-print" onclick="window.print()">Print / Save PDF</button>
|
|
<script>
|
|
window.onload = function() {
|
|
setTimeout(function() {
|
|
window.print();
|
|
}, 500);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`);
|
|
printWindow.document.close();
|
|
|
|
toast.success(`📄 ${t('pdf_exported', 'Statement PDF Generated')}`, {
|
|
description: `${filteredPayments.length} billing items compiled into PDF statement.`
|
|
});
|
|
};
|
|
|
|
return (
|
|
<EmployerLayout title={t('payment_history', 'Payment History')}>
|
|
<Head title={`${t('payment_history', 'Payment History')} - ${t('employer_portal', 'Employer Portal')}`} />
|
|
|
|
<div className="space-y-8 select-none pb-16">
|
|
|
|
{/* Header Title Section */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-black text-slate-900 tracking-tight">{t('payment_history', 'Payment History')}</h1>
|
|
<p className="text-xs font-medium text-slate-500 mt-1">
|
|
{t('payment_history_desc', 'Track subscription plans, document verification, and direct hiring invoice details.')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Dashboard Stats Panel */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
{/* Card 1: Current Active Plan */}
|
|
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs relative overflow-hidden group hover:scale-[1.01] transition-all duration-300">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400">{t('current_active_plan', 'Current Plan')}</span>
|
|
<span className="p-1.5 bg-blue-50 text-blue-600 rounded-xl border border-blue-100">
|
|
<CreditCard className="w-4 h-4" />
|
|
</span>
|
|
</div>
|
|
<div className="mt-4">
|
|
<h2 className="text-base font-black tracking-tight text-slate-900 truncate" title={currentPlan}>{currentPlan}</h2>
|
|
</div>
|
|
<p className="text-[10px] text-emerald-600 font-bold mt-2 flex items-center">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-1.5 animate-pulse" />
|
|
{subscriptionStatus || 'Active'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Card 2: Next Renewal Date */}
|
|
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs relative overflow-hidden group hover:scale-[1.01] transition-all duration-300">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400">{t('next_renewal_date', 'Next Renewal')}</span>
|
|
<span className="p-1.5 bg-amber-50 text-amber-600 rounded-xl border border-amber-100">
|
|
<Calendar className="w-4 h-4" />
|
|
</span>
|
|
</div>
|
|
<div className="mt-4">
|
|
<h2 className="text-base font-black tracking-tight text-slate-900">{expiresAt}</h2>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 font-medium mt-2">
|
|
{t('auto_renew_enabled', 'Auto-renewal is enabled')}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Card 3: Total Spent */}
|
|
<div className="bg-gradient-to-br from-[#185FA5] to-blue-800 p-6 rounded-3xl text-white shadow-md relative overflow-hidden group hover:scale-[1.01] transition-all duration-300">
|
|
<div className="absolute right-[-20px] bottom-[-20px] opacity-10 group-hover:scale-110 transition-transform duration-500">
|
|
<TrendingUp className="w-40 h-40" />
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-[10px] font-black uppercase tracking-widest text-blue-200">{t('total_amount_invested', 'Total Spent')}</span>
|
|
<span className="p-1.5 bg-white/10 rounded-xl text-blue-200">
|
|
<DollarSign className="w-4 h-4" />
|
|
</span>
|
|
</div>
|
|
<div className="mt-3 flex items-baseline space-x-1.5">
|
|
<h2 className="text-2xl font-black tracking-tight">{totalSpent.toFixed(2)}</h2>
|
|
<span className="text-xs font-bold text-blue-200">AED</span>
|
|
</div>
|
|
<p className="text-[10px] text-blue-200/80 font-medium mt-1">
|
|
{t('across_all_periods', 'Across active subscription periods')}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Card 4: Successful Orders */}
|
|
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs relative overflow-hidden group hover:scale-[1.01] transition-all duration-300">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-[10px] font-black uppercase tracking-widest text-slate-400">{t('authorized_invoices', 'Authorized Receipts')}</span>
|
|
<span className="p-1.5 bg-emerald-50 text-emerald-600 rounded-xl border border-emerald-100">
|
|
<CheckCircle2 className="w-4 h-4" />
|
|
</span>
|
|
</div>
|
|
<div className="mt-4">
|
|
<h2 className="text-base font-black tracking-tight text-slate-900">{successfulCount} Invoices</h2>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 font-medium mt-2">
|
|
{t('fully_vat_compliant', 'Fully UAE compliant & VAT logged')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table and Filter Section */}
|
|
<div className="bg-white rounded-3xl border border-slate-200 shadow-sm overflow-hidden">
|
|
|
|
{/* Filter Controls Header */}
|
|
<div className="p-6 border-b border-slate-100 bg-slate-50/50 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
|
|
{/* Search and Filters */}
|
|
<div className="flex flex-wrap items-center gap-3 flex-1">
|
|
<div className="relative w-full sm:w-64">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<input
|
|
type="text"
|
|
placeholder={t('search_by_description', 'Search by description or ID...')}
|
|
className="w-full pl-10 pr-4 py-2 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-100 focus:border-[#185FA5] transition-all outline-none"
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<select
|
|
className="px-3 py-2 bg-white border border-slate-200 rounded-xl text-xs font-bold text-slate-600 outline-none focus:ring-2 focus:ring-blue-100 focus:border-[#185FA5] cursor-pointer"
|
|
value={statusFilter}
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
>
|
|
<option value="all">📁 {t('all_statuses', 'All Statuses')}</option>
|
|
<option value="success">🟢 {t('status_success', 'Success')}</option>
|
|
<option value="pending">🟡 {t('status_pending', 'Pending')}</option>
|
|
<option value="failed">🔴 {t('status_failed', 'Failed')}</option>
|
|
</select>
|
|
|
|
<select
|
|
className="px-3 py-2 bg-white border border-slate-200 rounded-xl text-xs font-bold text-slate-600 outline-none focus:ring-2 focus:ring-blue-100 focus:border-[#185FA5] cursor-pointer"
|
|
value={sortBy}
|
|
onChange={(e) => setSortBy(e.target.value)}
|
|
>
|
|
<option value="date_desc">📅 {t('date_newest', 'Date: Newest First')}</option>
|
|
<option value="date_asc">📅 {t('date_oldest', 'Date: Oldest First')}</option>
|
|
<option value="amount_desc">💰 {t('amount_highest', 'Amount: Highest First')}</option>
|
|
<option value="amount_asc">💰 {t('amount_lowest', 'Amount: Lowest First')}</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Export Buttons */}
|
|
<div className="flex items-center space-x-2.5">
|
|
<div className="flex items-center bg-white border border-slate-200 rounded-xl p-1 shadow-xs">
|
|
<button
|
|
onClick={handleCopy}
|
|
className="px-3 py-1.5 text-[10px] font-black text-slate-500 hover:text-[#185FA5] hover:bg-blue-50 rounded-lg transition-all"
|
|
>
|
|
COPY
|
|
</button>
|
|
<button
|
|
onClick={handleExportCSV}
|
|
className="px-3 py-1.5 text-[10px] font-black text-slate-500 hover:text-[#185FA5] hover:bg-blue-50 rounded-lg transition-all"
|
|
>
|
|
CSV
|
|
</button>
|
|
<button
|
|
onClick={handleExportPDF}
|
|
className="px-3 py-1.5 text-[10px] font-black text-slate-500 hover:text-[#185FA5] hover:bg-blue-50 rounded-lg transition-all"
|
|
>
|
|
PDF
|
|
</button>
|
|
<button
|
|
onClick={handlePrint}
|
|
className="px-3 py-1.5 text-[10px] font-black text-slate-500 hover:text-[#185FA5] hover:bg-blue-50 rounded-lg transition-all"
|
|
>
|
|
PRINT
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table Data */}
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left">
|
|
<thead>
|
|
<tr className="border-b border-slate-100">
|
|
<th className="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('invoice_id', 'Invoice ID')}</th>
|
|
<th className="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('date_time', 'Date & Time')}</th>
|
|
<th className="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">{t('description', 'Description')}</th>
|
|
<th className="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest text-center">{t('status', 'Status')}</th>
|
|
<th className="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest text-right">{t('amount', 'Amount')}</th>
|
|
<th className="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest text-right">{t('actions', 'Actions')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-50">
|
|
{paginatedPayments.length > 0 ? (
|
|
paginatedPayments.map((payment) => (
|
|
<tr key={payment.id} className="group hover:bg-slate-50/50 transition-colors">
|
|
<td className="px-6 py-5">
|
|
<div className="flex items-center space-x-2 font-mono text-xs font-black text-[#185FA5]">
|
|
<FileText className="w-3.5 h-3.5 text-slate-400" />
|
|
<span>{payment.invoice_no}</span>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-5">
|
|
<div className="text-xs font-bold text-slate-700">{payment.date}</div>
|
|
<div className="text-[10px] text-slate-400 font-medium mt-0.5">{payment.time}</div>
|
|
</td>
|
|
<td className="px-6 py-5">
|
|
<div className="text-xs font-black text-slate-900">{payment.description}</div>
|
|
<div className="text-[10px] text-slate-400 font-medium mt-0.5">PayTabs Secured Transfer</div>
|
|
</td>
|
|
<td className="px-6 py-5 text-center">
|
|
<span className={`inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-black tracking-tight border ${
|
|
payment.status === 'success'
|
|
? 'bg-emerald-50 text-emerald-700 border-emerald-100'
|
|
: (payment.status === 'pending' ? 'bg-amber-50 text-amber-700 border-amber-100' : 'bg-rose-50 text-rose-700 border-rose-100')
|
|
}`}>
|
|
<span className={`w-1 h-1 rounded-full mr-1.5 ${
|
|
payment.status === 'success'
|
|
? 'bg-emerald-500'
|
|
: (payment.status === 'pending' ? 'bg-amber-500' : 'bg-rose-500')
|
|
}`} />
|
|
{payment.status.toUpperCase()}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-5 text-right">
|
|
<span className="text-sm font-black text-slate-800">
|
|
{payment.amount.toFixed(2)}{' '}
|
|
<span className="text-[10px] font-bold text-slate-400">{payment.currency}</span>
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-5 text-right">
|
|
<button
|
|
onClick={() => downloadReceipt(payment)}
|
|
className="p-2 bg-blue-50 text-[#185FA5] hover:bg-[#185FA5] hover:text-white rounded-lg transition-all"
|
|
title={t('download_pdf_invoice', 'Download PDF Invoice')}
|
|
>
|
|
<Download className="w-3.5 h-3.5" />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<td colSpan="6" className="p-20 text-center">
|
|
<div className="space-y-3">
|
|
<CreditCard className="w-12 h-12 text-slate-200 mx-auto" />
|
|
<div className="text-base font-bold text-slate-400">
|
|
{t('no_payments_logged', 'No transaction items logged')}
|
|
</div>
|
|
<p className="text-xs text-slate-400 font-medium">
|
|
{t('try_adjusting_filters', 'Try adjusting your search criteria or filter levels.')}
|
|
</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Footer Summary Info & Pagination */}
|
|
<div className="px-6 py-4 bg-slate-50/50 border-t border-slate-100 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-slate-500 font-bold">
|
|
<div className="uppercase tracking-wider text-[10px]">
|
|
{t('showing_invoices', 'Showing {start} to {end} of {total} billing items')
|
|
.replace('{start}', filteredPayments.length > 0 ? (currentPage - 1) * itemsPerPage + 1 : 0)
|
|
.replace('{end}', Math.min(currentPage * itemsPerPage, filteredPayments.length))
|
|
.replace('{total}', filteredPayments.length)}
|
|
</div>
|
|
{totalPages > 1 && (
|
|
<div className="flex items-center space-x-1">
|
|
<button
|
|
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
|
|
disabled={currentPage === 1}
|
|
className="px-2.5 py-1.5 border border-slate-200 rounded-lg bg-white hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
|
>
|
|
{t('prev', 'Prev')}
|
|
</button>
|
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
|
|
<button
|
|
key={page}
|
|
onClick={() => setCurrentPage(page)}
|
|
className={`px-3 py-1.5 rounded-lg border transition-all ${
|
|
currentPage === page
|
|
? 'bg-[#185FA5] border-[#185FA5] text-white'
|
|
: 'bg-white border-slate-200 hover:bg-slate-50 text-slate-600'
|
|
}`}
|
|
>
|
|
{page}
|
|
</button>
|
|
))}
|
|
<button
|
|
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
|
|
disabled={currentPage === totalPages}
|
|
className="px-2.5 py-1.5 border border-slate-200 rounded-lg bg-white hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
|
>
|
|
{t('next', 'Next')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</EmployerLayout>
|
|
);
|
|
}
|