Files
clienthub/src/components/payment-status.tsx
T
simone 11870e15d3 feat: Client Portal redesign to Quiet Luxury + milestone stepper
- New MilestoneStepper primitive (horizontal per-phase progress)
- Token-migrate portal shell, phase cards, sidebar cards, kanban to dual-theme
- Soft-tint status pills (emerald/amber/muted) replacing solid badges
- embedded prop on ClientDashboard fixes double-header in multi-project tabs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:54:58 +02:00

107 lines
3.6 KiB
TypeScript

import type { ClientView } from '@/lib/client-view';
interface PaymentStatusProps {
accepted_total: string;
payments: ClientView['payments'];
/** Label for the total row. Defaults to "Totale Investimento" */
totalLabel?: string;
/** Override the displayed amount (retainer: show offer accepted_total, not project total) */
overrideAmount?: string;
/** When true, hide individual payment rows (Acconto/Saldo) — used for retainer offers */
hideRows?: boolean;
}
type PaymentStatusValue = 'da_saldare' | 'inviata' | 'saldato';
const statusConfig: Record<
PaymentStatusValue,
{ label: string; pillClass: string }
> = {
da_saldare: {
label: 'Da Saldare',
pillClass:
'bg-amber-50 text-amber-700 border-amber-100 dark:bg-amber-500/10 dark:text-amber-400 dark:border-amber-500/20',
},
inviata: {
label: 'Inviata',
pillClass:
'bg-blue-50 text-blue-700 border-blue-100 dark:bg-blue-500/10 dark:text-blue-400 dark:border-blue-500/20',
},
saldato: {
label: 'Saldato',
pillClass:
'bg-emerald-50 text-emerald-700 border-emerald-100 dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20',
},
};
export function PaymentStatus({
accepted_total,
payments,
totalLabel = 'Totale Investimento',
overrideAmount,
hideRows = false,
}: PaymentStatusProps) {
const displayAmount = overrideAmount ?? accepted_total;
const totalFormatted = parseFloat(displayAmount || '0').toLocaleString('it-IT', {
style: 'currency',
currency: 'EUR',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
return (
<div className="rounded-xl border border-border-light bg-card shadow-card overflow-hidden">
{/* Totale — unico importo visibile al cliente (LOCKED) */}
<div className="px-5 py-5 border-b border-border-light bg-muted">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1">
{totalLabel}
</p>
<p className="text-2xl font-bold text-foreground tracking-tight">
{totalFormatted}
</p>
</div>
{/* Righe pagamento: solo etichetta + stato, MAI importo singolo — omesse se hideRows */}
{!hideRows && (
<div className="px-5 py-4 space-y-2">
{payments.length === 0 ? (
<p className="text-sm text-muted-foreground italic py-2">
Nessun piano di pagamento configurato.
</p>
) : (
payments.map((payment) => {
const status = payment.status as PaymentStatusValue;
const config = statusConfig[status] ?? statusConfig.da_saldare;
return (
<div
key={payment.id}
className="flex items-center justify-between gap-4 rounded-lg border border-border-light p-3"
>
<p className="text-xs font-semibold text-foreground">
{payment.label}
</p>
{/* Pill stato — nessun importo */}
<span
className={`rounded border px-2 py-1 text-[10px] font-bold uppercase shrink-0 ${config.pillClass}`}
>
{config.label}
</span>
</div>
);
})
)}
</div>
)}
{/* Nota informativa */}
<div className="px-5 py-3 border-t border-border-light bg-muted">
<p className="text-[11px] text-muted-foreground italic">
Per dettagli o domande sui pagamenti, contattaci direttamente.
</p>
</div>
</div>
);
}