@flowselections/pick-schermen-kwekers 1.0.80 → 1.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist-lib/components/PickstationsPage.d.ts.map +1 -1
  2. package/dist-lib/components/PickstationsPage.js +147 -18
  3. package/dist-lib/components/fs-ui/DataTable.d.ts +22 -0
  4. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -0
  5. package/dist-lib/components/fs-ui/DataTable.js +13 -0
  6. package/dist-lib/components/fs-ui/EmptyState.d.ts +9 -0
  7. package/dist-lib/components/fs-ui/EmptyState.d.ts.map +1 -0
  8. package/dist-lib/components/fs-ui/EmptyState.js +5 -0
  9. package/dist-lib/components/fs-ui/FilterChips.d.ts +15 -0
  10. package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -0
  11. package/dist-lib/components/fs-ui/FilterChips.js +10 -0
  12. package/dist-lib/components/fs-ui/FiltersSheet.d.ts +16 -0
  13. package/dist-lib/components/fs-ui/FiltersSheet.d.ts.map +1 -0
  14. package/dist-lib/components/fs-ui/FiltersSheet.js +9 -0
  15. package/dist-lib/components/fs-ui/HoverTip.d.ts +11 -0
  16. package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
  17. package/dist-lib/components/fs-ui/HoverTip.js +19 -0
  18. package/dist-lib/components/fs-ui/KpiTile.d.ts +2 -0
  19. package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
  20. package/dist-lib/components/fs-ui/KpiTile.js +1 -0
  21. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +8 -0
  22. package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
  23. package/dist-lib/components/fs-ui/MobilePrimaryAction.js +6 -0
  24. package/dist-lib/components/fs-ui/NoticeBar.d.ts +6 -0
  25. package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
  26. package/dist-lib/components/fs-ui/NoticeBar.js +12 -0
  27. package/dist-lib/components/fs-ui/PageHeader.d.ts +12 -0
  28. package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
  29. package/dist-lib/components/fs-ui/PageHeader.js +9 -0
  30. package/dist-lib/components/fs-ui/Pagination.d.ts +8 -0
  31. package/dist-lib/components/fs-ui/Pagination.d.ts.map +1 -0
  32. package/dist-lib/components/fs-ui/Pagination.js +7 -0
  33. package/dist-lib/components/fs-ui/PillTabs.d.ts +17 -0
  34. package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
  35. package/dist-lib/components/fs-ui/PillTabs.js +23 -0
  36. package/dist-lib/components/fs-ui/RowMenu.d.ts +12 -0
  37. package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
  38. package/dist-lib/components/fs-ui/RowMenu.js +7 -0
  39. package/dist-lib/components/fs-ui/SearchInput.d.ts +7 -0
  40. package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
  41. package/dist-lib/components/fs-ui/SearchInput.js +12 -0
  42. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +8 -0
  43. package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
  44. package/dist-lib/components/fs-ui/ShortcutHelp.js +13 -0
  45. package/dist-lib/components/fs-ui/StatusIcon.d.ts +8 -0
  46. package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
  47. package/dist-lib/components/fs-ui/StatusIcon.js +10 -0
  48. package/dist-lib/components/fs-ui/TableSkeleton.d.ts +6 -0
  49. package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
  50. package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
  51. package/dist-lib/components/fs-ui/Toolbar.d.ts +10 -0
  52. package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
  53. package/dist-lib/components/fs-ui/Toolbar.js +7 -0
  54. package/dist-lib/components/fs-ui/UpdatedAt.d.ts +7 -0
  55. package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
  56. package/dist-lib/components/fs-ui/UpdatedAt.js +13 -0
  57. package/dist-lib/components/pick/DocumentChoiceMelding.d.ts +1 -1
  58. package/dist-lib/components/pick/DocumentChoiceMelding.d.ts.map +1 -1
  59. package/dist-lib/components/pick/PickScherm.js +9 -6
  60. package/dist-lib/components/pick/QueueTable.js +1 -1
  61. package/dist-lib/components/pick/ReferentiedataBanner.d.ts +2 -2
  62. package/dist-lib/components/pick/ReferentiedataBanner.d.ts.map +1 -1
  63. package/dist-lib/components/pick/ReferentiedataBanner.js +3 -4
  64. package/dist-lib/components/settings/ReferentiedataPickdocumentenCard.d.ts.map +1 -1
  65. package/dist-lib/components/settings/ReferentiedataPickdocumentenCard.js +10 -3
  66. package/dist-lib/hooks/useCartStickerStates.d.ts +1 -1
  67. package/dist-lib/hooks/useCartStickerStates.d.ts.map +1 -1
  68. package/dist-lib/hooks/usePickConfig.d.ts +11 -11
  69. package/dist-lib/hooks/usePickConfig.d.ts.map +1 -1
  70. package/dist-lib/hooks/usePickPrintStatus.d.ts +2 -2
  71. package/dist-lib/hooks/usePickQueue.d.ts +3 -3
  72. package/dist-lib/hooks/usePickQueue.d.ts.map +1 -1
  73. package/dist-lib/hooks/usePickReferenceData.d.ts +1 -1
  74. package/dist-lib/hooks/usePickReferenceData.d.ts.map +1 -1
  75. package/dist-lib/integrations/supabase/client.js +1 -1
  76. package/dist-lib/integrations/supabase/client.server.js +1 -1
  77. package/dist-lib/integrations/supabase/types.d.ts +909 -1
  78. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  79. package/dist-lib/lib/__tests__/labels.test.d.ts +2 -0
  80. package/dist-lib/lib/__tests__/labels.test.d.ts.map +1 -0
  81. package/dist-lib/lib/__tests__/labels.test.js +24 -0
  82. package/dist-lib/lib/labels.d.ts +9 -0
  83. package/dist-lib/lib/labels.d.ts.map +1 -0
  84. package/dist-lib/lib/labels.js +65 -0
  85. package/dist-lib/lib/layoutApi.d.ts +23 -0
  86. package/dist-lib/lib/layoutApi.d.ts.map +1 -0
  87. package/dist-lib/lib/layoutApi.js +94 -0
  88. package/dist-lib/lib/layoutMaker/documentProfiles.d.ts +50 -0
  89. package/dist-lib/lib/layoutMaker/documentProfiles.d.ts.map +1 -0
  90. package/dist-lib/lib/layoutMaker/documentProfiles.js +458 -0
  91. package/dist-lib/lib/layoutMaker/documentTypes.d.ts +123 -0
  92. package/dist-lib/lib/layoutMaker/documentTypes.d.ts.map +1 -0
  93. package/dist-lib/lib/layoutMaker/documentTypes.js +74 -0
  94. package/dist-lib/lib/layoutMaker/logoLibrary.d.ts +13 -0
  95. package/dist-lib/lib/layoutMaker/logoLibrary.d.ts.map +1 -0
  96. package/dist-lib/lib/layoutMaker/logoLibrary.js +54 -0
  97. package/dist-lib/lib/layoutMaker/pakbonBuilder.d.ts +196 -0
  98. package/dist-lib/lib/layoutMaker/pakbonBuilder.d.ts.map +1 -0
  99. package/dist-lib/lib/layoutMaker/pakbonBuilder.js +813 -0
  100. package/dist-lib/lib/layoutMaker/pakbonHtml.d.ts +48 -0
  101. package/dist-lib/lib/layoutMaker/pakbonHtml.d.ts.map +1 -0
  102. package/dist-lib/lib/layoutMaker/pakbonHtml.js +703 -0
  103. package/dist-lib/lib/layoutRenderer.d.ts +7 -0
  104. package/dist-lib/lib/layoutRenderer.d.ts.map +1 -0
  105. package/dist-lib/lib/layoutRenderer.js +233 -0
  106. package/dist-lib/lib/layoutTypes.d.ts +84 -0
  107. package/dist-lib/lib/layoutTypes.d.ts.map +1 -0
  108. package/dist-lib/lib/layoutTypes.js +25 -0
  109. package/dist-lib/lib/orderContext.d.ts +50 -0
  110. package/dist-lib/lib/orderContext.d.ts.map +1 -0
  111. package/dist-lib/lib/orderContext.js +162 -0
  112. package/dist-lib/lib/pakbonOrderInput.d.ts +8 -0
  113. package/dist-lib/lib/pakbonOrderInput.d.ts.map +1 -0
  114. package/dist-lib/lib/pakbonOrderInput.js +150 -0
  115. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"PickstationsPage.d.ts","sourceRoot":"","sources":["../../src/components/PickstationsPage.tsx"],"names":[],"mappings":"AAmGA,wBAAgB,gBAAgB,gCA6L/B"}
