@flowselections/floriday-klanten-module 1.0.14 → 1.0.16

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 (101) hide show
  1. package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
  2. package/dist-lib/components/CustomerAvatar.js +1 -1
  3. package/dist-lib/components/klanten/BackLink.d.ts +12 -0
  4. package/dist-lib/components/klanten/BackLink.d.ts.map +1 -0
  5. package/dist-lib/components/klanten/BackLink.js +12 -0
  6. package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts +4 -0
  7. package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts.map +1 -0
  8. package/dist-lib/components/klanten/FloridayAssortmentTab.js +80 -0
  9. package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts +8 -0
  10. package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts.map +1 -0
  11. package/dist-lib/components/klanten/FloridayDeeplinkButton.js +9 -0
  12. package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
  13. package/dist-lib/components/klanten/GroupedKlantDetail.js +19 -5
  14. package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
  15. package/dist-lib/components/klanten/ImportKlanten.js +46 -13
  16. package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
  17. package/dist-lib/components/klanten/KlantDetail.js +9 -3
  18. package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
  19. package/dist-lib/components/klanten/KlantenOverzicht.js +6 -3
  20. package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
  21. package/dist-lib/components/klanten/NetwerkPagina.js +42 -10
  22. package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
  23. package/dist-lib/components/klanten/NieuweKlant.js +23 -8
  24. package/dist-lib/components/klanten/PriceGroupBadges.d.ts +7 -0
  25. package/dist-lib/components/klanten/PriceGroupBadges.d.ts.map +1 -0
  26. package/dist-lib/components/klanten/PriceGroupBadges.js +13 -0
  27. package/dist-lib/components/klanten/SyncStatusChip.d.ts +4 -0
  28. package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -0
  29. package/dist-lib/components/klanten/SyncStatusChip.js +49 -0
  30. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts +8 -0
  31. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -0
  32. package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +75 -0
  33. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts +4 -0
  34. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts.map +1 -0
  35. package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +269 -0
  36. package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts +2 -0
  37. package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts.map +1 -0
  38. package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +133 -0
  39. package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts +8 -0
  40. package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts.map +1 -0
  41. package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.js +18 -0
  42. package/dist-lib/index.d.ts +1 -0
  43. package/dist-lib/index.d.ts.map +1 -1
  44. package/dist-lib/index.js +6 -1
  45. package/dist-lib/integrations/supabase/auth-middleware.d.ts +762 -3
  46. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/client.d.ts +762 -3
  48. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  49. package/dist-lib/integrations/supabase/client.js +4 -3
  50. package/dist-lib/integrations/supabase/client.server.d.ts +762 -3
  51. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  52. package/dist-lib/integrations/supabase/types.d.ts +789 -8
  53. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  54. package/dist-lib/lib/accounting/twinfield/client.server.d.ts +21 -0
  55. package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -0
  56. package/dist-lib/lib/accounting/twinfield/client.server.js +171 -0
  57. package/dist-lib/lib/accounting/twinfield/mapping.d.ts +46 -0
  58. package/dist-lib/lib/accounting/twinfield/mapping.d.ts.map +1 -0
  59. package/dist-lib/lib/accounting/twinfield/mapping.js +109 -0
  60. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +36318 -0
  61. package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -0
  62. package/dist-lib/lib/accounting/twinfield/worker.functions.js +48 -0
  63. package/dist-lib/lib/accounting/twinfield/worker.server.d.ts +12 -0
  64. package/dist-lib/lib/accounting/twinfield/worker.server.d.ts.map +1 -0
  65. package/dist-lib/lib/accounting/twinfield/worker.server.js +124 -0
  66. package/dist-lib/lib/accounting.functions.d.ts +2286 -9
  67. package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
  68. package/dist-lib/lib/customer-analytics.functions.d.ts +762 -3
  69. package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
  70. package/dist-lib/lib/customer-schemas.d.ts +48 -46
  71. package/dist-lib/lib/customer-schemas.d.ts.map +1 -1
  72. package/dist-lib/lib/customer-schemas.js +46 -3
  73. package/dist-lib/lib/customers.functions.d.ts +18288 -129
  74. package/dist-lib/lib/customers.functions.d.ts.map +1 -1
  75. package/dist-lib/lib/customers.functions.js +146 -10
  76. package/dist-lib/lib/delivery-locations.functions.d.ts +3048 -12
  77. package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
  78. package/dist-lib/lib/external-customers.functions.d.ts +3048 -12
  79. package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
  80. package/dist-lib/lib/floriday-assortment.functions.d.ts +12127 -0
  81. package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -0
  82. package/dist-lib/lib/floriday-assortment.functions.js +213 -0
  83. package/dist-lib/lib/floriday-discounts.functions.d.ts +1800 -282
  84. package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
  85. package/dist-lib/lib/floriday-trade-items.functions.d.ts +12111 -0
  86. package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -0
  87. package/dist-lib/lib/floriday-trade-items.functions.js +107 -0
  88. package/dist-lib/lib/local-discounts.functions.d.ts +5336 -23
  89. package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
  90. package/dist-lib/lib/overdue-invoices.functions.d.ts +762 -3
  91. package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
  92. package/dist-lib/lib/price-groups-local.functions.d.ts +84841 -0
  93. package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -0
  94. package/dist-lib/lib/price-groups-local.functions.js +148 -0
  95. package/dist-lib/lib/price-groups.functions.d.ts +12120 -0
  96. package/dist-lib/lib/price-groups.functions.d.ts.map +1 -0
  97. package/dist-lib/lib/price-groups.functions.js +115 -0
  98. package/dist-lib/lib/pricing.d.ts +24 -0
  99. package/dist-lib/lib/pricing.d.ts.map +1 -0
  100. package/dist-lib/lib/pricing.js +60 -0
  101. package/package.json +6 -3
