@flowselections/floriday-klanten-module 1.0.8 → 1.0.10

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 (51) hide show
  1. package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts +4 -0
  2. package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts.map +1 -0
  3. package/dist-lib/components/klanten/CustomerAnalyticsTab.js +90 -0
  4. package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts +4 -0
  5. package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -0
  6. package/dist-lib/components/klanten/DeliveryLocationsTab.js +94 -0
  7. package/dist-lib/components/klanten/GroupedKlantDetail.d.ts +4 -0
  8. package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -0
  9. package/dist-lib/components/klanten/GroupedKlantDetail.js +152 -0
  10. package/dist-lib/components/klanten/KlantDetail.d.ts +4 -0
  11. package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -0
  12. package/dist-lib/components/klanten/KlantDetail.js +43 -0
  13. package/dist-lib/components/klanten/KlantDetailLayout.d.ts +75 -0
  14. package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -0
  15. package/dist-lib/components/klanten/KlantDetailLayout.js +172 -0
  16. package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
  17. package/dist-lib/components/klanten/KlantenOverzicht.js +12 -32
  18. package/dist-lib/components/klanten/NetwerkPagina.d.ts +2 -0
  19. package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -0
  20. package/dist-lib/components/klanten/NetwerkPagina.js +23 -0
  21. package/dist-lib/components/klanten/NieuweKlant.d.ts +2 -0
  22. package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -0
  23. package/dist-lib/components/klanten/NieuweKlant.js +57 -0
  24. package/dist-lib/index.d.ts +4 -0
  25. package/dist-lib/index.d.ts.map +1 -1
  26. package/dist-lib/index.js +5 -1
  27. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2797 -144
  28. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  29. package/dist-lib/integrations/supabase/client.d.ts +2797 -144
  30. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  31. package/dist-lib/integrations/supabase/client.server.d.ts +2797 -144
  32. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  33. package/dist-lib/integrations/supabase/types.d.ts +2870 -134
  34. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  35. package/dist-lib/integrations/supabase/types.js +2 -0
  36. package/dist-lib/lib/accounting.functions.d.ts +17599 -9640
  37. package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/customer-analytics.functions.d.ts +9347 -0
  39. package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -0
  40. package/dist-lib/lib/customer-analytics.functions.js +165 -0
  41. package/dist-lib/lib/customers.functions.d.ts +42763 -24186
  42. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  43. package/dist-lib/lib/delivery-locations.functions.d.ts +37258 -0
  44. package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -0
  45. package/dist-lib/lib/delivery-locations.functions.js +150 -0
  46. package/dist-lib/lib/eu-regions.d.ts +14 -0
  47. package/dist-lib/lib/eu-regions.d.ts.map +1 -0
  48. package/dist-lib/lib/eu-regions.js +68 -0
  49. package/dist-lib/lib/external-customers.functions.d.ts +25112 -14500
  50. package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
  51. package/package.json +5 -4
