Compare commits

...

11 Commits

37 changed files with 1062 additions and 199 deletions

4
.env
View File

@@ -1 +1,3 @@
VITE_SIMPLE_REST_URL=http://localhost:8083/core/api/admin VITE_AUTH_URL=http://localhost:8080
VITE_SECURITY_REST_URL=http://localhost:8081/security
VITE_GATEWAY_REST_URL=http://localhost:8081/core/api/admin

View File

@@ -1,21 +0,0 @@
module.exports = {
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"prettier",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
env: {
browser: true,
es2021: true,
},
settings: {
react: {
version: "detect",
},
},
};

31
eslint.config.mjs Normal file
View File

@@ -0,0 +1,31 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import pluginReact from 'eslint-plugin-react';
export default [
{
files: ['**/*.{js,mjs,cjs,jsx}'],
rules: {
semi: 0,
'no-unused-vars': 1,
'comma-dangle': [1, {
arrays: 'only-multiline',
objects: 'only-multiline',
imports: 'only-multiline',
exports: 'only-multiline',
functions: 'only-multiline',
}],
yoda: ['error', 'always'],
'operator-linebreak': ['error', 'before'],
indent: ['error', 2, { SwitchCase: 1 }],
'space-before-function-paren': ['error', {
anonymous: 'never',
named: 'never',
asyncArrow: 'always',
}],
},
},
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
pluginReact.configs.flat.recommended,
];

View File

@@ -14,14 +14,23 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^5.16.14", "@mui/icons-material": "^5.16.14",
"@mui/material": "^5.16.14", "@mui/material": "^5.16.14",
"@mui/utils": "^6.4.1",
"@tanstack/react-query": "^5.63.0", "@tanstack/react-query": "^5.63.0",
"framer-motion": "^12.0.1",
"lodash": "^4.17.21",
"prop-types": "^15.8.1",
"ra-data-simple-rest": "^5.4.0", "ra-data-simple-rest": "^5.4.0",
"ra-i18n-polyglot": "^5.5.0",
"ra-language-english": "^5.5.0",
"ra-language-french": "^5.5.0",
"react": "^18.3.0", "react": "^18.3.0",
"react-admin": "^5.4.0", "react-admin": "^5.4.0",
"react-dom": "^18.3.0" "react-dom": "^18.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.14",
"@types/node": "^20.10.7", "@types/node": "^20.10.7",
"@types/prop-types": "^15.7.14",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^5.60.1",

144
pnpm-lock.yaml generated
View File