1
+ {"version":3,"file":"PickstationsPage.d.ts","sourceRoot":"","sources":["../../src/components/PickstationsPage.tsx"],"names":[],"mappings":"AAsHA,wBAAgB,gBAAgB,gCAkU/B"}
@@ -1,28 +1,86 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { useMutation, useQueryClient } from "@tanstack/react-query";
4
- import { Button, Card, CardContent, Dialog, DialogContent, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, toast, } from "@flowselections/core";
5
- import { AlertTriangle, Copy, ExternalLink, Loader2, Pencil, Plus, Printer, Trash2, Unlink, } from "lucide-react";
4
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, toast, } from "@flowselections/core";
5
+ import { AlertTriangle, Copy, ExternalLink, Loader2, Pencil, Plus, Printer, Unlink, Warehouse } from "lucide-react";
6
6
  import { pickApi } from "../lib/pickApi.js";
7
7
  import { usePickStations } from "../hooks/usePickConfig.js";
8
8
  import { useKoppelingVoorBron } from "../hooks/useKoppelingVoorBron.js";
9
9
  import { ReferentiedataBanner } from "./pick/ReferentiedataBanner.js";
10
10
  import { DOCUMENT_AUTO_TEXT } from "../lib/pick156.js";
11
11
  import { StationDevices } from "./pick/StationDevices.js";
12
+ import { plainError } from "../lib/labels.js";
13
+ import { PageHeader, KbdHint } from "./fs-ui/PageHeader.js";
14
+ import { UpdatedAt } from "./fs-ui/UpdatedAt.js";
15
+ import { PillTabs } from "./fs-ui/PillTabs.js";
16
+ import { Toolbar } from "./fs-ui/Toolbar.js";
17
+ import { SearchInput } from "./fs-ui/SearchInput.js";
18
+ import { FilterChips } from "./fs-ui/FilterChips.js";
19
+ import { FiltersSheet, SidePanel } from "./fs-ui/FiltersSheet.js";
20
+ import { DataTable, TwoLine } from "./fs-ui/DataTable.js";
21
+ import { StatusIcon } from "./fs-ui/StatusIcon.js";
22
+ import { RowMenu } from "./fs-ui/RowMenu.js";
23
+ import { EmptyState } from "./fs-ui/EmptyState.js";
24
+ import { TableSkeleton } from "./fs-ui/TableSkeleton.js";
25
+ import { Pagination } from "./fs-ui/Pagination.js";
26
+ import { ShortcutHelp, isTypingTarget } from "./fs-ui/ShortcutHelp.js";
27
+ import { MobilePrimaryAction } from "./fs-ui/MobilePrimaryAction.js";
12
28
  const emptyDraft = { name: "", description: "", active: true };
29
+ const COLS = [340, 380, 128, 56];
30
+ const SHORTCUTS = [
31
+ [["/"], "Zoeken"],
32
+ [["N"], "Nieuw pickstation"],
33
+ [["J", "K"], "Volgende / vorige station"],
34
+ [["Enter"], "Geselecteerd station openen"],
35
+ [["Esc"], "Zoeken wissen / sluiten"],
36
+ [["?"], "Dit overzicht"],
37
+ ];
13
38
  function publicUrl(slug) {
14
39
  const origin = typeof window !== "undefined" ? window.location.origin : "";
15
40
  return `${origin}/pickstation/${slug ?? ""}`;
16
41
  }