@@ -1,23 +1,55 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Link } from "@tanstack/react-router";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
2
  import { useQuery } from "@tanstack/react-query";
4
3
  import { useServerFn } from "@tanstack/react-start";
5
4
  import { useState } from "react";
6
5
  import { Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Switch, Label, } from "@flowselections/core";
7
- import { ArrowLeft, Search } from "lucide-react";
6
+ import { Search, MapPin, Globe } from "lucide-react";
7
+ import { BackLink } from "../../components/klanten/BackLink";
8
8
  import { listFloridayNetwork } from "../../lib/customers.functions";
9
+ import { CustomerAvatar } from "../CustomerAvatar";
10
+ import { PriceGroupBadges } from "./PriceGroupBadges";
11
+ import { FloridayDeeplinkButton } from "./FloridayDeeplinkButton";
12
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
13
+ const TYPE_OPTIONS = [
14
+ { value: "CUSTOMER", label: "Klant" },
15
+ { value: "AGENT", label: "Agent" },
16
+ { value: "SUPPLIER", label: "Leverancier" },
17
+ { value: "CARRIER", label: "Vervoerder" },
18
+ { value: "OTHER", label: "Overig" },
19
+ ];
9
20
  export function NetwerkPagina() {
10
21
  const [search, setSearch] = useState("");
11
- const [hideUnknown, setHideUnknown] = useState(true);
22
+ const [hideUnknown, setHideUnknown] = useState(false);
23
+ const [types, setTypes] = useState(["CUSTOMER", "AGENT"]);
24
+ const [includeExpired, setIncludeExpired] = useState(false);
12
25
  const list = useServerFn(listFloridayNetwork);
13
26
  const { data, isLoading } = useQuery({
14
- queryKey: ["floriday-network", search],
15
- queryFn: () => list({ data: { search } }),
27
+ queryKey: ["floriday-network", search, types, includeExpired],
28
+ queryFn: () => list({ data: { search, types, includeExpired, limit: 1000 } }),
16
29
  });
17
30
  const all = data?.organizations ?? [];
18
- const orgs = hideUnknown
19
- ? all.filter((o) => o.organization_name && o.organization_name.trim() !== "")
20
- : all;
31
+ const named = all.filter((o) => o.organization_name && o.organization_name.trim() !== "");
32
+ const orgs = hideUnknown ? named : all;
21
33
  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}`)))) })] }) }) })] }));
34
+ const totalMatching = data?.total ?? all.length;
35
+ const truncated = !!data?.truncated;
36
+ const toggleType = (value) => {
37
+ setTypes((prev) => prev.includes(value) ? prev.filter((t) => t !== value) : [...prev, value]);
38
+ };
39
+ return (_jsx(TooltipPrimitive.Provider, { delayDuration: 200, children: _jsxs("main", { className: "p-6 space-y-4", children: [_jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { children: [_jsx(BackLink, {}), _jsx("h1", { className: "text-2xl font-semibold", 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" })] }), _jsx("div", { className: "flex items-center gap-2 flex-wrap", children: TYPE_OPTIONS.map((opt) => {
40
+ const active = types.includes(opt.value);
41
+ return (_jsx("button", { type: "button", onClick: () => toggleType(opt.value), className: "inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors " +
42
+ (active
43
+ ? "bg-primary text-primary-foreground border-primary"
44
+ : "bg-card text-muted-foreground border-border hover:bg-muted"), children: opt.label }, opt.value));
45
+ }) }), _jsxs("div", { className: "flex items-center gap-6 flex-wrap text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "include-expired", checked: includeExpired, onCheckedChange: setIncludeExpired }), _jsx(Label, { htmlFor: "include-expired", className: "cursor-pointer", children: "Toon be\u00EBindigde relaties" })] }), _jsxs("div", { className: "flex items-center gap-2", 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("div", { className: "text-xs text-muted-foreground", children: isLoading ? ("Laden…") : (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-foreground", children: orgs.length.toLocaleString("nl-NL") }), " ", "weergegeven van", " ", _jsx("span", { className: "font-medium text-foreground", children: totalMatching.toLocaleString("nl-NL") }), " ", "organisaties \u00B7 bron: Floriday", truncated && (_jsx("span", { className: "ml-1 text-warning", children: "(verfijn de zoekopdracht om alles te zien)" }))] })) }), _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: "Adres" }), _jsx(TableHead, { children: "Land" }), _jsx(TableHead, { children: "Type" }), _jsx(TableHead, { children: "Prijscategorie" }), _jsx(TableHead, { children: "Bijgewerkt" })] }) }), _jsx(TableBody, { children: isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Laden..." }) })) : orgs.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center py-8 text-muted-foreground", children: "Geen organisaties gevonden." }) })) : (orgs.map((o) => {
46
+ const displayName = o.organization_name && o.organization_name.trim() !== ""
47
+ ? o.organization_name
48
+ : null;
49
+ const addressBits = [
50
+ o.address_line,
51
+ [o.postal_code, o.city].filter(Boolean).join(" "),
52
+ ].filter((x) => x && String(x).trim() !== "");
53
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "py-3", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx(CustomerAvatar, { logoUrl: o.logo_url, name: displayName ?? "?", size: "sm" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [displayName ? (_jsx("span", { className: "truncate", children: displayName })) : (_jsx("span", { className: "italic text-muted-foreground", children: "Onbekende organisatie" })), o.is_expired && (_jsx(Badge, { variant: "outline", className: "text-[10px] uppercase tracking-wide", children: "be\u00EBindigd" }))] }), o.commercial_name && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: o.commercial_name })), o.website && (_jsxs("a", { href: o.website.startsWith("http") ? o.website : `https://${o.website}`, target: "_blank", rel: "noreferrer", className: "text-xs text-muted-foreground inline-flex items-center gap-1 hover:underline", children: [_jsx(Globe, { className: "h-3 w-3" }), " ", o.website] })), _jsx("div", { className: "mt-1", children: _jsx(FloridayDeeplinkButton, { organizationId: o.organization_id }) })] })] }) }), _jsx(TableCell, { children: o.gln ? _jsx(Badge, { variant: "outline", children: o.gln }) : "—" }), _jsx(TableCell, { className: "text-sm", children: addressBits.length ? (_jsx("div", { className: "min-w-0", children: _jsxs("div", { className: "inline-flex items-start gap-1 text-muted-foreground", children: [_jsx(MapPin, { className: "h-3 w-3 mt-0.5 shrink-0" }), _jsx("span", { className: "truncate", children: addressBits.join(", ") })] }) })) : ("—") }), _jsx(TableCell, { children: o.country ?? "—" }), _jsx(TableCell, { className: "text-xs text-muted-foreground uppercase tracking-wide", children: o.organization_type ?? "—" }), _jsx(TableCell, { children: _jsx(PriceGroupBadges, { groups: o.price_groups }) }), _jsx(TableCell, { className: "text-muted-foreground text-sm", children: new Date(o.fetched_at).toLocaleDateString("nl-NL") })] }, `${o.connection_id}-${o.organization_id}`));
54
+ })) })] }) }) })] }) }));
23
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,WAAW,gCAiN1B"}
1
+ {"version":3,"file":"NieuweKlant.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/NieuweKlant.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,WAAW,gCA2T1B"}
@@ -5,8 +5,9 @@ import { useForm } from "react-hook-form";
5
5
  import { zodResolver } from "@hookform/resolvers/zod";
