Commit 60d72da80be1f0080b1bef7a262ddd401b2e5e07

Authored by zichun
1 parent dc8ee0ff

feat(web): @rjsf deps + metadata API client + types + i18n + routes

web/package-lock.json
@@ -8,6 +8,9 @@ @@ -8,6 +8,9 @@
8 "name": "vibe-erp-web", 8 "name": "vibe-erp-web",
9 "version": "0.0.0", 9 "version": "0.0.0",
10 "dependencies": { 10 "dependencies": {
  11 + "@rjsf/core": "^5.24.13",
  12 + "@rjsf/utils": "^5.24.13",
  13 + "@rjsf/validator-ajv8": "^5.24.13",
11 "react": "^18.3.1", 14 "react": "^18.3.1",
12 "react-dom": "^18.3.1", 15 "react-dom": "^18.3.1",
13 "react-router-dom": "^6.28.0" 16 "react-router-dom": "^6.28.0"
@@ -807,6 +810,62 @@ @@ -807,6 +810,62 @@
807 "node": ">=14.0.0" 810 "node": ">=14.0.0"
808 } 811 }
809 }, 812 },
  813 + "node_modules/@rjsf/core": {
  814 + "version": "5.24.13",
  815 + "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.13.tgz",
  816 + "integrity": "sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==",
  817 + "license": "Apache-2.0",
  818 + "dependencies": {
  819 + "lodash": "^4.17.21",
  820 + "lodash-es": "^4.17.21",
  821 + "markdown-to-jsx": "^7.4.1",
  822 + "prop-types": "^15.8.1"
  823 + },
  824 + "engines": {
  825 + "node": ">=14"
  826 + },
  827 + "peerDependencies": {
  828 + "@rjsf/utils": "^5.24.x",
  829 + "react": "^16.14.0 || >=17"
  830 + }
  831 + },
  832 + "node_modules/@rjsf/utils": {
  833 + "version": "5.24.13",
  834 + "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz",
  835 + "integrity": "sha512-rNF8tDxIwTtXzz5O/U23QU73nlhgQNYJ+Sv5BAwQOIyhIE2Z3S5tUiSVMwZHt0julkv/Ryfwi+qsD4FiE5rOuw==",
  836 + "license": "Apache-2.0",
  837 + "dependencies": {
  838 + "json-schema-merge-allof": "^0.8.1",
  839 + "jsonpointer": "^5.0.1",
  840 + "lodash": "^4.17.21",
  841 + "lodash-es": "^4.17.21",
  842 + "react-is": "^18.2.0"
  843 + },
  844 + "engines": {
  845 + "node": ">=14"
  846 + },
  847 + "peerDependencies": {
  848 + "react": "^16.14.0 || >=17"
  849 + }
  850 + },
  851 + "node_modules/@rjsf/validator-ajv8": {
  852 + "version": "5.24.13",
  853 + "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz",
  854 + "integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==",
  855 + "license": "Apache-2.0",
  856 + "dependencies": {
  857 + "ajv": "^8.12.0",
  858 + "ajv-formats": "^2.1.1",
  859 + "lodash": "^4.17.21",
  860 + "lodash-es": "^4.17.21"
  861 + },
  862 + "engines": {
  863 + "node": ">=14"
  864 + },
  865 + "peerDependencies": {
  866 + "@rjsf/utils": "^5.24.x"
  867 + }
  868 + },
