@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.
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,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { StoreApi } from 'zustand/vanilla';
3
- import { type ResolvedInventoryConfig } from './InventoryContext';
4
- import type { InventoryDataProvider } from './data/types';
5
- import type { InventoryUIState } from './store';
3
+ import { type ResolvedInventoryConfig } from '../context';
4
+ import type { InventoryDataProvider } from '../data/types';
5
+ import type { InventoryUIState } from '../store';
6
6
  import type { PluginRegistryDef } from '@fayz-ai/core';
7
7
  export declare function InventoryPage({ config, provider, store, registries }: {
8
8
  config: ResolvedInventoryConfig;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InventoryPage.d.ts","sourceRoot":"","sources":["../../src/views/InventoryPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,eAAe,CAAA;AA+CzE,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IACrE,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IACjC,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAA;CACjC,qBAqHA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductCrudForm.d.ts","sourceRoot":"","sources":["../../src/views/ProductCrudForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAgBzB,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,qBAuF7F"}
1
+ {"version":3,"file":"ProductCrudForm.d.ts","sourceRoot":"","sources":["../../src/views/ProductCrudForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAgBzB,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,qBA2F7F"}
@@ -1,6 +1,6 @@
1
1
  import type { StoreApi } from 'zustand';
2
2
  import type { DashboardWidgetDef } from '@fayz-ai/core';
3
- import { type ResolvedInventoryConfig } from '../InventoryContext';
3
+ import { type ResolvedInventoryConfig } from '../context';
4
4
  import type { InventoryDataProvider } from '../data/types';
5
5
  import type { InventoryUIState } from '../store';
6
6
  export declare function createInventoryDashboardWidgets(ctx: {
@@ -1 +1 @@
1
- {"version":3,"file":"dashboardWidgets.d.ts","sourceRoot":"","sources":["../../src/views/dashboardWidgets.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAMvD,OAAO,EAAmF,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AACnJ,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAiEhD,wBAAgB,+BAA+B,CAAC,GAAG,EAAE;IACnD,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;CAClC,GAAG,kBAAkB,EAAE,CAoBvB"}
1
+ {"version":3,"file":"dashboardWidgets.d.ts","sourceRoot":"","sources":["../../src/views/dashboardWidgets.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAMvD,OAAO,EAAmF,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAC1I,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAiEhD,wBAAgB,+BAA+B,CAAC,GAAG,EAAE;IACnD,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;CAClC,GAAG,kBAAkB,EAAE,CAoBvB"}
@@ -1,5 +1,5 @@
1
1
  import type { EntityDef } from '@fayz-ai/core';
2
- import { type InventoryCurrency, type ProductTypeOption } from '../InventoryContext';
2
+ import { type InventoryCurrency, type ProductTypeOption } from '../context';
3
3
  type T = (key: string, params?: Record<string, string | number>) => string;
4
4
  export declare function buildProductEntity(t: T, productTypes: ProductTypeOption[], currency: InventoryCurrency): EntityDef;
5
5
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"productEntity.d.ts","sourceRoot":"","sources":["../../src/views/productEntity.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,eAAe,CAAA;AACxD,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgBpG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;AAE1E,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,CAAC,EACJ,YAAY,EAAE,iBAAiB,EAAE,EACjC,QAAQ,EAAE,iBAAiB,GAC1B,SAAS,CAmGX"}
1
+ {"version":3,"file":"productEntity.d.ts","sourceRoot":"","sources":["../../src/views/productEntity.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,eAAe,CAAA;AACxD,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAgB3F,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;AAE1E,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,CAAC,EACJ,YAAY,EAAE,iBAAiB,EAAE,EACjC,QAAQ,EAAE,iBAAiB,GAC1B,SAAS,CAmGX"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.9.0-next.1",
6
+ "version": "0.9.1",
7
7
  "description": "Fayz SDK — plugin-inventory plugin",
8
8
  "type": "module",
9
9
  "sideEffects": false,
@@ -12,14 +12,12 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "exports": {
14
14
  ".": {
15
- "source": "./src/index.ts",
16
15
  "types": "./dist/index.d.ts",
17
16
  "import": "./dist/index.js"
18
17
  }
19
18
  },
20
19
  "files": [
21
- "dist",
22
- "src"
20
+ "dist"
23
21
  ],
24
22
  "peerDependencies": {
25
23
  "react": "^18.0.0 || ^19.0.0",
@@ -29,9 +27,9 @@
29
27
  },
30
28
  "dependencies": {
31
29
  "@tanstack/react-table": "^8.20.0",
32
- "@fayz-ai/core": "^0.9.0-next.1",
33
- "@fayz-ai/ui": "^0.9.0-next.1",
34
- "@fayz-ai/saas": "^0.9.0-next.1"
30
+ "@fayz-ai/core": "^0.10.0",
31
+ "@fayz-ai/ui": "^0.10.0",
32
+ "@fayz-ai/admin": "^0.10.0"
35
33
  },
36
34
  "devDependencies": {
37
35
  "@types/react": "^18.3.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"InventoryContext.d.ts","sourceRoot":"","sources":["../src/InventoryContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AACxF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAE9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACjC,SAAS,EAAE,cAAc,EAAE,CAAA;CAC5B;AAID,eAAO,MAAM,wBAAwB;;;;;EAAsB,CAAA;AAC3D,eAAO,MAAM,kBAAkB,+BAAgB,CAAA;AAC/C,eAAO,MAAM,oBAAoB,6BAAkB,CAAA;AACnD,eAAO,MAAM,iBAAiB,oDAAe,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"InventoryPage.d.ts","sourceRoot":"","sources":["../src/InventoryPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC3F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,eAAe,CAAA;AA+CzE,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IACrE,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IACjC,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAA;CACjC,qBAqHA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../src/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAsEtD,eAAO,MAAM,mBAAmB,EAAE,iBAAiB,EAwClD,CAAA"}
@@ -1,42 +0,0 @@
1
- import { createPluginContext, formatCurrency, type CurrencyConfig } from '@fayz-ai/saas'
2
- import type { InventoryPluginLabels } from './index'
3
- import type { InventoryDataProvider } from './data/types'
4
- import type { InventoryUIState } from './store'
5
-
6
- export interface InventoryModules {
7
- /** Product catalogue screens. Off when the host registers products elsewhere. */
8
- products: boolean
9
- recipes: boolean
10
- stockLocations: boolean
11
- batchTracking: boolean
12
- }
13
-
14
- export type InventoryCurrency = CurrencyConfig
15
-
16
- export interface ProductTypeOption {
17
- value: string
18
- label: string
19
- }
20
-
21
- export interface LocationOption {
22
- id: string
23
- name: string
24
- isHQ?: boolean
25
- }
26
-
27
- export interface ResolvedInventoryConfig {
28
- modules: InventoryModules
29
- labels: InventoryPluginLabels
30
- currency: InventoryCurrency
31
- productTypes: ProductTypeOption[]
32
- locations: LocationOption[]
33
- }
34
-
35
- const ctx = createPluginContext<ResolvedInventoryConfig, InventoryDataProvider, InventoryUIState>('InventoryPage')
36
-
37
- export const InventoryContextProvider = ctx.ContextProvider
38
- export const useInventoryConfig = ctx.useConfig
39
- export const useInventoryProvider = ctx.useProvider
40
- export const useInventoryStore = ctx.useStore
41
-
42
- export { formatCurrency }
@@ -1,176 +0,0 @@
1
- import React, { useState, useMemo } from 'react'
2
- import type { StoreApi } from 'zustand/vanilla'
3
- import { ModulePage, PageTransition, type ModuleNavItem } from '@fayz-ai/ui'
4
- import { InventoryContextProvider, type ResolvedInventoryConfig } from './InventoryContext'
5
- import type { InventoryDataProvider } from './data/types'
6
- import type { InventoryUIState } from './store'
7
- import type { PluginRegistryDef, PluginQuickAction } from '@fayz-ai/core'
8
- import { useModuleNavigation, ModuleActionBar, createViewRouter } from '@fayz-ai/saas'
9
- import { useTranslation } from '@fayz-ai/core'
10
- import { DashboardView } from './views/DashboardView'
11
- import { ProductListView } from './views/ProductListView'
12
- import { ProductCrudForm } from './views/ProductCrudForm'
13
- import { StockMovementView } from './views/StockMovementView'
14
- import { MovementHistoryView } from './views/MovementHistoryView'
15
- import { RecipesView } from './views/RecipesView'
16
- import { RecipeFormView } from './views/RecipeFormView'
17
- import { RecipeDetailView } from './views/RecipeDetailView'
18
- import { InventoryGeneralSettings } from './components/InventoryGeneralSettings'
19
- import { InventoryOnboarding } from './components/InventoryOnboarding'
20
-
21
- function buildNav(config: ResolvedInventoryConfig, view: string, navigate: (v: string) => void, t: (key: string) => string): ModuleNavItem[] {
22
- const items: ModuleNavItem[] = [
23
- { id: 'dashboard', label: t('inventory.nav.dashboard'), icon: 'BarChart3', active: view === 'dashboard', onClick: () => navigate('dashboard') },
24
- ...(config.modules.products ? [{
25
- id: 'products', label: t('inventory.nav.products'), icon: 'Package', active: view.startsWith('products'),
26
- children: [
27
- { id: 'products-new', label: t('inventory.nav.new'), active: view === 'products-new', onClick: () => navigate('products-new') },
28
- { id: 'products-list', label: t('inventory.nav.list'), active: view === 'products-list', onClick: () => navigate('products-list') },
29
- ],
30
- } as ModuleNavItem] : []),
31
- {
32
- id: 'stock', label: t('inventory.nav.stock'), icon: 'ArrowUpCircle',
33
- children: [
34
- { id: 'stock-entry', label: t('inventory.nav.entry'), active: view === 'stock-entry', onClick: () => navigate('stock-entry') },
35
- { id: 'stock-exit', label: t('inventory.nav.exit'), active: view === 'stock-exit', onClick: () => navigate('stock-exit') },
36
- { id: 'stock-history', label: t('inventory.nav.history'), active: view === 'stock-history', onClick: () => navigate('stock-history') },
37
- ],
38
- },
39
- ]
40
-
41
- if (config.modules.recipes) {
42
- items.push({
43
- id: 'recipes', label: config.labels.recipes, icon: 'BookOpen',
44
- children: [
45
- { id: 'recipes-list', label: config.labels.recipesList, active: view === 'recipes-list' || view.startsWith('recipes-detail:'), onClick: () => navigate('recipes-list') },
46
- { id: 'recipes-new', label: config.labels.recipesNew, active: view === 'recipes-new', onClick: () => navigate('recipes-new') },
47
- ],
48
- })
49
- }
50
-
51
- return items
52
- }
53
-
54
- export function InventoryPage({ config, provider, store, registries }: {
55
- config: ResolvedInventoryConfig
56
- provider: InventoryDataProvider
57
- store: StoreApi<InventoryUIState>
58
- registries?: PluginRegistryDef[]
59
- }) {
60
- const t = useTranslation()
61
- const { view, direction, navigate } = useModuleNavigation('/inventory', {
62
- dashboard: 0,
63
- 'products-list': 0, 'products-new': 1,
64
- 'stock-entry': 1, 'stock-exit': 1, 'stock-history': 0,
65
- 'recipes-list': 0, 'recipes-new': 1, 'recipes-detail': 1,
66
- settings: 1,
67
- }, 'dashboard')
68
-
69
- const [onboardingComplete, setOnboardingComplete] = useState(() => {
70
- try { return localStorage.getItem('saas-core:inventory-onboarded') === 'true' } catch { return false }
71
- })
72
-
73
- const isSettings = view === 'settings'
74
- const isSummary = view === 'dashboard' || view === ''
75
- const nav = buildNav(config, view, navigate, t)
76
-
77
- const quickActions = useMemo<PluginQuickAction[]>(() => {
78
- const actions: PluginQuickAction[] = [
79
- // Dropped with the catalogue module: offering "new product" while the
80
- // screen is gone would dead-end the user.
81
- ...(config.modules.products ? [{
82
- id: 'new-product',
83
- label: t('inventory.quickActions.newProduct'),
84
- icon: 'Package',
85
- description: t('inventory.quickActions.newProductDesc'),
86
- action: () => navigate('products-new'),
87
- } as PluginQuickAction] : []),
88
- {
89
- id: 'stock-entry',
90
- label: t('inventory.quickActions.stockEntry'),
91
- icon: 'ArrowUpRight',
92
- description: t('inventory.quickActions.stockEntryDesc'),
93
- action: () => navigate('stock-entry'),
94
- },
95
- {
96
- id: 'stock-exit',
97
- label: t('inventory.quickActions.stockExit'),
98
- icon: 'ArrowDownRight',
99
- description: t('inventory.quickActions.stockExitDesc'),
100
- action: () => navigate('stock-exit'),
101
- },
102
- ]
103
- return actions
104
- }, [])
105
-
106
- if (!onboardingComplete) {
107
- return (
108
- <InventoryContextProvider config={config} provider={provider} store={store}>
109
- <InventoryOnboarding onComplete={() => { setOnboardingComplete(true); try { localStorage.setItem('saas-core:inventory-onboarded', 'true') } catch {} }} />
110
- </InventoryContextProvider>
111
- )
112
- }
113
-
114
- if (isSettings && registries && registries.length > 0) {
115
- return (
116
- <InventoryContextProvider config={config} provider={provider} store={store}>
117
- <PageTransition transitionKey="settings" direction={direction}>
118
- <div style={{ padding: '24px' }}>
119
- <div style={{ marginBottom: '16px' }}>
120
- <h1 style={{ fontSize: '20px', fontWeight: 600, margin: 0 }}>{t('inventory.settingsPage.title')}</h1>
121
- <p style={{ color: 'var(--muted-foreground, #6b7280)', margin: '4px 0 0', fontSize: '14px' }}>
122
- {t('inventory.settingsPage.subtitle')}
123
- </p>
124
- </div>
125
- <InventoryGeneralSettings />
126
- </div>
127
- </PageTransition>
128
- </InventoryContextProvider>
129
- )
130
- }
131
-
132
- const renderView = createViewRouter([
133
- // Routes go with the module, not just the nav entry: leaving them mounted
134
- // keeps a second product form reachable by URL.
135
- ...(config.modules.products ? [
136
- { id: 'products-list', render: () => <ProductListView onNew={() => navigate('products-new')} onEdit={(id: string) => navigate(`products-edit:${id}`)} /> },
137
- { id: 'products-new', render: () => <ProductCrudForm onSaved={() => navigate('products-list')} /> },
138
- { id: 'products-edit', render: ({ id }: { id?: string }) => <ProductCrudForm editId={id!} onSaved={() => navigate('products-list')} /> },
139
- ] : []),
140
- { id: 'stock-entry', render: () => <StockMovementView defaultType="entry" onSaved={() => navigate('stock-history')} /> },
141
- { id: 'stock-exit', render: () => <StockMovementView defaultType="exit" onSaved={() => navigate('stock-history')} /> },
142
- { id: 'stock-history', render: () => <MovementHistoryView onViewDetail={(id) => navigate(`stock-detail:${id}`)} /> },
143
- { id: 'stock-detail', render: ({ id }) => {
144
- const movement = store.getState().movements.find((m) => m.id === id)
145
- return movement
146
- ? <StockMovementView defaultType={movement.movementType} viewMovement={movement} onSaved={() => navigate('stock-history')} />
147
- : <MovementHistoryView onViewDetail={(mid) => navigate(`stock-detail:${mid}`)} />
148
- } },
149
- { id: 'recipes-list', render: () => <RecipesView onNew={() => navigate('recipes-new')} onView={(id) => navigate(`recipes-detail:${id}`)} /> },
150
- { id: 'recipes-new', render: () => <RecipeFormView onSaved={(id) => id ? navigate(`recipes-detail:${id}`) : navigate('recipes-list')} /> },
151
- { id: 'recipes-detail', render: ({ id }) => <RecipeDetailView recipeId={id!} onBack={() => navigate('recipes-list')} /> },
152
- { id: 'dashboard', render: () => <DashboardView /> },
153
- ], 'dashboard')
154
-
155
- return (
156
- <InventoryContextProvider config={config} provider={provider} store={store}>
157
- <ModulePage
158
- title={config.labels.pageTitle}
159
- subtitle={config.labels.pageSubtitle}
160
- nav={nav}
161
- showHeader={isSummary}
162
- viewKey={view}
163
- direction={direction}
164
- headerAction={
165
- <ModuleActionBar
166
- quickActions={quickActions}
167
- settingsPath={registries && registries.length > 0 ? '/settings/inventory' : undefined}
168
- settingsLabel="Inventory Settings"
169
- />
170
- }
171
- >
172
- {renderView(view)}
173
- </ModulePage>
174
- </InventoryContextProvider>
175
- )
176
- }
package/src/README.md DELETED
@@ -1,177 +0,0 @@
1
- # Inventory Plugin
2
-
3
- Product catalog, stock management, movement tracking, and recipe/production formulas for any SaaS vertical.
4
-
5
- ## Quick Start
6
-
7
- ```typescript
8
- import { createInventoryPlugin } from '@fayz-ai/saas-core/plugins/inventory'
9
-
10
- // In your defineSaas config:
11
- plugins: [
12
- createInventoryPlugin({
13
- currency: { code: 'BRL', locale: 'pt-BR', symbol: 'R$' },
14
- }),
15
- ]
16
- ```
17
-
18
- ## Configuration
19
-
20
- | Option | Type | Default | Description |
21
- |--------|------|---------|-------------|
22
- | `modules.recipes` | boolean | true | Production recipes/technical specs |
23
- | `modules.stockLocations` | boolean | true | Multi-location stock |
24
- | `modules.batchTracking` | boolean | false | Batch numbers + expiry dates |
25
- | `productTypes` | array | ingredient, sale, intermediate, asset | Available product classifications |
26
- | `currency` | object | BRL/pt-BR | Currency code, locale, symbol |
27
- | `locations` | array | [] | Business units (shows unit picker when 1+) |
28
- | `dataProvider` | InventoryDataProvider | mock | Custom data provider |
29
- | `navPosition` | number | 4 | Navigation order |
30
-
31
- ## Architecture
32
-
33
- ```
34
- src/plugins/inventory/
35
- index.ts # createInventoryPlugin() factory
36
- types.ts # Pure TS domain types (zero deps)
37
- registries.ts # CRUD entities: units, categories, locations
38
- store.ts # Zustand UI state + provider actions
39
- InventoryContext.tsx # React contexts: config, provider, store
40
- InventoryPage.tsx # Main page with ModulePage layout
41
- data/
42
- types.ts # InventoryDataProvider interface
43
- mock.ts # In-memory mock provider
44
- views/ # Dashboard, Products, Stock, Recipes
45
- components/ # Settings, onboarding
46
- migrations/ # SQL migration files
47
- ```
48
-
49
- ### Data Flow
50
-
51
- Same pattern as the Financial plugin:
52
-
53
- ```
54
- createInventoryPlugin(options)
55
- → resolves config
56
- → creates provider (mock or custom)
57
- → creates zustand store
58
- → returns PluginManifest
59
-
60
- InventoryPage
61
- → InventoryContextProvider wraps all views
62
- → useModuleNavigation for view switching + animations
63
- → views call useInventoryStore(selector) for data
64
- ```
65
-
66
- ## Type System
67
-
68
- ### Product Types
69
-
70
- Products are classified by type — a product can be one type at a time:
71
-
72
- | Type | Description | Example |
73
- |------|-------------|---------|
74
- | `ingredient` | Raw material consumed in production | Hair dye, flour |
75
- | `sale` | Sold directly to customers | Shampoo bottle, menu item |
76
- | `intermediate` | Produced internally from ingredients | Pre-mixed color, dough |
77
- | `asset` | Fixed asset for patrimony tracking | Equipment, furniture |
78
-
79
- The `productTypes` option lets verticals customize available types:
80
-
81
- ```typescript
82
- // Restaurant — simplified types
83
- createInventoryPlugin({
84
- productTypes: [
85
- { value: 'ingredient', label: 'Ingredient' },
86
- { value: 'sale', label: 'Menu Item' },
87
- ],
88
- })
89
- ```
90
-
91
- ### Stock Movements
92
-
93
- | Movement Type | Direction | Description |
94
- |--------------|-----------|-------------|
95
- | `entry` | +quantity | Receiving goods from supplier |
96
- | `exit` | -quantity | Using/selling goods |
97
- | `adjustment` | +/- | Quantity correction after audit |
98
- | `transfer` | neutral | Between stock locations |
99
- | `loss` | -quantity | Waste, damage, or expiry |
100
-
101
- ### Core Entities
102
-
103
- | Entity | Purpose |
104
- |--------|---------|
105
- | `Product` | Master product with type, pricing, stock levels |
106
- | `StockMovement` | Transaction log: entries, exits, adjustments |
107
- | `StockPosition` | Physical inventory by location + batch + expiry |
108
- | `StockLocation` | Warehouses, storage areas |
109
- | `Recipe` | Production formula for intermediate/final products |
110
- | `RecipeIngredient` | What goes into a recipe |
111
- | `MeasurementUnit` | Units of measure (kg, L, unit, box) |
112
- | `ProductCategory` | Hierarchical product organization |
113
-
114
- ## Registries (Plugin Settings)
115
-
116
- Accessible via gear icon or global Settings > Inventory tab:
117
-
118
- | Registry | Seed Data |
119
- |----------|-----------|
120
- | Measurement Units | Unit, Box, Kg, g, L, mL |
121
- | Product Categories | — |
122
- | Stock Locations | — |
123
-
124
- ## Database Migrations
125
-
126
- Run in order:
127
-
128
- 1. `001_inventory_base.sql` — product_categories, stock_locations, products, stock_movements, stock_positions
129
- 2. `002_recipes.sql` — recipes, recipe_ingredients
130
- 3. `003_measurement_units.sql` — measurement_units
131
-
132
- All tables use `tenant_id` for multi-tenant isolation.
133
-
134
- ## Views
135
-
136
- | View | Route suffix | Description |
137
- |------|-------------|-------------|
138
- | Dashboard | `/inventory` | KPIs: total products, low stock, stock value |
139
- | Products List | `/inventory/products/list` | Filterable by type |
140
- | Products New | `/inventory/products/new` | Create product |
141
- | Stock Entry | `/inventory/stock/entry` | Record goods received |
142
- | Stock Exit | `/inventory/stock/exit` | Record goods consumed |
143
- | Stock History | `/inventory/stock/history` | Movement log |
144
- | Recipes | `/inventory/recipes/list` | Production formulas |
145
- | Settings | `/inventory/settings/*` | Registry management |
146
-
147
- ## Extending
148
-
149
- ### Custom Data Provider
150
-
151
- ```typescript
152
- import type { InventoryDataProvider } from '@fayz-ai/saas-core/plugins/inventory'
153
-
154
- const supabaseProvider: InventoryDataProvider = {
155
- async getProducts(query) { /* Supabase query */ },
156
- async createMovement(input) { /* Supabase insert + trigger */ },
157
- // ... implement all methods
158
- }
159
-
160
- createInventoryPlugin({ dataProvider: supabaseProvider })
161
- ```
162
-
163
- ### Future: Unit Conversion System
164
-
165
- The types support multi-unit products (base unit, content unit, purchase unit) following the beautyplace pattern. Example:
166
-
167
- ```
168
- Shampoo bottle (base: UNIT)
169
- - Content: 300mL per unit
170
- - Purchase: BOX of 12 units
171
- ```
172
-
173
- This will be implemented when the Supabase provider is built.
174
-
175
- ### Future: DANFE/NF-e Import
176
-
177
- Invoice XML import for automatic stock entry — will be added as a sub-module.
@@ -1,39 +0,0 @@
1
- import React from 'react'
2
- import { SettingsGroup, ToggleRow, useTenantPluginSettings } from '@fayz-ai/saas'
3
- import { useTranslation } from '@fayz-ai/core'
4
-
5
- // Per-tenant defaults = the values that used to be hardcoded here. Persisted +
6
- // re-hydrated per tenant; behavioural consumption is a follow-up.
7
- const INVENTORY_DEFAULTS = {
8
- lowStockAlerts: true,
9
- requireReason: true,
10
- autoDeduct: false,
11
- requireSku: false,
12
- allowNegative: false,
13
- lowStockEmail: false,
14
- expiryWarnings: true,
15
- }
16
-
17
- export function InventoryGeneralSettings() {
18
- const t = useTranslation()
19
- const s = useTenantPluginSettings('inventory', INVENTORY_DEFAULTS)
20
- return (
21
- <div className="space-y-4">
22
- <SettingsGroup title={t('inventory.settings.stockManagement')} description={t('inventory.settings.stockManagementDesc')}>
23
- <ToggleRow label={t('inventory.settings.lowStockAlerts')} description={t('inventory.settings.lowStockAlertsDesc')} checked={s.get('lowStockAlerts')} onChange={(v) => s.set('lowStockAlerts', v)} />
24
- <ToggleRow label={t('inventory.settings.requireReason')} description={t('inventory.settings.requireReasonDesc')} checked={s.get('requireReason')} onChange={(v) => s.set('requireReason', v)} />
25
- <ToggleRow label={t('inventory.settings.autoDeduct')} description={t('inventory.settings.autoDeductDesc')} checked={s.get('autoDeduct')} onChange={(v) => s.set('autoDeduct', v)} />
26
- </SettingsGroup>
27
-
28
- <SettingsGroup title={t('inventory.settings.products')} description={t('inventory.settings.productsDesc')}>
29
- <ToggleRow label={t('inventory.settings.requireSku')} description={t('inventory.settings.requireSkuDesc')} checked={s.get('requireSku')} onChange={(v) => s.set('requireSku', v)} />
30
- <ToggleRow label={t('inventory.settings.allowNegative')} description={t('inventory.settings.allowNegativeDesc')} checked={s.get('allowNegative')} onChange={(v) => s.set('allowNegative', v)} />
31
- </SettingsGroup>
32
-
33
- <SettingsGroup title={t('inventory.settings.notifications')} description={t('inventory.settings.notificationsDesc')}>
34
- <ToggleRow label={t('inventory.settings.lowStockEmail')} description={t('inventory.settings.lowStockEmailDesc')} checked={s.get('lowStockEmail')} onChange={(v) => s.set('lowStockEmail', v)} />
35
- <ToggleRow label={t('inventory.settings.expiryWarnings')} description={t('inventory.settings.expiryWarningsDesc')} checked={s.get('expiryWarnings')} onChange={(v) => s.set('expiryWarnings', v)} />
36
- </SettingsGroup>
37
- </div>
38
- )
39
- }
@@ -1,60 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { Package, Ruler, MapPin, ChevronRight, Check } from 'lucide-react'
3
- import { useTranslation } from '@fayz-ai/core'
4
- import { Button } from '@fayz-ai/ui'
5
-
6
- const STEPS = [
7
- { id: 'welcome', icon: Package, titleKey: 'inventory.onboarding.welcome', descKey: 'inventory.onboarding.description' },
8
- { id: 'units', icon: Ruler, titleKey: 'inventory.onboarding.units.title', descKey: 'inventory.onboarding.units.description' },
9
- { id: 'locations', icon: MapPin, titleKey: 'inventory.onboarding.locations.title', descKey: 'inventory.onboarding.locations.description' },
10
- ]
11
-
12
- export function InventoryOnboarding({ onComplete }: { onComplete: () => void }) {
13
- const t = useTranslation()
14
- const [step, setStep] = useState(0)
15
- const current = STEPS[step]
16
- const isLast = step === STEPS.length - 1
17
- const Icon = current.icon
18
-
19
- return (
20
- <div className="flex items-center justify-center py-12">
21
- <div className="w-full max-w-lg">
22
- <div className="flex items-center gap-1 mb-8">
23
- {STEPS.map((_, i) => (
24
- <div key={i} className={`h-1 flex-1 rounded-full transition-colors ${i <= step ? 'bg-primary' : 'bg-muted'}`} />
25
- ))}
26
- </div>
27
- <div className="rounded-card border bg-card p-8 text-center space-y-6 shadow-sm">
28
- <div className="flex justify-center">
29
- <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary/10">
30
- <Icon className="h-8 w-8 text-primary" />
31
- </div>
32
- </div>
33
- <div>
34
- <h2 className="text-xl font-bold">{t(current.titleKey)}</h2>
35
- <p className="text-sm text-muted-foreground mt-2 max-w-sm mx-auto">{t(current.descKey)}</p>
36
- </div>
37
- <div className="flex items-center justify-center gap-3 pt-2">
38
- {step > 0 && (
39
- <Button variant="outline" size="lg" onClick={() => setStep(step - 1)}>{t('common.back')}</Button>
40
- )}
41
- {isLast ? (
42
- <Button variant="default" size="lg" onClick={onComplete}>
43
- <Check className="h-4 w-4" /> {t('inventory.onboarding.start')}
44
- </Button>
45
- ) : (
46
- <Button variant="default" size="lg" onClick={() => setStep(step + 1)}>
47
- {step === 0 ? t('inventory.onboarding.getStarted') : t('common.continue')} <ChevronRight className="h-4 w-4" />
48
- </Button>
49
- )}
50
- </div>
51
- {step === 0 && (
52
- <button onClick={onComplete} className="text-xs text-muted-foreground hover:text-foreground transition-colors">
53
- {t('inventory.onboarding.skip')}
54
- </button>
55
- )}
56
- </div>
57
- </div>
58
- </div>
59
- )
60
- }
@@ -1,27 +0,0 @@
1
- import React from 'react'
2
- import { ChevronLeft } from 'lucide-react'
3
- import { PluginRegistryManager } from '@fayz-ai/saas'
4
- import type { PluginRegistryDef } from '@fayz-ai/core'
5
- import { useTranslation } from '@fayz-ai/core'
6
-
7
- export function InventorySettings({ registries, routeBase, onClose }: {
8
- registries: PluginRegistryDef[]
9
- routeBase: string
10
- onClose: () => void
11
- }) {
12
- const t = useTranslation()
13
- return (
14
- <div className="space-y-4">
15
- <div className="flex items-center gap-3">
16
- <button onClick={onClose} className="flex h-8 w-8 items-center justify-center rounded-lg border hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors">
17
- <ChevronLeft className="h-4 w-4" />
18
- </button>
19
- <div>
20
- <h2 className="text-lg font-bold">{t('inventory.settingsPage.title')}</h2>
21
- <p className="text-xs text-muted-foreground">{t('inventory.settingsPage.subtitle')}</p>
22
- </div>
23
- </div>
24
- <PluginRegistryManager registries={registries} routeBase={routeBase} />
25
- </div>
26
- )
27
- }
package/src/data/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export type { InventoryDataProvider } from './types'
2
- export { createMockInventoryProvider } from './mock'