mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
import { RiCheckboxCircleFill, RiExternalLinkLine } from "@remixicon/react";
|
|
import Link from "next/link";
|
|
import MoneyValues from "@/components/shared/money-values";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
HoverCard,
|
|
HoverCardContent,
|
|
HoverCardTrigger,
|
|
} from "@/components/ui/hover-card";
|
|
import type { DashboardInvoice } from "@/lib/dashboard/invoices";
|
|
import {
|
|
buildInvoiceDetailsHref,
|
|
buildInvoiceInitials,
|
|
formatInvoicePaymentDate,
|
|
getInvoiceShareLabel,
|
|
parseInvoiceDueDate,
|
|
} from "@/lib/dashboard/invoices-helpers";
|
|
import { INVOICE_PAYMENT_STATUS } from "@/lib/faturas";
|
|
import { getAvatarSrc } from "@/lib/pagadores/utils";
|
|
import { isDateOnlyPast } from "@/lib/utils/date";
|
|
import { InvoiceLogo } from "./invoice-logo";
|
|
|
|
type InvoiceListItemProps = {
|
|
invoice: DashboardInvoice;
|
|
onPay: (invoiceId: string) => void;
|
|
};
|
|
|
|
export function InvoiceListItem({ invoice, onPay }: InvoiceListItemProps) {
|
|
const dueInfo = parseInvoiceDueDate(invoice.period, invoice.dueDay);
|
|
const isPaid = invoice.paymentStatus === INVOICE_PAYMENT_STATUS.PAID;
|
|
const isOverdue =
|
|
!isPaid && dueInfo.date !== null && isDateOnlyPast(dueInfo.date);
|
|
const paymentInfo = formatInvoicePaymentDate(invoice.paidAt);
|
|
const breakdown = invoice.pagadorBreakdown ?? [];
|
|
const hasBreakdown = breakdown.length > 0;
|
|
const detailHref = buildInvoiceDetailsHref(invoice.cardId, invoice.period);
|
|
|
|
const linkNode = (
|
|
<Link
|
|
prefetch
|
|
href={detailHref}
|
|
className="inline-flex max-w-full items-center gap-1 text-sm font-medium text-foreground underline-offset-2 hover:text-primary hover:underline"
|
|
>
|
|
<span className="truncate">{invoice.cardName}</span>
|
|
<RiExternalLinkLine
|
|
className="size-3 shrink-0 text-muted-foreground"
|
|
aria-hidden
|
|
/>
|
|
</Link>
|
|
);
|
|
|
|
return (
|
|
<li className="flex items-center justify-between border-b border-dashed last:border-b-0 last:pb-0">
|
|
<div className="flex min-w-0 flex-1 items-center gap-2 py-2">
|
|
<InvoiceLogo
|
|
cardName={invoice.cardName}
|
|
logo={invoice.logo}
|
|
size={36}
|
|
containerClassName="size-9.5"
|
|
/>
|
|
|
|
<div className="min-w-0">
|
|
{hasBreakdown ? (
|
|
<HoverCard openDelay={150}>
|
|
<HoverCardTrigger asChild>{linkNode}</HoverCardTrigger>
|
|
<HoverCardContent align="start" className="w-72 space-y-3">
|
|
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
Distribuição por pagador
|
|
</p>
|
|
<ul className="space-y-2">
|
|
{breakdown.map((share, index) => (
|
|
<li
|
|
key={`${invoice.id}-${
|
|
share.pagadorId ?? share.pagadorName ?? index
|
|
}`}
|
|
className="flex items-center gap-3"
|
|
>
|
|
<Avatar className="size-9">
|
|
<AvatarImage
|
|
src={getAvatarSrc(share.pagadorAvatar)}
|
|
alt={`Avatar de ${share.pagadorName}`}
|
|
/>
|
|
<AvatarFallback>
|
|
{buildInvoiceInitials(share.pagadorName)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-medium text-foreground">
|
|
{share.pagadorName}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{getInvoiceShareLabel(
|
|
share.amount,
|
|
Math.abs(invoice.totalAmount),
|
|
)}
|
|
</p>
|
|
</div>
|
|
<div className="text-sm font-semibold text-foreground">
|
|
<MoneyValues amount={share.amount} />
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
) : (
|
|
linkNode
|
|
)}
|
|
|
|
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
{!isPaid ? <span>{dueInfo.label}</span> : null}
|
|
{isPaid && paymentInfo ? (
|
|
<span className="text-success">{paymentInfo.label}</span>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex shrink-0 flex-col items-end">
|
|
<MoneyValues amount={Math.abs(invoice.totalAmount)} />
|
|
<Button
|
|
type="button"
|
|
size="sm"
|
|
variant="link"
|
|
className="h-auto p-0 disabled:opacity-100"
|
|
disabled={isPaid}
|
|
onClick={() => onPay(invoice.id)}
|
|
>
|
|
{isPaid ? (
|
|
<span className="flex items-center gap-1 text-success">
|
|
<RiCheckboxCircleFill className="size-3" /> Pago
|
|
</span>
|
|
) : isOverdue ? (
|
|
<span className="overdue-blink">
|
|
<span className="overdue-blink-primary text-destructive">
|
|
Atrasado
|
|
</span>
|
|
<span className="overdue-blink-secondary">Pagar</span>
|
|
</span>
|
|
) : (
|
|
<span>Pagar</span>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|