@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,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { StoreApi } from 'zustand/vanilla';
|
|
3
|
-
import { type ResolvedInventoryConfig } from '
|
|
4
|
-
import type { InventoryDataProvider } from '
|
|
5
|
-
import type { InventoryUIState } from '
|
|
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,
|
|
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 '../
|
|
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,
|
|
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 '../
|
|
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,
|
|
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.
|
|
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.
|
|
33
|
-
"@fayz-ai/ui": "^0.
|
|
34
|
-
"@fayz-ai/
|
|
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"}
|
package/dist/registries.d.ts.map
DELETED
|
@@ -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"}
|
package/src/InventoryContext.tsx
DELETED
|
@@ -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 }
|
package/src/InventoryPage.tsx
DELETED
|
@@ -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