@@ -20,25 +20,52 @@ importers:
'@mui/material': '@mui/material':
specifier: ^5.16.14 specifier: ^5.16.14
version: 5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/utils':
specifier: ^6.4.1
version: 6.4.1(@types/react@18.3.18)(react@18.3.1)
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^5.63.0 specifier: ^5.63.0
version: 5.63.0(react@18.3.1) version: 5.63.0(react@18.3.1)
framer-motion:
specifier: ^12.0.1
version: 12.0.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
lodash:
specifier: ^4.17.21
version: 4.17.21
prop-types:
specifier: ^15.8.1
version: 15.8.1
ra-data-simple-rest: ra-data-simple-rest:
specifier: ^5.4.0 specifier: ^5.4.0
version: 5.4.3(ra-core@5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)) version: 5.4.3(ra-core@5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1))
ra-i18n-polyglot:
specifier: ^5.5.0
version: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
ra-language-english:
specifier: ^5.5.0
version: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
ra-language-french:
specifier: ^5.5.0
version: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
react: react:
specifier: ^18.3.0 specifier: ^18.3.0
version: 18.3.1 version: 18.3.1
react-admin: react-admin:
specifier: ^5.4.0 specifier: ^5.4.0
version: 5.4.3(@mui/utils@6.3.1(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@19.0.0)(react@18.3.1) version: 5.4.3(@mui/utils@6.4.1(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@19.0.0)(react@18.3.1)
react-dom: react-dom:
specifier: ^18.3.0 specifier: ^18.3.0
version: 18.3.1(react@18.3.1) version: 18.3.1(react@18.3.1)
devDependencies: devDependencies:
'@types/lodash':
specifier: ^4.17.14
version: 4.17.14
'@types/node': '@types/node':
specifier: ^20.10.7 specifier: ^20.10.7
version: 20.17.11 version: 20.17.11
'@types/prop-types':
specifier: ^15.7.14
version: 15.7.14
'@types/react': '@types/react':
specifier: ^18.3.3 specifier: ^18.3.3
version: 18.3.18 version: 18.3.18
@@ -494,8 +521,8 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
'@mui/utils@6.3.1': '@mui/utils@6.4.1':
resolution: {integrity: sha512-sjGjXAngoio6lniQZKJ5zGfjm+LD2wvLwco7FbKe1fu8A7VIFmz2SwkLb+MDPLNX1lE7IscvNNyh1pobtZg2tw==} resolution: {integrity: sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
peerDependencies: peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -644,6 +671,9 @@ packages:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/lodash@4.17.14':
resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==}
'@types/node@20.17.11': '@types/node@20.17.11':
resolution: {integrity: sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==} resolution: {integrity: sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==}
@@ -1102,6 +1132,20 @@ packages:
for-each@0.3.3: for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
framer-motion@12.0.1:
resolution: {integrity: sha512-u6p0Qc4cY/AEQAtrC7qiYlXla39qnWoI4JXY7OCNBDXwJ5yRBD8HU+RhaOqqziw2m/b0BDh32f44W94+wXonMQ==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fs.realpath@1.0.0: fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -1420,6 +1464,12 @@ packages:
minimatch@3.1.2: minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
motion-dom@12.0.0:
resolution: {integrity: sha512-CvYd15OeIR6kHgMdonCc1ihsaUG4MYh/wrkz8gZ3hBX/uamyZCXN9S9qJoYF03GqfTt7thTV/dxnHYX4+55vDg==}
motion-utils@12.0.0:
resolution: {integrity: sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==}
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1553,25 +1603,28 @@ packages:
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
ra-core@5.4.3: ra-core@5.5.0:
resolution: {integrity: sha512-Jf9y7e7R0qKw9xd5cfGHg9jpYtphLjbRjnaZjDA2VjB/a+N37yl9krJAsUv2HA8nubhOO6RCkJE61m4Wx8rv9Q==} resolution: {integrity: sha512-dqZioqegMaMrXtns/a2H0zfth+kVbT+fvZAmDRvXIs6xmz/ulle6XIEz21sJZqAN5WG0+0FPvncdOPFRwMBVRg==}
peerDependencies: peerDependencies:
react: ^18.0.0 || ^19.0.0 react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0
react-hook-form: ^7.53.0 react-hook-form: ^7.53.0
react-router: ^6.22.0 react-router: ^6.28.1 || ^7.1.1
react-router-dom: ^6.22.0 react-router-dom: ^6.28.1 || ^7.1.1
ra-data-simple-rest@5.4.3: ra-data-simple-rest@5.4.3:
resolution: {integrity: sha512-G2eCL2CbKC0/dxVzkc7FSfAevgX6rfz6i9HAfyLIILoeQQ1xnpES3DdmaQUQGhhyzLxww5KjVmGBFFbgzw9sBw==} resolution: {integrity: sha512-G2eCL2CbKC0/dxVzkc7FSfAevgX6rfz6i9HAfyLIILoeQQ1xnpES3DdmaQUQGhhyzLxww5KjVmGBFFbgzw9sBw==}
peerDependencies: peerDependencies:
ra-core: ^5.0.0 ra-core: ^5.0.0
ra-i18n-polyglot@5.4.3: ra-i18n-polyglot@5.5.0:
resolution: {integrity: sha512-/O2FIjzMrU1JZp26YbAuqikleOwcRHVbEuBZg8uoIwCf3YWRcFknxgOTXbE4/iozQsY2pW5XB5YU0a2Oy58eCg==} resolution: {integrity: sha512-DQYZ4jF2pnGMi5VPXDOtfJ+cRoJDCvrFU896C38fUcLbPbwjt351UtolFffAOQahzrH3XDITTDf2H9g2R3Njrw==}
ra-language-english@5.4.3: ra-language-english@5.5.0:
resolution: {integrity: sha512-i53rYz1yMNv3a+GcJe/WZRhDJafmf935VeZM71fR9u1fptR0QMCHGSvY76aC63TUfKybXZerwx8x75d+5M5/EA==} resolution: {integrity: sha512-omk5U83R3cZkDS1srwx0Xb02Ts2fGXdqwDvBK2xLeDgZSlcukOAJpen8NFGwi0wFN7DcSW1PpwpXbV80gRawIg==}
ra-language-french@5.5.0:
resolution: {integrity: sha512-4WtojBh1vCqopDE4w6EE7KpaWLFk8JeEH+eMMkHMD4dF7HjYq7TsNUMsUUapH3srY0d6DFdX9FwbQt/0OIgfDQ==}
ra-ui-materialui@5.4.3: ra-ui-materialui@5.4.3:
resolution: {integrity: sha512-6Fhjtl+0NcPvlaLBz8W9TcUyCsKlEpobmk5HONiKD91sdIitinZyJeZl/ThaOTftZs7qDsoHJarmXLiLl017HA==} resolution: {integrity: sha512-6Fhjtl+0NcPvlaLBz8W9TcUyCsKlEpobmk5HONiKD91sdIitinZyJeZl/ThaOTftZs7qDsoHJarmXLiLl017HA==}
@@ -1618,9 +1671,6 @@ packages:
react-is@16.13.1: react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
react-is@19.0.0: react-is@19.0.0:
resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==}
@@ -2358,7 +2408,7 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.3.18 '@types/react': 18.3.18
'@mui/utils@6.3.1(@types/react@18.3.18)(react@18.3.1)': '@mui/utils@6.4.1(@types/react@18.3.18)(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.26.0 '@babel/runtime': 7.26.0
'@mui/types': 7.2.21(@types/react@18.3.18) '@mui/types': 7.2.21(@types/react@18.3.18)
@@ -2475,6 +2525,8 @@ snapshots:
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/lodash@4.17.14': {}
'@types/node@20.17.11': '@types/node@20.17.11':
dependencies: dependencies:
undici-types: 6.19.8 undici-types: 6.19.8
@@ -3122,6 +3174,16 @@ snapshots:
dependencies: dependencies:
is-callable: 1.2.7 is-callable: 1.2.7
framer-motion@12.0.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
motion-dom: 12.0.0
motion-utils: 12.0.0
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.3.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
fs.realpath@1.0.0: {} fs.realpath@1.0.0: {}
fsevents@2.3.3: fsevents@2.3.3:
@@ -3442,6 +3504,12 @@ snapshots:
dependencies: dependencies:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
motion-dom@12.0.0:
dependencies:
motion-utils: 12.0.0
motion-utils@12.0.0: {}
ms@2.1.3: {} ms@2.1.3: {}
nanoid@3.3.8: {} nanoid@3.3.8: {}
@@ -3574,7 +3642,7 @@ snapshots:
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
ra-core@5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1): ra-core@5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@tanstack/react-query': 5.63.0(react@18.3.1) '@tanstack/react-query': 5.63.0(react@18.3.1)
clsx: 2.1.1 clsx: 2.1.1
@@ -3588,19 +3656,19 @@ snapshots:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
react-error-boundary: 4.1.2(react@18.3.1) react-error-boundary: 4.1.2(react@18.3.1)
react-hook-form: 7.54.2(react@18.3.1) react-hook-form: 7.54.2(react@18.3.1)
react-is: 18.3.1 react-is: 19.0.0
react-router: 6.28.1(react@18.3.1) react-router: 6.28.1(react@18.3.1)
react-router-dom: 6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: 6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ra-data-simple-rest@5.4.3(ra-core@5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)): ra-data-simple-rest@5.4.3(ra-core@5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)):
dependencies: dependencies:
query-string: 7.1.3 query-string: 7.1.3
ra-core: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-core: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
ra-i18n-polyglot@5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1): ra-i18n-polyglot@5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
node-polyglot: 2.6.0 node-polyglot: 2.6.0
ra-core: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-core: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
- react-dom - react-dom
@@ -3608,9 +3676,9 @@ snapshots:
- react-router - react-router
- react-router-dom - react-router-dom
ra-language-english@5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1): ra-language-english@5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
ra-core: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-core: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
- react-dom - react-dom
@@ -3618,11 +3686,21 @@ snapshots:
- react-router - react-router
- react-router-dom - react-router-dom
ra-ui-materialui@5.4.3(1d367da1498e3e624475fcfb8fc76db3): ra-language-french@5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1):
dependencies:
ra-core: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
transitivePeerDependencies:
- react
- react-dom
- react-hook-form
- react-router
- react-router-dom
ra-ui-materialui@5.4.3(2dfba0dbd71240bce4cdd9b5efbeb161):
dependencies: dependencies:
'@mui/icons-material': 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@mui/icons-material': 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
'@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) '@mui/utils': 6.4.1(@types/react@18.3.18)(react@18.3.1)
'@tanstack/react-query': 5.63.0(react@18.3.1) '@tanstack/react-query': 5.63.0(react@18.3.1)
autosuggest-highlight: 3.3.4 autosuggest-highlight: 3.3.4
clsx: 2.1.1 clsx: 2.1.1
@@ -3632,7 +3710,7 @@ snapshots:
jsonexport: 3.2.0 jsonexport: 3.2.0
lodash: 4.17.21 lodash: 4.17.21
query-string: 7.1.3 query-string: 7.1.3
ra-core: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-core: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
react-dropzone: 14.3.5(react@18.3.1) react-dropzone: 14.3.5(react@18.3.1)
@@ -3643,16 +3721,16 @@ snapshots:
react-router-dom: 6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: 6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-admin@5.4.3(@mui/utils@6.3.1(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@19.0.0)(react@18.3.1): react-admin@5.4.3(@mui/utils@6.4.1(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@19.0.0)(react@18.3.1):
dependencies: dependencies:
'@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1)
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
'@mui/icons-material': 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@mui/icons-material': 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)
'@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ra-core: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-core: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
ra-i18n-polyglot: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-i18n-polyglot: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
ra-language-english: 5.4.3(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1) ra-language-english: 5.5.0(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.54.2(react@18.3.1))(react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.28.1(react@18.3.1))(react@18.3.1)
ra-ui-materialui: 5.4.3(1d367da1498e3e624475fcfb8fc76db3) ra-ui-materialui: 5.4.3(2dfba0dbd71240bce4cdd9b5efbeb161)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
react-hook-form: 7.54.2(react@18.3.1) react-hook-form: 7.54.2(react@18.3.1)
@@ -3688,8 +3766,6 @@ snapshots:
react-is@16.13.1: {} react-is@16.13.1: {}
react-is@18.3.1: {}
react-is@19.0.0: {} react-is@19.0.0: {}
react-refresh@0.14.2: {} react-refresh@0.14.2: {}

View File

@@ -0,0 +1,26 @@
<svg width="136" height="148" viewBox="0 0 136 148" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="LOGO" clip-path="url(#clip0_3197_10050)">
<g id="Group">
<path id="Vector" d="M5.0702 147.802H3.85324V138.172H0.5V137.091H8.42345V138.172H5.0702V147.802Z" fill="#E87722"/>
<path id="Vector_2" d="M14.5572 137.041C15.4313 137.041 16.1622 137.14 16.6972 137.384C17.2812 137.582 17.7182 137.924 17.9631 138.366C18.2533 138.808 18.4002 139.398 18.4002 140.087C18.4002 140.678 18.3022 141.119 18.1101 141.511C17.9142 141.904 17.624 142.201 17.3339 142.444C16.9948 142.692 16.652 142.837 16.2639 142.985L19.1801 147.802H17.722L15.0959 143.332H12.9069V147.802H11.6899V137.041H14.5572ZM14.463 138.122H12.9069V142.25H14.6099C15.484 142.25 16.117 142.052 16.505 141.709C16.8931 141.367 17.138 140.826 17.138 140.137C17.138 139.398 16.8931 138.907 16.4561 138.614C16.019 138.271 15.386 138.122 14.463 138.122Z" fill="#E87722"/>
<path id="Vector_3" d="M29.2135 147.802L27.9024 144.364H23.6223L22.3111 147.802H21.0452L25.2273 136.992H26.3463L30.4795 147.802H29.2135ZM27.5143 143.282L26.2483 139.893C26.1994 139.794 26.1504 139.646 26.1014 139.452C26.0524 139.257 25.9545 139.059 25.9055 138.812C25.8565 138.614 25.8075 138.42 25.7585 138.271C25.7096 138.469 25.6606 138.663 25.6116 138.861C25.5626 139.059 25.5136 139.254 25.4647 139.402C25.4157 139.551 25.3667 139.695 25.3177 139.844L24.0518 143.233H27.503V143.282H27.5143Z" fill="#E87722"/>
<path id="Vector_4" d="M42.0463 142.349C42.0463 143.579 41.8014 144.562 41.3644 145.395C40.9273 146.229 40.2944 146.82 39.4693 147.212C38.6441 147.604 37.6231 147.802 36.4551 147.802H33.5879V137.041H36.798C37.868 137.041 38.7911 137.239 39.571 137.631C40.3471 138.023 40.9801 138.614 41.3682 139.402C41.8542 140.236 42.0501 141.173 42.0501 142.349H42.0463ZM40.7314 142.399C40.7314 141.416 40.5845 140.628 40.2454 139.992C39.9063 139.356 39.4693 138.911 38.8363 138.568C38.2523 138.275 37.4761 138.126 36.5983 138.126H34.8011V146.724H36.3082C37.7663 146.724 38.8853 146.382 39.6124 145.643C40.3886 144.904 40.7314 143.827 40.7314 142.399Z" fill="#E87722"/>
<path id="Vector_5" d="M51.9816 147.802H46.0513V137.041H51.9816V138.122H47.3134V141.61H51.7367V142.692H47.3134V146.671H51.9816V147.802Z" fill="#E87722"/>
<path id="Vector_6" d="M67.5158 144.954C67.5158 145.594 67.3689 146.134 67.0298 146.576C66.7397 147.018 66.2988 147.36 65.7186 147.608C65.1384 147.855 64.5017 147.951 63.7255 147.951C63.3374 147.951 62.9494 147.951 62.5575 147.901C62.2184 147.852 61.8756 147.802 61.5855 147.753C61.2954 147.654 61.0015 147.604 60.8093 147.459V146.279C61.1974 146.427 61.6344 146.572 62.1695 146.721C62.7045 146.869 63.2395 146.919 63.8235 146.919C64.3585 146.919 64.7955 146.869 65.1836 146.721C65.5717 146.572 65.8166 146.378 66.0087 146.081C66.2046 145.788 66.2988 145.491 66.2988 145.099C66.2988 144.756 66.2009 144.409 66.0539 144.166C65.907 143.922 65.6169 143.674 65.2778 143.476C64.8897 143.278 64.4037 143.035 63.7707 142.837C63.3337 142.688 62.8966 142.494 62.5538 142.296C62.2147 142.098 61.9208 141.854 61.6797 141.607C61.4385 141.359 61.2916 141.066 61.1447 140.773C61.0467 140.43 60.9487 140.084 60.9487 139.642C60.9487 139.052 61.0957 138.56 61.3858 138.168C61.6759 137.776 62.0677 137.429 62.6027 137.235C63.1378 137.037 63.7217 136.893 64.3547 136.893C64.9387 136.893 65.4247 136.942 65.9108 137.041C66.3968 137.14 66.8339 137.289 67.2709 137.483L66.8828 138.564C66.4948 138.416 66.1067 138.271 65.6659 138.172C65.2288 138.073 64.7918 138.024 64.3547 138.024C63.9177 138.024 63.5296 138.073 63.1867 138.222C62.8966 138.37 62.6517 138.515 62.4558 138.762C62.3089 139.01 62.2109 139.303 62.2109 139.646C62.2109 140.038 62.3089 140.335 62.4558 140.579C62.6027 140.826 62.8439 141.07 63.1867 141.268C63.5296 141.466 64.0119 141.66 64.5958 141.858C65.2288 142.106 65.7638 142.349 66.2009 142.597C66.6379 142.844 66.977 143.187 67.2219 143.53C67.4178 143.922 67.512 144.364 67.512 144.954H67.5158Z" fill="#E87722"/>
<path id="Vector_7" d="M77.2704 147.802H71.3401V137.041H77.2704V138.122H72.6023V141.61H77.0255V142.692H72.6023V146.671H77.2704V147.802Z" fill="#E87722"/>
<path id="Vector_8" d="M84.1879 137.041C85.062 137.041 85.793 137.14 86.328 137.384C86.912 137.582 87.349 137.924 87.5939 138.366C87.884 138.808 88.031 139.398 88.031 140.087C88.031 140.678 87.933 141.119 87.7408 141.511C87.5487 141.904 87.2548 142.201 86.9647 142.444C86.6256 142.692 86.2827 142.837 85.8947 142.985L88.8109 147.802H87.3528L84.7267 143.332H82.5377V147.802H81.2717V137.041H84.1879ZM84.09 138.122H82.5339V142.25H84.2369C85.111 142.25 85.744 142.052 86.132 141.709C86.5201 141.367 86.765 140.826 86.765 140.137C86.765 139.398 86.5201 138.907 86.0831 138.614C85.646 138.271 85.013 138.122 84.09 138.122Z" fill="#E87722"/>
<path id="Vector_9" d="M99.5714 137.041L95.7322 147.802H94.4662L90.627 137.041H91.9381L94.4172 144.116C94.5152 144.409 94.6132 144.657 94.7074 144.95C94.8053 145.197 94.8543 145.441 94.9033 145.689C94.9523 145.936 95.0012 146.18 95.0502 146.378C95.0992 146.13 95.1482 145.936 95.2461 145.689C95.3441 145.441 95.3931 145.197 95.4421 144.95C95.54 144.702 95.638 144.409 95.7322 144.116L98.2113 137.041H99.5714Z" fill="#E87722"/>
<path id="Vector_10" d="M102.714 147.802V137.041H103.931V147.802H102.714Z" fill="#E87722"/>
<path id="Vector_11" d="M113.056 138.027C112.472 138.027 111.937 138.126 111.451 138.32C110.965 138.515 110.577 138.812 110.234 139.204C109.891 139.596 109.65 140.038 109.503 140.579C109.356 141.119 109.258 141.759 109.258 142.444C109.258 143.328 109.405 144.116 109.695 144.801C109.985 145.441 110.377 145.982 110.961 146.325C111.545 146.667 112.227 146.865 113.052 146.865C113.538 146.865 113.975 146.816 114.412 146.766C114.849 146.667 115.237 146.568 115.629 146.473V147.555C115.241 147.703 114.853 147.802 114.412 147.897C113.975 147.947 113.489 147.996 112.905 147.996C111.835 147.996 110.912 147.749 110.181 147.307C109.45 146.865 108.915 146.226 108.527 145.392C108.188 144.558 107.992 143.575 107.992 142.444C107.992 141.61 108.09 140.872 108.331 140.232C108.576 139.543 108.915 138.953 109.303 138.461C109.74 137.97 110.275 137.578 110.908 137.33C111.541 137.083 112.269 136.938 113.097 136.938C113.632 136.938 114.167 136.988 114.653 137.087C115.139 137.186 115.626 137.334 116.014 137.528L115.528 138.61C115.188 138.461 114.797 138.317 114.409 138.218C113.972 138.069 113.534 138.02 113.048 138.02L113.056 138.027Z" fill="#E87722"/>
<path id="Vector_12" d="M125.47 147.802H119.54V137.041H125.47V138.122H120.802V141.61H125.225V142.692H120.802V146.671H125.47V147.802Z" fill="#E87722"/>
<path id="Vector_13" d="M135.5 144.954C135.5 145.594 135.353 146.134 135.014 146.576C134.724 147.018 134.283 147.36 133.703 147.608C133.123 147.855 132.486 147.951 131.71 147.951C131.322 147.951 130.933 147.951 130.542 147.901C130.203 147.852 129.86 147.802 129.57 147.753C129.279 147.654 128.986 147.604 128.793 147.459V146.279C129.182 146.427 129.619 146.572 130.154 146.721C130.689 146.869 131.224 146.919 131.808 146.919C132.343 146.919 132.78 146.869 133.168 146.721C133.556 146.572 133.801 146.378 133.993 146.081C134.189 145.788 134.283 145.491 134.283 145.099C134.283 144.756 134.185 144.409 134.038 144.166C133.891 143.922 133.601 143.674 133.262 143.476C132.874 143.278 132.388 143.035 131.755 142.837C131.318 142.688 130.881 142.494 130.538 142.296C130.195 142.098 129.905 141.854 129.664 141.607C129.423 141.359 129.276 141.066 129.129 140.773C129.031 140.43 128.933 140.084 128.933 139.642C128.933 139.052 129.08 138.56 129.37 138.168C129.66 137.776 130.052 137.429 130.587 137.235C131.122 137.041 131.706 136.893 132.339 136.893C132.923 136.893 133.409 136.942 133.895 137.041C134.381 137.14 134.818 137.289 135.255 137.483L134.867 138.564C134.479 138.416 134.091 138.271 133.65 138.172C133.213 138.073 132.776 138.024 132.339 138.024C131.902 138.024 131.514 138.073 131.171 138.222C130.881 138.37 130.636 138.515 130.44 138.762C130.293 139.01 130.195 139.303 130.195 139.646C130.195 140.038 130.293 140.335 130.44 140.579C130.587 140.822 130.828 141.07 131.171 141.268C131.514 141.466 131.996 141.66 132.58 141.858C133.213 142.106 133.748 142.349 134.185 142.597C134.622 142.844 134.961 143.187 135.206 143.53C135.353 143.922 135.496 144.364 135.496 144.954H135.5Z" fill="#E87722"/>
</g>
<path id="Vector_14" d="M54.717 99.2213H44.3936V127.247H49.1785V114.719H55.3575C59.615 114.719 63.4204 111.216 63.4204 106.989C63.3827 102.725 59.2382 99.2213 54.717 99.2213ZM52.6448 112.93H49.1408V100.935H52.6448C55.9227 100.973 58.5601 103.448 58.5601 106.913C58.5601 110.378 55.9227 112.892 52.6448 112.93ZM24.2364 127.285H28.9837L15.8722 98.5359H14.7042L0.5 127.285H2.83597L8.11073 116.776H19.4138L24.2364 127.285ZM9.05265 114.986L14.0637 104.971L18.6226 114.986H9.05265ZM113.003 127.285H110.856L121.179 113.425L111.76 99.1832H116.658L123.741 109.998L131.766 99.1832H133.951L124.758 111.521L135.195 127.285H130.297L122.196 114.986L113.003 127.285ZM83.2007 100.973V112.701H93.3735V114.795H83.2007V125.457H97.857V127.285H78.5288V99.1832H97.857V101.011L83.2007 100.973Z" fill="#575756"/>
<path id="Vector_15" d="M93.7126 76.336C93.7126 70.3196 98.5352 65.4456 104.488 65.4456C110.441 65.4456 115.264 70.3196 115.264 76.336C115.264 82.3525 110.441 87.2265 104.488 87.2265C98.5352 87.2265 93.7126 82.3525 93.7126 76.336ZM92.0548 34.3735C86.667 31.746 80.1112 34.0688 77.5115 39.5141L62.2901 71.4239C59.6904 76.8691 61.9887 83.4948 67.3764 86.1222C72.7642 88.7497 79.32 86.4269 81.9197 80.9816L97.1412 49.0718C99.7409 43.6265 97.4426 37.0009 92.0548 34.3735ZM73.4047 1.13086C68.0923 -1.53464 61.5742 0.673916 58.9368 6.043L26.8738 71.5001C24.2364 76.8691 26.4217 83.4567 31.7341 86.1222C37.0466 88.7877 43.5647 86.5792 46.202 81.2101L78.2651 15.753C80.9024 10.3459 78.7172 3.79636 73.4047 1.13086Z" fill="#E87722"/>
</g>
<defs>
<clipPath id="clip0_3197_10050">
<rect width="135" height="148" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -1,14 +1,7 @@
import { import { Admin, Resource } from "react-admin";
Admin,
EditGuesser,
ListGuesser,
Resource,
ShowGuesser,
} from "react-admin";
import { Layout } from "./Layout"; import { Layout } from "./Layout";
import { dataProvider } from "./dataProvider"; import { AdminDashboard, PersonList, PersonShow, PersonEdit } from '@admin';
import { authProvider } from "./authProvider"; import { authProvider, dataProvider, i18nProvider, darkTheme, lightTheme } from "@core";
import { AdminDashboard } from '@admin';
export const App = () => ( export const App = () => (
<Admin <Admin
@@ -16,12 +9,16 @@ export const App = () => (
dashboard={AdminDashboard} dashboard={AdminDashboard}
dataProvider={dataProvider} dataProvider={dataProvider}
authProvider={authProvider} authProvider={authProvider}
i18nProvider={i18nProvider}
theme={lightTheme}
darkTheme={darkTheme}
> >
<Resource <Resource
name="person" name="person"
list={ListGuesser} options={{ label: "Users" }}
edit={EditGuesser} list={PersonList}
show={ShowGuesser} show={PersonShow}
edit={PersonEdit}
/> />
</Admin> </Admin>
); );

View File

@@ -1,10 +1,64 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { CheckForApplicationUpdate, Layout as RALayout } from "react-admin"; import { Box, Stack } from "@mui/material";
import { blueGrey } from "@mui/material/colors";
import { CheckForApplicationUpdate, useSidebarState } from 'react-admin';
import { AdminMenu } from "@admin"; import { AdminMenu } from "@admin";
import { AdminAppbar, AdminSidebar } from "@core";
export const Layout = ({ children }: { children: ReactNode }) => ( export const Layout = ({ children }: { children: ReactNode }) => {
<RALayout menu={AdminMenu}> const [open] = useSidebarState();
{children}
return (
<Box
sx={{
bgcolor: (theme) =>
"dark" === theme.palette.mode ? '#313131' : '#fafafb',
position: "relative",
display: "flex",
flexDirection: "column",
minHeight: "100vw",
zIndex: 1,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
width: "100%",
overflowX: "auto",
}}
>
<Stack sx={{ flexGrow: 1 }} direction="row">
<Stack
sx={{
bgcolor: (theme) =>
"dark" === theme.palette.mode ? blueGrey[900] : "white",
position: "relative",
}}
direction="column"
>
<AdminSidebar>
<Stack
sx={{
justifyContent: "center",
alignItems: "center",
p: 3,
mb: 3,
}}
>
{open && <img src="/img/ats-logo-desktop.svg" alt="Ats Logo" />}
</Stack>
<AdminMenu />
</AdminSidebar>
</Stack>
<Stack sx={{ flexGrow: 1 }} direction="column">
<AdminAppbar />
<Box sx={{ p: 3 }}>{children}</Box>
<CheckForApplicationUpdate /> <CheckForApplicationUpdate />
</RALayout> </Stack>
); </Stack>
</Box>
</Box>
);
};

View File

@@ -1,20 +1,18 @@
import { Card, CardContent } from "@mui/material"; import { Card, CardContent, Typography } from "@mui/material";
import { Title, useDataProvider } from "react-admin"; import { Title } from "react-admin";
import { useQuery } from "@tanstack/react-query";
export const AdminDashboard = () => { export const AdminDashboard = () => {
const dataProvider = useDataProvider();
const { data } = useQuery({
queryKey: ["dashboard"],
queryFn: () => dataProvider.person(),
});
console.log(data);
return ( return (
<Card> <Card>
<Title title="Welcome to the administration" /> <Title title="Welcome to the administration" />
<CardContent>Lorem ipsum sic dolor amet...</CardContent> <CardContent>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet
impedit, temporibus? Amet dolor fuga hic libero molestiae nemo
perferendis quas quis repellendus voluptas? Aliquid doloremque, est
labore odit optio similique.
</Typography>
</CardContent>
</Card> </Card>
); );
}; };

View File

@@ -1,7 +1,20 @@
import { Menu } from "react-admin"; import { Menu } from "react-admin";
import { DashboardTwoTone, Person4TwoTone } from '@mui/icons-material';
import { AdminMenuItem, AdminMenuLabel } from "@core";
export const AdminMenu = () => ( export const AdminMenu = () => (
<Menu> <Menu>
<Menu.DashboardItem /> <AdminMenuItem
to="/"
leftIcon={<DashboardTwoTone />}
primaryText="ra.page.dashboard"
/>
<AdminMenuLabel label="ra.menu.main.userManagement" id="people">
<AdminMenuItem
to="/person"
leftIcon={<Person4TwoTone />}
primaryText="ra.menu.people"
/>
</AdminMenuLabel>
</Menu> </Menu>
); );

View File

@@ -1,2 +1,3 @@
export { AdminMenu } from './admin-menu'; export { AdminMenu } from './admin-menu';
export { AdminDashboard } from './admin-dashboard'; export { AdminDashboard } from './admin-dashboard';
export * from './resources';

View File

@@ -0,0 +1 @@
export * from './person';

View File

@@ -0,0 +1,39 @@
import { FC } from "react";
import { isObject } from "lodash";
import { useRecordContext } from "react-admin";
import { Chip, Stack } from "@mui/material";
interface FieldProps {
source: string;
}
interface People {
name: string;
}
export const ArrayField: FC<FieldProps> = ({ source }) => {
const record = useRecordContext();
const objects = record?.[source];
if (!objects) return "";
return (
<Stack direction="row" gap={1}>
{objects.map((currentObject: People | string, key: number) => {
if (isObject(currentObject)) {
return (
<Chip
key={`${key}-people`}
size="small"
label={currentObject.name}
/>
);
}
return (
<Chip key={`${key}-people`} size="small" label={currentObject} />
);
})}
</Stack>
);
};

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { TextField } from '@mui/material';
interface FilterNameProps {
setFirstName: (firstName: string) => void;
}
export const FilterName: FC<FilterNameProps> = ({ setFirstName }) => {
return (
<TextField
label="First name"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setFirstName(event.target.value);
}}
/>
);
};

View File

@@ -0,0 +1,2 @@
export { ArrayField } from "./array-field.tsx";
export { FilterName } from './filter-name.tsx';

View File

@@ -0,0 +1,76 @@
import {
BooleanInput,
DateInput,
Edit,
Labeled,
SimpleForm,
TextInput,
} from "react-admin";
import { Box, Card, CardContent, CardHeader, Stack } from "@mui/material";
export const PersonEdit = () => {
return (
<Edit>
<SimpleForm>
<Card sx={{ width: "100%", m: 1 }}>
<CardHeader title="General info" />
<CardContent>
<Stack
sx={{
flexWrap: "wrap",
justifyContent: "flex-start",
alignItems: "center",
}}
direction="row"
columnGap={5}
rowGap={2}
>
<Labeled label="Name">
<Stack direction="row" gap={1}>
<TextInput sx={{ textTransform: "" }} source="title" />
<TextInput source="firstName" />
<TextInput source="lastName" />
</Stack>
</Labeled>
<TextInput source="email" />
<BooleanInput
source="userAccess"
label="Access to the Platform"
/>
<Labeled label="Position">
<TextInput source="jobPosition" />
</Labeled>
<Labeled label="Phone number">
<TextInput source="phoneNumber" />
</Labeled>
<Labeled label="Mobile number">
<TextInput source="mobileNumber" />
</Labeled>
<Box />
</Stack>
</CardContent>
</Card>
<Card sx={{ width: "100%", m: 1 }}>
<CardHeader title="Platform info" />
<CardContent>
<Stack
sx={{ justifyContent: "flex-start", alignItems: "center" }}
direction="row"
gap={5}
>
<Labeled label="Last changes date">
<DateInput source="updateDate" />
</Labeled>
<Labeled label="User Access Active From">
<DateInput source="userAccessFrom" />
</Labeled>
<Labeled label="User Access Active To">
<DateInput source="userAccessTo" />
</Labeled>
</Stack>
</CardContent>
</Card>
</SimpleForm>
</Edit>
);
};

View File

@@ -0,0 +1,4 @@
export { PersonList } from './list.tsx';
export { PersonShow } from './show.tsx';
export { PersonEdit } from './edit.tsx';
export * from './components';

View File

@@ -0,0 +1,88 @@
import { Fragment, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Paper } from "@mui/material";
import {
BooleanField,
Datagrid,
isEmpty,
ListContextProvider,
ListToolbar,
Pagination,
TextField,
Title,
TopToolbar,
useDataProvider,
WrapperField,
} from "react-admin";
import { ArrayField, FilterName } from "@admin";
export const PersonList = () => {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [firstName, setFirstName] = useState("");
const dataProvider = useDataProvider();
const { data, isPending } = useQuery({
queryKey: ["personList", page, perPage, firstName],
queryFn: () => {
const httpParams = new URLSearchParams({
page: `${page - 1}`,
size: `${perPage}`,
...(isEmpty(firstName)
? {}
: { filters: `firstName::like_ignore_case::${firstName}` }),
});
return dataProvider.personList(httpParams);
},
});
return (
<Fragment>
<Title title="Users" />
<ListToolbar
actions={
<TopToolbar>
<FilterName setFirstName={setFirstName} />
</TopToolbar>
}
/>
<Paper elevation={3}>
<Datagrid
resource="person"
data={data?.content}
total={data?.totalElements}
isPending={isPending}
sort={undefined}
bulkActionButtons={false}
>
<TextField source="firstName" />
<TextField source="lastName" />
<TextField source="email" label="Email User ID" />
<TextField source="id" label="User ID" />
<TextField source="status" />
<BooleanField source="userAccess" label="Access to the Platform" />
<WrapperField label="Company">
<ArrayField source="parties" />
</WrapperField>
<WrapperField label="Programs assigned">
<ArrayField source="assignedPrograms" />
</WrapperField>
<WrapperField label="Functions assigned">
<ArrayField source="assignedFunctions" />
</WrapperField>
</Datagrid>
<ListContextProvider
value={{
page,
perPage,
total: data?.totalElements ?? 0,
setPage,
setPerPage,
}}
>
<Pagination />
</ListContextProvider>
</Paper>
</Fragment>
);
};

View File

@@ -0,0 +1,114 @@
import React from "react";
import {
BooleanField,
DateField,
Labeled,
Show,
SimpleShowLayout,
TextField,
} from "react-admin";
import {
Box,
Card,
CardContent,
CardHeader,
Stack,
Typography,
} from "@mui/material";
import { ArrayField } from "@admin";
export const PersonShow = () => {
return (
<Show>
<SimpleShowLayout>
<Card>
<CardHeader title="General info" />
<CardContent>
<Stack
sx={{
flexWrap: "wrap",
justifyContent: "flex-start",
alignItems: "center",
}}
direction="row"
columnGap={5}
rowGap={2}
>
<Labeled label="Name">
<Stack direction="row" gap={1}>
<TextField sx={{ textTransform: "" }} source="title" />
<TextField source="firstName" />
<TextField source="lastName" />
</Stack>
</Labeled>
<Labeled label="E-mail address">
<TextField source="email" />
</Labeled>
<Labeled label="User ID">
<TextField source="id" />
</Labeled>
<Labeled label="Access to the Platform">
<BooleanField source="userAccess" />
</Labeled>
<Box>
<Typography
sx={{ color: "rgba(255, 255, 255, 0.7)" }}
variant="caption"
>
Company
</Typography>
<ArrayField source="parties" />
</Box>
<Box sx={{ flexBasis: "100%" }} />
<Labeled label="Access to the Platform">
<BooleanField source="userAccess" />
</Labeled>
<Labeled label="Position">
<TextField source="jobPosition" emptyText={"\u2014"} />
</Labeled>
<Labeled label="Phone number">
<TextField source="phoneNumber" emptyText={"\u2014"} />
</Labeled>
<Labeled label="Mobile number">
<TextField source="mobileNumber" emptyText={"\u2014"} />
</Labeled>
<Box />
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader title="Platform info" />
<CardContent>
<Stack
sx={{ justifyContent: "flex-start", alignItems: "center" }}
direction="row"
gap={5}
>
<Labeled label="Last changes date">
<DateField
source="updateDate"
locales="fr-CA"
emptyText={"\u2014"}
/>
</Labeled>
<Labeled label="User Access Active From">
<DateField
source="userAccessFrom"
locales="fr-CA"
emptyText={"\u2014"}
/>
</Labeled>
<Labeled label="User Access Active To">
<DateField
source="userAccessTo"
locales="fr-CA"
emptyText={"\u2014"}
/>
</Labeled>
</Stack>
</CardContent>
</Card>
</SimpleShowLayout>
</Show>
);
};

View File

@@ -0,0 +1,95 @@
import { AuthProvider, HttpError } from "react-admin";
export const authProvider: AuthProvider = {
login: async ({ username, password }) => {
const responseLogin = await login(username, password);
if (responseLogin.status < 200 || responseLogin.status >= 300) {
return Promise.reject(
new HttpError("Unauthorized", 401, {
message: "Invalid username or password",
}),
);
}
const { access_token } = await responseLogin.json();
localStorage.setItem("user", access_token);
localStorage.setItem("token", access_token);
const responseCSRF = await csrf();
console.log(responseCSRF);
return Promise.resolve();
},
logout: () => {
localStorage.removeItem("user");
return Promise.resolve();
},
checkError: () => Promise.resolve(),
checkAuth: () =>
localStorage.getItem("user") ? Promise.resolve() : Promise.reject(),
getPermissions: () => {
return Promise.resolve(undefined);
},
getIdentity: () => {
const persistedUser = localStorage.getItem("user");
const user = persistedUser ? JSON.parse(persistedUser) : null;
return Promise.resolve(user);
},
};
// @ts-ignore
const login = async (username, password) => {
let response;
try {
response = await fetch(
new Request(`${import.meta.env.VITE_AUTH_URL}/atsp-idp/token`, {
method: "POST",
credentials: "include",
body: new URLSearchParams({
grant_type: "authorization_code",
code: "code",
client_id: "client_id",
}),
headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded",
}),
}),
);
} catch (_error) {
return Promise.reject(
new HttpError("Unauthorized", 401, {
message: "Invalid username or password",
}),
);
}
return response;
};
const csrf = async () => {
let response;
try {
response = await fetch(
new Request(`${import.meta.env.VITE_SECURITY_REST_URL}/csrf`, {
method: "GET",
credentials: "include",
headers: new Headers({
Accept: "Accept application/json, text/plain, */*",
Priority: "u=4",
}),
}),
);
} catch (_error) {
return Promise.reject(
new HttpError("Unauthorized", 401, {
message: "Invalid username or password",
}),
);
}
return response;
};

