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

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 +14 -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 +36447 -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"}