@flowselections/layout-maker 1.32.0 → 1.38.4

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 (115) hide show
  1. package/README.md +139 -139
  2. package/dist-lib/components/AfdrukKnop.d.ts +19 -19
  3. package/dist-lib/components/ComputersLijst.d.ts.map +1 -1
  4. package/dist-lib/components/ComputersLijst.js +5 -5
  5. package/dist-lib/components/DocumentenPage.d.ts.map +1 -1
  6. package/dist-lib/components/DocumentenPage.js +3 -2
  7. package/dist-lib/components/LayoutPrinterSelect.d.ts +8 -0
  8. package/dist-lib/components/LayoutPrinterSelect.d.ts.map +1 -0
  9. package/dist-lib/components/LayoutPrinterSelect.js +23 -0
  10. package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
  11. package/dist-lib/components/LayoutsPage.js +16 -3
  12. package/dist-lib/components/NetwerkPrintersDialog.d.ts +7 -7
  13. package/dist-lib/components/PaginaDocument.d.ts +4 -1
  14. package/dist-lib/components/PaginaDocument.d.ts.map +1 -1
  15. package/dist-lib/components/PaginaDocument.js +10 -5
  16. package/dist-lib/components/PakbonBuilderPage.d.ts.map +1 -1
  17. package/dist-lib/components/PakbonBuilderPage.js +6 -2
  18. package/dist-lib/components/PakbonPreview.d.ts +4 -2
  19. package/dist-lib/components/PakbonPreview.d.ts.map +1 -1
  20. package/dist-lib/components/PakbonPreview.js +55 -12
  21. package/dist-lib/components/PakbonSectiesPanel.d.ts +2 -2
  22. package/dist-lib/components/PakbonSectiesPanel.d.ts.map +1 -1
  23. package/dist-lib/components/PakbonSectiesPanel.js +7 -2
  24. package/dist-lib/components/PdfMapAutoSaver.d.ts +6 -0
  25. package/dist-lib/components/PdfMapAutoSaver.d.ts.map +1 -0
  26. package/dist-lib/components/PdfMapAutoSaver.js +12 -0
  27. package/dist-lib/components/PdfMapGlobaleBanner.d.ts +12 -0
  28. package/dist-lib/components/PdfMapGlobaleBanner.d.ts.map +1 -0
  29. package/dist-lib/components/PdfMapGlobaleBanner.js +30 -0
  30. package/dist-lib/components/PdfMapToestemmingBanner.d.ts +7 -0
  31. package/dist-lib/components/PdfMapToestemmingBanner.d.ts.map +1 -0
  32. package/dist-lib/components/PdfMapToestemmingBanner.js +139 -0
  33. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  34. package/dist-lib/components/PrintQueuePage.js +42 -3
  35. package/dist-lib/components/PrintenPage.d.ts.map +1 -1
  36. package/dist-lib/components/PrintenPage.js +3 -2
  37. package/dist-lib/components/PrinterSetupWizard.d.ts.map +1 -1
  38. package/dist-lib/components/PrinterSetupWizard.js +28 -5
  39. package/dist-lib/components/PrintersRedirectPage.d.ts +1 -1
  40. package/dist-lib/components/PrintersRedirectPage.js +14 -14
  41. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  42. package/dist-lib/components/PrintplekDetailPage.js +30 -4
  43. package/dist-lib/components/PrintplekWizard.d.ts.map +1 -1
  44. package/dist-lib/components/PrintplekWizard.js +81 -3
  45. package/dist-lib/components/ProductiebonBuilderPage.d.ts.map +1 -1
  46. package/dist-lib/components/ProductiebonBuilderPage.js +3 -1
  47. package/dist-lib/index.d.ts +10 -2
  48. package/dist-lib/index.d.ts.map +1 -1
  49. package/dist-lib/index.js +13 -2
  50. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1396 -727
  51. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  52. package/dist-lib/integrations/supabase/client.server.d.ts +1396 -727
  53. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  54. package/dist-lib/integrations/supabase/client.server.js +1 -1
  55. package/dist-lib/integrations/supabase/moduleClient.d.ts +24008 -0
  56. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -0
  57. package/dist-lib/integrations/supabase/moduleClient.js +29 -0
  58. package/dist-lib/integrations/supabase/types.d.ts +1403 -750
  59. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  60. package/dist-lib/lib/aiLayout.functions.d.ts +1396 -727
  61. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  62. package/dist-lib/lib/aiLayout.server.js +9 -9
  63. package/dist-lib/lib/browserPrint.js +1 -1
  64. package/dist-lib/lib/code128.d.ts +5 -0
  65. package/dist-lib/lib/code128.d.ts.map +1 -0
  66. package/dist-lib/lib/code128.js +51 -0
  67. package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -1
  68. package/dist-lib/lib/htmlPdf.server.js +101 -6
  69. package/dist-lib/lib/layoutPreview.d.ts.map +1 -1
  70. package/dist-lib/lib/layoutPreview.js +11 -8
  71. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  72. package/dist-lib/lib/orderData.server.js +123 -10
  73. package/dist-lib/lib/pakbonBuilder.d.ts +28 -2
  74. package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
  75. package/dist-lib/lib/pakbonBuilder.js +69 -8
  76. package/dist-lib/lib/pakbonHtml.d.ts +14 -2
  77. package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
  78. package/dist-lib/lib/pakbonHtml.js +155 -63
  79. package/dist-lib/lib/pakbonScan.d.ts +44 -0
  80. package/dist-lib/lib/pakbonScan.d.ts.map +1 -0
  81. package/dist-lib/lib/pakbonScan.js +105 -0
  82. package/dist-lib/lib/pdfJobSaver.d.ts +20 -0
  83. package/dist-lib/lib/pdfJobSaver.d.ts.map +1 -0
  84. package/dist-lib/lib/pdfJobSaver.js +173 -0
  85. package/dist-lib/lib/pdfMap.d.ts +48 -0
  86. package/dist-lib/lib/pdfMap.d.ts.map +1 -0
  87. package/dist-lib/lib/pdfMap.js +219 -0
  88. package/dist-lib/lib/pdfPrinter.d.ts +9 -0
  89. package/dist-lib/lib/pdfPrinter.d.ts.map +1 -0
  90. package/dist-lib/lib/pdfPrinter.js +34 -0
  91. package/dist-lib/lib/previewPdf.d.ts.map +1 -1
  92. package/dist-lib/lib/previewPdf.js +41 -9
  93. package/dist-lib/lib/printApi.d.ts.map +1 -1
  94. package/dist-lib/lib/printApi.js +9 -1
  95. package/dist-lib/lib/printQueue.server.d.ts +7 -0
  96. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  97. package/dist-lib/lib/printQueue.server.js +346 -52
  98. package/dist-lib/lib/printService.functions.d.ts +5365 -2689
  99. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  100. package/dist-lib/lib/productiebonHtml.js +15 -15
  101. package/dist-lib/lib/profielHtml.js +18 -18
  102. package/dist-lib/lib/supabaseAdmin.server.d.ts +5 -5
  103. package/dist-lib/lib/supabaseAdmin.server.js +43 -43
  104. package/dist-lib/lib/supabaseClient.d.ts +1 -3
  105. package/dist-lib/lib/supabaseClient.d.ts.map +1 -1
  106. package/dist-lib/lib/supabaseClient.js +4 -8
  107. package/dist-lib/lib/testpagina.js +3 -3
  108. package/dist-lib/lib/testprint.js +4 -4
  109. package/dist-lib/lib/useDocumentData.d.ts +10 -0
  110. package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
  111. package/dist-lib/lib/useDocumentData.js +19 -0
  112. package/dist-lib/lib/voorbeeldPrint.d.ts +4 -0
  113. package/dist-lib/lib/voorbeeldPrint.d.ts.map +1 -1
  114. package/dist-lib/lib/voorbeeldPrint.js +13 -2
  115. package/package.json +151 -151
