@fayz-ai/plugin-inventory 0.1.1
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/InventoryContext.d.ts +37 -0
- package/dist/InventoryContext.d.ts.map +1 -0
- package/dist/InventoryPage.d.ts +13 -0
- package/dist/InventoryPage.d.ts.map +1 -0
- package/dist/components/InventoryGeneralSettings.d.ts +3 -0
- package/dist/components/InventoryGeneralSettings.d.ts.map +1 -0
- package/dist/components/InventoryOnboarding.d.ts +5 -0
- package/dist/components/InventoryOnboarding.d.ts.map +1 -0
- package/dist/components/InventorySettings.d.ts +8 -0
- package/dist/components/InventorySettings.d.ts.map +1 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +22 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +2936 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2930 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +28 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +213 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/DashboardView.d.ts +8 -0
- package/dist/views/DashboardView.d.ts.map +1 -0
- package/dist/views/MovementHistoryView.d.ts +5 -0
- package/dist/views/MovementHistoryView.d.ts.map +1 -0
- package/dist/views/ProductCrudForm.d.ts +6 -0
- package/dist/views/ProductCrudForm.d.ts.map +1 -0
- package/dist/views/ProductFormView.d.ts +6 -0
- package/dist/views/ProductFormView.d.ts.map +1 -0
- package/dist/views/ProductListView.d.ts +6 -0
- package/dist/views/ProductListView.d.ts.map +1 -0
- package/dist/views/RecipeDetailView.d.ts +6 -0
- package/dist/views/RecipeDetailView.d.ts.map +1 -0
- package/dist/views/RecipeFormView.d.ts +5 -0
- package/dist/views/RecipeFormView.d.ts.map +1 -0
- package/dist/views/RecipesView.d.ts +6 -0
- package/dist/views/RecipesView.d.ts.map +1 -0
- package/dist/views/StockMovementView.d.ts +8 -0
- package/dist/views/StockMovementView.d.ts.map +1 -0
- package/dist/views/dashboardWidgets.d.ts +11 -0
- package/dist/views/dashboardWidgets.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts +6 -0
- package/dist/views/productEntity.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/InventoryContext.tsx +40 -0
- package/src/InventoryPage.tsx +170 -0
- package/src/README.md +177 -0
- package/src/components/InventoryGeneralSettings.tsx +26 -0
- package/src/components/InventoryOnboarding.tsx +60 -0
- package/src/components/InventorySettings.tsx +27 -0
- package/src/data/index.ts +2 -0
- package/src/data/mock.ts +266 -0
- package/src/data/supabase.ts +358 -0
- package/src/data/types.ts +35 -0
- package/src/index.ts +191 -0
- package/src/lib/tenant.ts +4 -0
- package/src/locales/en.ts +242 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +242 -0
- package/src/migrations/001_inventory_base.sql +69 -0
- package/src/migrations/002_recipes.sql +34 -0
- package/src/migrations/003_measurement_units.sql +13 -0
- package/src/registries.ts +111 -0
- package/src/store.ts +127 -0
- package/src/types.ts +256 -0
- package/src/views/DashboardView.tsx +11 -0
- package/src/views/MovementHistoryView.tsx +104 -0
- package/src/views/ProductCrudForm.tsx +99 -0
- package/src/views/ProductFormView.tsx +283 -0
- package/src/views/ProductListView.tsx +107 -0
- package/src/views/RecipeDetailView.tsx +192 -0
- package/src/views/RecipeFormView.tsx +235 -0
- package/src/views/RecipesView.tsx +103 -0
- package/src/views/StockMovementView.tsx +516 -0
- package/src/views/dashboardWidgets.tsx +101 -0
- package/src/views/productEntity.tsx +124 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { EntityDef, FieldDef } from '@fayz-ai/core'
|
|
3
|
+
import { formatCurrency, type InventoryCurrency, type ProductTypeOption } from '../InventoryContext'
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// The single source of truth for the Product entity — drives BOTH the generic
|
|
7
|
+
// list (CrudListView columns + facet) and the generic form (CrudFormPage
|
|
8
|
+
// sections). Persistence stays in the inventory provider/store; this only
|
|
9
|
+
// describes fields, columns and layout.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const TYPE_DESCRIPTION_KEYS: Record<string, string> = {
|
|
13
|
+
ingredient: 'inventory.productForm.typeIngredient',
|
|
14
|
+
sale: 'inventory.productForm.typeSale',
|
|
15
|
+
intermediate: 'inventory.productForm.typeIntermediate',
|
|
16
|
+
asset: 'inventory.productForm.typeAsset',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type T = (key: string, params?: Record<string, string | number>) => string
|
|
20
|
+
|
|
21
|
+
export function buildProductEntity(
|
|
22
|
+
t: T,
|
|
23
|
+
productTypes: ProductTypeOption[],
|
|
24
|
+
currency: InventoryCurrency,
|
|
25
|
+
): EntityDef {
|
|
26
|
+
const typeOptions = productTypes.map((pt) => ({
|
|
27
|
+
label: pt.label,
|
|
28
|
+
value: pt.value,
|
|
29
|
+
description: TYPE_DESCRIPTION_KEYS[pt.value] ? t(TYPE_DESCRIPTION_KEYS[pt.value]) : undefined,
|
|
30
|
+
}))
|
|
31
|
+
const typeLabel = (v: unknown) => productTypes.find((p) => p.value === v)?.label ?? String(v ?? '')
|
|
32
|
+
|
|
33
|
+
// Field order is chosen so the table columns come out as
|
|
34
|
+
// Product · Type · Stock · Cost · Value while the form groups stay correct.
|
|
35
|
+
const fields: FieldDef[] = [
|
|
36
|
+
// — General Information (form) + Product column (table) —
|
|
37
|
+
{
|
|
38
|
+
key: 'name', label: t('inventory.productForm.name'), type: 'text', required: true,
|
|
39
|
+
group: 'general', placeholder: t('inventory.productForm.namePlaceholder'),
|
|
40
|
+
searchable: true, showInTable: true, sortable: true,
|
|
41
|
+
renderCell: (_v, row: any) => (
|
|
42
|
+
<div>
|
|
43
|
+
<p className="font-medium">{row.name}</p>
|
|
44
|
+
{row.sku && <p className="text-xs text-muted-foreground">{row.sku}</p>}
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
{ key: 'brand', label: t('inventory.productForm.brand'), type: 'text', group: 'general', placeholder: t('inventory.productForm.brandPlaceholder'), showInTable: false },
|
|
49
|
+
{ key: 'sku', label: t('inventory.productForm.sku'), type: 'text', group: 'general', placeholder: t('inventory.productForm.skuPlaceholder'), searchable: true, showInTable: false },
|
|
50
|
+
{ key: 'barcode', label: t('inventory.productForm.barcode'), type: 'text', group: 'general', placeholder: t('inventory.productForm.barcodePlaceholder'), searchable: true, showInTable: false },
|
|
51
|
+
{ key: 'description', label: t('inventory.productForm.description'), type: 'textarea', group: 'general', span: 2, placeholder: t('inventory.productForm.descriptionPlaceholder'), showInTable: false },
|
|
52
|
+
|
|
53
|
+
// — Classification (form: segmented) + Type column (table: badge) —
|
|
54
|
+
{
|
|
55
|
+
key: 'productType', label: t('inventory.productForm.classification'), type: 'segmented',
|
|
56
|
+
group: 'classification', span: 2, options: typeOptions,
|
|
57
|
+
showInTable: true, sortable: false,
|
|
58
|
+
renderCell: (v) => (
|
|
59
|
+
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium bg-muted text-muted-foreground">
|
|
60
|
+
{typeLabel(v)}
|
|
61
|
+
</span>
|
|
62
|
+
),
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// — Stock column (list only) — placed before Cost for the desired column order
|
|
66
|
+
{
|
|
67
|
+
key: 'currentQuantity', label: t('inventory.productList.stock'), type: 'number',
|
|
68
|
+
showInForm: false, showInTable: true, sortable: true,
|
|
69
|
+
renderCell: (_v, row: any) => (
|
|
70
|
+
<span className={`text-right block ${row.currentQuantity <= row.minQuantity ? 'text-destructive font-medium' : ''}`}>
|
|
71
|
+
{row.currentQuantity}
|
|
72
|
+
</span>
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// — Pricing (form) + Cost column (table) —
|
|
77
|
+
{
|
|
78
|
+
key: 'costPrice', label: t('inventory.productForm.costPrice'), type: 'currency',
|
|
79
|
+
group: 'pricing', currency: currency.code, currencySymbol: currency.symbol, currencyLocale: currency.locale,
|
|
80
|
+
showInTable: true, sortable: false,
|
|
81
|
+
renderCell: (v) => <span className="text-right block text-muted-foreground">{formatCurrency(Number(v) || 0, currency)}</span>,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// — Value column (list only): stock × cost —
|
|
85
|
+
{
|
|
86
|
+
key: 'value', label: t('inventory.productList.value'), type: 'number',
|
|
87
|
+
showInForm: false, showInTable: true, sortable: false,
|
|
88
|
+
renderCell: (_v, row: any) => (
|
|
89
|
+
<span className="text-right block font-medium">{formatCurrency((row.currentQuantity || 0) * (row.costPrice || 0), currency)}</span>
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
{ key: 'salePrice', label: t('inventory.productForm.salePrice'), type: 'currency', group: 'pricing', currency: currency.code, currencySymbol: currency.symbol, currencyLocale: currency.locale, showInTable: false },
|
|
94
|
+
{
|
|
95
|
+
key: 'margin', label: t('inventory.productForm.margin'), type: 'computed', group: 'pricing', showInTable: false,
|
|
96
|
+
compute: (vals) => {
|
|
97
|
+
const cost = Number(vals.costPrice) || 0
|
|
98
|
+
const sale = Number(vals.salePrice) || 0
|
|
99
|
+
if (sale <= 0 || cost <= 0) return null
|
|
100
|
+
const m = ((sale - cost) / cost) * 100
|
|
101
|
+
return { display: `${m.toFixed(1)}%`, tone: m >= 0 ? 'positive' : 'negative' }
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// — Stock Levels (form only) —
|
|
106
|
+
{ key: 'minQuantity', label: t('inventory.productForm.minQuantity'), type: 'number', group: 'stock', min: 0, placeholder: '0', hint: t('inventory.productForm.minQuantityHint'), showInTable: false },
|
|
107
|
+
{ key: 'maxQuantity', label: t('inventory.productForm.maxQuantity'), type: 'number', group: 'stock', min: 0, placeholder: t('inventory.productForm.optional'), hint: t('inventory.productForm.maxQuantityHint'), showInTable: false },
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
name: t('inventory.stock.product'),
|
|
112
|
+
namePlural: t('inventory.nav.products'),
|
|
113
|
+
icon: 'Package',
|
|
114
|
+
displayField: 'name',
|
|
115
|
+
fields,
|
|
116
|
+
fieldGroups: [
|
|
117
|
+
{ id: 'general', label: t('inventory.productForm.generalInfo'), columns: 2, imageSlot: true },
|
|
118
|
+
{ id: 'classification', label: t('inventory.productForm.classification'), description: t('inventory.productForm.classificationDesc'), columns: 1 },
|
|
119
|
+
{ id: 'pricing', label: t('inventory.productForm.pricing'), columns: 3 },
|
|
120
|
+
{ id: 'stock', label: t('inventory.productForm.stockLevels'), description: t('inventory.productForm.stockLevelsDesc'), columns: 2 },
|
|
121
|
+
],
|
|
122
|
+
facets: [{ field: 'productType', allLabel: t('crud.list.allFacet') }],
|
|
123
|
+
}
|
|
124
|
+
}
|