@@ -0,0 +1,172 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Card, CardContent, Badge, Separator, } from "@flowselections/core";
4
+ import { Mail, Phone, Globe, MapPin, Hash, Building2, ChevronDown, ChevronRight, Users, Truck, Package, ShoppingCart, Clock, } from "lucide-react";
5
+ import { CustomerAvatar } from "../CustomerAvatar";
6
+ function firstNonEmpty(values) {
7
+ for (const v of values) {
8
+ if (v != null && (typeof v !== "string" || v.trim() !== ""))
9
+ return v;
10
+ }
11
+ return null;
12
+ }
13
+ const COUNTRY_NAMES = {
14
+ NL: "Nederland", BE: "België", DE: "Duitsland", FR: "Frankrijk",
15
+ ES: "Spanje", IT: "Italië", GB: "Verenigd Koninkrijk", UK: "Verenigd Koninkrijk",
16
+ PL: "Polen", DK: "Denemarken", SE: "Zweden", NO: "Noorwegen",
17
+ AT: "Oostenrijk", CH: "Zwitserland", PT: "Portugal", IE: "Ierland",
18
+ LU: "Luxemburg", CZ: "Tsjechië", FI: "Finland", US: "Verenigde Staten",
19
+ };
20
+ export function formatCountry(value) {
21
+ if (!value)
22
+ return null;
23
+ const trimmed = value.trim();
24
+ if (trimmed.length === 2)
25
+ return COUNTRY_NAMES[trimmed.toUpperCase()] ?? trimmed;
26
+ return trimmed;
27
+ }
28
+ export function adaptLocalToProfile(c, contacts = []) {
29
+ const primary = contacts.find((x) => x.is_active !== false) ?? contacts[0] ?? null;
30
+ const email = c.email ?? primary?.email ?? null;
31
+ const phone = c.phone ?? primary?.phone ?? null;
32
+ const rfh = c.custom_fields?.rfh_relation_id ??
33
+ c.floriday_raw?.rfhRelationId ??
34
+ c.floriday_raw?.rfhCompanyAccountId ??
35
+ null;
36
+ return {
37
+ logo_url: c.logo_url ?? null,
38
+ company_name: c.company_name,
39
+ commercial_name: c.commercial_name ?? null,
40
+ is_active: !!c.is_active,
41
+ class_badge: c.customer_class ?? null,
42
+ email,
43
+ phone,
44
+ website: c.website ?? null,
45
+ street: c.street ?? null,
46
+ house_number: c.house_number ?? null,
47
+ postal_code: c.postal_code ?? null,
48
+ city: c.city ?? null,
49
+ country: formatCountry(c.country ?? null),
50
+ vat_number: c.vat_number ?? null,
51
+ gln: c.gln ?? null,
52
+ rfh_id: rfh ? String(rfh) : null,
53
+ organization_type: c.organization_type ?? null,
54
+ primary_contact_name: primary?.full_name ?? null,
55
+ email_from_contact: !c.email && !!primary?.email,
56
+ phone_from_contact: !c.phone && !!primary?.phone,
57
+ has_contacts: contacts.length > 0,
58
+ };
59
+ }
60
+ export function adaptGroupedToProfile(g) {
61
+ const sources = g.sources ?? [];
62
+ const pick = (k) => firstNonEmpty(sources.map((s) => s[k]));
63
+ const rfh = firstNonEmpty(sources.map((s) => s.raw?.custom_fields?.rfh_relation_id ??
64
+ s.raw?.custom_fields?.rfh_company_account_id ??
65
+ s.raw?.rfhRelationId ??
66
+ s.raw?.rfhCompanyAccountId)) ?? null;
67
+ const orgType = firstNonEmpty(sources.map((s) => s.raw?.organization_type ?? s.raw?.organizationType));
68
+ return {
69
+ logo_url: g.logo_url ?? pick("logo_url") ?? null,
70
+ company_name: g.display_name,
71
+ commercial_name: g.commercial_name ?? pick("commercial_name") ?? null,
72
+ is_active: !!g.is_active,
73
+ class_badge: null,
74
+ email: g.email ?? pick("email"),
75
+ phone: g.phone ?? pick("phone"),
76
+ website: g.website ?? pick("website"),
77
+ street: pick("street"),
78
+ house_number: pick("house_number"),
79
+ postal_code: pick("postal_code"),
80
+ city: g.city ?? pick("city"),
81
+ country: formatCountry(g.country ?? pick("country")),
82
+ vat_number: g.vat_number ?? pick("vat_number"),
83
+ gln: g.gln ?? pick("gln"),
84
+ rfh_id: rfh ? String(rfh) : null,
85
+ organization_type: orgType ? String(orgType) : null,
86
+ primary_contact_name: g.contact_person ?? pick("contact_person"),
87
+ email_from_contact: false,
88
+ phone_from_contact: false,
89
+ has_contacts: !!(g.contact_person || pick("contact_person")),
90
+ };
91
+ }
92
+ export function adaptExternalToProfile(c) {
93
+ const rfh = c.raw?.custom_fields?.rfh_relation_id ??
94
+ c.raw?.custom_fields?.rfh_company_account_id ??
95
+ c.raw?.rfhRelationId ??
96
+ c.raw?.rfhCompanyAccountId ??
97
+ null;
98
+ const orgType = c.raw?.organization_type ?? c.raw?.organizationType ?? null;
99
+ return {
100
+ logo_url: c.logo_url,
101
+ company_name: c.company_name,
102
+ commercial_name: c.commercial_name,
103
+ is_active: !!c.is_active,
104
+ class_badge: null,
105
+ email: c.email,
106
+ phone: c.phone,
107
+ website: c.website,
108
+ street: c.street,
109
+ house_number: c.house_number,
110
+ postal_code: c.postal_code,
111
+ city: c.city,
112
+ country: formatCountry(c.country),
113
+ vat_number: c.vat_number,
114
+ gln: c.gln,
115
+ rfh_id: rfh ? String(rfh) : null,
116
+ organization_type: orgType ? String(orgType) : null,
117
+ primary_contact_name: c.contact_person,
118
+ email_from_contact: false,
119
+ phone_from_contact: false,
120
+ has_contacts: !!c.contact_person,
121
+ };
122
+ }
123
+ export function ProfileCard({ profile: p }) {
124
+ const address = [p.street, p.house_number].filter(Boolean).join(" ");
125
+ const cityLine = [p.postal_code, p.city].filter(Boolean).join(" ");
126
+ const fullAddress = [address, cityLine, p.country].filter(Boolean).join(", ");
127
+ const [showTech, setShowTech] = useState(false);
128
+ const hasTech = !!(p.gln || p.rfh_id || p.organization_type);
129
+ return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Technische gegevens"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: p.organization_type })] }))] }))] }) }));
130
+ }
131
+ export function SectionLabel({ children }) {
132
+ return _jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground font-medium mb-2", children: children });
133
+ }
134
+ export function QuickAction({ icon, label, href, external }) {
135
+ const disabled = !href;
136
+ const cls = "flex flex-col items-center justify-center gap-1 rounded-md border border-border bg-background p-2.5 text-xs transition-colors hover:bg-muted disabled:opacity-40 disabled:hover:bg-background";
137
+ if (disabled) {
138
+ return _jsxs("button", { type: "button", disabled: true, className: cls, children: [icon, _jsx("span", { children: label })] });
139
+ }
140
+ return (_jsxs("a", { href: href, target: external ? "_blank" : undefined, rel: external ? "noopener noreferrer" : undefined, className: cls, children: [icon, _jsx("span", { children: label })] }));
141
+ }
142
+ export function InfoRow({ icon, label, value, href, external, mono, hint, }) {
143
+ const content = value ?? "—";
144
+ return (_jsxs("div", { className: "flex items-start gap-3 text-sm", children: [_jsx("span", { className: "text-muted-foreground mt-0.5 shrink-0", children: icon }), _jsxs("div", { className: "min-w-0 flex-1", children: [label && _jsx("div", { className: "text-xs text-muted-foreground", children: label }), href && value ? (_jsx("a", { href: href, target: external ? "_blank" : undefined, rel: external ? "noopener noreferrer" : undefined, className: `hover:underline break-words ${mono ? "font-mono text-xs" : ""}`, children: content })) : (_jsx("div", { className: `break-words ${mono ? "font-mono text-xs" : ""} ${!value ? "text-muted-foreground" : ""}`, children: content })), hint && value && (_jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: hint }))] })] }));
145
+ }
146
+ export function KpiStrip({ counts, onSelect, }) {
147
+ const items = [
148
+ { key: "contacts", icon: _jsx(Users, { className: "h-4 w-4" }), label: "Contacten", value: counts.contacts },
149
+ { key: "locations", icon: _jsx(Truck, { className: "h-4 w-4" }), label: "Locaties", value: counts.locations },
150
+ { key: "assortment", icon: _jsx(Package, { className: "h-4 w-4" }), label: "Assortiment", value: counts.assortment },
151
+ { key: "orders", icon: _jsx(ShoppingCart, { className: "h-4 w-4" }), label: "Bestellingen", value: counts.orders },
152
+ ];
153
+ return (_jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-5 divide-y md:divide-y-0 md:divide-x divide-border", children: [items.map((it) => (_jsxs("button", { type: "button", onClick: () => onSelect(it.key), className: "flex flex-col items-start gap-1 p-4 text-left transition-colors hover:bg-muted/50", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [it.icon, " ", it.label] }), _jsx("span", { className: "text-2xl font-bold", children: it.value })] }, it.key))), _jsxs("div", { className: "flex flex-col items-start gap-1 p-4 col-span-2 md:col-span-1", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(Clock, { className: "h-4 w-4" }), " Laatste sync"] }), _jsx("span", { className: "text-sm font-medium", children: counts.lastSync ? new Date(counts.lastSync).toLocaleString("nl-NL", { dateStyle: "short", timeStyle: "short" }) : "—" })] })] }) }) }));
154
+ }
155
+ export function ActivityItem({ entry }) {
156
+ const actionLabels = {
157
+ create: "Aangemaakt",
158
+ insert: "Aangemaakt",
159
+ update: "Bijgewerkt",
160
+ delete: "Verwijderd",
161
+ sync: "Bijgewerkt vanuit extern systeem",
162
+ };
163
+ const label = actionLabels[entry.action] ?? "Wijziging";
164
+ return (_jsx("div", { className: "border-l-2 border-primary pl-3 py-1", children: _jsxs("div", { className: "flex justify-between items-center text-sm", children: [_jsx("strong", { children: label }), _jsx("span", { className: "text-xs text-muted-foreground", children: new Date(entry.created_at).toLocaleString("nl-NL") })] }) }));
165
+ }
166
+ export function BadgeList({ label, items }) {
167
+ const list = items ?? [];
168
+ return (_jsxs("div", { children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground mb-1", children: label }), list.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground", children: "\u2014" })) : (_jsx("div", { className: "flex flex-wrap gap-1", children: list.map((s) => (_jsx(Badge, { variant: "secondary", children: s }, s))) }))] }));
169
+ }
170
+ export function EmptyState({ message }) {
171
+ return (_jsx(Card, { children: _jsx(CardContent, { className: "pt-6 text-center text-sm text-muted-foreground", children: message }) }));
172
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,gBAAgB,gCAuW/B"}
1
+ {"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,gBAAgB,gCAoT/B"}
@@ -3,18 +3,19 @@ import { useNavigate } from "@tanstack/react-router";
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { useMemo, useState } from "react";
6
- import { Button, Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsList, TabsTrigger, Skeleton, } from "@flowselections/core";
7
- import { Plus, Search, Network, Users, UserCheck, UserX, Cloud, HardDrive, X, ArrowUpDown, Mail, MapPin, } from "lucide-react";
6
+ import { Button, Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsList, TabsTrigger, Skeleton, Switch, Label, } from "@flowselections/core";
7
+ import { Plus, Search, Network, Users, UserCheck, X, ArrowUpDown, Mail, MapPin, } from "lucide-react";
8
8
  import { listCustomers, listCustomerClasses } from "../../lib/customers.functions";
9
9
  import { listExternalCustomers } from "../../lib/external-customers.functions";
10
10
  import { CustomerAvatar } from "../CustomerAvatar";
11
- import { ExternalCacheBanner } from "./ExternalCacheBanner";
11
+ import { formatCountry } from "./KlantDetailLayout";
12
12
  export function KlantenOverzicht() {
13
13
  const navigate = useNavigate();
14
14
  const [search, setSearch] = useState("");
15
15
  const [customerClass, setCustomerClass] = useState("__all__");
16
16
  const [country, setCountry] = useState("__all__");
17
17
  const [view, setView] = useState("all");
18
+ const [showInactive, setShowInactive] = useState(false);
18
19
  const [sort, setSort] = useState({ col: "company_name", dir: "asc" });
19
20
  const list = useServerFn(listCustomers);
20
21
  const listExternal = useServerFn(listExternalCustomers);
@@ -35,22 +36,13 @@ export function KlantenOverzicht() {
35
36
  });
36
37
  const isLoading = loadingLocal || loadingExternal;
37
38
  const providers = externalData?.providers ?? [];
38
- const providerLabel = useMemo(() => {
39
- const m = new Map();
40
- for (const p of providers)
41
- m.set(p.provider, p.label);
42
- return m;
43
- }, [providers]);
44
39
  const allRows = useMemo(() => {
45
40
  const local = (localData?.customers ?? []).map((c) => ({
46
41
  key: `local-${c.id}`,
47
42
  id: c.id,
48
43
  group_key: null,
49
44
  source: "local",
50
- source_label: "Lokaal",
51
45
  providers: [],
52
- provider_labels: [],
53
- source_count: 1,
54
46
  company_name: c.company_name,
55
47
  commercial_name: c.commercial_name ?? null,
56
48
  contact_person: c.contact_person ?? null,
@@ -63,16 +55,12 @@ export function KlantenOverzicht() {
63
55
  }));
64
56
  const ext = (externalData?.customers ?? []).map((c) => {
65
57
  const provs = c.providers ?? [];
66
- const labels = provs.map((p) => providerLabel.get(p) ?? p);
67
58
  return {
68
59
  key: `grp-${c.group_key}`,
69
60
  id: null,
70
61
  group_key: c.group_key,
71
62
  source: provs[0] ?? "external",
72
- source_label: labels[0] ?? "Extern",
73
63
  providers: provs,
74
- provider_labels: labels,
75
- source_count: c.source_count ?? provs.length,
76
64
  company_name: c.display_name,
77
65
  commercial_name: c.commercial_name,
78
66
  contact_person: c.contact_person,
@@ -85,19 +73,14 @@ export function KlantenOverzicht() {
85
73
  };
86
74
  });
87
75
  return [...local, ...ext];
88
- }, [localData, externalData, providerLabel]);
76
+ }, [localData, externalData]);
89
77
  const totals = useMemo(() => {
90
78
  const t = {
91
79
  all: allRows.length,
92
80
  active: allRows.filter((c) => c.is_active).length,
93
- inactive: allRows.filter((c) => !c.is_active).length,
94
- local: allRows.filter((c) => c.source === "local").length,
95
81
  };
96
- for (const p of providers) {
97
- t[p.provider] = allRows.filter((c) => c.providers.includes(p.provider)).length;
98
- }
99
82
  return t;
100
- }, [allRows, providers]);
83
+ }, [allRows]);
101
84
  const countries = useMemo(() => {
102
85
  const set = new Set();
103
86
  allRows.forEach((c) => { if (c.country)
@@ -108,12 +91,8 @@ export function KlantenOverzicht() {
108
91
  let rows = allRows;
109
92
  if (view === "active")
110
93
  rows = rows.filter((c) => c.is_active);
111
- else if (view === "inactive")
112
- rows = rows.filter((c) => !c.is_active);
113
- else if (view === "local")
114
- rows = rows.filter((c) => c.source === "local");
115
- else if (view !== "all")
116
- rows = rows.filter((c) => c.providers.includes(view));
94
+ if (!showInactive && view !== "active")
95
+ rows = rows.filter((c) => c.is_active);
117
96
  if (customerClass !== "__all__")
118
97
  rows = rows.filter((c) => c.customer_class === customerClass);
119
98
  if (country !== "__all__")
@@ -130,7 +109,7 @@ export function KlantenOverzicht() {
130
109
  const cmp = av.localeCompare(bv);
131
110
  return sort.dir === "asc" ? cmp : -cmp;
132
111
  });
133
- }, [allRows, view, customerClass, country, search, sort]);
112
+ }, [allRows, view, customerClass, country, search, sort, showInactive]);
134
113
  const activeFilterChips = [];
