@flowselections/floriday-klanten-module 1.0.49 → 1.0.51
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/CustomerAvatar.js +1 -1
- package/dist-lib/components/klanten/BewerkKlant.js +1 -1
- package/dist-lib/components/klanten/BewerkKlantRoute.js +1 -1
- package/dist-lib/components/klanten/BulkStickerUploadDialog.js +1 -1
- package/dist-lib/components/klanten/CustomerAnalyticsTab.js +2 -2
- package/dist-lib/components/klanten/CustomerAssortmentTab.js +4 -4
- package/dist-lib/components/klanten/DeliveryLocationsTab.js +6 -6
- package/dist-lib/components/klanten/DiscountsTab.js +3 -3
- package/dist-lib/components/klanten/ExternalCacheBanner.d.ts +1 -1
- package/dist-lib/components/klanten/ExternalCacheBanner.d.ts.map +1 -1
- package/dist-lib/components/klanten/ExternalCacheBanner.js +2 -5
- package/dist-lib/components/klanten/FloridayNetworkStatus.d.ts.map +1 -1
- package/dist-lib/components/klanten/FloridayNetworkStatus.js +5 -3
- package/dist-lib/components/klanten/GroupedKlantDetailRoute.js +1 -1
- package/dist-lib/components/klanten/ImportKlanten.js +1 -1
- package/dist-lib/components/klanten/KlantBewerkenDialog.js +2 -2
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +15 -3
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts +1 -1
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetailLayout.js +7 -5
- package/dist-lib/components/klanten/KlantDetailRoute.js +1 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +18 -18
- package/dist-lib/components/klanten/LocalDiscountFormDialog.js +1 -1
- package/dist-lib/components/klanten/LocalDiscountsTab.js +2 -2
- package/dist-lib/components/klanten/NetwerkPagina.js +3 -3
- package/dist-lib/components/klanten/NieuweKlant.js +1 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlant.js +1 -1
- package/dist-lib/components/klanten/OvernemenTwinfieldKlantRoute.js +1 -1
- package/dist-lib/components/klanten/RouteError.js +1 -1
- package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -1
- package/dist-lib/components/klanten/SyncStatusChip.js +5 -4
- package/dist-lib/components/klanten/pricegroups/CustomerPickerDialog.js +6 -6
- package/dist-lib/components/klanten/pricegroups/CustomerPriceGroupSelect.js +3 -3
- package/dist-lib/components/klanten/pricegroups/PriceGroupEditRoute.js +1 -1
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +13 -13
- package/dist-lib/components/klanten/pricegroups/PriceGroupNewRoute.js +1 -1
- package/dist-lib/components/klanten/pricegroups/PriceGroupsPage.js +4 -4
- package/dist-lib/components/klanten/pricegroups/PriceGroupsRoute.js +1 -1
- package/dist-lib/components/klanten/pricegroups/ProductPickerDialog.d.ts +1 -1
- package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.d.ts +1 -1
- package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.js +4 -4
- package/dist-lib/components/klanten/useCustomerDefaults.d.ts +1 -1
- package/dist-lib/index.d.ts +34 -34
- package/dist-lib/index.js +35 -35
- package/dist-lib/integrations/supabase/auth-attacher.js +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +450 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/accounting/exact/client.server.d.ts +1 -1
- package/dist-lib/lib/accounting/exact/client.server.js +2 -2
- package/dist-lib/lib/accounting/exact/sync.server.d.ts +1 -1
- package/dist-lib/lib/accounting/exact/sync.server.js +3 -3
- package/dist-lib/lib/accounting/floriday/sync.server.d.ts +1 -1
- package/dist-lib/lib/accounting/floriday/sync.server.js +1 -1
- package/dist-lib/lib/accounting/registry.server.d.ts +1 -1
- package/dist-lib/lib/accounting/registry.server.js +2 -2
- package/dist-lib/lib/accounting/twinfield/client.server.js +2 -2
- package/dist-lib/lib/accounting/twinfield/field-coverage.test.js +6 -6
- package/dist-lib/lib/accounting/twinfield/mapping.js +3 -3
- package/dist-lib/lib/accounting/twinfield/rmw-push.server.d.ts +2 -2
- package/dist-lib/lib/accounting/twinfield/rmw-push.server.js +4 -4
- package/dist-lib/lib/accounting/twinfield/rmw.server.d.ts +1 -1
- package/dist-lib/lib/accounting/twinfield/rmw.server.js +2 -2
- package/dist-lib/lib/accounting/twinfield/sandbox-rmw.server.d.ts +6 -6
- package/dist-lib/lib/accounting/twinfield/sandbox-rmw.server.js +12 -12
- package/dist-lib/lib/accounting/twinfield/sandbox.server.js +3 -3
- package/dist-lib/lib/accounting/twinfield/sync.server.js +4 -4
- package/dist-lib/lib/accounting/twinfield/worker.functions.d.ts +7 -7
- package/dist-lib/lib/accounting/twinfield/worker.functions.js +6 -6
- package/dist-lib/lib/accounting/twinfield/worker.server.js +8 -8
- package/dist-lib/lib/accounting.functions.d.ts +4 -4
- package/dist-lib/lib/accounting.functions.js +2 -2
- package/dist-lib/lib/countries.test.js +3 -3
- package/dist-lib/lib/customer-analytics.functions.d.ts +1 -1
- package/dist-lib/lib/customer-analytics.functions.js +1 -1
- package/dist-lib/lib/customer-assortment.functions.d.ts +1 -1
- package/dist-lib/lib/customer-assortment.functions.js +3 -3
- package/dist-lib/lib/customer-defaults.test.js +3 -3
- package/dist-lib/lib/customer-insert.d.ts +1 -1
- package/dist-lib/lib/customer-insert.js +2 -2
- package/dist-lib/lib/customer-notes.functions.d.ts +2 -2
- package/dist-lib/lib/customer-notes.functions.js +1 -1
- package/dist-lib/lib/customer-orders.functions.d.ts +1 -1
- package/dist-lib/lib/customer-orders.functions.js +1 -1
- package/dist-lib/lib/customer-revenue.functions.d.ts +1 -1
- package/dist-lib/lib/customer-revenue.functions.js +2 -2
- package/dist-lib/lib/customer-schemas.js +1 -1
- package/dist-lib/lib/customer-schemas.test.js +1 -1
- package/dist-lib/lib/customer-stickers.functions.d.ts +9 -9
- package/dist-lib/lib/customer-stickers.functions.js +2 -2
- package/dist-lib/lib/customers.functions.d.ts +29 -29
- package/dist-lib/lib/customers.functions.js +11 -11
- package/dist-lib/lib/delivery-locations.functions.d.ts +7 -7
- package/dist-lib/lib/delivery-locations.functions.js +2 -2
- package/dist-lib/lib/external-customers.functions.d.ts +4 -4
- package/dist-lib/lib/external-customers.functions.js +2 -2
- package/dist-lib/lib/floriday-app-url.functions.d.ts +1 -1
- package/dist-lib/lib/floriday-app-url.functions.js +2 -2
- package/dist-lib/lib/floriday-assortment.functions.d.ts +1 -1
- package/dist-lib/lib/floriday-assortment.functions.js +2 -2
- package/dist-lib/lib/floriday-discounts.functions.d.ts +2 -2
- package/dist-lib/lib/floriday-discounts.functions.js +2 -2
- package/dist-lib/lib/floriday-network-status.functions.d.ts +3 -1
- package/dist-lib/lib/floriday-network-status.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-network-status.functions.js +39 -18
- package/dist-lib/lib/floriday-price-import.functions.d.ts +2 -2
- package/dist-lib/lib/floriday-price-import.functions.js +2 -2
- package/dist-lib/lib/floriday-sticker-import.functions.d.ts +1 -1
- package/dist-lib/lib/floriday-sticker-import.functions.js +1 -1
- package/dist-lib/lib/floriday-trade-items.functions.d.ts +1 -1
- package/dist-lib/lib/floriday-trade-items.functions.js +1 -1
- package/dist-lib/lib/floriday-trade-settings.functions.d.ts +1 -1
- package/dist-lib/lib/floriday-trade-settings.functions.js +1 -1
- package/dist-lib/lib/friendly-twinfield-error.d.ts +8 -0
- package/dist-lib/lib/friendly-twinfield-error.d.ts.map +1 -0
- package/dist-lib/lib/friendly-twinfield-error.js +16 -0
- package/dist-lib/lib/local-discounts.functions.d.ts +7 -7
- package/dist-lib/lib/local-discounts.functions.js +1 -1
- package/dist-lib/lib/logistics-events.functions.d.ts +1 -1
- package/dist-lib/lib/logistics-events.functions.js +1 -1
- package/dist-lib/lib/logistics-strategy.functions.d.ts +4 -4
- package/dist-lib/lib/logistics-strategy.functions.js +1 -1
- package/dist-lib/lib/overdue-invoices.functions.d.ts +1 -1
- package/dist-lib/lib/overdue-invoices.functions.js +2 -2
- package/dist-lib/lib/price-groups-local.functions.d.ts +15 -15
- package/dist-lib/lib/price-groups-local.functions.js +1 -1
- package/dist-lib/lib/price-groups.functions.d.ts +1 -1
- package/dist-lib/lib/price-groups.functions.js +1 -1
- package/dist-lib/lib/twinfield-adopt.functions.d.ts +5 -5
- package/dist-lib/lib/twinfield-adopt.functions.js +7 -7
- package/dist-lib/lib/twinfield-customer-defaults.functions.d.ts +2 -2
- package/dist-lib/lib/twinfield-customer-defaults.functions.js +2 -2
- package/dist-lib/lib/twinfield-customer-defaults.server.js +1 -1
- package/dist-lib/lib/twinfield-ledger.functions.d.ts +2 -2
- package/dist-lib/lib/twinfield-ledger.functions.js +2 -2
- package/dist-lib/lib/twinfield-ledger.server.js +2 -2
- package/dist-lib/lib/twinfield-required-fields.js +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef } from "react";
|
|
3
3
|
import { REFRESH_MS } from "../../lib/refresh-intervals.js";
|
|
4
|
-
import { friendlyError } from "../../lib/errors";
|
|
4
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
5
5
|
import { useNavigate } from "@tanstack/react-router";
|
|
6
6
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
7
7
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from "@tanstack/react-router";
|
|
3
|
-
import { BewerkKlant } from "./BewerkKlant";
|
|
3
|
+
import { BewerkKlant } from "./BewerkKlant.js";
|
|
4
4
|
/** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
|
|
5
5
|
export function BewerkKlantRoute({ id: propId } = {}) {
|
|
6
6
|
const params = useParams({ strict: false });
|
|
@@ -7,7 +7,7 @@ import { toast } from "sonner";
|
|
|
7
7
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
8
8
|
import { ALLOWED_STICKER_MIME, commitStickerBatch, discardStickerBatch, suggestStickerMatches, } from "../../lib/customer-stickers.functions.js";
|
|
9
9
|
import { friendlyError } from "../../lib/errors.js";
|
|
10
|
-
import { SearchableSelect } from "./SearchableSelect";
|
|
10
|
+
import { SearchableSelect } from "./SearchableSelect.js";
|
|
11
11
|
const BUCKET = "customer-stickers";
|
|
12
12
|
const MAX_SIZE = 10 * 1024 * 1024;
|
|
13
13
|
const ACCEPT = ".pdf,.png,.jpg,.jpeg,.svg";
|
|
@@ -7,8 +7,8 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
7
7
|
import { Card, CardContent, CardHeader, CardTitle, Badge, Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Input, } from "@flowselections/core";
|
|
8
8
|
import { ResponsiveContainer, LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, PieChart, Pie, Cell, Legend, } from "recharts";
|
|
9
9
|
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
10
|
-
import { getCustomerAnalytics } from "../../lib/customer-analytics.functions";
|
|
11
|
-
import { decodeDeep } from "../../lib/decode-html";
|
|
10
|
+
import { getCustomerAnalytics } from "../../lib/customer-analytics.functions.js";
|
|
11
|
+
import { decodeDeep } from "../../lib/decode-html.js";
|
|
12
12
|
function presetRange(key) {
|
|
13
13
|
const now = new Date();
|
|
14
14
|
const y = now.getFullYear(), m = now.getMonth(), d = now.getDate();
|
|
@@ -12,10 +12,10 @@ import { importFloridayStickers } from "../../lib/floriday-sticker-import.functi
|
|
|
12
12
|
import { decodeDeep } from "../../lib/decode-html.js";
|
|
13
13
|
import { friendlyError } from "../../lib/errors.js";
|
|
14
14
|
import { formatMoney } from "../../lib/format-currency.js";
|
|
15
|
-
import { FloridayPriceImportDialog } from "./FloridayPriceImportDialog";
|
|
16
|
-
import { CustomerStickerCell } from "./CustomerStickerCell";
|
|
17
|
-
import { CustomerStickerFormatDialog } from "./CustomerStickerFormatDialog";
|
|
18
|
-
import { BulkStickerUploadDialog } from "./BulkStickerUploadDialog";
|
|
15
|
+
import { FloridayPriceImportDialog } from "./FloridayPriceImportDialog.js";
|
|
16
|
+
import { CustomerStickerCell } from "./CustomerStickerCell.js";
|
|
17
|
+
import { CustomerStickerFormatDialog } from "./CustomerStickerFormatDialog.js";
|
|
18
|
+
import { BulkStickerUploadDialog } from "./BulkStickerUploadDialog.js";
|
|
19
19
|
function money(value, currency) {
|
|
20
20
|
if (value == null)
|
|
21
21
|
return "—";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { friendlyError } from "../../lib/errors";
|
|
2
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
3
3
|
import { useCustomerDefaults } from "./useCustomerDefaults.js";
|
|
4
4
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import { useServerFn } from "@tanstack/react-start";
|
|
7
7
|
import { Button, Card, CardContent, Badge, toast, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@flowselections/core";
|
|
8
8
|
import { MapPin, Plus, Pencil, Trash2, Truck, Sparkles, PackageCheck, Download } from "lucide-react";
|
|
9
|
-
import { listCarriers, listDeliveryLocations, upsertDeliveryLocation, deleteDeliveryLocation, pickCarrierForLocation, importFloridayDeliveryLocations, } from "../../lib/delivery-locations.functions";
|
|
10
|
-
import { EU_COUNTRIES, findCountry } from "../../lib/eu-regions";
|
|
11
|
-
import { decodeDeep } from "../../lib/decode-html";
|
|
12
|
-
import { countryNameToFlag } from "../../lib/countries";
|
|
9
|
+
import { listCarriers, listDeliveryLocations, upsertDeliveryLocation, deleteDeliveryLocation, pickCarrierForLocation, importFloridayDeliveryLocations, } from "../../lib/delivery-locations.functions.js";
|
|
10
|
+
import { EU_COUNTRIES, findCountry } from "../../lib/eu-regions.js";
|
|
11
|
+
import { decodeDeep } from "../../lib/decode-html.js";
|
|
12
|
+
import { countryNameToFlag } from "../../lib/countries.js";
|
|
13
13
|
const EMPTY = {
|
|
14
14
|
name: "", street: "", house_number: "", postal_code: "",
|
|
15
15
|
city: "", province: "", country: "",
|
|
@@ -60,7 +60,7 @@ export function DeliveryLocationsTab({ customerId, groupKey, externalProvider, e
|
|
|
60
60
|
mutationFn: () => importFn({ data: { preview: true } }),
|
|
61
61
|
onSuccess: (res) => {
|
|
62
62
|
if (res.cacheEmpty) {
|
|
63
|
-
toast.info("
|
|
63
|
+
toast.info("Er zijn nog geen afleverlocaties uit Floriday opgehaald.");
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
setImportPreview(res);
|
|
@@ -6,8 +6,8 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
6
6
|
import { Card, CardContent, Badge, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
|
|
7
7
|
import { Info, Percent, FileText, Users } from "lucide-react";
|
|
8
8
|
import { listCustomerDiscounts, } from "../../lib/floriday-discounts.functions.js";
|
|
9
|
-
import { EmptyState } from "./KlantDetailLayout";
|
|
10
|
-
import { ExternalCacheBanner } from "./ExternalCacheBanner";
|
|
9
|
+
import { EmptyState } from "./KlantDetailLayout.js";
|
|
10
|
+
import { ExternalCacheBanner } from "./ExternalCacheBanner.js";
|
|
11
11
|
import { decodeDeep } from "../../lib/decode-html.js";
|
|
12
12
|
import { formatMoney } from "../../lib/format-currency.js";
|
|
13
13
|
const SOURCE_LABEL = {
|
|
@@ -77,7 +77,7 @@ export function DiscountsTab({ buyerOrganizationId }) {
|
|
|
77
77
|
}
|
|
78
78
|
const s = data?.summary;
|
|
79
79
|
const rows = data?.rows ?? [];
|
|
80
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsgroep-regels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Laatst gesynchroniseerd", value: formatDateTime(s?.last_fetched_at), icon: _jsx(Info, { className: "h-4 w-4" }), small: true })] }), rows.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx(ExternalCacheBanner, { lastFetchedAt: s?.last_fetched_at ?? null, providers: [], title: "Floriday-kortingen", description: "De beschikbare kortings- en prijsgegevens worden automatisch bijgewerkt vanuit Floriday." }), _jsx(EmptyState, { message: "Voor deze klant zijn nog geen kortingen of prijsafspraken beschikbaar." })] })) : (_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 artikel of ID\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: "Sales Strategy" }), _jsx(SelectItem, { value: "price_group", children: "Prijsgroep" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground ml-auto", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel / Groep" }), _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(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center text-muted-foreground py-6", children: "Geen kortingen die aan de filters voldoen." }) })) : filtered.map((r, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id ?? r.product_group_id ?? "—" }), r.trade_item_name && (_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate max-w-[280px]", children: r.trade_item_id }))] }), _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 || "—" })] }, `${r.source_id}-${r.trade_item_id}-${i}`))) })] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "
|
|
80
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsgroep-regels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Laatst gesynchroniseerd", value: formatDateTime(s?.last_fetched_at), icon: _jsx(Info, { className: "h-4 w-4" }), small: true })] }), rows.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx(ExternalCacheBanner, { lastFetchedAt: s?.last_fetched_at ?? null, providers: [], title: "Floriday-kortingen", description: "De beschikbare kortings- en prijsgegevens worden automatisch bijgewerkt vanuit Floriday." }), _jsx(EmptyState, { message: "Voor deze klant zijn nog geen kortingen of prijsafspraken beschikbaar." })] })) : (_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 artikel of ID\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: "Sales Strategy" }), _jsx(SelectItem, { value: "price_group", children: "Prijsgroep" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground ml-auto", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel / Groep" }), _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(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center text-muted-foreground py-6", children: "Geen kortingen die aan de filters voldoen." }) })) : filtered.map((r, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id ?? r.product_group_id ?? "—" }), r.trade_item_name && (_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate max-w-[280px]", children: r.trade_item_id }))] }), _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 || "—" })] }, `${r.source_id}-${r.trade_item_id}-${i}`))) })] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Kortingen pas je aan in Floriday; hier zie je ze alleen." })] }) }))] }));
|
|
81
81
|
}
|
|
82
82
|
function SummaryCard({ label, value, icon, small, }) {
|
|
83
83
|
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: small ? "text-sm mt-1" : "text-2xl font-semibold mt-1", children: value })] }) }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExternalProvider } from "../../lib/external-customers.functions";
|
|
1
|
+
import type { ExternalProvider } from "../../lib/external-customers.functions.js";
|
|
2
2
|
export declare function ExternalCacheBanner({ lastFetchedAt, providers, title, description, }: {
|
|
3
3
|
lastFetchedAt: string | null;
|
|
4
4
|
providers: ExternalProvider[];
|
|
@@ -1 +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,EACT,KAA2C,EAC3C,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+
|
|
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,EACT,KAA2C,EAC3C,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAmBA"}
|
|
@@ -14,9 +14,6 @@ function relativeTime(iso) {
|
|
|
14
14
|
return `${day} dag${day === 1 ? "" : "en"} geleden`;
|
|
15
15
|
}
|
|
16
16
|
export function ExternalCacheBanner({ lastFetchedAt, providers, title = "Externe klantgegevens (read-only)", description, }) {
|
|
17
|
-
|
|
18
|
-
|
|
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: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description ?? (_jsxs(_Fragment, { children: ["Data wordt automatisch verfrist vanuit de gekoppelde bronnen", 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"] })] }));
|
|
17
|
+
void providers;
|
|
18
|
+
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: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description ?? (_jsx(_Fragment, { children: "Gegevens worden automatisch bijgewerkt uit Floriday en Twinfield." })) })] }), _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 ? `Laatst bijgewerkt: ${relativeTime(lastFetchedAt)}` : "Nog niet opgehaald"] })] }));
|
|
22
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloridayNetworkStatus.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayNetworkStatus.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FloridayNetworkStatus.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayNetworkStatus.tsx"],"names":[],"mappings":"AAMA,iGAAiG;AACjG,wBAAgB,qBAAqB,CAAC,EAAE,cAAc,EAAE,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,sCAiBnF"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
3
|
import { useServerFn } from "@tanstack/react-start";
|
|
4
|
-
import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions";
|
|
5
|
-
import { formatDate } from "../../lib/format-date";
|
|
4
|
+
import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions.js";
|
|
5
|
+
import { formatDate } from "../../lib/format-date.js";
|
|
6
|
+
import { useCustomerDefaults } from "./useCustomerDefaults.js";
|
|
6
7
|
/** Alleen weergave: staat deze Floriday-organisatie in je Floriday-netwerk? Geen klantstatus. */
|
|
7
8
|
export function FloridayNetworkStatus({ organizationId }) {
|
|
8
9
|
const fn = useServerFn(getFloridayNetworkStatus);
|
|
10
|
+
const { isAdmin } = useCustomerDefaults();
|
|
9
11
|
const { data } = useQuery({
|
|
10
12
|
queryKey: ["floriday-network-status", organizationId],
|
|
11
13
|
queryFn: () => fn({ data: { organizationId } }),
|
|
@@ -14,5 +16,5 @@ export function FloridayNetworkStatus({ organizationId }) {
|
|
|
14
16
|
if (!data)
|
|
15
17
|
return null;
|
|
16
18
|
const label = data.inNetwork === null ? "onbekend" : data.inNetwork ? "ja" : "nee";
|
|
17
|
-
return (_jsxs("div", { className: "mt-2 text-xs text-muted-foreground space-y-0.5", children: [_jsxs("p", { children: ["In je Floriday-netwerk: ", _jsx("span", { className: "font-medium text-foreground", children: label })] }), data.endDate && _jsxs("p", { children: ["Einddatum in Floriday: ", formatDate(data.endDate)] })] }));
|
|
19
|
+
return (_jsxs("div", { className: "mt-2 text-xs text-muted-foreground space-y-0.5", children: [_jsxs("p", { children: ["In je Floriday-netwerk: ", _jsx("span", { className: "font-medium text-foreground", children: label })] }), isAdmin === true && data.reason && _jsxs("p", { children: ["Details voor beheer: ", data.reason] }), data.endDate && _jsxs("p", { children: ["Einddatum in Floriday: ", formatDate(data.endDate)] })] }));
|
|
18
20
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from "@tanstack/react-router";
|
|
3
|
-
import { GroupedKlantDetail } from "./GroupedKlantDetail";
|
|
3
|
+
import { GroupedKlantDetail } from "./GroupedKlantDetail.js";
|
|
4
4
|
/** Route-onafhankelijk: leest `$groupKey` uit welke route de shell ook gebruikt. */
|
|
5
5
|
export function GroupedKlantDetailRoute({ groupKey: propKey } = {}) {
|
|
6
6
|
const params = useParams({ strict: false });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { friendlyError } from "../../lib/errors";
|
|
2
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
3
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
4
4
|
import { useEffect, useMemo, useState } from "react";
|
|
5
5
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@flowselections/core";
|
|
4
|
-
import { BewerkKlant } from "./BewerkKlant";
|
|
5
|
-
import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant";
|
|
4
|
+
import { BewerkKlant } from "./BewerkKlant.js";
|
|
5
|
+
import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant.js";
|
|
6
6
|
/**
|
|
7
7
|
* Bewerken/overnemen in een dialoog, zodat de module geen eigen routes nodig
|
|
8
8
|
* heeft. Werkt daardoor in elke shell, ongeacht welke routes die declareert.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAuXjD"}
|
|
@@ -4,8 +4,8 @@ import { REFRESH_MS } from "../../lib/refresh-intervals.js";
|
|
|
4
4
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
-
import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
|
8
|
-
import { Trash2, Mail, Phone, Power, Pencil, } from "lucide-react";
|
|
7
|
+
import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from "@flowselections/core";
|
|
8
|
+
import { Trash2, Mail, Phone, Power, Pencil, MoreHorizontal, } from "lucide-react";
|
|
9
9
|
import { getCustomer, deleteCustomer, updateCustomer } from "../../lib/customers.functions.js";
|
|
10
10
|
import { ProfileCard, KpiStrip, BadgeList, EmptyState, ActivityItem, adaptLocalToProfile, } from "./KlantDetailLayout.js";
|
|
11
11
|
import { DeliveryLocationsTab } from "./DeliveryLocationsTab.js";
|
|
@@ -39,6 +39,7 @@ export function KlantDetail({ id }) {
|
|
|
39
39
|
const saveNote = useServerFn(upsertCustomerNote);
|
|
40
40
|
const [tab, setTab] = useState("overview");
|
|
41
41
|
const [editOpen, setEditOpen] = useState(false);
|
|
42
|
+
const [confirm, setConfirm] = useState(null);
|
|
42
43
|
const { data, isLoading } = useQuery({
|
|
43
44
|
queryKey: ["customer", id],
|
|
44
45
|
queryFn: () => get({ data: { id } }).then(decodeDeep),
|
|
@@ -103,9 +104,20 @@ export function KlantDetail({ id }) {
|
|
|
103
104
|
if (!data)
|
|
104
105
|
return _jsx("main", { className: "p-6", children: "Klant niet gevonden" });
|
|
105
106
|
const c = data.customer;
|
|
107
|
+
const linkedToTwinfield = !!(c.twinfield_code ?? c.twinfield_debtor_code);
|
|
106
108
|
const missingTwinfield = getMissingTwinfieldFields(c);
|
|
107
109
|
const priceGroups = data.price_groups ?? c.price_groups ?? [];
|
|
108
|
-
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(BlockedBadge, { blocked: c.blocked }), _jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }), _jsxs(Button, { variant: "outline", size: "sm",
|
|
110
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(BlockedBadge, { blocked: c.blocked }), _jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(MoreHorizontal, { className: "h-4 w-4 mr-2" }), " Meer"] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onSelect: () => setConfirm("active"), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive", onSelect: () => setConfirm("delete"), children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] })] })] }), _jsx(AlertDialog, { open: confirm !== null, onOpenChange: (o) => !o && setConfirm(null), children: _jsxs(AlertDialogContent, { children: [confirm === "delete" ? (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt alleen hier verwijderd. In de boekhouding (Twinfield) blijft de klant bestaan, en in Floriday verandert niets. Dit kun je niet ongedaan maken."] })] })) : c.is_active ? (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant op inactief zetten?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt hier inactief.", " ", linkedToTwinfield
|
|
111
|
+
? "In de boekhouding (Twinfield) wordt de klant verborgen; de gegevens blijven daar bewaard."
|
|
112
|
+
: "Deze klant staat niet in de boekhouding, dus daar verandert niets.", " ", "In Floriday verandert niets. Je kunt de klant later weer activeren."] })] })) : (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant activeren?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt hier weer actief.", " ", linkedToTwinfield
|
|
113
|
+
? "In de boekhouding (Twinfield) wordt de klant weer zichtbaar."
|
|
114
|
+
: "Deze klant staat niet in de boekhouding, dus daar verandert niets.", " ", "In Floriday verandert niets."] })] })), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => {
|
|
115
|
+
if (confirm === "delete")
|
|
116
|
+
delMut.mutate();
|
|
117
|
+
else
|
|
118
|
+
toggleActive.mutate(!c.is_active);
|
|
119
|
+
setConfirm(null);
|
|
120
|
+
}, children: confirm === "delete" ? "Verwijderen" : c.is_active ? "Op inactief zetten" : "Activeren" })] })] }) })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts), notes: noteData?.note ?? null, onSaveNotes: (n) => noteMut.mutate(n), isSaving: noteMut.isPending || noteLoading }), _jsxs("div", { className: "space-y-4 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [keyData?.customerKey ? (_jsx(CustomerPriceGroupSelect, { customerKey: keyData.customerKey })) : null, priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Floriday-prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] }))] }), _jsx(KpiStrip, { counts: {
|
|
109
121
|
contacts: data.contacts.length,
|
|
110
122
|
locations: data.delivery_locations.length,
|
|
111
123
|
assortment: null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { ExternalCustomer, GroupedCustomer } from "../../lib/external-customers.functions";
|
|
2
|
+
import type { ExternalCustomer, GroupedCustomer } from "../../lib/external-customers.functions.js";
|
|
3
3
|
export type CustomerProfile = {
|
|
4
4
|
logo_url: string | null;
|
|
5
5
|
company_name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAY5D,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAChB,MAAM,wCAAwC,CAAC;AAShD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,6DAA6D;IAC7D,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C,CAAC;AA8BF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,GAAE,GAAG,EAAO,GAAG,eAAe,CAkCjF;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,eAAe,GAAG,eAAe,CAyCzE;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAiC3E;AAED,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EAAE,CAAC,EACV,KAAK,EACL,WAAW,EACX,QAAgB,GACjB,EAAE;IACD,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,+BA0MA;AAGD,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEjE;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,+BAWjI;AAED,wBAAgB,OAAO,CAAC,EACtB,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAC/C,EAAE;IACD,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACrI,+BAyBA;AAED,wBAAgB,QAAQ,CAAC,EACvB,MAAM,EAAE,QAAQ,GACjB,EAAE;IACD,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3F,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,+BA6BA;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,+BAiBrG;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,+BAgBhG;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,+BAM1D"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCustomerDefaults } from "./useCustomerDefaults.js";
|
|
2
3
|
import { useState, useEffect } from "react";
|
|
3
4
|
import { formatDateTime } from "../../lib/format-date.js";
|
|
4
5
|
import { Card, CardContent, Badge, Separator, Textarea, Button, } from "@flowselections/core";
|
|
5
6
|
import { Mail, Phone, Globe, MapPin, Hash, Building2, ChevronDown, ChevronRight, Users, Truck, Package, ShoppingCart, StickyNote, Pencil, Check, X, } from "lucide-react";
|
|
6
|
-
import { CustomerAvatar } from "../CustomerAvatar";
|
|
7
|
-
import { formatCountry, countryNameToFlag } from "../../lib/countries";
|
|
7
|
+
import { CustomerAvatar } from "../CustomerAvatar.js";
|
|
8
|
+
import { formatCountry, countryNameToFlag } from "../../lib/countries.js";
|
|
8
9
|
function formatOrgType(t) {
|
|
9
10
|
if (t === "CUSTOMER")
|
|
10
11
|
return "Koper";
|
|
@@ -19,8 +20,8 @@ function firstNonEmpty(values) {
|
|
|
19
20
|
}
|
|
20
21
|
return null;
|
|
21
22
|
}
|
|
22
|
-
import { formatPaymentMethodLabel } from "../../lib/payment-method";
|
|
23
|
-
import { FloridayNetworkStatus } from "./FloridayNetworkStatus";
|
|
23
|
+
import { formatPaymentMethodLabel } from "../../lib/payment-method.js";
|
|
24
|
+
import { FloridayNetworkStatus } from "./FloridayNetworkStatus.js";
|
|
24
25
|
function fmtAddr(a) {
|
|
25
26
|
if (!a)
|
|
26
27
|
return null;
|
|
@@ -152,6 +153,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
|
|
|
152
153
|
const countryDisplay = countryFlag ? `${countryFlag} ${p.country}` : p.country;
|
|
153
154
|
const fullAddress = [address, cityLine, countryDisplay].filter(Boolean).join(", ");
|
|
154
155
|
const [showTech, setShowTech] = useState(false);
|
|
156
|
+
const { isAdmin } = useCustomerDefaults();
|
|
155
157
|
const hasTech = !!(p.gln || p.rfh_id || p.organization_type || p.rfh_company_account_id || p.plantion_number);
|
|
156
158
|
const editable = !!onSaveNotes;
|
|
157
159
|
useEffect(() => {
|
|
@@ -162,7 +164,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
|
|
|
162
164
|
await onSaveNotes?.(draft);
|
|
163
165
|
setIsEditing(false);
|
|
164
166
|
};
|
|
165
|
-
return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] }), p.floriday_organization_id && _jsx(FloridayNetworkStatus, { organizationId: p.floriday_organization_id })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), (p.payment_providers?.length || p.mailing_address) ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [p.payment_providers && p.payment_providers.length > 0 && (_jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Financi\u00EBle afhandeling", value: p.payment_providers.join(", ") })), p.mailing_address && (_jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), label: "Postadres", value: p.mailing_address }))] })] })) : null, p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "
|
|
167
|
+
return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] }), p.floriday_organization_id && _jsx(FloridayNetworkStatus, { organizationId: p.floriday_organization_id })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), (p.payment_providers?.length || p.mailing_address) ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [p.payment_providers && p.payment_providers.length > 0 && (_jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Financi\u00EBle afhandeling", value: p.payment_providers.join(", ") })), p.mailing_address && (_jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), label: "Postadres", value: p.mailing_address }))] })] })) : null, p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && isAdmin === true && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Details voor beheer"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), p.rfh_company_account_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-bedrijfsaccount", value: p.rfh_company_account_id, mono: true }), p.plantion_number && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Plantion-registratienr.", value: p.plantion_number, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: formatOrgType(p.organization_type) })] }))] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(SectionLabel, { children: "Notitie" }), editable && !isEditing && (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", "aria-label": "Notitie bewerken", children: [_jsx(Pencil, { className: "h-3 w-3" }), "Bewerken"] }))] }), isEditing ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Textarea, { value: draft, onChange: (e) => setDraft(e.target.value), rows: 4, placeholder: "Voeg een korte notitie over deze klant toe...", disabled: isSaving, className: "text-sm resize-none" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
|
|
166
168
|
setDraft(notes ?? "");
|
|
167
169
|
setIsEditing(false);
|
|
168
170
|
}, disabled: isSaving, children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Annuleren"] }), _jsx(Button, { type: "button", size: "sm", onClick: handleSave, disabled: isSaving || draft === (notes ?? ""), children: isSaving ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" }), "Opslaan..."] })) : (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Opslaan"] })) })] })] })) : notes ? (_jsx("p", { className: "text-sm whitespace-pre-wrap text-foreground", children: notes })) : editable ? (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex w-full items-center gap-2 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Notitie toevoegen"] })) : (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Geen notitie"] }))] })] }) }));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from "@tanstack/react-router";
|
|
3
|
-
import { KlantDetail } from "./KlantDetail";
|
|
3
|
+
import { KlantDetail } from "./KlantDetail.js";
|
|
4
4
|
/** Route-onafhankelijk: leest `$id` uit welke route de shell ook gebruikt. */
|
|
5
5
|
export function KlantDetailRoute({ id: propId } = {}) {
|
|
6
6
|
const params = useParams({ strict: false });
|
|
@@ -5,25 +5,25 @@ import { formatDate } from "../../lib/format-date.js";
|
|
|
5
5
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import { useServerFn } from "@tanstack/react-start";
|
|
7
7
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { usePersistedState, markReturnToList, consumeReturnToList } from "../../lib/persisted-state";
|
|
9
|
-
import { formatEur } from "../../lib/format-currency";
|
|
8
|
+
import { usePersistedState, markReturnToList, consumeReturnToList } from "../../lib/persisted-state.js";
|
|
9
|
+
import { formatEur } from "../../lib/format-currency.js";
|
|
10
10
|
import { Button, Input, Card, CardContent, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Tabs, TabsList, TabsTrigger, Skeleton, } from "@flowselections/core";
|
|
11
11
|
import { Plus, Search, Users, UserCheck, UserX, AlertCircle, X, ArrowUpDown, Mail, MapPin, Upload, ChevronLeft, ChevronRight, Pencil, } from "lucide-react";
|
|
12
12
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
13
|
-
import { listCustomers } from "../../lib/customers.functions";
|
|
14
|
-
import { listExternalCustomers } from "../../lib/external-customers.functions";
|
|
15
|
-
import { listOverdueBalances } from "../../lib/overdue-invoices.functions";
|
|
16
|
-
import { listRevenueActivity } from "../../lib/customer-revenue.functions";
|
|
17
|
-
import { supabase } from "../../integrations/supabase/client";
|
|
18
|
-
import { decodeDeep } from "../../lib/decode-html";
|
|
19
|
-
import { friendlyError, isBackendUnavailable, retryOnTransient, retryBackoff } from "../../lib/errors";
|
|
20
|
-
import { CustomerAvatar } from "../CustomerAvatar";
|
|
21
|
-
import { PriceGroupBadges } from "./PriceGroupBadges";
|
|
22
|
-
import { KlantBewerkenDialog } from "./KlantBewerkenDialog";
|
|
23
|
-
import { ChannelBadges } from "./ChannelBadges";
|
|
24
|
-
import { derivePaymentMethod, paymentToneClasses } from "../../lib/payment-method";
|
|
25
|
-
import { formatCountry, getCountryFlagAndName, countryNameToFlag } from "../../lib/countries";
|
|
26
|
-
import { MultiSearchableSelect } from "./MultiSearchableSelect";
|
|
13
|
+
import { listCustomers } from "../../lib/customers.functions.js";
|
|
14
|
+
import { listExternalCustomers } from "../../lib/external-customers.functions.js";
|
|
15
|
+
import { listOverdueBalances } from "../../lib/overdue-invoices.functions.js";
|
|
16
|
+
import { listRevenueActivity } from "../../lib/customer-revenue.functions.js";
|
|
17
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
18
|
+
import { decodeDeep } from "../../lib/decode-html.js";
|
|
19
|
+
import { friendlyError, isBackendUnavailable, retryOnTransient, retryBackoff } from "../../lib/errors.js";
|
|
20
|
+
import { CustomerAvatar } from "../CustomerAvatar.js";
|
|
21
|
+
import { PriceGroupBadges } from "./PriceGroupBadges.js";
|
|
22
|
+
import { KlantBewerkenDialog } from "./KlantBewerkenDialog.js";
|
|
23
|
+
import { ChannelBadges } from "./ChannelBadges.js";
|
|
24
|
+
import { derivePaymentMethod, paymentToneClasses } from "../../lib/payment-method.js";
|
|
25
|
+
import { formatCountry, getCountryFlagAndName, countryNameToFlag } from "../../lib/countries.js";
|
|
26
|
+
import { MultiSearchableSelect } from "./MultiSearchableSelect.js";
|
|
27
27
|
export function KlantenOverzicht() {
|
|
28
28
|
const navigate = useNavigate();
|
|
29
29
|
const queryClient = useQueryClient();
|
|
@@ -482,7 +482,7 @@ export function KlantenOverzicht() {
|
|
|
482
482
|
queryClient.invalidateQueries({ queryKey: ["overdue-balances"] });
|
|
483
483
|
queryClient.invalidateQueries({ queryKey: ["customer-revenue-12m"] });
|
|
484
484
|
}, children: "Opnieuw proberen" })] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3", children: [_jsx(StatCard, { icon: _jsx(Users, { className: "h-5 w-5" }), tint: "muted", label: "Alle klanten", value: totals.all, tooltip: "Totaal aantal klanten (lokaal + gekoppelde bronnen).", active: view === "all", onClick: () => { setView("all"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserCheck, { className: "h-5 w-5" }), tint: "success", label: "Met omzet", value: totals.active, tooltip: revenueData
|
|
485
|
-
? `Klanten met minimaal één bestelling of factuur sinds ${formatDateNl(revenueData.since)}.\nTotale omzet in die periode: ${formatEuro(revenueData.totals.amount)} · ${revenueData.totals.transactions} transacties`
|
|
485
|
+
? `Klanten met minimaal één bestelling of factuur sinds ${formatDateNl(revenueData.since)}.\nTotale omzet in die periode: ${formatEuro(revenueData.totals.amount)} · ${revenueData.totals.transactions} ${revenueData.totals.transactions === 1 ? "transactie" : "transacties"}`
|
|
486
486
|
: "Klanten met minimaal één bestelling in de afgelopen 12 maanden.", active: view === "active", onClick: () => { setView("active"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserX, { className: "h-5 w-5" }), tint: "warning", label: "Zonder omzet", value: totals.inactive, tooltip: revenueData
|
|
487
487
|
? `Klanten zonder enige bestelling of factuur sinds ${formatDateNl(revenueData.since)}.`
|
|
488
488
|
: "Klanten zonder omzet in de afgelopen 12 maanden.", active: view === "inactive", onClick: () => { setView("inactive"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(AlertCircle, { className: "h-5 w-5" }), tint: "destructive", label: "Achterstallige betalingen", value: totals.overdue, tooltip: (overdueData?.totals
|
|
@@ -545,7 +545,7 @@ function SortHeader({ label, col, sort, onClick, }) {
|
|
|
545
545
|
}
|
|
546
546
|
function StatusPill({ active, missing }) {
|
|
547
547
|
if (missing) {
|
|
548
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium bg-destructive/10 text-destructive", title: "Deze
|
|
548
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium bg-destructive/10 text-destructive", title: "Deze klant staat niet meer in de boekhouding (Twinfield).", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-destructive" }), "Niet meer in Twinfield"] }));
|
|
549
549
|
}
|
|
550
550
|
return (_jsxs("span", { className: `inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${active
|
|
551
551
|
? "bg-primary/10 text-primary"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { friendlyError } from "../../lib/errors";
|
|
2
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { friendlyError } from "../../lib/errors";
|
|
2
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
3
3
|
import { formatDate } from "../../lib/format-date.js";
|
|
4
4
|
import { useMemo, useState } from "react";
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -7,7 +7,7 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
7
7
|
import { Card, CardContent, Badge, Input, Button, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@flowselections/core";
|
|
8
8
|
import { Percent, FileText, Users, Plus, Pencil, Trash2 } from "lucide-react";
|
|
9
9
|
import { listLocalCustomerDiscounts, deleteLocalDiscount, } from "../../lib/local-discounts.functions.js";
|
|
10
|
-
import { LocalDiscountFormDialog } from "./LocalDiscountFormDialog";
|
|
10
|
+
import { LocalDiscountFormDialog } from "./LocalDiscountFormDialog.js";
|
|
11
11
|
import { decodeDeep } from "../../lib/decode-html.js";
|
|
12
12
|
import { formatMoney } from "../../lib/format-currency.js";
|
|
13
13
|
const SOURCE_LABEL = {
|
|
@@ -8,9 +8,9 @@ import { Search, MapPin, Globe } from "lucide-react";
|
|
|
8
8
|
import { BackLink } from "./BackLink.js";
|
|
9
9
|
import { countryNameToFlag } from "../../lib/countries.js";
|
|
10
10
|
import { listFloridayNetwork } from "../../lib/customers.functions.js";
|
|
11
|
-
import { CustomerAvatar } from "../CustomerAvatar";
|
|
12
|
-
import { PriceGroupBadges } from "./PriceGroupBadges";
|
|
13
|
-
import { FloridayDeeplinkButton } from "./FloridayDeeplinkButton";
|
|
11
|
+
import { CustomerAvatar } from "../CustomerAvatar.js";
|
|
12
|
+
import { PriceGroupBadges } from "./PriceGroupBadges.js";
|
|
13
|
+
import { FloridayDeeplinkButton } from "./FloridayDeeplinkButton.js";
|
|
14
14
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
15
15
|
import { decodeDeep } from "../../lib/decode-html.js";
|
|
16
16
|
const TYPE_OPTIONS = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { friendlyError } from "../../lib/errors";
|
|
2
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
3
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { friendlyError } from "../../lib/errors";
|
|
2
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
3
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
4
4
|
import { useEffect } from "react";
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from "@tanstack/react-router";
|
|
3
|
-
import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant";
|
|
3
|
+
import { OvernemenTwinfieldKlant } from "./OvernemenTwinfieldKlant.js";
|
|
4
4
|
/** Route-onafhankelijk: leest `$code` uit welke route de shell ook gebruikt. */
|
|
5
5
|
export function OvernemenTwinfieldKlantRoute({ code: propCode } = {}) {
|
|
6
6
|
const params = useParams({ strict: false });
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useRouter } from "@tanstack/react-router";
|
|
3
3
|
import { Button, Card, CardContent } from "@flowselections/core";
|
|
4
4
|
import { AlertCircle, RefreshCw } from "lucide-react";
|
|
5
|
-
import { friendlyError } from "../../lib/errors";
|
|
5
|
+
import { friendlyError } from "../../lib/errors.js";
|
|
6
6
|
/** Nette foutweergave voor route-loaders en render-fouten binnen Klanten. */
|
|
7
7
|
export function RouteError({ error }) {
|
|
8
8
|
const router = useRouter();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"AAaA,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAsGpE"}
|
|
@@ -7,6 +7,7 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
7
7
|
import { Badge } from "@flowselections/core";
|
|
8
8
|
import { CheckCircle2, Clock, AlertTriangle, Loader2 } from "lucide-react";
|
|
9
9
|
import { getTwinfieldSyncStatus, retryTwinfieldPush, } from "../../lib/accounting/twinfield/worker.functions.js";
|
|
10
|
+
import { friendlyTwinfieldError, GENERIC_BOOKKEEPING_ERROR } from "../../lib/friendly-twinfield-error.js";
|
|
10
11
|
export function SyncStatusChip({ customerId }) {
|
|
11
12
|
const qc = useQueryClient();
|
|
12
13
|
const [retrying, setRetrying] = useState(false);
|
|
@@ -49,16 +50,16 @@ export function SyncStatusChip({ customerId }) {
|
|
|
49
50
|
const failedAt = latest?.updated_at ?? latest?.created_at ?? null;
|
|
50
51
|
const failedLongEnough = failedAt && Date.now() - new Date(failedAt).getTime() > FOUT_ZICHTBAAR_NA_MS;
|
|
51
52
|
if ((status === "failed" || status === "dead") && failedLongEnough) {
|
|
52
|
-
const reason = latest?.last_error ?? customer?.twinfield_sync_error;
|
|
53
|
-
return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex max-w-full items-center gap-1.5 text-xs", title: reason ??
|
|
53
|
+
const reason = friendlyTwinfieldError(latest?.last_error ?? customer?.twinfield_sync_error);
|
|
54
|
+
return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex max-w-full items-center gap-1.5 text-xs", title: reason ?? GENERIC_BOOKKEEPING_ERROR, children: _jsxs(Badge, { variant: "destructive", className: "gap-1 max-w-full", children: [retrying ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(AlertTriangle, { className: "h-3 w-3" }), _jsx("span", { className: "truncate", children: "Niet bijgewerkt in de boekhouding \u2014 opnieuw proberen" })] }) }));
|
|
54
55
|
}
|
|
55
56
|
if (status === "pending" || status === "processing" || status === "failed" || status === "dead") {
|
|
56
|
-
const waiting = latest?.last_error
|
|
57
|
+
const waiting = friendlyTwinfieldError(latest?.last_error);
|
|
57
58
|
return (_jsxs(Badge, { variant: "secondary", className: "gap-1 max-w-full", title: waiting ?? undefined, children: [status === "processing" ? (_jsx(Loader2, { className: "h-3 w-3 animate-spin" })) : (_jsx(Clock, { className: "h-3 w-3" })), _jsx("span", { className: "truncate", children: "Wordt bijgewerkt" })] }));
|
|
58
59
|
}
|
|
59
60
|
if (synced) {
|
|
60
61
|
const when = lastPushAt ? formatDateTime(lastPushAt) : null;
|
|
61
|
-
const warning = customer?.twinfield_sync_error;
|
|
62
|
+
const warning = friendlyTwinfieldError(customer?.twinfield_sync_error);
|
|
62
63
|
return (_jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [_jsxs(Badge, { variant: "outline", className: "gap-1 text-success-soft-foreground border-success bg-success-soft", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "Gesynchroniseerd", code ? ` · Twinfield ${code}` : "", when ? ` · laatst verstuurd ${when}` : ""] }), warning && (_jsxs(Badge, { variant: "secondary", className: "gap-1 max-w-full", title: warning, children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), _jsx("span", { className: "truncate", children: warning })] }))] }));
|
|
63
64
|
}
|
|
64
65
|
return null;
|