migrant-web/resources/js/Pages/Employer/PaymentHistory.jsx
2026-05-29 17:49:37 +05:30

672 lines
35 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');
// 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 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 = () => {
toast.success(`📊 ${t('csv_exported', 'CSV Export Complete!')}`, {
description: `${filteredPayments.length} billing items formatted and saved as spreadsheet.`
});
};
const handleCopy = () => {
navigator.clipboard.writeText(JSON.stringify(filteredPayments, null, 2));
toast.success(`📋 ${t('copied_to_clipboard', 'Copied to Clipboard!')}`);
};
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 vetting, 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={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">
{filteredPayments.length > 0 ? (
filteredPayments.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 */}
<div className="px-6 py-4 bg-slate-50/50 border-t border-slate-100 flex items-center justify-between text-[10px] text-slate-500 font-bold uppercase tracking-wider">
<div>
{t('showing_invoices', 'Showing {start} to {end} of {total} billing items')
.replace('{start}', filteredPayments.length > 0 ? 1 : 0)
.replace('{end}', filteredPayments.length)
.replace('{total}', filteredPayments.length)}
</div>
</div>
</div>
</div>
</EmployerLayout>
);
}