135
114
  if (search)
136
115
  activeFilterChips.push({ key: "s", label: `Zoek: "${search}"`, clear: () => setSearch("") });
@@ -139,7 +118,8 @@ export function KlantenOverzicht() {
139
118
  if (country !== "__all__")
140
119
  activeFilterChips.push({ key: "co", label: `Land: ${country}`, clear: () => setCountry("__all__") });
141
120
  const toggleSort = (col) => setSort((s) => (s.col === col ? { col, dir: s.dir === "asc" ? "desc" : "asc" } : { col, dir: "asc" }));
142
- return (_jsxs("main", { className: "p-6 space-y-5 max-w-[1400px] mx-auto", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Klanten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je lokale relaties en bekijk Floriday-klanten uit de cache." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/klanten/netwerk" }), children: [_jsx(Network, { className: "h-4 w-4 mr-2" }), " Floriday-netwerk"] }), _jsxs(Button, { onClick: () => navigate({ to: "/klanten/nieuw" }), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Nieuwe klant"] })] })] }), _jsx(ExternalCacheBanner, { lastFetchedAt: externalData?.last_fetched_at ?? null, providers: providers }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-5 gap-3", children: [_jsx(StatCard, { icon: _jsx(Users, { className: "h-4 w-4" }), label: "Totaal", value: totals.all ?? 0, active: view === "all", onClick: () => setView("all") }), _jsx(StatCard, { icon: _jsx(UserCheck, { className: "h-4 w-4" }), label: "Actief", value: totals.active ?? 0, active: view === "active", onClick: () => setView("active") }), _jsx(StatCard, { icon: _jsx(UserX, { className: "h-4 w-4" }), label: "Inactief", value: totals.inactive ?? 0, active: view === "inactive", onClick: () => setView("inactive") }), _jsx(StatCard, { icon: _jsx(HardDrive, { className: "h-4 w-4" }), label: "Lokaal", value: totals.local ?? 0, active: view === "local", onClick: () => setView("local") }), providers.filter((p) => p.is_enabled).map((p) => (_jsx(StatCard, { icon: _jsx(Cloud, { className: "h-4 w-4" }), label: p.label, value: totals[p.provider] ?? 0, active: view === p.provider, onClick: () => setView(p.provider) }, p.provider)))] }), _jsx(Tabs, { value: view, onValueChange: (v) => setView(v), children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle (", totals.all ?? 0, ")"] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief (", totals.active ?? 0, ")"] }), _jsxs(TabsTrigger, { value: "inactive", children: ["Inactief (", totals.inactive ?? 0, ")"] }), _jsxs(TabsTrigger, { value: "local", children: ["Lokaal (", totals.local ?? 0, ")"] }), providers.filter((p) => p.is_enabled).map((p) => (_jsxs(TabsTrigger, { value: p.provider, children: [p.label, " (", totals[p.provider] ?? 0, ")"] }, p.provider)))] }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, contactpersoon of e-mail...", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs(Select, { value: customerClass, onValueChange: setCustomerClass, children: [_jsx(SelectTrigger, { className: "w-40", children: _jsx(SelectValue, { placeholder: "Klasse" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle klassen" }), (classData?.classes ?? []).map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] }), _jsxs(Select, { value: country, onValueChange: setCountry, children: [_jsx(SelectTrigger, { className: "w-36", children: _jsx(SelectValue, { placeholder: "Land" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle landen" }), countries.map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] })] }), activeFilterChips.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [activeFilterChips.map((chip) => (_jsxs("button", { type: "button", onClick: chip.clear, className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted/50 px-3 py-1 text-xs hover:bg-muted", children: [chip.label, " ", _jsx(X, { className: "h-3 w-3" })] }, chip.key))), _jsx("button", { type: "button", onClick: () => { setSearch(""); setCustomerClass("__all__"); setCountry("__all__"); }, className: "text-xs text-muted-foreground hover:text-foreground underline", children: "Alles wissen" })] })), _jsx("div", { className: "text-sm text-muted-foreground", children: isLoading ? "Laden..." : `${customers.length} ${customers.length === 1 ? "klant" : "klanten"} gevonden` }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: "Bedrijf", col: "company_name", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Contact" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Klasse", col: "customer_class", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Land", col: "country", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: isLoading ? (Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: 6 }).map((__, j) => (_jsx(TableCell, { className: "py-4", children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, j))) }, i)))) : customers.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, children: _jsx(EmptyState, { onAdd: () => navigate({ to: "/klanten/nieuw" }) }) }) })) : customers.map((c) => {
121
+ const lastUpdated = externalData?.last_fetched_at ?? null;
122
+ return (_jsxs("main", { className: "p-6 space-y-5 max-w-[1400px] mx-auto", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Klanten" }), _jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: ["Beheer en doorzoek al je klanten.", lastUpdated && (_jsxs("span", { className: "ml-2 text-xs", children: ["\u00B7 Bijgewerkt ", relativeTime(lastUpdated)] }))] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate({ to: "/klanten/netwerk" }), children: [_jsx(Network, { className: "h-4 w-4 mr-2" }), " Klanten toevoegen uit netwerk"] }), _jsxs(Button, { onClick: () => navigate({ to: "/klanten/nieuw" }), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Nieuwe klant"] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3 max-w-md", children: [_jsx(StatCard, { icon: _jsx(Users, { className: "h-4 w-4" }), label: "Totaal", value: totals.all ?? 0, active: view === "all", onClick: () => setView("all") }), _jsx(StatCard, { icon: _jsx(UserCheck, { className: "h-4 w-4" }), label: "Actief", value: totals.active ?? 0, active: view === "active", onClick: () => setView("active") })] }), _jsx(Tabs, { value: view, onValueChange: (v) => setView(v), children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle (", totals.all ?? 0, ")"] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief (", totals.active ?? 0, ")"] })] }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs("div", { className: "relative flex-1 min-w-[240px] max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op bedrijf, contactpersoon of e-mail...", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs(Select, { value: customerClass, onValueChange: setCustomerClass, children: [_jsx(SelectTrigger, { className: "w-40", children: _jsx(SelectValue, { placeholder: "Klasse" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle klassen" }), (classData?.classes ?? []).map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] }), _jsxs(Select, { value: country, onValueChange: setCountry, children: [_jsx(SelectTrigger, { className: "w-36", children: _jsx(SelectValue, { placeholder: "Land" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__all__", children: "Alle landen" }), countries.map((c) => (_jsx(SelectItem, { value: c, children: c }, c)))] })] }), _jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [_jsx(Switch, { id: "show-inactive", checked: showInactive, onCheckedChange: setShowInactive }), _jsx(Label, { htmlFor: "show-inactive", className: "text-sm cursor-pointer", children: "Toon inactieve klanten" })] })] }), activeFilterChips.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [activeFilterChips.map((chip) => (_jsxs("button", { type: "button", onClick: chip.clear, className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted/50 px-3 py-1 text-xs hover:bg-muted", children: [chip.label, " ", _jsx(X, { className: "h-3 w-3" })] }, chip.key))), _jsx("button", { type: "button", onClick: () => { setSearch(""); setCustomerClass("__all__"); setCountry("__all__"); }, className: "text-xs text-muted-foreground hover:text-foreground underline", children: "Alles wissen" })] })), _jsx("div", { className: "text-sm text-muted-foreground", children: isLoading ? "Laden..." : `${customers.length} ${customers.length === 1 ? "klant" : "klanten"} gevonden` }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: "Bedrijf", col: "company_name", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Contact" }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Klasse", col: "customer_class", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: "Land", col: "country", sort: sort, onClick: toggleSort }) }), _jsx(TableHead, { children: "Status" })] }) }), _jsx(TableBody, { children: isLoading ? (Array.from({ length: 6 }).map((_, i) => (_jsx(TableRow, { children: Array.from({ length: 5 }).map((__, j) => (_jsx(TableCell, { className: "py-4", children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, j))) }, i)))) : customers.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, children: _jsx(EmptyState, { onAdd: () => navigate({ to: "/klanten/nieuw" }) }) }) })) : customers.map((c) => {
143
123
  const secondary = c.commercial_name && c.commercial_name !== c.company_name
144
124
  ? c.commercial_name
145
125
  : c.city ?? c.email ?? null;
@@ -148,7 +128,7 @@ export function KlantenOverzicht() {
148
128
  : navigate({
149
129
  to: "/klanten/groep/$groupKey",
150
130
  params: { groupKey: c.group_key },
151
- }), children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CustomerAvatar, { logoUrl: c.logo_url, name: c.company_name, size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: c.company_name }), secondary && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: secondary })] })] }) }), _jsx(TableCell, { className: "py-3", children: c.contact_person || c.email ? (_jsxs("div", { className: "min-w-0", children: [c.contact_person && _jsx("div", { className: "text-sm truncate", children: c.contact_person }), c.email && (_jsxs("a", { href: `mailto:${c.email}`, onClick: (e) => e.stopPropagation(), className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline truncate", children: [_jsx(Mail, { className: "h-3 w-3" }), " ", c.email] }))] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: c.customer_class ? _jsx(Badge, { variant: "secondary", children: c.customer_class }) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3 text-sm", children: c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MapPin, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", c.country] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: c.source === "local" ? (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs", children: [_jsx(HardDrive, { className: "h-3.5 w-3.5 text-muted-foreground" }), c.source_label] })) : (_jsx("div", { className: "flex flex-wrap gap-1", children: c.provider_labels.map((lbl) => (_jsxs(Badge, { variant: "outline", className: "text-[10px] font-normal", children: [_jsx(Cloud, { className: "h-3 w-3 mr-1" }), lbl] }, lbl))) })) }), _jsx(TableCell, { className: "py-3", children: _jsx(StatusPill, { active: c.is_active }) })] }, c.key));
131
+ }), children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CustomerAvatar, { logoUrl: c.logo_url, name: c.company_name, size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium truncate", children: c.company_name }), secondary && _jsx("div", { className: "text-xs text-muted-foreground truncate", children: secondary })] })] }) }), _jsx(TableCell, { className: "py-3", children: c.contact_person || c.email ? (_jsxs("div", { className: "min-w-0", children: [c.contact_person && _jsx("div", { className: "text-sm truncate", children: c.contact_person }), c.email && (_jsxs("a", { href: `mailto:${c.email}`, onClick: (e) => e.stopPropagation(), className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline truncate", children: [_jsx(Mail, { className: "h-3 w-3" }), " ", c.email] }))] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: c.customer_class ? _jsx(Badge, { variant: "secondary", children: c.customer_class }) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3 text-sm", children: c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MapPin, { className: "h-3.5 w-3.5 text-muted-foreground" }), " ", formatCountry(c.country)] })) : _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(StatusPill, { active: c.is_active }) })] }, c.key));
152
132
  }) })] }) }) })] }));