810 "node_modules/@rolldown/pluginutils": { 869 "node_modules/@rolldown/pluginutils": {
811 "version": "1.0.0-beta.27", 870 "version": "1.0.0-beta.27",
812 "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", 871 "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -1314,6 +1373,39 @@ @@ -1314,6 +1373,39 @@
1314 "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 1373 "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1315 } 1374 }
1316 }, 1375 },
  1376 + "node_modules/ajv": {
  1377 + "version": "8.18.0",
  1378 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
  1379 + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
  1380 + "license": "MIT",
  1381 + "dependencies": {
  1382 + "fast-deep-equal": "^3.1.3",
  1383 + "fast-uri": "^3.0.1",
  1384 + "json-schema-traverse": "^1.0.0",
  1385 + "require-from-string": "^2.0.2"
  1386 + },
  1387 + "funding": {
  1388 + "type": "github",
  1389 + "url": "https://github.com/sponsors/epoberezkin"
  1390 + }
  1391 + },
  1392 + "node_modules/ajv-formats": {
  1393 + "version": "2.1.1",
  1394 + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
  1395 + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
  1396 + "license": "MIT",
  1397 + "dependencies": {
  1398 + "ajv": "^8.0.0"
  1399 + },
  1400 + "peerDependencies": {
  1401 + "ajv": "^8.0.0"
  1402 + },
  1403 + "peerDependenciesMeta": {
  1404 + "ajv": {
  1405 + "optional": true
  1406 + }
  1407 + }
  1408 + },
1317 "node_modules/any-promise": { 1409 "node_modules/any-promise": {
1318 "version": "1.3.0", 1410 "version": "1.3.0",
1319 "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 1411 "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -1531,6 +1623,27 @@ @@ -1531,6 +1623,27 @@
1531 "node": ">= 6" 1623 "node": ">= 6"
1532 } 1624 }
1533 }, 1625 },
  1626 + "node_modules/compute-gcd": {
  1627 + "version": "1.2.1",
  1628 + "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz",
  1629 + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==",
  1630 + "dependencies": {
  1631 + "validate.io-array": "^1.0.3",
  1632 + "validate.io-function": "^1.0.2",
  1633 + "validate.io-integer-array": "^1.0.0"
  1634 + }
  1635 + },
  1636 + "node_modules/compute-lcm": {
  1637 + "version": "1.1.2",
  1638 + "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz",
  1639 + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==",
  1640 + "dependencies": {
  1641 + "compute-gcd": "^1.2.1",
  1642 + "validate.io-array": "^1.0.3",
  1643 + "validate.io-function": "^1.0.2",
  1644 + "validate.io-integer-array": "^1.0.0"
  1645 + }
  1646 + },
1534 "node_modules/convert-source-map": { 1647 "node_modules/convert-source-map": {
1535 "version": "2.0.0", 1648 "version": "2.0.0",
1536 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1649 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -1646,6 +1759,12 @@ @@ -1646,6 +1759,12 @@
1646 "node": ">=6" 1759 "node": ">=6"
1647 } 1760 }
1648 }, 1761 },
  1762 + "node_modules/fast-deep-equal": {
  1763 + "version": "3.1.3",
  1764 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
  1765 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
  1766 + "license": "MIT"
  1767 + },
1649 "node_modules/fast-glob": { 1768 "node_modules/fast-glob": {
1650 "version": "3.3.3", 1769 "version": "3.3.3",
1651 "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 1770 "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -1676,6 +1795,22 @@ @@ -1676,6 +1795,22 @@
1676 "node": ">= 6" 1795 "node": ">= 6"
1677 } 1796 }
1678 }, 1797 },
  1798 + "node_modules/fast-uri": {
  1799 + "version": "3.1.0",
  1800 + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
  1801 + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
  1802 + "funding": [
  1803 + {
  1804 + "type": "github",
  1805 + "url": "https://github.com/sponsors/fastify"
  1806 + },
  1807 + {
  1808 + "type": "opencollective",
  1809 + "url": "https://opencollective.com/fastify"
  1810 + }
  1811 + ],
  1812 + "license": "BSD-3-Clause"
  1813 + },
