diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 7d1076afea1fb64ca5faf82d7f3571398707174a..0000000000000000000000000000000000000000 --- a/.eslintignore +++ /dev/null @@ -1,8 +0,0 @@ -.angular/ -.git/ -.idea/ -.vscode/ -build/ -coverage/ -dist/ -node_modules/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 9ba7297321c400ed682d6a466502ea05f5cdb4c1..0000000000000000000000000000000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "root": true, - "ignorePatterns": ["projects/**/*"], - "overrides": [ - { - "files": ["*.ts"], - "parserOptions": { - "project": ["tsconfig.json"], - "createDefaultProgram": true - }, - "extends": ["plugin:@angular-eslint/recommended", "plugin:@angular-eslint/template/process-inline-templates"], - "rules": { - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "app", - "style": "camelCase" - } - ], - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "app", - "style": "kebab-case" - } - ] - } - }, - { - "files": ["*.html"], - "extends": ["plugin:@angular-eslint/template/recommended"], - "rules": {} - } - ] -} diff --git a/.gitignore b/.gitignore index 16d341382ee247137cbff20b0f45ab9a1b5d6aa6..b9bb85dc0036123bb380b6fee60b128d2f6b70de 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,9 @@ api/db.json # Documentation generated with compodoc documentation +CHANGELOG.md + +package-lock.json + # External libs /projects diff --git a/.prettierrc b/.prettierrc index 154ffae7c56d124b37eb3ed98dcd465e9075f4a8..8d3dfb047c8c168b52ced5119a342a3ff2339806 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,20 +1,8 @@ { - "printWidth": 128, - "tabWidth": 2, + "printWidth": 120, + "singleQuote": true, "useTabs": false, + "tabWidth": 2, "semi": true, - "singleQuote": true, - "quoteProps": "as-needed", - "jsxSingleQuote": true, - "trailingComma": "none", - "bracketSpacing": true, - "bracketSameLine": true, - "arrowParens": "always", - "requirePragma": false, - "insertPragma": false, - "proseWrap": "preserve", - "htmlWhitespaceSensitivity": "ignore", - "vueIndentScriptAndStyle": false, - "endOfLine": "lf", - "embeddedLanguageFormatting": "auto" + "bracketSpacing": true } diff --git a/Dockerfile b/Dockerfile index 0ff4ce3b51ae5254dc74edbc6da0bc7246a5a3b8..ed43a52e792b667e1a3e60c707bc8f8ddbabecfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,6 @@ RUN npm install --silent COPY angular.json . COPY tsconfig.json . COPY tsconfig.app.json . -COPY tsconfig.base.json . COPY ngsw-config.json . COPY /nginx/nginx.conf . COPY /src ./src diff --git a/angular.json b/angular.json index 65ced314becae9fee01332c27255dbb26eccb17d..432e248a537d676d6ff0607603719794c3a8acda 100644 --- a/angular.json +++ b/angular.json @@ -35,10 +35,7 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "localize": true, - "allowedCommonJsDependencies": [ - "lodash", - "leaflet.locatecontrol" - ], + "allowedCommonJsDependencies": ["lodash", "leaflet.locatecontrol"], "assets": [ "src/favicon.ico", "src/assets", @@ -98,14 +95,10 @@ "ngswConfigPath": "ngsw-config.json" }, "fr": { - "localize": [ - "fr" - ] + "localize": ["fr"] }, "en": { - "localize": [ - "en" - ] + "localize": ["en"] } }, "defaultConfiguration": "" @@ -141,14 +134,8 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets", - "src/manifest.webmanifest" - ], - "styles": [ - "src/styles.scss" - ], + "assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"], + "styles": ["src/styles.scss"], "scripts": [] } }, diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index e307628da37491a44b858c4336f005f9f73174c7..0000000000000000000000000000000000000000 --- a/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - preset: 'jest-preset-angular', - setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], - moduleNameMapper: { - '@gouvfr-anct/(.*)': '<rootDir>/dist/@gouvfr-anct/$1', - }, -}; diff --git a/jest.setup.ts b/jest.setup.ts deleted file mode 100644 index dfd9199e4bd73c6535a527e79252e1a9c641d592..0000000000000000000000000000000000000000 --- a/jest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import 'jest-preset-angular/jest-preset'; diff --git a/package-lock.json b/package-lock.json index 075d640612bf912ff00b8eea9c0f2ed03e20844e..3495f16125622b664b3dea65d77776a071dc05a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,13 +116,6 @@ } } }, - "@esbuild/linux-loong64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", - "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", - "dev": true, - "optional": true - }, "@jest/environment": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", @@ -310,146 +303,6 @@ "esbuild-windows-arm64": "0.15.7" } }, - "esbuild-android-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", - "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", - "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", - "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", - "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", - "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", - "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", - "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", - "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", - "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", - "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", - "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", - "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", - "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", - "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", - "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", - "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", - "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", - "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", - "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", - "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", - "dev": true, - "optional": true - }, "escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -657,12 +510,6 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -865,29 +712,6 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1402.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.2.tgz", - "integrity": "sha512-ICcK7OKViMhLkj4btnH/8nv0wjxuKchT/LDN6jfb9gUYUuoon190q0/L/U6ORDwvmjD6sUTurStzOxjuiS0KIg==", - "dev": true, - "requires": { - "@angular-devkit/core": "14.2.2", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "14.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.2.tgz", - "integrity": "sha512-ofDhTmJqoAkmkJP0duwUaCxDBMxPlc+AWYwgs3rKKZeJBb0d+tchEXHXevD5bYbbRfXtnwM+Vye2XYHhA4nWAA==", - "dev": true, - "requires": { - "ajv": "8.11.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.1.0", - "rxjs": "6.6.7", - "source-map": "0.7.4" - } - }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -1092,6 +916,13 @@ "to-fast-properties": "^2.0.0" } }, + "@esbuild/linux-loong64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz", + "integrity": "sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A==", + "dev": true, + "optional": true + }, "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -1112,18 +943,6 @@ "debug": "4" } }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -1169,6 +988,152 @@ "esbuild-windows-arm64": "0.15.5" } }, + "esbuild-android-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz", + "integrity": "sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz", + "integrity": "sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz", + "integrity": "sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz", + "integrity": "sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz", + "integrity": "sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz", + "integrity": "sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz", + "integrity": "sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz", + "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz", + "integrity": "sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz", + "integrity": "sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz", + "integrity": "sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz", + "integrity": "sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz", + "integrity": "sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz", + "integrity": "sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz", + "integrity": "sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz", + "integrity": "sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz", + "integrity": "sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA==", + "dev": true, + "optional": true + }, + "esbuild-wasm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.5.tgz", + "integrity": "sha512-lTJOEKekN/4JI/eOEq0wLcx53co2N6vaT/XjBz46D1tvIVoUEyM0o2K6txW6gEotf31szFD/J1PbxmnbkGlK9A==", + "dev": true + }, + "esbuild-windows-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz", + "integrity": "sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz", + "integrity": "sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz", + "integrity": "sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA==", + "dev": true, + "optional": true + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1198,12 +1163,6 @@ "debug": "4" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "json5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", @@ -1252,9 +1211,9 @@ } }, "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-support": { @@ -1265,14 +1224,6 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } } } @@ -1287,47 +1238,6 @@ "rxjs": "6.6.7" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1402.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.2.tgz", - "integrity": "sha512-ICcK7OKViMhLkj4btnH/8nv0wjxuKchT/LDN6jfb9gUYUuoon190q0/L/U6ORDwvmjD6sUTurStzOxjuiS0KIg==", - "dev": true, - "requires": { - "@angular-devkit/core": "14.2.2", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "14.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.2.tgz", - "integrity": "sha512-ofDhTmJqoAkmkJP0duwUaCxDBMxPlc+AWYwgs3rKKZeJBb0d+tchEXHXevD5bYbbRfXtnwM+Vye2XYHhA4nWAA==", - "dev": true, - "requires": { - "ajv": "8.11.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.1.0", - "rxjs": "6.6.7", - "source-map": "0.7.4" - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -1337,12 +1247,6 @@ "tslib": "^1.9.0" } }, - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -1418,60 +1322,14 @@ "rxjs": "6.6.7" }, "dependencies": { - "@angular-devkit/core": { - "version": "14.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.2.tgz", - "integrity": "sha512-ofDhTmJqoAkmkJP0duwUaCxDBMxPlc+AWYwgs3rKKZeJBb0d+tchEXHXevD5bYbbRfXtnwM+Vye2XYHhA4nWAA==", - "dev": true, - "requires": { - "ajv": "8.11.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.1.0", - "rxjs": "6.6.7", - "source-map": "0.7.4" - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "magic-string": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", - "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true + "requires": { + "tslib": "^1.9.0" + } }, "tslib": { "version": "1.14.1", @@ -1603,12 +1461,6 @@ "lru-cache": "^6.0.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -1649,18 +1501,6 @@ "@typescript-eslint/visitor-keys": "5.36.2" } }, - "@typescript-eslint/type-utils": { - "version": "5.36.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz", - "integrity": "sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.36.2", - "@typescript-eslint/utils": "5.36.2", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, "@typescript-eslint/types": { "version": "5.36.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.36.2.tgz", @@ -1759,12 +1599,6 @@ "lru-cache": "^6.0.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -1922,12 +1756,6 @@ "lru-cache": "^6.0.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -2003,41 +1831,6 @@ "yargs": "17.5.1" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1402.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.2.tgz", - "integrity": "sha512-ICcK7OKViMhLkj4btnH/8nv0wjxuKchT/LDN6jfb9gUYUuoon190q0/L/U6ORDwvmjD6sUTurStzOxjuiS0KIg==", - "dev": true, - "requires": { - "@angular-devkit/core": "14.2.2", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "14.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.2.tgz", - "integrity": "sha512-ofDhTmJqoAkmkJP0duwUaCxDBMxPlc+AWYwgs3rKKZeJBb0d+tchEXHXevD5bYbbRfXtnwM+Vye2XYHhA4nWAA==", - "dev": true, - "requires": { - "ajv": "8.11.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.1.0", - "rxjs": "6.6.7", - "source-map": "0.7.4" - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -2068,12 +1861,6 @@ "has": "^1.0.3" } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -2085,15 +1872,6 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -2103,18 +1881,6 @@ "lru-cache": "^6.0.0" } }, - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -2389,15 +2155,6 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, - "magic-string": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.3.tgz", - "integrity": "sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -3260,9 +3017,9 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", "dev": true, "requires": { "regenerate": "^1.4.2" @@ -3320,9 +3077,9 @@ "dev": true }, "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true } } @@ -8537,13 +8294,6 @@ "universalify": "^2.0.0" } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8685,7 +8435,7 @@ "object-assign": "^4.1.1", "open": "8.2.1", "proxy-middleware": "^0.15.0", - "send": "^0.17.2", + "send": "^0.18.0", "serve-index": "^1.9.1" }, "dependencies": { @@ -8743,7 +8493,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } @@ -8757,26 +8507,6 @@ "websocket-driver": ">=0.5.1" } }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -8786,7 +8516,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, "open": { @@ -8800,6 +8530,12 @@ "is-wsl": "^2.2.0" } }, + "proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8810,37 +8546,48 @@ } }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true } } }, @@ -9038,9 +8785,9 @@ "dev": true }, "@esbuild/linux-loong64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz", - "integrity": "sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", + "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", "dev": true, "optional": true }, @@ -9172,21 +8919,6 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, - "@gouvfr-anct/mediation-numerique": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@gouvfr-anct/mediation-numerique/-/mediation-numerique-0.0.17.tgz", - "integrity": "sha512-b36izQpeYWs4Qhx0mA62g893/kTFvP031LJpW6KyhxRhwhifAIDZ59Oh6FS7hkjTE0pUwJ9fx6+1aCWjZmq33Q==", - "requires": { - "tslib": "^2.3.0" - }, - "dependencies": { - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - } - } - }, "@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -10255,60 +9987,6 @@ "@angular-devkit/core": "14.2.2", "@angular-devkit/schematics": "14.2.2", "jsonc-parser": "3.1.0" - }, - "dependencies": { - "@angular-devkit/core": { - "version": "14.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.2.tgz", - "integrity": "sha512-ofDhTmJqoAkmkJP0duwUaCxDBMxPlc+AWYwgs3rKKZeJBb0d+tchEXHXevD5bYbbRfXtnwM+Vye2XYHhA4nWAA==", - "dev": true, - "requires": { - "ajv": "8.11.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.1.0", - "rxjs": "6.6.7", - "source-map": "0.7.4" - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@sinclair/typebox": { @@ -10526,9 +10204,9 @@ "dev": true }, "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", "dev": true, "requires": { "@types/body-parser": "*", @@ -10538,9 +10216,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.30", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", - "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", "dev": true, "requires": { "@types/node": "*", @@ -10611,16 +10289,6 @@ "@types/jasmine": "*" } }, - "@types/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ==", - "dev": true, - "requires": { - "jest-matcher-utils": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, "@types/jsdom": { "version": "16.2.15", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.15.tgz", @@ -10828,6 +10496,18 @@ "@typescript-eslint/visitor-keys": "5.37.0" } }, + "@typescript-eslint/type-utils": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz", + "integrity": "sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.37.0", + "@typescript-eslint/utils": "5.37.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, "@typescript-eslint/types": { "version": "5.37.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz", @@ -10914,12 +10594,6 @@ "lru-cache": "^6.0.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11031,12 +10705,6 @@ "lru-cache": "^6.0.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11065,41 +10733,41 @@ } }, "@typescript-eslint/type-utils": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz", - "integrity": "sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ==", + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz", + "integrity": "sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.37.0", - "@typescript-eslint/utils": "5.37.0", + "@typescript-eslint/typescript-estree": "5.36.2", + "@typescript-eslint/utils": "5.36.2", "debug": "^4.3.4", "tsutils": "^3.21.0" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.37.0.tgz", - "integrity": "sha512-F67MqrmSXGd/eZnujjtkPgBQzgespu/iCZ+54Ok9X5tALb9L2v3G+QBSoWkXG0p3lcTJsL+iXz5eLUEdSiJU9Q==", + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz", + "integrity": "sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.37.0", - "@typescript-eslint/visitor-keys": "5.37.0" + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/visitor-keys": "5.36.2" } }, "@typescript-eslint/types": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz", - "integrity": "sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA==", + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.36.2.tgz", + "integrity": "sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz", - "integrity": "sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==", + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz", + "integrity": "sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==", "dev": true, "requires": { - "@typescript-eslint/types": "5.37.0", - "@typescript-eslint/visitor-keys": "5.37.0", + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/visitor-keys": "5.36.2", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -11108,26 +10776,26 @@ } }, "@typescript-eslint/utils": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.37.0.tgz", - "integrity": "sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ==", + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.36.2.tgz", + "integrity": "sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.37.0", - "@typescript-eslint/types": "5.37.0", - "@typescript-eslint/typescript-estree": "5.37.0", + "@typescript-eslint/scope-manager": "5.36.2", + "@typescript-eslint/types": "5.36.2", + "@typescript-eslint/typescript-estree": "5.36.2", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/visitor-keys": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz", - "integrity": "sha512-Hp7rT4cENBPIzMwrlehLW/28EVCOcE9U1Z1BQTc8EA8v5qpr7GRGuG+U58V5tTY48zvUOA3KHvw3rA8tY9fbdA==", + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz", + "integrity": "sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==", "dev": true, "requires": { - "@typescript-eslint/types": "5.37.0", + "@typescript-eslint/types": "5.36.2", "eslint-visitor-keys": "^3.3.0" } }, @@ -11172,12 +10840,6 @@ "lru-cache": "^6.0.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11674,9 +11336,9 @@ }, "dependencies": { "ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -11959,9 +11621,9 @@ "dev": true }, "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -11986,13 +11648,13 @@ "dev": true }, "autoprefixer": { - "version": "10.4.9", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.9.tgz", - "integrity": "sha512-Uu67eduPEmOeA0vyJby5ghu1AAELCCNSsLAjK+lz6kYzNM5sqnBO36MqfsjhPjQF/BaJM5U/UuFYyl7PavY/wQ==", + "version": "10.4.10", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.10.tgz", + "integrity": "sha512-nMaiDARyp1e74c8IeAXkr+BmFKa8By4Zak7tyaNPF09Iu39WFpNXOWrVirmXjKr+5cOyERwvtbMOLYz6iBJYgQ==", "dev": true, "requires": { "browserslist": "^4.21.3", - "caniuse-lite": "^1.0.30001394", + "caniuse-lite": "^1.0.30001399", "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -12413,12 +12075,6 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -13606,9 +13262,9 @@ } }, "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true }, "cookie-signature": { @@ -14215,9 +13871,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, "detect-indent": { @@ -14441,9 +14097,9 @@ } }, "electron-to-chromium": { - "version": "1.4.248", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz", - "integrity": "sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg==", + "version": "1.4.249", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.249.tgz", + "integrity": "sha512-GMCxR3p2HQvIw47A599crTKYZprqihoBL4lDSAUmr7IYekXFK5t/WgEBrGJDCa2HWIZFQEkGuMqPCi05ceYqPQ==", "dev": true }, "emittery": { @@ -14513,6 +14169,12 @@ "ws": "~8.2.3" }, "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -14722,148 +14384,148 @@ } }, "esbuild-android-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz", - "integrity": "sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", + "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", "dev": true, "optional": true }, "esbuild-android-arm64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz", - "integrity": "sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", + "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", "dev": true, "optional": true }, "esbuild-darwin-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz", - "integrity": "sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", + "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", "dev": true, "optional": true }, "esbuild-darwin-arm64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz", - "integrity": "sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", + "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", "dev": true, "optional": true }, "esbuild-freebsd-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz", - "integrity": "sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", + "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", "dev": true, "optional": true }, "esbuild-freebsd-arm64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz", - "integrity": "sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", + "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", "dev": true, "optional": true }, "esbuild-linux-32": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz", - "integrity": "sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", + "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", "dev": true, "optional": true }, "esbuild-linux-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz", - "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", + "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", "dev": true, "optional": true }, "esbuild-linux-arm": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz", - "integrity": "sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", + "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", "dev": true, "optional": true }, "esbuild-linux-arm64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz", - "integrity": "sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", + "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", "dev": true, "optional": true }, "esbuild-linux-mips64le": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz", - "integrity": "sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", + "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", "dev": true, "optional": true }, "esbuild-linux-ppc64le": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz", - "integrity": "sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", + "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", "dev": true, "optional": true }, "esbuild-linux-riscv64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz", - "integrity": "sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", + "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", "dev": true, "optional": true }, "esbuild-linux-s390x": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz", - "integrity": "sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", + "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", "dev": true, "optional": true }, "esbuild-netbsd-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz", - "integrity": "sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", + "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", "dev": true, "optional": true }, "esbuild-openbsd-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz", - "integrity": "sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", + "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", "dev": true, "optional": true }, "esbuild-sunos-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz", - "integrity": "sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", + "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", "dev": true, "optional": true }, "esbuild-wasm": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.5.tgz", - "integrity": "sha512-lTJOEKekN/4JI/eOEq0wLcx53co2N6vaT/XjBz46D1tvIVoUEyM0o2K6txW6gEotf31szFD/J1PbxmnbkGlK9A==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.7.tgz", + "integrity": "sha512-CBtlw6nnCYuyD83yjZCi778nTZXJzvzomwaxwhkNMcOGDiD56/5uKQZI8FjxAH3vAV09hRb17oN3gmp+bKnguw==", "dev": true }, "esbuild-windows-32": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz", - "integrity": "sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", + "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", "dev": true, "optional": true }, "esbuild-windows-64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz", - "integrity": "sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", + "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", "dev": true, "optional": true }, "esbuild-windows-arm64": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz", - "integrity": "sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", + "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", "dev": true, "optional": true }, @@ -15171,12 +14833,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -15340,7 +14996,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, "event-emitter": { @@ -15484,38 +15140,6 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -15531,12 +15155,6 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -15552,19 +15170,6 @@ "unpipe": "~1.0.0" } }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -15610,41 +15215,17 @@ "side-channel": "^1.0.4" } }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true } } }, @@ -15918,9 +15499,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", "dev": true }, "forever-agent": { @@ -15955,7 +15536,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, "from": { @@ -15980,24 +15561,21 @@ "dev": true }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "dependencies": { - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true } } }, @@ -16367,6 +15945,14 @@ "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^4.0.0" + }, + "dependencies": { + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + } } }, "graceful-fs": { @@ -18067,13 +17653,6 @@ "walker": "^1.0.7" }, "dependencies": { - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -19976,6 +19555,15 @@ "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==", "dev": true }, + "magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -20104,7 +19692,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, "memfs": { @@ -20399,9 +19987,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "minimist-options": { @@ -20662,13 +20250,6 @@ "stylus": "^0.59.0" }, "dependencies": { - "@esbuild/linux-loong64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", - "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", - "dev": true, - "optional": true - }, "ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -20738,146 +20319,6 @@ "esbuild-windows-arm64": "0.15.7" } }, - "esbuild-android-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", - "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", - "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", - "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", - "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", - "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", - "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", - "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", - "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", - "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", - "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", - "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", - "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", - "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", - "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", - "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", - "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", - "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", - "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", - "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", - "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", - "dev": true, - "optional": true - }, "glob": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", @@ -21347,17 +20788,6 @@ "micromatch": "^4.0.4" } }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -21429,12 +20859,6 @@ "rimraf": "^3.0.0" } }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -22606,7 +22030,8 @@ }, "ansi-regex": { "version": "5.0.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -22949,12 +22374,6 @@ } } }, - "proxy-middleware": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", - "integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=", - "dev": true - }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -23839,25 +23258,6 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -23873,23 +23273,11 @@ "ee-first": "1.1.1" } }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true } } }, @@ -24050,9 +23438,9 @@ "dev": true }, "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "smart-buffer": { @@ -24658,6 +24046,26 @@ "requires": { "ms": "2.1.2" } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } } } }, @@ -25082,7 +24490,7 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "throat": { @@ -25608,7 +25016,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, "verror": { diff --git a/package.json b/package.json index 71952d5da5d115db9b92371c9c428b4e73011cf6..3e3d43aa7310c98f94dd65d12f4343a91300b255 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "@angular/router": "^14.2.1", "@angular/service-worker": "^14.2.1", "@asymmetrik/ngx-leaflet": "^8.1.0", - "@gouvfr-anct/mediation-numerique": "^0.0.17", "@ngx-translate/core": "^13.0.0", "ag-grid-angular": "^26.2.0", "ag-grid-community": "^26.2.1", @@ -57,7 +56,6 @@ "@compodoc/compodoc": "^1.1.16", "@types/jasmine": "~4.0.0", "@types/jasminewd2": "~2.0.3", - "@types/jest": "^27.5.0", "@types/leaflet": "^1.5.17", "@types/leaflet.locatecontrol": "^0.60.7", "@types/node": "^16.0.0", @@ -81,7 +79,6 @@ "prettier": "^2.1.2", "protractor": "~7.0.0", "standard-version": "^9.3.2", - "ts-jest": "^27.1.4", "ts-node": "~8.3.0", "tslint": "~6.1.0", "typescript": "~4.8.3" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 7adad9d257c3ddcf4eafcbbfd9390049aebf8af3..54d1b57608e88214239ef3f3c171c63b462ff8a3 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { OrientationFormComponent } from './form/orientation-form/orientation-fo import { AdminGuard } from './guards/admin.guard'; import { AuthGuard } from './guards/auth.guard'; import { LoginGuard } from './guards/login.guard'; +import { DeactivateGuard } from './guards/deactivate.guard'; import { LegalNoticeComponent } from './legal-notice/legal-notice.component'; import { LoginComponent } from './login/login.component'; import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsletter-subscription.component'; @@ -17,9 +18,10 @@ import { ResetEmailComponent } from './reset-email/reset-email.component'; import { ResetPasswordComponent } from './reset-password/reset-password.component'; import { StructureResolver } from './resolvers/structure.resolver'; import { PasswordFormComponent } from './shared/components'; -import { StructureDetailsWrapperComponent } from './structure/components/structure-details-wrapper/structure-details-wrapper.component'; -import { StructureExcludeComponent } from './structure/structure-exclude/structure-exclude.component'; import { StructureJoinComponent } from './structure/structure-join/structure-join.component'; +import { StructureDetailsComponent } from './structure-list/components/structure-details/structure-details.component'; +import { StructureListComponent } from './structure-list/structure-list.component'; +import { StructureExcludeComponent } from './structure/structure-exclude/structure-exclude.component'; const footerOutletRoute: Route = { path: '', @@ -31,7 +33,7 @@ const routes: Routes = [ { path: 'print', outlet: 'print', - children: [{ path: 'structure', component: StructureDetailsWrapperComponent }, footerOutletRoute], + children: [{ path: 'structure', component: StructureDetailsComponent }, footerOutletRoute], }, { path: 'print', @@ -58,7 +60,7 @@ const routes: Routes = [ { path: '', outlet: 'left-pane', - component: StructureDetailsWrapperComponent, + component: StructureDetailsComponent, }, ], }, @@ -72,6 +74,16 @@ const routes: Routes = [ footerOutletRoute, ], }, + { + path: 'structures', + children: [ + { + path: '', + component: StructureListComponent, + }, + footerOutletRoute, + ], + }, { path: 'legal-notice', children: [ @@ -140,7 +152,7 @@ const routes: Routes = [ path: '', outlet: 'left-pane', data: { fullScreen: true }, - component: StructureDetailsWrapperComponent, + component: StructureDetailsComponent, }, ], }, diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1c7cd7fc2f59d8ba4443859ef03677633ab88200..c8a2e3bb52ba34504346327d366444ac92896dee 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -4,6 +4,7 @@ import { ProfileService } from './profile/services/profile.service'; import { AuthService } from './services/auth.service'; import { RouterListenerService } from './services/routerListener.service'; import { UpdateService } from './services/update.service'; +import { PrintService } from './shared/service/print.service'; @Component({ selector: 'app-root', @@ -15,6 +16,7 @@ export class AppComponent implements OnInit { public loading = true; constructor( + public printService: PrintService, private authService: AuthService, private profilService: ProfileService, private routerListener: RouterListenerService, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4fb3e41a9ddb870211c306b1df5280e2eaed662e..81abddae8001a0d238da0439c834fa2bfd358820 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,55 +1,51 @@ -import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { LOCALE_ID, NgModule } from '@angular/core'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ServiceWorkerModule } from '@angular/service-worker'; -import { GeometryPolygonConfiguration, MapModule, StructureModule } from '@gouvfr-anct/mediation-numerique'; import { ToastrModule } from 'ngx-toastr'; -import metropole from '../assets/geojson/metropole.json'; -import { environment } from '../environments/environment'; + import { AppRoutingModule } from './app-routing.module'; + import { AppComponent } from './app.component'; import { CartoComponent } from './carto/carto.component'; import { CustomBreakPointsProvider } from './config/custom-breakpoint'; -import { CustomHttpInterceptor } from './config/http-interceptor'; -import { InitialPosition } from './config/map/initial-position'; -import { MarkerType } from './config/map/marker-type'; -import { ZoomLevel } from './config/map/zoomLevel.enum'; -import { ContactComponent } from './contact/contact.component'; import { FooterComponent } from './footer/footer.component'; -import { FormViewModule } from './form/form-view/form-view.module'; -import { OrientationComponent } from './form/orientation-form/component/orientation-modal/orientation-modal.component'; -import { StructureDetailPrintComponent } from './form/orientation-form/component/structure-detail-print/structure-detail-print.component'; -import { StructureListPrintComponent } from './form/orientation-form/component/structure-list-print/structure-list-print.component'; -import { StructurePrintHeaderComponent } from './form/orientation-form/component/structure-print-header/structure-print-header.component'; -import { OrientationFormComponent } from './form/orientation-form/orientation-form.component'; -import { AdminGuard } from './guards/admin.guard'; -import { AuthGuard } from './guards/auth.guard'; -import { DeactivateGuard } from './guards/deactivate.guard'; -import { LoginGuard } from './guards/login.guard'; -import { RoleGuard } from './guards/role.guard'; import { HeaderComponent } from './header/header.component'; +import { SharedModule } from './shared/shared.module'; +import { MapModule } from './map/map.module'; +import { StructureListComponent } from './structure-list/structure-list.component'; +import { CardComponent } from './structure-list/components/card/card.component'; +import { StructureListSearchComponent } from './structure-list/components/structure-list-search/structure-list-search.component'; +import { StructureDetailsComponent } from './structure-list/components/structure-details/structure-details.component'; +import { ModalFilterComponent } from './structure-list/components/modal-filter/modal-filter.component'; import { LegalNoticeComponent } from './legal-notice/legal-notice.component'; -import { LoginComponent } from './login/login.component'; -import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsletter-subscription.component'; import { PageComponent } from './page/page.component'; +import { ContactComponent } from './contact/contact.component'; +import { AuthGuard } from './guards/auth.guard'; +import { LoginGuard } from './guards/login.guard'; +import { CustomHttpInterceptor } from './config/http-interceptor'; import { ResetEmailComponent } from './reset-email/reset-email.component'; import { ResetPasswordComponent } from './reset-password/reset-password.component'; -import { StructureResolver } from './resolvers/structure.resolver'; +import { AdminGuard } from './guards/admin.guard'; +import { DeactivateGuard } from './guards/deactivate.guard'; import { TempUserResolver } from './resolvers/temp-user.resolver'; -import { GeojsonService } from './services/geojson.service'; +import { StructureJoinComponent } from './structure/structure-join/structure-join.component'; import { RouterListenerService } from './services/routerListener.service'; -import { StructureService } from './services/structure.service'; +import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsletter-subscription.component'; +import { OrientationFormComponent } from './form/orientation-form/orientation-form.component'; +import { StructureDetailPrintComponent } from './form/orientation-form/component/structure-detail-print/structure-detail-print.component'; +import { StructureListPrintComponent } from './form/orientation-form/component/structure-list-print/structure-list-print.component'; +import { StructurePrintHeaderComponent } from './form/orientation-form/component/structure-print-header/structure-print-header.component'; +import { OrientationComponent } from './form/orientation-form/component/orientation-modal/orientation-modal.component'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { environment } from '../environments/environment'; +import { StructureResolver } from './resolvers/structure.resolver'; +import { RoleGuard } from './guards/role.guard'; import { UpdateService } from './services/update.service'; import { DataShareConsentComponent } from './shared/components/data-share-consent/data-share-consent.component'; -import { SharedModule } from './shared/shared.module'; -import { SearchService } from './structure-list/services/search.service'; -import { StructureDetailsActionsComponent } from './structure/components/structure-details-actions/structure-details-actions.component'; -import { StructureDetailsModalsComponent } from './structure/components/structure-details-modals/structure-details-modals.component'; -import { StructureDetailsWrapperComponent } from './structure/components/structure-details-wrapper/structure-details-wrapper.component'; -import { TclAccessComponent } from './structure/components/tcl-access/tcl-access.component'; +import { FormViewModule } from './form/form-view/form-view.module'; +import { LoginComponent } from './login/login.component'; import { StructureExcludeComponent } from './structure/structure-exclude/structure-exclude.component'; -import { StructureJoinComponent } from './structure/structure-join/structure-join.component'; @NgModule({ declarations: [ @@ -57,6 +53,11 @@ import { StructureJoinComponent } from './structure/structure-join/structure-joi HeaderComponent, FooterComponent, CartoComponent, + StructureListComponent, + CardComponent, + StructureListSearchComponent, + ModalFilterComponent, + StructureDetailsComponent, LegalNoticeComponent, PageComponent, ContactComponent, @@ -71,27 +72,16 @@ import { StructureJoinComponent } from './structure/structure-join/structure-joi DataShareConsentComponent, OrientationComponent, LoginComponent, - TclAccessComponent, - StructureDetailsModalsComponent, - StructureDetailsActionsComponent, StructureExcludeComponent, - StructureDetailsWrapperComponent, ], imports: [ BrowserModule, HttpClientModule, AppRoutingModule, SharedModule, - MapModule.forRoot( - metropole as GeometryPolygonConfiguration, - ZoomLevel, - InitialPosition, - MarkerType, - GeojsonService - ), + MapModule, BrowserAnimationsModule, ToastrModule.forRoot(), - StructureModule.forRoot(SearchService, StructureService), FormViewModule, ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production, diff --git a/src/app/carto/carto.component.html b/src/app/carto/carto.component.html index 5069d39529f43f54388c99a223b5277a7efe37dd..c2a6236850b69c37d5cc5839f9e5da605f930a08 100644 --- a/src/app/carto/carto.component.html +++ b/src/app/carto/carto.component.html @@ -4,7 +4,6 @@ </div> <div class="panes-container" fxLayout="row"> <app-structure-list - #ref (searchEvent)="getStructures($event)" [structureList]="structures" [location]="currentLocation" @@ -13,29 +12,9 @@ [selectedStructure]="currentStructure" (updatedStructure)="updateStructures($event)" class="left-pane" - [ngClass]="{ mapPhone: isMapPhone === true }" + [ngClass]="{ mapPhone: isMapPhone == true }" fxLayout="column" - > - <div slot="structure-list-actions"> - <app-button - tabindex="0" - (action)="addStructure()" - [text]="'Ajouter une structure'" - [style]="buttonTypeEnum.Secondary" - [extraClass]="'small-text'" - ></app-button> - </div> - <div slot="structure-list-elements"> - <app-card - *ngFor="let structure of ref.structureList" - [structure]="structure" - [isClaimed]="isAdmin ? structure.isClaimed : true" - (showDetails)="ref.showDetails($event)" - (hover)="ref.handleCardHover($event)" - class="structure-card" - ></app-card> - </div> - </app-structure-list> + ></app-structure-list> <div class="btnSwitch"> <app-button [style]="buttonTypeEnum.ButtonPhone" @@ -52,7 +31,7 @@ [isMapPhone]="isMapPhone" [searchedValue]="searchedValue" class="right-pane" - [ngClass]="{ mapPhone: isMapPhone === true }" + [ngClass]="{ mapPhone: isMapPhone == true }" ></app-map> </div> </div> diff --git a/src/app/carto/carto.component.ts b/src/app/carto/carto.component.ts index 25aee8c3b706a09d65cf26ce36a5c341f1ae475e..ede0edf85e8491f01ad55ffb5b253f2717d4fbd6 100644 --- a/src/app/carto/carto.component.ts +++ b/src/app/carto/carto.component.ts @@ -1,20 +1,21 @@ import { Component, OnInit } from '@angular/core'; import { Meta } from '@angular/platform-browser'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Filter, GeoJson, Structure } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { ActivatedRoute } from '@angular/router'; import * as _ from 'lodash'; -import { Observable } from 'rxjs'; +import { GeoJson } from '../map/models/geojson.model'; import { ProfileService } from '../profile/services/profile.service'; -import { AuthService } from '../services/auth.service'; +import { Structure } from '../models/structure.model'; import { GeojsonService } from '../services/geojson.service'; import { StructureService } from '../services/structure.service'; +import { ButtonType } from '../shared/components/button/buttonType.enum'; +import { Filter } from '../structure-list/models/filter.model'; import { CustomRegExp } from '../utils/CustomRegExp'; +import { Observable } from 'rxjs'; @Component({ selector: 'app-carto', templateUrl: './carto.component.html', - styleUrls: ['./carto.component.scss'], + styleUrls: ['./carto.component.scss'] }) export class CartoComponent implements OnInit { public filters: Filter[] = []; @@ -35,9 +36,7 @@ export class CartoComponent implements OnInit { private geoJsonService: GeojsonService, private activatedRoute: ActivatedRoute, private profileService: ProfileService, - private meta: Meta, - private router: Router, - private authService: AuthService + private meta: Meta ) {} ngOnInit(): void { @@ -55,7 +54,7 @@ export class CartoComponent implements OnInit { this.meta.updateTag({ name: 'description', - content: 'Recense tous les lieux, accompagnements et ateliers de médiation numérique de la Métropole de Lyon.', + content: 'Recense tous les lieux, accompagnements et ateliers de médiation numérique de la Métropole de Lyon.' }); } @@ -106,12 +105,7 @@ export class CartoComponent implements OnInit { * @param lat user latitde * @param sortByDistance if set to `true`, structures data is sort by distance. Default value is `true` */ - private updateStructuresdistance( - structures: Structure[], - lon: number, - lat: number, - sortByDistance: boolean = true - ): void { + private updateStructuresdistance(structures: Structure[], lon: number, lat: number, sortByDistance: boolean = true): void { Promise.all( structures.map(async (structure) => { if (this.geolocation) { @@ -122,7 +116,7 @@ export class CartoComponent implements OnInit { } return structure; }) - ).then(async (structureList: Structure[]) => { + ).then((structureList) => { if (sortByDistance) { structureList = _.sortBy(structureList, ['distance']); } @@ -160,10 +154,7 @@ export class CartoComponent implements OnInit { * @param lat number */ private getStructurePosition(structure: Structure, lon: number, lat: number): Structure { - structure.distance = parseInt( - this.geoJsonService.getDistance(structure.getLat(), structure.getLon(), lat, lon, 'M'), - 10 - ); + structure.distance = parseInt(this.geoJsonService.getDistance(structure.getLat(), structure.getLon(), lat, lon, 'M'), 10); return structure; } @@ -216,14 +207,6 @@ export class CartoComponent implements OnInit { this.isMapPhone = !this.isMapPhone; } - public addStructure(): void { - if (!this.authService.isLoggedIn()) { - this.router.navigateByUrl('/login'); - } else { - this.router.navigateByUrl('/form/structure'); - } - } - public get isAdmin(): boolean { return this.profileService.isAdmin(); } diff --git a/src/app/config/map/initial-position.ts b/src/app/config/map/initial-position.ts deleted file mode 100644 index 6c530828f598387ffb8369cc4db7ec55bc912f26..0000000000000000000000000000000000000000 --- a/src/app/config/map/initial-position.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum InitialPosition { - latitude = 45.764043, - longitude = 4.835659, -} diff --git a/src/app/form/footer-form/footer-form.component.ts b/src/app/form/footer-form/footer-form.component.ts index 718eb2abeab5adbfcb2b183d1fb266f27eb161bc..6ad1b384a181f59d95fbae9b880479345bdd7389 100644 --- a/src/app/form/footer-form/footer-form.component.ts +++ b/src/app/form/footer-form/footer-form.component.ts @@ -1,11 +1,11 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; import { Router } from '@angular/router'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { User } from '../../models/user.model'; import { ProfileService } from '../../profile/services/profile.service'; import { AuthService } from '../../services/auth.service'; import { NewsletterService } from '../../services/newsletter.service'; +import { ButtonType } from '../../shared/components/button/buttonType.enum'; import { Utils } from '../../utils/utils'; import { accountFormStep } from '../form-view/account-form/accountFormStep.enum'; import { formType } from '../form-view/formType.enum'; diff --git a/src/app/form/form-view/form-view.component.ts b/src/app/form/form-view/form-view.component.ts index ec58518c16bb18cb1f7c9d0a24db80e1d3dab52d..f0cfa33ef8195890bd25a401139135ecc68498e4 100644 --- a/src/app/form/form-view/form-view.component.ts +++ b/src/app/form/form-view/form-view.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; -import { PersonalOffer, Structure } from '@gouvfr-anct/mediation-numerique'; import { forkJoin, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; +import { PersonalOffer } from '../../models/personalOffer.model'; +import { Structure } from '../../models/structure.model'; import { StructureWithOwners } from '../../models/structureWithOwners.model'; import { User } from '../../models/user.model'; import { ProfileService } from '../../profile/services/profile.service'; diff --git a/src/app/form/form-view/global-components/information-step/information-step.component.ts b/src/app/form/form-view/global-components/information-step/information-step.component.ts index 8b8dcdc9abed30660520a59b7559124d10a1b52b..0c8cd021c53fc22b0cf62c6cb848ffaca0846275 100644 --- a/src/app/form/form-view/global-components/information-step/information-step.component.ts +++ b/src/app/form/form-view/global-components/information-step/information-step.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Router } from '@angular/router'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; import { accountFormStep } from '../../account-form/accountFormStep.enum'; import { formType } from '../../formType.enum'; import { personalOfferFormStep } from '../../personal-offer-form/personalOfferFormStep.enum'; diff --git a/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts b/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts index 86f182b7a8a45903cc02a48380df1fe462b73635..7013868bbe658a59450c5288da604db4acb65025 100644 --- a/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts +++ b/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts @@ -1,7 +1,9 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category, Module } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { Structure } from '../../../../models/structure.model'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; +import { Category } from '../../../../structure-list/models/category.model'; +import { Module } from '../../../../structure-list/models/module.model'; import { SearchService } from '../../../../structure-list/services/search.service'; @Component({ diff --git a/src/app/form/form-view/personal-offer-form/personal-offer-training-type/personal-offer-training-type.component.ts b/src/app/form/form-view/personal-offer-form/personal-offer-training-type/personal-offer-training-type.component.ts index e1e36bf78d89ecf803d9ffce5619883a1ef9b757..59e5e07b9df3356954ce88e9309480e74cb7e49e 100644 --- a/src/app/form/form-view/personal-offer-form/personal-offer-training-type/personal-offer-training-type.component.ts +++ b/src/app/form/form-view/personal-offer-form/personal-offer-training-type/personal-offer-training-type.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-personal-offer-training-type', diff --git a/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts b/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts index 6a9b4d4423f59603ad41818c19f1e94d9878f574..bd6272479d9c0e44900b5f78947c92146b82e1f1 100644 --- a/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts +++ b/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts @@ -1,8 +1,8 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { Employer } from '../../../../models/employer.model'; import { ProfileService } from '../../../../profile/services/profile.service'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; @Component({ selector: 'app-profile-employer-selection', diff --git a/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts b/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts index bcdc1655da332dfdfe8db964c96bee8e3339e5ee..03a2b57ad1d55d7e27932387d5097fed123116b6 100644 --- a/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts +++ b/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts @@ -1,8 +1,8 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { Job } from '../../../../models/job.model'; import { ProfileService } from '../../../../profile/services/profile.service'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; @Component({ selector: 'app-profile-job-selection', diff --git a/src/app/form/form-view/profile-form/profile-structure-choice/profile-structure-choice.component.ts b/src/app/form/form-view/profile-form/profile-structure-choice/profile-structure-choice.component.ts index 085a0706416ef1272d2b69a0db9bce6611cef967..cb9c5c7903b4eda7704d720eaf9f282046e647fd 100644 --- a/src/app/form/form-view/profile-form/profile-structure-choice/profile-structure-choice.component.ts +++ b/src/app/form/form-view/profile-form/profile-structure-choice/profile-structure-choice.component.ts @@ -1,9 +1,10 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Filter, Structure } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { Structure } from '../../../../models/structure.model'; import { ProfileService } from '../../../../profile/services/profile.service'; import { StructureService } from '../../../../services/structure.service'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; +import { Filter } from '../../../../structure-list/models/filter.model'; @Component({ selector: 'app-profile-structure-choice', diff --git a/src/app/form/form-view/structure-form/structure-access-modality/structure-access-modality.component.ts b/src/app/form/form-view/structure-form/structure-access-modality/structure-access-modality.component.ts index d2f130bbbbb636a6e765048e9d4116a69ba50101..aa0d7c280a2eedb8109d803c1fecd1d7378c4db2 100644 --- a/src/app/form/form-view/structure-form/structure-access-modality/structure-access-modality.component.ts +++ b/src/app/form/form-view/structure-form/structure-access-modality/structure-access-modality.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-structure-access-modality', diff --git a/src/app/form/form-view/structure-form/structure-digital-helping-accompaniment/structure-digital-helping-accompaniment.component.ts b/src/app/form/form-view/structure-form/structure-digital-helping-accompaniment/structure-digital-helping-accompaniment.component.ts index 4d86cfb7257b8a1bd86e2e9ae59be6230c928428..53c04ed83a479f7cc075a49068ec6edc833cade2 100644 --- a/src/app/form/form-view/structure-form/structure-digital-helping-accompaniment/structure-digital-helping-accompaniment.component.ts +++ b/src/app/form/form-view/structure-form/structure-digital-helping-accompaniment/structure-digital-helping-accompaniment.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-structure-digital-helping-accompaniment', diff --git a/src/app/form/form-view/structure-form/structure-equipments/structure-equipments.component.ts b/src/app/form/form-view/structure-form/structure-equipments/structure-equipments.component.ts index 646007d8bb2681aeb7d34b0cbb55429234ad00ac..a4e52968ad4bf028141333e45dd235a794e75937 100644 --- a/src/app/form/form-view/structure-form/structure-equipments/structure-equipments.component.ts +++ b/src/app/form/form-view/structure-form/structure-equipments/structure-equipments.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { AbstractControl, UntypedFormGroup } from '@angular/forms'; -import { Module } from '@gouvfr-anct/mediation-numerique'; +import { Module } from '../../../../structure-list/models/module.model'; @Component({ selector: 'app-structure-equipments', diff --git a/src/app/form/form-view/structure-form/structure-form.component.ts b/src/app/form/form-view/structure-form/structure-form.component.ts index dac57368eaa1224fe329a78ee8ee779e7e2a83ed..69d15eeebea56426a3899da6f7c2ac41e4759b06 100644 --- a/src/app/form/form-view/structure-form/structure-form.component.ts +++ b/src/app/form/form-view/structure-form/structure-form.component.ts @@ -1,10 +1,13 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { Address, Category, Module, Structure } from '@gouvfr-anct/mediation-numerique'; +import { Address } from '../../../models/address.model'; +import { Structure } from '../../../models/structure.model'; import { User } from '../../../models/user.model'; import { ProfileService } from '../../../profile/services/profile.service'; import { CategoryEnum } from '../../../shared/enum/category.enum'; +import { Category } from '../../../structure-list/models/category.model'; +import { Module } from '../../../structure-list/models/module.model'; import { SearchService } from '../../../structure-list/services/search.service'; import { formType } from '../formType.enum'; import { structureFormStep } from './structureFormStep.enum'; diff --git a/src/app/form/form-view/structure-form/structure-labels/structure-labels.component.ts b/src/app/form/form-view/structure-form/structure-labels/structure-labels.component.ts index 46b2c712a9fdd3fdfec93c64a0a458e7900c9d22..b5db68fdec3c36dc284be111cc52d9dd303f6949 100644 --- a/src/app/form/form-view/structure-form/structure-labels/structure-labels.component.ts +++ b/src/app/form/form-view/structure-form/structure-labels/structure-labels.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-structure-labels', diff --git a/src/app/form/form-view/structure-form/structure-name-and-address/structure-name-and-address.component.ts b/src/app/form/form-view/structure-form/structure-name-and-address/structure-name-and-address.component.ts index 68ba5ae02871f3cbb22b90a8f9ed4b12af9c0973..fed97c1604fdb9190c175cadf3e480f1d309345f 100644 --- a/src/app/form/form-view/structure-form/structure-name-and-address/structure-name-and-address.component.ts +++ b/src/app/form/form-view/structure-form/structure-name-and-address/structure-name-and-address.component.ts @@ -1,6 +1,7 @@ +import { ThisReceiver } from '@angular/compiler'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Address } from '@gouvfr-anct/mediation-numerique'; +import { Address } from '../../../../models/address.model'; @Component({ selector: 'app-structure-name-and-address', diff --git a/src/app/form/form-view/structure-form/structure-other-services/structure-other-services.component.ts b/src/app/form/form-view/structure-form/structure-other-services/structure-other-services.component.ts index b22d43ff16ed4ad9ffb3129f4400680db876d709..2df67bf3fa23ad4a08c2db878b6d000be2a868d3 100644 --- a/src/app/form/form-view/structure-form/structure-other-services/structure-other-services.component.ts +++ b/src/app/form/form-view/structure-form/structure-other-services/structure-other-services.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-structure-other-services', diff --git a/src/app/form/form-view/structure-form/structure-public-target/structure-public-target.component.ts b/src/app/form/form-view/structure-form/structure-public-target/structure-public-target.component.ts index a68ed6e86e67972088b60fe0b708aecdd9cba9ad..f72179bdad32368eadb5674102568336f61c062c 100644 --- a/src/app/form/form-view/structure-form/structure-public-target/structure-public-target.component.ts +++ b/src/app/form/form-view/structure-form/structure-public-target/structure-public-target.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-structure-public-target', diff --git a/src/app/form/form-view/structure-form/structure-training-type/structure-training-type.component.ts b/src/app/form/form-view/structure-form/structure-training-type/structure-training-type.component.ts index de5cbbe42bb1fe1b54d74c3c1d1e09920f755761..08a6b93e7343cad7ec4d37f6f803e27a7084d24d 100644 --- a/src/app/form/form-view/structure-form/structure-training-type/structure-training-type.component.ts +++ b/src/app/form/form-view/structure-form/structure-training-type/structure-training-type.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { Category } from '@gouvfr-anct/mediation-numerique'; +import { Category } from '../../../../structure-list/models/category.model'; @Component({ selector: 'app-structure-training-type', diff --git a/src/app/form/orientation-form/component/structure-detail-print/structure-detail-print.component.ts b/src/app/form/orientation-form/component/structure-detail-print/structure-detail-print.component.ts index f8d1c4b8083fa9231ef4f39d8e43bcfb06574937..10ca2469315e01705afef5a3e112d90c133ff9e9 100644 --- a/src/app/form/orientation-form/component/structure-detail-print/structure-detail-print.component.ts +++ b/src/app/form/orientation-form/component/structure-detail-print/structure-detail-print.component.ts @@ -1,42 +1,42 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { AccessModality } from '../../../../config/map/access-modality.enum'; -import { TclStopPoint } from '../../../../models/tclStopPoint.model'; -import { AuthService } from '../../../../services/auth.service'; -import { TclService } from '../../../../services/tcl.service'; - -@Component({ - selector: 'app-structure-detail-print', - templateUrl: './structure-detail-print.component.html', - styleUrls: ['./structure-detail-print.component.scss'], -}) -export class StructureDetailPrintComponent implements OnInit { - @Input() public structure: Structure; - @Output() public closeDetails: EventEmitter<boolean> = new EventEmitter<boolean>(); - @Output() public dataReady: EventEmitter<boolean> = new EventEmitter<boolean>(); - public accessModality = AccessModality; - public tclStopPoints: TclStopPoint[] = []; - - constructor(private tclService: TclService, private authService: AuthService) {} - - async ngOnInit(): Promise<void> { - // GetTclStopPoints - this.getTclStopPoints(); - const index = this.structure.proceduresAccompaniment.indexOf('autres'); - if (index > -1) { - this.structure.proceduresAccompaniment.splice(index, 1); - } - } - - public keepOriginalOrder = (a, _b) => a.key; - - public userIsLoggedIn(): boolean { - return this.authService.isLoggedIn(); - } - - public getTclStopPoints(): void { - this.tclService.getTclStopPointBycoord(this.structure.getLon(), this.structure.getLat()).subscribe((res) => { - this.tclStopPoints = res; - }); - } -} +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Structure } from '../../../../models/structure.model'; +import * as _ from 'lodash'; +import { TclService } from '../../../../services/tcl.service'; +import { TclStopPoint } from '../../../../models/tclStopPoint.model'; +import { AuthService } from '../../../../services/auth.service'; +import { AccessModality } from '../../../../structure-list/enum/access-modality.enum'; +@Component({ + selector: 'app-structure-detail-print', + templateUrl: './structure-detail-print.component.html', + styleUrls: ['./structure-detail-print.component.scss'] +}) +export class StructureDetailPrintComponent implements OnInit { + @Input() public structure: Structure; + @Output() public closeDetails: EventEmitter<boolean> = new EventEmitter<boolean>(); + @Output() public dataReady: EventEmitter<boolean> = new EventEmitter<boolean>(); + public accessModality = AccessModality; + public tclStopPoints: TclStopPoint[] = []; + + constructor(private tclService: TclService, private authService: AuthService) {} + + async ngOnInit(): Promise<void> { + // GetTclStopPoints + this.getTclStopPoints(); + const index = this.structure.proceduresAccompaniment.indexOf('autres'); + if (index > -1) { + this.structure.proceduresAccompaniment.splice(index, 1); + } + } + + public keepOriginalOrder = (a, _b) => a.key; + + public userIsLoggedIn(): boolean { + return this.authService.isLoggedIn(); + } + + public getTclStopPoints(): void { + this.tclService.getTclStopPointBycoord(this.structure.getLon(), this.structure.getLat()).subscribe((res) => { + this.tclStopPoints = res; + }); + } +} diff --git a/src/app/form/orientation-form/component/structure-list-print/structure-list-print.component.ts b/src/app/form/orientation-form/component/structure-list-print/structure-list-print.component.ts index 9f4c959cf4e66459a660170204ca1e17f75bffde..dc15a3df0d69e9e8c23574b7efe04899def8f0d5 100644 --- a/src/app/form/orientation-form/component/structure-list-print/structure-list-print.component.ts +++ b/src/app/form/orientation-form/component/structure-list-print/structure-list-print.component.ts @@ -1,17 +1,18 @@ -import { Component, Input } from '@angular/core'; -import { Filter, Structure } from '@gouvfr-anct/mediation-numerique'; - -@Component({ - selector: 'app-structure-list-print', - templateUrl: './structure-list-print.component.html', - styleUrls: ['./structure-list-print.component.scss'], -}) -export class StructureListPrintComponent { - @Input() public structures: Structure[]; - @Input() public filters: Filter[]; - @Input() public beneficiaryNeedCommentary: string; - @Input() public beneficiaryName: string; - @Input() public structureAccompaniment: string; - @Input() public beneficiaryPassNumeric: boolean; - @Input() public contactAccompaniment: string; -} +import { Component, Input, OnInit } from '@angular/core'; +import { Structure } from '../../../../models/structure.model'; +import * as _ from 'lodash'; +import { Filter } from '../../../../structure-list/models/filter.model'; +@Component({ + selector: 'app-structure-list-print', + templateUrl: './structure-list-print.component.html', + styleUrls: ['./structure-list-print.component.scss'], +}) +export class StructureListPrintComponent { + @Input() public structures: Structure[]; + @Input() public filters: Filter[]; + @Input() public beneficiaryNeedCommentary: string; + @Input() public beneficiaryName: string; + @Input() public structureAccompaniment: string; + @Input() public beneficiaryPassNumeric: boolean; + @Input() public contactAccompaniment: string; +} diff --git a/src/app/form/orientation-form/component/structure-print-header/structure-print-header.component.ts b/src/app/form/orientation-form/component/structure-print-header/structure-print-header.component.ts index 3d50bd4cf4a09017f8c8742938fe4e154c9cdc76..ee7b51cf1d9c8a0327cac110d73303a0fdadef44 100644 --- a/src/app/form/orientation-form/component/structure-print-header/structure-print-header.component.ts +++ b/src/app/form/orientation-form/component/structure-print-header/structure-print-header.component.ts @@ -1,69 +1,70 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Filter } from '@gouvfr-anct/mediation-numerique'; - -@Component({ - selector: 'app-structure-print-header', - templateUrl: './structure-print-header.component.html', - styleUrls: ['./structure-print-header.component.scss'], -}) -export class StructurePrintHeaderComponent implements OnInit { - @Input() public beneficiaryNeedCommentary: string | null; - @Input() public beneficiaryName: string | null; - @Input() public structureAccompaniment: string; - @Input() public beneficiaryPassNumeric: boolean; - @Input() public contactAccompaniment: string | null; - @Input() public contactAccompanimentPhone: string | null; - @Input() public filters: Filter[]; - - public date: string; - public formations: Filter[] = []; - public assistances: Filter[] = []; - public equipments: Filter[] = []; - public specificNeeds: Filter[] = []; - - constructor(private route: ActivatedRoute) {} - - async ngOnInit(): Promise<void> { - this.date = new Date().toLocaleDateString('fr-FR', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - }); - - this.filters.forEach((elem) => { - switch (elem.name) { - case 'proceduresAccompaniment': - this.assistances.push(elem); - break; - case 'publicsAccompaniment': - this.specificNeeds.push(elem); - break; - case 'equipmentsAndServices': - this.equipments.push(elem); - break; - case 'accessRight': - this.formations.push(elem); - break; - case 'baseSkills': - this.formations.push(elem); - break; - case 'socialAndProfessional': - this.formations.push(elem); - break; - case 'parentingHelp': - this.formations.push(elem); - break; - case 'digitalCultureSecurity': - this.formations.push(elem); - break; - - default: - break; - } - }); - } -} +import { Component, Input, OnInit } from '@angular/core'; +import * as _ from 'lodash'; +import { ActivatedRoute } from '@angular/router'; +import { PrintService } from '../../../../shared/service/print.service'; +import { Filter } from '../../../../structure-list/models/filter.model'; +@Component({ + selector: 'app-structure-print-header', + templateUrl: './structure-print-header.component.html', + styleUrls: ['./structure-print-header.component.scss'], +}) +export class StructurePrintHeaderComponent implements OnInit { + @Input() public beneficiaryNeedCommentary: string | null; + @Input() public beneficiaryName: string | null; + @Input() public structureAccompaniment: string; + @Input() public beneficiaryPassNumeric: boolean; + @Input() public contactAccompaniment: string | null; + @Input() public contactAccompanimentPhone: string | null; + @Input() public filters: Filter[]; + + public date: string; + public formations: Filter[] = []; + public assistances: Filter[] = []; + public equipments: Filter[] = []; + public specificNeeds: Filter[] = []; + + constructor(private printService: PrintService, private route: ActivatedRoute) {} + + async ngOnInit(): Promise<void> { + this.date = new Date().toLocaleDateString('fr-FR', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }); + + this.filters.forEach((elem) => { + switch (elem.name) { + case 'proceduresAccompaniment': + this.assistances.push(elem); + break; + case 'publicsAccompaniment': + this.specificNeeds.push(elem); + break; + case 'equipmentsAndServices': + this.equipments.push(elem); + break; + case 'accessRight': + this.formations.push(elem); + break; + case 'baseSkills': + this.formations.push(elem); + break; + case 'socialAndProfessional': + this.formations.push(elem); + break; + case 'parentingHelp': + this.formations.push(elem); + break; + case 'digitalCultureSecurity': + this.formations.push(elem); + break; + + default: + break; + } + }); + } +} diff --git a/src/app/form/orientation-form/orientation-form.component.html b/src/app/form/orientation-form/orientation-form.component.html index ef39b6dd10bbcd1432f872edc2a6dff1cc6bffd3..a31428cb853239ccb27f4137edd6d6266b0764f2 100644 --- a/src/app/form/orientation-form/orientation-form.component.html +++ b/src/app/form/orientation-form/orientation-form.component.html @@ -454,6 +454,12 @@ [filters]="filters" ></app-structure-list-print> +<app-structure-details + *ngIf="showStructureDetails" + [structure]="selectedStructure" + (closeDetails)="closeDetails()" +></app-structure-details> + <!-- <app-orientation-modal *ngIf="displayModal" [openned]="true" diff --git a/src/app/form/orientation-form/orientation-form.component.ts b/src/app/form/orientation-form/orientation-form.component.ts index a2023765182b63abab78ad02bad8d6623cc7f9e2..af6c80377e30c5e31a09a063152f9e6e3c47722a 100644 --- a/src/app/form/orientation-form/orientation-form.component.ts +++ b/src/app/form/orientation-form/orientation-form.component.ts @@ -1,12 +1,16 @@ import { Component, HostListener, OnInit } from '@angular/core'; import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { Meta } from '@angular/platform-browser'; -import { Address, Category, Filter, Module, Structure } from '@gouvfr-anct/mediation-numerique'; +import { Address } from '../../models/address.model'; import { OrientationFormFilters } from '../../models/orientation-filter.object'; +import { Structure } from '../../models/structure.model'; import { GeojsonService } from '../../services/geojson.service'; import { RouterListenerService } from '../../services/routerListener.service'; import { StructureService } from '../../services/structure.service'; import { CategoryEnum } from '../../shared/enum/category.enum'; +import { Category } from '../../structure-list/models/category.model'; +import { Filter } from '../../structure-list/models/filter.model'; +import { Module } from '../../structure-list/models/module.model'; import { SearchService } from '../../structure-list/services/search.service'; import { CustomRegExp } from '../../utils/CustomRegExp'; import { Utils } from '../../utils/utils'; diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index 34c597c03db5312c4225e6ab42b5c33215919838..0ef560d72f13e46f3151e6a50b43f9de0eee9a0e 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -1,10 +1,10 @@ import { animate, animateChild, query, style, transition, trigger } from '@angular/animations'; import { Component } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { Structure } from '../models/structure.model'; import { ProfileService } from '../profile/services/profile.service'; import { AuthService } from '../services/auth.service'; +import { ButtonType } from '../shared/components/button/buttonType.enum'; @Component({ selector: 'app-header', diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index 2244ac0c512e8f6b35391373cb4455a5a33556fd..db47a53afbf8b67a547cd0233509f1976e80cb5d 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -1,13 +1,13 @@ import { Component, OnInit } from '@angular/core'; import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; +import { Structure } from '../models/structure.model'; import { User } from '../models/user.model'; import { AuthService } from '../services/auth.service'; import { StructureService } from '../services/structure.service'; +import { ButtonType } from '../shared/components/button/buttonType.enum'; import { CustomRegExp } from '../utils/CustomRegExp'; @Component({ diff --git a/src/app/map/components/index.ts b/src/app/map/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..45941a73cbaa1d10fc7670c9d256c3b051669356 --- /dev/null +++ b/src/app/map/components/index.ts @@ -0,0 +1,6 @@ +import { MapComponent } from './map.component'; + +export { MapComponent }; + +// tslint:disable-next-line:variable-name +export const MapComponents = [MapComponent]; diff --git a/src/app/map/components/layers.enum.ts b/src/app/map/components/layers.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..3878af93cdc11e780d3563cdeb364bc63b407b51 --- /dev/null +++ b/src/app/map/components/layers.enum.ts @@ -0,0 +1,5 @@ +export enum Layers { + mdm = 'mdm', + user = 'user', + structure = 'structure', +} diff --git a/src/app/map/components/map.component.html b/src/app/map/components/map.component.html new file mode 100644 index 0000000000000000000000000000000000000000..3f5bbc3ed071e86483b5fa3c55c1cf8c5d020db2 --- /dev/null +++ b/src/app/map/components/map.component.html @@ -0,0 +1,8 @@ +<div + id="map" + class="body-wrap" + [ngClass]="{ orientation: isOrientationForm }" + leaflet + [leafletOptions]="mapOptions" + (leafletMapReady)="onMapReady($event)" +></div> diff --git a/src/app/map/components/map.component.scss b/src/app/map/components/map.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..a699808a9befcca4813cbf074f4148a84bc9f69c --- /dev/null +++ b/src/app/map/components/map.component.scss @@ -0,0 +1,132 @@ +@import '../../../assets/scss/color'; +@import '../../../assets/scss/layout'; +@import '../../../assets/scss/icons'; +@import '../../../assets/scss/typography'; +@import '../../../assets/scss/shapes'; +@import '../../../assets/scss/buttons'; +@import '../../../assets/scss/breakpoint'; +@import '../../../assets/scss/z-index'; + +#map { + height: 100%; +} + +/*** Right controls ***/ +::ng-deep .leaflet-popup-close-button { + display: none; +} +::ng-deep .leaflet-left { + right: 0; + left: unset; + .leaflet-control { + margin-left: 0; + margin-right: 10px; + border: none; + box-shadow: 0px 2px 8px rgba($black, 0.25); + &.leaflet-bar a:first-child { + border-top-left-radius: 8px; + border-top-right-radius: 8px; + } + &.leaflet-bar a:last-child { + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + } + } +} +::ng-deep .leaflet-control-zoom { + a { + color: $grey-1; + opacity: 0.55; + &:hover { + opacity: 1; + } + } +} + +/*** Marker ***/ +::ng-deep .leaflet-marker-icon { + &:hover { + z-index: calc($map-selected-marker - 1) !important; + svg { + fill: $primary-color-dark; + &.mdm { + fill: $gold; + } + &.france-service { + fill: $primary-color; + } + } + } +} + +/*** POP-UP ***/ +::ng-deep .leaflet-popup { + border-radius: 6px; + bottom: -15px !important; + h1 { + color: $grey-1; + @include lato-bold-20; + font-size: 18px; + margin: 0; + } + p { + color: $grey-3; + @include lato-regular-16; + font-size: 16px; + margin: 0 0 13px 0; + font-style: italic; + } + .pop-up { + text-align: center; + padding-top: 20px; + &.orientation { + padding: 0; + text-align: -webkit-center; + } + + button { + @include btn-search-filter; + @include lato-bold-14; + font-size: 16px; + } + + .orientationButton { + display: flex; + padding: 10px 20px; + border-radius: 20px; + margin: 0 4px; + color: $black; + background-color: $white; + border: solid 1px $grey-3; + min-width: 120px; + } + } + span { + margin-right: 4px; + &.eye { + margin-right: 11px; + } + } +} +::ng-deep .leaflet-popup-content-wrapper { + border-radius: 6px; +} +::ng-deep .leaflet-popup-content { + width: 240px; +} +::ng-deep .leaflet-popup-tip-container { + display: none; +} +@media print { + .map-wrapper { + display: none; + } +} + +.body-wrap { + height: 400px; +} + +::ng-deep .on-top-marker { + z-index: $map-selected-marker !important; +} diff --git a/src/app/map/components/map.component.ts b/src/app/map/components/map.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9666d18f5542f97b5b2ec3740c78003be600e95b --- /dev/null +++ b/src/app/map/components/map.component.ts @@ -0,0 +1,327 @@ +import { Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import L, { geoJSON, latLng, layerGroup, Map, MapOptions, tileLayer } from 'leaflet'; +import * as _ from 'lodash'; +import metropole from '../../../assets/geojson/metropole.json'; +import { Structure } from '../../models/structure.model'; +import { GeojsonService } from '../../services/geojson.service'; +import { GeoJsonProperties } from '../models/geoJsonProperties.model'; +import { MapService } from '../services/map.service'; +import { MarkerType } from './markerType.enum'; +import { ZoomLevel } from './zoomLevel.enum'; + +@Component({ + selector: 'app-map', + templateUrl: './map.component.html', + styleUrls: ['./map.component.scss'], +}) +export class MapComponent implements OnChanges { + @Input() public isOrientationForm = false; + @Input() public structures: Structure[] = []; + @Input() public structuresToPrint: Structure[] = []; + @Input() public toogleToolTipId: string; + @Input() public selectedMarkerId: string; + @Input() public isMapPhone: boolean; + @Input() public searchedValue: string | [number, number]; + @Output() public selectedStructure: EventEmitter<Structure> = new EventEmitter<Structure>(); + @Output() public onOrientationButtonClick: EventEmitter<Structure> = new EventEmitter<Structure>(); + private currentStructure: Structure; + + public map: Map; + public mapOptions: MapOptions; + public zoomOptions = { animate: true, duration: 0.5 }; + + // Add listener on the popup button to show details of structure + @HostListener('document:click', ['$event']) + public clickout(event): void { + if (event.target.classList.contains('btnShowDetails')) { + this.selectedStructure.emit(this.currentStructure); + } + if (event.target.classList.contains('add')) { + this.onOrientationButtonClick.emit(this.currentStructure); + this.getStructuresPositions(this.structures); + } + } + + constructor(private mapService: MapService, private geoJsonService: GeojsonService) { + this.initializeMapOptions(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.searchedValue && !changes.searchedValue.firstChange) { + if (changes.searchedValue.currentValue) { + this.processTownCoordinate(changes.searchedValue.currentValue); + } else { + this.map.setView(this.mapOptions.center, this.mapOptions.zoom); + } + } + if (changes.isMapPhone) { + if (this.isMapPhone) { + setTimeout(() => { + this.map.invalidateSize(); + }, 0); + } + } + if (changes.structures) { + this.handleStructurePosition(changes.structures.previousValue); + } + // Handle map marker tooltip + if (changes.toogleToolTipId && changes.toogleToolTipId.currentValue !== changes.toogleToolTipId.previousValue) { + if (changes.toogleToolTipId.previousValue !== undefined) { + if (this.isToPrint(changes.toogleToolTipId.previousValue)) { + this.mapService.setAddedToListMarker( + changes.toogleToolTipId.previousValue, + this.getMarkerTypeByStructureId(changes.toogleToolTipId.previousValue) + ); + } else { + this.mapService.setUnactiveMarker( + changes.toogleToolTipId.previousValue, + this.getMarkerTypeByStructureId(changes.toogleToolTipId.previousValue) + ); + } + } + if (changes.toogleToolTipId.currentValue !== undefined) { + this.mapService.setActiveMarker( + changes.toogleToolTipId.currentValue, + this.getMarkerTypeByStructureId(changes.toogleToolTipId.currentValue) + ); + } + } + // Handle map marker if none selected + if (changes.selectedMarkerId && this.map) { + this.map.closePopup(); + if (changes.selectedMarkerId.currentValue === undefined) { + this.mapService.setDefaultMarker( + changes.selectedMarkerId.previousValue, + this.getMarkerTypeByStructureId(changes.selectedMarkerId.previousValue) + ); + this.map.setView(this.mapOptions.center, this.mapOptions.zoom); + } + } + // Handle map marker if one is set with url or selected + if (this.mapService.getMarker(this.selectedMarkerId)) { + this.mapService.setSelectedMarker(this.selectedMarkerId, this.getMarkerTypeByStructureId(this.selectedMarkerId)); + this.centerLeafletMapOnMarker(this.selectedMarkerId); + } + + this.closePreviousMarker(changes); + + if (changes.structuresToPrint) { + if (changes.structuresToPrint.currentValue < changes.structuresToPrint.previousValue) { + this.mapService?.setUnactiveMarker( + this.toogleToolTipId, + this.getMarkerTypeByStructureId(changes.structuresToPrint.previousValue) + ); + } + this.structuresToPrint.forEach((structure: Structure) => { + this.mapService.setAddedToListMarker(structure._id, this.getMarkerTypeByStructureId(structure._id)); + }); + } + } + + public processTownCoordinate(queryString: string): void { + this.geoJsonService.getTownshipCoord(queryString).subscribe( + (townData) => { + if (townData.length > 0) { + const bounds = new L.LatLngBounds(townData.map((dataArray) => dataArray.reverse())); + this.map.fitBounds(bounds); + } + }, + (err) => { + this.map.flyTo(this.mapOptions.center, this.mapOptions.zoom, this.zoomOptions); + } + ); + } + + /** + * Create a user position marcker and center the map on it with a zoom level defined in ZoomLevel + * @param coords {[number, number]} Map center position + */ + public centerOnCoordinates(coords: [number, number]): void { + this.mapService.createMarker(coords[1], coords[0], MarkerType.user, 'userLocation').addTo(this.map); + this.map.flyTo(new L.LatLng(coords[1], coords[0]), ZoomLevel.userPosition, this.zoomOptions); + } + + /** + * Get structures positions and add marker corresponding to those positons on the map + */ + private handleStructurePosition(previousStructuresValue: Structure[]): void { + // If there is more structure than before, append them + if ( + previousStructuresValue && + previousStructuresValue.length > 0 && + previousStructuresValue.length < this.structures.length + ) { + this.getStructuresPositions(_.differenceWith(this.structures, previousStructuresValue, _.isEqual)); + } else if (this.structures) { + this.map = this.mapService.cleanMap(this.map); + this.getStructuresPositions(this.structures); + this.structuresToPrint.forEach((structure: Structure) => { + this.mapService.setAddedToListMarker(structure._id, this.getMarkerTypeByStructureId(structure._id)); + }); + } + } + + private isToPrint(id: string): boolean { + return this.structuresToPrint.findIndex((elem) => elem._id == id) > -1 ? true : false; + } + + /** + * Returns according marker type base on {MarkerType} + * @param {Structure} structure + * @returns {MarkerType} + */ + private getMarkerType(structure: Structure): MarkerType { + return structure?.labelsQualifications?.includes('conseillerNumFranceServices') + ? MarkerType.conseillerFrance + : MarkerType.structure; + } + + /** + * Return the map marker type given a structure id + * @param {string} id - Structure id + * @returns {MarkerType} + */ + private getMarkerTypeByStructureId(id: string): MarkerType { + return this.getMarkerType(this.structures.find((structure) => structure._id === id)); + } + + private getStructuresPositions(structureList: Structure[]): void { + for (const structure of structureList) { + this.mapService + .createMarker( + structure.getLat(), + structure.getLon(), + this.getMarkerType(structure), + structure._id, + this.buildToolTip(structure) + ) + .addTo(this.map) + // store structure before user click on button + .on('popupopen', () => { + this.currentStructure = structure; + }); + } + } + + /** + * Create tooltip for display + * @param structure Structure + */ + private buildToolTip(structure: Structure): string { + let cssAvailabilityClass = structure.isOpen ? 'available' : null; + if (cssAvailabilityClass === null) { + if (structure.openedOn.day) { + cssAvailabilityClass = 'unavailable'; + } else { + cssAvailabilityClass = 'unknown'; + } + } + return ( + '<h1>' + + structure.structureName + + '</h1>' + + '<p>' + + structure.getLabelTypeStructure() + + '</p>' + + (this.isOrientationForm + ? '<div class="pop-up orientation"><button type="button" class="orientationButton btnShowDetails"><span class="ico-gg-eye-alt eye"></span>Voir</button></div>' + : '<div class="pop-up"><button type="button" class="btnShowDetails">Voir</button></div>') + ); + } + + private buildMdmPopUp(mdmProperties: GeoJsonProperties): string { + return `<h1>${mdmProperties.nom}</h1><p>${mdmProperties.adresse}</p>`; + } + + /** + * Add marker when map is ready to be showned + * @param map map + */ + public onMapReady(map: Map): void { + this.map = map; + if (this.searchedValue) { + if (Array.isArray(this.searchedValue)) { + this.centerOnCoordinates(this.searchedValue); + } + } + } + + /** + * Init map options : + * - Metropole bounds based on a WMS service hosted by data.grandlyon.com + * - Map Layer based on open street maps + */ + private initializeMapOptions(): void { + // Init mdm + this.initMDMLayer(); + // Init WMS service with param from data.grandlyon.com + layerGroup(); + const carteLayer = tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { + attribution: '© <a href="https://carto.com/attributions">CARTO</a>', + maxZoom: ZoomLevel.max, + }); + // Center is set on townhall + // Zoom is blocked on 11 to prevent people to zoom out from metropole + this.mapOptions = { + center: latLng(45.764043, 4.835659), + maxZoom: ZoomLevel.max, + zoom: ZoomLevel.regular, + minZoom: ZoomLevel.min, + layers: [carteLayer], + }; + } + + private initMDMLayer(): void { + this.geoJsonService.getMDMGeoJson().subscribe((res) => { + res.forEach((mdm) => { + this.mapService + .createMarker( + mdm.geometry.getLat(), + mdm.geometry.getLon(), + MarkerType.mdm, + null, + this.buildMdmPopUp(mdm.properties) + ) + .addTo(this.map); + }); + this.initMetropoleLayer(); + }); + } + + private centerLeafletMapOnMarker(markerId: string): void { + if (this.mapService.getMarker(markerId)) { + const marker = this.mapService.getMarker(markerId); + const latLngs = marker.getLatLng(); + this.map.flyTo(new L.LatLng(latLngs.lat, latLngs.lng), ZoomLevel.max, this.zoomOptions); + } + } + + private initMetropoleLayer(): void { + this.map.addLayer( + geoJSON( + { + type: metropole.features[0].geometry.type, + coordinates: metropole.features[0].geometry.coordinates, + } as any, + { style: () => ({ color: '#a00000', fillOpacity: 0, weight: 1 }) } + ) + ); + } + + /** + * Close previous markers + * - if strucure is closed + * - if a new marker is selected + */ + private closePreviousMarker(changes: SimpleChanges): void { + if ( + (changes.selectedMarkerId?.currentValue === undefined && changes.selectedMarkerId?.previousValue) || + changes.selectedMarkerId?.currentValue !== changes.selectedMarkerId?.previousValue + ) { + this.mapService.setUnactiveMarker( + changes.selectedMarkerId.previousValue, + this.getMarkerTypeByStructureId(changes.selectedMarkerId.previousValue) + ); + } + } +} diff --git a/src/app/config/map/marker-type.ts b/src/app/map/components/markerType.enum.ts similarity index 93% rename from src/app/config/map/marker-type.ts rename to src/app/map/components/markerType.enum.ts index 3f1159bb9b417bfb1e2f71781b77e41c30e12858..ade600852dfb0fc14221e0b73e6e8efc8638dbff 100644 --- a/src/app/config/map/marker-type.ts +++ b/src/app/map/components/markerType.enum.ts @@ -1,6 +1,6 @@ -export enum MarkerType { - structure = 0, - mdm = 1, - conseillerFrance = 2, - user = 3, -} +export enum MarkerType { + structure = 0, + mdm = 1, + conseillerFrance = 2, + user = 3, +} diff --git a/src/app/config/map/zoomLevel.enum.ts b/src/app/map/components/zoomLevel.enum.ts similarity index 93% rename from src/app/config/map/zoomLevel.enum.ts rename to src/app/map/components/zoomLevel.enum.ts index 02e7a5e9432fbbc9f8562d86ee37a33fd501e4a0..fe020b772f172ac8fc926271fe7d10bb4e035449 100644 --- a/src/app/config/map/zoomLevel.enum.ts +++ b/src/app/map/components/zoomLevel.enum.ts @@ -1,6 +1,6 @@ -export enum ZoomLevel { - min = 10, - regular = 12, - userPosition = 15, - max = 19, -} +export enum ZoomLevel { + min = 10, + regular = 12, + userPosition = 15, + max = 19, +} diff --git a/src/app/map/map.module.ts b/src/app/map/map.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbb935ca5b3f27df1df0efded58dccc7d0380c1a --- /dev/null +++ b/src/app/map/map.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { CommonModule, DatePipe } from '@angular/common'; +import { SharedModule } from '../shared/shared.module'; +import { MapComponents } from './components'; +import { LeafletModule } from '@asymmetrik/ngx-leaflet'; +import { BrowserModule } from '@angular/platform-browser'; +@NgModule({ + imports: [CommonModule, BrowserModule, SharedModule, LeafletModule], + declarations: [MapComponents], + providers: [DatePipe], + exports: [MapComponents], +}) +export class MapModule {} diff --git a/src/app/map/models/addressGeometry.model.ts b/src/app/map/models/addressGeometry.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7612f2fc5fb6cd32127ca4e6779560bccd9f103 --- /dev/null +++ b/src/app/map/models/addressGeometry.model.ts @@ -0,0 +1,16 @@ +export class AddressGeometry { + public coordinates: Array<number>; + public type: string; + + constructor(obj?: any) { + Object.assign(this, obj); + } + + public getLat(): number { + return this.coordinates[1]; + } + + public getLon(): number { + return this.coordinates[0]; + } +} diff --git a/src/app/map/models/geoJsonProperties.model.ts b/src/app/map/models/geoJsonProperties.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..adbd5378f31e46cf1e6c27283f758cf9f498f053 --- /dev/null +++ b/src/app/map/models/geoJsonProperties.model.ts @@ -0,0 +1,10 @@ +export class GeoJsonProperties { + public city: string; + public country: string; + public name: string; + public nom: string; + public postcode: string; + public state: string; + public adresse: string; + public ville: string; +} diff --git a/src/app/map/models/geojson.model.ts b/src/app/map/models/geojson.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ca263895f084b62e528a5dc49280ded859bdab9 --- /dev/null +++ b/src/app/map/models/geojson.model.ts @@ -0,0 +1,14 @@ +import { AddressGeometry } from './addressGeometry.model'; +import { GeoJsonProperties } from './geoJsonProperties.model'; + +export class GeoJson { + public geometry: AddressGeometry; + public type: string; + public properties: GeoJsonProperties; + + constructor(obj?: any) { + Object.assign(this, obj, { + geometry: obj && obj.geometry ? new AddressGeometry(obj.geometry) : null, + }); + } +} diff --git a/src/app/map/services/map.service.spec.ts b/src/app/map/services/map.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd06fb3f41abb24d4fd933ea73c71ff3cbd27970 --- /dev/null +++ b/src/app/map/services/map.service.spec.ts @@ -0,0 +1,46 @@ +import { TestBed } from '@angular/core/testing'; + +import { MapService } from './map.service'; + +describe('MapService', () => { + let service: MapService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MapService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should create marke with coord {lat: 45.764043, lng: 4.835659}', () => { + const marker = service.createMarker(45.764043, 4.835659, 1); + + expect(marker.getLatLng().lat).toEqual(45.764043); + expect(marker.getLatLng().lng).toEqual(4.835659); + }); + it('should add marker to map with icon ic_marker.png', () => { + const marker = service.createMarker(45.764043, 4.835659, 1); + expect(marker.getIcon().options.iconSize).toEqual([19, 24]); + expect(marker.getIcon().options.iconAnchor).toEqual([9, 0]); + }); + + it('should cerate marker with popup', () => { + const marker = service.createMarker(45.764043, 4.835659, 1, null, '<p>Hello <br/>World !</p>'); + + expect(marker.getPopup().getContent()).toEqual('<p>Hello <br/>World !</p>'); + }); + + it('should get marker', () => { + const marker = service.createMarker(45.764043, 4.835659, 1, "53", '<p>Hello <br/>World !</p>'); + expect(marker).toEqual(service.getMarker("53")); + }); + it('should not get marker, with missing id', () => { + service.createMarker(45.764043, 4.835659, 1, null, '<p>Hello <br/>World !</p>'); + expect(service.getMarker("2")).toEqual(null); + }); + it('should not get marker, empty', () => { + expect(service.getMarker("2")).toEqual(null); + }); +}); diff --git a/src/app/map/services/map.service.ts b/src/app/map/services/map.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5c641f9085d4a77dde27ac648bd0b9175979f69 --- /dev/null +++ b/src/app/map/services/map.service.ts @@ -0,0 +1,179 @@ +import { Injectable } from '@angular/core'; +import { DivIcon, Map, Marker } from 'leaflet'; +import { Layers } from '../components/layers.enum'; +import { MarkerType } from '../components/markerType.enum'; +import { + markerIcon, + markerIconActive, + markerIconAddedToList, + markerIconHover, + markerIconMdm, + markerIconMdmActive, + userLocationIcon, +} from './marker'; +@Injectable({ + providedIn: 'root', +}) +export class MapService { + private static markersList = {}; + private isMarkerActive = false; + + public createMarker(lat: number, lon: number, markerType: MarkerType, id?: string, tooltip?: string): Marker { + const marker = new Marker([lat, lon], { + icon: this.getMarkerIcon(markerType), + attribution: this.getLayerAttributton(markerType), + }); + marker.on('mouseclick', (evt) => { + evt.target.openPopup(); + }); + + // handle icon change when select marker + marker.on('click', (evt) => { + evt.target.setIcon(this.getActiveMarkerIcon(markerType)); + }); + + if (tooltip) { + marker.bindPopup(tooltip); + + // handle icon change when unselect + marker.getPopup().on('remove', (evt) => { + marker.setIcon(this.getMarkerIcon(markerType)); + }); + } + + if (id) { + MapService.markersList[id] = marker; + } + return marker; + } + + private getLayerAttributton(markerType: MarkerType): string { + if (markerType === MarkerType.mdm) { + return Layers.mdm; + } else if (markerType === MarkerType.user) { + return Layers.user; + } else { + return Layers.structure; + } + } + + // Note: Marke IconFranceService has been removed temporarly on order to rework on buisness needs. + // This comment is applied for the next 4 methods + private getMarkerIcon(markerType: MarkerType): DivIcon { + switch (markerType) { + case MarkerType.mdm: + return markerIconMdm; + case MarkerType.conseillerFrance: + return markerIcon; + case MarkerType.user: + return userLocationIcon; + default: + return markerIcon; + } + } + + private getActiveMarkerIcon(markerType: MarkerType): DivIcon { + switch (markerType) { + case MarkerType.mdm: + return markerIconMdmActive; + case MarkerType.conseillerFrance: + // return markerIconFranceServiceActive; + return markerIconActive; + case MarkerType.user: + return userLocationIcon; + default: + return markerIconActive; + } + } + + private getAddedToListMarkerIcon(markerType: MarkerType): DivIcon { + switch (markerType) { + case MarkerType.conseillerFrance: + // return markerIconFranceServiceAddedToList; + return markerIconAddedToList; + case MarkerType.user: + return userLocationIcon; + default: + return markerIconAddedToList; + } + } + + private getHoverMarkerIcon(markerType: MarkerType): DivIcon { + switch (markerType) { + case MarkerType.conseillerFrance: + return markerIconHover; + case MarkerType.user: + return userLocationIcon; + default: + return markerIconHover; + } + } + + /** + * @param id marker id + */ + public setActiveMarker(id: string, type: MarkerType = MarkerType.structure): void { + this.getMarker(id).setIcon(this.getHoverMarkerIcon(type)); + } + + public setAddedToListMarker(id: string, type: MarkerType = MarkerType.structure): void { + this.getMarker(id).setIcon(this.getAddedToListMarkerIcon(type)); + } + + public setUnactiveMarker(id: string, type: MarkerType = MarkerType.structure): void { + // To skip mouseleave when user emit click on structure list + this.getMarker(id)?.setIcon(this.getMarkerIcon(type)); + this.isMarkerActive = false; + } + + /** + * Set a tooltip + * @param id markerId + * @param html html to display + */ + public setToolTip(id: string, html: string): void { + this.getMarker(id).bindTooltip(html); + } + + /** + * Set a marker as selected by changing icon color + * @param id markerId + * @param html html to display + */ + public setSelectedMarker(id: string, type: MarkerType = MarkerType.structure): void { + if (id) { + this.getMarker(id)?.setIcon(this.getActiveMarkerIcon(type)); + this.isMarkerActive = true; + } + } + + /** + * Set a marker as selected by changing icon color + * @param id markerId + * @param html html to display + */ + public setDefaultMarker(id: string, type: MarkerType = MarkerType.structure): void { + if (id) { + this.getMarker(id).setIcon(this.getMarkerIcon(type)); + } + } + + /** + * Get marker by id + */ + public getMarker(id: string): Marker { + return MapService.markersList[id] ? MapService.markersList[id] : null; + } + + public cleanMap(map: Map): Map { + MapService.markersList = {}; + if (map) { + map.eachLayer((layer) => { + if (layer instanceof Marker && layer.options.attribution == Layers.structure) { + map.removeLayer(layer); + } + }); + } + return map; + } +} diff --git a/src/app/map/services/marker.ts b/src/app/map/services/marker.ts new file mode 100644 index 0000000000000000000000000000000000000000..35efbc6ed376d8781d8163247edf5a732eb43a6d --- /dev/null +++ b/src/app/map/services/marker.ts @@ -0,0 +1,79 @@ +import { divIcon } from 'leaflet'; + +export const markerIcon = divIcon({ + className: null, + html: '<svg width="48" height="48" fill="#4C4D53"><use xlink:href="assets/ico/sprite.svg#map-marker"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const markerIconActive = divIcon({ + className: 'on-top-marker', + html: '<svg width="48" height="48"><use xlink:href="assets/ico/sprite.svg#map-markerSelected"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const markerIconHover = divIcon({ + className: 'on-top-marker', + html: '<svg width="48" height="48"><use xlink:href="assets/ico/sprite.svg#map-markerHover"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const markerIconAddedToList = divIcon({ + className: null, + html: '<svg width="48" height="48"><use xlink:href="assets/ico/sprite.svg#map-marker-added"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const userLocationIcon = divIcon({ + className: null, + html: '<svg width="34" height="34"><use xlink:href="assets/ico/sprite.svg#user-location"></use></svg>', + iconSize: [34, 34], + iconAnchor: [17, 0], +}); +export const markerIconMdm = divIcon({ + className: null, + html: + '<svg width="19" height="24" fill="#D4C4A9" class="mdm"><use xlink:href="assets/ico/sprite.svg#mdm"></use></svg>', + iconSize: [19, 24], + iconAnchor: [9, 0], +}); +export const markerIconMdmActive = divIcon({ + className: null, + html: '<svg width="19" height="24"><use xlink:href="assets/ico/sprite.svg#mdmActive"></use></svg>', + iconSize: [19, 24], + iconAnchor: [9, 0], +}); +export const markerIconFranceService = divIcon({ + className: null, + html: + '<svg width="48" height="48" fill="#ED3939" class="france-service"><use xlink:href="assets/ico/sprite.svg#conseillerFranceService"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const markerIconFranceServiceActive = divIcon({ + className: null, + html: + '<svg width="48" height="48"><use xlink:href="assets/ico/sprite.svg#conseillerFranceServiceSelected"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const markerIconFranceServiceHover = divIcon({ + className: null, + html: '<svg width="48" height="48"><use xlink:href="assets/ico/sprite.svg#conseillerFranceServiceHover"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); +export const markerIconFranceServiceAddedToList = divIcon({ + className: null, + html: '<svg width="48" height="48"><use xlink:href="assets/ico/sprite.svg#conseillerFranceServiceAdded"></use></svg>', + iconSize: [48, 48], + iconAnchor: [24, 48], + popupAnchor: [0, -48], +}); diff --git a/src/app/models/address.model.ts b/src/app/models/address.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ef6bc349a222c69531a34eac3331b4174c974a2 --- /dev/null +++ b/src/app/models/address.model.ts @@ -0,0 +1,7 @@ +export class Address { + numero: string = null; + street: string = null; + commune: string = null; + postcode?: number = null; + coordinates? = []; +} diff --git a/src/app/models/day.model.ts b/src/app/models/day.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..005ccfa242ebb935a533cd0d2f151dded3377e1f --- /dev/null +++ b/src/app/models/day.model.ts @@ -0,0 +1,12 @@ +import { Time } from './time.model'; + +export class Day { + open: boolean = false; + time: Time[]; + + constructor(obj?: any) { + Object.assign(this, obj, { + time: obj && obj.time ? obj.time.map((time) => new Time(time)) : [], + }); + } +} diff --git a/src/app/models/openingDay.model.ts b/src/app/models/openingDay.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..71ba8d444c97b55c4393830dfa483caf5cff4845 --- /dev/null +++ b/src/app/models/openingDay.model.ts @@ -0,0 +1,9 @@ +export class OpeningDay { + day: string = null; + schedule: string = null; + + constructor(day?: string, schedule?: string) { + this.day = day; + this.schedule = schedule; + } +} diff --git a/src/app/models/orientation-filter.object.ts b/src/app/models/orientation-filter.object.ts index 65d327d9cf0bd0b47c4310e209112ae9e997029c..49fe9ca54662b52e32c1a6fe7afc51396d1e5a2c 100644 --- a/src/app/models/orientation-filter.object.ts +++ b/src/app/models/orientation-filter.object.ts @@ -1,13 +1,15 @@ -import { Address, Category } from '@gouvfr-anct/mediation-numerique'; - -export class OrientationFormFilters { - specificProfile: Category; - handicap: boolean; - passNumeric: boolean; - structureAccompaniment: string; - contactAccompanimentPhone: string; - contactAccompanimentEmail: string; - beneficiaryName: string; - beneficiaryNeedCommentary: string; - address: Address; -} +import { Category } from '../structure-list/models/category.model'; +import { Module } from '../structure-list/models/module.model'; +import { Address } from './address.model'; + +export class OrientationFormFilters { + specificProfile: Category; + handicap: boolean; + passNumeric: boolean; + structureAccompaniment: string; + contactAccompanimentPhone: string; + contactAccompanimentEmail: string; + beneficiaryName: string; + beneficiaryNeedCommentary: string; + address: Address; +} diff --git a/src/app/models/owner.model.ts b/src/app/models/owner.model.ts index 55bc8c6c60a4f38d153d977935a5e5028a17bcef..45a6de715c2934e0fac3ff79d39b6890db89d016 100644 --- a/src/app/models/owner.model.ts +++ b/src/app/models/owner.model.ts @@ -1,5 +1,4 @@ export class Owner { email: string; _id: string; - // id: string; } diff --git a/src/app/models/personalOffer.model.ts b/src/app/models/personalOffer.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..e2edde9968953196aab6d86608dece76363d9f32 --- /dev/null +++ b/src/app/models/personalOffer.model.ts @@ -0,0 +1,9 @@ +export class PersonalOffer { + public publicsAccompaniment: string[] = []; + public proceduresAccompaniment: string[] = []; + public baseSkills: string[] = []; + public accessRight: string[] = []; + public digitalCultureSecurity: string[] = []; + public socialAndProfessional: string[] = []; + public parentingHelp: string[] = []; +} diff --git a/src/app/models/structure.model.ts b/src/app/models/structure.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..344e442ea40ef2f2c895ea841c480a9954c9d73d --- /dev/null +++ b/src/app/models/structure.model.ts @@ -0,0 +1,202 @@ +import { Equipment } from '../structure-list/enum/equipment.enum'; +import { typeStructureEnum } from '../shared/enum/typeStructure.enum'; +import { Weekday } from '../structure-list/enum/weekday.enum'; +import { Address } from './address.model'; +import { Day } from './day.model'; +import { OpeningDay } from './openingDay.model'; +import { Week } from './week.model'; +import { PersonalOffer } from './personalOffer.model'; + +export class Structure { + public _id: string = null; + public numero: string = null; + public createdAt: string = null; + public updatedAt: string = null; + public structureName: string = null; + public structureType: string = null; + public description: string = null; + public address: Address = new Address(); + public contactPhone: string = null; + public contactMail: string = null; + public website: string = null; + public facebook: string = null; + public twitter: string = null; + public instagram: string = null; + public linkedin: string = null; + public lockdownActivity: string = null; + public pmrAccess: boolean = null; + public placeOfReception: boolean = null; + public choiceCompletion: boolean = null; + public contactPersonFirstName: string = null; + public contactPersonLastName: string = null; + public contactPersonEmail: string = null; + public publicsAccompaniment: string[] = []; + public proceduresAccompaniment: string[] = []; + public remoteAccompaniment: boolean = null; + public accessModality: string[] = []; + public labelsQualifications: string[] = []; + public publics: string[] = []; + public nbComputers: number = null; + public nbPrinters: number = null; + public nbTablets: number = null; + public nbNumericTerminal: number = null; + public nbScanners: number = null; + public exceptionalClosures: string = null; + public equipmentsAndServices: string[] = []; + public hours: Week; + public freeWorkShop: boolean = null; + public otherDescription: string = null; + + public isOpen: boolean = false; + public openedOn: OpeningDay = new OpeningDay(); + public baseSkills: string[] = []; + public accessRight: string[] = []; + public parentingHelp: string[] = []; + public socialAndProfessional: string[] = []; + public digitalCultureSecurity: string[] = []; + + public distance?: number; + public coord?: number[] = []; + public dataShareConsentDate?: string; + + public accountVerified: boolean = false; + + public personalOffers: PersonalOffer[] = []; + + public alreadySelected? = false; + public isClaimed?: boolean = null; + + constructor(obj?: any) { + Object.assign(this, obj, { + hours: obj && obj.hours ? new Week(obj.hours) : new Week(), + }); + } + + public getDayhours(day: Weekday): Day { + switch (day) { + case Weekday.monday: + return this.hours.monday; + case Weekday.tuesday: + return this.hours.tuesday; + case Weekday.thursday: + return this.hours.thursday; + case Weekday.wednesday: + return this.hours.wednesday; + case Weekday.friday: + return this.hours.friday; + case Weekday.saturday: + return this.hours.saturday; + case Weekday.sunday: + return this.hours.sunday; + default: + return null; + } + } + + /** + * Check if a structure has equipments + */ + public hasEquipments(): boolean { + if (this.equipmentsAndServices.length && this.hasNotOnlyEmptyEquipments()) { + return true; + } + return false; + } + + /** + * Verify that a structure as not only equipments with 0 as value. This is mostly use for display. + * @returns {Boolean} validation + */ + public hasNotOnlyEmptyEquipments(): boolean { + if (this.nbComputers + this.nbPrinters + this.nbTablets + this.nbNumericTerminal + this.nbScanners > 0) return true; + return false; + } + + /** + * Check if a structure has pass Numeric label + */ + public hasPassNumeric(): boolean { + return this.labelsQualifications.includes('passNumerique'); + } + + /** + * Return a range, according to the distance, between [1,3] to get a distance reference. + * - [0,5km] => 1 + * - [5km,10km] => 2 + * - [10km, [ => 3 + */ + public getDistanceRange(): number { + if (!this.distance) { + return 3; + } else { + // If it's in km + if (this.distance > 10000) { + return 3; + } else if (this.distance < 5000) { + // If it's between 0 and 500 m + return 1; + } else { + return 2; + } + } + } + + public getLat(): number { + return this.coord[1]; + } + + public getLon(): number { + return this.coord[0]; + } + + public getEquipmentsIcon(equipment: Equipment): string { + switch (equipment) { + case Equipment.wifi: + return 'wifi'; + case Equipment.bornes: + return 'borne'; + case Equipment.printer: + return 'print'; + case Equipment.tablet: + return 'tablet'; + case Equipment.computer: + return 'computer'; + case Equipment.scanner: + return 'scan'; + default: + return null; + } + } + + public getEquipmentsTitle(equipment: Equipment): string { + switch (equipment) { + case Equipment.wifi: + return 'Wifi en accès libre'; + case Equipment.bornes: + return this.nbNumericTerminal > 1 ? 'Bornes numériques' : 'Borne numérique'; + case Equipment.printer: + return this.nbPrinters > 1 ? 'Imprimantes' : 'Imprimante'; + case Equipment.tablet: + return this.nbTablets > 1 ? 'Tablettes' : 'Tablette'; + case Equipment.computer: + return this.nbComputers > 1 ? 'Ordinateurs' : 'Ordinateur'; + case Equipment.scanner: + return this.nbScanners > 1 ? 'Scanners' : 'Scanner'; + default: + return null; + } + } + + public getLabelTypeStructure(): string { + return typeStructureEnum[this.structureType] ? typeStructureEnum[this.structureType] : ''; + } + + public hasSocialNetwork(): boolean { + return ( + (this.facebook !== null && this.facebook !== '') || + (this.instagram !== null && this.instagram !== '') || + (this.linkedin !== null && this.linkedin !== '') || + (this.twitter !== null && this.twitter !== '') + ); + } +} diff --git a/src/app/models/structureWithOwners.model.ts b/src/app/models/structureWithOwners.model.ts index cf8219dcb8a0d7e14443f3533513510d3e3d5c98..e3d2a6340abb8cf3ac225e16e43a0657c6f41d38 100644 --- a/src/app/models/structureWithOwners.model.ts +++ b/src/app/models/structureWithOwners.model.ts @@ -1,7 +1,7 @@ -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { Owner } from './owner.model'; - -export class StructureWithOwners { - structure: Structure; - owners: Owner[]; -} +import { Owner } from './owner.model'; +import { Structure } from './structure.model'; + +export class StructureWithOwners { + structure: Structure; + owners: Owner[]; +} diff --git a/src/app/models/time.model.ts b/src/app/models/time.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..7db99ae8b9cd374e8e06e1cdb410a54810fd025c --- /dev/null +++ b/src/app/models/time.model.ts @@ -0,0 +1,20 @@ +export class Time { + opening: string; + closing: string; + + constructor(obj?: any) { + Object.assign(this, obj); + } + + public formatOpeningDate(): string { + return this.formatDate(this.opening); + } + + public formatClosingDate(): string { + return this.formatDate(this.closing); + } + + private formatDate(n: string): string { + return n.replace(':', 'h'); + } +} diff --git a/src/app/models/week.model.ts b/src/app/models/week.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..14942b10743042703483e7882544e0d1a238f5af --- /dev/null +++ b/src/app/models/week.model.ts @@ -0,0 +1,59 @@ +import { Day } from './day.model'; + +export class Week { + monday: Day; + tuesday: Day; + wednesday: Day; + thursday: Day; + friday: Day; + saturday: Day; + sunday: Day; + + constructor(obj?: any) { + Object.assign(this, obj, { + monday: obj && obj.monday ? new Day(obj.monday) : new Day(), + tuesday: obj && obj.tuesday ? new Day(obj.tuesday) : new Day(), + wednesday: obj && obj.wednesday ? new Day(obj.wednesday) : new Day(), + thursday: obj && obj.thursday ? new Day(obj.thursday) : new Day(), + friday: obj && obj.friday ? new Day(obj.friday) : new Day(), + saturday: obj && obj.saturday ? new Day(obj.saturday) : new Day(), + sunday: obj && obj.sunday ? new Day(obj.sunday) : new Day(), + }); + } + + public getDayTranslation(day: string): string { + switch (day) { + case 'monday': + return 'lundi'; + case 'tuesday': + return 'mardi'; + case 'thursday': + return 'jeudi'; + case 'wednesday': + return 'mercredi'; + case 'friday': + return 'vendredi'; + case 'saturday': + return 'samedi'; + case 'sunday': + return 'dimanche'; + default: + return null; + } + } + + public hasData() { + if ( + this.monday.time.length === 0 && + this.tuesday.time.length === 0 && + this.wednesday.time.length === 0 && + this.thursday.time.length === 0 && + this.friday.time.length === 0 && + this.saturday.time.length === 0 && + this.sunday.time.length === 0 + ) { + return false; + } + return true; + } +} diff --git a/src/app/profile/edit/edit.component.ts b/src/app/profile/edit/edit.component.ts index 57146a1b7eeff793026b8fd572e4731b395c3817..ca4a6e47513ef01f48846487875952a5697faf60 100644 --- a/src/app/profile/edit/edit.component.ts +++ b/src/app/profile/edit/edit.component.ts @@ -1,15 +1,15 @@ -import { HttpErrorResponse } from '@angular/common/http'; import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { HttpErrorResponse } from '@angular/common/http'; import { forkJoin, of } from 'rxjs'; import { catchError, first, map } from 'rxjs/operators'; -import { Employer } from '../../models/employer.model'; import { Job } from '../../models/job.model'; import { User } from '../../models/user.model'; +import { Employer } from '../../models/employer.model'; import { AuthService } from '../../services/auth.service'; +import { ProfileService } from '../services/profile.service'; import { NotificationService } from '../../services/notification.service'; +import { ButtonType } from '../../shared/components/button/buttonType.enum'; import { CustomRegExp } from '../../utils/CustomRegExp'; -import { ProfileService } from '../services/profile.service'; enum tabsEnum { details, diff --git a/src/app/profile/profile.component.ts b/src/app/profile/profile.component.ts index d6534cc2ab5969752ea250db701015a4ea2a33cc..d977fbecdd4bd3f6e1f41cf44b30de1d48ad39f1 100644 --- a/src/app/profile/profile.component.ts +++ b/src/app/profile/profile.component.ts @@ -1,10 +1,10 @@ import { UserService } from './../services/user.service'; import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { StructureWithOwners } from '../models/structureWithOwners.model'; import { User } from '../models/user.model'; import { StructureService } from '../services/structure.service'; +import { ButtonType } from '../shared/components/button/buttonType.enum'; import { ProfileService } from './services/profile.service'; import { Utils } from '../utils/utils'; import { catchError, map } from 'rxjs/operators'; diff --git a/src/app/profile/services/profile.service.ts b/src/app/profile/services/profile.service.ts index 12787c79376f66de6e6a9e694e03e66ef059467d..50b1dadffdc7acbae1b9589e3e713f6cf95d63ac 100644 --- a/src/app/profile/services/profile.service.ts +++ b/src/app/profile/services/profile.service.ts @@ -1,15 +1,15 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import decode from 'jwt-decode'; import { Observable } from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; -import { Employer } from '../../models/employer.model'; -import { Job } from '../../models/job.model'; import { User } from '../../models/user.model'; +import decode from 'jwt-decode'; +import { UserRole } from '../../shared/enum/userRole.enum'; import { AuthService } from '../../services/auth.service'; +import { Structure } from '../../models/structure.model'; +import { Employer } from '../../models/employer.model'; +import { Job } from '../../models/job.model'; +import { catchError, map } from 'rxjs/operators'; import { NotificationService } from '../../services/notification.service'; -import { UserRole } from '../../shared/enum/userRole.enum'; @Injectable({ providedIn: 'root', @@ -116,6 +116,7 @@ export class ProfileService { } public updateDetails(newDetails: { name: string; surname: string; phone: string }): Observable<User | Error> { + console.log('profile service updates details'); return this.http.post<User>(`${this.baseUrl}/details`, newDetails).pipe( map((user) => user), catchError(() => { diff --git a/src/app/profile/structure-edition-summary/structure-edition-summary.component.ts b/src/app/profile/structure-edition-summary/structure-edition-summary.component.ts index 4b54f47e35a85355770bb277bbb1334a9bdbbf79..dca4ba56fbd5d30d0826203db3ed9c1b82fd3f76 100644 --- a/src/app/profile/structure-edition-summary/structure-edition-summary.component.ts +++ b/src/app/profile/structure-edition-summary/structure-edition-summary.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; import { structureFormStep } from '../../form/form-view/structure-form/structureFormStep.enum'; +import { Structure } from '../../models/structure.model'; import { formUtils, IStructureSummary } from '../../utils/formUtils'; @Component({ diff --git a/src/app/reset-password/reset-password.component.ts b/src/app/reset-password/reset-password.component.ts index 678801fb249c390a4654a72346bc3f3110b3a2b5..834a043f4ea2d441752b5be116429b6326aa4826 100644 --- a/src/app/reset-password/reset-password.component.ts +++ b/src/app/reset-password/reset-password.component.ts @@ -1,14 +1,14 @@ import { Component, OnInit } from '@angular/core'; import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { AuthService } from '../services/auth.service'; import { NotificationService } from '../services/notification.service'; +import { ButtonType } from '../shared/components/button/buttonType.enum'; @Component({ selector: 'app-reset-password', templateUrl: './reset-password.component.html', - styleUrls: ['./reset-password.component.scss'], + styleUrls: ['./reset-password.component.scss'] }) export class ResetPasswordComponent implements OnInit { public resetForm: UntypedFormGroup; @@ -28,7 +28,7 @@ export class ResetPasswordComponent implements OnInit { ngOnInit(): void { this.resetForm = this.formBuilder.group({ - email: ['', Validators.required], + email: ['', Validators.required] }); } diff --git a/src/app/resolvers/structure.resolver.ts b/src/app/resolvers/structure.resolver.ts index 9e5a06d0ee42f3958a2725e1e4462da0d4c23edc..a7eb4d9ce340cdd8845b257f4b7b30be730ff03d 100644 --- a/src/app/resolvers/structure.resolver.ts +++ b/src/app/resolvers/structure.resolver.ts @@ -1,22 +1,22 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { Observable } from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; -import { StructureService } from '../services/structure.service'; - -@Injectable() -export class StructureResolver implements Resolve<Structure> { - constructor(private structureService: StructureService, private router: Router) {} - - resolve(route: ActivatedRouteSnapshot): Observable<Structure> { - const structureId = route.params.id; - return this.structureService.getStructure(structureId).pipe( - map((res) => res), - catchError(() => { - this.router.navigate(['/home']); - return new Observable<Structure>(); - }) - ); - } -} +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { Structure } from '../models/structure.model'; +import { StructureService } from '../services/structure.service'; + +@Injectable() +export class StructureResolver implements Resolve<Structure> { + constructor(private structureService: StructureService, private router: Router) {} + + resolve(route: ActivatedRouteSnapshot): Observable<Structure> { + const structureId = route.params.id; + return this.structureService.getStructure(structureId).pipe( + map((res) => res), + catchError(() => { + this.router.navigate(['/home']); + return new Observable<Structure>(); + }) + ); + } +} diff --git a/src/app/services/geojson.service.ts b/src/app/services/geojson.service.ts index 35af50b1bfefa000ee27a4a9c78c96070ba4184d..b598e926a79fc6c9d463dbdaa8cbd401a32859a9 100644 --- a/src/app/services/geojson.service.ts +++ b/src/app/services/geojson.service.ts @@ -1,9 +1,9 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { GeoJson } from '@gouvfr-anct/mediation-numerique'; -import * as _ from 'lodash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { GeoJson } from '../map/models/geojson.model'; +import * as _ from 'lodash'; @Injectable({ providedIn: 'root', diff --git a/src/app/services/personal-offer.service.ts b/src/app/services/personal-offer.service.ts index 62ef5ba1a2fcfd8c601dee24dac26c3e308e30fe..aa57be29be6269016ef634e2ca24a9403791c29f 100644 --- a/src/app/services/personal-offer.service.ts +++ b/src/app/services/personal-offer.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { PersonalOffer } from '@gouvfr-anct/mediation-numerique'; import { Observable } from 'rxjs'; +import { PersonalOffer } from '../models/personalOffer.model'; @Injectable({ providedIn: 'root', diff --git a/src/app/services/structure.service.spec.ts b/src/app/services/structure.service.spec.ts index 9afc7dc24d32133be90149c369298d06e456355c..7d193dcb4c0169073060e00264b21b3ef6e80aa7 100644 --- a/src/app/services/structure.service.spec.ts +++ b/src/app/services/structure.service.spec.ts @@ -1,61 +1,63 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; -import { Day, Structure, Time, Week } from '@gouvfr-anct/mediation-numerique'; -import { StructureService } from './structure.service'; - -const { DateTime } = require('luxon'); - -describe('StructureService', () => { - let structureService: StructureService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [StructureService], - }); - structureService = TestBed.inject(StructureService); - }); - - it('Mise à jour ouverture de la structure : should return true', () => { - // Init structure avec aucun horaire - const s: Structure = new Structure(); - s.hours = new Week(); - s.hours.monday = new Day(false); - s.hours.tuesday = new Day(false); - s.hours.wednesday = new Day(false); - s.hours.thursday = new Day(false); - s.hours.friday = new Day(false); - s.hours.saturday = new Day(false); - s.hours.sunday = new Day(false); - s.hours.thursday.open = true; - s.hours.thursday.time = new Array( - new Time({ opening: 805, closing: 1200 }), - new Time({ opening: 1400, closing: 1600 }) - ); - - // Init date sur un jeudi à 9h05 - const dt = new DateTime.local(2020, 10, 8, 9, 5); - const result = structureService.updateOpeningStructure(s); - expect(result.isOpen).toEqual(true); - }); - - it('Mise à jour ouverture de la structure : should return false', () => { - // Init structure avec aucun horaire - const s: Structure = new Structure(); - s.hours = new Week(); - s.hours.monday = new Day(); - s.hours.tuesday = new Day(); - s.hours.wednesday = new Day(); - s.hours.thursday = new Day(); - s.hours.friday = new Day(); - s.hours.saturday = new Day(); - s.hours.sunday = new Day(); - - s.hours.thursday.open = true; - s.hours.thursday.time = new Array(new Time({ opening: 1400, closing: 1600 })); - // Init date sur un jeudi à 9h05 - const dt = new DateTime.local(2020, 10, 8, 9, 5); - const result = structureService.updateOpeningStructure(s); - expect(result.isOpen).toEqual(false); - }); -}); +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { Day } from '../models/day.model'; +import { Structure } from '../models/structure.model'; +import { Time } from '../models/time.model'; +import { Week } from '../models/week.model'; +import { StructureService } from './structure.service'; +const { DateTime } = require('luxon'); + +describe('StructureService', () => { + let structureService: StructureService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [StructureService], + }); + structureService = TestBed.inject(StructureService); + }); + + it('Mise à jour ouverture de la structure : should return true', () => { + // Init structure avec aucun horaire + const s: Structure = new Structure(); + s.hours = new Week(); + s.hours.monday = new Day(false); + s.hours.tuesday = new Day(false); + s.hours.wednesday = new Day(false); + s.hours.thursday = new Day(false); + s.hours.friday = new Day(false); + s.hours.saturday = new Day(false); + s.hours.sunday = new Day(false); + s.hours.thursday.open = true; + s.hours.thursday.time = new Array( + new Time({ opening: 805, closing: 1200 }), + new Time({ opening: 1400, closing: 1600 }) + ); + + // Init date sur un jeudi à 9h05 + const dt = new DateTime.local(2020, 10, 8, 9, 5); + const result = structureService.updateOpeningStructure(s); + expect(result.isOpen).toEqual(true); + }); + + it('Mise à jour ouverture de la structure : should return false', () => { + // Init structure avec aucun horaire + const s: Structure = new Structure(); + s.hours = new Week(); + s.hours.monday = new Day(); + s.hours.tuesday = new Day(); + s.hours.wednesday = new Day(); + s.hours.thursday = new Day(); + s.hours.friday = new Day(); + s.hours.saturday = new Day(); + s.hours.sunday = new Day(); + + s.hours.thursday.open = true; + s.hours.thursday.time = new Array(new Time({ opening: 1400, closing: 1600 })); + // Init date sur un jeudi à 9h05 + const dt = new DateTime.local(2020, 10, 8, 9, 5); + const result = structureService.updateOpeningStructure(s); + expect(result.isOpen).toEqual(false); + }); +}); diff --git a/src/app/services/structure.service.ts b/src/app/services/structure.service.ts index 2d24291a6b1b841ce74db43743921c2d78d061eb..3c7e9eab88532c4fea1569dbae94338747e5de47 100644 --- a/src/app/services/structure.service.ts +++ b/src/app/services/structure.service.ts @@ -1,14 +1,20 @@ -import { WeekDay } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Filter, OpeningDay, Structure, Weekday } from '@gouvfr-anct/mediation-numerique'; -import * as _ from 'lodash'; +import { WeekDay } from '@angular/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import * as _ from 'lodash'; +const { DateTime } = require('luxon'); + +import { Structure } from '../models/structure.model'; +import { Day } from '../models/day.model'; +import { OpeningDay } from '../models/openingDay.model'; +import { Weekday } from '../structure-list/enum/weekday.enum'; +import { Time } from '../models/time.model'; +import { Filter } from '../structure-list/models/filter.model'; +import { User } from '../models/user.model'; import { StructureWithOwners } from '../models/structureWithOwners.model'; import { TempUser } from '../models/temp-user.model'; -import { User } from '../models/user.model'; -const { DateTime } = require('luxon'); @Injectable({ providedIn: 'root', @@ -166,9 +172,7 @@ export class StructureService { } public getStructureWithOwners(structureId: string, profile: User): Observable<StructureWithOwners> { - return this.http.post<StructureWithOwners>(`${this.baseUrl}/${structureId}/withOwners`, { - emailUser: profile?.email, - }); + return this.http.post<any>(`${this.baseUrl}/${structureId}/withOwners`, { emailUser: profile?.email }); } public sendMailOnStructureError(structureId: string, content: string): Observable<any> { diff --git a/src/app/shared/components/address-autocomplete/address-autocomplete.component.ts b/src/app/shared/components/address-autocomplete/address-autocomplete.component.ts index b7e7f683bae4aa69a0e4be16e5652c1bd717a31f..f079a7934fa946e79aea7cd21c6dbf759f4788e9 100644 --- a/src/app/shared/components/address-autocomplete/address-autocomplete.component.ts +++ b/src/app/shared/components/address-autocomplete/address-autocomplete.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { Address } from '@gouvfr-anct/mediation-numerique'; +import { Address } from '../../../models/address.model'; import { AddressService } from '../../service/address.service'; @Component({ diff --git a/src/app/shared/components/button/button.component.html b/src/app/shared/components/button/button.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e8495712311bdc7cdf0bd14feaadcdd8db6c65a5 --- /dev/null +++ b/src/app/shared/components/button/button.component.html @@ -0,0 +1,268 @@ +<ng-container *ngIf="style === buttonTypeEnum.Regular"> + <button class="btn-regular" type="{{ type }}" (click)="doAction()" [disabled]="disabled"> + <div *ngIf="!iconBtn" class="text">{{ text }}</div> + <div + *ngIf="iconBtn && iconPos === 'left'" + fxLayout="row center" + class="text withIcon left" + fxLayoutAlign="space-around center" + > + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span>{{ text }}</span> + </div> + <div + *ngIf="iconBtn && iconPos === 'right'" + fxLayout="row center" + class="text withIcon right" + fxLayoutAlign="space-around center" + > + <span>{{ text }}</span> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.searchIcon"> + <button class="searchIcon" type="{{ type }}" (click)="doAction()"> + <div fxLayout="row center" class="searchIcon withIcon" fxLayoutAlign="space-between center"> + <app-svg-icon [type]="'ico'" [icon]="iconBtn" [iconColor]="'currentColor'" [iconClass]="'icon-30'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.Primary"> + <button + class="btn-regular primary" + type="{{ type }}" + (click)="doAction()" + [disabled]="disabled" + [ngClass]="extraClass" + > + <div *ngIf="!iconBtn" class="text">{{ text }}</div> + <div + *ngIf="iconBtn && iconPos === 'left'" + fxLayout="row center" + class="text withIcon left" + fxLayoutAlign="space-around center" + > + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span [ngClass]="extraClass">{{ text }}</span> + </div> + <div + *ngIf="iconBtn && iconPos === 'right'" + fxLayout="row center" + class="text withIcon right" + fxLayoutAlign="space-around center" + > + <span [ngClass]="extraClass">{{ text }}</span> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.modalPrimary"> + <button class="btn-regular modal-primary" type="{{ type }}" (click)="doAction()" [disabled]="disabled"> + <div *ngIf="!iconBtn" class="text">{{ text }}</div> + <div *ngIf="iconBtn && iconPos === 'left'" fxLayout="row center" class="text" fxLayoutAlign="space-around center"> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span>{{ text }}</span> + </div> + <div *ngIf="iconBtn && iconPos === 'right'" fxLayout="row center" class="text" fxLayoutAlign="space-around center"> + <span>{{ text }}</span> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.modalSecondary"> + <button + class="btn-regular modal-secondary" + type="{{ type }}" + (click)="doAction()" + [disabled]="disabled" + [ngClass]="{ disabled: disabled }" + > + <div *ngIf="!iconBtn" class="text">{{ text }}</div> + <div *ngIf="iconBtn && iconPos === 'left'" fxLayout="row center" class="text" fxLayoutAlign="space-around center"> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span>{{ text }}</span> + </div> + <div *ngIf="iconBtn && iconPos === 'right'" fxLayout="row center" class="text" fxLayoutAlign="space-around center"> + <span>{{ text }}</span> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.Secondary || style === buttonTypeEnum.SecondaryWide"> + <button + [ngClass]="{ + 'btn-regular secondary': true, + wide: style === buttonTypeEnum.SecondaryWide + }" + type="{{ type }}" + (click)="doAction()" + [disabled]="disabled" + > + <div *ngIf="!iconBtn" [ngClass]="extraClass" class="text">{{ text }}</div> + <div + *ngIf="iconBtn && iconPos === 'left'" + fxLayout="row center" + class="text withIcon left" + fxLayoutAlign="space-around center" + > + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span>{{ text }}</span> + </div> + <div + *ngIf="iconBtn && iconPos === 'right'" + fxLayout="row center" + class="text withIcon right" + fxLayoutAlign="space-around center" + > + <span>{{ text }}</span> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.SecondaryUltraWide"> + <button class="btn-regular secondary ultrawide" type="{{ type }}" (click)="doAction()" [disabled]="disabled"> + <div *ngIf="!iconBtn" [ngClass]="extraClass" class="text">{{ text }}</div> + <div + *ngIf="iconBtn && iconPos === 'left'" + fxLayout="row center" + class="text withIcon left" + fxLayoutAlign="center center" + > + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span>{{ text }}</span> + </div> + <div + *ngIf="iconBtn && iconPos === 'right'" + fxLayout="row center" + class="text withIcon right" + fxLayoutAlign="center center" + > + <span>{{ text }}</span> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.SecondaryOnlyIcon"> + <button class="btn-regular secondary" type="{{ type }}" (click)="doAction()" [disabled]="disabled"> + <div *ngIf="iconBtn" fxLayout="row center" class="text withIcon center" fxLayoutAlign="space-around center"> + <app-svg-icon [type]="iconType" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.ButtonPhone"> + <button [disabled]="disabled" class="btn-switch-phone" type="{{ type }}" (click)="doAction()"> + <div *ngIf="!iconBtn" class="text">{{ text }}</div> + <div + *ngIf="iconBtn" + fxLayout="row center" + class="text withIcon" + fxLayoutAlign="space-around center" + fxLayoutGap="13px" + > + <app-svg-icon + class="iconBtn" + [type]="'ico'" + [iconClass]="'icon-32'" + [icon]="iconBtn" + [iconColor]="'currentColor'" + ></app-svg-icon> + {{ text }} + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.Filter"> + <button + [disabled]="disabled" + class="btn-filter-phone" + type="{{ type }}" + (click)="doAction()" + [ngClass]="{ disabled: disabled }" + > + <div *ngIf="!iconBtn" class="text">{{ text }}</div> + <div + *ngIf="iconBtn" + fxLayout="row center" + class="text withIcon" + fxLayoutAlign="space-around center" + fxLayoutGap="13px" + > + <app-svg-icon [type]="'ico'" [iconClass]="'icon-32'" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + {{ text }} + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.Tertiary"> + <button + class="btn-regular tertiary" + type="{{ type }}" + (click)="doAction()" + [disabled]="disabled" + [ngClass]="extraClass" + > + <div>{{ text }}</div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.CheckButton"> + <button + class="btn-regular tertiary checkButton" + type="{{ type }}" + (click)="doAction()" + [disabled]="disabled" + [ngClass]="extraClass" + > + <div fxLayout="row center" fxLayoutAlign="space-around center" fxLayoutGap="13px"> + <app-svg-icon + *ngIf="extraClass" + [type]="'ico'" + [icon]="'validate'" + [iconClass]="'icon-28'" + [iconColor]="'currentColor'" + ></app-svg-icon> + {{ text }} + </div> + </button> +</ng-container> + +<ng-container *ngIf="style === buttonTypeEnum.IconOnly"> + <button + class="btn-regular icon-only" + type="{{ type }}" + (click)="doAction()" + [disabled]="disabled" + [ngClass]="{ active: active }" + > + <div *ngIf="iconBtn" class="text withIcon"> + <app-svg-icon + *ngIf="iconBtn" + [type]="iconType" + [icon]="iconBtn" + [iconColor]="" + [iconClass]="'icon-28'" + [iconColor]="active ? 'green' : 'currentColor'" + ></app-svg-icon> + </div> + </button> +</ng-container> +<ng-container *ngIf="style === buttonTypeEnum.TagCloudButton"> + <button class="btn-tags-cloud" fxLayout="row" (click)="doAction()"> + <span>{{ text }}</span> + <app-svg-icon + [type]="'ico'" + [iconClass]="'icon-centered'" + [icon]="'tagDelete'" + [iconColor]="'white'" + ></app-svg-icon> + </button> +</ng-container> diff --git a/src/app/shared/components/button/button.component.scss b/src/app/shared/components/button/button.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..2f34723d86d217c1e9dc403474fffebd94e86834 --- /dev/null +++ b/src/app/shared/components/button/button.component.scss @@ -0,0 +1,320 @@ +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/breakpoint'; +@import '../../../../assets/scss/shapes'; + +@mixin btn-bold { + @include lato-bold-13; + line-height: 18px; +} +@mixin btn-bold-underline { + @include btn-bold; + text-decoration: underline; +} +@mixin btn-regular { + @include lato-regular-13; + line-height: 19px; +} +button { + outline: none; + border-radius: 4px; + cursor: pointer; + border: 1px solid; + padding: 0; +} +.searchIcon { + background: transparent; + border: none; + & > svg { + width: 30px; + height: 30px; + } +} +.btnSearch { + @include background-hash($grey-2); + border-color: $grey-4; + padding: 0 0 4px 4px; +} + +.btn-regular { + background: $grey-8; + border-radius: 5px 5px 4px 5px; + border: 0; + div:first-child { + width: 125px; + } + &:hover { + @include background-hash($grey-1); + padding: 0; + } + &:focus { + border-color: $primary-color; + } + &:active { + background: none; + border-color: $grey-8; + } + .searchButton { + background: $grey-6; + } + &.primary { + border: 0; + @include btn-bold; + .text { + background: $primary-color; + border: 1px solid $grey-1; + line-height: 15px; + + color: $white; + &.withIcon { + color: $white; + } + } + } + &.modal-primary { + border: none; + width: 100%; + @include btn-bold; + .text { + display: flex; + text-align: center; + align-items: center; + justify-content: center; + background: $primary-color; + margin: auto; + color: $white; + width: auto; + line-height: 15px; + &.withIcon { + color: $white; + } + } + } + &.modal-secondary { + border: none; + width: 100%; + @include btn-regular; + .text { + display: flex; + text-align: center; + align-items: center; + justify-content: center; + margin: auto; + color: $grey-1; + width: auto; + line-height: 15px; + &.withIcon { + color: $grey-1; + } + } + } + &.secondary { + div:first-child { + width: unset; + } + border: 0; + background: $white; + @include btn-regular; + .text { + background: $white; + border: 1px solid $grey-1; + color: $grey-1; + font-size: 14px; + line-height: 15px; + &.withIcon { + color: $grey-1; + } + } + &.wide { + div:first-child { + min-width: 50px; + width: 184px; + } + } + &.ultrawide { + div:first-child { + min-width: 50px; + width: 400px; + @media #{$tablet} { + width: 200px; + } + span { + padding-inline: 4px; + } + } + } + .small-text { + height: 22px !important; + } + &.wide { + div:first-child { + min-width: 50px; + width: 184px; + } + } + &.ultrawide { + div:first-child { + min-width: 50px; + width: 400px; + @media #{$tablet} { + width: 200px; + } + span { + padding-inline: 4px; + } + } + } + } + &.tertiary { + div:first-child { + min-width: 50px; + width: unset; + } + &:hover { + border: 1px solid $grey-4; + box-sizing: border-box; + background: $grey-7 !important; + } + border: 1px solid transparent; + background: $grey-7; + height: 36px; + color: $black; + padding: 0px 16px; + border-radius: 20px; + min-width: 50px; + + @include btn-regular; + &.selected { + background-color: $green-1 !important; + color: white !important; + &:hover { + background-color: $green-1 !important; + } + } + &.checkButton { + padding: 0px 14px; + font-weight: bold; + div:first-child { + padding: 0 14px; + } + &.selected { + padding: unset; + } + } + } + + &.icon-only { + div:first-child { + width: unset; + } + &.active { + .text { + border-color: $green-1; + } + &:hover { + background: unset !important; + } + } + &.center { + padding-left: 6px !important; + padding-right: 6px !important; + } + } + .text { + position: relative; + top: -1px; + right: -1px; + border: 1px solid $grey-1; + background: $white; + height: 31px; + color: $grey-1; + padding: 3px 15px; + display: table-cell; + vertical-align: middle; + border-radius: 4px; + font-size: 14px; + line-height: 15px; + &.withIcon { + color: $black; + height: 36px; + &.left { + padding-left: 8px !important; + } + &.right { + padding-right: 8px !important; + } + &.center { + padding-left: 6px !important; + padding-right: 6px !important; + } + } + } +} + +.btn-switch-phone { + background: $black; + height: 40px; + width: 124px; + color: $white; + padding: 4px 28px; + border-radius: 20px; + .iconBtn { + margin-right: 6px; + } +} + +.btn-filter-phone { + background: $white; + height: 40px; + color: $grey-1; + padding: 4px 37px 4px 37px; + border-color: $grey-4; + @include btn-normal; + &.containCheckedFilters { + border-color: $primary-color; + } +} +.fullButton { + width: 125px !important; +} +.fullWidth { + width: 100% !important; +} +.bigButton { + width: 280px !important; + .text { + width: inherit !important; + } +} +.btn-tags-cloud { + appearance: none; + + @include lato-regular-12; + justify-content: center; + align-items: center; + box-sizing: border-box; + height: 25px; + border-radius: 20px; + padding: 5px 10px 5px 15px; + max-width: 150px; + color: $grey-8; + border-style: none; + margin-top: 5px; + background: $grey-1; + text-overflow: ellipsis; + + span { + text-overflow: ellipsis; + max-width: 130px; + white-space: nowrap; + overflow: hidden; + display: inline-block; + } + &.unchecked { + background: $grey-1; + } +} +button:disabled { + opacity: 0.4; + cursor: not-allowed; +} diff --git a/src/app/shared/components/button/button.component.spec.ts b/src/app/shared/components/button/button.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b2d1343741e4a367e4dce58f1549fc88dd4b29d --- /dev/null +++ b/src/app/shared/components/button/button.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ButtonComponent } from './button.component'; + +describe('button', () => { + let component: ButtonComponent; + let fixture: ComponentFixture<ButtonComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ButtonComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/button/button.component.ts b/src/app/shared/components/button/button.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..744ce81133ac79a5e30dda09fe2a513be37860e3 --- /dev/null +++ b/src/app/shared/components/button/button.component.ts @@ -0,0 +1,26 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonType } from './buttonType.enum'; + +@Component({ + selector: 'app-button', + templateUrl: './button.component.html', + styleUrls: ['./button.component.scss'], +}) +export class ButtonComponent { + @Input() public style: ButtonType = ButtonType.Regular; + @Input() public text: string; + @Input() public type: string; + @Input() public iconType = 'ico'; + @Input() public iconBtn: string; + @Input() public iconPos = 'left'; + @Input() public extraClass: string; + @Input() public disabled = false; + @Input() public active = false; + @Output() public action = new EventEmitter(); + + public buttonTypeEnum = ButtonType; + + public doAction(): void { + this.action.emit(); + } +} diff --git a/src/app/shared/components/button/buttonType.enum.ts b/src/app/shared/components/button/buttonType.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..99cd644b020d619d231a142fa18c2f6e3d269344 --- /dev/null +++ b/src/app/shared/components/button/buttonType.enum.ts @@ -0,0 +1,17 @@ +export enum ButtonType { + Regular, + Primary, + Secondary, + SecondaryWide, + SecondaryUltraWide, + SecondaryOnlyIcon, + Tertiary, + ButtonPhone, + Filter, + IconOnly, + CheckButton, + searchIcon, + modalPrimary, + modalSecondary, + TagCloudButton, +} diff --git a/src/app/shared/components/data-share-consent/data-share-consent.component.ts b/src/app/shared/components/data-share-consent/data-share-consent.component.ts index 48be3289a03185c4ff2e601f5a06a9499daca5a1..04717b46d6a0120e25af9cd5ebcbcd838a8b2154 100644 --- a/src/app/shared/components/data-share-consent/data-share-consent.component.ts +++ b/src/app/shared/components/data-share-consent/data-share-consent.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { AbstractControl, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; import { Subject } from 'rxjs'; +import { Structure } from '../../../models/structure.model'; import { StructureService } from '../../../services/structure.service'; @Component({ diff --git a/src/app/shared/components/hour-picker/hour-picker.component.ts b/src/app/shared/components/hour-picker/hour-picker.component.ts index a4b6a43bf13d19ff0473b3b42162790544fb7277..40b8366424ec6bae9a82d68e48c89463ae36a6ee 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.ts +++ b/src/app/shared/components/hour-picker/hour-picker.component.ts @@ -1,14 +1,15 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; -import { Day, Time } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { Day } from '../../../models/day.model'; +import { Time } from '../../../models/time.model'; +import { ButtonType } from '../button/buttonType.enum'; import { WeekDayEnum } from '../../enum/weekDay.enum'; import { CheckHours } from '../../validator/form'; @Component({ selector: 'app-hour-picker', templateUrl: './hour-picker.component.html', - styleUrls: ['./hour-picker.component.scss'] + styleUrls: ['./hour-picker.component.scss'], }) export class HourPickerComponent implements OnChanges, OnDestroy { @Input() modifiedFields: any; @@ -23,7 +24,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { private isInputSelected = false; public copiedDayName = ''; public structure = { - hours: this.initHoursDefault() + hours: this.initHoursDefault(), }; public structureHoursDefault: any[] = this.initHoursDefault(); @@ -45,44 +46,44 @@ export class HourPickerComponent implements OnChanges, OnDestroy { name: 'Lundi', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false + active: false, }, { name: 'Mardi', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false + active: false, }, { name: 'Mercredi', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false + active: false, }, { name: 'Jeudi', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false + active: false, }, { name: 'Vendredi', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false + active: false, }, { name: 'Samedi', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false + active: false, }, { name: 'Dimanche', hours: [{ start: '', end: '', error: 'incomplete' }], open: false, - active: false - } + active: false, + }, ]; } @@ -100,20 +101,20 @@ export class HourPickerComponent implements OnChanges, OnDestroy { return { start: hour.opening, end: hour.closing, - error: null + error: null, }; } else { if (hour.opening) { return { start: hour.opening, end: '', - error: 'incomplete' + error: 'incomplete', }; } else { return { start: '', end: hour.closing, - error: 'incomplete' + error: 'incomplete', }; } } @@ -125,16 +126,21 @@ export class HourPickerComponent implements OnChanges, OnDestroy { this.structure.hours = this.structureHoursDefault; } - private parseToDay(data: { name: string; hours: { start: string; end: string }[]; open: boolean; active: boolean }): Day { + private parseToDay(data: { + name: string; + hours: { start: string; end: string }[]; + open: boolean; + active: boolean; + }): Day { return new Day({ open: data.open, time: data.hours.map( (hour) => new Time({ opening: hour.start, - closing: hour.end + closing: hour.end, }) - ) + ), }); } @@ -146,7 +152,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { thursday: this.createDay(this.parseToDay(this.structure.hours[3])), friday: this.createDay(this.parseToDay(this.structure.hours[4])), saturday: this.createDay(this.parseToDay(this.structure.hours[5])), - sunday: this.createDay(this.parseToDay(this.structure.hours[6])) + sunday: this.createDay(this.parseToDay(this.structure.hours[6])), }); } @@ -209,7 +215,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { day.hours.push({ start: '', end: '', - error: 'incomplete' + error: 'incomplete', }); this.submitForm(); } @@ -298,14 +304,14 @@ export class HourPickerComponent implements OnChanges, OnDestroy { private createDay(day: Day): UntypedFormGroup { return new UntypedFormGroup({ open: new UntypedFormControl(day.open, Validators.required), - time: new UntypedFormArray(day.time.map((oneTime) => this.createTime(oneTime))) as UntypedFormArray + time: new UntypedFormArray(day.time.map((oneTime) => this.createTime(oneTime))) as UntypedFormArray, }); } private createTime(time: Time): UntypedFormGroup { return new UntypedFormGroup({ opening: new UntypedFormControl(time.opening, Validators.required), - closing: new UntypedFormControl(time.closing, [Validators.required, CheckHours(time.opening)]) + closing: new UntypedFormControl(time.closing, [Validators.required, CheckHours(time.opening)]), }); } } diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index 5b59fcfe43d862110780eae044579c8f761fa971..b8449db73ec60f3a1a5985da8ee7824e5dc2be29 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -1,23 +1,28 @@ -import { InformationStepComponent } from '../../form/form-view/global-components/information-step/information-step.component'; +import { ButtonComponent } from './button/button.component'; +import { LogoCardComponent } from './logo-card/logo-card.component'; +import { SvgIconComponent } from './svg-icon/svg-icon.component'; +import { ValidatorFormComponent } from './validator-form/validator-form.component'; +import { CreateAccountFormComponent } from './create-account-form/create-account-form.component'; import { AddressAutocompleteComponent } from './address-autocomplete/address-autocomplete.component'; +import { StructureTypePickerComponent } from './structure-type-picker/structure-type-picker.component'; import { CheckboxFormComponent } from './checkbox-form/checkbox-form.component'; -import { CreateAccountFormComponent } from './create-account-form/create-account-form.component'; -import { CopyPasteComponent } from './hour-picker/copy-paste/copy-paste.component'; import { HourPickerComponent } from './hour-picker/hour-picker.component'; -import { LogoCardComponent } from './logo-card/logo-card.component'; +import { CopyPasteComponent } from './hour-picker/copy-paste/copy-paste.component'; +import { RadioFormComponent } from './radio-form/radio-form.component'; import { ModalConfirmationComponent } from './modal-confirmation/modal-confirmation.component'; import { ModalJoinConfirmationComponent } from './modal-join-confirmation/modal-join-confirmation.component'; +import { StructureOptionsModalComponent } from './structure-options-modal/structure-options-modal.component'; import { ModalOptionsComponent } from './modal-options/modal-options.component'; +import { TextInputModalComponent } from './text-input-modal/text-input-modal.component'; import { PasswordFormComponent } from './password-form/password-form.component'; -import { RadioFormComponent } from './radio-form/radio-form.component'; -import { StructureOptionsModalComponent } from './structure-options-modal/structure-options-modal.component'; -import { StructureTypePickerComponent } from './structure-type-picker/structure-type-picker.component'; import { TrainingTypePickerComponent } from './training-type-picker/training-type-picker.component'; -import { ValidatorFormComponent } from './validator-form/validator-form.component'; +import { InformationStepComponent } from '../../form/form-view/global-components/information-step/information-step.component'; // tslint:disable-next-line: max-line-length export { LogoCardComponent, + SvgIconComponent, + ButtonComponent, ValidatorFormComponent, CreateAccountFormComponent, AddressAutocompleteComponent, @@ -29,6 +34,7 @@ export { ModalConfirmationComponent, StructureOptionsModalComponent, ModalOptionsComponent, + TextInputModalComponent, PasswordFormComponent, TrainingTypePickerComponent, }; @@ -36,6 +42,8 @@ export { // tslint:disable-next-line:variable-name export const SharedComponents = [ LogoCardComponent, + SvgIconComponent, + ButtonComponent, ValidatorFormComponent, CreateAccountFormComponent, AddressAutocompleteComponent, @@ -48,6 +56,7 @@ export const SharedComponents = [ ModalJoinConfirmationComponent, StructureOptionsModalComponent, ModalOptionsComponent, + TextInputModalComponent, PasswordFormComponent, TrainingTypePickerComponent, InformationStepComponent, diff --git a/src/app/shared/components/logo-card/logo-card.component.ts b/src/app/shared/components/logo-card/logo-card.component.ts index 69699d655c92211f6dd54cbb8fb82cd85ec30951..ef2c578380b08104f9e833e5475cfdc96fe14b49 100644 --- a/src/app/shared/components/logo-card/logo-card.component.ts +++ b/src/app/shared/components/logo-card/logo-card.component.ts @@ -1,21 +1,21 @@ -import { Component, Input } from '@angular/core'; -import { Demarches } from '../../enum/demarches.enum'; -import { Labels } from '../../enum/labels.enum'; - -@Component({ - selector: 'app-logo-card', - templateUrl: './logo-card.component.html', - styleUrls: ['./logo-card.component.scss'], -}) -export class LogoCardComponent { - @Input() public logoPath: string; - @Input() public name: string; - - public getName(key: string): string { - if (Labels[key]) { - return Labels[key]; - } else { - return Demarches[key]; - } - } -} +import { Component, Input } from '@angular/core'; +import { Demarches } from '../../enum/demarches.enum'; +import { Labels } from '../../enum/labels.emum'; + +@Component({ + selector: 'app-logo-card', + templateUrl: './logo-card.component.html', + styleUrls: ['./logo-card.component.scss'] +}) +export class LogoCardComponent { + @Input() public logoPath: string; + @Input() public name: string; + + public getName(key: string): string { + if (Labels[key]) { + return Labels[key]; + } else { + return Demarches[key]; + } + } +} diff --git a/src/app/shared/components/modal-confirmation/modal-confirmation.component.ts b/src/app/shared/components/modal-confirmation/modal-confirmation.component.ts index 65c1c7204df2d628c7fcea94ed98165ecbe9e251..34024d0048fe5a6d26c8f33d6da2dd59937bee83 100644 --- a/src/app/shared/components/modal-confirmation/modal-confirmation.component.ts +++ b/src/app/shared/components/modal-confirmation/modal-confirmation.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { ButtonType } from '../button/buttonType.enum'; @Component({ selector: 'app-modal-confirmation', diff --git a/src/app/shared/components/modal-join-confirmation/modal-join-confirmation.component.ts b/src/app/shared/components/modal-join-confirmation/modal-join-confirmation.component.ts index 68932b6fd23460184067117e5a94ecc91c19131b..cc82850fcd35daea219a13da47d8bac38e9a5607 100644 --- a/src/app/shared/components/modal-join-confirmation/modal-join-confirmation.component.ts +++ b/src/app/shared/components/modal-join-confirmation/modal-join-confirmation.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; +import { ButtonType } from '../button/buttonType.enum'; @Component({ selector: 'app-join-modal-confirmation', diff --git a/src/app/shared/components/password-form/password-form.component.ts b/src/app/shared/components/password-form/password-form.component.ts index 4b5eda019f2a07a806c19941873b9e31e95feff3..7dc2c89ad6baab94f50a272cbf093b7e4fa5f5e0 100644 --- a/src/app/shared/components/password-form/password-form.component.ts +++ b/src/app/shared/components/password-form/password-form.component.ts @@ -1,12 +1,11 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { ProfileService } from '../../../profile/services/profile.service'; import { AuthService } from '../../../services/auth.service'; import { CustomRegExp } from '../../../utils/CustomRegExp'; import { MustMatch } from '../../validator/form'; - +import { ButtonType } from '../button/buttonType.enum'; @Component({ selector: 'app-password-form', templateUrl: './password-form.component.html', diff --git a/src/app/shared/components/structure-options-modal/structure-options-modal.component.ts b/src/app/shared/components/structure-options-modal/structure-options-modal.component.ts index ac85deb9c332e0360f67a340e45bd43e1f799747..2ff02f91dbabe59b8258de79bb5c7c8a9e8ea59d 100644 --- a/src/app/shared/components/structure-options-modal/structure-options-modal.component.ts +++ b/src/app/shared/components/structure-options-modal/structure-options-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { StructureWithOwners } from '../../../models/structureWithOwners.model'; @@ -10,6 +10,7 @@ import { AuthService } from '../../../services/auth.service'; import { StructureService } from '../../../services/structure.service'; import { CustomRegExp } from '../../../utils/CustomRegExp'; import { FunctionTypeModalOptions } from '../../enum/functionTypeModalOptions.enum'; +import { MustMatch } from '../../validator/form'; @Component({ selector: 'app-structure-options-modal', diff --git a/src/app/shared/components/structure-type-picker/structure-type-picker.component.ts b/src/app/shared/components/structure-type-picker/structure-type-picker.component.ts index 315fa1b681891393e9c900945e0e703957ea67ca..f896a6258da62187a4f6ef730f8d5fe01c14e90d 100644 --- a/src/app/shared/components/structure-type-picker/structure-type-picker.component.ts +++ b/src/app/shared/components/structure-type-picker/structure-type-picker.component.ts @@ -1,8 +1,9 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { typeStructureEnum } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { StructureType } from '../../../models/structure-type.model'; +import { Structure } from '../../../models/structure.model'; import { StructureTypeService } from '../../../services/structure-type.service'; +import { typeStructureEnum } from '../../enum/typeStructure.enum'; +import { ButtonType } from '../button/buttonType.enum'; export enum structureTypes { public = 'Publique', diff --git a/src/app/shared/components/svg-icon/svg-icon.component.html b/src/app/shared/components/svg-icon/svg-icon.component.html new file mode 100644 index 0000000000000000000000000000000000000000..5c875441709253e2124049a08056d8bd3f5dbe7e --- /dev/null +++ b/src/app/shared/components/svg-icon/svg-icon.component.html @@ -0,0 +1,8 @@ +<div app-tooltipDirective title="{{ title }}"> + <ng-template #tooltipTemplate> + <div class="tooltip">{{ title }}</div> + </ng-template> + <svg aria-hidden="true" class="icon" [ngClass]="iconClass" [attr.fill]="iconColor" [attr.stroke]="iconColor"> + <use [attr.xlink:href]="'assets/' + type + '/sprite.svg#' + icon"></use> + </svg> +</div> diff --git a/src/app/shared/components/svg-icon/svg-icon.component.scss b/src/app/shared/components/svg-icon/svg-icon.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..5630b1b400d76c7ab725362408ece7a0c9c22f50 --- /dev/null +++ b/src/app/shared/components/svg-icon/svg-icon.component.scss @@ -0,0 +1,104 @@ +@import '../../../../assets/scss/color'; + +:host { + display: flex; + align-items: center; +} + +.icon { + display: inline-block; + height: 2em; + width: 1.5em; + &.icon-full { + width: unset; + height: unset; + } + &.icon-16 { + height: 16px; + width: 16px; + } + &.icon-26 { + height: 26px; + width: 26px; + } + &.icon-28 { + width: 28px; + height: 28px; + } + &.icon-30 { + width: 30px; + height: 30px; + } + &.icon-32 { + width: 32px; + height: 32px; + } + &.icon-52 { + width: 52px; + height: 52px; + } + &.icon-40 { + width: 40px; + height: 40px; + } + &.icon-75 { + width: 4.688em; + } + &.icon-80 { + height: 80px; + width: 80px; + } + &.icon-112 { + height: 112px; + width: 112px; + } + &.validation { + height: 36px; + width: 20px; + } + &.validation-small { + height: 26px; + width: 20px; + } + &.hover { + cursor: pointer; + &:hover { + opacity: 0.8; + } + } + &.white { + fill: $white; + stroke: $white; + path { + stroke: $white; + fill: $white; + } + } + &.grey { + fill: $grey-3; + stroke: $grey-3; + } + &.grey-1 { + fill: $grey-1; + stroke: $grey-1; + } + &.green { + fill: $green-1; + stroke: $green-1; + } + &.backArrow { + height: 40px; + width: 40px; + margin-right: 1rem; + } +} + +svg { + // Scale the SVG to cover the whole app-icon container. + + top: 0.125em; + position: relative; +} +.icon-centered { + padding-top: 0.15rem; +} diff --git a/src/app/shared/components/svg-icon/svg-icon.component.spec.ts b/src/app/shared/components/svg-icon/svg-icon.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4276b578fc651e589a56909d491cfdf6203cf1c --- /dev/null +++ b/src/app/shared/components/svg-icon/svg-icon.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SvgIconComponent } from './svg-icon.component'; + +describe('SvgIconComponent', () => { + let component: SvgIconComponent; + let fixture: ComponentFixture<SvgIconComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SvgIconComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SvgIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/svg-icon/svg-icon.component.ts b/src/app/shared/components/svg-icon/svg-icon.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..23badf796fc5f101a3262319ef4eeddec3fc7965 --- /dev/null +++ b/src/app/shared/components/svg-icon/svg-icon.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-svg-icon', + templateUrl: './svg-icon.component.html', + styleUrls: ['./svg-icon.component.scss'], +}) +export class SvgIconComponent { + @Input() icon: string; + @Input() iconClass: string; + @Input() type: string; + @Input() iconColor: string = 'none'; + @Input() title: string = null; + constructor() {} +} diff --git a/src/app/shared/components/text-input-modal/text-input-modal.component.html b/src/app/shared/components/text-input-modal/text-input-modal.component.html new file mode 100644 index 0000000000000000000000000000000000000000..1ba0fc04d93cef1364cd11e8e0201c94aed4a6da --- /dev/null +++ b/src/app/shared/components/text-input-modal/text-input-modal.component.html @@ -0,0 +1,13 @@ +<div *ngIf="openned" class="modalBackground" ng-controller="myCtrl"> + <div class="modal"> + <div class="contentModal" fxLayout="column" fxLayoutAlign="space-around center"> + <h3>ATTENTION</h3> + <p>{{ content }}</p> + <textarea #myText id="story" class="textarea" name="story" rows="6" placeholder="{{ placeholder }}"></textarea> + <div class="footerModal" fxLayout="row" fxLayoutAlign="space-around center"> + <button class="btn-primary small leave" (click)="closeModal(true, myText.value)">Confirmer</button> + <button class="btn-primary small" (click)="closeModal(false, myText.value)">Annuler</button> + </div> + </div> + </div> +</div> diff --git a/src/app/shared/components/text-input-modal/text-input-modal.component.scss b/src/app/shared/components/text-input-modal/text-input-modal.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..a085676ce7a73ec3b74f3a03979bf1dce7d0ee55 --- /dev/null +++ b/src/app/shared/components/text-input-modal/text-input-modal.component.scss @@ -0,0 +1,64 @@ +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/shapes'; +@import '../../../../assets/scss/z-index'; + +.modalExitContainer { + width: 100%; + height: 100%; + z-index: $modal-z-index; + position: absolute; + content: ''; + top: 0; + background-color: $modal-background; + .modal { + .contentModal { + width: 100%; + background: $white; + padding: 35px 20px 18px 20px; + h3 { + @include lato-bold-18; + color: $orange-warning; + } + p { + @include lato-bold-16; + color: $grey-1; + text-align: center; + } + .footerModal { + width: 100%; + margin-top: 14px; + @include lato-bold-16; + .leave { + background: none; + color: $grey-1; + text-decoration: underline; + } + } + } + width: 350px; + margin: auto; + border-radius: 6px; + @include background-hash($grey-2); + border: 1px solid $grey-4; + margin-top: 50vh; + transform: translateY(-50%); + } +} +.modalBackground { + .modal { + .contentModal { + padding: 20px; + } + } +} + +.textarea { + padding: 13px 8px; + background: $grey-8; + border: 1px solid $grey-4; + border-radius: 1px; + resize: none; + width: 100%; + @include lato-regular-16; +} diff --git a/src/app/shared/components/text-input-modal/text-input-modal.component.spec.ts b/src/app/shared/components/text-input-modal/text-input-modal.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..75318c94dccbbe3ffb5cda8aea349fd58f4e7ca4 --- /dev/null +++ b/src/app/shared/components/text-input-modal/text-input-modal.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TextInputModalComponent } from './text-input-modal.component'; + +describe('ModalConfirmationComponent', () => { + let component: TextInputModalComponent; + let fixture: ComponentFixture<TextInputModalComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TextInputModalComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TextInputModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/text-input-modal/text-input-modal.component.ts b/src/app/shared/components/text-input-modal/text-input-modal.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..524396cda60cc474a8de4e2af85c3a3a06166244 --- /dev/null +++ b/src/app/shared/components/text-input-modal/text-input-modal.component.ts @@ -0,0 +1,21 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-text-input-modal', + templateUrl: './text-input-modal.component.html', + styleUrls: ['./text-input-modal.component.scss'], +}) +export class TextInputModalComponent { + @Input() public openned: boolean; + @Input() public content: string; + @Input() public placeholder: string; + @Output() closed = new EventEmitter<boolean>(); + @Output() newContent = new EventEmitter<{ content: string; shouldSend: boolean }>(); + + public myContent: string; + constructor() {} + + public closeModal(shouldSend: boolean, content: string) { + this.newContent.emit({ content, shouldSend }); + } +} diff --git a/src/app/shared/components/training-type-picker/training-type-picker.component.ts b/src/app/shared/components/training-type-picker/training-type-picker.component.ts index e2555e93cd423a191056615dc97046f22030e9aa..a82331982d7a860aedf41930355b31a1f82ba67c 100644 --- a/src/app/shared/components/training-type-picker/training-type-picker.component.ts +++ b/src/app/shared/components/training-type-picker/training-type-picker.component.ts @@ -1,8 +1,9 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Category, Module } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; -import { cloneDeep, remove } from 'lodash'; +import { Category } from '../../../structure-list/models/category.model'; +import { Module } from '../../../structure-list/models/module.model'; import { SearchService } from '../../../structure-list/services/search.service'; +import { cloneDeep, remove } from 'lodash'; +import { ButtonType } from '../button/buttonType.enum'; @Component({ selector: 'app-training-type-picker', diff --git a/src/app/shared/directives/index.ts b/src/app/shared/directives/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..924e8410cadc985320d9a2af73b00218a316fe38 --- /dev/null +++ b/src/app/shared/directives/index.ts @@ -0,0 +1,7 @@ +import { TooltipDirective } from '../components/tooltip/tooltip.directive'; +import { ModalOutsideDirective } from './modalOutside.directive'; + +export { ModalOutsideDirective }; + +// tslint:disable-next-line:variable-name +export const SharedDirectives = [ModalOutsideDirective, TooltipDirective]; diff --git a/src/app/shared/directives/modalOutside.directive.ts b/src/app/shared/directives/modalOutside.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8ebc284c1ccfb67aa0b0527df5e39322e834ec5 --- /dev/null +++ b/src/app/shared/directives/modalOutside.directive.ts @@ -0,0 +1,18 @@ +/* tslint:disable:member-ordering */ +import { Directive, ElementRef, HostListener, Output, EventEmitter } from '@angular/core'; + +@Directive({ + selector: '[clickOutside]', +}) +export class ModalOutsideDirective { + constructor(private _elementRef: ElementRef) {} + + @Output('clickOutside') clickOutside: EventEmitter<any> = new EventEmitter(); + + @HostListener('document:mousedown', ['$event.target']) onMouseEnter(targetElement) { + const clickedInside = this._elementRef.nativeElement.contains(targetElement); + if (!clickedInside) { + this.clickOutside.emit(null); + } + } +} diff --git a/src/app/shared/enum/labels.enum.ts b/src/app/shared/enum/labels.emum.ts similarity index 97% rename from src/app/shared/enum/labels.enum.ts rename to src/app/shared/enum/labels.emum.ts index 841a3a828c9043f508dbd36ef60fe7f44d9a1b9c..675f045002847e68277e09e9c743b84e16ca00ed 100644 --- a/src/app/shared/enum/labels.enum.ts +++ b/src/app/shared/enum/labels.emum.ts @@ -1,9 +1,9 @@ -export enum Labels { - passNumerique = 'Pass numérique', - maisonFranceService = 'Maison france service', - aidantsConnect = 'Aidants connect', - fabriqueDeTerritoire = 'Fabrique de territoire', - demarcheMetropolitaine = 'Démarches Métropolitaines', - pix = 'Évaluation des compétences numériques', - conseillerNumFranceServices = 'Conseiller numérique', -} +export enum Labels { + passNumerique = 'Pass numérique', + maisonFranceService = 'Maison france service', + aidantsConnect = 'Aidants connect', + fabriqueDeTerritoire = 'Fabrique de territoire', + demarcheMetropolitaine = 'Démarches Métropolitaines', + pix = 'Évaluation des compétences numériques', + conseillerNumFranceServices = 'Conseiller numérique', +} diff --git a/src/app/shared/enum/typeStructure.enum.ts b/src/app/shared/enum/typeStructure.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..a024b8b2ac4fb8fcdec36fd6e1ca40d456615dd0 --- /dev/null +++ b/src/app/shared/enum/typeStructure.enum.ts @@ -0,0 +1,33 @@ +export enum typeStructureEnum { + fablab = 'Fablab', + // A supprimer ? + + //A remplacer par Association ? + associationQuartier = 'Structure associative de quartier', + associationCaritative = 'Association caritative', + + // En attente de suppression remplacer par CAF CARSAT, Pole Emploi et CCAS + grandOrganismePublic = 'Grand organisme public (CAF, CARSAT, Pôle emploi...)', + + mdm = 'Maison de la Métropole', + mairie = 'Mairie', + CAF = 'CAF', + CCAS = 'CCAS', + CARSAT = 'CARSAT', + poleEmploi = 'Pole Emploi', + mediatheque = 'Médiathèque/Bibliothèque', + prefecture = 'Préfecture', + bijPij = 'BIJ/PIJ', + logement = 'Logement', + MaisonFranceService = 'Maison France Service', + + association = 'Association', + centreSocio = 'Centre socio-culturel', + mjc = 'MJC / Cyberbase', + pimms = 'PIMMS', + sij = 'Structure information jeunesse (SIJ)', + missionsLocales = 'Missions locales', + + formation = 'Structure de formation', + insertion = "Structure d'insertion", +} diff --git a/src/app/shared/pipes/day.pipe.ts b/src/app/shared/pipes/day.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..f8a64598e1b294a248e95e347875f853a63d5139 --- /dev/null +++ b/src/app/shared/pipes/day.pipe.ts @@ -0,0 +1,25 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'day', pure: false }) +export class DayPipe implements PipeTransform { + transform(day: string): any { + switch (day) { + case 'monday': + return 'lundi'; + case 'tuesday': + return 'mardi'; + case 'thursday': + return 'jeudi'; + case 'wednesday': + return 'mercredi'; + case 'friday': + return 'vendredi'; + case 'saturday': + return 'samedi'; + case 'sunday': + return 'dimanche'; + default: + return null; + } + } +} diff --git a/src/app/shared/pipes/index.ts b/src/app/shared/pipes/index.ts index c8178d45addbcda9339634549dcd7fed6a29257a..21bf9cf7e194188fe39c2c3257bb4e42f813da2a 100644 --- a/src/app/shared/pipes/index.ts +++ b/src/app/shared/pipes/index.ts @@ -1,6 +1,8 @@ -import { UrlPipe } from './url.pipe'; - -export { UrlPipe }; - -// tslint:disable-next-line:variable-name -export const SharedPipes = [UrlPipe]; +import { DayPipe } from './day.pipe'; +import { PhonePipe } from './phone.pipe'; +import { UrlPipe } from './url.pipe'; + +export { DayPipe, PhonePipe, UrlPipe }; + +// tslint:disable-next-line:variable-name +export const SharedPipes = [DayPipe, PhonePipe, UrlPipe]; diff --git a/src/app/shared/pipes/phone.pipe.ts b/src/app/shared/pipes/phone.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..af1e964f77bab9faf286c0171103acb5573fa631 --- /dev/null +++ b/src/app/shared/pipes/phone.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'phone' }) +export class PhonePipe implements PipeTransform { + transform(value: string): string { + //Remove dot and space from the phone string and add space in each 2 numbers + const regexArray = value.replace(/\s|\./g, '').match(/.{1,2}/g); + return regexArray.join(' '); + } +} diff --git a/src/app/shared/service/print.service.ts b/src/app/shared/service/print.service.ts index f8eac10b2c6e7ff0b2e59426dceade32e1376343..115614975ab825878c8f0f1c6a8205bdad81a8d2 100644 --- a/src/app/shared/service/print.service.ts +++ b/src/app/shared/service/print.service.ts @@ -1,48 +1,48 @@ -import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; - -@Injectable({ - providedIn: 'root', -}) -export class PrintService { - public isPrinting = false; - public structure: Structure; - public structures: Structure[]; - - constructor(private router: Router) {} - - public printDocument(documentName: string, structure: Structure): void { - this.isPrinting = true; - this.structure = structure; - this.router.navigate([ - '/', - { - outlets: { - print: ['print', documentName], - }, - }, - ]); - } - - public printDocuments(documentName: string, structures: Structure[]): void { - this.isPrinting = true; - this.structures = structures; - this.router.navigate([ - '/', - { - outlets: { - print: ['print', documentName], - }, - }, - ]); - } - - public onDataReady(): void { - setTimeout(() => { - window.print(); - this.isPrinting = false; - this.router.navigate([{ outlets: { print: null } }]); - }, 1500); - } -} +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Structure } from '../../models/structure.model'; + +@Injectable({ + providedIn: 'root', +}) +export class PrintService { + public isPrinting = false; + public structure: Structure; + public structures: Structure[]; + + constructor(private router: Router) {} + + public printDocument(documentName: string, structure: Structure): void { + this.isPrinting = true; + this.structure = structure; + this.router.navigate([ + '/', + { + outlets: { + print: ['print', documentName], + }, + }, + ]); + } + + public printDocuments(documentName: string, structures: Structure[]): void { + this.isPrinting = true; + this.structures = structures; + this.router.navigate([ + '/', + { + outlets: { + print: ['print', documentName], + }, + }, + ]); + } + + public onDataReady(): void { + setTimeout(() => { + window.print(); + this.isPrinting = false; + this.router.navigate([{ outlets: { print: null } }]); + }, 1500); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 83111888f7149e97f5701c4fc98d52fee7772ee3..c0829cf6b1ac5f9abd45de845b404f473f5ed840 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,51 +1,33 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { FlexLayoutModule } from '@angular/flex-layout'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { - ButtonModule, - DayModule, - ModalModule, - PhoneModule, - SvgIconModule, - TextInputModalModule, -} from '@gouvfr-anct/mediation-numerique/shared'; -import { SharedComponents } from './components'; -import { AddressAutocompleteComponent } from './components/address-autocomplete/address-autocomplete.component'; -import { HourPickerComponent } from './components/hour-picker/hour-picker.component'; -import { SharedPipes } from './pipes'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - RouterModule, - FlexLayoutModule, - ReactiveFormsModule, - ButtonModule, - SvgIconModule, - TextInputModalModule, - ModalModule, - DayModule, - PhoneModule, - ], - declarations: [...SharedPipes, ...SharedComponents, AddressAutocompleteComponent, HourPickerComponent], - exports: [ - ...SharedPipes, - ...SharedComponents, - CommonModule, - RouterModule, - FlexLayoutModule, - FormsModule, - ReactiveFormsModule, - ButtonModule, - ButtonModule, - SvgIconModule, - TextInputModalModule, - ModalModule, - DayModule, - PhoneModule, - ], -}) -export class SharedModule {} +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { SharedComponents } from './components'; +import { SharedPipes } from './pipes'; +import { SharedDirectives } from './directives'; +import { SvgIconComponent } from './components/svg-icon/svg-icon.component'; +import { AddressAutocompleteComponent } from './components/address-autocomplete/address-autocomplete.component'; +import { HourPickerComponent } from './components/hour-picker/hour-picker.component'; +@NgModule({ + imports: [CommonModule, FormsModule, RouterModule, FlexLayoutModule, ReactiveFormsModule], + declarations: [ + ...SharedPipes, + ...SharedComponents, + ...SharedDirectives, + SvgIconComponent, + AddressAutocompleteComponent, + HourPickerComponent, + ], + exports: [ + ...SharedPipes, + ...SharedComponents, + ...SharedDirectives, + CommonModule, + RouterModule, + FlexLayoutModule, + FormsModule, + ReactiveFormsModule, + ], +}) +export class SharedModule {} diff --git a/src/app/structure-list/components/card/card.component.html b/src/app/structure-list/components/card/card.component.html new file mode 100644 index 0000000000000000000000000000000000000000..79c33855b964fd6c7a72bede87c912c0361c1930 --- /dev/null +++ b/src/app/structure-list/components/card/card.component.html @@ -0,0 +1,46 @@ +<div + class="structure" + fxLayout="column" + (click)="cardClicked()" + (mouseenter)="cardHover()" + [ngClass]="{ orientation: isOrientation }" +> + <div class="left"> + <div fxLayout="row" fxLayoutAlign="space-between baseline" fxLayoutGap="16px"> + <div fxLayout="column" fxLayoutAlign="end"> + <span class="structure-name" [ngClass]="{ notClaimed: !isClaimed }">{{ structure.structureName }}</span> + <span class="typeStructure">{{ structure.getLabelTypeStructure() }}</span> + </div> + <div *ngIf="!isOrientation" fxLayout="column" fxLayoutAlign="none end"> + <div class="distanceStructure"> + {{ this.structure.address.commune }} + </div> + <div class="distance" *ngIf="structure.distance"> + {{ this.formatDistance() }} + </div> + </div> + </div> + <div class="distance" *ngIf="isOrientation && structure.distance"> + {{ this.formatDistance() }} + </div> + </div> + <div class="actions right" *ngIf="isOrientation"> + <div + fxLayout="row" + *ngIf="!isSelected" + class="selection-button selected" + (click)="cardAddToList(); $event.stopPropagation()" + > + <app-svg-icon class="add-icon" [type]="'ico'" [icon]="'add'" [iconColor]="'green'"></app-svg-icon>Ajouter + </div> + <div + fxLayout="row" + *ngIf="isSelected" + class="selection-button to-select" + (click)="cardAddToList(); $event.stopPropagation()" + > + <app-svg-icon class="add-icon" [type]="'ico'" [icon]="'validate'" [iconColor]="'white'"></app-svg-icon> + Ajouté + </div> + </div> +</div> diff --git a/src/app/structure-list/components/card/card.component.scss b/src/app/structure-list/components/card/card.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..0613c38040988de6a4de619bccd1040db7a7385b --- /dev/null +++ b/src/app/structure-list/components/card/card.component.scss @@ -0,0 +1,89 @@ +@import '../../../../assets/scss/icons'; +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/breakpoint'; + +.structure { + padding: 12px 24px; + border-bottom: 1px solid $grey-8 !important; + min-height: 110px; + display: flex; + justify-content: center; + cursor: pointer; + @media #{$large-phone} { + height: unset; + } + .typeStructure { + color: $grey-3; + @include lato-regular-16; + font-style: italic; + } + &:hover { + .structure-name { + text-decoration: underline; + } + } + .structure-name { + @include lato-bold-18; + color: $grey-1; + padding-bottom: 5px; + width: 100%; + + &.notClaimed { + color: $primary-color; + } + } + .distanceStructure { + @include lato-regular-16; + color: $grey-3; + text-align: right; + } +} + +.selection-button { + font-style: normal; + font-weight: bold; + justify-content: center; + align-items: center; + min-width: 120px; + height: 40px; + border-radius: 20px; +} + +.selected { + border: solid 1px $grey-3; + background-color: $white; + color: $black; +} +.to-select { + color: $white; + border-style: none; + background: #47c562; +} + +.distance { + @include lato-regular-14; + color: $grey-3; +} + +.add-icon { + color: $green-1; +} + +.orientation { + flex-direction: row !important; + + .left { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 70%; + } + + .right { + margin-top: 6%; + @media #{$large-phone} { + margin-left: unset; + } + } +} diff --git a/src/app/structure-list/components/card/card.component.spec.ts b/src/app/structure-list/components/card/card.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..873f374bc18036ca517fd9e0f3d48fecac66ee0b --- /dev/null +++ b/src/app/structure-list/components/card/card.component.spec.ts @@ -0,0 +1,183 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardComponent } from './card.component'; +import { HttpClientModule } from '@angular/common/http'; +import { Structure } from '../../../models/structure.model'; +import { OpeningDay } from '../../../models/openingDay.model'; + +describe('CardComponent', () => { + let component: CardComponent; + let fixture: ComponentFixture<CardComponent>; + let structure: Structure; + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HttpClientModule], + declarations: [CardComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CardComponent); + component = fixture.debugElement.componentInstance; + structure = new Structure({ + id: 1, + numero: '26-63', + updatedAt: '2020-10-08T15:17:00.000Z', + structureRepresentation: 'Un établissement principal (siège social)', + structureName: 'Régie de Quartier Armstrong', + structureType: 'Tiers-lieu & coworking, FabLab', + description: "Association loi 1901 dont l'objet est l'insertion par l'économie social et solidaire", + address: { + numero: 2, + street: 'rue du test', + commune: 'villeurbanne', + }, + + contactPhone: '04 72 21 03 07', + contactMail: 'sguillet@rqa.fr', + website: '', + facebook: '', + twitter: '@rqainfo69', + instagram: '', + gender: 'Madame', + contactName: 'GUILLET', + contactSurname: 'Séverine', + fonction: 'Autres', + pmrAccess: '', + publicsAccompaniment: 'Tout public', + exceptionalClosures: '', + proceduresAccompaniment: 'Accompagnant CAF', + autresAccompagnements: '', + baseSkills: 260, + accessRight: 176, + socialAndProfessional: 254, + parentingHelp: '', + digitalCultureSecurity: 264, + hours: { + monday: { + open: true, + time: [ + { + opening: 1330, + closing: 1630, + }, + { + opening: null, + closing: null, + }, + ], + }, + tuesday: { + open: true, + time: [ + { + opening: 830, + closing: 1130, + }, + { + opening: 1330, + closing: 1630, + }, + ], + }, + wednesday: { + open: true, + time: [ + { + opening: 1330, + closing: 1630, + }, + { + opening: null, + closing: null, + }, + ], + }, + thursday: { + open: true, + time: [ + { + opening: 830, + closing: 1130, + }, + { + opening: 1330, + closing: 1630, + }, + ], + }, + friday: { + open: true, + time: [ + { + opening: 830, + closing: 1130, + }, + { + opening: 1330, + closing: 1530, + }, + ], + }, + saturday: { + open: false, + time: [ + { + opening: null, + closing: null, + }, + { + opening: null, + closing: null, + }, + ], + }, + sunday: { + open: false, + time: [ + { + opening: null, + closing: null, + }, + { + opening: null, + closing: null, + }, + ], + }, + openedOn: new OpeningDay('monday', null), + }, + openedOn: new OpeningDay('monday', null), + }); + component.structure = structure; + fixture.detectChanges(); // calls NgOnit + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should transform a distance into km', () => { + component.structure.distance = 4320; + const distance = component.formatDistance(); + expect(distance).toEqual('4.3 km'); + }); + it('should transform a distance into m', () => { + component.structure.distance = 400; + const distance = component.formatDistance(); + expect(distance).toEqual('400 m'); + }); + + it('should emit structure to show details', () => { + spyOn(component.showDetails, 'emit'); + component.cardClicked(); + expect(component.showDetails.emit).toHaveBeenCalled(); + expect(component.showDetails.emit).toHaveBeenCalledWith(structure); + }); + it('should emit structure on cardHover', () => { + spyOn(component.hover, 'emit'); + component.cardHover(); + expect(component.hover.emit).toHaveBeenCalled(); + expect(component.hover.emit).toHaveBeenCalledWith(structure); + }); +}); diff --git a/src/app/structure-list/components/card/card.component.ts b/src/app/structure-list/components/card/card.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b891466c8c688140d7b4c833a201e33dba781cd --- /dev/null +++ b/src/app/structure-list/components/card/card.component.ts @@ -0,0 +1,74 @@ +import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Structure } from '../../../models/structure.model'; +import { ProfileService } from '../../../profile/services/profile.service'; +import { StructureService } from '../../../services/structure.service'; + +@Component({ + selector: 'app-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'], +}) +export class CardComponent implements OnInit { + @Input() public structure: Structure; + @Input() public isSelected: boolean; + @Input() public isOrientation: boolean; + @Output() public showDetails: EventEmitter<Structure> = new EventEmitter<Structure>(); + @Output() public addToList: EventEmitter<Structure> = new EventEmitter<Structure>(); + @Output() public hover: EventEmitter<Structure> = new EventEmitter<Structure>(); + public isClaimed = true; + + constructor( + private route: ActivatedRoute, + private router: Router, + private profileService: ProfileService, + private structureService: StructureService + ) {} + ngOnInit(): void { + if (this.profileService.isAdmin()) { + this.setClaimIndicator(); + } + } + + // Check if structure haven't owners to help admin vision. + async setClaimIndicator() { + this.isClaimed = await this.structureService.isClaimed(this.structure._id, null).toPromise(); + } + + /** + * Display distance in m or km according to value + */ + public formatDistance(): string { + if (this.structure.distance > 1000) { + return (this.structure.distance / 1000).toFixed(1).toString() + ' km'; + } else { + return this.structure.distance + ' m'; + } + } + + public cardClicked(): void { + this.showDetails.emit(this.structure); + if (!this.isOrientation) { + const queryString = this.route.snapshot.queryParamMap.get('search'); + this.router.navigate([], { + relativeTo: this.route, + queryParams: queryString + ? { + id: this.structure._id, + search: queryString, + } + : { + id: this.structure._id, + }, + }); + } + } + + public cardHover(): void { + this.hover.emit(this.structure); + } + + public cardAddToList(): void { + this.addToList.emit(this.structure); + } +} diff --git a/src/app/structure-list/components/modal-filter/modal-filter.component.html b/src/app/structure-list/components/modal-filter/modal-filter.component.html new file mode 100644 index 0000000000000000000000000000000000000000..37b27ad7bc3765273f30aeba635e6bb7ef216304 --- /dev/null +++ b/src/app/structure-list/components/modal-filter/modal-filter.component.html @@ -0,0 +1,181 @@ +<div *ngIf="modalType" fxLayout="column" fxLayoutAlign="center center" [ngClass]="['modal', 'modal' + getModalType()]"> + <div class="body-wrap" fxLayout="column" fxLayoutAlign="space-between"> + <div class="titleFilter" fxLayout="row" fxLayoutAlign="space-between"> + <span>Filtres</span> + <div (click)="closeModal()" class="ico-close-details"></div> + </div> + <!-- Filter with single category --> + <div class="contentModal" fxLayout="row wrap" fxLayoutAlign="flex-start" *ngIf="categories.length === 1"> + <div class="blockFiltre" *ngFor="let c of categories"> + <ul class="blockLigne"> + <div fxLayout="row" fxLayoutAlign="start center" class="ligneFiltre" *ngFor="let module of c.modules"> + <li class="checkbox"> + <div class="checkboxItem"> + <label fxLayout="row" fxLayoutAlign="start center"> + <input + type="checkbox" + [checked]="searchService.getIndex(checkedModules, module.id, c.id) > -1" + [value]="module.id" + (change)="onCheckboxChange($event, c.id, module.text)" + /> + <span class="customCheck customCheckPrimary"></span> + <div class="label">{{ module.text }}</div> + </label> + </div> + </li> + </div> + </ul> + </div> + </div> + <!-- "Other filters" backdrop fullwidth modal --> + <div + class="contentModal maxModal" + fxLayout="row wrap" + fxLayoutAlign="flex-start" + *ngIf="categories.length > 1 && getModalType() === 'moreFilters'" + > + <div + class="blockLigne" + (clickOutside)="getModalType() === 'moreFilters' ? closeModal() : ''" + [ngClass]="{ backDropModal: getModalType() === 'moreFilters' }" + > + <div class="headerMoreFilters" *ngIf="getModalType() === 'moreFilters'"> + Plus de filtres + <div class="iconClose" (click)="closeModal()"> + <app-svg-icon + [iconClass]="'icon-28'" + [iconColor]="'grey-1'" + [icon]="'closeModal'" + [type]="'ico'" + ></app-svg-icon> + </div> + </div> + <div class="scroll-container"> + <div class="blockFiltre" *ngFor="let c of categories"> + <p>{{ c.name }}</p> + <ul class="blockLigne smallList" [ngClass]="{ show: this.toggledCategories.includes(c.id) }"> + <div fxLayout="row" fxLayoutAlign="start center" class="ligneFiltre" *ngFor="let module of c.modules"> + <li class="checkbox"> + <div class="checkboxItem"> + <label fxLayout="row" fxLayoutAlign="start center"> + <input + type="checkbox" + [checked]="searchService.getIndex(checkedModules, module.id, c.id) > -1" + [value]="module.id" + (change)="onCheckboxChange($event, c.id, module.displayText)" + /> + <span class="customCheck customCheckPrimary"></span> + <div class="label">{{ module.text }}</div> + </label> + </div> + </li> + </div> + </ul> + </div> + </div> + <div + class="footer" + fxLayout="row" + fxLayoutAlign="center center" + [ngClass]="{ backDropModalFooter: getModalType() === 'moreFilters' }" + > + <div class="half-width"> + <app-button + [style]="buttonTypeEnum.modalSecondary" + [text]="'Effacer'" + (click)="clearFilters()" + tabindex="0" + ></app-button> + </div> + <div class="half-width"> + <app-button + [style]="buttonTypeEnum.modalPrimary" + [text]="'Appliquer'" + (click)="emitModules(checkedModules)" + ></app-button> + </div> + </div> + </div> + </div> + <!-- Filter with multiple categories --> + <div + class="contentModal maxModal max-height" + fxLayout="row wrap" + fxLayoutAlign="flex-start" + *ngIf="categories.length > 1 && getModalType() !== 'moreFilters'" + > + <ul class="blockLigne"> + <div class="blockFiltre" *ngFor="let c of categories"> + <li class="checkbox"> + <div class="checkboxItem categoryCheckBox"> + <div fxLayout="row" fxLayoutAlign="start center"> + <label> + <input + type="checkbox" + class="multiCheck" + [checked]="getCategoryCheckboxStatus(c) === 'checked'" + (change)="handleCategoryCheckBox($event, c)" + /> + <span + class="customCheck customCheckPrimary" + [ngClass]="{ halfCheck: getCategoryCheckboxStatus(c) === 'halfChecked' }" + ></span> + </label> + <div + fxLayout="row" + fxLayoutAlign="space-between center" + class="w-100 clickable" + (click)="toggleShowCategory(c.id)" + > + <div class="label">{{ c.name }}</div> + <div class="arrow" [ngClass]="{ toggled: this.toggledCategories.includes(c.id) }"></div> + </div> + </div> + <ul class="blockLigne smallList" [ngClass]="{ show: this.toggledCategories.includes(c.id) }"> + <div fxLayout="row" fxLayoutAlign="start center" class="ligneFiltre" *ngFor="let module of c.modules"> + <li class="checkbox"> + <div class="checkboxItem"> + <label fxLayout="row" fxLayoutAlign="start center"> + <input + type="checkbox" + [checked]="searchService.getIndex(checkedModules, module.id, c.id) > -1" + [value]="module.id" + (change)="onCheckboxChange($event, c.id, module.text)" + /> + <span class="customCheck customCheckPrimary"></span> + <div class="label">{{ module.text }}</div> + </label> + </div> + </li> + </div> + </ul> + </div> + </li> + </div> + </ul> + </div> + <div + class="footer" + fxLayout="row" + fxLayoutAlign="center center" + [ngClass]="{ backDropModalFooter: getModalType() === 'moreFilters' }" + *ngIf="getModalType() !== 'moreFilters'" + > + <div class="half-width"> + <app-button + [style]="buttonTypeEnum.modalSecondary" + [text]="'Effacer'" + (click)="clearFilters()" + tabindex="0" + ></app-button> + </div> + <div class="half-width"> + <app-button + [style]="buttonTypeEnum.modalPrimary" + [text]="'Appliquer'" + (click)="emitModules(checkedModules)" + ></app-button> + </div> + </div> + </div> +</div> diff --git a/src/app/structure-list/components/modal-filter/modal-filter.component.scss b/src/app/structure-list/components/modal-filter/modal-filter.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..c443be97c66da9e684834422ef02baaa86034954 --- /dev/null +++ b/src/app/structure-list/components/modal-filter/modal-filter.component.scss @@ -0,0 +1,223 @@ +@import '../../../../assets/scss/icons'; +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/breakpoint'; +@import '../../../../assets/scss/shapes'; +@import '../../../../assets/scss/hyperlink'; +@import '../../../../assets/scss/z-index'; + +.modalaccompaniment { + left: 212px; + @media #{$large-desktop} { + left: 273px; + } +} +.modaltraining { + left: 331px; + @media #{$large-desktop} { + left: 417px; + } +} +.modalpublic { + left: 431px; + @media #{$large-desktop} { + left: 513px; + } +} +.modalequipments { + left: 519px; + @media #{$large-desktop} { + left: 603px; + } +} +.maxModal .blockLigne { + box-sizing: border-box; + width: 360px; + .smallList { + display: block; + box-sizing: border-box; + background: $grey-8; + max-width: 300px; + padding: 0.5rem !important; + margin-top: 1rem !important; + } +} +.modal { + max-width: 360px; + width: auto; + z-index: $modal-z-index !important; + position: fixed; + box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.25); + border-radius: 8px; + margin-top: 25px; + @media #{$large-phone} { + height: 100%; + max-height: auto; + max-width: auto; + width: 100%; + position: fixed; + top: 0; + left: 0; + border: none; + padding: 0; + } + background: white; + .body-wrap { + @media #{$large-phone} { + height: 100vh; + height: -webkit-fill-available; + } + .titleFilter { + display: none !important; + margin: 27px 25px 0px 25px; + @include lato-bold-26; + @media #{$large-phone} { + display: flex !important; + } + } + } + .contentModal { + overflow-y: auto; + max-width: 1100px; + border-bottom: 1px solid $grey; + @media #{$large-phone} { + max-height: none; + height: 100%; + } + .blockFiltre { + width: auto; + margin: 25px 20px; + margin-bottom: calc(25px - 1rem); + min-width: 200px; + + @media #{$large-phone} { + margin: 0 18px; + padding: 25px 0; + min-width: 0; + } + .categoryCheckBox { + .halfCheck { + position: relative; + &:before { + content: ''; + position: absolute; + display: block; + width: 10px; + left: 4px; + top: 8px; + transform: rotate(0deg); + border-bottom: solid 2px $grey-2; + border-radius: 0; + } + } + ul { + display: none; + } + .show { + display: block !important; + } + } + } + .blockLigne { + padding-left: 0; + margin: 0px; + li { + margin-bottom: 1rem; + } + } + label { + @include lato-regular-16; + color: $grey-1; + } + + .arrow { + cursor: pointer; + margin-left: auto; + background-color: transparent; + border-bottom: 1px solid black; + border-right: 1px solid black; + transform: translateY(-25%) rotate(45deg); + height: 7px; + width: 7px; + transition: all 300ms ease; + margin-top: -5px; + } + .toggled { + transform: translateY(25%) rotate(-135deg); + } + &.max-height { + max-height: 50vh; + overflow-y: overlay; + } + } + .footer { + box-sizing: border-box; + padding: 0.5rem; + .reset { + width: 45%; + text-align: center; + color: $grey-4; + } + .half-width { + width: 50%; + padding: 0 4px; + } + } +} +.modalmoreFilters { + width: 100%; + min-width: 100%; + height: 100vh; + min-height: 100vh; + top: 0; + left: 0; + display: flex; + flex-direction: column; + background: rgba(0, 0, 0, 0.8) !important; + margin: 0; + border-radius: 0; + .scroll-container { + max-height: 390px; + overflow-y: scroll; + } + .maxModal { + max-width: 360px; + margin: auto; + background: white; + margin-top: 10%; + border-radius: 8px; + // overflow: hidden; + } + .backDropModal { + background: white; + width: 360px; + min-height: 40vh; + } + .backDropModalFooter { + background: white; + width: 360px; + margin: auto; + border-radius: 0 0 8px 8px; + } + .headerMoreFilters { + position: relative; + text-align: center; + color: $grey-1; + background: white; + padding: 1rem; + @include lato-bold-18; + align-items: center; + justify-content: center; + border-bottom: solid 1px $grey-6; + border-radius: 8px 8px 0 0; + .iconClose { + cursor: pointer; + position: absolute; + right: 14px; + top: 7px; + } + } +} +a { + @include hyperlink; +} diff --git a/src/app/structure-list/components/modal-filter/modal-filter.component.spec.ts b/src/app/structure-list/components/modal-filter/modal-filter.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..08583519907633fe953eb0ef7551c451e787c89d --- /dev/null +++ b/src/app/structure-list/components/modal-filter/modal-filter.component.spec.ts @@ -0,0 +1,104 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TypeModal } from '../../enum/typeModal.enum'; +import { Category } from '../../models/category.model'; +import { Module } from '../../models/module.model'; + +import { ModalFilterComponent } from './modal-filter.component'; + +describe('ModalFilterComponent', () => { + let component: ModalFilterComponent; + let fixture: ComponentFixture<ModalFilterComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ModalFilterComponent], + imports: [HttpClientTestingModule, ReactiveFormsModule], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ModalFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + // emitModules function + it('should emit modules', () => { + const modules: Module[] = [ + { id: '176', text: 'training', count: 3 }, + { id: '173', text: 'training', count: 2 }, + { id: '172', text: 'training', count: 2 }, + ]; + spyOn(component.searchEvent, 'emit'); + component.emitModules(modules); + expect(component.searchEvent.emit).toHaveBeenCalled(); + expect(component.searchEvent.emit).toHaveBeenCalledWith(modules); + }); + + // onCheckboxChange function + it('should add a module to checkedModule array', () => { + const modules: Module[] = [ + { id: '176', text: 'training', count: 0 }, + { id: '173', text: 'training', count: 0 }, + { id: '172', text: 'training', count: 0 }, + ]; + component.checkedModules = modules; + const evt = { target: { checked: true, value: '175' } }; + component.onCheckboxChange(evt, 'training'); + expect(component.checkedModules.length).toEqual(4); + }); + it('should remove a module to checkedModule array', () => { + const modules: Module[] = [ + { id: '176', text: 'training', count: 0 }, + { id: '173', text: 'training', count: 0 }, + { id: '172', text: 'training', count: 0 }, + ]; + component.checkedModules = modules; + const evt = { target: { checked: false, value: '173' } }; + component.onCheckboxChange(evt, 'training'); + expect(component.checkedModules.length).toEqual(2); + }); + + // clearFilters function + it('should remove all modules checked from same modal, here morefilters', () => { + const modules: Module[] = [ + { id: '176', text: 'morefilters', count: 0 }, + { id: '173', text: 'morefilters', count: 0 }, + { id: '172', text: 'morefilters', count: 0 }, + { id: '179', text: 'training', count: 0 }, + { id: '190', text: 'training', count: 0 }, + { id: '167', text: 'training', count: 0 }, + ]; + component.checkedModules = modules; + const category: Category = new Category({ id: 'morefilters', modules: [modules[0], modules[1], modules[2]] }); + component.categories = [category]; + component.clearFilters(); + expect(component.checkedModules.length).toEqual(3); + }); + + // getModalType function + it('should return string of type about current enum', () => { + component.modalType = TypeModal.training; + const resultTraining = component.getModalType(); + component.modalType = TypeModal.accompaniment; + const resultAccopaniment = component.getModalType(); + component.modalType = TypeModal.moreFilters; + const resultMoreFilters = component.getModalType(); + expect(resultTraining).toEqual('training'); + expect(resultMoreFilters).toEqual('moreFilters'); + expect(resultAccopaniment).toEqual(''); + }); + + // closeModal function + it('should emit modules', () => { + spyOn(component.closeEvent, 'emit'); + component.closeModal(); + expect(component.closeEvent.emit).toHaveBeenCalled(); + }); +}); diff --git a/src/app/structure-list/components/modal-filter/modal-filter.component.ts b/src/app/structure-list/components/modal-filter/modal-filter.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..0620f70b841a8e71f8bea09dcd9de2e7a312af86 --- /dev/null +++ b/src/app/structure-list/components/modal-filter/modal-filter.component.ts @@ -0,0 +1,125 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { ButtonType } from '../../../shared/components/button/buttonType.enum'; +import { TypeModal } from '../../enum/typeModal.enum'; +import { Category } from '../../models/category.model'; +import { Module } from '../../models/module.model'; +import { SearchService } from '../../services/search.service'; + +@Component({ + selector: 'app-modal-filter', + templateUrl: './modal-filter.component.html', + styleUrls: ['./modal-filter.component.scss'], +}) +export class ModalFilterComponent implements OnInit, OnChanges { + constructor(public searchService: SearchService) {} + + @Input() public modalType: TypeModal; + @Input() public categories: Category[]; + //checked modules filter list + @Input() public modules: Module[] = []; + @Output() searchEvent = new EventEmitter(); + @Output() closeEvent = new EventEmitter(); + public buttonTypeEnum = ButtonType; + + // Checkbox variable + public checkedModules: Module[] = []; + public toggledCategories: string[] = []; + ngOnInit(): void { + // Manage checkbox + this.checkedModules = this.modules.slice(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.modalType) { + this.checkedModules = this.modules.slice(); + } + } + + // Management of the checkbox event (Check / Uncheck) + public onCheckboxChange(event, categ: string, text?: string): void { + const checkValue: string = event.target.value; + if (event.target.checked) { + this.checkedModules.push(new Module(checkValue, categ, text ? text : checkValue)); + } else { + // Check if the module is present in the list and remove it + if (this.searchService.getIndex(this.checkedModules, checkValue, categ) > -1) { + this.checkedModules.splice(this.searchService.getIndex(this.checkedModules, checkValue, categ), 1); + } + } + } + // Clear only filters in the current modal + public clearFilters(): void { + this.categories.forEach((categ: Category) => { + categ.modules.forEach((module: Module) => { + const index = this.searchService.getIndex(this.checkedModules, module.id, categ.id); + if (index > -1) { + this.checkedModules.splice(index, 1); + } + }); + }); + this.emitModules(this.checkedModules); + } + + // Sends an array containing all modules + public emitModules(m: Module[]): void { + this.searchEvent.emit(m); + } + + public getModalType(): string { + switch (this.modalType) { + case TypeModal.accompaniment: + return 'accompaniment'; + case TypeModal.training: + return 'training'; + case TypeModal.public: + return 'public'; + case TypeModal.equipments: + return 'equipments'; + case TypeModal.moreFilters: + return 'moreFilters'; + default: + return ''; + } + } + + public closeModal(): void { + this.closeEvent.emit(); + } + + public handleCategoryCheckBox(event, category: Category): void { + const modules = this.categories.filter((c: Category) => c.id === category.id)[0].modules; + if (event.target.checked) { + for (const module of modules) { + if (this.searchService.getIndex(this.checkedModules, module.id, category.id) === -1) { + this.checkedModules.push(new Module(module.id, category.id, module.displayText)); + } + } + } else { + for (const module of modules) { + if (this.searchService.getIndex(this.checkedModules, module.id, category.id) > -1) { + this.checkedModules.splice(this.searchService.getIndex(this.checkedModules, module.id, category.id), 1); + } + } + } + } + + public toggleShowCategory(categoryId: string): void { + if (this.toggledCategories.includes(categoryId)) { + const index = this.toggledCategories.indexOf(categoryId); + this.toggledCategories.splice(index); + } else { + this.toggledCategories.push(categoryId); + } + } + + public getCategoryCheckboxStatus(c: Category): string { + const selectedModule: Module[] = this.checkedModules.filter((m) => m.text === c.id); + if (selectedModule.length === c.modules.length) { + return 'checked'; + } else if (selectedModule.length === 0) { + return 'unchecked'; + } else if (selectedModule.length && selectedModule.length > 0) { + return 'halfChecked'; + } + } +} diff --git a/src/app/structure-list/components/structure-details/structure-details.component.html b/src/app/structure-list/components/structure-details/structure-details.component.html new file mode 100644 index 0000000000000000000000000000000000000000..785ae3639c958d1a26c6257ad5894fa496ee39df --- /dev/null +++ b/src/app/structure-list/components/structure-details/structure-details.component.html @@ -0,0 +1,559 @@ +<div class="structure-details" [ngClass]="{ fullScreen: fullScreen === true }" [@slideInOut] *ngIf="structure"> + <div class="structure-details-container"> + <!-- Header info --> + <div class="structure-details-title" fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="space-evenly center"> + <app-svg-icon [type]="'ico'" [icon]="'structureAvatar'" [iconClass]="'icon-52'"></app-svg-icon> + <h1 class="bold">{{ structure.structureName }}</h1> + <div class="ico-close"> + <div (click)="close()" class="ico-close-details"></div> + </div> + </div> + <div class="structure-details-content" fxLayout="row" fxLayoutAlign="center center" *ngIf="isLoading"> + <img class="loader-gif" src="/assets/gif/loader_circle.gif" alt /> + </div> + <!-- Content --> + <div class="structure-details-content" *ngIf="!isLoading"> + <!-- Action buttons bar --> + <div class="structure-buttons hide-on-print" fxLayout="row" fxLayoutAlign="space-evenly"> + <!-- Voir le conseiller numérique - Hidden until functionnality is developed --> + <!--div class="clickableDiv" role="button" tabindex="0"> + <app-svg-icon + class="icon" + [type]="'ico'" + [icon]="'advisor'" + [iconClass]="'icon-32'" + fxLayoutAlign="space-evenly" + ></app-svg-icon> + <div class="iconTitle">Voir le conseiller numérique</div> + </div--> + <!-- Voir le site --> + <div *ngIf="structure.website" class="clickableDiv" role="button" (click)="goToWebsite()" tabindex="0"> + <app-svg-icon class="icon" [type]="'ico'" [icon]="'web'" [iconClass]="'icon-32'"></app-svg-icon> + <div class="iconTitle">Voir le site</div> + </div> + <!-- Voir la plaquette - Hidden until functionnality is developed --> + <!--div class="clickableDiv" role="button" tabindex="0"> + <app-svg-icon class="icon" [type]="'ico'" [icon]="'docs'" [iconClass]="'icon-32'"></app-svg-icon> + <div class="iconTitle">Voir la plaquette</div> + </div--> + <!-- Imprimer --> + <div role="button" class="printButton clickableDiv" (click)="print()" tabindex="0"> + <app-svg-icon class="icon" [type]="'ico'" [icon]="'printStructure'" [iconClass]="'icon-32'"></app-svg-icon> + <div class="iconTitle">Imprimer</div> + </div> + <!-- Signaler une erreur --> + <div class="clickableDiv" role="button" (click)="displayModalError()" tabindex="0"> + <app-svg-icon class="icon" [type]="'ico'" [icon]="'watch'" [iconClass]="'icon-32'"></app-svg-icon> + <div class="iconTitle">Signaler une erreur</div> + </div> + <!-- Je travaille ici --> + <div + *ngIf="!profileService.isLinkedToStructure(structure._id)" + class="clickableDiv" + role="button" + (click)="handleJoin()" + tabindex="0" + > + <app-svg-icon class="icon" [type]="'ico'" [icon]="'workhere'" [iconClass]="'icon-32'"></app-svg-icon> + <div class="iconTitle">Je travaille ici</div> + </div> + <!-- Modifier la structure --> + <div + *ngIf="profileService.isLinkedToStructure(structure._id) || profileService.isAdmin()" + class="clickableDiv" + role="button" + (click)="handleModify()" + tabindex="0" + > + <app-svg-icon class="icon" [type]="'ico'" [icon]="'modifyStructure'" [iconClass]="'icon-32'"></app-svg-icon> + <div class="iconTitle">Modifier cette structure</div> + </div> + </div> + + <!-- Admin menu --> + <div *ngIf="profileService.isAdmin()" class="structure-details-block hide-on-print"> + Administrateur(s) de cette structure: + <div *ngIf="structureAdmins.length === 0">Aucun administrateur</div> + <div *ngIf="structureAdmins.length > 0"> + <div *ngFor="let structureAdmin of structureAdmins"> + {{ structureAdmin.email }} + </div> + </div> + <a (click)="toggleDeleteModal()" class="primary" tabindex="0"> Supprimer cette structure </a> + </div> + + <div class="structure-details-block"> + <div fxLayout="column" fxLayoutGap="10px"> + <!-- Informations--> + <div fxLayout="column"> + <h2>Informations</h2> + <div class="info-block"> + <div> + {{ structure.getLabelTypeStructure() }} + </div> + <div *ngIf="structure.address"> + {{ structure.address.numero }} {{ structure.address.street }}, {{ structure.address.commune }} + </div> + <div *ngIf="structure.contactPhone"> + {{ structure.contactPhone | phone }} + </div> + <div *ngIf="structure.contactMail && structure.contactMail !== 'unknown@unknown.com'"> + <a href="mailto:{{ structure.contactMail }}">{{ structure.contactMail }}</a> + </div> + </div> + <!-- Social networks--> + <div *ngIf="structure.hasSocialNetwork()" fxLayout="row" fxLayoutAlign="none baseline" fxLayoutGap="4px"> + <a + *ngIf="structure.facebook" + target="_blank" + class="custom-link" + rel="noopener noreferrer" + [href]="'http://' + structure.facebook" + > + <app-svg-icon + [type]="'ico'" + [icon]="'facebook'" + [title]="'Facebook'" + [iconClass]="'icon-30'" + ></app-svg-icon + ></a> + <a + *ngIf="structure.twitter" + target="_blank" + class="custom-link" + rel="noopener noreferrer" + [href]="'http://' + structure.twitter" + > + <app-svg-icon + [type]="'ico'" + [icon]="'twitter'" + [title]="'Twitter'" + [iconClass]="'icon-30'" + ></app-svg-icon + ></a> + <a + *ngIf="structure.instagram" + target="_blank" + class="custom-link" + rel="noopener noreferrer" + [href]="'http://' + structure.instagram" + > + <app-svg-icon + [type]="'ico'" + [icon]="'instagram'" + [title]="'Instagram'" + [iconClass]="'icon-30'" + ></app-svg-icon + ></a> + <a + *ngIf="structure.linkedin" + target="_blank" + class="custom-link" + rel="noopener noreferrer" + [href]="'http://' + structure.linkedin" + > + <app-svg-icon + [type]="'ico'" + [icon]="'linkedin'" + [title]="'Linkedin'" + [iconClass]="'icon-30'" + ></app-svg-icon + ></a> + </div> + </div> + </div> + <div *ngIf="structure.description" class="description">{{ structure.description }}</div> + <div *ngIf="structure.lockdownActivity && lockdownInfoDisplay" class="info"> + {{ structure.lockdownActivity }} + </div> + </div> + + <div + *ngIf="structure.accessModality.length > 0 || structure.hours.hasData() || structure.remoteAccompaniment" + class="structure-details-block" + fxLayout="column" + > + <div class="hours-services-block"> + <!-- Opening Hours --> + <div *ngIf="structure.hours.hasData()" fxLayout="column"> + <h2>Horaires</h2> + <div fxLayout="column" class="opening-hours"> + <div *ngFor="let day of structure.hours | keyvalue: keepOriginalOrder"> + <div *ngIf="day.value.open" class="opening-hour" fxLayout="row" fxLayoutAlign="flex-start flex-start"> + <h4 class="day">{{ day.key | day }}</h4> + <div class="opening-time" fxLayout="column" fxLayoutAlign="none flex-start"> + <div *ngFor="let timeRange of day.value.time" class="daily-opening-time"> + <p *ngIf="timeRange.opening"> + {{ timeRange.formatOpeningDate() }} - {{ timeRange.formatClosingDate() }} + </p> + </div> + </div> + </div> + </div> + </div> + </div> + <!-- services --> + <div *ngIf="structure.accessModality.length > 0" fxLayout="column"> + <h2>Services</h2> + <div fxLayout="column" fxLayoutGap="10px" class="services-block"> + <div fxLayout="column" fxLayoutGap="8px"> + <div + fxLayout="row" + fxLayoutAlign="none flex-end" + fxLayoutGap="8px" + *ngFor="let acces of structure.accessModality" + > + <p>{{ getAccessLabel(acces) }}</p> + </div> + <p *ngIf="structure.pmrAccess">Accessible aux personnes à mobilité réduite</p> + </div> + <div + *ngFor="let public of structure.publics" + fxLayout="row" + fxLayoutAlign="none flex-end" + fxLayoutGap="8px" + > + <p>{{ getPublicLabel(public) }}</p> + </div> + <div + *ngFor="let accompaniment of structure.publicsAccompaniment" + fxLayout="row" + fxLayoutAlign="none flex-end" + fxLayoutGap="8px" + > + <p>{{ accompaniment }}</p> + </div> + </div> + </div> + </div> + <div *ngIf="structure.exceptionalClosures" class="bold-info"> + <p class="description">{{ structure.exceptionalClosures }}</p> + </div> + <div *ngIf="structure.remoteAccompaniment" class="bold-info"> + <h3>Cette structure propose un accompagnement à distance.</h3> + </div> + </div> + + <!-- Labellisation --> + <div + *ngIf="structure.labelsQualifications.length" + fxLayout="column" + class="structure-details-block" + fxLayoutAlign="baseline baseline" + fxLayoutGap="8px" + > + <h2>Labellisations</h2> + <div class="wrapper"> + <div *ngFor="let labels of structure.labelsQualifications"> + <app-logo-card [name]="labels"></app-logo-card> + </div> + </div> + </div> + + <!-- Members --> + <div + *ngIf="userIsLoggedIn() && structureAdmins.length" + fxLayout="column" + class="structure-details-block" + fxLayoutAlign="baseline baseline" + fxLayoutGap="8px" + > + <h2>Membres</h2> + <div fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="baseline baseline"> + <div *ngFor="let member of structureAdmins" class="member-card"> + <app-svg-icon + class="avatar" + [type]="'avatar'" + [icon]="'defaultAvatar'" + [iconClass]="'icon-40'" + ></app-svg-icon> + <div class="info-member"> + <p class="member">{{ member.name | uppercase }} {{ member.surname | titlecase }}</p> + <p class="job" *ngIf="displayJobEmployer(member)">{{ displayJobEmployer(member) }}</p> + </div> + </div> + </div> + </div> + + <!-- Aides numérique --> + <div + *ngIf="structure.proceduresAccompaniment.length || structure.otherDescription" + fxLayout="column" + class="structure-details-block" + fxLayoutAlign="baseline baseline" + fxLayoutGap="12px" + > + <h2>Aides au numérique</h2> + <div fxLayout="column"> + <div class="wrapper"> + <div *ngFor="let accompagnement of structure.proceduresAccompaniment.sort()"> + <app-logo-card *ngIf="accompagnement != 'autres'" [name]="accompagnement"></app-logo-card> + </div> + </div> + <p *ngIf="structure.otherDescription" fxLayout="column"> + {{ structure.otherDescription }} + </p> + </div> + </div> + + <!-- Formation --> + <div + *ngIf=" + isBaseSkills() || isAccessRights() || isParentingHelp() || isSocialAndProfessional() || isDigitalSecurity() + " + fxLayout="column" + class="structure-details-block noSeparator" + fxLayoutAlign="baseline baseline" + > + <h2>Formations</h2> + <div *ngIf="structure.freeWorkShop"> + <span *ngIf="multipleWorkshop()" class="bold-info">L'accès à ces formations est gratuit</span> + <span *ngIf="!multipleWorkshop()" class="bold-info">L'accès à cette formation est gratuit</span> + </div> + <div class="formationDetails"> + <!--Toggle BaseSkills--> + <div *ngIf="isBaseSkills()" class="collapse" [ngClass]="{ notCollapsed: !showBaseSkills }"> + <div fxLayout="column"> + <div + class="collapseHeader" + fxLayout="row" + fxLayoutGap="20px" + fxLayoutAlign=" center" + (click)="toggleBaseSkills()" + > + <div class="titleCollapse">Compétences de base</div> + <div class="logo"> + <svg class="show" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#unfold'"></use> + </svg> + <svg class="hide" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#fold'"></use> + </svg> + </div> + </div> + <div class="detailsContainer" [@show]="showBaseSkills"> + <div class="details" *ngFor="let skill of baseSkills">{{ skill.text }}</div> + </div> + </div> + </div> + <!--Toggle accessRights--> + <div *ngIf="isAccessRights()" class="collapse" [ngClass]="{ notCollapsed: !showAccessRights }"> + <div fxLayout="column"> + <div + class="collapseHeader" + fxLayout="row" + fxLayoutGap="20px" + fxLayoutAlign=" center" + (click)="toggleAccessRights()" + > + <div class="titleCollapse">Accès aux droits</div> + <div class="logo"> + <svg class="show" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#unfold'"></use> + </svg> + <svg class="hide" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#fold'"></use> + </svg> + </div> + </div> + <div class="detailsContainer" [@show]="showAccessRights"> + <div class="details" *ngFor="let rights of accessRights">{{ rights.text }}</div> + </div> + </div> + </div> + <!--Toggle parentingHelp--> + <div *ngIf="isParentingHelp()" class="collapse" [ngClass]="{ notCollapsed: !showParentingHelp }"> + <div fxLayout="column"> + <div + class="collapseHeader" + fxLayout="row" + fxLayoutGap="20px" + fxLayoutAlign=" center" + (click)="toggleParentingHelp()" + > + <div class="titleCollapse">Aide à la parentalité</div> + <div class="logo"> + <svg class="show" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#unfold'"></use> + </svg> + <svg class="hide" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#fold'"></use> + </svg> + </div> + </div> + <div class="detailsContainer" [@show]="showParentingHelp"> + <div class="details" *ngFor="let help of parentingHelp">{{ help.text }}</div> + </div> + </div> + </div> + <!--Toggle socialAndProfessional--> + <div + *ngIf="isSocialAndProfessional()" + class="collapse" + [ngClass]="{ notCollapsed: !showSocialAndProfessional }" + > + <div fxLayout="column"> + <div + class="collapseHeader" + fxLayout="row" + fxLayoutGap="20px" + fxLayoutAlign=" center" + (click)="toggleSocialAndProfessional()" + > + <div class="titleCollapse">Insertion sociale et professionnelle</div> + <div class="logo"> + <svg class="show" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#unfold'"></use> + </svg> + <svg class="hide" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#fold'"></use> + </svg> + </div> + </div> + <div class="detailsContainer" [@show]="showSocialAndProfessional"> + <div class="details" *ngFor="let skill of socialAndProfessional">{{ skill.text }}</div> + </div> + </div> + </div> + <!--Toggle digitalSecurity--> + <div *ngIf="isDigitalSecurity()" class="collapse" [ngClass]="{ notCollapsed: !showDigitalSecurity }"> + <div fxLayout="column"> + <div + class="collapseHeader" + fxLayout="row" + fxLayoutGap="20px" + fxLayoutAlign=" center" + (click)="toggleDigitalSecurity()" + > + <div class="titleCollapse">Culture et sécurité numérique</div> + <div class="logo"> + <svg class="show" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#unfold'"></use> + </svg> + <svg class="hide" aria-hidden="true"> + <use [attr.xlink:href]="'assets/form/sprite.svg#fold'"></use> + </svg> + </div> + </div> + <div class="detailsContainer" [@show]="showDigitalSecurity"> + <div class="details" *ngFor="let skill of digitalCultureSecurity">{{ skill.text }}</div> + </div> + </div> + </div> + </div> + </div> + + <!-- Matériel et wifi --> + <div + *ngIf="structure.hasEquipments()" + fxLayout="column" + class="structure-details-block" + fxLayoutAlign="baseline baseline" + > + <h2>Matériel et wifi</h2> + <div fxLayout="column"> + <div *ngIf="filterOnlyEquipments(structure.equipmentsAndServices).includes('wifiEnAccesLibre')"> + {{ getEquipmentsLabel('wifiEnAccesLibre') }} + </div> + <p *ngFor="let equipement of filterOnlyEquipments(structure.equipmentsAndServices)" class="no-margin-bottom"> + <span *ngIf="equipement == 'ordinateurs' && structure.nbComputers" + >{{ getEquipmentsLabel(equipement) }} : {{ structure.nbComputers }}</span + > + <span *ngIf="equipement == 'tablettes' && structure.nbTablets" + >{{ getEquipmentsLabel(equipement) }} : {{ structure.nbTablets }}</span + > + <span *ngIf="equipement == 'bornesNumeriques' && structure.nbNumericTerminal"> + {{ getEquipmentsLabel(equipement) }} : {{ structure.nbNumericTerminal }}</span + > + <span *ngIf="equipement == 'imprimantes' && structure.nbPrinters" + >{{ getEquipmentsLabel(equipement) }} : {{ structure.nbPrinters }}</span + > + <span *ngIf="equipement == 'scanners' && structure.nbScanners" + >{{ getEquipmentsLabel(equipement) }} : {{ structure.nbScanners }}</span + > + </p> + </div> + </div> + + <!-- Transport --> + <div + *ngIf="tclStopPoints.length" + fxLayout="column" + class="structure-details-block noSeparator" + fxLayoutAlign="baseline baseline" + > + <h2>Accès</h2> + <div fxLayout="column wrap" fxLayoutGap="24px"> + <div *ngFor="let tclStop of tclStopPoints | slice: 0:3"> + {{ tclStop.name }} + <div fxLayout="row wrap" fxLayoutGap="16px"> + <p *ngFor="let sub of tclStop.subLines"> + <app-svg-icon [type]="'tcl'" [icon]="sub" [iconClass]="'icon-75'"></app-svg-icon> + </p> + <p *ngFor="let tram of tclStop.tramLines"> + <app-svg-icon [type]="'tcl'" [icon]="tram" [iconClass]="'icon-75'"></app-svg-icon> + </p> + <p *ngFor="let bus of tclStop.busLines"> + <app-svg-icon [type]="'tcl'" [icon]="bus" [iconClass]="'icon-75'"></app-svg-icon> + </p> + </div> + </div> + </div> + </div> + <!-- Mise à jour --> + <div fxLayout="column" class="structure-details-block" fxLayoutAlign="baseline baseline" fxLayoutGap="20px"> + <div fxLayout="row" fxLayoutAlign="none flex-start" fxLayoutGap="13px"> + <p class="updated">Mise à jour le {{ structure.updatedAt | date: 'mediumDate' }}</p> + </div> + </div> + </div> + + <app-modal-confirmation + [openned]="deleteModalOpenned" + [content]="'Voulez-vous vraiment supprimer cette structure ?'" + (closed)="deleteStructure($event)" + ></app-modal-confirmation> + + <app-join-modal-confirmation + [openned]="claimModalOpenned" + [title]="'Travaillez-vous ici ?'" + [primaryContent]=" + 'Un message sera envoyé aux administrateurs Rés\'IN pour valider l\'affectation de votre compte à la structure' + " + [secondaryContent]="structure.structureName" + [customConfirmationText]="'Rejoindre la structure'" + (closed)="claimStructure($event)" + ></app-join-modal-confirmation> + + <app-join-modal-confirmation + [openned]="joinModalOpenned" + [title]="'Travaillez-vous ici ?'" + [primaryContent]="'Un message sera envoyé à un administrateur de la structure'" + [secondaryContent]="structure.structureName" + [customConfirmationText]="'Rejoindre la structure'" + (closed)="joinStructure($event)" + ></app-join-modal-confirmation> + + <app-join-modal-confirmation + [openned]="pendingModalOpenned" + [title]="'Travaillez-vous ici ?'" + [primaryContent]=" + 'Un message a déjà été envoyé aux administrateurs Rés\'IN pour validation, vous recevrez un email quand votre compte sera rattaché à la structure' + " + [secondaryContent]="structure.structureName" + [customConfirmationText]="'OK'" + [displayCancelButton]="false" + (closed)="togglePendingModal()" + ></app-join-modal-confirmation> + + <app-text-input-modal + [openned]="structureErrorModalOpenned" + [placeholder]="'Décrivez l\'erreur ici. Ex: Horaires faux...'" + [content]=" + 'Voulez-vous notifier res\'in d\'une erreur sur la fiche de cet acteur ? Votre commentaire sera envoyé à l\'acteur en question ainsi qu\'aux administrateurs.' + " + (closed)="sendErrorEmail($event)" + (newContent)="sendErrorEmail($event)" + ></app-text-input-modal> + </div> +</div> diff --git a/src/app/structure-list/components/structure-details/structure-details.component.scss b/src/app/structure-list/components/structure-details/structure-details.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..79b7b871a907f892dca2993efc848ac047071392 --- /dev/null +++ b/src/app/structure-list/components/structure-details/structure-details.component.scss @@ -0,0 +1,341 @@ +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/breakpoint'; +@import '../../../../assets/scss/layout'; +@import '../../../../assets/scss/z-index'; + +a { + padding: unset; + text-decoration: underline; + font-size: inherit; + font-weight: inherit; +} + +p:empty { + margin: 0; +} + +h1 { + @include lato-bold-20; + color: $grey-1; +} +h2 { + @include lato-bold-14; + color: $grey-3; + text-transform: uppercase; + margin-top: 0; + margin-bottom: 12px; +} +h3 { + @include lato-regular-16; + margin: 0 0 8px 0; +} + +.structure-details-container { + position: absolute; + z-index: $structure-details-z-index; + height: calc(100vh - $header-height - 1px); // -1 is to prevent limit case + width: 100%; + background-color: $white; + overflow: hidden; + border-bottom: 1px solid $grey-5; + border-right: 1px solid $grey-5; +} + +.structure-details-title { + height: 65px; + border-bottom: 1px solid $grey-5; + padding: 2px 16px 2px 24px; + .ico-close { + margin-left: auto; + } +} + +.structure-details-content { + height: calc(100% - 65px); + padding: 0px 8px; + overflow-y: auto; + scrollbar-gutter: stable; + @include lato-regular-14; +} + +.structure-buttons { + width: 100%; + margin: 0 0 16px 0; + position: relative; + .clickableDiv { + text-align: center; + height: 90px; + width: 115.2px; + display: flex; + flex-direction: column; + cursor: pointer; + .icon { + margin-top: 20px; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + } + .iconTitle { + @include lato-regular-13; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + } + &:hover { + text-decoration: underline; + } + } + @media #{$tablet} { + .printButton { + display: none !important; + } + } +} + +.structure-details-block { + margin: 0 20px; + padding: 24px 0; + border-bottom: 2px solid $grey-8; + &.noSeparator { + border-bottom: none; + padding-bottom: 0px; + } + .member-card { + display: flex; + justify-content: center; + align-items: center; + .avatar { + background-color: $grey-8; + border-radius: 4px; + } + .info-member { + margin-left: 1rem; + p { + margin: 0; + } + .member { + @include lato-bold-14; + } + .job { + @include lato-regular-14; + } + } + } + + .info-block > div { + margin-top: 4px; + &:first-of-type { + margin-top: 0px; + } + } + + .description { + white-space: pre-wrap; + margin-top: 8px; + } + + .info { + color: $red-1; + margin-top: 8px; + } + + .hours-services-block { + display: flex; + flex-direction: row; + & > div { + flex: 1; + } + @media #{$large-phone} { + flex-direction: column; + } + + .opening-hours { + margin-bottom: 8px; + .opening-hour { + margin-bottom: 8px; + .day { + min-width: 70px; + margin-top: 0; + margin-left: 0; + margin-bottom: 0; + @include lato-regular-14; + color: $grey-3; + text-transform: capitalize; + } + .daily-opening-time { + p { + margin: 0 0 4px 0; + } + } + } + } + } + + .services-block { + margin-bottom: 8px; + p { + display: list-item; + margin: 0 0 0 25px; + } + } + + .wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + @media #{$large-phone} { + grid-template-columns: 1fr; + } + } + + .formationDetails { + width: 100%; + .collapse { + margin-bottom: 13px; + @media #{$small-phone} { + width: 95% !important; + } + &.notCollapsed { + border-bottom: 2px solid $grey-8; + .logo { + .hide { + display: none; + } + .show { + display: block; + } + } + } + .titleCollapse { + width: 100%; + @include lato-regular-16; + } + .collapseHeader { + cursor: pointer; + } + .logo { + height: 24px; + width: 24px; + svg { + width: 100%; + height: 100%; + fill: $grey-1; + } + } + .logo, + .titleCollapse { + .hide { + display: block; + } + .show { + display: none; + } + } + .detailsContainer { + margin: 8px 0px; + padding: 8px 0; + background-color: $grey-8; + overflow: hidden; + } + .details { + padding: 8px 16px; + } + } + } + + .updated { + @include lato-regular-14; + color: $grey-3; + font-style: italic; + } +} + +p, +.custom-link { + @include lato-regular-16; + margin-top: 9px; + margin-bottom: 9px; + &.no-margin { + margin-top: unset; + margin-bottom: unset; + } + &.no-margin-bottom { + margin-bottom: unset; + } +} +.custom-link { + ::ng-deep svg { + border: 1px solid $white; + border-radius: 20px; + } + ::ng-deep svg:hover { + border-color: $grey-4; + } +} + +.bold-info { + @include lato-bold-16; +} + +@media print { + .structure-details { + height: unset !important; + overflow: hidden; + z-index: unset; + width: unset; + position: unset !important; + } + .structure-details-container, + .structure-details-content { + background-color: unset; + z-index: unset; + position: unset; + top: unset; + left: unset; + max-width: unset; + width: unset; + height: unset; + padding: unset; + overflow: hidden; + border-right: 0; + } + + .hide-on-print { + display: none !important; + } +} + +@keyframes fadeBackground { + 0% { + background-color: $modal-background-transparent; + } + 100% { + background-color: $modal-background; + } +} +.fullScreen { + width: calc(100% + 600px) !important; + background-color: $modal-background; + animation: fadeBackground 0.5s; + max-width: unset !important; + @media #{$tablet} { + width: 100% !important; + } +} +.structure-details { + position: fixed; + z-index: $structure-details-z-index; + height: calc(100vh - $header-height); + width: 100%; + max-width: 600px; + + .structure-details-container { + max-width: 600px; + opacity: 1 !important; + @media #{$tablet} { + max-width: unset; + } + } +} diff --git a/src/app/structure-list/components/structure-details/structure-details.component.spec.ts b/src/app/structure-list/components/structure-details/structure-details.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1e70665c38558bbc6f00435bab37ff7d5237aef --- /dev/null +++ b/src/app/structure-list/components/structure-details/structure-details.component.spec.ts @@ -0,0 +1,75 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Structure } from '../../../models/structure.model'; +import { AccessModality } from '../../enum/access-modality.enum'; +import { Category } from '../../models/category.model'; +import { Module } from '../../models/module.model'; +import { StructureDetailsComponent } from './structure-details.component'; + +describe('StructureDetailsComponent', () => { + let component: StructureDetailsComponent; + let fixture: ComponentFixture<StructureDetailsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [StructureDetailsComponent], + imports: [HttpClientTestingModule, RouterTestingModule], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(StructureDetailsComponent); + component = fixture.componentInstance; + let structure: Structure = new Structure(); + structure.baseSkills = ['123', '234', '817']; + component.structure = structure; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit close action', () => { + spyOn(component.closeDetails, 'emit'); + component.close(); + expect(component.closeDetails.emit).toHaveBeenCalled(); + expect(component.closeDetails.emit).toHaveBeenCalledWith(true); + }); + + it('should return icon name with a string input', () => { + const iconNameGroup = component.getAccessIcon(AccessModality.free); + const iconNameCalendar = component.getAccessIcon(AccessModality.meeting); + const iconNameTel = component.getAccessIcon(AccessModality.numeric); + expect(iconNameGroup).toEqual('group'); + expect(iconNameCalendar).toEqual('calendar'); + expect(iconNameTel).toEqual('tel'); + }); + + it('should update array with right modules', () => { + let baseSkillssReferentiel = new Category(); + let accessRightsReferentiel = new Category(); + const mo1 = new Module('132', 'Uniquement sur RDV'); + const mo2 = new Module('145', 'Accès libre'); + const mo3 = new Module('112', 'Téléphone / Visio'); + const arrayModule: Module[] = [mo1, mo2, mo3]; + const m1 = new Module('260', 'm1'); + const m2 = new Module('259', 'm2'); + const m3 = new Module('261', 'm3'); + const m4 = new Module('249', 'm4'); + const m5 = new Module('222', 'm5'); + const arrayModuleBase: Module[] = [m1, m2, m3, m4, m5]; + baseSkillssReferentiel.name = 'categ2'; + baseSkillssReferentiel.modules = arrayModuleBase; + component.baseSkillssReferentiel = baseSkillssReferentiel; + accessRightsReferentiel.name = 'categ1'; + accessRightsReferentiel.modules = arrayModule; + component.accessRightsReferentiel = accessRightsReferentiel; + component.structure.baseSkills = ['260', '261']; + component.structure.accessRight = ['145', '112']; + component.setServiceCategories(); + expect(component.baseSkills).toEqual([m1, m3]); + expect(component.accessRights).toEqual([mo2, mo3]); + }); +}); diff --git a/src/app/structure-list/components/structure-details/structure-details.component.ts b/src/app/structure-list/components/structure-details/structure-details.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fc9768db01d0e96a16ea666123bb449f9e39dfc --- /dev/null +++ b/src/app/structure-list/components/structure-details/structure-details.component.ts @@ -0,0 +1,409 @@ +import { animate, AUTO_STYLE, state, style, transition, trigger } from '@angular/animations'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Location } from '@angular/common'; +import * as _ from 'lodash'; +import { ParametersService } from '../../../admin/services/parameters.service'; +import { Owner } from '../../../models/owner.model'; +import { Structure } from '../../../models/structure.model'; +import { TclStopPoint } from '../../../models/tclStopPoint.model'; +import { User } from '../../../models/user.model'; +import { ProfileService } from '../../../profile/services/profile.service'; +import { AuthService } from '../../../services/auth.service'; +import { StructureService } from '../../../services/structure.service'; +import { TclService } from '../../../services/tcl.service'; +import { PrintService } from '../../../shared/service/print.service'; +import { Utils } from '../../../utils/utils'; +import { AccessModality } from '../../enum/access-modality.enum'; +import { Equipment } from '../../enum/equipment.enum'; +import { PublicCategorie } from '../../enum/public.enum'; +import { Category } from '../../models/category.model'; +import { Module } from '../../models/module.model'; +import { SearchService } from '../../services/search.service'; + +@Component({ + selector: 'app-structure-details', + templateUrl: './structure-details.component.html', + styleUrls: ['./structure-details.component.scss'], + animations: [ + trigger('slideInOut', [ + transition(':enter', [style({ left: '-600px' }), animate('200ms ease-in', style({ left: '0' }))]), + transition(':leave', [animate('200ms ease-in', style({ left: '-600px' }))]), + ]), + trigger('fadeInOut', [ + transition(':enter', [ + style({ backgroundColor: 'rgb(00, 00, 00, 0)' }), + animate('200ms ease-in', style({ backgroundColor: 'rgb(00, 00, 00, 0.6)' })), + ]), + transition(':leave', [animate('200ms ease-in', style({ backgroundColor: 'rgb(00, 00, 00, 0)' }))]), + ]), + trigger('show', [ + state('true', style({ height: AUTO_STYLE, visibility: AUTO_STYLE, margin: '8px 0' })), + state('false', style({ height: '0px', visibility: 'hidden', margin: '0' })), + transition('true => false', animate('300ms ease-out')), + transition('false => true', animate('300ms ease-out')), + ]), + ], +}) +export class StructureDetailsComponent implements OnInit { + @Input() public structure: Structure; + @Output() public closeDetails: EventEmitter<boolean> = new EventEmitter<boolean>(); + public accessModality = AccessModality; + + public baseSkillssReferentiel: Category; + public accessRightsReferentiel: Category; + public digitalCultureSecuritysReferentiel: Category; + public socialAndProfessionalsReferentiel: Category; + public parentingHelpsReferentiel: Category; + public baseSkills: Module[]; + public accessRights: Module[]; + public parentingHelp: Module[]; + public socialAndProfessional: Module[]; + public digitalCultureSecurity: Module[]; + public showBaseSkills = false; + public showAccessRights = false; + public showParentingHelp = false; + public showSocialAndProfessional = false; + public showDigitalSecurity = false; + public tclStopPoints: TclStopPoint[] = []; + public printMode = false; + public isLoading = true; + public lockdownInfoDisplay = false; + public currentProfile: User = null; + public deleteModalOpenned = false; + public structureErrorModalOpenned = false; + public claimModalOpenned: boolean; + public joinModalOpenned = false; + public pendingModalOpenned = false; + public structureAdmins: Owner[] = []; + public fullScreen = false; + + constructor( + private printService: PrintService, + private searchService: SearchService, + private structureService: StructureService, + private tclService: TclService, + private profileService: ProfileService, + private authService: AuthService, + private route: ActivatedRoute, + private parametersService: ParametersService, + private location: Location, + private router: Router + ) { + this.route.url.subscribe((url) => { + if (url.length > 0 && url[0].path === 'structure') { + this.structure = new Structure(this.printService.structure); + this.printMode = true; + // Display formations for printing + this.toggleAccessRights(); + this.toggleBaseSkills(); + this.toggleDigitalSecurity(); + this.toggleParentingHelp(); + this.toggleSocialAndProfessional(); + this.initForm(); + } + }); + this.parametersService.getParameters().subscribe((params) => { + this.lockdownInfoDisplay = params.lockdownInfoDisplay; + }); + } + + async ngOnInit(): Promise<void> { + this.route.queryParams.subscribe((queryParams) => { + if (queryParams.id) { + this.structureService.getStructure(queryParams.id).subscribe((structure) => { + this.structure = new Structure(structure); + this.initForm(); + }); + } else if (!this.printMode) { + this.structure = null; + } + }); + this.route.data.subscribe((data) => { + if (data.fullScreen) { + this.fullScreen = true; + } + }); + } + + private async initForm(): Promise<void> { + if (this.userIsLoggedIn()) { + this.currentProfile = await this.profileService.getProfile(); + this.structureService.getStructureWithOwners(this.structure._id, this.currentProfile).subscribe((res) => { + this.structureAdmins = res.owners; + }); + } + this.structure.isClaimed = await this.structureService + .isClaimed(this.structure._id, this.currentProfile) + .toPromise(); + + // GetTclStopPoints + this.getTclStopPoints(); + this.searchService.getCategoriesTraining().subscribe((referentiels) => { + referentiels.forEach((referentiel) => { + if (referentiel.isBaseSkills()) { + this.baseSkillssReferentiel = referentiel; + } else if (referentiel.isRigthtsAccess()) { + this.accessRightsReferentiel = referentiel; + } else if (referentiel.isDigitalCultureSecurity()) { + this.digitalCultureSecuritysReferentiel = referentiel; + } else if (referentiel.isParentingHelp()) { + this.parentingHelpsReferentiel = referentiel; + } else if (referentiel.isSocialAndProfessional()) { + this.socialAndProfessionalsReferentiel = referentiel; + } + }); + this.setServiceCategories(); + if (this.printMode) { + this.printService.onDataReady(); + } + this.isLoading = false; + }); + const index = this.structure.proceduresAccompaniment.indexOf('autres'); + if (index > -1) { + this.structure.proceduresAccompaniment.splice(index, 1); + } + } + + public userIsLoggedIn(): boolean { + return this.authService.isLoggedIn(); + } + + public getEquipmentsLabel(equipment: Equipment): string { + switch (equipment) { + case Equipment.wifi: + return 'Wifi en accès libre'; + case Equipment.bornes: + return this.structure.nbNumericTerminal > 1 ? 'Bornes numériques' : 'Borne numérique'; + case Equipment.printer: + return this.structure.nbPrinters > 1 ? 'Imprimantes' : 'Imprimante'; + case Equipment.tablet: + return this.structure.nbTablets > 1 ? 'Tablettes' : 'Tablette'; + case Equipment.computer: + return this.structure.nbComputers > 1 ? 'Ordinateurs' : 'Ordinateur'; + case Equipment.scanner: + return this.structure.nbScanners > 1 ? 'Scanners' : 'Scanner'; + default: + return null; + } + } + + public close(): void { + this.route.url.subscribe((urls) => { + if (urls.length > 0 && urls[0].path !== 'orientation') { + this.router.navigate(['/acteurs'], { + relativeTo: this.route, + queryParams: { + id: null, + }, + queryParamsHandling: 'merge', + }); + } else { + this.isLoading = true; + this.location.back(); + } + }); + } + + public print(): void { + this.printService.printDocument('structure', this.structure); + } + + public toggleDeleteModal(): void { + this.deleteModalOpenned = !this.deleteModalOpenned; + } + + public toggleClaimModal(): void { + this.claimModalOpenned = !this.claimModalOpenned; + } + + public toggleJoinModal(): void { + this.joinModalOpenned = !this.joinModalOpenned; + } + + public togglePendingModal(): void { + this.pendingModalOpenned = !this.pendingModalOpenned; + } + + public handleJoin(): void { + if (this.userIsLoggedIn()) { + if (this.structure.isClaimed) { + if (this.profileService.isPendingLinkedToStructure(this.structure._id)) { + this.togglePendingModal(); + } else { + this.toggleJoinModal(); + } + } else { + this.toggleClaimModal(); + } + } else { + this.router.navigate(['login'], { queryParams: { returnUrl: `acteurs?id=${this.structure._id}` } }); + } + } + public handleModify(): void { + this.router.navigateByUrl(`/profile/edit-structure/${this.structure._id}`); + } + + public deleteStructure(shouldDelete: boolean): void { + this.toggleDeleteModal(); + if (shouldDelete) { + this.structureService.delete(this.structure._id).subscribe((res) => { + this.reload(); + }); + } + } + + private reload(): void { + this.router.routeReuseStrategy.shouldReuseRoute = () => false; + this.router.onSameUrlNavigation = 'reload'; + this.router.navigate(['./'], { relativeTo: this.route }); + } + + public claimStructure(shouldClaim: boolean): void { + this.toggleClaimModal(); + if (shouldClaim) { + this.structureService + .claimStructureWithAccount(this.structure._id, this.authService.userValue.username) + .subscribe(); + this.router.navigate(['join', this.structure._id], { state: { isClaimed: this.structure.isClaimed } }); + } + } + + public joinStructure(shouldJoin: boolean): void { + this.toggleJoinModal(); + if (shouldJoin) { + this.structureService.joinStructure(this.structure._id, this.authService.userValue.username).subscribe(); + this.router.navigate(['join', this.structure._id], { state: { isClaimed: this.structure.isClaimed } }); + } + } + + public getAccessLabel(accessModality: AccessModality): string { + switch (accessModality) { + case AccessModality.free: + return 'Accès libre'; + case AccessModality.meeting: + return 'Sur rendez-vous'; + case AccessModality.meetingOnly: + return 'Uniquement sur RDV'; + case AccessModality.numeric: + return 'Téléphone / Visio'; + default: + return null; + } + } + + public getPublicLabel(tagetPublic: PublicCategorie): string { + switch (tagetPublic) { + case PublicCategorie.young: + return 'Jeunes (16 - 25 ans)'; + case PublicCategorie.adult: + return 'Adultes'; + case PublicCategorie.elderly: + return 'Séniors (+ de 65 ans)'; + case PublicCategorie.all: + return 'Tout public'; + case PublicCategorie.under16Years: + return 'Moins de 16 ans'; + case PublicCategorie.women: + return 'Uniquement femmes'; + default: + return null; + } + } + + public setServiceCategories(): void { + this.baseSkills = this.structure.baseSkills.map((skill) => + _.find(this.baseSkillssReferentiel.modules, { id: skill }) + ); + this.accessRights = this.structure.accessRight.map((rights) => + _.find(this.accessRightsReferentiel.modules, { id: rights }) + ); + this.parentingHelp = this.structure.parentingHelp.map((help) => + _.find(this.parentingHelpsReferentiel.modules, { id: help }) + ); + this.socialAndProfessional = this.structure.socialAndProfessional.map((skill) => + _.find(this.socialAndProfessionalsReferentiel.modules, { id: skill }) + ); + this.digitalCultureSecurity = this.structure.digitalCultureSecurity.map((skill) => + _.find(this.digitalCultureSecuritysReferentiel.modules, { id: skill }) + ); + } + + public keepOriginalOrder = (a, b) => a.key; + + public isBaseSkills(): boolean { + return this.baseSkills && this.baseSkills[0] !== undefined; + } + public isAccessRights(): boolean { + return this.accessRights && this.accessRights[0] !== undefined; + } + public isParentingHelp(): boolean { + return this.parentingHelp && this.parentingHelp[0] !== undefined; + } + public isSocialAndProfessional(): boolean { + return this.socialAndProfessional && this.socialAndProfessional[0] !== undefined; + } + public isDigitalSecurity(): boolean { + return this.digitalCultureSecurity && this.digitalCultureSecurity[0] !== undefined; + } + + public getTclStopPoints(): void { + this.tclService.getTclStopPointBycoord(this.structure.getLon(), this.structure.getLat()).subscribe((res) => { + this.tclStopPoints = res; + }); + } + + public filterOnlyEquipments(equipmentsAndServices: string[]): string[] { + return equipmentsAndServices.filter((eqpt) => + ['ordinateurs', 'tablettes', 'bornesNumeriques', 'imprimantes', 'scanners', 'wifiEnAccesLibre'].includes(eqpt) + ); + } + + public displayModalError(): void { + this.structureErrorModalOpenned = !this.structureErrorModalOpenned; + } + + public sendErrorEmail(modalValue: any): void { + this.displayModalError(); + if (modalValue.shouldSend) { + this.structureService.sendMailOnStructureError(this.structure._id, modalValue.content).subscribe(() => {}); + } + } + + public multipleWorkshop(): boolean { + if ( + this.structure.baseSkills.length + + this.structure.accessRight.length + + this.structure.parentingHelp.length + + this.structure.socialAndProfessional.length + + this.structure.digitalCultureSecurity.length > + 1 + ) { + return true; + } + return false; + } + + public toggleBaseSkills(): void { + this.showBaseSkills = !this.showBaseSkills; + } + public toggleAccessRights(): void { + this.showAccessRights = !this.showAccessRights; + } + public toggleParentingHelp(): void { + this.showParentingHelp = !this.showParentingHelp; + } + public toggleSocialAndProfessional(): void { + this.showSocialAndProfessional = !this.showSocialAndProfessional; + } + public toggleDigitalSecurity(): void { + this.showDigitalSecurity = !this.showDigitalSecurity; + } + + public goToWebsite(): void { + window.open(this.structure.website, '_blank'); + } + public displayJobEmployer(profile: User): string { + return new Utils().getJobEmployer(profile); + } +} diff --git a/src/app/structure-list/components/structure-list-search/structure-list-search.component.html b/src/app/structure-list/components/structure-list-search/structure-list-search.component.html new file mode 100644 index 0000000000000000000000000000000000000000..f73769ce693f3d75ff9126a24df29d26242895c5 --- /dev/null +++ b/src/app/structure-list/components/structure-list-search/structure-list-search.component.html @@ -0,0 +1,167 @@ +<div class="block"> + <div class="content"> + <form + class="inputSearch" + [formGroup]="searchForm" + fxLayout="row" + fxLayoutGap="4px" + fxLayoutAlign=" center" + (ngSubmit)="applyFilter(searchForm.value.searchTerm)" + > + <div fxLayout="row" fxLayoutAlign="space-between center" class="container"> + <input type="text" formControlName="searchTerm" placeholder="Rechercher une association, une commune..." /> + <button + *ngIf="this.searchForm.get('searchTerm').value?.length > 0" + (click)="clearInput()" + class="icon close" + type="button" + > + <div class="ico-close-search"></div> + </button> + <span *ngIf="this.searchForm.get('searchTerm').value?.length > 0" class="separation"></span> + <app-button [style]="buttonTypeEnum.searchIcon" [iconBtn]="'search'" [type]="'submit'"></app-button> + </div> + </form> + <div (clickOutside)="closeModal()" class="btn-container"> + <div class="btnSection" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="4px"> + <button + class="btn-filter isntPhoneContent" + type="button" + fxLayout="row" + [ngClass]="{ + selected: modalTypeOpened === TypeModal.accompaniment, + containCheckedFilters: numberAccompanimentChecked + }" + fxLayoutAlign="space-between center" + (click)="openModal(TypeModal.accompaniment)" + > + <span>Aide numérique</span> + <div class="arrow"></div> + </button> + <button + class="btn-filter isntPhoneContent" + type="button" + fxLayout="row" + [ngClass]="{ + selected: modalTypeOpened === TypeModal.training, + containCheckedFilters: numberTrainingChecked + }" + fxLayoutAlign="space-between center" + (click)="openModal(TypeModal.training)" + > + <span>Ateliers</span> + <div class="arrow"></div> + </button> + <button + class="btn-filter isntPhoneContent" + type="button" + fxLayout="row" + [ngClass]="{ + selected: modalTypeOpened === TypeModal.public, + containCheckedFilters: numberPublicChecked + }" + fxLayoutAlign="space-between center" + (click)="openModal(TypeModal.public)" + > + <span>Public</span> + <div class="arrow"></div> + </button> + <button + class="btn-filter isntPhoneContent" + type="button" + fxLayout="row" + [ngClass]="{ + selected: modalTypeOpened === TypeModal.equipments, + containCheckedFilters: numberEquipmentChecked + }" + fxLayoutAlign="space-between center" + (click)="openModal(TypeModal.equipments)" + > + <span>Matériel et wifi</span> + <div class="arrow"></div> + </button> + <div + class="checkboxButton" + [ngClass]="{ + checked: searchService.getIndex(checkedModulesFilter, 'passNumerique', 'labelsQualifications') > -1 + }" + > + <label fxLayout="row" fxLayoutAlign="center center"> + <input + type="checkbox" + value="passNumerique" + [checked]="searchService.getIndex(checkedModulesFilter, 'passNumerique', 'labelsQualifications') > -1" + (change)="externalCheckboxCheck($event, 'labelsQualifications', 'Pass numérique')" + /> + <div class="label pass">Pass numérique</div> + </label> + </div> + <div + class="checkboxButton" + [ngClass]="{ + checked: + searchService.getIndex(checkedModulesFilter, 'conseillerNumFranceServices', 'labelsQualifications') > -1 + }" + > + <label fxLayout="row" fxLayoutAlign="center center"> + <input + type="checkbox" + value="conseillerNumFranceServices" + [checked]=" + searchService.getIndex(checkedModulesFilter, 'conseillerNumFranceServices', 'labelsQualifications') > -1 + " + (change)="externalCheckboxCheck($event, 'labelsQualifications', 'Conseillers numériques')" + /> + <div class="label pass">Conseillers numériques</div> + </label> + </div> + <div + class="checkboxButton isntPhoneContent" + [ngClass]="{ + checked: searchService.getIndex(checkedModulesFilter, 'accesLibre', 'accessModality') > -1 + }" + > + <label fxLayout="row" fxLayoutAlign="center center"> + <input + type="checkbox" + value="accesLibre" + [checked]="searchService.getIndex(checkedModulesFilter, 'accesLibre', 'accessModality') > -1" + (change)="externalCheckboxCheck($event, 'accessModality', 'Accès libre')" + /> + <div class="label pass">Accès libre</div> + </label> + </div> + <app-button + class="isntPhoneContent last-button" + [style]="buttonTypeEnum.Tertiary" + [text]="'Plus de filtres'" + fxLayout="row" + fxLayoutAlign="space-between center" + (action)="openModal(TypeModal.moreFilters)" + ></app-button> + <div *ngIf="modalTypeOpened"> + <app-modal-filter + [modalType]="modalTypeOpened" + [categories]="getModalCategory()" + [modules]="checkedModulesFilter" + (searchEvent)="fetchResults($event)" + (closeEvent)="closeModal()" + ></app-modal-filter> + </div> + </div> + </div> + </div> + + <div *ngIf="checkedModulesFilter.length" fxLayout="row wrap" fxLayoutGap="4px" class="filterTags isntPhoneContent"> + <div class="title">Filtres :</div> + <app-button + *ngFor="let filter of checkedModulesFilter" + [style]="buttonTypeEnum.TagCloudButton" + [text]="filter.displayText ? filter.displayText : filter.id" + (action)="removeFilter(filter)" + ></app-button> + <div class="reset-icon" (click)="resetFilters()"> + <app-svg-icon [type]="'ico'" [icon]="'tagReset'" [iconColor]="'black'"></app-svg-icon> + </div> + </div> +</div> diff --git a/src/app/structure-list/components/structure-list-search/structure-list-search.component.scss b/src/app/structure-list/components/structure-list-search/structure-list-search.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..6b0c60d2b63c2553059431d7f6d14d3959a4d88a --- /dev/null +++ b/src/app/structure-list/components/structure-list-search/structure-list-search.component.scss @@ -0,0 +1,258 @@ +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/inputs'; +@import '../../../../assets/scss/hyperlink'; +@import '../../../../assets/scss/breakpoint'; +@import '../../../../assets/scss/buttons'; + +.block { + padding: 0 0.5rem; + border-bottom: solid 1px $grey-4; + @media #{$large-tablet} { + padding: 0 10px; + } +} +.content { + margin-bottom: 0.5rem; + display: flex; + align-items: center; + + input { + @include lato-regular-13; + @include input-search; + margin-top: unset; + } + .inputSearch { + padding: 6px 10px 6px 6px; + width: 200px; + min-width: 200px; + background-color: $grey-8; + color: $grey-3; + height: 36px; + border-radius: 50px; + margin-right: 0.25rem; + @media #{$large-desktop} { + width: 300px; + min-width: 250px; + } + @media #{$large-tablet} { + width: 100%; + margin-bottom: 0.5rem; + margin-right: 0; + } + .container { + width: 100%; + height: 40px; + .separation { + border-right: solid 1px $grey-4; + width: 5px; + height: 23px; + margin-right: 5px; + } + } + } + @media #{$large-tablet} { + flex-direction: column !important; + } +} +.btn-container { + width: 100%; + display: flex; +} +.btnSection { + width: 100%; + @media #{$large-tablet} { + display: contents !important; + } + button { + background: $white; + height: 36px; + border: 1px solid $grey-4; + padding: 10px 12px; + outline: none; + border-radius: 50px; + cursor: pointer; + text-align: left; + transition: all 300ms ease; + line-height: 110%; + @include btn-normal; + @include lato-regular-13; + &:hover:not(.selected) { + background: $grey-7; + } + .arrow { + background-color: transparent; + border-bottom: 1px solid black; + border-right: 1px solid black; + transform: translateY(-25%) rotate(45deg); + margin: 0 5px 0 10px; + height: 7px; + width: 7px; + transition: all 300ms ease; + } + &:focus { + border-color: $focus-color; + } + } + .selected { + background-color: $primary-color; + border-color: $primary-color !important; + color: $white; + .arrow { + background-color: transparent; + border-bottom: 1px solid $white; + border-right: 1px solid $white; + transform: translateY(25%) rotate(-135deg); + margin: 0 5px 0 10px; + height: 7px; + width: 7px; + } + } + .btn-filter { + height: 40px; + span { + line-height: 110%; + } + } + .containCheckedFilters { + border-color: $primary-color; + } + .checkboxButton { + box-sizing: border-box; + @include btn-filter; + width: auto; + border-radius: 50px; + padding: 0px 10px; + display: flex; + align-items: center; + justify-content: center; + transition: all 300ms ease; + @include lato-regular-13; + line-height: 110%; + @media #{$large-tablet} { + width: max-content; + } + + &.checked { + border-color: $primary-color; + background: $primary-color-light; + } + &:hover:not(.checked) { + background: $grey-8; + } + label { + cursor: pointer; + font-size: inherit; + font-weight: inherit; + } + input[type='checkbox'] { + appearance: none; + border-radius: 50%; + width: 20px; + min-width: 20px; + height: 20px; + border: solid 1px $grey-4; + background: white; + margin: 0; + margin-right: 5px; + transition: all 300ms ease; + position: relative; + + &:checked { + background: $primary-color; + border: none; + &:after { + border-bottom: 2px solid white; + border-left: 2px solid white; + content: ''; + height: 4px; + left: 4px; + opacity: 1; + position: absolute; + top: 6px; + transform: rotate(-45deg); + width: 10px; + } + } + } + } +} +::ng-deep .btn-regular.tertiary { + height: 40px !important; + div { + line-height: normal; + } +} +.last-button { + margin-left: auto; +} +.footerSearchSection { + margin: 8px 0px 8px 0px; + height: 40px; +} + +.icon { + background-color: transparent; + border: 1px solid transparent; + outline: none; + cursor: pointer; + &.pin { + padding: 4px 6px 8px 6px; + &:hover { + .ico-pin-search { + background-color: $primary-color; + } + } + &:focus { + border-color: $primary-color; + .ico-pin-search { + background-color: $primary-color; + } + } + &:active { + border-color: transparent; + .ico-pin-search { + background-color: $blue-light; + } + } + } + &.close { + &:focus { + border-color: $primary-color; + } + &:active { + border-color: transparent; + } + } +} +a { + @include hyperlink; + text-align: right; +} + +.phoneSection { + margin: 9px 0px 18px 0px; + display: none; + .btnSection { + padding: 0; + } +} +@media #{$large-tablet} { + .isntPhoneContent { + display: none !important; + } + .phoneSection { + display: block; + } +} +.filterTags { + margin: 0.5rem 0 0 0; + .title { + margin-top: 5px; + color: $grey-3; + } + .reset-icon { + padding-top: 0.2rem; + cursor: pointer; + } +} diff --git a/src/app/structure-list/components/structure-list-search/structure-list-search.component.spec.ts b/src/app/structure-list/components/structure-list-search/structure-list-search.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cac4adfa66f31be62dfaf480b0d3a1643612953 --- /dev/null +++ b/src/app/structure-list/components/structure-list-search/structure-list-search.component.spec.ts @@ -0,0 +1,149 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { of } from 'rxjs'; +import { GeoJson } from '../../../map/models/geojson.model'; +import { GeojsonService } from '../../../services/geojson.service'; +import { TypeModal } from '../../enum/typeModal.enum'; +import { Filter } from '../../models/filter.model'; +import { Module } from '../../models/module.model'; +import { StructureListSearchComponent } from './structure-list-search.component'; + +describe('StructureListSearchComponent', () => { + let component: StructureListSearchComponent; + let fixture: ComponentFixture<StructureListSearchComponent>; + let geoService: GeojsonService; + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [StructureListSearchComponent], + imports: [HttpClientTestingModule, ReactiveFormsModule], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(StructureListSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + geoService = TestBed.inject(GeojsonService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + // applyFilter function + it('should emit filters', () => { + const filter: Filter[] = [new Filter('query', 'valInput')]; + spyOn(component.searchEvent, 'emit'); + component.applyFilter('valInput'); + expect(component.searchEvent.emit).toHaveBeenCalled(); + expect(component.searchEvent.emit).toHaveBeenCalledWith(filter); + }); + + // countCheckFiltersOnModules function + it('should return a number of checked elements in an array', () => { + const checkedModules: Module[] = [ + { id: '176', text: 'training', count: 0 }, + { id: '173', text: 'training', count: 0 }, + { id: '172', text: 'training', count: 0 }, + ]; + + const nbCheckedElements: number = component.countCheckFiltersOnModules(checkedModules, 2); + expect(nbCheckedElements).toEqual(1); + }); + it('should return 0 of checked elements in an array', () => { + const checkedModules: Module[] = [ + { id: '176', text: 'training', count: 0 }, + { id: '173', text: 'training', count: 0 }, + { id: '172', text: 'training', count: 0 }, + ]; + + const nbCheckedElements: number = component.countCheckFiltersOnModules(checkedModules, 3); + expect(nbCheckedElements).toEqual(0); + }); + + // fetchResults function + it('should update number of checked elements in current filter', () => { + const checkedModules: Module[] = [ + { id: '176', text: 'training', count: 0 }, + { id: '173', text: 'accompaniment', count: 0 }, + { id: '172', text: 'accompaniment', count: 0 }, + { id: '180', text: 'moreFilters', count: 0 }, + { id: '130', text: 'moreFilters', count: 0 }, + { id: '219', text: 'moreFilters', count: 0 }, + ]; + component.modalTypeOpened = TypeModal.training; + component.numberAccompanimentChecked = 2; + component.numberMoreFiltersChecked = 3; + component.fetchResults(checkedModules); + expect(component.numberTrainingChecked).toEqual(1); + }); + // openModal function + it('should open modal', () => { + component.openModal(TypeModal.training); + expect(component.modalTypeOpened).toEqual(TypeModal.training); + }); + // closeModal function + it('should close modal', () => { + component.modalTypeOpened = TypeModal.training; + component.closeModal(); + expect(component.modalTypeOpened).toBeUndefined(); + }); + // externalCheckboxCheck function + it('should add numericPass filter to array of current filters and increment by one number of moreFilters element', () => { + const evt = { target: { checked: true, value: 'Pass numérique' } }; + const categ = 'Labels et qualifications'; + component.externalCheckboxCheck(evt, categ); + const expectArray: Module[] = [new Module(evt.target.value, categ)]; + expect(component.checkedModulesFilter).toEqual(expectArray); + expect(component.numberMoreFiltersChecked).toEqual(1); + }); + it('should remove conseillerNumFranceServices filter to array of current filters and increment by one number of moreFilters element', () => { + const evt = { target: { checked: false, value: 'Conseiller numérique' } }; + const categ = 'Labels et qualifications'; + const checkedModules: Module[] = [{ id: evt.target.value, text: categ, count: 0 }]; + component.checkedModulesFilter = checkedModules; + component.externalCheckboxCheck(evt, categ); + expect(component.checkedModulesFilter.length).toEqual(0); + expect(component.numberMoreFiltersChecked).toEqual(0); + }); + it('should add conseillerNumFranceServices filter to array of current filters and increment by one number of moreFilters element', () => { + const evt = { target: { checked: true, value: 'Conseiller numérique' } }; + const categ = 'Labels et qualifications'; + component.externalCheckboxCheck(evt, categ); + const expectArray: Module[] = [new Module(evt.target.value, categ)]; + expect(component.checkedModulesFilter).toEqual(expectArray); + expect(component.numberMoreFiltersChecked).toEqual(1); + }); + it('should remove numericPass filter to array of current filters and increment by one number of moreFilters element', () => { + const evt = { target: { checked: false, value: 'Pass numérique' } }; + const categ = 'Labels et qualifications'; + const checkedModules: Module[] = [{ id: evt.target.value, text: categ, count: 0 }]; + component.checkedModulesFilter = checkedModules; + component.externalCheckboxCheck(evt, categ); + expect(component.checkedModulesFilter.length).toEqual(0); + expect(component.numberMoreFiltersChecked).toEqual(0); + }); + // learInput function + it('should reset form', () => { + component.searchForm.setValue({ searchTerm: 'someSearchTerm' }); + component.clearInput(); + expect(component.searchForm.get('searchTerm').value).toBeNull(); + }); + // locateMe function + it('should update form with the correct address ', () => { + let fakeGeo: GeoJson = new GeoJson({ properties: { name: 'Rue du lac' } }); + + spyOn(navigator.geolocation, 'getCurrentPosition').and.callFake(function () { + var position = { coords: { latitude: 45.7585243, longitude: 4.85442 } }; + arguments[0](position); + }); + spyOn(geoService, 'getAddressByCoord').and.callFake(function () { + return of(fakeGeo); + }); + component.locateMe(); + expect(navigator.geolocation.getCurrentPosition).toHaveBeenCalled(); + expect(geoService.getAddressByCoord).toHaveBeenCalled(); + expect(component.searchForm.get('searchTerm').value).toBe('Rue du lac'); + }); +}); diff --git a/src/app/structure-list/components/structure-list-search/structure-list-search.component.ts b/src/app/structure-list/components/structure-list-search/structure-list-search.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..fafe341d5c9efaf8495ef5e2b8f8f1b47dcf81a1 --- /dev/null +++ b/src/app/structure-list/components/structure-list-search/structure-list-search.component.ts @@ -0,0 +1,249 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ButtonType } from '../../../shared/components/button/buttonType.enum'; +import { TypeModal } from '../../enum/typeModal.enum'; +import { Category } from '../../models/category.model'; +import { Filter } from '../../models/filter.model'; +import { Module } from '../../models/module.model'; +import { SearchService } from '../../services/search.service'; + +@Component({ + selector: 'app-structure-list-search', + templateUrl: './structure-list-search.component.html', + styleUrls: ['./structure-list-search.component.scss'], +}) +export class StructureListSearchComponent implements OnInit { + @Output() searchEvent = new EventEmitter(); + public locate = false; + // Show/hide form createStructure + public addStructureFormModal = false; + public buttonTypeEnum = ButtonType; + + // Form search input + public searchForm: FormGroup; + public modalTypeOpened: TypeModal; + // Checkbox variable + public checkedModulesFilter: Module[]; + + public numberTrainingChecked = 0; + public numberAccompanimentChecked = 0; + public numberPublicChecked = 0; + public numberEquipmentChecked = 0; + public numberMoreFiltersChecked = 0; + + // Modal categories + public categoriesTraining: Category[] = []; + public categoriesAccompaniment: Category[] = []; + public categoriesPublic: Category[] = []; + public categoriesEquipment: Category[] = []; + public categoriesMoreFilters: Category[] = []; + + public queryString: string; + // Modal confirmation variable + public isConfirmationModalOpen = false; + public confirmationModalContent = + 'Afin d’ajouter votre structure,vous allez être redirigé vers le formulaire Grand Lyon à remplir.'; + + constructor( + public searchService: SearchService, + private fb: FormBuilder, + private activatedRoute: ActivatedRoute, + private route: ActivatedRoute, + private router: Router + ) { + this.searchForm = this.fb.group({ + searchTerm: this.activatedRoute.snapshot.queryParamMap.get('search') + ? this.activatedRoute.snapshot.queryParamMap.get('search') + : '', + }); + } + ngOnInit(): void { + // Will store the different categories + this.getData(); + this.queryString = this.activatedRoute.snapshot.queryParamMap.get('search'); + this.checkedModulesFilter = new Array(); + if (this.queryString) { + const filters: Filter[] = []; + filters.push(new Filter('query', this.queryString)); + this.searchEvent.emit(filters); + } + } + + public convertModulesTofilters(modules: Module[], term?: string): Filter[] { + const filters: Filter[] = []; + if (term) { + filters.push(new Filter('query', term)); + } + // Add checked box filter + modules.forEach((cm) => { + filters.push(new Filter(cm.text, cm.id, cm.displayText)); + }); + return filters; + } + + // Accessor to template angular. + public get TypeModal(): typeof TypeModal { + return TypeModal; + } + + // Clear input search + public clearInput(): void { + this.searchForm.reset(); + this.applyFilter(null); + } + + // Sends an array containing all filters + public applyFilter(term: string): void { + // Add search input filter + if (term) { + this.router.navigate(['/acteurs'], { + relativeTo: this.route, + queryParams: { + search: term, + }, + queryParamsHandling: 'merge', + }); + } else if (!term) { + this.router.navigate(['/acteurs'], { + relativeTo: this.route, + }); + } + const filters = this.convertModulesTofilters(this.checkedModulesFilter, term); + // Send filters + this.searchEvent.emit(filters); + } + + public fetchResults(checkedModules: Module[]): void { + const inputTerm = this.searchForm.get('searchTerm').value; + // Check if some modules is checked in filters + if (this.checkedModulesFilter !== checkedModules) { + this.countCheckFiltersOnModules(checkedModules); + } + // Store checked modules + this.checkedModulesFilter = checkedModules; + // Close modal after receive filters from her. + this.closeModal(); + this.applyFilter(inputTerm); + } + + // Check if some modules is checked on filter and store number of modules checked + public countCheckFiltersOnModules(checkedModules: Module[]): void { + this.numberAccompanimentChecked = checkedModules.filter( + (module) => module.text === 'proceduresAccompaniment' + ).length; + this.numberTrainingChecked = checkedModules.filter( + (module) => + module.text === 'baseSkills' || + module.text === 'socialAndProfessional' || + module.text === 'parentingHelp' || + module.text === 'accessRight' || + module.text === 'digitalCultureSecurity' + ).length; + this.numberPublicChecked = checkedModules.filter((module) => module.text === 'publicsAccompaniment').length; + this.numberEquipmentChecked = checkedModules.filter((module) => module.text === 'equipmentsAndServices').length; + this.numberMoreFiltersChecked = checkedModules.filter( + (module) => module.text === 'labelsQualifications' || module.text === 'accessModality' + ).length; + } + + public getModalCategory(): Category[] { + switch (this.modalTypeOpened) { + case TypeModal.accompaniment: + return this.categoriesAccompaniment; + case TypeModal.training: + return this.categoriesTraining; + + case TypeModal.public: + return this.categoriesPublic; + + case TypeModal.equipments: + return this.categoriesEquipment; + + case TypeModal.moreFilters: + return this.categoriesMoreFilters; + default: + throw new Error('Modal type not handle'); + } + } + + // Open the modal and display the list according to the right filter button + public openModal(modalType: TypeModal): void { + // if modal already opened, reset type + if (this.modalTypeOpened === modalType) { + this.closeModal(); + } else if (this.modalTypeOpened !== modalType) { + this.modalTypeOpened = modalType; + } + } + + public closeModal(): void { + this.modalTypeOpened = undefined; + } + + // Management of the checkbox event (Check / Uncheck) + public externalCheckboxCheck(event, categ, displayName): void { + const checkValue: string = event.target.value; + const inputTerm = this.searchForm.get('searchTerm').value; + if (event.target.checked) { + this.checkedModulesFilter.push(new Module(checkValue, categ, displayName)); + this.numberMoreFiltersChecked++; + } else { + // Check if the module is present in the list and remove it + const index = this.checkedModulesFilter.findIndex((m: Module) => m.id === checkValue && m.text === categ); + if (index > -1) { + this.checkedModulesFilter.splice(index, 1); + this.countCheckFiltersOnModules(this.checkedModulesFilter); + } + } + this.applyFilter(inputTerm); + } + + // Get the categories for each modal type + private getData(): void { + this.searchService.getCategoriesAccompaniment().subscribe((res) => { + const categories: Category[] = res; + categories.forEach((category) => { + this.categoriesAccompaniment.push(category); + }); + }); + this.searchService.getCategoriesTraining().subscribe((res) => { + const categories: Category[] = res; + categories.forEach((category) => { + this.categoriesTraining.push(category); + }); + }); + this.searchService.getCategoriesOthers().subscribe((res) => { + const categories: Category[] = res; + categories.forEach((category) => { + if (category.id === 'publicsAccompaniment') { + this.categoriesPublic.push(category); + } else if (category.id === 'equipmentsAndServices') { + this.categoriesEquipment.push(category); + } else if (category.id === 'labelsQualifications' || category.id === 'accessModality') { + this.categoriesMoreFilters.push(category); + } + }); + }); + } + + public resetFilters(): void { + this.checkedModulesFilter = []; + this.numberTrainingChecked = 0; + this.numberAccompanimentChecked = 0; + this.numberPublicChecked = 0; + this.numberEquipmentChecked = 0; + this.numberMoreFiltersChecked = 0; + const inputTerm = this.searchForm.get('searchTerm').value; + const filters = this.convertModulesTofilters(this.checkedModulesFilter, inputTerm); + this.searchEvent.emit(filters); + } + public removeFilter(module: Module): void { + const index = this.checkedModulesFilter.findIndex((m: Module) => m.id === module.id); + this.checkedModulesFilter.splice(index, 1); + const inputTerm = this.searchForm.get('searchTerm').value; + const filters = this.convertModulesTofilters(this.checkedModulesFilter, inputTerm); + this.countCheckFiltersOnModules(this.checkedModulesFilter); + this.searchEvent.emit(filters); + } +} diff --git a/src/app/config/map/access-modality.enum.ts b/src/app/structure-list/enum/access-modality.enum.ts similarity index 95% rename from src/app/config/map/access-modality.enum.ts rename to src/app/structure-list/enum/access-modality.enum.ts index a0a9f8db5bd6065decdaf5665d87beef4207d246..81c7f7edd2811afdaa458731d89af3532bf1d7e2 100644 --- a/src/app/config/map/access-modality.enum.ts +++ b/src/app/structure-list/enum/access-modality.enum.ts @@ -1,6 +1,6 @@ -export enum AccessModality { - free = 'accesLibre', - numeric = 'telephoneVisio', - meetingOnly = 'uniquementSurRdv', - meeting = 'surRdv', -} +export enum AccessModality { + free = 'accesLibre', + numeric = 'telephoneVisio', + meetingOnly = 'uniquementSurRdv', + meeting = 'surRdv', +} diff --git a/src/app/structure-list/enum/equipment.enum.ts b/src/app/structure-list/enum/equipment.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..880b4a16d47af4ba2eb3245b417ec1075f4432c1 --- /dev/null +++ b/src/app/structure-list/enum/equipment.enum.ts @@ -0,0 +1,8 @@ +export enum Equipment { + wifi = 'wifiEnAccesLibre', + bornes = 'bornesNumeriques', + printer = 'imprimantes', + tablet = 'tablettes', + computer = 'ordinateurs', + scanner = 'scanners', +} diff --git a/src/app/structure-list/enum/public.enum.ts b/src/app/structure-list/enum/public.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..905f79748193cb9859118321b0bfd92efce23f37 --- /dev/null +++ b/src/app/structure-list/enum/public.enum.ts @@ -0,0 +1,8 @@ +export enum PublicCategorie { + under16Years = 'moinsDe16Ans', + young = 'jeunes1625Ans', + adult = 'adultes', + elderly = 'seniorsPlusDe65Ans', + all = 'toutPublic', + women = 'uniquementFemmes', +} diff --git a/src/app/structure-list/enum/typeModal.enum.ts b/src/app/structure-list/enum/typeModal.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..232ac284462a50e8d6437494984f0e6316ca705e --- /dev/null +++ b/src/app/structure-list/enum/typeModal.enum.ts @@ -0,0 +1,7 @@ +export enum TypeModal { + accompaniment = 1, + training, + public, + equipments, + moreFilters, +} diff --git a/src/app/structure-list/enum/weekday.enum.ts b/src/app/structure-list/enum/weekday.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4889072c313a09838c9411201f1554cf7a3e931 --- /dev/null +++ b/src/app/structure-list/enum/weekday.enum.ts @@ -0,0 +1,9 @@ +export enum Weekday { + monday = 1, + tuesday, + wednesday, + thursday, + friday, + saturday, + sunday, +} diff --git a/src/app/structure-list/models/category.model.ts b/src/app/structure-list/models/category.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..d269f41b886725b17861e30c8bddf9ae85bd9074 --- /dev/null +++ b/src/app/structure-list/models/category.model.ts @@ -0,0 +1,39 @@ +import { Module } from './module.model'; + +export class Category { + name: string; + surname: string; + id: string; + modules: Module[]; + + constructor(obj?: any) { + Object.assign(this, obj, { + modules: + obj && obj.modules + ? obj.modules.map( + (module) => new Module(module.display_id ? module.display_id : module.id, module.text, module.text) + ) + : null, + }); + } + + public isBaseSkills(): boolean { + return this.id === 'baseSkills'; + } + + public isRigthtsAccess(): boolean { + return this.id === 'accessRight'; + } + + public isParentingHelp(): boolean { + return this.id === 'parentingHelp'; + } + + public isDigitalCultureSecurity(): boolean { + return this.id === 'digitalCultureSecurity'; + } + + public isSocialAndProfessional(): boolean { + return this.id === 'socialAndProfessional'; + } +} diff --git a/src/app/structure-list/models/filter.model.ts b/src/app/structure-list/models/filter.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..25d7ff8253cb742637510467035c853e8c7a77f0 --- /dev/null +++ b/src/app/structure-list/models/filter.model.ts @@ -0,0 +1,13 @@ +export class Filter { + name: string; + value: string; + text?: string; + checked: boolean; + + constructor(name: string, value: any, text?: string) { + this.name = name; + this.value = value.toString(); + this.text = text; + this.checked = true; + } +} diff --git a/src/app/structure-list/models/module.model.ts b/src/app/structure-list/models/module.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..be98b490fa32851530ecbe8720ab086dad3d040c --- /dev/null +++ b/src/app/structure-list/models/module.model.ts @@ -0,0 +1,12 @@ +export class Module { + id: string; + text: string; + count: number; + displayText?: string; + + constructor(id: string, text: string, displayText?: string) { + this.id = id; + this.text = text; + this.displayText = displayText; + } +} diff --git a/src/app/structure-list/services/search.service.ts b/src/app/structure-list/services/search.service.ts index c96e86387858b452f702394db3b24783eef04924..edd0de785b39c8c3646097353949205cb54b82a6 100644 --- a/src/app/structure-list/services/search.service.ts +++ b/src/app/structure-list/services/search.service.ts @@ -1,8 +1,9 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Category, Module } from '@gouvfr-anct/mediation-numerique'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { Category } from '../models/category.model'; +import { Module } from '../models/module.model'; @Injectable({ providedIn: 'root', diff --git a/src/app/structure-list/structure-list.component.html b/src/app/structure-list/structure-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..68eda11340e9105ce29b7ea00c10858c0b54e24e --- /dev/null +++ b/src/app/structure-list/structure-list.component.html @@ -0,0 +1,27 @@ +<div class="structureList-container"> + <div class="structureListHeader hide-on-print"> + <div class="nbStructuresLabel" [ngPlural]="structureList.length"> + <ng-template ngPluralCase="0">0 structure</ng-template> + <ng-template ngPluralCase="1">1 structure</ng-template> + <ng-template ngPluralCase="other">{{ structureList.length }} structures</ng-template> + </div> + <app-button + tabindex="0" + (action)="addStructure()" + [text]="'Ajouter une structure'" + [style]="buttonTypeEnum.Secondary" + [extraClass]="'small-text'" + ></app-button> + </div> + + <div id="listCard" class="listCard" (mouseleave)="mouseLeave()"> + <app-card + *ngFor="let structure of structureList" + [structure]="structure" + (showDetails)="showDetails($event, filters)" + (hover)="handleCardHover($event)" + class="structure-card" + ></app-card> + <p *ngIf="structureList && structureList.length <= 0">Il n'y a aucune réponse correspondant à votre recherche</p> + </div> +</div> diff --git a/src/app/structure-list/structure-list.component.scss b/src/app/structure-list/structure-list.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..b8108121d4b2ce054cebaacb8247e14670da3e9a --- /dev/null +++ b/src/app/structure-list/structure-list.component.scss @@ -0,0 +1,40 @@ +@import '../../assets/scss/color'; +@import '../../assets/scss/icons'; +@import '../../assets/scss/typography'; +@import '../../assets/scss/buttons'; + +.structureList-container { + overflow-y: auto; + scrollbar-gutter: stable; +} + +.listCard > p { + margin-left: 1rem; +} + +.structureListHeader { + height: 50px; + display: flex; + flex-direction: row; + align-items: center; + margin: 8px 16px; + .nbStructuresLabel { + @include lato-regular-14; + color: $grey-3; + flex: 1; + } +} + +::ng-deep .structure-card:last-child .structure { + border-bottom: unset !important; +} + +@media print { + .listCard { + display: none; + } + + .hide-on-print { + display: none !important; + } +} diff --git a/src/app/structure-list/structure-list.component.spec.ts b/src/app/structure-list/structure-list.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..77af804ccc53702dee9100089cf6bed6fe7f6d19 --- /dev/null +++ b/src/app/structure-list/structure-list.component.spec.ts @@ -0,0 +1,196 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { OpeningDay } from '../models/openingDay.model'; +import { Structure } from '../models/structure.model'; +import { StructureListComponent } from './structure-list.component'; + +describe('StructureListComponent', () => { + let component: StructureListComponent; + let fixture: ComponentFixture<StructureListComponent>; + let structure: Structure; + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [StructureListComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(StructureListComponent); + component = fixture.debugElement.componentInstance; + structure = new Structure({ + id: 1, + numero: '26-63', + updatedAt: '2020-10-08T15:17:00.000Z', + nomDeLusager: 'Erwan Le luron', + structureRepresentation: 'Un établissement principal (siège social)', + structureName: 'Régie de Quartier Armstrong', + structureType: 'Tiers-lieu & coworking, FabLab', + description: "Association loi 1901 dont l'objet est l'insertion par l'économie social et solidaire", + n: 2, + adressWay: 21356, + contactPhone: '04 72 21 03 07', + contactMail: 'sguillet@rqa.fr', + website: '', + facebook: '', + twitter: '@rqainfo69', + instagram: '', + gender: 'Madame', + contactName: 'GUILLET', + contactSurname: 'Séverine', + fonction: 'Autres', + pmrAccess: '', + choixMultiples: 'Tout public', + exceptionalClosures: '', + proceduresAccompaniment: 'Accompagnant CAF', + autresAccompagnements: '', + baseSkills: 260, + accessRight: 176, + socialAndProfessional: 254, + parentingHelp: '', + digitalCultureSecurity: 264, + wifiEnAccesLibre: 'True', + nbComputers: '', + nombre: '', + tablettes: '', + bornesNumeriques: '', + imprimantes: '', + autresEspacesProposesParLaStructure: 'Espace libre service', + appartenezVousAUnReseauDeMediation: '', + precisezLequel: '', + idDeLitemStructureDansDirectus: 123, + statutDeLitemStructureDansDirectus: '', + idDeLitemOffreDansDirectus: '', + statut: 'Erreur lors du versement des données offre', + hours: { + monday: { + open: true, + time: [ + { + opening: 1330, + closing: 1630, + }, + { + opening: null, + closing: null, + }, + ], + }, + tuesday: { + open: true, + time: [ + { + opening: 830, + closing: 1130, + }, + { + opening: 1330, + closing: 1630, + }, + ], + }, + wednesday: { + open: true, + time: [ + { + opening: 1330, + closing: 1630, + }, + { + opening: null, + closing: null, + }, + ], + }, + thursday: { + open: true, + time: [ + { + opening: 830, + closing: 1130, + }, + { + opening: 1330, + closing: 1630, + }, + ], + }, + friday: { + open: true, + time: [ + { + opening: 830, + closing: 1130, + }, + { + opening: 1330, + closing: 1530, + }, + ], + }, + saturday: { + open: false, + time: [ + { + opening: null, + closing: null, + }, + { + opening: null, + closing: null, + }, + ], + }, + sunday: { + open: false, + time: [ + { + opening: null, + closing: null, + }, + { + opening: null, + closing: null, + }, + ], + }, + }, + openedOn: new OpeningDay('monday', null), + }); + const structureList = new Array<Structure>(structure); + structureList.length = 4; + component.structureList = structureList; + fixture.detectChanges(); // calls NgOnit + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit id structure and update variables to open details', () => { + spyOn(component.selectedMarkerId, 'emit'); + component.showDetails(structure); + expect(component.selectedMarkerId.emit).toHaveBeenCalled(); + expect(component.selectedMarkerId.emit).toHaveBeenCalledWith(structure._id); + expect(component.structure).toBe(structure); + }); + + it('should emit id structure and update variables to close details', () => { + spyOn(component.selectedMarkerId, 'emit'); + component.closeDetails(); + expect(component.selectedMarkerId.emit).toHaveBeenCalled(); + expect(component.selectedMarkerId.emit).toHaveBeenCalledWith(); + }); + + it('should emit id structure to display map marker', () => { + spyOn(component.displayMapMarkerId, 'emit'); + component.handleCardHover(structure); + expect(component.displayMapMarkerId.emit).toHaveBeenCalled(); + expect(component.displayMapMarkerId.emit).toHaveBeenCalledWith([structure._id]); + }); + + it('should emit undefined id structure to remove map marker', () => { + spyOn(component.displayMapMarkerId, 'emit'); + component.mouseLeave(); + expect(component.displayMapMarkerId.emit).toHaveBeenCalled(); + expect(component.displayMapMarkerId.emit).toHaveBeenCalledWith([undefined]); + }); +}); diff --git a/src/app/structure-list/structure-list.component.ts b/src/app/structure-list/structure-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..df44ef9e3d900f8e1451bfc45becc5a68e27b670 --- /dev/null +++ b/src/app/structure-list/structure-list.component.ts @@ -0,0 +1,83 @@ +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GeoJson } from '../map/models/geojson.model'; +import { Structure } from '../models/structure.model'; +import { AuthService } from '../services/auth.service'; +import { StructureService } from '../services/structure.service'; +import { ButtonType } from '../shared/components/button/buttonType.enum'; + +@Component({ + selector: 'app-structure-list', + templateUrl: './structure-list.component.html', + styleUrls: ['./structure-list.component.scss'], +}) +export class StructureListComponent implements OnChanges { + @Input() public structureList: Structure[]; + @Input() public location: GeoJson; + @Input() public selectedStructure: Structure = new Structure(); + @Output() public displayMapMarkerId: EventEmitter<string> = new EventEmitter<string>(); + @Output() public selectedMarkerId: EventEmitter<string> = new EventEmitter<string>(); + @Output() public updatedStructure: EventEmitter<Structure> = new EventEmitter<Structure>(); + + public buttonTypeEnum = ButtonType; + public structure: Structure; + + constructor( + private route: ActivatedRoute, + private router: Router, + private structureService: StructureService, + private authService: AuthService + ) { + this.route.queryParams.subscribe((queryParams) => { + if (queryParams.id) { + if (!this.structure) { + this.structureService.getStructure(queryParams.id).subscribe((s) => { + this.showDetails(new Structure(s)); + }); + } + } else { + this.closeDetails(); + } + }); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.selectedStructure && this.selectedStructure) { + this.showDetails(this.selectedStructure); + this.router.navigate([], { + relativeTo: this.route, + queryParams: { + id: this.selectedStructure._id, + }, + }); + } + if (changes.structureList) { + document.getElementById('listCard').scrollTo(0, 0); + } + } + + public addStructure(): void { + if (!this.authService.isLoggedIn()) { + this.router.navigateByUrl('/login'); + } else { + this.router.navigateByUrl('/form/structure'); + } + } + + public showDetails(event: Structure): void { + this.structure = event; + this.selectedMarkerId.emit(this.structure._id); + } + + public closeDetails(): void { + this.selectedMarkerId.emit(); + } + + public handleCardHover(structure: Structure): void { + this.displayMapMarkerId.emit(structure._id); + } + + public mouseLeave(): void { + this.displayMapMarkerId.emit(undefined); + } +} diff --git a/src/app/structure/components/structure-details-actions/structure-details-actions.component.html b/src/app/structure/components/structure-details-actions/structure-details-actions.component.html deleted file mode 100644 index ef0a447f0909c8667212c248c9a1282b5f5d6524..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-actions/structure-details-actions.component.html +++ /dev/null @@ -1,24 +0,0 @@ -<div - *ngIf="!profileService.isLinkedToStructure(structure._id)" - fxLayout="column" - fxLayoutAlign="center center" - class="clickableDiv" - role="button" - (click)="handleJoin()" - tabindex="0" -> - <app-svg-icon class="icon" [type]="'ico'" [icon]="'workhere'" [iconClass]="'icon-32'"></app-svg-icon> - <div class="iconTitle">Je travaille ici</div> -</div> -<div - *ngIf="profileService.isLinkedToStructure(structure._id) || profileService.isAdmin()" - fxLayout="column" - fxLayoutAlign="center center" - class="clickableDiv" - role="button" - (click)="handleModify()" - tabindex="0" -> - <app-svg-icon class="icon" [type]="'ico'" [icon]="'modifyStructure'" [iconClass]="'icon-32'"></app-svg-icon> - <div class="iconTitle">Modifier cette structure</div> -</div> diff --git a/src/app/structure/components/structure-details-actions/structure-details-actions.component.scss b/src/app/structure/components/structure-details-actions/structure-details-actions.component.scss deleted file mode 100644 index 687a84df76c8da089dbd0e8ec90581766c8f0cec..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-actions/structure-details-actions.component.scss +++ /dev/null @@ -1,39 +0,0 @@ -@import '../../../../assets/scss/typography'; -@import '../../../../assets/scss/breakpoint'; -.clickableDiv { - text-align: center; - height: 90px; - width: 115.2px; - display: flex; - flex-direction: column; - cursor: pointer; - .icon { - margin-top: 20px; - flex: 1; - display: flex; - justify-content: center; - align-items: center; - } - .iconTitle { - @include lato-regular-13; - height: 36px; - display: flex; - justify-content: center; - align-items: center; - } - &:hover { - text-decoration: underline; - } - @media #{$large-phone} { - width: 50%; - } -} - -.clickableDiv:first-child { - @media #{$tablet} { - margin-right: 16px; - } - @media #{$phone} { - margin-right: unset; - } -} diff --git a/src/app/structure/components/structure-details-actions/structure-details-actions.component.ts b/src/app/structure/components/structure-details-actions/structure-details-actions.component.ts deleted file mode 100644 index 82f62e56796fda948a2db30953de3c87b7c8911f..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-actions/structure-details-actions.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { Owner } from '../../../models/owner.model'; -import { User } from '../../../models/user.model'; -import { ProfileService } from '../../../profile/services/profile.service'; -import { AuthService } from '../../../services/auth.service'; -import { StructureService } from '../../../services/structure.service'; - -@Component({ - selector: 'app-structure-details-actions', - templateUrl: 'structure-details-actions.component.html', - styleUrls: ['./structure-details-actions.component.scss'], -}) -export class StructureDetailsActionsComponent implements OnInit { - @Input() public structure: Structure; - - @Output() public claimChange: EventEmitter<boolean> = new EventEmitter<boolean>(); - @Output() public deleteModal: EventEmitter<void> = new EventEmitter<void>(); - @Output() public pendingModal: EventEmitter<void> = new EventEmitter<void>(); - @Output() public claimModal: EventEmitter<void> = new EventEmitter<void>(); - @Output() public joinModal: EventEmitter<void> = new EventEmitter<void>(); - - public currentProfile: User = null; - public structureAdmins: Owner[] = []; - public joinModalOpenned = false; - public pendingModalOpenned = false; - public claimModalOpenned = false; - - public constructor( - public readonly profileService: ProfileService, - private structureService: StructureService, - private router: Router, - private authService: AuthService - ) {} - - public async ngOnInit(): Promise<void> { - if (this.userIsLoggedIn()) { - this.currentProfile = await this.profileService.getProfile(); - - if (this.profileService.isAdmin()) { - this.structureService.getStructureWithOwners(this.structure._id, this.currentProfile).subscribe((res) => { - this.structureAdmins = res.owners; - }); - } - } - this.claimChange.emit(await this.structureService.isClaimed(this.structure._id, this.currentProfile).toPromise()); - } - - public userIsLoggedIn(): boolean { - return this.authService.isLoggedIn(); - } - - public togglePendingModal(): void { - // this.pendingModalOpenned = !this.pendingModalOpenned; - this.pendingModal.emit(); - } - - public toggleJoinModal(): void { - // this.joinModalOpenned = !this.joinModalOpenned; - this.joinModal.emit(); - } - - public toggleClaimModal(): void { - // this.claimModalOpenned = !this.claimModalOpenned; - this.claimModal.emit(); - } - - public handleJoin(): void { - if (this.userIsLoggedIn()) { - if (this.structure.isClaimed) { - if (this.profileService.isPendingLinkedToStructure(this.structure._id)) { - this.togglePendingModal(); - } else { - this.toggleJoinModal(); - } - } else { - this.toggleClaimModal(); - } - } else { - this.router.navigate(['login'], { queryParams: { returnUrl: `acteurs?id=${this.structure._id}` } }); - } - } - - public handleModify(): void { - this.router.navigateByUrl(`/profile/edit-structure/${this.structure._id}`); - } -} diff --git a/src/app/structure/components/structure-details-modals/structure-details-modals.component.html b/src/app/structure/components/structure-details-modals/structure-details-modals.component.html deleted file mode 100644 index c59aea08fdeda9805263f0efdd2a7ddc079accc1..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-modals/structure-details-modals.component.html +++ /dev/null @@ -1,45 +0,0 @@ -<app-modal-confirmation - [openned]="deleteModalOpenned" - [content]="'Voulez-vous vraiment supprimer cette structure ?'" - (closed)="deleteStructure($event)" -></app-modal-confirmation> - -<app-modal-confirmation - [openned]="claimModalOpenned" - [content]=" - 'Voulez-vous vraiment revendiquer cette structure ? Une demande sera envoyée à l\'administrateur pour validation' - " - (closed)="claimStructure($event)" -></app-modal-confirmation> - -<app-join-modal-confirmation - [openned]="claimModalOpenned" - [title]="'Travaillez-vous ici ?'" - [primaryContent]=" - 'Un message sera envoyé aux administrateurs Rés\'IN pour valider l\'affectation de votre compte à la structure' - " - [secondaryContent]="structure.structureName" - [customConfirmationText]="'Rejoindre la structure'" - (closed)="claimStructure($event)" -></app-join-modal-confirmation> - -<app-join-modal-confirmation - [openned]="joinModalOpenned" - [title]="'Travaillez-vous ici ?'" - [primaryContent]="'Un message sera envoyé à un administrateur de la structure'" - [secondaryContent]="structure.structureName" - [customConfirmationText]="'Rejoindre la structure'" - (closed)="joinStructure($event)" -></app-join-modal-confirmation> - -<app-join-modal-confirmation - [openned]="pendingModalOpenned" - [title]="'Travaillez-vous ici ?'" - [primaryContent]=" - 'Un message a déjà été envoyé aux administrateurs Rés\'IN pour validation, vous recevrez un email quand votre compte sera rattaché à la structure' - " - [secondaryContent]="structure.structureName" - [customConfirmationText]="'OK'" - [displayCancelButton]="false" - (closed)="togglePendingModal()" -></app-join-modal-confirmation> diff --git a/src/app/structure/components/structure-details-modals/structure-details-modals.component.ts b/src/app/structure/components/structure-details-modals/structure-details-modals.component.ts deleted file mode 100644 index f504eb5aab655eb1f56bf0c9989d950578ecddcb..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-modals/structure-details-modals.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { User } from '../../../models/user.model'; -import { ProfileService } from '../../../profile/services/profile.service'; -import { AuthService } from '../../../services/auth.service'; -import { StructureService } from '../../../services/structure.service'; - -@Component({ - selector: 'app-structure-details-modals', - templateUrl: './structure-details-modals.component.html', -}) -export class StructureDetailsModalsComponent { - @Input() public structure: Structure; - @Output() public claimChange: EventEmitter<boolean> = new EventEmitter<boolean>(); - - public deleteModalOpenned = false; - public claimModalOpenned = false; - public joinModalOpenned = false; - public pendingModalOpenned = false; - - public constructor( - private profileService: ProfileService, - private authService: AuthService, - private route: ActivatedRoute, - private router: Router, - private structureService: StructureService - ) {} - - public toggleClaimModal(): void { - this.claimModalOpenned = !this.claimModalOpenned; - } - - public toggleDeleteModal(): void { - this.deleteModalOpenned = !this.deleteModalOpenned; - } - - public toggleJoinModal(): void { - this.joinModalOpenned = !this.joinModalOpenned; - } - - public togglePendingModal(): void { - this.pendingModalOpenned = !this.pendingModalOpenned; - } - - private reload(): void { - this.router.routeReuseStrategy.shouldReuseRoute = () => false; - this.router.onSameUrlNavigation = 'reload'; - this.router.navigate(['./'], { relativeTo: this.route }); - } - - public claimStructure(shouldClaim: boolean): void { - this.toggleClaimModal(); - if (shouldClaim) { - this.structureService - .claimStructureWithAccount(this.structure._id, this.authService.userValue.username) - .subscribe(); - this.router.navigate(['join', this.structure._id], { state: { isClaimed: this.structure.isClaimed } }); - } - } - - public deleteStructure(shouldDelete: boolean): void { - this.toggleDeleteModal(); - if (shouldDelete) { - this.structureService.delete(this.structure._id).subscribe((_res) => { - this.reload(); - }); - } - } - - public joinStructure(shouldClaim: boolean): void { - this.toggleJoinModal(); - if (shouldClaim) { - this.structureService.joinStructure(this.structure._id, this.authService.userValue.username).subscribe((_res) => { - this.profileService.getProfile().then((_user: User) => { - this.claimChange.emit(true); - }); - }); - } - } -} diff --git a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.html b/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.html deleted file mode 100644 index 642b684ff581a1e48fa74dd7ae2407a235a10f97..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.html +++ /dev/null @@ -1,57 +0,0 @@ -<app-structure-details class="structureList-details" [structure]="structure" *ngIf="structure"> - <app-structure-details-modals - #modals - [structure]="structure" - (claimChange)="updateClaim(structure._id, $event)" - slot="structure-details-modals" - ></app-structure-details-modals> - <app-tcl-access [structure]="structure" slot="structure-details-access"></app-tcl-access> - <app-structure-details-actions - [structure]="structure" - (deleteModal)="modals.toggleDeleteModal()" - (claimModal)="modals.toggleClaimModal()" - (joinModal)="modals.toggleJoinModal()" - (pendingModal)="modals.togglePendingModal()" - (claimChange)="updateClaim(structure._id, $event)" - class="structure-details-actions" - slot="structure-details-actions" - ></app-structure-details-actions> - <!-- Admin display --> - <div *ngIf="profileService.isAdmin()" slot="structure-admin-actions" class="structure-details-block hide-on-print"> - Administrateur(s) de cette structure: - <div *ngIf="structureAdmins.length === 0">Aucun administrateur</div> - <div *ngIf="structureAdmins.length > 0"> - <div *ngFor="let structureAdmin of structureAdmins"> - {{ structureAdmin.email }} - </div> - </div> - <a (click)="modals.toggleDeleteModal()" class="primary" tabindex="0">Supprimer cette structure</a> - </div> - <!-- Members --> - <div - *ngIf="userIsLoggedIn() && structureAdmins.length" - fxLayout="column" - class="structure-details-block" - fxLayoutAlign="baseline baseline" - fxLayoutGap="8px" - slot="structure-members" - > - <h2>Membres</h2> - <div fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="baseline baseline"> - <div *ngFor="let member of structureAdmins" class="member-card"> - <a [routerLink]="'/profile/' + member._id"> - <app-svg-icon - class="avatar" - [type]="'avatar'" - [icon]="'defaultAvatar'" - [iconClass]="'icon-40'" - ></app-svg-icon> - <div class="info-member"> - <p class="member">{{ member.name | uppercase }} {{ member.surname | titlecase }}</p> - <p class="job" *ngIf="displayJobEmployer(member)">{{ displayJobEmployer(member) }}</p> - </div> - </a> - </div> - </div> - </div> -</app-structure-details> diff --git a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.scss b/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.scss deleted file mode 100644 index 8dee4cd6877e3920aaa328ef043cbaa8bc3a39da..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.scss +++ /dev/null @@ -1,203 +0,0 @@ -@import '../../../../assets/scss/color'; -@import '../../../../assets/scss/typography'; -@import '../../../../assets/scss/breakpoint'; - -a { - padding: unset; - text-decoration: underline; - font-size: inherit; - font-weight: inherit; -} - -p:empty { - margin: 0; -} - -h1 { - @include lato-bold-20; - color: $grey-1; -} -h2 { - @include lato-bold-14; - color: $grey-3; - text-transform: uppercase; - margin-top: 0; - margin-bottom: 12px; -} -h3 { - @include lato-regular-16; - margin: 0 0 8px 0; -} - -.member-card { - display: flex; - justify-content: center; - align-items: center; - a { - text-decoration: none; - display: flex; - align-items: center; - } - a:hover { - text-decoration: underline; - } - .avatar { - background-color: $grey-8; - border-radius: 4px; - } - .info-member { - margin-left: 1rem; - p { - margin: 0; - } - .member { - @include lato-bold-14; - } - .job { - @include lato-regular-14; - } - } -} -.structure-details-actions { - flex-direction: row; - box-sizing: border-box; - display: flex; - place-content: stretch space-evenly; - align-items: stretch; -} - -.structure-details-block { - margin: 0 20px; - padding: 24px 0; - border-bottom: 2px solid $grey-8; - &.noSeparator { - border-bottom: none; - padding-bottom: 0px; - } - - .info-block > div { - margin-top: 4px; - &:first-of-type { - margin-top: 0px; - } - } - - .description { - white-space: pre-wrap; - margin-top: 8px; - } - - .info { - color: $red-1; - margin-top: 8px; - } - - .hours-services-block { - display: flex; - flex-direction: row; - & > div { - flex: 1; - } - @media #{$large-phone} { - flex-direction: column; - } - - .opening-hours { - margin-bottom: 8px; - .opening-hour { - margin-bottom: 8px; - .day { - min-width: 70px; - margin-top: 0; - margin-left: 0; - margin-bottom: 0; - @include lato-regular-14; - color: $grey-3; - text-transform: capitalize; - } - .daily-opening-time { - p { - margin: 0 0 4px 0; - } - } - } - } - } - - .services-block { - margin-bottom: 8px; - p { - display: list-item; - margin: 0 0 0 25px; - } - } - - .wrapper { - display: grid; - grid-template-columns: 1fr 1fr; - @media #{$large-phone} { - grid-template-columns: 1fr; - } - } - - .formationDetails { - width: 100%; - .collapse { - margin-bottom: 13px; - @media #{$small-phone} { - width: 95% !important; - } - &.notCollapsed { - border-bottom: 2px solid $grey-8; - .logo { - .hide { - display: none; - } - .show { - display: block; - } - } - } - .titleCollapse { - width: 100%; - @include lato-regular-16; - } - .collapseHeader { - cursor: pointer; - } - .logo { - height: 24px; - width: 24px; - svg { - width: 100%; - height: 100%; - fill: $grey-1; - } - } - .logo, - .titleCollapse { - .hide { - display: block; - } - .show { - display: none; - } - } - .detailsContainer { - margin: 8px 0px; - padding: 8px 0; - background-color: $grey-8; - overflow: hidden; - } - .details { - padding: 8px 16px; - } - } - } - - .updated { - @include lato-regular-14; - color: $grey-3; - font-style: italic; - } -} diff --git a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.spec.ts b/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.spec.ts deleted file mode 100644 index ad781c9bb655465dcdf1cf3580dd284727bb236c..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { StructureDetailsWrapperComponent } from './structure-details-wrapper.component'; - -describe('StructureDetailsWrapperComponent', () => { - let component: StructureDetailsWrapperComponent; - let fixture: ComponentFixture<StructureDetailsWrapperComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ StructureDetailsWrapperComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(StructureDetailsWrapperComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.ts b/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.ts deleted file mode 100644 index a5537dadbce59e13c93e6dcb82a12a4469c169a4..0000000000000000000000000000000000000000 --- a/src/app/structure/components/structure-details-wrapper/structure-details-wrapper.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { BehaviorSubject } from 'rxjs'; -import { ParametersService } from '../../../admin/services/parameters.service'; -import { Owner } from '../../../models/owner.model'; -import { User } from '../../../models/user.model'; -import { ProfileService } from '../../../profile/services/profile.service'; -import { AuthService } from '../../../services/auth.service'; -import { StructureService } from '../../../services/structure.service'; -import { Utils } from '../../../utils/utils'; - -type StructureClaim = { [structureId: string]: boolean }; - -@Component({ - selector: 'app-structure-details-wrapper', - templateUrl: './structure-details-wrapper.component.html', - styleUrls: ['./structure-details-wrapper.component.scss'], -}) -export class StructureDetailsWrapperComponent implements OnInit { - @Input() public structure: Structure; - public lockdownInfoDisplay = false; - private _structureClaimsState: StructureClaim = {}; - private _structureClaims: BehaviorSubject<StructureClaim> = new BehaviorSubject({}); - public structureAdmins: Owner[] = []; - constructor( - private route: ActivatedRoute, - private structureService: StructureService, - private parametersService: ParametersService, - private profileService: ProfileService, - private authService: AuthService - ) { - this.parametersService.getParameters().subscribe((params) => { - this.lockdownInfoDisplay = params.lockdownInfoDisplay; - }); - } - - ngOnInit(): void { - this.route.queryParams.subscribe((queryParams) => { - if (queryParams.id) { - this.structureService.getStructure(queryParams.id).subscribe((structure) => { - this.structure = new Structure(structure); - this.initData(); - }); - } - }); - } - - async initData(): Promise<void> { - let currentProfile = null; - if (this.userIsLoggedIn()) { - currentProfile = await this.profileService.getProfile(); - this.structureService.getStructureWithOwners(this.structure._id, currentProfile).subscribe((res) => { - this.structureAdmins = res.owners; - }); - } - this.structure.isClaimed = await this.structureService.isClaimed(this.structure._id, currentProfile).toPromise(); - } - - public updateClaim(structureId: string, isClaim: boolean): void { - this._structureClaimsState[structureId] = isClaim; - this._structureClaims.next(this._structureClaimsState); - } - - public userIsLoggedIn(): boolean { - return this.authService.isLoggedIn(); - } - - public displayJobEmployer(profile: User): string { - return new Utils().getJobEmployer(profile); - } -} diff --git a/src/app/structure/components/tcl-access/tcl-access.component.html b/src/app/structure/components/tcl-access/tcl-access.component.html deleted file mode 100644 index e533fc43867ba908df20ad491137116dfa79a117..0000000000000000000000000000000000000000 --- a/src/app/structure/components/tcl-access/tcl-access.component.html +++ /dev/null @@ -1,24 +0,0 @@ -<div - *ngIf="tclStopPoints.length" - fxLayout="column" - class="structure-details-block noSeparator" - fxLayoutAlign="baseline baseline" -> - <h2>Accès</h2> - <div fxLayout="column wrap" fxLayoutGap="24px"> - <div *ngFor="let tclStop of tclStopPoints | slice: 0:3"> - {{ tclStop.name }} - <div fxLayout="row wrap" fxLayoutGap="16px"> - <p *ngFor="let sub of tclStop.subLines"> - <app-svg-icon [type]="'tcl'" [icon]="sub" [iconClass]="'icon-75'"></app-svg-icon> - </p> - <p *ngFor="let tram of tclStop.tramLines"> - <app-svg-icon [type]="'tcl'" [icon]="tram" [iconClass]="'icon-75'"></app-svg-icon> - </p> - <p *ngFor="let bus of tclStop.busLines"> - <app-svg-icon [type]="'tcl'" [icon]="bus" [iconClass]="'icon-75'"></app-svg-icon> - </p> - </div> - </div> - </div> -</div> diff --git a/src/app/structure/components/tcl-access/tcl-access.component.ts b/src/app/structure/components/tcl-access/tcl-access.component.ts deleted file mode 100644 index d28757a771cc978ee1b5470c488a93643ba53621..0000000000000000000000000000000000000000 --- a/src/app/structure/components/tcl-access/tcl-access.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { TclService } from '../../../services/tcl.service'; - -export class TclStopPoint { - public name: string; - public tramLines: string[]; - public subLines: string[]; - public busLines: string[]; - - constructor(obj?: any) { - Object.assign(this, obj); - } -} - -@Component({ - selector: 'app-tcl-access', - templateUrl: 'tcl-access.component.html', - styleUrls: ['../structure-details-wrapper/structure-details-wrapper.component.scss'], -}) -export class TclAccessComponent implements OnInit { - @Input() public structure: Structure; - - public tclStopPoints: TclStopPoint[] = []; - - public constructor(private tclService: TclService) {} - - public async ngOnInit(): Promise<void> { - this.getTclStopPoints(); - } - - public getTclStopPoints(): void { - this.tclService.getTclStopPointBycoord(this.structure.getLon(), this.structure.getLat()).subscribe((res) => { - this.tclStopPoints = res; - }); - } -} diff --git a/src/app/structure/structure-exclude/structure-exclude.component.ts b/src/app/structure/structure-exclude/structure-exclude.component.ts index 6bd574af8b6655c7e52d6a49f5cb7930c0dff560..3bdcc5685c7dc102e17a7d03b4a1a5b669409f31 100644 --- a/src/app/structure/structure-exclude/structure-exclude.component.ts +++ b/src/app/structure/structure-exclude/structure-exclude.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Structure } from '@gouvfr-anct/mediation-numerique'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; -import { NotificationService } from '../../services/notification.service'; +import { ButtonType } from '../../shared/components/button/buttonType.enum'; +import { Router, ActivatedRoute } from '@angular/router'; import { StructureService } from '../../services/structure.service'; import { UserService } from '../../services/user.service'; +import { NotificationService } from '../../services/notification.service'; +import { Structure } from '../../models/structure.model'; @Component({ selector: 'app-structure-exclude', diff --git a/src/app/structure/structure-join/structure-join.component.ts b/src/app/structure/structure-join/structure-join.component.ts index ac6971da6e9fd2762c052a22a3044c09c0cc712b..19093c82abf070301de48268044d4d3d88c8fd7e 100644 --- a/src/app/structure/structure-join/structure-join.component.ts +++ b/src/app/structure/structure-join/structure-join.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { ButtonType } from '@gouvfr-anct/mediation-numerique/shared'; import { formType } from '../../form/form-view/formType.enum'; import { structureFormStep } from '../../form/form-view/structure-form/structureFormStep.enum'; import { RouterListenerService } from '../../services/routerListener.service'; +import { ButtonType } from '../../shared/components/button/buttonType.enum'; @Component({ selector: 'app-structure-join', templateUrl: './structure-join.component.html', diff --git a/src/app/utils/formUtils.ts b/src/app/utils/formUtils.ts index a32b44a60c4e8183ad51e1f6f5f5504e21d4f2f0..8eb7a98849e4669c86eb96f15af91b2e23b13707 100644 --- a/src/app/utils/formUtils.ts +++ b/src/app/utils/formUtils.ts @@ -1,6 +1,8 @@ import { UntypedFormArray, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; -import { Day, Structure, Time } from '@gouvfr-anct/mediation-numerique'; import { structureFormStep } from '../form/form-view/structure-form/structureFormStep.enum'; +import { Day } from '../models/day.model'; +import { Time } from '../models/time.model'; +import { Structure } from '../models/structure.model'; import { CustomRegExp } from './CustomRegExp'; export interface IStructureSummary { @@ -82,13 +84,19 @@ export class formUtils { street: new UntypedFormControl(structure.address.street, Validators.required), commune: new UntypedFormControl(structure.address.commune, Validators.required), }), - contactMail: new UntypedFormControl(structure.contactMail === 'unknown@unknown.com' ? null : structure.contactMail, [ - Validators.required, - Validators.pattern(CustomRegExp.EMAIL), - ]), + contactMail: new UntypedFormControl( + structure.contactMail === 'unknown@unknown.com' ? null : structure.contactMail, + [Validators.required, Validators.pattern(CustomRegExp.EMAIL)] + ), contactPhone: new UntypedFormControl(structure.contactPhone, [Validators.pattern(CustomRegExp.PHONE)]), - contactPersonFirstname: new UntypedFormControl(structure.contactPersonLastName, !isEditMode && Validators.required), - contactPersonLastname: new UntypedFormControl(structure.contactPersonLastName, !isEditMode && Validators.required), + contactPersonFirstname: new UntypedFormControl( + structure.contactPersonLastName, + !isEditMode && Validators.required + ), + contactPersonLastname: new UntypedFormControl( + structure.contactPersonLastName, + !isEditMode && Validators.required + ), contactPersonEmail: new UntypedFormControl( structure.contactPersonEmail, !isEditMode && [Validators.pattern(CustomRegExp.EMAIL), Validators.required] @@ -126,18 +134,24 @@ export class formUtils { Validators.max(1000), ] ), - nbPrinters: new UntypedFormControl(structure.equipmentsAndServices.includes('imprimantes') ? structure.nbPrinters : 0, [ - Validators.required, - Validators.pattern(CustomRegExp.NO_NEGATIVE_NUMBER), - Validators.min(0), - Validators.max(1000), - ]), - nbTablets: new UntypedFormControl(structure.equipmentsAndServices.includes('tablettes') ? structure.nbTablets : 0, [ - Validators.required, - Validators.pattern(CustomRegExp.NO_NEGATIVE_NUMBER), - Validators.min(0), - Validators.max(1000), - ]), + nbPrinters: new UntypedFormControl( + structure.equipmentsAndServices.includes('imprimantes') ? structure.nbPrinters : 0, + [ + Validators.required, + Validators.pattern(CustomRegExp.NO_NEGATIVE_NUMBER), + Validators.min(0), + Validators.max(1000), + ] + ), + nbTablets: new UntypedFormControl( + structure.equipmentsAndServices.includes('tablettes') ? structure.nbTablets : 0, + [ + Validators.required, + Validators.pattern(CustomRegExp.NO_NEGATIVE_NUMBER), + Validators.min(0), + Validators.max(1000), + ] + ), nbNumericTerminal: new UntypedFormControl( structure.equipmentsAndServices.includes('bornesNumeriques') ? structure.nbNumericTerminal : 0, [ @@ -147,12 +161,15 @@ export class formUtils { Validators.max(1000), ] ), - nbScanners: new UntypedFormControl(structure.equipmentsAndServices.includes('scanners') ? structure.nbScanners : 0, [ - Validators.required, - Validators.pattern(CustomRegExp.NO_NEGATIVE_NUMBER), - Validators.min(0), - Validators.max(1000), - ]), + nbScanners: new UntypedFormControl( + structure.equipmentsAndServices.includes('scanners') ? structure.nbScanners : 0, + [ + Validators.required, + Validators.pattern(CustomRegExp.NO_NEGATIVE_NUMBER), + Validators.min(0), + Validators.max(1000), + ] + ), freeWorkShop: new UntypedFormControl(structure.freeWorkShop, [Validators.required]), dataShareConsentDate: new UntypedFormControl(structure.dataShareConsentDate), personalOffers: new UntypedFormControl(structure.personalOffers), diff --git a/tsconfig.base.json b/tsconfig.base.json deleted file mode 100644 index 1fb0d63f775580e464fd08754fe4e36740493046..0000000000000000000000000000000000000000 --- a/tsconfig.base.json +++ /dev/null @@ -1,19 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "moduleResolution": "node", - "importHelpers": true, - "target": "es2015", - "module": "es2020", - "lib": ["es2018", "dom"], - "resolveJsonModule": true, - "esModuleInterop": true - } -} diff --git a/tsconfig.json b/tsconfig.json index b8e7b47302e7cec88f60945eb2a2ca6717e7d4de..9a937470c1a287467538d83f95928792f6ea9506 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,19 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "paths": { - "@gouvfr-anct/*": ["dist/@gouvfr-anct/*"] - } - } -} +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "module": "es2020", + "lib": ["es2018", "dom"], + "resolveJsonModule": true, + "esModuleInterop": true + } +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 28a3525e6b52ad2f3d74057e33030a4b2d19fbf9..1fde99220b26798b39cfc44013c710d00ef00853 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -1,10 +1,10 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": ["node", "jasmine", "jest"] - }, - "files": ["src/test.ts", "src/polyfills.ts"], - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] -} +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["node", "jasmine"] + }, + "files": ["src/test.ts", "src/polyfills.ts"], + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +}