diff --git a/.fleet/settings.json b/.fleet/settings.json new file mode 100644 index 0000000..23fd35f --- /dev/null +++ b/.fleet/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..32553af --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Gebruik een multi-architecture versie van Node +FROM node:22.0.0 as build + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +RUN npm install -g @angular/cli + +COPY . . + +RUN ng build --configuration=production + +# Gebruik een multi-architecture versie van Nginx +FROM nginx:latest + +# Kopieer de gecompileerde bestanden van Angular naar de Nginx-webserver +COPY --from=build app/dist/pay-point/browser /usr/share/nginx/html + +EXPOSE 80 diff --git a/angular.json b/angular.json index c80709f..8e42371 100644 --- a/angular.json +++ b/angular.json @@ -29,9 +29,21 @@ { "glob": "**/*", "input": "public" + }, + { + "glob": "**/*", + "input": "node_modules/@taiga-ui/icons/src", + "output": "assets/taiga-ui/icons" + }, + { + "glob": "**/*", + "input": "node_modules/@taiga-ui/icons/src", + "output": "assets/taiga-ui/icons" } ], "styles": [ + "node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", + "node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less", "src/styles.scss" ], "scripts": [] diff --git a/package-lock.json b/package-lock.json index 4c9ac92..25dbc11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^19.0.0", + "@angular/cdk": "^19.0.0", "@angular/common": "^19.0.0", "@angular/compiler": "^19.0.0", "@angular/core": "^19.0.0", @@ -16,6 +17,21 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", + "@auth0/angular-jwt": "^5.2.0", + "@taiga-ui/addon-charts": "^4.25.0", + "@taiga-ui/addon-commerce": "^4.25.0", + "@taiga-ui/addon-doc": "^4.25.0", + "@taiga-ui/addon-mobile": "^4.25.0", + "@taiga-ui/addon-table": "^4.25.0", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/event-plugins": "^4.0.2", + "@taiga-ui/icons": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/layout": "^4.25.0", + "angular-calendar": "^0.31.1", + "date-fns": "^4.1.0", + "jwt-decode": "^4.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -214,7 +230,7 @@ "version": "19.1.7", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.7.tgz", "integrity": "sha512-q0I6L9KTqyQ7D5M8H+fWLT+yjapvMNb7SRdfU6GzmexO66Dpo83q4HDzuDKIPDF29Yl0ELs9ICJqe9yUXh6yDQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ajv": "8.17.1", @@ -242,7 +258,7 @@ "version": "19.1.7", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.1.7.tgz", "integrity": "sha512-AP6FvhMybCYs3gs+vzEAzSU1K//AFT3SVTRFv+C3WMO5dLeAHeGzM8I2dxD5EHQQtqIE/8apP6CxGrnpA5YlFg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@angular-devkit/core": "19.1.7", @@ -426,6 +442,23 @@ } } }, + "node_modules/@angular/cdk": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.1.4.tgz", + "integrity": "sha512-PyvJ1VbYjW8tVnVHvcasiqI9eNWf8EJnr0in1QWnhpSbpVpVpc4yjbgnu2pTrW9mPo/YjV4pF+qs6E97y9mdYQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "19.1.7", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.1.7.tgz", @@ -617,6 +650,18 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@auth0/angular-jwt": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.2.0.tgz", + "integrity": "sha512-9FS2L0QwGNlxA/zgeehCcsR9CZscouyXkoIj1fODM36A8BLfdzg9k9DWAXUQ2Drjk0AypGAFzeNZR4vsLMhdeQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -3132,7 +3177,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -3333,6 +3378,56 @@ "win32" ] }, + "node_modules/@maskito/angular": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.2.1.tgz", + "integrity": "sha512-Qb9qY6AeG23KWuROF2gcHzUxXiaaMIFuoA/ekqI4TAtBDEO2D7ImQ0oTkdsFfbDLZoI3phmy70H4f+gidEnfag==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "2.8.1" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@angular/forms": ">=16.0.0", + "@maskito/core": "^3.2.1" + } + }, + "node_modules/@maskito/core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.2.1.tgz", + "integrity": "sha512-gpoJbFFsq2CFz9smqEvZfTPVMt/gkRoiL14idL1sLXEDbWZyeKce0SgusHzHXaOkEegly4BIJGazCNG2hsPNYg==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@maskito/kit": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.2.1.tgz", + "integrity": "sha512-p9Tmr4BMZs7geNWMYMQaP4Dc+Sre8OeE6Vynz1RGsJJdjxmocY2mihXKldecS8c9aXfHvLU658F9YtorL3Akjg==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@maskito/core": "^3.2.1" + } + }, + "node_modules/@maskito/phone": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.2.1.tgz", + "integrity": "sha512-F1hrnpP1UyMhVO0nZAMoA0kkS8rVTc4kVUZ8mcxz98weIAKYo3wiuLOMXb1cg7/ejcpEnqDTRe1rzBxwtRAKeQ==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@maskito/core": "^3.2.1", + "@maskito/kit": "^3.2.1", + "libphonenumber-js": ">=1.0.0" + } + }, + "node_modules/@mattlewis92/dom-autoscroller": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@mattlewis92/dom-autoscroller/-/dom-autoscroller-2.4.2.tgz", + "integrity": "sha512-YbrUWREPGEjE/FU6foXcAT1YbVwqD/jkYnY1dFb0o4AxtP3s4xKBthlELjndZih8uwsDWgQZx1eNskRNe2BgZQ==", + "license": "MIT" + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -3722,6 +3817,88 @@ "node": ">= 10" } }, + "node_modules/@ng-web-apis/common": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-4.11.1.tgz", + "integrity": "sha512-fXbcMrd/+L+9j9knbgXbDwYe30H4Wt0hQzvqyhpXTVrc0jYwlk3MJTYrnazKz5HvP9318caEv5n4qt3HMf5uPQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@ng-web-apis/intersection-observer": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-4.11.1.tgz", + "integrity": "sha512-KjODVVx20yG/U5bnPvp5voihL5DSVFuYwZVY9DNRvaFIcQPMy1tL1t9/oJOdxj7zUSFDL8+Z0RoJbsvArezuSg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": ">=4.11.1" + } + }, + "node_modules/@ng-web-apis/mutation-observer": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-4.11.1.tgz", + "integrity": "sha512-YFnkGFE0gd03q4HBJL+WPl3YZRZNq7dVV8yD5uqr0ZCDgmOAMBilrp42FuHBPaYkF73Rs2EpKsKHWz1jASqBbQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": ">=4.11.1" + } + }, + "node_modules/@ng-web-apis/platform": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@ng-web-apis/platform/-/platform-4.11.1.tgz", + "integrity": "sha512-BrhkUIEEAD7wcwR65LSXHYOD6L3IvAb4aV94S8tzxUNeGUPwikX5glQJBT1UwkHWXQjANPKTCNyK1LO+cMPgkw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + } + }, + "node_modules/@ng-web-apis/resize-observer": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-4.11.1.tgz", + "integrity": "sha512-q8eJ6sovnMhfqIULN1yyhqT35Y2a60vB42p9CUBWPeeVammU+QHE/imPCMCJgnti9cdPZfnyI/+TeYNIUg7mzg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": ">=4.11.1" + } + }, + "node_modules/@ng-web-apis/screen-orientation": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@ng-web-apis/screen-orientation/-/screen-orientation-4.11.1.tgz", + "integrity": "sha512-HS/kWTgVjXVDqMLcJbl5uty+1sV10m9PeDag74tzktIDAB06diFQJQGfcQaA0o0IBisT3fOysf9gHV5sXxSOFw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": ">=4.11.1", + "rxjs": ">=7.0.0" + } + }, "node_modules/@ngtools/webpack": { "version": "19.1.7", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.1.7.tgz", @@ -3743,7 +3920,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -3757,7 +3934,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3767,7 +3944,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4662,11 +4839,18 @@ "win32" ] }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@schematics/angular": { "version": "19.1.7", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.1.7.tgz", "integrity": "sha512-BB8yMGmYDZzSb8Nu+Ln0TKyeoS3++f9STCYw30NwM3IViHxJJYxu/zowzwSa9TjftIzdCpbOaPxGS0vU9UOUDQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@angular-devkit/core": "19.1.7", @@ -4779,6 +4963,334 @@ "dev": true, "license": "MIT" }, + "node_modules/@taiga-ui/addon-charts": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.25.0.tgz", + "integrity": "sha512-nH6m7hXHzgMWmazxZN01/sdmvG2dX8yhFM8Lk4LUCnrK0Daa2aKMIYMIWoqSg9Z9e1s5qhcmAtUfWYaSKYnFqw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": "^4.11.1", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0" + } + }, + "node_modules/@taiga-ui/addon-commerce": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.25.0.tgz", + "integrity": "sha512-FISgafEymyiLy1avgfYTsjGzIFtksJQMK2uC0mebTxZlzYelEcmpJQnBaqwODGlxbavagmYz7cT/1ptPzGIvTw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/forms": ">=16.0.0", + "@maskito/angular": "^3.2.1", + "@maskito/core": "^3.2.1", + "@maskito/kit": "^3.2.1", + "@ng-web-apis/common": "^4.11.1", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/i18n": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/addon-doc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-doc/-/addon-doc-4.25.0.tgz", + "integrity": "sha512-jgbCZYDuyOPx4J+5blLOEmtY5ybkYctqKfJ+NAQ36Sn72nV+X9diXla5SNFgGEw8WjfcfHr/VRTskLWsqX3p0Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/cdk": ">=16.0.0", + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/forms": ">=16.0.0", + "@angular/router": ">=16.0.0", + "@ng-web-apis/common": "^4.11.1", + "@taiga-ui/addon-mobile": "^4.25.0", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/legacy": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "markdown-it": "^14.1.0", + "ngx-highlightjs": "^10.0.0" + } + }, + "node_modules/@taiga-ui/addon-mobile": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.25.0.tgz", + "integrity": "sha512-4RSriHziB/Tcf2xQY+N0W1/eoyDGBy6uVb27gjOsvZTHAajuR03euzn8FqEwkIPX57lQ+m/nn+FQ4U2Gv+zTSg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/cdk": ">=16.0.0", + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": "^4.11.1", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/layout": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/addon-table": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.25.0.tgz", + "integrity": "sha512-KQYPq3D7zXrQGpmd5vkkW7onSVBPgxZ6kxU9xAZOiBjItUQZKEOrNAJnLemIxSGwATEwdZ7ndh3TPaugFqL1LQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@ng-web-apis/intersection-observer": "^4.11.1", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/i18n": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/cdk": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.25.0.tgz", + "integrity": "sha512-I8grhmHx0MUvMcDj+q9Q1FyLe9sc9ZzKE3BgGF3UL/Mcq8L/C1MDrMe0OWPZipGB1mSs0gMYIyVshwmd5H7mHg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.8.1" + }, + "optionalDependencies": { + "@angular-devkit/core": ">=16.0.0", + "@angular-devkit/schematics": ">=16.0.0", + "@schematics/angular": ">=16.0.0", + "ng-morph": "^4.8.4", + "parse5": ">=7.2.1" + }, + "peerDependencies": { + "@angular/animations": ">=16.0.0", + "@angular/cdk": ">=16.0.0", + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/forms": ">=16.0.0", + "@ng-web-apis/common": "^4.11.1", + "@ng-web-apis/mutation-observer": "^4.11.1", + "@ng-web-apis/platform": "^4.11.1", + "@ng-web-apis/resize-observer": "^4.11.1", + "@ng-web-apis/screen-orientation": "^4.11.1", + "@taiga-ui/event-plugins": "^4.4.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/core": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.25.0.tgz", + "integrity": "sha512-pVjLxwq/C9SRkH5xdIgeqmo8ZIeCnqgpHkXjo7qVdAMsW0vRemw+CSQzHjVxt6fLvsaHTLKR6F9OuZCHrno0hw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/animations": ">=16.0.0", + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/forms": ">=16.0.0", + "@angular/platform-browser": ">=16.0.0", + "@angular/router": ">=16.0.0", + "@ng-web-apis/common": "^4.11.1", + "@ng-web-apis/mutation-observer": "^4.11.1", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/event-plugins": "^4.4.0", + "@taiga-ui/i18n": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/event-plugins": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.4.0.tgz", + "integrity": "sha512-Tv8C0p5EZXl7s1Vc+MrLbAblbYvyswomY/xvyFcI9NgMj6JyfsStu6jpCiRMfzojz3G70PRFsk0+WwI19lRJCQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@angular/platform-browser": ">=16.0.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/i18n": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.25.0.tgz", + "integrity": "sha512-NHRlc4YYteXUyf0t/ob76S3A97bb+2CAd0jGi1hbeb16gop0eeicE/E7whrqVjR5iQomhec5+74p1Rve1PXOaA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@ng-web-apis/common": "^4.11.1", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/icons": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.25.0.tgz", + "integrity": "sha512-KndcaPF+O+/IY6k2YgaAUogUJ1tPezMYBw7QH2R91RmERQdvtnVO0/bl4j2KqodilSwmWCJiAPkZhSdNCdyfMQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.0" + } + }, + "node_modules/@taiga-ui/kit": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.25.0.tgz", + "integrity": "sha512-iStxHahkdjlKI//e1ZjcjGma5s0/FI1wEEB+u3yOMa7VuYWEQSjRKPm2V8GE9Ozi/8Ovq+oj1oiEHFn7ZaCeQw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/forms": ">=16.0.0", + "@angular/router": ">=16.0.0", + "@maskito/angular": "^3.2.1", + "@maskito/core": "^3.2.1", + "@maskito/kit": "^3.2.1", + "@maskito/phone": "^3.2.1", + "@ng-web-apis/common": "^4.11.1", + "@ng-web-apis/intersection-observer": "^4.11.1", + "@ng-web-apis/mutation-observer": "^4.11.1", + "@ng-web-apis/resize-observer": "^4.11.1", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/i18n": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/layout": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.25.0.tgz", + "integrity": "sha512-cJxuzrNA+GM7QhZz9LAve4+LMWS/QSPMuYW1DEVtbB2Yxcv3qfvr61cjcqYRzRsMNLCl9JZXqzfitelC4DDB5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/polymorpheus": "^4.8.0", + "rxjs": ">=7.0.0" + } + }, + "node_modules/@taiga-ui/legacy": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.25.0.tgz", + "integrity": "sha512-/EEpZbPVBI91H2CJtllsS3FWfyPDdaYQc8lwiQp9voQoARuMeP14EC/ZzBHdBN8GMMZXdGL77/L60Hs2H4sdrQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": ">=2.8.1" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0" + } + }, + "node_modules/@taiga-ui/polymorpheus": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/polymorpheus/-/polymorpheus-4.8.0.tgz", + "integrity": "sha512-gNXk8SVxXf/5wtmm6XeFMQ9RzY0xbM9E4vFxSGwnNegVZtv3T08YX2uoxPgUbgck2/GS9N5B5KvjjbVa0T0L9A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0", + "@angular/platform-browser": ">=16.0.0" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.24.0.tgz", + "integrity": "sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.4", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@ts-morph/common/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", @@ -4987,6 +5499,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "license": "MIT", + "optional": true + }, "node_modules/@types/node": { "version": "22.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", @@ -5366,7 +5885,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5383,7 +5902,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -5410,6 +5929,51 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-calendar": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/angular-calendar/-/angular-calendar-0.31.1.tgz", + "integrity": "sha512-pjSIpoAaUzS/gx+14eOr4hPZhlQ8HxpiZypCSGqJNptq5PD+vOdVQ3h/Aaqnk86GraVcAQPXqfu64MtdKwTVNw==", + "license": "MIT", + "dependencies": { + "@scarf/scarf": "^1.1.1", + "angular-draggable-droppable": "^8.0.0", + "angular-resizable-element": "^7.0.0", + "calendar-utils": "^0.10.4", + "positioning": "^2.0.1", + "tslib": "^2.4.1" + }, + "funding": { + "url": "https://github.com/sponsors/mattlewis92" + }, + "peerDependencies": { + "@angular/core": ">=15.0.0" + } + }, + "node_modules/angular-draggable-droppable": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/angular-draggable-droppable/-/angular-draggable-droppable-8.0.0.tgz", + "integrity": "sha512-+gpSNBbygjV1pxTxsM3UPJKcXHXJabYoTtKcgQe74rGnb1umKc07XCBD1qDzvlG/kocthvhQ12qfYOYzHnE3ZA==", + "license": "MIT", + "dependencies": { + "@mattlewis92/dom-autoscroller": "^2.4.2", + "tslib": "^2.4.1" + }, + "peerDependencies": { + "@angular/core": ">=15.0.0" + } + }, + "node_modules/angular-resizable-element": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/angular-resizable-element/-/angular-resizable-element-7.0.2.tgz", + "integrity": "sha512-/BGuNiA38n9klexHO1xgnsA3VYigj9v+jUGjKtBRgfB26bCxZKsNWParSu2k3EqbATrfAJC4Nl8f7cORpJFf4w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=15.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5466,7 +6030,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5509,9 +6073,18 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -5519,6 +6092,26 @@ "dev": true, "license": "MIT" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -5631,14 +6224,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -5719,7 +6312,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -5791,7 +6384,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5802,7 +6395,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -5848,7 +6441,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -6034,6 +6627,12 @@ "node": ">=18" } }, + "node_modules/calendar-utils": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/calendar-utils/-/calendar-utils-0.10.4.tgz", + "integrity": "sha512-gBK4xCJ42yjaUKwuUha6cZOfxAmGzvSgbdAaX3xLRioeKbYoOK1x1qeD6dch72rsMZlTgATPbBBx42bnkStqgQ==", + "license": "MIT" + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -6100,7 +6699,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -6176,7 +6775,7 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -6304,7 +6903,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -6338,11 +6937,18 @@ "node": ">=0.10.0" } }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT", + "optional": true + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -6355,7 +6961,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/colorette": { @@ -6442,7 +7048,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/connect": { @@ -6752,6 +7358,16 @@ "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -6814,7 +7430,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -7138,7 +7754,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -7542,14 +8157,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7566,7 +8181,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -7586,7 +8201,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -7603,7 +8218,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -7626,7 +8241,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -8043,7 +8658,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -8091,6 +8706,16 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hosted-git-info": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz", @@ -8333,7 +8958,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -8463,7 +9088,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/ini": { @@ -8556,7 +9181,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8579,7 +9204,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -8611,7 +9236,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -8634,7 +9259,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -8686,7 +9311,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -8954,7 +9579,7 @@ "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, + "devOptional": true, "license": "MIT" }, "node_modules/json5": { @@ -8974,7 +9599,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jsonfile": { @@ -8997,6 +9622,15 @@ ], "license": "MIT" }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -9460,6 +10094,13 @@ "node": ">=0.10.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.20.tgz", + "integrity": "sha512-/ipwAMvtSZRdiQBHqW1qxqeYiBMzncOQLVA+62MWYr7N4m7Q2jqpJ0WgT7zlOEOpyLRSqrMXidbJpC0J77AaKA==", + "license": "MIT", + "peer": true + }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -9485,6 +10126,16 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/listr2": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", @@ -9622,7 +10273,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -9766,7 +10417,7 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -9811,6 +10462,24 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -9821,6 +10490,13 @@ "node": ">= 0.4" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT", + "peer": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -9872,7 +10548,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 8" @@ -9892,7 +10568,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -9906,7 +10582,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -9955,7 +10631,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -10006,7 +10682,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -10320,6 +10996,26 @@ "multicast-dns": "cli.js" } }, + "node_modules/multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", @@ -10398,6 +11094,66 @@ "dev": true, "license": "MIT" }, + "node_modules/ng-morph": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.8.4.tgz", + "integrity": "sha512-XwL53wCOhyaAxvoekN74ONbWUK30huzp+GpZYyC01RfaG2AX9l7YlC1mGG/l7Rx7YXtFAk85VFnNJqn2e46K8g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "jsonc-parser": "3.3.1", + "minimatch": "10.0.1", + "multimatch": "5.0.0", + "ts-morph": "23.0.0" + }, + "peerDependencies": { + "@angular-devkit/core": ">=16.0.0", + "@angular-devkit/schematics": ">=16.0.0", + "tslib": "^2.7.0" + } + }, + "node_modules/ng-morph/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ng-morph/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ngx-highlightjs": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-10.0.0.tgz", + "integrity": "sha512-F5VXB6vnpiTPMADUoCEkyc2wnqpKUNvfeAP4tO//NrwdQP2sQK6MfPg+jHL2adoJE5LIchsJlpq6C0r+KmlOSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "highlight.js": "^11.8.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "rxjs": ">=7.0.0" + } + }, "node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", @@ -10843,7 +11599,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -10867,7 +11623,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10877,7 +11633,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -10890,7 +11646,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -10906,7 +11662,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -10920,14 +11676,14 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/ora/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11119,7 +11875,7 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "entities": "^4.5.0" @@ -11166,6 +11922,13 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT", + "optional": true + }, "node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -11258,7 +12021,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -11304,6 +12067,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/positioning": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/positioning/-/positioning-2.0.1.tgz", + "integrity": "sha512-DsAgM42kV/ObuwlRpAzDTjH9E8fGKkMDJHWFX+kfNXSxh7UCCQxEmdjv/Ws5Ft1XDnt3JT8fIDYeKNSE2TbttA==", + "license": "MIT" + }, "node_modules/postcss": { "version": "8.4.49", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", @@ -11526,6 +12295,16 @@ "dev": true, "license": "MIT" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -11556,7 +12335,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -11613,7 +12392,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -11754,7 +12533,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11871,7 +12650,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -11958,7 +12737,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -11991,7 +12770,7 @@ "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, + "devOptional": true, "funding": [ { "type": "github", @@ -12713,7 +13492,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">= 8" @@ -12901,7 +13680,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -13025,7 +13804,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -13259,7 +14038,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -13305,6 +14084,17 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-morph": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-23.0.0.tgz", + "integrity": "sha512-FcvFx7a9E8TUe6T3ShihXJLiJOiqyafzFKUO4aqIHDUCIvADdGNShcbc2W5PMr3LerXRv7mafvFZ9lRENxJmug==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ts-morph/common": "~0.24.0", + "code-block-writer": "^13.0.1" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -13401,6 +14191,13 @@ "node": "*" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT", + "peer": true + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -13566,7 +14363,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -13761,7 +14558,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" diff --git a/package.json b/package.json index d232f2e..b80b4c7 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "private": true, "dependencies": { "@angular/animations": "^19.0.0", + "@angular/cdk": "^19.0.0", "@angular/common": "^19.0.0", "@angular/compiler": "^19.0.0", "@angular/core": "^19.0.0", @@ -18,6 +19,21 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", + "@auth0/angular-jwt": "^5.2.0", + "@taiga-ui/addon-charts": "^4.25.0", + "@taiga-ui/addon-commerce": "^4.25.0", + "@taiga-ui/addon-doc": "^4.25.0", + "@taiga-ui/addon-mobile": "^4.25.0", + "@taiga-ui/addon-table": "^4.25.0", + "@taiga-ui/cdk": "^4.25.0", + "@taiga-ui/core": "^4.25.0", + "@taiga-ui/event-plugins": "^4.0.2", + "@taiga-ui/icons": "^4.25.0", + "@taiga-ui/kit": "^4.25.0", + "@taiga-ui/layout": "^4.25.0", + "angular-calendar": "^0.31.1", + "date-fns": "^4.1.0", + "jwt-decode": "^4.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/public/assets/fonts/custom-fonts.css b/public/assets/fonts/custom-fonts.css new file mode 100644 index 0000000..03f5fec --- /dev/null +++ b/public/assets/fonts/custom-fonts.css @@ -0,0 +1,13 @@ +@font-face { + font-family: 'Lato'; + src: url('/assets/fonts/lato.regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Lato'; + src: url('/assets/fonts/lato.heavy.ttf') format('truetype'); + font-weight: bold; + font-style: normal; +} diff --git a/public/assets/fonts/lato.heavy.ttf b/public/assets/fonts/lato.heavy.ttf new file mode 100644 index 0000000..fc70ab7 Binary files /dev/null and b/public/assets/fonts/lato.heavy.ttf differ diff --git a/public/assets/fonts/lato.regular.ttf b/public/assets/fonts/lato.regular.ttf new file mode 100644 index 0000000..adbfc46 Binary files /dev/null and b/public/assets/fonts/lato.regular.ttf differ diff --git a/public/assets/logo-minimal.png b/public/assets/logo-minimal.png new file mode 100644 index 0000000..b30365a Binary files /dev/null and b/public/assets/logo-minimal.png differ diff --git a/public/assets/logo.png b/public/assets/logo.png new file mode 100644 index 0000000..8d00503 Binary files /dev/null and b/public/assets/logo.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 57614f9..3a65c41 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/app/app.component.html b/src/app/app.component.html index 36093e1..6aae851 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,336 +1,3 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - + + + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29..d9a0db4 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,9 @@ +html { + background-color: #222222; +} + +tui-root { + background-color: #222222; +} + + diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 642245d..2de4162 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,5 +1,5 @@ -import { TestBed } from '@angular/core/testing'; -import { AppComponent } from './app.component'; +import {TestBed} from '@angular/core/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { beforeEach(async () => { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1820037..b47e16c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,9 +1,12 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import {TuiRoot} from "@taiga-ui/core"; +import {Component} from '@angular/core'; +import {RouterOutlet} from '@angular/router'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, + TuiRoot, + TuiRoot], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a1e7d6f..18be1be 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,8 +1,12 @@ -import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import {NG_EVENT_PLUGINS} from "@taiga-ui/event-plugins"; +import {provideAnimations} from "@angular/platform-browser/animations"; +import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core'; +import {provideRouter, withHashLocation} from '@angular/router'; -import { routes } from './app.routes'; +import {routes} from './app.routes'; +import {provideHttpClient, withInterceptors, withInterceptorsFromDi} from '@angular/common/http'; +import {AuthInterceptor} from './interceptors/auth-interceptor'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] + providers: [provideAnimations(), provideZoneChangeDetection({eventCoalescing: true}), provideRouter(routes, withHashLocation()), provideHttpClient(withInterceptorsFromDi()), NG_EVENT_PLUGINS, NG_EVENT_PLUGINS, provideHttpClient(withInterceptors([AuthInterceptor]))], }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index dc39edb..ee4820a 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,3 +1,28 @@ -import { Routes } from '@angular/router'; +import {Routes} from '@angular/router'; +import {AgendaComponent} from './pages/agenda/agenda.component'; +import {LoginComponent} from './pages/login/login.component'; +import {HomeComponent} from './pages/home/home.component'; +import {KlantenComponent} from './pages/klanten/klanten.component'; -export const routes: Routes = []; +export const routes: Routes = [ + {path: '', redirectTo: 'login', pathMatch: 'full'}, + {path: 'login', component: LoginComponent}, + { + path: 'home', + component: HomeComponent, + children: [ + { + path: 'agenda', + component: AgendaComponent, + }, + { + path: 'klanten', + component: KlantenComponent, + }, + // { + // path: 'dashboard', + // component: DashboardComponent, + // }, + ], + }, +]; diff --git a/src/app/components/customer-details/customer-details.component.html b/src/app/components/customer-details/customer-details.component.html new file mode 100644 index 0000000..30dc765 --- /dev/null +++ b/src/app/components/customer-details/customer-details.component.html @@ -0,0 +1,19 @@ +