1679 "node_modules/fastq": { 1814 "node_modules/fastq": {
1680 "version": "1.20.1", 1815 "version": "1.20.1",
1681 "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", 1816 "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -1865,6 +2000,35 @@ @@ -1865,6 +2000,35 @@
1865 "node": ">=6" 2000 "node": ">=6"
1866 } 2001 }
1867 }, 2002 },
  2003 + "node_modules/json-schema-compare": {
  2004 + "version": "0.2.2",
  2005 + "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz",
  2006 + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==",
  2007 + "license": "MIT",
  2008 + "dependencies": {
  2009 + "lodash": "^4.17.4"
  2010 + }
  2011 + },
  2012 + "node_modules/json-schema-merge-allof": {
  2013 + "version": "0.8.1",
  2014 + "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz",
  2015 + "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==",
  2016 + "license": "MIT",
  2017 + "dependencies": {
  2018 + "compute-lcm": "^1.1.2",
  2019 + "json-schema-compare": "^0.2.2",
  2020 + "lodash": "^4.17.20"
  2021 + },
  2022 + "engines": {
  2023 + "node": ">=12.0.0"
  2024 + }
  2025 + },
  2026 + "node_modules/json-schema-traverse": {
  2027 + "version": "1.0.0",
  2028 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
  2029 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
  2030 + "license": "MIT"
  2031 + },
1868 "node_modules/json5": { 2032 "node_modules/json5": {
1869 "version": "2.2.3", 2033 "version": "2.2.3",
1870 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2034 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -1878,6 +2042,15 @@ @@ -1878,6 +2042,15 @@
1878 "node": ">=6" 2042 "node": ">=6"
1879 } 2043 }
1880 }, 2044 },
  2045 + "node_modules/jsonpointer": {
  2046 + "version": "5.0.1",
  2047 + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
  2048 + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
  2049 + "license": "MIT",
  2050 + "engines": {
  2051 + "node": ">=0.10.0"
  2052 + }
  2053 + },
1881 "node_modules/lilconfig": { 2054 "node_modules/lilconfig": {
1882 "version": "3.1.3", 2055 "version": "3.1.3",
1883 "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", 2056 "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@@ -1898,6 +2071,18 @@ @@ -1898,6 +2071,18 @@
1898 "dev": true, 2071 "dev": true,
1899 "license": "MIT" 2072 "license": "MIT"
1900 }, 2073 },
  2074 + "node_modules/lodash": {
  2075 + "version": "4.18.1",
  2076 + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
  2077 + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
  2078 + "license": "MIT"
  2079 + },
  2080 + "node_modules/lodash-es": {
  2081 + "version": "4.18.1",
  2082 + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
  2083 + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
  2084 + "license": "MIT"
  2085 + },
1901 "node_modules/loose-envify": { 2086 "node_modules/loose-envify": {
1902 "version": "1.4.0", 2087 "version": "1.4.0",
1903 "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 2088 "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -1920,6 +2105,23 @@ @@ -1920,6 +2105,23 @@
1920 "yallist": "^3.0.2" 2105 "yallist": "^3.0.2"
1921 } 2106 }
1922 }, 2107 },
  2108 + "node_modules/markdown-to-jsx": {
  2109 + "version": "7.7.17",
  2110 + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.17.tgz",
  2111 + "integrity": "sha512-7mG/1feQ0TX5I7YyMZVDgCC/y2I3CiEhIRQIhyov9nGBP5eoVrOXXHuL5ZP8GRfxVZKRiXWJgwXkb9It+nQZfQ==",
  2112 + "license": "MIT",
  2113 + "engines": {
  2114 + "node": ">= 10"
  2115 + },
  2116 + "peerDependencies": {
  2117 + "react": ">= 0.14.0"
  2118 + },
  2119 + "peerDependenciesMeta": {
  2120 + "react": {
  2121 + "optional": true
  2122 + }
  2123 + }
  2124 + },
