@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,170 @@
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 { ProductFormView } from './views/ProductFormView'
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
+ {
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
+ },
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
+ {
80
+ id: 'new-product',
81
+ label: t('inventory.quickActions.newProduct'),
82
+ icon: 'Package',
83
+ description: t('inventory.quickActions.newProductDesc'),
84
+ action: () => navigate('products-new'),
85
+ },
86
+ {
87
+ id: 'stock-entry',
88
+ label: t('inventory.quickActions.stockEntry'),
89
+ icon: 'ArrowUpRight',
90
+ description: t('inventory.quickActions.stockEntryDesc'),
91
+ action: () => navigate('stock-entry'),
92
+ },
93
+ {
94
+ id: 'stock-exit',
95
+ label: t('inventory.quickActions.stockExit'),
96
+ icon: 'ArrowDownRight',
97
+ description: t('inventory.quickActions.stockExitDesc'),
98
+ action: () => navigate('stock-exit'),
99
+ },
100
+ ]
101
+ return actions
102
+ }, [])
103
+
104
+ if (!onboardingComplete) {
105
+ return (
106
+ <InventoryContextProvider config={config} provider={provider} store={store}>
107
+ <InventoryOnboarding onComplete={() => { setOnboardingComplete(true); try { localStorage.setItem('saas-core:inventory-onboarded', 'true') } catch {} }} />
108
+ </InventoryContextProvider>
109
+ )
110
+ }
111
+
112
+ if (isSettings && registries && registries.length > 0) {
113
+ return (
114
+ <InventoryContextProvider config={config} provider={provider} store={store}>
115
+ <PageTransition transitionKey="settings" direction={direction}>
116
+ <div style={{ padding: '24px' }}>
117
+ <div style={{ marginBottom: '16px' }}>
118
+ <h1 style={{ fontSize: '20px', fontWeight: 600, margin: 0 }}>{t('inventory.settingsPage.title')}</h1>
119
+ <p style={{ color: 'var(--muted-foreground, #6b7280)', margin: '4px 0 0', fontSize: '14px' }}>
120
+ {t('inventory.settingsPage.subtitle')}
121
+ </p>
122
+ </div>
123
+ <InventoryGeneralSettings />
124
+ </div>
125
+ </PageTransition>
126
+ </InventoryContextProvider>
127
+ )
128
+ }
129
+
130
+ const renderView = createViewRouter([
131
+ { id: 'products-list', render: () => <ProductListView onNew={() => navigate('products-new')} onEdit={(id) => navigate(`products-edit:${id}`)} /> },
132
+ { id: 'products-new', render: () => <ProductFormView onSaved={() => navigate('products-list')} /> },
133
+ { id: 'products-edit', render: ({ id }) => <ProductFormView editId={id!} onSaved={() => navigate('products-list')} /> },
134
+ { id: 'stock-entry', render: () => <StockMovementView defaultType="entry" onSaved={() => navigate('stock-history')} /> },
135
+ { id: 'stock-exit', render: () => <StockMovementView defaultType="exit" onSaved={() => navigate('stock-history')} /> },
136
+ { id: 'stock-history', render: () => <MovementHistoryView onViewDetail={(id) => navigate(`stock-detail:${id}`)} /> },
137
+ { id: 'stock-detail', render: ({ id }) => {
138
+ const movement = store.getState().movements.find((m) => m.id === id)
139
+ return movement
140
+ ? <StockMovementView defaultType={movement.movementType} viewMovement={movement} onSaved={() => navigate('stock-history')} />
141
+ : <MovementHistoryView onViewDetail={(mid) => navigate(`stock-detail:${mid}`)} />
142
+ } },
143
+ { id: 'recipes-list', render: () => <RecipesView onNew={() => navigate('recipes-new')} onView={(id) => navigate(`recipes-detail:${id}`)} /> },
144
+ { id: 'recipes-new', render: () => <RecipeFormView onSaved={(id) => id ? navigate(`recipes-detail:${id}`) : navigate('recipes-list')} /> },
145
+ { id: 'recipes-detail', render: ({ id }) => <RecipeDetailView recipeId={id!} onBack={() => navigate('recipes-list')} /> },
146
+ { id: 'dashboard', render: () => <DashboardView /> },
147
+ ], 'dashboard')
148
+
149
+ return (
150
+ <InventoryContextProvider config={config} provider={provider} store={store}>
151
+ <ModulePage
152
+ title={config.labels.pageTitle}
153
+ subtitle={config.labels.pageSubtitle}
154
+ nav={nav}
155
+ showHeader={isSummary}
156
+ viewKey={view}
157
+ direction={direction}
158
+ headerAction={
159
+ <ModuleActionBar
160
+ quickActions={quickActions}
161
+ settingsPath={registries && registries.length > 0 ? '/settings/inventory' : undefined}
162
+ settingsLabel="Inventory Settings"
163
+ />
164
+ }
165
+ >
166
+ {renderView(view)}
167
+ </ModulePage>
168
+ </InventoryContextProvider>
169
+ )
170
+ }
package/src/README.md ADDED
@@ -0,0 +1,177 @@
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 createSaasApp 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.
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+ import { SettingsGroup, ToggleRow } from '@fayz-ai/saas'
3
+ import { useTranslation } from '@fayz-ai/core'
4
+
5
+ export function InventoryGeneralSettings() {
6
+ const t = useTranslation()
7
+ return (
8
+ <div className="space-y-4">
9
+ <SettingsGroup title={t('inventory.settings.stockManagement')} description={t('inventory.settings.stockManagementDesc')}>
10
+ <ToggleRow label={t('inventory.settings.lowStockAlerts')} description={t('inventory.settings.lowStockAlertsDesc')} checked={true} onChange={() => {}} />
11
+ <ToggleRow label={t('inventory.settings.requireReason')} description={t('inventory.settings.requireReasonDesc')} checked={true} onChange={() => {}} />
12
+ <ToggleRow label={t('inventory.settings.autoDeduct')} description={t('inventory.settings.autoDeductDesc')} checked={false} onChange={() => {}} />
13
+ </SettingsGroup>
14
+
15
+ <SettingsGroup title={t('inventory.settings.products')} description={t('inventory.settings.productsDesc')}>
16
+ <ToggleRow label={t('inventory.settings.requireSku')} description={t('inventory.settings.requireSkuDesc')} checked={false} onChange={() => {}} />
17
+ <ToggleRow label={t('inventory.settings.allowNegative')} description={t('inventory.settings.allowNegativeDesc')} checked={false} onChange={() => {}} />
18
+ </SettingsGroup>
19
+
20
+ <SettingsGroup title={t('inventory.settings.notifications')} description={t('inventory.settings.notificationsDesc')}>
21
+ <ToggleRow label={t('inventory.settings.lowStockEmail')} description={t('inventory.settings.lowStockEmailDesc')} checked={false} onChange={() => {}} />
22
+ <ToggleRow label={t('inventory.settings.expiryWarnings')} description={t('inventory.settings.expiryWarningsDesc')} checked={true} onChange={() => {}} />
23
+ </SettingsGroup>
24
+ </div>
25
+ )
26
+ }
@@ -0,0 +1,60 @@
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
+ }
@@ -0,0 +1,27 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ export type { InventoryDataProvider } from './types'
2
+ export { createMockInventoryProvider } from './mock'