@flowselections/logistiek-engine 1.0.27 → 1.0.29

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 (101) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/components/AfmetingenBronnenCard.d.ts +2 -0
  3. package/dist-lib/components/AfmetingenBronnenCard.d.ts.map +1 -0
  4. package/dist-lib/components/AfmetingenBronnenCard.js +46 -0
  5. package/dist-lib/components/BufferDebugCard.d.ts.map +1 -1
  6. package/dist-lib/components/BufferDebugCard.js +6 -1
  7. package/dist-lib/components/ConsistentieCard.d.ts.map +1 -1
  8. package/dist-lib/components/ConsistentieCard.js +1 -1
  9. package/dist-lib/components/GeblokkeerdeOrdersCard.d.ts +2 -0
  10. package/dist-lib/components/GeblokkeerdeOrdersCard.d.ts.map +1 -0
  11. package/dist-lib/components/GeblokkeerdeOrdersCard.js +44 -0
  12. package/dist-lib/components/KarplanStatusCard.d.ts +2 -0
  13. package/dist-lib/components/KarplanStatusCard.d.ts.map +1 -0
  14. package/dist-lib/components/KarplanStatusCard.js +31 -0
  15. package/dist-lib/components/RegiePage.d.ts.map +1 -1
  16. package/dist-lib/components/RegiePage.js +4 -1
  17. package/dist-lib/components/RuleEnginePage.d.ts.map +1 -1
  18. package/dist-lib/components/RuleEnginePage.js +7 -4
  19. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +2 -0
  20. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +1 -0
  21. package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +41 -0
  22. package/dist-lib/components/rule-engine/RuleFieldInput.d.ts.map +1 -1
  23. package/dist-lib/components/rule-engine/RuleFieldInput.js +9 -0
  24. package/dist-lib/hooks/useGeblokkeerdeOrders.d.ts +28 -0
  25. package/dist-lib/hooks/useGeblokkeerdeOrders.d.ts.map +1 -0
  26. package/dist-lib/hooks/useGeblokkeerdeOrders.js +94 -0
  27. package/dist-lib/hooks/useRecalcJob.d.ts.map +1 -1
  28. package/dist-lib/hooks/useRecalcJob.js +26 -1
  29. package/dist-lib/hooks/useScannersPrinters.d.ts +11 -0
  30. package/dist-lib/hooks/useScannersPrinters.d.ts.map +1 -0
  31. package/dist-lib/hooks/useScannersPrinters.js +65 -0
  32. package/dist-lib/index.d.ts +3 -0
  33. package/dist-lib/index.d.ts.map +1 -1
  34. package/dist-lib/index.js +3 -0
  35. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2105 -68
  36. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  37. package/dist-lib/integrations/supabase/client.d.ts +20293 -0
  38. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  39. package/dist-lib/integrations/supabase/client.js +13 -0
  40. package/dist-lib/integrations/supabase/client.server.d.ts +2105 -68
  41. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  42. package/dist-lib/integrations/supabase/client.server.js +1 -1
  43. package/dist-lib/integrations/supabase/moduleClient.d.ts +2105 -68
  44. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  45. package/dist-lib/integrations/supabase/types.d.ts +2135 -54
  46. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/types.js +1 -0
  48. package/dist-lib/lib/attachAuthToken.d.ts +2 -0
  49. package/dist-lib/lib/attachAuthToken.d.ts.map +1 -0
  50. package/dist-lib/lib/attachAuthToken.js +50 -0
  51. package/dist-lib/lib/bufferMatching.d.ts.map +1 -1
  52. package/dist-lib/lib/bufferMatching.js +1 -0
  53. package/dist-lib/lib/cartPlanRequests.server.d.ts +13 -0
  54. package/dist-lib/lib/cartPlanRequests.server.d.ts.map +1 -0
  55. package/dist-lib/lib/cartPlanRequests.server.js +168 -0
  56. package/dist-lib/lib/controlScan.server.d.ts +12 -0
  57. package/dist-lib/lib/controlScan.server.d.ts.map +1 -0
  58. package/dist-lib/lib/controlScan.server.js +197 -0
  59. package/dist-lib/lib/dimensionData.server.d.ts +11 -1
  60. package/dist-lib/lib/dimensionData.server.d.ts.map +1 -1
  61. package/dist-lib/lib/dimensionData.server.js +58 -48
  62. package/dist-lib/lib/dimensionPlanner.d.ts +26 -1
  63. package/dist-lib/lib/dimensionPlanner.d.ts.map +1 -1
  64. package/dist-lib/lib/dimensionPlanner.js +37 -6
  65. package/dist-lib/lib/floridayDocuments.functions.d.ts +6314 -203
  66. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  67. package/dist-lib/lib/floridayDocuments.functions.js +4 -3
  68. package/dist-lib/lib/floridayPush.functions.d.ts +2106 -69
  69. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  70. package/dist-lib/lib/floridayPush.functions.js +2 -1
  71. package/dist-lib/lib/handlers/logistics-tick.server.d.ts.map +1 -1
  72. package/dist-lib/lib/handlers/logistics-tick.server.js +64 -0
  73. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +10480 -295
  74. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  75. package/dist-lib/lib/loadCarrierPlanning.functions.js +6 -5
  76. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +42 -0
  77. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
  78. package/dist-lib/lib/loadCarrierPlanning.server.js +583 -31
  79. package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -1
  80. package/dist-lib/lib/logisticsPlanner.js +24 -9
  81. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +4231 -157
  82. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  83. package/dist-lib/lib/logisticsRecalcJob.functions.js +3 -2
  84. package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -1
  85. package/dist-lib/lib/logisticsRecalcJob.server.js +1 -0
  86. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +2106 -69
  87. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  88. package/dist-lib/lib/logisticsRecalculate.functions.js +3 -1
  89. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  90. package/dist-lib/lib/modulePrompts.js +2560 -1586
  91. package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
  92. package/dist-lib/lib/planQueue.server.js +6 -0
  93. package/dist-lib/lib/readableServerErrors.d.ts +2 -0
  94. package/dist-lib/lib/readableServerErrors.d.ts.map +1 -0
  95. package/dist-lib/lib/readableServerErrors.js +36 -0
  96. package/dist-lib/lib/ruleEngineSchema.d.ts +1 -1
  97. package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
  98. package/dist-lib/lib/ruleEngineSchema.js +72 -51
  99. package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
  100. package/dist-lib/lib/ruleEngineWizard.js +5 -6
  101. package/package.json +1 -1
