@flowselections/floriday-voorraad 1.0.150 → 1.0.154
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/fs-ui/DataTable.d.ts +42 -0
- package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/DataTable.js +68 -0
- package/dist-lib/components/fs-ui/EmptyState.d.ts +9 -0
- package/dist-lib/components/fs-ui/EmptyState.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/EmptyState.js +5 -0
- package/dist-lib/components/fs-ui/FilterChips.d.ts +13 -0
- package/dist-lib/components/fs-ui/FilterChips.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/FilterChips.js +8 -0
- package/dist-lib/components/fs-ui/FilterPanel.d.ts +19 -0
- package/dist-lib/components/fs-ui/FilterPanel.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/FilterPanel.js +10 -0
- package/dist-lib/components/fs-ui/FiltersButton.d.ts +6 -0
- package/dist-lib/components/fs-ui/FiltersButton.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/FiltersButton.js +7 -0
- package/dist-lib/components/fs-ui/HeaderButton.d.ts +16 -0
- package/dist-lib/components/fs-ui/HeaderButton.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/HeaderButton.js +10 -0
- package/dist-lib/components/fs-ui/HoverTip.d.ts +15 -0
- package/dist-lib/components/fs-ui/HoverTip.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/HoverTip.js +24 -0
- package/dist-lib/components/fs-ui/InlineAlert.d.ts +12 -0
- package/dist-lib/components/fs-ui/InlineAlert.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/InlineAlert.js +15 -0
- package/dist-lib/components/fs-ui/KpiRow.d.ts +10 -0
- package/dist-lib/components/fs-ui/KpiRow.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/KpiRow.js +8 -0
- package/dist-lib/components/fs-ui/KpiTile.d.ts +21 -0
- package/dist-lib/components/fs-ui/KpiTile.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/KpiTile.js +40 -0
- package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts +13 -0
- package/dist-lib/components/fs-ui/MobilePrimaryAction.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/MobilePrimaryAction.js +10 -0
- package/dist-lib/components/fs-ui/NoticeBar.d.ts +10 -0
- package/dist-lib/components/fs-ui/NoticeBar.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/NoticeBar.js +16 -0
- package/dist-lib/components/fs-ui/PageHeader.d.ts +19 -0
- package/dist-lib/components/fs-ui/PageHeader.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/PageHeader.js +9 -0
- package/dist-lib/components/fs-ui/Pagination.d.ts +10 -0
- package/dist-lib/components/fs-ui/Pagination.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/Pagination.js +10 -0
- package/dist-lib/components/fs-ui/PillTabs.d.ts +17 -0
- package/dist-lib/components/fs-ui/PillTabs.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/PillTabs.js +28 -0
- package/dist-lib/components/fs-ui/RowMenu.d.ts +13 -0
- package/dist-lib/components/fs-ui/RowMenu.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/RowMenu.js +7 -0
- package/dist-lib/components/fs-ui/SearchInput.d.ts +11 -0
- package/dist-lib/components/fs-ui/SearchInput.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/SearchInput.js +55 -0
- package/dist-lib/components/fs-ui/ShortcutHelp.d.ts +8 -0
- package/dist-lib/components/fs-ui/ShortcutHelp.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/ShortcutHelp.js +6 -0
- package/dist-lib/components/fs-ui/StatusIcon.d.ts +13 -0
- package/dist-lib/components/fs-ui/StatusIcon.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/StatusIcon.js +15 -0
- package/dist-lib/components/fs-ui/TableSkeleton.d.ts +7 -0
- package/dist-lib/components/fs-ui/TableSkeleton.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/TableSkeleton.js +6 -0
- package/dist-lib/components/fs-ui/Toolbar.d.ts +12 -0
- package/dist-lib/components/fs-ui/Toolbar.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/Toolbar.js +8 -0
- package/dist-lib/components/fs-ui/UpdatedAt.d.ts +10 -0
- package/dist-lib/components/fs-ui/UpdatedAt.d.ts.map +1 -0
- package/dist-lib/components/fs-ui/UpdatedAt.js +18 -0
- package/dist-lib/components/voorraad/ArtikelCompositionCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelCompositionCard.js +46 -15
- package/dist-lib/components/voorraad/ArtikelWizard.js +4 -4
- package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts +7 -6
- package/dist-lib/components/voorraad/FloridayConnectionNotice.d.ts.map +1 -1
- package/dist-lib/components/voorraad/FloridayConnectionNotice.js +15 -12
- package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/FustenPage.js +92 -26
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -1
- package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +3 -1
- package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/OrderHistoryCard.js +26 -50
- package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/PublicatiesPage.js +42 -36
- package/dist-lib/components/voorraad/Sneltoetsen.d.ts +14 -0
- package/dist-lib/components/voorraad/Sneltoetsen.d.ts.map +1 -0
- package/dist-lib/components/voorraad/Sneltoetsen.js +44 -0
- package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts +42 -1
- package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockIntegrityAlert.js +38 -7
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +10 -46
- package/dist-lib/components/voorraad/StockPushMonitor.js +2 -2
- package/dist-lib/components/voorraad/TelmomentButton.d.ts +5 -1
- package/dist-lib/components/voorraad/TelmomentButton.d.ts.map +1 -1
- package/dist-lib/components/voorraad/TelmomentButton.js +13 -6
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +11 -40
- package/dist-lib/components/voorraad/ValidationPage.d.ts +1 -0
- package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationPage.js +33 -5
- package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts +4 -1
- package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadBeheerCard.js +26 -5
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +67 -38
- package/dist-lib/components/voorraad/VoorraadSturingCard.js +1 -1
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -1
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +42 -19
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -1
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +1 -1
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -1
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +1 -1
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +8 -2
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -1
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +62 -27
- package/dist-lib/hooks/useLocalTradeItems.d.ts +14 -0
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +5 -3
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +6 -3
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +16 -8
- package/dist-lib/hooks/useVoorraadLijst.d.ts +10 -0
- package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadLijst.js +22 -4
- package/dist-lib/integrations/supabase/types.d.ts +85 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/alle-rijen.d.ts +15 -0
- package/dist-lib/lib/alle-rijen.d.ts.map +1 -0
- package/dist-lib/lib/alle-rijen.js +17 -0
- package/dist-lib/lib/db-error-message.d.ts.map +1 -1
- package/dist-lib/lib/db-error-message.js +3 -1
- package/dist-lib/lib/labels.d.ts +42 -0
- package/dist-lib/lib/labels.d.ts.map +1 -0
- package/dist-lib/lib/labels.js +152 -0
- package/dist-lib/lib/notatie.d.ts +4 -0
- package/dist-lib/lib/notatie.d.ts.map +1 -0
- package/dist-lib/lib/notatie.js +18 -0
- package/dist-lib/lib/voorraad-lijst.d.ts +2 -0
- package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -1
- package/dist-lib/lib/voorraad-lijst.js +14 -3
- package/package.json +2 -1
- package/sql/floriday-voorraad-schema.sql +315 -39
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { Boxes, Minus, Plus, RefreshCw,
|
|
4
|
-
import { Button,
|
|
3
|
+
import { AlertTriangle, Boxes, CalendarClock, Layers, Minus, Plus, RefreshCw, TrendingDown, PackageX, CircleSlash } from "lucide-react";
|
|
4
|
+
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, toast } from "@flowselections/core";
|
|
5
|
+
import { fustReden } from "../../lib/labels.js";
|
|
6
|
+
import { datum, getal } from "../../lib/notatie.js";
|
|
7
|
+
import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
|
|
8
|
+
import { NoticeBar } from "../fs-ui/NoticeBar.js";
|
|
9
|
+
import { KpiRow } from "../fs-ui/KpiRow.js";
|
|
10
|
+
import { KpiTile } from "../fs-ui/KpiTile.js";
|
|
11
|
+
import { PillTabs } from "../fs-ui/PillTabs.js";
|
|
12
|
+
import { Toolbar } from "../fs-ui/Toolbar.js";
|
|
13
|
+
import { SearchInput } from "../fs-ui/SearchInput.js";
|
|
14
|
+
import { FiltersButton } from "../fs-ui/FiltersButton.js";
|
|
15
|
+
import { FilterChips } from "../fs-ui/FilterChips.js";
|
|
16
|
+
import { FilterPanel, FilterGroup } from "../fs-ui/FilterPanel.js";
|
|
17
|
+
import { DataTable } from "../fs-ui/DataTable.js";
|
|
18
|
+
import { EmptyState } from "../fs-ui/EmptyState.js";
|
|
19
|
+
import { Pagination } from "../fs-ui/Pagination.js";
|
|
20
|
+
import { HoverTip } from "../fs-ui/HoverTip.js";
|
|
21
|
+
import { RowMenu } from "../fs-ui/RowMenu.js";
|
|
22
|
+
import { HeaderButton } from "../fs-ui/HeaderButton.js";
|
|
23
|
+
import { Sneltoetsen } from "./Sneltoetsen.js";
|
|
24
|
+
const BRON_LABEL = { alles: "Alle bronnen", vbn: "Standaard fust", leverancier: "Leverancier" };
|
|
5
25
|
import { FustStartmomentCard, FustInstellingenCell } from "./FustAdministratie.js";
|
|
6
26
|
import { fustNaam, fustTekorten, saldoBijhouden } from "../../lib/fust-administratie.js";
|
|
7
27
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
8
28
|
import { useFusten } from "../../hooks/useFusten.js";
|
|
9
29
|
import { fetchFloricodeFustMap, fetchFloridayCustomPackageMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
|
|
30
|
+
import { PageHeader } from "../fs-ui/PageHeader.js";
|
|
10
31
|
/** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
|
|
11
32
|
function DimensionCell({ fust, floricode, customPackages, onSave, }) {
|
|
12
33
|
const [editing, setEditing] = useState(false);
|
|
@@ -22,7 +43,7 @@ function DimensionCell({ fust, floricode, customPackages, onSave, }) {
|
|
|
22
43
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
23
44
|
};
|
|
24
45
|
if (!editing) {
|
|
25
|
-
return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "
|
|
46
|
+
return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "Standaardmaat" : resolved.source === "floriday" ? "Floriday eigen fust" : "handmatig" })), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
|
|
26
47
|
setDraft({
|
|
27
48
|
length_mm: fust.length_mm?.toString() ?? "",
|
|
28
49
|
width_mm: fust.width_mm?.toString() ?? "",
|
|
@@ -56,20 +77,20 @@ function MutationHistory({ fust }) {
|
|
|
56
77
|
}
|
|
57
78
|
};
|
|
58
79
|
return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: toggle, className: "text-xs text-primary hover:underline", children: open ? "Historie verbergen" : "Historie" }), open && (_jsxs("div", { className: "mt-2 rounded-md border border-border bg-muted/30 p-2 text-xs", children: [rows === null && _jsx("p", { className: "text-muted-foreground", children: "Laden\u2026" }), rows !== null && rows.length === 0 && (_jsx("p", { className: "text-muted-foreground", children: "Nog geen mutaties." })), rows !== null &&
|
|
59
|
-
rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children:
|
|
60
|
-
? "Verkoop"
|
|
61
|
-
: m.reason === "sale_cancelled"
|
|
62
|
-
? "Order geannuleerd"
|
|
63
|
-
: m.reason === "manual_sale"
|
|
64
|
-
? "Handmatige verkoop"
|
|
65
|
-
: m.reason === "startmoment"
|
|
66
|
-
? "Startmoment / beginstand"
|
|
67
|
-
: "Handmatig", m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-rose-600" : "text-emerald-600"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
|
|
80
|
+
rows.map((m) => (_jsxs("div", { className: "flex items-center justify-between gap-3 py-0.5", children: [_jsx("span", { className: "text-muted-foreground", children: datum(m.created_at, true) }), _jsxs("span", { className: "flex-1 truncate", children: [fustReden(m.reason), m.note ? ` — ${m.note}` : ""] }), _jsx("span", { className: `tabular-nums font-medium ${m.delta < 0 ? "text-destructive" : "text-primary"}`, children: m.delta > 0 ? `+${m.delta}` : m.delta })] }, m.id)))] }))] }));
|
|
68
81
|
}
|
|
69
82
|
export function FustenPage() {
|
|
70
83
|
const { activeId } = useFloridayConnection();
|
|
71
84
|
const { items, loading, syncing, error, sync, adjust, patch } = useFusten(activeId);
|
|
72
85
|
const [search, setSearch] = useState("");
|
|
86
|
+
const [tab, setTab] = useState("alles");
|
|
87
|
+
const [bron, setBron] = useState("alles");
|
|
88
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
89
|
+
const [startOpen, setStartOpen] = useState(false);
|
|
90
|
+
const [detailId, setDetailId] = useState(null);
|
|
91
|
+
const [pagina, setPagina] = useState(1);
|
|
92
|
+
const [grootte, setGrootte] = useState(25);
|
|
93
|
+
const [bijgewerkt, setBijgewerkt] = useState(null);
|
|
73
94
|
const [floricode, setFloricode] = useState(null);
|
|
74
95
|
const [floricodeError, setFloricodeError] = useState(null);
|
|
75
96
|
useEffect(() => {
|
|
@@ -84,14 +105,29 @@ export function FustenPage() {
|
|
|
84
105
|
const [busyId, setBusyId] = useState(null);
|
|
85
106
|
const [amounts, setAmounts] = useState({});
|
|
86
107
|
const negative = useMemo(() => fustTekorten(items), [items]);
|
|
108
|
+
const onderMin = (f) => f.minimum_stock > 0 && f.stock < f.minimum_stock;
|
|
109
|
+
const tekort = (f) => saldoBijhouden(f) && f.stock < 0;
|
|
87
110
|
const filtered = useMemo(() => {
|
|
88
111
|
const q = search.trim().toLowerCase();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
112
|
+
return items.filter((f) => {
|
|
113
|
+
if (tab === "onder_minimum" && !onderMin(f))
|
|
114
|
+
return false;
|
|
115
|
+
if (tab === "tekort" && !tekort(f))
|
|
116
|
+
return false;
|
|
117
|
+
if (tab === "niet_in_gebruik" && f.in_use)
|
|
118
|
+
return false;
|
|
119
|
+
if (bron === "leverancier" && f.source !== "SUPPLIER")
|
|
120
|
+
return false;
|
|
121
|
+
if (bron === "vbn" && f.source === "SUPPLIER")
|
|
122
|
+
return false;
|
|
123
|
+
if (!q)
|
|
124
|
+
return true;
|
|
125
|
+
return fustNaam(f).toLowerCase().includes(q) || f.name.toLowerCase().includes(q) || String(f.vbn_code ?? "").includes(q) || f.fust_key.toLowerCase().includes(q);
|
|
126
|
+
});
|
|
127
|
+
}, [items, search, tab, bron]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
128
|
+
useEffect(() => setPagina(1), [search, tab, bron, grootte]);
|
|
129
|
+
useEffect(() => { if (!loading)
|
|
130
|
+
setBijgewerkt(new Date()); }, [loading, items]);
|
|
95
131
|
const totals = useMemo(() => ({
|
|
96
132
|
types: items.length,
|
|
97
133
|
stock: items.reduce((sum, f) => sum + f.stock, 0),
|
|
@@ -114,11 +150,41 @@ export function FustenPage() {
|
|
|
114
150
|
setBusyId(null);
|
|
115
151
|
}
|
|
116
152
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
const tabs = [
|
|
154
|
+
{ value: "alles", label: "Alles", icon: _jsx(Layers, {}), count: items.length },
|
|
155
|
+
{ value: "onder_minimum", label: "Onder minimum", icon: _jsx(TrendingDown, {}), count: totals.belowMin },
|
|
156
|
+
{ value: "tekort", label: "Tekort", icon: _jsx(AlertTriangle, {}), count: negative.length },
|
|
157
|
+
{ value: "niet_in_gebruik", label: "Niet in gebruik", icon: _jsx(CircleSlash, {}), count: items.filter((f) => !f.in_use).length },
|
|
158
|
+
];
|
|
159
|
+
const TAB_ICON = { alles: _jsx(Layers, {}), onder_minimum: _jsx(TrendingDown, {}), tekort: _jsx(AlertTriangle, {}), niet_in_gebruik: _jsx(CircleSlash, {}) };
|
|
160
|
+
const notices = [
|
|
161
|
+
...(error ? ["Fusten laden is niet gelukt. Probeer het opnieuw."] : []),
|
|
162
|
+
...(floricodeError ? ["De standaardmaten van fusten konden niet geladen worden. Alleen zelf ingevoerde maten worden getoond."] : []),
|
|
163
|
+
...(negative.length > 0 ? [
|
|
164
|
+
_jsxs("span", { role: "button", tabIndex: 0, className: "hover:underline", onClick: (e) => { e.stopPropagation(); setTab("tekort"); }, children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort sinds het startmoment"] }, "tekort"),
|
|
165
|
+
] : []),
|
|
166
|
+
];
|
|
167
|
+
const detail = items.find((f) => f.id === detailId) ?? null;
|
|
168
|
+
const zichtbaar = filtered.slice((pagina - 1) * grootte, pagina * grootte);
|
|
169
|
+
const chips = [
|
|
170
|
+
...(tab !== "alles" ? [{ key: "tab", label: tabs.find((t) => t.value === tab).label, icon: TAB_ICON[tab], onRemove: () => setTab("alles") }] : []),
|
|
171
|
+
...(bron !== "alles" ? [{ key: "bron", label: BRON_LABEL[bron], onRemove: () => setBron("alles") }] : []),
|
|
172
|
+
];
|
|
173
|
+
return (_jsxs("main", { className: "flex w-full min-w-0 flex-col gap-4 overflow-x-clip p-4 pb-[calc(7rem+env(safe-area-inset-bottom))] sm:p-6 sm:pb-24", children: [_jsx(Sneltoetsen, {}), _jsx(PageHeader, { title: "Fusten", updated: _jsx(UpdatedAt, { at: bijgewerkt, info: "Fusttypen komen vanzelf uit de beladingen van je artikelen." }), actions: _jsxs(_Fragment, { children: [_jsx(HeaderButton, { icon: _jsx(CalendarClock, {}), label: "Startmoment", onClick: () => setStartOpen(true) }), _jsx(HeaderButton, { icon: _jsx(RefreshCw, { className: syncing ? "animate-spin motion-reduce:animate-none" : "" }), label: "Bijwerken", onClick: () => void sync(), disabled: syncing })] }) }), _jsx(NoticeBar, { items: notices }), _jsxs(KpiRow, { children: [_jsx(KpiTile, { title: "Fusttypen", icon: _jsx(Boxes, {}), loading: loading, value: getal(totals.types), sub: "Uit beladingen", onClick: () => setTab("alles"), active: false }), _jsx(KpiTile, { title: "Totale fustvoorraad", icon: _jsx(Layers, {}), loading: loading, value: getal(totals.stock), sub: "Fust" }), _jsx(KpiTile, { title: "Onder minimum", icon: _jsx(TrendingDown, {}), loading: loading, tone: totals.belowMin > 0 ? "warning" : "default", value: getal(totals.belowMin), sub: "Fusttypen", onClick: () => setTab("onder_minimum"), active: tab === "onder_minimum" }), _jsx(KpiTile, { title: "Tekort", icon: _jsx(PackageX, {}), loading: loading, tone: negative.length > 0 ? "warning" : "default", value: getal(negative.length), sub: "Sinds het startmoment", onClick: () => setTab("tekort"), active: tab === "tekort" })] }), _jsx(PillTabs, { tabs: tabs, value: tab, onChange: setTab, label: "Fusten filteren" }), _jsx(Toolbar, { filters: _jsx(FiltersButton, { count: bron !== "alles" ? 1 : 0, onClick: () => setFilterOpen(true) }), chips: _jsx(FilterChips, { chips: chips, onClearAll: () => { setTab("alles"); setBron("alles"); } }), children: _jsx(SearchInput, { value: search, onChange: setSearch, placeholder: "Zoek fust op naam of code" }) }), _jsx(DataTable, { rows: zichtbaar, getId: (f) => f.id, loading: loading, onOpen: (f) => setDetailId(f.id), openLabel: "Fust openen", empty: _jsx(EmptyState, { icon: _jsx(Boxes, { className: "h-6 w-6" }), title: items.length === 0 ? "Nog geen fusttypen" : "Niets gevonden", text: items.length === 0 ? "Voeg een belading toe aan een artikel; het fust verschijnt dan vanzelf." : "Geen fusten in deze weergave." }), rowActions: (f) => (_jsx(RowMenu, { label: fustNaam(f), items: [
|
|
174
|
+
{ label: "Details en instellingen", onClick: () => setDetailId(f.id) },
|
|
175
|
+
] })), columns: [
|
|
176
|
+
{ key: "fust", header: "Fust", width: "240px", render: (f) => (_jsxs("div", { className: "min-w-0", children: [_jsx(HoverTip, { label: fustNaam(f), content: _jsxs("div", { className: "text-xs", children: [fustNaam(f), f.eigen_naam?.trim() ? ` (${f.name})` : "", f.vbn_code ? ` · code ${f.vbn_code}` : ""] }), align: "start", className: "block max-w-full truncate text-left", children: _jsx("span", { className: "block truncate font-medium", children: fustNaam(f) }) }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [f.source === "SUPPLIER" ? "Leverancier" : "Standaard fust", f.in_use ? "" : " · niet meer in gebruik"] })] })) },
|
|
177
|
+
{ key: "afmeting", header: "Afmeting", width: "160px", render: (f) => {
|
|
178
|
+
const r = resolveFustDimensions(f, floricode, customPackages);
|
|
179
|
+
const l = formatFustDimensions(r);
|
|
180
|
+
return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate " + (l ? "tabular-nums" : "text-muted-foreground"), children: l ?? "Onbekend" }), r.source !== "onbekend" && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: r.source === "floricode" ? "Standaardmaat" : r.source === "floriday" ? "Eigen fust uit Floriday" : "Handmatig" })] }));
|
|
181
|
+
} },
|
|
182
|
+
{ key: "gebruikt", header: "Gebruikt door", width: "128px", render: (f) => _jsxs("span", { className: "tabular-nums", children: [getal(f.used_by_count), " ", _jsx("span", { className: "text-xs text-muted-foreground", children: "artikelen" })] }) },
|
|
183
|
+
{ key: "voorraad", header: "Voorraad", width: "152px", render: (f) => (_jsxs("div", { className: "min-w-0", children: [tekort(f)
|
|
184
|
+
? _jsxs("div", { className: "truncate font-semibold tabular-nums text-destructive", children: ["Tekort ", getal(Math.abs(f.stock)), " fust"] })
|
|
185
|
+
: _jsxs("div", { className: "truncate font-semibold tabular-nums " + (onderMin(f) ? "text-destructive" : ""), children: [getal(f.stock), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "fust" })] }), !saldoBijhouden(f) && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: "Saldo niet bijgehouden" })] })) },
|
|
186
|
+
{ key: "minimum", header: "Minimum", width: "120px", render: (f) => (_jsx(Input, { type: "number", "aria-label": `Minimum ${fustNaam(f)}`, className: "h-8 w-24", defaultValue: f.minimum_stock, onClick: (e) => e.stopPropagation(), onBlur: (e) => { const v = Math.max(0, Math.trunc(Number(e.target.value) || 0)); if (v !== f.minimum_stock)
|
|
187
|
+
void patch(f.id, { minimum_stock: v }); } })) },
|
|
188
|
+
{ key: "aanpassen", header: "Aanpassen", width: "184px", render: (f) => (_jsxs("div", { className: "flex items-center gap-1", onClick: (e) => e.stopPropagation(), children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", "aria-label": "Afboeken", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, -1), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx(Input, { className: "h-8 w-20 text-center", type: "number", placeholder: "Aantal", "aria-label": `Aantal ${fustNaam(f)}`, value: amounts[f.id] ?? "", onChange: (e) => setAmounts((a) => ({ ...a, [f.id]: e.target.value })) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-8 w-8", "aria-label": "Bijboeken", disabled: busyId === f.id || !(Number(amounts[f.id]) > 0), onClick: () => void mutate(f, 1), children: _jsx(Plus, { className: "h-4 w-4" }) })] })) },
|
|
189
|
+
] }), _jsx(Pagination, { page: pagina, pageSize: grootte, totalCount: filtered.length, onPage: setPagina, onPageSize: setGrootte }), _jsx(FilterPanel, { open: filterOpen, onOpenChange: setFilterOpen, onClear: () => setBron("alles"), children: _jsx(FilterGroup, { title: "Bron", value: bron, onChange: setBron, options: Object.keys(BRON_LABEL).map((b) => ({ value: b, label: BRON_LABEL[b] })) }) }), _jsx(Dialog, { open: startOpen, onOpenChange: setStartOpen, children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Startmoment" }) }), _jsx(FustStartmomentCard, { items: items, onSaved: () => void sync() })] }) }), _jsx(Dialog, { open: !!detail, onOpenChange: (o) => !o && setDetailId(null), children: _jsxs(DialogContent, { className: "left-auto right-0 top-0 h-[100dvh] max-w-md translate-x-0 translate-y-0 overflow-auto rounded-none sm:rounded-none", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: detail ? fustNaam(detail) : "" }) }), detail && (_jsxs("div", { className: "space-y-5 text-sm", children: [_jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Naam en saldo" }), _jsx(FustInstellingenCell, { fust: detail, onSaved: () => void sync() })] }), _jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Afmeting" }), _jsx(DimensionCell, { fust: detail, floricode: floricode, customPackages: customPackages, onSave: (v) => patch(detail.id, v) })] }), _jsxs("section", { className: "space-y-1", children: [_jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Boekingen" }), _jsx(MutationHistory, { fust: detail })] })] }))] }) })] }));
|
|
124
190
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,gBAAgB,gCAoY/B"}
|
|
@@ -20,6 +20,7 @@ import { saveOwnPackings } from "../../lib/product-packings.js";
|
|
|
20
20
|
import { assertArticleCode } from "../../lib/article-code.js";
|
|
21
21
|
import { EMPTY_SIMPLE_PACKING, engineOordeelRegel, missingPackingFields, simplePackingToRow, } from "../../lib/simple-packing.js";
|
|
22
22
|
import { resolveEngineCarrier } from "../../lib/engine-carrier.js";
|
|
23
|
+
import { PageHeader } from "../fs-ui/PageHeader.js";
|
|
23
24
|
const STEPS = ["Naam en code", "Voorraad", "Prijs", "Belading"];
|
|
24
25
|
export function NieuwArtikelPage() {
|
|
25
26
|
const carriers = useLoadCarriers();
|
|
@@ -186,5 +187,5 @@ export function NieuwArtikelPage() {
|
|
|
186
187
|
? "rounded-md border p-3 text-sm"
|
|
187
188
|
: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: [_jsxs("p", { className: "font-medium", children: ["Controle karindeling: ", oordeel.tekst] }), oordeel.regels.length > 0 && (_jsx("ul", { className: "list-disc pl-5", children: oordeel.regels.map((r) => (_jsx("li", { children: r }, r))) }))] })), hasFloriday ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Wil je het artikel ook op Floriday zetten? Daarvoor is nog nodig:" }), _jsxs("ul", { className: "list-disc pl-5 text-sm", children: [!hasPrice && _jsx("li", { children: "Prijs per stuk (excl. btw)" }), missing.map((m) => (_jsx("li", { children: m }, m)))] })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "De voorraad is opgeslagen als eigen voorraad. Die gaat niet naar Floriday." })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [hasFloriday && (_jsxs(Button, { onClick: () => navigate({ to: "/voorraad/$id", params: { id: createdId } }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Op Floriday zetten"] })), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/voorraad", children: "Terug naar voorraad" }) })] })] }) }));
|
|
188
189
|
}
|
|
189
|
-
return (_jsxs("main", { className: "
|
|
190
|
+
return (_jsxs("main", { className: "flex max-w-3xl flex-col gap-4 p-4 pb-24 sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { crumb: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), " Voorraad"] }), title: "Artikel toevoegen", meta: _jsxs("span", { children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] }) }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), _jsxs(Card, { className: "space-y-4 rounded-xl p-4 shadow-sm sm:p-5", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: canSaveStock ? stock : "", onChange: (e) => setStock(e.target.value), disabled: !canSaveStock, "aria-describedby": "na-stock-help", autoFocus: true }), !canSaveStock && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: "Koppeling wordt gecontroleerd\u2026" })), canSaveStock && !hasFloriday && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT }))] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), step === 3 && (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Zonder volledige belading kan de karindeling dit artikel niet plannen. Alle vijf velden zijn verplicht." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-fust", children: "Fust (VBN-code)" }), _jsxs("select", { id: "na-fust", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value), children: [_jsx("option", { value: "", children: "Kies een fust\u2026" }), fusts.map((f) => (_jsxs("option", { value: String(f.vbn), children: [f.vbn, " \u2013 ", f.name] }, f.vbn)))] }), _jsx(Input, { "aria-label": "Andere VBN-fustcode", placeholder: "Of typ een VBN-fustcode, bijv. 412", inputMode: "numeric", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-ppc", children: "Planten per fust" }), _jsx(Input, { id: "na-ppc", inputMode: "numeric", value: packing.piecesPerPackage, onChange: (e) => setPack("piecesPerPackage", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-cpl", children: "Fusten per laag" }), _jsx(Input, { id: "na-cpl", inputMode: "numeric", value: packing.packagesPerLayer, onChange: (e) => setPack("packagesPerLayer", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lpc", children: "Lagen per kar" }), _jsx(Input, { id: "na-lpc", inputMode: "numeric", value: packing.layersPerLoadCarrier, onChange: (e) => setPack("layersPerLoadCarrier", e.target.value) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lc", children: "Ladingdrager" }), _jsxs("select", { id: "na-lc", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.loadCarrier, onChange: (e) => setPack("loadCarrier", e.target.value), children: [_jsx("option", { value: "", children: "Kies een ladingdrager\u2026" }), carriers.items.map((c) => (_jsx("option", { value: c.code, children: c.naam }, c.code)))] }), carriers.error && _jsx("p", { className: "text-xs text-destructive", children: carriers.error }), engineCarrier.melding && _jsx("p", { className: "text-xs text-destructive", children: engineCarrier.melding })] }), missingPacking.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Nog invullen: ", missingPacking.join(", "), "."] }))] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim() || !packingReady, onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), hasFloriday && (_jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" }))] }));
|
|
190
191
|
}
|
|
@@ -4,7 +4,9 @@ interface Props {
|
|
|
4
4
|
productId?: string | null;
|
|
5
5
|
/** Artikelen met dezelfde voorraadsleutel, uit voorraad_artikel_detail. */
|
|
6
6
|
productIds?: string[];
|
|
7
|
+
/** Stuks per fust van de hoofdverpakking (voor omrekenen naar stuks). */
|
|
8
|
+
stuksPerFust?: number | null;
|
|
7
9
|
}
|
|
8
|
-
export declare function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds }: Props): import("react").JSX.Element;
|
|
10
|
+
export declare function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds, stuksPerFust }: Props): import("react").JSX.Element;
|
|
9
11
|
export {};
|
|
10
12
|
//# sourceMappingURL=OrderHistoryCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/OrderHistoryCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OrderHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/OrderHistoryCard.tsx"],"names":[],"mappings":"AAmBA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgBD,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,KAAK,+BA6JrH"}
|
|
@@ -1,49 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { Card, CardContent
|
|
4
|
-
import { Loader2, ClipboardList, ExternalLink } from "lucide-react";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!s)
|
|
18
|
-
return "Nog te picken";
|
|
19
|
-
if (["verzonden", "geleverd", "shipped", "delivered"].includes(s))
|
|
20
|
-
return "Verzonden";
|
|
21
|
-
if (["nieuw", "open", "new", "pending"].includes(s))
|
|
22
|
-
return "Nog te picken";
|
|
23
|
-
if (["ready", "klaar", "klaar voor verzending"].includes(s))
|
|
24
|
-
return "Klaar voor verzending";
|
|
25
|
-
if (["geannuleerd", "cancelled", "canceled"].includes(s))
|
|
26
|
-
return "geannuleerd";
|
|
27
|
-
if (s === "concept")
|
|
28
|
-
return "concept";
|
|
29
|
-
if (s === "nog te picken")
|
|
30
|
-
return "Nog te picken";
|
|
31
|
-
return raw ?? "Nog te picken";
|
|
32
|
-
}
|
|
33
|
-
function formatDate(v) {
|
|
34
|
-
if (!v)
|
|
35
|
-
return "—";
|
|
36
|
-
try {
|
|
37
|
-
const d = new Date(v);
|
|
38
|
-
if (Number.isNaN(d.getTime()))
|
|
39
|
-
return v;
|
|
40
|
-
return d.toLocaleDateString("nl-NL", { day: "2-digit", month: "short", year: "numeric" });
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return v;
|
|
44
|
-
}
|
|
3
|
+
import { Card, CardContent } from "@flowselections/core";
|
|
4
|
+
import { Loader2, ClipboardList, ExternalLink, Circle, CircleDot, CheckCircle2, XCircle, CircleDashed } from "lucide-react";
|
|
5
|
+
import { orderStatus, EENHEID } from "../../lib/labels.js";
|
|
6
|
+
import { datum, getal } from "../../lib/notatie.js";
|
|
7
|
+
import { StatusIcon } from "../fs-ui/StatusIcon.js";
|
|
8
|
+
const TONE = { open: "open", bezig: "bezig", klaar: "klaar", fout: "fout", concept: "open" };
|
|
9
|
+
const ICON = { open: _jsx(Circle, {}), bezig: _jsx(CircleDot, {}), klaar: _jsx(CheckCircle2, {}), fout: _jsx(XCircle, {}), concept: _jsx(CircleDashed, {}) };
|
|
10
|
+
/** Aantal altijd in stuks; fust als tweede regel wanneer de verpakking bekend is. */
|
|
11
|
+
function stuksEnFust(qty, unit, stuksPerFust) {
|
|
12
|
+
const e = EENHEID[(unit ?? "stuks").trim().toLowerCase()] ?? "stuks";
|
|
13
|
+
const spf = stuksPerFust && stuksPerFust > 0 ? stuksPerFust : null;
|
|
14
|
+
if (e === "fust")
|
|
15
|
+
return { stuks: spf ? qty * spf : null, fust: qty };
|
|
16
|
+
return { stuks: qty, fust: spf ? Math.round((qty / spf) * 100) / 100 : null };
|
|
45
17
|
}
|
|
46
|
-
|
|
18
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
19
|
+
export function OrderHistoryCard({ tradeItemKey, productName, productId, productIds: bekendeIds, stuksPerFust }) {
|
|
47
20
|
const [rows, setRows] = useState(null);
|
|
48
21
|
const [error, setError] = useState(null);
|
|
49
22
|
useEffect(() => {
|
|
@@ -86,7 +59,7 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
|
|
|
86
59
|
}
|
|
87
60
|
const { data, error } = await supabase
|
|
88
61
|
.from("order_items")
|
|
89
|
-
.select("order_id,quantity,unit,orders!inner(id,order_number,customer_name,order_date,created_at,status,order_source,deleted_at)")
|
|
62
|
+
.select("order_id,quantity,unit,plants_per_crate,orders!inner(id,order_number,customer_name,order_date,created_at,status,order_source,deleted_at)")
|
|
90
63
|
.or(orFilters.join(","))
|
|
91
64
|
.is("orders.deleted_at", null)
|
|
92
65
|
.order("created_at", { ascending: false, foreignTable: "orders" })
|
|
@@ -102,6 +75,7 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
|
|
|
102
75
|
customer_name: r.orders?.customer_name ?? null,
|
|
103
76
|
quantity: r.quantity ?? 0,
|
|
104
77
|
unit: r.unit ?? null,
|
|
78
|
+
plants_per_crate: r.plants_per_crate == null ? null : Number(r.plants_per_crate),
|
|
105
79
|
order_date: r.orders?.order_date ?? null,
|
|
106
80
|
created_at: r.orders?.created_at ?? "",
|
|
107
81
|
status: r.orders?.status ?? null,
|
|
@@ -111,8 +85,10 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
|
|
|
111
85
|
setRows(mapped);
|
|
112
86
|
}
|
|
113
87
|
catch (e) {
|
|
114
|
-
if (!cancelled)
|
|
88
|
+
if (!cancelled) {
|
|
89
|
+
console.warn("Bestelgeschiedenis laden mislukt:", e);
|
|
115
90
|
setError(e.message);
|
|
91
|
+
}
|
|
116
92
|
}
|
|
117
93
|
};
|
|
118
94
|
load();
|
|
@@ -127,11 +103,11 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId, product
|
|
|
127
103
|
supabase.removeChannel(channel);
|
|
128
104
|
};
|
|
129
105
|
}, [tradeItemKey, productName, productId, bekendeIds?.join("|")]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
130
|
-
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
"bg-slate-100 text-slate-700 border-slate-200";
|
|
106
|
+
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsx("div", { className: "text-sm text-destructive", children: "Bestelgeschiedenis laden lukt niet. Probeer opnieuw." })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bestellingen laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen bestellingen voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border border-border", children: _jsxs("table", { className: "w-full min-w-[640px] table-fixed text-sm [&_thead_th]:h-10 [&_thead_th]:px-2 [&_thead_th]:text-left [&_thead_th]:align-middle [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: "140px" } }), _jsx("col", {}), _jsx("col", { style: { width: "132px" } }), _jsx("col", { style: { width: "120px" } }), _jsx("col", { style: { width: "184px" } }), _jsx("col", { style: { width: "44px" } })] }), _jsx("thead", { children: _jsxs("tr", { className: "border-b border-border", children: [_jsx("th", { children: "Ordernummer" }), _jsx("th", { children: "Klant" }), _jsx("th", { children: "Aantal" }), _jsx("th", { children: "Besteldatum" }), _jsx("th", { children: "Status" }), _jsx("th", { children: _jsx("span", { className: "sr-only", children: "Openen" }) })] }) }), _jsx("tbody", { children: rows.map((r, i) => {
|
|
107
|
+
const st = orderStatus(r.status);
|
|
108
|
+
const q = stuksEnFust(Number(r.quantity ?? 0), r.unit, r.plants_per_crate ?? stuksPerFust);
|
|
134
109
|
const href = `/floriday-verkoop/verkooporders/${r.order_id}`;
|
|
135
|
-
|
|
110
|
+
const cel = "truncate px-2 py-0 align-middle";
|
|
111
|
+
return (_jsxs("tr", { className: "h-[60px] border-b border-border last:border-0 hover:bg-muted/40", children: [_jsx("td", { className: cel, children: _jsx("a", { href: href, className: "font-medium text-primary hover:underline", children: r.order_number }) }), _jsx("td", { className: cel, children: r.customer_name ?? "—" }), _jsxs("td", { className: cel + " tabular-nums", children: [_jsx("div", { className: "truncate", children: q.stuks == null ? "—" : `${getal(q.stuks)} stuks` }), q.fust != null && _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [getal(q.fust), " fust"] })] }), _jsx("td", { className: cel + " tabular-nums", children: datum(r.order_date ?? r.created_at) }), _jsx("td", { className: cel, children: _jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [_jsx(StatusIcon, { tone: TONE[st.tone], icon: ICON[st.tone], label: st.label }), _jsx("span", { className: "truncate", children: st.label })] }) }), _jsx("td", { className: cel + " text-muted-foreground", children: _jsx("a", { href: href, "aria-label": "Order openen", className: "inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-muted", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5" }) }) })] }, `${r.order_id}-${i}`));
|
|
136
112
|
}) })] }) }))] })] }));
|
|
137
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PublicatiesPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PublicatiesPage.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,eAAe,gCA0F9B"}
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Link } from "@tanstack/react-router";
|
|
3
|
-
import {
|
|
4
|
-
import { ArrowLeft,
|
|
3
|
+
import { Button } from "@flowselections/core";
|
|
4
|
+
import { ArrowLeft, CheckCircle2, CircleDot, XCircle, CircleDashed, RefreshCw, Send } from "lucide-react";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
import { usePublishJobs } from "../../hooks/usePublishJob.js";
|
|
7
7
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: status === "running"
|
|
20
|
-
? "bg-muted text-muted-foreground"
|
|
21
|
-
: "bg-amber-100 text-amber-800";
|
|
22
|
-
return (_jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${tone}`, children: STATUS_LABEL[status] ?? status }));
|
|
23
|
-
}
|
|
8
|
+
import { PageHeader } from "../fs-ui/PageHeader.js";
|
|
9
|
+
import { UpdatedAt } from "../fs-ui/UpdatedAt.js";
|
|
10
|
+
import { HeaderButton } from "../fs-ui/HeaderButton.js";
|
|
11
|
+
import { DataTable } from "../fs-ui/DataTable.js";
|
|
12
|
+
import { EmptyState } from "../fs-ui/EmptyState.js";
|
|
13
|
+
import { Pagination } from "../fs-ui/Pagination.js";
|
|
14
|
+
import { StatusIcon } from "../fs-ui/StatusIcon.js";
|
|
15
|
+
import { publicatieStatus } from "../../lib/labels.js";
|
|
16
|
+
import { datum } from "../../lib/notatie.js";
|
|
17
|
+
const TONE = { open: "open", bezig: "bezig", klaar: "klaar", fout: "fout", concept: "open" };
|
|
18
|
+
const ICON = { open: _jsx(CircleDashed, {}), bezig: _jsx(CircleDot, {}), klaar: _jsx(CheckCircle2, {}), fout: _jsx(XCircle, {}), concept: _jsx(CircleDashed, {}) };
|
|
24
19
|
export function PublicatiesPage() {
|
|
25
20
|
const { jobs, loading, reload } = usePublishJobs(50);
|
|
26
21
|
const [stepsByJob, setStepsByJob] = useState({});
|
|
@@ -47,22 +42,33 @@ export function PublicatiesPage() {
|
|
|
47
42
|
cancelled = true;
|
|
48
43
|
};
|
|
49
44
|
}, [jobs]);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
const [pagina, setPagina] = useState(1);
|
|
46
|
+
const [grootte, setGrootte] = useState(25);
|
|
47
|
+
const [bijgewerkt, setBijgewerkt] = useState(null);
|
|
48
|
+
useEffect(() => { if (!loading)
|
|
49
|
+
setBijgewerkt(new Date()); }, [loading, jobs]);
|
|
50
|
+
const rijen = jobs.map((job) => {
|
|
51
|
+
const target = (job.product_id ?? job.trade_item_id);
|
|
52
|
+
// Oude "Actie nodig" sluit vanzelf zodra een latere publicatie van hetzelfde artikel (op id) wel gelukt is.
|
|
53
|
+
const repaired = job.status === "failed" &&
|
|
54
|
+
jobs.some((o) => o.id !== job.id && (o.product_id ?? o.trade_item_id) === target && target != null &&
|
|
55
|
+
new Date(o.created_at) > new Date(job.created_at) && (o.status === "succeeded" || o.status === "succeeded_with_warnings"));
|
|
56
|
+
const steps = (stepsByJob[job.id] ?? []).filter((s) => s.status !== "skipped");
|
|
57
|
+
const failed = repaired ? [] : steps.filter((s) => s.status === "failed");
|
|
58
|
+
for (const f of failed)
|
|
59
|
+
if (f.error_message)
|
|
60
|
+
console.warn("Publicatiestap mislukt:", f.label, f.error_message);
|
|
61
|
+
return { job, target, repaired, steps, failed };
|
|
62
|
+
});
|
|
63
|
+
const zichtbaar = rijen.slice((pagina - 1) * grootte, pagina * grootte);
|
|
64
|
+
return (_jsxs("main", { className: "flex w-full min-w-0 flex-col gap-4 overflow-x-clip p-4 pb-[calc(7rem+env(safe-area-inset-bottom))] sm:p-6 sm:pb-24", children: [_jsx(PageHeader, { crumb: _jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), " Voorraad"] }), title: "Naar Floriday gestuurd", updated: _jsx(UpdatedAt, { at: bijgewerkt, info: "Artikelen die je naar Floriday hebt gestuurd, met wat er eventueel nog moet gebeuren." }), actions: _jsx(HeaderButton, { icon: _jsx(RefreshCw, {}), label: "Vernieuwen", onClick: () => void reload() }) }), _jsx(DataTable, { rows: zichtbaar, getId: (r) => r.job.id, loading: loading, keyboard: false, empty: _jsx(EmptyState, { icon: _jsx(Send, { className: "h-6 w-6" }), title: "Nog niets verstuurd", text: "Er zijn nog geen artikelen naar Floriday gestuurd." }), columns: [
|
|
65
|
+
{ key: "artikel", header: "Artikel", width: "260px", render: (r) => (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: r.job.title || "Artikel" }), r.job.environment !== "live" && _jsx("div", { className: "truncate text-xs text-muted-foreground", children: "Testomgeving" })] })) },
|
|
66
|
+
{ key: "moment", header: "Verstuurd", width: "152px", render: (r) => _jsx("span", { className: "tabular-nums", children: datum(r.job.created_at, true) }) },
|
|
67
|
+
{ key: "stappen", header: "Stappen", width: "260px", render: (r) => (_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "truncate tabular-nums", children: [r.steps.filter((s) => s.status === "done").length, " van ", r.steps.length, " klaar"] }), r.failed.length > 0 && _jsx("div", { className: "truncate text-xs text-destructive", children: r.failed.map((s) => s.label).join(", ") })] })) },
|
|
68
|
+
{ key: "status", header: "Status", width: "176px", render: (r) => {
|
|
69
|
+
const st = publicatieStatus(r.repaired ? "repaired" : r.job.status);
|
|
70
|
+
return _jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [_jsx(StatusIcon, { tone: TONE[st.tone], icon: ICON[st.tone], label: st.label }), _jsx("span", { className: "truncate", children: st.label })] });
|
|
71
|
+
} },
|
|
72
|
+
{ key: "actie", header: _jsx("span", { className: "sr-only", children: "Actie" }), width: "120px", render: (r) => r.target && r.failed.length > 0 ? (_jsx(Button, { asChild: true, size: "sm", variant: "outline", children: _jsx(Link, { to: "/voorraad/$id", params: { id: String(r.target) }, search: { hervat: r.job.id }, children: "Afronden" }) })) : null },
|
|
73
|
+
] }), _jsx(Pagination, { page: pagina, pageSize: grootte, totalCount: rijen.length, onPage: setPagina, onPageSize: (n) => { setGrootte(n); setPagina(1); } })] }));
|
|
68
74
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sneltoetsen zoals in Verkooporders: N = nieuw (alleen als er een maak-actie is),
|
|
3
|
+
* ? = overzicht. "/" (zoeken), J/K/Enter (rijen) en Esc zitten in SearchInput en DataTable.
|
|
4
|
+
* Nooit tijdens typen in een invoerveld en niet terwijl er een dialoog open is.
|
|
5
|
+
*/
|
|
6
|
+
export declare function Sneltoetsen({ onNieuw, nieuwLabel }: {
|
|
7
|
+
onNieuw?: () => void;
|
|
8
|
+
nieuwLabel?: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
/** Kleine sneltoets-hint in een primaire knop, exact naar Verkooporders. */
|
|
11
|
+
export declare function Kbd({ children }: {
|
|
12
|
+
children: string;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=Sneltoetsen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sneltoetsen.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/Sneltoetsen.tsx"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,UAA4B,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,+BAsBnH;AAED,4EAA4E;AAC5E,wBAAgB,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAErD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { isTypen } from "../fs-ui/SearchInput.js";
|
|
4
|
+
import { ShortcutHelp } from "../fs-ui/ShortcutHelp.js";
|
|
5
|
+
/**
|
|
6
|
+
* Sneltoetsen zoals in Verkooporders: N = nieuw (alleen als er een maak-actie is),
|
|
7
|
+
* ? = overzicht. "/" (zoeken), J/K/Enter (rijen) en Esc zitten in SearchInput en DataTable.
|
|
8
|
+
* Nooit tijdens typen in een invoerveld en niet terwijl er een dialoog open is.
|
|
9
|
+
*/
|
|
10
|
+
export function Sneltoetsen({ onNieuw, nieuwLabel = "Nieuw artikel" }) {
|
|
11
|
+
const [open, setOpen] = useState(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const h = (e) => {
|
|
14
|
+
if (e.metaKey || e.ctrlKey || e.altKey || isTypen(e.target))
|
|
15
|
+
return;
|
|
16
|
+
if (e.key === "?") {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
setOpen(true);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (document.querySelector("[role=dialog]"))
|
|
22
|
+
return;
|
|
23
|
+
if ((e.key === "n" || e.key === "N") && onNieuw) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
onNieuw();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
window.addEventListener("keydown", h);
|
|
29
|
+
return () => window.removeEventListener("keydown", h);
|
|
30
|
+
}, [onNieuw]);
|
|
31
|
+
const shortcuts = [
|
|
32
|
+
[["/"], "Zoeken"],
|
|
33
|
+
...(onNieuw ? [[["N"], nieuwLabel]] : []),
|
|
34
|
+
[["J", "K"], "Volgende / vorige rij"],
|
|
35
|
+
[["Enter"], "Geselecteerde rij openen"],
|
|
36
|
+
[["Esc"], "Zoeken wissen / sluiten"],
|
|
37
|
+
[["?"], "Dit overzicht"],
|
|
38
|
+
];
|
|
39
|
+
return _jsx(ShortcutHelp, { open: open, onOpenChange: setOpen, shortcuts: shortcuts });
|
|
40
|
+
}
|
|
41
|
+
/** Kleine sneltoets-hint in een primaire knop, exact naar Verkooporders. */
|
|
42
|
+
export function Kbd({ children }) {
|
|
43
|
+
return _jsx("kbd", { "aria-hidden": true, className: "ml-2 hidden rounded border border-primary-foreground/30 bg-primary-foreground/15 px-1.5 font-mono text-[10px] leading-4 text-primary-foreground sm:inline-block", children: children });
|
|
44
|
+
}
|