feat(jobs): clinic-side 'Düzeltme İste' (revision request) flow

Up to now the only thing a clinic could do on a prova was approve it.
If the casting didn't fit there was no way to bounce the case back to
the lab short of cancelling the whole thing. Real-world flow needs a
'try again, this is what's wrong' lever, so:

  - requestRevisionAction (clinic only): pre-conditions
    in_progress + at_clinic + currentStep set; flips location → at_lab
    while leaving currentStep untouched so the same prova stage repeats
    after the lab redoes the work. Requires a note (the lab can't fix
    what it doesn't know is broken) — appended to job_status_history
    with a '[Düzeltme talebi]' prefix and surfaced to the lab via
    notification.
  - JobActionsPanel: when the clinic side sees a prova (in_progress +
    at_clinic) it now shows two buttons — Onayla as before, plus
    Düzeltme İste (variant=destructive). The dialog requires a note
    before submit.
This commit is contained in:
kovakmedya
2026-05-22 16:03:36 +03:00
parent d7d2ac557b
commit 53e443b4f1
2 changed files with 157 additions and 1 deletions
@@ -9,6 +9,7 @@ import {
Loader2,
PackageCheck,
Play,
RotateCcw,
Send,
X,
} from "lucide-react";
@@ -32,6 +33,7 @@ import {
cancelJobAction,
handToClinicAction,
markDeliveredAction,
requestRevisionAction,
} from "@/lib/appwrite/job-actions";
import { initialJobActionState } from "@/lib/appwrite/job-types";
import type { Job, TenantKind } from "@/lib/appwrite/schema";
@@ -67,7 +69,10 @@ export function JobActionsPanel({
{/* Clinic finished the prova — approve and send back to lab */}
{isClinic && job.status === "in_progress" && isAtClinic && (
<ApproveAtClinicButton job={job} />
<>
<ApproveAtClinicButton job={job} />
<RequestRevisionButton job={job} />
</>
)}
{/* Final delivery — clinic took it from the lab */}
@@ -236,6 +241,68 @@ function ApproveAtClinicButton({ job }: { job: Job }) {
);
}
function RequestRevisionButton({ job }: { job: Job }) {
const router = useRouter();
const [state, action, pending] = useActionState(
requestRevisionAction,
initialJobActionState,
);
const [open, setOpen] = useState(false);
useEffect(() => {
if (state.ok) {
toast.success("Düzeltme talebi gönderildi.");
setOpen(false);
router.refresh();
} else if (state.error) {
toast.error(state.error);
}
}, [state, router]);
return (
<Dialog open={open} onOpenChange={setOpen}>
<Button variant="outline" onClick={() => setOpen(true)}>
<RotateCcw className="size-4" />
Düzeltme İste
</Button>
<DialogContent>
<DialogHeader>
<DialogTitle>Provayı reddet, lab&apos;a geri gönder</DialogTitle>
<DialogDescription>
Bu aşamayı reddettiğinizde aynı adımda kalır ve laboratuvar
yeniden çalışır. Neyin düzeltilmesi gerektiğini lütfen yazın.
</DialogDescription>
</DialogHeader>
<form action={action} className="grid gap-3">
<input type="hidden" name="jobId" value={job.$id} />
<div className="grid gap-2">
<Label htmlFor="note">Düzeltme notu *</Label>
<Textarea
id="note"
name="note"
rows={4}
required
maxLength={1000}
placeholder="Örn. Distalde temas yok, oklüzyon yüksek geldi."
/>
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Vazgeç
</Button>
</DialogClose>
<Button type="submit" variant="destructive" disabled={pending}>
{pending ? <Loader2 className="size-4 animate-spin" /> : <RotateCcw className="size-4" />}
Düzeltme İste
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}
function DeliverButton({ jobId }: { jobId: string }) {
const router = useRouter();
const [state, action, pending] = useActionState(markDeliveredAction, initialJobActionState);