@flowselections/logistiek-engine 1.0.120 → 1.0.124

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 (38) hide show
  1. package/dist-lib/components/BeheerPage.d.ts +2 -0
  2. package/dist-lib/components/BeheerPage.d.ts.map +1 -0
  3. package/dist-lib/components/BeheerPage.js +73 -0
  4. package/dist-lib/components/FlowPage.d.ts +1 -0
  5. package/dist-lib/components/FlowPage.d.ts.map +1 -1
  6. package/dist-lib/components/FlowPage.js +7 -1
  7. package/dist-lib/components/LogistiekEnginePage.d.ts.map +1 -1
  8. package/dist-lib/components/LogistiekEnginePage.js +14 -2
  9. package/dist-lib/components/ModulePromptsPage.d.ts +1 -0
  10. package/dist-lib/components/ModulePromptsPage.d.ts.map +1 -1
  11. package/dist-lib/components/ModulePromptsPage.js +7 -1
  12. package/dist-lib/components/RolPoort.d.ts +7 -0
  13. package/dist-lib/components/RolPoort.d.ts.map +1 -0
  14. package/dist-lib/components/RolPoort.js +12 -0
  15. package/dist-lib/components/RuleEnginePage.d.ts +2 -1
  16. package/dist-lib/components/RuleEnginePage.d.ts.map +1 -1
  17. package/dist-lib/components/RuleEnginePage.js +7 -1
  18. package/dist-lib/components/WerkwijzeHerindelingCard.d.ts.map +1 -1
  19. package/dist-lib/components/WerkwijzeHerindelingCard.js +2 -2
  20. package/dist-lib/hooks/useEngineRollen.d.ts +14 -0
  21. package/dist-lib/hooks/useEngineRollen.d.ts.map +1 -0
  22. package/dist-lib/hooks/useEngineRollen.js +50 -0
  23. package/dist-lib/index.d.ts +3 -0
  24. package/dist-lib/index.d.ts.map +1 -1
  25. package/dist-lib/index.js +3 -0
  26. package/dist-lib/integrations/supabase/types.d.ts +543 -3
  27. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  28. package/dist-lib/lib/floridayPush.server.d.ts.map +1 -1
  29. package/dist-lib/lib/floridayPush.server.js +10 -0
  30. package/dist-lib/lib/handlers/logistics-tick.server.js +13 -0
  31. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  32. package/dist-lib/lib/modulePrompts.js +154 -0
  33. package/dist-lib/lib/moduleVersion.d.ts +1 -1
  34. package/dist-lib/lib/moduleVersion.js +1 -1
  35. package/dist-lib/lib/shippingNoteQueue.server.d.ts +5 -0
  36. package/dist-lib/lib/shippingNoteQueue.server.d.ts.map +1 -1
  37. package/dist-lib/lib/shippingNoteQueue.server.js +61 -0
  38. package/package.json +6 -1