1923 "node_modules/merge2": { 2125 "node_modules/merge2": {
1924 "version": "1.4.1", 2126 "version": "1.4.1",
1925 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 2127 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2003,7 +2205,6 @@ @@ -2003,7 +2205,6 @@
2003 "version": "4.1.1", 2205 "version": "4.1.1",
2004 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 2206 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
2005 "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 2207 "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
2006 - "dev": true,  
2007 "license": "MIT", 2208 "license": "MIT",
2008 "engines": { 2209 "engines": {
2009 "node": ">=0.10.0" 2210 "node": ">=0.10.0"
@@ -2229,6 +2430,23 @@ @@ -2229,6 +2430,23 @@
2229 "dev": true, 2430 "dev": true,
2230 "license": "MIT" 2431 "license": "MIT"
2231 }, 2432 },
  2433 + "node_modules/prop-types": {
  2434 + "version": "15.8.1",
  2435 + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
  2436 + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
  2437 + "license": "MIT",
  2438 + "dependencies": {
  2439 + "loose-envify": "^1.4.0",
  2440 + "object-assign": "^4.1.1",
  2441 + "react-is": "^16.13.1"
  2442 + }
  2443 + },
  2444 + "node_modules/prop-types/node_modules/react-is": {
  2445 + "version": "16.13.1",
  2446 + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
  2447 + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
  2448 + "license": "MIT"
  2449 + },
2232 "node_modules/queue-microtask": { 2450 "node_modules/queue-microtask": {
2233 "version": "1.2.3", 2451 "version": "1.2.3",
2234 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2452 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2275,6 +2493,12 @@ @@ -2275,6 +2493,12 @@
2275 "react": "^18.3.1" 2493 "react": "^18.3.1"
2276 } 2494 }
2277 }, 2495 },
  2496 + "node_modules/react-is": {
  2497 + "version": "18.3.1",
  2498 + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
  2499 + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
  2500 + "license": "MIT"
  2501 + },
2278 "node_modules/react-refresh": { 2502 "node_modules/react-refresh": {
2279 "version": "0.17.0", 2503 "version": "0.17.0",
2280 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", 2504 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -2340,6 +2564,15 @@ @@ -2340,6 +2564,15 @@
2340 "node": ">=8.10.0" 2564 "node": ">=8.10.0"
2341 } 2565 }
2342 }, 2566 },
  2567 + "node_modules/require-from-string": {
  2568 + "version": "2.0.2",
  2569 + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
  2570 + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
  2571 + "license": "MIT",
  2572 + "engines": {
  2573 + "node": ">=0.10.0"
  2574 + }
  2575 + },
2343 "node_modules/resolve": { 2576 "node_modules/resolve": {
2344 "version": "1.22.11", 2577 "version": "1.22.11",
2345 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", 2578 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -2694,6 +2927,39 @@ @@ -2694,6 +2927,39 @@
2694 "dev": true, 2927 "dev": true,
2695 "license": "MIT" 2928 "license": "MIT"
2696 }, 2929 },
  2930 + "node_modules/validate.io-array": {
  2931 + "version": "1.0.6",
  2932 + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
  2933 + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==",
  2934 + "license": "MIT"
  2935 + },
  2936 + "node_modules/validate.io-function": {
  2937 + "version": "1.0.2",
  2938 + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
  2939 + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ=="
  2940 + },
  2941 + "node_modules/validate.io-integer": {
  2942 + "version": "1.0.5",
  2943 + "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz",
  2944 + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==",
  2945 + "dependencies": {
  2946 + "validate.io-number": "^1.0.3"
  2947 + }
  2948 + },
  2949 + "node_modules/validate.io-integer-array": {
  2950 + "version": "1.0.0",
  2951 + "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz",
  2952 + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==",
  2953 + "dependencies": {
  2954 + "validate.io-array": "^1.0.3",
  2955 + "validate.io-integer": "^1.0.4"
  2956 + }
  2957 + },
  2958 + "node_modules/validate.io-number": {
  2959 + "version": "1.0.3",
  2960 + "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz",
  2961 + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg=="
  2962 + },
