diff --git a/src/app/(dashboard)/jobs/[jobId]/components/job-actions-panel.tsx b/src/app/(dashboard)/jobs/[jobId]/components/job-actions-panel.tsx index a5744a6..61d8bba 100644 --- a/src/app/(dashboard)/jobs/[jobId]/components/job-actions-panel.tsx +++ b/src/app/(dashboard)/jobs/[jobId]/components/job-actions-panel.tsx @@ -1,7 +1,6 @@ "use client"; import { useActionState, useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import { ArrowRight, Check, @@ -87,17 +86,13 @@ export function JobActionsPanel({ } function AcceptButton({ jobId }: { jobId: string }) { - const router = useRouter(); const [state, action, pending] = useActionState(acceptJobAction, initialJobActionState); useEffect(() => { - if (state.ok) { - toast.success("İş işleme alındı, alt yapı üretimi başladı."); - router.refresh(); - } else if (state.error) { - toast.error(state.error); - } - }, [state, router]); + // Success path redirects from the server action, so state.ok never + // shows up here — we only need to surface errors. + if (state.error) toast.error(state.error); + }, [state]); return (
@@ -111,19 +106,12 @@ function AcceptButton({ jobId }: { jobId: string }) { } function HandToClinicButton({ job }: { job: Job }) { - const router = useRouter(); const [state, action, pending] = useActionState(handToClinicAction, initialJobActionState); const [open, setOpen] = useState(false); useEffect(() => { - if (state.ok) { - toast.success("Klinik tarafına gönderildi."); - setOpen(false); - router.refresh(); - } else if (state.error) { - toast.error(state.error); - } - }, [state, router]); + if (state.error) toast.error(state.error); + }, [state]); const isFinal = job.currentStep === "cila_bitim"; const stageLabel = @@ -180,19 +168,12 @@ function HandToClinicButton({ job }: { job: Job }) { } function ApproveAtClinicButton({ job }: { job: Job }) { - const router = useRouter(); const [state, action, pending] = useActionState(approveAtClinicAction, initialJobActionState); const [open, setOpen] = useState(false); useEffect(() => { - if (state.ok) { - toast.success("Prova onaylandı, lab tarafına gönderildi."); - setOpen(false); - router.refresh(); - } else if (state.error) { - toast.error(state.error); - } - }, [state, router]); + if (state.error) toast.error(state.error); + }, [state]); const stageLabel = job.currentStep === "alt_yapi_prova" ? "alt yapı" : "üst yapı"; @@ -242,7 +223,6 @@ function ApproveAtClinicButton({ job }: { job: Job }) { } function RequestRevisionButton({ job }: { job: Job }) { - const router = useRouter(); const [state, action, pending] = useActionState( requestRevisionAction, initialJobActionState, @@ -250,14 +230,8 @@ function RequestRevisionButton({ job }: { job: Job }) { 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]); + if (state.error) toast.error(state.error); + }, [state]); return ( @@ -304,17 +278,11 @@ function RequestRevisionButton({ job }: { job: Job }) { } function DeliverButton({ jobId }: { jobId: string }) { - const router = useRouter(); const [state, action, pending] = useActionState(markDeliveredAction, initialJobActionState); useEffect(() => { - if (state.ok) { - toast.success("İş teslim alındı."); - router.refresh(); - } else if (state.error) { - toast.error(state.error); - } - }, [state, router]); + if (state.error) toast.error(state.error); + }, [state]); return ( @@ -328,19 +296,12 @@ function DeliverButton({ jobId }: { jobId: string }) { } function CancelButton({ jobId }: { jobId: string }) { - const router = useRouter(); const [state, action, pending] = useActionState(cancelJobAction, initialJobActionState); const [open, setOpen] = useState(false); useEffect(() => { - if (state.ok) { - toast.success("İş iptal edildi."); - setOpen(false); - router.refresh(); - } else if (state.error) { - toast.error(state.error); - } - }, [state, router]); + if (state.error) toast.error(state.error); + }, [state]); return ( diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index a789e43..ffea413 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -1,5 +1,7 @@ +import { Suspense } from "react"; import { redirect } from "next/navigation"; +import { FlashToast } from "@/components/flash-toast"; import { getActiveContext } from "@/lib/appwrite/active-context"; import { countUnreadNotifications } from "@/lib/appwrite/notification-helpers"; import { getLogoUrl } from "@/lib/appwrite/storage"; @@ -40,6 +42,9 @@ export default async function DashboardLayout({ unreadCount={unreadCount} > {children} + + + ); } diff --git a/src/components/flash-toast.tsx b/src/components/flash-toast.tsx new file mode 100644 index 0000000..c54c05b --- /dev/null +++ b/src/components/flash-toast.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { toast } from "sonner"; + +const MESSAGES: Record = { + accepted: "İş işleme alındı, alt yapı üretimi başladı.", + handed: "Klinik tarafına gönderildi.", + approved: "Prova onaylandı, lab tarafına geri gönderildi.", + revision: "Düzeltme talebi gönderildi.", + delivered: "İş teslim alındı.", + cancelled: "İş iptal edildi.", +}; + +/** + * Show a one-shot toast based on ?flash=, then strip the param from + * the URL so a refresh doesn't replay it. Mounted in the dashboard layout + * so it works on every page that server actions might redirect to. + */ +export function FlashToast() { + const router = useRouter(); + const pathname = usePathname(); + const params = useSearchParams(); + const fired = useRef(null); + + useEffect(() => { + const flash = params.get("flash"); + if (!flash) return; + // Avoid double-firing under React Strict Mode in dev. + if (fired.current === flash) return; + fired.current = flash; + + const message = MESSAGES[flash] ?? null; + if (message) toast.success(message); + + const next = new URLSearchParams(params.toString()); + next.delete("flash"); + const query = next.toString(); + router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false }); + }, [params, pathname, router]); + + return null; +} diff --git a/src/lib/appwrite/job-actions.ts b/src/lib/appwrite/job-actions.ts index e19e9d6..141bc45 100644 --- a/src/lib/appwrite/job-actions.ts +++ b/src/lib/appwrite/job-actions.ts @@ -1,6 +1,7 @@ "use server"; import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; import { AppwriteException, ID, Permission, Query, Role } from "node-appwrite"; import { z } from "zod"; @@ -330,7 +331,9 @@ export async function acceptJobAction( revalidatePath(`/jobs/${jobId}`); revalidatePath("/jobs/inbound"); revalidatePath("/jobs/outbound"); - return { ok: true }; + // Redirect forces a full RSC payload reload — bypasses any client-side + // cache that router.refresh() might otherwise miss. + redirect(`/jobs/${jobId}?flash=accepted`); } /** @@ -498,7 +501,7 @@ export async function handToClinicAction( revalidatePath("/jobs/inbound"); revalidatePath("/jobs/outbound"); revalidatePath("/finance"); - return { ok: true }; + redirect(`/jobs/${jobId}?flash=handed`); } /** @@ -581,7 +584,7 @@ export async function approveAtClinicAction( revalidatePath(`/jobs/${jobId}`); revalidatePath("/jobs/inbound"); revalidatePath("/jobs/outbound"); - return { ok: true }; + redirect(`/jobs/${jobId}?flash=approved`); } /** @@ -670,7 +673,7 @@ export async function requestRevisionAction( revalidatePath(`/jobs/${jobId}`); revalidatePath("/jobs/inbound"); revalidatePath("/jobs/outbound"); - return { ok: true }; + redirect(`/jobs/${jobId}?flash=revision`); } export async function markDeliveredAction( @@ -727,7 +730,7 @@ export async function markDeliveredAction( revalidatePath("/jobs/outbound"); revalidatePath("/jobs/inbound"); revalidatePath("/finance"); - return { ok: true }; + redirect(`/jobs/${jobId}?flash=delivered`); } export async function cancelJobAction( @@ -777,5 +780,5 @@ export async function cancelJobAction( revalidatePath(`/jobs/${jobId}`); revalidatePath("/jobs/inbound"); revalidatePath("/jobs/outbound"); - return { ok: true }; + redirect(`/jobs/${jobId}?flash=cancelled`); }