feat: complete approval workflow

This commit is contained in:
2026-08-04 16:51:51 +01:00
parent c2136bc250
commit 809eab9d4a
26 changed files with 1156 additions and 41 deletions

View File

@@ -1,5 +1,5 @@
import { Head, Link, useForm } from '@inertiajs/react';
import { Ban, CalendarDays, Plus } from 'lucide-react';
import { Ban, CalendarDays, MessageSquareText, Plus } from 'lucide-react';
import { useState } from 'react';
import InputError from '@/components/input-error';
import { PageHeading } from '@/components/page-heading';
@@ -22,7 +22,11 @@ export default function ReservationsIndex({
reservations: Paginated<Reservation>;
}) {
const [selected, setSelected] = useState<Reservation | null>(null);
const [clarification, setClarification] = useState<Reservation | null>(
null,
);
const cancelForm = useForm({ reason: '' });
const clarificationForm = useForm({ response: '' });
const format = new Intl.DateTimeFormat('pt-PT', {
dateStyle: 'medium',
timeStyle: 'short',
@@ -54,6 +58,25 @@ export default function ReservationsIndex({
});
}
function openClarification(reservation: Reservation) {
clarificationForm.reset();
clarificationForm.clearErrors();
setClarification(reservation);
}
function submitClarification(event: React.FormEvent) {
event.preventDefault();
if (!clarification) {
return;
}
clarificationForm.post(`/requisicoes/${clarification.uuid}/responder`, {
preserveScroll: true,
onSuccess: () => setClarification(null),
});
}
return (
<>
<Head title="As minhas requisições" />
@@ -87,7 +110,20 @@ export default function ReservationsIndex({
<span className="font-mono text-sm font-medium">
{reservation.reference}
</span>
<div>
<div className="flex flex-col items-start gap-1">
{reservation.status ===
'awaiting_information' && (
<Button
type="button"
variant="secondary"
size="sm"
onClick={() =>
openClarification(reservation)
}
>
<MessageSquareText /> Responder
</Button>
)}
<p className="text-sm font-medium">
{reservation.items
.map((item) => item.asset.name)
@@ -232,6 +268,61 @@ export default function ReservationsIndex({
</form>
</DialogContent>
</Dialog>
<Dialog
open={clarification !== null}
onOpenChange={(open) => !open && setClarification(null)}
>
<DialogContent>
<form onSubmit={submitClarification}>
<DialogHeader>
<DialogTitle>
Prestar esclarecimentos
</DialogTitle>
<DialogDescription>
{clarification?.decision_reason ??
'O aprovador pediu informação adicional para poder decidir.'}
</DialogDescription>
</DialogHeader>
<div className="my-5">
<Label htmlFor="clarification-response">
Resposta
</Label>
<textarea
id="clarification-response"
value={clarificationForm.data.response}
onChange={(event) =>
clarificationForm.setData(
'response',
event.target.value,
)
}
rows={5}
className="mt-2 w-full rounded-md border bg-transparent px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
autoFocus
/>
<InputError
message={clarificationForm.errors.response}
/>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setClarification(null)}
>
Voltar
</Button>
<Button
type="submit"
disabled={clarificationForm.processing}
>
<MessageSquareText /> Enviar resposta
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</main>
</>
);