2697 "node_modules/vite": { 2963 "node_modules/vite": {
2698 "version": "5.4.21", 2964 "version": "5.4.21",
2699 "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", 2965 "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
web/package.json
@@ -9,6 +9,9 @@ @@ -9,6 +9,9 @@
9 "preview": "vite preview" 9 "preview": "vite preview"
10 }, 10 },
11 "dependencies": { 11 "dependencies": {
  12 + "@rjsf/core": "^5.24.13",
  13 + "@rjsf/utils": "^5.24.13",
  14 + "@rjsf/validator-ajv8": "^5.24.13",
12 "react": "^18.3.1", 15 "react": "^18.3.1",
13 "react-dom": "^18.3.1", 16 "react-dom": "^18.3.1",
14 "react-router-dom": "^6.28.0" 17 "react-router-dom": "^6.28.0"
web/src/App.tsx
@@ -42,6 +42,11 @@ import { ShopFloorPage } from '@/pages/ShopFloorPage' @@ -42,6 +42,11 @@ import { ShopFloorPage } from '@/pages/ShopFloorPage'
42 import { AccountsPage } from '@/pages/AccountsPage' 42 import { AccountsPage } from '@/pages/AccountsPage'
43 import { JournalEntriesPage } from '@/pages/JournalEntriesPage' 43 import { JournalEntriesPage } from '@/pages/JournalEntriesPage'
44 44
  45 +// Stub pages (will be replaced in later tasks)
  46 +function MetadataAdminPage() { return <div>Metadata Admin - coming soon</div> }
  47 +function FormDesignerPage() { return <div>Form Designer - coming soon</div> }
  48 +function ListViewDesignerPage() { return <div>List View Designer - coming soon</div> }
  49 +
45 export default function App() { 50 export default function App() {
46 return ( 51 return (
47 <Routes> 52 <Routes>
@@ -83,6 +88,11 @@ export default function App() { @@ -83,6 +88,11 @@ export default function App() {
83 <Route path="shop-floor" element={<ShopFloorPage />} /> 88 <Route path="shop-floor" element={<ShopFloorPage />} />
84 <Route path="accounts" element={<AccountsPage />} /> 89 <Route path="accounts" element={<AccountsPage />} />
85 <Route path="journal-entries" element={<JournalEntriesPage />} /> 90 <Route path="journal-entries" element={<JournalEntriesPage />} />
  91 + <Route path="admin/metadata" element={<MetadataAdminPage />} />
  92 + <Route path="admin/metadata/forms/new" element={<FormDesignerPage />} />
  93 + <Route path="admin/metadata/forms/:slug/edit" element={<FormDesignerPage />} />
  94 + <Route path="admin/metadata/list-views/new" element={<ListViewDesignerPage />} />
  95 + <Route path="admin/metadata/list-views/:slug/edit" element={<ListViewDesignerPage />} />
86 </Route> 96 </Route>
87 <Route path="*" element={<Navigate to="/" replace />} /> 97 <Route path="*" element={<Navigate to="/" replace />} />
88 </Routes> 98 </Routes>
web/src/api/client.ts
@@ -26,9 +26,14 @@ @@ -26,9 +26,14 @@
26 26
27 import type { 27 import type {
28 Account, 28 Account,
  29 + CustomFieldDef,
  30 + FormDefinition,
29 Item, 31 Item,
30 JournalEntry, 32 JournalEntry,
  33 + ListViewDefinition,
31 Location, 34 Location,
  35 + MetadataEntity,
  36 + MetadataPermission,
32 MetaInfo, 37 MetaInfo,
33 Partner, 38 Partner,
34 PurchaseOrder, 39 PurchaseOrder,
@@ -75,7 +80,7 @@ export function registerUnauthorizedHandler(handler: () =&gt; void) { @@ -75,7 +80,7 @@ export function registerUnauthorizedHandler(handler: () =&gt; void) {
75 onUnauthorized = handler 80 onUnauthorized = handler
76 } 81 }
77 82
78 -async function apiFetch<T>( 83 +export async function apiFetch<T>(
79 path: string, 84 path: string,
80 init: RequestInit = {}, 85 init: RequestInit = {},
81 expectJson = true, 86 expectJson = true,
@@ -283,6 +288,36 @@ export const production = { @@ -283,6 +288,36 @@ export const production = {
283 288
284 // ─── Finance ───────────────────────────────────────────────────────── 289 // ─── Finance ─────────────────────────────────────────────────────────
285 290
  291 +// ─── Metadata admin ─────────────────────────────────────────────────
  292 +
  293 +export const metadata = {
  294 + entities: () => apiFetch<MetadataEntity[]>('/api/v1/_meta/metadata/entities'),
  295 + permissions: () => apiFetch<MetadataPermission[]>('/api/v1/_meta/metadata/permissions'),
  296 + menus: () => apiFetch<any[]>('/api/v1/_meta/metadata/menus'),
  297 + customFields: () => apiFetch<CustomFieldDef[]>('/api/v1/_meta/metadata/custom-fields'),
  298 + customFieldsFor: (entity: string) => apiFetch<CustomFieldDef[]>(`/api/v1/_meta/metadata/custom-fields/${entity}`),
  299 + listForms: () => apiFetch<FormDefinition[]>('/api/v1/_meta/metadata/forms'),
  300 + getForm: (slug: string) => apiFetch<FormDefinition>(`/api/v1/_meta/metadata/forms/${slug}`),
  301 + saveForm: (slug: string, body: Omit<FormDefinition, 'source'>) =>
  302 + apiFetch<FormDefinition>(`/api/v1/_meta/metadata/forms/${slug}`, { method: 'PUT', body: JSON.stringify(body) }),
  303 + deleteForm: (slug: string) =>
  304 + apiFetch<void>(`/api/v1/_meta/metadata/forms/${slug}`, { method: 'DELETE' }, false),
  305 + listListViews: () => apiFetch<ListViewDefinition[]>('/api/v1/_meta/metadata/list-views'),
  306 + getListView: (slug: string) => apiFetch<ListViewDefinition>(`/api/v1/_meta/metadata/list-views/${slug}`),
  307 + saveListView: (slug: string, body: Omit<ListViewDefinition, 'source'>) =>
  308 + apiFetch<ListViewDefinition>(`/api/v1/_meta/metadata/list-views/${slug}`, { method: 'PUT', body: JSON.stringify(body) }),
  309 + deleteListView: (slug: string) =>
  310 + apiFetch<void>(`/api/v1/_meta/metadata/list-views/${slug}`, { method: 'DELETE' }, false),
  311 + createCustomField: (body: Omit<CustomFieldDef, 'source'>) =>
  312 + apiFetch<CustomFieldDef>('/api/v1/_meta/metadata/custom-fields', { method: 'POST', body: JSON.stringify(body) }),
  313 + updateCustomField: (key: string, body: Omit<CustomFieldDef, 'source'>) =>
  314 + apiFetch<CustomFieldDef>(`/api/v1/_meta/metadata/custom-fields/${key}`, { method: 'PUT', body: JSON.stringify(body) }),
  315 + deleteCustomField: (key: string) =>
  316 + apiFetch<void>(`/api/v1/_meta/metadata/custom-fields/${key}`, { method: 'DELETE' }, false),
  317 +}
  318 +
  319 +// ─── Finance ─────────────────────────────────────────────────────────
  320 +
286 export const finance = { 321 export const finance = {
287 listAccounts: () => apiFetch<Account[]>('/api/v1/finance/accounts'), 322 listAccounts: () => apiFetch<Account[]>('/api/v1/finance/accounts'),
288 createAccount: (body: { 323 createAccount: (body: {
web/src/i18n/messages.ts
@@ -35,6 +35,7 @@ export const en = { @@ -35,6 +35,7 @@ export const en = {
35 'nav.system': 'System', 35 'nav.system': 'System',
36 'nav.users': 'Users', 36 'nav.users': 'Users',
37 'nav.roles': 'Roles', 37 'nav.roles': 'Roles',
  38 + 'nav.metadataAdmin': 'Metadata',
38 39
39 // ─── Actions ─────────────────────────────────────────────── 40 // ─── Actions ───────────────────────────────────────────────
40 'action.confirm': 'Confirm', 41 'action.confirm': 'Confirm',
@@ -62,6 +63,11 @@ export const en = { @@ -62,6 +63,11 @@ export const en = {
62 'action.newRole': '+ New Role', 63 'action.newRole': '+ New Role',
63 'action.newAccount': '+ New Account', 64 'action.newAccount': '+ New Account',
64 'action.adjustStock': 'Adjust Stock', 65 'action.adjustStock': 'Adjust Stock',
  66 + 'action.addField': 'Add Field',
  67 + 'action.addSection': 'Add Section',
  68 + 'action.discard': 'Discard',
  69 + 'action.delete': 'Delete',
  70 + 'action.newCustomField': 'New Custom Field',
65 71
66 // ─── Status badges ──────────────────────────────────────── 72 // ─── Status badges ────────────────────────────────────────
67 'status.DRAFT': 'Draft', 73 'status.DRAFT': 'Draft',
@@ -96,6 +102,30 @@ export const en = { @@ -96,6 +102,30 @@ export const en = {
96 'label.noRows': 'No rows.', 102 'label.noRows': 'No rows.',
97 'label.loading': 'Loading…', 103 'label.loading': 'Loading…',
98 'label.error': 'Error', 104 'label.error': 'Error',
  105 +
  106 + // ─── Metadata admin ───────────────────────────────────────
  107 + 'page.metadataAdmin.title': 'Metadata Admin',
  108 + 'tab.entities': 'Entities',
  109 + 'tab.customFields': 'Custom Fields',
  110 + 'tab.permissions': 'Permissions',
  111 + 'tab.menus': 'Menus',
  112 + 'tab.forms': 'Forms',
  113 + 'tab.listViews': 'List Views',
  114 + 'page.formDesigner.title': 'Form Designer',
  115 + 'page.listViewDesigner.title': 'List View Designer',
  116 + 'label.slug': 'Slug',
  117 + 'label.entity': 'Entity',
  118 + 'label.purpose': 'Purpose',
  119 + 'label.preview': 'Preview',
  120 + 'label.source': 'Source',
  121 + 'label.columns': 'Columns',
  122 + 'label.filters': 'Filters',
  123 + 'label.sorting': 'Sorting',
  124 + 'label.pageSize': 'Page Size',
  125 + 'label.fieldKey': 'Field Key',
  126 + 'label.targetEntity': 'Target Entity',
  127 + 'label.fieldType': 'Field Type',
  128 + 'confirm.delete': 'Are you sure?',
99 } as const 129 } as const
100 130
101 export const zhCN: Record<MessageKey, string> = { 131 export const zhCN: Record<MessageKey, string> = {
@@ -122,6 +152,7 @@ export const zhCN: Record&lt;MessageKey, string&gt; = { @@ -122,6 +152,7 @@ export const zhCN: Record&lt;MessageKey, string&gt; = {
122 'nav.system': '系统', 152 'nav.system': '系统',
123 'nav.users': '用户', 153 'nav.users': '用户',
124 'nav.roles': '角色', 154 'nav.roles': '角色',
  155 + 'nav.metadataAdmin': '元数据',
125 156
126 // ─── 操作 ────────────────────────────────────────────────── 157 // ─── 操作 ──────────────────────────────────────────────────
127 'action.confirm': '确认', 158 'action.confirm': '确认',
@@ -149,6 +180,11 @@ export const zhCN: Record&lt;MessageKey, string&gt; = { @@ -149,6 +180,11 @@ export const zhCN: Record&lt;MessageKey, string&gt; = {
149 'action.newRole': '+ 新角色', 180 'action.newRole': '+ 新角色',
150 'action.newAccount': '+ 新科目', 181 'action.newAccount': '+ 新科目',
151 'action.adjustStock': '库存调整', 182 'action.adjustStock': '库存调整',
  183 + 'action.addField': '添加字段',
  184 + 'action.addSection': '添加分区',
  185 + 'action.discard': '放弃',
  186 + 'action.delete': '删除',
  187 + 'action.newCustomField': '新建自定义字段',
152 188
153 // ─── 状态 ────────────────────────────────────────────────── 189 // ─── 状态 ──────────────────────────────────────────────────
154 'status.DRAFT': '草稿', 190 'status.DRAFT': '草稿',
@@ -183,6 +219,30 @@ export const zhCN: Record&lt;MessageKey, string&gt; = { @@ -183,6 +219,30 @@ export const zhCN: Record&lt;MessageKey, string&gt; = {
183 'label.noRows': '暂无数据', 219 'label.noRows': '暂无数据',
184 'label.loading': '加载中…', 220 'label.loading': '加载中…',
185 'label.error': '错误', 221 'label.error': '错误',
  222 +
  223 + // ─── 元数据管理 ────────────────────────────────────────────
  224 + 'page.metadataAdmin.title': '元数据管理',
  225 + 'tab.entities': '实体',
  226 + 'tab.customFields': '自定义字段',
  227 + 'tab.permissions': '权限',
  228 + 'tab.menus': '菜单',
  229 + 'tab.forms': '表单',
  230 + 'tab.listViews': '列表视图',
  231 + 'page.formDesigner.title': '表单设计器',
  232 + 'page.listViewDesigner.title': '列表视图设计器',
  233 + 'label.slug': '标识',
  234 + 'label.entity': '实体',
  235 + 'label.purpose': '用途',
  236 + 'label.preview': '预览',
  237 + 'label.source': '来源',
  238 + 'label.columns': '列',
  239 + 'label.filters': '筛选',
  240 + 'label.sorting': '排序',
  241 + 'label.pageSize': '每页行数',
  242 + 'label.fieldKey': '字段键',
  243 + 'label.targetEntity': '目标实体',
  244 + 'label.fieldType': '字段类型',
  245 + 'confirm.delete': '确定删除?',
186 } 246 }
187 247
188 export const locales = { 248 export const locales = {
web/src/layout/AppLayout.tsx
@@ -64,6 +64,7 @@ const NAV: NavGroup[] = [ @@ -64,6 +64,7 @@ const NAV: NavGroup[] = [
64 items: [ 64 items: [
65 { to: '/users', labelKey: 'nav.users' }, 65 { to: '/users', labelKey: 'nav.users' },
66 { to: '/roles', labelKey: 'nav.roles' }, 66 { to: '/roles', labelKey: 'nav.roles' },
  67 + { to: '/admin/metadata', labelKey: 'nav.metadataAdmin' },
67 ], 68 ],
68 }, 69 },
69 ] 70 ]
web/src/types/api.ts
@@ -284,3 +284,71 @@ export interface JournalEntry { @@ -284,3 +284,71 @@ export interface JournalEntry {
284 postedAt: string 284 postedAt: string
285 lines: JournalEntryLine[] 285 lines: JournalEntryLine[]
286 } 286 }
  287 +
  288 +// ─── Metadata Definitions ───────────────────────────────────────────
  289 +
  290 +export type FormPurpose = 'create' | 'edit' | 'user-task' | 'view'
  291 +
  292 +export interface FormDefinition {
  293 + entityName: string
  294 + slug: string
  295 + title: string
  296 + purpose: FormPurpose
  297 + jsonSchema: Record<string, unknown>
  298 + uiSchema: Record<string, unknown>
  299 + version: number
  300 + source?: string
  301 +}
  302 +
  303 +export interface ListViewColumnDef {
  304 + field: string
  305 + label: string
  306 + width?: string
  307 + sortable: boolean
  308 + format?: 'date' | 'money' | 'status-badge' | 'link'
  309 +}
  310 +
  311 +export interface ListViewDefinition {
  312 + entityName: string
  313 + slug: string
  314 + title: string
  315 + columns: ListViewColumnDef[]
  316 + defaultSort?: { field: string; direction: 'asc' | 'desc' }
  317 + filters?: { field: string; operator: string; label: string }[]
  318 + pageSize: number
  319 + version: number
  320 + source?: string
  321 +}
  322 +
  323 +export interface CustomFieldType {
  324 + kind: string
  325 + maxLength?: number
  326 + precision?: number
  327 + scale?: number
  328 + targetEntity?: string
  329 + allowedValues?: string[]
  330 +}
  331 +
  332 +export interface CustomFieldDef {
  333 + key: string
  334 + targetEntity: string
  335 + type: CustomFieldType
  336 + required: boolean
  337 + pii: boolean
  338 + labelTranslations: Record<string, string>
  339 + source?: string
  340 +}
  341 +
  342 +export interface MetadataEntity {
  343 + name: string
  344 + pbc: string
  345 + table: string
  346 + description?: string
  347 + source?: string
  348 +}
  349 +
  350 +export interface MetadataPermission {
  351 + key: string
  352 + description: string
  353 + source?: string
  354 +}