From 60d72da80be1f0080b1bef7a262ddd401b2e5e07 Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 10 Apr 2026 13:35:01 +0800 Subject: [PATCH] feat(web): @rjsf deps + metadata API client + types + i18n + routes --- web/package-lock.json | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- web/package.json | 3 +++ web/src/App.tsx | 10 ++++++++++ web/src/api/client.ts | 37 ++++++++++++++++++++++++++++++++++++- web/src/i18n/messages.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/layout/AppLayout.tsx | 1 + web/src/types/api.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 445 insertions(+), 2 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 9d195ae..1f4167e 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,9 @@ "name": "vibe-erp-web", "version": "0.0.0", "dependencies": { + "@rjsf/core": "^5.24.13", + "@rjsf/utils": "^5.24.13", + "@rjsf/validator-ajv8": "^5.24.13", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.28.0" @@ -807,6 +810,62 @@ "node": ">=14.0.0" } }, + "node_modules/@rjsf/core": { + "version": "5.24.13", + "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.13.tgz", + "integrity": "sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "markdown-to-jsx": "^7.4.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@rjsf/utils": "^5.24.x", + "react": "^16.14.0 || >=17" + } + }, + "node_modules/@rjsf/utils": { + "version": "5.24.13", + "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz", + "integrity": "sha512-rNF8tDxIwTtXzz5O/U23QU73nlhgQNYJ+Sv5BAwQOIyhIE2Z3S5tUiSVMwZHt0julkv/Ryfwi+qsD4FiE5rOuw==", + "license": "Apache-2.0", + "dependencies": { + "json-schema-merge-allof": "^0.8.1", + "jsonpointer": "^5.0.1", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.14.0 || >=17" + } + }, + "node_modules/@rjsf/validator-ajv8": { + "version": "5.24.13", + "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz", + "integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==", + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@rjsf/utils": "^5.24.x" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1314,6 +1373,39 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1531,6 +1623,27 @@ "node": ">= 6" } }, + "node_modules/compute-gcd": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, + "node_modules/compute-lcm": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", + "dependencies": { + "compute-gcd": "^1.2.1", + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1646,6 +1759,12 @@ "node": ">=6" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1676,6 +1795,22 @@ "node": ">= 6" } }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -1865,6 +2000,35 @@ "node": ">=6" } }, + "node_modules/json-schema-compare": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/json-schema-merge-allof": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz", + "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==", + "license": "MIT", + "dependencies": { + "compute-lcm": "^1.1.2", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.20" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1878,6 +2042,15 @@ "node": ">=6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -1898,6 +2071,18 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1920,6 +2105,23 @@ "yallist": "^3.0.2" } }, + "node_modules/markdown-to-jsx": { + "version": "7.7.17", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.17.tgz", + "integrity": "sha512-7mG/1feQ0TX5I7YyMZVDgCC/y2I3CiEhIRQIhyov9nGBP5eoVrOXXHuL5ZP8GRfxVZKRiXWJgwXkb9It+nQZfQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2003,7 +2205,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2229,6 +2430,23 @@ "dev": true, "license": "MIT" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2275,6 +2493,12 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -2340,6 +2564,15 @@ "node": ">=8.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -2694,6 +2927,39 @@ "dev": true, "license": "MIT" }, + "node_modules/validate.io-array": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", + "license": "MIT" + }, + "node_modules/validate.io-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" + }, + "node_modules/validate.io-integer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", + "dependencies": { + "validate.io-number": "^1.0.3" + } + }, + "node_modules/validate.io-integer-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-integer": "^1.0.4" + } + }, + "node_modules/validate.io-number": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==" + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", diff --git a/web/package.json b/web/package.json index 17c449c..bb0006e 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,9 @@ "preview": "vite preview" }, "dependencies": { + "@rjsf/core": "^5.24.13", + "@rjsf/utils": "^5.24.13", + "@rjsf/validator-ajv8": "^5.24.13", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.28.0" diff --git a/web/src/App.tsx b/web/src/App.tsx index ae02fcc..c0e67d1 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -42,6 +42,11 @@ import { ShopFloorPage } from '@/pages/ShopFloorPage' import { AccountsPage } from '@/pages/AccountsPage' import { JournalEntriesPage } from '@/pages/JournalEntriesPage' +// Stub pages (will be replaced in later tasks) +function MetadataAdminPage() { return
Metadata Admin - coming soon
} +function FormDesignerPage() { return
Form Designer - coming soon
} +function ListViewDesignerPage() { return
List View Designer - coming soon
} + export default function App() { return ( @@ -83,6 +88,11 @@ export default function App() { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> } /> diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 2b53941..3727bcd 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -26,9 +26,14 @@ import type { Account, + CustomFieldDef, + FormDefinition, Item, JournalEntry, + ListViewDefinition, Location, + MetadataEntity, + MetadataPermission, MetaInfo, Partner, PurchaseOrder, @@ -75,7 +80,7 @@ export function registerUnauthorizedHandler(handler: () => void) { onUnauthorized = handler } -async function apiFetch( +export async function apiFetch( path: string, init: RequestInit = {}, expectJson = true, @@ -283,6 +288,36 @@ export const production = { // ─── Finance ───────────────────────────────────────────────────────── +// ─── Metadata admin ───────────────────────────────────────────────── + +export const metadata = { + entities: () => apiFetch('/api/v1/_meta/metadata/entities'), + permissions: () => apiFetch('/api/v1/_meta/metadata/permissions'), + menus: () => apiFetch('/api/v1/_meta/metadata/menus'), + customFields: () => apiFetch('/api/v1/_meta/metadata/custom-fields'), + customFieldsFor: (entity: string) => apiFetch(`/api/v1/_meta/metadata/custom-fields/${entity}`), + listForms: () => apiFetch('/api/v1/_meta/metadata/forms'), + getForm: (slug: string) => apiFetch(`/api/v1/_meta/metadata/forms/${slug}`), + saveForm: (slug: string, body: Omit) => + apiFetch(`/api/v1/_meta/metadata/forms/${slug}`, { method: 'PUT', body: JSON.stringify(body) }), + deleteForm: (slug: string) => + apiFetch(`/api/v1/_meta/metadata/forms/${slug}`, { method: 'DELETE' }, false), + listListViews: () => apiFetch('/api/v1/_meta/metadata/list-views'), + getListView: (slug: string) => apiFetch(`/api/v1/_meta/metadata/list-views/${slug}`), + saveListView: (slug: string, body: Omit) => + apiFetch(`/api/v1/_meta/metadata/list-views/${slug}`, { method: 'PUT', body: JSON.stringify(body) }), + deleteListView: (slug: string) => + apiFetch(`/api/v1/_meta/metadata/list-views/${slug}`, { method: 'DELETE' }, false), + createCustomField: (body: Omit) => + apiFetch('/api/v1/_meta/metadata/custom-fields', { method: 'POST', body: JSON.stringify(body) }), + updateCustomField: (key: string, body: Omit) => + apiFetch(`/api/v1/_meta/metadata/custom-fields/${key}`, { method: 'PUT', body: JSON.stringify(body) }), + deleteCustomField: (key: string) => + apiFetch(`/api/v1/_meta/metadata/custom-fields/${key}`, { method: 'DELETE' }, false), +} + +// ─── Finance ───────────────────────────────────────────────────────── + export const finance = { listAccounts: () => apiFetch('/api/v1/finance/accounts'), createAccount: (body: { diff --git a/web/src/i18n/messages.ts b/web/src/i18n/messages.ts index b4b409d..5eae9bb 100644 --- a/web/src/i18n/messages.ts +++ b/web/src/i18n/messages.ts @@ -35,6 +35,7 @@ export const en = { 'nav.system': 'System', 'nav.users': 'Users', 'nav.roles': 'Roles', + 'nav.metadataAdmin': 'Metadata', // ─── Actions ─────────────────────────────────────────────── 'action.confirm': 'Confirm', @@ -62,6 +63,11 @@ export const en = { 'action.newRole': '+ New Role', 'action.newAccount': '+ New Account', 'action.adjustStock': 'Adjust Stock', + 'action.addField': 'Add Field', + 'action.addSection': 'Add Section', + 'action.discard': 'Discard', + 'action.delete': 'Delete', + 'action.newCustomField': 'New Custom Field', // ─── Status badges ──────────────────────────────────────── 'status.DRAFT': 'Draft', @@ -96,6 +102,30 @@ export const en = { 'label.noRows': 'No rows.', 'label.loading': 'Loading…', 'label.error': 'Error', + + // ─── Metadata admin ─────────────────────────────────────── + 'page.metadataAdmin.title': 'Metadata Admin', + 'tab.entities': 'Entities', + 'tab.customFields': 'Custom Fields', + 'tab.permissions': 'Permissions', + 'tab.menus': 'Menus', + 'tab.forms': 'Forms', + 'tab.listViews': 'List Views', + 'page.formDesigner.title': 'Form Designer', + 'page.listViewDesigner.title': 'List View Designer', + 'label.slug': 'Slug', + 'label.entity': 'Entity', + 'label.purpose': 'Purpose', + 'label.preview': 'Preview', + 'label.source': 'Source', + 'label.columns': 'Columns', + 'label.filters': 'Filters', + 'label.sorting': 'Sorting', + 'label.pageSize': 'Page Size', + 'label.fieldKey': 'Field Key', + 'label.targetEntity': 'Target Entity', + 'label.fieldType': 'Field Type', + 'confirm.delete': 'Are you sure?', } as const export const zhCN: Record = { @@ -122,6 +152,7 @@ export const zhCN: Record = { 'nav.system': '系统', 'nav.users': '用户', 'nav.roles': '角色', + 'nav.metadataAdmin': '元数据', // ─── 操作 ────────────────────────────────────────────────── 'action.confirm': '确认', @@ -149,6 +180,11 @@ export const zhCN: Record = { 'action.newRole': '+ 新角色', 'action.newAccount': '+ 新科目', 'action.adjustStock': '库存调整', + 'action.addField': '添加字段', + 'action.addSection': '添加分区', + 'action.discard': '放弃', + 'action.delete': '删除', + 'action.newCustomField': '新建自定义字段', // ─── 状态 ────────────────────────────────────────────────── 'status.DRAFT': '草稿', @@ -183,6 +219,30 @@ export const zhCN: Record = { 'label.noRows': '暂无数据', 'label.loading': '加载中…', 'label.error': '错误', + + // ─── 元数据管理 ──────────────────────────────────────────── + 'page.metadataAdmin.title': '元数据管理', + 'tab.entities': '实体', + 'tab.customFields': '自定义字段', + 'tab.permissions': '权限', + 'tab.menus': '菜单', + 'tab.forms': '表单', + 'tab.listViews': '列表视图', + 'page.formDesigner.title': '表单设计器', + 'page.listViewDesigner.title': '列表视图设计器', + 'label.slug': '标识', + 'label.entity': '实体', + 'label.purpose': '用途', + 'label.preview': '预览', + 'label.source': '来源', + 'label.columns': '列', + 'label.filters': '筛选', + 'label.sorting': '排序', + 'label.pageSize': '每页行数', + 'label.fieldKey': '字段键', + 'label.targetEntity': '目标实体', + 'label.fieldType': '字段类型', + 'confirm.delete': '确定删除?', } export const locales = { diff --git a/web/src/layout/AppLayout.tsx b/web/src/layout/AppLayout.tsx index 6e1745b..d238347 100644 --- a/web/src/layout/AppLayout.tsx +++ b/web/src/layout/AppLayout.tsx @@ -64,6 +64,7 @@ const NAV: NavGroup[] = [ items: [ { to: '/users', labelKey: 'nav.users' }, { to: '/roles', labelKey: 'nav.roles' }, + { to: '/admin/metadata', labelKey: 'nav.metadataAdmin' }, ], }, ] diff --git a/web/src/types/api.ts b/web/src/types/api.ts index f0963d4..85b5627 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -284,3 +284,71 @@ export interface JournalEntry { postedAt: string lines: JournalEntryLine[] } + +// ─── Metadata Definitions ─────────────────────────────────────────── + +export type FormPurpose = 'create' | 'edit' | 'user-task' | 'view' + +export interface FormDefinition { + entityName: string + slug: string + title: string + purpose: FormPurpose + jsonSchema: Record + uiSchema: Record + version: number + source?: string +} + +export interface ListViewColumnDef { + field: string + label: string + width?: string + sortable: boolean + format?: 'date' | 'money' | 'status-badge' | 'link' +} + +export interface ListViewDefinition { + entityName: string + slug: string + title: string + columns: ListViewColumnDef[] + defaultSort?: { field: string; direction: 'asc' | 'desc' } + filters?: { field: string; operator: string; label: string }[] + pageSize: number + version: number + source?: string +} + +export interface CustomFieldType { + kind: string + maxLength?: number + precision?: number + scale?: number + targetEntity?: string + allowedValues?: string[] +} + +export interface CustomFieldDef { + key: string + targetEntity: string + type: CustomFieldType + required: boolean + pii: boolean + labelTranslations: Record + source?: string +} + +export interface MetadataEntity { + name: string + pbc: string + table: string + description?: string + source?: string +} + +export interface MetadataPermission { + key: string + description: string + source?: string +} -- libgit2 0.22.2