+ + {{ customer.firstName }} {{ customer.lastName }} +

+

+ + {{ customer.email }} +

+ +
+

+ Eerst volgende afspraak: +

+

{{ appointment.startDate }}

+
diff --git a/src/app/components/customer-details/customer-details.component.scss b/src/app/components/customer-details/customer-details.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/customer-details/customer-details.component.ts b/src/app/components/customer-details/customer-details.component.ts new file mode 100644 index 0000000..64673fd --- /dev/null +++ b/src/app/components/customer-details/customer-details.component.ts @@ -0,0 +1,37 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {ReactiveFormsModule} from "@angular/forms"; +import {TuiIcon} from "@taiga-ui/core"; +import {TuiTextareaModule} from "@taiga-ui/legacy"; +import {Customer} from '../../models/customer'; +import {AppointmentService} from '../../services/appointment.service'; +import {Appointment} from '../../models/appointment'; +import {DateFormatter} from '../../utils/date-formatter'; +import {NgIf} from '@angular/common'; + +@Component({ + selector: 'app-customer-details', + imports: [ + ReactiveFormsModule, + TuiIcon, + TuiTextareaModule, + NgIf + ], + templateUrl: './customer-details.component.html', + styleUrl: './customer-details.component.scss' +}) +export class CustomerDetailsComponent implements OnInit { + + @Input() customer: Customer; + appointment: Appointment; + + constructor(private appointmentService: AppointmentService) { + } + + ngOnInit(): void { + this.appointmentService.getMostRecentAppointment(this.customer.id).subscribe((response: Appointment) => + this.appointment = response + ) + } + + protected readonly DateFormatter = DateFormatter; +} diff --git a/src/app/components/details/details.component.html b/src/app/components/details/details.component.html new file mode 100644 index 0000000..9e1380a --- /dev/null +++ b/src/app/components/details/details.component.html @@ -0,0 +1,80 @@ +

