import { useCallback, useEffect, useRef, useState } from "react"; import apiFetch from "../utils/api"; import { useAlert } from "../context/AlertContext"; /** * Shared "Zobrazit PDF" handlers for the document pages (offers, issued * orders) — both call their GET `…/:id/file` endpoint (archived NAS PDF with * live-render fallback). Hardened flow extracted from OfferDetail: * - pre-opens the tab synchronously (popup blockers only allow it inside the * click's call stack), then streams the blob URL into it; * - double-click guard via a ref (state updates are async, a ref is not); * - 401 → silently close the tab (apiFetch already flagged the dead session); * - blob URLs are revoked after 60 s; pending revoke timeouts are cleared on * unmount so they can't fire into a dead page. */ /** * List variant — ONE hook instance serves every row. `openPdf(id, url)` * fetches that row's PDF; `pdfLoadingId` reports which row is in flight (use * it for the per-row spinner + disabled state). */ export function useDocumentListPdf() { const alert = useAlert(); const [pdfLoadingId, setPdfLoadingId] = useState(null); const loadingRef = useRef(false); const blobTimeoutsRef = useRef[]>([]); useEffect(() => { // The array object is stable (we only push to it, never reassign), so // capturing it here keeps the cleanup in sync with the ref's content. const timeouts = blobTimeoutsRef.current; return () => { timeouts.forEach(clearTimeout); }; }, []); const openPdf = useCallback( async (id: number, url: string) => { if (loadingRef.current) return; const newWindow = window.open("", "_blank"); loadingRef.current = true; setPdfLoadingId(id); try { const response = await apiFetch(url); if (response.status === 401) { newWindow?.close(); return; } if (!response.ok) { newWindow?.close(); alert.error("Nepodařilo se vygenerovat PDF"); return; } const blob = await response.blob(); const blobUrl = URL.createObjectURL(blob); if (newWindow) newWindow.location.href = blobUrl; const timeoutId = setTimeout(() => URL.revokeObjectURL(blobUrl), 60000); blobTimeoutsRef.current.push(timeoutId); } catch { newWindow?.close(); alert.error("Chyba připojení"); } finally { loadingRef.current = false; setPdfLoadingId(null); } }, [alert], ); return { openPdf, pdfLoadingId }; } /** * Detail variant — a single fixed URL, `openPdf()` takes no arguments. * Implemented on top of the list variant so the hardened flow lives once. */ export function useDocumentPdf(url: string | null) { const { openPdf: openListPdf, pdfLoadingId } = useDocumentListPdf(); const openPdf = useCallback(async () => { if (!url) return; await openListPdf(0, url); }, [url, openListPdf]); return { openPdf, pdfLoading: pdfLoadingId !== null }; }