@@ -0,0 +1,2 @@
1
+ export declare function BeheerPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=BeheerPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BeheerPage.d.ts","sourceRoot":"","sources":["../../src/components/BeheerPage.tsx"],"names":[],"mappings":"AAmJA,wBAAgB,UAAU,gCAQzB"}
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/BeheerPage.tsx — orders opnieuw indelen (alleen beheer)
4
+ // ============================================================================
5
+ // Tweestaps: eerst het aantal orders bekijken, dan met precies dat aantal
6
+ // bevestigen. De database controleert de rol en het aantal opnieuw.
7
+ // GEEN <Header /> hier — die levert de shell.
8
+ // ============================================================================
9
+ import { useState } from "react";
10
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, } from "@flowselections/core";
11
+ import { RotateCcw } from "lucide-react";
12
+ import { supabase } from "../integrations/supabase/moduleClient.js";
13
+ import { RolPoort } from "./RolPoort";
14
+ import { ENGINE_TOEGANG } from "../hooks/useEngineRollen";
15
+ function HerindelenBeheer() {
16
+ const [datum, setDatum] = useState("");
17
+ const [ookBevroren, setOokBevroren] = useState(false);
18
+ const [voor, setVoor] = useState(null);
19
+ const [open, setOpen] = useState(false);
20
+ const [bezig, setBezig] = useState(false);
21
+ const [melding, setMelding] = useState(null);
22
+ const args = () => ({ p_delivery_date: datum || null, p_include_frozen: ookBevroren });
23
+ const bekijk = async () => {
24
+ setBezig(true);
25
+ setMelding(null);
26
+ try {
27
+ const { data, error } = await supabase.rpc("logistics_replan_preview", args());
28
+ if (error)
29
+ throw error;
30
+ const d = (data ?? {});
31
+ setVoor({
32
+ aantal: Number(d["aantal"] ?? 0),
33
+ geprintOfGepickt: Number(d["geprintOfGepickt"] ?? 0),
34
+ totaalOpen: Number(d["totaalOpen"] ?? 0),
35
+ });
36
+ setOpen(true);
37
+ }
38
+ catch (e) {
39
+ setMelding(e?.message ? String(e.message) : "Het aantal orders kon niet worden opgehaald.");
40
+ }
41
+ finally {
42
+ setBezig(false);
43
+ }
44
+ };
45
+ const voerUit = async () => {
46
+ if (!voor)
47
+ return;
48
+ setOpen(false);
49
+ setBezig(true);
50
+ try {
51
+ const { data, error } = await supabase.rpc("logistics_replan_selected", {
52
+ p_confirm_count: voor.aantal,
53
+ ...args(),
54
+ });
55
+ if (error)
56
+ throw error;
57
+ setMelding(`${Number(data?.aangemeld ?? 0)} order(s) opnieuw aangemeld voor indeling.`);
58
+ }
59
+ catch (e) {
60
+ setMelding(e?.message ? String(e.message) : "Opnieuw indelen is niet gelukt.");
61
+ }
62
+ finally {
63
+ setBezig(false);
64
+ setVoor(null);
65
+ }
66
+ };
67
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Orders opnieuw indelen" }), _jsx(CardDescription, { children: "Standaard worden orders die al geprint of gepickt zijn overgeslagen. Je ziet eerst om hoeveel orders het gaat." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-2 sm:max-w-xs", children: [_jsx(Label, { htmlFor: "herindel-datum", children: "Leverdatum (leeg = alle open orders)" }), _jsx(Input, { id: "herindel-datum", type: "date", value: datum, onChange: (e) => setDatum(e.target.value) })] }), _jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: ookBevroren, onChange: (e) => setOokBevroren(e.target.checked) }), _jsx("span", { children: "Ook orders die al geprint of gepickt zijn. Oude pakbonnen en barcodes vervallen dan en kunnen niet meer worden gescand." })] }), _jsxs(Button, { onClick: () => void bekijk(), disabled: bezig, children: [_jsx(RotateCcw, { className: "h-4 w-4" }), "Aantal bekijken"] }), melding ? _jsx("p", { className: "text-sm", children: melding }) : null] }), _jsx(AlertDialog, { open: open, onOpenChange: setOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsxs(AlertDialogTitle, { children: ["Je deelt ", voor?.aantal ?? 0, " order(s) opnieuw in"] }), _jsx(AlertDialogDescription, { children: ookBevroren
68
+ ? `Inclusief ${voor?.geprintOfGepickt ?? 0} order(s) die al geprint of gepickt zijn. Dit kan niet ongedaan worden gemaakt.`
69
+ : `${voor?.geprintOfGepickt ?? 0} order(s) die al geprint of gepickt zijn, blijven ongemoeid.` })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsxs(AlertDialogAction, { onClick: () => void voerUit(), disabled: (voor?.aantal ?? 0) === 0, children: ["Ja, ", voor?.aantal ?? 0, " order(s) opnieuw indelen"] })] })] }) })] }));
70
+ }
71
+ export function BeheerPage() {
72
+ return (_jsx(RolPoort, { rollen: ENGINE_TOEGANG.beheer, children: _jsx("div", { className: "space-y-6 p-6", children: _jsx(HerindelenBeheer, {}) }) }));
73
+ }
@@ -1,2 +1,3 @@
1
+ /** Alleen zichtbaar voor de juiste rollen; de database controleert zelf ook. */
1
2
  export declare function FlowPage(): import("react").JSX.Element;
2
3
  //# sourceMappingURL=FlowPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FlowPage.d.ts","sourceRoot":"","sources":["../../src/components/FlowPage.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,QAAQ,gCAitBvB"}
1
+ {"version":3,"file":"FlowPage.d.ts","sourceRoot":"","sources":["../../src/components/FlowPage.tsx"],"names":[],"mappings":"AA0xBA,gFAAgF;AAChF,wBAAgB,QAAQ,gCAMvB"}
@@ -29,11 +29,13 @@ import { useRecalcJob, phaseLabel } from "../hooks/useRecalcJob";
29
29
  import { RuleFieldInput } from "./rule-engine/RuleFieldInput";
30
30
  import { CollapsibleGroup } from "./rule-engine/CollapsibleGroup";
31
31
  import { ConditionsEditor } from "./rule-engine/ConditionsEditor";
32
+ import { RolPoort } from "./RolPoort";
33
+ import { ENGINE_TOEGANG } from "../hooks/useEngineRollen";
32
34
  const WIDE_TYPES = ["priority", "toggle", "days", "radio", "textarea", "guidance"];
33
35
  function fieldClass(field) {
34
36
  return WIDE_TYPES.includes(field.type) ? "md:col-span-2" : "";
35
37
  }