package/README.md CHANGED
@@ -1,24 +1,24 @@
1
- # logistiek Engine
2
-
3
- blank project start
4
-
5
- This project was built with [Lovable](https://lovable.dev).
6
-
7
- ## Build with Lovable
8
-
9
- Continue developing this project in the [Lovable editor](https://lovable.dev/projects/316ec9c5-0f01-408a-b7ec-3d4dccf9b65d).
10
-
11
- - **Ship faster**: describe what you want to build and Lovable handles the code.
12
- - **Stay in sync**: every change made in Lovable is committed straight to this repository.
13
- - **Full ownership**: this code is yours. Push to `main` on GitHub and your changes sync back into Lovable, ready for your next prompt.
14
-
15
- ## Development
16
-
17
- Prefer working locally? You need Node.js and npm — [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating).
18
-
19
- ```sh
20
- git clone <this-repository-url>
21
- cd <repository-name>
22
- npm i
23
- npm run dev
24
- ```
1
+ # logistiek Engine
2
+
3
+ blank project start
4
+
5
+ This project was built with [Lovable](https://lovable.dev).
6
+
7
+ ## Build with Lovable
8
+
9
+ Continue developing this project in the [Lovable editor](https://lovable.dev/projects/316ec9c5-0f01-408a-b7ec-3d4dccf9b65d).
10
+
11
+ - **Ship faster**: describe what you want to build and Lovable handles the code.
12
+ - **Stay in sync**: every change made in Lovable is committed straight to this repository.
13
+ - **Full ownership**: this code is yours. Push to `main` on GitHub and your changes sync back into Lovable, ready for your next prompt.
14
+
15
+ ## Development
16
+
17
+ Prefer working locally? You need Node.js and npm — [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating).
18
+
19
+ ```sh
20
+ git clone <this-repository-url>
21
+ cd <repository-name>
22
+ npm i
23
+ npm run dev
24
+ ```
@@ -0,0 +1,2 @@
1
+ export declare function AfmetingenBronnenCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=AfmetingenBronnenCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AfmetingenBronnenCard.d.ts","sourceRoot":"","sources":["../../src/components/AfmetingenBronnenCard.tsx"],"names":[],"mappings":"AAuDA,wBAAgB,qBAAqB,gCAsHpC"}
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/AfmetingenBronnenCard.tsx — herkomst van de afmetingen
4
+ // ============================================================================
5
+ // Toont per product waar het fust en de planthoogte vandaan komen: handmatig
6
+ // ingevuld, uit de Floriday-koppeling, of afgeleid uit de belading. Producten
7
+ // zonder bruikbare afmetingen staan met reden onderaan; die karren krijgen
8
+ // bewust geen laagindeling (fail closed).
9
+ // GEEN <Header /> hier — die levert de shell.
10
+ // ============================================================================
11
+ import { useCallback, useEffect, useState } from "react";
12
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, supabase, } from "@flowselections/core";
13
+ import { RefreshCw, Ruler } from "lucide-react";
14
+ const BRON_LABEL = {
15
+ handmatig: "Handmatig ingevuld",
16
+ floriday: "Uit Floriday",
17
+ belading: "Afgeleid uit de belading",
18
+ };
19
+ function bron(waarde) {
20
+ if (!waarde)
21
+ return "—";
22
+ return BRON_LABEL[waarde] ?? waarde;
23
+ }
24
+ export function AfmetingenBronnenCard() {
25
+ const [rijen, setRijen] = useState([]);
26
+ const [loading, setLoading] = useState(true);
27
+ const laden = useCallback(async () => {
28
+ setLoading(true);
29
+ const { data } = await supabase
30
+ .from("logistics_product_dimensions")
31
+ .select("product_id, product_name, fust_name, fust_source, fust_length_mm, fust_width_mm, fust_height_mm, plant_height_cm, height_source, dimension_source, missing_reason")
32
+ .order("product_name", { ascending: true })
33
+ .limit(500);
34
+ setRijen(Array.isArray(data) ? data : []);
35
+ setLoading(false);
36
+ }, []);
37
+ useEffect(() => {
38
+ void laden();
39
+ }, [laden]);
40
+ const compleet = rijen.filter((r) => !r.missing_reason);
41
+ const onvolledig = rijen.filter((r) => r.missing_reason);
42
+ const uitFloriday = compleet.filter((r) => r.height_source === "floriday").length;
43
+ const uitBelading = compleet.filter((r) => r.height_source === "belading").length;
44
+ const handmatig = compleet.filter((r) => r.height_source === "handmatig").length;
45
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Ruler, { className: "h-4 w-4" }), "Afmetingen per product"] }), _jsx(CardDescription, { children: "De Engine leidt fust en planthoogte zelf af: eerst handmatig, dan Floriday, dan berekend uit de belading. Ontbreekt alles, dan wordt er bewust geen laagindeling gemaakt." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Verversen"] })] }) }), _jsx(CardContent, { className: "space-y-4", children: loading ? (_jsx(Skeleton, { className: "h-24 w-full" })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-2 text-xs", children: [_jsxs(Badge, { variant: "default", children: [compleet.length, " met afmetingen"] }), _jsxs(Badge, { variant: "secondary", children: [handmatig, " handmatig"] }), _jsxs(Badge, { variant: "secondary", children: [uitFloriday, " uit Floriday"] }), _jsxs(Badge, { variant: "secondary", children: [uitBelading, " uit de belading"] }), onvolledig.length > 0 ? (_jsxs(Badge, { variant: "destructive", children: [onvolledig.length, " onvolledig"] })) : null] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Fust" }), _jsx(TableHead, { children: "Fustmaat (mm)" }), _jsx(TableHead, { children: "Planthoogte" }), _jsx(TableHead, { children: "Herkomst hoogte" })] }) }), _jsxs(TableBody, { children: [compleet.map((r) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: r.product_name ?? "—" }), _jsxs(TableCell, { children: [r.fust_name ?? "—", r.fust_source ? (_jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: bron(r.fust_source) })) : null] }), _jsxs(TableCell, { className: "tabular-nums", children: [r.fust_length_mm, " \u00D7 ", r.fust_width_mm, " \u00D7 ", r.fust_height_mm] }), _jsxs(TableCell, { className: "tabular-nums", children: [r.plant_height_cm, " cm"] }), _jsx(TableCell, { children: bron(r.height_source) })] }, r.product_id))), compleet.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "text-sm text-muted-foreground", children: "Nog geen enkel product met volledige afmetingen." }) })) : null] })] }), onvolledig.length > 0 ? (_jsxs("div", { className: "space-y-2 rounded-md border border-destructive/40 bg-destructive/5 p-3", children: [_jsx("p", { className: "text-sm font-medium", children: "Deze producten kunnen niet op afmetingen worden ingedeeld" }), _jsx("ul", { className: "space-y-1 text-sm text-muted-foreground", children: onvolledig.map((r) => (_jsxs("li", { children: [_jsx("span", { className: "font-medium text-foreground", children: r.product_name ?? "Onbekend product" }), " — ", r.missing_reason] }, r.product_id))) })] })) : null] })) })] }));
46
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"BufferDebugCard.d.ts","sourceRoot":"","sources":["../../src/components/BufferDebugCard.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGpF,UAAU,KAAK;IACb,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,EAAE,CACf,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACtC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,KACvB,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAChG,iBAAiB,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,gBAAgB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAClH,WAAW,CAAC,EAAE,CACZ,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,EAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAClC,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACzC;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,GACZ,EAAE,KAAK,+BAsMP"}
1
+ {"version":3,"file":"BufferDebugCard.d.ts","sourceRoot":"","sources":["../../src/components/BufferDebugCard.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGpF,UAAU,KAAK;IACb,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,EAAE,CACf,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACtC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,KACvB,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAChG,iBAAiB,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,gBAAgB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAClH,WAAW,CAAC,EAAE,CACZ,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,EAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAClC,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACzC;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,GACZ,EAAE,KAAK,+BAmNP"}
@@ -64,6 +64,11 @@ export function BufferDebugCard({ taken, strategyId, simuleerBuffers, herindelen
64
64
  ? ` · ${indelingTotaal.overgeslagen} vergrendeld overgeslagen`
65
65
  : "", indelingTotaal.mislukt > 0 ? ` · ${indelingTotaal.mislukt} mislukt` : ""] }), indelingTotaal.fouten.length > 0 && (_jsx("ul", { className: "text-destructive mt-1 list-disc pl-4 text-xs", children: indelingTotaal.fouten.map((f, i) => (_jsx("li", { children: f }, i))) }))] })), orderResultaat && (_jsxs("p", { className: "text-muted-foreground text-sm", children: ["Laatste herberekening: ", Number(orderResultaat.updated ?? 0), " van", " ", Number(orderResultaat.checked ?? 0), " orders verplaatst in", " ", Number(orderResultaat.durationMs ?? 0), " ms."] })), orders.length === 0 && (_jsx("p", { className: "text-muted-foreground text-sm", children: "Er zijn geen openstaande orders om te simuleren." })), resultaat === null && orders.length > 0 && (_jsxs("p", { className: "text-muted-foreground text-sm", children: ["Klik op \u201CSimuleren\u201D om ", orders.length, " openstaande order(s) door de combineerregels te halen."] })), groepen.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Gevonden buffers" }), _jsx("ul", { className: "space-y-2", children: groepen.map((g) => (_jsxs("li", { className: "rounded-lg border border-border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Badge, { variant: Number(g.count) > 1 ? "default" : "outline", children: [Number(g.count), " order", Number(g.count) === 1 ? "" : "s"] }), _jsx("span", { className: "text-sm", children: (Array.isArray(g.orderRefs) ? g.orderRefs : []).join(", ") })] }), _jsx("p", { className: "text-muted-foreground mt-1 font-mono text-xs break-all", children: String(g.bufferKey ?? "") })] }, String(g.bufferRef)))) })] })), regels.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Uitleg per order" }), _jsx("ul", { className: "space-y-2", children: regels.map((r) => {
