@flowselections/layout-maker 1.45.9 → 1.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +139 -139
  2. package/dist-lib/components/AfdrukKnop.d.ts +19 -19
  3. package/dist-lib/components/AfdrukKnop.js +64 -64
  4. package/dist-lib/components/DocumentenPage.d.ts.map +1 -1
  5. package/dist-lib/components/DocumentenPage.js +2 -3
  6. package/dist-lib/components/NetwerkPrintersDialog.d.ts +7 -7
  7. package/dist-lib/components/NetwerkPrintersDialog.js +79 -79
  8. package/dist-lib/components/PakbonPreview.d.ts.map +1 -1
  9. package/dist-lib/components/PakbonPreview.js +2 -2
  10. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  11. package/dist-lib/components/PrintQueuePage.js +45 -74
  12. package/dist-lib/components/PrintenPage.d.ts.map +1 -1
  13. package/dist-lib/components/PrintenPage.js +2 -3
  14. package/dist-lib/components/PrinterSetupWizard.d.ts.map +1 -1
  15. package/dist-lib/components/PrinterSetupWizard.js +5 -28
  16. package/dist-lib/components/PrintersRedirectPage.d.ts +1 -1
  17. package/dist-lib/components/PrintersRedirectPage.js +14 -14
  18. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  19. package/dist-lib/components/PrintplekDetailPage.js +3 -25
  20. package/dist-lib/components/PrintplekWizard.d.ts.map +1 -1
  21. package/dist-lib/components/PrintplekWizard.js +3 -85
  22. package/dist-lib/index.d.ts +1 -8
  23. package/dist-lib/index.d.ts.map +1 -1
  24. package/dist-lib/index.js +1 -10
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts +5386 -289
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  27. package/dist-lib/integrations/supabase/client.server.d.ts +5386 -289
  28. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  29. package/dist-lib/integrations/supabase/client.server.js +1 -1
  30. package/dist-lib/integrations/supabase/moduleClient.d.ts +5386 -289
  31. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/types.d.ts +5404 -243
  33. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  34. package/dist-lib/lib/aiLayout.functions.d.ts +5386 -289
  35. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  36. package/dist-lib/lib/aiLayout.server.js +9 -9
  37. package/dist-lib/lib/browserPrint.js +1 -1
  38. package/dist-lib/lib/fieldRegistry.js +1 -0
  39. package/dist-lib/lib/layoutPreview.js +7 -7
  40. package/dist-lib/lib/logoLibrary.d.ts +2 -1
  41. package/dist-lib/lib/logoLibrary.d.ts.map +1 -1
  42. package/dist-lib/lib/logoLibrary.js +42 -2
  43. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  44. package/dist-lib/lib/orderData.server.js +13 -0
  45. package/dist-lib/lib/pakbonBuilder.d.ts +26 -0
  46. package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
  47. package/dist-lib/lib/pakbonBuilder.js +37 -0
  48. package/dist-lib/lib/pakbonContent.d.ts.map +1 -1
  49. package/dist-lib/lib/pakbonContent.js +2 -2
  50. package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
  51. package/dist-lib/lib/pakbonHtml.js +60 -60
  52. package/dist-lib/lib/pakbonPresentation.d.ts.map +1 -1
  53. package/dist-lib/lib/pakbonPresentation.js +2 -4
  54. package/dist-lib/lib/printApi.d.ts +0 -2
  55. package/dist-lib/lib/printApi.d.ts.map +1 -1
  56. package/dist-lib/lib/printApi.js +0 -21
  57. package/dist-lib/lib/printQueue.server.d.ts +8 -0
  58. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  59. package/dist-lib/lib/printQueue.server.js +162 -66
  60. package/dist-lib/lib/printService.functions.d.ts +51639 -52
  61. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  62. package/dist-lib/lib/printService.functions.js +69 -0
  63. package/dist-lib/lib/printService.server.d.ts +2 -0
  64. package/dist-lib/lib/printService.server.d.ts.map +1 -1
  65. package/dist-lib/lib/printService.server.js +10 -0
  66. package/dist-lib/lib/productiebonHtml.js +15 -15
  67. package/dist-lib/lib/profielHtml.d.ts.map +1 -1
  68. package/dist-lib/lib/profielHtml.js +22 -19
  69. package/dist-lib/lib/supabaseAdmin.server.d.ts +5 -5
  70. package/dist-lib/lib/supabaseAdmin.server.js +43 -43
  71. package/dist-lib/lib/testpagina.js +3 -3
  72. package/dist-lib/lib/testprint.js +4 -4
  73. package/dist-lib/lib/voorbeeldPrint.d.ts.map +1 -1
  74. package/dist-lib/lib/voorbeeldPrint.js +7 -39
  75. package/package.json +3 -3
  76. package/dist-lib/components/PdfMapAutoSaver.d.ts +0 -6
  77. package/dist-lib/components/PdfMapAutoSaver.d.ts.map +0 -1
  78. package/dist-lib/components/PdfMapAutoSaver.js +0 -12
  79. package/dist-lib/components/PdfMapGlobaleBanner.d.ts +0 -12
  80. package/dist-lib/components/PdfMapGlobaleBanner.d.ts.map +0 -1
  81. package/dist-lib/components/PdfMapGlobaleBanner.js +0 -30
  82. package/dist-lib/components/PdfMapToestemmingBanner.d.ts +0 -8
  83. package/dist-lib/components/PdfMapToestemmingBanner.d.ts.map +0 -1
  84. package/dist-lib/components/PdfMapToestemmingBanner.js +0 -245
  85. package/dist-lib/lib/pdfJobSaver.d.ts +0 -24
  86. package/dist-lib/lib/pdfJobSaver.d.ts.map +0 -1
  87. package/dist-lib/lib/pdfJobSaver.js +0 -376
  88. package/dist-lib/lib/pdfMap.d.ts +0 -83
  89. package/dist-lib/lib/pdfMap.d.ts.map +0 -1
  90. package/dist-lib/lib/pdfMap.js +0 -332
  91. package/dist-lib/lib/pdfOpslagStatus.d.ts +0 -23
  92. package/dist-lib/lib/pdfOpslagStatus.d.ts.map +0 -1
  93. package/dist-lib/lib/pdfOpslagStatus.js +0 -74
  94. package/dist-lib/lib/pdfPrinter.d.ts +0 -9
  95. package/dist-lib/lib/pdfPrinter.d.ts.map +0 -1
  96. package/dist-lib/lib/pdfPrinter.js +0 -34
