@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.
- package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
- package/dist-lib/components/CustomerAvatar.js +1 -1
- package/dist-lib/components/klanten/BackLink.d.ts +12 -0
- package/dist-lib/components/klanten/BackLink.d.ts.map +1 -0
- package/dist-lib/components/klanten/BackLink.js +12 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts +4 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.d.ts.map +1 -0
- package/dist-lib/components/klanten/FloridayAssortmentTab.js +80 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts +8 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.d.ts.map +1 -0
- package/dist-lib/components/klanten/FloridayDeeplinkButton.js +9 -0
- package/dist-lib/components/klanten/GroupedKlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/GroupedKlantDetail.js +19 -5
- package/dist-lib/components/klanten/ImportKlanten.d.ts.map +1 -1
- package/dist-lib/components/klanten/ImportKlanten.js +46 -13
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +9 -3
- package/dist-lib/components/klanten/KlantenOverzicht.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +6 -3
- package/dist-lib/components/klanten/NetwerkPagina.d.ts.map +1 -1
- package/dist-lib/components/klanten/NetwerkPagina.js +42 -10
- package/dist-lib/components/klanten/NieuweKlant.d.ts.map +1 -1
- package/dist-lib/components/klanten/NieuweKlant.js +23 -8
- package/dist-lib/components/klanten/PriceGroupBadges.d.ts +7 -0
- package/dist-lib/components/klanten/PriceGroupBadges.d.ts.map +1 -0
- package/dist-lib/components/klanten/PriceGroupBadges.js +13 -0
- package/dist-lib/components/klanten/SyncStatusChip.d.ts +4 -0
- package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -0
- package/dist-lib/components/klanten/SyncStatusChip.js +49 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts +8 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +75 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts +4 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +269 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts +2 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +133 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts +8 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts.map +1 -0
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.js +18 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +6 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +762 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +762 -3
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +4 -3
- package/dist-lib/integrations/supabase/client.server.d.ts +762 -3
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +789 -8
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts +21 -0
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/client.server.js +171 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts +46 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/mapping.js +109 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +36318 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/worker.functions.js +48 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.d.ts +12 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/worker.server.js +124 -0
- package/dist-lib/lib/accounting.functions.d.ts +2286 -9
- package/dist-lib/lib/accounting.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-analytics.functions.d.ts +762 -3
- package/dist-lib/lib/customer-analytics.functions.d.ts.map +1 -1
- package/dist-lib/lib/customer-schemas.d.ts +48 -46
- package/dist-lib/lib/customer-schemas.d.ts.map +1 -1
- package/dist-lib/lib/customer-schemas.js +46 -3
- package/dist-lib/lib/customers.functions.d.ts +18288 -129
- package/dist-lib/lib/customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/customers.functions.js +146 -10
- package/dist-lib/lib/delivery-locations.functions.d.ts +3048 -12
- package/dist-lib/lib/delivery-locations.functions.d.ts.map +1 -1
- package/dist-lib/lib/external-customers.functions.d.ts +3048 -12
- package/dist-lib/lib/external-customers.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-assortment.functions.d.ts +12127 -0
- package/dist-lib/lib/floriday-assortment.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-assortment.functions.js +213 -0
- package/dist-lib/lib/floriday-discounts.functions.d.ts +1800 -282
- package/dist-lib/lib/floriday-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-trade-items.functions.d.ts +12111 -0
- package/dist-lib/lib/floriday-trade-items.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-trade-items.functions.js +107 -0
- package/dist-lib/lib/local-discounts.functions.d.ts +5336 -23
- package/dist-lib/lib/local-discounts.functions.d.ts.map +1 -1
- package/dist-lib/lib/overdue-invoices.functions.d.ts +762 -3
- package/dist-lib/lib/overdue-invoices.functions.d.ts.map +1 -1
- package/dist-lib/lib/price-groups-local.functions.d.ts +84841 -0
- package/dist-lib/lib/price-groups-local.functions.d.ts.map +1 -0
- package/dist-lib/lib/price-groups-local.functions.js +148 -0
- package/dist-lib/lib/price-groups.functions.d.ts +12120 -0
- package/dist-lib/lib/price-groups.functions.d.ts.map +1 -0
- package/dist-lib/lib/price-groups.functions.js +115 -0
- package/dist-lib/lib/pricing.d.ts +24 -0
- package/dist-lib/lib/pricing.d.ts.map +1 -0
- package/dist-lib/lib/pricing.js +60 -0
- package/package.json +6 -3
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { useNavigate } from "@tanstack/react-router";
|
|
6
|
+
import { Button, Input, Label, Separator, Card, CardContent, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Checkbox, } from "@flowselections/core";
|
|
7
|
+
import { ArrowRight, Check, ChevronDown, PencilLine, Calculator, Users, Globe2, Circle, CheckCircle2, Info, ChevronLeft, ChevronRight, ImageIcon } from "lucide-react";
|
|
8
|
+
import { BackLink } from "../BackLink";
|
|
9
|
+
import { createCustomerPriceGroup, updateCustomerPriceGroup, getCustomerPriceGroup, setPriceGroupMembers, } from "../../../lib/price-groups-local.functions";
|
|
10
|
+
import { computePrice } from "../../../lib/pricing";
|
|
11
|
+
import { listTradeItemsForPreview } from "../../../lib/floriday-trade-items.functions";
|
|
12
|
+
import { ProductPickerDialog } from "./ProductPickerDialog";
|
|
13
|
+
import { CustomerPickerDialog } from "./CustomerPickerDialog";
|
|
14
|
+
import { UserPlus, Users2 } from "lucide-react";
|
|
15
|
+
import { listExternalCustomers } from "../../../lib/external-customers.functions";
|
|
16
|
+
import { Badge } from "@flowselections/core";
|
|
17
|
+
import { toast } from "sonner";
|
|
18
|
+
const emptyInput = {
|
|
19
|
+
name: "",
|
|
20
|
+
currency: "EUR",
|
|
21
|
+
pricing_mode: "manual",
|
|
22
|
+
group_type: "customer_specific",
|
|
23
|
+
price_scope: "incl_delivery",
|
|
24
|
+
include_stickers: false,
|
|
25
|
+
unit: "per_fust",
|
|
26
|
+
calc_type: "discount",
|
|
27
|
+
calc_value_type: "fixed",
|
|
28
|
+
calc_value: 0,
|
|
29
|
+
handling_fee: 0,
|
|
30
|
+
distribute_over: "ladingdrager",
|
|
31
|
+
rounding: "none",
|
|
32
|
+
rounding_step: 0.05,
|
|
33
|
+
is_active: true,
|
|
34
|
+
};
|
|
35
|
+
const STEP_TITLES = [
|
|
36
|
+
"Prijsgroep",
|
|
37
|
+
"Levering, diensten en afname hoeveelheid",
|
|
38
|
+
"Prijsberekening",
|
|
39
|
+
"Doelgroep",
|
|
40
|
+
];
|
|
41
|
+
function OptionCard({ selected, onClick, icon, title, description, }) {
|
|
42
|
+
return (_jsxs("button", { type: "button", onClick: onClick, className: "text-left rounded-lg border p-4 transition flex gap-3 items-start " +
|
|
43
|
+
(selected
|
|
44
|
+
? "border-primary ring-2 ring-primary/30 bg-primary/5"
|
|
45
|
+
: "hover:border-primary/50 hover:bg-muted/40"), children: [_jsx("div", { className: "mt-0.5 " + (selected ? "text-primary" : "text-muted-foreground"), children: icon }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium text-sm", children: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description })] })] }));
|
|
46
|
+
}
|
|
47
|
+
function RadioBanner({ selected, onClick, title, }) {
|
|
48
|
+
return (_jsxs("button", { type: "button", onClick: onClick, className: "w-full text-left rounded-lg border px-4 py-3 flex items-center gap-3 transition " +
|
|
49
|
+
(selected
|
|
50
|
+
? "bg-primary text-primary-foreground border-primary"
|
|
51
|
+
: "bg-background hover:border-primary/50 hover:bg-muted/40"), children: [selected
|
|
52
|
+
? _jsx(CheckCircle2, { className: "h-5 w-5 shrink-0" })
|
|
53
|
+
: _jsx(Circle, { className: "h-5 w-5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "font-medium text-sm", children: title })] }));
|
|
54
|
+
}
|
|
55
|
+
function BulletList({ items }) {
|
|
56
|
+
return (_jsx("ul", { className: "space-y-1.5 pl-1", children: items.map((t, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 mt-0.5 text-primary shrink-0" }), _jsx("span", { children: t })] }, i))) }));
|
|
57
|
+
}
|
|
58
|
+
function StepSection({ index, title, subtitle, open, completed, disabled, onToggle, children, }) {
|
|
59
|
+
return (_jsxs(Card, { className: open ? "ring-1 ring-primary/40" : "", children: [_jsxs("button", { type: "button", onClick: disabled ? undefined : onToggle, disabled: disabled, className: "w-full flex items-center gap-3 px-6 py-4 text-left " +
|
|
60
|
+
(disabled ? "cursor-not-allowed opacity-60" : "hover:bg-muted/40"), children: [_jsx("div", { className: "h-8 w-8 rounded-full flex items-center justify-center text-sm font-medium shrink-0 " +
|
|
61
|
+
(completed
|
|
62
|
+
? "bg-primary/15 text-primary border border-primary/30"
|
|
63
|
+
: open
|
|
64
|
+
? "bg-primary text-primary-foreground"
|
|
65
|
+
: "bg-muted text-muted-foreground border"), children: completed && !open ? _jsx(Check, { className: "h-4 w-4" }) : index + 1 }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-base " + (open ? "font-semibold" : "font-medium"), children: title }), !open && subtitle && (_jsx("div", { className: "text-xs text-muted-foreground truncate mt-0.5", children: subtitle }))] }), !disabled && (_jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground transition-transform " + (open ? "rotate-180" : "") }))] }), open && (_jsx(CardContent, { className: "pt-0 pb-6 px-6 space-y-4 border-t", children: _jsx("div", { className: "pt-6", children: children }) }))] }));
|
|
66
|
+
}
|
|
67
|
+
export function PriceGroupFormPage({ editingId }) {
|
|
68
|
+
const qc = useQueryClient();
|
|
69
|
+
const navigate = useNavigate();
|
|
70
|
+
const createFn = useServerFn(createCustomerPriceGroup);
|
|
71
|
+
const updateFn = useServerFn(updateCustomerPriceGroup);
|
|
72
|
+
const getFn = useServerFn(getCustomerPriceGroup);
|
|
73
|
+
const setMembersFn = useServerFn(setPriceGroupMembers);
|
|
74
|
+
const listCustomersFn = useServerFn(listExternalCustomers);
|
|
75
|
+
const [openStep, setOpenStep] = useState(0);
|
|
76
|
+
const [maxReached, setMaxReached] = useState(0);
|
|
77
|
+
const [form, setForm] = useState(emptyInput);
|
|
78
|
+
const [error, setError] = useState(null);
|
|
79
|
+
const [basePrice, setBasePrice] = useState(10);
|
|
80
|
+
const [selectedItemId, setSelectedItemId] = useState(null);
|
|
81
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
82
|
+
const [customerPickerOpen, setCustomerPickerOpen] = useState(false);
|
|
83
|
+
const [selectedCustomerKeys, setSelectedCustomerKeys] = useState([]);
|
|
84
|
+
const listTradeItemsFn = useServerFn(listTradeItemsForPreview);
|
|
85
|
+
const { data: tradeItemsData } = useQuery({
|
|
86
|
+
queryKey: ["trade-items-preview"],
|
|
87
|
+
queryFn: () => listTradeItemsFn(),
|
|
88
|
+
enabled: form.pricing_mode === "calculated",
|
|
89
|
+
staleTime: 5 * 60000,
|
|
90
|
+
});
|
|
91
|
+
const items = tradeItemsData?.items ?? [];
|
|
92
|
+
const selectedItem = useMemo(() => {
|
|
93
|
+
if (items.length === 0)
|
|
94
|
+
return null;
|
|
95
|
+
return items.find((i) => i.id === selectedItemId) ?? items[0];
|
|
96
|
+
}, [items, selectedItemId]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!selectedItemId && items[0])
|
|
99
|
+
setSelectedItemId(items[0].id);
|
|
100
|
+
}, [items, selectedItemId]);
|
|
101
|
+
const cycleItem = (dir) => {
|
|
102
|
+
if (items.length === 0 || !selectedItem)
|
|
103
|
+
return;
|
|
104
|
+
const idx = items.findIndex((i) => i.id === selectedItem.id);
|
|
105
|
+
const next = (idx + dir + items.length) % items.length;
|
|
106
|
+
setSelectedItemId(items[next].id);
|
|
107
|
+
};
|
|
108
|
+
const { data: existing, isLoading: loadingExisting } = useQuery({
|
|
109
|
+
queryKey: ["customer-price-group", editingId],
|
|
110
|
+
queryFn: () => getFn({ data: { id: editingId } }),
|
|
111
|
+
enabled: !!editingId,
|
|
112
|
+
});
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (editingId && existing) {
|
|
115
|
+
const g = existing.group;
|
|
116
|
+
setForm({
|
|
117
|
+
name: g.name, currency: g.currency, pricing_mode: g.pricing_mode,
|
|
118
|
+
group_type: g.group_type ?? "customer_specific",
|
|
119
|
+
price_scope: g.price_scope, include_stickers: g.include_stickers,
|
|
120
|
+
unit: g.unit, calc_type: g.calc_type, calc_value_type: g.calc_value_type,
|
|
121
|
+
calc_value: g.calc_value, handling_fee: g.handling_fee,
|
|
122
|
+
distribute_over: g.distribute_over ?? "ladingdrager",
|
|
123
|
+
rounding: g.rounding ?? "none",
|
|
124
|
+
rounding_step: g.rounding_step != null ? Number(g.rounding_step) : 0.05,
|
|
125
|
+
is_active: g.is_active,
|
|
126
|
+
});
|
|
127
|
+
setSelectedCustomerKeys(existing.customerKeys ?? []);
|
|
128
|
+
setMaxReached(STEP_TITLES.length - 1);
|
|
129
|
+
}
|
|
130
|
+
}, [editingId, existing]);
|
|
131
|
+
const { data: allCustomersData } = useQuery({
|
|
132
|
+
queryKey: ["external-customers-picker"],
|
|
133
|
+
queryFn: () => listCustomersFn({ data: { search: "", status: "all", limit: 2000 } }),
|
|
134
|
+
});
|
|
135
|
+
const customersByKey = useMemo(() => {
|
|
136
|
+
const map = new Map();
|
|
137
|
+
for (const c of allCustomersData?.customers ?? [])
|
|
138
|
+
map.set(c.group_key, c);
|
|
139
|
+
return map;
|
|
140
|
+
}, [allCustomersData]);
|
|
141
|
+
const patch = (p) => setForm((f) => ({ ...f, ...p }));
|
|
142
|
+
const preview = useMemo(() => {
|
|
143
|
+
const calc = {
|
|
144
|
+
pricing_mode: form.pricing_mode,
|
|
145
|
+
calc_type: form.calc_type ?? null,
|
|
146
|
+
calc_value_type: form.calc_value_type ?? null,
|
|
147
|
+
calc_value: form.calc_value ?? null,
|
|
148
|
+
handling_fee: form.handling_fee ?? null,
|
|
149
|
+
distribute_over: form.distribute_over ?? null,
|
|
150
|
+
rounding: form.rounding,
|
|
151
|
+
rounding_step: form.rounding_step ?? 0.05,
|
|
152
|
+
};
|
|
153
|
+
return computePrice(basePrice, calc, {
|
|
154
|
+
unitsPerFust: selectedItem?.units_per_fust ?? 1,
|
|
155
|
+
packagesPerLayer: selectedItem?.packages_per_layer ?? 1,
|
|
156
|
+
layersPerCarrier: selectedItem?.layers_per_carrier ?? 1,
|
|
157
|
+
});
|
|
158
|
+
}, [form, basePrice, selectedItem]);
|
|
159
|
+
const handlingSuffix = useMemo(() => {
|
|
160
|
+
if (!selectedItem)
|
|
161
|
+
return "";
|
|
162
|
+
const upf = selectedItem.units_per_fust;
|
|
163
|
+
const ppl = selectedItem.packages_per_layer;
|
|
164
|
+
const lpc = selectedItem.layers_per_carrier;
|
|
165
|
+
switch (form.distribute_over) {
|
|
166
|
+
case "fust": return `/${upf}`;
|
|
167
|
+
case "laag": return `/(${upf}×${ppl})`;
|
|
168
|
+
case "ladingdrager": return `/(${upf}×${ppl}×${lpc})`;
|
|
169
|
+
default: return "";
|
|
170
|
+
}
|
|
171
|
+
}, [selectedItem, form.distribute_over]);
|
|
172
|
+
const goBack = () => navigate({ to: "/klanten/prijsgroepen" });
|
|
173
|
+
const saveMut = useMutation({
|
|
174
|
+
mutationFn: async () => {
|
|
175
|
+
if (form.group_type === "customer_specific" && selectedCustomerKeys.length === 0) {
|
|
176
|
+
throw new Error("Selecteer minimaal één organisatie in stap 4 voordat je een klantspecifieke prijsgroep opslaat.");
|
|
177
|
+
}
|
|
178
|
+
const row = editingId
|
|
179
|
+
? await updateFn({ data: { id: editingId, input: form } })
|
|
180
|
+
: await createFn({ data: form });
|
|
181
|
+
const id = row?.id ?? editingId;
|
|
182
|
+
if (id) {
|
|
183
|
+
const keys = form.group_type === "customer_specific" ? selectedCustomerKeys : [];
|
|
184
|
+
await setMembersFn({ data: { id, customerKeys: keys } });
|
|
185
|
+
}
|
|
186
|
+
return row;
|
|
187
|
+
},
|
|
188
|
+
onSuccess: () => {
|
|
189
|
+
qc.invalidateQueries({ queryKey: ["customer-price-groups"] });
|
|
190
|
+
qc.invalidateQueries({ queryKey: ["customer-price-group", editingId] });
|
|
191
|
+
goBack();
|
|
192
|
+
},
|
|
193
|
+
onError: (e) => {
|
|
194
|
+
const msg = e?.message ?? "Opslaan mislukt";
|
|
195
|
+
setError(msg);
|
|
196
|
+
toast.error(msg);
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
const canProceed = (s) => (s === 0 ? form.name.trim().length > 0 : true);
|
|
200
|
+
const goToStep = (i) => {
|
|
201
|
+
if (i > maxReached)
|
|
202
|
+
return;
|
|
203
|
+
setOpenStep((cur) => (cur === i ? -1 : i));
|
|
204
|
+
};
|
|
205
|
+
const nextStep = (i) => {
|
|
206
|
+
if (!canProceed(i))
|
|
207
|
+
return;
|
|
208
|
+
// Van stap 2 naar stap 4 wanneer prijzen handmatig zijn (skip prijsberekening).
|
|
209
|
+
let target = i + 1;
|
|
210
|
+
if (i === 1 && form.pricing_mode === "manual")
|
|
211
|
+
target = 3;
|
|
212
|
+
// Skip stap 4 (Doelgroep) voor algemene prijsgroepen.
|
|
213
|
+
if (target === 3 && form.group_type !== "customer_specific") {
|
|
214
|
+
setMaxReached((m) => Math.max(m, 3));
|
|
215
|
+
setOpenStep(-1);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const next = Math.min(target, STEP_TITLES.length - 1);
|
|
219
|
+
setMaxReached((m) => Math.max(m, next));
|
|
220
|
+
setOpenStep(next);
|
|
221
|
+
};
|
|
222
|
+
const summaries = [
|
|
223
|
+
form.name
|
|
224
|
+
? `${form.name} · ${form.pricing_mode === "manual" ? "Handmatig" : "Berekend"} · ${form.group_type === "general" ? "Algemeen" : "Klantspecifiek"} · ${form.currency}`
|
|
225
|
+
: undefined,
|
|
226
|
+
`${form.price_scope === "incl_delivery" ? "Incl. levering" : "Excl. levering"} · ${form.unit === "per_fust" ? "Per fust" : form.unit === "per_laag" ? "Per laag" : "Per ladingdrager"}${form.include_stickers ? " · incl. stickers" : ""}`,
|
|
227
|
+
form.pricing_mode !== "calculated"
|
|
228
|
+
? "Handmatige prijzen — niet van toepassing"
|
|
229
|
+
: (() => {
|
|
230
|
+
const typ = form.calc_type === "surcharge" ? "Opslag" : "Korting";
|
|
231
|
+
const val = form.calc_value_type === "percentage"
|
|
232
|
+
? `${form.calc_value ?? 0}%`
|
|
233
|
+
: `€ ${(form.calc_value ?? 0).toFixed(3)}`;
|
|
234
|
+
const rnd = form.rounding === "none"
|
|
235
|
+
? "niet afronden"
|
|
236
|
+
: `${form.rounding === "up" ? "omhoog" : form.rounding === "down" ? "omlaag" : "dichtstbijzijnde"} op € ${(form.rounding_step ?? 0.05).toFixed(2)}`;
|
|
237
|
+
return `${typ} ${val} · afhandelingskosten € ${(form.handling_fee ?? 0).toFixed(2)} · ${rnd}`;
|
|
238
|
+
})(),
|
|
239
|
+
form.group_type !== "customer_specific"
|
|
240
|
+
? "Algemene prijsgroep — geen doelgroep"
|
|
241
|
+
: selectedCustomerKeys.length === 0
|
|
242
|
+
? "Nog geen klanten"
|
|
243
|
+
: `${selectedCustomerKeys.length} klant${selectedCustomerKeys.length === 1 ? "" : "en"}`,
|
|
244
|
+
];
|
|
245
|
+
if (editingId && loadingExisting) {
|
|
246
|
+
return _jsx("div", { className: "p-6 text-sm text-muted-foreground", children: "Laden\u2026" });
|
|
247
|
+
}
|
|
248
|
+
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 space-y-4", children: [_jsx(BackLink, { to: "/klanten/prijsgroepen", label: "Terug naar prijsgroepen" }), _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: editingId ? "Prijsgroep bewerken" : "Nieuwe prijsgroep" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Stel prijsafspraken en berekening in. Klanten koppel je op het overzicht." })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-[1fr_340px] items-start", children: [_jsxs("div", { className: "space-y-4 min-w-0", children: [_jsx(StepSection, { index: 0, title: STEP_TITLES[0], subtitle: summaries[0], open: openStep === 0, completed: maxReached > 0, disabled: false, onToggle: () => goToStep(0), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx(Label, { children: "Naam van de prijsgroep" }), _jsx("div", { className: "text-xs text-muted-foreground mb-1.5", children: "Alleen zichtbaar voor jou." }), _jsx(Input, { value: form.name, onChange: (e) => patch({ name: e.target.value }), placeholder: "Bijvoorbeeld: trouwe klanten" })] }), _jsxs("div", { className: "max-w-sm", children: [_jsx(Label, { children: "Valuta" }), _jsxs(Select, { value: form.currency, onValueChange: (v) => patch({ currency: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "EUR", children: "Euro" }), _jsx(SelectItem, { value: "USD", children: "Amerikaanse dollar" })] })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Prijsbepaling" }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-3 mt-2", children: [_jsx(OptionCard, { selected: form.pricing_mode === "manual", onClick: () => patch({ pricing_mode: "manual" }), icon: _jsx(PencilLine, { className: "h-5 w-5" }), title: "Prijzen handmatig invoeren", description: "Geef per prijsgroep handmatig een prijs in bij het aanbieden van artikelen." }), _jsx(OptionCard, { selected: form.pricing_mode === "calculated", onClick: () => patch({ pricing_mode: "calculated" }), icon: _jsx(Calculator, { className: "h-5 w-5" }), title: "Prijzen berekenen", description: "Bereken prijzen automatisch op basis van een basisprijs met een door jou samen te stellen berekening." })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Type prijsgroep" }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-3 mt-2", children: [_jsx(OptionCard, { selected: form.group_type === "general", onClick: () => patch({ group_type: "general" }), icon: _jsx(Globe2, { className: "h-5 w-5" }), title: "Algemene prijsgroep", description: "Zichtbaar voor alle klanten (alleen voor partijprijzen)." }), _jsx(OptionCard, { selected: form.group_type === "customer_specific", onClick: () => patch({ group_type: "customer_specific" }), icon: _jsx(Users, { className: "h-5 w-5" }), title: "Klantspecifieke prijsgroep", description: "Zichtbaar voor klanten die zijn ingedeeld in deze prijsgroep." })] })] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(0), disabled: !canProceed(0), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] }) }), _jsx(StepSection, { index: 1, title: STEP_TITLES[1], subtitle: summaries[1], open: openStep === 1, completed: maxReached > 1, disabled: maxReached < 1, onToggle: () => goToStep(1), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-3", children: [_jsx(RadioBanner, { selected: form.price_scope === "incl_delivery", onClick: () => patch({ price_scope: "incl_delivery" }), title: "Prijzen inclusief levering en transport" }), form.price_scope === "incl_delivery" && (_jsx(BulletList, { items: [
|
|
249
|
+
"Prijzen zichtbaar voor klanten die direct inkopen op Floriday",
|
|
250
|
+
"Prijzen zijn inclusief levering en transport, ongeacht het bestelvolume",
|
|
251
|
+
] })), _jsx(RadioBanner, { selected: form.price_scope === "excl_delivery", onClick: () => patch({ price_scope: "excl_delivery" }), title: "Prijzen exclusief levering en transport" }), form.price_scope === "excl_delivery" && (_jsx(BulletList, { items: [
|
|
252
|
+
"Prijzen zichtbaar voor klanten die direct inkopen op Floriday",
|
|
253
|
+
"De ingestelde transport- en afleverkosten in de levervoorwaarden worden in rekening gebracht",
|
|
254
|
+
] }))] }), _jsxs("div", { className: "pt-2", children: [_jsx("div", { className: "text-sm text-muted-foreground mb-2", children: "Diensten" }), _jsx(Separator, { className: "mb-3" }), _jsxs("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [_jsx(Checkbox, { checked: form.include_stickers, onCheckedChange: (v) => patch({ include_stickers: v === true }) }), _jsx("span", { className: "text-sm font-medium", children: "Inclusief stickeren" })] })] }), _jsxs("div", { className: "pt-2", children: [_jsx("div", { className: "text-sm text-muted-foreground mb-2", children: "Afname" }), _jsx(Separator, { className: "mb-3" }), _jsxs("div", { className: "max-w-sm", children: [_jsx(Label, { children: "Afname-eenheid" }), _jsxs(Select, { value: form.unit, onValueChange: (v) => patch({ unit: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "- Selecteer een eenheid -" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "per_fust", children: "Per fust" }), _jsx(SelectItem, { value: "per_laag", children: "Per laag" }), _jsx(SelectItem, { value: "per_ladingdrager", children: "Per ladingdrager" })] })] })] }), _jsxs("div", { className: "text-xs text-muted-foreground mt-2 leading-relaxed", children: ["Afname-eenheid per aangeboden artikel.", _jsx("br", {}), "Deze instelling geldt voor alle klanten in deze prijsgroep, per aangeboden artikel, niet per bestelling."] })] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(1), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] }) }), _jsx(StepSection, { index: 2, title: STEP_TITLES[2], subtitle: summaries[2], open: openStep === 2, completed: form.pricing_mode === "calculated" && maxReached >= 2 && openStep !== 2, disabled: maxReached < 2 || form.pricing_mode !== "calculated", onToggle: () => goToStep(2), children: _jsx("div", { className: "space-y-4", children: form.pricing_mode !== "calculated" ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 border rounded-md bg-muted/30", children: "Deze prijsgroep gebruikt handmatige prijzen. Prijsberekening is niet van toepassing." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx(Label, { children: "Korting/opslag per stuk" }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3 mt-1.5", children: [_jsxs(Select, { value: form.calc_type ?? "discount", onValueChange: (v) => patch({ calc_type: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "discount", children: "Korting" }), _jsx(SelectItem, { value: "surcharge", children: "Opslag" })] })] }), _jsxs(Select, { value: form.calc_value_type ?? "fixed", onValueChange: (v) => patch({ calc_value_type: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "percentage", children: "Percentage" }), _jsx(SelectItem, { value: "fixed", children: "Vast bedrag" })] })] }), _jsxs("div", { className: "relative", children: [form.calc_value_type !== "percentage" && (_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" })), _jsx(Input, { type: "number", step: form.calc_value_type === "percentage" ? "1" : "0.001", value: form.calc_value ?? 0, onChange: (e) => patch({ calc_value: Number(e.target.value) }), className: form.calc_value_type === "percentage" ? "pr-8" : "pl-7" }), form.calc_value_type === "percentage" && (_jsx("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "%" }))] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsxs(Label, { className: "flex items-center gap-1.5", children: ["Afhandelingskosten (optioneel)", _jsx(Info, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-label": "Extra kosten die bovenop de prijs komen. Kies hoe deze verdeeld worden." })] }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: form.handling_fee ?? 0, onChange: (e) => patch({ handling_fee: Number(e.target.value) }), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Verdeeld over het aantal planten of bloemen op een" }), _jsxs(Select, { value: form.distribute_over ?? "ladingdrager", onValueChange: (v) => patch({ distribute_over: v }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "ladingdrager", children: "Ladingdrager" }), _jsx(SelectItem, { value: "laag", children: "Laag" }), _jsx(SelectItem, { value: "fust", children: "Fust" })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { children: "Prijs afronden" }), _jsxs(Select, { value: form.rounding, onValueChange: (v) => patch({ rounding: v }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: "Niet afronden" }), _jsx(SelectItem, { value: "up", children: "Omhoog" }), _jsx(SelectItem, { value: "down", children: "Omlaag" }), _jsx(SelectItem, { value: "nearest", children: "Dichtstbijzijnde" })] })] })] }), form.rounding !== "none" && (_jsxs("div", { children: [_jsx(Label, { children: "Op" }), _jsxs(Select, { value: String(form.rounding_step ?? 0.05), onValueChange: (v) => patch({ rounding_step: Number(v) }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "0.01", children: "\u20AC 0,01" }), _jsx(SelectItem, { value: "0.05", children: "\u20AC 0,05" }), _jsx(SelectItem, { value: "0.1", children: "\u20AC 0,10" })] })] })] }))] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(2), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] })) }) }), _jsxs(StepSection, { index: 3, title: STEP_TITLES[3], subtitle: summaries[3], open: openStep === 3, completed: form.group_type === "customer_specific" && maxReached >= 3 && openStep !== 3 && selectedCustomerKeys.length > 0, disabled: maxReached < 3 || form.group_type !== "customer_specific", onToggle: () => goToStep(3), children: [form.group_type !== "customer_specific" ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 border rounded-md bg-muted/30", children: "Algemene prijsgroepen zijn zichtbaar voor alle klanten \u2014 een doelgroep is niet van toepassing." })) : selectedCustomerKeys.length === 0 ? (_jsxs("div", { className: "rounded-lg border bg-muted/20 p-8", children: [_jsxs("div", { className: "flex flex-col items-center text-center gap-3", children: [_jsxs("div", { className: "relative h-16 w-16 rounded-full bg-muted flex items-center justify-center", children: [_jsx(Users2, { className: "h-8 w-8 text-muted-foreground/60" }), _jsx("span", { className: "absolute -top-1 -right-1 h-6 w-6 rounded-full bg-background border flex items-center justify-center", children: _jsx(UserPlus, { className: "h-3.5 w-3.5 text-muted-foreground" }) })] }), _jsx("div", { children: _jsx("div", { className: "font-medium text-sm", children: "Nog geen klanten toegevoegd" }) })] }), _jsx("div", { className: "flex justify-end mt-4", children: _jsxs(Button, { variant: "outline", onClick: () => setCustomerPickerOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-1.5" }), " Organisaties wijzigen"] }) })] })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: [_jsx("strong", { className: "text-foreground", children: selectedCustomerKeys.length }), " ", "klant", selectedCustomerKeys.length === 1 ? "" : "en", " gekoppeld"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setCustomerPickerOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-1.5" }), " Organisaties wijzigen"] })] }), _jsx("div", { className: "max-h-[320px] overflow-y-auto border rounded-md divide-y", children: selectedCustomerKeys.map((k) => {
|
|
255
|
+
const c = customersByKey.get(k);
|
|
256
|
+
return (_jsxs("div", { className: "flex items-center gap-3 p-2.5", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c?.display_name ?? k }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c ? [c.city, c.country].filter(Boolean).join(" · ") || "—" : "Niet in cache" })] }), _jsx("div", { className: "flex gap-1", children: (c?.providers ?? []).slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, k));
|
|
257
|
+
}) })] })), _jsx(CustomerPickerDialog, { open: customerPickerOpen, onOpenChange: setCustomerPickerOpen, value: selectedCustomerKeys, onChange: setSelectedCustomerKeys })] }), error && _jsx("div", { className: "text-sm text-destructive", children: error })] }), form.pricing_mode === "calculated" && (_jsxs("aside", { className: "lg:sticky lg:top-4 space-y-3", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-sm font-medium", children: "Voorbeeld prijscalculatie" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => cycleItem(-1), disabled: items.length < 2, "aria-label": "Vorige product", children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => cycleItem(1), disabled: items.length < 2, "aria-label": "Volgend product", children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] }), _jsxs("button", { type: "button", onClick: () => setPickerOpen(true), className: "w-full flex items-center gap-3 text-left hover:bg-muted/30 rounded-md p-2 -m-2", children: [_jsx("div", { className: "h-14 w-14 rounded bg-muted flex items-center justify-center overflow-hidden shrink-0", children: selectedItem?.image_url
|
|
258
|
+
? _jsx("img", { src: selectedItem.image_url, alt: "", className: "h-full w-full object-contain" })
|
|
259
|
+
: _jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium truncate", children: selectedItem?.name ?? "Geen product beschikbaar" }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: selectedItem
|
|
260
|
+
? [selectedItem.packaging_code,
|
|
261
|
+
`${selectedItem.units_per_fust}×${selectedItem.packages_per_layer}×${selectedItem.layers_per_carrier}`,
|
|
262
|
+
selectedItem.carrier_code]
|
|
263
|
+
.filter(Boolean).join(" - ")
|
|
264
|
+
: "Wacht op Floriday-cache" })] })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Basisprijs" }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: basePrice, onChange: (e) => setBasePrice(Number(e.target.value)), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx("div", { className: "flex justify-end text-xs text-muted-foreground mb-1", children: "Prijzen" }), _jsxs("div", { className: "space-y-1.5 text-sm tabular-nums border-t pt-2", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Basisprijs" }), _jsxs("span", { children: ["\u20AC ", basePrice.toFixed(2)] })] }), Math.abs(preview.discountAmount) > 0 && (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { className: "text-muted-foreground", children: [form.calc_type === "surcharge" ? "Opslag" : "Korting", " ", form.calc_value_type === "percentage"
|
|
265
|
+
? `${(form.calc_value ?? 0).toLocaleString("nl-NL")}%`
|
|
266
|
+
: `€ ${(form.calc_value ?? 0).toFixed(3)}`] }), _jsxs("span", { children: [preview.discountAmount < 0 ? "− " : "", "\u20AC ", Math.abs(preview.discountAmount).toFixed(2)] })] })), preview.handlingPerUnit > 0 && (_jsxs("div", { className: "flex justify-between items-baseline gap-2", children: [_jsxs("span", { className: "text-muted-foreground truncate", children: ["Afhandelingskosten (optioneel)", " ", _jsxs("span", { className: "italic text-muted-foreground/80", children: ["\u20AC ", (form.handling_fee ?? 0).toFixed(2).replace(".", ","), handlingSuffix] })] }), _jsxs("span", { children: ["\u20AC ", preview.handlingPerUnit.toFixed(3)] })] })), _jsxs("div", { className: "flex justify-between border-t pt-1.5", children: [_jsx("span", { className: form.rounding === "none" ? "font-medium" : "text-muted-foreground", children: "Berekende prijs" }), _jsxs("span", { className: form.rounding === "none" ? "font-medium" : "", children: ["\u20AC ", preview.adjusted.toFixed(3)] })] }), form.rounding !== "none" && (_jsxs("div", { className: "flex justify-between font-medium border-t pt-1.5", children: [_jsx("span", { children: "Afgeronde prijs" }), _jsxs("span", { children: ["\u20AC ", preview.rounded.toFixed(2)] })] }))] })] })] }) }), _jsx(ProductPickerDialog, { open: pickerOpen, onOpenChange: setPickerOpen, items: items, onSelect: (i) => setSelectedItemId(i.id) })] }))] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t z-10", children: _jsxs("div", { className: "max-w-6xl mx-auto px-6 py-3 flex items-center justify-between", children: [_jsx(Button, { variant: "ghost", onClick: goBack, children: "Annuleren" }), _jsx(Button, { onClick: () => saveMut.mutate(), disabled: saveMut.isPending ||
|
|
267
|
+
!form.name.trim() ||
|
|
268
|
+
(form.group_type === "customer_specific" && selectedCustomerKeys.length === 0), children: saveMut.isPending ? "Opslaan…" : "Opslaan" })] }) })] }));
|
|
269
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PriceGroupsPage.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/PriceGroupsPage.tsx"],"names":[],"mappings":"AAiHA,wBAAgB,eAAe,gCAoO9B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { Button, Input, Card, CardContent, Badge, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, } from "@flowselections/core";
|
|
6
|
+
import { Link, useNavigate } from "@tanstack/react-router";
|
|
7
|
+
import { Plus, Search, MoreVertical, GripVertical, EyeOff, Eye, Pencil, Users, Copy, Trash2, Tag, ChevronLeft, ChevronRight } from "lucide-react";
|
|
8
|
+
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core";
|
|
9
|
+
import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable, arrayMove, } from "@dnd-kit/sortable";
|
|
10
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
11
|
+
import { listCustomerPriceGroups, deleteCustomerPriceGroup, reorderCustomerPriceGroups, updateCustomerPriceGroup, createCustomerPriceGroup, } from "../../../lib/price-groups-local.functions";
|
|
12
|
+
import { CustomerPickerDialog } from "./CustomerPickerDialog";
|
|
13
|
+
function unitLabel(u) {
|
|
14
|
+
switch (u) {
|
|
15
|
+
case "per_fust": return "Per fust";
|
|
16
|
+
case "per_stuk": return "Per stuk";
|
|
17
|
+
case "per_ladingdrager": return "Per ladingdrager";
|
|
18
|
+
default: return u;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function calcSummary(g) {
|
|
22
|
+
if (g.pricing_mode !== "calculated")
|
|
23
|
+
return "—";
|
|
24
|
+
if (!g.calc_type || !g.calc_value_type || !g.calc_value)
|
|
25
|
+
return "Geen berekening";
|
|
26
|
+
const verb = g.calc_type === "discount" ? "Korting" : "Opslag";
|
|
27
|
+
const val = g.calc_value_type === "percentage" ? `${g.calc_value}%` : `€ ${Number(g.calc_value).toFixed(2)}`;
|
|
28
|
+
return `${verb} — ${val}`;
|
|
29
|
+
}
|
|
30
|
+
function SortableRow({ g, onEdit, onMembers, onDelete, onDuplicate, onToggleActive }) {
|
|
31
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: g.id });
|
|
32
|
+
const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.6 : 1 };
|
|
33
|
+
return (_jsxs(TableRow, { ref: setNodeRef, style: style, className: isDragging ? "bg-muted/50" : undefined, children: [_jsx(TableCell, { className: "w-[36px] p-0 pl-2", children: _jsx("button", { type: "button", className: "cursor-grab touch-none text-muted-foreground hover:text-foreground p-2", "aria-label": "Verslepen", ...attributes, ...listeners, children: _jsx(GripVertical, { className: "h-4 w-4" }) }) }), _jsxs(TableCell, { children: [_jsxs("div", { className: "font-medium flex items-center gap-2", children: [g.name, !g.is_active && _jsx(Badge, { variant: "secondary", className: "text-[10px]", children: "Inactief" })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Laatst gewijzigd: ", new Date(g.updated_at).toLocaleString("nl-NL")] })] }), _jsx(TableCell, { children: _jsx(Badge, { variant: g.pricing_mode === "calculated" ? "default" : "secondary", children: g.pricing_mode === "calculated" ? "Berekende prijzen" : "Handmatige prijzen" }) }), _jsxs(TableCell, { className: "tabular-nums", children: [g.member_count, " klanten"] }), _jsxs(TableCell, { className: "text-sm", children: [g.price_scope === "incl_delivery" ? "Levering en transport" : "Exclusief levering", g.include_stickers && _jsx("div", { className: "text-xs text-muted-foreground", children: "Incl. stickers" })] }), _jsx(TableCell, { className: "text-sm", children: unitLabel(g.unit) }), _jsx(TableCell, { className: "text-sm", children: g.currency }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: calcSummary(g) }), _jsx(TableCell, { className: "w-[52px] text-right pr-2", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Acties", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: onEdit, children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), "Bewerken"] }), _jsxs(DropdownMenuItem, { onClick: onMembers, children: [_jsx(Users, { className: "h-4 w-4 mr-2" }), "Klanten toewijzen"] }), _jsxs(DropdownMenuItem, { onClick: onDuplicate, children: [_jsx(Copy, { className: "h-4 w-4 mr-2" }), "Dupliceren"] }), _jsx(DropdownMenuItem, { onClick: onToggleActive, children: g.is_active
|
|
34
|
+
? (_jsxs(_Fragment, { children: [_jsx(EyeOff, { className: "h-4 w-4 mr-2" }), "Op inactief zetten"] }))
|
|
35
|
+
: (_jsxs(_Fragment, { children: [_jsx(Eye, { className: "h-4 w-4 mr-2" }), "Activeren"] })) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: onDelete, className: "text-destructive focus:text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), "Verwijderen"] })] })] }) })] }));
|
|
36
|
+
}
|
|
37
|
+
export function PriceGroupsPage() {
|
|
38
|
+
const qc = useQueryClient();
|
|
39
|
+
const navigate = useNavigate();
|
|
40
|
+
const listFn = useServerFn(listCustomerPriceGroups);
|
|
41
|
+
const delFn = useServerFn(deleteCustomerPriceGroup);
|
|
42
|
+
const reorderFn = useServerFn(reorderCustomerPriceGroups);
|
|
43
|
+
const updateFn = useServerFn(updateCustomerPriceGroup);
|
|
44
|
+
const createFn = useServerFn(createCustomerPriceGroup);
|
|
45
|
+
const { data: groups = [], isLoading } = useQuery({
|
|
46
|
+
queryKey: ["customer-price-groups"],
|
|
47
|
+
queryFn: () => listFn(),
|
|
48
|
+
});
|
|
49
|
+
const [search, setSearch] = useState("");
|
|
50
|
+
const [statusFilter, setStatusFilter] = useState("all");
|
|
51
|
+
const [membersFor, setMembersFor] = useState(null);
|
|
52
|
+
const [page, setPage] = useState(1);
|
|
53
|
+
const [pageSize, setPageSize] = useState(25);
|
|
54
|
+
const resetPage = () => setPage(1);
|
|
55
|
+
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
56
|
+
const filtered = useMemo(() => {
|
|
57
|
+
const q = search.trim().toLowerCase();
|
|
58
|
+
return groups.filter((g) => {
|
|
59
|
+
if (statusFilter === "active" && !g.is_active)
|
|
60
|
+
return false;
|
|
61
|
+
if (statusFilter === "inactive" && g.is_active)
|
|
62
|
+
return false;
|
|
63
|
+
if (!q)
|
|
64
|
+
return true;
|
|
65
|
+
return g.name.toLowerCase().includes(q);
|
|
66
|
+
});
|
|
67
|
+
}, [groups, search, statusFilter]);
|
|
68
|
+
const reorderMut = useMutation({
|
|
69
|
+
mutationFn: (ids) => reorderFn({ data: { ids } }),
|
|
70
|
+
onMutate: async (ids) => {
|
|
71
|
+
await qc.cancelQueries({ queryKey: ["customer-price-groups"] });
|
|
72
|
+
const prev = qc.getQueryData(["customer-price-groups"]);
|
|
73
|
+
if (prev) {
|
|
74
|
+
const map = new Map(prev.map((g) => [g.id, g]));
|
|
75
|
+
const next = ids.map((id, idx) => ({ ...map.get(id), sort_order: idx + 1 }));
|
|
76
|
+
qc.setQueryData(["customer-price-groups"], next);
|
|
77
|
+
}
|
|
78
|
+
return { prev };
|
|
79
|
+
},
|
|
80
|
+
onError: (_e, _v, ctx) => { if (ctx?.prev)
|
|
81
|
+
qc.setQueryData(["customer-price-groups"], ctx.prev); },
|
|
82
|
+
onSettled: () => qc.invalidateQueries({ queryKey: ["customer-price-groups"] }),
|
|
83
|
+
});
|
|
84
|
+
const deleteMut = useMutation({
|
|
85
|
+
mutationFn: (id) => delFn({ data: { id } }),
|
|
86
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["customer-price-groups"] }),
|
|
87
|
+
});
|
|
88
|
+
const toggleActiveMut = useMutation({
|
|
89
|
+
mutationFn: async (g) => {
|
|
90
|
+
const { id, member_count, created_at, updated_at, sort_order, ...rest } = g;
|
|
91
|
+
return updateFn({ data: { id, input: { ...rest, is_active: !g.is_active } } });
|
|
92
|
+
},
|
|
93
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["customer-price-groups"] }),
|
|
94
|
+
});
|
|
95
|
+
const duplicateMut = useMutation({
|
|
96
|
+
mutationFn: async (g) => {
|
|
97
|
+
const { id, member_count, created_at, updated_at, sort_order, ...rest } = g;
|
|
98
|
+
return createFn({ data: { ...rest, name: `${g.name} (kopie)` } });
|
|
99
|
+
},
|
|
100
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["customer-price-groups"] }),
|
|
101
|
+
});
|
|
102
|
+
const handleDragEnd = (event) => {
|
|
103
|
+
const { active, over } = event;
|
|
104
|
+
if (!over || active.id === over.id)
|
|
105
|
+
return;
|
|
106
|
+
const ids = filtered.map((g) => g.id);
|
|
107
|
+
const oldIndex = ids.indexOf(String(active.id));
|
|
108
|
+
const newIndex = ids.indexOf(String(over.id));
|
|
109
|
+
if (oldIndex < 0 || newIndex < 0)
|
|
110
|
+
return;
|
|
111
|
+
// Reorder only over full list to keep global ordering stable
|
|
112
|
+
const fullIds = groups.map((g) => g.id);
|
|
113
|
+
const from = fullIds.indexOf(String(active.id));
|
|
114
|
+
const to = fullIds.indexOf(String(over.id));
|
|
115
|
+
const next = arrayMove(fullIds, from, to);
|
|
116
|
+
reorderMut.mutate(next);
|
|
117
|
+
};
|
|
118
|
+
const active = groups.filter((g) => g.is_active).length;
|
|
119
|
+
const inactive = groups.length - active;
|
|
120
|
+
const total = filtered.length;
|
|
121
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
122
|
+
const currentPage = Math.min(page, totalPages);
|
|
123
|
+
const startIdx = total === 0 ? 0 : (currentPage - 1) * pageSize;
|
|
124
|
+
const endIdx = Math.min(startIdx + pageSize, total);
|
|
125
|
+
const pageRows = filtered.slice(startIdx, endIdx);
|
|
126
|
+
return (_jsxs("main", { className: "p-6 space-y-5", 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: "Prijsgroepen" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer prijsafspraken en wijs klanten uit elke bron toe aan een prijsgroep." })] }), _jsx(Button, { asChild: true, children: _jsxs(Link, { to: "/klanten/prijsgroepen/nieuw", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Prijsgroep toevoegen"] }) })] }), _jsxs(Card, { children: [_jsx("div", { className: "border-b px-4 pt-3", children: _jsx(Tabs, { value: statusFilter, onValueChange: (v) => { setStatusFilter(v); resetPage(); }, children: _jsxs(TabsList, { className: "flex-wrap", children: [_jsxs(TabsTrigger, { value: "all", children: ["Alle ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: groups.length })] }), _jsxs(TabsTrigger, { value: "active", children: ["Actief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: active })] }), _jsxs(TabsTrigger, { value: "inactive", children: ["Inactief ", _jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: inactive })] })] }) }) }), _jsx("div", { className: "flex items-center gap-2 flex-wrap p-4 border-b", 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 naam\u2026", value: search, onChange: (e) => { setSearch(e.target.value); resetPage(); }, className: "pl-9" })] }) }), _jsx(CardContent, { className: "p-0", children: _jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[36px]" }), _jsx(TableHead, { children: "Naam" }), _jsx(TableHead, { children: "Type" }), _jsx(TableHead, { children: "Klanten" }), _jsx(TableHead, { children: "Diensten" }), _jsx(TableHead, { children: "Afname-eenheid" }), _jsx(TableHead, { children: "Valuta" }), _jsx(TableHead, { children: "Berekening" }), _jsx(TableHead, { className: "w-[52px]" })] }) }), _jsx(TableBody, { children: isLoading ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 9, className: "text-center py-8 text-muted-foreground text-sm", children: "Laden\u2026" }) })) : pageRows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 9, children: _jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [_jsx("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center mb-3", children: _jsx(Tag, { className: "h-5 w-5 text-muted-foreground" }) }), _jsx("div", { className: "font-medium", children: "Geen prijsgroepen gevonden" }), _jsx("div", { className: "text-sm text-muted-foreground mt-1", children: groups.length === 0
|
|
127
|
+
? "Voeg je eerste prijsgroep toe om te beginnen."
|
|
128
|
+
: "Pas je filters aan of voeg een nieuwe prijsgroep toe." }), _jsx(Button, { asChild: true, className: "mt-4", children: _jsxs(Link, { to: "/klanten/prijsgroepen/nieuw", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Prijsgroep toevoegen"] }) })] }) }) })) : (_jsx(SortableContext, { items: pageRows.map((g) => g.id), strategy: verticalListSortingStrategy, children: pageRows.map((g) => (_jsx(SortableRow, { g: g, onEdit: () => navigate({ to: "/klanten/prijsgroepen/$id", params: { id: g.id } }), onMembers: () => setMembersFor(g.id), onDelete: () => {
|
|
129
|
+
if (confirm(`Prijsgroep "${g.name}" verwijderen?`))
|
|
130
|
+
deleteMut.mutate(g.id);
|
|
131
|
+
}, onDuplicate: () => duplicateMut.mutate(g), onToggleActive: () => toggleActiveMut.mutate(g) }, g.id))) })) })] }) }) }), _jsxs("div", { className: "flex items-center justify-between gap-3 flex-wrap px-4 py-3 border-t text-sm", children: [_jsxs("div", { className: "text-muted-foreground", children: [total, " prijsgroep", total === 1 ? "" : "en"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => { setPageSize(Number(v)); resetPage(); }, children: [_jsx(SelectTrigger, { className: "w-20", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: [10, 25, 50, 100].map((n) => (_jsx(SelectItem, { value: String(n), children: n }, n))) })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: currentPage <= 1, children: [_jsx(ChevronLeft, { className: "h-4 w-4 mr-1" }), "Vorige"] }), _jsxs("span", { className: "text-muted-foreground tabular-nums", children: [currentPage, " / ", totalPages] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.min(totalPages, p + 1)), disabled: currentPage >= totalPages, children: ["Volgende", _jsx(ChevronRight, { className: "h-4 w-4 ml-1" })] })] })] })] }), membersFor && (_jsx(CustomerPickerDialog, { priceGroupId: membersFor, open: !!membersFor, onOpenChange: (o) => { if (!o)
|
|
132
|
+
setMembersFor(null); } }))] }));
|
|
133
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TradeItemPreview } from "../../../lib/floriday-trade-items.functions";
|
|
2
|
+
export declare function ProductPickerDialog({ open, onOpenChange, items, onSelect, }: {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (v: boolean) => void;
|
|
5
|
+
items: TradeItemPreview[];
|
|
6
|
+
onSelect: (item: TradeItemPreview) => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=ProductPickerDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductPickerDialog.d.ts","sourceRoot":"","sources":["../../../../src/components/klanten/pricegroups/ProductPickerDialog.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAEpF,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,GACpC,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC5C,+BA0DA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, Input, } from "@flowselections/core";
|
|
4
|
+
import { ImageIcon, Search } from "lucide-react";
|
|
5
|
+
export function ProductPickerDialog({ open, onOpenChange, items, onSelect, }) {
|
|
6
|
+
const [q, setQ] = useState("");
|
|
7
|
+
const filtered = useMemo(() => {
|
|
8
|
+
const s = q.trim().toLowerCase();
|
|
9
|
+
if (!s)
|
|
10
|
+
return items;
|
|
11
|
+
return items.filter((i) => i.name.toLowerCase().includes(s) ||
|
|
12
|
+
(i.vbn_code ?? "").toLowerCase().includes(s));
|
|
13
|
+
}, [items, q]);
|
|
14
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Kies een product voor de voorbeeldberekening" }) }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { autoFocus: true, placeholder: "Zoek op naam of VBN-code", value: q, onChange: (e) => setQ(e.target.value), className: "pl-9" })] }), _jsxs("div", { className: "max-h-[420px] overflow-y-auto -mx-2 px-2 divide-y", children: [filtered.length === 0 && (_jsx("div", { className: "text-sm text-muted-foreground py-6 text-center", children: "Geen producten gevonden." })), filtered.map((i) => (_jsxs("button", { type: "button", onClick: () => { onSelect(i); onOpenChange(false); }, className: "w-full flex items-center gap-3 py-2.5 text-left hover:bg-muted/40 rounded-md px-2", children: [_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center overflow-hidden shrink-0", children: i.image_url
|
|
15
|
+
? _jsx("img", { src: i.image_url, alt: "", className: "h-full w-full object-contain" })
|
|
16
|
+
: _jsx(ImageIcon, { className: "h-5 w-5 text-muted-foreground" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium truncate", children: i.name }), _jsxs("div", { className: "text-xs text-muted-foreground truncate", children: [[i.packaging_code, `${i.units_per_fust}×${i.packages_per_layer}×${i.layers_per_carrier}`, i.carrier_code]
|
|
17
|
+
.filter(Boolean).join(" - "), i.vbn_code ? ` · ${i.vbn_code}` : ""] })] })] }, i.id)))] })] }) }));
|
|
18
|
+
}
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ export { NieuweKlant } from "./components/klanten/NieuweKlant";
|
|
|
8
8
|
export { ImportKlanten } from "./components/klanten/ImportKlanten";
|
|
9
9
|
export { KlantDetailRoute } from "./components/klanten/KlantDetailRoute";
|
|
10
10
|
export { GroupedKlantDetailRoute } from "./components/klanten/GroupedKlantDetailRoute";
|
|
11
|
+
export { PriceGroupsPage } from "./components/klanten/pricegroups/PriceGroupsPage";
|
|
11
12
|
export declare const floridayKlantenModule: FlowModule;
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAC;AAEnF,eAAO,MAAM,qBAAqB,EAAE,UAgBnC,CAAC"}
|
package/dist-lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// 3. Add your nav item — use `children` if you need a dropdown
|
|
14
14
|
// 4. Done! You don't need to touch any other file for registration.
|
|
15
15
|
// ============================================================================
|
|
16
|
-
import { Users } from "lucide-react";
|
|
16
|
+
import { Users, Tag } from "lucide-react";
|
|
17
17
|
import { CustomerFieldsCard } from "./components/settings/CustomerFieldsCard";
|
|
18
18
|
export * from "./_core-safelist";
|
|
19
19
|
export { KlantenOverzicht } from "./components/klanten/KlantenOverzicht";
|
|
@@ -24,6 +24,7 @@ export { NieuweKlant } from "./components/klanten/NieuweKlant";
|
|
|
24
24
|
export { ImportKlanten } from "./components/klanten/ImportKlanten";
|
|
25
25
|
export { KlantDetailRoute } from "./components/klanten/KlantDetailRoute";
|
|
26
26
|
export { GroupedKlantDetailRoute } from "./components/klanten/GroupedKlantDetailRoute";
|
|
27
|
+
export { PriceGroupsPage } from "./components/klanten/pricegroups/PriceGroupsPage";
|
|
27
28
|
export const floridayKlantenModule = {
|
|
28
29
|
id: "customers",
|
|
29
30
|
name: "Klanten",
|
|
@@ -32,6 +33,10 @@ export const floridayKlantenModule = {
|
|
|
32
33
|
label: "Klanten",
|
|
33
34
|
href: "/klanten",
|
|
34
35
|
icon: Users,
|
|
36
|
+
children: [
|
|
37
|
+
{ label: "Overzicht", href: "/klanten", icon: Users },
|
|
38
|
+
{ label: "Prijsgroepen", href: "/klanten/prijsgroepen", icon: Tag },
|
|
39
|
+
],
|
|
35
40
|
},
|
|
36
41
|
settingsCards: [
|
|
37
42
|
{ component: CustomerFieldsCard, order: 10 },
|