@flowselections/floriday-klanten-module 1.0.12 → 1.0.13
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/DiscountsTab.d.ts +4 -0
- package/dist-lib/components/klanten/DiscountsTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/DiscountsTab.js +92 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/GroupedKlantDetail.js +4 -1
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +2 -1
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +83 -23
- package/dist-lib/components/klanten/LocalDiscountFormDialog.d.ts +10 -0
- package/dist-lib/components/klanten/LocalDiscountFormDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/LocalDiscountFormDialog.js +107 -0
- package/dist-lib/components/klanten/LocalDiscountsTab.d.ts +4 -0
- package/dist-lib/components/klanten/LocalDiscountsTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/LocalDiscountsTab.js +101 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1497 -43
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1497 -43
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1497 -43
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1519 -39
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/accounting.functions.d.ts +4512 -150
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-analytics.functions.d.ts +1497 -43
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.d.ts +10178 -0
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/delivery-locations.functions.d.ts +5816 -0
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts +5816 -0
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-discounts.functions.d.ts +21974 -0
- package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-discounts.functions.js +83 -0
- package/dist-lib/lib/local-discounts.functions.d.ts +76898 -0
- package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -0
- package/dist-lib/lib/local-discounts.functions.js +185 -0
- package/dist-lib/lib/overdue-invoices.functions.d.ts +10984 -0
- package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -0
- package/dist-lib/lib/overdue-invoices.functions.js +72 -0
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { Card, CardContent, Badge, Input, Button, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
|
|
6
|
+
import { Percent, FileText, Users, Plus, Pencil, Trash2 } from "lucide-react";
|
|
7
|
+
import { listLocalCustomerDiscounts, deleteLocalDiscount, } from "../../lib/local-discounts.functions";
|
|
8
|
+
import { LocalDiscountFormDialog } from "./LocalDiscountFormDialog";
|
|
9
|
+
const SOURCE_LABEL = {
|
|
10
|
+
agreement: "Contract",
|
|
11
|
+
sales_strategy: "Kortingsregel",
|
|
12
|
+
price_group: "Staffel",
|
|
13
|
+
};
|
|
14
|
+
const SOURCE_ICON = {
|
|
15
|
+
agreement: _jsx(FileText, { className: "h-3 w-3" }),
|
|
16
|
+
sales_strategy: _jsx(Percent, { className: "h-3 w-3" }),
|
|
17
|
+
price_group: _jsx(Users, { className: "h-3 w-3" }),
|
|
18
|
+
};
|
|
19
|
+
function fmtMoney(v, currency = "EUR") {
|
|
20
|
+
if (v == null)
|
|
21
|
+
return "—";
|
|
22
|
+
try {
|
|
23
|
+
return new Intl.NumberFormat("nl-NL", { style: "currency", currency }).format(v);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return `${v.toFixed(2)} ${currency}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function fmtDiscount(row) {
|
|
30
|
+
if (row.discount_type == null || row.discount_value == null)
|
|
31
|
+
return "—";
|
|
32
|
+
return row.discount_type === "percentage"
|
|
33
|
+
? `${row.discount_value}%`
|
|
34
|
+
: fmtMoney(row.discount_value, row.currency);
|
|
35
|
+
}
|
|
36
|
+
function fmtDate(v) {
|
|
37
|
+
if (!v)
|
|
38
|
+
return "—";
|
|
39
|
+
try {
|
|
40
|
+
return new Date(v).toLocaleDateString("nl-NL");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isActiveOn(row, date) {
|
|
47
|
+
if (row.valid_from && row.valid_from > date)
|
|
48
|
+
return false;
|
|
49
|
+
if (row.valid_until && row.valid_until < date)
|
|
50
|
+
return false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
export function LocalDiscountsTab({ customerId }) {
|
|
54
|
+
const qc = useQueryClient();
|
|
55
|
+
const fn = useServerFn(listLocalCustomerDiscounts);
|
|
56
|
+
const del = useServerFn(deleteLocalDiscount);
|
|
57
|
+
const { data, isLoading, error } = useQuery({
|
|
58
|
+
queryKey: ["local-discounts", customerId],
|
|
59
|
+
queryFn: () => fn({ data: { customerId } }),
|
|
60
|
+
});
|
|
61
|
+
const [source, setSource] = useState("all");
|
|
62
|
+
const [search, setSearch] = useState("");
|
|
63
|
+
const [onlyActive, setOnlyActive] = useState(true);
|
|
64
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
65
|
+
const [editing, setEditing] = useState(null);
|
|
66
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
67
|
+
const delMut = useMutation({
|
|
68
|
+
mutationFn: (row) => del({ data: { source: row.source, id: row.source_id } }),
|
|
69
|
+
onSuccess: () => {
|
|
70
|
+
toast.success("Korting verwijderd");
|
|
71
|
+
qc.invalidateQueries({ queryKey: ["local-discounts", customerId] });
|
|
72
|
+
},
|
|
73
|
+
onError: (err) => toast.error(err.message),
|
|
74
|
+
});
|
|
75
|
+
const filtered = useMemo(() => {
|
|
76
|
+
const rows = data?.rows ?? [];
|
|
77
|
+
return rows.filter((r) => {
|
|
78
|
+
if (source !== "all" && r.source !== source)
|
|
79
|
+
return false;
|
|
80
|
+
if (onlyActive && !isActiveOn(r, today))
|
|
81
|
+
return false;
|
|
82
|
+
if (search) {
|
|
83
|
+
const q = search.toLowerCase();
|
|
84
|
+
const hay = [r.product_name, r.product_id].filter(Boolean).join(" ").toLowerCase();
|
|
85
|
+
if (!hay.includes(q))
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}, [data, source, search, onlyActive, today]);
|
|
91
|
+
if (isLoading)
|
|
92
|
+
return _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: "Kortingen laden\u2026" }) });
|
|
93
|
+
if (error) {
|
|
94
|
+
return (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 text-sm text-destructive", children: ["Kortingen konden niet worden geladen: ", error.message] }) }));
|
|
95
|
+
}
|
|
96
|
+
const s = data?.summary;
|
|
97
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-3", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Kortingsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Staffels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) })] }), _jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { placeholder: "Zoek op product\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "max-w-xs" }), _jsxs(Select, { value: source, onValueChange: (v) => setSource(v), children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Bron" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle bronnen" }), _jsx(SelectItem, { value: "agreement", children: "Contract" }), _jsx(SelectItem, { value: "sales_strategy", children: "Kortingsregel" }), _jsx(SelectItem, { value: "price_group", children: "Staffel" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] }), _jsxs(Button, { className: "ml-auto", size: "sm", onClick: () => { setEditing(null); setDialogOpen(true); }, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), " Nieuwe korting"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { className: "text-right", children: "Basisprijs" }), _jsx(TableHead, { className: "text-right", children: "Korting" }), _jsx(TableHead, { className: "text-right", children: "Netto" }), _jsx(TableHead, { children: "Geldig" }), _jsx(TableHead, { className: "text-right", children: "Min. hvh" }), _jsx(TableHead, { className: "w-20" })] }) }), _jsx(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 8, className: "text-center text-muted-foreground py-6", children: "Nog geen kortingen voor deze klant." }) })) : filtered.map((r) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: r.product_name ?? "—" }), _jsx(TableCell, { children: _jsxs(Badge, { variant: "secondary", className: "gap-1", children: [SOURCE_ICON[r.source], SOURCE_LABEL[r.source]] }) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtMoney(r.base_price, r.currency) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtDiscount(r) }), _jsx(TableCell, { className: "text-right tabular-nums font-medium", children: fmtMoney(r.net_price, r.currency) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [fmtDate(r.valid_from), " \u2013 ", fmtDate(r.valid_until)] }), _jsx(TableCell, { className: "text-right tabular-nums", children: r.min_quantity || "—" }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex gap-1 justify-end", children: [_jsx(Button, { variant: "ghost", size: "icon", onClick: () => { setEditing(r); setDialogOpen(true); }, children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => delMut.mutate(r), children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }) })] }, `${r.source}-${r.source_id}`))) })] }) })] }) }), _jsx(LocalDiscountFormDialog, { open: dialogOpen, onOpenChange: setDialogOpen, customerId: customerId, editing: editing })] }));
|
|
98
|
+
}
|
|
99
|
+
function SummaryCard({ label, value, icon, }) {
|
|
100
|
+
return (_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs uppercase text-muted-foreground", children: [icon, label] }), _jsx("div", { className: "text-2xl font-semibold mt-1", children: value })] }) }));
|
|
101
|
+
}
|