@fayz-ai/plugin-inventory 0.9.0 → 0.9.2

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.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/dist/{InventoryContext.d.ts → context.d.ts} +2 -2
  3. package/dist/context.d.ts.map +1 -0
  4. package/dist/data/registries.d.ts.map +1 -0
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -3
  8. package/dist/index.js.map +1 -1
  9. package/dist/{InventoryPage.d.ts → views/InventoryPage.d.ts} +3 -3
  10. package/dist/views/InventoryPage.d.ts.map +1 -0
  11. package/dist/views/ProductCrudForm.d.ts.map +1 -1
  12. package/dist/views/dashboardWidgets.d.ts +1 -1
  13. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  14. package/dist/views/productEntity.d.ts +1 -1
  15. package/dist/views/productEntity.d.ts.map +1 -1
  16. package/package.json +5 -7
  17. package/dist/InventoryContext.d.ts.map +0 -1
  18. package/dist/InventoryPage.d.ts.map +0 -1
  19. package/dist/registries.d.ts.map +0 -1
  20. package/src/InventoryContext.tsx +0 -42
  21. package/src/InventoryPage.tsx +0 -176
  22. package/src/README.md +0 -177
  23. package/src/components/InventoryGeneralSettings.tsx +0 -39
  24. package/src/components/InventoryOnboarding.tsx +0 -60
  25. package/src/components/InventorySettings.tsx +0 -27
  26. package/src/data/index.ts +0 -2
  27. package/src/data/mock.ts +0 -266
  28. package/src/data/supabase.ts +0 -361
  29. package/src/data/tables.ts +0 -10
  30. package/src/data/types.ts +0 -35
  31. package/src/index.ts +0 -238
  32. package/src/lib/tenant.ts +0 -4
  33. package/src/locales/en.ts +0 -242
  34. package/src/locales/index.ts +0 -7
  35. package/src/locales/pt-BR.ts +0 -242
  36. package/src/migrations/000_plg_rename.sql +0 -27
  37. package/src/migrations/001_inventory_base.sql +0 -69
  38. package/src/migrations/002_recipes.sql +0 -34
  39. package/src/migrations/003_measurement_units.sql +0 -13
  40. package/src/migrations/index.ts +0 -161
  41. package/src/registries.ts +0 -111
  42. package/src/store.ts +0 -127
  43. package/src/types.ts +0 -256
  44. package/src/views/DashboardView.tsx +0 -11
  45. package/src/views/MovementHistoryView.tsx +0 -104
  46. package/src/views/ProductCrudForm.tsx +0 -104
  47. package/src/views/ProductListView.tsx +0 -58
  48. package/src/views/RecipeDetailView.tsx +0 -192
  49. package/src/views/RecipeFormView.tsx +0 -241
  50. package/src/views/RecipesView.tsx +0 -106
  51. package/src/views/StockMovementView.tsx +0 -518
  52. package/src/views/dashboardWidgets.tsx +0 -101
  53. package/src/views/productEntity.tsx +0 -124
  54. /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