@flowselections/klanten-installatie-techniek-planner 0.0.0-stage → 1.0.20

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 (91) hide show
  1. package/dist-lib/_core-safelist.d.ts +2 -0
  2. package/dist-lib/_core-safelist.d.ts.map +1 -0
  3. package/dist-lib/_core-safelist.js +15 -0
  4. package/dist-lib/components/KlantenDetailPage.d.ts +2 -0
  5. package/dist-lib/components/KlantenDetailPage.d.ts.map +1 -0
  6. package/dist-lib/components/KlantenDetailPage.js +30 -0
  7. package/dist-lib/components/KlantenListPage.d.ts +6 -0
  8. package/dist-lib/components/KlantenListPage.d.ts.map +1 -0
  9. package/dist-lib/components/KlantenListPage.js +105 -0
  10. package/dist-lib/components/TemplateModuleLogo.d.ts +6 -0
  11. package/dist-lib/components/TemplateModuleLogo.d.ts.map +1 -0
  12. package/dist-lib/components/TemplateModuleLogo.js +4 -0
  13. package/dist-lib/components/TemplatePage.d.ts +2 -0
  14. package/dist-lib/components/TemplatePage.d.ts.map +1 -0
  15. package/dist-lib/components/TemplatePage.js +4 -0
  16. package/dist-lib/components/WerkbonDetailPage.d.ts +2 -0
  17. package/dist-lib/components/WerkbonDetailPage.d.ts.map +1 -0
  18. package/dist-lib/components/WerkbonDetailPage.js +96 -0
  19. package/dist-lib/components/klanten/KlantDialog.d.ts +10 -0
  20. package/dist-lib/components/klanten/KlantDialog.d.ts.map +1 -0
  21. package/dist-lib/components/klanten/KlantDialog.js +37 -0
  22. package/dist-lib/components/klanten/KlantFormSections.d.ts +45 -0
  23. package/dist-lib/components/klanten/KlantFormSections.d.ts.map +1 -0
  24. package/dist-lib/components/klanten/KlantFormSections.js +45 -0
  25. package/dist-lib/components/klanten/KlantGeschiedenisCard.d.ts +6 -0
  26. package/dist-lib/components/klanten/KlantGeschiedenisCard.d.ts.map +1 -0
  27. package/dist-lib/components/klanten/KlantGeschiedenisCard.js +105 -0
  28. package/dist-lib/components/klanten/KlantKoppelingCard.d.ts +8 -0
  29. package/dist-lib/components/klanten/KlantKoppelingCard.d.ts.map +1 -0
  30. package/dist-lib/components/klanten/KlantKoppelingCard.js +69 -0
  31. package/dist-lib/components/klanten/KlantNotitiesCard.d.ts +6 -0
  32. package/dist-lib/components/klanten/KlantNotitiesCard.d.ts.map +1 -0
  33. package/dist-lib/components/klanten/KlantNotitiesCard.js +119 -0
  34. package/dist-lib/components/klanten/KlantStatsCard.d.ts +4 -0
  35. package/dist-lib/components/klanten/KlantStatsCard.d.ts.map +1 -0
  36. package/dist-lib/components/klanten/KlantStatsCard.js +21 -0
  37. package/dist-lib/components/klanten/KlantenPodium.d.ts +8 -0
  38. package/dist-lib/components/klanten/KlantenPodium.d.ts.map +1 -0
  39. package/dist-lib/components/klanten/KlantenPodium.js +16 -0
  40. package/dist-lib/components/klanten/useKlantForm.d.ts +42 -0
  41. package/dist-lib/components/klanten/useKlantForm.d.ts.map +1 -0
  42. package/dist-lib/components/klanten/useKlantForm.js +240 -0
  43. package/dist-lib/index.d.ts +8 -0
  44. package/dist-lib/index.d.ts.map +1 -0
  45. package/dist-lib/index.js +21 -0
  46. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  47. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  48. package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
  49. package/dist-lib/integrations/supabase/auth-middleware.d.ts +13 -0
  50. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  51. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  52. package/dist-lib/integrations/supabase/client.d.ts +4 -0
  53. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  54. package/dist-lib/integrations/supabase/client.js +5 -0
  55. package/dist-lib/integrations/supabase/client.server.d.ts +4 -0
  56. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  57. package/dist-lib/integrations/supabase/client.server.js +30 -0
  58. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +6 -0
  59. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -0
  60. package/dist-lib/integrations/supabase/previewAuthStorage.js +99 -0
  61. package/dist-lib/integrations/supabase/types.d.ts +36671 -0
  62. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  63. package/dist-lib/integrations/supabase/types.js +94 -0
  64. package/dist-lib/lib/itkClient.d.ts +76 -0
  65. package/dist-lib/lib/itkClient.d.ts.map +1 -0
  66. package/dist-lib/lib/itkClient.js +5 -0
  67. package/dist-lib/lib/klantRules.d.ts +27 -0
  68. package/dist-lib/lib/klantRules.d.ts.map +1 -0
  69. package/dist-lib/lib/klantRules.js +31 -0
  70. package/dist-lib/lib/klantRules.test.d.ts +2 -0
  71. package/dist-lib/lib/klantRules.test.d.ts.map +1 -0
  72. package/dist-lib/lib/klantRules.test.js +50 -0
  73. package/dist-lib/lib/klantStatsClient.d.ts +19 -0
  74. package/dist-lib/lib/klantStatsClient.d.ts.map +1 -0
  75. package/dist-lib/lib/klantStatsClient.js +44 -0
  76. package/dist-lib/lib/planningClient.d.ts +22 -0
  77. package/dist-lib/lib/planningClient.d.ts.map +1 -0
  78. package/dist-lib/lib/planningClient.js +94 -0
  79. package/dist-lib/lib/statusConfig.d.ts +7 -0
  80. package/dist-lib/lib/statusConfig.d.ts.map +1 -0
  81. package/dist-lib/lib/statusConfig.js +8 -0
  82. package/dist-lib/lib/utils.d.ts +3 -0
  83. package/dist-lib/lib/utils.d.ts.map +1 -0
  84. package/dist-lib/lib/utils.js +5 -0
  85. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  86. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  87. package/dist-lib/lib/validationSchemas.js +25 -0
  88. package/dist-lib/styles.css +1 -0
  89. package/package.json +226 -4
  90. package/public/logo.png +0 -0
  91. package/README.md +0 -3