36
- export function FlowPage() {
38
+ function FlowPageInhoud() {
37
39
  const { configs, activeConfig, activeId, setActiveId, loading, saving, error, patchSettings, saveConfig, activateConfig, } = useRuleEngine();
38
40
  const { items: strategieen } = useLogistiekStrategieen();
39
41
  const [openStep, setOpenStep] = useState(null);
@@ -308,3 +310,7 @@ export function FlowPage() {
308
310
  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));
309
311
  }), _jsxs("div", { className: "flex flex-wrap gap-2 pt-1", children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: saving || busy, onClick: () => void opslaan(false), children: [_jsx(Save, { className: "h-4 w-4" }), " Opslaan"] }), _jsxs(Button, { size: "sm", disabled: saving || busy, onClick: () => void opslaan(true), children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), " Opslaan & opnieuw berekenen"] })] })] })] }) }))] })), !loading && activeConfig && _jsx(FlowOrders, { onRoute: onRoute })] }));
310
312
  }
313
+ /** Alleen zichtbaar voor de juiste rollen; de database controleert zelf ook. */
314
+ export function FlowPage() {
315
+ return (_jsx(RolPoort, { rollen: ENGINE_TOEGANG.flow, children: _jsx(FlowPageInhoud, {}) }));
316
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"LogistiekEnginePage.d.ts","sourceRoot":"","sources":["../../src/components/LogistiekEnginePage.tsx"],"names":[],"mappings":"AAuFA,wBAAgB,mBAAmB,gCAgPlC"}
1
+ {"version":3,"file":"LogistiekEnginePage.d.ts","sourceRoot":"","sources":["../../src/components/LogistiekEnginePage.tsx"],"names":[],"mappings":"AAkGA,wBAAgB,mBAAmB,gCAiPlC"}
@@ -7,23 +7,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  // ============================================================================
8
8
  import { useState } from "react";
9
9
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Skeleton, } from "@flowselections/core";
10
- import { Container, GitBranch, ListChecks, Pencil, Plus, Puzzle, SlidersHorizontal, Trash2, Workflow } from "lucide-react";
10
+ import { Container, GitBranch, ListChecks, Pencil, Plus, Puzzle, ShieldCheck, SlidersHorizontal, Trash2, Workflow } from "lucide-react";
11
11
  import { Link } from "@tanstack/react-router";
12
12
  import { StrategieDialog } from "./StrategieDialog";
13
13
  import { WerkwijzeHerindelingCard } from "./WerkwijzeHerindelingCard";
14
14
  import { WerktijdenCard } from "./WerktijdenCard";
15
+ import { ENGINE_TOEGANG, heeftToegang, useEngineRollen } from "../hooks/useEngineRollen";
15
16
  import { useLogistiekStrategieen, } from "../hooks/useLogistiekStrategieen";
