"use client"; import { useRouter } from "next/navigation"; import { createContext, useContext, useEffect, useRef, useState, type ReactNode } from "react"; import { entwurfFreigeben, entwurfSperren } from "@/actions/hireDrafts"; import { useToast } from "@/components/ui/Toast"; import type { Entwurf } from "@/lib/entwuerfe"; import type { OpenPositionResolved } from "@/lib/positions"; import { HireWizard } from "./HireWizard"; type Location = { id: string; name: string; country: string }; type OpenWizardOptions = { draftId?: string; positionId?: string }; type HireWizardContextValue = { openWizard: (options?: OpenWizardOptions) => void; }; const HireWizardContext = createContext(null); /** * Wie oft die Sperre aufgefrischt wird, solange der Assistent offen ist. * * Deutlich kürzer als die Frist in der Datenbank (15 Minuten, * app_entwurf_sperrfrist): zwischen zwei Takten darf eine Antwort ausfallen, * ohne dass die Sperre wegläuft. Wer eine halbe Stunde an einem Entwurf * sitzt, soll ihn nicht auf halbem Weg an eine Kollegin verlieren. */ const TAKT_MS = 5 * 60 * 1000; export function HireWizardProvider({ children, openPositions, locations, drafts, }: { children: ReactNode; openPositions: OpenPositionResolved[]; locations: Location[]; drafts: Entwurf[]; }) { const { showToast } = useToast(); const router = useRouter(); const [open, setOpen] = useState(false); const [resumeDraft, setResumeDraft] = useState(null); const [initialPositionId, setInitialPositionId] = useState(undefined); // Forces HireWizard to remount fresh each time it's opened, so its // internal draft/step state is (re-)initialized directly from the current // resumeDraft/initialPositionId props at mount time — no reset-on-open // effect needed inside HireWizard itself. const [openKey, setOpenKey] = useState(0); // Die Kennung des Entwurfs, dessen Sperre wir halten. Als Ref, weil sie im // Aufräumen des Effekts gebraucht wird — über den Zustand gelesen wäre es // dort der Stand von vorhin. const gesperrt = useRef(null); async function openWizard(options?: OpenWizardOptions) { // Ohne Entwurf gibt es nichts zu sperren: eine neue Einstellung entsteht // erst beim Speichern. if (options?.draftId) { const result = await entwurfSperren(options.draftId); if (!result.success) { showToast(result.error ?? "Der Entwurf lässt sich gerade nicht öffnen.", "error"); // Die Liste holt sich den Stand: wer die Sperre hält, steht danach // an der Zeile, statt dass nur eine Meldung aufblitzt. router.refresh(); return; } gesperrt.current = options.draftId; } setResumeDraft(options?.draftId ? (drafts.find((d) => d.id === options.draftId) ?? null) : null); setInitialPositionId(options?.positionId); setOpen(true); setOpenKey((k) => k + 1); } function closeWizard() { setOpen(false); freigeben(); } function freigeben() { const id = gesperrt.current; if (!id) return; gesperrt.current = null; void entwurfFreigeben(id); } // Auffrischen, solange der Assistent offen ist. Ohne das liefe die Frist // einem langen Ausfüllen davon, und das Speichern am Ende fände seine // eigene Sperre abgelaufen. // // `!open` ist doppelt gemoppelt: freigeben() räumt beim Zumachen auch // gesperrt.current weg, und daran scheitert der Takt ohnehin. Es steht // trotzdem da, weil es die Bedingung ausspricht, um die es geht — dass // nach dem Zumachen nichts mehr aufgefrischt wird, hängt sonst an einer // Zuweisung drei Funktionen weiter oben. useEffect(() => { if (!open || !gesperrt.current) return; const timer = setInterval(() => { if (gesperrt.current) void entwurfSperren(gesperrt.current); }, TAKT_MS); return () => clearInterval(timer); }, [open, openKey]); // Beim Verlassen der Seite: dieselbe Freigabe wie beim Schliessen. Sie // erreicht den Server nicht immer — ein zugeklappter Laptop schickt nichts // mehr. Deshalb ist sie die Höflichkeit und nicht die Absicherung; die // Absicherung ist die Frist in der Datenbank. useEffect(() => () => freigeben(), []); return ( {children} ); } export function useHireWizard(): HireWizardContextValue { const ctx = useContext(HireWizardContext); if (!ctx) throw new Error("useHireWizard must be used within a HireWizardProvider"); return ctx; }