@flowselections/floriday-klanten-module 1.0.6 → 1.0.7
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/ExternalCacheBanner.d.ts +6 -0
- package/dist-lib/components/klanten/ExternalCacheBanner.d.ts.map +1 -0
- package/dist-lib/components/klanten/ExternalCacheBanner.js +22 -0
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +111 -79
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +4264 -1024
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +4264 -1024
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +4264 -1024
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +4364 -1041
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +10 -0
- package/dist-lib/lib/accounting/floriday/sync.server.d.ts.map +1 -1
- package/dist-lib/lib/accounting/floriday/sync.server.js +5 -3
- package/dist-lib/lib/accounting.functions.d.ts +13842 -4122
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.d.ts +29788 -14456
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.js +8 -31
- package/dist-lib/lib/external-customers.functions.d.ts +22814 -0
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -0
- package/dist-lib/lib/external-customers.functions.js +131 -0
- package/package.json +2 -2
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExternalProvider } from "../../lib/external-customers.functions";
|
|
2
|
+
export declare function ExternalCacheBanner({ lastFetchedAt, providers, }: {
|
|
3
|
+
lastFetchedAt: string | null;
|
|
4
|
+
providers: ExternalProvider[];
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=ExternalCacheBanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalCacheBanner.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ExternalCacheBanner.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAa/E,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,SAAS,GACV,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;CAC/B,+BAqBA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Cloud, Clock } from "lucide-react";
|
|
3
|
+
function relativeTime(iso) {
|
|
4
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
5
|
+
const min = Math.floor(diff / 60000);
|
|
6
|
+
if (min < 1)
|
|
7
|
+
return "zojuist";
|
|
8
|
+
if (min < 60)
|
|
9
|
+
return `${min} min geleden`;
|
|
10
|
+
const hr = Math.floor(min / 60);
|
|
11
|
+
if (hr < 24)
|
|
12
|
+
return `${hr} uur geleden`;
|
|
13
|
+
const day = Math.floor(hr / 24);
|
|
14
|
+
return `${day} dag${day === 1 ? "" : "en"} geleden`;
|
|
15
|
+
}
|
|
16
|
+
export function ExternalCacheBanner({ lastFetchedAt, providers, }) {
|
|
17
|
+
const labels = providers
|
|
18
|
+
.filter((p) => p.is_enabled)
|
|
19
|
+
.map((p) => p.label)
|
|
20
|
+
.join(", ");
|
|
21
|
+
return (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm", children: [_jsx(Cloud, { className: "h-4 w-4 mt-0.5 text-muted-foreground shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: "Externe klantgegevens (read-only)" }), _jsxs("div", { className: "text-xs text-muted-foreground mt-0.5", children: ["Data wordt automatisch verfrist door de gekoppelde authenticatiemodules", labels ? `: ${labels}` : "", "."] })] }), _jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground shrink-0", children: [_jsx(Clock, { className: "h-3.5 w-3.5" }), lastFetchedAt ? `Laatste cache: ${relativeTime(lastFetchedAt)}` : "Nog geen cache"] })] }));
|
|
22
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KlantenOverzicht.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantenOverzicht.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,gBAAgB,gCAuW/B"}
|
|
@@ -1,109 +1,136 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useNavigate } from "@tanstack/react-router";
|
|
3
|
-
import { useQuery
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
4
|
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
-
import {
|
|
6
|
-
import { Button, Input, Card, CardContent, Badge,
|
|
7
|
-
import { Plus,
|
|
8
|
-
import { listCustomers,
|
|
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";
|
|
8
|
+
import { listCustomers, listCustomerClasses } from "../../lib/customers.functions";
|
|
9
|
+
import { listExternalCustomers } from "../../lib/external-customers.functions";
|
|
9
10
|
import { CustomerAvatar } from "../CustomerAvatar";
|
|
11
|
+
import { ExternalCacheBanner } from "./ExternalCacheBanner";
|
|
10
12
|
export function KlantenOverzicht() {
|
|
11
13
|
const navigate = useNavigate();
|
|
12
|
-
const qc = useQueryClient();
|
|
13
14
|
const [search, setSearch] = useState("");
|
|
14
15
|
const [customerClass, setCustomerClass] = useState("__all__");
|
|
15
16
|
const [country, setCountry] = useState("__all__");
|
|
16
17
|
const [view, setView] = useState("all");
|
|
17
18
|
const [sort, setSort] = useState({ col: "company_name", dir: "asc" });
|
|
18
19
|
const list = useServerFn(listCustomers);
|
|
19
|
-
const
|
|
20
|
+
const listExternal = useServerFn(listExternalCustomers);
|
|
20
21
|
const classesFn = useServerFn(listCustomerClasses);
|
|
21
|
-
const activeFilter = view === "active" ? "active" : view === "inactive" ? "inactive" : "all";
|
|
22
|
-
const source = view === "floriday" ? "floriday" : view === "local" ? "local" : "all";
|
|
23
22
|
const { data: classData } = useQuery({
|
|
24
23
|
queryKey: ["customer-classes"],
|
|
25
24
|
queryFn: () => classesFn({ data: undefined }),
|
|
26
25
|
});
|
|
27
|
-
const { data:
|
|
28
|
-
queryKey: ["customers"
|
|
26
|
+
const { data: localData, isLoading: loadingLocal } = useQuery({
|
|
27
|
+
queryKey: ["customers-local"],
|
|
29
28
|
queryFn: () => list({
|
|
30
|
-
data: { search: "", customer_class: "", country: "", is_active: "all", source: "
|
|
29
|
+
data: { search: "", customer_class: "", country: "", is_active: "all", source: "local" },
|
|
31
30
|
}),
|
|
32
31
|
});
|
|
33
|
-
const { data, isLoading } = useQuery({
|
|
34
|
-
queryKey: ["customers"
|
|
35
|
-
queryFn: () =>
|
|
36
|
-
data: {
|
|
37
|
-
search,
|
|
38
|
-
customer_class: customerClass === "__all__" ? "" : customerClass,
|
|
39
|
-
country: "",
|
|
40
|
-
is_active: activeFilter,
|
|
41
|
-
source,
|
|
42
|
-
},
|
|
43
|
-
}),
|
|
32
|
+
const { data: externalData, isLoading: loadingExternal } = useQuery({
|
|
33
|
+
queryKey: ["customers-external"],
|
|
34
|
+
queryFn: () => listExternal({ data: { status: "all" } }),
|
|
44
35
|
});
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
36
|
+
const isLoading = loadingLocal || loadingExternal;
|
|
37
|
+
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
|
+
const allRows = useMemo(() => {
|
|
45
|
+
const local = (localData?.customers ?? []).map((c) => ({
|
|
46
|
+
key: `local-${c.id}`,
|
|
47
|
+
id: c.id,
|
|
48
|
+
group_key: null,
|
|
49
|
+
source: "local",
|
|
50
|
+
source_label: "Lokaal",
|
|
51
|
+
providers: [],
|
|
52
|
+
provider_labels: [],
|
|
53
|
+
source_count: 1,
|
|
54
|
+
company_name: c.company_name,
|
|
55
|
+
commercial_name: c.commercial_name ?? null,
|
|
56
|
+
contact_person: c.contact_person ?? null,
|
|
57
|
+
email: c.email ?? null,
|
|
58
|
+
city: c.city ?? null,
|
|
59
|
+
country: c.country ?? null,
|
|
60
|
+
customer_class: c.customer_class ?? null,
|
|
61
|
+
logo_url: c.logo_url ?? null,
|
|
62
|
+
is_active: !!c.is_active,
|
|
63
|
+
}));
|
|
64
|
+
const ext = (externalData?.customers ?? []).map((c) => {
|
|
65
|
+
const provs = c.providers ?? [];
|
|
66
|
+
const labels = provs.map((p) => providerLabel.get(p) ?? p);
|
|
67
|
+
return {
|
|
68
|
+
key: `grp-${c.group_key}`,
|
|
69
|
+
id: null,
|
|
70
|
+
group_key: c.group_key,
|
|
71
|
+
source: provs[0] ?? "external",
|
|
72
|
+
source_label: labels[0] ?? "Extern",
|
|
73
|
+
providers: provs,
|
|
74
|
+
provider_labels: labels,
|
|
75
|
+
source_count: c.source_count ?? provs.length,
|
|
76
|
+
company_name: c.display_name,
|
|
77
|
+
commercial_name: c.commercial_name,
|
|
78
|
+
contact_person: c.contact_person,
|
|
79
|
+
email: c.email,
|
|
80
|
+
city: c.city,
|
|
81
|
+
country: c.country,
|
|
82
|
+
customer_class: null,
|
|
83
|
+
logo_url: c.logo_url,
|
|
84
|
+
is_active: c.is_active,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return [...local, ...ext];
|
|
88
|
+
}, [localData, externalData, providerLabel]);
|
|
89
|
+
const totals = useMemo(() => {
|
|
90
|
+
const t = {
|
|
91
|
+
all: allRows.length,
|
|
92
|
+
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
|
+
};
|
|
96
|
+
for (const p of providers) {
|
|
97
|
+
t[p.provider] = allRows.filter((c) => c.providers.includes(p.provider)).length;
|
|
98
|
+
}
|
|
99
|
+
return t;
|
|
100
|
+
}, [allRows, providers]);
|
|
88
101
|
const countries = useMemo(() => {
|
|
89
102
|
const set = new Set();
|
|
90
|
-
|
|
103
|
+
allRows.forEach((c) => { if (c.country)
|
|
91
104
|
set.add(c.country); });
|
|
92
105
|
return Array.from(set).sort();
|
|
93
|
-
}, [
|
|
94
|
-
const baseCustomers = data?.customers ?? [];
|
|
106
|
+
}, [allRows]);
|
|
95
107
|
const customers = useMemo(() => {
|
|
96
|
-
let rows =
|
|
108
|
+
let rows = allRows;
|
|
109
|
+
if (view === "active")
|
|
110
|
+
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));
|
|
117
|
+
if (customerClass !== "__all__")
|
|
118
|
+
rows = rows.filter((c) => c.customer_class === customerClass);
|
|
97
119
|
if (country !== "__all__")
|
|
98
120
|
rows = rows.filter((c) => c.country === country);
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
121
|
+
if (search) {
|
|
122
|
+
const s = search.toLowerCase();
|
|
123
|
+
rows = rows.filter((c) => [c.company_name, c.commercial_name, c.contact_person, c.email, c.city]
|
|
124
|
+
.filter(Boolean)
|
|
125
|
+
.some((v) => String(v).toLowerCase().includes(s)));
|
|
126
|
+
}
|
|
127
|
+
return [...rows].sort((a, b) => {
|
|
128
|
+
const av = String(a[sort.col] ?? "").toLowerCase();
|
|
129
|
+
const bv = String(b[sort.col] ?? "").toLowerCase();
|
|
102
130
|
const cmp = av.localeCompare(bv);
|
|
103
131
|
return sort.dir === "asc" ? cmp : -cmp;
|
|
104
132
|
});
|
|
105
|
-
|
|
106
|
-
}, [baseCustomers, country, sort]);
|
|
133
|
+
}, [allRows, view, customerClass, country, search, sort]);
|
|
107
134
|
const activeFilterChips = [];
|
|
108
135
|
if (search)
|
|
109
136
|
activeFilterChips.push({ key: "s", label: `Zoek: "${search}"`, clear: () => setSearch("") });
|
|
@@ -112,11 +139,16 @@ export function KlantenOverzicht() {
|
|
|
112
139
|
if (country !== "__all__")
|
|
113
140
|
activeFilterChips.push({ key: "co", label: `Land: ${country}`, clear: () => setCountry("__all__") });
|
|
114
141
|
const toggleSort = (col) => setSort((s) => (s.col === col ? { col, dir: s.dir === "asc" ? "desc" : "asc" } : { col, dir: "asc" }));
|
|
115
|
-
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 relaties en
|
|
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) => {
|
|
116
143
|
const secondary = c.commercial_name && c.commercial_name !== c.company_name
|
|
117
144
|
? c.commercial_name
|
|
118
145
|
: c.city ?? c.email ?? null;
|
|
119
|
-
return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () =>
|
|
146
|
+
return (_jsxs(TableRow, { className: "cursor-pointer hover:bg-muted/50", onClick: () => c.source === "local"
|
|
147
|
+
? navigate({ to: "/klanten/$id", params: { id: c.id } })
|
|
148
|
+
: navigate({
|
|
149
|
+
to: "/klanten/groep/$groupKey",
|
|
150
|
+
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));
|
|
120
152
|
}) })] }) }) })] }));
|
|
121
153
|
}
|
|
122
154
|
function StatCard({ icon, label, value, active, onClick, }) {
|
|
@@ -131,8 +163,8 @@ function StatusPill({ active }) {
|
|
|
131
163
|
? "bg-primary/10 text-primary"
|
|
132
164
|
: "bg-muted text-muted-foreground"}`, children: [_jsx("span", { className: `h-1.5 w-1.5 rounded-full ${active ? "bg-primary" : "bg-muted-foreground"}` }), active ? "Actief" : "Inactief"] }));
|
|
133
165
|
}
|
|
134
|
-
function EmptyState({
|
|
135
|
-
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center gap-3", children: [_jsx("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center", children: _jsx(Users, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Geen klanten gevonden" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Pas je filters aan
|
|
166
|
+
function EmptyState({ onAdd }) {
|
|
167
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center gap-3", children: [_jsx("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center", children: _jsx(Users, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Geen klanten gevonden" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Pas je filters aan of voeg handmatig een klant toe." })] }), _jsx("div", { className: "flex gap-2 mt-2", children: _jsxs(Button, { size: "sm", onClick: onAdd, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), " Nieuwe klant"] }) })] }));
|
|
136
168
|
}
|
|
137
169
|
function relativeTime(iso) {
|
|
138
170
|
const d = new Date(iso);
|