16
17
  const SNELKOPPELINGEN = [
17
18
  {
18
19
  to: "/logistiek-engine/rule-engine",
19
20
  icon: SlidersHorizontal,
20
21
  titel: "Werkwijze instellen",
22
+ rollen: ENGINE_TOEGANG.ruleEngine,
21
23
  uitleg: "Bepaal stap voor stap hoe orders worden verzameld, gepickt en klaargezet.",
22
24
  },
23
25
  {
24
26
  to: "/logistiek-engine/flow",
25
27
  icon: GitBranch,
26
28
  titel: "Flow",
29
+ rollen: ENGINE_TOEGANG.flow,
27
30
  uitleg: "Volg per order de reis door de flow en pas de werkwijze aan.",
28
31
  },
29
32
  {
@@ -48,10 +51,19 @@ const SNELKOPPELINGEN = [
48
51
  to: "/logistiek-engine/modules",
49
52
  icon: Puzzle,
50
53
  titel: "Modules",
54
+ rollen: ENGINE_TOEGANG.modules,
51
55
  uitleg: "Koppelprompts voor aangesloten modules bekijken en downloaden.",
52
56
  },
57
+ {
58
+ to: "/logistiek-engine/beheer",
59
+ icon: ShieldCheck,
60
+ titel: "Beheer",
61
+ rollen: ENGINE_TOEGANG.beheer,
62
+ uitleg: "Orders opnieuw indelen, met bevestiging van het aantal.",
63
+ },
53
64
  ];
54
65
  export function LogistiekEnginePage() {
66
+ const { rollen } = useEngineRollen();
55
67
  const { items, loading, saving, error, create, update, remove, setActive, clearActive } = useLogistiekStrategieen();
56
68
  const [dialogOpen, setDialogOpen] = useState(false);
57
69
  const [editing, setEditing] = useState(null);
@@ -76,7 +88,7 @@ export function LogistiekEnginePage() {
76
88
  setEditing(item);
77
89
  setDialogOpen(true);
78
90
  };
79
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Hier bepaal je hoe orders in je bedrijf logistiek worden afgehandeld." }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-4", children: SNELKOPPELINGEN.map(({ to, icon: Icon, titel, uitleg }) => (_jsx(Link, { to: to, className: "group", children: _jsx(Card, { className: "h-full transition-colors group-hover:border-primary", children: _jsxs(CardHeader, { className: "p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary", children: _jsx(Icon, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-sm font-semibold leading-tight", children: titel })] }), _jsx(CardDescription, { className: "pt-1 text-xs leading-relaxed", children: uitleg })] }) }) }, to))) }), _jsx(WerkwijzeHerindelingCard, {}), _jsx(WerktijdenCard, {}), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Werkwijzen" }), _jsx(CardDescription, { children: "Alleen een werkwijze die aan staat wordt op je orders toegepast." })] }), _jsxs(Button, { size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "h-4 w-4" }), "Nieuwe werkwijze"] })] }) }), _jsx(CardContent, { children: loading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" })] })) : error ? (_jsx("p", { className: "text-sm text-destructive", children: "Kon de gegevens niet ophalen. Probeer het over een moment opnieuw." })) : safeItems.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Je hebt nog geen werkwijze ingesteld." }), _jsxs(Button, { variant: "outline", size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "h-4 w-4" }), "Eerste werkwijze aanmaken"] })] })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "w-48 text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: safeItems.map((item) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-3 w-3 shrink-0 rounded-full border border-border", style: { backgroundColor: item.color ?? "#3b82f6" } }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: item.name ?? "—" }), item.description && (_jsx("p", { className: "text-xs text-muted-foreground", children: item.description }))] })] }) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "radio", name: "actieve-werkwijze", className: "h-4 w-4 accent-primary", checked: !!item.is_active, disabled: saving, onChange: () => kiesWerkwijze(item), "aria-label": `${item.name ?? "Werkwijze"} als actieve werkwijze kiezen` }), _jsx("span", { className: "sr-only", children: "Actief" })] }), _jsx(Badge, { variant: item.is_active ? "default" : "secondary", children: item.is_active ? "Actief" : "Uit" }), item.is_active && (_jsx(Button, { size: "sm", variant: "ghost", disabled: saving, onClick: () => void clearActive(), children: "Uitzetten" }))] }) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { size: "sm", variant: "outline", asChild: true, children: _jsxs(Link, { to: "/logistiek-engine/rule-engine", search: { strategy: String(item.id), naam: item.name ?? "" }, children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Instellen"] }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => openEdit(item), "aria-label": "Bewerken", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => setDeleting(item), "aria-label": "Verwijderen", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) })] }, String(item.id)))) })] })) })] }), _jsx(StrategieDialog, { open: dialogOpen, onOpenChange: setDialogOpen, strategie: editing, saving: saving, onSubmit: (input) => (editing ? update(editing.id, input) : create(input)) }), _jsx(AlertDialog, { open: !!deleting, onOpenChange: (open) => !open && setDeleting(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Werkwijze verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["\"", deleting?.name, "\" wordt definitief verwijderd. Dit kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: async () => {
91
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Hier bepaal je hoe orders in je bedrijf logistiek worden afgehandeld." }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-4", children: SNELKOPPELINGEN.filter((k) => !k.rollen || heeftToegang(rollen, k.rollen)).map(({ to, icon: Icon, titel, uitleg }) => (_jsx(Link, { to: to, className: "group", children: _jsx(Card, { className: "h-full transition-colors group-hover:border-primary", children: _jsxs(CardHeader, { className: "p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary", children: _jsx(Icon, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-sm font-semibold leading-tight", children: titel })] }), _jsx(CardDescription, { className: "pt-1 text-xs leading-relaxed", children: uitleg })] }) }) }, to))) }), _jsx(WerkwijzeHerindelingCard, {}), _jsx(WerktijdenCard, {}), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Werkwijzen" }), _jsx(CardDescription, { children: "Alleen een werkwijze die aan staat wordt op je orders toegepast." })] }), _jsxs(Button, { size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "h-4 w-4" }), "Nieuwe werkwijze"] })] }) }), _jsx(CardContent, { children: loading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" }), _jsx(Skeleton, { className: "h-8 w-full" })] })) : error ? (_jsx("p", { className: "text-sm text-destructive", children: "Kon de gegevens niet ophalen. Probeer het over een moment opnieuw." })) : safeItems.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Je hebt nog geen werkwijze ingesteld." }), _jsxs(Button, { variant: "outline", size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "h-4 w-4" }), "Eerste werkwijze aanmaken"] })] })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { className: "w-48 text-right", children: "Acties" })] }) }), _jsx(TableBody, { children: safeItems.map((item) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-3 w-3 shrink-0 rounded-full border border-border", style: { backgroundColor: item.color ?? "#3b82f6" } }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: item.name ?? "—" }), item.description && (_jsx("p", { className: "text-xs text-muted-foreground", children: item.description }))] })] }) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "radio", name: "actieve-werkwijze", className: "h-4 w-4 accent-primary", checked: !!item.is_active, disabled: saving, onChange: () => kiesWerkwijze(item), "aria-label": `${item.name ?? "Werkwijze"} als actieve werkwijze kiezen` }), _jsx("span", { className: "sr-only", children: "Actief" })] }), _jsx(Badge, { variant: item.is_active ? "default" : "secondary", children: item.is_active ? "Actief" : "Uit" }), item.is_active && (_jsx(Button, { size: "sm", variant: "ghost", disabled: saving, onClick: () => void clearActive(), children: "Uitzetten" }))] }) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { size: "sm", variant: "outline", asChild: true, children: _jsxs(Link, { to: "/logistiek-engine/rule-engine", search: { strategy: String(item.id), naam: item.name ?? "" }, children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Instellen"] }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => openEdit(item), "aria-label": "Bewerken", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => setDeleting(item), "aria-label": "Verwijderen", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) })] }, String(item.id)))) })] })) })] }), _jsx(StrategieDialog, { open: dialogOpen, onOpenChange: setDialogOpen, strategie: editing, saving: saving, onSubmit: (input) => (editing ? update(editing.id, input) : create(input)) }), _jsx(AlertDialog, { open: !!deleting, onOpenChange: (open) => !open && setDeleting(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Werkwijze verwijderen?" }), _jsxs(AlertDialogDescription, { children: ["\"", deleting?.name, "\" wordt definitief verwijderd. Dit kan niet ongedaan worden gemaakt."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: async () => {
80
92
  if (deleting)
81
93
  await remove(deleting.id);
82
94
  setDeleting(null);
@@ -1,2 +1,3 @@
1
+ /** Alleen zichtbaar voor de juiste rollen; de database controleert zelf ook. */
1
2
  export declare function ModulePromptsPage(): import("react").JSX.Element;
2
3
  //# sourceMappingURL=ModulePromptsPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ModulePromptsPage.d.ts","sourceRoot":"","sources":["../../src/components/ModulePromptsPage.tsx"],"names":[],"mappings":"AAkDA,wBAAgB,iBAAiB,gCA0LhC"}
1
+ {"version":3,"file":"ModulePromptsPage.d.ts","sourceRoot":"","sources":["../../src/components/ModulePromptsPage.tsx"],"names":[],"mappings":"AAiPA,gFAAgF;AAChF,wBAAgB,iBAAiB,gCAMhC"}
@@ -10,6 +10,8 @@ import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitl
10
10
  import { Check, ChevronDown, ChevronUp, Copy, Download, Puzzle } from "lucide-react";
11
11
  import { Link } from "@tanstack/react-router";
12
12
  import { MODULE_PROMPTS, buildAllModulePrompts, buildRevisionPrompt, modulePromptFilename, } from "../lib/modulePrompts";
13
+ import { RolPoort } from "./RolPoort";
14
+ import { ENGINE_TOEGANG } from "../hooks/useEngineRollen";
13
15
  function downloadMarkdown(filename, content) {
14
16
  const blob = new Blob([content], { type: "text/markdown;charset=utf-8" });
15
17
  const url = URL.createObjectURL(blob);
@@ -30,7 +32,7 @@ async function copyText(content) {
30
32
  return false;
31
33
  }
32
34
  }
33
- export function ModulePromptsPage() {
35
+ function ModulePromptsPageInhoud() {
34
36
  const [open, setOpen] = useState(null);
35
37
  const [copied, setCopied] = useState(null);
36
38
  const [openModules, setOpenModules] = useState([]);
@@ -64,3 +66,7 @@ export function ModulePromptsPage() {
64
66
  }) }))] }, key));
