@flowselections/floriday-klanten-module 1.0.7 → 1.0.9
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.
- package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts +4 -0
- package/dist-lib/components/klanten/CustomerAnalyticsTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/CustomerAnalyticsTab.js +90 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts +4 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/DeliveryLocationsTab.js +94 -0
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts +75 -0
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -0
- package/dist-lib/components/klanten/KlantDetailLayout.js +172 -0
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +12 -32
- package/dist-lib/index.js +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +3903 -361
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +3903 -361
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +3903 -361
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +3994 -340
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +10 -1
- package/dist-lib/lib/accounting.functions.d.ts +19056 -8430
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-analytics.functions.d.ts +9287 -0
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -0
- package/dist-lib/lib/customer-analytics.functions.js +165 -0
- package/dist-lib/lib/customers.functions.d.ts +46628 -21828
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/delivery-locations.functions.d.ts +37018 -0
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -0
- package/dist-lib/lib/delivery-locations.functions.js +150 -0
- package/dist-lib/lib/eu-regions.d.ts +14 -0
- package/dist-lib/lib/eu-regions.d.ts.map +1 -0
- package/dist-lib/lib/eu-regions.js +68 -0
- package/dist-lib/lib/external-customers.functions.d.ts +26750 -12582
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -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,
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
112
|
-
rows = rows.filter((c) =>
|
|
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
|
-
|
|
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:
|
|
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, }) {
|