@@ -0,0 +1,105 @@
1
+ // ============================================================================
2
+ // src/lib/pakbonScan.ts — registreert wat er achter een pakbonbarcode zit
3
+ // ----------------------------------------------------------------------------
4
+ // De barcodewaarde blijft eigendom van de Logistics Engine: deze module
5
+ // genereert of wijzigt nooit een barcode. Wel legt zij vast WAT er op de
6
+ // gedrukte pakbon staat, zodat een scanner daaruit een afleverbrief kan
7
+ // opbouwen: order, klant, kar en de gedrukte regels.
8
+ // ============================================================================
9
+ import { fustVerzamellijst } from "./pakbonBuilder.js";
10
+ function tekst(waarde) {
11
+ return waarde === undefined || waarde === null ? "" : String(waarde).trim();
12
+ }
13
+ function getal(waarde) {
14
+ const n = Number(waarde);
15
+ return Number.isFinite(n) && n !== 0 ? n : null;
16
+ }
17
+ /**
18
+ * Bouwt de scanregel uit de order zoals die op de pakbon is gerenderd.
19
+ * Bij een deelkar (`partial_order`) staan uitsluitend de fusten van die
20
+ * deelkar op de pakbon, dus ook uitsluitend die regels in de registratie.
21
+ */
22
+ export function bouwPakbonScanInhoud(order, opties) {
23
+ const barcode = tekst(opties.barcode);
24
+ if (!barcode)
25
+ return null;
26
+ const o = (order && typeof order === "object" ? order : {});
27
+ const alleKarren = Array.isArray(o["karren"]) ? o["karren"] : [];
28
+ const cart = (o["cart"] && typeof o["cart"] === "object" ? o["cart"] : {});
29
+ const cartId = tekst(cart["cart_id"]);
30
+ // Een order kan meerdere karren bevatten. Voor een deelkar mag de scanner
31
+ // nooit de inhoud van de andere karren terugkrijgen: selecteer dus eerst op
32
+ // het door de Engine meegestuurde cart_id.
33
+ const passendeKarren = cartId
34
+ ? alleKarren.filter((kar) => tekst(kar["cart_id"]) === cartId)
35
+ : alleKarren;
36
+ // Bij partial_order is cart_items de autoritatieve inhoud van deze deelkar.
37
+ // Alleen als die lijst ontbreekt, gebruiken we de passende karregels.
38
+ const cartItems = Array.isArray(cart["cart_items"]) ? cart["cart_items"] : [];
39
+ const karren = passendeKarren;
40
+ const eersteKar = (karren[0] ?? {});
41
+ const regels = [];
42
+ if (opties.layoutVariant !== "partial_order" || !cartItems.length) {
43
+ for (const groep of fustVerzamellijst(karren)) {
44
+ for (const pr of groep.producten) {
45
+ const bestaand = regels.find((r) => r.artikelnummer === pr.artikelnummer && r.fustsoort === (pr.fustsoort || groep.fusttype));
46
+ if (bestaand) {
47
+ bestaand.aantal += Number(pr.aantal) || 0;
48
+ continue;
49
+ }
50
+ regels.push({
51
+ artikelnummer: pr.artikelnummer,
52
+ artikelnaam: pr.artikelnaam,
53
+ potmaat: pr.potmaat,
54
+ kleur: pr.kleur,
55
+ fustsoort: pr.fustsoort || groep.fusttype,
56
+ planten_per_fust: Number(pr.planten_per_fust) || 0,
57
+ aantal: Number(pr.aantal) || 0,
58
+ fust_aantal: Number(groep.aantal) || 0,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ // De Engine kan de deelkarinhoud direct als cart_items meesturen. Die lijst
64
+ // wint bij partial_order en bevat dan uitsluitend deze barcode-kar.
65
+ if (opties.layoutVariant === "partial_order" && cartItems.length) {
66
+ for (const item of cartItems) {
67
+ regels.push({
68
+ artikelnummer: tekst(item["artikelnummer"] ?? item["artikelcode"]),
69
+ artikelnaam: tekst(item["artikelnaam"] ?? item["product_name"] ?? item["omschrijving"]),
70
+ potmaat: tekst(item["potmaat"]),
71
+ kleur: tekst(item["kleur"]),
72
+ fustsoort: tekst(item["fustsoort"] ?? item["fusttype"]),
73
+ planten_per_fust: Number(item["planten_per_fust"] ?? 0) || 0,
74
+ aantal: Number(item["aantal"] ?? item["quantity"] ?? 0) || 0,
75
+ fust_aantal: Number(item["fust_aantal"] ?? item["aantal_fusten"] ?? 0) || 0,
76
+ });
77
+ }
78
+ }
79
+ return {
80
+ barcode,
81
+ order_ref: tekst(o["order"]?.referentie) || null,
82
+ order_nummer: tekst(o["order"]?.nummer) || null,
83
+ klant_naam: tekst(o["klant"]?.naam) || null,
84
+ klant_nummer: tekst(o["klant"]?.nummer) || null,
85
+ cart_id: tekst(cart["cart_id"] ?? eersteKar["cart_id"]) || null,
86
+ cart_label: tekst(cart["cart_label"] ?? eersteKar["cart_label"]) || null,
87
+ cart_index: getal(cart["cart_index"] ?? eersteKar["cart_index"]),
88
+ cart_total: getal(cart["cart_total"] ?? eersteKar["cart_total"]),
89
+ layout_variant: opties.layoutVariant ?? null,
90
+ layout_id: opties.layoutId ?? null,
91
+ print_job_id: opties.printJobId ?? null,
92
+ is_herprint: opties.isHerprint === true,
93
+ regels,
94
+ };
95
+ }
96
+ /** Slaat de scaninhoud op; een herprint werkt dezelfde barcode bij. */
97
+ export async function bewaarPakbonScanInhoud(db, inhoud) {
98
+ if (!inhoud)
99
+ return;
100
+ const { error } = await db
101
+ .from("pakbon_barcode_inhoud")
102
+ .upsert({ ...inhoud, updated_at: new Date().toISOString() }, { onConflict: "barcode" });
103
+ if (error)
104
+ throw new Error(error.message ?? "Opslaan van pakbon-scaninhoud mislukt");
105
+ }
@@ -0,0 +1,20 @@
1
+ type PdfServiceState = {
2
+ gestart: boolean;
3
+ bezig: boolean;
4
+ interval: number | null;
5
+ gemeldePrinters: Set<string>;
6
+ };
7
+ declare global {
8
+ interface Window {
9
+ __layoutMakerPdfService?: PdfServiceState;
10
+ }
11
+ }
12
+ /** Start één permanente lokale PDF-afhandelaar voor de volledige shell. */
13
+ export declare function startPdfMapService(): void;
14
+ /**
15
+ * Wacht na een printactie op de server-PDF en schrijft hem vervolgens direct
16
+ * naar de map die op deze computer voor de virtuele printer is gekozen.
17
+ */
18
+ export declare function bewaarPdfJobZodraKlaar(jobId: string, printerId: string): Promise<boolean>;
19
+ export {};
20
+ //# sourceMappingURL=pdfJobSaver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdfJobSaver.d.ts","sourceRoot":"","sources":["../../src/lib/pdfJobSaver.ts"],"names":[],"mappings":"AA2BA,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;CAC9B,CAAC;AAEF,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,uBAAuB,CAAC,EAAE,eAAe,CAAC;KAC3C;CACF;AA8HD,2EAA2E;AAC3E,wBAAgB,kBAAkB,IAAI,IAAI,CAyBzC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAyB/F"}
@@ -0,0 +1,173 @@
1
+ import { supabase } from "./supabaseClient.js";
2
+ import { toast } from "@flowselections/core";
3
+ import { bewaarInPdfMap, herstelPdfMapToestemming, heeftPdfMap, luisterNaarPdfMapWijziging, pdfMapToestemming, } from "./pdfMap.js";
4
+ const BEWAARD_SLEUTEL = "layout-maker-pdf-bewaard";
5
+ const WACHT_INTERVAL_MS = 2000;
6
+ const MAX_WACHTTIJD_MS = 5 * 60 * 1000;
7
+ const SERVICE_INTERVAL_MS = 5000;
8
+ async function noteerLokaleStatus(jobId, status, fout = null) {
9
+ await supabase
10
+ .from("print_jobs")
11
+ .update({
12
+ lokale_pdf_status: status,
13
+ lokale_pdf_fout: fout,
14
+ ...(status === "opgeslagen"
15
+ ? { status: "voltooid", lokaal_opgeslagen_op: new Date().toISOString() }
16
+ : {}),
17
+ })
18
+ .eq("id", jobId);
19
+ }
20
+ function veiligeBestandsnaam(naam) {
21
+ return naam.replace(/[\\/:*?"<>|]/g, "-").replace(/\s+/g, " ").trim() || "document";
22
+ }
23
+ function pdfBlob(base64) {
24
+ const bytes = Uint8Array.from(atob(base64), (teken) => teken.charCodeAt(0));
25
+ return new Blob([bytes], { type: "application/pdf" });
26
+ }
27
+ function markeerBewaard(jobId) {
28
+ try {
29
+ const ruw = localStorage.getItem(BEWAARD_SLEUTEL);
30
+ const gelezen = ruw ? JSON.parse(ruw) : [];
31
+ const ids = new Set(Array.isArray(gelezen) ? gelezen.map(String) : []);
32
+ ids.add(jobId);
33
+ localStorage.setItem(BEWAARD_SLEUTEL, JSON.stringify(Array.from(ids).slice(-500)));
34
+ }
35
+ catch {
36
+ // Opslaan in de gekozen map is al gelukt; lokale historie is alleen deduplicatie.
37
+ }
38
+ }
39
+ async function schrijfPdfJob(job) {
40
+ if (!job.printer_id || !job.resultaat_pdf_base64)
41
+ return "overgeslagen";
42
+ const toestemming = await pdfMapToestemming(job.printer_id);
43
+ if (toestemming === "ontbreekt") {
44
+ await noteerLokaleStatus(job.id, "map_ontbreekt", "Kies op deze Mac eerst een PDF-map.");
45
+ return "overgeslagen";
46
+ }
47
+ if (toestemming !== "granted") {
48
+ await noteerLokaleStatus(job.id, "toestemming_nodig", "Klik op 'Geef maptoestemming' om deze Mac opnieuw toegang tot de PDF-map te geven.");
49
+ return "overgeslagen";
50
+ }
51
+ const naam = `${veiligeBestandsnaam(job.documentnaam ?? "document")}-${job.id.slice(0, 8)}.pdf`;
52
+ const gelukt = await bewaarInPdfMap(job.printer_id, naam, pdfBlob(job.resultaat_pdf_base64));
53
+ if (!gelukt) {
54
+ await noteerLokaleStatus(job.id, "schrijffout", "De PDF kon niet naar de gekozen map worden geschreven.");
55
+ return "overgeslagen";
56
+ }
57
+ markeerBewaard(job.id);
58
+ await noteerLokaleStatus(job.id, "opgeslagen");
59
+ return "opgeslagen";
60
+ }
61
+ async function verwerkGereedstaandePdfs(state) {
62
+ if (state.bezig)
63
+ return;
64
+ state.bezig = true;
65
+ try {
66
+ const { data, error } = await supabase
67
+ .from("print_jobs")
68
+ .select("id, status, printer_id, documentnaam, resultaat_pdf_base64, lokale_pdf_status")
69
+ .in("lokale_pdf_status", ["gereed", "map_ontbreekt", "toestemming_nodig", "schrijffout", "timeout"])
70
+ .not("resultaat_pdf_base64", "is", null)
71
+ .order("voltooid_op", { ascending: true })
72
+ .limit(100);
73
+ if (error || !Array.isArray(data))
74
+ return;
75
+ let opgeslagen = 0;
76
+ for (const job of data) {
77
+ if (await schrijfPdfJob(job) === "opgeslagen") {
78
+ opgeslagen += 1;
79
+ if (job.printer_id)
80
+ state.gemeldePrinters.delete(job.printer_id);
81
+ continue;
82
+ }
83
+ if (!job.printer_id || state.gemeldePrinters.has(job.printer_id))
84
+ continue;
85
+ const toestemming = await pdfMapToestemming(job.printer_id);
86
+ if (toestemming === "prompt" || toestemming === "denied") {
87
+ state.gemeldePrinters.add(job.printer_id);
88
+ toast.error("De PDF-map heeft opnieuw toestemming nodig", {
89
+ description: "Geef deze Mac opnieuw toegang; gereedstaande PDF's worden daarna direct opgeslagen.",
90
+ action: {
91
+ label: "Geef maptoestemming",
92
+ onClick: () => {
93
+ void herstelPdfMapToestemming(job.printer_id).then((resultaat) => {
94
+ if (resultaat === "granted") {
95
+ state.gemeldePrinters.delete(job.printer_id);
96
+ void verwerkGereedstaandePdfs(state);
97
+ }
98
+ else {
99
+ toast.error("Maptoestemming is niet verleend");
100
+ }
101
+ });
102
+ },
103
+ },
104
+ duration: 15000,
105
+ });
106
+ }
107
+ }
108
+ if (opgeslagen > 0) {
109
+ toast.success(opgeslagen === 1
110
+ ? "PDF opgeslagen in de gekozen map op deze Mac"
111
+ : `${opgeslagen} PDF's opgeslagen in de gekozen map op deze Mac`);
112
+ }
113
+ }
114
+ finally {
115
+ state.bezig = false;
116
+ }
117
+ }
118
+ /** Start één permanente lokale PDF-afhandelaar voor de volledige shell. */
119
+ export function startPdfMapService() {
120
+ if (typeof window === "undefined")
121
+ return;
122
+ const bestaand = window.__layoutMakerPdfService;
123
+ if (bestaand?.gestart) {
124
+ void verwerkGereedstaandePdfs(bestaand);
125
+ return;
126
+ }
127
+ const state = {
128
+ gestart: true,
129
+ bezig: false,
130
+ interval: null,
131
+ gemeldePrinters: new Set(),
132
+ };
133
+ window.__layoutMakerPdfService = state;
134
+ const controleer = () => void verwerkGereedstaandePdfs(state);
135
+ state.interval = window.setInterval(controleer, SERVICE_INTERVAL_MS);
136
+ window.addEventListener("focus", controleer);
137
+ window.addEventListener("online", controleer);
138
+ document.addEventListener("visibilitychange", () => {
139
+ if (document.visibilityState === "visible")
140
+ controleer();
141
+ });
142
+ luisterNaarPdfMapWijziging(controleer);
143
+ controleer();
144
+ }
145
+ /**
146
+ * Wacht na een printactie op de server-PDF en schrijft hem vervolgens direct
147
+ * naar de map die op deze computer voor de virtuele printer is gekozen.
148
+ */
149
+ export async function bewaarPdfJobZodraKlaar(jobId, printerId) {
150
+ if (!(await heeftPdfMap(printerId))) {
151
+ await noteerLokaleStatus(jobId, "map_ontbreekt", "Kies op deze computer eerst een PDF-map.");
152
+ return false;
153
+ }
154
+ const deadline = Date.now() + MAX_WACHTTIJD_MS;
155
+ while (Date.now() < deadline) {
156
+ const { data, error } = await supabase
157
+ .from("print_jobs")
158
+ .select("id, status, documentnaam, resultaat_pdf_base64, lokale_pdf_status")
159
+ .eq("id", jobId)
160
+ .maybeSingle();
161
+ if (error)
162
+ return false;
163
+ const job = data;
164
+ if (job?.status === "mislukt" || job?.status === "geannuleerd")
165
+ return false;
166
+ if (job?.resultaat_pdf_base64) {
167
+ return (await schrijfPdfJob({ ...job, printer_id: printerId })) === "opgeslagen";
168
+ }
169
+ await new Promise((resolve) => window.setTimeout(resolve, WACHT_INTERVAL_MS));
170
+ }
171
+ await noteerLokaleStatus(jobId, "timeout", "De PDF was niet binnen vijf minuten gereed.");
172
+ return false;
173
+ }
@@ -0,0 +1,48 @@
1
+ export declare function mapKiezenMogelijk(): boolean;
2
+ export declare function luisterNaarPdfMapWijziging(handler: () => void): () => void;
3
+ export type PdfMapKeuze = {
4
+ status: "gekozen";
5
+ naam: string;
6
+ } | {
7
+ status: "geannuleerd";
8
+ } | {
9
+ status: "niet-ondersteund";
10
+ } | {
11
+ status: "iframe-geblokkeerd";
12
+ } | {
13
+ status: "fout";
14
+ melding: string;
15
+ };
16
+ /**
17
+ * Browsers blokkeren showDirectoryPicker in een cross-origin iframe (zoals de
18
+ * Lovable-preview). De aanroeper kan dan aanbieden om de app in een eigen
19
+ * tabblad te openen, waar de verkenner wel werkt.
20
+ */
21
+ export declare function draaitInIframe(): boolean;
22
+ /** Opent de huidige pagina in een eigen tabblad (buiten het preview-iframe). */
23
+ export declare function openInEigenTabblad(): void;
24
+ /**
25
+ * Opent de mappenverkenner van het besturingssysteem zodat de gebruiker een
26
+ * map kan selecteren. Start in Downloads zodat het pad snel te vinden is.
27
+ * Geeft terug wat er is gebeurd zodat de aanroeper gericht kan reageren.
28
+ */
29
+ export declare function kiesPdfMap(printerId: string): Promise<PdfMapKeuze>;
30
+ /** Is er voor deze printer een map gekozen op deze computer? */
31
+ export declare function heeftPdfMap(printerId: string): Promise<boolean>;
32
+ /** Naam van de gekozen map (voor meldingen), of null. */
33
+ export declare function pdfMapNaam(printerId: string): Promise<string | null>;
34
+ /**
35
+ * Probeert het bestand rechtstreeks in de gekozen map te zetten. Lukt dat niet
36
+ * (andere computer, geen toestemming, browser zonder ondersteuning), dan geeft
37
+ * de functie false terug en kan de aanroeper gewoon downloaden.
38
+ */
39
+ export declare function bewaarInPdfMap(printerId: string, bestandsnaam: string, blob: Blob): Promise<boolean>;
40
+ /** Controleert toestemming zonder een bestandsactie te starten. */
41
+ export declare function pdfMapToestemming(printerId: string): Promise<"granted" | "prompt" | "denied" | "ontbreekt">;
42
+ /**
43
+ * Vraagt vanuit een expliciete gebruikersklik opnieuw schrijftoestemming voor
44
+ * een eerder gekozen map. Achtergrondtaken mogen deze browserprompt niet zelf
45
+ * openen, omdat browsers daarvoor een actieve gebruikershandeling vereisen.
46
+ */
47
+ export declare function herstelPdfMapToestemming(printerId: string): Promise<"granted" | "denied" | "ontbreekt">;
48
+ //# sourceMappingURL=pdfMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdfMap.d.ts","sourceRoot":"","sources":["../../src/lib/pdfMap.ts"],"names":[],"mappings":"AAsBA,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AA6BD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAI1E;AAgDD,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GACzB;IAAE,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAC9B;IAAE,MAAM,EAAE,oBAAoB,CAAA;CAAE,GAChC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAMxC;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAqCxE;AAED,gEAAgE;AAChE,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAErE;AAED,yDAAyD;AACzD,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG1E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,OAAO,CAAC,CAmBlB;AAED,mEAAmE;AACnE,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC,CAWjH;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC,CAe7C"}
@@ -0,0 +1,219 @@
1
+ // ============================================================================
2
+ // src/lib/pdfMap.ts — keuze van de map op de eigen computer waarin PDF's van
3
+ // de virtuele "Opslaan als PDF"-printer worden bewaard. De maphandle blijft
4
+ // lokaal in IndexedDB staan (per printer); de servers zien alleen de naam.
5
+ // ============================================================================
6
+ const DB_NAAM = "layout-maker-pdfmap";
7
+ const STORE = "handles";
8
+ const BEWAARD_SLEUTEL = "layout-maker-pdf-bewaard";
9
+ const GLOBALE_HANDLE_SLEUTEL = "__gekozen_pdf_map__";
10
+ const PDF_MAP_GEWIJZIGD_EVENT = "layout-maker:pdf-map-gewijzigd";
11
+ export function mapKiezenMogelijk() {
12
+ return typeof window !== "undefined" && "showDirectoryPicker" in window;
13
+ }
14
+ function openDb() {
15
+ return new Promise((resolve, reject) => {
16
+ const req = indexedDB.open(DB_NAAM, 1);
17
+ req.onupgradeneeded = () => {
18
+ if (!req.result.objectStoreNames.contains(STORE))
19
+ req.result.createObjectStore(STORE);
20
+ };
21
+ req.onsuccess = () => resolve(req.result);
22
+ req.onerror = () => reject(req.error);
23
+ });
24
+ }
25
+ async function bewaarHandle(printerId, handle) {
26
+ const db = await openDb();
27
+ await new Promise((resolve, reject) => {
28
+ const tx = db.transaction(STORE, "readwrite");
29
+ const store = tx.objectStore(STORE);
30
+ // De map hoort bij deze browser/computer, niet bij een database-printer-id.
31
+ // Bewaar daarom ook onder één vaste sleutel. Dit voorkomt dat een nieuwe of
32
+ // dubbele virtuele PDF-printer de reeds goedgekeurde map onzichtbaar maakt.
33
+ store.put(handle, GLOBALE_HANDLE_SLEUTEL);
34
+ store.put(handle, printerId);
35
+ tx.oncomplete = () => resolve();
36
+ tx.onerror = () => reject(tx.error);
37
+ });
38
+ window.dispatchEvent(new CustomEvent(PDF_MAP_GEWIJZIGD_EVENT, { detail: { printerId } }));
39
+ }
40
+ export function luisterNaarPdfMapWijziging(handler) {
41
+ if (typeof window === "undefined")
42
+ return () => undefined;
43
+ window.addEventListener(PDF_MAP_GEWIJZIGD_EVENT, handler);
44
+ return () => window.removeEventListener(PDF_MAP_GEWIJZIGD_EVENT, handler);
45
+ }
46
+ async function leesHandle(printerId) {
47
+ try {
48
+ const db = await openDb();
49
+ const lees = (sleutel) => new Promise((resolve, reject) => {
50
+ const tx = db.transaction(STORE, "readonly");
51
+ const req = tx.objectStore(STORE).get(sleutel);
52
+ req.onsuccess = () => resolve(req.result ?? null);
53
+ req.onerror = () => reject(req.error);
54
+ });
55
+ const globaal = await lees(GLOBALE_HANDLE_SLEUTEL);
56
+ if (globaal)
57
+ return globaal;
58
+ const direct = await lees(printerId);
59
+ if (direct) {
60
+ await bewaarHandle(printerId, direct);
61
+ return direct;
62
+ }
63
+ const gevonden = await new Promise((resolve, reject) => {
64
+ const tx = db.transaction(STORE, "readonly");
65
+ const store = tx.objectStore(STORE);
66
+ const cursor = store.openCursor();
67
+ cursor.onsuccess = () => {
68
+ const rij = cursor.result;
69
+ if (!rij) {
70
+ resolve(null);
71
+ return;
72
+ }
73
+ const handle = rij.value;
74
+ if (handle?.name && typeof handle.getFileHandle === "function") {
75
+ resolve(handle);
76
+ return;
77
+ }
78
+ rij.continue();
79
+ };
80
+ cursor.onerror = () => reject(cursor.error);
81
+ });
82
+ if (gevonden)
83
+ await bewaarHandle(printerId, gevonden);
84
+ return gevonden;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ /**
91
+ * Browsers blokkeren showDirectoryPicker in een cross-origin iframe (zoals de
92
+ * Lovable-preview). De aanroeper kan dan aanbieden om de app in een eigen
93
+ * tabblad te openen, waar de verkenner wel werkt.
94
+ */
95
+ export function draaitInIframe() {
96
+ try {
97
+ return typeof window !== "undefined" && window.self !== window.top;
98
+ }
99
+ catch {
100
+ return true;
101
+ }
102
+ }
103
+ /** Opent de huidige pagina in een eigen tabblad (buiten het preview-iframe). */
104
+ export function openInEigenTabblad() {
105
+ window.open(window.location.href, "_blank", "noopener");
106
+ }
107
+ /**
108
+ * Opent de mappenverkenner van het besturingssysteem zodat de gebruiker een
109
+ * map kan selecteren. Start in Downloads zodat het pad snel te vinden is.
110
+ * Geeft terug wat er is gebeurd zodat de aanroeper gericht kan reageren.
111
+ */
112
+ export async function kiesPdfMap(printerId) {
113
+ if (!mapKiezenMogelijk())
114
+ return { status: "niet-ondersteund" };
115
+ try {
116
+ const handle = (await window.showDirectoryPicker({
117
+ mode: "readwrite",
118
+ startIn: "downloads",
119
+ }));
120
+ await bewaarHandle(printerId, handle);
121
+ // Een nieuwe mapkeuze is ook een expliciete opdracht om reeds voltooide
122
+ // PDF-jobs opnieuw naar deze map te schrijven.
123
+ try {
124
+ localStorage.removeItem(BEWAARD_SLEUTEL);
125
+ }
126
+ catch {
127
+ // Lokale opslag kan door browserbeleid uitgeschakeld zijn.
128
+ }
129
+ return { status: "gekozen", naam: handle.name };
130
+ }
131
+ catch (fout) {
132
+ // De gebruiker drukt op Annuleren in de verkenner → AbortError, geen fout.
133
+ if (fout instanceof DOMException && fout.name === "AbortError") {
134
+ return { status: "geannuleerd" };
135
+ }
136
+ // Cross-origin iframe → SecurityError ("Cross origin sub frames aren't
137
+ // allowed to show a file picker"). Aparte status zodat de UI een
138
+ // "open in eigen tabblad"-knop kan tonen.
139
+ if (fout instanceof DOMException &&
140
+ (fout.name === "SecurityError" || /cross.?origin/i.test(fout.message))) {
141
+ return { status: "iframe-geblokkeerd" };
142
+ }
143
+ return {
144
+ status: "fout",
145
+ melding: fout instanceof Error ? fout.message : "Onbekende fout",
146
+ };
147
+ }
148
+ }
149
+ /** Is er voor deze printer een map gekozen op deze computer? */
150
+ export async function heeftPdfMap(printerId) {
151
+ return (await leesHandle(printerId)) !== null;
152
+ }
153
+ /** Naam van de gekozen map (voor meldingen), of null. */
154
+ export async function pdfMapNaam(printerId) {
155
+ const h = await leesHandle(printerId);
156
+ return h?.name ?? null;
157
+ }
158
+ /**
159
+ * Probeert het bestand rechtstreeks in de gekozen map te zetten. Lukt dat niet
160
+ * (andere computer, geen toestemming, browser zonder ondersteuning), dan geeft
161
+ * de functie false terug en kan de aanroeper gewoon downloaden.
162
+ */
163
+ export async function bewaarInPdfMap(printerId, bestandsnaam, blob) {
164
+ const handle = await leesHandle(printerId);
165
+ if (!handle)
166
+ return false;
167
+ try {
168
+ const toestemming = (await handle.queryPermission?.({ mode: "readwrite" })) ?? "granted";
169
+ if (toestemming !== "granted") {
170
+ const nieuw = await handle.requestPermission?.({ mode: "readwrite" });
171
+ if (nieuw !== "granted")
172
+ return false;
173
+ }
174
+ const bestand = await handle.getFileHandle(bestandsnaam, { create: true });
175
+ const writable = await bestand.createWritable();
176
+ await writable.write(blob);
177
+ await writable.close();
178
+ return true;
179
+ }
180
+ catch (fout) {
181
+ console.error("[layout-maker] PDF kon niet lokaal worden opgeslagen", fout);
182
+ return false;
183
+ }
184
+ }
185
+ /** Controleert toestemming zonder een bestandsactie te starten. */
186
+ export async function pdfMapToestemming(printerId) {
187
+ const handle = await leesHandle(printerId);
188
+ if (!handle)
189
+ return "ontbreekt";
190
+ try {
191
+ return ((await handle.queryPermission?.({ mode: "readwrite" })) ?? "granted");
192
+ }
193
+ catch {
194
+ return "denied";
195
+ }
196
+ }
197
+ /**
198
+ * Vraagt vanuit een expliciete gebruikersklik opnieuw schrijftoestemming voor
199
+ * een eerder gekozen map. Achtergrondtaken mogen deze browserprompt niet zelf
200
+ * openen, omdat browsers daarvoor een actieve gebruikershandeling vereisen.
201
+ */
202
+ export async function herstelPdfMapToestemming(printerId) {
203
+ const handle = await leesHandle(printerId);
204
+ if (!handle)
205
+ return "ontbreekt";
206
+ try {
207
+ // requestPermission moet rechtstreeks in deze klik-callstack plaatsvinden;
208
+ // een voorafgaande await kan de tijdelijke gebruikersactivatie laten verlopen.
209
+ if (handle.requestPermission) {
210
+ const nieuw = await handle.requestPermission({ mode: "readwrite" });
211
+ return nieuw === "granted" ? "granted" : "denied";
212
+ }
213
+ const huidig = (await handle.queryPermission?.({ mode: "readwrite" })) ?? "granted";
214
+ return huidig === "granted" ? "granted" : "denied";
215
+ }
216
+ catch {
217
+ return "denied";
218
+ }
219
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Zorgt dat de virtuele PDF-printer bestaat. Geeft true terug als de printer
3
+ * er na afloop is (bestaand of net aangemaakt), en welke melding past.
4
+ */
5
+ export declare function zorgVoorPdfPrinter(): Promise<{
6
+ ok: boolean;
7
+ alAanwezig: boolean;
8
+ }>;
9
+ //# sourceMappingURL=pdfPrinter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdfPrinter.d.ts","sourceRoot":"","sources":["../../src/lib/pdfPrinter.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAClD,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC,CAsBD"}
@@ -0,0 +1,34 @@
1
+ // ============================================================================
2
+ // src/lib/pdfPrinter.ts — gedeelde helper voor de virtuele "Opslaan als
3
+ // PDF"-printer. Documenten die hierheen gaan worden niet geprint maar als
4
+ // downloadbare PDF in de printwachtrij bewaard. Er is maar één PDF-printer
5
+ // nodig; een tweede wordt niet aangemaakt.
6
+ // ============================================================================
7
+ import { supabase } from "./supabaseClient.js";
8
+ /**
9
+ * Zorgt dat de virtuele PDF-printer bestaat. Geeft true terug als de printer
10
+ * er na afloop is (bestaand of net aangemaakt), en welke melding past.
11
+ */
12
+ export async function zorgVoorPdfPrinter() {
13
+ const { data: bestaand } = await supabase
14
+ .from("printers")
15
+ .select("id")
16
+ .eq("afdrukmethode", "pdf")
17
+ .eq("actief", true)
18
+ .limit(1);
19
+ if ((bestaand ?? []).length > 0)
20
+ return { ok: true, alAanwezig: true };
21
+ const { error } = await supabase
22
+ .from("printers")
23
+ .insert({
24
+ naam: "Opslaan als PDF",
25
+ omschrijving: "Virtuele printer: documenten worden als PDF in de printwachtrij bewaard.",
26
+ actief: true,
27
+ online: true,
28
+ afdrukmethode: "pdf",
29
+ papierformaat: "A4",
30
+ });
31
+ if (error)
32
+ throw error;
33
+ return { ok: true, alAanwezig: false };
34
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"previewPdf.d.ts","sourceRoot":"","sources":["../../src/lib/previewPdf.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAkExC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoK1F"}
1
+ {"version":3,"file":"previewPdf.d.ts","sourceRoot":"","sources":["../../src/lib/previewPdf.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAqFxC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoM1F"}