17
42
  function StationPrintplek({ station }) {
18
43
  const { plekken, printplekId, printplekNaam, gekoppeld, laden, koppel, ontkoppel } = useKoppelingVoorBron("pickstation", station.id, station.name);
19
- return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: `plek-${station.id}`, children: "Printplek" }), _jsxs(Select, { value: printplekId ?? undefined, onValueChange: (id) => void koppel(id), disabled: laden, children: [_jsx(SelectTrigger, { id: `plek-${station.id}`, children: _jsx(SelectValue, { placeholder: "Kies een printplek" }) }), _jsx(SelectContent, { children: plekken.map((p) => (_jsx(SelectItem, { value: p.id, children: p.naam }, p.id))) })] }), gekoppeld ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Printer, { className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "truncate", children: printplekNaam })] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => void ontkoppel(), children: [_jsx(Unlink, { className: "mr-2 h-4 w-4" }), " Loskoppelen"] })] })) : (_jsxs("p", { className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 px-2 py-1.5 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), "Nog geen printplek gekoppeld \u2014 de opdracht blijft in de wachtrij staan."] }))] }));
44
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: `plek-${station.id}`, children: "Printplek" }), _jsxs(Select, { value: printplekId ?? undefined, onValueChange: (id) => void koppel(id), disabled: laden, children: [_jsx(SelectTrigger, { id: `plek-${station.id}`, children: _jsx(SelectValue, { placeholder: "Kies een printplek" }) }), _jsx(SelectContent, { children: plekken.map((p) => (_jsx(SelectItem, { value: p.id, children: p.naam }, p.id))) })] }), gekoppeld ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("p", { className: "flex min-w-0 items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Printer, { className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "truncate", children: printplekNaam })] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => void ontkoppel(), children: [_jsx(Unlink, { className: "mr-2 h-4 w-4" }), " Loskoppelen"] })] })) : (_jsxs("p", { className: "flex items-start gap-2 text-sm text-warning", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), "Kies een printplek, anders wordt er niets geprint."] }))] }));
20
45
  }
21
46
  export function PickstationsPage() {
22
- const { data: stations, isLoading } = usePickStations();
47
+ const { data: stations, isLoading, dataUpdatedAt } = usePickStations();
23
48
  const queryClient = useQueryClient();
24
49
  const [draft, setDraft] = useState(null);
25
- const safeStations = Array.isArray(stations) ? stations : [];
50
+ const [confirmDelete, setConfirmDelete] = useState(null);
51
+ const [panelId, setPanelId] = useState(null);
52
+ const [tab, setTab] = useState("active");
53
+ const [search, setSearch] = useState("");
54
+ const [multi, setMulti] = useState("all");
55
+ const [country, setCountry] = useState("all");
56
+ const [filtersOpen, setFiltersOpen] = useState(false);
57
+ const [helpOpen, setHelpOpen] = useState(false);
58
+ const [page, setPage] = useState(1);
59
+ const [pageSize, setPageSize] = useState(25);
60
+ const [focused, setFocused] = useState(null);
61
+ const searchRef = useRef(null);
62
+ const safeStations = useMemo(() => (Array.isArray(stations) ? stations : []), [stations]);
63
+ const filteredNoTab = useMemo(() => {
64
+ const q = search.trim().toLowerCase();
65
+ return safeStations.filter((s) => {
66
+ if (q && !`${s.name} ${s.description ?? ""}`.toLowerCase().includes(q))
67
+ return false;
68
+ if (multi !== "all" && s.allow_multi_screen !== (multi === "yes"))
69
+ return false;
70
+ if (country !== "all" && s.require_country_selection !== (country === "yes"))
71
+ return false;
72
+ return true;
73
+ });
74
+ }, [safeStations, search, multi, country]);
75
+ const counts = {
76
+ active: filteredNoTab.filter((s) => s.active).length,
77
+ inactive: filteredNoTab.filter((s) => !s.active).length,
78
+ all: filteredNoTab.length,
79
+ };
80
+ const rows = filteredNoTab.filter((s) => (tab === "all" ? true : tab === "active" ? s.active : !s.active));
81
+ const pageRows = rows.slice((page - 1) * pageSize, page * pageSize);
82
+ const panelStation = safeStations.find((s) => s.id === panelId) ?? null;
83
+ useEffect(() => setPage(1), [tab, search, multi, country, pageSize]);
26
84
  const save = useMutation({
27
85
  mutationFn: async (d) => {
28
86
  await pickApi.upsert("pick_stations", {
@@ -37,7 +95,7 @@ export function PickstationsPage() {
37
95
  setDraft(null);
38
96
  queryClient.invalidateQueries({ queryKey: ["pick", "stations"] });
39
97
  },
40
- onError: (e) => toast.error(e instanceof Error ? e.message : "Opslaan mislukt."),
98
+ onError: (e) => toast.error(plainError(e, "Opslaan lukt niet, probeer het opnieuw.")),
41
99
  });
42
100
  const remove = useMutation({
43
101
  mutationFn: async (id) => {
@@ -45,9 +103,11 @@ export function PickstationsPage() {
45
103
  },
46
104
  onSuccess: () => {
47
105
  toast.success("Pickstation verwijderd.");
106
+ setConfirmDelete(null);
107
+ setPanelId(null);
48
108
  queryClient.invalidateQueries({ queryKey: ["pick", "stations"] });
49
109
  },
50
- onError: (e) => toast.error(e instanceof Error ? e.message : "Verwijderen mislukt."),
110
+ onError: (e) => toast.error(plainError(e, "Verwijderen lukt niet, probeer het opnieuw.")),
51
111
  });
52
112
  const copyLink = async (station) => {
53
113
  const url = publicUrl(station.slug);
@@ -59,13 +119,82 @@ export function PickstationsPage() {
59
119
  toast.error(url);
60
120
  }
61
121
  };
62
- return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Pickstations" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer je pickstations en de link die je met de medewerkers deelt." })] }), _jsxs(Button, { onClick: () => setDraft({ ...emptyDraft }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuw pickstation"] })] }), _jsx(ReferentiedataBanner, {}), isLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden..."] })) : safeStations.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen pickstations." })) : (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3", children: safeStations.map((station) => (_jsx(Card, { className: "border-2", children: _jsxs(CardContent, { className: "space-y-4 p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: station.name }), _jsx("p", { className: "text-sm text-muted-foreground", children: station.description })] }), _jsx("span", { className: "rounded-full px-2 py-1 text-xs font-medium " +
63
- (station.active
64
- ? "bg-primary/10 text-primary"
65
- : "bg-muted text-muted-foreground"), children: station.active ? "Actief" : "Inactief" })] }), _jsx("code", { className: "block truncate rounded bg-muted px-2 py-1 text-xs text-muted-foreground", children: publicUrl(station.slug) }), _jsx(StationPrintplek, { station: station }), _jsx("p", { className: "text-sm text-muted-foreground", children: DOCUMENT_AUTO_TEXT }), _jsx(StationDevices, { stationId: station.id, slug: station.slug ?? null }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { className: "flex-1", asChild: true, children: _jsxs("a", { href: publicUrl(station.slug), target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Open pickstation"] }) }), _jsxs(Button, { variant: "outline", onClick: () => copyLink(station), children: [_jsx(Copy, { className: "mr-2 h-4 w-4" }), " Link kopi\u00EBren"] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setDraft({
66
- id: station.id,
67
- name: station.name,
68
- description: station.description ?? "",
69
- active: station.active,
70
- }), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), " Wijzigen"] }), _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive", onClick: () => remove.mutate(station.id), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), " Verwijderen"] })] })] }) }, station.id))) })), _jsx(Dialog, { open: draft !== null, onOpenChange: (open) => !open && setDraft(null), children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: draft?.id ? "Pickstation wijzigen" : "Nieuw pickstation" }) }), draft && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "station-name", children: "Naam" }), _jsx(Input, { id: "station-name", value: draft.name, onChange: (e) => setDraft({ ...draft, name: e.target.value }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "station-desc", children: "Omschrijving (optioneel)" }), _jsx(Textarea, { id: "station-desc", value: draft.description, onChange: (e) => setDraft({ ...draft, description: e.target.value }) })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-2", children: [_jsx(Label, { htmlFor: "station-active", children: "Actief" }), _jsx(Switch, { id: "station-active", checked: draft.active, onCheckedChange: (v) => setDraft({ ...draft, active: v }) })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => setDraft(null), children: "Annuleren" }), _jsxs(Button, { disabled: !draft.name.trim() || save.isPending, onClick: () => save.mutate(draft), children: [save.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Opslaan"] })] })] }))] }) })] }));
122
+ const openStation = (s) => window.open(publicUrl(s.slug), "_blank", "noreferrer");
123
+ const edit = (s) => setDraft({ id: s.id, name: s.name, description: s.description ?? "", active: s.active });
124
+ // Sneltoetsen zoals Verkooporders; nooit tijdens typen.
125
+ useEffect(() => {
126
+ const onKey = (e) => {
127
+ if (e.metaKey || e.ctrlKey || e.altKey || isTypingTarget(e.target))
128
+ return;
129
+ if (draft || confirmDelete || panelId || filtersOpen || helpOpen)
130
+ return;
131
+ const idx = pageRows.findIndex((r) => r.id === focused);
132
+ if (e.key === "/") {
133
+ e.preventDefault();
134
+ searchRef.current?.focus();
135
+ }
136
+ else if (e.key === "n" || e.key === "N") {
137
+ e.preventDefault();
138
+ setDraft({ ...emptyDraft });
139
+ }
140
+ else if (e.key === "j" || e.key === "J") {
141
+ e.preventDefault();
142
+ const n = pageRows[Math.min(pageRows.length - 1, idx + 1)];
143
+ if (n)
144
+ setFocused(n.id);
145
+ }
146
+ else if (e.key === "k" || e.key === "K") {
147
+ e.preventDefault();
148
+ const n = pageRows[Math.max(0, idx - 1)];
149
+ if (n)
150
+ setFocused(n.id);
151
+ }
152
+ else if (e.key === "Enter" && focused) {
153
+ e.preventDefault();
154
+ setPanelId(focused);
155
+ }
156
+ else if (e.key === "Escape") {
157
+ if (search)
158
+ setSearch("");
159
+ else
160
+ setFocused(null);
161
+ }
162
+ else if (e.key === "?") {
163
+ e.preventDefault();
164
+ setHelpOpen(true);
165
+ }
166
+ };
167
+ window.addEventListener("keydown", onKey);
168
+ return () => window.removeEventListener("keydown", onKey);
169
+ }, [pageRows, focused, search, draft, confirmDelete, panelId, filtersOpen, helpOpen]);
170
+ const statusIcon = (s) => (_jsx(StatusIcon, { tone: s.active ? "ok" : "off", label: s.active ? "Actief" : "Uitgezet", showLabel: true }));
171
+ const menu = (s) => (_jsx(RowMenu, { label: `Acties voor ${s.name}`, items: [
172
+ { label: "Pickstation openen", onSelect: () => openStation(s) },
173
+ { label: "Link kopiëren", onSelect: () => void copyLink(s) },
174
+ { label: "Printplek en apparaten", onSelect: () => setPanelId(s.id) },
175
+ { label: "Wijzigen", onSelect: () => edit(s) },
176
+ { label: "Verwijderen", onSelect: () => setConfirmDelete(s), destructive: true, separatorBefore: true },
177
+ ] }));
178
+ const columns = [
179
+ { key: "name", header: "Station", width: COLS[0], cell: (s) => _jsx(TwoLine, { top: s.name, bottom: s.description ?? "" }) },
180
+ { key: "link", header: "Link voor medewerkers", width: COLS[1], cell: (s) => _jsx(TwoLine, { top: `/pickstation/${s.slug ?? ""}`, bottom: "Open of kopieer via het menu" }) },
181
+ { key: "status", header: "Status", width: COLS[2], cell: statusIcon },
182
+ { key: "menu", header: "", width: COLS[3], cell: menu },
183
+ ];
184
+ const yesNoLabel = { yes: "Ja", no: "Nee" };
185
+ const chips = [
186
+ ...(multi !== "all" ? [{ key: "multi", label: `Meerdere schermen: ${yesNoLabel[multi]}`, onClear: () => setMulti("all") }] : []),
187
+ ...(country !== "all" ? [{ key: "country", label: `Land kiezen: ${yesNoLabel[country]}`, onClear: () => setCountry("all") }] : []),
188
+ ];
189
+ const clearAll = () => { setMulti("all"); setCountry("all"); };
190
+ const YesNoField = ({ id, label, value, onChange }) => (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: id, children: label }), _jsxs(Select, { value: value, onValueChange: (v) => onChange(v), children: [_jsx(SelectTrigger, { id: id, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alles" }), _jsx(SelectItem, { value: "yes", children: "Ja" }), _jsx(SelectItem, { value: "no", children: "Nee" })] })] })] }));
191
+ return (_jsxs("main", { className: "flex w-full min-w-0 flex-col gap-4 overflow-x-clip p-4 pb-[calc(7rem+env(safe-area-inset-bottom))] sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { title: "Pickstations", updated: _jsx(UpdatedAt, { at: dataUpdatedAt, info: DOCUMENT_AUTO_TEXT }), actions: _jsxs(Button, { size: "sm", className: "hidden sm:inline-flex", onClick: () => setDraft({ ...emptyDraft }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuw pickstation", _jsx(KbdHint, { k: "N" })] }) }), _jsx(ReferentiedataBanner, {}), _jsx(PillTabs, { label: "Status pickstation", value: tab, onChange: setTab, tabs: [
192
+ { id: "active", label: "Actief", count: counts.active },
193
+ { id: "inactive", label: "Uitgezet", count: counts.inactive },
194
+ { id: "all", label: "Alle", count: counts.all },
195
+ ] }), _jsx(Toolbar, { search: _jsx(SearchInput, { ref: searchRef, value: search, onChange: setSearch, placeholder: "Zoek op naam of omschrijving" }), filterCount: chips.length, onOpenFilters: () => setFiltersOpen(true), chips: _jsx(FilterChips, { chips: chips, onClearAll: clearAll }) }), _jsx("div", { className: "w-full min-w-0 overflow-clip rounded-xl border border-border bg-card shadow-sm", children: isLoading ? (_jsx(TableSkeleton, { cols: COLS })) : rows.length === 0 ? (_jsx(EmptyState, { icon: _jsx(Warehouse, { className: "h-6 w-6" }), title: safeStations.length === 0 ? "Nog geen pickstations" : "Geen pickstations gevonden", text: safeStations.length === 0 ? "Maak een pickstation en deel de link met de medewerkers." : "Pas de zoekterm of filters aan.", action: safeStations.length === 0 ? (_jsxs(Button, { size: "sm", onClick: () => setDraft({ ...emptyDraft }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuw pickstation"] })) : chips.length > 0 || search ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => { clearAll(); setSearch(""); }, children: "Alles wissen" })) : undefined })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "hidden md:block", children: _jsx(DataTable, { label: "Pickstations", columns: columns, rows: pageRows, rowKey: (s) => s.id, focusedKey: focused, onOpen: (s) => { setFocused(s.id); setPanelId(s.id); } }) }), _jsx("ul", { className: "divide-y divide-border md:hidden", children: pageRows.map((s) => (_jsxs("li", { className: "flex items-center gap-3 p-4", onClick: () => setPanelId(s.id), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-semibold text-foreground", children: s.name }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: s.description || `/pickstation/${s.slug ?? ""}` }), _jsx("div", { className: "mt-1", children: statusIcon(s) })] }), menu(s)] }, s.id))) })] })) }), !isLoading && rows.length > 0 && (_jsx(Pagination, { page: page, pageSize: pageSize, total: rows.length, onPage: setPage, onPageSize: setPageSize })), _jsxs(MobilePrimaryAction, { label: "Nieuw pickstation", onClick: () => setDraft({ ...emptyDraft }), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuw pickstation"] }), _jsxs(FiltersSheet, { open: filtersOpen, onOpenChange: setFiltersOpen, onClearAll: clearAll, children: [_jsx(YesNoField, { id: "f-multi", label: "Meerdere schermen toegestaan", value: multi, onChange: setMulti }), _jsx(YesNoField, { id: "f-country", label: "Land kiezen verplicht", value: country, onChange: setCountry })] }), _jsx(SidePanel, { open: panelStation !== null, onOpenChange: (o) => !o && setPanelId(null), title: panelStation?.name ?? "Pickstation", footer: panelStation && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => edit(panelStation), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), " Wijzigen"] }), _jsx(Button, { size: "sm", asChild: true, children: _jsxs("a", { href: publicUrl(panelStation.slug), target: "_blank", rel: "noreferrer", children: [_jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Pickstation openen"] }) })] })), children: panelStation && (_jsxs(_Fragment, { children: [panelStation.description && _jsx("p", { className: "text-sm text-muted-foreground", children: panelStation.description }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Link voor medewerkers" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("code", { className: "min-w-0 flex-1 truncate rounded bg-muted px-2 py-1.5 text-xs text-muted-foreground", children: publicUrl(panelStation.slug) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void copyLink(panelStation), children: [_jsx(Copy, { className: "mr-2 h-4 w-4" }), " Kopi\u00EBren"] })] })] }), _jsx(StationPrintplek, { station: panelStation }), _jsx("p", { className: "text-sm text-muted-foreground", children: DOCUMENT_AUTO_TEXT }), _jsx(StationDevices, { stationId: panelStation.id, slug: panelStation.slug ?? null })] })) }), _jsx(ShortcutHelp, { open: helpOpen, onOpenChange: setHelpOpen, shortcuts: SHORTCUTS }), _jsx(AlertDialog, { open: confirmDelete !== null, onOpenChange: (o) => !o && setConfirmDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Pickstation verwijderen?" }), _jsxs(AlertDialogDescription, { children: [confirmDelete?.name, " verdwijnt, en de link werkt daarna niet meer. Dit kun je niet ongedaan maken."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsxs(AlertDialogAction, { className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", onClick: (e) => {
196
+ e.preventDefault();
197
+ if (confirmDelete)
198
+ remove.mutate(confirmDelete.id);
199
+ }, children: [remove.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Verwijderen"] })] })] }) }), _jsx(Dialog, { open: draft !== null, onOpenChange: (open) => !open && setDraft(null), children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: draft?.id ? "Pickstation wijzigen" : "Nieuw pickstation" }) }), draft && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "station-name", children: "Naam" }), _jsx(Input, { id: "station-name", value: draft.name, onChange: (e) => setDraft({ ...draft, name: e.target.value }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "station-desc", children: "Omschrijving (optioneel)" }), _jsx(Textarea, { id: "station-desc", value: draft.description, onChange: (e) => setDraft({ ...draft, description: e.target.value }) })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-2", children: [_jsx(Label, { htmlFor: "station-active", children: "Actief" }), _jsx(Switch, { id: "station-active", checked: draft.active, onCheckedChange: (v) => setDraft({ ...draft, active: v }) })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => setDraft(null), children: "Annuleren" }), _jsxs(Button, { disabled: !draft.name.trim() || save.isPending, onClick: () => save.mutate(draft), children: [save.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), " Opslaan"] })] })] }))] }) })] }));
71
200
  }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from "react";