+ + {{ DateFormatter.getDate(appointment.startDate.toString()) }} +

+

+ + {{ DateFormatter.getFormattedTime(appointment.startHour, appointment.startMinute) }} + - {{ DateFormatter.getFormattedTime(appointment.endHour, appointment.endMinute) }} +

+

+ + {{ appointment.customer.firstName }} {{ appointment.customer.lastName }} +

+ +
+ Notities +
+ + +
+ + +
+ + + +

Weet je dit zeker?

+
+
+ + +
+
+ + + + + diff --git a/src/app/components/details/details.component.scss b/src/app/components/details/details.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/details/details.component.ts b/src/app/components/details/details.component.ts new file mode 100644 index 0000000..a126356 --- /dev/null +++ b/src/app/components/details/details.component.ts @@ -0,0 +1,74 @@ +import {Component, EventEmitter, inject, Input, OnInit, Output} from '@angular/core'; +import {Appointment} from '../../models/appointment'; +import {DateFormatter} from '../../utils/date-formatter'; +import {TuiAlertService, TuiButton, TuiGroup, TuiIcon} from '@taiga-ui/core'; +import {TuiTextareaModule} from '@taiga-ui/legacy'; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {NgIf} from '@angular/common'; +import {ModalComponent} from '../modal/modal.component'; +import {EditItemComponent} from '../edit-item/edit-item.component'; +import {AppointmentService} from '../../services/appointment.service'; + +@Component({ + selector: 'app-details', + imports: [ + TuiIcon, + TuiTextareaModule, + ReactiveFormsModule, + NgIf, + TuiButton, + TuiGroup, + ModalComponent, + EditItemComponent + ], + templateUrl: './details.component.html', + styleUrl: './details.component.scss' +}) +export class DetailsComponent implements OnInit { + private readonly alerts = inject(TuiAlertService); + + ngOnInit(): void { + if (this.appointment.description) { + this.testForm.get('testValue1').setValue(this.appointment.description) + } + } + + constructor(private appointmentService: AppointmentService) { + } + + @Input() appointment: Appointment; + @Output() appointmentDeleted = new EventEmitter(); + @Output() appointmentEdited = new EventEmitter(); + open: boolean = true; + readonly = true; + showDeleteModal = false; + showEditModal = false; + + protected testForm = new FormGroup({ + testValue1: new FormControl('', Validators.required), + testValue2: new FormControl('This one can be expanded', Validators.required), + testValue3: new FormControl( + 'This one can be expanded (expandable on focus)', + Validators.required, + ), + }); + + deleteAppointment() { + this.appointmentService.deleteAppointment(this.appointment).subscribe(() => { + this.showDeleteModal = false; + this.appointmentDeleted.emit(this.appointment); + } + ) + } + + protected readonly DateFormatter = DateFormatter; + + updateAppointment($event: any) { + this.showEditModal = false; + + this.appointmentService.getAppointment($event).subscribe((appointment: Appointment) => { + this.appointment = appointment + this.appointmentEdited.emit(this.appointment); + }) + } +} diff --git a/src/app/components/edit-item/edit-item.component.html b/src/app/components/edit-item/edit-item.component.html new file mode 100644 index 0000000..1737078 --- /dev/null +++ b/src/app/components/edit-item/edit-item.component.html @@ -0,0 +1,149 @@ +
+ + Titel + + +
+ +
+ +
+
+ + Klant + + + +
+
+
+
+ + Datum + +
+
+ + Van + +
+
+ + Tot + +
+
+
+ Notities +
+
+ + + +
+ + Voornaam + + +
+ + + Achternaam + + +
+ + Email + + +
+ +
+
diff --git a/src/app/components/edit-item/edit-item.component.scss b/src/app/components/edit-item/edit-item.component.scss new file mode 100644 index 0000000..92d5777 --- /dev/null +++ b/src/app/components/edit-item/edit-item.component.scss @@ -0,0 +1,15 @@ +.toolbar { + button { + margin-left: 8px; + } + + tui-combo-box { + width: 50vw; + } + + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: center; + +} diff --git a/src/app/components/edit-item/edit-item.component.ts b/src/app/components/edit-item/edit-item.component.ts new file mode 100644 index 0000000..299bc56 --- /dev/null +++ b/src/app/components/edit-item/edit-item.component.ts @@ -0,0 +1,146 @@ +import {Component, EventEmitter, inject, Input, OnInit, Output} from '@angular/core'; +import {NgForOf, NgIf} from "@angular/common"; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; +import {TuiAlertService, TuiButton, TuiGroup, TuiIcon} from "@taiga-ui/core"; +import { + TuiComboBoxModule, + TuiInputDateModule, + TuiInputModule, + TuiInputTimeModule, + TuiTextareaModule, + TuiTextfieldControllerModule +} from "@taiga-ui/legacy"; +import {Appointment} from '../../models/appointment'; +import {TuiDay, TuiTime} from '@taiga-ui/cdk'; +import {AppointmentService} from '../../services/appointment.service'; +import {TuiDataListWrapperComponent, TuiFilterByInputPipe, TuiStringifyContentPipe} from '@taiga-ui/kit'; +import {Customer} from '../../models/customer'; +import {CustomerService} from '../../services/customer.service'; +import {ModalComponent} from '../modal/modal.component'; + +@Component({ + selector: 'app-edit-item', + imports: [ + NgForOf, + NgIf, + ReactiveFormsModule, + TuiButton, + TuiGroup, + TuiInputDateModule, + TuiInputModule, + TuiInputTimeModule, + TuiTextareaModule, + TuiTextfieldControllerModule, + TuiComboBoxModule, + TuiDataListWrapperComponent, + TuiStringifyContentPipe, + TuiFilterByInputPipe, + ModalComponent, + TuiIcon + ], + templateUrl: './edit-item.component.html', + styleUrl: './edit-item.component.scss' +}) +export class EditItemComponent implements OnInit { + + @Input() appointment: Appointment; + testForm: FormGroup; + quickActions = ['Knippen', 'Kleuren', 'Knippen + Kleuren'] + waiting: boolean = false; + protected value: TuiDay | null = null; + @Output() appointmentUpdateEvent = new EventEmitter(); + showNewCustomer = false; + customerForm: FormGroup; + private readonly alerts = inject(TuiAlertService); + + + constructor(private appointmentService: AppointmentService, private customerService: CustomerService) { + } + + ngOnInit(): void { + console.log(this.appointment); + let date = new Date(this.appointment.startDate); + this.testForm = new FormGroup({ + title: new FormControl(this.appointment.title, Validators.required), + notes: new FormControl(this.appointment.description), + startTime: new FormControl(new TuiTime(this.appointment.startHour, this.appointment.startMinute), Validators.required), + endTime: new FormControl(new TuiTime(this.appointment.endHour, this.appointment.endMinute), Validators.required), + date: new FormControl(new TuiDay(date.getFullYear(), date.getMonth(), date.getDate()), Validators.required), + }) + this.control = new FormControl(this.appointment.customer) + + this.getCustomers() + this.customerForm = new FormGroup({ + firstName: new FormControl('', Validators.required), + lastName: new FormControl('', Validators.required), + email: new FormControl('', Validators.required), + }) + } + + updateAppointment() { + const title = this.testForm.get('title').value + const description = this.testForm.get('notes').value + const startTime = this.testForm.get('startTime').value + const endTime = this.testForm.get('endTime').value + let date = this.testForm.get('date').value + let correctDate = new Date(date.year, date.month, date.day + 1) + + const customer = this.control.value; + this.appointment.startDate = correctDate; + this.appointment.title = title; + this.appointment.description = description + this.appointment.startHour = startTime.hours + this.appointment.startMinute = startTime.minutes + this.appointment.endHour = endTime.hours + this.appointment.endMinute = endTime.minutes + this.appointment.customer = customer; + this.appointment.durationInMinutes = (this.appointment.endHour * 60 + this.appointment.endMinute) - (this.appointment.startHour * 60 + this.appointment.startMinute); + + this.waiting = true + this.appointmentService.updateAppointment(this.appointment).subscribe(() => { + this.waiting = false + this.appointmentUpdateEvent.emit(this.appointment.id) + this.alerts.open(`Afspraak ${title} is aangepast.`) + .subscribe(); + }) + } + + formIsValid() { + return this.testForm.valid ? 'active' : 'disabled' + } + + addAction(action: string) { + this.testForm.get('title').setValue(`${action} `) + } + + protected control = new FormControl( + null, + ); + + toggleCustomerModal() { + this.showNewCustomer = !this.showNewCustomer; + } + + protected items: Customer[] = []; + + protected readonly stringify = (item: Customer): string => + `${item.firstName} ${item.lastName}`; + + saveCustomer() { + const firstName = this.customerForm.get('firstName').value + const lastName = this.customerForm.get('lastName').value + const email = this.customerForm.get('email').value + const customer = new Customer(firstName, lastName, email); + + this.customerService.addCustomer(customer).subscribe(() => { + this.showNewCustomer = false; + this.getCustomers() + }) + } + + getCustomers() { + this.customerService.getCustomers().subscribe(response => { + this.items = response; + }) + } +} diff --git a/src/app/components/modal/modal.component.html b/src/app/components/modal/modal.component.html new file mode 100644 index 0000000..93e0209 --- /dev/null +++ b/src/app/components/modal/modal.component.html @@ -0,0 +1,20 @@ + + diff --git a/src/app/components/modal/modal.component.scss b/src/app/components/modal/modal.component.scss new file mode 100644 index 0000000..3edf2bb --- /dev/null +++ b/src/app/components/modal/modal.component.scss @@ -0,0 +1,42 @@ +/* Overlay achtergrond */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); /* Donkere overlay */ + backdrop-filter: blur(5px); /* Maakt de achtergrond wazig */ + z-index: 998; /* Zorgt dat het onder de modal blijft */ +} + +/* Modal venster */ +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; + min-width: 300px; + width: 50vw; + z-index: 999; /* Zorgt dat het boven de overlay blijft */ +} + +/* Modal header */ +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +/* Sluitknop */ +button { + background: red; + color: white; + border: none; + padding: 5px 10px; + cursor: pointer; +} diff --git a/src/app/components/modal/modal.component.spec.ts b/src/app/components/modal/modal.component.spec.ts new file mode 100644 index 0000000..ed5c9d2 --- /dev/null +++ b/src/app/components/modal/modal.component.spec.ts @@ -0,0 +1,23 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ModalComponent} from './modal.component'; + +describe('ModalComponent', () => { + let component: ModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ModalComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/modal/modal.component.ts b/src/app/components/modal/modal.component.ts new file mode 100644 index 0000000..3d3bdce --- /dev/null +++ b/src/app/components/modal/modal.component.ts @@ -0,0 +1,17 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {TuiButton} from '@taiga-ui/core'; + +@Component({ + selector: 'app-modal', + imports: [TuiButton], + templateUrl: './modal.component.html', + styleUrls: ['./modal.component.scss'] +}) +export class ModalComponent { + @Input() title: string = 'Modal Title'; + @Output() close = new EventEmitter(); + + closeModal() { + this.close.emit(); + } +} diff --git a/src/app/components/new-item/new-item.component.html b/src/app/components/new-item/new-item.component.html new file mode 100644 index 0000000..64e300b --- /dev/null +++ b/src/app/components/new-item/new-item.component.html @@ -0,0 +1,150 @@ +
+ + Titel + + +
+ +
+ +
+
+ + Klant + + + +
+
+
+
+ + Datum + +
+
+ + Van + +
+
+ + Tot + +
+
+
+ Notities +
+
+ + + +
+ + Voornaam + + +
+ + + Achternaam + + +
+ + Email + + +
+ +
+
diff --git a/src/app/components/new-item/new-item.component.scss b/src/app/components/new-item/new-item.component.scss new file mode 100644 index 0000000..92d5777 --- /dev/null +++ b/src/app/components/new-item/new-item.component.scss @@ -0,0 +1,15 @@ +.toolbar { + button { + margin-left: 8px; + } + + tui-combo-box { + width: 50vw; + } + + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: center; + +} diff --git a/src/app/components/new-item/new-item.component.ts b/src/app/components/new-item/new-item.component.ts new file mode 100644 index 0000000..089d7f4 --- /dev/null +++ b/src/app/components/new-item/new-item.component.ts @@ -0,0 +1,128 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {NgForOf, NgIf} from "@angular/common"; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; +import {TuiButton, TuiGroup, TuiIcon} from "@taiga-ui/core"; +import { + TuiComboBoxModule, + TuiInputDateModule, + TuiInputModule, + TuiInputTimeModule, + TuiTextareaModule, + TuiTextfieldControllerModule +} from "@taiga-ui/legacy"; +import {Appointment} from '../../models/appointment'; +import {TuiDay} from '@taiga-ui/cdk'; +import {AppointmentService} from '../../services/appointment.service'; +import { + TuiButtonLoading, + TuiDataListWrapperComponent, + TuiFilterByInputPipe, + TuiStringifyContentPipe +} from '@taiga-ui/kit'; +import {Customer} from '../../models/customer'; +import {CustomerService} from '../../services/customer.service'; +import {ModalComponent} from '../modal/modal.component'; + +@Component({ + selector: 'app-new-item', + imports: [ + NgForOf, + NgIf, + ReactiveFormsModule, + TuiButton, + TuiGroup, + TuiInputDateModule, + TuiInputModule, + TuiInputTimeModule, + TuiTextareaModule, + TuiTextfieldControllerModule, + TuiComboBoxModule, + TuiDataListWrapperComponent, + TuiStringifyContentPipe, + TuiFilterByInputPipe, + ModalComponent, + TuiIcon, + TuiButtonLoading + ], + templateUrl: './new-item.component.html', + styleUrl: './new-item.component.scss' +}) +export class NewItemComponent implements OnInit { + + @Input() testForm: FormGroup; + quickActions = ['Knippen', 'Kleuren', 'Knippen + Kleuren'] + waiting: boolean = false; + protected value: TuiDay | null = null; + @Output() appointmentAddedEvent = new EventEmitter(); + showNewCustomer = false; + customerForm: FormGroup; + + constructor(private appointmentService: AppointmentService, private customerService: CustomerService) { + } + + ngOnInit(): void { + this.getCustomers() + this.customerForm = new FormGroup({ + firstName: new FormControl('', Validators.required), + lastName: new FormControl('', Validators.required), + email: new FormControl('', Validators.required), + }) + } + + registerAppointment() { + const title = this.testForm.get('title').value + const description = this.testForm.get('notes').value + const startTime = this.testForm.get('startTime').value + const endTime = this.testForm.get('endTime').value + let date = this.testForm.get('date').value + let correctDate = new Date(date.year, date.month, date.day, startTime.hours, startTime.minutes); + + const customer = this.control.value; + + const appointment = new Appointment(title, description, startTime.hours, startTime.minutes, endTime.hours, endTime.minutes, correctDate, customer) + this.waiting = true + this.appointmentService.addAppointment(appointment).subscribe(() => { + this.waiting = false + this.appointmentAddedEvent.emit(title) + }) + } + + formIsValid() { + return this.testForm.valid ? 'active' : 'disabled' + } + + addAction(action: string) { + this.testForm.get('title').setValue(`${action} `) + } + + protected readonly control = new FormControl( + null, + ); + + toggleCustomerModal() { + this.showNewCustomer = !this.showNewCustomer; + } + + protected items: Customer[] = []; + + protected readonly stringify = (item: Customer): string => + `${item.firstName} ${item.lastName}`; + + saveCustomer() { + const firstName = this.customerForm.get('firstName').value + const lastName = this.customerForm.get('lastName').value + const email = this.customerForm.get('email').value + const customer = new Customer(firstName, lastName, email); + + this.customerService.addCustomer(customer).subscribe(() => { + this.showNewCustomer = false; + this.getCustomers() + }) + } + + getCustomers() { + this.customerService.getCustomers().subscribe(response => { + this.items = response; + }) + } +} diff --git a/src/app/interceptors/auth-interceptor.ts b/src/app/interceptors/auth-interceptor.ts new file mode 100644 index 0000000..4c087f9 --- /dev/null +++ b/src/app/interceptors/auth-interceptor.ts @@ -0,0 +1,25 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import { inject } from '@angular/core'; +import {AuthService} from '../services/auth.service'; + +const excludedUrls = ['/auth/login']; + +export const AuthInterceptor: HttpInterceptorFn = (req, next) => { + const authService = inject(AuthService); + const token = authService.getToken(); + + if (excludedUrls.some(url => req.url.includes(url))) { + return next(req); + } + + if (token) { + const cloned = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + return next(cloned); + } + + return next(req); +}; diff --git a/src/app/models/appointment.ts b/src/app/models/appointment.ts new file mode 100644 index 0000000..ddc9a61 --- /dev/null +++ b/src/app/models/appointment.ts @@ -0,0 +1,31 @@ +import {Customer} from './customer'; + +export class Appointment { + id: string; + title: string; + description: string; + startDate: Date; + startHour: number; + startMinute: number; + endHour: number; + endMinute: number; + durationInMinutes: number; + customer: Customer; + + constructor(title: string, description: string, startHour: number, startMinute: number, endHour: number, endMinute: number, date: Date, customer: Customer) { + this.title = title; + this.description = description; + this.startHour = startHour; + this.startMinute = startMinute; + this.endHour = endHour; + this.endMinute = endMinute; + this.startDate = date; + this.customer = customer; + + console.log(this) + + // Bereken de totale duur in minuten + this.durationInMinutes = (endHour * 60 + endMinute) - (startHour * 60 + startMinute); + } + +} diff --git a/src/app/models/customer.ts b/src/app/models/customer.ts new file mode 100644 index 0000000..0fd6bfe --- /dev/null +++ b/src/app/models/customer.ts @@ -0,0 +1,13 @@ +export class Customer { + id: number; + firstName: string; + lastName: string; + email: string; + + + constructor(firstName: string, lastName: string, email: string) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } +} diff --git a/src/app/models/user-dto.ts b/src/app/models/user-dto.ts new file mode 100644 index 0000000..9596a7b --- /dev/null +++ b/src/app/models/user-dto.ts @@ -0,0 +1,9 @@ +export class UserDto { + username:string + fullName:string + email:string + token:string + + constructor() { + } +} diff --git a/src/app/models/user.ts b/src/app/models/user.ts new file mode 100644 index 0000000..e31cca8 --- /dev/null +++ b/src/app/models/user.ts @@ -0,0 +1,11 @@ +export class User { + email: string; + exp: number; + firstName: string; + iat: number; + iss: string; + jti: string; + lastName: string; + sub: string; + username: string; +} diff --git a/src/app/models/week-day.ts b/src/app/models/week-day.ts new file mode 100644 index 0000000..7dfa2f9 --- /dev/null +++ b/src/app/models/week-day.ts @@ -0,0 +1,9 @@ +export enum WeekDay { + zondag = 0, + maandag = 1, + dinsdag = 2, + woensdag = 3, + donderdag = 4, + vrijdag = 5, + zaterdag = 6, +} diff --git a/src/app/pages/agenda/agenda.component.html b/src/app/pages/agenda/agenda.component.html new file mode 100644 index 0000000..ee4d62a --- /dev/null +++ b/src/app/pages/agenda/agenda.component.html @@ -0,0 +1,84 @@ +
+
+ +

{{ getDate() }}

+ +
+
+ +
+
+ + +
+
+
+
+ {{ hour.toString().padStart(2, '0') }}:00 + +
+ + {{ appointment.title }} + + + {{ appointment.customer.firstName }} {{ appointment.customer.lastName }} + + + {{ getFormattedTime(appointment.startHour, appointment.startMinute) }} + - {{ getFormattedTime(appointment.endHour, appointment.endMinute) }} + +
+
+
+
+
+ + + + + + + + diff --git a/src/app/pages/agenda/agenda.component.scss b/src/app/pages/agenda/agenda.component.scss new file mode 100644 index 0000000..399a180 --- /dev/null +++ b/src/app/pages/agenda/agenda.component.scss @@ -0,0 +1,141 @@ +.container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + background-color: #f9f9f9; + margin: 10px 0; + padding: 10px; + border-radius: 5px; +} + +h2 { + margin: 0; +} + +p { + margin: 5px 0 0; +} + +.heading { + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.calendar { + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: center; +} + +.time-slot { + height: 25px; + display: flex; + align-items: center; + font-size: 14px; + background: #f9f9f9; + transition: background 0.3s ease; +} + +.time-slot.claimed { + background: #d1e7dd; /* Lichtgroene achtergrond voor gereserveerde blokken */ + color: #155724; + font-weight: bold; +} + +.time-slot:nth-child(odd) { + background: #ececec; +} + +.time { + width: 50px; + font-weight: bold; + margin-top: 20px; + margin-left: 8px; +} + +.content { + overflow-y: scroll; + max-height: 70vh; +} + +.agenda-container { + display: flex; + flex-direction: column; + border: 1px solid #ddd; + position: relative; +} + +.time-slot { + position: relative; + border-bottom: 1px solid #ccc; + height: 59px; + display: flex; + flex-direction: column; + align-items: flex-start; + font-size: 16px; + background: #f9f9f9; +} + +.appointment { + position: absolute; + left: 80px; + width: calc(100% - 100px); + background: #1e88e5; + color: white; + border-radius: 4px; + border-left: 5px solid #1565c0; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); + + z-index: 10; + cursor: pointer; + display: grid; + grid-auto-flow: column; + align-items: center; + //gap: 5px; + + min-height: 20px; +} + +.appointment.large { + grid-auto-flow: row; + align-items: flex-start; +} + +strong, p { + margin-left: 8px; +} + +.appointment-time { + margin-left: 8px; +} + +.toolbar { + button { + margin-left: 8px; + } + + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: center; + +} + +.date { + cursor: pointer; +} + +.date:hover { + text-decoration: underline; +} diff --git a/src/app/pages/agenda/agenda.component.ts b/src/app/pages/agenda/agenda.component.ts new file mode 100644 index 0000000..d01ac4e --- /dev/null +++ b/src/app/pages/agenda/agenda.component.ts @@ -0,0 +1,206 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {CommonModule, NgFor, NgIf} from '@angular/common'; +import {TuiAlertService, TuiButton, TuiCalendar, tuiDateFormatProvider, TuiIcon} from '@taiga-ui/core'; +import {Appointment} from '../../models/appointment'; +import {ModalComponent} from '../../components/modal/modal.component'; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import { + TuiInputDateModule, + TuiInputModule, + TuiInputTimeModule, + TuiTextareaModule, + TuiTextfieldControllerModule +} from '@taiga-ui/legacy'; +import {TuiDay, TuiTime} from '@taiga-ui/cdk'; +import {AppointmentService} from '../../services/appointment.service'; +import {DetailsComponent} from '../../components/details/details.component'; +import {DateFormatter} from '../../utils/date-formatter'; +import {WeekDay} from '../../models/week-day'; +import {NewItemComponent} from '../../components/new-item/new-item.component'; + +@Component({ + selector: 'app-agenda', + imports: [NgFor, + TuiButton, CommonModule, NgIf, ModalComponent, ReactiveFormsModule, + TuiInputTimeModule, TuiTextfieldControllerModule, + TuiInputModule, TuiTextareaModule, TuiInputDateModule, TuiIcon, DetailsComponent, TuiCalendar, NewItemComponent], + templateUrl: './agenda.component.html', + providers: [tuiDateFormatProvider({separator: '-'}), AppointmentService], + styleUrl: './agenda.component.scss' +}) +export class AgendaComponent implements OnInit { + + constructor(private appointmentService: AppointmentService) { + } + + ngOnInit(): void { + this.getAppointmentsByDate(this.selectedDate); + } + + timeSlots: number[] = Array.from({length: 24}, (_, i) => i); // 24 uren + appointments: Appointment[] = []; + isModalOpen = false + today = new Date() + selectedDate = new Date() + waiting: boolean = false; + selectedAppointment: Appointment; + protected value: TuiDay | null = null; + showCalendar: boolean = false; + + private readonly alerts = inject(TuiAlertService); + protected appointmentForm = new FormGroup({ + title: new FormControl('', Validators.required), + notes: new FormControl(''), + startTime: new FormControl(new TuiTime(this.today.getHours(), this.today.getMinutes()), Validators.required), + endTime: new FormControl(new TuiTime(this.getHours(), this.getMinutes()), Validators.required), + date: new FormControl(new TuiDay(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate()), Validators.required), + }); + + registerAppointment(title: string): void { + this.getAppointmentsByDate(this.selectedDate); + this.waiting = false + this.isModalOpen = false + this.showNotification(title) + this.resetForms() + } + + protected showNotification(message: string): void { + this.alerts + .open(`Afspraak ${message} is aangemaakt.`) + .subscribe(); + } + + getAppointmentsForHour(hour: number) { + return this.appointments.filter(appointment => appointment.startHour === hour); + } + + getDate(): string { + const date = this.selectedDate + + const weekDay = WeekDay[date.getDay()]; + const day = date.getDate().toString().padStart(2, '0'); // Dag met leading zero (01, 02, ..., 31) + const monthName = date.toLocaleString('nl-NL', {month: 'long'}); + const year = date.getFullYear().toString(); // Volledig jaar + + return `${weekDay} ${day} ${monthName} ${year}`; + } + + resetForms() { + this.appointmentForm = new FormGroup({ + title: new FormControl('', Validators.required), + notes: new FormControl(''), + startTime: new FormControl(new TuiTime(this.today.getHours(), this.today.getMinutes()), Validators.required), + endTime: new FormControl(new TuiTime(this.getEndTime().getHours(), this.getEndTime().getMinutes()), Validators.required), + date: new FormControl(new TuiDay(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate()), Validators.required), + }); + } + + nextDay() { + this.selectedDate.setDate(this.selectedDate.getDate() + 1); + this.getAppointmentsByDate(this.selectedDate); + this.appointmentForm.get('date').setValue(new TuiDay(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate())) + } + + previousDay() { + this.selectedDate.setDate(this.selectedDate.getDate() - 1); + this.getAppointmentsByDate(this.selectedDate); + this.appointmentForm.get('date').setValue(new TuiDay(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate())) + } + + getHours() { + let hours = this.today.getHours() + console.log(hours) + if (hours > 23) { + return 23 + } + return hours + } + + getMinutes() { + let minutes = this.today.getMinutes() + 30 + if (minutes > 59) { + return 59 + } + return minutes + } + + getEndTime(): Date { + const endTime = new Date(this.today); // Kopieer startTime + endTime.setMinutes(endTime.getMinutes() + 30); // 30 minuten toevoegen + + // Controleer of de dag nog steeds hetzelfde is + if (endTime.getDate() !== this.today.getDate()) { + endTime.setHours(23, 59, 59, 999); // Zet naar 23:59:59 als het overloopt + } + + return endTime; + } + + + setToday() { + this.selectedDate = new Date() + this.getAppointmentsByDate(this.selectedDate); + this.appointmentForm.get('date').setValue(new TuiDay(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate())) + } + + getAppointmentsByDate(date: Date) { + this.appointmentService.getAppointmentsByDate(date).subscribe(appointments => { + this.appointments = appointments; + }) + } + + getInlineStyles(appointment: Appointment): { [key: string]: string } { + return { + '--duration': `${appointment.durationInMinutes}`, + '--start-minute': `${appointment.startMinute}`, + 'top': `${appointment.startMinute}px`, // Startpositie binnen het uur + 'height': `${appointment.durationInMinutes}px`, // Hoogte over meerdere uren + }; + } + + getFormattedTime(hour: number, minute: number): string { + return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; + } + + getAppointmentHeight(appointment: Appointment): number { + const startInMinutes = (appointment.startHour * 60) + appointment.startMinute; + const endInMinutes = (appointment.endHour * 60) + appointment.endMinute; + return (endInMinutes - startInMinutes); // 50px per uur + } + + selectAppointment(appointment: Appointment) { + this.selectedAppointment = appointment; + } + + protected readonly DateFormatter = DateFormatter; + + onDayClick(day: TuiDay) { + this.value = day; + this.selectedDate = new Date(day.year, day.month, day.day); + this.getAppointmentsByDate(this.selectedDate); + this.appointmentForm.get('date').setValue(new TuiDay(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate())) + this.toggleCalendar() + } + + toggleCalendar() { + this.showCalendar = !this.showCalendar + } + + closeNewItemModal() { + this.isModalOpen = false + this.resetForms() + } + + appointmentIsDeleted(appointment: Appointment) { + this.selectedAppointment = undefined; + this.alerts + .open(`Afspraak ${appointment.title} is verwijderd.`) + .subscribe(); + this.getAppointmentsByDate(this.selectedDate); + } + + appointmentIsEdited($event: Appointment) { + this.getAppointmentsByDate(this.selectedDate); + } +} + diff --git a/src/app/pages/dashboard/dashboard.component.html b/src/app/pages/dashboard/dashboard.component.html new file mode 100644 index 0000000..159fbb7 --- /dev/null +++ b/src/app/pages/dashboard/dashboard.component.html @@ -0,0 +1,265 @@ +
+ +
+ + +
+
+

+ Registration form + Tell us about yourself +

+
+ + + + +
+ + +
+
+
+

+ Sidebar content + Use CSS grid to position +

+
+
+
+
diff --git a/src/app/pages/dashboard/dashboard.component.scss b/src/app/pages/dashboard/dashboard.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/dashboard/dashboard.component.ts b/src/app/pages/dashboard/dashboard.component.ts new file mode 100644 index 0000000..eb97275 --- /dev/null +++ b/src/app/pages/dashboard/dashboard.component.ts @@ -0,0 +1,67 @@ +import {NgForOf, NgIf} from '@angular/common'; +import {Component, signal} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {RouterLink} from '@angular/router'; +import {TuiPortals, TuiRepeatTimes} from '@taiga-ui/cdk'; +import {TuiAppearance, TuiButton, TuiDataList, TuiDropdown, TuiLink, TuiTextfield, TuiTitle,} from '@taiga-ui/core'; +import {TuiBadge, TuiBreadcrumbs, TuiChevron, TuiDataListDropdownManager, TuiFade, TuiTabs,} from '@taiga-ui/kit'; +import {TuiCardLarge, TuiForm, TuiHeader, TuiNavigation} from '@taiga-ui/layout'; + +const ICON = + "data:image/svg+xml,%0A%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='32' height='32' rx='8' fill='url(%23paint0_linear_2036_35276)'/%3E%3Cmask id='mask0_2036_35276' style='mask-type:alpha' maskUnits='userSpaceOnUse' x='6' y='5' width='20' height='21'%3E%3Cpath d='M18.2399 9.36607C21.1347 10.1198 24.1992 9.8808 26 7.4922C26 7.4922 21.5645 5 16.4267 5C11.2888 5 5.36726 8.69838 6.05472 16.6053C6.38707 20.4279 6.65839 23.7948 6.65839 23.7948C8.53323 22.1406 9.03427 19.4433 8.97983 16.9435C8.93228 14.7598 9.55448 12.1668 12.1847 10.4112C14.376 8.94865 16.4651 8.90397 18.2399 9.36607Z' fill='url(%23paint1_linear_2036_35276)'/%3E%3Cpath d='M11.3171 20.2647C9.8683 17.1579 10.7756 11.0789 16.4267 11.0789C20.4829 11.0789 23.1891 12.8651 22.9447 18.9072C22.9177 19.575 22.9904 20.2455 23.2203 20.873C23.7584 22.3414 24.7159 24.8946 24.7159 24.8946C23.6673 24.5452 22.8325 23.7408 22.4445 22.7058L21.4002 19.921L21.2662 19.3848C21.0202 18.4008 20.136 17.7104 19.1217 17.7104H17.5319L17.6659 18.2466C17.9119 19.2306 18.7961 19.921 19.8104 19.921L22.0258 26H10.4754C10.7774 24.7006 12.0788 23.2368 11.3171 20.2647Z' fill='url(%23paint2_linear_2036_35276)'/%3E%3C/mask%3E%3Cg mask='url(%23mask0_2036_35276)'%3E%3Crect x='4' y='4' width='24' height='24' fill='white'/%3E%3C/g%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_2036_35276' x1='0' y1='0' x2='32' y2='32' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23A681D4'/%3E%3Cstop offset='1' stop-color='%237D31D4'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint1_linear_2036_35276' x1='6.0545' y1='24.3421' x2='28.8119' y2='3.82775' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.0001' stop-opacity='0.996458'/%3E%3Cstop offset='0.317708'/%3E%3Cstop offset='1' stop-opacity='0.32'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint2_linear_2036_35276' x1='6.0545' y1='24.3421' x2='28.8119' y2='3.82775' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0.0001' stop-opacity='0.996458'/%3E%3Cstop offset='0.317708'/%3E%3Cstop offset='1' stop-opacity='0.32'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A"; + + +@Component({ + selector: 'app-dashboard', + imports: [ + FormsModule, + NgForOf, + NgIf, + RouterLink, + TuiAppearance, + TuiBadge, + TuiBreadcrumbs, + TuiButton, + TuiCardLarge, + TuiChevron, + TuiDataList, + TuiDataListDropdownManager, + TuiDropdown, + TuiFade, + TuiForm, + TuiHeader, + TuiLink, + TuiNavigation, + TuiRepeatTimes, + TuiTabs, + TuiTextfield, + TuiTitle, + ], + templateUrl: './dashboard.component.html', + styleUrl: './dashboard.component.scss' +}) +export class DashboardComponent extends TuiPortals { + protected expanded = signal(false); + protected open = false; + protected switch = false; + protected readonly routes: any = {}; + protected readonly breadcrumbs = ['Home', 'Angular', 'Repositories', 'Taiga UI']; + + protected readonly drawer = { + Components: [ + {name: 'Button', icon: ICON}, + {name: 'Input', icon: ICON}, + {name: 'Tooltip', icon: ICON}, + ], + Essentials: [ + {name: 'Getting started', icon: ICON}, + {name: 'Showcase', icon: ICON}, + {name: 'Typography', icon: ICON}, + ], + }; + + protected handleToggle(): void { + this.expanded.update((e) => !e); + } + +} diff --git a/src/app/pages/home/home.component.html b/src/app/pages/home/home.component.html new file mode 100644 index 0000000..6532c47 --- /dev/null +++ b/src/app/pages/home/home.component.html @@ -0,0 +1,41 @@ + +
+ +
+
+ + + + +
+ + diff --git a/src/app/pages/home/home.component.scss b/src/app/pages/home/home.component.scss new file mode 100644 index 0000000..f383df7 --- /dev/null +++ b/src/app/pages/home/home.component.scss @@ -0,0 +1,62 @@ +.navbar { + background-color: #f8f9fa; + display: flex +} + +ul { + display: flex; + flex-direction: row; + justify-content: center; + list-style-type: none; + align-items: center; +} + +li { + margin: 0 10px; +} + +a { + text-decoration: none; + color: black; + font-size: 16px; +} + +img { + display: block; + max-width: 230px; + max-height: 95px; + width: auto; + height: auto; +} + +hr { + clear: both; + visibility: hidden; +} + +tui-avatar { + margin-right: 32px; + cursor: pointer; +} + +.active { + font-weight: bold; +} + +.dropdown { + font-size: 0.8125rem; + line-height: 1.25rem; + padding: 0.25rem 0.75rem; + margin-left: 4px; + margin-right: 4px; + margin-bottom: 8px; +} + +.t-content{ + margin-right: 24px; +} + +button{ + background: transparent; + border: none; +} diff --git a/src/app/pages/home/home.component.ts b/src/app/pages/home/home.component.ts new file mode 100644 index 0000000..f4aa5e1 --- /dev/null +++ b/src/app/pages/home/home.component.ts @@ -0,0 +1,55 @@ +import {Component, OnInit} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {Router, RouterLink, RouterLinkActive, RouterModule} from '@angular/router'; +import {TuiAvatar, TuiChevron,} from '@taiga-ui/kit'; +import {AuthService} from '../../services/auth.service'; +import {User} from '../../models/user'; +import {TuiButton, TuiDropdown} from '@taiga-ui/core'; +import {TuiActiveZone, TuiObscured} from '@taiga-ui/cdk'; + +@Component({ + selector: 'app-home', + imports: [TuiAvatar, RouterModule, FormsModule, RouterLink, RouterLinkActive, TuiDropdown, TuiObscured, TuiActiveZone, TuiButton, TuiChevron], + templateUrl: './home.component.html', + styleUrl: './home.component.scss' +}) +export class HomeComponent implements OnInit { + user: User + fullName: string + + getInitials(): string { + this.user = this.authService.getUserInfo(); + this.fullName = this.user.firstName + ' ' + this.user.lastName; + return this.fullName.split(' ').map((n) => n[0]).join('').substring(0, 2); + } + + protected open = false; + + protected onClick(): void { + this.open = !this.open; + } + + protected onObscured(obscured: boolean): void { + if (obscured) { + this.open = false; + } + } + + protected onActiveZone(active: boolean): void { + this.open = active && this.open; + } + + constructor(private authService: AuthService, private router: Router) { + } + + ngOnInit(): void { + if (!this.authService.isAuthenticated()) { + this.router.navigate(['login']); + } + } + + logout() { + this.authService.logout(); + this.router.navigate(['login']); + } +} diff --git a/src/app/pages/klanten/klanten.component.html b/src/app/pages/klanten/klanten.component.html new file mode 100644 index 0000000..5186ace --- /dev/null +++ b/src/app/pages/klanten/klanten.component.html @@ -0,0 +1,101 @@ +
+
+

Klanten

+
+
+
+ +
+ + + + + + + + + + + + + +
NaamEmail
{{ customer.firstName }} {{ customer.lastName }}{{ customer.email }}
+ +
+
+ + +
+ + Voornaam + + +
+ + + Achternaam + + +
+ + Email + + +
+ +
+
+ + + + + diff --git a/src/app/pages/klanten/klanten.component.scss b/src/app/pages/klanten/klanten.component.scss new file mode 100644 index 0000000..cf10c6d --- /dev/null +++ b/src/app/pages/klanten/klanten.component.scss @@ -0,0 +1,72 @@ +h1 { + text-align: center; +} + +h2 { + margin-left: 8px; +} + + +.heading { + justify-content: center; +} + +.content { + display: flex; + flex-direction: column; + justify-content: center; +} + +.styled-table { + width: 100%; /* Tabel breedte */ + border-collapse: collapse; /* Verwijdert dubbele randen */ + margin: 20px 0; /* Ruimte rondom de tabel */ + font-size: 16px; + text-align: left; /* Tekst uitlijning */ + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* Zachte schaduw */ + border-radius: 10px; /* Afgeronde hoeken */ + overflow: hidden; /* Hoeken correct afronden */ +} + +.styled-table thead { + background-color: #868383; /* Blauwe kopregel */ + color: #ffffff; /* Witte tekst */ + //text-transform: uppercase; /* Hoofdletters */ + font-weight: bold; +} + +.styled-table th, .styled-table td { + padding: 12px 15px; /* Ruimte binnen de cellen */ +} + +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; /* Lichte scheidingslijn */ +} + +.styled-table tbody tr:nth-child(even) { + background-color: #f3f3f3; /* Afwisselende rijkleuren */ +} + +.styled-table tbody tr:hover { + background-color: #d3d2d2; /* Hover effect */ + cursor: pointer; + transition: background-color 0.3s ease; +} + +.styled-table tbody tr:last-of-type { + border-bottom: none; /* Verwijdert de onderste scheidingslijn */ +} + +.toolbar { + button { + margin-left: 8px; + } + + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: center; + +} + + diff --git a/src/app/pages/klanten/klanten.component.ts b/src/app/pages/klanten/klanten.component.ts new file mode 100644 index 0000000..f53e35d --- /dev/null +++ b/src/app/pages/klanten/klanten.component.ts @@ -0,0 +1,71 @@ +import {Component, OnInit} from '@angular/core'; +import {NgForOf, NgIf} from '@angular/common'; +import {Customer} from '../../models/customer'; +import {CustomerService} from '../../services/customer.service'; +import {TuiButton, TuiIcon} from '@taiga-ui/core'; +import {ModalComponent} from '../../components/modal/modal.component'; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {TuiInputCopyModule, TuiInputModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy'; +import {CustomerDetailsComponent} from '../../components/customer-details/customer-details.component'; + +@Component({ + selector: 'app-klanten', + imports: [ + NgForOf, + TuiButton, + TuiIcon, + ModalComponent, + NgIf, + ReactiveFormsModule, + TuiInputCopyModule, + TuiInputModule, + TuiTextfieldControllerModule, + CustomerDetailsComponent + ], + templateUrl: './klanten.component.html', + styleUrl: './klanten.component.scss' +}) +export class KlantenComponent implements OnInit { + customers: Customer[]; + showNewCustomer: boolean = false; + customerForm: FormGroup; + selectedCustomer: Customer; + + constructor(private customerService: CustomerService) { + } + + ngOnInit(): void { + this.getCustomers(); + this.customerForm = new FormGroup({ + firstName: new FormControl('', Validators.required), + lastName: new FormControl('', Validators.required), + email: new FormControl('', [Validators.required, Validators.email]), + }) + } + + toggleCustomerModal() { + this.showNewCustomer = !this.showNewCustomer; + } + + saveCustomer() { + const firstName = this.customerForm.get('firstName').value + const lastName = this.customerForm.get('lastName').value + const email = this.customerForm.get('email').value + const customer = new Customer(firstName, lastName, email); + + this.customerService.addCustomer(customer).subscribe(() => { + this.showNewCustomer = false; + this.getCustomers() + }) + } + + getCustomers() { + this.customerService.getCustomers().subscribe(customers => { + this.customers = customers + }); + } + + selectCustomer(customer: Customer) { + this.selectedCustomer = customer + } +} diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html new file mode 100644 index 0000000..17a6b01 --- /dev/null +++ b/src/app/pages/login/login.component.html @@ -0,0 +1,23 @@ +
+
+
+
+ Logo +
+ + + + + + + + + +
+ +
+ +
+
diff --git a/src/app/pages/login/login.component.scss b/src/app/pages/login/login.component.scss new file mode 100644 index 0000000..74ccb57 --- /dev/null +++ b/src/app/pages/login/login.component.scss @@ -0,0 +1,49 @@ +.center-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + /* Adjust as needed */ +} + +tui-error{ + text-align: center; +} + +.header { + text-align: center; + margin-bottom: 28px; + max-width: 300px; + max-height: 300px; +} + +::ng-deep button.custom-button { + background-color: #222222 !important; + /* Pas kleur aan */ + color: white !important; + /* Tekstkleur aanpassen */ + width: 300px; +} + +.container { + max-width: 100%; + width: 100%; + height: 100%; + --s: 200px; /* control the size */ + --c1: #1d1d1d; + --c2: #4e4f51; + --c3: #3c3c3c; + + background: repeating-conic-gradient( + from 30deg, + #0000 0 120deg, + var(--c3) 0 180deg + ) calc(0.5 * var(--s)) calc(0.5 * var(--s) * 0.577), + repeating-conic-gradient( + from 30deg, + var(--c1) 0 60deg, + var(--c2) 0 120deg, + var(--c3) 0 180deg + ); + background-size: var(--s) calc(var(--s) * 0.577); +} diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts new file mode 100644 index 0000000..2309b5a --- /dev/null +++ b/src/app/pages/login/login.component.ts @@ -0,0 +1,57 @@ +import {Component} from '@angular/core'; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {Router} from '@angular/router'; +import {TuiAppearance, TuiButton, TuiError, TuiTextfield,} from '@taiga-ui/core'; +import {TuiCardLarge, TuiForm, TuiHeader} from '@taiga-ui/layout'; +import {AuthService} from '../../services/auth.service'; +import {UserDto} from '../../models/user-dto'; +import {HttpErrorResponse} from '@angular/common/http'; +import {TuiValidationError} from '@taiga-ui/cdk'; + +@Component({ + selector: 'app-login', + imports: [ + ReactiveFormsModule, + TuiAppearance, + TuiButton, + TuiCardLarge, + TuiForm, + TuiHeader, + TuiTextfield, + TuiError, + ], + templateUrl: './login.component.html', + styleUrl: './login.component.scss' +}) +export class LoginComponent { + form: FormGroup; + protected enabled = false; + + protected error = new TuiValidationError('Ongeldige gebruikersnaam of wachtwoord.'); + + protected get computedError(): TuiValidationError | null { + return this.enabled ? this.error : null; + } + + constructor(private router: Router, private authService: AuthService) { + this.form = new FormGroup({ + username: new FormControl('', Validators.required), + password: new FormControl('', Validators.required) + }); + } + + + login() { + this.authService.login(this.form.get('username').value, this.form.get('password').value).subscribe({ + next: (user: UserDto) => { + localStorage.setItem('token', user.token); + this.router.navigate(['/home/agenda']); + }, + error: (err: HttpErrorResponse) => { + if (err.status === 401) { + this.enabled = true; + } + }, + }); + } +} diff --git a/src/app/services/appointment.service.ts b/src/app/services/appointment.service.ts new file mode 100644 index 0000000..0bb0ff6 --- /dev/null +++ b/src/app/services/appointment.service.ts @@ -0,0 +1,45 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Appointment} from '../models/appointment'; + +@Injectable({ + providedIn: 'root', +}) +export class AppointmentService { + baseApi = "http://localhost:8080/api/appointments"; + // baseApi = "https://api.melvanveen.nl/api/appointments"; + + constructor(private http: HttpClient) { + } + + getAllAppointments() { + return this.http.get(`${this.baseApi}`); + } + + getAppointmentsByDate(date: Date) { + const day = date.getDate().toString().padStart(2, '0') + const month = (date.getMonth() + 1).toString().padStart(2, '0') + const year = date.getFullYear(); + return this.http.get(`${this.baseApi}/date?start=${year}-${month}-${day}`, {}); + } + + addAppointment(appointment: Appointment) { + return this.http.post(`${this.baseApi}`, appointment); + } + + deleteAppointment(appointment: Appointment) { + return this.http.delete(`${this.baseApi}?id=${appointment.id}`) + } + + updateAppointment(appointment: Appointment) { + return this.http.put(`${this.baseApi}`, appointment); + } + + getAppointment(id: string) { + return this.http.get(`${this.baseApi}/${id}`); + } + + getMostRecentAppointment(id: number) { + return this.http.get(`${this.baseApi}/recent/${id}`); + } +} diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..4757e1d --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { JwtHelperService } from '@auth0/angular-jwt'; +import { Observable } from 'rxjs'; +import {jwtDecode} from 'jwt-decode'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private baseApi = 'http://localhost:8080/api/auth/login'; + // baseApi = "https://api.melvanveen.nl/api/auth/login"; + jwtHelper = new JwtHelperService(); + + constructor(private http: HttpClient) {} + + login(username: string, password: string): Observable { + return this.http.post(this.baseApi, { username, password }); + } + + isAuthenticated(): boolean { + const token = localStorage.getItem('token'); + return token !== null && !this.jwtHelper.isTokenExpired(token); + } + + logout(): void { + localStorage.removeItem('token'); + } + + getToken(): string | null { + return localStorage.getItem('token'); + } + + getUserInfo(): any { + const token = this.getToken(); + if (token) { + return jwtDecode(token); + } + return null; + } +} + diff --git a/src/app/services/customer.service.ts b/src/app/services/customer.service.ts new file mode 100644 index 0000000..f83b141 --- /dev/null +++ b/src/app/services/customer.service.ts @@ -0,0 +1,24 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Customer} from '../models/customer'; +import {Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class CustomerService { + baseApi = "http://localhost:8080/api/customers"; + // baseApi = "https://api.melvanveen.nl/api/customers"; + + + constructor(private http: HttpClient) { + } + + getCustomers(): Observable { + return this.http.get(`${this.baseApi}`); + } + + addCustomer(customer: Customer): Observable { + return this.http.post(`${this.baseApi}`, customer); + } +} diff --git a/src/app/utils/date-formatter.ts b/src/app/utils/date-formatter.ts new file mode 100644 index 0000000..5601cba --- /dev/null +++ b/src/app/utils/date-formatter.ts @@ -0,0 +1,34 @@ +import {WeekDay} from '../models/week-day'; + +export class DateFormatter { + static getFormattedTime(hour: number, minute: number): string { + return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; + } + + static getHours(date: Date) { + let hours = date.getHours() + console.log(hours) + if (hours > 23) { + return 23 + } + return hours + } + + static getMinutes(date: Date) { + let minutes = date.getMinutes() + 30 + if (minutes > 59) { + return 59 + } + return minutes + } + + static getDate(dateString: string): string { + const date = new Date(dateString) + const weekDay = WeekDay[date.getDay()]; + const day = date.getDate().toString().padStart(2, '0'); // Dag met leading zero (01, 02, ..., 31) + const monthName = date.toLocaleString('nl-NL', {month: 'long'}); + const year = date.getFullYear().toString(); // Volledig jaar + + return `${weekDay} ${day} ${monthName} ${year}`; + } +} diff --git a/src/index.html b/src/index.html index 26e9ebc..9eaf429 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,6 @@ - + diff --git a/src/main.ts b/src/main.ts index 35b00f3..e4260ef 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ -import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; -import { AppComponent } from './app/app.component'; +import {bootstrapApplication} from '@angular/platform-browser'; +import {appConfig} from './app/app.config'; +import {AppComponent} from './app/app.component'; bootstrapApplication(AppComponent, appConfig) .catch((err) => console.error(err)); diff --git a/src/styles.scss b/src/styles.scss index 90d4ee0..afab0dd 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1 +1,18 @@ /* You can add global styles to this file, and also import other style files */ +.container { + max-width: 70vw; + margin: 0 auto; + padding: 20px; +} + +.heading { + margin-bottom: 12px; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.content { + overflow-y: scroll; + max-height: 70vh; +} diff --git a/tsconfig.json b/tsconfig.json index 5525117..268414f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", - "strict": true, + "strict": false, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true,