@fayz-ai/plugin-inventory 0.1.1 → 0.1.3
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.cjs +265 -365
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +226 -326
- package/dist/index.js.map +1 -1
- package/dist/views/ProductListView.d.ts.map +1 -1
- package/dist/views/StockMovementView.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/InventoryPage.tsx +3 -3
- package/src/data/supabase.ts +2 -0
- package/src/views/ProductListView.tsx +34 -84
- package/src/views/StockMovementView.tsx +18 -16
- package/dist/views/ProductFormView.d.ts +0 -6
- package/dist/views/ProductFormView.d.ts.map +0 -1
- package/src/views/ProductFormView.tsx +0 -283
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React2 = require('react');
|
|
4
4
|
var ui = require('@fayz-ai/ui');
|
|
5
5
|
var saas = require('@fayz-ai/saas');
|
|
6
6
|
var core = require('@fayz-ai/core');
|
|
@@ -10,7 +10,7 @@ var vanilla = require('zustand/vanilla');
|
|
|
10
10
|
|
|
11
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
var ctx = saas.createPluginContext("InventoryPage");
|
|
@@ -21,339 +21,238 @@ var useInventoryStore = ctx.useStore;
|
|
|
21
21
|
function DashboardView() {
|
|
22
22
|
return /* @__PURE__ */ jsxRuntime.jsx(ui.DashboardCanvas, { surface: "plugin-home", domain: "inventory", showHeader: false, className: "space-y-6" });
|
|
23
23
|
}
|
|
24
|
-
var
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
24
|
+
var TYPE_DESCRIPTION_KEYS = {
|
|
25
|
+
ingredient: "inventory.productForm.typeIngredient",
|
|
26
|
+
sale: "inventory.productForm.typeSale",
|
|
27
|
+
intermediate: "inventory.productForm.typeIntermediate",
|
|
28
|
+
asset: "inventory.productForm.typeAsset"
|
|
29
|
+
};
|
|
30
|
+
function buildProductEntity(t2, productTypes, currency) {
|
|
31
|
+
const typeOptions = productTypes.map((pt) => ({
|
|
32
|
+
label: pt.label,
|
|
33
|
+
value: pt.value,
|
|
34
|
+
description: TYPE_DESCRIPTION_KEYS[pt.value] ? t2(TYPE_DESCRIPTION_KEYS[pt.value]) : void 0
|
|
35
|
+
}));
|
|
36
|
+
const typeLabel = (v) => productTypes.find((p) => p.value === v)?.label ?? String(v ?? "");
|
|
37
|
+
const fields = [
|
|
38
|
+
// — General Information (form) + Product column (table) —
|
|
39
|
+
{
|
|
40
|
+
key: "name",
|
|
41
|
+
label: t2("inventory.productForm.name"),
|
|
42
|
+
type: "text",
|
|
43
|
+
required: true,
|
|
44
|
+
group: "general",
|
|
45
|
+
placeholder: t2("inventory.productForm.namePlaceholder"),
|
|
46
|
+
searchable: true,
|
|
47
|
+
showInTable: true,
|
|
48
|
+
sortable: true,
|
|
49
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
50
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: row.name }),
|
|
51
|
+
row.sku && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: row.sku })
|
|
52
|
+
] })
|
|
53
|
+
},
|
|
54
|
+
{ key: "brand", label: t2("inventory.productForm.brand"), type: "text", group: "general", placeholder: t2("inventory.productForm.brandPlaceholder"), showInTable: false },
|
|
55
|
+
{ key: "sku", label: t2("inventory.productForm.sku"), type: "text", group: "general", placeholder: t2("inventory.productForm.skuPlaceholder"), searchable: true, showInTable: false },
|
|
56
|
+
{ key: "barcode", label: t2("inventory.productForm.barcode"), type: "text", group: "general", placeholder: t2("inventory.productForm.barcodePlaceholder"), searchable: true, showInTable: false },
|
|
57
|
+
{ key: "description", label: t2("inventory.productForm.description"), type: "textarea", group: "general", span: 2, placeholder: t2("inventory.productForm.descriptionPlaceholder"), showInTable: false },
|
|
58
|
+
// — Classification (form: segmented) + Type column (table: badge) —
|
|
59
|
+
{
|
|
60
|
+
key: "productType",
|
|
61
|
+
label: t2("inventory.productForm.classification"),
|
|
62
|
+
type: "segmented",
|
|
63
|
+
group: "classification",
|
|
64
|
+
span: 2,
|
|
65
|
+
options: typeOptions,
|
|
66
|
+
showInTable: true,
|
|
67
|
+
sortable: false,
|
|
68
|
+
renderCell: (v) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium bg-muted text-muted-foreground", children: typeLabel(v) })
|
|
69
|
+
},
|
|
70
|
+
// — Stock column (list only) — placed before Cost for the desired column order
|
|
71
|
+
{
|
|
72
|
+
key: "currentQuantity",
|
|
73
|
+
label: t2("inventory.productList.stock"),
|
|
74
|
+
type: "number",
|
|
75
|
+
showInForm: false,
|
|
76
|
+
showInTable: true,
|
|
77
|
+
sortable: true,
|
|
78
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-right block ${row.currentQuantity <= row.minQuantity ? "text-destructive font-medium" : ""}`, children: row.currentQuantity })
|
|
79
|
+
},
|
|
80
|
+
// — Pricing (form) + Cost column (table) —
|
|
81
|
+
{
|
|
82
|
+
key: "costPrice",
|
|
83
|
+
label: t2("inventory.productForm.costPrice"),
|
|
84
|
+
type: "currency",
|
|
85
|
+
group: "pricing",
|
|
86
|
+
currency: currency.code,
|
|
87
|
+
currencySymbol: currency.symbol,
|
|
88
|
+
currencyLocale: currency.locale,
|
|
89
|
+
showInTable: true,
|
|
90
|
+
sortable: false,
|
|
91
|
+
renderCell: (v) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block text-muted-foreground", children: saas.formatCurrency(Number(v) || 0, currency) })
|
|
92
|
+
},
|
|
93
|
+
// — Value column (list only): stock × cost —
|
|
94
|
+
{
|
|
95
|
+
key: "value",
|
|
96
|
+
label: t2("inventory.productList.value"),
|
|
97
|
+
type: "number",
|
|
98
|
+
showInForm: false,
|
|
99
|
+
showInTable: true,
|
|
100
|
+
sortable: false,
|
|
101
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right block font-medium", children: saas.formatCurrency((row.currentQuantity || 0) * (row.costPrice || 0), currency) })
|
|
102
|
+
},
|
|
103
|
+
{ key: "salePrice", label: t2("inventory.productForm.salePrice"), type: "currency", group: "pricing", currency: currency.code, currencySymbol: currency.symbol, currencyLocale: currency.locale, showInTable: false },
|
|
104
|
+
{
|
|
105
|
+
key: "margin",
|
|
106
|
+
label: t2("inventory.productForm.margin"),
|
|
107
|
+
type: "computed",
|
|
108
|
+
group: "pricing",
|
|
109
|
+
showInTable: false,
|
|
110
|
+
compute: (vals) => {
|
|
111
|
+
const cost = Number(vals.costPrice) || 0;
|
|
112
|
+
const sale = Number(vals.salePrice) || 0;
|
|
113
|
+
if (sale <= 0 || cost <= 0) return null;
|
|
114
|
+
const m = (sale - cost) / cost * 100;
|
|
115
|
+
return { display: `${m.toFixed(1)}%`, tone: m >= 0 ? "positive" : "negative" };
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
// — Stock Levels (form only) —
|
|
119
|
+
{ key: "minQuantity", label: t2("inventory.productForm.minQuantity"), type: "number", group: "stock", min: 0, placeholder: "0", hint: t2("inventory.productForm.minQuantityHint"), showInTable: false },
|
|
120
|
+
{ key: "maxQuantity", label: t2("inventory.productForm.maxQuantity"), type: "number", group: "stock", min: 0, placeholder: t2("inventory.productForm.optional"), hint: t2("inventory.productForm.maxQuantityHint"), showInTable: false }
|
|
121
|
+
];
|
|
122
|
+
return {
|
|
123
|
+
name: t2("inventory.stock.product"),
|
|
124
|
+
namePlural: t2("inventory.nav.products"),
|
|
125
|
+
icon: "Package",
|
|
126
|
+
displayField: "name",
|
|
127
|
+
fields,
|
|
128
|
+
fieldGroups: [
|
|
129
|
+
{ id: "general", label: t2("inventory.productForm.generalInfo"), columns: 2, imageSlot: true },
|
|
130
|
+
{ id: "classification", label: t2("inventory.productForm.classification"), description: t2("inventory.productForm.classificationDesc"), columns: 1 },
|
|
131
|
+
{ id: "pricing", label: t2("inventory.productForm.pricing"), columns: 3 },
|
|
132
|
+
{ id: "stock", label: t2("inventory.productForm.stockLevels"), description: t2("inventory.productForm.stockLevelsDesc"), columns: 2 }
|
|
133
|
+
],
|
|
134
|
+
facets: [{ field: "productType", allLabel: t2("crud.list.allFacet") }]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
59
137
|
function ProductListView({ onNew, onEdit }) {
|
|
60
138
|
const t2 = core.useTranslation();
|
|
61
139
|
const { currency, productTypes } = useInventoryConfig();
|
|
62
140
|
const products = useInventoryStore((s) => s.products);
|
|
63
141
|
const productsTotal = useInventoryStore((s) => s.productsTotal);
|
|
64
|
-
|
|
142
|
+
useInventoryStore((s) => s.productsLoading);
|
|
65
143
|
const fetchProducts = useInventoryStore((s) => s.fetchProducts);
|
|
66
|
-
const [search, setSearch] =
|
|
67
|
-
const [typeFilter, setTypeFilter] =
|
|
68
|
-
|
|
69
|
-
|
|
144
|
+
const [search, setSearch] = React2.useState("");
|
|
145
|
+
const [typeFilter, setTypeFilter] = React2.useState();
|
|
146
|
+
const [loadedOnce, setLoadedOnce] = React2.useState(false);
|
|
147
|
+
React2.useEffect(() => {
|
|
148
|
+
let active = true;
|
|
149
|
+
Promise.resolve(fetchProducts({ productType: typeFilter, search: search || void 0 })).finally(() => {
|
|
150
|
+
if (active) setLoadedOnce(true);
|
|
151
|
+
});
|
|
152
|
+
return () => {
|
|
153
|
+
active = false;
|
|
154
|
+
};
|
|
70
155
|
}, [typeFilter, search]);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
] });
|
|
97
|
-
}
|
|
98
|
-
var TYPE_DESCRIPTION_KEYS = {
|
|
99
|
-
ingredient: "inventory.productForm.typeIngredient",
|
|
100
|
-
sale: "inventory.productForm.typeSale",
|
|
101
|
-
intermediate: "inventory.productForm.typeIntermediate",
|
|
102
|
-
asset: "inventory.productForm.typeAsset"
|
|
103
|
-
};
|
|
104
|
-
function FormSection({ title, subtitle, children }) {
|
|
105
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
|
|
106
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 py-3 border-b", children: [
|
|
107
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: title }),
|
|
108
|
-
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: subtitle })
|
|
109
|
-
] }),
|
|
110
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-5 space-y-4", children })
|
|
111
|
-
] });
|
|
156
|
+
const entity = React2.useMemo(() => buildProductEntity(t2, productTypes, currency), [t2, productTypes, currency]);
|
|
157
|
+
const facets = React2.useMemo(() => [{
|
|
158
|
+
field: "productType",
|
|
159
|
+
allLabel: t2("crud.list.allFacet"),
|
|
160
|
+
options: productTypes.map((p) => ({ value: p.value, label: p.label }))
|
|
161
|
+
}], [productTypes, t2]);
|
|
162
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
163
|
+
saas.CrudListView,
|
|
164
|
+
{
|
|
165
|
+
entityDef: entity,
|
|
166
|
+
items: loadedOnce ? products : null,
|
|
167
|
+
total: productsTotal,
|
|
168
|
+
search,
|
|
169
|
+
onSearchChange: setSearch,
|
|
170
|
+
searchPlaceholder: t2("inventory.productList.searchPlaceholder"),
|
|
171
|
+
facets,
|
|
172
|
+
activeFilters: { productType: typeFilter },
|
|
173
|
+
onFacetChange: (_field, value) => setTypeFilter(value),
|
|
174
|
+
onNew,
|
|
175
|
+
addLabel: t2("inventory.productList.newProduct"),
|
|
176
|
+
onRowClick: (row) => onEdit?.(row.id)
|
|
177
|
+
}
|
|
178
|
+
);
|
|
112
179
|
}
|
|
113
|
-
function
|
|
180
|
+
function ProductCrudForm({ editId, onSaved }) {
|
|
114
181
|
const t2 = core.useTranslation();
|
|
115
182
|
const { productTypes, currency } = useInventoryConfig();
|
|
116
183
|
const provider = useInventoryProvider();
|
|
117
184
|
const createProduct = useInventoryStore((s) => s.createProduct);
|
|
118
185
|
const isEdit = !!editId;
|
|
119
|
-
const [
|
|
120
|
-
const [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const [productType, setProductType] = React11.useState(productTypes[0]?.value ?? "ingredient");
|
|
125
|
-
const [costPrice, setCostPrice] = React11.useState(0);
|
|
126
|
-
const [salePrice, setSalePrice] = React11.useState(0);
|
|
127
|
-
const [minQuantity, setMinQuantity] = React11.useState(0);
|
|
128
|
-
const [maxQuantity, setMaxQuantity] = React11.useState(0);
|
|
129
|
-
const [description, setDescription] = React11.useState("");
|
|
130
|
-
const [saving, setSaving] = React11.useState(false);
|
|
131
|
-
React11.useEffect(() => {
|
|
132
|
-
if (!editId || loaded) return;
|
|
186
|
+
const [initialData, setInitialData] = React2__default.default.useState(isEdit ? null : {});
|
|
187
|
+
const [headerName, setHeaderName] = React2__default.default.useState("");
|
|
188
|
+
React2__default.default.useEffect(() => {
|
|
189
|
+
if (!editId) return;
|
|
190
|
+
let cancelled = false;
|
|
133
191
|
(async () => {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
192
|
+
const p = await provider.getProductById(editId);
|
|
193
|
+
if (cancelled) return;
|
|
194
|
+
setHeaderName(p?.name ?? "");
|
|
195
|
+
setInitialData(
|
|
196
|
+
p ? {
|
|
197
|
+
name: p.name,
|
|
198
|
+
brand: p.brand ?? "",
|
|
199
|
+
sku: p.sku ?? "",
|
|
200
|
+
barcode: p.barcode ?? "",
|
|
201
|
+
description: p.description ?? "",
|
|
202
|
+
productType: p.productType,
|
|
203
|
+
costPrice: p.costPrice,
|
|
204
|
+
salePrice: p.salePrice ?? 0,
|
|
205
|
+
minQuantity: p.minQuantity,
|
|
206
|
+
maxQuantity: p.maxQuantity ?? 0
|
|
207
|
+
} : {}
|
|
208
|
+
);
|
|
147
209
|
})();
|
|
210
|
+
return () => {
|
|
211
|
+
cancelled = true;
|
|
212
|
+
};
|
|
148
213
|
}, [editId]);
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (ready && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
154
|
-
}, [ready]);
|
|
155
|
-
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
156
|
-
ui.useSaveBar({
|
|
157
|
-
dirty,
|
|
158
|
-
saving,
|
|
159
|
-
onSave: () => {
|
|
160
|
-
void handleSave();
|
|
161
|
-
},
|
|
162
|
-
onDiscard: () => onSaved?.(),
|
|
163
|
-
saveLabel: t2("inventory.productForm.save")
|
|
164
|
-
});
|
|
165
|
-
async function handleSave() {
|
|
166
|
-
if (!name.trim()) {
|
|
214
|
+
const entity = React2__default.default.useMemo(() => buildProductEntity(t2, productTypes, currency), [t2, productTypes, currency]);
|
|
215
|
+
async function save(values) {
|
|
216
|
+
const name = String(values.name ?? "").trim();
|
|
217
|
+
if (!name) {
|
|
167
218
|
ui.toast.error(t2("common.formIncomplete"));
|
|
168
|
-
|
|
219
|
+
throw new Error("name required");
|
|
169
220
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await createProduct({
|
|
187
|
-
name,
|
|
188
|
-
sku: sku || void 0,
|
|
189
|
-
barcode: barcode || void 0,
|
|
190
|
-
brand: brand || void 0,
|
|
191
|
-
productType,
|
|
192
|
-
costPrice,
|
|
193
|
-
salePrice: salePrice || void 0,
|
|
194
|
-
minQuantity,
|
|
195
|
-
maxQuantity: maxQuantity || void 0,
|
|
196
|
-
description: description || void 0
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
onSaved?.();
|
|
200
|
-
} finally {
|
|
201
|
-
setSaving(false);
|
|
221
|
+
const payload = {
|
|
222
|
+
name,
|
|
223
|
+
sku: values.sku || void 0,
|
|
224
|
+
barcode: values.barcode || void 0,
|
|
225
|
+
brand: values.brand || void 0,
|
|
226
|
+
productType: values.productType ?? productTypes[0]?.value,
|
|
227
|
+
costPrice: Number(values.costPrice) || 0,
|
|
228
|
+
salePrice: Number(values.salePrice) || void 0,
|
|
229
|
+
minQuantity: Number(values.minQuantity) || 0,
|
|
230
|
+
maxQuantity: Number(values.maxQuantity) || void 0,
|
|
231
|
+
description: values.description || void 0
|
|
232
|
+
};
|
|
233
|
+
if (isEdit && editId) {
|
|
234
|
+
await provider.updateProduct(editId, payload);
|
|
235
|
+
} else {
|
|
236
|
+
await createProduct(payload);
|
|
202
237
|
}
|
|
238
|
+
onSaved?.();
|
|
203
239
|
}
|
|
204
|
-
const title = isEdit ?
|
|
240
|
+
const title = isEdit ? headerName || t2("inventory.productForm.editProduct") : t2("inventory.productForm.newProduct");
|
|
205
241
|
const subtitle = isEdit ? void 0 : t2("inventory.productForm.addToCatalog");
|
|
206
|
-
const margin = salePrice > 0 && costPrice > 0 ? ((salePrice - costPrice) / costPrice * 100).toFixed(1) : null;
|
|
207
242
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5", children: [
|
|
208
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
209
|
-
|
|
243
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title, subtitle, onBack: onSaved, parentLabel: t2("inventory.nav.products") }),
|
|
244
|
+
initialData && /* @__PURE__ */ jsxRuntime.jsx(
|
|
245
|
+
saas.CrudFormPage,
|
|
210
246
|
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
247
|
+
entityDef: entity,
|
|
248
|
+
mode: isEdit ? "edit" : "create",
|
|
249
|
+
initialData,
|
|
250
|
+
onSubmit: save,
|
|
251
|
+
onCancel: () => onSaved?.(),
|
|
252
|
+
namePlural: t2("inventory.nav.products"),
|
|
253
|
+
hideBreadcrumb: true
|
|
215
254
|
}
|
|
216
|
-
)
|
|
217
|
-
/* @__PURE__ */ jsxRuntime.jsxs(FormSection, { title: t2("inventory.productForm.generalInfo"), children: [
|
|
218
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
|
|
219
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-20 h-20 rounded-lg border-2 border-dashed border-muted flex items-center justify-center text-muted-foreground hover:border-primary/30 hover:text-primary/50 transition-colors cursor-pointer", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImagePlus, { className: "h-5 w-5" }) }) }),
|
|
220
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 grid gap-3 sm:grid-cols-2", children: [
|
|
221
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:col-span-1", children: [
|
|
222
|
-
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
223
|
-
t2("inventory.productForm.name"),
|
|
224
|
-
" *"
|
|
225
|
-
] }),
|
|
226
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
227
|
-
"input",
|
|
228
|
-
{
|
|
229
|
-
type: "text",
|
|
230
|
-
value: name,
|
|
231
|
-
onChange: (e) => setName(e.target.value),
|
|
232
|
-
placeholder: t2("inventory.productForm.namePlaceholder"),
|
|
233
|
-
autoFocus: true,
|
|
234
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
235
|
-
}
|
|
236
|
-
)
|
|
237
|
-
] }),
|
|
238
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
239
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.brand") }),
|
|
240
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
241
|
-
"input",
|
|
242
|
-
{
|
|
243
|
-
type: "text",
|
|
244
|
-
value: brand,
|
|
245
|
-
onChange: (e) => setBrand(e.target.value),
|
|
246
|
-
placeholder: t2("inventory.productForm.brandPlaceholder"),
|
|
247
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
248
|
-
}
|
|
249
|
-
)
|
|
250
|
-
] })
|
|
251
|
-
] })
|
|
252
|
-
] }),
|
|
253
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
254
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
255
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.sku") }),
|
|
256
|
-
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "text", value: sku, onChange: (e) => setSku(e.target.value), placeholder: t2("inventory.productForm.skuPlaceholder"), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" })
|
|
257
|
-
] }),
|
|
258
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
259
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.barcode") }),
|
|
260
|
-
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "text", value: barcode, onChange: (e) => setBarcode(e.target.value), placeholder: t2("inventory.productForm.barcodePlaceholder"), className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" })
|
|
261
|
-
] })
|
|
262
|
-
] }),
|
|
263
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
264
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.description") }),
|
|
265
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
266
|
-
"textarea",
|
|
267
|
-
{
|
|
268
|
-
value: description,
|
|
269
|
-
onChange: (e) => setDescription(e.target.value),
|
|
270
|
-
rows: 2,
|
|
271
|
-
placeholder: t2("inventory.productForm.descriptionPlaceholder"),
|
|
272
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
|
273
|
-
}
|
|
274
|
-
)
|
|
275
|
-
] })
|
|
276
|
-
] }),
|
|
277
|
-
/* @__PURE__ */ jsxRuntime.jsx(FormSection, { title: t2("inventory.productForm.classification"), subtitle: t2("inventory.productForm.classificationDesc"), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-4", children: productTypes.map((pt) => {
|
|
278
|
-
const active = productType === pt.value;
|
|
279
|
-
const descKey = TYPE_DESCRIPTION_KEYS[pt.value];
|
|
280
|
-
const desc = descKey ? t2(descKey) : "";
|
|
281
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
282
|
-
"button",
|
|
283
|
-
{
|
|
284
|
-
onClick: () => setProductType(pt.value),
|
|
285
|
-
className: `rounded-lg border-2 p-3 text-left transition-all ${active ? "border-primary bg-primary/5" : "border-transparent bg-muted/20 hover:bg-muted/40"}`,
|
|
286
|
-
children: [
|
|
287
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm font-medium ${active ? "text-primary" : ""}`, children: pt.label }),
|
|
288
|
-
desc && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5 leading-tight", children: desc })
|
|
289
|
-
]
|
|
290
|
-
},
|
|
291
|
-
pt.value
|
|
292
|
-
);
|
|
293
|
-
}) }) }),
|
|
294
|
-
/* @__PURE__ */ jsxRuntime.jsx(FormSection, { title: t2("inventory.productForm.pricing"), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [
|
|
295
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
296
|
-
ui.CurrencyInput,
|
|
297
|
-
{
|
|
298
|
-
label: t2("inventory.productForm.costPrice"),
|
|
299
|
-
value: costPrice,
|
|
300
|
-
onChange: setCostPrice,
|
|
301
|
-
symbol: currency.symbol,
|
|
302
|
-
locale: currency.locale,
|
|
303
|
-
currencyCode: currency.code
|
|
304
|
-
}
|
|
305
|
-
),
|
|
306
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
307
|
-
ui.CurrencyInput,
|
|
308
|
-
{
|
|
309
|
-
label: t2("inventory.productForm.salePrice"),
|
|
310
|
-
value: salePrice,
|
|
311
|
-
onChange: setSalePrice,
|
|
312
|
-
symbol: currency.symbol,
|
|
313
|
-
locale: currency.locale,
|
|
314
|
-
currencyCode: currency.code
|
|
315
|
-
}
|
|
316
|
-
),
|
|
317
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
318
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.margin") }),
|
|
319
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 rounded-lg border bg-muted/20 px-3 py-2 text-sm tabular-nums text-right", children: margin !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: Number(margin) >= 0 ? "text-success" : "text-destructive", children: [
|
|
320
|
-
margin,
|
|
321
|
-
"%"
|
|
322
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "\u2014" }) })
|
|
323
|
-
] })
|
|
324
|
-
] }) }),
|
|
325
|
-
/* @__PURE__ */ jsxRuntime.jsx(FormSection, { title: t2("inventory.productForm.stockLevels"), subtitle: t2("inventory.productForm.stockLevelsDesc"), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2 max-w-md", children: [
|
|
326
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
327
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.minQuantity") }),
|
|
328
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
329
|
-
"input",
|
|
330
|
-
{
|
|
331
|
-
type: "number",
|
|
332
|
-
min: 0,
|
|
333
|
-
value: minQuantity,
|
|
334
|
-
onChange: (e) => setMinQuantity(Number(e.target.value) || 0),
|
|
335
|
-
placeholder: "0",
|
|
336
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
337
|
-
}
|
|
338
|
-
),
|
|
339
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-1", children: t2("inventory.productForm.minQuantityHint") })
|
|
340
|
-
] }),
|
|
341
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
342
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.productForm.maxQuantity") }),
|
|
343
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
344
|
-
"input",
|
|
345
|
-
{
|
|
346
|
-
type: "number",
|
|
347
|
-
min: 0,
|
|
348
|
-
value: maxQuantity,
|
|
349
|
-
onChange: (e) => setMaxQuantity(Number(e.target.value) || 0),
|
|
350
|
-
placeholder: t2("inventory.productForm.optional"),
|
|
351
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
352
|
-
}
|
|
353
|
-
),
|
|
354
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground mt-1", children: t2("inventory.productForm.maxQuantityHint") })
|
|
355
|
-
] })
|
|
356
|
-
] }) })
|
|
255
|
+
)
|
|
357
256
|
] });
|
|
358
257
|
}
|
|
359
258
|
var MOVEMENT_TYPES = [
|
|
@@ -441,26 +340,38 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
|
|
|
441
340
|
const fetchProducts = useInventoryStore((s) => s.fetchProducts);
|
|
442
341
|
const fetchLocations = useInventoryStore((s) => s.fetchLocations);
|
|
443
342
|
const createMovement = useInventoryStore((s) => s.createMovement);
|
|
444
|
-
const [step, setStep] =
|
|
445
|
-
const [positions, setPositions] =
|
|
446
|
-
const [movementType, setMovementType] =
|
|
447
|
-
const [productId, setProductId] =
|
|
448
|
-
const [productLabel, setProductLabel] =
|
|
449
|
-
const [quantity, setQuantity] =
|
|
450
|
-
const [unitCost, setUnitCost] =
|
|
451
|
-
const [locationId, setLocationId] =
|
|
452
|
-
const [destLocationId, setDestLocationId] =
|
|
453
|
-
const [reason, setReason] =
|
|
454
|
-
const [documentNumber, setDocumentNumber] =
|
|
455
|
-
const [notes, setNotes] =
|
|
456
|
-
const [batchNumber, setBatchNumber] =
|
|
457
|
-
const [expirationDate, setExpirationDate] =
|
|
458
|
-
const [saving, setSaving] =
|
|
459
|
-
const [savedMovement, setSavedMovement] =
|
|
460
|
-
|
|
343
|
+
const [step, setStep] = React2.useState(1);
|
|
344
|
+
const [positions, setPositions] = React2.useState([]);
|
|
345
|
+
const [movementType, setMovementType] = React2.useState(defaultType);
|
|
346
|
+
const [productId, setProductId] = React2.useState("");
|
|
347
|
+
const [productLabel, setProductLabel] = React2.useState("");
|
|
348
|
+
const [quantity, setQuantity] = React2.useState(1);
|
|
349
|
+
const [unitCost, setUnitCost] = React2.useState(0);
|
|
350
|
+
const [locationId, setLocationId] = React2.useState("");
|
|
351
|
+
const [destLocationId, setDestLocationId] = React2.useState("");
|
|
352
|
+
const [reason, setReason] = React2.useState("");
|
|
353
|
+
const [documentNumber, setDocumentNumber] = React2.useState("");
|
|
354
|
+
const [notes, setNotes] = React2.useState("");
|
|
355
|
+
const [batchNumber, setBatchNumber] = React2.useState("");
|
|
356
|
+
const [expirationDate, setExpirationDate] = React2.useState("");
|
|
357
|
+
const [saving, setSaving] = React2.useState(false);
|
|
358
|
+
const [savedMovement, setSavedMovement] = React2.useState(null);
|
|
359
|
+
React2.useEffect(() => {
|
|
461
360
|
fetchProducts({});
|
|
462
361
|
fetchLocations();
|
|
463
362
|
}, []);
|
|
363
|
+
const positionByLocation = React2.useMemo(() => {
|
|
364
|
+
const map = {};
|
|
365
|
+
for (const pos of positions) {
|
|
366
|
+
if (pos.stockLocationId) {
|
|
367
|
+
map[pos.stockLocationId] = (map[pos.stockLocationId] ?? 0) + pos.quantity;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return map;
|
|
371
|
+
}, [positions]);
|
|
372
|
+
const sortedLocations = React2.useMemo(() => {
|
|
373
|
+
return [...locations].sort((a, b) => (positionByLocation[b.id] ?? 0) - (positionByLocation[a.id] ?? 0));
|
|
374
|
+
}, [locations, positionByLocation]);
|
|
464
375
|
if (viewMovement) {
|
|
465
376
|
return /* @__PURE__ */ jsxRuntime.jsx(MovementDetail, { movement: viewMovement, currency, onClose: () => onSaved?.() });
|
|
466
377
|
}
|
|
@@ -495,18 +406,6 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
|
|
|
495
406
|
setPositions([]);
|
|
496
407
|
}
|
|
497
408
|
}
|
|
498
|
-
const positionByLocation = React11.useMemo(() => {
|
|
499
|
-
const map = {};
|
|
500
|
-
for (const pos of positions) {
|
|
501
|
-
if (pos.stockLocationId) {
|
|
502
|
-
map[pos.stockLocationId] = (map[pos.stockLocationId] ?? 0) + pos.quantity;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return map;
|
|
506
|
-
}, [positions]);
|
|
507
|
-
const sortedLocations = React11.useMemo(() => {
|
|
508
|
-
return [...locations].sort((a, b) => (positionByLocation[b.id] ?? 0) - (positionByLocation[a.id] ?? 0));
|
|
509
|
-
}, [locations, positionByLocation]);
|
|
510
409
|
const canProceedStep1 = !!productId;
|
|
511
410
|
const canProceedStep2 = quantity > 0 && (!needsReason || reason.trim()) && (!needsDest || destLocationId);
|
|
512
411
|
const title = defaultType === "entry" ? t2("inventory.stock.entry") : defaultType === "exit" ? t2("inventory.stock.exit") : t2("inventory.stock.movement");
|
|
@@ -810,11 +709,11 @@ function MovementHistoryView({ onViewDetail } = {}) {
|
|
|
810
709
|
const movements = useInventoryStore((s) => s.movements);
|
|
811
710
|
const movementsLoading = useInventoryStore((s) => s.movementsLoading);
|
|
812
711
|
const fetchMovements = useInventoryStore((s) => s.fetchMovements);
|
|
813
|
-
const [search, setSearch] =
|
|
814
|
-
|
|
712
|
+
const [search, setSearch] = React2.useState("");
|
|
713
|
+
React2.useEffect(() => {
|
|
815
714
|
fetchMovements({ search: search || void 0 });
|
|
816
715
|
}, [search]);
|
|
817
|
-
const columns =
|
|
716
|
+
const columns = React2.useMemo(() => [
|
|
818
717
|
{
|
|
819
718
|
accessorKey: "movementDate",
|
|
820
719
|
header: t2("inventory.history.columnDate"),
|
|
@@ -901,7 +800,7 @@ function RecipesView({ onNew, onView }) {
|
|
|
901
800
|
const recipes = useInventoryStore((s) => s.recipes);
|
|
902
801
|
const recipesLoading = useInventoryStore((s) => s.recipesLoading);
|
|
903
802
|
const fetchRecipes = useInventoryStore((s) => s.fetchRecipes);
|
|
904
|
-
|
|
803
|
+
React2.useEffect(() => {
|
|
905
804
|
fetchRecipes();
|
|
906
805
|
}, []);
|
|
907
806
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
@@ -973,15 +872,15 @@ function RecipeFormView({ onSaved }) {
|
|
|
973
872
|
const t2 = core.useTranslation();
|
|
974
873
|
const provider = useInventoryProvider();
|
|
975
874
|
const createRecipe = useInventoryStore((s) => s.createRecipe);
|
|
976
|
-
const [name, setName] =
|
|
977
|
-
const [description, setDescription] =
|
|
978
|
-
const [productId, setProductId] =
|
|
979
|
-
const [productName, setProductName] =
|
|
980
|
-
const [yieldQuantity, setYieldQuantity] =
|
|
981
|
-
const [prepTime, setPrepTime] =
|
|
982
|
-
const [instructions, setInstructions] =
|
|
983
|
-
const [ingredients, setIngredients] =
|
|
984
|
-
const [saving, setSaving] =
|
|
875
|
+
const [name, setName] = React2.useState("");
|
|
876
|
+
const [description, setDescription] = React2.useState("");
|
|
877
|
+
const [productId, setProductId] = React2.useState("");
|
|
878
|
+
const [productName, setProductName] = React2.useState("");
|
|
879
|
+
const [yieldQuantity, setYieldQuantity] = React2.useState(1);
|
|
880
|
+
const [prepTime, setPrepTime] = React2.useState("");
|
|
881
|
+
const [instructions, setInstructions] = React2.useState("");
|
|
882
|
+
const [ingredients, setIngredients] = React2.useState([]);
|
|
883
|
+
const [saving, setSaving] = React2.useState(false);
|
|
985
884
|
function addIngredient() {
|
|
986
885
|
setIngredients([...ingredients, { _id: nextId(), productId: "", productName: "", quantity: 1, unitId: "", unitName: "", notes: "" }]);
|
|
987
886
|
}
|
|
@@ -1193,10 +1092,10 @@ function DetailSkeleton() {
|
|
|
1193
1092
|
function RecipeDetailView({ recipeId, onBack }) {
|
|
1194
1093
|
const t2 = core.useTranslation();
|
|
1195
1094
|
const provider = useInventoryProvider();
|
|
1196
|
-
const [recipe, setRecipe] =
|
|
1197
|
-
const [ingredients, setIngredients] =
|
|
1198
|
-
const [loading, setLoading] =
|
|
1199
|
-
|
|
1095
|
+
const [recipe, setRecipe] = React2.useState(null);
|
|
1096
|
+
const [ingredients, setIngredients] = React2.useState([]);
|
|
1097
|
+
const [loading, setLoading] = React2.useState(true);
|
|
1098
|
+
React2.useEffect(() => {
|
|
1200
1099
|
setLoading(true);
|
|
1201
1100
|
Promise.all([
|
|
1202
1101
|
provider.getRecipeById(recipeId),
|
|
@@ -1355,7 +1254,7 @@ var STEPS = [
|
|
|
1355
1254
|
];
|
|
1356
1255
|
function InventoryOnboarding({ onComplete }) {
|
|
1357
1256
|
const t2 = core.useTranslation();
|
|
1358
|
-
const [step, setStep] =
|
|
1257
|
+
const [step, setStep] = React2.useState(0);
|
|
1359
1258
|
const current = STEPS[step];
|
|
1360
1259
|
const isLast = step === STEPS.length - 1;
|
|
1361
1260
|
const Icon = current.icon;
|
|
@@ -1434,7 +1333,7 @@ function InventoryPage({ config, provider, store, registries }) {
|
|
|
1434
1333
|
"recipes-detail": 1,
|
|
1435
1334
|
settings: 1
|
|
1436
1335
|
}, "dashboard");
|
|
1437
|
-
const [onboardingComplete, setOnboardingComplete] =
|
|
1336
|
+
const [onboardingComplete, setOnboardingComplete] = React2.useState(() => {
|
|
1438
1337
|
try {
|
|
1439
1338
|
return localStorage.getItem("saas-core:inventory-onboarded") === "true";
|
|
1440
1339
|
} catch {
|
|
@@ -1444,7 +1343,7 @@ function InventoryPage({ config, provider, store, registries }) {
|
|
|
1444
1343
|
const isSettings = view === "settings";
|
|
1445
1344
|
const isSummary = view === "dashboard" || view === "";
|
|
1446
1345
|
const nav = buildNav(config, view, navigate, t2);
|
|
1447
|
-
const quickActions =
|
|
1346
|
+
const quickActions = React2.useMemo(() => {
|
|
1448
1347
|
const actions = [
|
|
1449
1348
|
{
|
|
1450
1349
|
id: "new-product",
|
|
@@ -1490,8 +1389,8 @@ function InventoryPage({ config, provider, store, registries }) {
|
|
|
1490
1389
|
}
|
|
1491
1390
|
const renderView = saas.createViewRouter([
|
|
1492
1391
|
{ id: "products-list", render: () => /* @__PURE__ */ jsxRuntime.jsx(ProductListView, { onNew: () => navigate("products-new"), onEdit: (id) => navigate(`products-edit:${id}`) }) },
|
|
1493
|
-
{ id: "products-new", render: () => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1494
|
-
{ id: "products-edit", render: ({ id }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1392
|
+
{ id: "products-new", render: () => /* @__PURE__ */ jsxRuntime.jsx(ProductCrudForm, { onSaved: () => navigate("products-list") }) },
|
|
1393
|
+
{ id: "products-edit", render: ({ id }) => /* @__PURE__ */ jsxRuntime.jsx(ProductCrudForm, { editId: id, onSaved: () => navigate("products-list") }) },
|
|
1495
1394
|
{ id: "stock-entry", render: () => /* @__PURE__ */ jsxRuntime.jsx(StockMovementView, { defaultType: "entry", onSaved: () => navigate("stock-history") }) },
|
|
1496
1395
|
{ id: "stock-exit", render: () => /* @__PURE__ */ jsxRuntime.jsx(StockMovementView, { defaultType: "exit", onSaved: () => navigate("stock-history") }) },
|
|
1497
1396
|
{ id: "stock-history", render: () => /* @__PURE__ */ jsxRuntime.jsx(MovementHistoryView, { onViewDetail: (id) => navigate(`stock-detail:${id}`) }) },
|
|
@@ -1527,7 +1426,7 @@ function InventoryPage({ config, provider, store, registries }) {
|
|
|
1527
1426
|
}
|
|
1528
1427
|
function useEnsureSummary() {
|
|
1529
1428
|
const fetchSummary = useInventoryStore((s) => s.fetchSummary);
|
|
1530
|
-
|
|
1429
|
+
React2.useEffect(() => {
|
|
1531
1430
|
void fetchSummary();
|
|
1532
1431
|
}, []);
|
|
1533
1432
|
}
|
|
@@ -1888,6 +1787,7 @@ function createSupabaseInventoryProvider() {
|
|
|
1888
1787
|
const { core} = getClients();
|
|
1889
1788
|
let qb = core.from("products").select("*", { count: "exact" });
|
|
1890
1789
|
if (query.search) qb = qb.ilike("name", `%${query.search}%`);
|
|
1790
|
+
if (query.productType) qb = qb.eq("metadata->>productType", query.productType);
|
|
1891
1791
|
if (query.isActive !== void 0) qb = qb.eq("is_active", query.isActive);
|
|
1892
1792
|
if (query.lowStockOnly) qb = qb.lte("stock", 0);
|
|
1893
1793
|
const page = query.page ?? 1;
|
|
@@ -2856,7 +2756,7 @@ function createInventoryPlugin(options) {
|
|
|
2856
2756
|
);
|
|
2857
2757
|
const store = createInventoryStore(provider);
|
|
2858
2758
|
const dashboardWidgets = createInventoryDashboardWidgets({ config, provider, store });
|
|
2859
|
-
const PageComponent = () =>
|
|
2759
|
+
const PageComponent = () => React2__default.default.createElement(InventoryPage, { config, provider, store, registries: inventoryRegistries });
|
|
2860
2760
|
return {
|
|
2861
2761
|
id: "inventory",
|
|
2862
2762
|
name: config.labels.pageTitle,
|
|
@@ -2919,7 +2819,7 @@ function createInventoryPlugin(options) {
|
|
|
2919
2819
|
label: "Inventory",
|
|
2920
2820
|
icon: "Package",
|
|
2921
2821
|
component: (() => {
|
|
2922
|
-
const Tab = () =>
|
|
2822
|
+
const Tab = () => React2__default.default.createElement(InventoryGeneralSettings);
|
|
2923
2823
|
Tab.displayName = "InventorySettingsTab";
|
|
2924
2824
|
return Tab;
|
|
2925
2825
|
})(),
|