6
6
  import { useMutation, useQuery } from "@tanstack/react-query";
7
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";
8
+ import { Button, Card, CardContent, Input, Label, Textarea, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@flowselections/core";
9
+ import { UserRound, MapPin, Landmark, ReceiptText, ChevronDown, Check, AlertCircle, Users, } from "lucide-react";
10
+ import { BackLink } from "../../components/klanten/BackLink";
10
11
  import { createCustomer, listCustomerClasses } from "../../lib/customers.functions";
11
12
  import { customerCreateSchema } from "../../lib/customer-schemas";
12
13
  export function NieuweKlant() {
@@ -15,12 +16,18 @@ export function NieuweKlant() {
15
16
  const classesFn = useServerFn(listCustomerClasses);
16
17
  const [dupes, setDupes] = useState([]);
17
18
  const [pendingData, setPendingData] = useState(null);
19
+ const [open, setOpen] = useState({
20
+ algemeen: true, adres: true, bank: true, facturering: true,
21
+ });
22
+ const toggle = (k) => setOpen((s) => ({ ...s, [k]: !s[k] }));
18
23
  const { data: classData } = useQuery({
19
24
  queryKey: ["customer-classes"],
20
25
  queryFn: () => classesFn({ data: undefined }),
21
26
  });
22
27
  const form = useForm({
23
28
  resolver: zodResolver(customerCreateSchema),
29
+ mode: "onBlur",
30
+ reValidateMode: "onChange",
24
31
  defaultValues: {
25
32
  company_name: "", vat_number: "", contact_person: "", email: "", phone: "",
26
33
  street: "", house_number: "", postal_code: "", city: "", country: "Nederland",
@@ -50,17 +57,25 @@ export function NieuweKlant() {
50
57
  });
51
58
  const onSubmit = (values) => {
52
59
  setPendingData(values);
53
- mut.mutate(values);
60
+ mut.mutate({ ...values, push_to_twinfield: true });
54
61
  };
55
62
  const forceCreate = () => {
56
63
  if (!pendingData)
57
64
  return;
58
65
  setDupes([]);
59
- mut.mutate({ ...pendingData, force: true });
66
+ mut.mutate({ ...pendingData, force: true, push_to_twinfield: true });
60
67
  };
61
- const fieldErr = (k) => form.formState.errors[k]?.message;
62
- return (_jsxs("main", { className: "p-6 max-w-5xl 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: "Algemeen" }) }), _jsxs(CardContent, { className: "grid grid-cols-2 gap-4", children: [_jsx(Field, { label: "Nummer", error: fieldErr("customer_number"), children: _jsx(Input, { ...form.register("customer_number") }) }), _jsx(Field, { label: "Bedrijfsnaam *", error: fieldErr("company_name"), children: _jsx(Input, { ...form.register("company_name") }) }), _jsx(Field, { label: "Taal", error: fieldErr("language"), children: _jsx(Input, { placeholder: "bv. nl, en", ...form.register("language") }) }), _jsx(Field, { label: "Website", error: fieldErr("website"), children: _jsx(Input, { ...form.register("website") }) }), _jsx(Field, { label: "BTW-nummer", error: fieldErr("vat_number"), children: _jsx(Input, { ...form.register("vat_number") }) }), _jsx(Field, { label: "KvK-nummer", error: fieldErr("coc_number"), children: _jsx(Input, { ...form.register("coc_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "T.a.v.", error: fieldErr("attention_to"), children: _jsx(Input, { ...form.register("attention_to") }) }), _jsx(Field, { label: "E-mail *", error: fieldErr("email"), children: _jsx(Input, { type: "email", ...form.register("email") }) }), _jsx(Field, { label: "E-mail voor facturering", error: fieldErr("billing_email"), children: _jsx(Input, { type: "email", ...form.register("billing_email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Faxnummer", error: fieldErr("fax"), children: _jsx(Input, { ...form.register("fax") }) }), _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)) })] }) })] })] }), _jsxs(Card, { className: "mt-4", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Adres" }) }), _jsxs(CardContent, { className: "grid grid-cols-2 gap-4", children: [_jsx(Field, { label: "Adrestype", error: fieldErr("address_type"), children: _jsx(Input, { placeholder: "bv. Bezoek, Post", ...form.register("address_type") }) }), _jsx(Field, { label: "ICP landcode", error: fieldErr("icp_country_code"), children: _jsx(Input, { placeholder: "bv. NL", ...form.register("icp_country_code") }) }), _jsx(Field, { label: "Adresregel 1", error: fieldErr("address_line1"), children: _jsx(Input, { ...form.register("address_line1") }) }), _jsx(Field, { label: "Adresregel 2", error: fieldErr("address_line2"), children: _jsx(Input, { ...form.register("address_line2") }) }), _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: "Plaats", error: fieldErr("city"), children: _jsx(Input, { ...form.register("city") }) }), _jsx(Field, { label: "Land *", error: fieldErr("country"), children: _jsx(Input, { ...form.register("country") }) })] })] }), _jsxs(Card, { className: "mt-4", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Bankgegevens" }) }), _jsxs(CardContent, { className: "grid grid-cols-2 gap-4", children: [_jsx(Field, { label: "Bank - Naam", children: _jsx(Input, { ...form.register("bank_name") }) }), _jsx(Field, { label: "Bank - Rekeninghouder", children: _jsx(Input, { ...form.register("bank_account_holder") }) }), _jsx(Field, { label: "Bank - Rekeningnummer", children: _jsx(Input, { ...form.register("bank_account_number") }) }), _jsx(Field, { label: "Bank - IBAN", children: _jsx(Input, { ...form.register("bank_iban") }) }), _jsx(Field, { label: "Bank - BIC", children: _jsx(Input, { ...form.register("bank_bic") }) }), _jsx(Field, { label: "Bank - Nationale bankcode", children: _jsx(Input, { ...form.register("bank_national_code") }) }), _jsx(Field, { label: "Bank - Adresregel 1", children: _jsx(Input, { ...form.register("bank_address_line1") }) }), _jsx(Field, { label: "Bank - Adresregel 2", children: _jsx(Input, { ...form.register("bank_address_line2") }) }), _jsx(Field, { label: "Bank - Huisnummer", children: _jsx(Input, { ...form.register("bank_house_number") }) }), _jsx(Field, { label: "Bank - Postcode", children: _jsx(Input, { ...form.register("bank_postal_code") }) }), _jsx(Field, { label: "Bank - Plaats", children: _jsx(Input, { ...form.register("bank_city") }) }), _jsx(Field, { label: "Bank - Staat", children: _jsx(Input, { ...form.register("bank_state") }) }), _jsx(Field, { label: "Bank - Land", children: _jsx(Input, { ...form.register("bank_country") }) })] })] }), _jsxs(Card, { className: "mt-4", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Facturering & betaling" }) }), _jsxs(CardContent, { className: "grid grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kortingspercentage (%)", error: fieldErr("discount_percentage"), children: _jsx(Input, { type: "number", step: "0.01", ...form.register("discount_percentage", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), children: _jsx(Input, { type: "number", ...form.register("discount_days", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", ...form.register("payment_term_days", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsx(Input, { placeholder: "bv. Inclusief, Exclusief", ...form.register("vat_display") }) }), _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" })] })] }) })] }));
68
+ const errors = form.formState.errors;
69
+ const errorCount = Object.keys(errors).length;
70
+ const touched = form.formState.touchedFields;
71
+ const values = form.watch();
72
+ const fieldErr = (k) => errors[k]?.message;
73
+ const isValid = (k) => !errors[k] && !!touched[k] && !!values[k];
74
+ return (_jsxs("main", { className: "min-h-screen bg-muted/30 pb-28", children: [_jsxs("div", { className: "max-w-6xl mx-auto px-6 pt-6", children: [_jsx(BackLink, {}), _jsxs("header", { className: "flex items-start gap-4 mb-6", children: [_jsx("div", { className: "h-12 w-12 shrink-0 rounded-full bg-primary/10 text-primary grid place-items-center", children: _jsx(UserRound, { className: "h-6 w-6" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Nieuwe klant" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Vul de gegevens in om een nieuwe klant aan te maken." })] })] }), errorCount > 0 && form.formState.isSubmitted && (_jsxs("div", { className: "mb-4 flex items-start gap-3 rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm", children: [_jsx(AlertCircle, { className: "h-5 w-5 shrink-0 text-destructive mt-0.5" }), _jsxs("div", { children: [_jsxs("p", { className: "font-medium text-destructive", children: ["Controleer ", errorCount, " ", errorCount === 1 ? "veld" : "velden"] }), _jsx("p", { className: "text-muted-foreground mt-0.5", children: "Er zijn een aantal velden nog niet correct ingevuld." })] })] })), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-4", children: [_jsx(Section, { k: "algemeen", title: "Algemeen", icon: _jsx(Users, { className: "h-5 w-5" }), open: open.algemeen, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [_jsx(Field, { label: "Nummer", error: fieldErr("customer_number"), success: isValid("customer_number"), children: _jsx(Input, { ...form.register("customer_number") }) }), _jsx(Field, { label: "Bedrijfsnaam", required: true, error: fieldErr("company_name"), success: isValid("company_name"), children: _jsx(Input, { "aria-invalid": !!errors.company_name, ...form.register("company_name") }) }), _jsx(Field, { label: "Taal", error: fieldErr("language"), children: _jsxs(Select, { value: form.watch("language") || "nl", onValueChange: (v) => form.setValue("language", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "nl", children: "nl" }), _jsx(SelectItem, { value: "en", children: "en" }), _jsx(SelectItem, { value: "de", children: "de" }), _jsx(SelectItem, { value: "fr", children: "fr" })] })] }) }), _jsx(Field, { label: "Website", error: fieldErr("website"), success: isValid("website"), children: _jsx(Input, { placeholder: "https://", ...form.register("website") }) }), _jsx(Field, { label: "BTW-nummer", error: fieldErr("vat_number"), success: isValid("vat_number"), children: _jsx(Input, { ...form.register("vat_number") }) }), _jsx(Field, { label: "KvK-nummer", error: fieldErr("coc_number"), success: isValid("coc_number"), children: _jsx(Input, { ...form.register("coc_number") }) }), _jsx(Field, { label: "Contactpersoon", error: fieldErr("contact_person"), success: isValid("contact_person"), children: _jsx(Input, { ...form.register("contact_person") }) }), _jsx(Field, { label: "T.a.v.", error: fieldErr("attention_to"), children: _jsx(Input, { ...form.register("attention_to") }) }), _jsx(Field, { label: "E-mail", required: true, error: fieldErr("email"), success: isValid("email"), children: _jsx(Input, { type: "email", "aria-invalid": !!errors.email, placeholder: "naam@bedrijf.nl", ...form.register("email") }) }), _jsx(Field, { label: "E-mail voor facturering", error: fieldErr("billing_email"), success: isValid("billing_email"), children: _jsx(Input, { type: "email", placeholder: "facturen@bedrijf.nl", ...form.register("billing_email") }) }), _jsx(Field, { label: "Telefoon", error: fieldErr("phone"), success: isValid("phone"), children: _jsx(Input, { ...form.register("phone") }) }), _jsx(Field, { label: "Faxnummer", error: fieldErr("fax"), children: _jsx(Input, { ...form.register("fax") }) }), _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(Section, { k: "adres", title: "Adres", icon: _jsx(MapPin, { className: "h-5 w-5" }), open: open.adres, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Adres type", error: fieldErr("address_type"), children: _jsx(Input, { placeholder: "bv. Bezoek, Post", ...form.register("address_type") }) }), _jsx(Field, { label: "ICP landcode", error: fieldErr("icp_country_code"), children: _jsx(Input, { placeholder: "bv. NL", ...form.register("icp_country_code") }) }), _jsx(Field, { label: "Adresregel 1", error: fieldErr("address_line1"), children: _jsx(Input, { ...form.register("address_line1") }) }), _jsx(Field, { label: "Adresregel 2", error: fieldErr("address_line2"), children: _jsx(Input, { ...form.register("address_line2") }) }), _jsx(Field, { label: "Straat", required: true, error: fieldErr("street"), success: isValid("street"), children: _jsx(Input, { "aria-invalid": !!errors.street, ...form.register("street") }) }), _jsx(Field, { label: "Huisnummer", required: true, error: fieldErr("house_number"), success: isValid("house_number"), children: _jsx(Input, { "aria-invalid": !!errors.house_number, ...form.register("house_number") }) }), _jsx(Field, { label: "Postcode", error: fieldErr("postal_code"), success: isValid("postal_code"), children: _jsx(Input, { ...form.register("postal_code") }) }), _jsx(Field, { label: "Plaats", error: fieldErr("city"), success: isValid("city"), children: _jsx(Input, { ...form.register("city") }) }), _jsx(Field, { label: "Land", required: true, error: fieldErr("country"), success: isValid("country"), children: _jsxs(Select, { value: form.watch("country") || "Nederland", onValueChange: (v) => form.setValue("country", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "Nederland", children: "Nederland" }), _jsx(SelectItem, { value: "Belgi\u00EB", children: "Belgi\u00EB" }), _jsx(SelectItem, { value: "Duitsland", children: "Duitsland" }), _jsx(SelectItem, { value: "Frankrijk", children: "Frankrijk" }), _jsx(SelectItem, { value: "Verenigd Koninkrijk", children: "Verenigd Koninkrijk" })] })] }) })] }) }), _jsx(Section, { k: "bank", title: "Bankgegevens", icon: _jsx(Landmark, { className: "h-5 w-5" }), open: open.bank, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Bank - Naam", children: _jsx(Input, { ...form.register("bank_name") }) }), _jsx(Field, { label: "Bank - Rekeninghouder", children: _jsx(Input, { ...form.register("bank_account_holder") }) }), _jsx(Field, { label: "Bank - Rekeningnummer", children: _jsx(Input, { ...form.register("bank_account_number") }) }), _jsx(Field, { label: "Bank - IBAN", children: _jsx(Input, { placeholder: "NL00 BANK 0000 0000 00", ...form.register("bank_iban") }) }), _jsx(Field, { label: "Bank - BIC", children: _jsx(Input, { ...form.register("bank_bic") }) }), _jsx(Field, { label: "Bank - Nationale bankcode", children: _jsx(Input, { ...form.register("bank_national_code") }) }), _jsx(Field, { label: "Bank - Adresregel 1", children: _jsx(Input, { ...form.register("bank_address_line1") }) }), _jsx(Field, { label: "Bank - Adresregel 2", children: _jsx(Input, { ...form.register("bank_address_line2") }) }), _jsx(Field, { label: "Bank - Huisnummer", children: _jsx(Input, { ...form.register("bank_house_number") }) }), _jsx(Field, { label: "Bank - Postcode", children: _jsx(Input, { ...form.register("bank_postal_code") }) }), _jsx(Field, { label: "Bank - Plaats", children: _jsx(Input, { ...form.register("bank_city") }) }), _jsx(Field, { label: "Bank - Staat", children: _jsx(Input, { ...form.register("bank_state") }) }), _jsx("div", { className: "md:col-span-2", children: _jsx(Field, { label: "Bank - Land", children: _jsx(Input, { ...form.register("bank_country") }) }) })] }) }), _jsx(Section, { k: "facturering", title: "Facturering & betaling", icon: _jsx(ReceiptText, { className: "h-5 w-5" }), open: open.facturering, onToggle: toggle, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Field, { label: "Kortingspercentage (%)", error: fieldErr("discount_percentage"), children: _jsx(Input, { type: "number", step: "0.01", ...form.register("discount_percentage", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Aantal kortingsdagen", error: fieldErr("discount_days"), children: _jsx(Input, { type: "number", ...form.register("discount_days", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Aantal vervaldagen", error: fieldErr("payment_term_days"), children: _jsx(Input, { type: "number", ...form.register("payment_term_days", { valueAsNumber: true }) }) }), _jsx(Field, { label: "Btw-weergave op factuur", error: fieldErr("vat_display"), children: _jsxs(Select, { value: form.watch("vat_display") || "", onValueChange: (v) => form.setValue("vat_display", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "bv. Inclusief, Exclusief" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "Inclusief", children: "Inclusief" }), _jsx(SelectItem, { value: "Exclusief", children: "Exclusief" })] })] }) }), _jsxs("div", { className: "md:col-span-2 space-y-1.5", children: [_jsx(Label, { children: "Notities" }), _jsx(Textarea, { rows: 4, ...form.register("notes") })] })] }) })] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 border-t bg-background/90 backdrop-blur z-20", children: _jsxs("div", { className: "max-w-6xl mx-auto px-6 py-3 flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => navigate({ to: "/klanten" }), children: "Annuleren" }), _jsxs(Button, { type: "button", onClick: form.handleSubmit(onSubmit), disabled: mut.isPending, children: [_jsx(Check, { className: "h-4 w-4 mr-2" }), 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" })] })] }) })] }));
75
+ }
76
+ function Section({ k, title, icon, open, onToggle, children, }) {
77
+ return (_jsxs(Card, { className: "rounded-2xl border shadow-sm overflow-hidden", children: [_jsxs("button", { type: "button", onClick: () => onToggle(k), className: "w-full flex items-center justify-between px-6 py-4 hover:bg-muted/40 transition-colors", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-primary", children: icon }), _jsx("span", { className: "text-base font-semibold", children: title })] }), _jsx(ChevronDown, { className: `h-5 w-5 text-muted-foreground transition-transform ${open ? "" : "-rotate-90"}` })] }), open && (_jsx(CardContent, { className: "px-6 pb-6 pt-0", children: children }))] }));
63
78
  }
64
- function Field({ label, error, children }) {
65
- return (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: label }), children, error && _jsx("p", { className: "text-xs text-destructive", children: error })] }));
79
+ function Field({ label, required, error, success, children, }) {
80
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "text-sm font-medium", children: [label, required && _jsx("span", { className: "text-destructive ml-0.5", children: "*" })] }), _jsxs("div", { className: `relative ${error ? "[&_input]:border-destructive [&_input]:focus-visible:ring-destructive/30" : ""}`, children: [children, success && !error && (_jsx(Check, { className: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-emerald-600" }))] }), error && _jsxs("p", { className: "text-xs text-destructive flex items-center gap-1", children: [_jsx(AlertCircle, { className: "h-3 w-3" }), error] })] }));
66
81
  }
@@ -0,0 +1,7 @@
1
+ import type { PriceGroup } from "../../lib/price-groups.functions";
2
+ export declare function PriceGroupBadges({ groups, max, emptyLabel, }: {
3
+ groups: PriceGroup[] | null | undefined;
4
+ max?: number;
5
+ emptyLabel?: string;
6
+ }): import("react").JSX.Element;
7
+ //# sourceMappingURL=PriceGroupBadges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceGroupBadges.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/PriceGroupBadges.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,GAAO,EACP,UAAgB,GACjB,EAAE;IACD,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,+BAoCA"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge } from "@flowselections/core";
3
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4
+ export function PriceGroupBadges({ groups, max = 1, emptyLabel = "—", }) {
5
+ const list = groups ?? [];
6
+ if (list.length === 0) {
7
+ return _jsx("span", { className: "text-muted-foreground", children: emptyLabel });
8
+ }
9
+ const visible = list.slice(0, max);
10
+ const extra = list.length - visible.length;
11
+ const tooltip = list.map((g) => g.name).join(", ");
12
+ return (_jsxs(TooltipPrimitive.Root, { children: [_jsx(TooltipPrimitive.Trigger, { asChild: true, children: _jsxs("span", { className: "inline-flex items-center gap-1 flex-wrap", children: [visible.map((g) => (_jsx(Badge, { variant: "secondary", className: "max-w-[160px] truncate", children: g.name }, g.id))), extra > 0 && (_jsxs(Badge, { variant: "outline", className: "text-xs", children: ["+", extra] }))] }) }), _jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { side: "top", sideOffset: 6, className: "z-50 max-w-xs rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md", children: [tooltip, _jsx(TooltipPrimitive.Arrow, { className: "fill-popover" })] }) })] }));
13
+ }
@@ -0,0 +1,4 @@
1
+ export declare function SyncStatusChip({ customerId }: {
2
+ customerId: string;
3
+ }): import("react").JSX.Element | null;
4
+ //# sourceMappingURL=SyncStatusChip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"AAUA,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAsEpE"}
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Badge } from "@flowselections/core";
6
+ import { CheckCircle2, Clock, AlertTriangle, Loader2 } from "lucide-react";
7
+ import { getTwinfieldSyncStatus, retryTwinfieldPush, } from "../../lib/accounting/twinfield/worker.functions";
8
+ export function SyncStatusChip({ customerId }) {
9
+ const qc = useQueryClient();
10
+ const [retrying, setRetrying] = useState(false);
11
+ const fetchStatus = useServerFn(getTwinfieldSyncStatus);
12
+ const doRetry = useServerFn(retryTwinfieldPush);
13
+ const { data } = useQuery({
14
+ queryKey: ["twinfield-sync-status", customerId],
15
+ queryFn: () => fetchStatus({ data: { customerId } }),
16
+ refetchInterval: 30000,
17
+ staleTime: 15000,
18
+ });
19
+ const retry = useMutation({
20
+ mutationFn: async () => {
21
+ setRetrying(true);
22
+ return doRetry({ data: { customerId } });
23
+ },
24
+ onSettled: () => {
25
+ setRetrying(false);
26
+ qc.invalidateQueries({ queryKey: ["twinfield-sync-status", customerId] });
27
+ },
28
+ });
29
+ if (!data)
30
+ return null;
31
+ const latest = data.latest;
32
+ const customer = data.customer;
33
+ const synced = customer?.twinfield_synced_at;
34
+ const status = latest?.status;
35
+ // Toon niets als er nooit een queue-rij is geweest én er ook geen sync bestaat
36
+ if (!synced && !latest)
37
+ return null;
38
+ if (status === "failed" || status === "dead") {
39
+ return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex items-center gap-1.5 text-xs", title: latest?.last_error ?? "Onbekende fout", children: _jsxs(Badge, { variant: "destructive", className: "gap-1", children: [retrying ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(AlertTriangle, { className: "h-3 w-3" }), "Synchronisatie mislukt \u2014 opnieuw proberen"] }) }));
40
+ }
41
+ if (status === "pending" || status === "processing") {
42
+ return (_jsxs(Badge, { variant: "secondary", className: "gap-1", children: [_jsx(Clock, { className: "h-3 w-3" }), "Wacht op synchronisatie"] }));
43
+ }
44
+ if (synced) {
45
+ const when = new Date(synced).toLocaleString("nl-NL", { dateStyle: "short", timeStyle: "short" });
46
+ return (_jsxs(Badge, { variant: "outline", className: "gap-1 text-emerald-700 border-emerald-200 bg-emerald-50", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "Gesynchroniseerd \u00B7 ", when] }));
47
+ }
48
+ return null;
49
+ }
@@ -0,0 +1,8 @@
1
+ export declare function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value, onChange, }: {
2
+ priceGroupId?: string;
3
+ open: boolean;
4
+ onOpenChange: (v: boolean) => void;
5
+ value?: string[];
6
+ onChange?: (keys: string[]) => void;
7
+ }): import("react").JSX.Element;
8
+ //# sourceMappingURL=CustomerPickerDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomerPickerDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/CustomerPickerDialog.tsx"],"names":[],"mappings":"AAaA,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,GAClD,EAAE;IACD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACrC,+BA+HA"}
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { useServerFn } from "@tanstack/react-start";
5
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Button, Input, Checkbox, Badge, } from "@flowselections/core";
6
+ import { Search } from "lucide-react";
7
+ import { listExternalCustomers } from "../../../lib/external-customers.functions";
8
+ import { setPriceGroupMembers, getCustomerPriceGroup, } from "../../../lib/price-groups-local.functions";
9
+ export function CustomerPickerDialog({ priceGroupId, open, onOpenChange, value, onChange, }) {
10
+ const qc = useQueryClient();
11
+ const listCustomers = useServerFn(listExternalCustomers);
12
+ const getGroup = useServerFn(getCustomerPriceGroup);
13
+ const setMembers = useServerFn(setPriceGroupMembers);
14
+ const [search, setSearch] = useState("");
15
+ const [selected, setSelected] = useState(new Set());
16
+ const isControlled = onChange !== undefined;
17
+ const { data: customersData, isLoading } = useQuery({
18
+ queryKey: ["external-customers-picker"],
19
+ queryFn: () => listCustomers({ data: { search: "", status: "all", limit: 2000 } }),
20
+ enabled: open,
21
+ });
22
+ const { data: group } = useQuery({
23
+ queryKey: ["customer-price-group", priceGroupId],
24
+ queryFn: () => getGroup({ data: { id: priceGroupId } }),
25
+ enabled: open && !!priceGroupId && !isControlled,
26
+ });
27
+ useEffect(() => {
28
+ if (!open)
29
+ return;
30
+ if (isControlled) {
31
+ setSelected(new Set(value ?? []));
32
+ }
33
+ else if (group?.customerKeys) {
34
+ setSelected(new Set(group.customerKeys));
35
+ }
36
+ }, [group, open, isControlled, value]);
37
+ const customers = customersData?.customers ?? [];
38
+ const filtered = useMemo(() => {
39
+ const q = search.trim().toLowerCase();
40
+ if (!q)
41
+ return customers;
42
+ return customers.filter((c) => [c.display_name, c.commercial_name, c.city, c.email, c.vat_number]
43
+ .filter(Boolean).some((v) => String(v).toLowerCase().includes(q)));
44
+ }, [customers, search]);
45
+ const toggle = (key) => {
46
+ setSelected((prev) => {
47
+ const n = new Set(prev);
48
+ if (n.has(key))
49
+ n.delete(key);
50
+ else
51
+ n.add(key);
52
+ return n;
53
+ });
54
+ };
55
+ const saveMut = useMutation({
56
+ mutationFn: () => setMembers({ data: { id: priceGroupId, customerKeys: Array.from(selected) } }),
57
+ onSuccess: () => {
58
+ qc.invalidateQueries({ queryKey: ["customer-price-groups"] });
59
+ qc.invalidateQueries({ queryKey: ["customer-price-group", priceGroupId] });
60
+ onOpenChange(false);
61
+ },
62
+ });
63
+ const handleSave = () => {
64
+ if (isControlled) {
65
+ onChange(Array.from(selected));
66
+ onOpenChange(false);
67
+ return;
68
+ }
69
+ saveMut.mutate();
70
+ };
71
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-[720px]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Klanten toewijzen" }) }), _jsxs("div", { className: "relative", 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 naam, plaats of BTW-nummer\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "pl-9" })] }), _jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsxs("span", { children: [filtered.length, " klanten"] }), _jsxs("span", { children: [_jsx("strong", { className: "text-foreground", children: selected.size }), " geselecteerd"] })] }), _jsx("div", { className: "max-h-[380px] overflow-y-auto border rounded-md divide-y", children: isLoading ? (_jsx("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : filtered.length === 0 ? (_jsx("div", { className: "p-6 text-center text-sm text-muted-foreground", children: "Geen klanten gevonden." })) : filtered.map((c) => {
72
+ const checked = selected.has(c.group_key);
73
+ return (_jsxs("label", { className: "flex items-center gap-3 p-2.5 hover:bg-muted/50 cursor-pointer", children: [_jsx(Checkbox, { checked: checked, onCheckedChange: () => toggle(c.group_key) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c.display_name }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: [c.city, c.country].filter(Boolean).join(" · ") || "—" })] }), _jsx("div", { className: "flex gap-1", children: c.providers.slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, c.group_key));
74
+ }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsx(Button, { onClick: handleSave, disabled: !isControlled && saveMut.isPending, children: !isControlled && saveMut.isPending ? "Opslaan…" : "Selectie opslaan" })] })] }) }));
75
+ }
@@ -0,0 +1,4 @@
1
+ export declare function PriceGroupFormPage({ editingId }: {
2
+ editingId?: string | null;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=PriceGroupFormPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceGroupFormPage.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/PriceGroupFormPage.tsx"],"names":[],"mappings":"AAkLA,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,+BA0rB9E"}