Files
compras/resources/js/components/receipt-documents.tsx
T
2026-11-03 11:51:44 +02:00

179 lines
7.9 KiB
TypeScript

import { useForm } from '@inertiajs/react';
import { useRef } from 'react';
import { Panel, controlClass } from '@/components/procurement';
import { Button } from '@/components/ui/button';
import { date } from '@/types/procurement';
export type ReceiptDocument = {
id: number;
document_type: string;
original_name: string;
size: number;
sha256: string;
uploaded_by: number;
created_at: string;
};
export default function ReceiptDocuments({
receiptId,
ready,
canAdd,
documents,
}: {
receiptId: number;
ready: boolean;
canAdd: boolean;
documents: ReceiptDocument[];
}) {
const input = useRef<HTMLInputElement>(null);
const form = useForm<{
operation_key: string;
document_type: string;
file: File | null;
}>({
operation_key: crypto.randomUUID(),
document_type: 'delivery_note',
file: null,
});
const labels: Record<string, string> = {
delivery_note: 'Guia de remessa',
quality: 'Evidência de qualidade',
other: 'Outro documento',
};
return (
<Panel
title="Anexos da recepção"
description="Evidências ligadas à confirmação preservada. Anexar não altera quantidades, qualidade ou entradas de stock."
>
<div id="receipt-documents" />
{!ready ? (
<p className="text-sm">
Anexos ainda indisponíveis nesta instalação.
</p>
) : (
<>
{documents.length === 0 && (
<p className="text-sm text-muted-foreground">
Sem anexos nesta recepção.
</p>
)}
<ul className="space-y-3">
{documents.map((document) => (
<li
key={document.id}
className="rounded border p-3 text-sm"
>
<a
className="font-medium underline"
href={`/recepcoes/${receiptId}/documentos/${document.id}`}
>
{document.original_name}
</a>
<p>
{labels[document.document_type]} ·{' '}
{Math.ceil(document.size / 1024)} KB ·
Registado por #{document.uploaded_by} ·{' '}
{date(document.created_at)}
</p>
<details className="mt-2">
<summary className="cursor-pointer">
Identificação do ficheiro preservado
</summary>
<code className="break-all">
SHA-256 {document.sha256}
</code>
</details>
</li>
))}
</ul>
{canAdd && (
<details className="mt-4 rounded border p-3">
<summary className="cursor-pointer text-sm font-medium">
Acrescentar evidência
</summary>
<form
className="mt-3 grid gap-3"
onSubmit={(event) => {
event.preventDefault();
form.post(
`/recepcoes/${receiptId}/documentos`,
{
preserveScroll: true,
onSuccess: () => {
form.setData({
operation_key:
crypto.randomUUID(),
document_type:
'delivery_note',
file: null,
});
if (input.current)
input.current.value = '';
},
},
);
}}
>
<label className="grid gap-1 text-sm">
Tipo de documento
<select
className={controlClass}
value={form.data.document_type}
onChange={(event) =>
form.setData(
'document_type',
event.target.value,
)
}
>
{Object.entries(labels).map(
([value, label]) => (
<option
key={value}
value={value}
>
{label}
</option>
),
)}
</select>
</label>
<label className="grid gap-1 text-sm">
Ficheiro PDF, JPG ou PNG — até 2 MB
<input
ref={input}
type="file"
accept=".pdf,.jpg,.jpeg,.png"
required
onChange={(event) =>
form.setData(
'file',
event.target.files?.[0] ?? null,
)
}
/>
</label>
<p className="text-sm text-muted-foreground">
Ficheiros idênticos do mesmo tipo são
reutilizados. Os documentos anteriores
permanecem preservados.
</p>
{Object.entries(form.errors).map(
([key, error]) => (
<p role="alert" key={key}>
{error}
</p>
),
)}
<Button disabled={form.processing}>
Anexar à recepção
</Button>
</form>
</details>
)}
</>
)}
</Panel>
);
}