View File

@@ -0,0 +1,43 @@
/**
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2023 @ www.splendidbear.org
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Fragment } from "react";
import {
AppBar,
LoadingIndicator,
TitlePortal,
ToggleThemeButton,
} from "react-admin";
import { blueGrey, grey } from "@mui/material/colors";
export const AdminAppbar = () => (
<AppBar
sx={{
bgcolor: (theme) =>
"dark" === theme.palette.mode ? blueGrey[800] : grey[200],
position: "relative",
minHeight: 50,
borderRadius: 0,
"& > .MuiToolbar-root": {
borderRadius: 0,
},
boxShadow: "none",
color: (theme) =>
"dark" === theme.palette.mode ? "white" : blueGrey[600],
}}
toolbar={
<Fragment>
<ToggleThemeButton />
<LoadingIndicator />
</Fragment>
}
>
<TitlePortal />
</AppBar>
);

View File

@@ -0,0 +1,53 @@
/**
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2024 @ www.splendidbear.org
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { FC, JSX, useMemo } from "react";
import { isObject } from "lodash";
import { Tooltip } from "@mui/material";
import { Menu, useSidebarState, useTranslate } from "react-admin";
interface AdminMenuItemProps {
to: string;
leftIcon: JSX.Element;
primaryText: string;
primaryTextTooltip: string;
}
export const AdminMenuItem: FC<AdminMenuItemProps> = ({
to,
leftIcon,
primaryText,
primaryTextTooltip = "n/a",
...rest
}) => {
const t = useTranslate();
const [open] = useSidebarState();
const isTextObject = useMemo(() => isObject(primaryText), [primaryText]);
if (!open) {
return (
<Tooltip
title={t(isTextObject ? primaryTextTooltip : primaryText)}
placement="right"
arrow
>
<Menu.Item to={to} leftIcon={leftIcon} {...rest} />
</Tooltip>
);
}
return (
<Menu.Item
to={to}
leftIcon={leftIcon}
primaryText={isTextObject ? primaryText : t(primaryText)}
{...rest}
/>
);
};

View File

@@ -0,0 +1,56 @@
/**
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2025 @ www.splendidbear.org
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { FC, Fragment, JSX, useState } from "react";
import { motion } from "framer-motion";
import { Collapse, Divider, Stack } from "@mui/material";
import { ChevronLeft } from "@mui/icons-material";
import { grey } from "@mui/material/colors";
import { useSidebarState, useTranslate } from "react-admin";
interface AdminMenuLabelProps {
label: string;
children: JSX.Element[];
id: string;
isDefaultOpen: boolean;
}
export const AdminMenuLabel: FC<AdminMenuLabelProps> = ({
label,
children,
isDefaultOpen = false,
}) => {
const t = useTranslate();
const [open] = useSidebarState();
const [isMenuOpened, setIsMenuOpened] = useState(isDefaultOpen);
return (
<Fragment>
<Stack
sx={{
justifyContent: "space-between",
alignItems: "center",
m: "5px 12px",
cursor: "pointer",
}}
direction="row"
onClick={() => setIsMenuOpened(!isMenuOpened)}
>
<Divider textAlign="left">{open && t(label)}</Divider>
<motion.div
animate={{ rotate: isMenuOpened ? -90 : 0 }}
transition={{ duration: 0.25 }}
>
<ChevronLeft sx={{ color: grey[400] }} />
</motion.div>
</Stack>
<Collapse in={isMenuOpened}>{children}</Collapse>
</Fragment>
);
};

View File

@@ -0,0 +1,45 @@
import type { ReactNode } from "react";
import PropTypes from "prop-types";
import { Box } from "@mui/material";
import { useLocale, useSidebarState } from "react-admin";
import { blueGrey } from "@mui/material/colors";
export const AdminSidebar = ({ children }: { children: ReactNode }) => {
const [open] = useSidebarState();
/** force redraw on locale change */
useLocale();
return (
<Box
sx={{
"& > .MuiPaper-root": {
bgcolor: blueGrey[900],
width: open ? "250px" : "50px",
},
"& .MuiMenuItem-root": {
padding: open ? "6px 16px" : 0,
...(open
? {}
: {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
"&.RaMenuItemLink-active": {
borderRadius: 0,
},
}),
},
"& .MuiChip-label": {
color: "white",
},
}}
>
{children}
</Box>
);
};
AdminSidebar.propTypes = {
children: PropTypes.node.isRequired,
};