@@ -1,245 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // ============================================================================
3
- // src/components/PdfMapToestemmingBanner.tsx — permanente, zichtbare controle om
4
- // de maptoestemming voor de virtuele "Opslaan als PDF"-printer te beheren.
5
- // Getoond op de Printplek-detailpagina en de Printwachtrij, zodat een gebruiker
6
- // altijd proactief toestemming kan (her)geven zonder op een toast te wachten.
7
- // ============================================================================
8
- import { useCallback, useEffect, useState } from "react";
9
- import { Button, toast } from "@flowselections/core";
10
- import { FolderCheck, FolderOpen, ShieldAlert, ShieldCheck } from "lucide-react";
11
- import { usePrinters } from "../lib/useDocumentData.js";
12
- import { zorgVoorPdfPrinter } from "../lib/pdfPrinter.js";
13
- import { supabase } from "../lib/supabaseClient.js";
14
- import { draaitInIframe, herstelPdfMapToestemming, kiesPdfMap, mapKiezenMogelijk, openInEigenTabblad, lokalePdfPrintplek, pdfMapKoppeling, pdfMapNaam, pdfMapToestemming, } from "../lib/pdfMap.js";
15
- export function PdfMapToestemmingBanner({ alleenActieNodig = false, zwevend = false, printplekId, } = {}) {
16
- const { printers, reload } = usePrinters();
17
- const pdfPrinter = printers.find((p) => p.afdrukmethode === "pdf");
18
- // De balk is alleen nuttig als "Opslaan als PDF" nog ergens als
19
- // afdrukmethode is ingesteld (printplek-regel, layoutkoppeling of
20
- // formaatregel). Zodra nergens meer een PDF-printer is gekozen, verdwijnt
21
- // de balk en wordt de virtuele printer ook niet meer vanzelf aangemaakt.
22
- const [inGebruik, setInGebruik] = useState(null);
23
- const meetGebruik = useCallback(async (printerId) => {
24
- const tabellen = [
25
- supabase.from("document_printplek_printers").select("print_plek_id").eq("printer_id", printerId).limit(1),
26
- supabase.from("printer_mappings").select("layout_id").eq("printer_id", printerId).limit(1),
27
- supabase.from("document_formaat_printers").select("documenttype").eq("printer_id", printerId).limit(1),
28
- ];
29
- try {
30
- const resultaten = await Promise.all(tabellen);
31
- return resultaten.some((r) => Array.isArray(r.data) && r.data.length > 0);
32
- }
33
- catch {
34
- // Bij een leesfout liever tonen dan stil verbergen.
35
- return true;
36
- }
37
- }, []);
38
- useEffect(() => {
39
- if (!pdfPrinter) {
40
- setInGebruik(null);
41
- return;
42
- }
43
- let actief = true;
44
- void meetGebruik(pdfPrinter.id).then((gebruikt) => {
45
- if (actief)
46
- setInGebruik(gebruikt);
47
- });
48
- return () => {
49
- actief = false;
50
- };
51
- }, [pdfPrinter?.id, meetGebruik]); // eslint-disable-line react-hooks/exhaustive-deps
52
- const [naam, setNaam] = useState(null);
53
- const [toestand, setToestand] = useState("laden");
54
- const [gekoppeldePrintplek, setGekoppeldePrintplek] = useState(null);
55
- const [persistent, setPersistent] = useState(null);
56
- const [instellingOpen, setInstellingOpen] = useState(false);
57
- const [aangevraagdePrintplek, setAangevraagdePrintplek] = useState(null);
58
- useEffect(() => {
59
- const params = new URLSearchParams(window.location.search);
60
- const gevraagd = params.get("pdf-printplek")?.trim() || null;
61
- setInstellingOpen(params.get("pdf-map-instellen") === "1");
62
- setAangevraagdePrintplek(gevraagd);
63
- void lokalePdfPrintplek().then((lokaal) => setGekoppeldePrintplek(gevraagd ?? lokaal));
64
- }, []);
65
- // Maak de virtuele PDF-printer alleen aan wanneer expliciet om het instellen
66
- // van de map gevraagd wordt (?pdf-map-instellen=1). Zonder die vraag én
67
- // zonder bestaande PDF-printer is er niets om toestemming voor te geven.
68
- useEffect(() => {
69
- if (pdfPrinter || !instellingOpen)
70
- return;
71
- let actief = true;
72
- void (async () => {
73
- try {
74
- await zorgVoorPdfPrinter();
75
- if (actief)
76
- await reload(true);
77
- }
78
- catch {
79
- // RLS of netwerkfout — stil negeren, banner blijft verborgen.
80
- }
81
- })();
82
- return () => {
83
- actief = false;
84
- };
85
- }, [pdfPrinter, instellingOpen, reload]);
86
- const laad = useCallback(async () => {
87
- if (!pdfPrinter) {
88
- setNaam(null);
89
- setToestand("ontbreekt");
90
- return;
91
- }
92
- setNaam(await pdfMapNaam(pdfPrinter.id));
93
- setToestand(await pdfMapToestemming(pdfPrinter.id));
94
- const koppeling = await pdfMapKoppeling();
95
- let doelPrintplek = printplekId ?? aangevraagdePrintplek ?? koppeling?.printPlekId ?? null;
96
- if (!doelPrintplek) {
97
- const { data } = await supabase
98
- .from("document_printplek_printers")
99
- .select("print_plek_id")
100
- .eq("printer_id", pdfPrinter.id);
101
- const ids = [
102
- ...new Set((Array.isArray(data) ? data : [])
103
- .map((rij) => String(rij.print_plek_id ?? "").trim())
104
- .filter(Boolean)),
105
- ];
106
- doelPrintplek = ids.length === 1 ? ids[0] ?? null : null;
107
- }
108
- setGekoppeldePrintplek(doelPrintplek);
109
- setPersistent(koppeling?.persistent ?? null);
110
- // Opnieuw meten of PDF nog ergens is ingesteld: kiest de gebruiker bij
111
- // alle documenten een echte printer, dan verdwijnt de balk direct.
112
- setInGebruik(await meetGebruik(pdfPrinter.id));
113
- }, [aangevraagdePrintplek, meetGebruik, pdfPrinter, printplekId]);
114
- useEffect(() => {
115
- void laad();
116
- }, [laad]);
117
- // Herverfris na focus zodat een net verleende toestemming meteen zichtbaar is.
118
- useEffect(() => {
119
- const h = () => void laad();
120
- window.addEventListener("focus", h);
121
- document.addEventListener("visibilitychange", h);
122
- return () => {
123
- window.removeEventListener("focus", h);
124
- document.removeEventListener("visibilitychange", h);
125
- };
126
- }, [laad]);
127
- // In een iframe (o.a. de Lovable-preview) heeft de balk geen nut: de
128
- // mappenverkenner werkt daar niet en de gebruiker kan alleen via "Open in
129
- // eigen tabblad" verder. De balk daar niet tonen houdt de preview schoon;
130
- // in een eigen tabblad blijft hij volledig zichtbaar.
131
- const inIframe = draaitInIframe();
132
- if (inIframe)
133
- return null;
134
- // Is "Opslaan als PDF" nergens meer ingesteld (geen printplek-, layout- of
135
- // formaatregel met deze printer), dan is de balk overbodig en verbergen we
136
- // hem volledig. Alleen bij een expliciete instelvraag blijft hij zichtbaar.
137
- if (!instellingOpen) {
138
- if (!pdfPrinter)
139
- return null;
140
- if (inGebruik === false)
141
- return null;
142
- }
143
- // De balk is op dit punt alleen nog zichtbaar als "Opslaan als PDF"
144
- // daadwerkelijk ergens is ingesteld (of de gebruiker expliciet aan het
145
- // instellen is); anders is hierboven al `null` teruggegeven.
146
- const vereistePrintplek = printplekId ?? aangevraagdePrintplek ?? gekoppeldePrintplek;
147
- const inOrde = !!pdfPrinter &&
148
- toestand === "granted" &&
149
- !!naam &&
150
- !!gekoppeldePrintplek &&
151
- (!vereistePrintplek || gekoppeldePrintplek === vereistePrintplek);
152
- if (alleenActieNodig && !instellingOpen && (!pdfPrinter ? false : toestand === "laden" || inOrde))
153
- return null;
154
- const ondersteund = mapKiezenMogelijk();
155
- const geefToestemming = async () => {
156
- if (!pdfPrinter)
157
- return;
158
- const resultaat = await herstelPdfMapToestemming(pdfPrinter.id);
159
- if (resultaat === "granted") {
160
- toast.success("Maptoestemming verleend", {
161
- description: "Gereedstaande PDF's worden nu naar de map opgeslagen.",
162
- });
163
- await laad();
164
- }
165
- else if (resultaat === "ontbreekt") {
166
- // Nog geen map gekozen — open de verkenner.
167
- await kiesNieuweMap();
168
- }
169
- else {
170
- toast.error("Maptoestemming is niet verleend");
171
- await laad();
172
- }
173
- };
174
- const kiesNieuweMap = async () => {
175
- if (!pdfPrinter)
176
- return;
177
- if (inIframe) {
178
- toast.error("Map kiezen werkt niet in een iframe", {
179
- description: "Open de app in een eigen tabblad om de mappenverkenner te gebruiken.",
180
- action: {
181
- label: "Open in eigen tabblad",
182
- onClick: () => openInEigenTabblad(printplekId ?? gekoppeldePrintplek ?? undefined),
183
- },
184
- duration: 15000,
185
- });
186
- return;
187
- }
188
- const doelPrintplek = printplekId ?? gekoppeldePrintplek ?? undefined;
189
- const uitkomst = await kiesPdfMap(pdfPrinter.id, doelPrintplek);
190
- if (uitkomst.status === "gekozen") {
191
- toast.success("Map gekozen", { description: `PDF's worden opgeslagen in “${uitkomst.naam}”.` });
192
- await laad();
193
- }
194
- else if (uitkomst.status === "niet-ondersteund") {
195
- toast.error("Deze browser ondersteunt geen mapkeuze", {
196
- description: "Gebruik Chrome, Edge of Safari op desktop.",
197
- });
198
- }
199
- else if (uitkomst.status === "iframe-geblokkeerd") {
200
- toast.error("Map kiezen werkt niet in een iframe", {
201
- description: "Open de app in een eigen tabblad.",
202
- action: {
203
- label: "Open in eigen tabblad",
204
- onClick: () => openInEigenTabblad(printplekId ?? gekoppeldePrintplek ?? undefined),
205
- },
206
- duration: 15000,
207
- });
208
- }
209
- else if (uitkomst.status === "fout") {
210
- toast.error("Map kiezen is mislukt", { description: uitkomst.melding });
211
- }
212
- };
213
- const icoon = inOrde
214
- ? ShieldCheck
215
- : toestand === "denied"
216
- ? ShieldAlert
217
- : FolderOpen;
218
- const Icoon = icoon;
219
- if (!pdfPrinter) {
220
- return (_jsxs("div", { role: "status", "aria-live": "polite", className: `${zwevend ? "fixed inset-x-4 top-4 z-[100] mx-auto max-w-4xl shadow-lg" : ""} flex flex-wrap items-center justify-between gap-3 rounded-lg border border-amber-300/60 bg-amber-50 p-3 dark:border-amber-700/60 dark:bg-amber-950/30`, children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(ShieldAlert, { className: "h-4 w-4 text-amber-600 dark:text-amber-400" }), _jsx("span", { className: "font-medium", children: "PDF-maptoestemming" }), _jsx("span", { className: "text-muted-foreground", children: "De virtuele \u201COpslaan als PDF\u201D-printer wordt aangemaakt of kon nog niet worden geladen." })] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
221
- void (async () => {
222
- try {
223
- await zorgVoorPdfPrinter();
224
- await reload(true);
225
- }
226
- catch (fout) {
227
- toast.error("PDF-printer kon niet worden aangemaakt", {
228
- description: fout instanceof Error ? fout.message : undefined,
229
- });
230
- }
231
- })();
232
- }, children: [_jsx(FolderOpen, { className: "mr-2 h-4 w-4" }), " PDF-printer aanmaken"] })] }));
233
- }
234
- return (_jsxs("div", { role: "status", "aria-live": "polite", className: `${inOrde
235
- ? "border-emerald-300/60 bg-emerald-50 dark:border-emerald-700/60 dark:bg-emerald-950/30"
236
- : "border-amber-300/60 bg-amber-50 dark:border-amber-700/60 dark:bg-amber-950/30"} ${zwevend
237
- ? "fixed inset-x-4 top-4 z-[100] mx-auto max-w-4xl shadow-lg"
238
- : ""} flex flex-wrap items-center justify-between gap-3 rounded-lg border p-3`, children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Icoon, { className: inOrde
239
- ? "h-4 w-4 text-emerald-600 dark:text-emerald-400"
240
- : "h-4 w-4 text-amber-600 dark:text-amber-400" }), _jsx("span", { className: "font-medium", children: "PDF-maptoestemming" }), _jsxs("span", { className: "text-muted-foreground", children: [naam
241
- ? `Map “${naam}” — toestemming ${toestand === "granted" ? "verleend" : "vereist vernieuwen"}`
242
- : "Nog geen map gekozen op deze computer", !ondersteund ? " (browser ondersteunt geen mapkeuze)" : "", inIframe
243
- ? " — de preview draait in een iframe; de mapkeuze geldt per webadres en moet hier opnieuw worden gezet"
244
- : "", !inIframe && gekoppeldePrintplek ? " — gekoppeld aan deze printplek" : "", !inIframe && persistent === false ? " — browseropslag is niet permanent gegarandeerd" : ""] })] }), _jsxs("div", { className: "flex gap-2", children: [naam ? (_jsxs(Button, { size: "sm", variant: inOrde ? "outline" : "default", onClick: () => void geefToestemming(), children: [_jsx(FolderCheck, { className: "mr-2 h-4 w-4" }), inOrde ? "Toestemming controleren" : "Maptoestemming geven"] })) : null, _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void kiesNieuweMap(), children: [_jsx(FolderOpen, { className: "mr-2 h-4 w-4" }), " ", naam ? "Andere map kiezen" : "Map kiezen"] }), inIframe ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => openInEigenTabblad(printplekId ?? gekoppeldePrintplek ?? undefined), children: "Open in eigen tabblad" })) : null] })] }));
245
- }
@@ -1,24 +0,0 @@
1
- import { supabase } from "./supabaseClient.js";
2
- type PdfServiceState = {
3
- gestart: boolean;
4
- bezig: boolean;
5
- interval: number | null;
6
- gemeldePrinters: Set<string>;
7
- writerId: string;
8
- kanaal?: ReturnType<typeof supabase.channel> | null;
9
- };
10
- declare global {
11
- interface Window {
12
- __layoutMakerPdfService?: PdfServiceState;
13
- }
14
- }
15
- /** Herstelt uitsluitend de lokale aflevering van een reeds gerenderde PDF. */
16
- export declare function bewaarPdfJobOpnieuw(jobId: string): Promise<boolean>;
17
- /** Downloadt exact het opgeslagen artefact; legacy jobs vallen terug op base64. */
18
- export declare function downloadPdfJob(jobId: string, documentnaam: string): Promise<void>;
19
- export declare function controleerPdfMap(printerId: string): Promise<"ok" | "map_ontbreekt" | "geen_rechten">;
20
- export declare function startPdfMapService(): void;
21
- /** Wacht alleen op het eindresultaat; de singleton met databaseclaim schrijft. */
22
- export declare function bewaarPdfJobZodraKlaar(jobId: string, _printerId: string): Promise<boolean>;
23
- export {};
24
- //# sourceMappingURL=pdfJobSaver.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pdfJobSaver.d.ts","sourceRoot":"","sources":["../../src/lib/pdfJobSaver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAgC5C,KAAK,eAAe,GAAG;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CACrD,CAAC;AAEF,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,uBAAuB,CAAC,EAAE,eAAe,CAAC;KAC3C;CACF;AAiND,8EAA8E;AAC9E,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgCzE;AAED,mFAAmF;AACnF,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBvF;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,eAAe,GAAG,cAAc,CAAC,CAI1G;AAoCD,wBAAgB,kBAAkB,IAAI,IAAI,CA+BzC;AAED,kFAAkF;AAClF,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAehG"}
@@ -1,376 +0,0 @@
1
- import { toast } from "@flowselections/core";
2
- import { supabase } from "./supabaseClient.js";
3
- import { startPdfMapGlobaleBanner } from "../components/PdfMapGlobaleBanner.js";
4
- import { bewaarInPdfMap, herstelPdfMapToestemming, koppelPdfMapAanPrintplek, luisterNaarPdfMapWijziging, lokalePdfPrintplek, pdfMapKoppeling, pdfMapToestemming, } from "./pdfMap.js";
5
- import { PDF_OPSLAG_MELDING } from "./pdfOpslagStatus.js";
6
- const SERVICE_INTERVAL_MS = 5000;
7
- const WACHT_INTERVAL_MS = 2000;
8
- const MAX_WACHTTIJD_MS = 5 * 60 * 1000;
9
- const WRITER_SLEUTEL = "layout-maker-pdf-writer-id";
10
- function writerId() {
11
- try {
12
- const bestaand = localStorage.getItem(WRITER_SLEUTEL)?.trim();
13
- if (bestaand)
14
- return bestaand;
15
- const nieuw = crypto.randomUUID();
16
- localStorage.setItem(WRITER_SLEUTEL, nieuw);
17
- return nieuw;
18
- }
19
- catch {
20
- return crypto.randomUUID();
21
- }
22
- }
23
- function veiligeBestandsnaam(naam) {
24
- return naam.replace(/[\\/:*?"<>|]/g, "-").replace(/\s+/g, " ").trim() || "document";
25
- }
26
- function pdfBlob(base64) {
27
- const bytes = Uint8Array.from(atob(base64), (teken) => teken.charCodeAt(0));
28
- return new Blob([bytes], { type: "application/pdf" });
29
- }
30
- async function laadPdfBlob(job) {
31
- if (job.artefact_pad) {
32
- const { data, error } = await supabase.storage.from("print-artifacts").download(job.artefact_pad);
33
- if (error || !data)
34
- throw new Error(`PDF-bestand kon niet worden opgehaald: ${error?.message ?? "onbekend"}`);
35
- return data;
36
- }
37
- if (job.resultaat_pdf_base64)
38
- return pdfBlob(job.resultaat_pdf_base64);
39
- throw new Error("Voor deze opdracht is geen PDF-bestand beschikbaar.");
40
- }
41
- async function rondClaimAf(jobId, schrijver, status, fout = null) {
42
- const { data, error } = await supabase.rpc("finish_local_pdf_job", {
43
- _job_id: jobId,
44
- _writer_id: schrijver,
45
- _status: status,
46
- _fout: fout ?? undefined,
47
- });
48
- if (error)
49
- throw new Error(`Lokale PDF-status kon niet worden opgeslagen: ${error.message}`);
50
- if (data !== true)
51
- throw new Error("De lokale PDF-claim is verlopen of door een andere werkplek overgenomen.");
52
- }
53
- /**
54
- * Geeft een geclaimde opdracht terug aan de wachtrij. Een tabblad zonder
55
- * bruikbare map mag nooit afkeuren wat een andere werkplek wél kan opslaan.
56
- */
57
- async function geefClaimTerug(jobId, schrijver) {
58
- const { error } = await supabase.rpc("release_local_pdf_job", {
59
- _job_id: jobId,
60
- _writer_id: schrijver,
61
- });
62
- if (error)
63
- console.warn("[layout-maker] PDF-opdracht teruggeven mislukt", error.message);
64
- }
65
- async function schrijfGeclaimdePdf(job, schrijver) {
66
- if (!job.printer_id || (!job.artefact_pad && !job.resultaat_pdf_base64))
67
- return false;
68
- const toestemming = await pdfMapToestemming(job.printer_id);
69
- if (toestemming !== "granted") {
70
- // Geen map of geen schrijfrecht op dít tabblad: terug in de wachtrij.
71
- await geefClaimTerug(job.id, schrijver);
72
- return false;
73
- }
74
- const naam = `${veiligeBestandsnaam(job.documentnaam ?? "document")}-${job.id.slice(0, 8)}.pdf`;
75
- let geschreven = false;
76
- try {
77
- geschreven = await bewaarInPdfMap(job.printer_id, naam, await laadPdfBlob(job));
78
- }
79
- catch {
80
- geschreven = false;
81
- }
82
- if (!geschreven) {
83
- await rondClaimAf(job.id, schrijver, "schrijffout", PDF_OPSLAG_MELDING.schrijffout);
84
- return false;
85
- }
86
- // Eerst het bestand sluiten, daarna pas de database bevestigen. Een fout in
87
- // deze bevestiging wordt nooit meer genegeerd of als succes aan de UI gemeld.
88
- await rondClaimAf(job.id, schrijver, "opgeslagen");
89
- return true;
90
- }
91
- async function bepaalEigenPrintplek() {
92
- const opgeslagen = await lokalePdfPrintplek();
93
- if (opgeslagen)
94
- return opgeslagen;
95
- // Zonder een in deze browser gekozen map doet dit tabblad niet mee: anders
96
- // kaapt een preview- of extra tabblad opdrachten die het niet kan opslaan.
97
- const koppeling = await pdfMapKoppeling();
98
- if (!koppeling?.handle)
99
- return null;
100
- // Wél een map, maar nog geen plek eraan gekoppeld (oude installatie):
101
- // uitsluitend als deze database exact één printplek met een PDF-printer heeft
102
- // kan er geen twijfel bestaan over de eigenaar van die map.
103
- const { data, error } = await supabase
104
- .from("document_printplek_printers")
105
- .select("print_plek_id, printers!inner(afdrukmethode)")
106
- .eq("printers.afdrukmethode", "pdf");
107
- if (error)
108
- return null;
109
- const ids = [...new Set((Array.isArray(data) ? data : []).map((rij) => String(rij.print_plek_id ?? "")).filter(Boolean))];
110
- if (ids.length !== 1)
111
- return null;
112
- const enigePrintplek = ids[0];
113
- if (!enigePrintplek)
114
- return null;
115
- await koppelPdfMapAanPrintplek(enigePrintplek);
116
- return enigePrintplek;
117
- }
118
- /** Heeft dít tabblad een bruikbare map met schrijfrecht voor deze printplek? */
119
- async function magOpdrachtenClaimen() {
120
- const koppeling = await pdfMapKoppeling();
121
- if (!koppeling?.handle)
122
- return false;
123
- try {
124
- const toestemming = await koppeling.handle.queryPermission?.({ mode: "readwrite" });
125
- return (toestemming ?? "granted") === "granted";
126
- }
127
- catch {
128
- return false;
129
- }
130
- }
131
- function meldMapProbleem(state, printerId) {
132
- if (state.gemeldePrinters.has(printerId))
133
- return;
134
- state.gemeldePrinters.add(printerId);
135
- toast.error("De PDF-map van deze werkplek is niet bruikbaar", {
136
- description: "Bevestig de maptoestemming en kies daarna bij de opdracht ‘Opnieuw opslaan’.",
137
- action: {
138
- label: "Maptoestemming regelen",
139
- onClick: () => {
140
- void herstelPdfMapToestemming(printerId).then((resultaat) => {
141
- if (resultaat === "granted")
142
- state.gemeldePrinters.delete(printerId);
143
- else
144
- toast.error("Maptoestemming is niet verleend");
145
- });
146
- },
147
- },
148
- duration: 15000,
149
- });
150
- }
151
- /** Schrijft hooguit dit aantal bestanden tegelijk naar de map. */
152
- const SCHRIJF_PARALLEL = 2;
153
- /** Vaste batchlimiet per ronde. */
154
- const CLAIM_MAX = 6;
155
- async function metPool(items, max, fn) {
156
- let volgende = 0;
157
- const werkers = Array.from({ length: Math.max(1, Math.min(max, items.length)) }, async () => {
158
- for (;;) {
159
- const i = volgende++;
160
- if (i >= items.length)
161
- return;
162
- await fn(items[i]);
163
- }
164
- });
165
- await Promise.all(werkers);
166
- }
167
- async function verwerkGereedstaandePdfs(state) {
168
- if (state.bezig)
169
- return;
170
- state.bezig = true;
171
- try {
172
- const printPlekId = await bepaalEigenPrintplek();
173
- if (!printPlekId)
174
- return;
175
- // Alleen een tabblad met een bruikbare map pakt werk op.
176
- if (!(await magOpdrachtenClaimen()))
177
- return;
178
- const { data, error } = await supabase.rpc("claim_local_pdf_jobs", {
179
- _print_plek_id: printPlekId,
180
- _writer_id: state.writerId,
181
- _max: CLAIM_MAX,
182
- });
183
- if (error)
184
- throw new Error(`PDF-opdrachten ophalen mislukt: ${error.message}`);
185
- let opgeslagen = 0;
186
- const jobs = (Array.isArray(data) ? data : []);
187
- await metPool(jobs, SCHRIJF_PARALLEL, async (job) => {
188
- try {
189
- if (await schrijfGeclaimdePdf(job, state.writerId)) {
190
- opgeslagen += 1;
191
- if (job.printer_id)
192
- state.gemeldePrinters.delete(job.printer_id);
193
- }
194
- else if (job.printer_id) {
195
- meldMapProbleem(state, job.printer_id);
196
- }
197
- }
198
- catch (fout) {
199
- console.error("[layout-maker] lokale PDF-opslag kon niet worden bevestigd", fout);
200
- toast.error(fout instanceof Error ? fout.message : "Lokale PDF-opslag mislukt");
201
- }
202
- });
203
- if (opgeslagen) {
204
- toast.success(opgeslagen === 1 ? "PDF opgeslagen in de ingestelde map" : `${opgeslagen} PDF's opgeslagen`);
205
- }
206
- // Zat de batch vol? Dan staat er mogelijk nog werk klaar.
207
- if (jobs.length >= CLAIM_MAX) {
208
- state.bezig = false;
209
- await verwerkGereedstaandePdfs(state);
210
- return;
211
- }
212
- }
213
- catch (fout) {
214
- console.error("[layout-maker] lokale PDF-service", fout);
215
- }
216
- finally {
217
- state.bezig = false;
218
- }
219
- }
220
- /** Herstelt uitsluitend de lokale aflevering van een reeds gerenderde PDF. */
221
- export async function bewaarPdfJobOpnieuw(jobId) {
222
- const printPlekId = await bepaalEigenPrintplek();
223
- if (!printPlekId)
224
- throw new Error("Kies eerst bij deze printplek de lokale PDF-map.");
225
- const { data: job, error: leesFout } = await supabase
226
- .from("print_jobs")
227
- .select("id, resultaat_pdf_base64, artefact_pad, lokale_pdf_print_plek_id")
228
- .eq("id", jobId)
229
- .maybeSingle();
230
- if (leesFout)
231
- throw leesFout;
232
- if (!job?.resultaat_pdf_base64 && !job?.artefact_pad)
233
- throw new Error("Voor deze opdracht is nog geen bestaande PDF beschikbaar.");
234
- if (job.lokale_pdf_print_plek_id && job.lokale_pdf_print_plek_id !== printPlekId) {
235
- throw new Error("Deze PDF hoort bij een andere printplek.");
236
- }
237
- const { error } = await supabase
238
- .from("print_jobs")
239
- .update({
240
- lokale_pdf_print_plek_id: printPlekId,
241
- lokale_pdf_status: "gereed",
242
- lokale_pdf_fout: null,
243
- lokale_pdf_claim_id: null,
244
- lokale_pdf_claim_tot: null,
245
- status: "bezig",
246
- foutmelding: null,
247
- voltooid_op: null,
248
- updated_at: new Date().toISOString(),
249
- })
250
- .eq("id", jobId);
251
- if (error)
252
- throw error;
253
- startPdfMapService();
254
- const state = window.__layoutMakerPdfService;
255
- if (state)
256
- await verwerkGereedstaandePdfs(state);
257
- return true;
258
- }
259
- /** Downloadt exact het opgeslagen artefact; legacy jobs vallen terug op base64. */
260
- export async function downloadPdfJob(jobId, documentnaam) {
261
- const { data, error } = await supabase
262
- .from("print_jobs")
263
- .select("id, artefact_pad, resultaat_pdf_base64")
264
- .eq("id", jobId)
265
- .maybeSingle();
266
- if (error)
267
- throw error;
268
- if (!data)
269
- throw new Error("De printopdracht bestaat niet meer.");
270
- const blob = await laadPdfBlob({
271
- ...data,
272
- status: "",
273
- printer_id: null,
274
- documentnaam,
275
- lokale_pdf_status: null,
276
- lokale_pdf_print_plek_id: null,
277
- artefact_sha256: null,
278
- artefact_bytes: null,
279
- });
280
- const url = URL.createObjectURL(blob);
281
- const link = document.createElement("a");
282
- link.href = url;
283
- link.download = `${veiligeBestandsnaam(documentnaam)}.pdf`;
284
- link.click();
285
- URL.revokeObjectURL(url);
286
- }
287
- export async function controleerPdfMap(printerId) {
288
- const toestemming = await pdfMapToestemming(printerId);
289
- if (toestemming === "ontbreekt")
290
- return "map_ontbreekt";
291
- return toestemming === "granted" ? "ok" : "geen_rechten";
292
- }
293
- /**
294
- * Volgt de printopdrachten van de eigen printplek live. Zodra er een PDF
295
- * gereed staat begint het wegschrijven direct; mislukt het abonnement, dan
296
- * blijft de poll het vangnet en wordt dat zichtbaar in de console.
297
- */
298
- async function abonneerOpGereedstaandePdfs(state, controleer) {
299
- try {
300
- const printPlekId = await bepaalEigenPrintplek();
301
- if (!printPlekId)
302
- return;
303
- if (state.kanaal)
304
- return;
305
- state.kanaal = supabase
306
- .channel(`layout-maker-lokale-pdf-${printPlekId}`)
307
- .on("postgres_changes", {
308
- event: "*",
309
- schema: "public",
310
- table: "print_jobs",
311
- filter: `lokale_pdf_print_plek_id=eq.${printPlekId}`,
312
- }, (payload) => {
313
- const rij = (payload.new ?? {});
314
- if (rij.lokale_pdf_status === "gereed")
315
- controleer();
316
- })
317
- .subscribe();
318
- }
319
- catch (fout) {
320
- console.warn("[layout-maker] live meeluisteren op PDF-opdrachten mislukt", fout);
321
- }
322
- }
323
- export function startPdfMapService() {
324
- if (typeof window === "undefined")
325
- return;
326
- // De zwevende mapmelding hoort bij deze service: zodra de map of toestemming
327
- // ontbreekt verschijnt hij boven elke pagina, en verdwijnt hij weer zodra de
328
- // toestemming verleend is.
329
- startPdfMapGlobaleBanner();
330
- const bestaand = window.__layoutMakerPdfService;
331
- if (bestaand?.gestart) {
332
- void verwerkGereedstaandePdfs(bestaand);
333
- return;
334
- }
335
- const state = {
336
- gestart: true,
337
- bezig: false,
338
- interval: null,
339
- gemeldePrinters: new Set(),
340
- writerId: writerId(),
341
- };
342
- window.__layoutMakerPdfService = state;
343
- const controleer = () => void verwerkGereedstaandePdfs(state);
344
- // Realtime is het snelle pad: zodra een opdracht 'gereed' staat begint het
345
- // wegschrijven meteen. De vijfsecondenpoll blijft uitsluitend vangnet.
346
- void abonneerOpGereedstaandePdfs(state, controleer);
347
- state.interval = window.setInterval(controleer, SERVICE_INTERVAL_MS);
348
- window.addEventListener("focus", controleer);
349
- window.addEventListener("online", controleer);
350
- document.addEventListener("visibilitychange", () => {
351
- if (document.visibilityState === "visible")
352
- controleer();
353
- });
354
- luisterNaarPdfMapWijziging(controleer);
355
- controleer();
356
- }
357
- /** Wacht alleen op het eindresultaat; de singleton met databaseclaim schrijft. */
358
- export async function bewaarPdfJobZodraKlaar(jobId, _printerId) {
359
- startPdfMapService();
360
- const deadline = Date.now() + MAX_WACHTTIJD_MS;
361
- while (Date.now() < deadline) {
362
- const { data, error } = await supabase
363
- .from("print_jobs")
364
- .select("status, lokale_pdf_status")
365
- .eq("id", jobId)
366
- .maybeSingle();
367
- if (error)
368
- return false;
369
- if (data?.lokale_pdf_status === "opgeslagen")
370
- return true;
371
- if (data?.status === "mislukt" || data?.status === "geannuleerd")
372
- return false;
373
- await new Promise((resolve) => window.setTimeout(resolve, WACHT_INTERVAL_MS));
374
- }
375
- return false;
376
- }