65
67
  }) })] }));
66
68
  }
69
+ /** Alleen zichtbaar voor de juiste rollen; de database controleert zelf ook. */
70
+ export function ModulePromptsPage() {
71
+ return (_jsx(RolPoort, { rollen: ENGINE_TOEGANG.modules, children: _jsx(ModulePromptsPageInhoud, {}) }));
72
+ }
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+ import { type EngineRol } from "../hooks/useEngineRollen";
3
+ export declare function RolPoort({ rollen, children }: {
4
+ rollen: readonly EngineRol[];
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
7
+ //# sourceMappingURL=RolPoort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RolPoort.d.ts","sourceRoot":"","sources":["../../src/components/RolPoort.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAEzF,wBAAgB,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IAAE,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAanG"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Card, CardContent, Skeleton } from "@flowselections/core";
3
+ import { heeftToegang, useEngineRollen } from "../hooks/useEngineRollen";
4
+ export function RolPoort({ rollen, children }) {
5
+ const { rollen: mijn, geladen } = useEngineRollen();
6
+ if (!geladen)
7
+ return _jsx(Skeleton, { className: "m-6 h-24" });
8
+ if (!heeftToegang(mijn, rollen)) {
9
+ return (_jsx(Card, { className: "m-6", children: _jsx(CardContent, { className: "p-6 text-sm text-muted-foreground", children: "Dit scherm is alleen beschikbaar voor beheer of ontwikkelaars." }) }));
10
+ }
11
+ return _jsx(_Fragment, { children: children });
12
+ }
@@ -3,5 +3,6 @@ export interface RuleEnginePageProps {
3
3
  strategyId?: string | null;
4
4
  strategyName?: string | null;
5
5
  }
6
- export declare function RuleEnginePage({ strategyId: strategyIdProp, strategyName: strategyNameProp, }?: RuleEnginePageProps): import("react").JSX.Element;
6
+ /** Alleen zichtbaar voor de juiste rollen; de database controleert zelf ook. */
7
+ export declare function RuleEnginePage(props: any): import("react").JSX.Element;
7
8
  //# sourceMappingURL=RuleEnginePage.d.ts.map
@@ -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,+BA+b1B"}
1
+ {"version":3,"file":"RuleEnginePage.d.ts","sourceRoot":"","sources":["../../src/components/RuleEnginePage.tsx"],"names":[],"mappings":"AAsEA,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AA4cD,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,+BAMxC"}
@@ -16,11 +16,13 @@ import { CollapsibleGroup } from "./rule-engine/CollapsibleGroup";
16
16
  import { ConditionsEditor } from "./rule-engine/ConditionsEditor";
17
17
  import { ImpactPanel } from "./rule-engine/ImpactPanel";
18
18
  import { VersionsPanel } from "./rule-engine/VersionsPanel";
19
+ import { RolPoort } from "./RolPoort";
20
+ import { ENGINE_TOEGANG } from "../hooks/useEngineRollen";
19
21
  const WIDE_TYPES = ["priority", "toggle", "days", "radio", "textarea", "guidance"];
20
22
  function fieldClass(field) {
21
23
  return WIDE_TYPES.includes(field.type) ? "md:col-span-2" : "";
22
24
  }
23
- export function RuleEnginePage({ strategyId: strategyIdProp = null, strategyName: strategyNameProp = null, } = {}) {
25
+ function RuleEnginePageInhoud({ strategyId: strategyIdProp = null, strategyName: strategyNameProp = null, } = {}) {
24
26
  // De shell rendert deze pagina zonder props; lees de gekozen werkwijze daarom zelf uit de URL.
25
27
  const routeSearch = useSearch({ strict: false });
26
28
  const urlSearch = useMemo(() => {
@@ -137,3 +139,7 @@ export function RuleEnginePage({ strategyId: strategyIdProp = null, strategyName
137
139
  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));
138
140
  }) })] }), _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" })] })] }) })] }));