66
66
  const trace = Array.isArray(r.trace) ? r.trace : [];
67
- return (_jsxs("li", { className: "rounded-lg border border-border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: String(r.orderRef) }), _jsx(Badge, { variant: r.buffer ? "secondary" : "outline", children: bufferReasonLabel(r.reason) })] }), _jsx("ul", { className: "mt-2 space-y-1", children: trace.map((t, i) => (_jsxs("li", { className: "text-muted-foreground text-xs", children: [_jsx("span", { className: t.result ? "text-foreground" : "text-destructive", children: bufferCriterionLabel(String(t.rule)) }), t.value ? _jsxs("span", { children: [" = ", String(t.value)] }) : null, t.message ? _jsxs("span", { children: [" \u2014 ", String(t.message)] }) : null] }, i))) })] }, String(r.orderRef)));
67
+ return (_jsxs("li", { className: "rounded-lg border border-border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: String(r.orderRef) }), _jsx(Badge, { variant: r.buffer ? "secondary" : "outline", children: bufferReasonLabel(r.reason) }), r.bufferCloseAt ? (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["sluit", " ", new Date(String(r.bufferCloseAt)).toLocaleString("nl-NL", {
68
+ day: "2-digit",
69
+ month: "2-digit",
70
+ hour: "2-digit",
71
+ minute: "2-digit",
72
+ }), r.closeReference === "pick_start" ? " (vóór start picken)" : " (vóór aanmelden)"] })) : null] }), _jsx("ul", { className: "mt-2 space-y-1", children: trace.map((t, i) => (_jsxs("li", { className: "text-muted-foreground text-xs", children: [_jsx("span", { className: t.result ? "text-foreground" : "text-destructive", children: bufferCriterionLabel(String(t.rule)) }), t.value ? _jsxs("span", { children: [" = ", String(t.value)] }) : null, t.message ? _jsxs("span", { children: [" \u2014 ", String(t.message)] }) : null] }, i))) })] }, String(r.orderRef)));
68
73
  }) })] }))] })] }));
69
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ConsistentieCard.d.ts","sourceRoot":"","sources":["../../src/components/ConsistentieCard.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,gBAAgB,gCAgF/B"}
1
+ {"version":3,"file":"ConsistentieCard.d.ts","sourceRoot":"","sources":["../../src/components/ConsistentieCard.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,gBAAgB,gCAuF/B"}
@@ -34,5 +34,5 @@ export function ConsistentieCard() {
34
34
  { label: "Aangemeld in pickwachtrij", waarde: check.announced_carts },
35
35
  ]
36
36
  : [];
37
- return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Scale, { className: "h-5 w-5 text-primary" }), "Zelfde cijfers in elke module"] }), _jsx(CardDescription, { children: "Karren van vandaag per venster. Alle vensters horen hetzelfde te tellen; de lever agenda telt alleen wat vandaag geleverd wordt." })] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => void laden(), disabled: loading, children: _jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }) })] }), _jsx(CardContent, { className: "space-y-4", children: loading && !check ? (_jsx(Skeleton, { className: "h-24 w-full" })) : !check ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Geen meting beschikbaar." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: check.ok ? "default" : "destructive", children: check.ok ? "Alles gelijk" : "Afwijking gevonden" }), check.orphan_tasks > 0 ? (_jsxs(Badge, { variant: "destructive", children: [check.orphan_tasks, " weestaken"] })) : null, _jsxs("span", { className: "text-muted-foreground text-xs", children: ["Peildag ", check.pick_date] })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-4", children: vensters.map((v) => (_jsxs("div", { className: "rounded-lg border p-3", children: [_jsx("div", { className: "text-muted-foreground text-xs", children: v.label }), _jsx("div", { className: "text-2xl font-semibold tabular-nums", children: v.waarde })] }, v.label))) }), !check.ok ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "E\u00E9n of meer modules tellen anders. Controleer of die module de laatste koppelprompt heeft toegepast; de Engine corrigeert niets zelf." })) : null] })) })] }));
37
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Scale, { className: "h-5 w-5 text-primary" }), "Zelfde cijfers in elke module"] }), _jsx(CardDescription, { children: "Karren van vandaag per venster. Alle vensters horen hetzelfde te tellen; de lever agenda telt alleen wat vandaag geleverd wordt." })] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => void laden(), disabled: loading, children: _jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }) })] }), _jsx(CardContent, { className: "space-y-4", children: loading && !check ? (_jsx(Skeleton, { className: "h-24 w-full" })) : !check ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Geen meting beschikbaar." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: check.ok ? "default" : "destructive", children: check.ok ? "Alles gelijk" : "Afwijking gevonden" }), check.orphan_tasks > 0 ? (_jsxs(Badge, { variant: "destructive", children: [check.orphan_tasks, " weestaken"] })) : null, (check.duplicate_carts ?? 0) > 0 ? (_jsxs(Badge, { variant: "destructive", children: [check.duplicate_carts, " dubbele karren"] })) : null, (check.orphan_carts ?? 0) > 0 ? (_jsxs(Badge, { variant: "destructive", children: [check.orphan_carts, " karren uit een oude buffer"] })) : null, _jsxs("span", { className: "text-muted-foreground text-xs", children: ["Peildag ", check.pick_date] })] }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-4", children: vensters.map((v) => (_jsxs("div", { className: "rounded-lg border p-3", children: [_jsx("div", { className: "text-muted-foreground text-xs", children: v.label }), _jsx("div", { className: "text-2xl font-semibold tabular-nums", children: v.waarde })] }, v.label))) }), !check.ok ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "E\u00E9n of meer modules tellen anders. Controleer of die module de laatste koppelprompt heeft toegepast; de Engine corrigeert niets zelf." })) : null] })) })] }));
38
38
  }