2
+ export interface Column<T> {
3
+ key: string;
4
+ header: string;
5
+ width: number;
6
+ cell: (row: T) => ReactNode;
7
+ }
8
+ /** Tabel exact als Verkooporders: table-fixed, colgroup in px, min 900 px, kop 40 px/11 px, rij 60 px, selectiestreep 3 px. */
9
+ export declare function DataTable<T>({ columns, rows, rowKey, focusedKey, onOpen, label }: {
10
+ columns: Column<T>[];
11
+ rows: T[];
12
+ rowKey: (r: T) => string;
13
+ focusedKey?: string | null;
14
+ onOpen?: (r: T) => void;
15
+ label: string;
16
+ }): import("react").JSX.Element;
17
+ /** Cel met hooguit twee regels: 14 px en 12 px grijs, afgekapt met volledige tekst als title. */
18
+ export declare function TwoLine({ top, bottom }: {
19
+ top: ReactNode;
20
+ bottom?: ReactNode;
21
+ }): import("react").JSX.Element;
22
+ //# sourceMappingURL=DataTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;CAC7B;AAED,+HAA+H;AAC/H,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,+BAiCnN;AAED,iGAAiG;AACjG,wBAAgB,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;IAAE,GAAG,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,+BAO9E"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Tabel exact als Verkooporders: table-fixed, colgroup in px, min 900 px, kop 40 px/11 px, rij 60 px, selectiestreep 3 px. */
3
+ export function DataTable({ columns, rows, rowKey, focusedKey, onOpen, label }) {
4
+ return (_jsxs("table", { "aria-label": label, className: "w-full min-w-[900px] table-fixed caption-bottom text-sm [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-10 [&_thead_th]:h-10 [&_thead_th]:bg-card [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground", children: [_jsx("colgroup", { children: columns.map((c) => _jsx("col", { style: { width: `${c.width}px` } }, c.key)) }), _jsx("thead", { children: _jsx("tr", { className: "border-b border-border", children: columns.map((c) => _jsx("th", { scope: "col", className: "px-2 text-left align-middle", children: c.header }, c.key)) }) }), _jsx("tbody", { children: rows.map((r) => {
5
+ const k = rowKey(r);
6
+ const focused = focusedKey === k;
7
+ return (_jsx("tr", { "data-focused": focused ? "true" : undefined, onClick: () => onOpen?.(r), style: { height: 60 }, className: "group cursor-pointer border-b border-border transition-colors last:border-0 hover:bg-muted/50 " + (focused ? "bg-muted/60" : ""), children: columns.map((c, i) => (_jsxs("td", { className: "relative min-w-0 overflow-hidden px-2 py-0 align-middle " + (i === 0 ? "" : ""), children: [i === 0 && focused && _jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-primary" }), c.cell(r)] }, c.key))) }, k));
8
+ }) })] }));
9
+ }
10
+ /** Cel met hooguit twee regels: 14 px en 12 px grijs, afgekapt met volledige tekst als title. */
11
+ export function TwoLine({ top, bottom }) {
12
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm text-foreground", title: typeof top === "string" ? top : undefined, children: top }), bottom != null && bottom !== "" && _jsx("div", { className: "truncate text-xs text-muted-foreground", title: typeof bottom === "string" ? bottom : undefined, children: bottom })] }));
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from "react";
2
+ /** Lege toestand: groot zacht icoon, korte zin, één actie. */
3
+ export declare function EmptyState({ icon, title, text, action }: {
4
+ icon: ReactNode;
5
+ title: string;
6
+ text?: string;
7
+ action?: ReactNode;
8
+ }): import("react").JSX.Element;
9
+ //# sourceMappingURL=EmptyState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/EmptyState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,+BAa9H"}
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Lege toestand: groot zacht icoon, korte zin, één actie. */
3
+ export function EmptyState({ icon, title, text, action }) {
4
+ return (_jsxs("div", { className: "flex flex-col items-center gap-3 px-4 py-14 text-center", children: [_jsx("span", { className: "inline-flex h-14 w-14 items-center justify-center rounded-full bg-muted text-muted-foreground", "aria-hidden": true, children: icon }), _jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold text-foreground", children: title }), text && _jsx("div", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: text })] }), action] }));
5
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function FilterChip({ label, onClear, icon }: {
3
+ label: string;
4
+ onClear: () => void;
5
+ icon?: ReactNode;
6
+ }): import("react").JSX.Element;
7
+ export declare function FilterChips({ chips, onClearAll }: {
8
+ chips: {
9
+ key: string;
10
+ label: string;
11
+ onClear: () => void;
12
+ }[];
13
+ onClearAll: () => void;
14
+ }): import("react").JSX.Element | null;
15
+ //# sourceMappingURL=FilterChips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterChips.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/FilterChips.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,+BAQ5G;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IAAE,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAA;CAAE,sCAY1I"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { X } from "lucide-react";
3
+ export function FilterChip({ label, onClear, icon }) {
4
+ return (_jsxs("button", { type: "button", onClick: onClear, "aria-label": `Filter ${label} wissen`, className: "inline-flex h-8 items-center gap-1.5 rounded-full border border-border bg-muted px-3 text-xs font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [icon, label, _jsx(X, { className: "h-3.5 w-3.5", "aria-hidden": true })] }));
5
+ }
6
+ export function FilterChips({ chips, onClearAll }) {
7
+ if (chips.length === 0)
8
+ return null;
9
+ return (_jsxs(_Fragment, { children: [chips.map((c) => (_jsx(FilterChip, { label: c.label, onClear: c.onClear }, c.key))), _jsx("button", { type: "button", onClick: onClearAll, className: "h-8 px-2 text-xs font-medium text-muted-foreground hover:text-foreground", children: "Alles wissen" })] }));
10
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from "react";
2
+ /** Zijpaneel rechts (op Dialog gebouwd, core heeft geen Sheet). */
3
+ export declare function SidePanel({ open, onOpenChange, title, children, footer }: {
4
+ open: boolean;
5
+ onOpenChange: (o: boolean) => void;
6
+ title: string;
7
+ children: ReactNode;
8
+ footer?: ReactNode;
9
+ }): import("react").JSX.Element;
10
+ export declare function FiltersSheet({ open, onOpenChange, children, onClearAll }: {
11
+ open: boolean;
12
+ onOpenChange: (o: boolean) => void;
13
+ children: ReactNode;
14
+ onClearAll: () => void;
15
+ }): import("react").JSX.Element;
16
+ //# sourceMappingURL=FiltersSheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FiltersSheet.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/FiltersSheet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,+BAYvL;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAA;CAAE,+BAgB5K"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@flowselections/core";
3
+ /** Zijpaneel rechts (op Dialog gebouwd, core heeft geen Sheet). */
4
+ export function SidePanel({ open, onOpenChange, title, children, footer }) {
5
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "left-auto right-0 top-0 flex h-[100dvh] max-h-[100dvh] w-full max-w-md translate-x-0 translate-y-0 flex-col gap-4 overflow-y-auto rounded-none border-l p-6 sm:rounded-none", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: title }) }), _jsx("div", { className: "flex-1 space-y-5", children: children }), footer && _jsx("div", { className: "flex justify-end gap-2 border-t border-border pt-4", children: footer })] }) }));
6
+ }
7
+ export function FiltersSheet({ open, onOpenChange, children, onClearAll }) {
8
+ return (_jsx(SidePanel, { open: open, onOpenChange: onOpenChange, title: "Filters", footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: onClearAll, children: "Alles wissen" }), _jsx(Button, { size: "sm", onClick: () => onOpenChange(false), children: "Klaar" })] }), children: children }));
9
+ }
@@ -0,0 +1,11 @@
1
+ import { type ReactNode } from "react";
2
+ /** Infoballon (referentie 1): opent bij aanwijzen, focus én tikken. */
3
+ export declare function HoverTip({ content, label, children, side, align, className, }: {
4
+ align?: "start" | "center" | "end";
5
+ content: ReactNode;
6
+ label: string;
7
+ children: ReactNode;
8
+ side?: "top" | "bottom" | "left" | "right";
9
+ className?: string;
10
+ }): import("react").JSX.Element;
11
+ //# sourceMappingURL=HoverTip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HoverTip.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/HoverTip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGzD,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,IAAY,EACZ,KAAgB,EAChB,SAAc,GACf,EAAE;IACD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAyCA"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from "react";
3
+ import { Popover, PopoverContent, PopoverTrigger } from "@flowselections/core";
4
+ /** Infoballon (referentie 1): opent bij aanwijzen, focus én tikken. */
5
+ export function HoverTip({ content, label, children, side = "top", align = "center", className = "", }) {
6
+ const [open, setOpen] = useState(false);
7
+ const timer = useRef(null);
8
+ const show = () => {
9
+ if (timer.current)
10
+ clearTimeout(timer.current);
11
+ setOpen(true);
12
+ };
13
+ const hide = () => {
14
+ if (timer.current)
15
+ clearTimeout(timer.current);
16
+ timer.current = setTimeout(() => setOpen(false), 80);
17
+ };
18
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { type: "button", "aria-label": label, onPointerEnter: show, onPointerLeave: hide, onFocus: show, onBlur: hide, onClick: (e) => e.stopPropagation(), className: "inline-flex shrink-0 items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " + className, children: children }), _jsx(PopoverContent, { side: side, align: align, avoidCollisions: true, collisionPadding: 8, onOpenAutoFocus: (e) => e.preventDefault(), onCloseAutoFocus: (e) => e.preventDefault(), onPointerEnter: show, onPointerLeave: hide, onClick: (e) => e.stopPropagation(), className: "w-auto min-w-[14rem] max-w-[20rem] rounded-lg p-3 text-xs leading-snug shadow-lg motion-reduce:animate-none", children: content })] }));
19
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=KpiTile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KpiTile.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/KpiTile.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ /** Vaste primaire knop onderin, alleen onder 768 px. */
3
+ export declare function MobilePrimaryAction({ onClick, children, label }: {
4
+ onClick: () => void;
5
+ children: ReactNode;
6
+ label: string;
7
+ }): import("react").JSX.Element;
8
+ //# sourceMappingURL=MobilePrimaryAction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MobilePrimaryAction.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/MobilePrimaryAction.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,+BAM5H"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button } from "@flowselections/core";
3
+ /** Vaste primaire knop onderin, alleen onder 768 px. */
4
+ export function MobilePrimaryAction({ onClick, children, label }) {
5
+ return (_jsx("div", { className: "fixed inset-x-0 bottom-0 z-30 border-t border-border bg-background/95 p-4 pb-[calc(1rem+env(safe-area-inset-bottom))] backdrop-blur sm:hidden", children: _jsx(Button, { className: "h-12 w-full", onClick: onClick, "aria-label": label, children: children }) }));
6
+ }
@@ -0,0 +1,6 @@
1
+ import { type ReactNode } from "react";
2
+ /** Eén inklapbare balk voor alle meldingen; hooguit 2 zichtbaar, de rest "+ n meldingen". */
3
+ export declare function NoticeBar({ items }: {
4
+ items: ReactNode[];
5
+ }): import("react").JSX.Element | null;
6
+ //# sourceMappingURL=NoticeBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoticeBar.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/NoticeBar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,EAAE,CAAA;CAAE,sCAuB1D"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ChevronDown, Info } from "lucide-react";
4
+ /** Eén inklapbare balk voor alle meldingen; hooguit 2 zichtbaar, de rest "+ n meldingen". */
5
+ export function NoticeBar({ items }) {
6
+ const [open, setOpen] = useState(false);
7
+ const list = items.filter(Boolean);
8
+ if (list.length === 0)
9
+ return null;
10
+ const shown = open ? list : list.slice(0, 2);
11
+ return (_jsxs("div", { className: "shrink-0 rounded-lg border border-border bg-warning-soft text-sm text-warning-soft-foreground", children: [_jsx("ul", { className: "space-y-1 px-3 py-2", children: shown.map((it, i) => (_jsxs("li", { className: "flex min-w-0 items-start gap-2", children: [_jsx(Info, { className: "mt-0.5 h-4 w-4 shrink-0", "aria-hidden": true }), _jsx("div", { className: "min-w-0 flex-1", children: it })] }, i))) }), list.length > 2 && (_jsxs("button", { type: "button", "aria-expanded": open, onClick: () => setOpen((o) => !o), className: "flex w-full items-center gap-2 border-t border-border/60 px-3 py-1.5 text-left text-xs font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [open ? "Minder tonen" : `+ ${list.length - 2} meldingen`, _jsx(ChevronDown, { className: "ml-auto h-4 w-4 transition-transform " + (open ? "rotate-180" : ""), "aria-hidden": true })] }))] }));
12
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ /** Paginakop zoals Verkooporders: h1, "Bijgewerkt", rechts de knoppen. */
3
+ export declare function PageHeader({ title, updated, actions }: {
4
+ title: string;
5
+ updated?: ReactNode;
6
+ actions?: ReactNode;
7
+ }): import("react").JSX.Element;
8
+ /** Kbd-hint in een primaire knop, alleen vanaf tablet. */
9
+ export declare function KbdHint({ k }: {
10
+ k: string;
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=PageHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageHeader.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/PageHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,+BAUlH;AAED,0DAA0D;AAC1D,wBAAgB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,+BAM3C"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Paginakop zoals Verkooporders: h1, "Bijgewerkt", rechts de knoppen. */
3
+ export function PageHeader({ title, updated, actions }) {
4
+ return (_jsxs("header", { className: "shrink-0 grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:flex sm:flex-wrap sm:justify-between", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "truncate text-xl font-bold leading-tight tracking-tight text-foreground sm:text-2xl", children: title }), updated] }), actions && _jsx("div", { className: "flex items-center gap-2", children: actions })] }));
5
+ }
6
+ /** Kbd-hint in een primaire knop, alleen vanaf tablet. */
7
+ export function KbdHint({ k }) {
8
+ return (_jsx("kbd", { "aria-hidden": true, className: "ml-2 hidden rounded border border-primary-foreground/30 bg-primary-foreground/15 px-1.5 font-mono text-[10px] leading-4 text-primary-foreground sm:inline-block", children: k }));
9
+ }
@@ -0,0 +1,8 @@
1
+ export declare function Pagination({ page, pageSize, total, onPage, onPageSize }: {
2
+ page: number;
3
+ pageSize: number;
4
+ total: number;
5
+ onPage: (p: number) => void;
6
+ onPageSize: (n: number) => void;
7
+ }): import("react").JSX.Element;
8
+ //# sourceMappingURL=Pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/Pagination.tsx"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,+BAqBxL"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@flowselections/core";
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ export function Pagination({ page, pageSize, total, onPage, onPageSize }) {
5
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
6
+ return (_jsxs("nav", { "aria-label": "Paginering", className: "shrink-0 flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground", children: [_jsx("div", { className: "tabular-nums", children: total === 0 ? "Geen resultaten" : `${(page - 1) * pageSize + 1}–${Math.min(page * pageSize, total)} van ${total}` }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "hidden items-center gap-2 sm:flex", children: [_jsx("span", { children: "Per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => onPageSize(Number(v)), children: [_jsx(SelectTrigger, { className: "h-8 w-20", "aria-label": "Resultaten per pagina", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: [10, 25, 50, 100].map((n) => _jsx(SelectItem, { value: String(n), children: n }, n)) })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-10 w-10 sm:h-8 sm:w-8", onClick: () => onPage(Math.max(1, page - 1)), disabled: page <= 1, "aria-label": "Vorige pagina", children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsxs("div", { className: "px-2 text-foreground tabular-nums", "aria-current": "page", children: [page, " / ", totalPages] }), _jsx(Button, { variant: "outline", size: "icon", className: "h-10 w-10 sm:h-8 sm:w-8", onClick: () => onPage(Math.min(totalPages, page + 1)), disabled: page >= totalPages, "aria-label": "Volgende pagina", children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] })] }));
7
+ }
@@ -0,0 +1,17 @@
1
+ import { type ComponentType } from "react";
2
+ export interface PillTab<T extends string> {
3
+ id: T;
4
+ label: string;
5
+ count: number;
6
+ icon?: ComponentType<{
7
+ className?: string;
8
+ }>;
9
+ }
10
+ /** Tabbladen exact als StatusTabs uit Verkooporders: één regel, horizontaal scrollen met vervaging. */
11
+ export declare function PillTabs<T extends string>({ tabs, value, onChange, label }: {
12
+ tabs: PillTab<T>[];
13
+ value: T;
14
+ onChange: (t: T) => void;
15
+ label: string;
16
+ }): import("react").JSX.Element;
17
+ //# sourceMappingURL=PillTabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PillTabs.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/PillTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAEpE,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM;IACvC,EAAE,EAAE,CAAC,CAAC;IACN,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,uGAAuG;AACvG,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,+BA2CrJ"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef } from "react";
3
+ /** Tabbladen exact als StatusTabs uit Verkooporders: één regel, horizontaal scrollen met vervaging. */
4
+ export function PillTabs({ tabs, value, onChange, label }) {
5
+ const listRef = useRef(null);
6
+ useLayoutEffect(() => {
7
+ const list = listRef.current;
8
+ const el = list?.querySelector(`[data-tab="${value}"]`);
9
+ if (el && list && (el.offsetLeft < list.scrollLeft || el.offsetLeft + el.offsetWidth > list.scrollLeft + list.clientWidth)) {
10
+ list.scrollLeft = el.offsetLeft - 8;
11
+ }
12
+ }, [value]);
13
+ return (_jsx("div", { className: "shrink-0", children: _jsx("div", { ref: listRef, role: "tablist", "aria-label": label, className: "relative -mx-4 flex items-center gap-2 overflow-x-auto px-4 pb-1 [scrollbar-width:none] [mask-image:linear-gradient(to_right,black_85%,transparent)] [&::-webkit-scrollbar]:hidden sm:mx-0 sm:px-0 sm:[mask-image:none]", children: tabs.map((t) => {
14
+ const active = value === t.id;
15
+ const Icon = t.icon;
16
+ return (_jsxs("button", { "data-tab": t.id, type: "button", role: "tab", "aria-selected": active, onClick: () => onChange(t.id), className: "relative flex h-9 items-center gap-2 whitespace-nowrap rounded-lg border px-3 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " +
17
+ (active
18
+ ? "border-primary/30 bg-primary/10 font-semibold text-foreground"
19
+ : t.count === 0
20
+ ? "border-border bg-card text-muted-foreground/70 hover:text-foreground"
21
+ : "border-border bg-card text-foreground/80 hover:bg-muted hover:text-foreground"), children: [Icon && _jsx(Icon, { className: "h-4 w-4 shrink-0" }), _jsx("span", { children: t.label }), _jsx("span", { className: "inline-flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-[11px] font-semibold tabular-nums " + (active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"), children: t.count })] }, t.id));
22
+ }) }) }));
23
+ }
@@ -0,0 +1,12 @@
1
+ export interface RowMenuItem {
2
+ label: string;
3
+ onSelect: () => void;
4
+ destructive?: boolean;
5
+ separatorBefore?: boolean;
6
+ }
7
+ /** Rijmenu exact als Verkooporders (drie puntjes, 32 px). */
8
+ export declare function RowMenu({ label, items }: {
9
+ label: string;
10
+ items: RowMenuItem[];
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=RowMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RowMenu.d.ts","sourceRoot":"","sources":["../../../src/components/fs-ui/RowMenu.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,6DAA6D;AAC7D,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,+BAkBhF"}