@flowselections/pick-schermen-kwekers 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +24 -0
  2. package/dist-lib/_core-safelist.d.ts +2 -0
  3. package/dist-lib/_core-safelist.d.ts.map +1 -0
  4. package/dist-lib/_core-safelist.js +15 -0
  5. package/dist-lib/components/PickBeheerPage.d.ts +2 -0
  6. package/dist-lib/components/PickBeheerPage.d.ts.map +1 -0
  7. package/dist-lib/components/PickBeheerPage.js +84 -0
  8. package/dist-lib/components/PickDashboardPage.d.ts +2 -0
  9. package/dist-lib/components/PickDashboardPage.d.ts.map +1 -0
  10. package/dist-lib/components/PickDashboardPage.js +54 -0
  11. package/dist-lib/components/PickKioskPage.d.ts +7 -0
  12. package/dist-lib/components/PickKioskPage.d.ts.map +1 -0
  13. package/dist-lib/components/PickKioskPage.js +18 -0
  14. package/dist-lib/components/PickstationDetailPage.d.ts +6 -0
  15. package/dist-lib/components/PickstationDetailPage.d.ts.map +1 -0
  16. package/dist-lib/components/PickstationDetailPage.js +25 -0
  17. package/dist-lib/components/PickstationsPage.d.ts +2 -0
  18. package/dist-lib/components/PickstationsPage.d.ts.map +1 -0
  19. package/dist-lib/components/PickstationsPage.js +18 -0
  20. package/dist-lib/components/TemplateModuleLogo.d.ts +6 -0
  21. package/dist-lib/components/TemplateModuleLogo.d.ts.map +1 -0
  22. package/dist-lib/components/TemplateModuleLogo.js +4 -0
  23. package/dist-lib/components/TemplateModulePage.d.ts +2 -0
  24. package/dist-lib/components/TemplateModulePage.d.ts.map +1 -0
  25. package/dist-lib/components/TemplateModulePage.js +44 -0
  26. package/dist-lib/components/TemplatePage.d.ts +2 -0
  27. package/dist-lib/components/TemplatePage.d.ts.map +1 -0
  28. package/dist-lib/components/TemplatePage.js +4 -0
  29. package/dist-lib/components/pick/ConfigCrud.d.ts +27 -0
  30. package/dist-lib/components/pick/ConfigCrud.d.ts.map +1 -0
  31. package/dist-lib/components/pick/ConfigCrud.js +64 -0
  32. package/dist-lib/components/pick/CountryTiles.d.ts +9 -0
  33. package/dist-lib/components/pick/CountryTiles.d.ts.map +1 -0
  34. package/dist-lib/components/pick/CountryTiles.js +13 -0
  35. package/dist-lib/components/pick/PickScherm.d.ts +10 -0
  36. package/dist-lib/components/pick/PickScherm.d.ts.map +1 -0
  37. package/dist-lib/components/pick/PickScherm.js +114 -0
  38. package/dist-lib/components/pick/QueueTable.d.ts +13 -0
  39. package/dist-lib/components/pick/QueueTable.d.ts.map +1 -0
  40. package/dist-lib/components/pick/QueueTable.js +29 -0
  41. package/dist-lib/components/settings/MijnModuleSettingsCard.d.ts +2 -0
  42. package/dist-lib/components/settings/MijnModuleSettingsCard.d.ts.map +1 -0
  43. package/dist-lib/components/settings/MijnModuleSettingsCard.js +42 -0
  44. package/dist-lib/hooks/useMijnModuleData.d.ts +13 -0
  45. package/dist-lib/hooks/useMijnModuleData.d.ts.map +1 -0
  46. package/dist-lib/hooks/useMijnModuleData.js +49 -0
  47. package/dist-lib/hooks/usePickConfig.d.ts +9 -0
  48. package/dist-lib/hooks/usePickConfig.d.ts.map +1 -0
  49. package/dist-lib/hooks/usePickConfig.js +29 -0
  50. package/dist-lib/hooks/usePickQueue.d.ts +34 -0
  51. package/dist-lib/hooks/usePickQueue.d.ts.map +1 -0
  52. package/dist-lib/hooks/usePickQueue.js +105 -0
  53. package/dist-lib/index.d.ts +20 -0
  54. package/dist-lib/index.d.ts.map +1 -0
  55. package/dist-lib/index.js +53 -0
  56. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  57. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  58. package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
  59. package/dist-lib/integrations/supabase/auth-middleware.d.ts +15877 -0
  60. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  61. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  62. package/dist-lib/integrations/supabase/client.d.ts +15873 -0
  63. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  64. package/dist-lib/integrations/supabase/client.js +13 -0
  65. package/dist-lib/integrations/supabase/client.server.d.ts +15873 -0
  66. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  67. package/dist-lib/integrations/supabase/client.server.js +30 -0
  68. package/dist-lib/integrations/supabase/types.d.ts +16383 -0
  69. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  70. package/dist-lib/integrations/supabase/types.js +78 -0
  71. package/dist-lib/lib/pickApi.d.ts +89 -0
  72. package/dist-lib/lib/pickApi.d.ts.map +1 -0
  73. package/dist-lib/lib/pickApi.js +204 -0
  74. package/dist-lib/lib/pickTypes.d.ts +165 -0
  75. package/dist-lib/lib/pickTypes.d.ts.map +1 -0
  76. package/dist-lib/lib/pickTypes.js +24 -0
  77. package/dist-lib/lib/utils.d.ts +3 -0
  78. package/dist-lib/lib/utils.d.ts.map +1 -0
  79. package/dist-lib/lib/utils.js +5 -0
  80. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  81. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  82. package/dist-lib/lib/validationSchemas.js +25 -0
  83. package/dist-lib/styles.css +1 -0
  84. package/package.json +107 -0
  85. package/public/logo.png +0 -0
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # pick schermen kwekers
2
+
3
+ blank project start
4
+
5
+ This project was built with [Lovable](https://lovable.dev).
6
+
7
+ ## Build with Lovable
8
+
9
+ Continue developing this project in the [Lovable editor](https://lovable.dev/projects/f7462658-1d60-42f1-8e22-dbefe36f44ef).
10
+
11
+ - **Ship faster**: describe what you want to build and Lovable handles the code.
12
+ - **Stay in sync**: every change made in Lovable is committed straight to this repository.
13
+ - **Full ownership**: this code is yours. Push to `main` on GitHub and your changes sync back into Lovable, ready for your next prompt.
14
+
15
+ ## Development
16
+
17
+ Prefer working locally? You need Node.js and npm — [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating).
18
+
19
+ ```sh
20
+ git clone <this-repository-url>
21
+ cd <repository-name>
22
+ npm i
23
+ npm run dev
24
+ ```
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_core-safelist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_core-safelist.d.ts","sourceRoot":"","sources":["../src/_core-safelist.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ // Forceert Tailwind classes die uit @flowselections/core komen maar niet
2
+ // voorkomen in de eigen broncode van deze module.
3
+ // Zonder dit bestand worden deze classes weggeoptimaliseerd op de
4
+ // Lovable productie build.
5
+ const _safelist = [
6
+ // Layout — Sidebar en shell structuur
7
+ 'h-full', 'flex-col', 'flex-1', 'min-h-screen', 'ml-64', 'pt-16',
8
+ 'items-start', 'items-center', 'justify-between', 'border-t',
9
+ 'w-64', 'h-screen', 'h-20', 'overflow-y-auto',
10
+ // Grid — InstellingenPage
11
+ 'grid', 'lg:grid-cols-2', 'gap-6', 'p-6', 'space-y-6',
12
+ // Toggle/Switch — aan/uit zichtbaarheid
13
+ 'data-[state=checked]:bg-primary', 'data-[state=unchecked]:bg-input',
14
+ ];
15
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function PickBeheerPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=PickBeheerPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickBeheerPage.d.ts","sourceRoot":"","sources":["../../src/components/PickBeheerPage.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,cAAc,gCAsN7B"}
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { Card, CardContent, CardHeader, CardTitle, Tabs, TabsContent, TabsList, TabsTrigger } from "@flowselections/core";
5
+ import { ConfigCrud } from "./pick/ConfigCrud";
6
+ import { pickApi } from "../lib/pickApi";
7
+ import { safeArray, SORT_FIELDS } from "../lib/pickTypes";
8
+ import { usePickDocumentTypes, usePickPrinters, usePickStationTypes, usePickStations, usePickTasks, } from "../hooks/usePickConfig";
9
+ const baseFields = (extra = []) => [
10
+ { key: "name", label: "Naam", type: "text", required: true },
11
+ { key: "description", label: "Omschrijving", type: "textarea", inTable: false },
12
+ ...extra,
13
+ { key: "color", label: "Kleur", type: "color", defaultValue: "#3b82f6" },
14
+ { key: "icon", label: "Icoon (lucide naam)", type: "text", inTable: false, defaultValue: "package" },
15
+ { key: "sort_order", label: "Volgorde", type: "number", defaultValue: 100 },
16
+ { key: "active", label: "Actief", type: "boolean", defaultValue: true },
17
+ ];
18
+ export function PickBeheerPage() {
19
+ const { data: stations } = usePickStations();
20
+ const { data: printers } = usePickPrinters();
21
+ const { data: types } = usePickStationTypes();
22
+ const { data: tasks } = usePickTasks();
23
+ const { data: docTypes } = usePickDocumentTypes();
24
+ const [screenStation, setScreenStation] = useState(undefined);
25
+ const logs = useQuery({ queryKey: ["pick", "logs"], queryFn: () => pickApi.logs(50) });
26
+ const opts = (rows) => safeArray(rows).map((r) => ({ value: r.id, label: r.name }));
27
+ const stationOptions = opts(stations);
28
+ const firstStation = screenStation ?? stationOptions[0]?.value;
29
+ return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Beheer pickstations" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Alles is databasegestuurd: stations, schermen, taken, documenttypes, statussen, landen en printers." })] }), _jsxs(Tabs, { defaultValue: "stations", children: [_jsxs(TabsList, { className: "flex flex-wrap", children: [_jsx(TabsTrigger, { value: "stations", children: "Pickstations" }), _jsx(TabsTrigger, { value: "screens", children: "Pickschermen" }), _jsx(TabsTrigger, { value: "types", children: "Types" }), _jsx(TabsTrigger, { value: "tasks", children: "Taken" }), _jsx(TabsTrigger, { value: "documents", children: "Documenttypes" }), _jsx(TabsTrigger, { value: "statuses", children: "Statussen" }), _jsx(TabsTrigger, { value: "countries", children: "Landen" }), _jsx(TabsTrigger, { value: "printers", children: "Printers" }), _jsx(TabsTrigger, { value: "permissions", children: "Rechten" }), _jsx(TabsTrigger, { value: "logs", children: "Logboek" })] }), _jsx(TabsContent, { value: "stations", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_stations", title: "Pickstations", description: "Onbeperkt aantal stations, elk met een eigen taak en standaardprinter.", fields: baseFields([
30
+ { key: "station_type_id", label: "Type", type: "select", options: opts(types) },
31
+ { key: "task_id", label: "Taak", type: "select", options: opts(tasks) },
32
+ { key: "default_printer_id", label: "Standaardprinter", type: "select", options: opts(printers), inTable: false },
33
+ { key: "location", label: "Locatie", type: "text", inTable: false },
34
+ { key: "department", label: "Afdeling", type: "text", inTable: false },
35
+ { key: "allow_multi_screen", label: "Meerdere schermen toegestaan", type: "boolean", defaultValue: true, inTable: false },
36
+ { key: "lock_timeout_seconds", label: "Lock timeout (sec)", type: "number", defaultValue: 120, inTable: false },
37
+ ]) }) }), _jsxs(TabsContent, { value: "screens", className: "mt-4 space-y-4", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Kies een pickstation" }) }), _jsx(CardContent, { className: "flex flex-wrap gap-2", children: stationOptions.map((s) => (_jsx("button", { onClick: () => setScreenStation(s.value), className: `rounded-md border px-4 py-2 text-sm ${firstStation === s.value ? "border-primary bg-primary/10 font-medium" : "hover:bg-muted"}`, children: s.label }, s.value))) })] }), firstStation && (_jsx(ConfigCrud, { table: "pick_station_screens", title: "Pickschermen", description: "Een station kan meerdere schermen bevatten, elk met eigen documenttype en sorteerregels.", filter: { column: "station_id", value: firstStation }, fields: baseFields([
38
+ { key: "document_type_id", label: "Documenttype", type: "select", options: opts(docTypes) },
39
+ { key: "printer_id", label: "Printer", type: "select", options: opts(printers), inTable: false },
40
+ { key: "kiosk_enabled", label: "Kioskmodus", type: "boolean", defaultValue: true, inTable: false },
41
+ { key: "primary_sort_field", label: "Sorteren op", type: "select", options: SORT_FIELDS, inTable: false, defaultValue: "announced_at" },
42
+ ]) }, firstStation))] }), _jsx(TabsContent, { value: "types", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_station_types", title: "Types pickstation", fields: baseFields() }) }), _jsx(TabsContent, { value: "tasks", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_station_tasks", title: "Taken", description: "Order picking, productie picking, retour picking \u2014 vrij uitbreidbaar.", fields: baseFields([{ key: "workflow", label: "Workflow", type: "text", inTable: false }]) }) }), _jsx(TabsContent, { value: "documents", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_document_types", title: "Documenttypes", fields: baseFields([
43
+ { key: "report_key", label: "Rapportkoppeling", type: "text", inTable: false },
44
+ { key: "print_template", label: "Printtemplate", type: "text", inTable: false },
45
+ { key: "default_printer_id", label: "Printer", type: "select", options: opts(printers), inTable: false },
46
+ { key: "priority", label: "Prioriteit", type: "number", defaultValue: 5 },
47
+ { key: "workflow", label: "Workflow", type: "text", inTable: false },
48
+ ]) }) }), _jsx(TabsContent, { value: "statuses", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_document_statuses", title: "Statussen", fields: [
49
+ { key: "code", label: "Code", type: "text" },
50
+ ...baseFields([{ key: "is_final", label: "Eindstatus", type: "boolean", defaultValue: false, inTable: false }]),
51
+ ] }) }), _jsx(TabsContent, { value: "countries", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_countries", title: "Landen", fields: [
52
+ { key: "code", label: "Landcode", type: "text" },
53
+ { key: "name", label: "Naam", type: "text" },
54
+ { key: "flag_emoji", label: "Vlag", type: "text" },
55
+ { key: "sort_order", label: "Volgorde", type: "number", defaultValue: 100 },
56
+ { key: "active", label: "Actief", type: "boolean", defaultValue: true },
57
+ ] }) }), _jsx(TabsContent, { value: "printers", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_station_printers", title: "Printers", orderBy: "name", fields: [
58
+ { key: "name", label: "Naam", type: "text" },
59
+ { key: "location", label: "Locatie", type: "text" },
60
+ { key: "connection", label: "Verbinding", type: "text", inTable: false },
61
+ {
62
+ key: "status",
63
+ label: "Status",
64
+ type: "select",
65
+ defaultValue: "online",
66
+ options: [
67
+ { value: "online", label: "Online" },
68
+ { value: "offline", label: "Offline" },
69
+ { value: "no_paper", label: "Geen papier" },
70
+ { value: "error", label: "Storing" },
71
+ { value: "unknown", label: "Onbekend" },
72
+ ],
73
+ },
74
+ { key: "active", label: "Actief", type: "boolean", defaultValue: true },
75
+ ] }) }), _jsx(TabsContent, { value: "permissions", className: "mt-4", children: _jsx(ConfigCrud, { table: "pick_station_permissions", title: "Rechten per gebruiker", orderBy: "created_at", description: "Beperk gebruikers tot een station, land of spoedwerk en bepaal wie mag herprinten of locks verbreken.", fields: [
76
+ { key: "user_id", label: "Gebruiker ID", type: "text" },
77
+ { key: "station_id", label: "Pickstation", type: "select", options: stationOptions },
78
+ { key: "country_code", label: "Land (leeg = alle)", type: "text" },
79
+ { key: "urgent_only", label: "Alleen spoed", type: "boolean", defaultValue: false, inTable: false },
80
+ { key: "can_reprint", label: "Mag herprinten", type: "boolean", defaultValue: false },
81
+ { key: "can_break_lock", label: "Mag lock verbreken", type: "boolean", defaultValue: false },
82
+ { key: "can_cancel", label: "Mag annuleren", type: "boolean", defaultValue: false },
83
+ ] }) }), _jsx(TabsContent, { value: "logs", className: "mt-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Laatste 50 acties" }) }), _jsxs(CardContent, { className: "space-y-1 text-sm", children: [safeArray(logs.data).map((l) => (_jsxs("div", { className: "flex justify-between border-b py-1 last:border-0", children: [_jsx("span", { className: "font-medium", children: l.action }), _jsx("span", { className: "text-muted-foreground", children: new Date(l.created_at).toLocaleString("nl-NL") })] }, l.id))), safeArray(logs.data).length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Nog geen acties gelogd." }))] })] }) })] })] }));
84
+ }
@@ -0,0 +1,2 @@
1
+ export declare function PickDashboardPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=PickDashboardPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickDashboardPage.d.ts","sourceRoot":"","sources":["../../src/components/PickDashboardPage.tsx"],"names":[],"mappings":"AASA,wBAAgB,iBAAiB,gCA8IhC"}
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { Badge, Card, CardContent, CardHeader, CardTitle } from "@flowselections/core";
5
+ import { Activity, Clock, Loader2, Printer, Users } from "lucide-react";
6
+ import { pickApi, pickDb } from "../lib/pickApi";
7
+ import { safeArray } from "../lib/pickTypes";
8
+ import { usePickPrinters, usePickStations } from "../hooks/usePickConfig";
9
+ export function PickDashboardPage() {
10
+ const queryClient = useQueryClient();
11
+ const { data: stations } = usePickStations();
12
+ const { data: printers } = usePickPrinters();
13
+ const dashboard = useQuery({
14
+ queryKey: ["pick", "dashboard"],
15
+ queryFn: pickApi.dashboard,
16
+ refetchInterval: 10000,
17
+ });
18
+ const heartbeats = useQuery({
19
+ queryKey: ["pick", "heartbeats"],
20
+ queryFn: pickApi.heartbeats,
21
+ refetchInterval: 10000,
22
+ });
23
+ useEffect(() => {
24
+ const channel = pickDb
25
+ .channel("pick-dashboard")
26
+ .on("postgres_changes", { event: "*", schema: "public", table: "pick_queue" }, () => {
27
+ queryClient.invalidateQueries({ queryKey: ["pick", "dashboard"] });
28
+ })
29
+ .on("postgres_changes", { event: "*", schema: "public", table: "pick_queue_locks" }, () => {
30
+ queryClient.invalidateQueries({ queryKey: ["pick", "dashboard"] });
31
+ })
32
+ .subscribe();
33
+ return () => {
34
+ pickDb.removeChannel(channel);
35
+ };
36
+ }, [queryClient]);
37
+ const rows = safeArray(dashboard.data);
38
+ const beats = safeArray(heartbeats.data);
39
+ const safeStations = safeArray(stations);
40
+ const safePrinters = safeArray(printers);
41
+ return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Live dashboard" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Realtime status van alle pickstations, schermen en printers." })] }), dashboard.isLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden..."] })) : (_jsxs("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3", children: [rows.map((row) => {
42
+ const station = safeStations.find((s) => s.id === row.station_id);
43
+ const online = beats.filter((b) => b.station_id === row.station_id &&
44
+ Date.now() - new Date(b.last_seen_at).getTime() < 60000).length;
45
+ const printer = safePrinters.find((p) => p.id === station?.default_printer_id);
46
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { children: row.station_name }), _jsx(Badge, { className: online > 0
47
+ ? "bg-emerald-600 text-white"
48
+ : "bg-muted text-muted-foreground", children: online > 0 ? `${online} online` : "offline" })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-3 gap-2 text-center", children: [_jsxs("div", { className: "rounded-lg bg-muted p-3", children: [_jsx("div", { className: "text-2xl font-bold", children: row.waiting }), _jsx("div", { className: "text-xs text-muted-foreground", children: "wachtend" })] }), _jsxs("div", { className: "rounded-lg bg-muted p-3", children: [_jsx("div", { className: "text-2xl font-bold", children: row.active }), _jsx("div", { className: "text-xs text-muted-foreground", children: "actief" })] }), _jsxs("div", { className: "rounded-lg bg-muted p-3", children: [_jsx("div", { className: "text-2xl font-bold", children: row.done_today }), _jsx("div", { className: "text-xs text-muted-foreground", children: "vandaag" })] })] }), _jsxs("div", { className: "space-y-1 text-sm text-muted-foreground", children: [_jsxs("p", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "h-4 w-4" }), "Gemiddelde wachttijd: ", Math.round(Number(row.avg_wait_seconds) / 60), " min"] }), _jsxs("p", { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-4 w-4" }), " Actieve sessies: ", row.active_sessions] }), _jsxs("p", { className: "flex items-center gap-2", children: [_jsx(Printer, { className: "h-4 w-4" }), " Printer: ", printer?.name ?? "geen", printer ? ` (${printer.status})` : ""] })] })] })] }, row.station_id));
49
+ }), rows.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen actieve pickstations." }))] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Activity, { className: "h-5 w-5" }), " Printerstatus"] }) }), _jsx(CardContent, { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: safePrinters.map((p) => (_jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: p.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: p.location ?? "-" })] }), _jsx(Badge, { className: p.status === "online"
50
+ ? "bg-emerald-600 text-white"
51
+ : p.status === "unknown"
52
+ ? "bg-muted text-muted-foreground"
53
+ : "bg-destructive text-destructive-foreground", children: p.status })] }, p.id))) })] })] }));
54
+ }
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ stationId: string;
3
+ screenId: string;
4
+ }
5
+ export declare function PickKioskPage({ stationId, screenId }: Props): import("react").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=PickKioskPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickKioskPage.d.ts","sourceRoot":"","sources":["../../src/components/PickKioskPage.tsx"],"names":[],"mappings":"AAMA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,KAAK,+BA4B3D"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Loader2 } from "lucide-react";
3
+ import { usePickScreens, usePickStations } from "../hooks/usePickConfig";
4
+ import { safeArray } from "../lib/pickTypes";
5
+ import { PickScherm } from "./pick/PickScherm";
6
+ export function PickKioskPage({ stationId, screenId }) {
7
+ const { data: stations, isLoading: l1 } = usePickStations();
8
+ const { data: screens, isLoading: l2 } = usePickScreens(stationId);
9
+ const station = safeArray(stations).find((s) => s.id === stationId);
10
+ const screen = safeArray(screens).find((s) => s.id === screenId);
11
+ if (l1 || l2) {
12
+ return (_jsx("div", { className: "flex min-h-screen items-center justify-center bg-background", children: _jsx(Loader2, { className: "h-8 w-8 animate-spin text-primary" }) }));
13
+ }
14
+ if (!station || !screen) {
15
+ return (_jsx("div", { className: "flex min-h-screen items-center justify-center bg-background", children: _jsx("p", { className: "text-muted-foreground", children: "Pickscherm niet gevonden." }) }));
16
+ }
17
+ return (_jsx("div", { className: "min-h-screen bg-background", children: _jsx(PickScherm, { station: station, screen: screen, kiosk: true }) }));
18
+ }
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ stationId: string;
3
+ }
4
+ export declare function PickstationDetailPage({ stationId }: Props): import("react").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=PickstationDetailPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickstationDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/PickstationDetailPage.tsx"],"names":[],"mappings":"AAOA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BA2FzD"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Link } from "@tanstack/react-router";
4
+ import { Badge, Button, Card, CardContent } from "@flowselections/core";
5
+ import { ArrowLeft, Loader2, Maximize2, MonitorPlay } from "lucide-react";
6
+ import { PickScherm } from "./pick/PickScherm";
7
+ import { usePickScreens, usePickStations } from "../hooks/usePickConfig";
8
+ export function PickstationDetailPage({ stationId }) {
9
+ const { data: stations, isLoading } = usePickStations();
10
+ const { data: screens, isLoading: screensLoading } = usePickScreens(stationId);
11
+ const [screenId, setScreenId] = useState(null);
12
+ const station = useMemo(() => (Array.isArray(stations) ? stations : []).find((s) => s.id === stationId), [stations, stationId]);
13
+ const safeScreens = (Array.isArray(screens) ? screens : []).filter((s) => s.active);
14
+ const screen = safeScreens.find((s) => s.id === screenId);
15
+ if (isLoading || screensLoading) {
16
+ return (_jsxs("main", { className: "flex items-center gap-2 p-6 text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden..."] }));
17
+ }
18
+ if (!station) {
19
+ return (_jsxs("main", { className: "space-y-4 p-6", children: [_jsx("p", { className: "text-muted-foreground", children: "Pickstation niet gevonden." }), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/pickstations", children: "Terug naar overzicht" }) })] }));
20
+ }
21
+ if (screen) {
22
+ return (_jsx("main", { className: "p-6", children: _jsx(PickScherm, { station: station, screen: screen, onBack: () => setScreenId(null) }) }));
23
+ }
24
+ 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: station.name }), _jsx("p", { className: "text-sm text-muted-foreground", children: station.description })] }), _jsx(Button, { variant: "outline", asChild: true, children: _jsxs(Link, { to: "/pickstations", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), " Alle pickstations"] }) })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3", children: [safeScreens.map((s) => (_jsx(Card, { className: "border-2 transition-all hover:border-primary", children: _jsxs(CardContent, { className: "space-y-4 p-6", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(MonitorPlay, { className: "mt-1 h-6 w-6 text-primary" }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: s.name }), _jsx("p", { className: "text-sm text-muted-foreground", children: s.description })] })] }), _jsx(Badge, { variant: "outline", children: s.screen_type }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { className: "h-12 flex-1 text-base", onClick: () => setScreenId(s.id), children: "Openen" }), _jsx(Button, { variant: "outline", size: "icon", className: "h-12 w-12", asChild: true, children: _jsx(Link, { to: "/kiosk/$stationId/$screenId", params: { stationId: station.id, screenId: s.id }, target: "_blank", "aria-label": "Openen in kioskmodus", children: _jsx(Maximize2, { className: "h-5 w-5" }) }) })] })] }) }, s.id))), safeScreens.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Dit pickstation heeft nog geen pickschermen. Voeg ze toe via Beheer." }))] })] }));
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare function PickstationsPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=PickstationsPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickstationsPage.d.ts","sourceRoot":"","sources":["../../src/components/PickstationsPage.tsx"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,gCA+E/B"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Link } from "@tanstack/react-router";
3
+ import { Badge, Button, Card, CardContent } from "@flowselections/core";
4
+ import { Gauge, Loader2, Settings2 } from "lucide-react";
5
+ import { usePickScreens, usePickStations, usePickTasks } from "../hooks/usePickConfig";
6
+ export function PickstationsPage() {
7
+ const { data: stations, isLoading } = usePickStations();
8
+ const { data: screens } = usePickScreens();
9
+ const { data: tasks } = usePickTasks();
10
+ const safeStations = (Array.isArray(stations) ? stations : []).filter((s) => s.active);
11
+ const safeScreens = Array.isArray(screens) ? screens : [];
12
+ const safeTasks = Array.isArray(tasks) ? tasks : [];
13
+ 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: "Kies een pickstation om te beginnen met picken en printen." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", asChild: true, children: _jsxs(Link, { to: "/pickstations/dashboard", children: [_jsx(Gauge, { className: "mr-2 h-4 w-4" }), " Live dashboard"] }) }), _jsx(Button, { variant: "outline", asChild: true, children: _jsxs(Link, { to: "/pickstations/beheer", children: [_jsx(Settings2, { className: "mr-2 h-4 w-4" }), " Beheer"] }) })] })] }), 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. Maak er een aan via Beheer." })) : (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-3", children: safeStations.map((station) => {
14
+ const stationScreens = safeScreens.filter((s) => s.station_id === station.id && s.active);
15
+ const task = safeTasks.find((t) => t.id === station.task_id);
16
+ return (_jsx(Link, { to: "/pickstations/$stationId", params: { stationId: station.id }, className: "block", children: _jsx(Card, { className: "h-full border-2 transition-all hover:border-primary hover:shadow-lg", children: _jsxs(CardContent, { className: "space-y-3 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: "h-10 w-10 shrink-0 rounded-lg", style: { backgroundColor: station.color ?? "#3b82f6" } })] }), _jsxs("div", { className: "flex flex-wrap gap-2 text-xs", children: [task && _jsx(Badge, { variant: "secondary", children: task.name }), station.location && _jsx(Badge, { variant: "outline", children: station.location }), station.department && _jsx(Badge, { variant: "outline", children: station.department }), _jsxs(Badge, { variant: "outline", children: [stationScreens.length, " schermen"] })] })] }) }) }, station.id));
17
+ }) }))] }));
18
+ }
@@ -0,0 +1,6 @@
1
+ interface TemplateModuleLogoProps {
2
+ className?: string;
3
+ }
4
+ export declare function TemplateModuleLogo({ className }: TemplateModuleLogoProps): import("react").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=TemplateModuleLogo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateModuleLogo.d.ts","sourceRoot":"","sources":["../../src/components/TemplateModuleLogo.tsx"],"names":[],"mappings":"AAQA,UAAU,uBAAuB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE,uBAAuB,+BAwBxE"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function TemplateModuleLogo({ className }) {
3
+ return (_jsxs("svg", { viewBox: "0 0 40 40", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className, "aria-hidden": "true", children: [_jsx("rect", { width: "40", height: "40", rx: "8", fill: "currentColor", opacity: "0.1" }), _jsx("text", { x: "50%", y: "50%", dominantBaseline: "middle", textAnchor: "middle", fontSize: "18", fontWeight: "600", fill: "currentColor", children: "M" })] }));
4
+ }
@@ -0,0 +1,2 @@
1
+ export declare function TemplateModulePage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=TemplateModulePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateModulePage.d.ts","sourceRoot":"","sources":["../../src/components/TemplateModulePage.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,kBAAkB,gCAmFjC"}
@@ -0,0 +1,44 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ // ============================================================================
3
+ // src/components/TemplateModulePage.tsx — EXAMPLE PAGE COMPONENT
4
+ // ============================================================================
5
+ // Pagina-componenten horen in src/components/, NIET alleen in src/routes/.
6
+ // src/routes/ wordt NIET naar npm gepubliceerd (zie tsconfig.build.json exclude).
7
+ //
8
+ // GEEN <Header /> hier — die zit in _authenticated.tsx via staticData.title
9
+ // ============================================================================
10
+ import { useState, useEffect } from "react";
11
+ import { useAuth, supabase, Button, Card, CardContent, CardHeader, CardTitle, toast, } from "@flowselections/core";
12
+ import { TemplateModuleLogo } from './TemplateModuleLogo';
13
+ export function TemplateModulePage() {
14
+ // Auth is guaranteed — this user is already logged in
15
+ const { user, isAdmin } = useAuth();
16
+ // Example state — replace with your own
17
+ const [items, setItems] = useState([]);
18
+ const [loading, setLoading] = useState(true);
19
+ useEffect(() => {
20
+ loadData();
21
+ }, []);
22
+ const loadData = async () => {
23
+ setLoading(true);
24
+ try {
25
+ // Example query — replace "products" with your table
26
+ const { data, error } = await supabase
27
+ .from("products")
28
+ .select("*")
29
+ .limit(10);
30
+ if (error)
31
+ throw error;
32
+ // ✅ Altijd Array.isArray bewaken op server-function data
33
+ // const safeItems = Array.isArray(data?.items) ? data.items : [];
34
+ setItems(Array.isArray(data) ? data : []);
35
+ }
36
+ catch {
37
+ toast.error("Fout bij het laden van gegevens");
38
+ }
39
+ finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [isAdmin && (_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Ingelogd als admin: ", user?.email] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Overzicht" }) }), _jsxs(CardContent, { children: [loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen gegevens gevonden." })) : (_jsxs("p", { className: "text-sm", children: [items.length, " items geladen."] })), _jsx(Button, { onClick: loadData, className: "mt-4", disabled: loading, children: "Vernieuwen" })] })] }), _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(TemplateModuleLogo, { className: "w-6 h-6 text-primary" }), _jsx("span", { children: "SVG-component (voorkeur voor vector-logo's)" })] })] }));
44
+ }
@@ -0,0 +1,2 @@
1
+ export declare function TemplatePage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=TemplatePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplatePage.d.ts","sourceRoot":"","sources":["../../src/components/TemplatePage.tsx"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,gCAS3B"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function TemplatePage() {
3
+ return (_jsxs("div", { className: "p-6", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Module pagina" }), _jsx("p", { className: "text-muted-foreground mt-2", children: "Vervang dit component met de pagina's van jouw module." })] }));
4
+ }
@@ -0,0 +1,27 @@
1
+ export type FieldType = "text" | "textarea" | "number" | "boolean" | "color" | "select";
2
+ export interface CrudField {
3
+ key: string;
4
+ label: string;
5
+ type: FieldType;
6
+ options?: {
7
+ value: string;
8
+ label: string;
9
+ }[];
10
+ defaultValue?: unknown;
11
+ inTable?: boolean;
12
+ required?: boolean;
13
+ }
14
+ interface Props {
15
+ table: string;
16
+ title: string;
17
+ description?: string;
18
+ fields: CrudField[];
19
+ orderBy?: string;
20
+ filter?: {
21
+ column: string;
22
+ value: string;
23
+ };
24
+ }
25
+ export declare function ConfigCrud({ table, title, description, fields, orderBy, filter }: Props): import("react").JSX.Element;
26
+ export {};
27
+ //# sourceMappingURL=ConfigCrud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigCrud.d.ts","sourceRoot":"","sources":["../../../src/components/pick/ConfigCrud.tsx"],"names":[],"mappings":"AAgCA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAExF,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAID,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAsB,EAAE,MAAM,EAAE,EAAE,KAAK,+BA0MtG"}
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { Button, Card, CardContent, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, toast, } from "@flowselections/core";
5
+ import { Loader2, Pencil, Plus, Trash2 } from "lucide-react";
6
+ import { pickApi, pickDb } from "../../lib/pickApi";
7
+ import { safeArray } from "../../lib/pickTypes";
8
+ export function ConfigCrud({ table, title, description, fields, orderBy = "sort_order", filter }) {
9
+ const queryClient = useQueryClient();
10
+ const [open, setOpen] = useState(false);
11
+ const [draft, setDraft] = useState({});
12
+ const queryKey = ["pick", "crud", table, filter?.value ?? "-"];
13
+ const { data, isLoading } = useQuery({
14
+ queryKey,
15
+ queryFn: async () => {
16
+ let q = pickDb.from(table).select("*").order(orderBy, { ascending: true });
17
+ if (filter)
18
+ q = q.eq(filter.column, filter.value);
19
+ const res = await q;
20
+ if (res.error)
21
+ throw new Error(res.error.message);
22
+ return safeArray(res.data);
23
+ },
24
+ });
25
+ const rows = safeArray(data);
26
+ const tableFields = useMemo(() => fields.filter((f) => f.inTable !== false), [fields]);
27
+ const save = useMutation({
28
+ mutationFn: async (row) => {
29
+ const payload = { ...row };
30
+ if (filter)
31
+ payload[filter.column] = filter.value;
32
+ return pickApi.upsert(table, payload);
33
+ },
34
+ onSuccess: () => {
35
+ toast.success("Opgeslagen");
36
+ setOpen(false);
37
+ queryClient.invalidateQueries({ queryKey });
38
+ queryClient.invalidateQueries({ queryKey: ["pick"] });
39
+ },
40
+ onError: (e) => toast.error(e instanceof Error ? e.message : "Opslaan mislukt (alleen beheerders)."),
41
+ });
42
+ const remove = useMutation({
43
+ mutationFn: (id) => pickApi.remove(table, id),
44
+ onSuccess: () => {
45
+ toast.success("Verwijderd");
46
+ queryClient.invalidateQueries({ queryKey });
47
+ queryClient.invalidateQueries({ queryKey: ["pick"] });
48
+ },
49
+ onError: (e) => toast.error(e instanceof Error ? e.message : "Verwijderen mislukt (alleen beheerders)."),
50
+ });
51
+ const openNew = () => {
52
+ const base = {};
53
+ for (const f of fields)
54
+ base[f.key] = f.defaultValue ?? (f.type === "boolean" ? true : "");
55
+ setDraft(base);
56
+ setOpen(true);
57
+ };
58
+ const openEdit = (row) => {
59
+ setDraft({ ...row });
60
+ setOpen(true);
61
+ };
62
+ const setField = (key, value) => setDraft((d) => ({ ...d, [key]: value }));
63
+ return (_jsxs(Card, { children: [_jsxs(CardContent, { className: "space-y-4 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: title }), description && _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), _jsxs(Button, { onClick: openNew, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Nieuw"] })] }), isLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Laden..."] })) : (_jsx("div", { className: "overflow-x-auto rounded-lg border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [tableFields.map((f) => (_jsx(TableHead, { children: f.label }, f.key))), _jsx(TableHead, { className: "text-right", children: "Acties" })] }) }), _jsxs(TableBody, { children: [rows.map((row) => (_jsxs(TableRow, { children: [tableFields.map((f) => (_jsx(TableCell, { children: f.type === "boolean" ? (row[f.key] ? "Ja" : "Nee") : f.type === "color" ? (_jsx("span", { className: "inline-block h-5 w-10 rounded", style: { backgroundColor: String(row[f.key] ?? "#ccc") } })) : f.type === "select" ? (f.options?.find((o) => o.value === row[f.key])?.label ?? "-") : (String(row[f.key] ?? "-")) }, f.key))), _jsxs(TableCell, { className: "space-x-2 text-right", children: [_jsx(Button, { size: "icon", variant: "ghost", onClick: () => openEdit(row), "aria-label": "Bewerken", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => remove.mutate(String(row.id)), "aria-label": "Verwijderen", children: _jsx(Trash2, { className: "h-4 w-4 text-destructive" }) })] })] }, row.id))), rows.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: tableFields.length + 1, className: "py-8 text-center text-muted-foreground", children: "Nog geen gegevens." }) }))] })] }) }))] }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { className: "max-h-[85vh] overflow-y-auto", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { children: [draft.id ? "Bewerken" : "Nieuw", " \u2014 ", title] }) }), _jsx("div", { className: "space-y-4", children: fields.map((f) => (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: `fld-${f.key}`, children: f.label }), f.type === "textarea" ? (_jsx(Textarea, { id: `fld-${f.key}`, value: String(draft[f.key] ?? ""), onChange: (e) => setField(f.key, e.target.value) })) : f.type === "boolean" ? (_jsx("div", { children: _jsx(Switch, { checked: Boolean(draft[f.key]), onCheckedChange: (v) => setField(f.key, v) }) })) : f.type === "select" ? (_jsxs(Select, { value: draft[f.key] ? String(draft[f.key]) : "none", onValueChange: (v) => setField(f.key, v === "none" ? null : v), children: [_jsx(SelectTrigger, { id: `fld-${f.key}`, children: _jsx(SelectValue, { placeholder: "Kies..." }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: "Geen" }), (f.options ?? []).map((o) => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value)))] })] })) : (_jsx(Input, { id: `fld-${f.key}`, type: f.type === "number" ? "number" : f.type === "color" ? "color" : "text", value: String(draft[f.key] ?? ""), onChange: (e) => setField(f.key, f.type === "number" ? Number(e.target.value) : e.target.value) }))] }, f.key))) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(false), children: "Annuleren" }), _jsxs(Button, { onClick: () => save.mutate(draft), disabled: save.isPending, children: [save.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Opslaan"] })] })] }) })] }));
64
+ }
@@ -0,0 +1,9 @@
1
+ import type { PickCountry } from "../../lib/pickTypes";
2
+ interface Props {
3
+ countries: PickCountry[];
4
+ counts: Record<string, number>;
5
+ onSelect: (code: string) => void;
6
+ }
7
+ export declare function CountryTiles({ countries, counts, onSelect }: Props): import("react").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=CountryTiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CountryTiles.d.ts","sourceRoot":"","sources":["../../../src/components/pick/CountryTiles.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,UAAU,KAAK;IACb,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,+BAmClE"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card } from "@flowselections/core";
3
+ export function CountryTiles({ countries, counts, onSelect }) {
4
+ const list = Array.isArray(countries) ? countries : [];
5
+ return (_jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5", children: [list.map((c) => {
6
+ const count = counts?.[c.code] ?? 0;
7
+ return (_jsxs(Card, { role: "button", tabIndex: 0, onClick: () => onSelect(c.code), onKeyDown: (e) => {
8
+ if (e.key === "Enter" || e.key === " ")
9
+ onSelect(c.code);
10
+ }, className: "cursor-pointer select-none border-2 p-6 text-center transition-all hover:border-primary hover:shadow-lg focus:border-primary focus:outline-none active:scale-[0.98]", children: [_jsx("div", { className: "text-5xl leading-none", children: c.flag ?? "🏳️" }), _jsx("div", { className: "mt-3 text-lg font-semibold text-foreground", children: c.name }), _jsx("div", { className: "mt-2 inline-flex min-w-12 items-center justify-center rounded-full px-3 py-1 text-sm font-bold " +
11
+ (count > 0 ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"), children: count })] }, c.id));
12
+ }), list.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen landen geconfigureerd." }))] }));
13
+ }
@@ -0,0 +1,10 @@
1
+ import type { PickScreen, PickStation } from "../../lib/pickTypes";
2
+ interface Props {
3
+ station: PickStation;
4
+ screen: PickScreen;
5
+ onBack?: () => void;
6
+ kiosk?: boolean;
7
+ }
8
+ export declare function PickScherm({ station, screen, onBack, kiosk }: Props): import("react").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=PickScherm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickScherm.d.ts","sourceRoot":"","sources":["../../../src/components/pick/PickScherm.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAiB,UAAU,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAelF,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,+BAgQnE"}