mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat: improve the invoices in the portal
This commit is contained in:
@@ -34,7 +34,6 @@ export default function MyPortalPage() {
|
||||
totalOutstanding,
|
||||
companyName,
|
||||
greeting,
|
||||
recentInvoices,
|
||||
dashboard,
|
||||
volumePoints,
|
||||
maxVolume,
|
||||
@@ -120,7 +119,7 @@ export default function MyPortalPage() {
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<InvoicesSection invoices={recentInvoices} />
|
||||
<InvoicesSection invoices={outstandingInvoices} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { formatCurrency } from "@/lib/currency";
|
||||
import type { PortalInvoice } from "@/services/invoices.service";
|
||||
import { Box, Group, Stack, Text } from "@mantine/core";
|
||||
import { Freight } from "@edr/types";
|
||||
import { CheckCircle2, ChevronRight, Clock3, Zap } from "lucide-react";
|
||||
import { ChevronRight, Clock3 } from "lucide-react";
|
||||
import { memo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { cv, INVOICE_BADGE } from "../constants";
|
||||
@@ -13,18 +13,22 @@ interface InvoicesSectionProps {
|
||||
invoices: PortalInvoice[];
|
||||
}
|
||||
|
||||
/** Max rows to list in the compact dashboard card. */
|
||||
const MAX_ROWS = 5;
|
||||
|
||||
const titleCase = (v: string) =>
|
||||
v ? v.charAt(0).toUpperCase() + v.slice(1).toLowerCase() : "";
|
||||
|
||||
export const InvoicesSection = memo(function InvoicesSection({
|
||||
invoices,
|
||||
}: InvoicesSectionProps) {
|
||||
const outstandingInvoices = invoices.filter(
|
||||
// Dashboard shows unpaid invoices only (pending + overdue).
|
||||
const pendingInvoices = invoices.filter(
|
||||
(inv) =>
|
||||
inv.status === Freight.InvoiceStatus.Pending ||
|
||||
inv.status === Freight.InvoiceStatus.Overdue,
|
||||
);
|
||||
const totalOutstanding = outstandingInvoices.reduce(
|
||||
const totalOutstanding = pendingInvoices.reduce(
|
||||
(sum, inv) => sum + Number(inv.totalAmount),
|
||||
0,
|
||||
);
|
||||
@@ -52,83 +56,74 @@ export const InvoicesSection = memo(function InvoicesSection({
|
||||
<Text fz={24} fw={800} mt={4} c="edr-text">
|
||||
{formatCurrency(totalOutstanding || 0, "ETB")}
|
||||
</Text>
|
||||
<Group justify="space-between" align="center" mt={8} wrap="nowrap">
|
||||
<Text fz={12} c="edr-amber-text">
|
||||
{outstandingInvoices.length} invoices unpaid
|
||||
</Text>
|
||||
<Group
|
||||
gap={5}
|
||||
align="center"
|
||||
px={14}
|
||||
py={8}
|
||||
bg="edr-accent"
|
||||
className="cursor-pointer rounded-[9px]"
|
||||
>
|
||||
<Zap size={15} color="#fff" />
|
||||
<Text fz={13} fw={700} c="white">
|
||||
Pay all
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Text fz={12} mt={8} c="edr-amber-text">
|
||||
{pendingInvoices.length} invoices unpaid
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{invoices.length === 0 ? (
|
||||
<EmptyState message="No invoices yet." />
|
||||
{pendingInvoices.length === 0 ? (
|
||||
<EmptyState message="No pending invoices." />
|
||||
) : (
|
||||
<Stack gap={0}>
|
||||
{invoices.map((invoice, i) => {
|
||||
{pendingInvoices.slice(0, MAX_ROWS).map((invoice, i) => {
|
||||
const badge = INVOICE_BADGE[invoice.status];
|
||||
const isPaid = invoice.status === Freight.InvoiceStatus.Paid;
|
||||
const isOverdue = invoice.status === Freight.InvoiceStatus.Overdue;
|
||||
const dueText = isPaid
|
||||
? "Paid"
|
||||
: isOverdue
|
||||
? "Overdue"
|
||||
: `Due ${new Date(invoice.dueAt).toLocaleDateString()}`;
|
||||
const DueIcon = isPaid ? CheckCircle2 : Clock3;
|
||||
const dueIconColor = isPaid ? cv("edr-green.5") : cv("edr-muted");
|
||||
const dueText = isOverdue
|
||||
? "Overdue"
|
||||
: `Due ${new Date(invoice.dueAt).toLocaleDateString()}`;
|
||||
const dueIconColor = isOverdue ? cv("edr-red") : cv("edr-muted");
|
||||
|
||||
return (
|
||||
<Box key={invoice.id}>
|
||||
{i > 0 && <Box h={1} bg="edr-divider" />}
|
||||
<Stack gap={8} py={10}>
|
||||
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
||||
<Box>
|
||||
<Text fz={13} fw={700} c="edr-text">
|
||||
{invoice.invoiceNumber}
|
||||
</Text>
|
||||
<Text fz={11} c="edr-muted">
|
||||
{titleCase(invoice.source)} · {titleCase(invoice.type)}
|
||||
</Text>
|
||||
</Box>
|
||||
<Text fz={14} fw={700} c="edr-text">
|
||||
{formatCurrency(
|
||||
Number(invoice.totalAmount),
|
||||
invoice.currency,
|
||||
)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Group gap={5} align="center">
|
||||
<DueIcon size={13} color={dueIconColor} />
|
||||
<Text fz={12} c="edr-muted">
|
||||
{dueText}
|
||||
</Text>
|
||||
</Group>
|
||||
{badge && (
|
||||
<Box
|
||||
bg={badge.bg}
|
||||
px={10}
|
||||
py={4}
|
||||
className="rounded-full"
|
||||
>
|
||||
<Text fz={11} fw={700} c={badge.text}>
|
||||
{badge.label}
|
||||
<Link
|
||||
to={`/billing/${invoice.id}`}
|
||||
className="no-underline"
|
||||
style={{ display: "block", color: "inherit" }}
|
||||
>
|
||||
<Stack
|
||||
gap={8}
|
||||
py={10}
|
||||
className="cursor-pointer rounded-[9px] transition-colors hover:bg-[var(--mantine-color-edr-soft-0)]"
|
||||
>
|
||||
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
||||
<Box>
|
||||
<Text fz={13} fw={700} c="edr-text">
|
||||
{invoice.invoiceNumber}
|
||||
</Text>
|
||||
<Text fz={11} c="edr-muted">
|
||||
{titleCase(invoice.source)} · {titleCase(invoice.type)}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
<Text fz={14} fw={700} c="edr-text">
|
||||
{formatCurrency(
|
||||
Number(invoice.totalAmount),
|
||||
invoice.currency,
|
||||
)}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Group gap={5} align="center">
|
||||
<Clock3 size={13} color={dueIconColor} />
|
||||
<Text fz={12} c="edr-muted">
|
||||
{dueText}
|
||||
</Text>
|
||||
</Group>
|
||||
{badge && (
|
||||
<Box
|
||||
bg={badge.bg}
|
||||
px={10}
|
||||
py={4}
|
||||
className="rounded-full"
|
||||
>
|
||||
<Text fz={11} fw={700} c={badge.text}>
|
||||
{badge.label}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Link>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user