diff --git a/.eslintrc.js b/.eslintrc.js index 5f23efcd4..22b3f197d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,23 +1,50 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - project: 'tsconfig.json', + project: 'tsconfig.eslint.json', sourceType: 'module', tsconfigRootDir: __dirname, }, - extends: ['plugin:@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:prettier/recommended'], - plugins: ['prettier', '@typescript-eslint', "react"], + plugins: ['prettier', '@typescript-eslint', 'react', 'boundaries', 'react-refresh'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'plugin:prettier/recommended', + 'plugin:boundaries/strict', + ], env: { browser: true, es6: true, }, settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + }, + }, react: { version: 'detect', }, + 'boundaries/include': ['src/*/**'], + 'boundaries/elements': [ + { type: 'consts', pattern: 'src/shared/consts/**' }, + { type: 'lib', pattern: 'src/shared/lib/**' }, + { type: 'api', pattern: 'src/shared/api/**' }, + { type: 'session', pattern: 'src/shared/session/**' }, + { type: 'routing', pattern: 'src/shared/routing/**' }, + { type: 'ui', pattern: 'src/shared/ui/**' }, + { type: 'tutorial', pattern: 'src/shared/tutorial/**' }, + { type: 'shared', pattern: 'src/shared/**' }, + { type: 'entities', pattern: 'src/entities/**' }, + { type: 'features', pattern: 'src/features/**' }, + { type: 'widgets', pattern: 'src/widgets/**' }, + { type: 'pages', pattern: 'src/pages/**' }, + { type: 'app', pattern: 'src/app/**' }, + ], }, rules: { - "react/react-in-jsx-scope": "off", + 'react-refresh/only-export-components': 'error', + 'react/react-in-jsx-scope': 'off', 'linebreak-style': 'off', 'prettier/prettier': [ 'off', @@ -29,12 +56,84 @@ module.exports = { }, ], 'no-console': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-empty-function': 'off', 'no-eval': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-explicit-any': 'off', 'react/jsx-key': 1, - "react/react-in-jsx-scope": 2, + 'react/react-in-jsx-scope': 2, + 'boundaries/element-types': [ + 'error', + { + default: 'disallow', + rules: [ + { + from: 'app', + allow: [ + 'pages', + 'widgets', + 'features', + 'entities', + 'shared', + 'ui', + 'tutorial', + 'routing', + 'session', + 'api', + 'lib', + 'consts', + ], + }, + { + from: 'pages', + allow: [ + 'widgets', + 'features', + 'entities', + 'shared', + 'ui', + 'tutorial', + 'routing', + 'session', + 'api', + 'lib', + 'consts', + ], + }, + { + from: 'widgets', + allow: [ + 'features', + 'entities', + 'shared', + 'ui', + 'tutorial', + 'routing', + 'session', + 'api', + 'lib', + 'consts', + ], + }, + { + from: 'features', + allow: ['entities', 'shared', 'ui', 'tutorial', 'routing', 'session', 'api', 'lib', 'consts'], + }, + { + from: 'entities', + allow: ['shared', 'ui', 'tutorial', 'routing', 'session', 'api', 'lib', 'consts'], + }, + { from: 'shared', allow: ['ui', 'tutorial', 'routing', 'session', 'api', 'lib', 'consts'] }, + { from: 'tutorial', allow: ['ui', 'routing', 'session', 'api', 'lib', 'consts'] }, + { from: 'ui', allow: ['routing', 'session', 'api', 'lib', 'consts'] }, + { from: 'routing', allow: ['session', 'api', 'lib', 'consts'] }, + { from: 'session', allow: ['api', 'lib', 'consts'] }, + { from: 'api', allow: ['lib', 'consts'] }, + { from: 'lib', allow: ['consts'] }, + ], + }, + ], }, -} \ No newline at end of file +} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 98f768666..57010ebec 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -97,7 +97,6 @@ jobs: cd client-app/LK echo ${{ secrets.TEST_SERVER_PASSWORD }} | sudo -S docker compose -p web pull echo ${{ secrets.TEST_SERVER_PASSWORD }} | sudo -S docker compose -p web up -d - - name: Kill VPN connection if: always() run: | diff --git a/.prettierignore b/.prettierignore index bf6a52f61..d61574f5f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ -*.js +# *.js locale docker-compose.yml \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js index 5a2633bf9..ce34b0883 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,27 +1,27 @@ module.exports = { - singleQuote: true, - trailingComma: 'all', - printWidth: 120, - tabWidth: 4, - semi: false, - plugins: ['@trivago/prettier-plugin-sort-imports'], - importOrderGroupNamespaceSpecifiers: true, - importOrderParserPlugins: ['typescript', 'jsx'], - importOrderSortSpecifiers: true, - importOrderSeparation: true, - importOrder: [ - '^react', - '', - '^@(.*).css$', - '^@app(.*)$', - '^@pages(.*)$', - '^@layouts(.*)$', - '^@widgets(.*)$', - '^@features(.*)$', - '^@entities(.*)$', - '^@shared(.*)$', - '^@ui(.*)$', - '^@utils(.*)$', - '^[./]', - ], + singleQuote: true, + trailingComma: 'all', + printWidth: 120, + tabWidth: 4, + semi: false, + plugins: ['@trivago/prettier-plugin-sort-imports'], + importOrderGroupNamespaceSpecifiers: true, + importOrderParserPlugins: ['typescript', 'jsx'], + importOrderSortSpecifiers: true, + importOrderSeparation: true, + importOrder: [ + '^react', + '', + '^@(.*).css$', + '^@app(.*)$', + '^@pages(.*)$', + '^@layouts(.*)$', + '^@widgets(.*)$', + '^@features(.*)$', + '^@entities(.*)$', + '^@shared(.*)$', + '^@ui(.*)$', + '^@utils(.*)$', + '^[./]', + ], } diff --git a/package-lock.json b/package-lock.json index f52011e2b..86d22978c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,15 +54,18 @@ "@vitejs/plugin-react": "^4.3.0", "core-js": "^3.39.0", "dependency-cruiser": "^16.2.4", - "eslint": "^8.40.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^10.0.1", + "eslint-import-resolver-typescript": "^3.7.0", + "eslint-plugin-boundaries": "^5.0.1", "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.18", "knip": "^3.0.2", "prettier": "^3.4.2", "tiny-invariant": "^1.3.1", - "typescript": "^5.0.4", + "typescript": "^5.7.3", "vite": "^4.3.6", "vite-plugin-checker": "^0.6.0", "vite-plugin-svgr": "^3.2.0", @@ -2233,23 +2236,25 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2265,10 +2270,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -2284,6 +2290,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -2292,10 +2299,11 @@ } }, "node_modules/@eslint/js": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", - "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2309,13 +2317,15 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2336,10 +2346,12 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2488,6 +2500,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/map-workspaces": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.4.tgz", @@ -3565,10 +3587,11 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "20.11.17", @@ -3655,10 +3678,11 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/styled-components": { "version": "5.1.20", @@ -3676,6 +3700,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.59.6", @@ -3710,6 +3735,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/scope-manager": "5.59.6", "@typescript-eslint/types": "5.59.6", @@ -3737,6 +3763,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "5.59.6", "@typescript-eslint/visitor-keys": "5.59.6" @@ -3754,6 +3781,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "5.59.6", "@typescript-eslint/utils": "5.59.6", @@ -3781,6 +3809,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3794,6 +3823,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/types": "5.59.6", "@typescript-eslint/visitor-keys": "5.59.6", @@ -3821,6 +3851,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -3847,6 +3878,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/types": "5.59.6", "eslint-visitor-keys": "^3.3.0" @@ -3859,6 +3891,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitejs/plugin-legacy": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-4.0.3.tgz", @@ -3995,6 +4034,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4030,6 +4070,22 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -4112,6 +4168,7 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4290,12 +4347,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4411,6 +4469,46 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4483,6 +4581,26 @@ "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4647,11 +4765,12 @@ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4916,6 +5035,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -5254,27 +5374,30 @@ } }, "node_modules/eslint": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", - "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5284,7 +5407,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -5294,9 +5416,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -5322,6 +5443,125 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.7", + "enhanced-resolve": "^5.15.0", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-boundaries": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-boundaries/-/eslint-plugin-boundaries-5.0.1.tgz", + "integrity": "sha512-QBw1TgoA3JnD+AQ8EHqZL8LtiFVREnFmD2BnHzVl38UKtRTds8rawIg03ZLwFs/90yKSh+DVVXRpvVK631Z/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.0", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-boundaries/node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", @@ -5394,6 +5634,16 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz", + "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -5437,6 +5687,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -5450,15 +5701,17 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5466,55 +5719,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5528,10 +5732,11 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5558,27 +5763,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -5592,12 +5776,13 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -5699,7 +5884,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -5764,10 +5950,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5917,10 +6104,14 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -6000,6 +6191,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6075,6 +6279,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6118,7 +6323,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/graphemer": { "version": "1.4.0", @@ -6212,6 +6418,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", @@ -6449,6 +6668,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -6462,12 +6691,16 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6572,6 +6805,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -6802,7 +7036,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7269,9 +7504,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.6", @@ -7300,7 +7536,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ndjson": { "version": "2.0.0", @@ -7504,17 +7741,18 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -8513,18 +8751,22 @@ } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8543,6 +8785,16 @@ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -8696,13 +8948,11 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -8722,24 +8972,6 @@ "node": "^14||^16||>=18" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -8809,6 +9041,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8863,6 +9096,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -9046,6 +9286,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -9234,6 +9475,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -9298,76 +9540,6 @@ "node": ">=10.13.0" } }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -9381,13 +9553,15 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^1.8.1" }, @@ -9437,10 +9611,11 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9757,55 +9932,6 @@ } } }, - "node_modules/vite-plugin-checker/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/vite-plugin-checker/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/vite-plugin-checker/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/vite-plugin-checker/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/vite-plugin-checker/node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -9815,27 +9941,6 @@ "node": ">= 12" } }, - "node_modules/vite-plugin-checker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/vite-plugin-checker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/vite-plugin-svgr": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-3.2.0.tgz", @@ -10018,10 +10123,11 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10061,39 +10167,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", diff --git a/package.json b/package.json index 74e4928bb..e3044c4ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lk", - "version": "3.16.5", + "version": "4.0.0", "description": "", "main": "index.js", "keywords": [], @@ -14,7 +14,8 @@ "reinstall": "rm -rf node_modules && rm -rf package-lock.json && npm cache clean -f && npm i", "reinstall-win": "npm rm -rf node_modules && npm rm -rf package-lock.json && npm cache clean -f && npm i", "knip": "knip", - "start:external": "npm run start -- --host" + "start:external": "npm run start -- --host", + "lint": "eslint src --ext .js,.jsx,.ts,.tsx" }, "dependencies": { "@farfetched/core": "^0.12.4", @@ -62,15 +63,18 @@ "@vitejs/plugin-react": "^4.3.0", "core-js": "^3.39.0", "dependency-cruiser": "^16.2.4", - "eslint": "^8.40.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^10.0.1", + "eslint-import-resolver-typescript": "^3.7.0", + "eslint-plugin-boundaries": "^5.0.1", "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.18", "knip": "^3.0.2", "prettier": "^3.4.2", "tiny-invariant": "^1.3.1", - "typescript": "^5.0.4", + "typescript": "^5.7.3", "vite": "^4.3.6", "vite-plugin-checker": "^0.6.0", "vite-plugin-svgr": "^3.2.0", diff --git a/src/app/index.tsx b/src/app/index.tsx index 715ae8232..eb8f9f4c7 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -2,12 +2,11 @@ import React, { useEffect } from 'react' import { HashRouter } from 'react-router-dom' import 'react-virtualized/styles.css' -import { ModalProvider } from 'widgets/modal/lib' - -import { appStarted } from '@shared/models/app-started' +import { appStarted } from '@shared/consts/models/app-started' import ErrorBoundary from '@shared/ui/error-boundary' +import { ModalProvider } from '@shared/ui/modal' -import Router from './routers/router' +import Router from './routing/router' const App = () => { useEffect(() => { diff --git a/src/app/routing/content-layout/hooks/use-content-layout.tsx b/src/app/routing/content-layout/hooks/use-content-layout.tsx new file mode 100644 index 000000000..8b5616f08 --- /dev/null +++ b/src/app/routing/content-layout/hooks/use-content-layout.tsx @@ -0,0 +1,30 @@ +import React, { useEffect } from 'react' + +import useIsShowWhatsNew from '@app/routing/content-layout/hooks/use-is-whats-new' + +import WhatsNew from '@widgets/whats-new' + +import useLkNotifications from '@entities/lk-notifications/hooks/use-lk-notifications' + +import useCurrentExactPage from '@shared/routing/hooks/use-current-exact-page' +import { useModal } from '@shared/ui/modal' + +const useContentLayout = () => { + const currentPage = useCurrentExactPage() + + const { open } = useModal() + const isShowWhatsNew = useIsShowWhatsNew() + // const { seen } = useShowTutorial() + + useLkNotifications() + + useEffect(() => { + if (isShowWhatsNew) { + isShowWhatsNew && open(, 'Что нового') + } + }, [isShowWhatsNew]) + + return { currentPage } +} + +export default useContentLayout diff --git a/src/app/routing/content-layout/hooks/use-is-whats-new.ts b/src/app/routing/content-layout/hooks/use-is-whats-new.ts new file mode 100644 index 000000000..8f3eb872e --- /dev/null +++ b/src/app/routing/content-layout/hooks/use-is-whats-new.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react' + +import { useUnit } from 'effector-react' + +import { LastUpdateWhatsNew } from '@shared/consts' +import { userModel } from '@shared/session' +import { tutorialModel } from '@shared/tutorial' + +const useIsShowWhatsNew = () => { + const { + data: { user }, + } = userModel.selectors.useUser() + const [heroVisited, tutorialState] = useUnit([tutorialModel.stores.heroVisited, tutorialModel.stores.tutorialState]) + const [isShowNotification, setIsShowNotification] = useState(false) + + const checkingShowNotification = (lastAccess: string) => { + const lastLocalAccess = localStorage.getItem('lastLocalAccess') || lastAccess + const UpdateWhatsNewDate = new Date(LastUpdateWhatsNew) + if ( + new Date(lastAccess) < UpdateWhatsNewDate && + new Date(lastLocalAccess) < UpdateWhatsNewDate && + (heroVisited || tutorialState) + ) { + localStorage.setItem('lastLocalAccess', new Date().toISOString()) + setIsShowNotification(true) + } + } + + useEffect(() => { + if (!!user) { + checkingShowNotification(user.lastaccess) + } + }, [user]) + + return isShowNotification +} + +export default useIsShowWhatsNew diff --git a/src/app/routing/content-layout/index.tsx b/src/app/routing/content-layout/index.tsx new file mode 100644 index 000000000..bde144dc6 --- /dev/null +++ b/src/app/routing/content-layout/index.tsx @@ -0,0 +1,91 @@ +import React, { Suspense, useRef, useState } from 'react' + +import { useScrollToTop } from '@app/routing/hooks/use-scroll-to-top' +import PrivateRouter from '@app/routing/private-router' + +import ConfirmMessage from '@widgets/confirm' +import Header from '@widgets/header' +import HintModal from '@widgets/hint-modal' +import { LeftSideBarTutorial } from '@widgets/navbar/leftside-bar' +import MobileBottomMenu from '@widgets/navbar/mobile-bottom-menu' +import PopUpMessage from '@widgets/pop-up-message' +import PopUpNotifications from '@widgets/pop-up-notifications' + +import ContextMenu from '@features/context-menu' + +import { menuModel } from '@entities/menu' + +import { userModel } from '@shared/session' +import { TutorialHero } from '@shared/tutorial/ui/tutorial-hero' +import ErrorBoundary from '@shared/ui/error-boundary' +import { Modal } from '@shared/ui/modal' + +import InitialLoader from '../../../shared/ui/initial-loader' +import Story from '../../../widgets/story' +import useContentLayout from './hooks/use-content-layout' +import { ContentWrapper, PageContent } from './styled' + +const ContentLayout = () => { + const { + data: { user }, + } = userModel.selectors.useUser() + const pageContentRef = useRef(null) + const { allRoutes } = menuModel.selectors.useMenu() + const { currentPage } = useContentLayout() + const [headerVisible, setHeaderVisible] = useState(false) + + const handleContentScroll = (e: React.UIEvent) => { + setHeaderVisible(e.currentTarget.scrollTop > 0) + } + + // const [isScrolled, setIsScrolled] = useState(false) + + // useEffect(() => { + // const handleScroll = () => { + // setIsScrolled(window.scrollY > 0) + // } + + // window.addEventListener('scroll', handleScroll) + + // return () => { + // window.removeEventListener('scroll', handleScroll) + // } + // }, []) + + useScrollToTop(pageContentRef.current!) + + return ( + <> + + + + + +
+ + + + + + + + + + + + + + + + + + ) +} + +export default React.memo(ContentLayout) diff --git a/src/app/routing/content-layout/styled.ts b/src/app/routing/content-layout/styled.ts new file mode 100644 index 000000000..cf51fe679 --- /dev/null +++ b/src/app/routing/content-layout/styled.ts @@ -0,0 +1,30 @@ +import styled from 'styled-components' + +export const ContentWrapper = styled.div` + display: flex; + width: 100%; + max-height: 100%; + flex: 1; + z-index: 3; + background: var(--theme); + overflow: hidden; + position: relative; + + @media (max-width: 1000px) { + font-size: 0.9em; + } +` + +export const PageContent = styled.div<{ withHeader?: boolean }>` + position: relative; + overflow-x: hidden; + overflow-y: scroll; + scrollbar-gutter: stable; + padding-top: ${({ withHeader }) => (withHeader ? 'var(--header-height)' : '0')}; + width: 100%; + flex: 1; + + @media (max-width: 1000px) { + margin-bottom: var(--mobile-bottom-menu-height); + } +` diff --git a/src/app/routing/hooks/use-redirect.ts b/src/app/routing/hooks/use-redirect.ts new file mode 100644 index 000000000..2dd9b2ffc --- /dev/null +++ b/src/app/routing/hooks/use-redirect.ts @@ -0,0 +1,9 @@ +import { useEffect } from 'react' + +export const useRedirect = () => { + useEffect(() => { + if (window.location.href === 'https://e.mospolytech.ru/?p=children#/home') { + window.location.replace('https://e.mospolytech.ru/old/index.php?p=children') + } + }, [window.location.href]) +} diff --git a/src/app/routing/hooks/use-scroll-to-top.tsx b/src/app/routing/hooks/use-scroll-to-top.tsx new file mode 100644 index 000000000..23d061903 --- /dev/null +++ b/src/app/routing/hooks/use-scroll-to-top.tsx @@ -0,0 +1,10 @@ +import { useEffect } from 'react' +import { useLocation } from 'react-router' + +export function useScrollToTop(element: { scrollTo: (x: number, y: number) => void } | null) { + const { pathname } = useLocation() + + useEffect(() => { + element?.scrollTo(0, 0) + }, [pathname]) +} diff --git a/src/app/routing/private-router.tsx b/src/app/routing/private-router.tsx new file mode 100644 index 000000000..cd4fedd72 --- /dev/null +++ b/src/app/routing/private-router.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Redirect, Route, Switch } from 'react-router' + +import { useUnit } from 'effector-react' + +import { menuModel } from '@entities/menu' + +import { HOME_ROUTE } from '@shared/routing' + +import { $allPages } from './routes/pages' + +const PrivateRouter = () => { + const allPages = useUnit($allPages) + const { allRoutes: availableRoutes } = menuModel.selectors.useMenu() + if (!availableRoutes || !allPages) return null + + return ( + + {Object.values(availableRoutes).map(({ path, isTemplate, id }) => { + return + })} + + + ) +} + +export default React.memo(PrivateRouter) diff --git a/src/app/routing/router.tsx b/src/app/routing/router.tsx new file mode 100644 index 000000000..186569be4 --- /dev/null +++ b/src/app/routing/router.tsx @@ -0,0 +1,38 @@ +import React, { Suspense } from 'react' +import { Redirect, Route, Switch } from 'react-router-dom' + +import { useUnit } from 'effector-react' + +import ContentLayout from '@app/routing/content-layout' +import { useScrollToTop } from '@app/routing/hooks/use-scroll-to-top' + +import { LOGIN_ROUTE } from '@shared/routing' +import { publicRoutes } from '@shared/routing/routes/public' +import { userModel } from '@shared/session' +import { useSetTutorial } from '@shared/tutorial/lib/use-set-tutorial' + +import { useRedirect } from './hooks/use-redirect' +import { publicPages } from './routes/public' + +const Router = () => { + useScrollToTop(window) // scroll window to top when change route + useRedirect() // redirect on broken links + useSetTutorial() + + const isAuthenticated = useUnit(userModel.stores.isAuthenticated) + + return isAuthenticated ? ( + + ) : ( + + + {publicRoutes.map(({ path, id }, i) => { + return + })} + + + + ) +} + +export default Router diff --git a/src/app/routing/routes/employee.tsx b/src/app/routing/routes/employee.tsx new file mode 100644 index 000000000..665c67462 --- /dev/null +++ b/src/app/routing/routes/employee.tsx @@ -0,0 +1,257 @@ +import React from 'react' +import { Redirect } from 'react-router-dom' + +import AllStaff from '@pages/all-staff' +import AllTeachersPage from '@pages/all-teachers' +import DownloadAdminFilesPage from '@pages/download-admin-files' +import PageIsNotReady from '@pages/page-is-not-ready' +import PaymentsPage from '@pages/payments' +import { OLD_LK_URL, isProduction } from '@shared/consts' +import { + CENTERS_ROUTE, + DOCUMENT_BLANKS_ROUTE, + KPI_ADMIN_ROUTE, + KPI_PPS_ROUTE, + ONBOARDING, + OOP_ROUTE, + ORDERS_ROUTE, + PPS_VOTE_ROUTE, + SC_NEWS_ROUTE, +} from '@shared/routing' + +import { + AllowanceInfo, + Allowances, + Article, + ArticleList, + CertificateFromPlaceOfWorkPage, + CertificateOfWorkExperiencePage, + CertificateTimeParentalLeavePage, + CertificationAndIssuanceOfDocs, + CertifiedCopiesOfMilitaryDocs, + Children, + ConnectingComputerPage, + ContactDetails, + ContactDetailsForm, + ContactInfoActualizationPage, + CopiesOfDocumentsFromPersonalFilePage, + CopyOfEmploymentRecordPage, + CourierPage, + CreateAllowance, + DataVerificationPage, + DefermentFromConscription, + EditPhonebookEmail, + EditPhonebookInnerPhone, + EditPhonebookSubdivision, + EmployeeArbitraryRequestPage, + GettingComputerEquipmentPage, + GuestPass, + GuestsAccommodationOnCampus, + GuestsPassageToCampus, + HelpfulInformation, + IncreaseAntiplagiatLimits, + IssuanceOfLicensesPage, + IssuanceOfPass, + LivingInResidentialPremises, + NumberOfUnusedVacationDaysPage, + Onboarding, + OtherItServicesPage, + PaymentForChildCarePage, + PaymentOfChildBirthAllowancePage, + PersonaIncomeTaxReferencePage, + PersonalNotificationsPage, + PhysicalEducationStudent, + PrinterMaintenancePage, + QuestionPersonalAccountPage, + QuestionSedPage, + Relocation, + Science, + TeacherPhysicalEducation, + TeachersApplicationsPage, + TerminationOfAgreement, + VacationSchedule, + VisaCertificatePage, + WorkOnTermsOfExternalConcurrencyPage, +} from './pages' +import { ApplicationRedirect } from './pages/redirect' +import { privateHiddenPages, privatePages } from './private' + +export const employeePages = { + onboarding: Onboarding, + ...privatePages, + + // свою основную задачу форма выполнила, а дальше ее скрываем. на случай, если надо будет проводить очередную сверку + 'contact-details': isProduction + ? () => PageIsNotReady({ errorText: 'Страница еще находится в разработке.', isRedirectButtonVisible: false }) + : ContactDetails, + 'contact-details-form': isProduction + ? () => PageIsNotReady({ errorText: 'Страница еще находится в разработке.', isRedirectButtonVisible: false }) + : ContactDetailsForm, + 'download-agreements': DownloadAdminFilesPage, + children: Children, + 'electronic-statements': () => { + React.useEffect(() => { + window.location.replace(`${OLD_LK_URL}/stats.php?m=items&act=st_list`) + }, []) + + return null + }, + 'project-activity': () => { + React.useEffect(() => { + window.location.replace(`${OLD_LK_URL}/?p=proj_main`) + }, []) + + return null + }, + payments: PaymentsPage, + 'pps-contest': () => { + React.useEffect(() => { + window.location.replace('https://mospolytech.ru/contest-pps/') + }, []) + + return null + }, + 'kpi-pps': () => { + React.useEffect(() => { + window.location.replace(`${OLD_LK_URL}/?p=${KPI_PPS_ROUTE?.slice(1, KPI_PPS_ROUTE.length)}`) + }, []) + + return null + }, + 'kpi-admin': () => { + React.useEffect(() => { + window.location.replace(`${OLD_LK_URL}/?p=${KPI_ADMIN_ROUTE?.slice(1, KPI_ADMIN_ROUTE.length)}`) + }, []) + + return null + }, + 'sc-news': () => PageIsNotReady({ oldVersionUrl: SC_NEWS_ROUTE }), + orders: () => PageIsNotReady({ oldVersionUrl: ORDERS_ROUTE }), + 'document-blanks': () => PageIsNotReady({ oldVersionUrl: DOCUMENT_BLANKS_ROUTE }), + doclist: PersonalNotificationsPage, + calendar: VacationSchedule, + 'physical-education': TeacherPhysicalEducation, + 'physical-education-student': PhysicalEducationStudent, + oop: () => PageIsNotReady({ oldVersionUrl: OOP_ROUTE }), + centers: () => PageIsNotReady({ oldVersionUrl: CENTERS_ROUTE }), + info: HelpfulInformation, + applications: TeachersApplicationsPage, + allowances: Allowances, + 'data-verification': DataVerificationPage, + 'all-staff': AllStaff, + 'publication-activity': Science, + 'article-list': ArticleList, + 'open-publication': () => { + React.useEffect(() => { + window.location.replace('https://e.mospolytech.ru/old/index.php?p=exp_concl') + }, []) + + return null + }, + 'export-control': () => { + React.useEffect(() => { + window.location.replace('https://e.mospolytech.ru/old/index.php?p=export_control') + }, []) + + return null + }, + // 'generate-schedule': GenerateSchedule, + kedo: () => { + React.useEffect(() => { + window.open('https://lk-staff.mospolytech.ru/applications/1SKabinet-sotrudnika') + window.history.back() + }, []) + return null + }, +} + +export const employeeHiddenPages = { + ...privateHiddenPages, + // remove after mobile version is ready + // #ASM + 'all-teachers': AllTeachersPage, + 'doclist-type': PersonalNotificationsPage, + 'allowances-role': Allowances, + 'allowance-info': AllowanceInfo, + 'create-allowances': CreateAllowance, + 'pps-vote': () => PageIsNotReady({ oldVersionUrl: PPS_VOTE_ROUTE }), + 'issuance-of-licenses-page': isProduction ? ApplicationRedirect : IssuanceOfLicensesPage, + 'getting-computer-equipment': GettingComputerEquipmentPage, + 'connecting-computer': ConnectingComputerPage, + 'printer-maintenance': PrinterMaintenancePage, + 'question-sed': QuestionSedPage, + 'question-personal-account': QuestionPersonalAccountPage, + 'other-it-services': OtherItServicesPage, + 'certificate-form-place-of-work': CertificateFromPlaceOfWorkPage, + 'visa-certificate': VisaCertificatePage, + 'certificate-of-work-experience': isProduction ? ApplicationRedirect : CertificateOfWorkExperiencePage, + 'number-of-unused-vacation-days': NumberOfUnusedVacationDaysPage, + 'increase-antiplagiat-limits': isProduction + ? () => + : IncreaseAntiplagiatLimits, + 'edit-phonebook-subdivision': EditPhonebookSubdivision, + 'edit-phonebook-inner-phone': EditPhonebookInnerPhone, + 'edit-phonebook-email': EditPhonebookEmail, + 'copy-of-employment-record': isProduction ? ApplicationRedirect : CopyOfEmploymentRecordPage, + 'copies-of-documents-from-personal-file': isProduction + ? ApplicationRedirect + : CopiesOfDocumentsFromPersonalFilePage, + 'work-on-terms-of-external-concurrency': isProduction ? ApplicationRedirect : WorkOnTermsOfExternalConcurrencyPage, + 'certificate-time-parental-leave': isProduction ? ApplicationRedirect : CertificateTimeParentalLeavePage, + 'arbitrary-request': isProduction ? ApplicationRedirect : EmployeeArbitraryRequestPage, + courier: isProduction ? ApplicationRedirect : CourierPage, + 'personal-income-tax-reference': isProduction ? ApplicationRedirect : PersonaIncomeTaxReferencePage, + 'payment-of-child-birth-allowance': isProduction ? ApplicationRedirect : PaymentOfChildBirthAllowancePage, + 'payment-for-child-care': isProduction ? ApplicationRedirect : PaymentForChildCarePage, + 'contact-info-actualization': ContactInfoActualizationPage, + 'data-verification': DataVerificationPage, + 'living-in-residential-premises': isProduction ? ApplicationRedirect : LivingInResidentialPremises, + 'guests-accommodation-on-campus': isProduction ? ApplicationRedirect : GuestsAccommodationOnCampus, + 'guests-passage-to-campus': isProduction ? ApplicationRedirect : GuestsPassageToCampus, + relocation: isProduction ? ApplicationRedirect : Relocation, + 'termination-of-agreement': isProduction ? ApplicationRedirect : TerminationOfAgreement, + 'issuance-of-pass': isProduction ? ApplicationRedirect : IssuanceOfPass, + 'guest-pass': isProduction ? ApplicationRedirect : GuestPass, + 'certification-and-issuance-of-docs': isProduction ? ApplicationRedirect : CertificationAndIssuanceOfDocs, + 'deferment-from-conscription': isProduction ? ApplicationRedirect : DefermentFromConscription, + 'certified-copies-of-military-docs': isProduction ? ApplicationRedirect : CertifiedCopiesOfMilitaryDocs, + 'social-environment': () => { + window.location.href = 'https://profkommospolytech.ru/' + + return + }, + 'psychological-help': () => { + window.location.href = + 'https://mospolytech.ru/studencheskaya-zhizn/medical-help/slujba-psihologicheskoy-pomoschi/' + + return + }, + 'health-care': () => { + window.location.href = 'https://mospolytech.ru/studencheskaya-zhizn/medical-help/medicinskaya-slujba/' + + return + }, + 'wifi-at-the-university': () => { + window.location.href = + 'https://e.mospolytech.ru/old/storage/files/Instruktsiya_dostupa_k_internetu_v_auditoriyah.pdf' + + return + }, + brandbook: () => { + window.location.href = 'https://mospolytech.ru/ob-universitete/brandbook/' + + return + }, + 'addresses-and-contacts': () => { + window.location.href = 'https://mospolytech.ru/ob-universitete/adresa-i-kontakty/' + + return + }, + 'structure-of-the-university': () => { + window.location.href = + 'https://mospolytech.ru/ob-universitete/rukovodstvo-i-struktura/strukturnye-podrazdeleniya/' + + return + }, + article: Article, +} diff --git a/src/app/routing/routes/pages/all-pages.ts b/src/app/routing/routes/pages/all-pages.ts new file mode 100644 index 000000000..130f137cc --- /dev/null +++ b/src/app/routing/routes/pages/all-pages.ts @@ -0,0 +1,23 @@ +import { createStore, sample } from 'effector' + +import { PageRoute } from '@shared/routing' +import { userModel } from '@shared/session' + +import { employeeHiddenPages, employeePages } from '../employee' +import { privateHiddenPages, privatePages } from '../private' +import { hiddenStudentPages, studentPages } from '../student' + +export const $allPages = createStore(null) + +sample({ + clock: userModel.stores.user, + source: userModel.stores.userRole, + fn: (userRoles) => ({ + ...privatePages, + ...privateHiddenPages, + ...(userRoles === 'stud' + ? { ...studentPages, ...hiddenStudentPages } + : { ...employeePages, ...employeeHiddenPages }), + }), + target: $allPages, +}) diff --git a/src/app/routing/routes/pages/employee.ts b/src/app/routing/routes/pages/employee.ts new file mode 100644 index 000000000..2da196810 --- /dev/null +++ b/src/app/routing/routes/pages/employee.ts @@ -0,0 +1,109 @@ +import { lazy } from 'react' + +export const CertificateFromPlaceOfWorkPage = lazy( + () => import('@pages/teachers-applications/pages/certificate-from-the-place-of-work'), +) +export const CopiesOfDocumentsFromPersonalFilePage = lazy( + () => import('@pages/teachers-applications/pages/copies-of-documents-from-the-personal-file'), +) +export const ContactInfoActualizationPage = lazy( + () => import('@pages/teachers-applications/pages/contact-info-actualization'), +) +export const ContactDetails = lazy(() => import('@pages/teachers-applications/pages/contact-details')) +export const ContactDetailsForm = lazy(() => import('@pages/teachers-applications/pages/contact-details/form')) +export const DataVerificationPage = lazy(() => import('@pages/teachers-applications/pages/data-verification')) +export const DownloadAdminFilesPage = lazy(() => import('@pages/download-admin-files')) +export const PersonalNotificationsPage = lazy(() => import('@pages/personal-notifications')) +export const TeachersApplicationsPage = lazy(() => import('@pages/teachers-applications')) +export const EmployeeArbitraryRequestPage = lazy(() => import('@pages/teachers-applications/pages/arbitrary-request')) +export const CertificateOfWorkExperiencePage = lazy( + () => import('@pages/teachers-applications/pages/certificate-of-work-experience'), +) +export const CertificateTimeParentalLeavePage = lazy( + () => import('@pages/teachers-applications/pages/certificate-time-parental-leave'), +) +export const ConnectingComputerPage = lazy(() => import('@pages/teachers-applications/pages/connecting-computer')) +export const CopyOfEmploymentRecordPage = lazy( + () => import('@pages/teachers-applications/pages/copy-of-the-employment-record'), +) +export const CourierPage = lazy(() => import('@pages/teachers-applications/pages/courier')) +export const TeacherPhysicalEducation = lazy(() => import('@pages/teacher-physical-education')) +export const GettingComputerEquipmentPage = lazy( + () => import('@pages/teachers-applications/pages/getting-computer-equipment'), +) +export const IssuanceOfLicensesPage = lazy(() => import('@pages/teachers-applications/pages/issuance-of-licenses')) +export const NumberOfUnusedVacationDaysPage = lazy( + () => import('@pages/teachers-applications/pages/number-of-unused-vacation-days'), +) +export const IncreaseAntiplagiatLimits = lazy( + () => import('@pages/teachers-applications/pages/increase-antiplagiat-limits'), +) + +export const EditPhonebookSubdivision = lazy( + () => import('@pages/teachers-applications/pages/edit-phonebook-subdivision'), +) +export const EditPhonebookInnerPhone = lazy( + () => import('@pages/teachers-applications/pages/edit-phonebook-inner-phone'), +) +export const EditPhonebookEmail = lazy(() => import('@pages/teachers-applications/pages/edit-phonebook-email')) + +export const OtherItServicesPage = lazy(() => import('@pages/teachers-applications/pages/other-it-services')) +export const PaymentForChildCarePage = lazy(() => import('@pages/teachers-applications/pages/payment-for-child-care')) +export const PaymentOfChildBirthAllowancePage = lazy( + () => import('@pages/teachers-applications/pages/payment-of-child-birth-allowance'), +) +export const PersonaIncomeTaxReferencePage = lazy( + () => import('@pages/teachers-applications/pages/persona-income-tax-reference'), +) +export const PrinterMaintenancePage = lazy(() => import('@pages/teachers-applications/pages/printer-maintenance')) +export const QuestionPersonalAccountPage = lazy( + () => import('@pages/teachers-applications/pages/question-personal-account'), +) +export const QuestionSedPage = lazy(() => import('@pages/teachers-applications/pages/question-sed')) +export const VisaCertificatePage = lazy(() => import('@pages/teachers-applications/pages/visa-certificate')) +export const WorkOnTermsOfExternalConcurrencyPage = lazy( + () => import('@pages/teachers-applications/pages/work-on-the-terms-of-external-concurrency'), +) + +export const LivingInResidentialPremises = lazy( + () => import('@pages/teachers-applications/pages/campus-office/living-in-residential-premises'), +) +export const GuestsAccommodationOnCampus = lazy( + () => import('@pages/teachers-applications/pages/campus-office/guests-accommodation-on-campus'), +) +export const GuestsPassageToCampus = lazy( + () => import('@pages/teachers-applications/pages/campus-office/guests-passage-to-campus'), +) +export const Relocation = lazy(() => import('@pages/teachers-applications/pages/campus-office/relocation')) +export const TerminationOfAgreement = lazy( + () => import('@pages/teachers-applications/pages/campus-office/termination-of-agreement'), +) + +export const IssuanceOfPass = lazy(() => import('@pages/teachers-applications/pages/pass-office/issuance-of-pass')) +export const GuestPass = lazy(() => import('@pages/teachers-applications/pages/pass-office/guest-pass')) + +export const CertificationAndIssuanceOfDocs = lazy( + () => + import( + '@pages/teachers-applications/pages/control-and-records-management-office/certification-and-issuance-of-docs' + ), +) + +export const DefermentFromConscription = lazy( + () => import('@pages/teachers-applications/pages/mobilization-department/deferment-from-conscription'), +) +export const CertifiedCopiesOfMilitaryDocs = lazy( + () => import('@pages/teachers-applications/pages/mobilization-department/certified-copies-of-military-docs'), +) + +export const VacationSchedule = lazy(() => import('@pages/vacation-schedule')) + +export const HelpfulInformation = lazy(() => import('@pages/helpful-information')) +export const Allowances = lazy(() => import('@pages/allowances/pages/allowances')) +export const AllowanceInfo = lazy(() => import('@pages/allowances/pages/info')) +export const CreateAllowance = lazy(() => import('@pages/allowances/pages/create-allowance')) +export const Onboarding = lazy(() => import('@pages/onboarding')) +export const Science = lazy(() => import('@pages/science/pages/upload')) +export const ArticleList = lazy(() => import('@pages/science/pages/article-list')) +export const Article = lazy(() => import('@pages/science/pages/article')) +export const Children = lazy(() => import('@pages/children')) diff --git a/src/app/routing/routes/pages/index.ts b/src/app/routing/routes/pages/index.ts new file mode 100644 index 000000000..493ada4cc --- /dev/null +++ b/src/app/routing/routes/pages/index.ts @@ -0,0 +1,5 @@ +export * from './public' +export * from './private' +export * from './employee' +export * from './student' +export * from './all-pages' diff --git a/src/app/routing/routes/pages/private.ts b/src/app/routing/routes/pages/private.ts new file mode 100644 index 000000000..ba0cf3324 --- /dev/null +++ b/src/app/routing/routes/pages/private.ts @@ -0,0 +1,23 @@ +import { lazy } from 'react' + +export const AllPages = lazy(() => import('@pages/all-pages')) +export const ApplicationsPage = lazy(() => import('@pages/applications')) +export const AllStudentsPage = lazy(() => import('@pages/all-students')) +export const AllTeachersPage = lazy(() => import('@pages/all-teachers')) +export const AllStaff = lazy(() => import('@pages/all-staff')) +export const ElectronicInteractionAgreementPage = lazy(() => import('@pages/electronic-interaction-agreement')) +export const AlertsPage = lazy(() => import('@pages/alerts')) +export const Home = lazy(() => import('@pages/home')) +export const InstructionsPage = lazy(() => import('@features/helpful-information/instructions')) +export const SafetyInformation = lazy(() => import('@pages/safety-information')) +export const PaymentsPage = lazy(() => import('@pages/payments')) +export const ProfilePage = lazy(() => import('@pages/profile')) +export const SchedulePage = lazy(() => import('@pages/schedule')) + +export const DecreisDirectivesPage = lazy(() => import('@pages/decreis-directives')) +export const MedicalCertificate = lazy(() => import('@pages/medical-certificate')) +export const LkNotificationsPage = lazy(() => import('@pages/lk-notifications')) + +export const TechnicalMaintenance = lazy(() => import('@pages/teachers-applications/pages/technical-maintenance')) +export const PhysicalEducationStudent = lazy(() => import('@pages/pe-student')) +export const ChatPage = lazy(() => import('@pages/chat')) diff --git a/src/app/routing/routes/pages/public.ts b/src/app/routing/routes/pages/public.ts new file mode 100644 index 000000000..94135302c --- /dev/null +++ b/src/app/routing/routes/pages/public.ts @@ -0,0 +1,9 @@ +import { lazy } from 'react' + +export const LoginPage = lazy(() => import('@pages/login')) +export const ForgotPasswordPage = lazy(() => import('@pages/forgot-password')) +export const FeedbackPage = lazy(() => import('@pages/feedback')) +export const CantAccessPage = lazy(() => import('@pages/cant-access')) +export const GetYourLoginPage = lazy(() => import('@pages/get-your-login')) +export const MemoFreshmenPage = lazy(() => import('@pages/memo-freshmen')) +export const MemoTeacherPage = lazy(() => import('@pages/memo-teacher')) diff --git a/src/app/routing/routes/pages/redirect.tsx b/src/app/routing/routes/pages/redirect.tsx new file mode 100644 index 000000000..8c490af00 --- /dev/null +++ b/src/app/routing/routes/pages/redirect.tsx @@ -0,0 +1,3 @@ +import PageIsNotReady from '@pages/page-is-not-ready' + +export const ApplicationRedirect = () => PageIsNotReady({ oldVersionUrl: '' }) diff --git a/src/app/routing/routes/pages/student.ts b/src/app/routing/routes/pages/student.ts new file mode 100644 index 000000000..9e2601ef0 --- /dev/null +++ b/src/app/routing/routes/pages/student.ts @@ -0,0 +1,130 @@ +import { lazy } from 'react' + +export const ApplicationForSuperiorRoom = lazy(() => import('@pages/application-for-superior-room')) +export const AcadPerformance = lazy(() => import('@pages/acad-performance')) +export const DormitoryPage = lazy(() => import('@pages/dormitory')) +export const ClarificationOfPassportDataApplication = lazy( + () => import('@pages/applications/pages/multifunctional-center/clarification-of-passport-data'), +) +export const ApplicationForSocialScrollarship = lazy( + () => import('@pages/applications/pages/trade-union-organization/social-scollarship'), +) + +export const ApplicationForCertificateOfAttendance = lazy( + () => import('@pages/applications/pages/multifunctional-center/certificate-of-attendance'), +) + +export const ApplicationSocialAgencies = lazy( + () => import('@pages/applications/pages/multifunctional-center/social-agencies'), +) + +export const ApplicationPaperCall = lazy(() => import('@pages/applications/pages/multifunctional-center/paper-call')) + +export const RegularAccommodationPage = lazy( + () => import('@pages/applications/pages/campus-management/regular-accommodation'), +) + +export const ProjectActivitiesPage = lazy(() => import('@pages/project-activities')) + +export const AcademicLeaveAccommodationPage = lazy( + () => import('@pages/applications/pages/campus-management/academic-leave-accommodation'), +) + +export const PreferentialAccommodationPage = lazy( + () => import('@pages/applications/pages/campus-management/preferential-accommodation'), +) + +export const TerminationOfEmploymentContractPage = lazy( + () => import('@pages/applications/pages/campus-management/termination-of-employment-contract'), +) + +export const RelocationInsideHostelPage = lazy( + () => import('@pages/applications/pages/campus-management/relocation-inside-hostel'), +) + +export const AccommodationForGraduatesPage = lazy( + () => import('@pages/applications/pages/campus-management/accommodation-for-graduates'), +) + +export const RelocationToAnotherHostelPage = lazy( + () => import('@pages/applications/pages/campus-management/relocation-to-another-hostel'), +) + +export const PaymentRecipient = lazy( + () => import('@pages/applications/pages/department-of-paid-services/paymnet-recipient'), +) + +export const RestoringTheMagneticPass = lazy( + () => import('@pages/applications/pages/multifunctional-center/restoring-the-magnetic-pass'), +) + +export const IncreasedStateAcademicScholarship = lazy( + () => import('@pages/applications/pages/trade-union-organization/increased-state-academic-scholarship'), +) + +export const RetakeForDiploma = lazy( + () => import('@pages/applications/pages/multifunctional-center/retake-for-diploma'), +) + +export const MilitaryRegistrationDocuments = lazy( + () => import('@pages/applications/pages/mobilization-department/military-registration-documents'), +) +export const MilitaryRegistration = lazy( + () => import('@pages/applications/pages/mobilization-department/military-registration'), +) +export const MilitaryForm4 = lazy(() => import('@pages/applications/pages/mobilization-department/military-form-4')) +export const MilitaryForm5 = lazy(() => import('@pages/applications/pages/mobilization-department/military-form-5')) +export const MilitaryCopies = lazy(() => import('@pages/applications/pages/mobilization-department/military-copies')) + +export const FinancialSupport = lazy( + () => import('@pages/applications/pages/trade-union-organization/financial-support'), +) + +export const ApplicationForFinancialAssistance = lazy( + () => import('@pages/applications/pages/trade-union-organization/financial-assistance'), +) + +export const ChangingPersonalData = lazy( + () => import('@pages/applications/pages/multifunctional-center/changing-personal-data'), +) + +export const StudentStatus = lazy(() => import('@pages/applications/pages/multifunctional-center/student-status')) +export const ParentContacts = lazy(() => import('@pages/applications/pages/other/family-contacts')) +export const MedicalCertificates086 = lazy(() => import('@pages/applications/pages/other/medical-certifitcates-086')) + +export const StateAccreditation = lazy( + () => import('@pages/applications/pages/multifunctional-center/state-accreditation'), +) + +export const ApplicationHolidaysAfterTraining = lazy( + () => import('@pages/applications/pages/multifunctional-center/holidays-after-training'), +) + +export const ApplicationProvisionAcademicLeave = lazy( + () => import('@pages/applications/pages/multifunctional-center/provision-academic-leave'), +) + +export const ExitAcademicLeave = lazy( + () => import('@pages/applications/pages/multifunctional-center/exit-academic-leave'), +) + +export const ApplicationIndependentlyDeduction = lazy( + () => import('@pages/applications/pages/multifunctional-center/independently-deducted'), +) + +export const ApplicationExtensionAttestation = lazy( + () => import('@pages/applications/pages/multifunctional-center/extension-attestation'), +) + +export const AccommodationCorrespondenceFormPage = lazy( + () => import('@pages/applications/pages/campus-management/accommodation-correspondence-form'), +) + +export const FamilyRoomPage = lazy(() => import('@pages/applications/pages/campus-management/family-room')) + +export const ArbitraryRequestPage = lazy(() => import('@pages/applications/pages/other/arbitrary-request')) +export const StudentEmploymentPage = lazy(() => import('@pages/student-employment')) +export const StudentEmploymentApplicationPage = lazy(() => import('@pages/applications/pages/other/student-employment')) + +export const TaxCertificatesPage = lazy(() => import('@pages/payments/tax-certificate')) +export const TaxCertificatePage = lazy(() => import('@pages/payments/tax-certificate/tax-certificate')) diff --git a/src/app/routing/routes/private.tsx b/src/app/routing/routes/private.tsx new file mode 100644 index 000000000..1e2c62463 --- /dev/null +++ b/src/app/routing/routes/private.tsx @@ -0,0 +1,62 @@ +import React from 'react' + +import { schedulePages } from '@pages/routes' +import SettingsPage from '@pages/settings' + +import { PageRoute } from '@shared/routing' + +import { + AlertsPage, + AllPages, + AllStudentsPage, + AllTeachersPage, + ChatPage, + DecreisDirectivesPage, + ElectronicInteractionAgreementPage, + FeedbackPage, + HelpfulInformation, + Home, + InstructionsPage, + LkNotificationsPage, + ProfilePage, + SafetyInformation, + SchedulePage, + TechnicalMaintenance, +} from './pages' + +export const privatePages: PageRoute = { + all: AllPages, + doclist: DecreisDirectivesPage, + alerts: AlertsPage, + home: Home, + settings: SettingsPage, + profile: ProfilePage, + 'lk-notifications': LkNotificationsPage, + 'electronic-interaction-agreement': ElectronicInteractionAgreementPage, + chat: ChatPage, + 'specific-chat': ChatPage, + schedule: SchedulePage, + 'all-students': AllStudentsPage, + feedback: FeedbackPage, + instructions: InstructionsPage, + vaccination: () => { + React.useEffect(() => { + window.location.replace('https://e.mospolytech.ru/old/index.php?p=vaccination') + }, []) + + return null + }, + 'safety-information': SafetyInformation, + 'technical-maintenance': TechnicalMaintenance, +} + +export const privateHiddenPages: PageRoute = { + // 'teachers-schedule': TeachersSchedule, + 'schedule-filter': SchedulePage, + ...schedulePages, + 'filtered-all-students': AllStudentsPage, + // move to student's after #ASM + 'filtered-all-teachers': AllTeachersPage, + + 'useful-info-template': HelpfulInformation, +} diff --git a/src/app/routing/routes/public.ts b/src/app/routing/routes/public.ts new file mode 100644 index 000000000..d85914ca5 --- /dev/null +++ b/src/app/routing/routes/public.ts @@ -0,0 +1,21 @@ +import { PageRoute } from '@shared/routing' + +import { + CantAccessPage, + FeedbackPage, + ForgotPasswordPage, + GetYourLoginPage, + LoginPage, + MemoFreshmenPage, + MemoTeacherPage, +} from './pages' + +export const publicPages: PageRoute = { + login: LoginPage, + 'forgot-password': ForgotPasswordPage, + feedback: FeedbackPage, + 'cant-access': CantAccessPage, + 'get-login': GetYourLoginPage, + 'student-attention': MemoFreshmenPage, + 'employee-attention': MemoTeacherPage, +} diff --git a/src/app/routing/routes/student.tsx b/src/app/routing/routes/student.tsx new file mode 100644 index 000000000..553716e51 --- /dev/null +++ b/src/app/routing/routes/student.tsx @@ -0,0 +1,127 @@ +import React from 'react' + +import AllTeachersPage from '@pages/all-teachers' +import FullTimePartTimeFormPage from '@pages/applications/pages/campus-management/full-time-part-time-form' +import ApplicationExitAcademicLeave from '@pages/applications/pages/multifunctional-center/exit-academic-leave' +import MedicalCertificate from '@pages/medical-certificate' +import PaymentsPage from '@pages/payments' +import { isProduction } from '@shared/consts' + +import { + AcadPerformance, + AcademicLeaveAccommodationPage, + AccommodationCorrespondenceFormPage, + AccommodationForGraduatesPage, + ApplicationExtensionAttestation, + ApplicationForCertificateOfAttendance, + ApplicationForFinancialAssistance, + ApplicationForSocialScrollarship, + ApplicationForSuperiorRoom, + ApplicationHolidaysAfterTraining, + ApplicationIndependentlyDeduction, + ApplicationPaperCall, + ApplicationProvisionAcademicLeave, + ApplicationSocialAgencies, + ApplicationsPage, + ArbitraryRequestPage, + ChangingPersonalData, + ClarificationOfPassportDataApplication, + DormitoryPage, + FamilyRoomPage, + FinancialSupport, + HelpfulInformation, + IncreasedStateAcademicScholarship, + MedicalCertificates086, + MilitaryCopies, + MilitaryForm4, + MilitaryForm5, + MilitaryRegistration, + MilitaryRegistrationDocuments, + ParentContacts, + PaymentRecipient, + PhysicalEducationStudent, + PreferentialAccommodationPage, + ProjectActivitiesPage, + RegularAccommodationPage, + RelocationInsideHostelPage, + RelocationToAnotherHostelPage, + RestoringTheMagneticPass, + RetakeForDiploma, + StateAccreditation, + StudentEmploymentApplicationPage, + StudentEmploymentPage, + StudentStatus, + TaxCertificatePage, + TaxCertificatesPage, + TerminationOfEmploymentContractPage, +} from './pages' +import { ApplicationRedirect } from './pages/redirect' +import { privateHiddenPages, privatePages } from './private' + +export const studentPages = { + // On this position just to make necessary order + applications: ApplicationsPage, + ...privatePages, + payments: PaymentsPage, + dormitory: DormitoryPage, + softskills: () => { + React.useEffect(() => { + window.location.replace('https://softskills.rsv.ru/') + }, []) + + return null + }, + 'acad-performance': AcadPerformance, + 'physical-education': PhysicalEducationStudent, + 'project-activity': ProjectActivitiesPage, + 'helpful-information': HelpfulInformation, + 'application-for-superior-room': ApplicationForSuperiorRoom, + 'all-teachers': AllTeachersPage, +} + +export const hiddenStudentPages = { + ...privateHiddenPages, + 'student-employment': StudentEmploymentPage, + 'student-employment-type': StudentEmploymentPage, + 'clarification-of-passport-data': ClarificationOfPassportDataApplication, + 'arbitrary-request': ArbitraryRequestPage, + 'student-employment-app': isProduction ? ApplicationRedirect : StudentEmploymentApplicationPage, + 'social-scollarship': isProduction ? ApplicationRedirect : ApplicationForSocialScrollarship, + 'certificate-of-attendance': ApplicationForCertificateOfAttendance, + 'social-agencies': ApplicationSocialAgencies, + 'paper-call': ApplicationPaperCall, + 'medical-certificate': MedicalCertificate, + 'regular-accommodation': RegularAccommodationPage, + 'full-time-part-time-form': FullTimePartTimeFormPage, + 'accommodation-correspondence-form': AccommodationCorrespondenceFormPage, + 'academic-leave-accommodation': AcademicLeaveAccommodationPage, + 'preferential-accommodation': PreferentialAccommodationPage, + 'family-room': FamilyRoomPage, + 'termination-of-employment-contract': TerminationOfEmploymentContractPage, + 'relocation-inside-hostel': RelocationInsideHostelPage, + 'relocation-to-another-hostel': RelocationToAnotherHostelPage, + 'accommodation-for-graduates': AccommodationForGraduatesPage, + 'payment-recipient': PaymentRecipient, + 'restoring-the-magnetic-pass': RestoringTheMagneticPass, + 'military-registration-documents': MilitaryRegistrationDocuments, + 'military-registration': MilitaryRegistration, + 'military-form-4': MilitaryForm4, + 'military-form-5': MilitaryForm5, + 'military-copies': MilitaryCopies, + 'retake-for-diploma': RetakeForDiploma, + 'increased-state-academic-scholarship': IncreasedStateAcademicScholarship, + 'financial-support': isProduction ? ApplicationRedirect : FinancialSupport, + 'financial-assistance': isProduction ? ApplicationRedirect : ApplicationForFinancialAssistance, + 'changing-personal-data': ChangingPersonalData, + 'student-status': StudentStatus, + 'family-contacts': ParentContacts, + 'medical-certificates-086': MedicalCertificates086, + 'state-accreditation': StateAccreditation, + 'holidays-after-training': ApplicationHolidaysAfterTraining, + 'provision-academic-leave': ApplicationProvisionAcademicLeave, + 'exit-academic-leave': ApplicationExitAcademicLeave, + 'independently-deducted': ApplicationIndependentlyDeduction, + 'extension-attestation': ApplicationExtensionAttestation, + 'tax-certificate': TaxCertificatesPage, + 'tax-certificate-form': TaxCertificatePage, +} diff --git a/src/entities/acad-performance/lib/prepare.ts b/src/entities/acad-performance/lib/prepare.ts index d292a29d5..93b05d374 100644 --- a/src/entities/acad-performance/lib/prepare.ts +++ b/src/entities/acad-performance/lib/prepare.ts @@ -1,4 +1,4 @@ -import { AcadPerformance } from '@api/model/acad-performance' +import { AcadPerformance } from '@shared/api/model/acad-performance' // const EXAM = 'exam' // const TEST = 'test' diff --git a/src/entities/admin-links/index.ts b/src/entities/admin-links/index.ts index 3c34686ef..a5a76f06f 100644 --- a/src/entities/admin-links/index.ts +++ b/src/entities/admin-links/index.ts @@ -1,8 +1,18 @@ +import { sample } from 'effector' + import { adminLinksApi } from '@shared/api' import { createDefaultStore } from '@shared/effector/create-default-store' +import { userModel } from '@shared/session' export const adminLinksModel = createDefaultStore({ api: { get: adminLinksApi.get, }, }) + +sample({ + clock: userModel.events.authenticated, + source: userModel.stores.userRole, + filter: (role) => role === 'staff', + target: adminLinksModel.effects.getFx, +}) diff --git a/src/entities/all-students/model/index.ts b/src/entities/all-students/model/index.ts index 323ddf0a7..bc90691aa 100644 --- a/src/entities/all-students/model/index.ts +++ b/src/entities/all-students/model/index.ts @@ -1,9 +1,9 @@ -import { studentApi } from '@api' -import { TStudent } from '@api/model' import { createEffect } from 'effector' -import { createPaginationList } from 'shared/effector/create-pagination-list' -import { SelectPage } from '@features/select' +import { studentApi } from '@shared/api' +import { TStudent } from '@shared/api/model' +import { createPaginationList } from '@shared/effector/create-pagination-list' +import { SelectPage } from '@shared/ui/select' const getFetchStudentsFx = createEffect( async (request: ServerListRequest): Promise> => { diff --git a/src/entities/all-teachers/model/index.ts b/src/entities/all-teachers/model/index.ts index 44284058b..46a30ade0 100644 --- a/src/entities/all-teachers/model/index.ts +++ b/src/entities/all-teachers/model/index.ts @@ -1,9 +1,9 @@ -import { teacherApi } from '@api' -import { TTeacher } from '@api/model' import { createEffect } from 'effector' -import { createPaginationList } from 'shared/effector/create-pagination-list' -import { SelectPage } from '@features/select' +import { teacherApi } from '@shared/api' +import { TTeacher } from '@shared/api/model' +import { createPaginationList } from '@shared/effector/create-pagination-list' +import { SelectPage } from '@shared/ui/select' const getFetchTeachersFx = createEffect( async (request: ServerListRequest): Promise> => { diff --git a/src/entities/allowances/lib/get-error-code.ts b/src/entities/allowances/lib/get-error-code.ts index c01d6a28f..4eebaa36e 100644 --- a/src/entities/allowances/lib/get-error-code.ts +++ b/src/entities/allowances/lib/get-error-code.ts @@ -1,4 +1,4 @@ -import { JobRoles } from '@shared/api/allowances-api' +import { JobRoles } from '@shared/api/allowances/allowances-api' export function getAllowancesErrorCode(jobRoles: JobRoles) { return jobRoles.map((job) => { diff --git a/src/entities/allowances/lib/get-status.ts b/src/entities/allowances/lib/get-status.ts index 89024ad03..b7ac8112c 100644 --- a/src/entities/allowances/lib/get-status.ts +++ b/src/entities/allowances/lib/get-status.ts @@ -1,6 +1,6 @@ -import { MessageType } from '@shared/ui/types' +import { MessageType } from '@shared/consts' -import { ApprovalStatus } from '../types' +import { ApprovalStatus } from '../../../shared/api/allowances/types' export const getStatusType = (status: ApprovalStatus): MessageType => status === 'Approved' diff --git a/src/entities/allowances/model/index.ts b/src/entities/allowances/model/index.ts index 120ee2558..2904fa7f5 100644 --- a/src/entities/allowances/model/index.ts +++ b/src/entities/allowances/model/index.ts @@ -1,11 +1,6 @@ import { createMutation, createQuery } from '@farfetched/core' import { Unit, createEffect, createEvent, createStore, sample } from 'effector' -import { SelectPage } from '@features/select' - -import { popUpMessageModel } from '@entities/pop-up-message' -import { userModel } from '@entities/user' - import { ConfirmRequest, JobRoles, @@ -16,26 +11,27 @@ import { getAllowancesNotifications, getHandbook, getPersonalAllowances, - getRoles, getSubordinates, inspectAllowance, removeFile, uploadFile, viewNotification, -} from '@shared/api/allowances-api' +} from '@shared/api/allowances/allowances-api' +import { AllowanceModified, Employee, Role, Subordnate } from '@shared/api/allowances/types' import { createDatePeriodField } from '@shared/effector/form/create-date-period-field' import { createFilesField } from '@shared/effector/form/create-file-filed' import { createInputField } from '@shared/effector/form/create-input-field' import { createSelectField } from '@shared/effector/form/create-select-field' +import { userModel } from '@shared/session' +import { popUpMessageModel } from '@shared/ui/pop-up-message' +import { SelectPage } from '@shared/ui/select' -import { allowanceStatus } from '../consts' -import { Allowance, AllowanceModified, Employee, Role, Subordnate } from '../types' +import { allowanceStatus } from '../../../shared/api/allowances/consts' export type AllAllowancesModified = { initiatorAllowances: AllowanceModified[] approverAllowances: AllowanceModified[] } -export type AllAllowances = { initiatorAllowances: Allowance[]; approverAllowances: Allowance[] } const appStarted = createEvent() const pageMounted = createEvent() @@ -122,6 +118,13 @@ const getSubordinatesFx = createEffect(async (userId: string) => { return getSubordinates(userId) }) +sample({ + clock: userModel.events.authenticated, + source: userModel.stores.userRole, + filter: (role) => role === 'staff', + target: appStarted, +}) + sample({ clock: getSubordinatesEvent, target: getSubordinatesFx, @@ -230,11 +233,6 @@ sample({ }), target: veridctMutation.start, }) - -const roleQuery = createQuery({ - handler: getRoles, -}) -const $roles = roleQuery.$data.map((jobs) => jobs?.map((job) => job.roles).flat() ?? []) const paymentIdentifierQuery = createQuery({ handler: getHandbook, }) @@ -321,10 +319,6 @@ sample({ }) $completed.on(setCompleted, (_, val) => val) -sample({ - clock: appStarted, - target: roleQuery.start, -}) sample({ clock: appStarted, target: notificationsQuery.start, @@ -394,7 +388,7 @@ sample({ }) sample({ clock: pageMounted, - source: roleQuery.$data, + source: userModel.stores.jobRoles, filter: (roles) => roles !== null, fn: (roles) => roles as JobRoles, target: getAllAllowances, @@ -406,7 +400,7 @@ sample({ sample({ clock: pageMounted, - source: roleQuery.$data, + source: userModel.stores.jobRoles, filter: (roles) => roles !== null, fn: (roles) => roles as JobRoles, target: getAllEmployees, @@ -446,7 +440,6 @@ sample({ sample({ clock: userModel.events.logout, target: [ - roleQuery.reset, sourceOfFundingQuery.reset, paymentIdentifierQuery.reset, allowanceQuery.reset, @@ -470,7 +463,6 @@ sample({ export const events = { pageMounted, personalAllowancesMounted, - appStarted, createSupplement, setCompleted, infoPageMounted, @@ -484,10 +476,6 @@ export const events = { } export const stores = { - jobRoles: roleQuery.$data, - rolesPending: roleQuery.$pending, - roles: $roles, - paymentIdentifiers: $paymentIdentifiers, allowances: $allowances, fileUploading: uploadFileMutation.$pending, diff --git a/src/entities/applications/consts.ts b/src/entities/applications/consts.ts index 446c01e4a..de7b141bf 100644 --- a/src/entities/applications/consts.ts +++ b/src/entities/applications/consts.ts @@ -64,29 +64,4 @@ export const TeacherMethodObtainingOptions = [ { id: 1, title: 'Лично' }, ] -export type SpecialFieldsName = - | 'personalMethod' - | 'postMethod' - | 'personalNature' - | 'Compensation' - | 'Compensation2' - | 'Address' - | 'collHoliday1' - | 'collHoliday2' - | 'collHoliday3' - | 'collHoliday4' - | 'collHoliday5' - | 'Structure1' - | 'Structure2' - | 'PartTime' - | 'collDog' - | 'dueToWithdrawal' - | 'medicalReport' - | 'employed' - | 'unemployed' - | 'universityTransfer' - | null - -export type SpecialFieldsNameConfig = { [key: string]: SpecialFieldsName } - export type ApplicationStatusType = (typeof ApplicationsConstants)[keyof typeof ApplicationsConstants] diff --git a/src/entities/applications/lib/create-form-store.ts b/src/entities/applications/lib/create-form-store.ts new file mode 100644 index 000000000..b00e3e7df --- /dev/null +++ b/src/entities/applications/lib/create-form-store.ts @@ -0,0 +1,144 @@ +import { AxiosResponse } from 'axios' +import { Effect, EventCallable, createEffect, createEvent, createStore, sample } from 'effector' +import { useStore } from 'effector-react' + +import { applicationsModel } from '@entities/applications' + +import { MessageType } from '@shared/consts' +import { popUpMessageModel } from '@shared/ui/pop-up-message' + +export interface TemplateFormStore { + data: DataType | null + completed: boolean + error: string | null + loading: boolean +} + +export interface TemplateFormStoreOutput { + selectors: { + useForm: () => TemplateFormStore + } + effects: { + getFormFx: Effect + postFormFx: Effect + } + events: { + changeCompleted: EventCallable<{ + completed: boolean + }> + clearStore: EventCallable + } +} + +interface APIType { + get?: (data?: string) => Promise> + post: (postData: PostDataType, formId?: string) => Promise> + put?: () => void +} + +interface Args { + defaultStore: TemplateFormStore + api: APIType +} + +export const createFormStore = ({ + defaultStore, + api, +}: Args): TemplateFormStoreOutput => { + const DEFAULT_STORE = defaultStore + + const useForm = () => { + return { + data: useStore($formStore).data, + loading: useStore(getFormFx.pending), + error: useStore($formStore).error, + completed: useStore($formStore).completed, + } + } + + const changeCompleted = createEvent<{ completed: boolean }>() + + const postFormFx = createEffect(async (postData: PostDataType): Promise => { + const response = await api.post(postData) + + if (response.data.result !== 'ok') throw new Error(response.data.error_text) + + return response.data + }) + + sample({ + clock: postFormFx.doneData, + fn: () => ({ + message: 'Данные формы успешно отправлены', + type: 'success' as MessageType, + }), + target: popUpMessageModel.events.evokePopUpMessage, + }) + + sample({ + clock: postFormFx.failData, + fn: (error) => ({ + message: `${error.message}`, + type: 'failure' as MessageType, + }), + target: popUpMessageModel.events.evokePopUpMessage, + }) + + sample({ + clock: postFormFx.doneData, + target: applicationsModel.effects.getApplicationsFx, + }) + + const getFormFx = createEffect(async (data?: string): Promise => { + if (api.get) { + try { + const response = await api.get(data) + + return { ...response.data } + } catch (error) { + throw new Error(error as string) + } + } + + return DEFAULT_STORE.data + }) + + const clearStore = createEvent() + + const $formStore = createStore>(DEFAULT_STORE) + .on(getFormFx, (oldData) => ({ + ...oldData, + error: null, + })) + .on(getFormFx.doneData, (oldData, newData) => ({ + ...oldData, + data: newData, + })) + .on(getFormFx.failData, (oldData, newData) => ({ + ...oldData, + error: newData.message, + })) + .on(changeCompleted, (oldData, newData) => ({ + ...oldData, + completed: newData.completed, + })) + .on(clearStore, () => ({ + ...DEFAULT_STORE, + })) + + return { + selectors: { + useForm, + }, + effects: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + getFormFx, + postFormFx, + }, + events: { + changeCompleted, + clearStore, + }, + } +} diff --git a/src/entities/applications/lib/get-method-obstaing.ts b/src/entities/applications/lib/get-method-obstaing.ts new file mode 100644 index 000000000..7311e34af --- /dev/null +++ b/src/entities/applications/lib/get-method-obstaing.ts @@ -0,0 +1,22 @@ +import { IInputAreaData } from '@shared/ui/input-area/model' +import { SpecialFieldsName, SpecialFieldsNameConfig } from '@shared/ui/input-area/types' + +type radioType = { id: number; title: string } + +const getMethodObtaining = (data: IInputAreaData[]): SpecialFieldsNameConfig => { + const methodObtainingField = data.find((item: IInputAreaData) => item.fieldName === 'method_obtaining') + let resultNameField: SpecialFieldsName = null + if (!!methodObtainingField?.value) { + const valueMethod = methodObtainingField?.value as radioType + if (valueMethod.title === 'На электронную почту') { + resultNameField = null + } else if (valueMethod.title === 'Лично') { + resultNameField = 'personalMethod' + } else if (valueMethod.title === 'На почтовый адрес') { + resultNameField = 'postMethod' + } + } + return { method_obtaining: resultNameField } +} + +export default getMethodObtaining diff --git a/src/entities/applications/model/index.ts b/src/entities/applications/model/index.ts index f4209d5a2..6bebf60cc 100644 --- a/src/entities/applications/model/index.ts +++ b/src/entities/applications/model/index.ts @@ -1,14 +1,14 @@ -import { applicationApi } from '@api' -import { Application, UserApplication, WorkerApplication } from '@api/model' -import { ApplicationFormCodes, ApplicationTeachersFormCodes } from '@utility-types/application-form-codes' -import { combine, createEffect, createStore, forward, sample } from 'effector' +import { combine, createEffect, createStore, sample } from 'effector' import { useStore } from 'effector-react/compat' import { applicationsModel } from '@entities/hr-applications' -import { popUpMessageModel } from '@entities/pop-up-message' -import { userModel } from '@entities/user' -import { MessageType } from '@shared/ui/types' +import { applicationApi } from '@shared/api' +import { ApplicationCreating } from '@shared/api/applications/application-api' +import { Application, UserApplication, WorkerApplication } from '@shared/api/model' +import { MessageType } from '@shared/consts' +import { userModel } from '@shared/session' +import { popUpMessageModel } from '@shared/ui/pop-up-message' interface ApplicationsStore { listApplication: Application[] | null @@ -16,12 +16,6 @@ interface ApplicationsStore { dataWorkerApplication: WorkerApplication[] | null error: string | null } - -export interface ApplicationCreating { - formId: ApplicationFormCodes | ApplicationTeachersFormCodes - args: { [key: string]: any } -} - const DEFAULT_STORE = { listApplication: null, error: null, dataUserApplication: null, dataWorkerApplication: null } const getWorkerPostsFx = createEffect(async (): Promise => { @@ -77,7 +71,16 @@ const useApplications = () => { } } -forward({ from: postApplicationFx.doneData, to: getApplicationsFx }) +sample({ clock: userModel.events.authenticated, target: getUserDataApplicationsFx }) + +sample({ + clock: userModel.events.authenticated, + source: userModel.stores.userRole, + filter: (role) => role === 'staff', + target: getWorkerPostsFx, +}) + +sample({ clock: postApplicationFx.doneData, target: getApplicationsFx }) sample({ clock: postApplicationFx.failData, diff --git a/src/entities/chat-messages/model.ts b/src/entities/chat-messages/model.ts index f0c66c33d..b6535ba84 100644 --- a/src/entities/chat-messages/model.ts +++ b/src/entities/chat-messages/model.ts @@ -5,11 +5,11 @@ import { interval } from 'patronum' import sanitize from 'sanitize-html' import { Chat, LastMessage, chatModel, chatsModel } from '@entities/chats' -import { userModel } from '@entities/user' -import { getFullUserName } from '@entities/user/lib/get-full-user-name' import { User } from '@shared/api/model' -import { pageVisibility } from '@shared/models/window-focus' +import { pageVisibility } from '@shared/consts/models/window-focus' +import { userModel } from '@shared/session' +import { getFullUserName } from '@shared/session/lib/get-full-user-name' import { addMessage, getChatMessages } from './api' import { AddChatMessage, ChatMessage, LocalChatMessage } from './type' @@ -46,7 +46,7 @@ const addChatMessageFx = attach({ effect: async ([chatId, user], body: AddChatMessage) => { if (!chatId) throw new Error('Чат не выбран') - const currentUser = user.currentUser + const currentUser = user const data = await addMessage({ ...body, chatId }) return { ...data, currentUser } @@ -124,8 +124,8 @@ sample({ readed: false, readed_opponent: false, files: params.files ?? [], - author_id: user.currentUser?.id.toString() ?? '', - author_name: getFullUserName(user.currentUser), + author_id: user?.id.toString() ?? '', + author_name: getFullUserName(user), msg_id: params.localId, }, ], @@ -157,8 +157,8 @@ sample({ readed_opponent: false, readed: true, files: params.files ?? [], - author_id: user.currentUser?.id.toString() ?? '', - author_name: getFullUserName(user.currentUser), + author_id: user?.id.toString() ?? '', + author_name: getFullUserName(user), msg_id: params.localId, }, ], diff --git a/src/entities/chats/model/chats.ts b/src/entities/chats/model/chats.ts index 3a68fb993..a3fced746 100644 --- a/src/entities/chats/model/chats.ts +++ b/src/entities/chats/model/chats.ts @@ -2,9 +2,8 @@ import { cache, concurrency, createQuery, onAbort, sessionStorageCache } from '@ import { createEvent, createStore, sample } from 'effector' import { interval } from 'patronum' -import { userModel } from '@entities/user' - -import { pageVisibility } from '@shared/models/window-focus' +import { pageVisibility } from '@shared/consts/models/window-focus' +import { userModel } from '@shared/session' import { getAllChats } from '../api' @@ -63,7 +62,7 @@ sample({ clock: tick, target: chatsQuery.start }) sample({ clock: userModel.stores.user, - filter: (user) => !!user.currentUser, + filter: (user) => !!user, target: load, }) diff --git a/src/entities/children/model/index.ts b/src/entities/children/model/index.ts index b553a63a0..90f6a6455 100644 --- a/src/entities/children/model/index.ts +++ b/src/entities/children/model/index.ts @@ -1,13 +1,12 @@ import { createMutation, createQuery } from '@farfetched/core' import { createEvent, createStore, sample } from 'effector' -import { popUpMessageModel } from '@entities/pop-up-message' - -import { deleteChildren, getChildrens, saveChildrens } from '@shared/api/childrens-api' +import { deleteChildren, getChildrens, saveChildrens } from '@shared/api/childrens/childrens-api' import { createCheckboxField } from '@shared/effector/form/create-checkbox-field' import { isNumber } from '@shared/lib/is-number' +import { popUpMessageModel } from '@shared/ui/pop-up-message' -import { Children } from '../types' +import { Children } from '../../../shared/api/childrens/types' const getChildrensQuery = createQuery({ handler: getChildrens, diff --git a/src/entities/contact-details/index.ts b/src/entities/contact-details/index.ts index 7a0a31f91..06d3ac9af 100644 --- a/src/entities/contact-details/index.ts +++ b/src/entities/contact-details/index.ts @@ -1,5 +1,6 @@ +import { createFormStore } from '@entities/applications/lib/create-form-store' + import { contactDetailsApi } from '@shared/api' -import { createFormStore } from '@shared/effector/create-form-store' export const contactDetailsModel = createFormStore({ defaultStore: { diff --git a/src/entities/contact-info-actualization/model/index.ts b/src/entities/contact-info-actualization/model/index.ts index cd566ea56..27d351115 100644 --- a/src/entities/contact-info-actualization/model/index.ts +++ b/src/entities/contact-info-actualization/model/index.ts @@ -1,5 +1,6 @@ -import { contactInfoActualizationApi } from '@api' -import { createFormStore } from 'shared/effector/create-form-store' +import { createFormStore } from '@entities/applications/lib/create-form-store' + +import { contactInfoActualizationApi } from '@shared/api' export const { effects, events, selectors } = createFormStore({ defaultStore: { diff --git a/src/entities/context-menu/model/index.ts b/src/entities/context-menu/model/index.ts index 69b455d8c..f8c4d3b53 100644 --- a/src/entities/context-menu/model/index.ts +++ b/src/entities/context-menu/model/index.ts @@ -1,10 +1,7 @@ -import React from 'react' - import { createEvent, createStore } from 'effector' import { useStore } from 'effector-react' -import calcPosition from 'widgets/context-menu/lib/calc-position' -import { Coordinates } from '@ui/types' +import { ClickEvent, Coordinates } from '@shared/ui/types' type ClickType = 'left-click' | 'right-click' | 'both' @@ -15,7 +12,14 @@ interface ContextMenuStore { position: Coordinates } -export type ClickEvent = React.MouseEvent | React.MouseEvent +const calcPosition = (e: ClickEvent, width = 265, height = 200) => { + e.preventDefault() + + return { + x: e.clientX < window.innerWidth / 2 ? e.clientX : e.clientX - width, + y: e.clientY < window.innerHeight / 2 ? e.clientY : e.clientY - height, + } +} const DEFAULT_STORE: ContextMenuStore = { open: false, diff --git a/src/entities/electronic-interaction/model/index.ts b/src/entities/electronic-interaction/model/index.ts index 56c8f80a3..0885216b7 100644 --- a/src/entities/electronic-interaction/model/index.ts +++ b/src/entities/electronic-interaction/model/index.ts @@ -1,11 +1,10 @@ -import { pepApi } from '@api' -import { ElectronicInteraction } from '@api/model' import { createEffect, createEvent, createStore, sample } from 'effector' -import { popUpMessageModel } from '@entities/pop-up-message' -import { userModel } from '@entities/user' - -import { MessageType } from '@shared/ui/types' +import { pepApi } from '@shared/api' +import { ElectronicInteraction } from '@shared/api/model' +import { MessageType } from '@shared/consts' +import { userModel } from '@shared/session' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const getElectronicInteraction = createEvent() const postElectronicInteraction = createEvent() diff --git a/src/entities/family-contacts/model/index.ts b/src/entities/family-contacts/model/index.ts index 179c52271..b7fa26ba8 100644 --- a/src/entities/family-contacts/model/index.ts +++ b/src/entities/family-contacts/model/index.ts @@ -1,9 +1,7 @@ import { createQuery } from '@farfetched/core' -import { createEvent, createStore, sample } from 'effector' - -import { popUpMessageModel } from '@entities/pop-up-message' - import { FamilyContacts, getFamilyContacts, saveFamilyContacts } from '@shared/api/family-contacts-api' +import { popUpMessageModel } from '@shared/ui/pop-up-message' +import { createEvent, createStore, sample } from 'effector' const getContacts = createEvent() const saveContacts = createEvent() diff --git a/src/entities/hint/model/index.ts b/src/entities/hint/model/index.ts index 1fef4599e..4d5c94d3b 100644 --- a/src/entities/hint/model/index.ts +++ b/src/entities/hint/model/index.ts @@ -1,7 +1,7 @@ import { createEvent, createStore } from 'effector' import { useStore } from 'effector-react/compat' -import { Coordinates } from '@ui/types' +import { Coordinates } from '@shared/ui/types' export interface Hint { isOpen: boolean diff --git a/src/entities/hr-applications/model/index.ts b/src/entities/hr-applications/model/index.ts index f8096a000..8dd2f9290 100644 --- a/src/entities/hr-applications/model/index.ts +++ b/src/entities/hr-applications/model/index.ts @@ -1,12 +1,13 @@ -import { hrApplicationApi } from '@api' -import { HrApplication, HrUserApplication } from '@api/model' import { createEvent, forward, sample } from 'effector' import { createEffect, createStore } from 'effector' import { useStore } from 'effector-react/compat' import { popUpMessageModelHr } from '@entities/pop-up-message-hr' -import { MessageType } from '@shared/ui/types' +import { hrApplicationApi } from '@shared/api' +import { ApplicationCreating } from '@shared/api/hr-application-api' +import { HrApplication, HrUserApplication } from '@shared/api/model' +import { MessageType } from '@shared/consts' interface ApplicationsStore { listApplication: HrApplication[] | null @@ -14,10 +15,6 @@ interface ApplicationsStore { error: string | null } -export interface ApplicationCreating { - [key: string]: any -} - const DEFAULT_STORE = { listApplication: null, error: null, dataUserApplication: null } const useApplications = () => { diff --git a/src/entities/increased-scholarship/model/index.ts b/src/entities/increased-scholarship/model/index.ts index 4e480231a..1171adf91 100644 --- a/src/entities/increased-scholarship/model/index.ts +++ b/src/entities/increased-scholarship/model/index.ts @@ -3,14 +3,13 @@ import axios from 'axios' import { createEvent, sample } from 'effector' import { applicationsModel } from '@entities/applications' -import { ApplicationCreating } from '@entities/applications/model' -import { popUpMessageModel } from '@entities/pop-up-message' -import { post } from '@shared/api/application-api' +import { ApplicationCreating, post } from '@shared/api/applications/application-api' import { createCheckboxField } from '@shared/effector/form/create-checkbox-field' import { createFilesField } from '@shared/effector/form/create-file-filed' import { createInputField } from '@shared/effector/form/create-input-field' import { createSelectField } from '@shared/effector/form/create-select-field' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const pageMounted = createEvent() const sendForm = createEvent() diff --git a/src/entities/lk-notifications/consts.ts b/src/entities/lk-notifications/consts.ts new file mode 100644 index 000000000..d4799a170 --- /dev/null +++ b/src/entities/lk-notifications/consts.ts @@ -0,0 +1,13 @@ +import { AllowanceNotificationType } from '@shared/api/allowances/types' +import { ALLOWANCE_INFO_CUT, DOCLIST_ALLOWANCES } from '@shared/routing' + +export const NotificationLinks: Record = { + RecepientDeclinedByZkgu: ALLOWANCE_INFO_CUT, + RecepientDeclinedByAppover: ALLOWANCE_INFO_CUT, + RecepientSelfDeclined: ALLOWANCE_INFO_CUT, + RecepientSelfConfirmed: ALLOWANCE_INFO_CUT, + FullyCompleted: ALLOWANCE_INFO_CUT, + ToApprove: ALLOWANCE_INFO_CUT, + ToConfirm: DOCLIST_ALLOWANCES, + AllowanceOrderCompleted: DOCLIST_ALLOWANCES, +} as const diff --git a/src/entities/lk-notifications/hooks/use-lk-notifications.ts b/src/entities/lk-notifications/hooks/use-lk-notifications.ts index 9bc37127d..0fca0cdc3 100644 --- a/src/entities/lk-notifications/hooks/use-lk-notifications.ts +++ b/src/entities/lk-notifications/hooks/use-lk-notifications.ts @@ -5,9 +5,9 @@ import { useUnit } from 'effector-react' import { electronicInteractionModel } from '@entities/electronic-interaction' import { menuModel } from '@entities/menu' import { userSettingsModel } from '@entities/settings' -import { userModel } from '@entities/user' -import { NotificationsResponse } from '@shared/api/lk-notification-api' +import { NotificationsResponse } from '@shared/api/notifications/lk-notification-api' +import { userModel } from '@shared/session' import { lkNotificationModel } from '..' import createNotification from '../lib/create-notification' diff --git a/src/entities/lk-notifications/index.ts b/src/entities/lk-notifications/index.ts index 35988fce9..f7e01603c 100644 --- a/src/entities/lk-notifications/index.ts +++ b/src/entities/lk-notifications/index.ts @@ -1,2 +1,2 @@ export * as lkNotificationModel from './model' -export type { TNotification, NotificationType } from './types' +export type { TNotification, NotificationType } from '../../shared/api/notifications/types' diff --git a/src/entities/lk-notifications/lib/create-allowance-path.ts b/src/entities/lk-notifications/lib/create-allowance-path.ts index 1bf3c2e51..75a12e54b 100644 --- a/src/entities/lk-notifications/lib/create-allowance-path.ts +++ b/src/entities/lk-notifications/lib/create-allowance-path.ts @@ -1,5 +1,6 @@ -import { NotificationLinks } from '@entities/allowances/consts' -import { AllowanceNotification } from '@entities/allowances/types' +import { AllowanceNotification } from '@shared/api/allowances/types' + +import { NotificationLinks } from '../consts' export const createAllowancePath = (allowance: AllowanceNotification) => { const page = NotificationLinks[allowance.notificationType] diff --git a/src/entities/lk-notifications/lib/create-notification.tsx b/src/entities/lk-notifications/lib/create-notification.tsx index c768d006f..61ce7d1a0 100644 --- a/src/entities/lk-notifications/lib/create-notification.tsx +++ b/src/entities/lk-notifications/lib/create-notification.tsx @@ -1,18 +1,20 @@ +import { allowancesModel } from '@entities/allowances' + +import { AllowanceNotification } from '@shared/api/allowances/types' import { ALERTS_ROUTE, + APPLICATIONS_ROUTE, CHAT_ROUTE, + DOCLIST_ALLOWANCES, + DOCLIST_ROUTE, PAYMENTS_ROUTE, PERSONAL_ELECTRONIC_INTERACTION, + PPS_CONTEST_ROUTE, SCHEDULE_ROUTE, -} from '@app/routes/general-routes' -import { APPLICATIONS_ROUTE } from '@app/routes/routes' -import { DOCLIST_ALLOWANCES, DOCLIST_ROUTE, HR_APPLICATIONS_ROUTE, PPS_CONTEST_ROUTE } from '@app/routes/teacher-routes' - -import { allowancesModel } from '@entities/allowances' -import { NotificationLinks } from '@entities/allowances/consts' -import { AllowanceNotification } from '@entities/allowances/types' +} from '@shared/routing' -import { NotificationType, TNotification } from '../types' +import { NotificationType, TNotification } from '../../../shared/api/notifications/types' +import { NotificationLinks } from '../consts' const createNotification = ( type: NotificationType, @@ -74,14 +76,6 @@ const createNotification = ( pageId: 'chat', canClose: false, }, - 'hr-applications': { - id, - title: title ?? '', - text: text ?? 'У вашей заявки изменился статус', - type: 'hr-applications', - goTo: HR_APPLICATIONS_ROUTE, - pageId: 'hr-applications', - }, 'kpi-pps': { id, title: title ?? '', diff --git a/src/entities/lk-notifications/lib/filter-notifications-via-settings.ts b/src/entities/lk-notifications/lib/filter-notifications-via-settings.ts index 84db3184a..6fcd5d848 100644 --- a/src/entities/lk-notifications/lib/filter-notifications-via-settings.ts +++ b/src/entities/lk-notifications/lib/filter-notifications-via-settings.ts @@ -1,6 +1,5 @@ -import { UserSettings } from '@entities/settings/types' - -import { NotificationsResponse } from '@shared/api/lk-notification-api' +import { NotificationsResponse } from '@shared/api/notifications/lk-notification-api' +import { UserSettings } from '@shared/api/settings' const typeSettingsDictionary: Record = { message: 'messages', diff --git a/src/entities/lk-notifications/model/index.ts b/src/entities/lk-notifications/model/index.ts index a3fb53718..09060c7d1 100644 --- a/src/entities/lk-notifications/model/index.ts +++ b/src/entities/lk-notifications/model/index.ts @@ -1,17 +1,18 @@ -// sort-imports-ignore -import { menuModel } from '@entities/menu' -import { allowancesModel } from '@entities/allowances' -import { NotificationTitles } from '@entities/allowances/consts' -import { UserSettings } from '@entities/settings/types' -import { userModel } from '@entities/user' -import { allowancesApi, lkNotificationApi } from '@shared/api' import { createEffect, createEvent, createStore, forward, sample } from 'effector' import { useStore } from 'effector-react' +import { allowancesModel } from '@entities/allowances' +import { menuModel } from '@entities/menu' + +import { allowancesApi, lkNotificationApi } from '@shared/api' +import { NotificationTitles } from '@shared/api/allowances/consts' +import { UserSettings } from '@shared/api/settings' +import { userModel } from '@shared/session' + +import { TNotification } from '../../../shared/api/notifications/types' import { createAllowancePath } from '../lib/create-allowance-path' import createNotification from '../lib/create-notification' import { filterNotificationsViaSettings } from '../lib/filter-notifications-via-settings' -import { TNotification } from '../types' type TStore = { notifications: TNotification[] diff --git a/src/entities/menu/lib/find-routes-by-config.ts b/src/entities/menu/lib/find-routes-by-config.ts index d3279fd35..6ed018b14 100644 --- a/src/entities/menu/lib/find-routes-by-config.ts +++ b/src/entities/menu/lib/find-routes-by-config.ts @@ -1,4 +1,4 @@ -import { IRoute, IRoutes } from '@app/routes/general-routes' +import { IRoutes, Page } from '@shared/routing' const findRoutesWidthConfig = (config: string[], allRoutes: IRoutes): IRoutes => { return config.reduce( @@ -6,7 +6,7 @@ const findRoutesWidthConfig = (config: string[], allRoutes: IRoutes): IRoutes => acc[id] = allRoutes[id] return acc }, - {} as { [key: string]: IRoute }, + {} as { [key: string]: Page }, ) } diff --git a/src/entities/menu/model/index.ts b/src/entities/menu/model/index.ts index 7919d8c27..f796fc991 100644 --- a/src/entities/menu/model/index.ts +++ b/src/entities/menu/model/index.ts @@ -1,26 +1,29 @@ -import { AdminLinks, User } from '@api/model' import { combine, createEvent, createStore, sample } from 'effector' import { useUnit } from 'effector-react' -import { IRoute, IRoutes } from '@app/routes/general-routes' -import { hiddenRoutes, privateRoutes } from '@app/routes/routes' -import { teachersHiddenRoutes, teachersPrivateRoutes } from '@app/routes/teacher-routes' - import { adminLinksModel } from '@entities/admin-links' -import { allowancesModel } from '@entities/allowances' -import { Role } from '@entities/allowances/types' import { userSettingsModel } from '@entities/settings' -import { UserSettings } from '@entities/settings/types' -import { userModel } from '@entities/user' -import { MenuType, REQUIRED_LEFTSIDE_BAR_CONFIG, REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG } from '@shared/constants' +import { Role } from '@shared/api/allowances/types' +import { AdminLinks, User } from '@shared/api/model' +import { UserSettings } from '@shared/api/settings' +import { MenuType, REQUIRED_LEFTSIDE_BAR_CONFIG, REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG } from '@shared/consts' +import { + IRoutes, + Page, + employeeHiddenRoutes, + employeeRoutes, + studentHiddenRoutes, + studentRoutes, +} from '@shared/routing' +import { userModel } from '@shared/session' import findRoutesByConfig from '../lib/find-routes-by-config' export interface Menu { allRoutes: IRoutes | null visibleRoutes: IRoutes | null - currentPage: IRoute | null + currentPage: Page | null isOpen: boolean } @@ -86,7 +89,7 @@ const getNewNotifications = (page: string, notifications: number, routes: IRoute const filterTeachersPrivateRoutes = (adminLinks: AdminLinks | null, allowancesRoles: Role[]): IRoutes => { if (!adminLinks) { - return teachersPrivateRoutes({ allowancesRoles }) + return employeeRoutes({ allowancesRoles }) } const { accepts, agreements, checkdata, studLogins } = adminLinks @@ -94,7 +97,7 @@ const filterTeachersPrivateRoutes = (adminLinks: AdminLinks | null, allowancesRo const hasAdminLinks = !!accepts.length || !!agreements.length || !!checkdata.length || !!studLogins?.length const adminRoute = 'download-agreements' - const filteredRoutes = Object.entries(teachersPrivateRoutes({ allowancesRoles })).filter( + const filteredRoutes = Object.entries(employeeRoutes({ allowancesRoles })).filter( ([key]) => key !== adminRoute || (key === adminRoute && hasAdminLinks), ) return Object.fromEntries(filteredRoutes) @@ -104,18 +107,18 @@ const $leftSidebar = combine( userModel.stores.user, userSettingsModel.stores.userSettings, adminLinksModel.store, - allowancesModel.stores.roles, + userModel.stores.roles, (user, settings, adminLinks, allowancesRoles) => { if (!user || !settings) return null return findRoutesByConfig( - getLeftsideBarConfig(user.currentUser, settings!, adminLinks.data), - user.currentUser?.user_status === 'staff' + getLeftsideBarConfig(user, settings!, adminLinks.data), + user?.user_status === 'staff' ? { ...filterTeachersPrivateRoutes(adminLinks.data, allowancesRoles), - ...teachersHiddenRoutes({ allowancesRoles }), + ...employeeHiddenRoutes({ allowancesRoles }), } - : { ...privateRoutes(), ...hiddenRoutes(user.currentUser) }, + : { ...studentRoutes(), ...studentHiddenRoutes() }, ) }, ) @@ -124,18 +127,18 @@ const $homeRoutes = combine( userModel.stores.user, userSettingsModel.stores.userSettings, adminLinksModel.store, - allowancesModel.stores.roles, + userModel.stores.roles, (user, settings, adminLinks, allowancesRoles) => { if (!user || !settings) return null return findRoutesByConfig( settings?.homePage.pages ?? DEFAULT_HOME_CONFIG, - user.currentUser?.user_status === 'staff' + user?.user_status === 'staff' ? { ...filterTeachersPrivateRoutes(adminLinks.data, allowancesRoles), - ...teachersHiddenRoutes({ allowancesRoles }), + ...employeeHiddenRoutes({ allowancesRoles }), } - : { ...privateRoutes(), ...hiddenRoutes(user.currentUser) }, + : { ...studentRoutes(), ...studentHiddenRoutes() }, ) }, ) @@ -145,14 +148,14 @@ sample({ userStore: userModel.stores.user, settings: userSettingsModel.stores.userSettings, adminLinks: adminLinksModel.store, - allowancesRoles: allowancesModel.stores.roles, + allowancesRoles: userModel.stores.roles, }, filter: ({ settings, userStore }) => { - return Boolean(settings) && Boolean(userStore.currentUser) + return Boolean(settings) && Boolean(userStore) }, fn: ({ settings, adminLinks, userStore, allowancesRoles }) => ({ homeRoutes: settings!.homePage.pages, - user: userStore.currentUser!, + user: userStore!, adminLinks: adminLinks.data!, allowancesRoles: allowancesRoles, }), @@ -176,16 +179,16 @@ const $menu = createStore(DEFAULT_STORE) ? filterTeachersPrivateRoutes(adminLinks, allowancesRoles)[ window.location.hash.slice(2, window.location.hash.length) ] - : privateRoutes()[window.location.hash.slice(2, window.location.hash.length)], + : studentRoutes()[window.location.hash.slice(2, window.location.hash.length)], allRoutes: user?.user_status === 'staff' ? { ...filterTeachersPrivateRoutes(adminLinks, allowancesRoles), - ...teachersHiddenRoutes({ allowancesRoles }), + ...employeeHiddenRoutes({ allowancesRoles }), } - : { ...privateRoutes(), ...hiddenRoutes(user) }, + : { ...studentRoutes(), ...studentHiddenRoutes() }, visibleRoutes: - user?.user_status === 'staff' ? filterTeachersPrivateRoutes(adminLinks, allowancesRoles) : privateRoutes(), + user?.user_status === 'staff' ? filterTeachersPrivateRoutes(adminLinks, allowancesRoles) : studentRoutes(), })) .on(changeNotifications, (oldData, { page, notifications }) => ({ ...oldData, diff --git a/src/entities/notification/model/index.ts b/src/entities/notification/model/index.ts index 20bb8e646..244559c6f 100644 --- a/src/entities/notification/model/index.ts +++ b/src/entities/notification/model/index.ts @@ -1,9 +1,9 @@ -import { docsApi, notificationApi } from '@api' -import { Notifications } from '@api/model/notification' import { createEffect, createEvent, createStore } from 'effector' import { useStore } from 'effector-react/compat' -import { userModel } from '@entities/user' +import { docsApi, notificationApi } from '@shared/api' +import { Notifications } from '@shared/api/model/notification' +import { userModel } from '@shared/session' export type NotificationType = 'notifications' | 'docs' | 'allowances' interface PersonalNotificationsStore { diff --git a/src/entities/payments/index.ts b/src/entities/payments/index.ts index 1ff0f6ee0..8c60d959c 100644 --- a/src/entities/payments/index.ts +++ b/src/entities/payments/index.ts @@ -1,4 +1,4 @@ export * as paymentsModel from './model' export * as thirdPartyAgreementModel from './third-party-agreement-model' -export * from './tax-certificate/model' export * as thirdPartyInteractionModel from './third-party-agreement-model/electronic-interaction' +export * from './tax-certificate/model' diff --git a/src/entities/payments/lib/change-can-sign.ts b/src/entities/payments/lib/change-can-sign.ts index 3c62ea79b..a3ee73074 100644 --- a/src/entities/payments/lib/change-can-sign.ts +++ b/src/entities/payments/lib/change-can-sign.ts @@ -1,4 +1,4 @@ -import { Payments } from '@api/model' +import { Payments } from '@shared/api/model' const changeCanSign = (payments: Payments | null, contractId: string, value: boolean): Payments | null => { if (!payments) return null diff --git a/src/entities/payments/model/index.ts b/src/entities/payments/model/index.ts index d01694e14..fbe19235e 100644 --- a/src/entities/payments/model/index.ts +++ b/src/entities/payments/model/index.ts @@ -1,12 +1,12 @@ -import { paymentApi } from '@api' -import { Payments, PaymentsContract } from '@api/model' import { combine, createEffect, createEvent, createStore, sample } from 'effector' -import { popUpMessageModel } from '@entities/pop-up-message' -import { userModel } from '@entities/user' - +import { paymentApi } from '@shared/api' +import { Payments, PaymentsContract } from '@shared/api/model' import { agreementSubmit } from '@shared/api/payment-api' -import { MessageType } from '@shared/ui/types' +import { MessageType } from '@shared/consts' +import { userModel } from '@shared/session' +import { tutorialModel } from '@shared/tutorial' +import { popUpMessageModel } from '@shared/ui/pop-up-message' import changeCanSign from '../lib/change-can-sign' @@ -95,6 +95,18 @@ const $paymentType = combine($combinedDormLength, $combinedEduLength, (combinedD : 'none', ) +sample({ + clock: getPaymentsFx.doneData, + source: tutorialModel.stores.roles, + fn: (oldRoles, { dormitory, education }) => { + const roles = [...oldRoles] + if (dormitory.length && !roles.includes('dormitory')) roles.push('dormitory') + if (education.length && !roles.includes('education')) roles.push('education') + return roles + }, + target: tutorialModel.events.setRoles, +}) + export const stores = { $loading, $completed, diff --git a/src/entities/payments/tax-certificate/model.tsx b/src/entities/payments/tax-certificate/model.tsx index 1882ba78b..4ac0cca9e 100644 --- a/src/entities/payments/tax-certificate/model.tsx +++ b/src/entities/payments/tax-certificate/model.tsx @@ -1,45 +1,35 @@ import { createMutation } from '@farfetched/core' +import { TaxCertificate, createTaxCertificate, getTaxCerts } from '@shared/api/payment-api' +import { userModel } from '@shared/session' +import { popUpMessageModel } from '@shared/ui/pop-up-message' import axios from 'axios' import { createEffect, createEvent, createStore, sample } from 'effector' import { and, not, reset } from 'patronum' -import { popUpMessageModel } from '@entities/pop-up-message' -import { userModel } from '@entities/user' - -import { TaxCertificate, createTaxCertificate, getTaxCerts } from '@shared/api/payment-api' - const pageMounted = createEvent() const certificatedRequested = createEvent<{ year: string }>() - const $taxCerts = createStore(null) - const createTaxCertificateMutation = createMutation({ handler: createTaxCertificate, }) - const getTaxCertsFx = createEffect(getTaxCerts) - sample({ clock: getTaxCertsFx.doneData, target: $taxCerts, }) - const $presentYears = $taxCerts.map((certificates) => { if (!certificates) return new Set() return new Set(certificates.map((certificate) => certificate.year)) }) - sample({ clock: pageMounted, filter: not($taxCerts), target: getTaxCertsFx, }) - sample({ clock: certificatedRequested, target: createTaxCertificateMutation.start, }) - sample({ clock: createTaxCertificateMutation.$succeeded, target: [ @@ -50,7 +40,6 @@ sample({ getTaxCertsFx, ], }) - sample({ clock: createTaxCertificateMutation.finished.failure, fn: ({ error }) => ({ @@ -62,12 +51,10 @@ sample({ }), target: popUpMessageModel.events.evokePopUpMessage, }) - reset({ clock: userModel.events.logout, target: [$taxCerts], }) - export const taxCertificateModel = { pageMounted, certificatedRequested, diff --git a/src/entities/payments/third-party-agreement-model/index.ts b/src/entities/payments/third-party-agreement-model/index.ts index 55c6228c7..18c612706 100644 --- a/src/entities/payments/third-party-agreement-model/index.ts +++ b/src/entities/payments/third-party-agreement-model/index.ts @@ -1,11 +1,9 @@ import { createMutation } from '@farfetched/core' +import { SendAgreementCodesReq, sendAgreementCodesApi, signThirdPartyAgreementApi } from '@shared/api/payment-api' +import { popUpMessageModel } from '@shared/ui/pop-up-message' import axios from 'axios' import { createEffect, createEvent, createStore, sample } from 'effector' -import { popUpMessageModel } from '@entities/pop-up-message' - -import { SendAgreementCodesReq, sendAgreementCodesApi, signThirdPartyAgreementApi } from '@shared/api/payment-api' - import { paymentsModel } from '..' type Emails = { clientEmail: string; userEmail: string } diff --git a/src/entities/pe-student-additional-points/model/pe-student-additional-points-model.ts b/src/entities/pe-student-additional-points/model/pe-student-additional-points-model.ts index a247b3881..f73fa77bb 100644 --- a/src/entities/pe-student-additional-points/model/pe-student-additional-points-model.ts +++ b/src/entities/pe-student-additional-points/model/pe-student-additional-points-model.ts @@ -1,12 +1,10 @@ import { createEffect, createEvent, sample } from 'effector' -import { modalModel } from 'widgets/modal/model' - -import { popUpMessageModel } from '@entities/pop-up-message' import { peApi } from '@shared/api' import { getPeErrorMsg } from '@shared/api/config/pe-config' - -import { AddStudentAdditionalPoints } from '../types' +import { AddStudentAdditionalPoints } from '@shared/api/physical-education' +import { modalModel } from '@shared/ui/modal/model' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const addAdditionPoints = createEvent() const removeAdditionPoints = createEvent<{ id: string }>() diff --git a/src/entities/pe-student-regulation-points/model/pe-student-regulation-points-model.ts b/src/entities/pe-student-regulation-points/model/pe-student-regulation-points-model.ts index 49277c2ed..dd6aa34a7 100644 --- a/src/entities/pe-student-regulation-points/model/pe-student-regulation-points-model.ts +++ b/src/entities/pe-student-regulation-points/model/pe-student-regulation-points-model.ts @@ -1,12 +1,10 @@ import { createEffect, createEvent, sample } from 'effector' -import { modalModel } from 'widgets/modal/model' - -import { popUpMessageModel } from '@entities/pop-up-message' import { peApi } from '@shared/api' import { getPeErrorMsg } from '@shared/api/config/pe-config' - -import { AddStudentRegulationPoints } from '../types' +import { AddStudentRegulationPoints } from '@shared/api/physical-education' +import { modalModel } from '@shared/ui/modal/model' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const addRegulationPoints = createEvent() const removeRegulationPoints = createEvent<{ id: string }>() diff --git a/src/entities/pe-student/model/index.ts b/src/entities/pe-student/model/index.ts index 87ca77ba4..8e6d9efa7 100644 --- a/src/entities/pe-student/model/index.ts +++ b/src/entities/pe-student/model/index.ts @@ -4,3 +4,4 @@ export * as selectedPEStudentModel from './selected-pe-student-model' export * as peStudentCompetitionModel from './pe-student-competition-model' export * as peStudentHealthGroupModel from './pe-student-health-group-model' export * as peStudentSpecializationModel from './pe-student-specialization-model' +export * as pEStudentSearchModel from './search' diff --git a/src/entities/pe-student/model/pe-student-competition-model.ts b/src/entities/pe-student/model/pe-student-competition-model.ts index ef8dc63ad..dfa47b91a 100644 --- a/src/entities/pe-student/model/pe-student-competition-model.ts +++ b/src/entities/pe-student/model/pe-student-competition-model.ts @@ -1,9 +1,7 @@ -import { createEffect, createEvent, createStore, sample } from 'effector' - -import { popUpMessageModel } from '@entities/pop-up-message' - import { peApi } from '@shared/api' import { getPeErrorMsg } from '@shared/api/config/pe-config' +import { popUpMessageModel } from '@shared/ui/pop-up-message' +import { createEffect, createEvent, createStore, sample } from 'effector' const load = createEvent() const remove = createEvent() diff --git a/src/entities/pe-student/model/pe-student-health-group-model.ts b/src/entities/pe-student/model/pe-student-health-group-model.ts index 5ceea7406..50ccf52b4 100644 --- a/src/entities/pe-student/model/pe-student-health-group-model.ts +++ b/src/entities/pe-student/model/pe-student-health-group-model.ts @@ -1,12 +1,9 @@ import { createEffect, createEvent, sample } from 'effector' -import { popUpMessageModel } from '@entities/pop-up-message' - import { peApi } from '@shared/api' import { getPeErrorMsg } from '@shared/api/config/pe-config' - -import { HealthGroup } from '../types' -import { SetHealthGroup } from '../types/set-health-group' +import { HealthGroup, SetHealthGroup } from '@shared/api/physical-education' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const setHealthGroup = createEvent<{ studentGuid: string; healthGroup: HealthGroup }>() diff --git a/src/entities/pe-student/model/pe-student-model.ts b/src/entities/pe-student/model/pe-student-model.ts index bb197ccb6..4aa287ab3 100644 --- a/src/entities/pe-student/model/pe-student-model.ts +++ b/src/entities/pe-student/model/pe-student-model.ts @@ -1,37 +1,16 @@ -import { attach, combine, createEvent, createStore, sample } from 'effector' -import { debounce } from 'patronum' +import { createEvent, createStore, sample } from 'effector' -import { pEStudentFilterModel } from '@pages/teacher-physical-education/model' - -import { PEStudent } from '@entities/pe-student/types' - -import { peApi } from '@shared/api' +import { PEStudent } from '@shared/api/physical-education' import { pEStudentVisitModel } from '.' const load = createEvent() const setPage = createEvent() -const $pEStudentsPage = createStore(0) - .on(setPage, (_, page) => page) - .on(pEStudentFilterModel.stores.$filters, () => 0) - -const loadPageFx = attach({ - source: { page: $pEStudentsPage, filters: pEStudentFilterModel.stores.$filters }, - effect: async ({ filters, page }) => { - const { data } = await peApi.getStudents(page, filters) - - return data.data - }, -}) - -debounce({ source: combine($pEStudentsPage, pEStudentFilterModel.stores.$filters), timeout: 200, target: load }) - -sample({ clock: load, target: loadPageFx }) +const $pEStudentsPage = createStore(0).on(setPage, (_, page) => page) -const $pEStudents = createStore([]).on(loadPageFx.doneData, (_, data) => data.students) -const $pEStudentsTotalCount = createStore(0).on(loadPageFx.doneData, (_, data) => data.totalCount) -const $loading = combine(loadPageFx.pending, Boolean) +const $pEStudents = createStore([]) +const $pEStudentsTotalCount = createStore(0) sample({ clock: pEStudentVisitModel.effects.addVisitFx.doneData, @@ -55,7 +34,6 @@ export const events = { } export const stores = { - $loading, $pEStudents, $pEStudentsPage, $pEStudentsTotalCount, diff --git a/src/entities/pe-student/model/pe-student-specialization-model.ts b/src/entities/pe-student/model/pe-student-specialization-model.ts index e0bf7812b..7ca0ff48d 100644 --- a/src/entities/pe-student/model/pe-student-specialization-model.ts +++ b/src/entities/pe-student/model/pe-student-specialization-model.ts @@ -1,12 +1,9 @@ import { createEffect, createEvent, sample } from 'effector' -import { popUpMessageModel } from '@entities/pop-up-message' - import { peApi } from '@shared/api' import { getPeErrorMsg } from '@shared/api/config/pe-config' - -import { Specialization } from '../types' -import { SetSpecialization } from '../types/set-specialization' +import { SetSpecialization, Specialization } from '@shared/api/physical-education' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const setSpecialization = createEvent<{ studentGuid: string; specialization: Specialization }>() diff --git a/src/entities/pe-student/model/pe-student-visits-model.ts b/src/entities/pe-student/model/pe-student-visits-model.ts index fc5e2ea58..68b694aa4 100644 --- a/src/entities/pe-student/model/pe-student-visits-model.ts +++ b/src/entities/pe-student/model/pe-student-visits-model.ts @@ -1,12 +1,10 @@ import { createEffect, createEvent, sample } from 'effector' -import { modalModel } from 'widgets/modal/model' - -import { popUpMessageModel } from '@entities/pop-up-message' import { peApi } from '@shared/api' import { getPeErrorMsg } from '@shared/api/config/pe-config' - -import { AddStudentVisits } from '../types/add-student-visits' +import { AddStudentVisits } from '@shared/api/physical-education' +import { modalModel } from '@shared/ui/modal/model' +import { popUpMessageModel } from '@shared/ui/pop-up-message' const addVisit = createEvent() const removeVisit = createEvent<{ id: string }>() diff --git a/src/entities/pe-student/model/search.ts b/src/entities/pe-student/model/search.ts new file mode 100644 index 000000000..eb67a1b2e --- /dev/null +++ b/src/entities/pe-student/model/search.ts @@ -0,0 +1,13 @@ +import { createEvent, createStore } from 'effector' + +const update = createEvent() + +const $search = createStore('').on(update, (_, updatedValue) => updatedValue) + +export const events = { + update, +} + +export const stores = { + $search, +} diff --git a/src/entities/pe-student/model/selected-pe-student-model.ts b/src/entities/pe-student/model/selected-pe-student-model.ts index db088fc13..1bc8dfc48 100644 --- a/src/entities/pe-student/model/selected-pe-student-model.ts +++ b/src/entities/pe-student/model/selected-pe-student-model.ts @@ -4,11 +4,11 @@ import { peStudentAdditionalPointsModel } from '@entities/pe-student-additional- import { peStudentRegulationPointsModel } from '@entities/pe-student-regulation-points/model' import { peApi } from '@shared/api' +import { PEStudentProfile } from '@shared/api/physical-education' import * as peStudentHealthGroupModel from './pe-student-health-group-model' import * as peStudentSpecializationModel from './pe-student-specialization-model' import { pEStudentVisitModel } from '.' -import { PEStudentProfile } from '../types' const setCurrentStudentId = createEvent() const resetStudentId = createEvent() diff --git a/src/entities/pe-student/types/student.ts b/src/entities/pe-student/types/student.ts index 45d440ba4..79ff36971 100644 --- a/src/entities/pe-student/types/student.ts +++ b/src/entities/pe-student/types/student.ts @@ -1,6 +1,4 @@ -import { WorkType } from '@entities/pe-student-additional-points/types' - -export type HealthGroup = 'None' | 'Basic' | 'Preparatory' | 'SpecialA' | 'SpecialB' | 'HealthLimitations' | 'Disabled' +import { HealthGroup, Specialization } from '@shared/api/physical-education' export const healthGroupToTitle: Record = { None: '-', @@ -11,22 +9,6 @@ export const healthGroupToTitle: Record = { HealthLimitations: 'ОВЗ', Disabled: 'Инвалид', } - -export type Specialization = - | 'None' - | 'Basketball' - | 'Volleyball' - | 'Aerobics' - | 'PowerLiftingAndCrossfit' - | 'StreetLiftingAndArmLifting' - | 'GeneralPhysicalTraining' - | 'GeneralPhysicalTrainingGym' - | 'FootRoom' - | 'SMG' - | 'TableTennis' - | 'NordicWalking' - | 'InternalTeam' - export const specializationToTitle: Record = { None: '-', Basketball: 'Баскетбол', @@ -42,55 +24,3 @@ export const specializationToTitle: Record = { NordicWalking: 'СМГ скандинавская ходьба', InternalTeam: 'Сборная', } - -export interface PEStudent { - studentGuid: string - fullName: string - groupNumber: string - hasDebt: boolean - visits: number - standardPoints: number - course: number - totalPoints: number - lmsPoints: number - healthGroup: HealthGroup - specialization: Specialization -} - -export type PEStudentProfile = { - studentGuid: string - fullName: string - groupNumber: string - hasDebt: boolean - healthGroup: HealthGroup - healthGroupTeacher: { guid: string; fullName: string } | null - specialization: Specialization - totalPoints: number - lmsPoints: number - visits: number - course: number - curator: { guid: string; fullName: string } | null - visitsHistory: { - id: number - date: string - teacherGuid: string - teacherFullName: string - }[] - pointsHistory: { - id: string - type: WorkType - comment: string - date: string - points: number - teacherGuid: string - teacherFullName: string - }[] - standardsHistory: { - id: string - type: string - teacherFullName: string - points: number - date: string - teacherGuid: string - }[] -} diff --git a/src/entities/pe-teacher/model.ts b/src/entities/pe-teacher/model.ts index 3f92d1e98..66b8c8d36 100644 --- a/src/entities/pe-teacher/model.ts +++ b/src/entities/pe-teacher/model.ts @@ -1,13 +1,12 @@ import { attach, createEvent, restore, sample } from 'effector' -import { userModel } from '@entities/user' - import { peApi } from '@shared/api' +import { userModel } from '@shared/session' const load = createEvent() const loadFx = attach({ - effect: async ({ currentUser }) => { + effect: async (currentUser) => { const { data } = await peApi.getTeacher(currentUser?.guid ?? '') return { ...data.data, id: currentUser?.guid ?? '' } @@ -17,6 +16,11 @@ const loadFx = attach({ const $peTeacher = restore(loadFx, null) +sample({ + clock: userModel.events.authenticated, + target: load, +}) + sample({ clock: load, target: loadFx }) const $isLoading = loadFx.pending diff --git a/src/entities/phonebook/model.ts b/src/entities/phonebook/model.ts index 2c8b42047..686f615d8 100644 --- a/src/entities/phonebook/model.ts +++ b/src/entities/phonebook/model.ts @@ -2,6 +2,7 @@ import { createEffect, createEvent, createStore, sample } from 'effector' import { phonebookApi } from '@shared/api' import { Subdivision } from '@shared/api/model/phonebook' +import { userModel } from '@shared/session' const setSubdivisionPath = createEvent() @@ -19,7 +20,16 @@ const getSubdivisionsFx = createEffect(async (): Promise => { } throw new Error('Не удалось загрузить подразделения') }) + +sample({ + clock: userModel.events.authenticated, + source: userModel.stores.userRole, + filter: (role) => role === 'staff', + target: getSubdivisions, +}) + sample({ clock: getSubdivisions, target: getSubdivisionsFx }) + const $pedningGetSubdividions = getSubdivisionsFx.pending const clearSubdivisionData = createEvent() diff --git a/src/entities/pop-up-message-hr/model/index.ts b/src/entities/pop-up-message-hr/model/index.ts index 972bbddf8..88787d307 100644 --- a/src/entities/pop-up-message-hr/model/index.ts +++ b/src/entities/pop-up-message-hr/model/index.ts @@ -1,7 +1,7 @@ import { createEvent, createStore } from 'effector' import { useStore } from 'effector-react/compat' -import { MessageType } from '@ui/types' +import { MessageType } from '@shared/consts' interface IPopUpMessage { message: ChildrenType diff --git a/src/entities/project-activites/index.ts b/src/entities/project-activites/index.ts index d5029ad65..b3bbc2ace 100644 --- a/src/entities/project-activites/index.ts +++ b/src/entities/project-activites/index.ts @@ -1,8 +1,22 @@ +import { sample } from 'effector' + import { projectActivitesApi } from '@shared/api' import { createDefaultStore } from '@shared/effector/create-default-store' +import { tutorialModel } from '@shared/tutorial' export const projectActivitesModel = createDefaultStore({ api: { get: projectActivitesApi.get, }, }) + +sample({ + clock: projectActivitesModel.store, + source: tutorialModel.stores.roles, + filter: (roles) => !roles.includes('has PA last semester'), + fn: (roles, pa) => + pa.data?.last_semestr_result !== 'Данные отсутствуют' + ? ([...roles, 'has PA last semester'] as const) + : roles.filter((role) => role !== 'has PA last semester'), + target: tutorialModel.stores.roles, +}) diff --git a/src/entities/release/utils.ts b/src/entities/release/utils.ts index 38c85be90..f8c48864c 100644 --- a/src/entities/release/utils.ts +++ b/src/entities/release/utils.ts @@ -1,6 +1,5 @@ -import { userModel } from '@entities/user' - -import { BrowserStorageKey } from '@shared/constants/browser-storage-key' +import { BrowserStorageKey } from '@shared/consts/browser-storage-key' +import { userModel } from '@shared/session' export const releaseClear = () => { const currentVersion = '0.0.1' diff --git a/src/entities/schedule/consts.ts b/src/entities/schedule/consts.ts index e72470c4c..0589c0986 100644 --- a/src/entities/schedule/consts.ts +++ b/src/entities/schedule/consts.ts @@ -1,11 +1,5 @@ import { IFullSchedule, IWeekEventSchedule } from '@shared/api/model' -import { IWeekDays, WEEK_DAYS } from '@shared/constants' - -export enum View { - day, - week, - month, -} +import { IWeekDays, WEEK_DAYS } from '@shared/consts' export const VIEWS = [ { diff --git a/src/entities/schedule/lib/get-calendar-schedule.tsx b/src/entities/schedule/lib/get-calendar-schedule.tsx index a4bf90b2c..2d56b21d6 100644 --- a/src/entities/schedule/lib/get-calendar-schedule.tsx +++ b/src/entities/schedule/lib/get-calendar-schedule.tsx @@ -1,12 +1,10 @@ import React from 'react' import { HiBookOpen } from 'react-icons/hi' -import { getSubjectIcon } from '@features/acad-performance/lib/get-subject-icon' - -import { ISubject, ITimeIntervalColor, TimeIntervalColor } from '@shared/api/model' -import { IWeekDays } from '@shared/constants' +import { DayCalendarEvent, ISubject, ITimeIntervalColor, TimeIntervalColor } from '@shared/api/model' +import { IWeekDays } from '@shared/consts' import { TimeType, getMinutesFromStringTime } from '@shared/lib/dates/get-minutes-from-string-time' -import { type DayCalendarEvent } from '@shared/ui/calendar' +import { getSubjectIcon } from '@shared/lib/get-subject-icon' import { getFullEndDate } from './get-full-end-date' import { getFullStartDate } from './get-full-start-date' diff --git a/src/entities/schedule/lib/get-current-day-schedule.ts b/src/entities/schedule/lib/get-current-day-schedule.ts index 9534e377a..023c7058e 100644 --- a/src/entities/schedule/lib/get-current-day-schedule.ts +++ b/src/entities/schedule/lib/get-current-day-schedule.ts @@ -1,5 +1,4 @@ -import { ISubject } from '@api/model' - +import { ISubject } from '@shared/api/model' import getDateWithoutTime from '@shared/lib/dates/get-date-without-time' import { getFullEndDate } from './get-full-end-date' diff --git a/src/entities/schedule/lib/get-full-end-date.ts b/src/entities/schedule/lib/get-full-end-date.ts index fc29e38e1..eae257689 100644 --- a/src/entities/schedule/lib/get-full-end-date.ts +++ b/src/entities/schedule/lib/get-full-end-date.ts @@ -1,4 +1,4 @@ -import { Months } from '@shared/models/months' +import { Months } from '@shared/consts/models/months' export const getFullEndDate = (endDate: string, startDate: string) => { const [endDay, endMonth] = endDate?.split(' ') ?? [null, null] diff --git a/src/entities/schedule/lib/get-full-start-date.ts b/src/entities/schedule/lib/get-full-start-date.ts index 371283a20..f6cea59e6 100644 --- a/src/entities/schedule/lib/get-full-start-date.ts +++ b/src/entities/schedule/lib/get-full-start-date.ts @@ -1,4 +1,4 @@ -import { Months } from '@shared/models/months' +import { Months } from '@shared/consts/models/months' export const getFullStartDate = (startDate: string) => { const [startDay, startMonth] = startDate.split(' ') diff --git a/src/entities/schedule/lib/get-monday.ts b/src/entities/schedule/lib/get-monday.ts new file mode 100644 index 000000000..89e4529df --- /dev/null +++ b/src/entities/schedule/lib/get-monday.ts @@ -0,0 +1,6 @@ +export const getMonday = (date: Date) => { + const localDate = new Date(date) + const day = localDate.getDay() + const diff = localDate.getDate() - day + (day == 0 ? -6 : 1) // adjust when day is sunday + return new Date(localDate.setDate(diff)) +} diff --git a/src/entities/schedule/lib/normalize-schedule.ts b/src/entities/schedule/lib/normalize-schedule.ts index c890ce81a..1910cfb46 100644 --- a/src/entities/schedule/lib/normalize-schedule.ts +++ b/src/entities/schedule/lib/normalize-schedule.ts @@ -1,4 +1,4 @@ -import { popUpMessageModel } from '@entities/pop-up-message' +import { getMonday } from '@entities/schedule/lib/get-monday' import { CapitalLettersWeekNames, @@ -10,9 +10,9 @@ import { RawSessionScheduleResponse, RawTeacherScheduleResponse, } from '@shared/api/model' -import { IWeekDayNames, TIME_IN_MS, WEEK_DAYS } from '@shared/constants' +import { IWeekDayNames, TIME_IN_MS, WEEK_DAYS } from '@shared/consts' import { getDateInSomeDays } from '@shared/lib/dates/get-date-in-some-days' -import { getMonday } from '@shared/ui/calendar/ui/week-days/lib/get-monday' +import { popUpMessageModel } from '@shared/ui/pop-up-message' import { EMPTY_WEEK, SCHEDULE_NO_RESULT } from '../consts' import { getCalendarSchedule } from './get-calendar-schedule' diff --git a/src/entities/schedule/lib/normalize-session-schedule.ts b/src/entities/schedule/lib/normalize-session-schedule.ts index da62626eb..d2e17df85 100644 --- a/src/entities/schedule/lib/normalize-session-schedule.ts +++ b/src/entities/schedule/lib/normalize-session-schedule.ts @@ -1,5 +1,5 @@ import { RawSessionScheduleResponse } from '@shared/api/model' -import { WEEK_DAYS } from '@shared/constants' +import { WEEK_DAYS } from '@shared/consts' import { getWeekDayFromDate } from '@shared/lib/dates/get-weekday-from-date' import { isValidDate } from '@shared/lib/dates/is-valid-date' diff --git a/src/entities/schedule/model/index.ts b/src/entities/schedule/model/index.ts index 6a3d67cb4..fc61a860d 100644 --- a/src/entities/schedule/model/index.ts +++ b/src/entities/schedule/model/index.ts @@ -1,10 +1,10 @@ -import { IFullSchedule, ISchedule, User } from '@api/model' import { createEffect, createEvent, createStore, sample } from 'effector' import { useStore } from 'effector-react/compat' -import { userModel } from '@entities/user' +import { IFullSchedule, ISchedule, User, View } from '@shared/api/model' +import { userModel } from '@shared/session' -import { EMPTY_WEEK, View } from '../consts' +import { EMPTY_WEEK } from '../consts' import { getGroupSchedule } from '../lib/get-group-schedule' import { getTeacherSchedule } from '../lib/get-teacher-schedule' diff --git a/src/entities/science/lib/get-default-columns.tsx b/src/entities/science/lib/get-default-columns.tsx new file mode 100644 index 000000000..917231e9b --- /dev/null +++ b/src/entities/science/lib/get-default-columns.tsx @@ -0,0 +1,75 @@ +import React from 'react' +import { FaCheck } from 'react-icons/fa6' + +import { ColumnProps } from '@shared/ui/table/types' + +export const getDefaultColumns = (): ColumnProps[] => [ + { + title: 'Название публикации', + field: 'articleTitle', + width: '150px', + showFull: true, + align: 'center', + }, + { + title: 'Авторы', + field: 'authors', + width: '130px', + showFull: true, + align: 'center', + }, + { + title: 'Год', + field: 'publicationYear', + width: '100px', + align: 'center', + sort: true, + search: true, + }, + { + title: 'Издательство', + field: 'publisher', + showFull: true, + align: 'center', + sort: true, + search: true, + }, + { + title: 'Номер страницы', + field: 'pageNumber', + width: '120px', + render: (value) => (value ? value : '-'), + showFull: true, + align: 'center', + }, + { + title: 'Scopus', + field: 'isScopus', + width: '115px', + align: 'center', + render: (value) => value && , + sort: true, + search: true, + }, + { + title: 'WoS', + field: 'isWoS', + width: '110px', + align: 'center', + render: (value) => value && , + sort: true, + search: true, + }, + { title: 'Тип', showFull: true, field: 'publicationType', align: 'center' }, + { + title: 'Количество цитирований', + field: 'quotesCount', + showFull: true, + width: '165px', + align: 'center', + sort: true, + search: true, + }, + { title: 'DOI', field: 'doi', showFull: true, align: 'center' }, + { title: 'Источник финансирования', field: 'fundingSource', showFull: true, width: '170px', align: 'center' }, +] diff --git a/src/entities/science/model/article.ts b/src/entities/science/model/article.ts index b59c58c73..852eed97d 100644 --- a/src/entities/science/model/article.ts +++ b/src/entities/science/model/article.ts @@ -1,7 +1,7 @@ import { createQuery } from '@farfetched/core' import { createEvent, sample } from 'effector' -import { getArticle, getArticleDetails } from '@shared/api/science-api' +import { getArticle, getArticleDetails } from '@shared/api/science/science-api' const getDetailsClicked = createEvent() const pageMounted = createEvent() diff --git a/src/entities/science/model/index.ts b/src/entities/science/model/index.ts index 2b074d700..79aaa8da8 100644 --- a/src/entities/science/model/index.ts +++ b/src/entities/science/model/index.ts @@ -3,17 +3,16 @@ import { IndexRange } from 'react-virtualized' import { createMutation } from '@farfetched/core' import { createEffect, createEvent, createStore, sample } from 'effector' -import { getDefaultColumns } from '@pages/science/lib/get-default-columns' +import { getDefaultColumns } from '@entities/science/lib/get-default-columns' -import { popUpMessageModel } from '@entities/pop-up-message' - -import { UploadReq, getAllArticles, uploadArticle } from '@shared/api/science-api' +import { UploadReq, getAllArticles, uploadArticle } from '@shared/api/science/science-api' import { createCheckboxField } from '@shared/effector/form/create-checkbox-field' import { createInputField } from '@shared/effector/form/create-input-field' +import { popUpMessageModel } from '@shared/ui/pop-up-message' import { ColumnProps } from '@shared/ui/table/types' +import { Article, Filter, Sort } from '../../../shared/api/science/types' import { scienceNameMap } from '../lib/nameMap' -import { Article, Filter, Sort } from '../types' import { TABLE_SIZE } from './consts' const pageMounted = createEvent() diff --git a/src/entities/settings/lib/get-default-settings.ts b/src/entities/settings/lib/get-default-settings.ts index 40cd32de3..ce7c8c97b 100644 --- a/src/entities/settings/lib/get-default-settings.ts +++ b/src/entities/settings/lib/get-default-settings.ts @@ -1,6 +1,5 @@ -import { REQUIRED_LEFTSIDE_BAR_CONFIG, REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG, ThemeVariant } from '@shared/constants' - -import { UserSettings } from '../types' +import { UserSettings } from '@shared/api/settings' +import { REQUIRED_LEFTSIDE_BAR_CONFIG, REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG, ThemeVariant } from '@shared/consts' export const getDefaultNewSettings = (isEmployee: boolean): UserSettings => { // get default theme from user preferences @@ -31,9 +30,3 @@ export const getDefaultNewSettings = (isEmployee: boolean): UserSettings => { syncAcrossAllDevices: false, } } - -export type PhoneSettingsType = { - phone_staff?: string - allow_mobphone_in?: boolean - allow_mobphone_out?: boolean -} diff --git a/src/entities/settings/lib/get-settings-key.ts b/src/entities/settings/lib/get-settings-key.ts index 63f5d7f24..879d3019e 100644 --- a/src/entities/settings/lib/get-settings-key.ts +++ b/src/entities/settings/lib/get-settings-key.ts @@ -1,4 +1,4 @@ -import { BrowserStorageKey } from '@shared/constants/browser-storage-key' +import { BrowserStorageKey } from '@shared/consts/browser-storage-key' export const getSettingsKey = (userId: string) => { return `${BrowserStorageKey.Settings}-${userId}` diff --git a/src/entities/settings/model/server-settings.ts b/src/entities/settings/model/server-settings.ts index b32c303b4..e09164381 100644 --- a/src/entities/settings/model/server-settings.ts +++ b/src/entities/settings/model/server-settings.ts @@ -1,9 +1,8 @@ import { createQuery } from '@farfetched/core' import { sample } from 'effector' -import { userModel } from '@entities/user' - import { getServerSettings } from '@shared/api/settings' +import { userModel } from '@shared/session' export const serverSettingsQuery = createQuery({ handler: getServerSettings, @@ -11,6 +10,6 @@ export const serverSettingsQuery = createQuery({ sample({ clock: userModel.stores.user, - filter: ({ currentUser }) => Boolean(currentUser), + filter: (currentUser) => Boolean(currentUser), target: serverSettingsQuery.start, }) diff --git a/src/entities/settings/model/users-settings-model.ts b/src/entities/settings/model/users-settings-model.ts index d1ad9943b..1d5597e4a 100644 --- a/src/entities/settings/model/users-settings-model.ts +++ b/src/entities/settings/model/users-settings-model.ts @@ -1,14 +1,14 @@ import { attach, createEffect, createEvent, createStore, sample } from 'effector' -import { userModel } from '@entities/user' - -import { setServerSettings } from '@shared/api/settings' -import { ThemeVariant } from '@shared/constants' -import { BrowserStorageKey } from '@shared/constants/browser-storage-key' +import { UserSettings, setServerSettings } from '@shared/api/settings' +import { ThemeVariant } from '@shared/consts' +import { BrowserStorageKey } from '@shared/consts/browser-storage-key' +import { userModel } from '@shared/session' +import { tutorialModel } from '@shared/tutorial' import { getDefaultNewSettings } from '../lib/get-default-settings' import { getSettingsKey } from '../lib/get-settings-key' -import { SettingsOldType, UserSettings } from '../types' +import { SettingsOldType } from '../types' import { serverSettingsQuery } from './server-settings' const update = createEvent>() @@ -23,7 +23,7 @@ const saveSettingsGlobalFx = createEffect((settings: UserSettings) => { const getSettingsFx = attach({ source: { userStore: userModel.stores.user, serverSettingsQueryData: serverSettingsQuery.$data }, - effect: ({ userStore: { currentUser }, serverSettingsQueryData }): UserSettings => { + effect: ({ userStore: currentUser, serverSettingsQueryData }): UserSettings => { const userGuid = currentUser?.guid const newSettings = localStorage.getItem(getSettingsKey(userGuid ?? '')) const defaultSettings = getDefaultNewSettings(currentUser?.user_status === 'staff') @@ -73,7 +73,7 @@ const getSettingsFx = attach({ sample({ source: { userStore: userModel.stores.user, isServerSettingsLoaded: serverSettingsQuery.$finished }, - filter: ({ userStore, isServerSettingsLoaded }) => Boolean(userStore.currentUser && isServerSettingsLoaded), + filter: ({ userStore, isServerSettingsLoaded }) => Boolean(userStore && isServerSettingsLoaded), fn: ({ userStore }) => { return userStore }, @@ -96,9 +96,9 @@ sample({ clock: $theme, filter: Boolean, target: setThemeToDocument }) sample({ source: { settings: $userSettings, userStore: userModel.stores.user }, - filter: ({ userStore, settings }) => Boolean(userStore.currentUser) && Boolean(settings), + filter: ({ userStore, settings }) => Boolean(userStore) && Boolean(settings), fn: ({ settings, userStore: user }) => { - return { settings: settings!, userId: user!.currentUser?.guid ?? '' } + return { settings: settings!, userId: user?.guid ?? '' } }, target: saveUsersSettingsLocalFx, }) @@ -110,7 +110,7 @@ sample({ isSettingsLoadedFromServer: serverSettingsQuery.$succeeded, }, filter: ({ userStore, settings, isSettingsLoadedFromServer }) => - Boolean(userStore.currentUser && settings?.syncAcrossAllDevices && isSettingsLoadedFromServer), + Boolean(userStore && settings?.syncAcrossAllDevices && isSettingsLoadedFromServer), fn: ({ settings }) => { return settings! }, @@ -140,6 +140,17 @@ sample({ target: saveSettingsGlobalFx, }) +sample({ + clock: $userSettings, + source: tutorialModel.stores.roles, + filter: (_, settings) => Boolean(settings), + fn: (roles, settings) => + settings?.homePage.hasPayment && settings?.homePage.hasSchedule + ? ([...roles, 'has widgets'] as const) + : roles.filter((role) => role !== 'has widgets'), + target: tutorialModel.stores.roles, +}) + export const stores = { userSettings: $userSettings, theme: $theme, diff --git a/src/entities/settings/types.ts b/src/entities/settings/types.ts index 7e4cb736c..81e6e1604 100644 --- a/src/entities/settings/types.ts +++ b/src/entities/settings/types.ts @@ -1,10 +1,4 @@ -import * as z from 'zod' - -import { ThemeVariant } from '@shared/constants' - -import { UserSettingsSchema } from './constants' - -export type UserSettings = z.infer +import { ThemeVariant } from '@shared/consts' export enum OldNameSettings { 'settings-home-page' = 'settings-home-page', diff --git a/src/entities/story/model/index.ts b/src/entities/story/model/index.ts index c5edf1f03..1b8f3a860 100644 --- a/src/entities/story/model/index.ts +++ b/src/entities/story/model/index.ts @@ -1,7 +1,27 @@ import { createEvent, createStore } from 'effector' import { useStore } from 'effector-react/compat' -import { StoryProps } from '@ui/story/ui/story-page' +import { Align, VerticalAlign } from '@shared/ui/types' + +export type StyledProps = { + textAlign?: Align + color?: string + align: { horizontal: Align; vertical: VerticalAlign } + imageAlign?: { horizontal?: Align; vertical?: VerticalAlign } + background?: string +} + +export type StoryProps = StyledProps & { + title: string + text?: string + image?: string + children?: ChildrenType + imageSize?: { width: string; height: string } + link?: { + text: string + to: string + } +} export interface IStory { isOpen: boolean diff --git a/src/entities/superior-room/model/index.ts b/src/entities/superior-room/model/index.ts index 23ef6f7f0..6538d360c 100644 --- a/src/entities/superior-room/model/index.ts +++ b/src/entities/superior-room/model/index.ts @@ -1,10 +1,10 @@ -import { superiorRoomApi } from '@api' -import { SuperiorRoom } from '@api/model' import { createEffect, createEvent, createStore } from 'effector' import { useStore } from 'effector-react/compat' import { forward } from 'effector/effector.mjs' -import { userModel } from '@entities/user' +import { superiorRoomApi } from '@shared/api' +import { SuperiorRoom } from '@shared/api/model' +import { userModel } from '@shared/session' interface SuperiorRoomStore { superiorRoom: SuperiorRoom | null diff --git a/src/entities/teacher-data-verification/model/index.ts b/src/entities/teacher-data-verification/model/index.ts index bba56162b..4e5f2c92e 100644 --- a/src/entities/teacher-data-verification/model/index.ts +++ b/src/entities/teacher-data-verification/model/index.ts @@ -1,10 +1,10 @@ -import { teacherDataVerificationApi } from '@api' -import { TeacherDataVerification } from '@api/model' import { createEffect, createEvent, createStore } from 'effector' import { useStore } from 'effector-react/compat' import { forward } from 'effector/effector.mjs' -import { userModel } from '@entities/user' +import { teacherDataVerificationApi } from '@shared/api' +import { TeacherDataVerification } from '@shared/api/model' +import { userModel } from '@shared/session' interface TeacherDataVerificationStore { teacherDataVerification: TeacherDataVerification | null diff --git a/src/entities/teachers-statement/model/index.ts b/src/entities/teachers-statement/model/index.ts index dc6a59f05..3267e4d94 100644 --- a/src/entities/teachers-statement/model/index.ts +++ b/src/entities/teachers-statement/model/index.ts @@ -1,6 +1,7 @@ -import { teacherStatementApi } from '@api' -import { UserApplication } from '@api/model' -import { createFormStore } from 'shared/effector/create-form-store' +import { createFormStore } from '@entities/applications/lib/create-form-store' + +import { teacherStatementApi } from '@shared/api' +import { UserApplication } from '@shared/api/model' export const { effects, events, selectors } = createFormStore({ defaultStore: { diff --git a/src/entities/technical-maintenance/model/index.ts b/src/entities/technical-maintenance/model/index.ts index ec87925e4..72a247fcc 100644 --- a/src/entities/technical-maintenance/model/index.ts +++ b/src/entities/technical-maintenance/model/index.ts @@ -1,12 +1,11 @@ import { createMutation } from '@farfetched/core' import { createEvent, createStore, sample } from 'effector' -import { SelectPage } from '@features/select' - import { applicationsModel } from '@entities/applications' -import { popUpMessageModel } from '@entities/pop-up-message' import { TechnicalMaintenance, postTechnicalMaintenance } from '@shared/api/technical-maintenance-api' +import { popUpMessageModel } from '@shared/ui/pop-up-message' +import { SelectPage } from '@shared/ui/select' const pageMounted = createEvent() const sendForm = createEvent() diff --git a/src/features/acad-performance/lib/create-select-items.ts b/src/features/acad-performance/lib/create-select-items.ts index de228b85f..ac7f34a35 100644 --- a/src/features/acad-performance/lib/create-select-items.ts +++ b/src/features/acad-performance/lib/create-select-items.ts @@ -1,6 +1,5 @@ -import { SelectPage } from '@features/select' - -import findSemestr from '@utils/find-semestr' +import findSemestr from '@shared/lib/find-semestr' +import { SelectPage } from '@shared/ui/select' const createSelectItems = (course: number | string) => { const count = findSemestr(new Date().toISOString(), course) diff --git a/src/features/acad-performance/lib/find-percentage.ts b/src/features/acad-performance/lib/find-percentage.ts index 1b673c393..916d97e95 100644 --- a/src/features/acad-performance/lib/find-percentage.ts +++ b/src/features/acad-performance/lib/find-percentage.ts @@ -1,6 +1,5 @@ -import { AcadPerformance } from '@api/model/acad-performance' - -import { GradeByScore } from '@shared/constants' +import { AcadPerformance } from '@shared/api/model/acad-performance' +import { GradeByScore } from '@shared/consts' type Percentage = { 5: number diff --git a/src/features/acad-performance/lib/find-progress-bar-color.ts b/src/features/acad-performance/lib/find-progress-bar-color.ts index 761433802..d0f7747c9 100644 --- a/src/features/acad-performance/lib/find-progress-bar-color.ts +++ b/src/features/acad-performance/lib/find-progress-bar-color.ts @@ -1,6 +1,4 @@ -import { IGrade } from '@api/model/acad-performance' - -import { ColorsByGrade, IColorPalette } from '@shared/constants' +import { ColorsByGrade, IColorPalette, IGrade } from '@shared/consts' const findProgressBarColor = (grade: keyof IGrade | undefined, returnColor?: boolean): IColorPalette | string => { if (!!grade) { diff --git a/src/features/acad-performance/lib/search.ts b/src/features/acad-performance/lib/search.ts index 77bf16315..6001b0292 100644 --- a/src/features/acad-performance/lib/search.ts +++ b/src/features/acad-performance/lib/search.ts @@ -1,10 +1,8 @@ -import { AcadPerformance } from '@api/model/acad-performance' - import { prepareData } from '@entities/acad-performance/lib/prepare' +import { AcadPerformance } from '@shared/api/model/acad-performance' import localizeDate from '@shared/lib/dates/localize-date' - -import normalizeString from '@utils/normalize-string' +import normalizeString from '@shared/lib/normalize-string' const search = (value: string, subjects: AcadPerformance[]) => { return prepareData( diff --git a/src/features/acad-performance/ui/atoms/subject-checker.tsx b/src/features/acad-performance/ui/atoms/subject-checker.tsx index ffdbf35c5..94612b854 100644 --- a/src/features/acad-performance/ui/atoms/subject-checker.tsx +++ b/src/features/acad-performance/ui/atoms/subject-checker.tsx @@ -1,12 +1,11 @@ import React from 'react' import { HiOutlineCheck, HiOutlineX } from 'react-icons/hi' -import { IGrade } from '@api/model/acad-performance' import styled from 'styled-components' import findProgressBarColor from '@features/acad-performance/lib/find-progress-bar-color' -import { GradeByScore } from '@shared/constants' +import { GradeByScore, IGrade } from '@shared/consts' interface Props { grade: keyof IGrade | undefined diff --git a/src/features/acad-performance/ui/molecules/subject-item.tsx b/src/features/acad-performance/ui/molecules/subject-item.tsx index 6a5cb048c..61d78e7d6 100644 --- a/src/features/acad-performance/ui/molecules/subject-item.tsx +++ b/src/features/acad-performance/ui/molecules/subject-item.tsx @@ -1,21 +1,20 @@ import React from 'react' -import { AcadPerformance } from '@api/model/acad-performance' import styled from 'styled-components' -import { useModal } from 'widgets' import findProgressBarColor from '@features/acad-performance/lib/find-progress-bar-color' -import { getSubjectIcon } from '@features/acad-performance/lib/get-subject-icon' -import { Icon } from '@features/all-pages' -import { GradeByScore, IColorPalette } from '@shared/constants' +import { AcadPerformance } from '@shared/api/model/acad-performance' +import { GradeByScore, IColorPalette } from '@shared/consts' import localizeDate from '@shared/lib/dates/localize-date' +import getShortName from '@shared/lib/get-short-name' +import { getSubjectIcon } from '@shared/lib/get-subject-icon' import DotSeparatedWords from '@shared/ui/dot-separated-words' import Flex from '@shared/ui/flex' +import { Icon } from '@shared/ui/icon' +import { useModal } from '@shared/ui/modal' import Subtext from '@shared/ui/subtext' -import getShortName from '@utils/get-short-name' - import { SubjectModal } from '.' import { SubjectCheker } from '../atoms' diff --git a/src/features/acad-performance/ui/molecules/subject-items.tsx b/src/features/acad-performance/ui/molecules/subject-items.tsx index 0aedd1cfa..5f3e8bb87 100644 --- a/src/features/acad-performance/ui/molecules/subject-items.tsx +++ b/src/features/acad-performance/ui/molecules/subject-items.tsx @@ -1,8 +1,7 @@ import React, { memo } from 'react' -import { AcadPerformance } from '@api/model/acad-performance' - -import { Divider } from '@ui/atoms' +import { AcadPerformance } from '@shared/api/model/acad-performance' +import { Divider } from '@shared/ui/atoms' import { SubjectItem } from '.' diff --git a/src/features/acad-performance/ui/molecules/subject-modal.tsx b/src/features/acad-performance/ui/molecules/subject-modal.tsx index 180818976..321fe3baf 100644 --- a/src/features/acad-performance/ui/molecules/subject-modal.tsx +++ b/src/features/acad-performance/ui/molecules/subject-modal.tsx @@ -1,19 +1,17 @@ import React from 'react' -import { AcadPerformance } from '@api/model/acad-performance' import styled from 'styled-components' -import { User } from 'widgets' import { SubjectIconAndBackground } from '@features/schedule/ui/subject/subject-icon-and-background' +import User from '@features/user' -import { IColorPalette } from '@shared/constants' +import { AcadPerformance } from '@shared/api/model/acad-performance' +import { IColorPalette } from '@shared/consts' import localizeDate from '@shared/lib/dates/localize-date' +import findSemestr from '@shared/lib/find-semestr' +import KeyValue from '@shared/ui/atoms/key-value' import { Title } from '@shared/ui/title' -import KeyValue from '@ui/atoms/key-value' - -import findSemestr from '@utils/find-semestr' - const Container = styled.div` width: 400px; diff --git a/src/features/add-new-chat/index.tsx b/src/features/add-new-chat/index.tsx index fec14ed90..222df6a80 100644 --- a/src/features/add-new-chat/index.tsx +++ b/src/features/add-new-chat/index.tsx @@ -2,8 +2,9 @@ import React from 'react' import { HiOutlinePencilAlt } from 'react-icons/hi' import styled from 'styled-components' -import { useModal } from 'widgets' -import { TutorialComponent } from 'widgets/tutorial/lib/with-tutorial' + +import { useModal } from '@shared/ui/modal' +import { TutorialComponent } from '@shared/ui/types' import { Modal } from './ui/modal' diff --git a/src/features/add-new-chat/ui/modal.tsx b/src/features/add-new-chat/ui/modal.tsx index 8784538bc..2b9adc920 100644 --- a/src/features/add-new-chat/ui/modal.tsx +++ b/src/features/add-new-chat/ui/modal.tsx @@ -3,22 +3,22 @@ import { FiUsers } from 'react-icons/fi' import { useUnit } from 'effector-react' import styled from 'styled-components' -import { useModal } from 'widgets' -import ListOfPeople from 'widgets/list-of-people' -import Slider from 'widgets/slider' import GroupModal from '@features/groups-list/group-modal' -import SearchWithHints from '@features/search-with-hints' +import ListOfPeople from '@features/list-of-people' import { paginationList as studentPaginationList } from '@entities/all-students' import { paginationList as teacherPaginationList } from '@entities/all-teachers' -import { userModel } from '@entities/user' import { studentApi } from '@shared/api' import { getGroups } from '@shared/api/student-api' import { getDivisions } from '@shared/api/teacher-api' import Masks from '@shared/lib/masks' +import { userModel } from '@shared/session' import { Wrapper } from '@shared/ui/atoms' +import { useModal } from '@shared/ui/modal' +import SearchWithHints from '@shared/ui/search-with-hints' +import Slider from '@shared/ui/slider' import { addNewChatModel } from '../model' import { SearchType } from '../type' @@ -40,16 +40,16 @@ export const Modal = () => { useEffect(() => { // doesn't work in sample xd - if (user.currentUser) { - addNewChatModel.events.setSearchMode(user.currentUser.user_status === 'stud' ? 'employee' : 'student') + if (user) { + addNewChatModel.events.setSearchMode(user.user_status === 'stud' ? 'employee' : 'student') } - }, [user.currentUser]) + }, [user]) useEffect(() => { setGroupSearch('') }, [searchMode]) - if (!user.currentUser) return null + if (!user) return null return ( diff --git a/src/features/all-pages/lib/get-group-pages.ts b/src/features/all-pages/lib/get-group-pages.ts index ff0a93303..909d61c48 100644 --- a/src/features/all-pages/lib/get-group-pages.ts +++ b/src/features/all-pages/lib/get-group-pages.ts @@ -1,8 +1,7 @@ -import { Groups, IRoutes } from '@app/routes/general-routes' +import { PETeacher } from '@shared/api/physical-education' +import { Groups, IRoutes, pageGroups } from '@shared/routing' -import { PETeacher } from '@entities/pe-teacher/types' - -type RoutesOrder = Record +type RoutesOrder = Record<(typeof pageGroups)[Groups], number> export const routesOrder: RoutesOrder = { Основное: 0, @@ -24,7 +23,7 @@ const getGroupPages = (routes: IRoutes | null, peTeacher: PETeacher | null, user ) .reduce( (acc, route) => { - const group = route?.group ? Groups[route.group] : Groups.OTHER + const group = (route?.group ? pageGroups[route.group] : pageGroups.OTHER) as Groups if (!acc[group]) acc[group] = {} acc[group][route.id] = route diff --git a/src/features/all-pages/lib/search.ts b/src/features/all-pages/lib/search.ts index b70192d2f..b44df6e05 100644 --- a/src/features/all-pages/lib/search.ts +++ b/src/features/all-pages/lib/search.ts @@ -1,19 +1,18 @@ -import { IRoute, IRoutes } from '@app/routes/general-routes' - -import normalizeString from '@utils/normalize-string' +import normalizeString from '@shared/lib/normalize-string' +import { IRoutes, Page } from '@shared/routing' const search = (value: string, routes: IRoutes) => { return Object.values(routes).reduce( (acc, el) => { if ( - normalizeString(el.title).includes(normalizeString(value)) || + normalizeString(el.title ?? '').includes(normalizeString(value)) || el?.keywords?.find((word) => normalizeString(word).includes(normalizeString(value))) ) { acc[el.id] = el } return acc }, - {} as { [key: string]: IRoute }, + {} as { [key: string]: Page }, ) } diff --git a/src/features/all-pages/model/index.ts b/src/features/all-pages/model/index.ts index 02f38fa9b..3b7e15733 100644 --- a/src/features/all-pages/model/index.ts +++ b/src/features/all-pages/model/index.ts @@ -1,16 +1,16 @@ import { combine, createEvent, sample } from 'effector' import { adminLinksModel } from '@entities/admin-links' -import { popUpMessageModel } from '@entities/pop-up-message' import { userSettingsModel } from '@entities/settings' -import { userModel } from '@entities/user' import { REQUIRED_HOME_PAGES_CONFIG, REQUIRED_LEFTSIDE_BAR_CONFIG, REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG, SIDEBAR_ITEMS_LIMIT_SIZE, -} from '@shared/constants' +} from '@shared/consts' +import { userModel } from '@shared/session' +import { popUpMessageModel } from '@shared/ui/pop-up-message' export const addPageToHome = createEvent<{ pageId: string }>() export const deletePageFromHome = createEvent<{ pageId: string }>() @@ -50,19 +50,15 @@ sample({ export const addPageToSidebar = createEvent<{ pageId: string }>() export const deletePageFromSidebar = createEvent<{ pageId: string }>() -export const $requiredSidebarItems = combine( - userModel.stores.user, - adminLinksModel.store, - ({ currentUser }, { data }) => { - if (currentUser?.status === '') { - return REQUIRED_LEFTSIDE_BAR_CONFIG - } +export const $requiredSidebarItems = combine(userModel.stores.user, adminLinksModel.store, (currentUser, { data }) => { + if (currentUser?.status === '') { + return REQUIRED_LEFTSIDE_BAR_CONFIG + } - const config = REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG + const config = REQUIRED_TEACHER_LEFTSIDE_BAR_CONFIG - return Object.values(data ?? {}).some((l) => l.length) ? [...config, 'download-agreements'] : config - }, -) + return Object.values(data ?? {}).some((l) => l.length) ? [...config, 'download-agreements'] : config +}) export const $sidebarItems = combine( $requiredSidebarItems, diff --git a/src/features/all-pages/ui/index.ts b/src/features/all-pages/ui/index.ts index 0279920ca..ca40c50e9 100644 --- a/src/features/all-pages/ui/index.ts +++ b/src/features/all-pages/ui/index.ts @@ -1,3 +1,2 @@ -export * from './atoms' export * from './molecules' export * from './organisms' diff --git a/src/features/all-pages/ui/molecules/all-pages-link.tsx b/src/features/all-pages/ui/molecules/all-pages-link.tsx index 25040bc07..0ac6b88d6 100644 --- a/src/features/all-pages/ui/molecules/all-pages-link.tsx +++ b/src/features/all-pages/ui/molecules/all-pages-link.tsx @@ -4,11 +4,11 @@ import { Link } from 'react-router-dom' import styled from 'styled-components' -import { ALL_ROUTE } from '@app/routes/general-routes' - import { menuModel } from '@entities/menu' -import Icon from '../atoms/icon' +import { ALL_ROUTE } from '@shared/routing' + +import Icon from '../../../../shared/ui/icon/icon' import { PageLinkWrapper } from './page-link-content' const AllPagesLinkWrapper = styled(PageLinkWrapper)` diff --git a/src/features/all-pages/ui/molecules/page-link-content.tsx b/src/features/all-pages/ui/molecules/page-link-content.tsx index 4d4f7abd5..97cdd3921 100644 --- a/src/features/all-pages/ui/molecules/page-link-content.tsx +++ b/src/features/all-pages/ui/molecules/page-link-content.tsx @@ -10,14 +10,12 @@ import LinkMoreButton from '@features/link-more-button' import { userSettingsModel } from '@entities/settings' -import { Colors, IColors } from '@shared/constants' +import { Colors, IColors } from '@shared/consts' +import getCorrectWordForm from '@shared/lib/get-correct-word-form' +import BlockWrapper from '@shared/ui/block/styles' +import { Button } from '@shared/ui/button' -import BlockWrapper from '@ui/block/styles' -import { Button } from '@ui/button' - -import getCorrectWordForm from '@utils/get-correct-word-form' - -import Icon from '../atoms/icon' +import Icon from '../../../../shared/ui/icon/icon' import { PageLinkProps } from './page-link' export const PageLinkWrapper = styled(BlockWrapper)<{ @@ -165,9 +163,9 @@ const PageLinkContent = (props: PageLinkProps & { maxWordLength: number }) => { notifications, title, isNew, - icon, - isExternalPage, - isOldLkPage, + icon: PageLinkIcon, + isExternal, + isOldLK, mode, id, orientation = 'vertical', @@ -189,19 +187,19 @@ const PageLinkContent = (props: PageLinkProps & { maxWordLength: number }) => { isVertical={isVertical} justifyContent="center" shadow={shadow} - color={color.length ? color : 'blue'} + color={color ?? 'blue'} hasNotifications={!!notifications} data-selected={props.isActive} > - {(isOldLkPage || isExternalPage) && isVertical && ( + {(isOldLK || isExternal) && isVertical && ( - {isOldLkPage && } - {isExternalPage && } + {isOldLK && } + {isExternal && } )}
- - {icon ?? } + + {PageLinkIcon ? : } {title} {!!notifications && ( diff --git a/src/features/all-pages/ui/molecules/page-link.tsx b/src/features/all-pages/ui/molecules/page-link.tsx index df1a735dd..6ac7dba35 100644 --- a/src/features/all-pages/ui/molecules/page-link.tsx +++ b/src/features/all-pages/ui/molecules/page-link.tsx @@ -2,14 +2,12 @@ import React from 'react' import { Link } from 'react-router-dom' import styled from 'styled-components' -import { useModal } from 'widgets' -import { TutorialComponent } from 'widgets/tutorial/lib/with-tutorial' - -import { IRoute } from '@app/routes/general-routes' import { menuModel } from '@entities/menu' -import { Direction } from '@ui/types' +import { Page } from '@shared/routing' +import { useModal } from '@shared/ui/modal' +import { Direction, TutorialComponent } from '@shared/ui/types' import PageLinkContent from './page-link-content' @@ -30,7 +28,7 @@ const AddPageWrapper = styled.div<{ width: number | string }>` export type PageLinkMode = 'use' | 'add' -export type PageLinkProps = IRoute & { +export type PageLinkProps = Page & { orientation?: Direction shadow?: boolean mode?: PageLinkMode diff --git a/src/features/all-pages/ui/organisms/found-pages.tsx b/src/features/all-pages/ui/organisms/found-pages.tsx index fbe5c95de..a0b38b87f 100644 --- a/src/features/all-pages/ui/organisms/found-pages.tsx +++ b/src/features/all-pages/ui/organisms/found-pages.tsx @@ -2,9 +2,8 @@ import React from 'react' import styled from 'styled-components' -import { IRoutes } from '@app/routes/general-routes' - -import { Error } from '@ui/error' +import { IRoutes } from '@shared/routing' +import { Error } from '@shared/ui/error' import { PageLink } from '../molecules' diff --git a/src/features/all-staff/lib/find-employee-by-fio.ts b/src/features/all-staff/lib/find-employee-by-fio.ts new file mode 100644 index 000000000..468c26b1b --- /dev/null +++ b/src/features/all-staff/lib/find-employee-by-fio.ts @@ -0,0 +1,21 @@ +import { Employee, Subdivision } from '@shared/api/model/phonebook' +import { uniqueByProperty } from '@shared/lib/uniq' + +export const findEmployeeByFio = (subdivisions: Subdivision[], fio: string) => { + const lowerCaseFio = fio.toLowerCase() + if (subdivisions.length === 0) return [] + const result: Employee[] = [] + subdivisions.forEach((subdiv) => { + if (subdiv.head?.fio?.toLowerCase().includes(lowerCaseFio)) { + result.push(subdiv.head) + } + subdiv.staff.forEach((employee) => { + if (employee.fio.toLowerCase().includes(lowerCaseFio)) { + result.push(employee) + } + }) + const nestedResult = findEmployeeByFio(subdiv.subdivs, lowerCaseFio) + result.push(...nestedResult) + }) + return uniqueByProperty(result, 'guid_person') +} diff --git a/src/features/all-staff/lib/find-employee.ts b/src/features/all-staff/lib/find-employee.ts new file mode 100644 index 000000000..0cd999ff7 --- /dev/null +++ b/src/features/all-staff/lib/find-employee.ts @@ -0,0 +1,27 @@ +import { Employee, Subdivision } from '@shared/api/model/phonebook' +import { uniqueByProperty } from '@shared/lib/uniq' + +export const findEmployee = (subdivisions: Subdivision[], query: string) => { + const lowerCaseQuery = query.toLowerCase() + if (subdivisions.length === 0) return [] + const result: Employee[] = [] + subdivisions.forEach((subdiv) => { + if ( + subdiv.head?.post?.toLowerCase().includes(lowerCaseQuery) || + subdiv.head?.fio?.toLowerCase().includes(lowerCaseQuery) + ) { + result.push(subdiv.head) + } + subdiv.staff.forEach((employee) => { + if ( + employee.post?.toLowerCase().includes(lowerCaseQuery) || + employee.fio?.toLowerCase().includes(lowerCaseQuery) + ) { + result.push(employee) + } + }) + const nestedResult = findEmployee(subdiv.subdivs, lowerCaseQuery) + result.push(...nestedResult) + }) + return uniqueByProperty(result, 'guid_person') +} diff --git a/src/features/all-staff/lib/find-subdivision-by-name.ts b/src/features/all-staff/lib/find-subdivision-by-name.ts new file mode 100644 index 000000000..106365f63 --- /dev/null +++ b/src/features/all-staff/lib/find-subdivision-by-name.ts @@ -0,0 +1,16 @@ +import { Subdivision } from '@shared/api/model/phonebook' + +export const findSubdivisionByName = (subdivisions: Subdivision[], name: string): Subdivision | null => { + const lowerCaseName = name.toLowerCase() + if (subdivisions.length === 0) return null + + let result: Subdivision | null = null + subdivisions.forEach((subdiv) => { + if (subdiv.name.toLowerCase() === lowerCaseName) { + result = subdiv + } + const nestedResult = findSubdivisionByName(subdiv.subdivs, name) + if (nestedResult) result = nestedResult + }) + return result +} diff --git a/src/features/all-staff/lib/get-employee-info.ts b/src/features/all-staff/lib/get-employee-info.ts new file mode 100644 index 000000000..fda2d6f9d --- /dev/null +++ b/src/features/all-staff/lib/get-employee-info.ts @@ -0,0 +1,25 @@ +import { Employee } from '@shared/api/model/phonebook' + +import { PhonebookInfo } from '../ui/phonebook-modal' + +export const getEmployeeInfo = (employee: Employee): PhonebookInfo[] => + employee.job.map((job) => ({ + subdivision: job.subdivision, + post: job.post, + attributes: [ + { + id: 'jobType', + title: 'Тип работы', + text: job.jobType, + }, + { + id: 'email', + title: 'Корпоративная электронная почта', + text: job.email, + }, + { id: 'innerPhone', title: 'Внутренний телефон', text: job.phone_inner }, + { id: 'mobile', title: 'Служебный мобильный телефон', text: job.phone_mobile }, + { id: 'address', title: 'Адрес рабочего места', text: job.address }, + { title: 'Номер кабинета', text: job.room }, + ], + })) diff --git a/src/features/all-staff/lib/get-subdivision-info.ts b/src/features/all-staff/lib/get-subdivision-info.ts new file mode 100644 index 000000000..9b6547c4a --- /dev/null +++ b/src/features/all-staff/lib/get-subdivision-info.ts @@ -0,0 +1,29 @@ +import { Subdivision } from '@shared/api/model/phonebook' + +import { PhonebookInfo } from '../ui/phonebook-modal' + +export const getSubdivisionInfo = (subdivision: Subdivision): PhonebookInfo[] => { + return [ + { + attributes: [ + { id: 'head', title: 'Руководитель', text: subdivision.head.fio }, + { + title: 'Корпоративная электронная почта подразделения', + text: subdivision.email, + }, + { + id: 'innerPhone', + title: 'Внутренний телефон', + text: subdivision.phone_inner, + }, + { + id: 'mobile', + title: 'Прямой телефон', + text: subdivision.phone_direct, + }, + { id: 'address', title: 'Адрес рабочего места', text: subdivision.address }, + { title: 'Номер кабинета', text: subdivision.room }, + ], + }, + ] +} diff --git a/src/features/all-staff/lib/get-subdivision-path.ts b/src/features/all-staff/lib/get-subdivision-path.ts new file mode 100644 index 000000000..34ffc238a --- /dev/null +++ b/src/features/all-staff/lib/get-subdivision-path.ts @@ -0,0 +1,19 @@ +import { Subdivision } from '@shared/api/model/phonebook' + +export const getSubdivisionPath = (subdivisions: Subdivision[], searchString: string): Subdivision[] | null => { + if (searchString === '') return null + const result: Subdivision[] = [] + const response = subdivisions.find((subdivision) => subdivision.name === searchString) + + if (!response) { + for (const subdivision of subdivisions) { + const res = getSubdivisionPath(subdivision.subdivs, searchString) + if (res && res.length > 0) result.push(...res, subdivision) + } + return result + } else { + result.push(response) + } + + return result +} diff --git a/src/features/all-staff/staff-modal.tsx b/src/features/all-staff/staff-modal.tsx new file mode 100644 index 000000000..4682108f9 --- /dev/null +++ b/src/features/all-staff/staff-modal.tsx @@ -0,0 +1,39 @@ +import React from 'react' + +import { useUnit } from 'effector-react' + +import { TeacherModal } from '@features/user/ui' + +import { phonebookModel } from '@entities/phonebook' + +import { userModel } from '@shared/session' +import { UserProps } from '@shared/ui/user-header/types' + +import { findEmployeeByFio } from './lib/find-employee-by-fio' +import { getEmployeeInfo } from './lib/get-employee-info' +import { PhonebookModal } from './ui/phonebook-modal' + +type Props = Pick + +export const StaffModal = ({ name, ...props }: Props) => { + const { + data: { user }, + } = userModel.selectors.useUser() + + const isStaff = user?.user_status === 'staff' + const { subdivisions } = useUnit({ + subdivisions: phonebookModel.stores.subdivisions, + }) + + if (!isStaff || !subdivisions) return + const employee = findEmployeeByFio(subdivisions, name)[0] + return ( + + ) +} diff --git a/src/features/all-staff/styled/index.ts b/src/features/all-staff/styled/index.ts new file mode 100644 index 000000000..a278b9b25 --- /dev/null +++ b/src/features/all-staff/styled/index.ts @@ -0,0 +1,101 @@ +import { Link } from 'react-router-dom' + +import styled from 'styled-components' + +import { MEDIA_QUERIES } from '@shared/ui/consts' +import Flex from '@shared/ui/flex' + +export const Header = styled.div<{ isEmployee: boolean }>` + display: flex; + flex-direction: column-reverse; + color: #fff; + min-height: 110px; + width: 100%; + padding-left: ${({ isEmployee }) => (isEmployee ? '125px' : '20px')}; + + ${MEDIA_QUERIES.isNotMobile} { + padding-left: ${({ isEmployee }) => (isEmployee ? '180px' : '0')}; + } + + @media (max-width: 800px) { + padding-bottom: 0; + } +` + +export const Wrapper = styled.div` + width: 700px; + padding-bottom: 20px; + /* padding: 40px 35px 20px 35px; */ + + @media (max-width: 800px) { + width: 100%; + /* padding: 20px 16px 10px 36px; */ + } +` + +export const Content = styled.div<{ isEmployee: boolean }>` + display: flex; + flex-direction: column; + gap: 18px; + min-width: 100%; + padding: 60px 20px 10px 20px; + + ${MEDIA_QUERIES.isNotMobile} { + padding: 35px 20px 10px ${({ isEmployee }) => (isEmployee ? '180px' : '0')}; + } +` + +export const Title = styled.h3` + font-size: 1.17rem; + line-height: 28px; +` + +export const Subtitle = styled.h4` + opacity: 0.7; + font-weight: 400; + font-size: 1.17rem; + line-height: 28px; +` + +export const Buttons = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + gap: 8px; + + a { + width: 100%; + } + button { + width: 100%; + } +` + +export const AvatarWrapper = styled.div` + position: absolute; + top: 100px; + left: 25px; + z-index: 50; + + ${MEDIA_QUERIES.isMobile} { + top: 95px; + left: 10px; + } +` + +export const ScrollWrapper = styled(Flex)` + overflow-x: hidden; + padding-right: 10px; + ${MEDIA_QUERIES.isNotMobile} { + overflow-y: scroll; + height: calc(100vh - 240px); + } + ${MEDIA_QUERIES.isTablet} { + height: calc(100vh - 300px); + } +` + +export const LinkStyled = styled(Link)` + width: 100%; +` diff --git a/src/features/all-staff/ui/info-item.tsx b/src/features/all-staff/ui/info-item.tsx new file mode 100644 index 000000000..008ecf954 --- /dev/null +++ b/src/features/all-staff/ui/info-item.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +import styled from 'styled-components' + +import Flex from '@shared/ui/flex' +import Subtext from '@shared/ui/subtext' + +export const InfoItem = ({ + title, + children, +}: { + title: string + children?: React.ReactElement[] | React.ReactElement | string | null +}) => { + if (!children) return null + return ( + + {title} + {children} + + ) +} + +const Content = styled.p` + padding-left: 14px; +` diff --git a/src/features/all-staff/ui/phonebook-modal.tsx b/src/features/all-staff/ui/phonebook-modal.tsx new file mode 100644 index 000000000..4f699017e --- /dev/null +++ b/src/features/all-staff/ui/phonebook-modal.tsx @@ -0,0 +1,199 @@ +import React from 'react' +import { FiClock, FiList } from 'react-icons/fi' +import { Link } from 'react-router-dom' + +import { useUnit } from 'effector-react' +import styled from 'styled-components' + +import { SendMessage } from '@features/send-first-message' + +import { getEnrichedTemplatePath } from '@entities/menu/lib/get-enriched-template-path' +import { phonebookModel } from '@entities/phonebook' + +import { SiteName } from '@shared/lib' +import { ALL_TEACHERS_ROUTE, SCHEDULE_FILTER_ROUTE } from '@shared/routing' +import Avatar from '@shared/ui/avatar' +import { Button } from '@shared/ui/button' +import Flex from '@shared/ui/flex' +import useCurrentDevice from '@shared/ui/hooks/use-current-device' +import List from '@shared/ui/list' +import { useModal } from '@shared/ui/modal' +import UserHeaderBackground from '@shared/ui/user-header/user-header-background' + +import { findEmployeeByFio } from '../lib/find-employee-by-fio' +import { findSubdivisionByName } from '../lib/find-subdivision-by-name' +import { getEmployeeInfo } from '../lib/get-employee-info' +import { getSubdivisionInfo } from '../lib/get-subdivision-info' +import { AvatarWrapper, Buttons, Content, Header, LinkStyled, Subtitle, Title, Wrapper } from '../styled' +import { InfoItem } from './info-item' +import { PlaceModal } from './place-modal' + +export type PhonebookInfo = { + subdivision?: string + post?: string + attributes: { + id?: 'email' | 'innerPhone' | 'mobile' | 'jobType' | 'head' | 'address' + title: string + text: string + }[] +} + +// TODO: make two separate components for divisions and employees +export const PhonebookModal = ({ + title, + info, + isEmployee, + avatar, + id, +}: { + id?: number | string + title: string + info: PhonebookInfo[] + avatar?: string + isEmployee?: boolean +}) => { + const { open, close } = useModal() + const { isMobile } = useCurrentDevice() + const subdivisions = useUnit(phonebookModel.stores.subdivisions) + + return ( + +
+ + {isEmployee && ( + + + + )} + {title} +
+ + + {info.map(({ subdivision, post, attributes }) => { + const subtitle = subdivision && post + const jobType = attributes.find((attribute) => attribute.id === 'jobType') + return ( + + {subtitle && ( + { + close() + + if (!subdivisions) return + const subdiv = findSubdivisionByName(subdivisions, subdivision) + if (!subdiv) return + open( + , + ) + }} + > + {subdivision + ' • ' + post} + + )} + {attributes.map(({ title, text, id }) => ( + + {text ? ( + id === 'email' ? ( + {text} + ) : id === 'head' ? ( + { + close() + + if (!subdivisions) return + const employee = findEmployeeByFio(subdivisions, text)[0] + if (!employee) return + open( + , + ) + }} + > + {text} + + ) : id === 'innerPhone' ? ( + text.split('; ').map((tel, i, arr) => ( + + {i === arr.length - 1 ? tel : `${tel}; `} + + )) + ) : id === 'mobile' ? ( + {text} + ) : id === 'address' ? ( + { + close() + + open(, text) + }} + > + {text} + + ) : ( + <>{text} + ) + ) : ( + '-' + )} + + ))} + + ) + })} + + + {isEmployee ? ( + + + +
+ {currentStep + 1} / {currentModule.steps.length} +
+ +
+ + setAnimation(val)} /> + , + portal, + )} + + ) + } + + return TutWrapper +} + +const Blocker = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 4; +` + +const FadeIn = keyframes` + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +` + +const BGFadeIn = keyframes` + 0% { + background-color: rgba(0, 0, 0, 0.66); + } + 100% { + background-color: rgba(0, 0, 0, 0); + } + ` + +const Layout = styled.div<{ + dimensions: Dimensions + position: Position + lastStep?: boolean + noPadding?: boolean +}>` + pointer-events: none; + position: fixed; + top: ${({ position: { top }, noPadding }) => (noPadding ? top : top - 10)}px; + left: ${({ position: { left }, noPadding }) => (noPadding ? left : left - 10)}px; + z-index: 6; + + width: ${({ dimensions: { width }, noPadding }) => (noPadding ? width : width + 20)}px; + height: ${({ dimensions: { height }, noPadding }) => (noPadding ? height : height + 20)}px; + + padding: ${({ noPadding }) => (noPadding ? '0' : '10px')}; + + border-radius: 10px; + box-shadow: rgba(0, 0, 0, 0.66) 0px 0px 0px 10000px; + + animation: ${({ lastStep }) => (lastStep ? FadeOut : BGFadeIn)} 0.2s ease-in forwards; +` + +const Hint = styled.div<{ + pageWidth: number + dimensions: Dimensions + childPosition: Position + relativePosition: HintPosition + lastStep?: boolean +}>` + position: fixed; + z-index: 6; + bottom: 0; + left: 0; + + width: calc(100% - 16px); + margin: 8px; + padding: 20px 30px; + + border-radius: 15px; + + color: #f4f4f4; + background: rgba(95, 109, 236, 0.6); + backdrop-filter: blur(6.5px); + + font-family: 'Montserrat'; + font-style: normal; + font-weight: 600; + font-size: 16px; + line-height: 20px; + + @media (min-width: 1000px) { + min-width: 250px; + width: 20%; + max-width: 600px; + + top: ${({ dimensions: { height }, childPosition: { top }, relativePosition }) => { + switch (relativePosition) { + case 'bottom': + return top + height + 10 + case 'top': + return top - 30 + default: + return top - 20 + } + }}px; + left: ${({ pageWidth, dimensions: { width }, childPosition: { left }, relativePosition }) => { + switch (relativePosition) { + case 'bottom': + case 'top': + return pageWidth - left > 250 ? left - 20 + 'px' : 'auto' + case 'left': + return 'auto' + case 'right': + return left + width + 10 + 'px' + } + }}; + right: ${({ pageWidth, dimensions: { width }, childPosition: { left, right }, relativePosition }) => { + switch (relativePosition) { + case 'bottom': + case 'top': + case 'right': + return pageWidth - left - width - 20 > 250 ? 'auto' : '10px' + case 'left': + return pageWidth - right + width + 10 + 'px' + } + }}; + transform: translateY(${({ relativePosition }) => (relativePosition === 'top' ? '-100%' : '0')}); + bottom: auto; + + margin: 10px; + animation: ${({ lastStep }) => (lastStep ? FadeOut : FadeIn)} 200ms ease-in forwards; + } +` + +const Description = styled.div` + margin-top: 20px; + font-size: 12px; + line-height: 140%; +` + +const Buttons = styled(Flex)` + margin-top: 30px; + left: 0; +` + +const Button = styled.button` + max-width: 26px; + aspect-ratio: 1; + background-color: transparent; + border: none; + padding: 0; + color: #f4f4f4; + + :disabled { + opacity: 0.5; + } + + :not(:disabled) { + cursor: pointer; + } + &:hover:not(:disabled) { + opacity: 0.7; + } + &:focus { + outline: none; + } +` diff --git a/src/shared/tutorial/model/index.ts b/src/shared/tutorial/model/index.ts new file mode 100644 index 000000000..a860f3755 --- /dev/null +++ b/src/shared/tutorial/model/index.ts @@ -0,0 +1,489 @@ +import { createMutation, createQuery } from '@farfetched/core' +import { createEffect, createEvent, createStore, sample } from 'effector' + +import { commonTutorialIds } from '@shared/api/tutorial/constants' +import { + TutorialData, + callUserInteraction, + changeTutorialState, + clear, + completeModule, + getUserTutorials, + initializeTutorials, + rerunModule, + resetTutorial, + syncModules, +} from '@shared/api/tutorial/tutorial-api' +import { TUTORIAL_HASH, TUTORIAL_PROGRESS, TUTORIAL_PROGRESS_DATE, TUTORIAL_PROGRESS_HASH } from '@shared/consts' +import { stringToHash } from '@shared/lib/stringToHash' +import { getKeys } from '@shared/lib/typescript/getKeys' +import { userModel } from '@shared/session' +import { popUpMessageModel } from '@shared/ui/pop-up-message' + +import { Module, Modules, TutorialId, TutorialRoles } from '../../api/tutorial/types' +import { ModuleData, createTutorials } from '../tutorials' + +const tutorialEnabled = createEvent() +const setHeroVisited = createEvent() +const setTutorialState = createEvent() +const increasedInteractions = createEvent() +const moduleCompleted = createEvent() +const moduleRestarted = createEvent() +const setCurrentTutorial = createEvent() +const nextStep = createEvent() +const prevStep = createEvent() +const resetStep = createEvent() +const getTutorialData = createEvent() +const clearProgress = createEvent() +const initialized = createEvent() + +const saveProgressLocally = createEffect(({ tutorials, hash }: { tutorials: TutorialData; hash: number }) => { + localStorage.setItem(TUTORIAL_PROGRESS, JSON.stringify(tutorials)) + localStorage.setItem(TUTORIAL_PROGRESS_DATE, new Date().toISOString()) + if (hash) localStorage.setItem(TUTORIAL_PROGRESS_HASH, hash.toString()) +}) + +const $tutorialState = createStore(false) + .on(setTutorialState, (_, value) => value) + .reset(userModel.events.logout) +const $heroVisited = createStore(false) + .on(setHeroVisited, (_, value) => { + localStorage.setItem('heroVisited', String(value)) + return value + }) + .reset(userModel.events.logout) +const $currentModule = createStore(null) + .on(moduleCompleted, (module) => ({ ...module, completed: true }) as Module) + .reset(userModel.events.logout) + +const $currentStep = createStore(0).reset(resetStep).reset(userModel.events.logout) +const $tutorials = createStore(null) + .on(moduleCompleted, (state, id) => { + if (!state) return null + const tutorials = { + ...state, + [id]: { + ...state[id], + completed: true, + }, + } + return tutorials + }) + .reset(userModel.events.logout) +const $interactions = createStore(0).reset(userModel.events.logout) + +const setRoles = createEvent() +const $roles = createStore([]) + .on(setRoles, (_, roles) => roles) + .reset(userModel.events.logout) + +const checkHash = createEffect(async () => { + const hash = stringToHash(JSON.stringify(commonTutorialIds)) + + const oldHash = localStorage.getItem(TUTORIAL_HASH) + + if (hash !== Number(oldHash)) throw new Error('hash changed') +}) + +const $userTutorialsData = createStore(null).reset(userModel.events.logout) + +sample({ + clock: userModel.events.authenticated, + source: userModel.stores.userGuid, + filter: Boolean, + target: [setRoles.prepend(() => []), checkHash], +}) + +sample({ + clock: checkHash.done, + target: getTutorialData, +}) + +sample({ + clock: checkHash.fail, + target: initialized.prepend(() => [...commonTutorialIds]), +}) +sample({ + clock: $roles, + fn: (roles) => createTutorials(roles), + target: $userTutorialsData, +}) + +sample({ + clock: nextStep, + source: { + currentStep: $currentStep, + currentModule: $currentModule, + }, + fn: ({ currentStep, currentModule }) => { + if (!currentModule) return 0 + const nextStep = currentStep + 1 + if (currentModule?.steps.length <= nextStep) return 0 + return nextStep + }, + target: $currentStep, +}) +sample({ + clock: prevStep, + source: { + currentStep: $currentStep, + currentModule: $currentModule, + }, + fn: ({ currentStep, currentModule }) => { + if (!currentModule) return 0 + const prevStep = currentStep - 1 + if (prevStep < 0) return currentStep + return prevStep + }, + target: $currentStep, +}) +sample({ + clock: setCurrentTutorial, + source: $tutorials, + fn: (tutorials, id) => { + if (!tutorials || !id) return null + const tutorial = tutorials[id] + return tutorial ?? null + }, + target: $currentModule, +}) + +sample({ + clock: moduleCompleted, + target: resetStep, +}) + +const rerunModuleMutation = createMutation({ + handler: rerunModule, +}) + +sample({ + clock: moduleRestarted, + target: rerunModuleMutation.start, +}) + +sample({ + clock: moduleRestarted, + source: $tutorials, + fn: (tutorials, id) => { + if (!tutorials || !id) return null + return { ...tutorials, [id]: { ...tutorials[id], completed: false } } + }, + target: $tutorials, +}) +sample({ + clock: rerunModuleMutation.$succeeded, + source: { id: rerunModuleMutation.__.$latestParams, tutorials: $tutorials }, + filter: Boolean, + fn: ({ id, tutorials }) => { + if (!tutorials || !id) return null + return { ...tutorials, [id]: { ...tutorials[id], completed: false } } + }, + target: $tutorials, +}) + +sample({ + clock: rerunModuleMutation.$failed, + fn: () => ({ + message: 'Не удалось перезапустить модуль', + type: 'failure' as const, + }), + target: popUpMessageModel.events.evokePopUpMessage, +}) + +const getTutorialDataQuery = createQuery({ + handler: getUserTutorials, +}) + +sample({ + clock: getTutorialData, + fn: () => Boolean(localStorage.getItem('heroVisited')), + target: setHeroVisited, +}) + +sample({ + clock: getTutorialData, + target: getTutorialDataQuery.start, +}) + +sample({ + clock: getTutorialDataQuery.finished.success, + fn: ({ result: { tutorialState } }) => tutorialState, + target: $tutorialState, +}) + +sample({ + clock: getTutorialDataQuery.finished.success, + fn: ({ result: { tutorials, hash } }) => ({ tutorials, hash }), + target: saveProgressLocally, +}) +sample({ + clock: getTutorialDataQuery.finished.failure, + target: [setTutorialState.prepend(() => false), setHeroVisited.prepend(() => true)], +}) + +sample({ + clock: getTutorialDataQuery.finished.success, + source: $userTutorialsData, + fn: (userTutorials, { result: { tutorials } }) => { + if (!userTutorials || !tutorials) return null + + return tutorials.reduce((acc, tutorial) => { + return { + ...acc, + [tutorial.id]: { + ...userTutorials[tutorial.id], + ...tutorial, + }, + } + }, {} as Modules) + }, + target: $tutorials, +}) + +sample({ + clock: $userTutorialsData, + source: $tutorials, + fn: (tutorials, userTutorials) => { + if (!userTutorials || !tutorials) return null + + const newTutorials = { ...tutorials } + getKeys(tutorials).forEach((key) => { + newTutorials[key] = { + ...tutorials[key], + ...userTutorials[key], + } + }) + + return newTutorials + }, + target: $tutorials, +}) + +sample({ + clock: getTutorialDataQuery.finished.success, + fn: ({ result: { interactions } }) => interactions, + target: $interactions, +}) +const updatedM = createMutation({ + handler: initializeTutorials, +}) + +sample({ + clock: initialized, + target: updatedM.start, +}) + +sample({ + clock: updatedM.finished.success, + source: updatedM.__.$latestParams, + fn: (req) => localStorage.setItem(TUTORIAL_HASH, stringToHash(JSON.stringify(req)).toString()), + target: getTutorialDataQuery.start, +}) + +const callInteractionMutation = createMutation({ + handler: callUserInteraction, +}) + +sample({ + clock: increasedInteractions, + target: callInteractionMutation.start, +}) + +const changeTutorialStateMutation = createMutation({ + handler: changeTutorialState, +}) + +sample({ + clock: tutorialEnabled, + target: changeTutorialStateMutation.start, +}) + +sample({ + clock: changeTutorialStateMutation.$succeeded, + source: changeTutorialStateMutation.__.$latestParams, + filter: Boolean, + target: $tutorialState, +}) +sample({ + clock: changeTutorialStateMutation.$succeeded, + target: getTutorialDataQuery.start, +}) + +sample({ + clock: changeTutorialStateMutation.$failed, + fn: () => ({ + message: 'Не удалось сохранить данные о прохождении', + type: 'failure' as const, + }), + target: popUpMessageModel.events.evokePopUpMessage, +}) +const resetTutorialMutation = createMutation({ + handler: resetTutorial, +}) + +sample({ + clock: clearProgress, + target: resetTutorialMutation.start, +}) + +const clearLocalProgressFx = createEffect(() => { + localStorage.removeItem(TUTORIAL_PROGRESS_HASH) + localStorage.removeItem(TUTORIAL_PROGRESS) + localStorage.removeItem(TUTORIAL_PROGRESS_DATE) +}) + +sample({ + clock: clearProgress, + target: clearLocalProgressFx, +}) + +const clearAll = createEvent() +const clearMut = createMutation({ + handler: clear, +}) +sample({ + clock: clearAll, + target: clearMut.start, +}) + +sample({ + clock: clearMut.$succeeded, + fn: () => { + localStorage.clear() + }, + target: userModel.events.logout, +}) + +sample({ + clock: resetTutorialMutation.$succeeded, + target: getTutorialDataQuery.start, +}) + +sample({ + clock: resetTutorialMutation.$succeeded, + fn: () => ({ + message: 'Прогресс успешно сброшен', + type: 'success' as const, + }), + target: popUpMessageModel.events.evokePopUpMessage, +}) + +sample({ + clock: resetTutorialMutation.$failed, + fn: () => ({ + message: 'Не удалось сбросить данные о прохождении', + type: 'failure' as const, + }), + target: popUpMessageModel.events.evokePopUpMessage, +}) + +const completeModuleMutation = createMutation({ + handler: completeModule, +}) + +const sync = createEvent<{ tutorials: TutorialData; date: string }>() +const syncMutation = createMutation({ + handler: syncModules, +}) + +sample({ + clock: getTutorialDataQuery.finished.success, + filter: ({ result: { hash } }) => + !!localStorage.getItem(TUTORIAL_PROGRESS_HASH) && + !!localStorage.getItem(TUTORIAL_PROGRESS) && + hash !== Number(localStorage.getItem(TUTORIAL_PROGRESS_HASH)), + fn: (): { tutorials: TutorialData; date: string } => { + const progress = JSON.parse(localStorage.getItem(TUTORIAL_PROGRESS) || '') + const date = localStorage.getItem(TUTORIAL_PROGRESS_DATE) || '' + return { tutorials: progress, date } + }, + target: sync, +}) + +sample({ + clock: sync, + target: syncMutation.start, +}) + +sample({ + clock: syncMutation.finished.success, + fn: ({ result }) => result, + target: saveProgressLocally, +}) +sample({ + clock: syncMutation.finished.success, + source: $tutorials, + filter: (_, { result: { tutorials } }) => Boolean(tutorials?.length), + fn: (userTutorials, { result: { tutorials } }) => { + if (!userTutorials || !tutorials) return null + + return tutorials.reduce((acc, tutorial) => { + return { + ...acc, + [tutorial.id]: { + ...userTutorials[tutorial.id], + ...tutorial, + }, + } + }, {} as Modules) + }, + target: $tutorials, +}) + +sample({ + clock: moduleCompleted, + target: completeModuleMutation.start, +}) + +sample({ + clock: completeModuleMutation.$succeeded, + target: getTutorialDataQuery.start, +}) + +sample({ + clock: completeModuleMutation.$failed, + fn: () => ({ + message: 'Не удалось сохранить данные о прохождении', + type: 'failure' as const, + }), + target: popUpMessageModel.events.evokePopUpMessage, +}) + +sample({ + clock: userModel.events.logout, + target: [getTutorialDataQuery.reset], +}) + +export const stores = { + tutorialState: $tutorialState, + currentModule: $currentModule, + currentStep: $currentStep, + tutorials: $tutorials, + heroVisited: $heroVisited, + interactions: $interactions, + roles: $roles, + userTutorials: $userTutorialsData, +} + +export const events = { + tutorialEnabled, + setHeroVisited, + moduleCompleted, + moduleRestarted, + setCurrentTutorial, + getTutorialData, + nextStep, + prevStep, + clearProgress, + resetStep, + increasedInteractions, + initialized, + clearAll, + sync, + setRoles, +} + +export const mutations = { + syncMutation, +} diff --git a/src/shared/tutorial/tutorials.ts b/src/shared/tutorial/tutorials.ts new file mode 100644 index 000000000..5d6bcff48 --- /dev/null +++ b/src/shared/tutorial/tutorials.ts @@ -0,0 +1,280 @@ +import { Module, TutorialId, TutorialRoles, TutorialStep } from '../api/tutorial/types' + +export type ModuleData = { + [id in TutorialId]: Pick +} + +export const createTutorials = (roles: TutorialRoles): ModuleData => { + return { + // sidebar: { + // path: '', + // name: 'Боковая панель', + // steps: [ + // { + // title: 'Боковая панель', + // description: 'Боковая панель всегда находится в поле зрения и содержит основную навигацию в приложении', + // }, + // { + // title: 'Профиль', + // description: 'Для доступа к профилю пользователя, кликните на иконку профиля в правом верхнем углу', + // }, + // { + // title: 'Поиск', + // description: + // 'Используйте поле поиска в верхней части страницы для быстрого поиска информации или контента', + // }, + // { + // title: 'Меню быстрого доступа', + // description: 'В этом меню вы можете получить быстрый доступ к часто используемым функциям', + // }, + // { + // title: 'Переход на главную', + // description: 'Нажмите на логотип, чтобы мгновенно перейти на главную страницу', + // }, + // ], + // }, + home: { + index: 0, + path: '/home', + name: 'Главная', + steps: [ + { + title: 'Главная', + description: 'На главной странице собрана основная информация приложения', + }, + { + title: 'Поиск', + description: 'Здесь есть поиск для быстрого поиска информации, раздела или контента', + }, + { + title: 'Меню быстрого доступа', + description: 'А также меню быстрого доступа к разделам. Его можно изменить или убрать в настройках', + }, + { + title: 'Уведомления', + description: + 'Просмотрите уведомления о важных событиях или сообщениях, щелкнув по иконке колокольчика. Уведомления можно выключить или изменить в настройках', + }, + ...(roles.includes('has widgets') + ? [ + { + title: 'Виджеты', + description: + 'Используйте виджеты для быстрого доступа к информации или функциям, размещенным на главной странице. Виджеты можно изменить в настройках', + }, + ] + : []), + ], + roles: ['stud', 'staff'], + }, + chat: { + index: 1, + path: '/chat', + name: 'Сообщения', + steps: [ + { + title: 'Список диалогов', + description: 'Здесь будут указаны все ваши чаты', + }, + { + title: 'Новое сообщение', + description: 'Чтобы создать новый чат, нажмите на эту кнопку', + }, + { + title: 'Поиск', + description: 'Для быстрого поиска диалога можно воспользоваться поиском', + }, + ], + roles: ['stud', 'staff'], + }, + payments: { + index: 2, + path: '/payments', + name: 'Договоры и оплаты', + steps: initPaymentsSteps(roles), + roles: initPaymentsRoles(roles), + }, + applications: { + index: 3, + path: '/applications', + name: 'Цифровые сервисы', + steps: [ + { + title: 'Цифровые сервисы', + description: 'В этом разделе можно заказать необходимую справку, подать заявление или запрос', + }, + { + title: 'Цифровые сервисы', + description: + 'В таблице можно посмотреть статус заявления и скачать файлы документов. Доступны поиск и фильтрация по запросам', + }, + { + title: 'Цифровые сервисы', + description: + 'В колонке «Структурное подразделение, адрес» указывается название подразделения и адрес, куда необходимо приехать за готовым документом', + }, + { + title: 'Подать заявление', + description: 'Чтобы подать заявление, нажмите на эту кнопку', + }, + ], + roles: ['stud', 'staff'], + }, + // 'medical-certificate': { + // path: '/medical-certificate', + // name: 'Предоставление медицинских справок', + // steps: [ + // ], + // }, + // schedule: { + // path: '/schedule', + // name: 'Расписание', + // steps: [ + // ], + // }, + 'stud-project-activity': { + index: 4, + path: '/project-activity', + name: 'Проектная деятельность', + steps: [ + { + title: 'Проектная деятельность', + description: 'В этом разделе можно узнать информацию о твоем проекте по проектной деятельности', + }, + { + title: 'Проект', + description: 'Здесь указано название проекта и полезная информация', + }, + { + title: 'Текущий семестр', + description: 'Раздел с текущим семестром. Для зачета нужно набрать 60 баллов', + }, + { + title: 'Предыдущий семестр', + description: + 'Если за предыдущий семестр стоит "не зачтено", то нужно донабрать баллы в текущем семестре', + }, + { + title: 'Итого', + description: 'В последнем разделе посчитано итоговое количество баллов и предварительная оценка', + }, + ], + roles: ['stud'], + }, + settings: { + index: 5, + path: '/configurations', + name: 'Настройки', + steps: [ + { + title: 'Настройки', + description: + 'Мы стараемся предоставить широкий набор настроек для персонализации Личного кабинета. В этом разделе можно все настроить под себя', + }, + { + title: 'Аккаунт', + description: + 'В этом разделе можно обновить контактную информацию, изменить фото профиля, поменять пароль или включить синхронизацию настроек между устройствами', + }, + { + title: 'Меню', + description: 'В этом разделе можно изменить список разделов для быстрого доступа с боковой панели', + }, + { + title: 'Внешний вид', + description: + 'Пока что в этом разделе можно изменить только визуальную тему Личного кабинета, но скоро появится больше возможностей для персонализации', + }, + { + title: 'Главная страница', + description: + 'Здесь можно настроить разделы быстрого доступа домашней страницы и отображение виджетов', + }, + { + title: 'Уведомления', + description: 'В этом разделе можно настроить получение и отображение уведомлений по темам', + }, + { + title: 'Обучение', + description: + 'В этом разделе можно выключить обучение, посмотреть прогресс обучения или запустить программу для выбранного раздела', + }, + ], + roles: ['stud', 'staff'], + }, + // 'stud-physical-education': { + // path: '/physical-education/student', + // name: 'Физическая культура', + // steps: [ + // { + // title: 'Чат', + // description: 'В этом разделе можно общаться в чате', + // }, + // { + // title: 'Чат', + // description: 'В этом разделе можно общаться в чате', + // }, + // ], + // }, + } +} + +const initPaymentsSteps = (roles: TutorialRoles): TutorialStep[] => { + const step2 = 'Тут отображается задолженность или переплата по договору' + const step3 = + 'Чтобы оплатить договор, нужно нажать на кнопку и отсканировать QR-код в банковском приложении. Если задолженности нет, то будет отображаться зеленая галочка' + + return [ + ...(roles.includes('education') && roles.includes('dormitory') + ? [ + { + title: 'Договоры и оплаты', + description: + 'В этом разделе можно посмотреть задолженность и другую информацию по договору. У тебя есть договор на общежитие и на обучение. Их можно переключать в этом меню', + }, + { + title: 'Задолженность', + description: step2, + }, + { + title: 'Оплата', + description: step3, + }, + ] + : roles.includes('education') + ? [ + { + title: 'Договоры и оплаты', + description: 'В этом разделе можно посмотреть задолженность и другую информацию по договору', + }, + { + title: 'Задолженность', + description: step2, + }, + { + title: 'Оплата', + description: step3, + }, + ] + : roles.includes('dormitory') + ? [ + { + title: 'Договоры и оплаты', + description: 'В этом разделе можно посмотреть задолженность и другую информацию по договору', + }, + { + title: 'Задолженность', + description: step2, + }, + { + title: 'Оплата', + description: step3, + }, + ] + : []), + ] +} +const initPaymentsRoles = (roles: TutorialRoles) => { + if (roles.includes('education') || roles.includes('dormitory')) return ['stud', 'staff'] + return [] +} diff --git a/src/shared/tutorial/types.ts b/src/shared/tutorial/types.ts new file mode 100644 index 000000000..30e92178d --- /dev/null +++ b/src/shared/tutorial/types.ts @@ -0,0 +1,24 @@ +import { TutorialId } from '@shared/api/tutorial/types' + +export type HintPosition = 'right' | 'bottom' | 'top' | 'left' + +export type Dimensions = { width: number; height: number } + +export type Position = { top: number; left: number; right: number; bottom: number } + +export interface TutorialWrapperProps { + tutorialModule?: { + id: TutorialId + step: number | number[] + params?: { + noPadding?: boolean + position?: HintPosition + + // TODO: implement + inside?: boolean + widthMatchParent?: boolean + heightMatchParent?: boolean + noScroll?: boolean + } + } +} diff --git a/src/shared/tutorial/ui/empty-div.tsx b/src/shared/tutorial/ui/empty-div.tsx new file mode 100644 index 000000000..2af3016fb --- /dev/null +++ b/src/shared/tutorial/ui/empty-div.tsx @@ -0,0 +1,8 @@ +import styled from 'styled-components' + +export const EmptyDiv = styled.div<{ height: number }>` + width: 100%; + height: ${({ height }) => height}vh; + + transition: height 0.2s ease-out; +` diff --git a/src/shared/tutorial/ui/fade-out-animation.ts b/src/shared/tutorial/ui/fade-out-animation.ts new file mode 100644 index 000000000..186e9fed6 --- /dev/null +++ b/src/shared/tutorial/ui/fade-out-animation.ts @@ -0,0 +1,9 @@ +import { keyframes } from 'styled-components' + +export const FadeOut = keyframes` + 0% { + opacity: 1; + } + 100% { + opacity: 0; + }` diff --git a/src/shared/tutorial/ui/skip-button.tsx b/src/shared/tutorial/ui/skip-button.tsx new file mode 100644 index 000000000..60b14ba6b --- /dev/null +++ b/src/shared/tutorial/ui/skip-button.tsx @@ -0,0 +1,64 @@ +import React from 'react' +import { IoMdClose } from 'react-icons/io' + +import { useUnit } from 'effector-react' +import styled from 'styled-components' + +import useCurrentDevice from '@shared/ui/hooks/use-current-device' + +import { tutorialModel } from '..' + +export const SkipButton = ({ setAnimation }: { setAnimation: (value: 'in' | 'out' | 'removed') => void }) => { + const module = useUnit(tutorialModel.stores.currentModule) + const { isMobile, isTablet } = useCurrentDevice() + if (!module) return null + if (isMobile || isTablet) + return ( + { + setAnimation('out') + + setTimeout(() => { + tutorialModel.events.moduleCompleted(module.id) + }, 200) + }} + > + + + ) + + return ( + { + setAnimation('out') + + setTimeout(() => { + tutorialModel.events.moduleCompleted(module.id) + }, 200) + }} + > + Пропустить {'>'} + + ) +} + +const Button = styled.button` + position: absolute; + color: #ffffff; + z-index: 6; + background: transparent; + border: none; + font-size: 18px; + cursor: pointer; +` + +const MobileButton = styled(Button)` + right: 1.5em; + top: 0.5em; + padding: 1rem; +` + +const DesktopButton = styled(Button)` + right: 2.5em; + bottom: 1.5em; +` diff --git a/src/shared/tutorial/ui/tutorial-action-plate.tsx b/src/shared/tutorial/ui/tutorial-action-plate.tsx new file mode 100644 index 000000000..b7cdeffb5 --- /dev/null +++ b/src/shared/tutorial/ui/tutorial-action-plate.tsx @@ -0,0 +1,34 @@ +import React, { useEffect } from 'react' +import { BsArrowUpRightCircleFill } from 'react-icons/bs' + +import { useUnit } from 'effector-react' + +import { BrightPlate } from '@shared/ui/bright-plate/bright-plate' + +import { tutorialModel } from '..' + +export const TutorialActionPlate = () => { + const [tutorialState, setHeroVisited, increasedInteractions, interactions] = useUnit([ + tutorialModel.stores.tutorialState, + tutorialModel.events.setHeroVisited, + tutorialModel.events.increasedInteractions, + tutorialModel.stores.interactions, + ]) + + useEffect(() => { + if (tutorialState === null && interactions < 5) { + increasedInteractions() + } + }, []) + + return ( + { + setHeroVisited(false) + }} + show={tutorialState === null && interactions < 5} + > + Начать обучение + + ) +} diff --git a/src/shared/tutorial/ui/tutorial-hero.tsx b/src/shared/tutorial/ui/tutorial-hero.tsx new file mode 100644 index 000000000..eb74ae09f --- /dev/null +++ b/src/shared/tutorial/ui/tutorial-hero.tsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react' + +import { useUnit } from 'effector-react' +import styled from 'styled-components' + +import { Colors } from '@shared/consts' +import { userModel } from '@shared/session' +import { Button } from '@shared/ui/button' +import useCurrentDevice from '@shared/ui/hooks/use-current-device' +import TrainingPic from '@shared/ui/images/tutorial-picture.jpg' + +import * as model from '../model' +import { Buttons, Hero, Text, Title, TutorialHeroLayout, TutorialHeroText } from './welcome-text' + +export const TutorialHero = () => { + const [isDeleted, setIsDeleted] = useState(false) + const [heroVisited, tutorialState, tutorialEnabled, setHeroVisited] = useUnit([ + model.stores.heroVisited, + model.stores.tutorialState, + model.events.tutorialEnabled, + model.events.setHeroVisited, + ]) + const { + data: { user }, + } = userModel.selectors.useUser() + + const { isMobile, isTablet } = useCurrentDevice() + if (tutorialState !== null || (heroVisited && !isDeleted)) return null + + return ( + setHeroVisited(true)}> + e.stopPropagation()} deleted={isDeleted}> + + + {user?.user_status === 'staff' ? ( + <> + + В Личном кабинете есть обучение, которое знакомит с основными разделами и рассказывает + о возможностях Личного кабинета. + + + Следить за прогрессом прохождения можно в настройках Личного кабинета. Также + в настройках можно напрямую запусить обучение по разделу. + + {!isMobile && !isTablet && ( + + Связь со студентами и сотрудниками, доступ к корпоративным контактным данным, + цифровые сервисы - все в одном месте. + + )} + + ) : ( + <> + Привет! + + В Личном кабинете есть обучение. Пройдя его, ты познакомишься с основными разделами + и возможностями Личного кабинета, которые сделают твое обучение удобнее. + + + Следить за прогрессом прохождения можно в настройках Личного кабинета. Также + в настройках можно напрямую запустить обучение по разделу. + + {!isMobile && !isTablet && ( + + Отслеживай расписание, заказывай справки, проводи оплаты, общайся с сотрудниками, + преподавателями и студентами - все в одном месте. + + )} + + )} + + +