@@ -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 KlantenDetailPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=KlantenDetailPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KlantenDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/KlantenDetailPage.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,iBAAiB,gCAkLhC"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useNavigate, useParams, Link } from "@tanstack/react-router";
3
+ import { Button, Card, CardContent, CardHeader, CardTitle, Separator, } from "@flowselections/core";
4
+ import { ArrowLeft, Trash2, Save, MapPin } from "lucide-react";
5
+ import { useKlantForm } from "./klanten/useKlantForm";
6
+ import { SegmentToggle, AlgemeneGegevensFields, ContactenFields, LocatiesFields, DynamicFieldsGrid, } from "./klanten/KlantFormSections";
7
+ import { KlantGeschiedenisCard } from "./klanten/KlantGeschiedenisCard";
8
+ import { KlantNotitiesCard } from "./klanten/KlantNotitiesCard";
9
+ import { KlantStatsCard } from "./klanten/KlantStatsCard";
10
+ export function KlantenDetailPage() {
11
+ const { id } = useParams({ from: "/_authenticated/installatietechniek-klanten-planner/$id" });
12
+ const navigate = useNavigate();
13
+ const { isNew, loading, saving, form, setForm, customerNumber, contacts, addContact, updateContact, removeContact, locations, addLocation, updateLocation, removeLocation, fields, values, setValues, save, remove, reload, } = useKlantForm({
14
+ customerId: id,
15
+ onSaved: (newId) => {
16
+ if (newId !== id) {
17
+ navigate({ to: "/installatietechniek-klanten-planner/$id", params: { id: newId } });
18
+ }
19
+ else {
20
+ void reload();
21
+ }
22
+ },
23
+ onDeleted: () => navigate({ to: "/installatietechniek-klanten-planner" }),
24
+ onNotFound: () => navigate({ to: "/installatietechniek-klanten-planner" }),
25
+ });
26
+ if (loading) {
27
+ return (_jsx("main", { className: "p-6", children: _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }) }));
28
+ }
29
+ return (_jsxs("main", { className: "p-6 space-y-6 max-w-5xl", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Link, { to: "/installatietechniek-klanten-planner", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "gap-2", children: [_jsx(ArrowLeft, { className: "w-4 h-4" }), "Terug"] }) }), _jsx("h1", { className: "text-2xl font-bold text-foreground", children: isNew ? "Nieuwe klant" : form.name || "Klant" }), customerNumber && (_jsx("span", { className: "font-mono text-xs text-muted-foreground bg-muted px-2 py-1 rounded", children: customerNumber }))] }), _jsxs("div", { className: "flex gap-2", children: [!isNew && (_jsxs(Button, { variant: "outline", onClick: remove, className: "gap-2 text-destructive", children: [_jsx(Trash2, { className: "w-4 h-4" }), "Verwijderen"] })), _jsxs(Button, { onClick: save, disabled: saving, className: "gap-2", children: [_jsx(Save, { className: "w-4 h-4" }), saving ? "Opslaan…" : "Opslaan"] })] })] }), !isNew && (_jsxs("nav", { className: "flex flex-wrap gap-3 text-xs text-muted-foreground border-b pb-3", children: [_jsx("a", { href: "#gegevens", className: "hover:text-foreground transition-colors", children: "Gegevens" }), form.segment === "Business" && (_jsx("a", { href: "#contactpersonen", className: "hover:text-foreground transition-colors", children: "Contactpersonen" })), _jsx("a", { href: "#werklocaties", className: "hover:text-foreground transition-colors", children: "Werklocaties" }), fields.length > 0 && (_jsx("a", { href: "#aanvullend", className: "hover:text-foreground transition-colors", children: "Aanvullend" })), _jsx("a", { href: "#werkbonnen", className: "hover:text-foreground transition-colors", children: "Werkbonnen" }), _jsx("a", { href: "#notities", className: "hover:text-foreground transition-colors", children: "Notities" })] })), !isNew && _jsx(KlantStatsCard, { customerId: id }), _jsxs(Card, { id: "gegevens", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Segment" }) }), _jsx(CardContent, { children: _jsx(SegmentToggle, { segment: form.segment, onChange: (s) => setForm((f) => ({ ...f, segment: s })) }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Algemene gegevens" }) }), _jsx(CardContent, { children: _jsx(AlgemeneGegevensFields, { form: form, setForm: setForm, isNew: isNew }) })] }), form.segment === "Business" && (_jsxs(Card, { id: "contactpersonen", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Contactpersonen" }) }), _jsx(CardContent, { children: _jsx(ContactenFields, { contacts: contacts, addContact: addContact, updateContact: updateContact, removeContact: removeContact }) })] })), _jsxs(Card, { id: "werklocaties", children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [_jsx(MapPin, { className: "w-4 h-4" }), "Werklocaties"] }) }), _jsx(CardContent, { children: _jsx(LocatiesFields, { locations: locations, addLocation: addLocation, updateLocation: updateLocation, removeLocation: removeLocation }) })] }), fields.length > 0 && (_jsxs(Card, { id: "aanvullend", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Aanvullende velden" }) }), _jsx(CardContent, { children: _jsx(DynamicFieldsGrid, { fields: fields, values: values, setValues: setValues }) })] })), !isNew && (_jsxs(_Fragment, { children: [_jsx(KlantGeschiedenisCard, { customerId: id }), _jsx(KlantNotitiesCard, { customerId: id })] })), _jsx(Separator, {}), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: save, disabled: saving, className: "gap-2", children: [_jsx(Save, { className: "w-4 h-4" }), saving ? "Opslaan…" : "Opslaan"] }) })] }));
30
+ }
@@ -0,0 +1,6 @@
1
+ import { type CustomerSegment } from "../lib/itkClient";
2
+ export declare function KlantenListPage(): import("react").JSX.Element;
3
+ export declare function SegmentBadge({ segment }: {
4
+ segment: CustomerSegment;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=KlantenListPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KlantenListPage.d.ts","sourceRoot":"","sources":["../../src/components/KlantenListPage.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAU3E,wBAAgB,eAAe,gCAsO9B;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,+BAkBrE"}
@@ -0,0 +1,105 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Button, Card, CardContent, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Badge, toast, } from "@flowselections/core";
4
+ import { Plus, Search, Building2, User } from "lucide-react";
5
+ import { db } from "../lib/itkClient";
6
+ import { fetchKlantStats, fetchTopKlanten, formatEuro } from "../lib/klantStatsClient";
7
+ import { KlantDialog } from "./klanten/KlantDialog";
8
+ import { KlantenPodium } from "./klanten/KlantenPodium";
9
+ const PAGE_SIZE = 50;
10
+ export function KlantenListPage() {
11
+ const [customers, setCustomers] = useState([]);
12
+ const [total, setTotal] = useState(0);
13
+ const [page, setPage] = useState(0);
14
+ const [stats, setStats] = useState({});
15
+ const [statsFailed, setStatsFailed] = useState(false);
16
+ const [top, setTop] = useState([]);
17
+ const [loading, setLoading] = useState(true);
18
+ const [loadError, setLoadError] = useState(false);
19
+ const [search, setSearch] = useState("");
20
+ const [debounced, setDebounced] = useState("");
21
+ const [segmentFilter, setSegmentFilter] = useState("all");
22
+ const [dialogOpen, setDialogOpen] = useState(false);
23
+ const [selectedId, setSelectedId] = useState(null);
24
+ const reqId = useRef(0);
25
+ useEffect(() => {
26
+ const t = setTimeout(() => setDebounced(search.trim()), 300);
27
+ return () => clearTimeout(t);
28
+ }, [search]);
29
+ useEffect(() => setPage(0), [debounced, segmentFilter]);
30
+ useEffect(() => {
31
+ void load();
32
+ // eslint-disable-next-line react-hooks/exhaustive-deps
33
+ }, [page, debounced, segmentFilter]);
34
+ useEffect(() => {
35
+ void loadTop();
36
+ }, []);
37
+ const openCustomer = (id) => {
38
+ setSelectedId(id);
39
+ setDialogOpen(true);
40
+ };
41
+ const loadTop = async () => {
42
+ try {
43
+ setTop(await fetchTopKlanten(3));
44
+ }
45
+ catch {
46
+ setTop([]);
47
+ }
48
+ };
49
+ const load = async () => {
50
+ const my = ++reqId.current;
51
+ setLoading(true);
52
+ setLoadError(false);
53
+ let query = db
54
+ .from("itk_customers")
55
+ .select("*", { count: "exact" })
56
+ .order("created_at", { ascending: false })
57
+ .range(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE - 1);
58
+ if (segmentFilter !== "all")
59
+ query = query.eq("segment", segmentFilter);
60
+ if (debounced) {
61
+ const q = debounced.replace(/[%_,()*\\]/g, " ").trim();
62
+ if (q) {
63
+ const p = `%${q}%`;
64
+ query = query.or(`name.ilike.${p},customer_number.ilike.${p},general_email.ilike.${p},billing_city.ilike.${p}`);
65
+ }
66
+ }
67
+ const { data, error, count } = await query;
68
+ if (my !== reqId.current)
69
+ return;
70
+ if (error) {
71
+ setLoadError(true);
72
+ setCustomers([]);
73
+ toast.error("Klanten konden niet geladen worden");
74
+ setLoading(false);
75
+ return;
76
+ }
77
+ const list = Array.isArray(data) ? data : [];
78
+ setCustomers(list);
79
+ setTotal(typeof count === "number" ? count : list.length);
80
+ setLoading(false);
81
+ try {
82
+ const s = await fetchKlantStats(list.map((c) => c.id));
83
+ if (my !== reqId.current)
84
+ return;
85
+ setStats(s);
86
+ setStatsFailed(false);
87
+ }
88
+ catch {
89
+ if (my !== reqId.current)
90
+ return;
91
+ setStats({});
92
+ setStatsFailed(true);
93
+ }
94
+ };
95
+ const filtered = customers;
96
+ const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
97
+ const statCell = (id, pick) => stats[id] ? pick(stats[id]) : statsFailed ? "n.b." : "—";
98
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 gap-y-3 sm:flex sm:flex-wrap sm:items-center sm:gap-6", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "truncate text-2xl font-bold text-foreground", children: "Klanten" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Beheer al je zakelijke en particuliere klanten" })] }), top.length > 0 && (_jsx("div", { className: "col-span-2 justify-self-start sm:col-span-1 sm:self-center sm:justify-self-auto", children: _jsx(KlantenPodium, { items: top, onSelect: openCustomer }) })), _jsxs(Button, { className: "col-start-2 row-start-1 shrink-0 justify-self-end gap-2 sm:col-start-auto sm:row-start-auto sm:ml-auto sm:justify-self-auto", onClick: () => openCustomer(null), children: [_jsx(Plus, { className: "w-4 h-4" }), "Nieuwe klant"] })] }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex gap-3 flex-wrap", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px]", children: [_jsx(Search, { className: "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op klantnr, naam, e-mail of plaats\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs(Select, { value: segmentFilter, onValueChange: (v) => setSegmentFilter(v), children: [_jsx(SelectTrigger, { className: "w-[180px]", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle segmenten" }), _jsx(SelectItem, { value: "Business", children: "Zakelijk" }), _jsx(SelectItem, { value: "Private", children: "Particulier" })] })] })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[130px]", children: "Klantnr." }), _jsx(TableHead, { className: "w-[30%]", children: "Naam" }), _jsx(TableHead, { children: "Segment" }), _jsx(TableHead, { children: "E-mail" }), _jsx(TableHead, { children: "Telefoon" }), _jsx(TableHead, { children: "Plaats" }), _jsx(TableHead, { className: "text-right", children: "Omzet" }), _jsx(TableHead, { className: "text-right", children: "Klussen" })] }) }), _jsx(TableBody, { children: loading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, className: "text-center text-muted-foreground py-8", children: "Laden\u2026" }) })) : filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, className: "text-center text-muted-foreground py-8", children: loadError ? "Klanten konden niet geladen worden" : "Geen klanten gevonden" }) })) : (filtered.map((c) => (_jsxs(TableRow, { className: "hover:bg-muted/40 cursor-pointer", onClick: () => openCustomer(c.id), children: [_jsx(TableCell, { className: "font-mono text-xs text-muted-foreground", children: c.customer_number ?? "—" }), _jsx(TableCell, { className: "font-medium text-foreground", children: c.name }), _jsx(TableCell, { children: _jsx(SegmentBadge, { segment: c.segment }) }), _jsx(TableCell, { className: "text-sm", children: c.general_email ?? "—" }), _jsx(TableCell, { className: "text-sm", children: c.general_phone ?? "—" }), _jsx(TableCell, { className: "text-sm", children: c.billing_city ?? "—" }), _jsx(TableCell, { className: "text-sm text-right tabular-nums", children: statCell(c.id, (s) => formatEuro(s.omzetCents)) }), _jsx(TableCell, { className: "text-sm text-right tabular-nums", children: statCell(c.id, (s) => s.klussen) })] }, c.id)))) })] }) }), _jsxs("div", { className: "flex items-center justify-between gap-3 text-sm text-muted-foreground", children: [_jsxs("span", { children: [total, " ", total === 1 ? "klant" : "klanten", statsFailed && " · omzet/klussen niet beschikbaar"] }), pageCount > 1 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: page === 0 || loading, onClick: () => setPage((p) => p - 1), children: "Vorige" }), _jsxs("span", { className: "tabular-nums", children: [page + 1, " / ", pageCount] }), _jsx(Button, { variant: "outline", size: "sm", disabled: page + 1 >= pageCount || loading, onClick: () => setPage((p) => p + 1), children: "Volgende" })] }))] })] }) }), _jsx(KlantDialog, { open: dialogOpen, onOpenChange: setDialogOpen, customerId: selectedId, onChanged: () => { void load(); void loadTop(); } })] }));
99
+ }
100
+ export function SegmentBadge({ segment }) {
101
+ if (segment === "Business") {
102
+ return (_jsxs(Badge, { className: "bg-primary text-primary-foreground gap-1 border-transparent", children: [_jsx(Building2, { className: "w-3 h-3" }), "Zakelijk"] }));
103
+ }
104
+ return (_jsxs(Badge, { className: "gap-1 border-transparent text-white", style: { backgroundColor: "hsl(160 84% 39%)" }, children: [_jsx(User, { className: "w-3 h-3" }), "Particulier"] }));
105
+ }
@@ -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 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,2 @@
1
+ export declare function WerkbonDetailPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=WerkbonDetailPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WerkbonDetailPage.d.ts","sourceRoot":"","sources":["../../src/components/WerkbonDetailPage.tsx"],"names":[],"mappings":"AAkGA,wBAAgB,iBAAiB,gCAoMhC"}
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { useParams, Link } from "@tanstack/react-router";
4
+ import { Button, Card, CardContent, CardHeader, CardTitle, } from "@flowselections/core";
5
+ import { ArrowLeft, Wrench, MapPin, CalendarClock, FileText, Euro, Info, } from "lucide-react";
6
+ import { db } from "../lib/itkClient";
7
+ const STATUS_LABEL = {
8
+ unassigned: "Niet toegewezen",
9
+ planned_hidden: "Ingepland",
10
+ planned_visible: "Ingepland",
11
+ in_progress: "In uitvoering",
12
+ completed: "Afgerond",
13
+ invoicing: "Facturatie",
14
+ };
15
+ const STATUS_STYLE = {
16
+ unassigned: "bg-muted text-muted-foreground",
17
+ planned_hidden: "bg-blue-500/15 text-blue-400",
18
+ planned_visible: "bg-blue-500/15 text-blue-400",
19
+ in_progress: "bg-amber-500/15 text-amber-400",
20
+ completed: "bg-emerald-500/15 text-emerald-400",
21
+ invoicing: "bg-purple-500/15 text-purple-400",
22
+ };
23
+ function formatDate(iso) {
24
+ if (!iso)
25
+ return "—";
26
+ try {
27
+ return new Date(iso).toLocaleString("nl-NL", {
28
+ day: "2-digit",
29
+ month: "short",
30
+ year: "numeric",
31
+ hour: "2-digit",
32
+ minute: "2-digit",
33
+ });
34
+ }
35
+ catch {
36
+ return iso;
37
+ }
38
+ }
39
+ function formatEuro(cents) {
40
+ if (cents == null)
41
+ return "—";
42
+ return new Intl.NumberFormat("nl-NL", {
43
+ style: "currency",
44
+ currency: "EUR",
45
+ }).format(cents / 100);
46
+ }
47
+ function Veld({ label, value }) {
48
+ return (_jsxs("div", { children: [_jsx("dt", { className: "text-xs text-muted-foreground", children: label }), _jsx("dd", { className: "text-sm text-foreground mt-0.5 break-words", children: value && value.trim() !== "" ? value : "—" })] }));
49
+ }
50
+ export function WerkbonDetailPage() {
51
+ const { id } = useParams({
52
+ from: "/_authenticated/installatietechniek-klanten-planner/werkbonnen/$id",
53
+ });
54
+ const [werkbon, setWerkbon] = useState(null);
55
+ const [loading, setLoading] = useState(true);
56
+ const [notFound, setNotFound] = useState(false);
57
+ const [loadError, setLoadError] = useState(false);
58
+ useEffect(() => {
59
+ void (async () => {
60
+ setLoading(true);
61
+ setNotFound(false);
62
+ setLoadError(false);
63
+ const { data, error } = await db
64
+ .from("werkbonnen")
65
+ .select("id, project_number, status, customer_id, customer_name, work_address, room_number, complaint_description, technician_task_description, technician_notes, internal_notes, scheduled_start_time, scheduled_end_time, deadline_date, estimated_duration_minutes, estimated_days, budget_mandate_cents, extra_costs_cents, first_time_fix, afgerond_op, externe_referentie, created_at, updated_at")
66
+ .eq("id", id)
67
+ .maybeSingle();
68
+ if (error) {
69
+ setLoadError(true);
70
+ }
71
+ else if (!data) {
72
+ setNotFound(true);
73
+ }
74
+ else {
75
+ setWerkbon(data);
76
+ }
77
+ setLoading(false);
78
+ })();
79
+ }, [id]);
80
+ if (loading) {
81
+ return (_jsx("main", { className: "p-6", children: _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }) }));
82
+ }
83
+ if (notFound || loadError || !werkbon) {
84
+ return (_jsxs("main", { className: "p-6 max-w-3xl space-y-4", children: [_jsx(Link, { to: "/installatietechniek-klanten-planner", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "gap-2", children: [_jsx(ArrowLeft, { className: "w-4 h-4" }), "Terug naar overzicht"] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "py-10 text-center space-y-2", children: [_jsx(Wrench, { className: "w-8 h-8 mx-auto text-muted-foreground" }), _jsx("h1", { className: "text-lg font-semibold text-foreground", children: notFound ? "Werkbon niet gevonden" : "Werkbon kan niet geladen worden" }), _jsx("p", { className: "text-sm text-muted-foreground", children: notFound
85
+ ? "Deze werkbon bestaat niet (meer) of je hebt er geen toegang toe."
86
+ : "Er ging iets mis bij het ophalen van de werkbon. Probeer het later opnieuw." })] }) })] }));
87
+ }
88
+ return (_jsxs("main", { className: "p-4 sm:p-6 space-y-6 max-w-5xl", children: [_jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx(Link, { to: "/installatietechniek-klanten-planner", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "gap-2", children: [_jsx(ArrowLeft, { className: "w-4 h-4" }), "Terug"] }) }), _jsx("h1", { className: "text-2xl font-bold text-foreground", children: werkbon.project_number ?? "Werkbon" }), _jsx("span", { className: "text-xs px-2 py-0.5 rounded-full font-medium " +
89
+ (STATUS_STYLE[werkbon.status] ?? "bg-muted text-muted-foreground"), children: STATUS_LABEL[werkbon.status] ?? werkbon.status })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [_jsx(Info, { className: "w-4 h-4" }), "Algemeen"] }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Veld, { label: "Werkbonnummer", value: werkbon.project_number }), _jsx(Veld, { label: "Klant", value: werkbon.customer_name }), _jsx(Veld, { label: "Externe referentie", value: werkbon.externe_referentie }), _jsx(Veld, { label: "First time fix", value: werkbon.first_time_fix == null
90
+ ? null
91
+ : werkbon.first_time_fix
92
+ ? "Ja"
93
+ : "Nee" }), _jsx(Veld, { label: "Aangemaakt op", value: formatDate(werkbon.created_at) }), _jsx(Veld, { label: "Laatst bijgewerkt", value: formatDate(werkbon.updated_at) })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [_jsx(MapPin, { className: "w-4 h-4" }), "Werklocatie"] }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsx(Veld, { label: "Adres", value: werkbon.work_address }), _jsx(Veld, { label: "Ruimte", value: werkbon.room_number ? `Ruimte ${werkbon.room_number}` : null })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [_jsx(CalendarClock, { className: "w-4 h-4" }), "Planning"] }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Veld, { label: "Geplande start", value: formatDate(werkbon.scheduled_start_time) }), _jsx(Veld, { label: "Gepland einde", value: formatDate(werkbon.scheduled_end_time) }), _jsx(Veld, { label: "Deadline", value: werkbon.deadline_date }), _jsx(Veld, { label: "Geschatte duur", value: werkbon.estimated_duration_minutes != null
94
+ ? `${werkbon.estimated_duration_minutes} minuten`
95
+ : null }), _jsx(Veld, { label: "Geschatte dagen", value: werkbon.estimated_days != null ? String(werkbon.estimated_days) : null }), _jsx(Veld, { label: "Afgerond op", value: formatDate(werkbon.afgerond_op) })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [_jsx(FileText, { className: "w-4 h-4" }), "Omschrijving & notities"] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx(Veld, { label: "Klachtomschrijving", value: werkbon.complaint_description }), _jsx(Veld, { label: "Taakomschrijving monteur", value: werkbon.technician_task_description }), _jsx(Veld, { label: "Notities monteur", value: werkbon.technician_notes }), _jsx(Veld, { label: "Interne notities", value: werkbon.internal_notes })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [_jsx(Euro, { className: "w-4 h-4" }), "Financieel"] }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsx(Veld, { label: "Budgetmandaat", value: formatEuro(werkbon.budget_mandate_cents) }), _jsx(Veld, { label: "Extra kosten", value: formatEuro(werkbon.extra_costs_cents) })] }) })] })] }));
96
+ }
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ /** `null` => nieuwe klant */
5
+ customerId: string | null;
6
+ onChanged?: () => void;
7
+ }
8
+ export declare function KlantDialog({ open, onOpenChange, customerId, onChanged }: Props): import("react").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=KlantDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KlantDialog.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDialog.tsx"],"names":[],"mappings":"AAqCA,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,6BAA6B;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,KAAK,+BA4L/E"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Link } from "@tanstack/react-router";
4
+ import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Tabs, TabsContent, TabsList, TabsTrigger, } from "@flowselections/core";
5
+ import { User, Users, MapPin, Wrench, StickyNote, Trash2, Save, ExternalLink, } from "lucide-react";
6
+ import { useKlantForm } from "./useKlantForm";
7
+ import { SegmentToggle, AlgemeneGegevensFields, ContactenFields, LocatiesFields, DynamicFieldsGrid, } from "./KlantFormSections";
8
+ import { KlantGeschiedenisCard } from "./KlantGeschiedenisCard";
9
+ import { KlantNotitiesCard } from "./KlantNotitiesCard";
10
+ import { KlantStatsCard } from "./KlantStatsCard";
11
+ import { SegmentBadge } from "../KlantenListPage";
12
+ export function KlantDialog({ open, onOpenChange, customerId, onChanged }) {
13
+ const [tab, setTab] = useState("gegevens");
14
+ const { isNew, loading, saving, form, setForm, customerNumber, contacts, addContact, updateContact, removeContact, locations, addLocation, updateLocation, removeLocation, fields, values, setValues, save, remove, } = useKlantForm({
15
+ customerId,
16
+ enabled: open,
17
+ onSaved: () => {
18
+ onChanged?.();
19
+ onOpenChange(false);
20
+ },
21
+ onDeleted: () => {
22
+ onChanged?.();
23
+ onOpenChange(false);
24
+ },
25
+ onNotFound: () => onOpenChange(false),
26
+ });
27
+ useEffect(() => {
28
+ if (open)
29
+ setTab("gegevens");
30
+ }, [open, customerId]);
31
+ useEffect(() => {
32
+ if (form.segment !== "Business" && tab === "contacten")
33
+ setTab("gegevens");
34
+ }, [form.segment, tab]);
35
+ const isBusiness = form.segment === "Business";
36
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-[720px] max-h-[85vh] overflow-hidden flex flex-col", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { className: "flex items-center gap-2 pr-8", children: [_jsx(User, { className: "w-5 h-5" }), isNew ? "Nieuwe klant" : form.name || "Klant", customerNumber && (_jsx("span", { className: "font-mono text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded", children: customerNumber })), !isNew && _jsx(SegmentBadge, { segment: form.segment })] }), _jsx(DialogDescription, { className: "sr-only", children: "Klantgegevens bewerken" })] }), loading ? (_jsx("p", { className: "text-sm text-muted-foreground py-8 text-center", children: "Laden\u2026" })) : (_jsxs(Tabs, { value: tab, onValueChange: setTab, className: "flex-1 overflow-hidden flex flex-col", children: [_jsxs(TabsList, { className: `grid w-full ${isBusiness ? "grid-cols-5" : "grid-cols-4"}`, children: [_jsxs(TabsTrigger, { value: "gegevens", title: "Gegevens", className: "gap-1.5 px-1.5 text-xs", children: [_jsx(User, { className: "w-3.5 h-3.5 shrink-0" }), _jsx("span", { className: "hidden lg:inline", children: "Gegevens" })] }), isBusiness && (_jsxs(TabsTrigger, { value: "contacten", title: "Contacten", className: "gap-1.5 px-1.5 text-xs", children: [_jsx(Users, { className: "w-3.5 h-3.5 shrink-0" }), _jsx("span", { className: "hidden lg:inline", children: "Contacten" })] })), _jsxs(TabsTrigger, { value: "locaties", title: "Locaties", className: "gap-1.5 px-1.5 text-xs", children: [_jsx(MapPin, { className: "w-3.5 h-3.5 shrink-0" }), _jsx("span", { className: "hidden lg:inline", children: "Locaties" })] }), _jsxs(TabsTrigger, { value: "werkbonnen", title: "Werkbonnen", disabled: isNew, className: "gap-1.5 px-1.5 text-xs", children: [_jsx(Wrench, { className: "w-3.5 h-3.5 shrink-0" }), _jsx("span", { className: "hidden lg:inline", children: "Werkbonnen" })] }), _jsxs(TabsTrigger, { value: "notities", title: "Notities", disabled: isNew, className: "gap-1.5 px-1.5 text-xs", children: [_jsx(StickyNote, { className: "w-3.5 h-3.5 shrink-0" }), _jsx("span", { className: "hidden lg:inline", children: "Notities" })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto mt-4 pr-1", children: [_jsxs(TabsContent, { value: "gegevens", className: "m-0 space-y-5", children: [!isNew && customerId && _jsx(KlantStatsCard, { customerId: customerId }), _jsx(SegmentToggle, { segment: form.segment, onChange: (s) => setForm((f) => ({ ...f, segment: s })) }), _jsx(AlgemeneGegevensFields, { form: form, setForm: setForm, isNew: isNew }), fields.length > 0 && (_jsxs("div", { className: "space-y-3 pt-2 border-t", children: [_jsx("p", { className: "text-sm font-medium text-foreground pt-3", children: "Aanvullende velden" }), _jsx(DynamicFieldsGrid, { fields: fields, values: values, setValues: setValues })] }))] }), isBusiness && (_jsx(TabsContent, { value: "contacten", className: "m-0", children: _jsx(ContactenFields, { contacts: contacts, addContact: addContact, updateContact: updateContact, removeContact: removeContact }) })), _jsx(TabsContent, { value: "locaties", className: "m-0", children: _jsx(LocatiesFields, { locations: locations, addLocation: addLocation, updateLocation: updateLocation, removeLocation: removeLocation, hideGpsFields: isNew }) }), !isNew && customerId && (_jsxs(_Fragment, { children: [_jsx(TabsContent, { value: "werkbonnen", className: "m-0", children: _jsx(KlantGeschiedenisCard, { customerId: customerId }) }), _jsx(TabsContent, { value: "notities", className: "m-0", children: _jsx(KlantNotitiesCard, { customerId: customerId }) })] }))] }), _jsxs("div", { className: "flex items-center justify-between gap-2 pt-4 border-t mt-4", children: [_jsxs("div", { className: "flex gap-2", children: [!isNew && (_jsxs(Button, { variant: "outline", onClick: remove, className: "gap-2 text-destructive", children: [_jsx(Trash2, { className: "w-4 h-4" }), "Verwijderen"] })), !isNew && customerId && (_jsx(Link, { to: "/installatietechniek-klanten-planner/$id", params: { id: customerId }, children: _jsxs(Button, { variant: "ghost", className: "gap-2", children: [_jsx(ExternalLink, { className: "w-4 h-4" }), _jsx("span", { className: "hidden sm:inline", children: "Volledige pagina" })] }) }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsxs(Button, { onClick: save, disabled: saving, className: "gap-2", children: [_jsx(Save, { className: "w-4 h-4" }), saving ? "Opslaan…" : "Opslaan"] })] })] })] }))] }) }));
37
+ }
@@ -0,0 +1,45 @@
1
+ import type { DynamicField } from "../../lib/itkClient";
2
+ import type { KlantFormState, NewContact, NewLocation } from "./useKlantForm";
3
+ export declare function Field({ label, children }: {
4
+ label: string;
5
+ children: React.ReactNode;
6
+ }): import("react").JSX.Element;
7
+ export declare function SegmentButton({ active, onClick, icon, label, }: {
8
+ active: boolean;
9
+ onClick: () => void;
10
+ icon: React.ReactNode;
11
+ label: string;
12
+ }): import("react").JSX.Element;
13
+ export declare function SegmentToggle({ segment, onChange, }: {
14
+ segment: KlantFormState["segment"];
15
+ onChange: (s: KlantFormState["segment"]) => void;
16
+ }): import("react").JSX.Element;
17
+ export declare function AlgemeneGegevensFields({ form, setForm, isNew, }: {
18
+ form: KlantFormState;
19
+ setForm: React.Dispatch<React.SetStateAction<KlantFormState>>;
20
+ isNew?: boolean;
21
+ }): import("react").JSX.Element;
22
+ export declare function ContactenFields({ contacts, addContact, updateContact, removeContact, }: {
23
+ contacts: NewContact[];
24
+ addContact: () => void;
25
+ updateContact: (idx: number, patch: Partial<NewContact>) => void;
26
+ removeContact: (idx: number) => void;
27
+ }): import("react").JSX.Element;
28
+ export declare function LocatiesFields({ locations, addLocation, updateLocation, removeLocation, hideGpsFields, }: {
29
+ locations: NewLocation[];
30
+ addLocation: () => void;
31
+ updateLocation: (idx: number, patch: Partial<NewLocation>) => void;
32
+ removeLocation: (idx: number) => void;
33
+ hideGpsFields?: boolean;
34
+ }): import("react").JSX.Element;
35
+ export declare function DynamicFieldInput({ field, value, onChange, }: {
36
+ field: DynamicField;
37
+ value: string;
38
+ onChange: (v: string) => void;
39
+ }): import("react").JSX.Element;
40
+ export declare function DynamicFieldsGrid({ fields, values, setValues, }: {
41
+ fields: DynamicField[];
42
+ values: Record<string, string>;
43
+ setValues: React.Dispatch<React.SetStateAction<Record<string, string>>>;
44
+ }): import("react").JSX.Element;
45
+ //# sourceMappingURL=KlantFormSections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KlantFormSections.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantFormSections.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE9E,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,+BAOtF;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,OAAO,EACP,IAAI,EACJ,KAAK,GACN,EAAE;IACD,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,+BAgBA;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,QAAQ,EAAE,CAAC,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;CAClD,+BAiBA;AAED,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,OAAO,EACP,KAAa,GACd,EAAE;IACD,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,+BAqDA;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,UAAU,EACV,aAAa,EACb,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IACjE,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,+BAqDA;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAqB,GACtB,EAAE;IACD,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IACnE,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,+BA8FA;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,+BA4CA;AAED,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,MAAM,EACN,SAAS,GACV,EAAE;IACD,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACzE,+BAaA"}
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Input, Label, Textarea, Checkbox, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
3
+ import { Building2, User, Plus, Trash2 } from "lucide-react";
4
+ export function Field({ label, children }) {
5
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { className: "text-xs font-medium text-muted-foreground", children: label }), children] }));
6
+ }
7
+ export function SegmentButton({ active, onClick, icon, label, }) {
8
+ return (_jsxs("button", { type: "button", onClick: onClick, className: "flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors " +
9
+ (active
10
+ ? "bg-primary text-primary-foreground shadow-sm"
11
+ : "text-muted-foreground hover:text-foreground"), children: [icon, label] }));
12
+ }
13
+ export function SegmentToggle({ segment, onChange, }) {
14
+ return (_jsxs("div", { className: "inline-flex rounded-lg border p-1 bg-muted/40", children: [_jsx(SegmentButton, { active: segment === "Business", onClick: () => onChange("Business"), icon: _jsx(Building2, { className: "w-4 h-4" }), label: "Zakelijk" }), _jsx(SegmentButton, { active: segment === "Private", onClick: () => onChange("Private"), icon: _jsx(User, { className: "w-4 h-4" }), label: "Particulier" })] }));
15
+ }
16
+ export function AlgemeneGegevensFields({ form, setForm, isNew = false, }) {
17
+ return (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [isNew && (_jsx("p", { className: "text-xs text-muted-foreground md:col-span-2", children: "Het klantnummer wordt automatisch toegekend na het opslaan." })), _jsx(Field, { label: "Naam *", children: _jsx(Input, { value: form.name, onChange: (e) => setForm((f) => ({ ...f, name: e.target.value })), placeholder: "Bedrijf of persoonsnaam" }) }), _jsx(Field, { label: "Algemeen e-mailadres *", children: _jsx(Input, { type: "email", value: form.general_email ?? "", onChange: (e) => setForm((f) => ({ ...f, general_email: e.target.value })), placeholder: "info@voorbeeld.nl" }) }), _jsx(Field, { label: "Algemeen telefoonnummer *", children: _jsx(Input, { value: form.general_phone ?? "", onChange: (e) => setForm((f) => ({ ...f, general_phone: e.target.value })), placeholder: "+31 \u2026" }) }), _jsx("div", {}), _jsx(Field, { label: "Factuuradres \u2014 straat *", children: _jsx(Input, { value: form.billing_street ?? "", onChange: (e) => setForm((f) => ({ ...f, billing_street: e.target.value })) }) }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Field, { label: "Postcode *", children: _jsx(Input, { value: form.billing_postcode ?? "", onChange: (e) => setForm((f) => ({ ...f, billing_postcode: e.target.value })) }) }), _jsx(Field, { label: "Plaats *", children: _jsx(Input, { value: form.billing_city ?? "", onChange: (e) => setForm((f) => ({ ...f, billing_city: e.target.value })) }) })] })] }));
18
+ }
19
+ export function ContactenFields({ contacts, addContact, updateContact, removeContact, }) {
20
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { size: "sm", variant: "outline", onClick: addContact, className: "gap-2", children: [_jsx(Plus, { className: "w-4 h-4" }), "Toevoegen"] }) }), contacts.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen contactpersonen." })), contacts.map((c, idx) => (_jsxs("div", { className: "border rounded-lg p-4 space-y-3 bg-muted/20", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Field, { label: "Naam", children: _jsx(Input, { value: c.name, onChange: (e) => updateContact(idx, { name: e.target.value }) }) }), _jsx(Field, { label: "Functie", children: _jsx(Input, { value: c.role ?? "", onChange: (e) => updateContact(idx, { role: e.target.value }) }) }), _jsx(Field, { label: "Telefoon", children: _jsx(Input, { value: c.phone ?? "", onChange: (e) => updateContact(idx, { phone: e.target.value }) }) }), _jsx(Field, { label: "E-mail", children: _jsx(Input, { type: "email", value: c.email ?? "", onChange: (e) => updateContact(idx, { email: e.target.value }) }) })] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { size: "sm", variant: "ghost", onClick: () => removeContact(idx), className: "text-destructive gap-2", children: [_jsx(Trash2, { className: "w-4 h-4" }), "Verwijderen"] }) })] }, idx)))] }));
21
+ }
22
+ export function LocatiesFields({ locations, addLocation, updateLocation, removeLocation, hideGpsFields = false, }) {
23
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { size: "sm", variant: "outline", onClick: addLocation, className: "gap-2", children: [_jsx(Plus, { className: "w-4 h-4" }), "Toevoegen"] }) }), locations.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Nog geen werklocaties." })), locations.map((l, idx) => (_jsxs("div", { className: "border rounded-lg p-4 space-y-3 bg-muted/20", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [_jsx(Field, { label: "Label *", children: _jsx(Input, { value: l.label, onChange: (e) => updateLocation(idx, { label: e.target.value }), placeholder: "Bijv. Hoofdvestiging" }) }), _jsx(Field, { label: "Straat + huisnummer", children: _jsx(Input, { value: l.street ?? "", onChange: (e) => updateLocation(idx, { street: e.target.value }) }) }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsx(Field, { label: "Postcode", children: _jsx(Input, { value: l.postcode ?? "", onChange: (e) => updateLocation(idx, { postcode: e.target.value }) }) }), _jsx(Field, { label: "Plaats", children: _jsx(Input, { value: l.city ?? "", onChange: (e) => updateLocation(idx, { city: e.target.value }) }) })] }), !hideGpsFields && (_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsx(Field, { label: "Latitude", children: _jsx(Input, { type: "number", step: "any", value: l.latitude ?? "", onChange: (e) => updateLocation(idx, {
24
+ latitude: e.target.value === "" ? null : Number(e.target.value),
25
+ }) }) }), _jsx(Field, { label: "Longitude", children: _jsx(Input, { type: "number", step: "any", value: l.longitude ?? "", onChange: (e) => updateLocation(idx, {
26
+ longitude: e.target.value === "" ? null : Number(e.target.value),
27
+ }) }) })] }))] }), _jsx(Field, { label: "Specifieke instructies", children: _jsx(Textarea, { value: l.instructions ?? "", onChange: (e) => updateLocation(idx, { instructions: e.target.value }), placeholder: "Bijv. Sleutel onder de mat", rows: 2 }) }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { size: "sm", variant: "ghost", onClick: () => removeLocation(idx), className: "text-destructive gap-2", children: [_jsx(Trash2, { className: "w-4 h-4" }), "Verwijderen"] }) })] }, idx)))] }));
28
+ }
29
+ export function DynamicFieldInput({ field, value, onChange, }) {
30
+ const label = field.field_name;
31
+ if (field.field_type === "text") {
32
+ return (_jsx(Field, { label: label, children: _jsx(Input, { value: value, onChange: (e) => onChange(e.target.value) }) }));
33
+ }
34
+ if (field.field_type === "date") {
35
+ return (_jsx(Field, { label: label, children: _jsx(Input, { type: "date", value: value, onChange: (e) => onChange(e.target.value) }) }));
36
+ }
37
+ if (field.field_type === "checkbox") {
38
+ return (_jsxs("div", { className: "flex items-center gap-2 pt-6", children: [_jsx(Checkbox, { checked: value === "true", onCheckedChange: (v) => onChange(v ? "true" : "false") }), _jsx(Label, { className: "text-sm", children: label })] }));
39
+ }
40
+ const options = Array.isArray(field.options) ? field.options : [];
41
+ return (_jsx(Field, { label: label, children: _jsxs(Select, { value: value, onValueChange: onChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies\u2026" }) }), _jsx(SelectContent, { children: options.map((opt) => (_jsx(SelectItem, { value: opt, children: opt }, opt))) })] }) }));
42
+ }
43
+ export function DynamicFieldsGrid({ fields, values, setValues, }) {
44
+ return (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: fields.map((field) => (_jsx(DynamicFieldInput, { field: field, value: values[field.id] ?? "", onChange: (v) => setValues((prev) => ({ ...prev, [field.id]: v })) }, field.id))) }));
45
+ }
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ customerId: string;
3
+ }
4
+ export declare function KlantGeschiedenisCard({ customerId }: Props): import("react").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=KlantGeschiedenisCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KlantGeschiedenisCard.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantGeschiedenisCard.tsx"],"names":[],"mappings":"AASA,UAAU,KAAK;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAiDD,wBAAgB,qBAAqB,CAAC,EAAE,UAAU,EAAE,EAAE,KAAK,+BA0I1D"}