View File

@@ -0,0 +1,4 @@
export { AdminSidebar } from './admin-sidebar';
export { AdminAppbar } from './admin-appbar';
export { AdminMenuItem } from './admin-menu-item';
export { AdminMenuLabel } from './admin-menu-label';

View File

@@ -1,19 +1,10 @@
import { useQueryEngine } from "@core"; import { useQueryEngine } from './hooks';
export const dataProviderExtension = () => { export const dataProviderExtension = () => {
const { fetchCommon } = useQueryEngine(); const { fetchCommon } = useQueryEngine();
const url = import.meta.env.VITE_GATEWAY_REST_URL;
return { return {
person: () => personList: (params: URLSearchParams) => fetchCommon(`${url}/person?${params}`),
fetchCommon(
"http://localhost:8081/core/api/admin/person",
{
headers: new Headers({
Accept: "application/json",
'X-XSRF-TOKEN': 'c3aKjTfLsPSdmiMtTaj933RbsQMR6IWmf9C5ZImMh9pCmDGkEETougKpiZew-UcZe4XJ5xE4nDpwirWLR-SOXbm94-Im-QLB',
'Cookie': 'LAST_LOGIN_LOCATION=http://localhost:3000; SESSION=652f7c23-fe94-4727-a980-59052e75aab2; XSRF-TOKEN=c2b75b9c-cd46-48ec-9ab0-847901d8da3e',
}),
},
),
}; };
}; };

