Files
compras/resources/js/components/procurement.tsx
T
nala c6373365bf
tests / ci (push) Canceled after 0s
commit9
2026-10-30 20:02:30 +02:00

167 lines
6.2 KiB
TypeScript

import { Link } from '@inertiajs/react';
import { ArrowUpRight, ClipboardList } from 'lucide-react';
import type { ReactNode } from 'react';
import { Badge } from '@/components/ui/badge';
import { date, money, statusLabels } from '@/types/procurement';
import type { Requisition } from '@/types/procurement';
export const controlClass =
'h-10 w-full rounded-lg border border-input bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring';
export function PageTitle({
eyebrow,
title,
description,
action,
}: {
eyebrow: string;
title: string;
description: string;
action?: ReactNode;
}) {
return (
<div className="flex flex-wrap items-end justify-between gap-4">
<div>
<p className="mb-2 text-xs font-semibold tracking-widest text-teal-700 uppercase dark:text-teal-400">
{eyebrow}
</p>
<h1 className="text-3xl font-semibold tracking-tight">
{title}
</h1>
<p className="mt-2 max-w-2xl text-sm text-muted-foreground">
{description}
</p>
</div>
{action}
</div>
);
}
export function Panel({
id,
title,
description,
children,
}: {
id?: string;
title: string;
description?: string;
children: ReactNode;
}) {
return (
<section
id={id}
className="scroll-mt-24 rounded-xl border bg-card p-5 shadow-xs"
>
<h2 className="font-semibold">{title}</h2>
{description && (
<p className="mt-1 text-sm text-muted-foreground">
{description}
</p>
)}
<div className="mt-5">{children}</div>
</section>
);
}
export function Status({ value }: { value: string }) {
return (
<Badge
variant="outline"
className={
value === 'approved'
? 'border-teal-200 bg-teal-50 text-teal-800 dark:bg-teal-950 dark:text-teal-200'
: value === 'pending'
? 'border-amber-200 bg-amber-50 text-amber-800 dark:bg-amber-950 dark:text-amber-200'
: value === 'rejected'
? 'border-rose-200 bg-rose-50 text-rose-800 dark:bg-rose-950 dark:text-rose-200'
: ''
}
>
{statusLabels[value] ?? value}
</Badge>
);
}
export function RequisitionTable({ rows }: { rows: Requisition[] }) {
if (!rows.length)
return (
<div className="py-12 text-center">
<ClipboardList className="mx-auto mb-3 size-9 text-muted-foreground/50" />
<p className="font-medium">Sem requisições para apresentar</p>
<p className="mt-1 text-sm text-muted-foreground">
Os pedidos de compra aparecem aqui depois de criados.
</p>
</div>
);
return (
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead className="border-b text-xs text-muted-foreground">
<tr>
<th className="pb-3 font-medium">Requisição</th>
<th className="pb-3 font-medium">Departamento</th>
<th className="pb-3 font-medium">Data</th>
<th className="pb-3 font-medium">Estado</th>
<th className="pb-3 text-right font-medium">
Total com IVA
</th>
<th>
<span className="sr-only">Abrir</span>
</th>
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.id} className="border-b last:border-0">
<td className="py-4 pr-4">
<Link
className="font-semibold hover:text-teal-700"
href={`/requisicoes/${r.id}`}
>
{r.number}
</Link>
<div className="mt-1 text-xs text-muted-foreground">
{r.requester?.name}
</div>
</td>
<td className="pr-4">
{r.department?.name ?? '—'}
</td>
<td className="pr-4 whitespace-nowrap">
{date(r.created_at)}
</td>
<td className="pr-4">
<Status value={r.status} />
</td>
<td className="text-right font-medium whitespace-nowrap">
{money(Number(r.total_minor))}
</td>
<td className="pl-3">
<Link
href={`/requisicoes/${r.id}`}
aria-label={`Abrir ${r.number}`}
>
<ArrowUpRight className="size-4" />
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export function FormErrors({ errors }: { errors: Record<string, string> }) {
if (!Object.keys(errors).length) return null;
return (
<div
role="alert"
className="rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive"
>
<p className="mb-1 font-medium">Verifique os dados:</p>
<ul className="list-inside list-disc">
{Object.entries(errors).map(([key, value]) => (
<li key={key}>{value}</li>
))}
</ul>
</div>
);
}