@@ -0,0 +1,2 @@
1
+ export declare function GeblokkeerdeOrdersCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=GeblokkeerdeOrdersCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GeblokkeerdeOrdersCard.d.ts","sourceRoot":"","sources":["../../src/components/GeblokkeerdeOrdersCard.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,sBAAsB,gCAsIrC"}
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/GeblokkeerdeOrdersCard.tsx — orders die vastlopen
4
+ // ============================================================================
5
+ // Een blokkade is een zichtbare eindtoestand: de order blijft staan tot er
6
+ // iets aan de order verandert of iemand hier op opnieuw proberen klikt.
7
+ // Alles komt uit de Engine-functies; hier wordt niets zelf berekend.
8
+ // GEEN <Header /> hier — die levert de shell.
9
+ // ============================================================================
10
+ import { useMemo, useState } from "react";
11
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, } from "@flowselections/core";
12
+ import { AlertTriangle, RefreshCw, RotateCcw } from "lucide-react";
13
+ import { useGeblokkeerdeOrders, redenLabel } from "../hooks/useGeblokkeerdeOrders";
14
+ function datum(waarde) {
15
+ if (!waarde)
16
+ return "—";
17
+ const d = new Date(waarde);
18
+ return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString("nl-NL");
19
+ }
20
+ export function GeblokkeerdeOrdersCard() {
21
+ const { rows, overzicht, loading, error, busy, refresh, retry } = useGeblokkeerdeOrders();
22
+ const [filter, setFilter] = useState("");
23
+ const [melding, setMelding] = useState(null);
24
+ const safeRows = Array.isArray(rows) ? rows : [];
25
+ const codes = useMemo(() => {
26
+ const set = new Map();
27
+ for (const r of safeRows)
28
+ set.set(r.reasonCode, (set.get(r.reasonCode) ?? 0) + 1);
29
+ return Array.from(set.entries()).sort((a, b) => b[1] - a[1]);
30
+ }, [safeRows]);
31
+ const zichtbaar = filter ? safeRows.filter((r) => r.reasonCode === filter) : safeRows;
32
+ const opnieuw = async (ids) => {
33
+ try {
34
+ const n = await retry(ids);
35
+ setMelding(n === 0
36
+ ? "Er is niets opnieuw aangeboden."
37
+ : `${n} order(s) opnieuw aangeboden. Ze verdwijnen zodra de verwerking ze heeft opgepakt.`);
38
+ }
39
+ catch (e) {
40
+ setMelding(e?.message ? String(e.message) : "Opnieuw proberen is niet gelukt.");
41
+ }
42
+ };
43
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-destructive" }), "Vastgelopen orders"] }), _jsx(CardDescription, { children: "Deze orders krijgen geen karrenindeling tot er iets aan de order verandert of je ze hier opnieuw aanbiedt." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [zichtbaar.length > 0 && (_jsxs(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => void opnieuw(zichtbaar.map((r) => r.orderId)), children: [_jsx(RotateCcw, { className: "mr-2 h-4 w-4" }), "Alles opnieuw proberen"] })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void refresh(), disabled: loading, children: _jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }) })] })] }), _jsxs(CardContent, { className: "space-y-4", children: [overzicht?.tickStale && (_jsx("p", { className: "rounded-md bg-muted p-3 text-sm text-muted-foreground", children: "De verwerking op de achtergrond heeft zich langer dan tien minuten niet gemeld. Opnieuw proberen zet de order wel klaar, maar hij wordt pas opgepakt zodra de verwerking weer draait." })), error && _jsx("p", { className: "text-sm text-destructive", children: error }), melding && _jsx("p", { className: "text-sm text-muted-foreground", children: melding }), loading && safeRows.length === 0 ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-6 w-full" }), _jsx(Skeleton, { className: "h-6 w-full" })] })) : safeRows.length === 0 ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Geen vastgelopen orders. Alles is ingedeeld of staat nog in de wachtrij", overzicht ? ` (${overzicht.pending} wachtend)` : "", "."] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Badge, { variant: filter === "" ? "default" : "outline", className: "cursor-pointer", onClick: () => setFilter(""), children: ["Alles (", safeRows.length, ")"] }), codes.map(([code, aantal]) => (_jsxs(Badge, { variant: filter === code ? "default" : "outline", className: "cursor-pointer", onClick: () => setFilter(code), children: [redenLabel(code), " (", aantal, ")"] }, code)))] }), _jsx("div", { className: "divide-y rounded-md border", children: zichtbaar.map((r) => (_jsxs("div", { className: "flex items-start justify-between gap-4 p-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-medium", children: r.orderRef }), _jsx(Badge, { variant: "secondary", children: redenLabel(r.reasonCode) }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Leverdatum ", datum(r.deliveryDate), " \u00B7 sinds ", datum(r.blockedSince)] })] }), r.reason && (_jsx("p", { className: "mt-1 line-clamp-2 text-sm text-muted-foreground", children: r.reason }))] }), _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => void opnieuw([r.orderId]), children: "Opnieuw" })] }, r.queueId))) })] }))] })] }));
44
+ }
@@ -0,0 +1,2 @@
1
+ export declare function KarplanStatusCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=KarplanStatusCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KarplanStatusCard.d.ts","sourceRoot":"","sources":["../../src/components/KarplanStatusCard.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,iBAAiB,gCAmFhC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/KarplanStatusCard.tsx — is de karplanning klaar?
4
+ // ============================================================================
5
+ // Toont per order van vandaag en de komende dagen of er een sluitend karplan
6
+ // ligt, hoeveel karren dat zijn en of de order al is vrijgegeven voor picken.
7
+ // Alles komt uit public.logistics_cart_plan_overview(); niets wordt hier
8
+ // zelf berekend. GEEN <Header /> hier — die levert de shell.
9
+ // ============================================================================
10
+ import { useCallback, useEffect, useState } from "react";
11
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, supabase, } from "@flowselections/core";
12
+ import { ClipboardList, RefreshCw } from "lucide-react";
13
+ export function KarplanStatusCard() {
14
+ const [rijen, setRijen] = useState([]);
15
+ const [loading, setLoading] = useState(true);
16
+ const laden = useCallback(async () => {
17
+ setLoading(true);
18
+ const { data } = await supabase.rpc("logistics_cart_plan_overview", {
19
+ p_days: 3,
20
+ });
21
+ setRijen(Array.isArray(data) ? data : []);
22
+ setLoading(false);
23
+ }, []);
24
+ useEffect(() => {
25
+ void laden();
26
+ }, [laden]);
27
+ const zonderPlan = rijen.filter((r) => !r.plan_ready).length;
28
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(ClipboardList, { className: "h-5 w-5 text-primary" }), "Karplanning per order"] }), _jsx(CardDescription, { children: "Orders van vandaag en de komende dagen. Een order hoort pas op het pickscherm te staan als het karplan klaar is \u00E9n de order is vrijgegeven." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${loading ? "animate-spin" : ""}` }), "Vernieuwen"] })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" })] })) : rijen.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen orders met een leverdatum in deze periode." })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: zonderPlan === 0
29
+ ? "Alle orders in deze periode hebben een sluitend karplan."
30
+ : `${zonderPlan} order(s) hebben nog geen sluitend karplan.` }), _jsx("div", { className: "divide-y rounded-md border", children: rijen.map((r) => (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 p-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium", children: r.order_ref }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Leverdatum ", r.delivery_date ?? "onbekend", r.reason ? ` — ${r.reason}` : ""] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: r.plan_ready ? "secondary" : "destructive", children: r.plan_ready ? `${r.cart_total ?? 0} karren` : "Geen karplan" }), _jsx(Badge, { variant: r.released ? "default" : "outline", children: r.released ? "Vrijgegeven" : "Nog niet vrij" })] })] }, r.order_ref))) })] })) })] }));
31
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"RegiePage.d.ts","sourceRoot":"","sources":["../../src/components/RegiePage.tsx"],"names":[],"mappings":"AAyEA,wBAAgB,SAAS,gCA4XxB"}
1
+ {"version":3,"file":"RegiePage.d.ts","sourceRoot":"","sources":["../../src/components/RegiePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,SAAS,gCAmYxB"}
@@ -18,6 +18,9 @@ import { CapaciteitMeldingenCard } from "./CapaciteitMeldingenCard";
18
18
  import { FloridayPushCard } from "./FloridayPushCard";
19
19
  import { PrintjobsCard } from "./PrintjobsCard";
20
20
  import { ConsistentieCard } from "./ConsistentieCard";
21
+ import { KarplanStatusCard } from "./KarplanStatusCard";
22
+ import { GeblokkeerdeOrdersCard } from "./GeblokkeerdeOrdersCard";
23
+ import { AfmetingenBronnenCard } from "./AfmetingenBronnenCard";
21
24
  import { LOGISTICS_MODULES, TASK_LABEL, TASK_STATUS_LABEL, } from "../lib/logisticsTasks";
22
25
  function tijd(value) {
23
26
  if (!value)
@@ -74,7 +77,7 @@ export function RegiePage() {
74
77
  }
75
78
  return counts;
76
79
  }, [safeTaken]);
77
- return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs("h1", { className: "flex items-center gap-2 text-2xl font-semibold tracking-tight", children: [_jsx(Workflow, { className: "h-6 w-6 text-primary" }), "Overzicht & meldingen"] }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Wat er vandaag klaarstaat en waar aandacht voor nodig is." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/logistiek-engine", children: "Terug naar overzicht" }) }), _jsxs(Button, { variant: "outline", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Vernieuwen"] })] })] }), _jsx(ConsistentieCard, {}), _jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Object.keys(LOGISTICS_MODULES).map((key) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-2", children: [_jsx(CardDescription, { children: LOGISTICS_MODULES[key] }), _jsx(CardTitle, { className: "text-2xl", children: perModule[key] ?? 0 })] }), _jsx(CardContent, { className: "text-muted-foreground text-xs", children: "openstaande en geplande taken" })] }, key))) }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Taken per order" }), _jsx(CardDescription, { children: "Elke order krijgt bij binnenkomst meteen de volledige keten aan taken toegewezen." }), _jsxs("div", { className: "pt-2", children: [_jsx(Label, { htmlFor: "regie-filter", className: "sr-only", children: "Zoeken" }), _jsx(Input, { id: "regie-filter", placeholder: "Zoek op ordernummer, taaktype of module\u2026", value: filter, onChange: (e) => setFilter(e.target.value), className: "max-w-sm" })] })] }), _jsx(CardContent, { className: "space-y-6", children: loading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" })] })) : perOrder.length === 0 ? (_jsxs("p", { className: "text-muted-foreground py-8 text-center text-sm", children: ["Nog geen taken. Zodra een module een melding wegschrijft in", " ", _jsx("code", { className: "bg-muted rounded px-1", children: "logistics_events" }), " maakt de database de keten meteen aan."] })) : (perOrder.map(([orderRef, orderTaken]) => {
80
+ return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsxs("h1", { className: "flex items-center gap-2 text-2xl font-semibold tracking-tight", children: [_jsx(Workflow, { className: "h-6 w-6 text-primary" }), "Overzicht & meldingen"] }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Wat er vandaag klaarstaat en waar aandacht voor nodig is." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/logistiek-engine", children: "Terug naar overzicht" }) }), _jsxs(Button, { variant: "outline", onClick: () => void laden(), disabled: loading, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Vernieuwen"] })] })] }), _jsx(ConsistentieCard, {}), _jsx(KarplanStatusCard, {}), _jsx(GeblokkeerdeOrdersCard, {}), _jsx(AfmetingenBronnenCard, {}), _jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Object.keys(LOGISTICS_MODULES).map((key) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-2", children: [_jsx(CardDescription, { children: LOGISTICS_MODULES[key] }), _jsx(CardTitle, { className: "text-2xl", children: perModule[key] ?? 0 })] }), _jsx(CardContent, { className: "text-muted-foreground text-xs", children: "openstaande en geplande taken" })] }, key))) }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Taken per order" }), _jsx(CardDescription, { children: "Elke order krijgt bij binnenkomst meteen de volledige keten aan taken toegewezen." }), _jsxs("div", { className: "pt-2", children: [_jsx(Label, { htmlFor: "regie-filter", className: "sr-only", children: "Zoeken" }), _jsx(Input, { id: "regie-filter", placeholder: "Zoek op ordernummer, taaktype of module\u2026", value: filter, onChange: (e) => setFilter(e.target.value), className: "max-w-sm" })] })] }), _jsx(CardContent, { className: "space-y-6", children: loading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" })] })) : perOrder.length === 0 ? (_jsxs("p", { className: "text-muted-foreground py-8 text-center text-sm", children: ["Nog geen taken. Zodra een module een melding wegschrijft in", " ", _jsx("code", { className: "bg-muted rounded px-1", children: "logistics_events" }), " maakt de database de keten meteen aan."] })) : (perOrder.map(([orderRef, orderTaken]) => {
78
81
  const spoed = orderTaken.some((t) => (t.priority ?? 0) > 0);
79
82
  const actief = orderTaken.some((t) => t.status !== "cancelled" && t.status !== "done" && t.status !== "superseded");
80
83
  return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("h2", { className: "flex items-center gap-2 font-medium", children: ["Order ", orderRef, _jsxs("span", { className: "text-muted-foreground text-xs font-normal", children: [orderTaken.length, " taken"] }), spoed && (_jsx(Badge, { variant: "destructive", className: "text-[10px]", children: "spoed" }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void zetPrioriteit(orderRef, spoed ? 0 : 100), children: [_jsx(ArrowUpCircle, { className: "mr-1 h-4 w-4" }), spoed ? "Normaal" : "Spoed"] }), actief && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void annuleerOrder(orderRef), children: [_jsx(Ban, { className: "mr-1 h-4 w-4" }), "Annuleren"] })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void verwijderOrder(orderRef), children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: "#" }), _jsx(TableHead, { children: "Taak" }), _jsx(TableHead, { children: "Module" }), _jsx(TableHead, { children: "Start" }), _jsx(TableHead, { children: "Gereed" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "text-right", children: "Actie" })] }) }), _jsx(TableBody, { children: orderTaken.map((t) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-muted-foreground", children: t.sequence + 1 }), _jsxs(TableCell, { className: "font-medium", children: [TASK_LABEL[t.task_type] ?? t.task_type, t.shadow && (_jsx(Badge, { variant: "outline", className: "ml-2 text-[10px]", children: "schaduw" }))] }), _jsx(TableCell, { className: "text-sm", children: LOGISTICS_MODULES[t.owner_module] ?? t.owner_module }), _jsxs(TableCell, { className: "text-sm", children: [tijd(t.planned_start), t.payload?.["pickWindowShifted"] === true && (_jsx(Badge, { variant: "outline", className: "ml-2 text-[10px]", title: "Het berekende moment lag in het verleden; deze taak is naar nu verschoven.", children: "te laat aangemeld" }))] }), _jsx(TableCell, { className: "text-sm", children: tijd(t.planned_end) }), _jsxs(TableCell, { children: [_jsx(Badge, { variant: statusVariant(t.status), children: TASK_STATUS_LABEL[t.status] ?? t.status }), t.claimed_by && (t.status === "claimed" || t.status === "in_progress") && (_jsxs("span", { className: "text-muted-foreground ml-2 text-xs", children: ["door ", t.claimed_by] })), t.error && _jsx("span", { className: "text-destructive ml-2 text-xs", children: t.error })] }), _jsxs(TableCell, { className: "space-x-1 text-right", children: [t.status === "planned" && (_jsx(Button, { variant: "outline", size: "sm", onClick: () => void claimTaak(t.id), children: "Claimen" })), (t.status === "claimed" || t.status === "in_progress") && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => void rondTaakAf(t.id), children: "Afronden" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void meldTaakMislukt(t.id, "Handmatig gemeld vanuit regieoverzicht"), children: "Mislukt" })] }))] })] }, t.id))) })] })] }, orderRef));
@@ -1 +1 @@
1
- {"version":3,"file":"RuleEnginePage.d.ts","sourceRoot":"","sources":["../../src/components/RuleEnginePage.tsx"],"names":[],"mappings":"AAmEA,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAQD,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EAAE,cAAqB,EACjC,YAAY,EAAE,gBAAuB,GACtC,GAAE,mBAAwB,+BAyZ1B"}
1
+ {"version":3,"file":"RuleEnginePage.d.ts","sourceRoot":"","sources":["../../src/components/RuleEnginePage.tsx"],"names":[],"mappings":"AAmEA,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAQD,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EAAE,cAAqB,EACjC,YAAY,EAAE,gBAAuB,GACtC,GAAE,mBAAwB,+BA2b1B"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // ============================================================================
3
3
  // src/components/RuleEnginePage.tsx — logistieke werkwijze instellen
4
4
  // ============================================================================
@@ -42,6 +42,7 @@ export function RuleEnginePage({ strategyId: strategyIdProp = null, strategyName
42
42
  const strategyName = strategyNameProp ?? urlSearch.naam ?? null;
43
43
  const { versions, activeConfig, loading, saving, error, createConfig, patchSettings, saveConfig, activateConfig, restoreVersion, deleteConfig, } = useRuleEngine({ strategyId, strategyName });
44
44
  const [stepIndex, setStepIndex] = useState(0);
45
+ const [sideOpen, setSideOpen] = useState(false);
45
46
  const [createOpen, setCreateOpen] = useState(false);
46
47
  const [newName, setNewName] = useState("");
47
48
  const [newDescription, setNewDescription] = useState("");
@@ -98,7 +99,7 @@ export function RuleEnginePage({ strategyId: strategyIdProp = null, strategyName
98
99
  toast.success("Opgeslagen — de herberekening loopt op de achtergrond");
99
100
  }
100
101
  catch (e) {
101
- toast.error("Opnieuw berekenen is niet gelukt. Probeer het nog een keer.");
102
+ toast.error(e?.message ?? "Opnieuw berekenen is niet gelukt. Probeer het nog een keer.");
102
103
  }
103
104
  finally {
104
105
  setRecalculating(false);
@@ -120,7 +121,9 @@ export function RuleEnginePage({ strategyId: strategyIdProp = null, strategyName
120
121
  ? "Bezig…"
121
122
  : "Opslaan & opnieuw berekenen"] })] })] }), error && (_jsx("p", { className: "rounded-md border border-destructive/50 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: "De gegevens konden niet worden geladen. Probeer het opnieuw." })), showJobLine && (_jsxs("div", { className: `rounded-md border px-3 py-2 text-sm ${job?.status === "error" ? "border-destructive/50 bg-destructive/5 text-destructive" : "border-border"}`, children: [_jsx("span", { children: running
122
123
  ? `${phaseLabel(job?.phase)} — ${job?.processed ?? 0} verwerkt`
123
- : "De laatste berekening is niet volledig gelukt." }), jobProblems.length > 0 && (_jsx("ul", { className: "mt-1 list-disc pl-5 text-xs", children: jobProblems.map((p, i) => (_jsx("li", { children: p }, i))) }))] })), !loading && activeConfig && !activeConfig.is_active && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-md border border-amber-500/60 bg-amber-500/5 px-3 py-2 text-sm", children: [_jsxs("span", { className: "flex items-start gap-2", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }), "Deze werkwijze staat nog op ", _jsx("strong", { children: "concept" }), " en telt nog niet mee bij het inplannen."] }), _jsxs(Button, { size: "sm", onClick: () => activateConfig(activeConfig.id), children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), "Nu activeren"] })] })), loading || (strategyId && !activeConfig && !error) ? (_jsxs("div", { className: "space-y-3", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-64 w-full" })] })) : !activeConfig ? (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Nog geen werkwijze" }), _jsx(CardDescription, { children: "Maak een werkwijze aan om vast te leggen hoe jouw orders verwerkt worden." })] }), _jsx(CardContent, { children: _jsxs(Button, { onClick: () => setCreateOpen(true), children: [_jsx(Plus, { className: "h-4 w-4" }), "Werkwijze aanmaken"] }) })] })) : (_jsxs("div", { className: "grid gap-6 lg:h-[calc(100vh-13rem)] lg:grid-cols-[220px_minmax(0,1fr)_320px]", children: [_jsx("nav", { className: "space-y-1 lg:overflow-y-auto lg:pr-1", children: WIZARD_STEPS.map((item, index) => (_jsxs("button", { type: "button", onClick: () => setStepIndex(index), className: `flex w-full items-center gap-3 rounded-lg border p-2.5 text-left transition-colors ${index === stepIndex
124
+ : "De laatste berekening is niet volledig gelukt." }), jobProblems.length > 0 && (_jsx("ul", { className: "mt-1 list-disc pl-5 text-xs", children: jobProblems.map((p, i) => (_jsx("li", { children: p }, i))) }))] })), !loading && activeConfig && !activeConfig.is_active && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-md border border-amber-500/60 bg-amber-500/5 px-3 py-2 text-sm", children: [_jsxs("span", { className: "flex items-start gap-2", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }), "Deze werkwijze staat nog op ", _jsx("strong", { children: "concept" }), " en telt nog niet mee bij het inplannen."] }), _jsxs(Button, { size: "sm", onClick: () => activateConfig(activeConfig.id), children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), "Nu activeren"] })] })), loading || (strategyId && !activeConfig && !error) ? (_jsxs("div", { className: "space-y-3", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-64 w-full" })] })) : !activeConfig ? (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Nog geen werkwijze" }), _jsx(CardDescription, { children: "Maak een werkwijze aan om vast te leggen hoe jouw orders verwerkt worden." })] }), _jsx(CardContent, { children: _jsxs(Button, { onClick: () => setCreateOpen(true), children: [_jsx(Plus, { className: "h-4 w-4" }), "Werkwijze aanmaken"] }) })] })) : (_jsxs("div", { className: `grid gap-6 lg:h-[calc(100vh-13rem)] ${sideOpen
125
+ ? "lg:grid-cols-[220px_minmax(0,1fr)_320px]"
126
+ : "lg:grid-cols-[220px_minmax(0,1fr)_44px]"}`, children: [_jsx("nav", { className: "space-y-1 lg:overflow-y-auto lg:pr-1", children: WIZARD_STEPS.map((item, index) => (_jsxs("button", { type: "button", onClick: () => setStepIndex(index), className: `flex w-full items-center gap-3 rounded-lg border p-2.5 text-left transition-colors ${index === stepIndex
124
127
  ? "border-primary bg-primary/5"
125
128
  : "border-transparent hover:bg-muted"}`, children: [_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold", children: index + 1 }), _jsx("span", { className: "text-sm font-medium", children: item.label })] }, item.id))) }), _jsxs("div", { className: "space-y-4 lg:min-h-0 lg:overflow-y-auto lg:pr-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { className: "text-base", children: step.label }), _jsx(CardDescription, { children: step.description })] }), _jsx(Badge, { variant: activeConfig.is_active ? "default" : "secondary", children: activeConfig.is_active ? "Actief" : "Concept" })] }) }), _jsx(CardContent, { className: "space-y-3", children: step.groups.map((group, groupIndex) => {
126
129
  const visibleFields = group.fields.filter((field) => fieldVisible(field, stepValues, settings));
@@ -130,5 +133,5 @@ export function RuleEnginePage({ strategyId: strategyIdProp = null, strategyName
130
133
  const advancedFields = visibleFields.filter((f) => isAdvancedField(f));
131
134
  const isLastGroup = groupIndex === step.groups.length - 1;
132
135
  return (_jsxs(CollapsibleGroup, { title: group.label, help: group.description, summary: groupSummary(group, stepValues), defaultOpen: groupIndex === 0, children: [basicFields.length > 0 && (_jsx("div", { className: "grid gap-4 md:grid-cols-2", children: basicFields.map((field) => (_jsx("div", { className: fieldClass(field), children: _jsx(RuleFieldInput, { field: field, value: stepValues[field.key], stepValues: stepValues, onChange: (value) => setFieldValue(field.key, value) }) }, field.key))) })), (advancedFields.length > 0 || (step.showConditions && isLastGroup)) && (_jsxs("details", { className: "rounded-lg border border-dashed border-border", children: [_jsx("summary", { className: "cursor-pointer px-3 py-2 text-sm font-medium text-muted-foreground", children: "Geavanceerd" }), _jsxs("div", { className: "space-y-4 border-t border-border px-3 py-4", children: [advancedFields.length > 0 && (_jsx("div", { className: "grid gap-4 md:grid-cols-2", children: advancedFields.map((field) => (_jsx("div", { className: fieldClass(field), children: _jsx(RuleFieldInput, { field: field, value: stepValues[field.key], stepValues: stepValues, onChange: (value) => setFieldValue(field.key, value) }) }, field.key))) })), step.showConditions && isLastGroup && (_jsx(ConditionsEditor, { conditions: stepConditions, onChange: setConditions }))] })] }))] }, group.id));
133
- }) })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => setStepIndex((i) => Math.max(0, i - 1)), disabled: stepIndex === 0, children: [_jsx(ChevronLeft, { className: "h-4 w-4" }), "Vorige"] }), _jsxs(Button, { variant: "outline", onClick: () => setStepIndex((i) => Math.min(WIZARD_STEPS.length - 1, i + 1)), disabled: stepIndex === WIZARD_STEPS.length - 1, children: ["Volgende", _jsx(ChevronRight, { className: "h-4 w-4" })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { variant: "outline", disabled: saving, onClick: () => saveConfig(activeConfig), children: [_jsx(Save, { className: "h-4 w-4" }), "Opslaan"] }), _jsxs(Button, { disabled: saving || activeConfig.is_active, onClick: () => activateConfig(activeConfig.id), children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), activeConfig.is_active ? "Actief" : "Activeren"] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Meer acties", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { onClick: () => deleteConfig(activeConfig.id), children: [_jsx(Trash2, { className: "h-4 w-4" }), "Werkwijze verwijderen"] }) })] })] })] })] }), _jsx("div", { className: "lg:min-h-0 lg:overflow-y-auto lg:pr-1", children: _jsxs(Tabs, { defaultValue: "impact", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { className: "flex-1", value: "impact", children: "Aandachtspunten" }), _jsx(TabsTrigger, { className: "flex-1", value: "versies", children: "Eerdere versies" })] }), _jsx(TabsContent, { value: "impact", className: "mt-3", children: _jsx(ImpactPanel, { settings: settings }) }), _jsx(TabsContent, { value: "versies", className: "mt-3", children: _jsx(VersionsPanel, { versions: versions, currentSettings: settings, onRestore: restoreVersion }) })] }) })] })), _jsx(Dialog, { open: createOpen, onOpenChange: setCreateOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Nieuwe werkwijze" }), _jsx(DialogDescription, { children: "Elke werkwijze beschrijft hoe orders bij jou verwerkt worden." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Bijv. Standaard werkwijze" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Omschrijving" }), _jsx(Textarea, { value: newDescription, onChange: (e) => setNewDescription(e.target.value), placeholder: "Waarvoor gebruik je deze werkwijze?" })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setCreateOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: handleCreate, disabled: !newName.trim(), children: "Aanmaken" })] })] }) })] }));
136
+ }) })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => setStepIndex((i) => Math.max(0, i - 1)), disabled: stepIndex === 0, children: [_jsx(ChevronLeft, { className: "h-4 w-4" }), "Vorige"] }), _jsxs(Button, { variant: "outline", onClick: () => setStepIndex((i) => Math.min(WIZARD_STEPS.length - 1, i + 1)), disabled: stepIndex === WIZARD_STEPS.length - 1, children: ["Volgende", _jsx(ChevronRight, { className: "h-4 w-4" })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { variant: "outline", disabled: saving, onClick: () => saveConfig(activeConfig), children: [_jsx(Save, { className: "h-4 w-4" }), "Opslaan"] }), _jsxs(Button, { disabled: saving || activeConfig.is_active, onClick: () => activateConfig(activeConfig.id), children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), activeConfig.is_active ? "Actief" : "Activeren"] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Meer acties", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { onClick: () => deleteConfig(activeConfig.id), children: [_jsx(Trash2, { className: "h-4 w-4" }), "Werkwijze verwijderen"] }) })] })] })] })] }), _jsx("div", { className: "lg:min-h-0 lg:overflow-y-auto lg:pr-1", children: sideOpen ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "mb-2 flex justify-end", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Paneel inklappen", onClick: () => setSideOpen(false), children: _jsx(ChevronRight, { className: "h-4 w-4" }) }) }), _jsxs(Tabs, { defaultValue: "impact", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { className: "flex-1", value: "impact", children: "Aandachtspunten" }), _jsx(TabsTrigger, { className: "flex-1", value: "versies", children: "Eerdere versies" })] }), _jsx(TabsContent, { value: "impact", className: "mt-3", children: _jsx(ImpactPanel, { settings: settings }) }), _jsx(TabsContent, { value: "versies", className: "mt-3", children: _jsx(VersionsPanel, { versions: versions, currentSettings: settings, onRestore: restoreVersion }) })] })] })) : (_jsx(Button, { variant: "outline", size: "icon", "aria-label": "Paneel uitklappen", onClick: () => setSideOpen(true), children: _jsx(ChevronLeft, { className: "h-4 w-4" }) })) })] })), _jsx(Dialog, { open: createOpen, onOpenChange: setCreateOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Nieuwe werkwijze" }), _jsx(DialogDescription, { children: "Elke werkwijze beschrijft hoe orders bij jou verwerkt worden." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Bijv. Standaard werkwijze" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Omschrijving" }), _jsx(Textarea, { value: newDescription, onChange: (e) => setNewDescription(e.target.value), placeholder: "Waarvoor gebruik je deze werkwijze?" })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setCreateOpen(false), children: "Annuleren" }), _jsx(Button, { onClick: handleCreate, disabled: !newName.trim(), children: "Aanmaken" })] })] }) })] }));
134
137
  }
@@ -0,0 +1,2 @@
1
+ export declare function FloridayConnectionBanner(): import("react").JSX.Element;
2
+ //# sourceMappingURL=FloridayConnectionBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloridayConnectionBanner.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/FloridayConnectionBanner.tsx"],"names":[],"mappings":"AAYA,wBAAgB,wBAAwB,gCAwDvC"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/rule-engine/FloridayConnectionBanner.tsx
4
+ // Toont welke Floriday-koppeling de documenten levert. De gegevens komen
5
+ // volledig uit de authenticatie-module; deze module kiest niets zelf.
6
+ // ============================================================================
7
+ import { useQuery } from "@tanstack/react-query";
8
+ import { Badge, Skeleton } from "@flowselections/core";
9
+ import { AlertTriangle, Link2 } from "lucide-react";
10
+ import { supabase } from "../../integrations/supabase/client";
11
+ import { getFloridayConnection } from "../../lib/floridayDocuments.functions";
12
+ export function FloridayConnectionBanner() {
13
+ const { data, isLoading } = useQuery({
14
+ queryKey: ["floriday-connection"],
15
+ queryFn: async () => {
16
+ try {
17
+ // Zonder sessie geeft de server-function een 401-Response terug; die
18
+ // mag nooit als throw naar de router lekken (blanco scherm).
19
+ const { data: sessionData } = await supabase.auth.getSession();
20
+ if (!sessionData.session) {
21
+ return { ok: false, error: "Geen actieve sessie." };
22
+ }
23
+ return await getFloridayConnection();
24
+ }
25
+ catch (e) {
26
+ // Geen sessie of gateway-fout: banner toont de foutstaat, geen crash.
27
+ const message = e instanceof Response ? `HTTP ${e.status}` : String(e?.message ?? e);
28
+ return { ok: false, error: message };
29
+ }
30
+ },
31
+ retry: false,
32
+ staleTime: 60000,
33
+ });
34
+ if (isLoading)
35
+ return _jsx(Skeleton, { className: "h-14 w-full rounded-lg" });
36
+ if (!data || data.ok !== true) {
37
+ return (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/5 p-3", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 text-destructive" }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-medium text-destructive", children: "Geen actieve Floriday-koppeling" }), _jsx("p", { className: "text-muted-foreground", children: "Documenten kunnen niet worden opgehaald zolang de Floriday-authenticatiemodule geen actieve koppeling heeft." })] })] }));
38
+ }
39
+ const c = data.connection;
40
+ return (_jsxs("div", { className: "flex flex-wrap items-center gap-3 rounded-lg border border-border bg-muted/40 p-3", children: [_jsx(Link2, { className: "h-4 w-4 text-muted-foreground" }), _jsxs("div", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: c?.label ?? c?.organization_name ?? "Floriday" }), c?.gln_code && _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 GLN ", c.gln_code] })] }), _jsx(Badge, { variant: data.environment === "live" ? "default" : "secondary", children: data.environment === "live" ? "Live" : "Staging" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Documenten worden opgehaald via de Floriday-authenticatiemodule." })] }));
41
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"RuleFieldInput.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/RuleFieldInput.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAM3E,UAAU,KAAK;IACb,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,sCAwT3E"}
1
+ {"version":3,"file":"RuleFieldInput.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/RuleFieldInput.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAQ3E,UAAU,KAAK;IACb,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,sCA+V3E"}
@@ -9,9 +9,11 @@ import { Input, Label, Switch, Select, SelectContent, SelectItem, SelectTrigger,
9
9
  import { fieldHelp } from "../../lib/ruleEngineWizard";
10
10
  import { FieldHelp, FieldLabel } from "./FieldHelp";
11
11
  import { usePickstations } from "../../hooks/usePickstations";
12
+ import { useScannersPrinters } from "../../hooks/useScannersPrinters";
12
13
  import { useDocumentLayouts } from "../../hooks/useDocumentLayouts";
13
14
  export function RuleFieldInput({ field, value, onChange, stepValues }) {
14
15
  const { stations, types, loading } = usePickstations();
16
+ const { scanners, printers, loading: devicesLoading } = useScannersPrinters();
15
17
  const { layouts: matching, allLayouts, autoLayout, loading: layoutsLoading, } = useDocumentLayouts(field.layoutTypes);
16
18
  const help = fieldHelp(field);
17
19
  if (field.type === "guidance") {
@@ -35,6 +37,13 @@ export function RuleFieldInput({ field, value, onChange, stepValues }) {
35
37
  return (_jsxs("div", { className: "space-y-2", children: [_jsx(FieldLabel, { label: field.label, help: help ??
36
38
  "Het pickstation waar dit document hoort. De pickschermen koppelen dat station aan een printplek uit de Layout maker; die printplek bepaalt de printer." }), _jsxs(Select, { value: String(value ?? ""), onValueChange: (v) => onChange(v === "__none__" ? "" : v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? "Laden…" : "Kies een pickstation" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "Niet instellen" }), options.map((option) => (_jsx(SelectItem, { value: option.id, children: option.name }, option.id)))] })] }), !loading && options.length === 0 && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Er zijn nog geen pickstations ingericht." }))] }));
37
39
  }
40
+ if (field.type === "scanner" || field.type === "printer") {
41
+ const isScanner = field.type === "scanner";
42
+ const options = isScanner ? scanners : printers;
43
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(FieldLabel, { label: field.label, help: help }), _jsxs(Select, { value: String(value ?? ""), onValueChange: (v) => onChange(v === "__none__" ? "" : v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: devicesLoading ? "Laden…" : isScanner ? "Kies een scanner" : "Kies een printer" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "Niet instellen" }), options.map((option) => (_jsxs(SelectItem, { value: option.id, children: [option.name, option.hint ? ` · ${option.hint}` : ""] }, option.id)))] })] }), !devicesLoading && options.length === 0 && (_jsx("p", { className: "text-xs text-destructive", children: isScanner
44
+ ? "Er staan nog geen actieve scanners in het systeem. Zonder scanner kan er niet gecontroleerd worden."
45
+ : "Er staan nog geen actieve printers in het systeem. Zonder printer blijft de afleverbrief wachten." }))] }));
46
+ }
38
47
  if (field.type === "days") {
39
48
  const selected = Array.isArray(value) ? value : [];
40
49
  const days = field.options ?? [];
@@ -0,0 +1,28 @@
1
+ export interface GeblokkeerdeOrder {
2
+ queueId: string;
3
+ orderId: string;
4
+ orderRef: string;
5
+ deliveryDate: string | null;
6
+ reason: string | null;
7
+ reasonCode: string;
8
+ attempts: number;
9
+ blockedSince: string | null;
10
+ }
11
+ export interface BlokkadeOverzicht {
12
+ blocked: number;
13
+ pending: number;
14
+ lastTickAt: string | null;
15
+ tickStale: boolean;
16
+ }
17
+ export declare const REDEN_LABEL: Record<string, string>;
18
+ export declare function redenLabel(code: string | null | undefined): string;
19
+ export declare function useGeblokkeerdeOrders(): {
20
+ rows: GeblokkeerdeOrder[];
21
+ overzicht: BlokkadeOverzicht | null;
22
+ loading: boolean;
23
+ error: string | null;
24
+ busy: boolean;
25
+ refresh: () => Promise<void>;
26
+ retry: (orderIds: string[]) => Promise<number>;
27
+ };
28
+ //# sourceMappingURL=useGeblokkeerdeOrders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGeblokkeerdeOrders.d.ts","sourceRoot":"","sources":["../../src/hooks/useGeblokkeerdeOrders.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAY9C,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAGzD;AAED,wBAAgB,qBAAqB;;;;;;;sBAqDhB,MAAM,EAAE;EAmB5B"}
@@ -0,0 +1,94 @@
1
+ // ============================================================================
2
+ // src/hooks/useGeblokkeerdeOrders.ts — geblokkeerde orders ophalen en opnieuw
3
+ // aanbieden. De Engine is de enige bron: alles komt uit de database-functies
4
+ // logistics_blocked_orders / logistics_blocked_overview / logistics_plan_retry.
5
+ // ============================================================================
6
+ import { useCallback, useEffect, useState } from "react";
7
+ import { supabase } from "../integrations/supabase/moduleClient";
8
+ export const REDEN_LABEL = {
9
+ belading_onbekend: "Belading onbekend",
10
+ belading_onvolledig: "Belading onvolledig",
11
+ beladingsprofiel_ontbreekt: "Beladingsprofiel ontbreekt",
12
+ afmetingen_onbekend: "Afmetingen onbekend",
13
+ afmetingen_plan_ongeldig: "Afmetingenplan ongeldig",
14
+ kargrens_overschreden: "Te veel karren",
15
+ geen_karren: "Geen karren berekend",
16
+ verificatie_mislukt: "Controle mislukt",
17
+ bufferdekking_onvolledig: "Buffer niet volledig gedekt",
18
+ karindeling_geblokkeerd: "Karindeling geblokkeerd",
19
+ other: "Overige reden",
20
+ };
21
+ export function redenLabel(code) {
22
+ if (!code)
23
+ return REDEN_LABEL.other;
24
+ return REDEN_LABEL[code] ?? code;
25
+ }
26
+ export function useGeblokkeerdeOrders() {
27
+ const [rows, setRows] = useState([]);
28
+ const [overzicht, setOverzicht] = useState(null);
29
+ const [loading, setLoading] = useState(true);
30
+ const [error, setError] = useState(null);
31
+ const [busy, setBusy] = useState(false);
32
+ const load = useCallback(async () => {
33
+ setLoading(true);
34
+ setError(null);
35
+ try {
36
+ const [list, summary] = await Promise.all([
37
+ supabase.rpc("logistics_blocked_orders"),
38
+ supabase.rpc("logistics_blocked_overview"),
39
+ ]);
40
+ if (list.error)
41
+ throw new Error(list.error.message);
42
+ if (summary.error)
43
+ throw new Error(summary.error.message);
44
+ const safe = Array.isArray(list.data) ? list.data : [];
45
+ setRows(safe.map((r) => ({
46
+ queueId: String(r.queue_id),
47
+ orderId: String(r.order_id),
48
+ orderRef: String(r.order_ref ?? r.order_id),
49
+ deliveryDate: r.delivery_date ?? null,
50
+ reason: r.reason ?? null,
51
+ reasonCode: String(r.reason_code ?? "other"),
52
+ attempts: Number(r.attempts ?? 0),
53
+ blockedSince: r.blocked_since ?? null,
54
+ })));
55
+ const o = (summary.data ?? {});
56
+ setOverzicht({
57
+ blocked: Number(o.blocked ?? 0),
58
+ pending: Number(o.pending ?? 0),
59
+ lastTickAt: o.lastTickAt ?? null,
60
+ tickStale: Boolean(o.tickStale ?? true),
61
+ });
62
+ }
63
+ catch (e) {
64
+ setError(e?.message ? String(e.message) : "Kon de geblokkeerde orders niet ophalen.");
65
+ }
66
+ finally {
67
+ setLoading(false);
68
+ }
69
+ }, []);
70
+ useEffect(() => {
71
+ void load();
72
+ const timer = setInterval(() => void load(), 30000);
73
+ return () => clearInterval(timer);
74
+ }, [load]);
75
+ const retry = useCallback(async (orderIds) => {
76
+ const ids = Array.isArray(orderIds) ? orderIds.filter(Boolean) : [];
77
+ if (ids.length === 0)
78
+ return 0;
79
+ setBusy(true);
80
+ try {
81
+ const { data, error: rpcError } = await supabase.rpc("logistics_plan_retry", {
82
+ p_order_ids: ids,
83
+ });
84
+ if (rpcError)
85
+ throw new Error(rpcError.message);
86
+ await load();
87
+ return Number(data ?? 0);
88
+ }
89
+ finally {
90
+ setBusy(false);
91
+ }
92
+ }, [load]);
93
+ return { rows, overzicht, loading, error, busy, refresh: load, retry };
94
+ }