@flowselections/logistiek-engine 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +24 -0
  2. package/dist-lib/_core-safelist.d.ts +2 -0
  3. package/dist-lib/_core-safelist.d.ts.map +1 -0
  4. package/dist-lib/_core-safelist.js +15 -0
  5. package/dist-lib/components/BeladingIndelingCard.d.ts +2 -0
  6. package/dist-lib/components/BeladingIndelingCard.d.ts.map +1 -0
  7. package/dist-lib/components/BeladingIndelingCard.js +77 -0
  8. package/dist-lib/components/BufferDebugCard.d.ts +19 -0
  9. package/dist-lib/components/BufferDebugCard.d.ts.map +1 -0
  10. package/dist-lib/components/BufferDebugCard.js +69 -0
  11. package/dist-lib/components/CapaciteitMeldingenCard.d.ts +2 -0
  12. package/dist-lib/components/CapaciteitMeldingenCard.d.ts.map +1 -0
  13. package/dist-lib/components/CapaciteitMeldingenCard.js +40 -0
  14. package/dist-lib/components/LadingdragersPage.d.ts +2 -0
  15. package/dist-lib/components/LadingdragersPage.d.ts.map +1 -0
  16. package/dist-lib/components/LadingdragersPage.js +102 -0
  17. package/dist-lib/components/LogistiekEngineLogo.d.ts +6 -0
  18. package/dist-lib/components/LogistiekEngineLogo.d.ts.map +1 -0
  19. package/dist-lib/components/LogistiekEngineLogo.js +4 -0
  20. package/dist-lib/components/LogistiekEnginePage.d.ts +2 -0
  21. package/dist-lib/components/LogistiekEnginePage.d.ts.map +1 -0
  22. package/dist-lib/components/LogistiekEnginePage.js +53 -0
  23. package/dist-lib/components/ModulePromptsPage.d.ts +2 -0
  24. package/dist-lib/components/ModulePromptsPage.d.ts.map +1 -0
  25. package/dist-lib/components/ModulePromptsPage.js +58 -0
  26. package/dist-lib/components/RegiePage.d.ts +2 -0
  27. package/dist-lib/components/RegiePage.d.ts.map +1 -0
  28. package/dist-lib/components/RegiePage.js +82 -0
  29. package/dist-lib/components/RuleEnginePage.d.ts +7 -0
  30. package/dist-lib/components/RuleEnginePage.d.ts.map +1 -0
  31. package/dist-lib/components/RuleEnginePage.js +114 -0
  32. package/dist-lib/components/StrategieDialog.d.ts +11 -0
  33. package/dist-lib/components/StrategieDialog.d.ts.map +1 -0
  34. package/dist-lib/components/StrategieDialog.js +38 -0
  35. package/dist-lib/components/VertragingMeldingenCard.d.ts +2 -0
  36. package/dist-lib/components/VertragingMeldingenCard.d.ts.map +1 -0
  37. package/dist-lib/components/VertragingMeldingenCard.js +32 -0
  38. package/dist-lib/components/VoorraadMeldingenCard.d.ts +2 -0
  39. package/dist-lib/components/VoorraadMeldingenCard.d.ts.map +1 -0
  40. package/dist-lib/components/VoorraadMeldingenCard.js +26 -0
  41. package/dist-lib/components/rule-engine/CollapsibleGroup.d.ts +12 -0
  42. package/dist-lib/components/rule-engine/CollapsibleGroup.d.ts.map +1 -0
  43. package/dist-lib/components/rule-engine/CollapsibleGroup.js +11 -0
  44. package/dist-lib/components/rule-engine/ConditionsEditor.d.ts +8 -0
  45. package/dist-lib/components/rule-engine/ConditionsEditor.d.ts.map +1 -0
  46. package/dist-lib/components/rule-engine/ConditionsEditor.js +31 -0
  47. package/dist-lib/components/rule-engine/FieldHelp.d.ts +14 -0
  48. package/dist-lib/components/rule-engine/FieldHelp.d.ts.map +1 -0
  49. package/dist-lib/components/rule-engine/FieldHelp.js +18 -0
  50. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +2 -0
  51. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +1 -0
  52. package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +41 -0
  53. package/dist-lib/components/rule-engine/ImpactPanel.d.ts +5 -0
  54. package/dist-lib/components/rule-engine/ImpactPanel.d.ts.map +1 -0
  55. package/dist-lib/components/rule-engine/ImpactPanel.js +25 -0
  56. package/dist-lib/components/rule-engine/RuleFieldInput.d.ts +9 -0
  57. package/dist-lib/components/rule-engine/RuleFieldInput.d.ts.map +1 -0
  58. package/dist-lib/components/rule-engine/RuleFieldInput.js +80 -0
  59. package/dist-lib/components/rule-engine/VersionsPanel.d.ts +10 -0
  60. package/dist-lib/components/rule-engine/VersionsPanel.d.ts.map +1 -0
  61. package/dist-lib/components/rule-engine/VersionsPanel.js +16 -0
  62. package/dist-lib/components/settings/LogistiekEngineSettingsCard.d.ts +2 -0
  63. package/dist-lib/components/settings/LogistiekEngineSettingsCard.d.ts.map +1 -0
  64. package/dist-lib/components/settings/LogistiekEngineSettingsCard.js +22 -0
  65. package/dist-lib/hooks/useCapaciteitMeldingen.d.ts +20 -0
  66. package/dist-lib/hooks/useCapaciteitMeldingen.d.ts.map +1 -0
  67. package/dist-lib/hooks/useCapaciteitMeldingen.js +46 -0
  68. package/dist-lib/hooks/useDocumentLayouts.d.ts +13 -0
  69. package/dist-lib/hooks/useDocumentLayouts.d.ts.map +1 -0
  70. package/dist-lib/hooks/useDocumentLayouts.js +54 -0
  71. package/dist-lib/hooks/useLadingdragers.d.ts +16 -0
  72. package/dist-lib/hooks/useLadingdragers.d.ts.map +1 -0
  73. package/dist-lib/hooks/useLadingdragers.js +86 -0
  74. package/dist-lib/hooks/useLogistiekStrategieen.d.ts +29 -0
  75. package/dist-lib/hooks/useLogistiekStrategieen.d.ts.map +1 -0
  76. package/dist-lib/hooks/useLogistiekStrategieen.js +111 -0
  77. package/dist-lib/hooks/useLogistiekTaken.d.ts +89 -0
  78. package/dist-lib/hooks/useLogistiekTaken.d.ts.map +1 -0
  79. package/dist-lib/hooks/useLogistiekTaken.js +314 -0
  80. package/dist-lib/hooks/usePickstations.d.ts +10 -0
  81. package/dist-lib/hooks/usePickstations.d.ts.map +1 -0
  82. package/dist-lib/hooks/usePickstations.js +51 -0
  83. package/dist-lib/hooks/useRecalcJob.d.ts +24 -0
  84. package/dist-lib/hooks/useRecalcJob.d.ts.map +1 -0
  85. package/dist-lib/hooks/useRecalcJob.js +83 -0
  86. package/dist-lib/hooks/useRuleEngine.d.ts +48 -0
  87. package/dist-lib/hooks/useRuleEngine.d.ts.map +1 -0
  88. package/dist-lib/hooks/useRuleEngine.js +222 -0
  89. package/dist-lib/hooks/useVertragingMeldingen.d.ts +29 -0
  90. package/dist-lib/hooks/useVertragingMeldingen.d.ts.map +1 -0
  91. package/dist-lib/hooks/useVertragingMeldingen.js +68 -0
  92. package/dist-lib/hooks/useVoorraadMeldingen.d.ts +30 -0
  93. package/dist-lib/hooks/useVoorraadMeldingen.d.ts.map +1 -0
  94. package/dist-lib/hooks/useVoorraadMeldingen.js +70 -0
  95. package/dist-lib/index.d.ts +36 -0
  96. package/dist-lib/index.d.ts.map +1 -0
  97. package/dist-lib/index.js +52 -0
  98. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  99. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  100. package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
  101. package/dist-lib/integrations/supabase/auth-middleware.d.ts +19781 -0
  102. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  103. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  104. package/dist-lib/integrations/supabase/client.d.ts +19777 -0
  105. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  106. package/dist-lib/integrations/supabase/client.js +13 -0
  107. package/dist-lib/integrations/supabase/client.server.d.ts +19777 -0
  108. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  109. package/dist-lib/integrations/supabase/client.server.js +30 -0
  110. package/dist-lib/integrations/supabase/types.d.ts +20355 -0
  111. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  112. package/dist-lib/integrations/supabase/types.js +78 -0
  113. package/dist-lib/lib/bufferMatching.d.ts +26 -0
  114. package/dist-lib/lib/bufferMatching.d.ts.map +1 -0
  115. package/dist-lib/lib/bufferMatching.js +73 -0
  116. package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts +23 -0
  117. package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts.map +1 -0
  118. package/dist-lib/lib/bulkLoadCarrierPlanning.server.js +126 -0
  119. package/dist-lib/lib/floridayDocuments.functions.d.ts +59415 -0
  120. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -0
  121. package/dist-lib/lib/floridayDocuments.functions.js +68 -0
  122. package/dist-lib/lib/floridayDocuments.server.d.ts +51 -0
  123. package/dist-lib/lib/floridayDocuments.server.d.ts.map +1 -0
  124. package/dist-lib/lib/floridayDocuments.server.js +109 -0
  125. package/dist-lib/lib/loadCarrierPlanner.d.ts +71 -0
  126. package/dist-lib/lib/loadCarrierPlanner.d.ts.map +1 -0
  127. package/dist-lib/lib/loadCarrierPlanner.js +374 -0
  128. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +98984 -0
  129. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -0
  130. package/dist-lib/lib/loadCarrierPlanning.functions.js +106 -0
  131. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +46 -0
  132. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -0
  133. package/dist-lib/lib/loadCarrierPlanning.server.js +515 -0
  134. package/dist-lib/lib/loadCarrierTaskSync.server.d.ts +3 -0
  135. package/dist-lib/lib/loadCarrierTaskSync.server.d.ts.map +1 -0
  136. package/dist-lib/lib/loadCarrierTaskSync.server.js +56 -0
  137. package/dist-lib/lib/loadCarrierTypes.d.ts +19 -0
  138. package/dist-lib/lib/loadCarrierTypes.d.ts.map +1 -0
  139. package/dist-lib/lib/loadCarrierTypes.js +41 -0
  140. package/dist-lib/lib/logisticsPlanner.d.ts +56 -0
  141. package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -0
  142. package/dist-lib/lib/logisticsPlanner.js +273 -0
  143. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +39575 -0
  144. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -0
  145. package/dist-lib/lib/logisticsRecalcJob.functions.js +30 -0
  146. package/dist-lib/lib/logisticsRecalcJob.server.d.ts +32 -0
  147. package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -0
  148. package/dist-lib/lib/logisticsRecalcJob.server.js +116 -0
  149. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +19792 -0
  150. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -0
  151. package/dist-lib/lib/logisticsRecalculate.functions.js +35 -0
  152. package/dist-lib/lib/logisticsTasks.d.ts +23 -0
  153. package/dist-lib/lib/logisticsTasks.d.ts.map +1 -0
  154. package/dist-lib/lib/logisticsTasks.js +92 -0
  155. package/dist-lib/lib/modulePrompts.d.ts +31 -0
  156. package/dist-lib/lib/modulePrompts.d.ts.map +1 -0
  157. package/dist-lib/lib/modulePrompts.js +1195 -0
  158. package/dist-lib/lib/ruleEngineImpact.d.ts +27 -0
  159. package/dist-lib/lib/ruleEngineImpact.d.ts.map +1 -0
  160. package/dist-lib/lib/ruleEngineImpact.js +257 -0
  161. package/dist-lib/lib/ruleEngineSchema.d.ts +69 -0
  162. package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -0
  163. package/dist-lib/lib/ruleEngineSchema.js +899 -0
  164. package/dist-lib/lib/ruleEngineWizard.d.ts +17 -0
  165. package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -0
  166. package/dist-lib/lib/ruleEngineWizard.js +204 -0
  167. package/dist-lib/lib/utils.d.ts +3 -0
  168. package/dist-lib/lib/utils.d.ts.map +1 -0
  169. package/dist-lib/lib/utils.js +5 -0
  170. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  171. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  172. package/dist-lib/lib/validationSchemas.js +25 -0
  173. package/dist-lib/styles.css +1 -0
  174. package/package.json +113 -0
  175. package/public/logo.png +0 -0
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/RegiePage.tsx — Regieoverzicht van de Logistics Engine
4
+ // ============================================================================
5
+ // Toont welke taken de Engine heeft uitgedeeld, per order en per module,
6
+ // plus een simulatieveld om te zien wat de Engine met een order zou doen.
7
+ // GEEN <Header /> hier — die levert de shell.
8
+ // ============================================================================
9
+ import { useMemo, useState } from "react";
10
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
11
+ import { ArrowUpCircle, Ban, RefreshCw, RotateCcw, Trash2, Workflow } from "lucide-react";
12
+ import { Link } from "@tanstack/react-router";
13
+ import { useLogistiekTaken } from "../hooks/useLogistiekTaken";
14
+ import { BeladingIndelingCard } from "./BeladingIndelingCard";
15
+ import { VoorraadMeldingenCard } from "./VoorraadMeldingenCard";
16
+ import { VertragingMeldingenCard } from "./VertragingMeldingenCard";
17
+ import { CapaciteitMeldingenCard } from "./CapaciteitMeldingenCard";
18
+ import { LOGISTICS_MODULES, TASK_LABEL, TASK_STATUS_LABEL, } from "../lib/logisticsTasks";
19
+ function tijd(value) {
20
+ if (!value)
21
+ return "—";
22
+ const d = new Date(value);
23
+ if (Number.isNaN(d.getTime()))
24
+ return "—";
25
+ return d.toLocaleString("nl-NL", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
26
+ }
27
+ function statusVariant(status) {
28
+ if (status === "done")
29
+ return "default";
30
+ if (status === "failed" || status === "cancelled")
31
+ return "destructive";
32
+ if (status === "in_progress" || status === "claimed")
33
+ return "secondary";
34
+ return "outline";
35
+ }
36
+ const WAARSCHUWING_LABEL = {
37
+ pick_start_in_past: "Pickstart lag in het verleden",
38
+ pick_window_too_short: "Pickvenster te kort",
39
+ pickup_in_past: "Ophaalmoment verstreken",
40
+ buffer_close_in_past: "Buffer sloot al",
41
+ rule_version_not_found: "Regelversie niet gevonden",
42
+ no_rule_config: "Geen regelset gekoppeld",
43
+ };
44
+ export function RegiePage() {
45
+ const { taken, planRuns, meldingen, loading, laden, verwijderOrder, herverwerkMelding, claimTaak, rondTaakAf, meldTaakMislukt, annuleerOrder, zetPrioriteit, } = useLogistiekTaken();
46
+ const [filter, setFilter] = useState("");
47
+ const safeTaken = Array.isArray(taken) ? taken : [];
48
+ const safeRuns = Array.isArray(planRuns) ? planRuns : [];
49
+ const safeMeldingen = Array.isArray(meldingen) ? meldingen : [];
50
+ const gefilterd = useMemo(() => {
51
+ const q = filter.trim().toLowerCase();
52
+ if (!q)
53
+ return safeTaken;
54
+ return safeTaken.filter((t) => t.order_ref.toLowerCase().includes(q) ||
55
+ t.task_type.toLowerCase().includes(q) ||
56
+ t.owner_module.toLowerCase().includes(q));
57
+ }, [safeTaken, filter]);
58
+ const perOrder = useMemo(() => {
59
+ const map = new Map();
60
+ for (const t of gefilterd) {
61
+ const list = map.get(t.order_ref) ?? [];
62
+ list.push(t);
63
+ map.set(t.order_ref, list);
64
+ }
65
+ return Array.from(map.entries());
66
+ }, [gefilterd]);
67
+ const perModule = useMemo(() => {
68
+ const counts = {};
69
+ for (const t of safeTaken) {
70
+ counts[t.owner_module] = (counts[t.owner_module] ?? 0) + 1;
71
+ }
72
+ return counts;
73
+ }, [safeTaken]);
74
+ 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("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]) => {
75
+ const spoed = orderTaken.some((t) => (t.priority ?? 0) > 0);
76
+ const actief = orderTaken.some((t) => t.status !== "cancelled" && t.status !== "done" && t.status !== "superseded");
77
+ 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 }), _jsx(TableCell, { className: "text-sm", children: tijd(t.planned_start) }), _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));
78
+ })) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Planrondes" }), _jsx(CardDescription, { children: "Elke keer dat de Engine (her)plande, met welke regelversie." })] }), _jsx(CardContent, { children: safeRuns.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nog geen planrondes." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Moment" }), _jsx(TableHead, { children: "Order" }), _jsx(TableHead, { children: "Aanleiding" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { children: "Regelversie" }), _jsx(TableHead, { children: "Taken" }), _jsx(TableHead, { children: "Signalen" })] }) }), _jsx(TableBody, { children: safeRuns.map((r) => {
79
+ const warnings = Array.isArray(r.warnings) ? r.warnings : [];
80
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm", children: tijd(r.created_at) }), _jsx(TableCell, { className: "font-medium", children: r.order_ref }), _jsx(TableCell, { className: "text-sm", children: r.trigger }), _jsx(TableCell, { className: "text-sm", children: r.source_module ?? "—" }), _jsxs(TableCell, { className: "text-sm", children: [r.rule_version ?? "—", r.settings_hash ? (_jsx("span", { className: "text-muted-foreground ml-1 font-mono text-xs", children: r.settings_hash.slice(0, 6) })) : null] }), _jsxs(TableCell, { className: "text-sm", children: [r.tasks_created, " nieuw / ", r.tasks_revised, " herzien"] }), _jsx(TableCell, { className: "text-sm", children: warnings.length === 0 ? (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) : (_jsx("div", { className: "flex flex-wrap gap-1", children: warnings.map((w, i) => (_jsx(Badge, { variant: "outline", children: WAARSCHUWING_LABEL[String(w.code)] ?? String(w.code) }, i))) })) })] }, r.id));
81
+ }) })] })) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Binnengekomen orders" }), _jsx(CardDescription, { children: "Elke binnengekomen order wordt automatisch omgezet naar de bijbehorende werkzaamheden." })] }), _jsx(CardContent, { children: safeMeldingen.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nog geen meldingen ontvangen." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Moment" }), _jsx(TableHead, { children: "Onderdeel" }), _jsx(TableHead, { children: "Soort" }), _jsx(TableHead, { children: "Order" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "w-24" })] }) }), _jsx(TableBody, { children: safeMeldingen.map((m) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm", children: tijd(m.created_at) }), _jsx(TableCell, { className: "text-sm", children: LOGISTICS_MODULES[m.source_module] ?? m.source_module }), _jsx(TableCell, { className: "text-sm", children: m.event_type }), _jsx(TableCell, { className: "font-medium", children: m.order_ref }), _jsxs(TableCell, { children: [_jsx(Badge, { variant: m.status === "failed" ? "destructive" : m.status === "processed" ? "default" : "outline", children: m.status === "processed" ? "Verwerkt" : m.status === "failed" ? "Mislukt" : "Wachtend" }), m.error && _jsx("p", { className: "text-destructive mt-1 text-xs", children: m.error })] }), _jsx(TableCell, { className: "text-right", children: m.status !== "processed" && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => void herverwerkMelding(m.id), children: [_jsx(RotateCcw, { className: "mr-1 h-4 w-4" }), "Opnieuw"] })) })] }, m.id))) })] })) })] }), _jsx(VoorraadMeldingenCard, {}), _jsx(VertragingMeldingenCard, {}), _jsx(CapaciteitMeldingenCard, {}), _jsx(BeladingIndelingCard, {})] }));
82
+ }
@@ -0,0 +1,7 @@
1
+ export interface RuleEnginePageProps {
2
+ /** Configureer alleen de werkwijze van deze strategie */
3
+ strategyId?: string | null;
4
+ strategyName?: string | null;
5
+ }
6
+ export declare function RuleEnginePage({ strategyId, strategyName }?: RuleEnginePageProps): import("react").JSX.Element;
7
+ //# sourceMappingURL=RuleEnginePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RuleEnginePage.d.ts","sourceRoot":"","sources":["../../src/components/RuleEnginePage.tsx"],"names":[],"mappings":"AAiEA,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,EAAE,UAAiB,EAAE,YAAmB,EAAE,GAAE,mBAAwB,+BAkYlG"}
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/RuleEnginePage.tsx — logistieke werkwijze instellen
4
+ // ============================================================================
5
+ import { useMemo, useState } from "react";
6
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Label, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Skeleton, } from "@flowselections/core";
7
+ import { AlertTriangle, CheckCircle2, ChevronLeft, ChevronRight, MoreVertical, Plus, RefreshCw, Save, Trash2, } from "lucide-react";
8
+ import { toast } from "@flowselections/core";
9
+ import { useRecalcJob, phaseLabel } from "../hooks/useRecalcJob";
10
+ import { fieldVisible, } from "../lib/ruleEngineSchema";
11
+ import { WIZARD_STEPS, groupSummary, isAdvancedField, } from "../lib/ruleEngineWizard";
12
+ import { useRuleEngine } from "../hooks/useRuleEngine";
13
+ import { RuleFieldInput } from "./rule-engine/RuleFieldInput";
14
+ import { CollapsibleGroup } from "./rule-engine/CollapsibleGroup";
15
+ import { ConditionsEditor } from "./rule-engine/ConditionsEditor";
16
+ import { ImpactPanel } from "./rule-engine/ImpactPanel";
17
+ import { VersionsPanel } from "./rule-engine/VersionsPanel";
18
+ const WIDE_TYPES = ["priority", "toggle", "days", "radio", "textarea"];
19
+ function fieldClass(field) {
20
+ return WIDE_TYPES.includes(field.type) ? "md:col-span-2" : "";
21
+ }
22
+ export function RuleEnginePage({ strategyId = null, strategyName = null } = {}) {
23
+ const { versions, activeConfig, loading, saving, error, createConfig, patchSettings, saveConfig, activateConfig, restoreVersion, deleteConfig, } = useRuleEngine({ strategyId, strategyName });
24
+ const [stepIndex, setStepIndex] = useState(0);
25
+ const [createOpen, setCreateOpen] = useState(false);
26
+ const [newName, setNewName] = useState("");
27
+ const [newDescription, setNewDescription] = useState("");
28
+ const [recalculating, setRecalculating] = useState(false);
29
+ const step = WIZARD_STEPS[stepIndex] ?? WIZARD_STEPS[0];
30
+ const settings = activeConfig?.settings ?? {};
31
+ const stepValues = settings?.[step.storeId]?.fields ?? {};
32
+ const stepConditions = settings?.[step.storeId]?.conditions ?? [];
33
+ const setFieldValue = (key, value) => {
34
+ if (!activeConfig)
35
+ return;
36
+ patchSettings(activeConfig.id, {
37
+ ...settings,
38
+ [step.storeId]: {
39
+ ...(settings[step.storeId] ?? { fields: {}, conditions: [] }),
40
+ fields: { ...stepValues, [key]: value },
41
+ },
42
+ });
43
+ };
44
+ const setConditions = (conditions) => {
45
+ if (!activeConfig)
46
+ return;
47
+ patchSettings(activeConfig.id, {
48
+ ...settings,
49
+ [step.storeId]: {
50
+ ...(settings[step.storeId] ?? { fields: {}, conditions: [] }),
51
+ conditions,
52
+ },
53
+ });
54
+ };
55
+ const handleCreate = async () => {
56
+ if (!newName.trim())
57
+ return;
58
+ await createConfig(newName.trim(), newDescription.trim() || undefined);
59
+ setNewName("");
60
+ setNewDescription("");
61
+ setCreateOpen(false);
62
+ setStepIndex(0);
63
+ };
64
+ const { job, starting, startRecalculation } = useRecalcJob();
65
+ const running = job?.status === "running";
66
+ const busy = starting || running || recalculating;
67
+ const handleSaveAndRecalculate = async () => {
68
+ if (!activeConfig)
69
+ return;
70
+ setRecalculating(true);
71
+ try {
72
+ await saveConfig(activeConfig);
73
+ // Een werkwijze telt pas mee als hij actief is.
74
+ if (!activeConfig.is_active)
75
+ await activateConfig(activeConfig.id);
76
+ // De herberekening draait als achtergrondtaak door, ook als je wegnavigeert.
77
+ await startRecalculation(strategyId);
78
+ toast.success("Opgeslagen — de herberekening loopt op de achtergrond");
79
+ }
80
+ catch (e) {
81
+ toast.error("Opnieuw berekenen is niet gelukt. Probeer het nog een keer.");
82
+ }
83
+ finally {
84
+ setRecalculating(false);
85
+ }
86
+ };
87
+ const jobProblems = useMemo(() => {
88
+ const list = [];
89
+ if (Array.isArray(job?.errors))
90
+ list.push(...job.errors.slice(0, 3));
91
+ if (Array.isArray(job?.steps)) {
92
+ list.push(...job.steps.filter((s) => !s.ok).map((s) => s.error ?? "Onderdeel niet gelukt").slice(0, 3));
93
+ }
94
+ return list;
95
+ }, [job]);
96
+ const showJobLine = Boolean(job) && (running || job?.status === "error" || jobProblems.length > 0);
97
+ return (_jsxs("div", { className: "space-y-4 p-6", children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h1", { className: "text-xl font-semibold tracking-tight", children: strategyName ? `Werkwijze: ${strategyName}` : "Werkwijze instellen" }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setCreateOpen(true), children: [_jsx(Plus, { className: "h-4 w-4" }), "Nieuw"] }), _jsxs(Button, { size: "sm", disabled: !activeConfig || saving || busy, onClick: handleSaveAndRecalculate, children: [busy ? _jsx(RefreshCw, { className: "h-4 w-4 animate-spin" }) : _jsx(Save, { className: "h-4 w-4" }), running
98
+ ? `${phaseLabel(job?.phase)}… (${job?.processed ?? 0})`
99
+ : busy
100
+ ? "Bezig…"
101
+ : "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
102
+ ? `${phaseLabel(job?.phase)} — ${job?.processed ?? 0} verwerkt`
103
+ : "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 ? (_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
104
+ ? "border-primary bg-primary/5"
105
+ : "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) => {
106
+ const visibleFields = group.fields.filter((field) => fieldVisible(field, stepValues, settings));
107
+ if (visibleFields.length === 0)
108
+ return null;
109
+ const basicFields = visibleFields.filter((f) => !isAdvancedField(f));
110
+ const advancedFields = visibleFields.filter((f) => isAdvancedField(f));
111
+ const isLastGroup = groupIndex === step.groups.length - 1;
112
+ 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], 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], onChange: (value) => setFieldValue(field.key, value) }) }, field.key))) })), step.showConditions && isLastGroup && (_jsx(ConditionsEditor, { conditions: stepConditions, onChange: setConditions }))] })] }))] }, group.id));
113
+ }) })] }), _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" })] })] }) })] }));
114
+ }
@@ -0,0 +1,11 @@
1
+ import type { LogistiekStrategie, StrategieInput } from "../hooks/useLogistiekStrategieen";
2
+ interface Props {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ strategie: LogistiekStrategie | null;
6
+ saving: boolean;
7
+ onSubmit: (input: StrategieInput) => Promise<boolean>;
8
+ }
9
+ export declare function StrategieDialog({ open, onOpenChange, strategie, saving, onSubmit }: Props): import("react").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=StrategieDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StrategieDialog.d.ts","sourceRoot":"","sources":["../../src/components/StrategieDialog.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAE3F,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAUD,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,+BA4FzF"}
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/StrategieDialog.tsx — strategie aanmaken of bewerken
4
+ // ============================================================================
5
+ import { useEffect, useState } from "react";
6
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Switch, Textarea, } from "@flowselections/core";
7
+ const EMPTY = {
8
+ name: "",
9
+ description: "",
10
+ priority: 100,
11
+ color: "#3b82f6",
12
+ is_active: false,
13
+ };
14
+ export function StrategieDialog({ open, onOpenChange, strategie, saving, onSubmit }) {
15
+ const [form, setForm] = useState(EMPTY);
16
+ useEffect(() => {
17
+ if (!open)
18
+ return;
19
+ setForm(strategie
20
+ ? {
21
+ name: strategie.name ?? "",
22
+ description: strategie.description ?? "",
23
+ priority: Number(strategie.priority ?? 100),
24
+ color: strategie.color ?? "#3b82f6",
25
+ is_active: !!strategie.is_active,
26
+ }
27
+ : EMPTY);
28
+ }, [open, strategie]);
29
+ const set = (key, value) => setForm((prev) => ({ ...prev, [key]: value }));
30
+ const handleSubmit = async () => {
31
+ if (!form.name.trim())
32
+ return;
33
+ const ok = await onSubmit({ ...form, name: form.name.trim() });
34
+ if (ok)
35
+ onOpenChange(false);
36
+ };
37
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: strategie ? "Werkwijze bewerken" : "Nieuwe werkwijze" }), _jsx(DialogDescription, { children: "Geef de werkwijze een herkenbare naam en kleur." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Naam" }), _jsx(Input, { value: form.name, onChange: (e) => set("name", e.target.value), placeholder: "Bijv. Standaard werkwijze" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Omschrijving" }), _jsx(Textarea, { value: form.description, onChange: (e) => set("description", e.target.value), placeholder: "Waarvoor gebruik je deze werkwijze?" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Kleur" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { type: "color", className: "h-10 w-14 p-1", value: form.color, onChange: (e) => set("color", e.target.value) }), _jsx(Input, { value: form.color, onChange: (e) => set("color", e.target.value) })] })] }), _jsxs("div", { className: "flex items-center justify-between rounded-lg border border-border p-3", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { className: "text-sm font-medium", children: "Aan" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Alleen een werkwijze die aan staat wordt op je orders toegepast." })] }), _jsx(Switch, { checked: form.is_active, onCheckedChange: (checked) => set("is_active", checked) })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { onClick: handleSubmit, disabled: saving || !form.name.trim(), children: strategie ? "Opslaan" : "Aanmaken" })] })] }) }));
38
+ }
@@ -0,0 +1,2 @@
1
+ export declare function VertragingMeldingenCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=VertragingMeldingenCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VertragingMeldingenCard.d.ts","sourceRoot":"","sources":["../../src/components/VertragingMeldingenCard.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,uBAAuB,gCA2EtC"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/VertragingMeldingenCard.tsx — orders die de leverdatum niet halen
4
+ // ============================================================================
5
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, } from "@flowselections/core";
6
+ import { CheckCircle2, Clock, RefreshCw, ThumbsUp } from "lucide-react";
7
+ import { VERTRAGING_PRIORITEIT, useVertragingMeldingen } from "../hooks/useVertragingMeldingen";
8
+ function tijd(value) {
9
+ if (!value)
10
+ return "onbekend";
11
+ try {
12
+ return new Date(value).toLocaleString("nl-NL", { dateStyle: "short", timeStyle: "short" });
13
+ }
14
+ catch {
15
+ return value;
16
+ }
17
+ }
18
+ function datum(value) {
19
+ if (!value)
20
+ return "onbekend";
21
+ try {
22
+ return new Date(value).toLocaleDateString("nl-NL", { dateStyle: "short" });
23
+ }
24
+ catch {
25
+ return value;
26
+ }
27
+ }
28
+ export function VertragingMeldingenCard() {
29
+ const { meldingen, loading, reload, scan, goedkeuren, afhandelen } = useVertragingMeldingen();
30
+ const safe = Array.isArray(meldingen) ? meldingen : [];
31
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "h-4 w-4 text-amber-500" }), "Vertraagde leveringen"] }), _jsx(CardDescription, { children: "Meldingen blijven zichtbaar tot ze zijn afgehandeld." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => void scan(), children: "Nu controleren" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void reload(), children: [_jsx(RefreshCw, { className: "mr-1 h-4 w-4" }), "Vernieuwen"] })] })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? (_jsx(Skeleton, { className: "h-24 w-full" })) : safe.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen vertraagde leveringen." })) : (safe.map((m) => (_jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsxs("p", { className: "text-sm font-semibold", children: ["Order ", m.order_ref, m.customer_name ? ` · ${m.customer_name}` : ""] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Gepland ", datum(m.planned_delivery_date), " \u00B7 verwacht ", datum(m.expected_delivery_date), " \u00B7 gemeld ", tijd(m.created_at)] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: "secondary", children: VERTRAGING_PRIORITEIT[m.priority] ?? "Normaal" }), m.current_status ? _jsx(Badge, { variant: "outline", children: m.current_status }) : null, m.reschedule_status === "awaiting_approval" ? (_jsx(Badge, { variant: "outline", children: "Wacht op goedkeuring" })) : null, m.customer_notified ? _jsx(Badge, { variant: "outline", children: "Klant ge\u00EFnformeerd" }) : null] })] }), m.reason ? (_jsxs("p", { className: "mt-2 text-sm text-muted-foreground", children: ["Reden: ", m.reason] })) : null, _jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [m.reschedule_status === "awaiting_approval" ? (_jsxs(Button, { size: "sm", onClick: () => void goedkeuren(m.id), children: [_jsx(ThumbsUp, { className: "mr-1 h-4 w-4" }), "Herplanning goedkeuren"] })) : null, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void afhandelen(m.id), children: [_jsx(CheckCircle2, { className: "mr-1 h-4 w-4" }), "Afgehandeld"] })] })] }, m.id)))) })] }));
32
+ }
@@ -0,0 +1,2 @@
1
+ export declare function VoorraadMeldingenCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=VoorraadMeldingenCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoorraadMeldingenCard.d.ts","sourceRoot":"","sources":["../../src/components/VoorraadMeldingenCard.tsx"],"names":[],"mappings":"AAyBA,wBAAgB,qBAAqB,gCAsFpC"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/VoorraadMeldingenCard.tsx — open voorraadtekorten
4
+ // ============================================================================
5
+ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, } from "@flowselections/core";
6
+ import { AlertTriangle, CheckCircle2, Mail, RefreshCw } from "lucide-react";
7
+ import { PRIORITEIT_LABELS, useVoorraadMeldingen } from "../hooks/useVoorraadMeldingen";
8
+ function tijd(value) {
9
+ try {
10
+ return new Date(value).toLocaleString("nl-NL", { dateStyle: "short", timeStyle: "short" });
11
+ }
12
+ catch {
13
+ return value;
14
+ }
15
+ }
16
+ export function VoorraadMeldingenCard() {
17
+ const { meldingen, loading, reload, afhandelen, mailOpnieuw } = useVoorraadMeldingen();
18
+ const safe = Array.isArray(meldingen) ? meldingen : [];
19
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-amber-500" }), "Onvoldoende voorraad"] }), _jsx(CardDescription, { children: "Meldingen blijven open staan tot ze zijn afgehandeld." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void reload(), children: [_jsx(RefreshCw, { className: "mr-1 h-4 w-4" }), "Vernieuwen"] })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? (_jsx(Skeleton, { className: "h-24 w-full" })) : safe.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen openstaande voorraadmeldingen." })) : (safe.map((m) => (_jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsxs("p", { className: "text-sm font-semibold", children: ["Order ", m.order_ref, m.customer_name ? ` · ${m.customer_name}` : ""] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Gemeld ", tijd(m.created_at), m.delivery_date ? ` · gewenste leverdatum ${m.delivery_date}` : ""] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: m.priority >= 3 ? "destructive" : "secondary", children: PRIORITEIT_LABELS[m.priority] ?? "Normaal" }), m.email_enabled && (_jsxs(Badge, { variant: m.email_status === "failed" ? "destructive" : "outline", className: "gap-1", title: m.email_error ?? undefined, children: [_jsx(Mail, { className: "h-3 w-3" }), m.email_status === "sent"
20
+ ? "Verstuurd"
21
+ : m.email_status === "queued"
22
+ ? "In wachtrij"
23
+ : m.email_status === "failed"
24
+ ? "Mislukt"
25
+ : "E-mail"] })), m.email_enabled && m.email_status !== "sent" && (_jsxs(Button, { size: "sm", variant: "ghost", onClick: () => void mailOpnieuw(m.id), children: [_jsx(Mail, { className: "mr-1 h-4 w-4" }), "Mail opnieuw"] })), _jsxs(Button, { size: "sm", variant: "ghost", onClick: () => void afhandelen(m.id), children: [_jsx(CheckCircle2, { className: "mr-1 h-4 w-4" }), "Afhandelen"] })] })] }), _jsx("ul", { className: "mt-2 space-y-1 text-sm", children: (Array.isArray(m.shortages) ? m.shortages : []).map((s, i) => (_jsxs("li", { className: "text-muted-foreground", children: [s.product ?? "Onbekend product", " \u2014 ", Number(s.missing ?? 0), " stuks tekort", " ", "(besteld ", Number(s.wanted ?? 0), ", voorraad ", Number(s.available ?? 0), ")"] }, i))) })] }, m.id)))) })] }));
26
+ }
@@ -0,0 +1,12 @@
1
+ import { type ReactNode } from "react";
2
+ interface Props {
3
+ title: string;
4
+ /** Korte samenvatting van de huidige keuze, zichtbaar als het blok dicht is. */
5
+ summary?: string;
6
+ help?: string;
7
+ defaultOpen?: boolean;
8
+ children: ReactNode;
9
+ }
10
+ export declare function CollapsibleGroup({ title, summary, help, defaultOpen, children }: Props): import("react").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=CollapsibleGroup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollapsibleGroup.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/CollapsibleGroup.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjD,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAmB,EAAE,QAAQ,EAAE,EAAE,KAAK,+BA2B9F"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/rule-engine/CollapsibleGroup.tsx — in- en uitklapbaar blok
4
+ // ============================================================================
5
+ import { useState } from "react";
6
+ import { ChevronDown } from "lucide-react";
7
+ import { FieldHelp } from "./FieldHelp";
8
+ export function CollapsibleGroup({ title, summary, help, defaultOpen = false, children }) {
9
+ const [open, setOpen] = useState(defaultOpen);
10
+ return (_jsxs("section", { className: "rounded-lg border border-border", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, className: "flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-muted/60", children: [_jsxs("span", { className: "min-w-0", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [title, _jsx(FieldHelp, { text: help })] }), !open && summary && (_jsx("span", { className: "mt-0.5 block truncate text-xs text-muted-foreground", children: summary }))] }), _jsx(ChevronDown, { className: `h-4 w-4 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })] }), open && _jsx("div", { className: "space-y-4 border-t border-border px-3 py-4", children: children })] }));
11
+ }
@@ -0,0 +1,8 @@
1
+ import { type RuleCondition } from "../../lib/ruleEngineSchema";
2
+ interface Props {
3
+ conditions: RuleCondition[];
4
+ onChange: (conditions: RuleCondition[]) => void;
5
+ }
6
+ export declare function ConditionsEditor({ conditions, onChange }: Props): import("react").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=ConditionsEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConditionsEditor.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/ConditionsEditor.tsx"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,4BAA4B,CAAC;AAEpC,UAAU,KAAK;IACb,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,QAAQ,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;CACjD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,KAAK,+BAiH/D"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/rule-engine/ConditionsEditor.tsx — voorwaarden per stap
4
+ // ============================================================================
5
+ import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
6
+ import { Plus, Trash2 } from "lucide-react";
7
+ import { CONDITION_FIELDS, CONDITION_OPERATORS, } from "../../lib/ruleEngineSchema";
8
+ export function ConditionsEditor({ conditions, onChange }) {
9
+ const safe = Array.isArray(conditions) ? conditions : [];
10
+ const add = () => {
11
+ onChange([
12
+ ...safe,
13
+ {
14
+ id: `c_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
15
+ field: "customer",
16
+ operator: "in",
17
+ values: [],
18
+ },
19
+ ]);
20
+ };
21
+ const update = (id, patch) => {
22
+ onChange(safe.map((c) => (c.id === id ? { ...c, ...patch } : c)));
23
+ };
24
+ const remove = (id) => onChange(safe.filter((c) => c.id !== id));
25
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-sm font-medium", children: "Voorwaarden" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder voorwaarden gelden deze regels voor alle orders." })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: add, children: [_jsx(Plus, { className: "h-4 w-4" }), "Voorwaarde"] })] }), safe.length === 0 ? (_jsx("p", { className: "rounded-lg border border-dashed border-border p-3 text-sm text-muted-foreground", children: "Geldt voor alle orders." })) : (_jsx("ul", { className: "space-y-2", children: safe.map((condition) => (_jsxs("li", { className: "flex flex-wrap items-end gap-2 rounded-lg border border-border p-3", children: [_jsxs("div", { className: "min-w-40 flex-1 space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Veld" }), _jsxs(Select, { value: condition.field, onValueChange: (value) => update(condition.id, { field: value }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CONDITION_FIELDS.map((f) => (_jsx(SelectItem, { value: f.value, children: f.label }, f.value))) })] })] }), _jsxs("div", { className: "min-w-36 flex-1 space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Operator" }), _jsxs(Select, { value: condition.operator, onValueChange: (value) => update(condition.id, { operator: value }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CONDITION_OPERATORS.map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] })] }), _jsxs("div", { className: "min-w-48 flex-[2] space-y-1", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Waarden (komma-gescheiden)" }), _jsx(Input, { value: (condition.values ?? []).join(", "), placeholder: "bijv. Klant A, Klant B", onChange: (e) => update(condition.id, {
26
+ values: e.target.value
27
+ .split(",")
28
+ .map((v) => v.trim())
29
+ .filter(Boolean),
30
+ }) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => remove(condition.id), "aria-label": "Voorwaarde verwijderen", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, condition.id))) }))] }));
31
+ }
@@ -0,0 +1,14 @@
1
+ interface FieldHelpProps {
2
+ text?: string;
3
+ }
4
+ export declare function FieldHelp({ text }: FieldHelpProps): import("react").JSX.Element | null;
5
+ interface FieldLabelProps {
6
+ label: string;
7
+ help?: string;
8
+ unit?: string;
9
+ className?: string;
10
+ }
11
+ /** Label met optioneel vraagteken-icoon voor de uitleg. */
12
+ export declare function FieldLabel({ label, help, unit, className }: FieldLabelProps): import("react").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=FieldHelp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldHelp.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/FieldHelp.tsx"],"names":[],"mappings":"AAUA,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,cAAc,sCAmBjD;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,eAAe,+BAU3E"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/rule-engine/FieldHelp.tsx — uitleg bij een instelling
4
+ // ============================================================================
5
+ // Toont een klein vraagteken naast het label. Hover of focus toont de uitleg,
6
+ // zodat de uitleg niet permanent ruimte inneemt op het scherm.
7
+ // ============================================================================
8
+ import { HelpCircle } from "lucide-react";
9
+ import { Label } from "@flowselections/core";
10
+ export function FieldHelp({ text }) {
11
+ if (!text)
12
+ return null;
13
+ return (_jsxs("span", { className: "group relative inline-flex align-middle", children: [_jsx("button", { type: "button", "aria-label": "Uitleg", className: "text-muted-foreground/70 transition-colors hover:text-foreground focus:text-foreground focus:outline-none", children: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }) }), _jsx("span", { role: "tooltip", className: "pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 hidden w-64 -translate-x-1/2 rounded-md border border-border bg-popover p-2.5 text-xs font-normal leading-relaxed text-popover-foreground shadow-md group-hover:block group-focus-within:block", children: text })] }));
14
+ }
15
+ /** Label met optioneel vraagteken-icoon voor de uitleg. */
16
+ export function FieldLabel({ label, help, unit, className }) {
17
+ return (_jsxs(Label, { className: `flex items-center gap-1.5 text-sm font-medium ${className ?? ""}`, children: [_jsxs("span", { children: [label, unit ? _jsxs("span", { className: "text-muted-foreground", children: [" (", unit, ")"] }) : null] }), _jsx(FieldHelp, { text: help })] }));
18
+ }
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ import type { RuleEngineSettings } from "../../lib/ruleEngineSchema";
2
+ export declare function ImpactPanel({ settings }: {
3
+ settings: RuleEngineSettings;
4
+ }): import("react").JSX.Element;
5
+ //# sourceMappingURL=ImpactPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImpactPanel.d.ts","sourceRoot":"","sources":["../../../src/components/rule-engine/ImpactPanel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAerE,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,kBAAkB,CAAA;CAAE,+BAqEzE"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/rule-engine/ImpactPanel.tsx — impact & samenvatting
4
+ // ============================================================================
5
+ import { Badge, Card, CardContent, CardHeader, CardTitle } from "@flowselections/core";
6
+ import { AlertTriangle, Info, ShieldAlert } from "lucide-react";
7
+ import { analyseImpact, summarize } from "../../lib/ruleEngineImpact";
8
+ const ICONS = {
9
+ info: Info,
10
+ warning: AlertTriangle,
11
+ risk: ShieldAlert,
12
+ };
13
+ const TONE = {
14
+ info: "text-muted-foreground",
15
+ warning: "text-amber-600",
16
+ risk: "text-destructive",
17
+ };
18
+ export function ImpactPanel({ settings }) {
19
+ const impact = analyseImpact(settings);
20
+ const lines = summarize(settings);
21
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsx(CardTitle, { className: "text-base", children: "Impact" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid grid-cols-3 gap-2 text-center", children: [_jsxs("div", { className: "rounded-lg border border-border p-2", children: [_jsxs("p", { className: "text-lg font-semibold", children: [impact.automationScore, "%"] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Automatisch" })] }), _jsxs("div", { className: "rounded-lg border border-border p-2", children: [_jsx("p", { className: "text-lg font-semibold", children: impact.manualSteps }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Handmatig" })] }), _jsxs("div", { className: "rounded-lg border border-border p-2", children: [_jsx("p", { className: "text-lg font-semibold", children: impact.scanMoments }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Scanmomenten" })] })] }), _jsx("ul", { className: "space-y-3", children: impact.signals.map((signal, index) => {
22
+ const Icon = ICONS[signal.level];
23
+ return (_jsxs("li", { className: "flex gap-2", children: [_jsx(Icon, { className: `mt-0.5 h-4 w-4 shrink-0 ${TONE[signal.level]}` }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: signal.title }), _jsx("p", { className: "text-xs text-muted-foreground", children: signal.detail })] })] }, index));
24
+ }) })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsx(CardTitle, { className: "text-base", children: "Samenvatting" }) }), _jsx(CardContent, { children: lines.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog niets ingesteld." })) : (_jsx("ul", { className: "space-y-2", children: lines.map((line, index) => (_jsxs("li", { className: "space-y-0.5 border-b border-border pb-2 last:border-0 last:pb-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: line.step }), _jsx("span", { className: "text-xs font-medium", children: line.label })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: line.value })] }, index))) })) })] })] }));
25
+ }