139
141
  }
142
+ /** Alleen zichtbaar voor de juiste rollen; de database controleert zelf ook. */
143
+ export function RuleEnginePage(props) {
144
+ return (_jsx(RolPoort, { rollen: ENGINE_TOEGANG.ruleEngine, children: _jsx(RuleEnginePageInhoud, { ...props }) }));
145
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"WerkwijzeHerindelingCard.d.ts","sourceRoot":"","sources":["../../src/components/WerkwijzeHerindelingCard.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,gCA+RvC"}
1
+ {"version":3,"file":"WerkwijzeHerindelingCard.d.ts","sourceRoot":"","sources":["../../src/components/WerkwijzeHerindelingCard.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,gCAkRvC"}
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
9
9
  // ============================================================================
10
10
  import { useCallback, useEffect, useRef, useState } from "react";
11
11
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton, } from "@flowselections/core";
12
- import { RefreshCw, RotateCcw, Ruler, Boxes, AlertTriangle } from "lucide-react";
12
+ import { RefreshCw, Ruler, Boxes } from "lucide-react";
13
13
  import { useWerkwijzeStatus } from "../hooks/useWerkwijzeStatus";
14
14
  const METHODE_LABEL = {
15
15
  belading: "Indelen op basis van belading",
@@ -122,7 +122,7 @@ export function WerkwijzeHerindelingCard() {
122
122
  setMelding(e?.message ? String(e.message) : "Volledig opnieuw indelen is niet gelukt.");
123
123
  }
124
124
  };
125
- 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: [status?.methode === "afmetingen" ? (_jsx(Ruler, { className: "h-4 w-4" })) : (_jsx(Boxes, { className: "h-4 w-4" })), "Werkwijze en herindeling"] }), _jsx(CardDescription, { children: "Zo worden karren op dit moment samengesteld. Verandert de methode, dan worden alle openstaande orders automatisch opnieuw ingedeeld." })] }), _jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => void refresh(), disabled: loading, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Verversen"] }), _jsxs(Button, { size: "sm", onClick: () => void opnieuw(), disabled: busy, children: [_jsx(RotateCcw, { className: "h-4 w-4" }), "Alles opnieuw indelen"] }), _jsxs(Button, { size: "sm", variant: "destructive", onClick: () => setBevestigOpen(true), disabled: busy, children: [_jsx(AlertTriangle, { className: "h-4 w-4" }), "Alle orders opnieuw indelen, ook gepickte orders"] })] })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? (_jsx(Skeleton, { className: "h-16 w-full" })) : error ? (_jsx("p", { className: "text-sm text-destructive", children: error })) : status ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { children: METHODE_LABEL[status.methode] ?? status.methode }), status.actieveWerkwijze ? (_jsxs("span", { className: "text-sm text-muted-foreground", children: ["volgens werkwijze \u201C", status.actieveWerkwijze, "\u201D"] })) : (_jsx("span", { className: "text-sm text-muted-foreground", children: "Er staat geen werkwijze aan; er wordt op belading ingedeeld." }))] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Karren volgens deze methode" }), _jsx("p", { className: "text-lg font-semibold", children: status.karrenVolgensMethode })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Karren van een oudere methode" }), _jsx("p", { className: "text-lg font-semibold", children: status.karrenOudeMethode })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Wacht op indelen" }), _jsx("p", { className: "text-lg font-semibold", children: openWerk }), running > 0 ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["waarvan ", running, " nu bezig"] })) : null, blocked > 0 ? (_jsxs("p", { className: "text-xs text-destructive", children: [blocked, " geblokkeerd"] })) : null] })] }), status.karrenOudeMethode > 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Deze karren zijn nog met een eerdere methode berekend. Ze worden vervangen zodra de order opnieuw is ingedeeld; al geprinte of gecontroleerde karren blijven staan." })) : null, melding ? _jsx("p", { className: "text-sm", children: melding }) : null, totaal > 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-muted", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": percentage, "aria-label": "Voortgang opnieuw indelen", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all duration-500", style: { width: `${percentage}%` } }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: bezigMetIndelen
125
+ 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: [status?.methode === "afmetingen" ? (_jsx(Ruler, { className: "h-4 w-4" })) : (_jsx(Boxes, { className: "h-4 w-4" })), "Werkwijze en herindeling"] }), _jsx(CardDescription, { children: "Zo worden karren op dit moment samengesteld. Verandert de methode, dan worden alle openstaande orders automatisch opnieuw ingedeeld. Handmatig opnieuw indelen staat onder Beheer." })] }), _jsx("div", { className: "flex flex-wrap justify-end gap-2", children: _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void refresh(), disabled: loading, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Verversen"] }) })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? (_jsx(Skeleton, { className: "h-16 w-full" })) : error ? (_jsx("p", { className: "text-sm text-destructive", children: error })) : status ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { children: METHODE_LABEL[status.methode] ?? status.methode }), status.actieveWerkwijze ? (_jsxs("span", { className: "text-sm text-muted-foreground", children: ["volgens werkwijze \u201C", status.actieveWerkwijze, "\u201D"] })) : (_jsx("span", { className: "text-sm text-muted-foreground", children: "Er staat geen werkwijze aan; er wordt op belading ingedeeld." }))] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Karren volgens deze methode" }), _jsx("p", { className: "text-lg font-semibold", children: status.karrenVolgensMethode })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Karren van een oudere methode" }), _jsx("p", { className: "text-lg font-semibold", children: status.karrenOudeMethode })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Wacht op indelen" }), _jsx("p", { className: "text-lg font-semibold", children: openWerk }), running > 0 ? (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["waarvan ", running, " nu bezig"] })) : null, blocked > 0 ? (_jsxs("p", { className: "text-xs text-destructive", children: [blocked, " geblokkeerd"] })) : null] })] }), status.karrenOudeMethode > 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Deze karren zijn nog met een eerdere methode berekend. Ze worden vervangen zodra de order opnieuw is ingedeeld; al geprinte of gecontroleerde karren blijven staan." })) : null, melding ? _jsx("p", { className: "text-sm", children: melding }) : null, totaal > 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-muted", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": percentage, "aria-label": "Voortgang opnieuw indelen", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-all duration-500", style: { width: `${percentage}%` } }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: bezigMetIndelen
126
126
  ? `${gereed} van ${totaal} order(s) ingedeeld — ${percentage}%`
