@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.
Files changed (93) hide show
  1. package/dist/InventoryContext.d.ts +37 -0
  2. package/dist/InventoryContext.d.ts.map +1 -0
  3. package/dist/InventoryPage.d.ts +13 -0
  4. package/dist/InventoryPage.d.ts.map +1 -0
  5. package/dist/components/InventoryGeneralSettings.d.ts +3 -0
  6. package/dist/components/InventoryGeneralSettings.d.ts.map +1 -0
  7. package/dist/components/InventoryOnboarding.d.ts +5 -0
  8. package/dist/components/InventoryOnboarding.d.ts.map +1 -0
  9. package/dist/components/InventorySettings.d.ts +8 -0
  10. package/dist/components/InventorySettings.d.ts.map +1 -0
  11. package/dist/data/index.d.ts +3 -0
  12. package/dist/data/index.d.ts.map +1 -0
  13. package/dist/data/mock.d.ts +3 -0
  14. package/dist/data/mock.d.ts.map +1 -0
  15. package/dist/data/supabase.d.ts +3 -0
  16. package/dist/data/supabase.d.ts.map +1 -0
  17. package/dist/data/types.d.ts +22 -0
  18. package/dist/data/types.d.ts.map +1 -0
  19. package/dist/index.cjs +2936 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.ts +48 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +2930 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lib/tenant.d.ts +3 -0
  26. package/dist/lib/tenant.d.ts.map +1 -0
  27. package/dist/locales/en.d.ts +2 -0
  28. package/dist/locales/en.d.ts.map +1 -0
  29. package/dist/locales/index.d.ts +2 -0
  30. package/dist/locales/index.d.ts.map +1 -0
  31. package/dist/locales/pt-BR.d.ts +2 -0
  32. package/dist/locales/pt-BR.d.ts.map +1 -0
  33. package/dist/registries.d.ts +3 -0
  34. package/dist/registries.d.ts.map +1 -0
  35. package/dist/store.d.ts +28 -0
  36. package/dist/store.d.ts.map +1 -0
  37. package/dist/types.d.ts +213 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/views/DashboardView.d.ts +8 -0
  40. package/dist/views/DashboardView.d.ts.map +1 -0
  41. package/dist/views/MovementHistoryView.d.ts +5 -0
  42. package/dist/views/MovementHistoryView.d.ts.map +1 -0
  43. package/dist/views/ProductCrudForm.d.ts +6 -0
  44. package/dist/views/ProductCrudForm.d.ts.map +1 -0
  45. package/dist/views/ProductFormView.d.ts +6 -0
  46. package/dist/views/ProductFormView.d.ts.map +1 -0
  47. package/dist/views/ProductListView.d.ts +6 -0
  48. package/dist/views/ProductListView.d.ts.map +1 -0
  49. package/dist/views/RecipeDetailView.d.ts +6 -0
  50. package/dist/views/RecipeDetailView.d.ts.map +1 -0
  51. package/dist/views/RecipeFormView.d.ts +5 -0
  52. package/dist/views/RecipeFormView.d.ts.map +1 -0
  53. package/dist/views/RecipesView.d.ts +6 -0
  54. package/dist/views/RecipesView.d.ts.map +1 -0
  55. package/dist/views/StockMovementView.d.ts +8 -0
  56. package/dist/views/StockMovementView.d.ts.map +1 -0
  57. package/dist/views/dashboardWidgets.d.ts +11 -0
  58. package/dist/views/dashboardWidgets.d.ts.map +1 -0
  59. package/dist/views/productEntity.d.ts +6 -0
  60. package/dist/views/productEntity.d.ts.map +1 -0
  61. package/package.json +55 -0
  62. package/src/InventoryContext.tsx +40 -0
  63. package/src/InventoryPage.tsx +170 -0
  64. package/src/README.md +177 -0
  65. package/src/components/InventoryGeneralSettings.tsx +26 -0
  66. package/src/components/InventoryOnboarding.tsx +60 -0
  67. package/src/components/InventorySettings.tsx +27 -0
  68. package/src/data/index.ts +2 -0
  69. package/src/data/mock.ts +266 -0
  70. package/src/data/supabase.ts +358 -0
  71. package/src/data/types.ts +35 -0
  72. package/src/index.ts +191 -0
  73. package/src/lib/tenant.ts +4 -0
  74. package/src/locales/en.ts +242 -0
  75. package/src/locales/index.ts +7 -0
  76. package/src/locales/pt-BR.ts +242 -0
  77. package/src/migrations/001_inventory_base.sql +69 -0
  78. package/src/migrations/002_recipes.sql +34 -0
  79. package/src/migrations/003_measurement_units.sql +13 -0
  80. package/src/registries.ts +111 -0
  81. package/src/store.ts +127 -0
  82. package/src/types.ts +256 -0
  83. package/src/views/DashboardView.tsx +11 -0
  84. package/src/views/MovementHistoryView.tsx +104 -0
  85. package/src/views/ProductCrudForm.tsx +99 -0
  86. package/src/views/ProductFormView.tsx +283 -0
  87. package/src/views/ProductListView.tsx +107 -0
  88. package/src/views/RecipeDetailView.tsx +192 -0
  89. package/src/views/RecipeFormView.tsx +235 -0
  90. package/src/views/RecipesView.tsx +103 -0
  91. package/src/views/StockMovementView.tsx +516 -0
  92. package/src/views/dashboardWidgets.tsx +101 -0
  93. 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
+ }