View File

@@ -0,0 +1,31 @@
import { fetchUtils, withLifecycleCallbacks } from 'react-admin';
import simpleRestProvider from "ra-data-simple-rest";
import { dataProviderExtension } from "@core";
const fetchJson = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: "application/json" });
}
options.credentials = "include";
options.headers.set(
"Authorization",
`Bearer ${localStorage.getItem("user")}`,
);
options.headers.set("X-XSRF-TOKEN", localStorage.getItem("user"));
return fetchUtils.fetchJson(url, options);
};
export const dataProvider = withLifecycleCallbacks(
{
...simpleRestProvider(import.meta.env.VITE_GATEWAY_REST_URL, fetchJson),
...dataProviderExtension(),
},
[
{
resource: "person",
afterRead: async (data: object) => {
return data.original;
},
},
],
);

View File

@@ -1,16 +1,20 @@
export const useQueryEngine = () => { export const useQueryEngine = () => {
const defaultHeaders = { const defaultHeaders = {
Accept: 'application/ld+json', Accept: "application/json",
'Content-Type': 'application/ld+json', "Content-Type": "application/json",
Origin: "http://localhost:3000",
Referer: "http://localhost:3000",
"Access-Control-Request-Headers":
"Origin, X-Requested-With, Content-Type, Accept, X-XSRF-TOKEN, Authorization, Cookie",
}; };
const response = async (request) => { const response = async (request: Request) => {
let response; let response;
try { try {
response = await fetch(request); response = await fetch(request);
} catch (e) { } catch (e) {
throw new Error('Network error'); throw new Error("Network error");
} }
if (200 > response?.status || 300 <= response?.status) { if (200 > response?.status || 300 <= response?.status) {
@@ -20,58 +24,80 @@ export const useQueryEngine = () => {
return response.json(); return response.json();
}; };
return ({ const decodeJwt = (token: string) => {
try {
return JSON.parse(window.atob(token.split(".")[1]));
} catch (e) {
return null;
}
};
return {
defaultHeaders, defaultHeaders,
fetchCommon: async (url, options = {}) => { fetchCommon: async (url: string, options: object = {}) => {
const token = decodeJwt(localStorage.getItem("user"));
document.cookie = `XSRF-TOKEN=${token.sub}; SESSION=${token.jti}`;
return await response( return await response(
new Request(url, { new Request(url, {
...(options?.method ? { method: options.method } : { method: 'GET' }), mode: "cors",
credentials: "include",
...(options?.method ? { method: options.method } : { method: "GET" }),
...(options.replaceHeaders ...(options.replaceHeaders
? { headers: options.replaceHeaders } ? { headers: new Headers(options.replaceHeaders) }
: { : {
headers: { headers: new Headers({
...defaultHeaders, ...defaultHeaders,
Authorization: `Bearer ${localStorage.getItem('user')}`, Authorization: `Bearer ${localStorage.getItem("user")}`,
"X-XSRF-TOKEN": localStorage.getItem("user"),
"Access-Control-Request-Method": options?.method
? options.method
: "GET",
...(options?.headers ? { ...options.headers } : {}), ...(options?.headers ? { ...options.headers } : {}),
}, }),
}), }),
...(options?.body ? { body: options.body } : {}), ...(options?.body ? { body: options.body } : {}),
}), }),
); );
}, },
fetchMultipart: async (url, options) => { fetchMultipart: async (url: string, options: object = {}) => {
return await response( return await response(
new Request(url, { new Request(url, {
method: 'POST', method: "POST",
headers: { headers: {
Accept: 'application/ld+json', Accept: "application/json",
/** /**
* DO NOT SPECIFY THIS - Because the boundary data in it * DO NOT SPECIFY THIS - Because the boundary data in it
* @see https://stackoverflow.com/a/71392989/3111514 * @see https://stackoverflow.com/a/71392989/3111514
*/ */
// 'Content-Type': 'multipart/form-data', // 'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${localStorage.getItem('user')}`, Authorization: `Bearer ${localStorage.getItem("user")}`,
}, },
...options, ...options,
}), }),
); );
}, },
fetchPlain: async (url, options = {}, withAuth = false) => { fetchPlain: async (url: string, options: object = {}, withAuth = false) => {
return await response( return await response(
new Request(url, { new Request(url, {
...(options?.method ? { method: options.method } : { method: 'GET' }), ...(options?.method ? { method: options.method } : { method: "GET" }),
...(options.replaceHeaders ...(options.replaceHeaders
? { headers: options.replaceHeaders } ? { headers: options.replaceHeaders }
: { : {
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
...(options?.headers ? { headers: options.headers } : {}), ...(options?.headers ? { headers: options.headers } : {}),
...(withAuth ? { Authorization: `Bearer ${localStorage.getItem('user')}` } : {}), ...(withAuth
? {
Authorization: `Bearer ${localStorage.getItem("user")}`,
}
: {}),
}, },
}), }),
...(options?.body ? { body: options.body } : {}), ...(options?.body ? { body: options.body } : {}),
}), }),
); );
}, },
}); };
}; };

View File

@@ -0,0 +1,14 @@
import polyglotI18nProvider from "ra-i18n-polyglot";
import fr from "ra-language-french";
import { enMessages } from './messages';
const translations = { en: enMessages, fr };
export const i18nProvider = polyglotI18nProvider(
(locale: string) => translations[locale],
"en", // default locale
[
{ locale: "en", name: "English" },
{ locale: "fr", name: "Français" },
],
);

View File

@@ -1,2 +1,8 @@
export { dataProviderExtension } from './data-provider-extension'; export { dataProviderExtension } from './data-provider-extension';
export { dataProvider } from './dataProvider';
export { authProvider } from './authProvider';
export { lightTheme, darkTheme } from './theme';
export { i18nProvider } from './i18nProvider';
export * from './hooks'; export * from './hooks';
export * from './components';
export * from './messages';

View File

@@ -0,0 +1,13 @@
import en from "ra-language-english";
import { deepmerge } from "@mui/utils";
export const enMessages = deepmerge(en, {
ra: {
menu: {
main: {
userManagement: "User management",
},
people: "Users",
},
},
});

View File

@@ -0,0 +1 @@
export { enMessages } from './en-messages';

16
src/admin-core/theme.ts Normal file
View File

@@ -0,0 +1,16 @@
import { defaultTheme, RaThemeOptions } from "react-admin";
import { deepmerge } from "@mui/utils";
export const lightTheme: RaThemeOptions = deepmerge(defaultTheme, {
components: {
'& .Apex-Sidebar': {
}
},
});
export const darkTheme: RaThemeOptions = deepmerge(lightTheme, {
palette: {
mode: "dark",
},
});

View File

@@ -1,63 +0,0 @@
import { AuthProvider, HttpError } from "react-admin";
export const authProvider: AuthProvider = {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
login: async ({ username, password }) => {
let response;
try {
response = await fetch(
new Request("http://localhost:8080/atsp-idp/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "authorization_code",
code: "code",
client_id: "client_id",
}),
headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded",
}),
}),
);
} catch (_error) {
return Promise.reject(
new HttpError("Unauthorized", 401, {
message: "Invalid username or password",
}),
);
}
if (response.status < 200 || response.status >= 300) {
return Promise.reject(
new HttpError("Unauthorized", 401, {
message: "Invalid username or password",
}),
);
}
const { refresh_token } = await response.json();
localStorage.setItem("token", refresh_token);
localStorage.setItem("user", refresh_token);
return Promise.resolve();
},
logout: () => {
localStorage.removeItem("user");
return Promise.resolve();
},
checkError: () => Promise.resolve(),
checkAuth: () =>
localStorage.getItem("user") ? Promise.resolve() : Promise.reject(),
getPermissions: () => {
return Promise.resolve(undefined);
},
getIdentity: () => {
const persistedUser = localStorage.getItem("user");
const user = persistedUser ? JSON.parse(persistedUser) : null;
return Promise.resolve(user);
},
};
export default authProvider;

View File

@@ -1,7 +0,0 @@
import simpleRestProvider from "ra-data-simple-rest";
import { dataProviderExtension } from "@core";
export const dataProvider = {
...simpleRestProvider(import.meta.env.VITE_SIMPLE_REST_URL),
...dataProviderExtension(),
};

View File

@@ -7,5 +7,15 @@
{ {
"path": "./tsconfig.node.json" "path": "./tsconfig.node.json"
} }
] ],
"compilerOptions": {
"paths": {
"@admin*": [
"./src/admin-components/*"
],
"@core*": [
"./src/admin-core/*"
],
}
}
} }

View File

@@ -7,6 +7,8 @@ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
host: true, host: true,
port: 3000,
cors: false,
}, },
base: "./", base: "./",
resolve: { resolve: {