127
127
  : `Klaar: ${totaal} order(s) opnieuw ingedeeld.` })] })) : null, pakbonnen && pakbonnen.totaal > 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-muted", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pakbonPercentage, "aria-label": "Voortgang pakbonnen klaarzetten", children: _jsx("div", { className: "h-full rounded-full bg-primary/70 transition-all duration-500", style: { width: `${pakbonPercentage}%` } }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: pakbonnen.open > 0 || pakbonBezig
128
128
  ? `Pakbonnen klaarzetten: ${pakbonnen.gereed} van ${pakbonnen.totaal} printklaar — ${pakbonPercentage}%`
@@ -0,0 +1,14 @@
1
+ export type EngineRol = "developer" | "superadmin" | "admin";
2
+ /** Wie mag wat zien. */
3
+ export declare const ENGINE_TOEGANG: {
4
+ modules: EngineRol[];
5
+ flow: EngineRol[];
6
+ ruleEngine: EngineRol[];
7
+ beheer: EngineRol[];
8
+ };
9
+ export declare function heeftToegang(rollen: readonly string[], nodig: readonly EngineRol[]): boolean;
10
+ export declare function useEngineRollen(): {
11
+ rollen: string[];
12
+ geladen: boolean;
13
+ };
14
+ //# sourceMappingURL=useEngineRollen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEngineRollen.d.ts","sourceRoot":"","sources":["../../src/hooks/useEngineRollen.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAC;AAE7D,wBAAwB;AACxB,eAAO,MAAM,cAAc;aACC,SAAS,EAAE;UACA,SAAS,EAAE;gBACL,SAAS,EAAE;YACN,SAAS,EAAE;CAC5D,CAAC;AAEF,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAG5F;AAED,wBAAgB,eAAe;;;EA0B9B"}
@@ -0,0 +1,50 @@
1
+ // ============================================================================
2
+ // src/hooks/useEngineRollen.ts — welke rollen heeft de ingelogde gebruiker?
3
+ // ============================================================================
4
+ // Alleen voor tonen/verbergen van schermen. De echte beveiliging zit in de
5
+ // databasefuncties zelf (o.a. logistics_is_beheer); verbergen is geen slot.
6
+ // ============================================================================
7
+ import { useEffect, useState } from "react";
8
+ import { supabase } from "../integrations/supabase/moduleClient.js";
9
+ /** Wie mag wat zien. */
10
+ export const ENGINE_TOEGANG = {
11
+ modules: ["developer"],
12
+ flow: ["developer", "superadmin"],
13
+ ruleEngine: ["developer", "superadmin"],
14
+ beheer: ["developer", "superadmin", "admin"],
15
+ };
16
+ export function heeftToegang(rollen, nodig) {
17
+ const safe = Array.isArray(rollen) ? rollen : [];
18
+ return nodig.some((r) => safe.includes(r));
19
+ }
20
+ export function useEngineRollen() {
21
+ const [rollen, setRollen] = useState([]);
22
+ const [geladen, setGeladen] = useState(false);
23
+ useEffect(() => {
24
+ let actief = true;
25
+ void (async () => {
26
+ try {
27
+ const { data: u } = await supabase.auth.getUser();
28
+ const uid = u?.user?.id;
29
+ if (!uid)
30
+ return;
31
+ const { data } = await supabase.from("user_roles").select("role").eq("user_id", uid);
32
+ const lijst = Array.isArray(data) ? data.map((r) => String(r?.role ?? "")) : [];
33
+ if (actief)
34
+ setRollen(lijst.filter(Boolean));
35
+ }
36
+ catch {
37
+ if (actief)
38
+ setRollen([]);
39
+ }
40
+ finally {
41
+ if (actief)
42
+ setGeladen(true);
43
+ }
44
+ })();
45
+ return () => {
46
+ actief = false;
47
+ };
48
+ }, []);
49
+ return { rollen, geladen };
50
+ }
@@ -33,6 +33,9 @@ export { LogistiekEnginePage } from './components/LogistiekEnginePage';
33
33
  export { RuleEnginePage } from './components/RuleEnginePage';