153
133
  }
154
134
  function StatCard({ icon, label, value, active, onClick, }) {
@@ -0,0 +1,2 @@
1
+ export declare function NetwerkPagina(): import("react").JSX.Element;
2
+ //# sourceMappingURL=NetwerkPagina.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NetwerkPagina.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NetwerkPagina.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,aAAa,gCAyH5B"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Link } from "@tanstack/react-router";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { useState } from "react";
6
+ import { Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Switch, Label, } from "@flowselections/core";
7
+ import { ArrowLeft, Search } from "lucide-react";
8
+ import { listFloridayNetwork } from "../../lib/customers.functions";
9
+ export function NetwerkPagina() {
10
+ const [search, setSearch] = useState("");
11
+ const [hideUnknown, setHideUnknown] = useState(true);
12
+ const list = useServerFn(listFloridayNetwork);
13
+ const { data, isLoading } = useQuery({
14
+ queryKey: ["floriday-network", search],
15
+ queryFn: () => list({ data: { search } }),
16
+ });
17
+ const all = data?.organizations ?? [];
18
+ const orgs = hideUnknown
19
+ ? all.filter((o) => o.organization_name && o.organization_name.trim() !== "")
20
+ : all;
21
+ const hiddenCount = all.length - orgs.length;
22
+ return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { children: [_jsxs(Link, { to: "/klanten", className: "text-sm text-muted-foreground hover:text-foreground inline-flex items-center gap-1", children: [_jsx(ArrowLeft, { className: "h-3 w-3" }), " Terug naar klanten"] }), _jsx("h1", { className: "text-2xl font-semibold mt-1", children: "Klanten toevoegen uit netwerk" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Doorzoek alle organisaties die via het netwerk beschikbaar zijn." })] }) }), _jsxs("div", { className: "relative max-w-md", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoek op organisatienaam of GLN...", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Switch, { id: "hide-unknown", checked: hideUnknown, onCheckedChange: setHideUnknown }), _jsx(Label, { htmlFor: "hide-unknown", className: "cursor-pointer", children: "Verberg organisaties zonder naam" }), hideUnknown && hiddenCount > 0 ? (_jsxs("span", { className: "text-muted-foreground", children: ["(", hiddenCount, " verborgen)"] })) : null] }), _jsx(Card, { children: _jsx(CardContent, { className: "p-0", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Organisatie" }), _jsx(TableHead, { children: "GLN" }), _jsx(TableHead, { children: "Plaats" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Bijgewerkt" })] }) }), _jsx(TableBody, { children: isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "text-center py-8 text-muted-foreground", children: "Laden..." }) })) : orgs.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "text-center py-8 text-muted-foreground", children: "Geen organisaties gevonden." }) })) : (orgs.map((o) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: o.organization_name && o.organization_name.trim() !== "" ? (o.organization_name) : (_jsx("span", { className: "italic text-muted-foreground", children: "Onbekende organisatie" })) }), _jsx(TableCell, { children: o.gln ? (_jsx(Badge, { variant: "outline", children: o.gln })) : ("—") }), _jsx(TableCell, { children: o.city ?? "—" }), _jsx(TableCell, { children: o.country ?? "—" }), _jsx(TableCell, { className: "text-muted-foreground text-sm", children: new Date(o.fetched_at).toLocaleDateString("nl-NL") })] }, `${o.connection_id}-${o.organization_id}`)))) })] }) }) })] }));
23
+ }
@@ -0,0 +1,2 @@
1
+ export declare function NieuweKlant(): import("react").JSX.Element;
2
+ //# sourceMappingURL=NieuweKlant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,WAAW,gCA4H1B"}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useNavigate } from "@tanstack/react-router";
3
+ import { useState } from "react";
4
+ import { useForm } from "react-hook-form";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { useMutation, useQuery } from "@tanstack/react-query";
7
+ import { useServerFn } from "@tanstack/react-start";
8
+ import { Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Textarea, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@flowselections/core";
9
+ import { ArrowLeft } from "lucide-react";
10
+ import { createCustomer, listCustomerClasses } from "../../lib/customers.functions";
11
+ import { customerCreateSchema } from "../../lib/customer-schemas";
12
+ export function NieuweKlant() {
13
+ const navigate = useNavigate();
14
+ const create = useServerFn(createCustomer);
15
+ const classesFn = useServerFn(listCustomerClasses);
16
+ const [dupes, setDupes] = useState([]);
17
+ const [pendingData, setPendingData] = useState(null);
18
+ const { data: classData } = useQuery({
19
+ queryKey: ["customer-classes"],
20
+ queryFn: () => classesFn({ data: undefined }),
21
+ });
22
+ const form = useForm({
23
+ resolver: zodResolver(customerCreateSchema),
24
+ defaultValues: {
25
+ company_name: "", vat_number: "", contact_person: "", email: "", phone: "",
26
+ street: "", house_number: "", postal_code: "", city: "", country: "Nederland",
27
+ customer_class: "", notes: "", is_active: true, force: false,
28
+ },
29
+ });
30
+ const mut = useMutation({
31
+ mutationFn: (data) => create({ data }),
32
+ onSuccess: (r) => {
33
+ if (!r.customer && r.duplicates?.length) {
34
+ setDupes(r.duplicates);
35
+ return;
36
+ }
37
+ toast.success("Klant aangemaakt");
38
+ navigate({ to: "/klanten/$id", params: { id: r.customer.id } });
39
+ },
40
+ onError: (e) => toast.error(e instanceof Error ? e.message : "Fout"),
41
+ });
42
+ const onSubmit = (values) => {
43
+ setPendingData(values);
44
+ mut.mutate(values);
45
+ };
46
+ const forceCreate = () => {
47
+ if (!pendingData)
48
+ return;
49
+ setDupes([]);
50
+ mut.mutate({ ...pendingData, force: true });
51
+ };
52
+ const fieldErr = (k) => form.formState.errors[k]?.message;
53
+ return (_jsxs("main", { className: "p-6 max-w-3xl mx-auto space-y-4", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate({ to: "/klanten" }), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-2" }), " Terug"] }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Nieuwe klant" }) }), _jsxs(CardContent, { className: "grid grid-cols-2 gap-4", children: [_jsx(Field, { label: "Bedrijfsnaam *", error: fieldErr("company_name"), children: _jsx(Input, { ...form.register("company_name") }) }), _jsx(Field, { label: "BTW-nummer", error: fieldErr("vat_number"), children: _jsx(Input, { ...form.register("vat_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "E-mail *", error: fieldErr("email"), children: _jsx(Input, { type: "email", ...form.register("email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Klantklasse", children: _jsxs(Select, { onValueChange: (v) => form.setValue("customer_class", v), defaultValue: "", children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies klasse..." }) }), _jsx(SelectContent, { children: (classData?.classes ?? []).map((c) => _jsx(SelectItem, { value: c, children: c }, c)) })] }) }), _jsx(Field, { label: "Straat *", error: fieldErr("street"), children: _jsx(Input, { ...form.register("street") }) }), _jsx(Field, { label: "Huisnummer *", error: fieldErr("house_number"), children: _jsx(Input, { ...form.register("house_number") }) }), _jsx(Field, { label: "Postcode", error: fieldErr("postal_code"), children: _jsx(Input, { ...form.register("postal_code") }) }), _jsx(Field, { label: "Stad", error: fieldErr("city"), children: _jsx(Input, { ...form.register("city") }) }), _jsx(Field, { label: "Land *", error: fieldErr("country"), children: _jsx(Input, { ...form.register("country") }) }), _jsxs("div", { className: "col-span-2", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { ...form.register("notes") })] })] })] }), _jsxs("div", { className: "flex justify-end gap-2 mt-4", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => navigate({ to: "/klanten" }), children: "Annuleren" }), _jsx(Button, { type: "submit", disabled: mut.isPending, children: mut.isPending ? "Opslaan..." : "Klant aanmaken" })] })] }), _jsx(AlertDialog, { open: dupes.length > 0, onOpenChange: (o) => !o && setDupes([]), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Mogelijke duplicaten gevonden" }), _jsx(AlertDialogDescription, { children: _jsx("ul", { className: "list-disc pl-5 space-y-1 mt-2", children: dupes.map((d) => _jsxs("li", { children: [_jsx("strong", { children: d.company_name }), " \u2014 ", d.reason] }, d.id + d.reason)) }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: forceCreate, children: "Toch aanmaken" })] })] }) })] }));
54
+ }
55
+ function Field({ label, error, children }) {
56
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: label }), children, error && _jsx("p", { className: "text-xs text-destructive", children: error })] }));
57
+ }
@@ -1,5 +1,9 @@
1
1
  import type { FlowModule } from "@flowselections/core";
