@fayz-ai/plugin-inventory 0.9.0-next.1 → 0.9.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/README.md +3 -3
- package/dist/{InventoryContext.d.ts → context.d.ts} +2 -2
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/{InventoryPage.d.ts → views/InventoryPage.d.ts} +3 -3
- package/dist/views/InventoryPage.d.ts.map +1 -0
- package/dist/views/ProductCrudForm.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/dist/views/productEntity.d.ts +1 -1
- package/dist/views/productEntity.d.ts.map +1 -1
- package/package.json +5 -7
- package/dist/InventoryContext.d.ts.map +0 -1
- package/dist/InventoryPage.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/InventoryContext.tsx +0 -42
- package/src/InventoryPage.tsx +0 -176
- package/src/README.md +0 -177
- package/src/components/InventoryGeneralSettings.tsx +0 -39
- package/src/components/InventoryOnboarding.tsx +0 -60
- package/src/components/InventorySettings.tsx +0 -27
- package/src/data/index.ts +0 -2
- package/src/data/mock.ts +0 -266
- package/src/data/supabase.ts +0 -361
- package/src/data/tables.ts +0 -10
- package/src/data/types.ts +0 -35
- package/src/index.ts +0 -238
- package/src/lib/tenant.ts +0 -4
- package/src/locales/en.ts +0 -242
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -242
- package/src/migrations/000_plg_rename.sql +0 -27
- package/src/migrations/001_inventory_base.sql +0 -69
- package/src/migrations/002_recipes.sql +0 -34
- package/src/migrations/003_measurement_units.sql +0 -13
- package/src/migrations/index.ts +0 -161
- package/src/registries.ts +0 -111
- package/src/store.ts +0 -127
- package/src/types.ts +0 -256
- package/src/views/DashboardView.tsx +0 -11
- package/src/views/MovementHistoryView.tsx +0 -104
- package/src/views/ProductCrudForm.tsx +0 -104
- package/src/views/ProductListView.tsx +0 -58
- package/src/views/RecipeDetailView.tsx +0 -192
- package/src/views/RecipeFormView.tsx +0 -241
- package/src/views/RecipesView.tsx +0 -106
- package/src/views/StockMovementView.tsx +0 -518
- package/src/views/dashboardWidgets.tsx +0 -101
- package/src/views/productEntity.tsx +0 -124
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
2
|
-
import { ArrowUpRight, ArrowDownRight } from 'lucide-react'
|
|
3
|
-
import type { StoreApi } from 'zustand'
|
|
4
|
-
import type { DashboardWidgetDef } from '@fayz-ai/core'
|
|
5
|
-
import { useTranslation } from '@fayz-ai/core'
|
|
6
|
-
import {
|
|
7
|
-
KpiCard, Card, CardHeader, CardTitle, CardContent,
|
|
8
|
-
defineKpiWidget, defineCustomWidget,
|
|
9
|
-
} from '@fayz-ai/ui'
|
|
10
|
-
import { InventoryContextProvider, useInventoryConfig, useInventoryStore, formatCurrency, type ResolvedInventoryConfig } from '../InventoryContext'
|
|
11
|
-
import type { InventoryDataProvider } from '../data/types'
|
|
12
|
-
import type { InventoryUIState } from '../store'
|
|
13
|
-
|
|
14
|
-
function useEnsureSummary() {
|
|
15
|
-
const fetchSummary = useInventoryStore((s) => s.fetchSummary)
|
|
16
|
-
useEffect(() => { void fetchSummary() }, [])
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function StockValueKpi() {
|
|
20
|
-
const t = useTranslation()
|
|
21
|
-
const { currency } = useInventoryConfig()
|
|
22
|
-
const summary = useInventoryStore((s) => s.summary)
|
|
23
|
-
useEnsureSummary()
|
|
24
|
-
return (
|
|
25
|
-
<KpiCard label={t('inventory.dashboard.stockValue')} icon="BarChart3"
|
|
26
|
-
value={formatCurrency(summary?.totalStockValue ?? 0, currency)} sub={t('inventory.dashboard.totalValue')} />
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function TotalProductsKpi() {
|
|
31
|
-
const t = useTranslation()
|
|
32
|
-
const summary = useInventoryStore((s) => s.summary)
|
|
33
|
-
useEnsureSummary()
|
|
34
|
-
return <KpiCard label={t('inventory.dashboard.totalProducts')} icon="Package" value={String(summary?.totalProducts ?? 0)} sub={t('inventory.dashboard.activeItems')} />
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function LowStockKpi() {
|
|
38
|
-
const t = useTranslation()
|
|
39
|
-
const summary = useInventoryStore((s) => s.summary)
|
|
40
|
-
useEnsureSummary()
|
|
41
|
-
return <KpiCard label={t('inventory.dashboard.lowStock')} icon="AlertTriangle" value={String(summary?.lowStockCount ?? 0)} sub={t('inventory.dashboard.belowMinimum')} />
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function OutOfStockKpi() {
|
|
45
|
-
const t = useTranslation()
|
|
46
|
-
const summary = useInventoryStore((s) => s.summary)
|
|
47
|
-
useEnsureSummary()
|
|
48
|
-
return <KpiCard label={t('inventory.dashboard.outOfStock')} icon="TrendingDown" value={String(summary?.outOfStockCount ?? 0)} sub={t('inventory.dashboard.needRestocking')} />
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function RecentActivityPanel() {
|
|
52
|
-
const t = useTranslation()
|
|
53
|
-
const summary = useInventoryStore((s) => s.summary)
|
|
54
|
-
useEnsureSummary()
|
|
55
|
-
return (
|
|
56
|
-
<Card>
|
|
57
|
-
<CardHeader><CardTitle>{t('inventory.dashboard.recentActivity')}</CardTitle></CardHeader>
|
|
58
|
-
<CardContent>
|
|
59
|
-
<div className="flex items-center gap-4 text-sm">
|
|
60
|
-
<div className="flex items-center gap-1.5">
|
|
61
|
-
<ArrowUpRight className="h-3.5 w-3.5 text-success" />
|
|
62
|
-
<span className="text-muted-foreground">{t('inventory.dashboard.entries')}</span>
|
|
63
|
-
<span className="font-medium">{summary?.movementsByType?.entry ?? 0}</span>
|
|
64
|
-
</div>
|
|
65
|
-
<div className="flex items-center gap-1.5">
|
|
66
|
-
<ArrowDownRight className="h-3.5 w-3.5 text-destructive" />
|
|
67
|
-
<span className="text-muted-foreground">{t('inventory.dashboard.exits')}</span>
|
|
68
|
-
<span className="font-medium">{summary?.movementsByType?.exit ?? 0}</span>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
<p className="mt-2 text-xs text-muted-foreground">{t('inventory.dashboard.last7Days')}</p>
|
|
72
|
-
</CardContent>
|
|
73
|
-
</Card>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function createInventoryDashboardWidgets(ctx: {
|
|
78
|
-
config: ResolvedInventoryConfig
|
|
79
|
-
provider: InventoryDataProvider
|
|
80
|
-
store: StoreApi<InventoryUIState>
|
|
81
|
-
}): DashboardWidgetDef[] {
|
|
82
|
-
const withCtx = (Inner: React.ComponentType): React.ComponentType<unknown> => {
|
|
83
|
-
const Wrapped = () => (
|
|
84
|
-
<InventoryContextProvider config={ctx.config} provider={ctx.provider} store={ctx.store}>
|
|
85
|
-
<Inner />
|
|
86
|
-
</InventoryContextProvider>
|
|
87
|
-
)
|
|
88
|
-
Wrapped.displayName = `InventoryWidget(${Inner.displayName ?? Inner.name})`
|
|
89
|
-
return Wrapped
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return [
|
|
93
|
-
// Stock value is inventory's headline KPI on the global home; the rest are
|
|
94
|
-
// home-hidden by default (shown on the inventory plugin-home, addable via Customize).
|
|
95
|
-
defineKpiWidget({ id: 'inventory.kpi.stock-value', title: 'inventory.dashboard.stockValue', domain: 'inventory', defaultOrder: 0, component: withCtx(StockValueKpi) }),
|
|
96
|
-
defineKpiWidget({ id: 'inventory.kpi.total-products', title: 'inventory.dashboard.totalProducts', domain: 'inventory', defaultOrder: 1, defaultVisible: false, component: withCtx(TotalProductsKpi) }),
|
|
97
|
-
defineKpiWidget({ id: 'inventory.kpi.low-stock', title: 'inventory.dashboard.lowStock', domain: 'inventory', defaultOrder: 2, defaultVisible: false, component: withCtx(LowStockKpi) }),
|
|
98
|
-
defineKpiWidget({ id: 'inventory.kpi.out-of-stock', title: 'inventory.dashboard.outOfStock', domain: 'inventory', defaultOrder: 3, defaultVisible: false, component: withCtx(OutOfStockKpi) }),
|
|
99
|
-
defineCustomWidget({ id: 'inventory.panel.recent-activity', title: 'inventory.dashboard.recentActivity', domain: 'inventory', span: 2, defaultOrder: 10, surfaces: ['plugin-home'], component: withCtx(RecentActivityPanel) }),
|
|
100
|
-
]
|
|
101
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
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
|
-
}
|
|
File without changes
|