34
34
  export { RegiePage } from './components/RegiePage';
35
35
  export { ModulePromptsPage } from './components/ModulePromptsPage';
36
+ export { BeheerPage } from './components/BeheerPage';
37
+ export { RolPoort } from './components/RolPoort';
38
+ export * from './hooks/useEngineRollen';
36
39
  export { LadingdragersPage } from './components/LadingdragersPage';
37
40
  export { LogistiekEngineQueuePage } from './components/LogistiekEngineQueuePage';
38
41
  export { FlowPage } from './components/FlowPage';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist-lib/index.js CHANGED
@@ -37,6 +37,9 @@ export { LogistiekEnginePage } from './components/LogistiekEnginePage';
37
37
  export { RuleEnginePage } from './components/RuleEnginePage';
38
38
  export { RegiePage } from './components/RegiePage';
39
39
  export { ModulePromptsPage } from './components/ModulePromptsPage';
40
+ export { BeheerPage } from './components/BeheerPage';
41
+ export { RolPoort } from './components/RolPoort';
42
+ export * from './hooks/useEngineRollen';
40
43
  export { LadingdragersPage } from './components/LadingdragersPage';
41
44
  export { LogistiekEngineQueuePage } from './components/LogistiekEngineQueuePage';
42
45
  export { FlowPage } from './components/FlowPage';