2
2
  export * from "./_core-safelist";
3
3
  export { KlantenOverzicht } from "./components/klanten/KlantenOverzicht";
4
+ export { KlantDetail } from "./components/klanten/KlantDetail";
5
+ export { GroupedKlantDetail } from "./components/klanten/GroupedKlantDetail";
6
+ export { NetwerkPagina } from "./components/klanten/NetwerkPagina";
7
+ export { NieuweKlant } from "./components/klanten/NieuweKlant";
4
8
  export declare const floridayKlantenModule: FlowModule;
5
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,eAAO,MAAM,qBAAqB,EAAE,UAYnC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAE/D,eAAO,MAAM,qBAAqB,EAAE,UAYnC,CAAC"}
package/dist-lib/index.js CHANGED
@@ -17,13 +17,17 @@ import { Users } from "lucide-react";
17
17
  import { CustomerFieldsCard } from "./components/settings/CustomerFieldsCard";
18
18
  export * from "./_core-safelist";
19
19
  export { KlantenOverzicht } from "./components/klanten/KlantenOverzicht";
20
+ export { KlantDetail } from "./components/klanten/KlantDetail";
21
+ export { GroupedKlantDetail } from "./components/klanten/GroupedKlantDetail";
22
+ export { NetwerkPagina } from "./components/klanten/NetwerkPagina";
23
+ export { NieuweKlant } from "./components/klanten/NieuweKlant";
20
24
  export const floridayKlantenModule = {
21
25
  id: "customers",
22
26
  name: "Klanten",
23
27
  version: "1.0.0",
24
28
  nav: {
25
29
  label: "Klanten",
26
- href: "/floriday-klanten",
30
+ href: "/klanten",
27
31
  icon: Users,
28
32
  },
29
33
  settingsCards: [