Compare commits

...
Author SHA1 Message Date
nessar 2cf2833683 refactor: improve type safety in DayInput test and remove unused heuristic counter in calendar service 2026-05-31 01:32:53 +02:00
nessar fb3f0ba800 feat: add date range selection to user input service and update translations 2026-05-31 01:29:07 +02:00
nessar 9c9114879d feat: implement internationalization with multi-language support and runtime language selection 2026-05-30 21:03:12 +02:00
nessar 2de09383b9 chore: install Node.js dependency and disable prerendering in build configuration 2026-05-30 18:16:11 +02:00
nessar 9a77665593 build: add oven/bun:latest image to build stage 2026-05-30 17:43:36 +02:00
nessar d22c8845a6 feat: add dayjs dependencies, adjust budget, and initialize agent verification rules 2026-05-30 17:05:59 +02:00
nessar a508c42694 chore: migrate vitest coverage provider from v8 to istanbul 2026-05-30 16:20:21 +02:00
nessar 7ce139c310 forks 2026-05-30 16:12:14 +02:00
nessar edc003ff49 chore: remove jasmine from compiler options types in tsconfig.spec.json 2026-05-30 16:07:24 +02:00
nessar 598e071bd6 style: update indentation in test-setup.ts to use tabs 2026-05-30 16:02:17 +02:00
nessar 11924df19e refactor: migrate testing framework from Karma/Jasmine to Vitest with Angular support 2026-05-30 15:57:36 +02:00
nessar 237d995e16 refactor: clean up karma configuration by removing unused launcher flags and simplifying reporter formatting 2026-05-30 15:45:45 +02:00
nessar 3a105097c3 feat: add Karma configuration with ChromeHeadlessCI launcher and update test CI pipeline 2026-05-30 15:40:16 +02:00
nessar 7702efc165 style: apply consistent code formatting and indentation to rules panel component 2026-05-30 15:26:28 +02:00
nessar 17afa909a9 chore: update healthcheck command to use bun instead of node in compose files 2026-05-30 15:03:59 +02:00
nessar 3b042fa4f5 chore: migrate build and CI pipeline from npm to bun 2026-05-30 15:01:57 +02:00
nessar 08ee84e5c9 feat: implement rules engine and management panel for custom holiday optimization 2026-05-30 15:00:07 +02:00
nessar c0549de4dd ci: switch to --password-stdin for secure docker registry login 2026-05-05 21:26:27 +02:00
nessar 4dd5ce6f5f ci: configure and use chromium-no-sandbox wrapper for test execution 2026-05-05 21:17:25 +02:00
nessar 9e99fc5511 refactor: modularize npm setup using hidden job template and add Chromium dependency for test stage 2026-05-05 21:12:50 +02:00
nessar e73b68e8d2 chore: migrate CI/CD pipeline from GitHub Actions to GitLab CI 2026-05-05 21:07:25 +02:00
dependabot[bot] 82e16e2598 chore(deps-dev): bump the development-dependencies group with 4 updates (#164)
Bumps the development-dependencies group with 4 updates: [@angular/build](https://github.com/angular/angular-cli), [@angular/cli](https://github.com/angular/angular-cli), [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@angular/build` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/v21.2.8/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.7...v21.2.8)

Updates `@angular/cli` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/v21.2.8/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.7...v21.2.8)

Updates `@angular/compiler-cli` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/compiler-cli)

Updates `typescript-eslint` from 8.59.0 to 8.59.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.59.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 22:12:11 +00:00
dependabot[bot] 58c13aa86c chore(deps): bump the angular group with 11 updates (#163)
Bumps the angular group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.9` | `21.2.10` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.7` | `21.2.8` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.9` | `21.2.10` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.9` | `21.2.10` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.9` | `21.2.10` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.9` | `21.2.10` |
| [@angular/material](https://github.com/angular/components) | `21.2.7` | `21.2.8` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.9` | `21.2.10` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.9` | `21.2.10` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.9` | `21.2.10` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `21.2.7` | `21.2.8` |


Updates `@angular/animations` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/animations)

Updates `@angular/cdk` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.7...v21.2.8)

Updates `@angular/common` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/common)

Updates `@angular/compiler` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/compiler)

Updates `@angular/core` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/core)

Updates `@angular/forms` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/forms)

Updates `@angular/material` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.7...v21.2.8)

Updates `@angular/platform-browser` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/platform-server)

Updates `@angular/router` from 21.2.9 to 21.2.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.10/packages/router)

Updates `@angular/ssr` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/v21.2.8/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.7...v21.2.8)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 22:11:19 +00:00
dependabot[bot] 52282705b3 chore(deps-dev): bump the development-dependencies group with 4 updates (#161)
Bumps the development-dependencies group with 4 updates: [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli), [eslint](https://github.com/eslint/eslint), [prettier](https://github.com/prettier/prettier) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@angular/compiler-cli` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/compiler-cli)

Updates `eslint` from 10.2.0 to 10.2.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.2.0...v10.2.1)

Updates `prettier` from 3.8.2 to 3.8.3
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.2...3.8.3)

Updates `typescript-eslint` from 8.58.2 to 8.59.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 10.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: prettier
  dependency-version: 3.8.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.59.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 20:37:22 +00:00
dependabot[bot] 3e5c1c4588 chore(deps): bump the angular group with 9 updates (#160)
Bumps the angular group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.8` | `21.2.9` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.6` | `21.2.7` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.8` | `21.2.9` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.8` | `21.2.9` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.8` | `21.2.9` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.8` | `21.2.9` |
| [@angular/material](https://github.com/angular/components) | `21.2.6` | `21.2.7` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.8` | `21.2.9` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.8` | `21.2.9` |


Updates `@angular/animations` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/animations)

Updates `@angular/cdk` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.6...v21.2.7)

Updates `@angular/common` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/common)

Updates `@angular/compiler` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/compiler)

Updates `@angular/core` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/core)

Updates `@angular/forms` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/forms)

Updates `@angular/material` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.6...v21.2.7)

Updates `@angular/platform-browser` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/platform-browser)

Updates `@angular/router` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/router)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 20:36:36 +00:00
dependabot[bot] 43083c1927 chore(deps): bump @angular/platform-server from 21.2.8 to 21.2.9 (#159)
Bumps [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) from 21.2.8 to 21.2.9.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/platform-server)

---
updated-dependencies:
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 00:21:58 +00:00
dependabot[bot] 6f897b263a chore(deps-dev): bump hono from 4.12.12 to 4.12.14 (#158)
Bumps [hono](https://github.com/honojs/hono) from 4.12.12 to 4.12.14.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.12...v4.12.14)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 02:44:45 +00:00
dependabot[bot] a580cdf571 chore(deps): bump follow-redirects from 1.15.11 to 1.16.0 (#157)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.11 to 1.16.0.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.11...v1.16.0)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 21:18:56 +00:00
dependabot[bot] 398964b8ab Bump date-holidays from 3.26.12 to 3.27.0 (#156)
Bumps [date-holidays](https://github.com/commenthol/date-holidays) from 3.26.12 to 3.27.0.
- [Changelog](https://github.com/commenthol/date-holidays/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commenthol/date-holidays/compare/v3.26.12...v3.27.0)

---
updated-dependencies:
- dependency-name: date-holidays
  dependency-version: 3.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 19:31:44 +00:00
dependabot[bot] 7a16c1f716 Bump the development-dependencies group with 8 updates (#155)
Bumps the development-dependencies group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.6` | `21.2.7` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.6` | `21.2.7` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.7` | `21.2.8` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.2` | `25.6.0` |
| [jasmine-core](https://github.com/jasmine/jasmine) | `6.1.0` | `6.2.0` |
| [jest-preset-angular](https://github.com/thymikee/jest-preset-angular) | `16.1.2` | `16.1.4` |
| [prettier](https://github.com/prettier/prettier) | `3.8.1` | `3.8.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.58.0` | `8.58.2` |


Updates `@angular/build` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.6...v21.2.7)

Updates `@angular/cli` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.6...v21.2.7)

Updates `@angular/compiler-cli` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/compiler-cli)

Updates `@types/node` from 25.5.2 to 25.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `jasmine-core` from 6.1.0 to 6.2.0
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v6.1.0...v6.2.0)

Updates `jest-preset-angular` from 16.1.2 to 16.1.4
- [Release notes](https://github.com/thymikee/jest-preset-angular/releases)
- [Changelog](https://github.com/thymikee/jest-preset-angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/thymikee/jest-preset-angular/compare/v16.1.2...v16.1.4)

Updates `prettier` from 3.8.1 to 3.8.2
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.1...3.8.2)

Updates `typescript-eslint` from 8.58.0 to 8.58.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.2/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: jasmine-core
  dependency-version: 6.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: jest-preset-angular
  dependency-version: 16.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: prettier
  dependency-version: 3.8.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.58.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 19:30:16 +00:00
dependabot[bot] 322b11c9bd Bump the angular group with 11 updates (#154)
Bumps the angular group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.7` | `21.2.8` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.5` | `21.2.6` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.7` | `21.2.8` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.7` | `21.2.8` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.7` | `21.2.8` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.7` | `21.2.8` |
| [@angular/material](https://github.com/angular/components) | `21.2.5` | `21.2.6` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.7` | `21.2.8` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.7` | `21.2.8` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.7` | `21.2.8` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `21.2.6` | `21.2.7` |


Updates `@angular/animations` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/animations)

Updates `@angular/cdk` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.5...v21.2.6)

Updates `@angular/common` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/common)

Updates `@angular/compiler` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/compiler)

Updates `@angular/core` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/core)

Updates `@angular/forms` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/forms)

Updates `@angular/material` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.5...v21.2.6)

Updates `@angular/platform-browser` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/platform-server)

Updates `@angular/router` from 21.2.7 to 21.2.8
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.8/packages/router)

Updates `@angular/ssr` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.6...v21.2.7)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 19:29:17 +00:00
dependabot[bot] b162a8512a Bump hono from 4.12.7 to 4.12.12 (#153)
Bumps [hono](https://github.com/honojs/hono) from 4.12.7 to 4.12.12.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.7...v4.12.12)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 05:23:44 +00:00
dependabot[bot] afc51e60de Bump @hono/node-server from 1.19.10 to 1.19.13 (#152)
Bumps [@hono/node-server](https://github.com/honojs/node-server) from 1.19.10 to 1.19.13.
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](https://github.com/honojs/node-server/compare/v1.19.10...v1.19.13)

---
updated-dependencies:
- dependency-name: "@hono/node-server"
  dependency-version: 1.19.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 03:14:38 +00:00
dependabot[bot] 0ad7aea819 Bump lodash from 4.17.23 to 4.18.1 (#151)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 18:27:38 +00:00
dependabot[bot] 789ff38b8c Bump the development-dependencies group with 5 updates (#150)
Bumps the development-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.5` | `21.2.6` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.5` | `21.2.6` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.6` | `21.2.7` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.0` | `25.5.2` |
| [eslint](https://github.com/eslint/eslint) | `10.1.0` | `10.2.0` |


Updates `@angular/build` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.5...v21.2.6)

Updates `@angular/cli` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.5...v21.2.6)

Updates `@angular/compiler-cli` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/compiler-cli)

Updates `@types/node` from 25.5.0 to 25.5.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.1.0 to 10.2.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.1.0...v10.2.0)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 18:20:37 +00:00
dependabot[bot] e29d54e3de Bump the angular group with 11 updates (#149)
Bumps the angular group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.6` | `21.2.7` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.4` | `21.2.5` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.6` | `21.2.7` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.6` | `21.2.7` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.6` | `21.2.7` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.6` | `21.2.7` |
| [@angular/material](https://github.com/angular/components) | `21.2.4` | `21.2.5` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.6` | `21.2.7` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.6` | `21.2.7` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.6` | `21.2.7` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `21.2.5` | `21.2.6` |


Updates `@angular/animations` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/animations)

Updates `@angular/cdk` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.4...v21.2.5)

Updates `@angular/common` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/common)

Updates `@angular/compiler` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/compiler)

Updates `@angular/core` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/core)

Updates `@angular/forms` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/forms)

Updates `@angular/material` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.4...v21.2.5)

Updates `@angular/platform-browser` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/platform-server)

Updates `@angular/router` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/router)

Updates `@angular/ssr` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.5...v21.2.6)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 18:19:39 +00:00
dependabot[bot] a873c6b6f9 Bump date-holidays from 3.26.11 to 3.26.12 (#148)
Bumps [date-holidays](https://github.com/commenthol/date-holidays) from 3.26.11 to 3.26.12.
- [Changelog](https://github.com/commenthol/date-holidays/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commenthol/date-holidays/compare/v3.26.11...v3.26.12)

---
updated-dependencies:
- dependency-name: date-holidays
  dependency-version: 3.26.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 19:43:15 +00:00
dependabot[bot] 7c05fb1944 Bump the development-dependencies group with 5 updates (#147)
Bumps the development-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.3` | `21.2.5` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.3` | `21.2.5` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.5` | `21.2.6` |
| [jest-preset-angular](https://github.com/thymikee/jest-preset-angular) | `16.1.1` | `16.1.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.2` | `8.58.0` |


Updates `@angular/build` from 21.2.3 to 21.2.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.3...v21.2.5)

Updates `@angular/cli` from 21.2.3 to 21.2.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.3...v21.2.5)

Updates `@angular/compiler-cli` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/compiler-cli)

Updates `jest-preset-angular` from 16.1.1 to 16.1.2
- [Release notes](https://github.com/thymikee/jest-preset-angular/releases)
- [Changelog](https://github.com/thymikee/jest-preset-angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/thymikee/jest-preset-angular/compare/v16.1.1...v16.1.2)

Updates `typescript-eslint` from 8.57.2 to 8.58.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: jest-preset-angular
  dependency-version: 16.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.58.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 19:42:11 +00:00
dependabot[bot] 0769892aba Bump the angular group with 11 updates (#146)
Bumps the angular group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.5` | `21.2.6` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.3` | `21.2.4` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.5` | `21.2.6` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.5` | `21.2.6` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.5` | `21.2.6` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.5` | `21.2.6` |
| [@angular/material](https://github.com/angular/components) | `21.2.3` | `21.2.4` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.5` | `21.2.6` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.5` | `21.2.6` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.5` | `21.2.6` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `21.2.3` | `21.2.5` |


Updates `@angular/animations` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/animations)

Updates `@angular/cdk` from 21.2.3 to 21.2.4
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.3...v21.2.4)

Updates `@angular/common` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/common)

Updates `@angular/compiler` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/compiler)

Updates `@angular/core` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/core)

Updates `@angular/forms` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/forms)

Updates `@angular/material` from 21.2.3 to 21.2.4
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.3...v21.2.4)

Updates `@angular/platform-browser` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/platform-server)

Updates `@angular/router` from 21.2.5 to 21.2.6
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.6/packages/router)

Updates `@angular/ssr` from 21.2.3 to 21.2.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.3...v21.2.5)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 19:41:14 +00:00
dependabot[bot] 5019b7bba8 Bump picomatch from 2.3.1 to 2.3.2 (#145)
Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2.
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 20:32:05 +00:00
dependabot[bot] 3c394a1823 Bump path-to-regexp from 8.3.0 to 8.4.0 (#144)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 8.3.0 to 8.4.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v8.3.0...v8.4.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-version: 8.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 20:31:54 +00:00
dependabot[bot] 0fa7368a92 Bump handlebars from 4.7.8 to 4.7.9 (#143)
Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.8 to 4.7.9.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.9/release-notes.md)
- [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.8...v4.7.9)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-version: 4.7.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-27 03:31:04 +00:00
CopilotandTHoguet cb5f440d02 Fix Docker publish not triggering after GITHUB_TOKEN-merged PRs (#142)
* Initial plan

* Fix Docker publish not running after GITHUB_TOKEN-merged PRs

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>
Agent-Logs-Url: https://github.com/THoguet/vac-optimizer/sessions/6d2692bd-36b2-4000-ade6-8136fa4bbd26

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>
2026-03-23 23:51:02 +01:00
dependabot[bot] 257ccab43d Bump the development-dependencies group with 5 updates (#140)
Bumps the development-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.2` | `21.2.3` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.2` | `21.2.3` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.4` | `21.2.5` |
| [eslint](https://github.com/eslint/eslint) | `10.0.3` | `10.1.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.1` | `8.57.2` |


Updates `@angular/build` from 21.2.2 to 21.2.3
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.2...v21.2.3)

Updates `@angular/cli` from 21.2.2 to 21.2.3
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.2...v21.2.3)

Updates `@angular/compiler-cli` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/compiler-cli)

Updates `eslint` from 10.0.3 to 10.1.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.0.3...v10.1.0)

Updates `typescript-eslint` from 8.57.1 to 8.57.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.2/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 10.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.57.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 18:39:07 +00:00
dependabot[bot] 59585328b2 Bump the angular group with 10 updates (#139)
Bumps the angular group with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.4` | `21.2.5` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.2` | `21.2.3` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.4` | `21.2.5` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.4` | `21.2.5` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.4` | `21.2.5` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.4` | `21.2.5` |
| [@angular/material](https://github.com/angular/components) | `21.2.2` | `21.2.3` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.4` | `21.2.5` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.4` | `21.2.5` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.4` | `21.2.5` |


Updates `@angular/animations` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/animations)

Updates `@angular/cdk` from 21.2.2 to 21.2.3
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.2...v21.2.3)

Updates `@angular/common` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/common)

Updates `@angular/compiler` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/compiler)

Updates `@angular/core` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/core)

Updates `@angular/forms` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/forms)

Updates `@angular/material` from 21.2.2 to 21.2.3
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.2...v21.2.3)

Updates `@angular/platform-browser` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/platform-server)

Updates `@angular/router` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.5/packages/router)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 18:38:06 +00:00
dependabot[bot] 180a9540d8 Bump flatted from 3.3.3 to 3.4.2 (#138)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 21:27:12 +00:00
dependabot[bot] 1471f0cc24 Bump @angular/ssr from 21.2.2 to 21.2.3 (#137)
Bumps [@angular/ssr](https://github.com/angular/angular-cli) from 21.2.2 to 21.2.3.
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.2...v21.2.3)

---
updated-dependencies:
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 21:25:41 +00:00
dependabot[bot] e69572c847 Bump date-holidays from 3.26.9 to 3.26.11 (#135)
Bumps [date-holidays](https://github.com/commenthol/date-holidays) from 3.26.9 to 3.26.11.
- [Changelog](https://github.com/commenthol/date-holidays/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commenthol/date-holidays/compare/v3.26.9...v3.26.11)

---
updated-dependencies:
- dependency-name: date-holidays
  dependency-version: 3.26.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 18:56:50 +00:00
dependabot[bot] 608874a11e Bump dayjs from 1.11.19 to 1.11.20 (#136)
Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.11.19 to 1.11.20.
- [Release notes](https://github.com/iamkun/dayjs/releases)
- [Changelog](https://github.com/iamkun/dayjs/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/iamkun/dayjs/compare/v1.11.19...v1.11.20)

---
updated-dependencies:
- dependency-name: dayjs
  dependency-version: 1.11.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 18:56:06 +00:00
dependabot[bot] 3b1a95c98a Bump the development-dependencies group with 6 updates (#134)
Bumps the development-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.1` | `21.2.2` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.1` | `21.2.2` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.2` | `21.2.4` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.4.0` | `25.5.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.2.0` | `30.3.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.0` | `8.57.1` |


Updates `@angular/build` from 21.2.1 to 21.2.2
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.1...v21.2.2)

Updates `@angular/cli` from 21.2.1 to 21.2.2
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.1...v21.2.2)

Updates `@angular/compiler-cli` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/compiler-cli)

Updates `@types/node` from 25.4.0 to 25.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `jest` from 30.2.0 to 30.3.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.3.0/packages/jest)

Updates `typescript-eslint` from 8.57.0 to 8.57.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: jest
  dependency-version: 30.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.57.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 18:55:45 +00:00
dependabot[bot] 99fc952dd1 Bump the angular group with 10 updates (#133)
Bumps the angular group with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.2` | `21.2.4` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.1` | `21.2.2` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.2` | `21.2.4` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.2` | `21.2.4` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.2` | `21.2.4` |
| [@angular/material](https://github.com/angular/components) | `21.2.1` | `21.2.2` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.2` | `21.2.4` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.2` | `21.2.4` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.2` | `21.2.4` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `21.2.1` | `21.2.2` |


Updates `@angular/animations` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/animations)

Updates `@angular/cdk` from 21.2.1 to 21.2.2
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.1...v21.2.2)

Updates `@angular/common` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/common)

Updates `@angular/core` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/core)

Updates `@angular/forms` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/forms)

Updates `@angular/material` from 21.2.1 to 21.2.2
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.1...v21.2.2)

Updates `@angular/platform-browser` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/platform-server)

Updates `@angular/router` from 21.2.2 to 21.2.4
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/router)

Updates `@angular/ssr` from 21.2.1 to 21.2.2
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.1...v21.2.2)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 18:54:40 +00:00
dependabot[bot] 7db0615f20 Bump @angular/compiler from 21.2.2 to 21.2.4 (#130)
Bumps [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) from 21.2.2 to 21.2.4.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.4/packages/compiler)

---
updated-dependencies:
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-14 02:58:24 +00:00
dependabot[bot] ca9477ad9e Bump tar from 7.5.10 to 7.5.11 (#129)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.10 to 7.5.11.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.10...v7.5.11)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 02:19:21 +00:00
dependabot[bot] 3d9fd8c334 Bump hono from 4.12.5 to 4.12.7 (#128)
Bumps [hono](https://github.com/honojs/hono) from 4.12.5 to 4.12.7.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.5...v4.12.7)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 02:03:41 +00:00
dependabot[bot] 95646daace Bump date-holidays from 3.26.8 to 3.26.9 (#127)
Bumps [date-holidays](https://github.com/commenthol/date-holidays) from 3.26.8 to 3.26.9.
- [Changelog](https://github.com/commenthol/date-holidays/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commenthol/date-holidays/compare/v3.26.8...v3.26.9)

---
updated-dependencies:
- dependency-name: date-holidays
  dependency-version: 3.26.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 19:53:10 +00:00
dependabot[bot] bacab0af5b Bump the development-dependencies group with 6 updates (#126)
Bumps the development-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.0` | `21.2.1` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.0` | `21.2.1` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.0` | `21.2.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.3.3` | `25.4.0` |
| [eslint](https://github.com/eslint/eslint) | `10.0.2` | `10.0.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.56.1` | `8.57.0` |


Updates `@angular/build` from 21.2.0 to 21.2.1
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.0...v21.2.1)

Updates `@angular/cli` from 21.2.0 to 21.2.1
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.0...v21.2.1)

Updates `@angular/compiler-cli` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/compiler-cli)

Updates `@types/node` from 25.3.3 to 25.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.0.2 to 10.0.3
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.0.2...v10.0.3)

Updates `typescript-eslint` from 8.56.1 to 8.57.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 10.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.57.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 19:51:26 +00:00
dependabot[bot] 4dd56650d8 Bump the angular group with 11 updates (#125)
Bumps the angular group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.0` | `21.2.2` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.0` | `21.2.1` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.0` | `21.2.2` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.0` | `21.2.2` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.0` | `21.2.2` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.0` | `21.2.2` |
| [@angular/material](https://github.com/angular/components) | `21.2.0` | `21.2.1` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.0` | `21.2.2` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.2.0` | `21.2.2` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.0` | `21.2.2` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `21.2.0` | `21.2.1` |


Updates `@angular/animations` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/animations)

Updates `@angular/cdk` from 21.2.0 to 21.2.1
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.0...v21.2.1)

Updates `@angular/common` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/common)

Updates `@angular/compiler` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/compiler)

Updates `@angular/core` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/core)

Updates `@angular/forms` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/forms)

Updates `@angular/material` from 21.2.0 to 21.2.1
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.2.0...v21.2.1)

Updates `@angular/platform-browser` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/platform-browser)

Updates `@angular/platform-server` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/platform-server)

Updates `@angular/router` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.2/packages/router)

Updates `@angular/ssr` from 21.2.0 to 21.2.1
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.2.0...v21.2.1)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 19:50:16 +00:00
dependabot[bot] 3302bc77a2 Bump aquasecurity/trivy-action from 0.34.2 to 0.35.0 (#124)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.34.2 to 0.35.0.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.34.2...0.35.0)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 19:48:54 +00:00
dependabot[bot] 6e571ec1b3 Bump tar from 7.5.9 to 7.5.10 (#120)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.9 to 7.5.10.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.9...v7.5.10)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 13:34:38 +00:00
dependabot[bot] 7eca27b629 Bump immutable from 5.1.4 to 5.1.5 (#119)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.4 to 5.1.5.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 23:50:21 +00:00
dependabot[bot] 9f9fe417c8 Bump @hono/node-server from 1.19.9 to 1.19.10 (#118)
Bumps [@hono/node-server](https://github.com/honojs/node-server) from 1.19.9 to 1.19.10.
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](https://github.com/honojs/node-server/compare/v1.19.9...v1.19.10)

---
updated-dependencies:
- dependency-name: "@hono/node-server"
  dependency-version: 1.19.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 21:19:24 +00:00
dependabot[bot] faea06f848 Bump hono from 4.12.3 to 4.12.5 (#117)
Bumps [hono](https://github.com/honojs/hono) from 4.12.3 to 4.12.5.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.3...v4.12.5)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 21:19:06 +00:00
dependabot[bot] 0c03df0039 Bump the development-dependencies group with 6 updates (#116)
Bumps the development-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `21.1.5` | `21.2.0` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.1.5` | `21.2.0` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.1.5` | `21.2.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.3.0` | `25.3.3` |
| [eslint](https://github.com/eslint/eslint) | `10.0.1` | `10.0.2` |
| [jest-preset-angular](https://github.com/thymikee/jest-preset-angular) | `16.0.0` | `16.1.1` |


Updates `@angular/build` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.1.5...v21.2.0)

Updates `@angular/cli` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.1.5...v21.2.0)

Updates `@angular/compiler-cli` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/compiler-cli)

Updates `@types/node` from 25.3.0 to 25.3.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.0.1 to 10.0.2
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.0.1...v10.0.2)

Updates `jest-preset-angular` from 16.0.0 to 16.1.1
- [Release notes](https://github.com/thymikee/jest-preset-angular/releases)
- [Changelog](https://github.com/thymikee/jest-preset-angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/thymikee/jest-preset-angular/compare/v16.0.0...v16.1.1)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 10.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: jest-preset-angular
  dependency-version: 16.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 20:12:39 +00:00
dependabot[bot] f0c4ccb194 Bump aquasecurity/trivy-action from 0.34.1 to 0.34.2 (#114)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.34.1 to 0.34.2.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.34.1...0.34.2)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-version: 0.34.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 20:11:05 +00:00
dependabot[bot] fc829ad0af Bump the angular group across 1 directory with 10 updates (#113)
Bumps the angular group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.1.5` | `21.2.0` |
| [@angular/cdk](https://github.com/angular/components) | `21.1.5` | `21.2.0` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.1.5` | `21.2.0` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.1.5` | `21.2.0` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.1.5` | `21.2.0` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.1.5` | `21.2.0` |
| [@angular/material](https://github.com/angular/components) | `21.1.5` | `21.2.0` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.1.5` | `21.2.0` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `21.1.5` | `21.2.0` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.1.5` | `21.2.0` |



Updates `@angular/animations` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/animations)

Updates `@angular/cdk` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.1.5...v21.2.0)

Updates `@angular/common` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/common)

Updates `@angular/compiler` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/compiler)

Updates `@angular/core` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/core)

Updates `@angular/forms` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/forms)

Updates `@angular/material` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/v21.1.5...v21.2.0)

Updates `@angular/platform-browser` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/platform-browser)

Updates `@angular/platform-server` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/platform-server)

Updates `@angular/router` from 21.1.5 to 21.2.0
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.0/packages/router)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 20:15:55 +00:00
dependabot[bot] b0266e31ea Bump @angular/ssr from 21.1.4 to 21.2.0 (#106)
Bumps [@angular/ssr](https://github.com/angular/angular-cli) from 21.1.4 to 21.2.0.
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.1.4...v21.2.0)

---
updated-dependencies:
- dependency-name: "@angular/ssr"
  dependency-version: 21.2.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 20:13:03 +00:00
dependabot[bot] 492cd3a34c Bump hono from 4.12.1 to 4.12.3 (#107)
Bumps [hono](https://github.com/honojs/hono) from 4.12.1 to 4.12.3.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.1...v4.12.3)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 20:13:01 +00:00
dependabot[bot] 96053b7f5d Bump aquasecurity/trivy-action from 0.34.0 to 0.34.1 (#102)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.34.0 to 0.34.1.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.34.0...0.34.1)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-version: 0.34.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 20:12:42 +00:00
dependabot[bot] 2013ac5256 Bump minimatch from 3.1.2 to 3.1.5 (#109)
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 20:12:08 +00:00
CopilotandTHoguet 9142cf56cf Add Dependabot auto-merge workflow for minor and patch updates (#112)
* Initial plan

* add Dependabot auto-merge workflow for minor and patch updates

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>
2026-03-01 21:08:03 +01:00
dependabot[bot] c05c08f994 Bump rollup from 4.58.0 to 4.59.0 (#110)
Bumps [rollup](https://github.com/rollup/rollup) from 4.58.0 to 4.59.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.58.0...v4.59.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.59.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 21:00:20 +01:00
dependabot[bot] 0aab8e1bb9 Bump the development-dependencies group with 4 updates (#104)
Bumps the development-dependencies group with 4 updates: [@angular/build](https://github.com/angular/angular-cli), [@angular/cli](https://github.com/angular/angular-cli), [jasmine-core](https://github.com/jasmine/jasmine) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@angular/build` from 21.1.4 to 21.1.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.1.4...v21.1.5)

Updates `@angular/cli` from 21.1.4 to 21.1.5
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/v21.1.4...v21.1.5)

Updates `jasmine-core` from 6.0.1 to 6.1.0
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v6.0.1...v6.1.0)

Updates `typescript-eslint` from 8.56.0 to 8.56.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.56.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 21.1.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.1.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: jasmine-core
  dependency-version: 6.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.56.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 21:45:43 +01:00
nessar 7127744454 fix: correct string concatenation in getClasses method 2026-02-21 17:36:50 +01:00
nessar 26d8bf5184 chore(deps): update Angular and related dependencies to version 21.1.x
- Updated Angular packages to version 21.1.5 for core modules, forms, and router.
- Updated Angular CDK, build, CLI, and compiler-cli to version 21.1.4.
- Updated express to version 5.2.1 and rxjs to version 7.8.2.
- Updated ESLint and related packages to the latest versions.
- Updated types for express and jasmine to the latest versions.
- Updated prettier and typescript to the latest versions.
- Updated karma and its plugins to the latest versions.
2026-02-21 17:29:20 +01:00
dependabot[bot] 9ff7cb503a Bump @types/node from 24.10.4 to 25.0.6 (#81)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.4 to 25.0.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.6
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:34:29 +01:00
dependabot[bot] 420dacf379 Bump lodash from 4.17.21 to 4.17.23 (#87)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:34:20 +01:00
dependabot[bot] a213caaf92 Bump date-holidays from 3.26.6 to 3.26.8 (#92)
Bumps [date-holidays](https://github.com/commenthol/date-holidays) from 3.26.6 to 3.26.8.
- [Changelog](https://github.com/commenthol/date-holidays/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commenthol/date-holidays/compare/v3.26.6...v3.26.8)

---
updated-dependencies:
- dependency-name: date-holidays
  dependency-version: 3.26.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:34:09 +01:00
dependabot[bot] cbdaa8991c Bump qs from 6.14.1 to 6.14.2 (#95)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:33:59 +01:00
dependabot[bot] 2caab553aa Bump aquasecurity/trivy-action from 0.33.1 to 0.34.0 (#96)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.33.1 to 0.34.0.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.33.1...0.34.0)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:33:55 +01:00
dependabot[bot] 7c1538ee66 Bump aquasecurity/trivy-action (#97)
Bumps the github_actions group with 1 update in the /.github/workflows directory: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action).


Updates `aquasecurity/trivy-action` from 0.33.1 to 0.34.0
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.33.1...0.34.0)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-version: 0.34.0
  dependency-type: direct:production
  dependency-group: github_actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:33:51 +01:00
dependabot[bot] 1ff20d4fcd Bump tar from 7.5.2 to 7.5.9 (#98)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.2 to 7.5.9.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.2...v7.5.9)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:33:47 +01:00
dependabot[bot] 2435f3f01b Bump @angular/core from 21.0.6 to 21.1.5 (#99)
Bumps [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) from 21.0.6 to 21.1.5.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.1.5/packages/core)

---
updated-dependencies:
- dependency-name: "@angular/core"
  dependency-version: 21.1.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-21 16:33:44 +01:00
dependabot[bot] 6078b552dd Bump actions/upload-artifact from 5 to 6 (#72)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-07 18:46:29 +01:00
nessar d07375b10f npm update 2026-01-07 18:44:13 +01:00
nessar 5769d2e653 remove loading for each change in "form" 2025-12-06 22:52:20 +01:00
nessar d6f5b7a7ed remove wrap useless 2025-12-06 22:28:34 +01:00
nessar d70489b111 chore: add peer dependencies to various packages in package-lock.json 2025-12-06 22:26:50 +01:00
dependabot[bot] d80749489f Bump jasmine-core (#70)
Bumps the development-dependencies group with 1 update in the / directory: [jasmine-core](https://github.com/jasmine/jasmine).


Updates `jasmine-core` from 5.12.1 to 5.13.0
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v5.12.1...v5.13.0)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-version: 5.13.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-06 22:25:54 +01:00
dependabot[bot] 8ce6582123 Bump actions/checkout from 4 to 6 (#58) 2025-12-06 21:21:27 +00:00
nessar 2b5468090a prettier 2025-12-06 22:19:44 +01:00
nessar 327255b466 chore: update dependencies and devDependencies in package.json
- Updated jest-preset-angular from ^15.0.3 to ^16.0.0
2025-12-06 22:18:12 +01:00
Théophyl Hoguetandcopilot-swe-agent[bot] d3515f89e5 T hoguet/issue16 (#61)
* feat: enhance loading spinner with progress indication and add warning for unplaced vacation days

* feat: upgrade Angular dependencies to version 21 and implement new animation features

- Updated Angular dependencies in package.json to version 21.x.x.
- Added `provideAnimationsAsync` to app configuration for improved animation handling.
- Refactored user input component to utilize a dynamic list of vacation days with animations.
- Introduced a new `DayInput` component for managing individual vacation days.
- Enhanced styling and animations for user input and day input components.
- Updated calendar service to support new vacation day structure.
- Adjusted tests to accommodate changes in component structure and dependencies.

* feat: enhance vacation optimization logic and UI components

* Fix signal immutability violations and cleanup unused code (#62)

* Initial plan

* fix: address PR review comments for signal immutability and cleanup

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

* fix: add --legacy-peer-deps for Angular 21 compatibility (#63)

* Initial plan

* fix: add --legacy-peer-deps to npm ci for Angular 21 compatibility

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

* Fix unit tests for VacDay[] type migration (#64)

* Initial plan

* fix: update unit tests to match VacDay[] type structure

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2025-11-30 03:17:57 +01:00
3272622abf feat: enhance loading spinner with progress indication and add warnin… (#49)
* feat: enhance loading spinner with progress indication and add warning for unplaced vacation days

* Update src/app/services/calendar-service.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/app/components/calendar-year/calendar-year.scss

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/app/components/user-input/user-input.html

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix setTimeout async anti-pattern in calendar-year component (#50)

* Initial plan

* fix: replace setTimeout async anti-pattern with queueMicrotask and void

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2025-11-18 00:45:05 +01:00
nessar 2d5cccff0c chore(deps): update Angular packages to version 20.3.10 2025-11-17 18:56:20 +01:00
dependabot[bot] f30f862aac Bump js-yaml from 3.14.1 to 3.14.2 (#47)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.14.1 to 3.14.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 3.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:51:14 +01:00
dependabot[bot] f3d3192aec Bump the development-dependencies group with 6 updates (#40)
Bumps the development-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/build](https://github.com/angular/angular-cli) | `20.3.8` | `20.3.9` |
| [@angular/cli](https://github.com/angular/angular-cli) | `20.3.8` | `20.3.9` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `20.3.9` | `20.3.10` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.0` | `9.39.1` |
| [eslint](https://github.com/eslint/eslint) | `9.39.0` | `9.39.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.46.3` | `8.46.4` |


Updates `@angular/build` from 20.3.8 to 20.3.9
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/20.3.8...20.3.9)

Updates `@angular/cli` from 20.3.8 to 20.3.9
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/20.3.8...20.3.9)

Updates `@angular/compiler-cli` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/compiler-cli)

Updates `@eslint/js` from 9.39.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

Updates `eslint` from 9.39.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.0...v9.39.1)

Updates `typescript-eslint` from 8.46.3 to 8.46.4
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.4/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/build"
  dependency-version: 20.3.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 20.3.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 20.3.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.46.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:50:58 +01:00
dependabot[bot] 97d7e520fc Bump the angular group with 10 updates (#39)
Bumps the angular group with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `20.2.11` | `20.2.12` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `20.3.9` | `20.3.10` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `20.3.9` | `20.3.10` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `20.3.9` | `20.3.10` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `20.3.9` | `20.3.10` |
| [@angular/material](https://github.com/angular/components) | `20.2.11` | `20.2.12` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `20.3.9` | `20.3.10` |
| [@angular/platform-server](https://github.com/angular/angular/tree/HEAD/packages/platform-server) | `20.3.9` | `20.3.10` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `20.3.9` | `20.3.10` |
| [@angular/ssr](https://github.com/angular/angular-cli) | `20.3.8` | `20.3.9` |


Updates `@angular/cdk` from 20.2.11 to 20.2.12
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/20.2.11...20.2.12)

Updates `@angular/common` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/common)

Updates `@angular/compiler` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/compiler)

Updates `@angular/core` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/core)

Updates `@angular/forms` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/forms)

Updates `@angular/material` from 20.2.11 to 20.2.12
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/20.2.11...20.2.12)

Updates `@angular/platform-browser` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/platform-browser)

Updates `@angular/platform-server` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/platform-server)

Updates `@angular/router` from 20.3.9 to 20.3.10
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/20.3.10/packages/router)

Updates `@angular/ssr` from 20.3.8 to 20.3.9
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/20.3.8...20.3.9)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-version: 20.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/material"
  dependency-version: 20.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-server"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 20.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/ssr"
  dependency-version: 20.3.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:50:50 +01:00
dependabot[bot] 9eeab40755 Bump node from 20-alpine to 25-alpine (#46)
Bumps node from 20-alpine to 25-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 25-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:49:03 +01:00
dependabot[bot] a06b2077a0 Bump github/codeql-action from 3 to 4 (#45)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:48:51 +01:00
dependabot[bot] a850869282 Bump actions/setup-node from 4 to 6 (#44)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:48:38 +01:00
dependabot[bot] 8b36565816 Bump codecov/codecov-action from 4 to 5 (#43)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:48:27 +01:00
dependabot[bot] 437d5fca9c Bump aquasecurity/trivy-action from 0.27.0 to 0.33.1 (#42)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.27.0 to 0.33.1.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.27.0...0.33.1)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-version: 0.33.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:48:17 +01:00
dependabot[bot] b86121efac Bump actions/upload-artifact from 4 to 5 (#41)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 18:48:05 +01:00
Théophyl HoguetandCopilot 81d974cbfe T hoguet/issue5 (#37)
* enregistrer les calendar settings dans le localstorage
Fixes #5

* Refactor calendar settings service to use a definitions map for settings and update related components to access settings via a getter method

* Update src/app/services/calendar-settings-service.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor calendar settings service to utilize a new storedSignal implementation for localStorage persistence and restore the stored-signal utility

* Update typescript-eslint to version 8.46.3 and enhance error handling in storedSignal function

* Update src/app/services/calendar-settings-service.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Enhance storedSignal function to accept an Injector for improved context handling in localStorage persistence

* Skip initial execution in storedSignal effect to prevent unnecessary localStorage writes

* Refactor get method in CalendarSettingsService to throw an error if the setting ID doesn't exist

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-07 00:19:38 +01:00
Théophyl Hoguet 1922b9acb2 [BUG] click behavior on tooltip day on mobile, slide not working (#36)
Fixes #34
2025-11-06 21:07:58 +01:00
729cc3f4cf Refactor calendar settings dialog to use dynamic settings from service (#30)
* Refactor calendar settings dialog to use dynamic settings from service

* Update src/app/components/dialog/calendar-settings-dialog/calendar-settings-dialog.html

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add type definition for calendar settings array (#35)

* Initial plan

* Add type definition for calendar settings array

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

* use inject

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2025-11-06 19:54:43 +01:00
dependabot[bot] f2b4d36709 Bump @types/node from 20.19.24 to 24.10.0 (#27)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.19.24 to 24.10.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.10.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 00:22:44 +01:00
dependabot[bot] 104bdb273d Bump jasmine-core in the development-dependencies group (#26)
Bumps the development-dependencies group with 1 update: [jasmine-core](https://github.com/jasmine/jasmine).


Updates `jasmine-core` from 5.9.0 to 5.12.1
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v5.9.0...v5.12.1)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-version: 5.12.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 00:22:33 +01:00
Théophyl Hoguet 000f37ec56 Change Trivy image reference in workflow 2025-11-04 00:19:01 +01:00
Théophyl Hoguet a2b5329716 Update image reference for Trivy vulnerability scanner 2025-11-04 00:16:37 +01:00
Théophyl Hoguet cef670955a Update image reference for Trivy vulnerability scan 2025-11-04 00:12:19 +01:00
Théophyl Hoguet 2027b412b3 Update image reference for Trivy scanner step 2025-11-04 00:09:34 +01:00
Théophyl Hoguet 6556dd7cf2 Update image reference for Trivy vulnerability scan 2025-11-04 00:02:57 +01:00
CopilotandTHoguet f7bd101fb7 Modernize CI/CD pipeline with comprehensive automation (#25)
* Initial plan

* Add comprehensive CI/CD workflows and tooling

- Add PR validation workflow with lint, format, type check, test, and build steps
- Improve Docker build workflow with caching, security scanning, and manual trigger
- Add CodeQL security analysis workflow
- Add Dependabot configuration for automated dependency updates
- Configure ESLint with TypeScript support
- Add npm scripts for linting and formatting
- Format code with Prettier
- Fix linting issues in source code

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

* Update README and fix build configuration

- Add CI/CD documentation to README
- Document code quality tools and scripts
- Disable font optimization in production build to avoid network dependency
- Add build cache configuration for GitHub Actions

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>
2025-11-03 21:09:09 +01:00
76 changed files with 7594 additions and 15624 deletions

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
---
trigger: always_on
---
# Agent Rules and Verification Constraints
All AI agents (including Cline, Antigravity, Cursor, and Copilot) must adhere to the following rules before declaring a task complete:
1. **Test Coverage (>= 80%)**:
- The overall project code coverage (Statements, Lines, and Functions) must be maintained at **80% or higher**.
- Verify coverage by running:
```bash
bun run test --watch=false --coverage
```
- If your changes introduce new code or modify existing code in a way that drops the coverage below 80%, you must write appropriate unit tests in `.spec.ts` files to bring it back up.
2. **Code Formatting (Prettier)**:
- All code files must be correctly formatted according to the project's Prettier config before finishing.
- Run the formatter with:
```bash
bun run format
```
3. **Successful Build**:
- The project must always build successfully for production without compilation or bundling errors before ending.
- Verify the build with:
```bash
bun run build
```
4. **GitLab Pipeline Checks**:
- You must execute the local equivalent of the GitLab CI/CD pipeline steps before completing your task. All of the following commands must pass:
- **Linting check**: `bun run lint` (or use `bun run lint:fix` to resolve auto-fixable issues)
- **Formatting check**: `bun run format:check`
- **TypeScript Type check**: `bunx tsc --noEmit`
- **Unit Tests and Coverage**: `bun run test --watch=false --coverage`
- **Production Build**: `bun run build -- --configuration production`
+33
View File
@@ -0,0 +1,33 @@
# Agent Rules and Verification Constraints
All AI agents (including Cline, Antigravity, Cursor, and Copilot) must adhere to the following rules before declaring a task complete:
1. **Test Coverage (>= 80%)**:
- The overall project code coverage (Statements, Lines, and Functions) must be maintained at **80% or higher**.
- Verify coverage by running:
```bash
bun run test --watch=false --coverage
```
- If your changes introduce new code or modify existing code in a way that drops the coverage below 80%, you must write appropriate unit tests in `.spec.ts` files to bring it back up.
2. **Code Formatting (Prettier)**:
- All code files must be correctly formatted according to the project's Prettier config before finishing.
- Run the formatter with:
```bash
bun run format
```
3. **Successful Build**:
- The project must always build successfully for production without compilation or bundling errors before ending.
- Verify the build with:
```bash
bun run build
```
4. **GitLab Pipeline Checks**:
- You must execute the local equivalent of the GitLab CI/CD pipeline steps before completing your task. All of the following commands must pass:
- **Linting check**: `bun run lint` (or use `bun run lint:fix` to resolve auto-fixable issues)
- **Formatting check**: `bun run format:check`
- **TypeScript Type check**: `bunx tsc --noEmit`
- **Unit Tests and Coverage**: `bun run test --watch=false --coverage`
- **Production Build**: `bun run build -- --configuration production`
-47
View File
@@ -1,47 +0,0 @@
You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
## TypeScript Best Practices
- Use strict type checking
- Prefer type inference when the type is obvious
- Avoid the `any` type; use `unknown` when type is uncertain
## Angular Best Practices
- Always use standalone components over NgModules
- Must NOT set `standalone: true` inside Angular decorators. It's the default.
- Use signals for state management
- Implement lazy loading for feature routes
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
- Use `NgOptimizedImage` for all static images.
- `NgOptimizedImage` does not work for inline base64 images.
## Components
- Keep components small and focused on a single responsibility
- Use `input()` and `output()` functions instead of decorators
- Use `computed()` for derived state
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
- Prefer inline templates for small components
- Prefer Reactive forms instead of Template-driven ones
- Do NOT use `ngClass`, use `class` bindings instead
- Do NOT use `ngStyle`, use `style` bindings instead
## State Management
- Use signals for local component state
- Use `computed()` for derived state
- Keep state transformations pure and predictable
- Do NOT use `mutate` on signals, use `update` or `set` instead
## Templates
- Keep templates simple and avoid complex logic
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
- Use the async pipe to handle observables
## Services
- Design services around a single responsibility
- Use the `providedIn: 'root'` option for singleton services
- Use the `inject()` function instead of constructor injection
-101
View File
@@ -1,101 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '27 7 * * 2'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: javascript-typescript
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- name: Run manual build steps
if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
-43
View File
@@ -1,43 +0,0 @@
name: Docker Build and Push
on:
push:
branches: [main]
release:
types: [published]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+3
View File
@@ -41,3 +41,6 @@ __screenshots__/
# System files
.DS_Store
Thumbs.db
# Auto-generated i18n files
/src/app/i18n-registry.ts
+109
View File
@@ -0,0 +1,109 @@
image: oven/bun:latest
stages:
- validate
- test
- build
- security
- docker
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ''
cache:
key:
files:
- bun.lock
paths:
- .bun/
.node_setup:
before_script:
- bun install --frozen-lockfile
# -- Validate Stage --
lint:
extends: .node_setup
stage: validate
script:
- bunx eslint "src/**/*.ts"
prettier:
extends: .node_setup
stage: validate
script:
- bunx prettier --check "src/**/*.{ts,html,scss,css,json}"
type-check:
extends: .node_setup
stage: validate
script:
- bunx tsc --noEmit
# -- Test Stage --
test:
extends: .node_setup
stage: test
script:
- bun run test --watch=false --coverage
artifacts:
when: always
paths:
- coverage/
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
expire_in: 7 days
# -- Build Stage --
build:
image: oven/bun:latest
extends: .node_setup
stage: build
script:
- apt-get update && apt-get install -y nodejs
- bun run build -- --configuration production
artifacts:
paths:
- dist/
expire_in: 7 days
# -- Security Stage --
include:
- template: Security/SAST.gitlab-ci.yml
trivy_scan:
stage: security
image:
name: aquasec/trivy:latest
entrypoint: ['']
before_script: [] # override global before_script
script:
# Trivy will scan the project directory
- trivy fs --format sarif --output trivy-results.sarif .
artifacts:
reports:
sast: trivy-results.sarif
expire_in: 7 days
# -- Docker Stage --
docker-build:
stage: docker
image: docker:24.0.5
services:
- docker:24.0.5-dind
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" -t "$CI_REGISTRY_IMAGE:latest" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
- docker push "$CI_REGISTRY_IMAGE:latest"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+12 -9
View File
@@ -1,32 +1,35 @@
# Stage 1: Build the Angular application
FROM node:20-alpine AS builder
FROM oven/bun:alpine AS builder
# Install nodejs to support Angular prerendering worker threads
RUN apk add --no-cache nodejs
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY package.json bun.lock* ./
# Install dependencies
RUN npm ci --only=production=false
RUN bun install
# Copy source code
COPY . .
# Build the application for production
RUN npm run build
RUN bun run build
# Stage 2: Production image
FROM node:20-alpine AS production
FROM oven/bun:alpine AS production
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY package.json bun.lock* ./
# Install only production dependencies
RUN npm ci --only=production && npm cache clean --force
RUN bun install --production
# Copy built application from builder stage
COPY --from=builder /app/dist ./dist
@@ -39,7 +42,7 @@ EXPOSE 4000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:4000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD bun -e "require('http').get('http://localhost:4000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Start the application
CMD ["node", "dist/vac-optimizer/server/server.mjs"]
CMD ["bun", "run", "dist/vac-optimizer/server/server.mjs"]
+45 -4
View File
@@ -7,11 +7,43 @@ This project was generated using [Angular CLI](https://github.com/angular/angula
To start a local development server, run:
```bash
ng serve
bun start
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code Quality
This project uses ESLint and Prettier to maintain code quality and consistency.
### Linting
Check for linting errors:
```bash
bun run lint
```
Auto-fix linting issues:
```bash
bun run lint:fix
```
### Formatting
Check code formatting:
```bash
bun run format:check
```
Format code:
```bash
bun run format
```
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
@@ -31,17 +63,17 @@ ng generate --help
To build the project run:
```bash
ng build
bun run build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
To execute unit tests, use the following command:
```bash
ng test
bun run test
```
## Running end-to-end tests
@@ -54,6 +86,15 @@ ng e2e
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## CI/CD
This project uses GitHub Actions for continuous integration and deployment:
- **PR Validation**: Automatically runs on pull requests to validate code quality, run tests, and build the application
- **Docker Build & Push**: Builds and publishes Docker images to GitHub Container Registry on main branch pushes and releases
- **CodeQL Analysis**: Runs security analysis weekly and on pull requests
- **Dependabot**: Automatically updates dependencies weekly
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
+15 -17
View File
@@ -26,16 +26,22 @@
"input": "public"
}
],
"styles": ["src/styles.scss"],
"styles": [
"src/styles.scss"
],
"server": "src/main.server.ts",
"outputMode": "server",
"ssr": {
"entry": "src/server.ts"
},
"prerender": false,
"allowedCommonJsDependencies": [
"deepmerge",
"moment-timezone",
"jalaali-js"
"jalaali-js",
"dayjs",
"dayjs/plugin/weekOfYear",
"dayjs/plugin/isoWeek"
]
},
"configurations": {
@@ -43,7 +49,7 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "2MB",
"maximumWarning": "2.5MB",
"maximumError": "3MB"
},
{
@@ -52,7 +58,10 @@
"maximumError": "16kB"
}
],
"outputHashing": "all"
"outputHashing": "all",
"optimization": {
"fonts": false
}
},
"development": {
"optimization": false,
@@ -78,20 +87,9 @@
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": ["src/styles.scss"]
}
"builder": "@analogjs/vitest-angular:test"
}
}
}
}
}
}
+3234
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -15,7 +15,7 @@ services:
test:
[
'CMD',
'node',
'bun',
'-e',
"require('http').get('http://localhost:4000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})",
]
+1 -1
View File
@@ -15,7 +15,7 @@ services:
test:
[
'CMD',
'node',
'bun',
'-e',
"require('http').get('http://localhost:4000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})",
]
+34
View File
@@ -0,0 +1,34 @@
import eslintJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
export default [
eslintJs.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
files: ['**/*.ts'],
plugins: {
prettier: prettierPlugin,
},
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
},
{
ignores: ['dist/**', 'node_modules/**', '.angular/**', 'coverage/**'],
},
];
-14691
View File
File diff suppressed because it is too large. Load diff
+51 -38
View File
@@ -3,11 +3,15 @@
"version": "1.5.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"serve:ssr:vac-optimizer": "node dist/vac-optimizer/server/server.mjs"
"start": "bun scripts/generate-langs.js && ng serve",
"build": "bun scripts/generate-langs.js && ng build",
"watch": "bun scripts/generate-langs.js && ng build --watch --configuration development",
"test": "bun scripts/generate-langs.js && ng test",
"serve:ssr:vac-optimizer": "bun dist/vac-optimizer/server/server.mjs",
"lint": "bun scripts/generate-langs.js && eslint \"src/**/*.ts\"",
"lint:fix": "bun scripts/generate-langs.js && eslint \"src/**/*.ts\" --fix",
"format": "bun scripts/generate-langs.js && prettier --write \"src/**/*.{ts,html,scss,css,json}\"",
"format:check": "bun scripts/generate-langs.js && prettier --check \"src/**/*.{ts,html,scss,css,json}\""
},
"prettier": {
"printWidth": 100,
@@ -23,42 +27,51 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "^20.2.10",
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.7",
"@angular/forms": "^20.3.0",
"@angular/material": "^20.2.10",
"@angular/platform-browser": "^20.3.0",
"@angular/platform-server": "^20.3.0",
"@angular/router": "^20.3.0",
"@angular/ssr": "^20.3.7",
"date-holidays": "^3.26.2",
"dayjs": "^1.11.19",
"express": "^5.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
"@angular/animations": "21.2.15",
"@angular/cdk": "^21.2.13",
"@angular/common": "21.2.15",
"@angular/compiler": "21.2.15",
"@angular/core": "21.2.15",
"@angular/forms": "21.2.15",
"@angular/material": "^21.2.13",
"@angular/platform-browser": "21.2.15",
"@angular/platform-browser-dynamic": "21.2.15",
"@angular/platform-server": "21.2.15",
"@angular/router": "21.2.15",
"@angular/ssr": "21.2.13",
"date-holidays": "^3.30.2",
"dayjs": "^1.11.21",
"express": "^5.2.1",
"rxjs": "~7.8.2",
"tslib": "^2.8.1"
},
"devDependencies": {
"@angular/build": "^20.3.7",
"@angular/cli": "^20.3.7",
"@angular/compiler-cli": "^20.3.0",
"@types/express": "^5.0.1",
"@types/jasmine": "~5.1.0",
"@analogjs/platform": "^2.5.2",
"@analogjs/vite-plugin-angular": "^2.5.2",
"@analogjs/vitest-angular": "^2.5.2",
"@angular/build": "21.2.13",
"@angular/cli": "21.2.13",
"@angular/compiler-cli": "21.2.15",
"@eslint/js": "^10.0.1",
"@nx/devkit": "^22.7.5",
"@nx/vite": "^22.0.0",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/node": "^20.17.19",
"eslint": "^9.38.0",
"@types/node": "^25.9.1",
"@vitest/coverage-istanbul": "^4.1.7",
"@vitest/coverage-v8": "^4.0.0",
"@vitest/ui": "^4.0.0",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"jasmine-core": "~5.9.0",
"jest": "^30.2.0",
"jest-preset-angular": "^15.0.3",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.6.2",
"typescript": "~5.9.2"
"eslint-plugin-prettier": "^5.5.6",
"jest": "^30.4.2",
"jest-preset-angular": "^16.1.5",
"jsdom": "^22.0.0",
"prettier": "^3.8.3",
"typescript": "~6.0.3",
"typescript-eslint": "^8.60.0",
"vite": "^7.0.0",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^4.0.0"
}
}
+52
View File
@@ -0,0 +1,52 @@
import "@angular/compiler";
import { CalendarService, SelectedDates, DayType, SelectableDayType } from './src/app/services/calendar-service';
import { DateCacheService } from './src/app/services/date-cache.service';
import { RuleService } from './src/app/services/rule-service';
const cs = new CalendarService();
cs['dateCache'] = {
getTodayComponents: () => {
const d = new Date();
return { date: d.getDate(), month: d.getMonth(), year: d.getFullYear() };
}
} as any;
const sd = cs.getSelectedDatesForPeriod(new Date(), new Date(new Date().setFullYear(new Date().getFullYear() + 1)));
console.log("Selected dates length:", sd._datesSelected.length);
const rules = [
{
id: 'default-samedi-malin',
name: 'Samedi Malin',
type: 'if_then',
enabled: true,
dayOfWeek: 6,
placeDays: 2,
placeType: 'cp',
placement: 'auto',
rewardDays: 1,
rewardType: 'other'
},
{
id: 'default-christmas',
name: 'Vacances de Noël',
type: 'time_window',
enabled: true,
startDate: '2026-12-19T23:00:00.000Z',
endDate: '2027-01-02T23:00:00.000Z',
placeDays: 5,
placeType: 'any'
}
] as any;
const vacs = [
{ id: '1', type: 'cp', numberOfDays: 5, expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)) },
{ id: '2', type: 'rtt', numberOfDays: 5, expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)) },
];
sd.optimizeVacations(vacs, rules, cs, new Date(), new Date(new Date().setFullYear(new Date().getFullYear() + 1))).then(res => {
console.log("Remaining:", res);
const vacPlaced = sd._datesSelected.filter(d => ['cp', 'rtt', 'other'].includes(d.type as string));
console.log("Placed vacs:", vacPlaced.length);
});
+33
View File
@@ -0,0 +1,33 @@
const fs = require('fs');
const path = require('path');
const i18nDir = path.join(__dirname, '../src/i18n');
const registryFile = path.join(__dirname, '../src/app/i18n-registry.ts');
if (!fs.existsSync(i18nDir)) {
console.error(`i18n directory not found at ${i18nDir}`);
process.exit(1);
}
const files = fs.readdirSync(i18nDir).filter((file) => file.endsWith('.json'));
let importStatements = '';
let registryEntries = '';
files.forEach((file) => {
const langCode = path.basename(file, '.json');
importStatements += `import ${langCode} from '../i18n/${file}';\n`;
registryEntries += `\t${langCode}: ${langCode} as AppTranslations,\n`;
});
const content = `// This file is auto-generated by scripts/generate-langs.js. Do not edit directly.
import { AppTranslations } from './services/translation-service';
${importStatements}
export const translationsMap: Record<string, AppTranslations> = {
${registryEntries}};
export const availableLanguagesList: string[] = Object.keys(translationsMap);
`;
fs.writeFileSync(registryFile, content, 'utf8');
console.log(`Successfully generated i18n registry at ${registryFile} with ${files.length} languages.`);
+1 -3
View File
@@ -4,9 +4,7 @@ import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(withRoutes(serverRoutes))
]
providers: [provideServerRendering(withRoutes(serverRoutes))],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
+2
View File
@@ -11,6 +11,7 @@ import {
withEventReplay,
withIncrementalHydration,
} from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
export const appConfig: ApplicationConfig = {
providers: [
@@ -18,5 +19,6 @@ export const appConfig: ApplicationConfig = {
provideZonelessChangeDetection(),
provideRouter(routes),
provideClientHydration(withEventReplay(), withIncrementalHydration()),
provideAnimationsAsync(),
],
};
+2 -2
View File
@@ -3,6 +3,6 @@ import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '**',
renderMode: RenderMode.Client
}
renderMode: RenderMode.Client,
},
];
+41 -13
View File
@@ -9,14 +9,52 @@
.content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
align-items: flex-start;
justify-content: space-between;
width: 100%;
padding: 0 24px;
box-sizing: border-box;
}
@media (max-width: 768px) {
app-user-input {
display: block;
flex: 3;
min-width: 350px;
margin-right: 16px;
}
app-calendar-year {
display: block;
flex: 7;
min-width: 700px;
margin-left: 16px;
}
@media (max-width: 1024px) {
.content {
flex-direction: column;
align-items: stretch;
padding: 0 16px;
}
app-user-input,
app-calendar-year {
flex: none;
width: 100%;
min-width: 0;
margin: 0;
}
app-user-input {
margin-bottom: 24px;
}
.mat-divider-vertical {
display: none;
}
.mat-divider-horizontal {
display: block !important;
}
}
@@ -27,16 +65,6 @@
z-index: 1000;
}
@media (max-width: 768px) {
.mat-divider-vertical {
display: none;
}
.mat-divider-horizontal {
display: block !important;
}
}
.mat-divider-vertical {
height: 80vh;
width: 1px;
+1 -1
View File
@@ -6,7 +6,7 @@ describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [App],
providers: [provideZonelessChangeDetection()]
providers: [provideZonelessChangeDetection()],
}).compileComponents();
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, model, signal } from '@angular/core';
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { provideNativeDateAdapter } from '@angular/material/core';
import { MatDivider } from '@angular/material/divider';
@@ -2,21 +2,17 @@
<h3>{{ getMonthName() }} {{ year }}</h3>
<div>
<div class="row header">
@if (calendarSettingsService.showWeekNumbers()) {
@if (calendarSettingsService.get('showWeekNumbers')()) {
<p class="day empty week-number"></p>
}
<p class="day">L</p>
<p class="day">M</p>
<p class="day">M</p>
<p class="day">J</p>
<p class="day">V</p>
<p class="day">S</p>
<p class="day">D</p>
@for (letter of getWeekdayLetters(); track $index) {
<p class="day">{{ letter }}</p>
}
</div>
<div class="cols">
@for (week of weeksInAMonth; track $index) {
<div class="row week">
@if (calendarSettingsService.showWeekNumbers()) {
@if (calendarSettingsService.get('showWeekNumbers')()) {
<p
class="day week-number"
[class.start]="$index === 0"
@@ -36,9 +32,8 @@
[class.selected]="selectedDates.isSelected(day)"
[class]="selectedDates.getClasses(day)"
class="day number"
[matTooltip]="selectedDates.getTooltip(day)"
[matTooltip]="getDayTooltip(day)"
[matTooltipShowDelay]="500"
[matTooltipTouchGestures]="'on'"
>
{{ day.getDate() }}
</p>
@@ -3,22 +3,21 @@ import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarMonth } from './calendar-month';
describe('CalendarMonth', () => {
let component: CalendarMonth;
let fixture: ComponentFixture<CalendarMonth>;
let component: CalendarMonth;
let fixture: ComponentFixture<CalendarMonth>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarMonth],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarMonth],
providers: [provideZonelessChangeDetection()],
}).compileComponents();
fixture = TestBed.createComponent(CalendarMonth);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(CalendarMonth);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,8 +1,9 @@
import { Component, Input, OnInit, inject } from '@angular/core';
import { CalendarService, Month, monthNames, SelectedDates } from '../../services/calendar-service';
import { CalendarService, Month, SelectedDates } from '../../services/calendar-service';
import { MatTooltip } from '@angular/material/tooltip';
import { CalendarSettingsService } from '../../services/calendar-settings-service';
import { DateCacheService } from '../../services/date-cache.service';
import { TranslationService } from '../../services/translation-service';
@Component({
selector: 'app-calendar-month',
@@ -13,6 +14,7 @@ import { DateCacheService } from '../../services/date-cache.service';
export class CalendarMonth implements OnInit {
protected calendarSettingsService = inject(CalendarSettingsService);
private dateCache = inject(DateCacheService);
protected translationService = inject(TranslationService);
@Input({ required: true })
monthIndex!: number;
@@ -20,6 +22,10 @@ export class CalendarMonth implements OnInit {
@Input({ required: true })
year!: number;
get tr() {
return this.translationService.tr;
}
// Use DateCacheService instead of creating Date objects
getNow(): Date {
return this.dateCache.getNow();
@@ -33,7 +39,24 @@ export class CalendarMonth implements OnInit {
selectedDates!: SelectedDates;
getMonthName(): string {
return monthNames[this.monthIndex];
const locale = this.translationService.locale();
const tempDate = new Date(this.year, this.monthIndex, 1);
const monthName = tempDate.toLocaleString(locale, { month: 'long' });
return monthName.charAt(0).toUpperCase() + monthName.slice(1);
}
getWeekdayLetters(): string[] {
// January 5, 2026 is a Monday
const baseDate = new Date(2026, 0, 5);
const locale = this.translationService.locale();
const letters: string[] = [];
for (let i = 0; i < 7; i++) {
const date = new Date(baseDate);
date.setDate(baseDate.getDate() + i);
const letter = date.toLocaleString(locale, { weekday: 'narrow' });
letters.push(letter.toUpperCase());
}
return letters;
}
getWeekNumber(week: (Date | null)[]): number | null {
@@ -45,6 +68,13 @@ export class CalendarMonth implements OnInit {
return null;
}
getDayTooltip(day: Date): string | null {
const tooltips = this.selectedDates.datesSelected
.filter((dateSelected) => dateSelected.isSelected(day) && dateSelected.type)
.map((dateSelected) => this.translationService.getDayTypeLabel(dateSelected.type!));
return tooltips.length > 0 ? tooltips.join(' + ') : null;
}
ngOnInit(): void {
this.weeksInAMonth = CalendarService.weeksInMonth(this.monthIndex, this.year);
}
@@ -1,4 +1,4 @@
<button matFab extended (click)="openDialog()">
<mat-icon>settings</mat-icon>
Calendar Settings
{{ tr().settings.title }}
</button>
@@ -3,22 +3,21 @@ import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarSettings } from './calendar-settings';
describe('CalendarSettings', () => {
let component: CalendarSettings;
let fixture: ComponentFixture<CalendarSettings>;
let component: CalendarSettings;
let fixture: ComponentFixture<CalendarSettings>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarSettings],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarSettings],
providers: [provideZonelessChangeDetection()],
}).compileComponents();
fixture = TestBed.createComponent(CalendarSettings);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(CalendarSettings);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,9 +1,10 @@
import { Component } from '@angular/core';
import { MatButton, MatButtonModule } from '@angular/material/button';
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatIcon, MatIconModule } from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import { CalendarSettingsDialog } from '../dialog/calendar-settings-dialog/calendar-settings-dialog';
import { MatDividerModule } from '@angular/material/divider';
import { TranslationService } from '../../services/translation-service';
@Component({
selector: 'app-calendar-settings',
@@ -12,6 +13,12 @@ import { MatDividerModule } from '@angular/material/divider';
styleUrl: './calendar-settings.scss',
})
export class CalendarSettings {
private readonly translationService = inject(TranslationService);
get tr() {
return this.translationService.tr;
}
constructor(private dialog: MatDialog) {}
openDialog() {
@@ -1,17 +1,24 @@
<div class="calendar-year">
@if (isLoading()) {
<div class="loading-overlay">
<div class="loading-spinner">
<mat-spinner></mat-spinner>
<p>Calcul en cours...</p>
<div class="loading-overlay">
<div class="loading-spinner">
<div class="spinner-container">
<mat-spinner [mode]="'determinate'" [value]="loadingProgress()"></mat-spinner>
<div class="spinner-text">{{ loadingProgress() }}%</div>
</div>
<p>{{ tr().loading }}</p>
</div>
</div>
</div>
}
@for (month of months; track $index) {
<mat-card class="card">
<mat-card-content>
<app-calendar-month [monthIndex]="month" [year]="getYear(month)" [selectedDates]="selectedDates"></app-calendar-month>
</mat-card-content>
</mat-card>
@for (item of monthsAndYears(); track $index) {
<mat-card class="card">
<mat-card-content>
<app-calendar-month
[monthIndex]="item.month"
[year]="item.year"
[selectedDates]="selectedDates"
></app-calendar-month>
</mat-card-content>
</mat-card>
}
</div>
@@ -5,16 +5,11 @@
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
max-width: 65vw;
width: 100%;
max-width: 100%;
position: relative;
}
@media (max-width: 768px) {
.calendar-year {
max-width: 100vw;
}
}
.loading-overlay {
position: absolute;
top: 0;
@@ -51,20 +46,32 @@
}
}
.spinner-container {
position: relative;
display: inline-block;
::ng-deep .mat-mdc-progress-spinner {
// Override Material spinner size
width: 100px;
height: 100px;
}
.spinner-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 20px;
font-weight: bold;
color: #333;
pointer-events: none;
@media (prefers-color-scheme: dark) {
color: #ddd;
}
}
}
.card {
margin: 16px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
:root {
@include mat.progress-spinner-overrides((size: 0.5rem, active-indicator-width: 0.25rem));
}
@@ -3,22 +3,21 @@ import { provideZonelessChangeDetection } from '@angular/core';
import { CalendarYear } from './calendar-year';
describe('CalendarYear', () => {
let component: CalendarYear;
let fixture: ComponentFixture<CalendarYear>;
let component: CalendarYear;
let fixture: ComponentFixture<CalendarYear>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarYear],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarYear],
providers: [provideZonelessChangeDetection()],
}).compileComponents();
fixture = TestBed.createComponent(CalendarYear);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(CalendarYear);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,16 +1,16 @@
import { Component, effect, signal, inject } from '@angular/core';
import { Component, effect, signal, inject, untracked, computed } from '@angular/core';
import { UserInputService } from '../../services/user-input-service';
import { MatCard, MatCardContent } from '@angular/material/card';
import { CalendarMonth } from '../calendar-month/calendar-month';
import { CalendarService, SelectedDates } from '../../services/calendar-service';
import { CommonModule } from '@angular/common';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { CalendarSettingsService } from '../../services/calendar-settings-service';
import { DateCacheService } from '../../services/date-cache.service';
import { RuleService } from '../../services/rule-service';
import { TranslationService } from '../../services/translation-service';
@Component({
selector: 'app-calendar-year',
imports: [MatCard, CalendarMonth, MatCardContent, CommonModule, MatProgressSpinner],
imports: [MatCard, CalendarMonth, MatCardContent, MatProgressSpinner],
templateUrl: './calendar-year.html',
styleUrl: './calendar-year.scss',
})
@@ -18,10 +18,17 @@ export class CalendarYear {
private dateCache = inject(DateCacheService);
private userInput = inject(UserInputService);
private calendarService = inject(CalendarService);
private calendarSettingsService = inject(CalendarSettingsService);
private ruleService = inject(RuleService);
protected readonly translationService = inject(TranslationService);
get tr() {
return this.translationService.tr;
}
protected isLoading = signal(true);
protected loadingProgress = signal(0);
// Use DateCacheService instead of creating Date objects
private get currentMonth() {
return this.dateCache.getCurrentMonth();
@@ -33,34 +40,63 @@ export class CalendarYear {
constructor() {
effect(() => {
this.selectedDates = this.calendarService.getSelectedDatesForYear();
const vacationData = this.userInput.vacationNumberSignal();
const c = this.calendarSettingsService.samediMalin();
console.log('Samedi Malin setting:', c);
this.userInput.computeTriggerSignal();
const startDate = this.userInput.startDate();
const endDate = this.userInput.endDate();
this.selectedDates = this.calendarService.getSelectedDatesForPeriod(startDate, endDate);
const vacationData = untracked(() => this.userInput.vacationNumberSignal());
const currentRules = untracked(() => this.ruleService.rules);
console.log('Optimizing vacations with data:', vacationData);
console.log('Using rules:', currentRules);
this.isLoading.set(true);
// Simulate server-side calculation with a small delay
setTimeout(() => {
if (this.selectedDates) {
// Create a copy to avoid mutating the original signal data
this.selectedDates.optimizeVacations(
{ ...vacationData },
this.calendarSettingsService,
this.calendarService,
);
}
this.isLoading.set(false);
}, 1);
this.loadingProgress.set(0);
// Defer execution to allow UI to update
queueMicrotask(() => {
void (async () => {
if (this.selectedDates) {
// Create a deep copy to avoid mutating the original signal data
const vacationDaysCopy = vacationData.map((vd) => ({ ...vd }));
const remainingDays = await this.selectedDates.optimizeVacations(
vacationDaysCopy,
currentRules,
this.calendarService,
startDate,
endDate,
(progress: number) => {
this.loadingProgress.set(progress);
},
);
// Update the remaining vacation days signal
this.userInput.remainingVacationDaysSignal.set(remainingDays);
}
this.isLoading.set(false);
})();
});
});
}
protected selectedDates: SelectedDates = new SelectedDates();
getYear(monthIndex: number): number {
const offset = this.currentMonth <= monthIndex ? 0 : 1;
return this.currentYear + offset;
}
protected monthsAndYears = computed(() => {
const start = this.userInput.startDate();
const end = this.userInput.endDate();
// months starting from actual date - cache this array
protected months: number[] = Array.from({ length: 12 }, (_, i) => (this.currentMonth + i) % 12);
const result: { month: number; year: number }[] = [];
const current = new Date(start.getFullYear(), start.getMonth(), 1);
const limit = new Date(end.getFullYear(), end.getMonth(), 1);
while (current <= limit) {
result.push({
month: current.getMonth(),
year: current.getFullYear(),
});
current.setMonth(current.getMonth() + 1);
}
return result;
});
}
@@ -1,29 +1,37 @@
<div class="container">
<div>
<h1>Calendar Settings</h1>
<p>Configure your calendar settings here.</p>
<div class="container" style="min-width: 280px; padding: 8px">
<div style="margin-bottom: 1.5rem; text-align: center">
<h1 style="margin: 0 0 0.5rem 0; font-size: 1.5rem">{{ tr().settings.title }}</h1>
<p style="margin: 0; opacity: 0.7; font-size: 0.9rem">{{ tr().settings.description }}</p>
</div>
<div style="display: flex; flex-direction: column; gap: 1rem">
<div style="align-self: flex-start">
<mat-slide-toggle
id="showWeekNumbers"
name="showWeekNumbers"
[checked]="calendarSettingsService.showWeekNumbers()"
(change)="calendarSettingsService.showWeekNumbers.set($event.checked)"
<div style="display: flex; flex-direction: column; gap: 1rem; width: 100%">
@for (setting of calendarSettingsService.settingsArray; track setting.id) {
<div style="align-self: flex-start; margin-bottom: 0.5rem">
<mat-slide-toggle
[id]="setting.id"
[name]="setting.id"
[checked]="setting.signal()"
(change)="setting.signal.set($event.checked)"
>
{{ getSettingDescription(setting.id) }}
</mat-slide-toggle>
</div>
}
<mat-form-field appearance="outline" style="width: 100%; margin-top: 0.5rem">
<mat-label>{{ tr().settings.selectLanguage }}</mat-label>
<mat-select
[value]="translationService.language()"
(valueChange)="translationService.setLanguage($event)"
>
Afficher les numéros de semaine
</mat-slide-toggle>
</div>
<div style="align-self: flex-start">
<mat-slide-toggle
id="samediMalin"
name="samediMalin"
[checked]="calendarSettingsService.samediMalin()"
(change)="calendarSettingsService.samediMalin.set($event.checked)"
>
Activer le Samedi Malin
</mat-slide-toggle>
</div>
@for (lang of translationService.availableLanguages; track lang) {
<mat-option [value]="lang">{{
translationService.getLanguageName(lang)
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<button mat-button (click)="closeDialog()">Fermer</button>
<button mat-button (click)="closeDialog()" style="margin-top: 1.5rem; width: 100%">
{{ tr().settings.close }}
</button>
</div>
@@ -1,42 +1,42 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { vi } from 'vitest';
import { CalendarSettingsDialog } from './calendar-settings-dialog';
describe('CalendarSettingsDialog', () => {
let component: CalendarSettingsDialog;
let fixture: ComponentFixture<CalendarSettingsDialog>;
let component: CalendarSettingsDialog;
let fixture: ComponentFixture<CalendarSettingsDialog>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarSettingsDialog],
providers: [
provideZonelessChangeDetection(),
{
provide: MatDialogRef,
useValue: {
close: jasmine.createSpy('close')
}
}
]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CalendarSettingsDialog],
providers: [
provideZonelessChangeDetection(),
{
provide: MatDialogRef,
useValue: {
close: vi.fn(),
},
},
],
}).compileComponents();
fixture = TestBed.createComponent(CalendarSettingsDialog);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(CalendarSettingsDialog);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have calendar settings service', () => {
expect(component['calendarSettingsService']).toBeDefined();
});
it('should have calendar settings service', () => {
expect(component['calendarSettingsService']).toBeDefined();
});
it('should close dialog when closeDialog is called', () => {
component.closeDialog();
expect(component.dialogRef.close).toHaveBeenCalled();
});
it('should close dialog when closeDialog is called', () => {
component.closeDialog();
expect(component.dialogRef.close).toHaveBeenCalled();
});
});
@@ -1,22 +1,34 @@
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDialogRef } from '@angular/material/dialog';
import { CalendarSettingsService } from '../../../services/calendar-settings-service';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { CdkFixedSizeVirtualScroll } from '@angular/cdk/scrolling';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { TranslationService } from '../../../services/translation-service';
@Component({
selector: 'app-calendar-settings-dialog',
imports: [MatButton, MatSlideToggle],
imports: [MatButton, MatSlideToggle, MatFormFieldModule, MatSelectModule],
templateUrl: './calendar-settings-dialog.html',
styleUrl: './calendar-settings-dialog.scss',
})
export class CalendarSettingsDialog {
constructor(
public dialogRef: MatDialogRef<CalendarSettingsDialog>,
protected calendarSettingsService: CalendarSettingsService,
) {}
public readonly dialogRef = inject<MatDialogRef<CalendarSettingsDialog>>(MatDialogRef);
protected readonly calendarSettingsService = inject(CalendarSettingsService);
protected readonly translationService = inject(TranslationService);
get tr() {
return this.translationService.tr;
}
getSettingDescription(id: string): string {
const tr = this.tr();
if (id === 'showWeekNumbers') {
return tr.settings.showWeekNumbers;
}
return id;
}
closeDialog() {
this.dialogRef.close();
@@ -0,0 +1,357 @@
<mat-expansion-panel class="rules-panel-container mat-elevation-z2">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>rule</mat-icon>
{{ tr().rules.title }}
</mat-panel-title>
<mat-panel-description> {{ tr().rules.subtitle }} </mat-panel-description>
</mat-expansion-panel-header>
<div class="priority-statement">
<mat-icon color="primary">info</mat-icon>
<span>{{ tr().rules.priorityInfo }}</span>
</div>
<div class="rules-list" cdkDropList (cdkDropListDropped)="drop($event)">
@for (rule of rules; track rule.id; let i = $index) {
<div
class="rule-item"
cdkDrag
[cdkDragDisabled]="editingRuleId() === rule.id"
[class.editing]="editingRuleId() === rule.id"
>
@if (editingRuleId() !== rule.id) {
<div class="rule-drag-grip">
<mat-icon class="drag-handle-icon">drag_handle</mat-icon>
</div>
<div class="rule-display">
<div class="rule-header">
<mat-slide-toggle
[checked]="rule.enabled"
(change)="toggleRule(rule.id)"
color="primary"
>
<span class="rule-name"
><span class="rule-number">{{ i + 1 }}.</span>
{{ rule.name }}</span
>
</mat-slide-toggle>
<div class="rule-actions">
<button mat-icon-button (click)="startEdit(rule, $event)">
<mat-icon>edit</mat-icon>
</button>
<button
mat-icon-button
color="warn"
(click)="deleteRule(rule.id, $event)"
>
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<div class="rule-summary" [innerHTML]="getRuleSummary(rule)"></div>
</div>
} @else {
<div class="rule-editor">
<h4>{{ tr().rules.editRule }}</h4>
<mat-form-field class="full-width">
<mat-label>{{ tr().rules.ruleName }}</mat-label>
<input matInput [(ngModel)]="$any(editBuffer).name" required />
</mat-form-field>
@if ($any(editBuffer).type === RuleType.IF_THEN) {
<div class="editor-row">
<mat-form-field>
<mat-label>{{ tr().rules.holidayFalls }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).dayOfWeek">
@for (day of daysOfWeek; track day.value) {
<mat-option [value]="day.value">{{
day.label
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="editor-row">
<span class="action-text">{{ tr().rules.place }}</span>
<mat-form-field class="small-input">
<mat-label>{{ tr().rules.days }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="$any(editBuffer).placeDays"
min="1"
/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.type }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).placeType">
@for (type of placeDayTypes; track type) {
<mat-option [value]="type">{{
getDayTypeLabel(type)
}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.position }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).placement">
@for (opt of placementOptions; track opt.value) {
<mat-option [value]="opt.value">{{
opt.label
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="editor-row">
<span class="action-text">{{ tr().rules.getBonus }}</span>
<mat-form-field class="small-input">
<mat-label>{{ tr().rules.days }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="$any(editBuffer).rewardDays"
min="0"
/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.type }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).rewardType">
@for (type of rewardDayTypes; track type) {
<mat-option [value]="type">{{
getDayTypeLabel(type)
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
} @else {
<div class="editor-row">
<mat-form-field>
<mat-label>{{ tr().rules.startDate }}</mat-label>
<input
matInput
[matDatepicker]="startPicker"
[(ngModel)]="$any(editBuffer).startDate"
/>
<mat-datepicker-toggle
matSuffix
[for]="startPicker"
></mat-datepicker-toggle>
<mat-datepicker #startPicker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.endDate }}</mat-label>
<input
matInput
[matDatepicker]="endPicker"
[(ngModel)]="$any(editBuffer).endDate"
/>
<mat-datepicker-toggle
matSuffix
[for]="endPicker"
></mat-datepicker-toggle>
<mat-datepicker #endPicker></mat-datepicker>
</mat-form-field>
</div>
<div class="editor-row">
<span class="action-text">{{ tr().rules.place }}</span>
<mat-form-field class="small-input">
<mat-label>{{ tr().rules.days }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="$any(editBuffer).placeDays"
min="1"
/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.type }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).placeType">
@for (type of placeDayTypes; track type) {
<mat-option [value]="type">{{
getDayTypeLabel(type)
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
}
<div class="editor-actions">
<button mat-button (click)="cancelEdit()">
{{ tr().rules.cancel }}
</button>
<button mat-flat-button color="primary" (click)="saveEdit()">
{{ tr().rules.save }}
</button>
</div>
</div>
}
</div>
}
@if (isAddingNew()) {
<div class="rule-item editing">
<div class="rule-editor">
<h4>{{ tr().rules.addRule }}</h4>
<mat-form-field class="full-width">
<mat-label>{{ tr().rules.ruleName }}</mat-label>
<input matInput [(ngModel)]="$any(editBuffer).name" required />
</mat-form-field>
@if (newRuleType() === RuleType.IF_THEN) {
<div class="editor-row">
<mat-form-field>
<mat-label>{{ tr().rules.holidayFalls }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).dayOfWeek">
@for (day of daysOfWeek; track day.value) {
<mat-option [value]="day.value">{{ day.label }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="editor-row">
<span class="action-text">{{ tr().rules.place }}</span>
<mat-form-field class="small-input">
<mat-label>{{ tr().rules.days }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="$any(editBuffer).placeDays"
min="1"
/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.type }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).placeType">
@for (type of placeDayTypes; track type) {
<mat-option [value]="type">{{
getDayTypeLabel(type)
}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.position }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).placement">
@for (opt of placementOptions; track opt.value) {
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="editor-row">
<span class="action-text">{{ tr().rules.getBonus }}</span>
<mat-form-field class="small-input">
<mat-label>{{ tr().rules.days }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="$any(editBuffer).rewardDays"
min="0"
/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.type }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).rewardType">
@for (type of rewardDayTypes; track type) {
<mat-option [value]="type">{{
getDayTypeLabel(type)
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
} @else {
<div class="editor-row">
<mat-form-field>
<mat-label>{{ tr().rules.startDate }}</mat-label>
<input
matInput
[matDatepicker]="startPicker"
[(ngModel)]="$any(editBuffer).startDate"
/>
<mat-datepicker-toggle
matSuffix
[for]="startPicker"
></mat-datepicker-toggle>
<mat-datepicker #startPicker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.endDate }}</mat-label>
<input
matInput
[matDatepicker]="endPicker"
[(ngModel)]="$any(editBuffer).endDate"
/>
<mat-datepicker-toggle
matSuffix
[for]="endPicker"
></mat-datepicker-toggle>
<mat-datepicker #endPicker></mat-datepicker>
</mat-form-field>
</div>
<div class="editor-row">
<span class="action-text">{{ tr().rules.place }}</span>
<mat-form-field class="small-input">
<mat-label>{{ tr().rules.days }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="$any(editBuffer).placeDays"
min="1"
/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ tr().rules.type }}</mat-label>
<mat-select [(ngModel)]="$any(editBuffer).placeType">
@for (type of placeDayTypes; track type) {
<mat-option [value]="type">{{
getDayTypeLabel(type)
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
}
<div class="editor-actions">
<button mat-button (click)="cancelEdit()">{{ tr().rules.cancel }}</button>
<button mat-flat-button color="primary" (click)="saveEdit()">
{{ tr().rules.add }}
</button>
</div>
</div>
</div>
}
<div class="add-buttons" [class.hidden]="isAddingNew() || editingRuleId()">
<button mat-stroked-button (click)="startAddNew(RuleType.IF_THEN)">
<mat-icon>add</mat-icon> {{ tr().rules.newIfThenRule }}
</button>
<button mat-stroked-button (click)="startAddNew(RuleType.TIME_WINDOW)">
<mat-icon>add</mat-icon> {{ tr().rules.newTimeWindowRule }}
</button>
</div>
</div>
</mat-expansion-panel>
@@ -0,0 +1,218 @@
.rules-panel-container {
margin-top: 1rem;
margin-bottom: 1rem;
border-radius: 12px;
overflow: hidden;
container-type: inline-size;
mat-panel-title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 500;
}
@container (max-width: 480px) {
mat-panel-description {
display: none !important;
}
}
}
.rules-list {
display: flex;
flex-direction: column;
gap: 12px;
padding-top: 8px;
}
.rule-item {
border: 1px solid var(--mat-sys-outline-variant);
border-radius: 8px;
transition: all 0.2s ease;
background: var(--mat-sys-surface-container);
&:not(.editing) {
cursor: grab;
&:active {
cursor: grabbing;
}
}
&:hover:not(.editing) {
border-color: var(--mat-sys-outline);
}
&.editing {
border: 1px solid var(--mat-sys-primary);
}
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 8px;
box-shadow:
0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.rules-list.cdk-drop-list-dragging .rule-item:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.rule-display {
padding: 16px;
}
.priority-statement {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--mat-sys-primary-container);
color: var(--mat-sys-on-primary-container);
border-radius: 8px;
font-size: 0.95rem;
margin-top: 8px;
margin-bottom: 8px;
mat-icon {
color: var(--mat-sys-primary);
flex-shrink: 0;
}
}
.rule-drag-grip {
display: flex;
justify-content: center;
align-items: center;
padding-top: 8px;
margin-bottom: -8px;
color: var(--mat-sys-outline);
opacity: 0.6;
cursor: grab;
transition: opacity 0.2s;
.rule-item:active & {
cursor: grabbing;
}
&:hover {
opacity: 1;
}
}
.rule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.rule-name {
font-weight: 600;
font-size: 1.05rem;
display: flex;
align-items: center;
gap: 6px;
.rule-number {
color: var(--mat-sys-primary);
font-weight: 700;
}
}
.rule-actions {
display: flex;
gap: 4px;
opacity: 0.6;
transition: opacity 0.2s;
.rule-item:hover & {
opacity: 1;
}
}
}
.rule-summary {
font-size: 0.95rem;
line-height: 1.5;
padding-left: 48px; /* Align with toggle switch text */
strong {
font-weight: 600;
}
}
.rule-editor {
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
background: var(--mat-sys-surface-container-high);
border-radius: 8px;
h4 {
margin: 0;
font-size: 1.1rem;
font-weight: 500;
}
.editor-row {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
.action-text {
font-weight: 500;
margin-right: 4px;
}
mat-form-field {
::ng-deep .mat-mdc-form-field-subscript-wrapper {
display: none;
}
}
.small-input {
width: 100px;
}
}
.full-width {
width: 100%;
}
.editor-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 8px;
}
}
.add-buttons {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
&.hidden {
display: none;
}
button {
border-radius: 24px;
width: 100%;
}
}
@@ -0,0 +1,109 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { RulesPanel } from './rules-panel';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RuleService, RuleType } from '../../services/rule-service';
import { TranslationService } from '../../services/translation-service';
describe('RulesPanel', () => {
let component: RulesPanel;
let fixture: ComponentFixture<RulesPanel>;
beforeEach(async () => {
// Clear localStorage before testing
localStorage.clear();
await TestBed.configureTestingModule({
imports: [RulesPanel, BrowserAnimationsModule],
providers: [provideZonelessChangeDetection(), RuleService],
}).compileComponents();
fixture = TestBed.createComponent(RulesPanel);
component = fixture.componentInstance;
// Force language to 'fr' so the default rule names match French assertions
const translationService = TestBed.inject(TranslationService);
translationService.setLanguage('fr');
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display initial rules from service', () => {
expect(component.rules.length).toBe(2);
});
it('should start adding new If-Then rule', () => {
component.startAddNew(RuleType.IF_THEN);
expect(component['isAddingNew']()).toBe(true);
expect(component['newRuleType']()).toBe(RuleType.IF_THEN);
expect(component['editBuffer'].type).toBe(RuleType.IF_THEN);
expect(component['editBuffer'].name).toBe('Nouvelle Règle');
});
it('should start adding new Time Window rule', () => {
component.startAddNew(RuleType.TIME_WINDOW);
expect(component['isAddingNew']()).toBe(true);
expect(component['newRuleType']()).toBe(RuleType.TIME_WINDOW);
expect(component['editBuffer'].type).toBe(RuleType.TIME_WINDOW);
expect(component['editBuffer'].name).toBe('Nouvelle Période');
});
it('should cancel edit', () => {
component.startAddNew(RuleType.IF_THEN);
component.cancelEdit();
expect(component['isAddingNew']()).toBe(false);
expect(component['editingRuleId']()).toBeNull();
expect(Object.keys(component['editBuffer']).length).toBe(0);
});
it('should save a new rule', () => {
const initialCount = component.rules.length;
component.startAddNew(RuleType.IF_THEN);
component['editBuffer'].name = 'Custom Test Rule';
component.saveEdit();
expect(component.rules.length).toBe(initialCount + 1);
expect(component.rules.some((r) => r.name === 'Custom Test Rule')).toBe(true);
});
it('should update an existing rule', () => {
const targetRule = component.rules[0];
// Create a mock event with stopPropagation
const mockEvent = { stopPropagation: () => {} } as Event;
component.startEdit(targetRule, mockEvent);
component['editBuffer'].name = 'Updated Name';
component.saveEdit();
expect(component.rules.find((r) => r.id === targetRule.id)?.name).toBe('Updated Name');
});
it('should delete a rule', () => {
const targetId = component.rules[0].id;
const initialCount = component.rules.length;
const mockEvent = { stopPropagation: () => {} } as Event;
component.deleteRule(targetId, mockEvent);
expect(component.rules.length).toBe(initialCount - 1);
expect(component.rules.some((r) => r.id === targetId)).toBe(false);
});
it('should toggle a rule', () => {
const targetRule = component.rules[0];
const initialState = targetRule.enabled;
component.toggleRule(targetRule.id);
expect(component.rules.find((r) => r.id === targetRule.id)?.enabled).toBe(!initialState);
});
});
@@ -0,0 +1,250 @@
import {
Component,
ChangeDetectionStrategy,
inject,
signal,
Injectable,
effect,
} from '@angular/core';
import {
RuleService,
RuleType,
IfThenRule,
TimeWindowRule,
VacationRule,
PlacementStrategy,
} from '../../services/rule-service';
import { SelectableDayType } from '../../services/calendar-service';
import { TranslationService } from '../../services/translation-service';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { FormsModule } from '@angular/forms';
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
NativeDateAdapter,
} from '@angular/material/core';
import { CdkDragDrop, CdkDropList, CdkDrag } from '@angular/cdk/drag-drop';
@Injectable()
class CustomDateAdapter extends NativeDateAdapter {
override format(date: Date, displayFormat: unknown): string {
if (displayFormat === 'input') {
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year}`;
}
return super.format(date, displayFormat as object);
}
}
const MY_DATE_FORMATS = {
parse: { dateInput: 'input' },
display: {
dateInput: 'input',
monthYearLabel: { year: 'numeric', month: 'short' },
dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' },
monthYearA11yLabel: { year: 'numeric', month: 'long' },
},
};
@Component({
selector: 'app-rules-panel',
imports: [
MatExpansionModule,
MatIconModule,
MatButtonModule,
MatSlideToggleModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
FormsModule,
CdkDropList,
CdkDrag,
],
templateUrl: './rules-panel.html',
styleUrl: './rules-panel.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{ provide: DateAdapter, useClass: CustomDateAdapter },
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
{ provide: MAT_DATE_FORMATS, useValue: MY_DATE_FORMATS },
],
})
export class RulesPanel {
public ruleService = inject(RuleService);
protected readonly translationService = inject(TranslationService);
private readonly dateAdapter = inject(DateAdapter);
get tr() {
return this.translationService.tr;
}
constructor() {
effect(() => {
this.dateAdapter.setLocale(this.translationService.locale());
});
}
protected readonly RuleType = RuleType;
protected readonly placeDayTypes = Object.values(SelectableDayType);
protected readonly rewardDayTypes = Object.values(SelectableDayType).filter(
(t) => t !== SelectableDayType.ANY,
);
protected get daysOfWeek() {
const tr = this.tr();
return [
{ value: 1, label: tr.rules.daysOfWeek[1] },
{ value: 2, label: tr.rules.daysOfWeek[2] },
{ value: 3, label: tr.rules.daysOfWeek[3] },
{ value: 4, label: tr.rules.daysOfWeek[4] },
{ value: 5, label: tr.rules.daysOfWeek[5] },
{ value: 6, label: tr.rules.daysOfWeek[6] },
{ value: 0, label: tr.rules.daysOfWeek[0] },
];
}
protected get placementOptions(): { value: PlacementStrategy; label: string }[] {
const tr = this.tr();
return [
{ value: 'before', label: tr.rules.placement.before },
{ value: 'after', label: tr.rules.placement.after },
{ value: 'around', label: tr.rules.placement.around },
{ value: 'auto', label: tr.rules.placement.auto },
];
}
protected editingRuleId = signal<string | null>(null);
protected isAddingNew = signal(false);
protected newRuleType = signal<RuleType>(RuleType.IF_THEN);
protected editBuffer: Partial<VacationRule> = {};
public get rules() {
return this.ruleService.rules;
}
public drop(event: CdkDragDrop<VacationRule[]>) {
if (event.previousIndex !== event.currentIndex) {
this.ruleService.reorderRules(event.previousIndex, event.currentIndex);
}
}
public toggleRule(id: string) {
this.ruleService.toggleRule(id);
}
public startEdit(rule: VacationRule, event: Event) {
event.stopPropagation();
this.editingRuleId.set(rule.id);
this.isAddingNew.set(false);
this.editBuffer = { ...rule };
}
public cancelEdit() {
this.editingRuleId.set(null);
this.isAddingNew.set(false);
this.editBuffer = {};
}
public saveEdit() {
if (this.editBuffer.id) {
this.ruleService.updateRule(this.editBuffer as VacationRule);
} else {
this.ruleService.addRule({
...this.editBuffer,
id: crypto.randomUUID(),
enabled: true,
} as VacationRule);
}
this.cancelEdit();
}
public deleteRule(id: string, event: Event) {
event.stopPropagation();
this.ruleService.deleteRule(id);
}
public startAddNew(type: RuleType) {
this.isAddingNew.set(true);
this.editingRuleId.set(null);
this.newRuleType.set(type);
if (type === RuleType.IF_THEN) {
this.editBuffer = {
name: this.tr().rules.defaultNewRuleName,
type: RuleType.IF_THEN,
dayOfWeek: 6,
placeDays: 2,
placeType: SelectableDayType.CP,
placement: 'auto',
rewardDays: 1,
rewardType: SelectableDayType.OTHER,
} as Partial<IfThenRule>;
} else {
this.editBuffer = {
name: this.tr().rules.defaultNewPeriodName,
type: RuleType.TIME_WINDOW,
startDate: new Date(),
endDate: new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000),
placeDays: 5,
placeType: SelectableDayType.CP,
} as Partial<TimeWindowRule>;
}
}
protected getDayTypeLabel(type: string | undefined | null): string {
if (!type) return '';
return this.translationService.getDayTypeLabel(type);
}
public getRuleSummary(rule: VacationRule): string {
const tr = this.tr();
if (rule.type === RuleType.IF_THEN) {
const ifThen = rule as IfThenRule;
const dayLabel = this.daysOfWeek.find((d) => d.value === ifThen.dayOfWeek)?.label ?? '';
const placeTypeLabel = this.getDayTypeLabel(ifThen.placeType);
const placementLabel = (
this.placementOptions.find((p) => p.value === ifThen.placement)?.label ?? ''
)
.trim()
.toLowerCase();
const rewardTypeLabel = this.getDayTypeLabel(ifThen.rewardType);
return tr.rules.ifThenTemplate
.replace('{day}', dayLabel)
.replace('{placeDays}', String(ifThen.placeDays))
.replace('{placeType}', placeTypeLabel)
.replace('{placement}', placementLabel)
.replace('{rewardDays}', String(ifThen.rewardDays))
.replace('{rewardType}', rewardTypeLabel);
} else {
const timeWindow = rule as TimeWindowRule;
const placeTypeLabel = this.getDayTypeLabel(timeWindow.placeType);
const startDateStr = new Date(timeWindow.startDate).toLocaleDateString(
this.translationService.locale(),
);
const endDateStr = new Date(timeWindow.endDate).toLocaleDateString(
this.translationService.locale(),
);
return tr.rules.timeWindowTemplate
.replace('{placeDays}', String(timeWindow.placeDays))
.replace('{placeType}', placeTypeLabel)
.replace('{startDate}', startDateStr)
.replace('{endDate}', endDateStr);
}
}
}
+76 -16
View File
@@ -1,18 +1,78 @@
<div class="user-input">
<p>Optimisez vos jours de vacances en fonction des jours fériés et des week-ends !</p>
<form [formGroup]="userInputForm">
<mat-form-field appearance="fill">
<mat-label>Combien de CP par an ?</mat-label>
<input matInput type="number" max="365" min="0" formControlName="CP" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Combien de jours de RTT par an ?</mat-label>
<input matInput type="number" max="365" min="0" formControlName="RTT" />
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Autres jours</mat-label>
<input matInput type="number" max="365" min="0" formControlName="Others" />
</mat-form-field>
</form>
<app-calendar-settings></app-calendar-settings>
<p>{{ tr().app.description }}</p>
<mat-card class="period-selection-card mat-elevation-z2">
<mat-card-content>
<h3 class="period-title">
<mat-icon>date_range</mat-icon> {{ tr().userInput.choosePeriod }}
</h3>
<mat-form-field appearance="outline" class="period-field">
<mat-label>{{ tr().userInput.choosePeriod }}</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input
matStartDate
[value]="startDate()"
(dateChange)="onStartDateChange($event.value)"
[placeholder]="tr().userInput.startDate"
/>
<input
matEndDate
[value]="endDate()"
(dateChange)="onEndDateChange($event.value)"
[placeholder]="tr().userInput.endDate"
/>
</mat-date-range-input>
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
</mat-card-content>
</mat-card>
<div class="listOfDays">
@for (day of daysOff; track day.id) {
<div class="day-input-wrapper" animate.enter="day-enter" animate.leave="day-leave">
<app-day-input
[day]="day"
(dayChange)="updateDay($index, $event)"
(deleteEntry)="removeFromList($index)"
></app-day-input>
</div>
}
<mat-card class="add-day-card mat-elevation-z2" (click)="addDay()">
<button matFab [aria-label]="tr().userInput.addVacationDay">
<mat-icon>add</mat-icon>
</button>
<h3>{{ tr().userInput.addVacationDay }}</h3>
</mat-card>
</div>
<div class="action-buttons">
<button matFab extended (click)="triggerComputation()">
<mat-icon>calculate</mat-icon>
{{ tr().userInput.calculate }}
</button>
<app-calendar-settings></app-calendar-settings>
</div>
<app-rules-panel></app-rules-panel>
@if (hasRemainingDays()) {
<mat-card class="mat-elevation-z2 warning-card">
<mat-card-content class="warning-content">
<mat-icon color="warn">warning</mat-icon>
<div class="warning">
<span>
{{
tr()
.userInput.remainingDaysWarning.replace(
'{count}',
remainingDays().toString()
)
.split('{plural}')
.join(remainingDays() > 1 ? 's' : '')
}}
</span>
@for (day of remainingDaysDetails(); track $index) {
<span class="remaining-day-detail">- {{ day }}</span>
}
<span> {{ tr().userInput.remainingDaysManualPlace }} </span>
</div>
</mat-card-content>
</mat-card>
}
</div>
@@ -13,9 +13,107 @@
font-size: 0.875rem;
margin-top: 0.25rem;
}
.warning-card {
margin-bottom: 1rem;
}
.warning-content {
display: flex;
align-items: center;
gap: 0.5rem;
}
.period-selection-card {
margin-bottom: 1rem;
.period-title {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0;
margin-bottom: 1rem;
}
.period-field {
width: 100%;
}
}
}
.listOfDays {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.day-input-wrapper {
animation: slide-fade-in 200ms ease-out;
@starting-style {
opacity: 0;
transform: translateX(-20px);
}
}
.day-enter {
animation: slide-fade-in 200ms ease-out;
}
.day-leave {
opacity: 0;
transform: translateX(20px);
transition:
opacity 200ms ease-in,
transform 200ms ease-in;
}
@keyframes slide-fade-in {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
form {
display: flex;
flex-direction: column;
}
.add-day-card {
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
gap: 1rem;
margin-bottom: 1rem;
cursor: pointer;
}
.add-day-card h3 {
flex-grow: 1;
margin: 0;
font-weight: normal;
}
.action-buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
gap: 1rem;
}
.warning {
display: flex;
flex-direction: column;
}
:host {
max-width: 100%;
}
@@ -1,41 +1,71 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { UserInput } from './user-input';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { UserInputService } from '../../services/user-input-service';
import { provideNativeDateAdapter } from '@angular/material/core';
describe('UserInput', () => {
let component: UserInput;
let fixture: ComponentFixture<UserInput>;
let component: UserInput;
let fixture: ComponentFixture<UserInput>;
let userInputService: UserInputService;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserInput],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserInput],
providers: [
provideZonelessChangeDetection(),
provideAnimationsAsync(),
provideNativeDateAdapter(),
],
}).compileComponents();
fixture = TestBed.createComponent(UserInput);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(UserInput);
component = fixture.componentInstance;
userInputService = TestBed.inject(UserInputService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should initialize form with values from service', () => {
expect(component.userInputForm).toBeDefined();
expect(component.userInputForm.get('CP')).toBeDefined();
expect(component.userInputForm.get('RTT')).toBeDefined();
expect(component.userInputForm.get('Others')).toBeDefined();
});
it('should initialize with vacation days from service', () => {
const vacationDays = userInputService.vacationNumberSignal();
expect(vacationDays).toBeDefined();
expect(Array.isArray(vacationDays)).toBe(true);
expect(vacationDays.length).toBeGreaterThan(0);
});
it('should have valid form controls', () => {
const cpControl = component.userInputForm.get('CP');
const rttControl = component.userInputForm.get('RTT');
const othersControl = component.userInputForm.get('Others');
it('should add a new vacation day', () => {
const initialLength = userInputService.vacationNumberSignal().length;
component.addDay();
expect(userInputService.vacationNumberSignal().length).toBe(initialLength + 1);
});
expect(cpControl).toBeTruthy();
expect(rttControl).toBeTruthy();
expect(othersControl).toBeTruthy();
});
it('should remove a vacation day', () => {
const initialLength = userInputService.vacationNumberSignal().length;
component.removeFromList(0);
expect(userInputService.vacationNumberSignal().length).toBe(initialLength - 1);
});
it('should trigger computation', () => {
const initialValue = userInputService.computeTriggerSignal();
component.triggerComputation();
expect(userInputService.computeTriggerSignal()).toBe(initialValue + 1);
});
it('should update start date', () => {
const newDate = new Date();
newDate.setFullYear(newDate.getFullYear() + 2);
component.onStartDateChange(newDate);
expect(userInputService.startDate().getFullYear()).toBe(newDate.getFullYear());
});
it('should update end date', () => {
const newDate = new Date();
newDate.setFullYear(newDate.getFullYear() + 3);
component.onEndDateChange(newDate);
expect(userInputService.endDate().getFullYear()).toBe(newDate.getFullYear());
});
});
+86 -45
View File
@@ -1,68 +1,109 @@
import { Component, OnInit, OnDestroy, effect } from '@angular/core';
import { MatFormField, MatLabel } from '@angular/material/form-field';
import { Component, computed, inject, ChangeDetectionStrategy } from '@angular/core';
import { MatInputModule } from '@angular/material/input';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { UserInputService } from '../../services/user-input-service';
import { VacationsNumber } from '../../services/calendar-service';
import { debounceTime, takeUntil, Subject } from 'rxjs';
import { CalendarSettings } from '../calendar-settings/calendar-settings';
import { RulesPanel } from '../rules-panel/rules-panel';
import { MatCard, MatCardContent } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon';
import { DayInput } from '../userInput/day-input/day-input';
import { MatButtonModule } from '@angular/material/button';
import { VacDay } from '../../services/calendar-service';
import { TranslationService } from '../../services/translation-service';
@Component({
selector: 'app-user-input',
imports: [
MatFormField,
MatLabel,
MatInputModule,
ReactiveFormsModule,
CalendarSettings,
MatDatepickerModule,
MatFormFieldModule,
CalendarSettings,
RulesPanel,
MatCard,
MatCardContent,
MatIcon,
MatButtonModule,
DayInput,
],
templateUrl: './user-input.html',
styleUrl: './user-input.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserInput implements OnInit, OnDestroy {
constructor(private userInputService: UserInputService) {
// Watch for signal changes and update form
effect(() => {
const vacationData = this.userInputService.vacationNumberSignal();
this.userInputForm.patchValue(
{
CP: vacationData.cp,
RTT: vacationData.rtt,
Others: vacationData.other,
},
{ emitEvent: false },
);
});
export class UserInput {
private userInputService: UserInputService = inject(UserInputService);
protected readonly translationService = inject(TranslationService);
get tr() {
return this.translationService.tr;
}
private destroy$ = new Subject<void>();
userInputForm: FormGroup = new FormGroup({
CP: new FormControl<number | null>(null),
RTT: new FormControl<number | null>(null),
Others: new FormControl<number | null>(null),
protected remainingDays = computed(() => {
const remaining = this.userInputService.remainingVacationDaysSignal();
return remaining.reduce((acc, day) => acc + day.numberOfDays, 0);
});
ngOnInit() {
this.userInputForm.valueChanges
.pipe(debounceTime(1500), takeUntil(this.destroy$))
.subscribe(() => this.submitForm());
protected remainingDaysDetails = computed(() => {
const remaining = this.userInputService.remainingVacationDaysSignal();
return remaining
.filter((day) => day.numberOfDays > 0)
.map((day) => {
const typeName = this.translationService.getDayTypeLabel(day.type);
const expiryDate = day.expiryDate.toLocaleDateString(
this.translationService.locale(),
{
day: 'numeric',
month: 'short',
year: 'numeric',
},
);
const expiresLabel = this.tr().userInput.expires;
return `${day.numberOfDays} ${typeName} (${expiresLabel} ${expiryDate})`;
});
});
protected hasRemainingDays = computed(() => this.remainingDays() > 0);
protected get daysOff() {
return this.userInputService.vacationNumberSignal();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
public addDay() {
this.userInputService.vacationNumberSignal.update((days) => [
...days,
this.userInputService.createVacationDay(),
]);
}
private submitForm() {
if (this.userInputForm.valid) {
const vacationData: VacationsNumber = {
cp: this.userInputForm.get('CP')?.value || 0,
rtt: this.userInputForm.get('RTT')?.value || 0,
other: this.userInputForm.get('Others')?.value || 0,
};
this.userInputService.vacationNumberSignal.set(vacationData);
}
public removeFromList(index: number) {
this.userInputService.vacationNumberSignal.update((days) =>
days.filter((_, i) => i !== index),
);
}
public updateDay(index: number, updatedDay: VacDay) {
this.userInputService.vacationNumberSignal.update((days) =>
days.map((day, i) => (i === index ? updatedDay : day)),
);
}
public triggerComputation() {
this.userInputService.requestComputation();
}
protected get startDate() {
return this.userInputService.startDate;
}
protected get endDate() {
return this.userInputService.endDate;
}
public onStartDateChange(date: Date | null) {
this.userInputService.setStartDate(date);
}
public onEndDateChange(date: Date | null) {
this.userInputService.setEndDate(date);
}
}
@@ -3,22 +3,21 @@ import { provideZonelessChangeDetection } from '@angular/core';
import { UserSettings } from './user-settings';
describe('UserSettings', () => {
let component: UserSettings;
let fixture: ComponentFixture<UserSettings>;
let component: UserSettings;
let fixture: ComponentFixture<UserSettings>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserSettings],
providers: [provideZonelessChangeDetection()]
})
.compileComponents();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserSettings],
providers: [provideZonelessChangeDetection()],
}).compileComponents();
fixture = TestBed.createComponent(UserSettings);
component = fixture.componentInstance;
fixture.detectChanges();
});
fixture = TestBed.createComponent(UserSettings);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,38 @@
<!-- <mat-list-item role="listitem"> -->
<mat-card class="card">
<mat-form-field class="numberInput">
<input
matInput
type="number"
min="0"
[value]="day().numberOfDays"
(input)="onInput($event)"
/>
</mat-form-field>
<mat-form-field class="dayTypeInput">
<mat-select (valueChange)="onTypeChange($event)" [value]="day().type">
<mat-option></mat-option>
@for (dayType of selectableDayTypes; track $index) {
<mat-option [value]="dayType">{{
translationService.getDayTypeLabel(dayType)
}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="dateInput">
<mat-label>{{ tr().userInput.chooseDate }}</mat-label>
<input
matInput
type="datetime"
[matDatepicker]="picker"
[value]="day().expiryDate"
(dateChange)="addEvent($event)"
/>
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<button matFab aria-label="Delete entry" (click)="deleteEntry.emit()">
<mat-icon>close</mat-icon>
</button>
</mat-card>
<!-- </mat-list-item> -->
@@ -0,0 +1,72 @@
:host {
display: block;
container-type: inline-size;
width: 100%;
}
.card {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 16px;
gap: 16px;
}
.dateInput {
flex-grow: 1;
}
.numberInput {
width: 120px;
}
::ng-deep .mat-mdc-form-field-subscript-wrapper {
display: none;
}
@container (max-width: 480px) {
.card {
flex-wrap: wrap;
gap: 12px;
padding: 12px;
align-items: center;
}
.numberInput {
width: 70px;
flex-grow: 0;
}
.dayTypeInput {
flex-grow: 1;
width: 0;
min-width: 120px;
}
.dateInput {
width: 100%;
flex-grow: 1;
flex-basis: 100%;
margin-top: 4px;
}
button[matFab] {
width: 40px;
height: 40px;
min-width: 40px;
line-height: 40px;
::ng-deep .mat-mdc-button-touch-target {
width: 40px;
height: 40px;
}
mat-icon {
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
}
}
@@ -0,0 +1,54 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { MatDatepickerInputEvent } from '@angular/material/datepicker';
import { vi } from 'vitest';
import { DayInput } from './day-input';
import { SelectableDayType, VacDay } from '../../../services/calendar-service';
describe('DayInput', () => {
let component: DayInput;
let fixture: ComponentFixture<DayInput>;
const mockVacDay: VacDay = {
id: 'test-id',
type: SelectableDayType.CP,
numberOfDays: 5,
expiryDate: new Date('2025-12-31'),
};
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DayInput],
providers: [provideAnimationsAsync()],
}).compileComponents();
fixture = TestBed.createComponent(DayInput);
component = fixture.componentInstance;
fixture.componentRef.setInput('day', mockVacDay);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should emit dayChange on input', () => {
const spy = vi.spyOn(component.dayChange, 'emit');
component.onInput({ target: { value: '3' } } as unknown as Event);
expect(spy).toHaveBeenCalledWith({ ...mockVacDay, numberOfDays: 3 });
});
it('should emit dayChange on type change', () => {
const spy = vi.spyOn(component.dayChange, 'emit');
component.onTypeChange(SelectableDayType.RTT);
expect(spy).toHaveBeenCalledWith({ ...mockVacDay, type: SelectableDayType.RTT });
});
it('should emit dayChange on date input', () => {
const spy = vi.spyOn(component.dayChange, 'emit');
const newDate = new Date('2026-01-01');
component.addEvent({ value: newDate } as unknown as MatDatepickerInputEvent<Date>);
expect(spy).toHaveBeenCalledWith({ ...mockVacDay, expiryDate: newDate });
});
});
@@ -0,0 +1,127 @@
import {
Component,
input,
output,
ChangeDetectionStrategy,
Injectable,
inject,
effect,
} from '@angular/core';
import { SelectableDayType, VacDay } from '../../../services/calendar-service';
import { TranslationService } from '../../../services/translation-service';
import { MatCard } from '@angular/material/card';
import { MatOption, MatSelect } from '@angular/material/select';
import { MatInput, MatInputModule, MatLabel } from '@angular/material/input';
import {
MatDatepicker,
MatDatepickerInput,
MatDatepickerInputEvent,
MatDatepickerToggle,
} from '@angular/material/datepicker';
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
NativeDateAdapter,
} from '@angular/material/core';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
@Injectable()
class CustomDateAdapter extends NativeDateAdapter {
override format(date: Date, displayFormat: unknown): string {
if (displayFormat === 'input') {
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year}`;
}
return super.format(date, displayFormat as object);
}
override parse(value: string): Date | null {
if (!value) return null;
const parts = value.split('/');
if (parts.length === 3) {
const day = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1;
const year = parseInt(parts[2], 10);
if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
return new Date(year, month, day);
}
}
return null;
}
}
const MY_DATE_FORMATS = {
parse: {
dateInput: 'input',
},
display: {
dateInput: 'input',
monthYearLabel: { year: 'numeric', month: 'short' },
dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' },
monthYearA11yLabel: { year: 'numeric', month: 'long' },
},
};
@Component({
selector: 'app-day-input',
imports: [
MatIconModule,
MatCard,
MatSelect,
MatOption,
MatInput,
MatDatepicker,
MatLabel,
MatDatepickerToggle,
MatDatepickerInput,
MatInputModule,
MatButtonModule,
],
templateUrl: './day-input.html',
styleUrl: './day-input.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{ provide: DateAdapter, useClass: CustomDateAdapter },
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
{ provide: MAT_DATE_FORMATS, useValue: MY_DATE_FORMATS },
],
})
export class DayInput {
public day = input.required<VacDay>();
public dayChange = output<VacDay>();
public deleteEntry = output<void>();
protected readonly translationService = inject(TranslationService);
private readonly dateAdapter = inject(DateAdapter);
get tr() {
return this.translationService.tr;
}
constructor() {
effect(() => {
this.dateAdapter.setLocale(this.translationService.locale());
});
}
protected readonly selectableDayTypes = Object.values(SelectableDayType).filter(
(t) => t !== SelectableDayType.ANY,
);
onInput(event: Event) {
const value = Number((event.target as HTMLInputElement).value);
this.dayChange.emit({ ...this.day(), numberOfDays: value });
}
onTypeChange(type: SelectableDayType) {
this.dayChange.emit({ ...this.day(), type });
}
addEvent(event: MatDatepickerInputEvent<Date>) {
this.dayChange.emit({ ...this.day(), expiryDate: event.value! });
}
}
+326 -60
View File
@@ -1,12 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { vi } from 'vitest';
import { provideZonelessChangeDetection } from '@angular/core';
import {
CalendarService,
DayType,
SelectedDate,
SelectedDates,
SelectableDayType,
VacDay,
} from './calendar-service';
import { DateRange } from '@angular/material/datepicker';
import { IfThenRule, TimeWindowRule, RuleType } from './rule-service';
describe('CalendarService', () => {
let service: CalendarService;
@@ -22,18 +26,15 @@ describe('CalendarService', () => {
expect(service).toBeTruthy();
});
describe('getHolidays', () => {
describe('getHolidaysForPeriod', () => {
it('should return an array of holidays', () => {
const holidays = service.getHolidays();
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
const holidays = service.getHolidaysForPeriod(startDate, endDate);
expect(Array.isArray(holidays)).toBe(true);
expect(holidays.length).toBeGreaterThan(0);
});
it('should cache holidays for the same year', () => {
const holidays1 = service.getHolidays();
const holidays2 = service.getHolidays();
expect(holidays1).toEqual(holidays2);
});
});
describe('static date calculation methods', () => {
@@ -53,9 +54,12 @@ describe('CalendarService', () => {
expect(months.length).toBe(12);
});
it('should calculate months from now', () => {
const months = service.monthsInAYearFromNow();
expect(months.length).toBe(12);
it('should calculate months in period', () => {
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
const months = service.monthsInPeriod(startDate, endDate);
expect(months.length).toBeGreaterThanOrEqual(12);
});
it('should return week number for a date', () => {
@@ -66,15 +70,21 @@ describe('CalendarService', () => {
});
});
describe('getSelectedDatesForYear', () => {
it('should return selected dates for the year', () => {
const selectedDates = service.getSelectedDatesForYear();
describe('getSelectedDatesForPeriod', () => {
it('should return selected dates for the period', () => {
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
const selectedDates = service.getSelectedDatesForPeriod(startDate, endDate);
expect(selectedDates).toBeDefined();
expect(selectedDates._datesSelected.length).toBeGreaterThan(0);
});
it('should include weekends', () => {
const selectedDates = service.getSelectedDatesForYear();
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
const selectedDates = service.getSelectedDatesForPeriod(startDate, endDate);
const hasWeekend = selectedDates._datesSelected.some(
(date) => date.type === DayType.WEEKEND,
);
@@ -82,7 +92,10 @@ describe('CalendarService', () => {
});
it('should include holidays', () => {
const selectedDates = service.getSelectedDatesForYear();
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
const selectedDates = service.getSelectedDatesForPeriod(startDate, endDate);
const hasHoliday = selectedDates._datesSelected.some(
(date) => date.type === DayType.CLOSED_DAY,
);
@@ -90,7 +103,10 @@ describe('CalendarService', () => {
});
it('should include today', () => {
const selectedDates = service.getSelectedDatesForYear();
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
const selectedDates = service.getSelectedDatesForPeriod(startDate, endDate);
const hasToday = selectedDates._datesSelected.some(
(date) => date.type === DayType.TODAY,
);
@@ -111,47 +127,32 @@ describe('CalendarService', () => {
describe('SelectedDate', () => {
it('should check if date is selected', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const selectedDate = new SelectedDate(DayType.CP, new DateRange<Date>(date, date));
expect(selectedDate.isSelected(date)).toBe(true);
});
it('should check if date is start', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const selectedDate = new SelectedDate(DayType.CP, new DateRange<Date>(date, date));
expect(selectedDate.isStart(date)).toBe(true);
});
it('should check if date is end', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const selectedDate = new SelectedDate(DayType.CP, new DateRange<Date>(date, date));
expect(selectedDate.isEnd(date)).toBe(true);
});
it('should return correct classes for date', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const selectedDate = new SelectedDate(DayType.CP, new DateRange<Date>(date, date));
const classes = selectedDate.getClasses(date);
expect(classes).toContain(DayType.CP);
});
it('should return tooltip for selected date', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(date, date),
);
const selectedDate = new SelectedDate(DayType.CP, new DateRange<Date>(date, date));
const tooltip = selectedDate.getTooltip(date);
expect(tooltip).toBeTruthy();
});
@@ -159,19 +160,13 @@ describe('SelectedDate', () => {
it('should calculate length in days', () => {
const startDate = new Date(2024, 0, 1);
const endDate = new Date(2024, 0, 3);
const selectedDate = new SelectedDate(
DayType.CP,
new DateRange<Date>(startDate, endDate),
);
const selectedDate = new SelectedDate(DayType.CP, new DateRange<Date>(startDate, endDate));
expect(selectedDate.getLengthInDays()).toBe(3);
});
it('should not mark TODAY as selected', () => {
const date = new Date(2024, 0, 1);
const selectedDate = new SelectedDate(
DayType.TODAY,
new DateRange<Date>(date, date),
);
const selectedDate = new SelectedDate(DayType.TODAY, new DateRange<Date>(date, date));
expect(selectedDate.isSelected(date)).toBe(false);
});
});
@@ -180,18 +175,14 @@ describe('SelectedDates', () => {
it('should push selected dates', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
selectedDates.push(new SelectedDate(DayType.CP, new DateRange<Date>(date, date)));
expect(selectedDates._datesSelected.length).toBe(1);
});
it('should check if date is selected', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
selectedDates.push(new SelectedDate(DayType.CP, new DateRange<Date>(date, date)));
expect(selectedDates.isSelected(date)).toBe(true);
});
@@ -212,9 +203,7 @@ describe('SelectedDates', () => {
it('should compute heuristics', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
selectedDates.push(new SelectedDate(DayType.CP, new DateRange<Date>(date, date)));
const heuristic = selectedDates.computeHeuristics();
expect(typeof heuristic).toBe('number');
});
@@ -222,9 +211,7 @@ describe('SelectedDates', () => {
it('should return correct classes', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
selectedDates.push(new SelectedDate(DayType.CP, new DateRange<Date>(date, date)));
const classes = selectedDates.getClasses(date);
expect(classes).toContain(DayType.CP);
});
@@ -232,10 +219,289 @@ describe('SelectedDates', () => {
it('should return tooltip', () => {
const selectedDates = new SelectedDates();
const date = new Date(2024, 0, 1);
selectedDates.push(
new SelectedDate(DayType.CP, new DateRange<Date>(date, date)),
);
selectedDates.push(new SelectedDate(DayType.CP, new DateRange<Date>(date, date)));
const tooltip = selectedDates.getTooltip(date);
expect(tooltip).toBeTruthy();
});
describe('optimizeVacations and rules engine', () => {
let calendarService: CalendarService;
let selectedDates: SelectedDates;
beforeEach(() => {
calendarService = TestBed.inject(CalendarService);
selectedDates = new SelectedDates();
});
it('should execute rules sequentially in order', async () => {
const vacationDays: VacDay[] = [];
const rules: (TimeWindowRule | IfThenRule)[] = [
{ id: '1', type: RuleType.IF_THEN, enabled: true } as IfThenRule,
{ id: '2', type: RuleType.TIME_WINDOW, enabled: true } as TimeWindowRule,
{ id: '3', type: RuleType.IF_THEN, enabled: true } as IfThenRule,
];
const applyTimeWindowRulesSpy = vi.spyOn(selectedDates, 'applyTimeWindowRules');
const applyIfThenRulesSpy = vi.spyOn(selectedDates, 'applyIfThenRules');
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 1);
await selectedDates.optimizeVacations(
vacationDays,
rules,
calendarService,
startDate,
endDate,
);
expect(applyIfThenRulesSpy.mock.calls.length).toBe(2);
expect(applyTimeWindowRulesSpy.mock.calls.length).toBe(1);
// First rule is IF_THEN
expect(applyIfThenRulesSpy.mock.calls[0][1][0].id).toBe('1');
// Second rule is TIME_WINDOW
expect(applyTimeWindowRulesSpy.mock.calls[0][1][0].id).toBe('2');
// Third rule is IF_THEN
expect(applyIfThenRulesSpy.mock.calls[1][1][0].id).toBe('3');
});
it('should apply time window rules', () => {
const futureYear = new Date().getFullYear() + 2;
const vacationDays: VacDay[] = [
{
id: '1',
type: SelectableDayType.CP,
numberOfDays: 5,
expiryDate: new Date(futureYear + 1, 11, 31),
},
];
const rules: TimeWindowRule[] = [
{
id: 't1',
name: 'Time Window',
type: RuleType.TIME_WINDOW,
enabled: true,
startDate: new Date(futureYear, 11, 20),
endDate: new Date(futureYear, 11, 25),
placeDays: 3,
placeType: SelectableDayType.CP,
},
];
// Mock monthsInPeriod to return a controlled range of days
vi.spyOn(calendarService, 'monthsInPeriod').mockReturnValue([
[
[
new Date(futureYear, 11, 20),
new Date(futureYear, 11, 21),
new Date(futureYear, 11, 22),
new Date(futureYear, 11, 23),
new Date(futureYear, 11, 24),
],
],
]);
const startDate = new Date(futureYear, 11, 20);
const endDate = new Date(futureYear, 11, 25);
selectedDates.applyTimeWindowRules(
vacationDays,
rules,
calendarService,
startDate,
endDate,
);
// Should have placed 3 days
const placedCount = selectedDates._datesSelected.filter(
(d) => d.type === DayType.CP,
).length;
expect(placedCount).toBe(3);
// Should have consumed 3 days from the pool
expect(vacationDays[0].numberOfDays).toBe(2);
});
it('should apply if-then rules', () => {
const futureYear = new Date().getFullYear() + 2;
const vacationDays: VacDay[] = [
{
id: '1',
type: SelectableDayType.CP,
numberOfDays: 5,
expiryDate: new Date(futureYear + 1, 11, 31),
},
];
const rules: IfThenRule[] = [
{
id: 'i1',
name: 'Samedi Malin',
type: RuleType.IF_THEN,
enabled: true,
dayOfWeek: 6, // Saturday
placeDays: 2,
placeType: SelectableDayType.CP,
placement: 'auto',
rewardDays: 1,
rewardType: SelectableDayType.OTHER,
},
];
// Setup a mock holiday on a Saturday
// Use a date far in the future so it doesn't fail the "past date" check
const saturdayHoliday = new Date(new Date().getFullYear() + 2, 11, 5); // Dec 5 is usually Saturday or close, let's just make sure we get a Saturday
// To guarantee it's a Saturday for the test:
while (saturdayHoliday.getDay() !== 6) {
saturdayHoliday.setDate(saturdayHoliday.getDate() + 1);
}
selectedDates._datesSelected.push(
new SelectedDate(
DayType.CLOSED_DAY,
new DateRange<Date>(saturdayHoliday, saturdayHoliday),
),
);
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 3);
selectedDates.applyIfThenRules(vacationDays, rules, startDate, endDate);
// Should have placed 2 CP days (e.g. Thu/Fri, Mon/Tue, or Fri/Mon)
const placedCP = selectedDates._datesSelected.filter(
(d) => d.type === DayType.CP,
).length;
expect(placedCP).toBe(2);
// Should have consumed 2 CP days
expect(vacationDays[0].numberOfDays).toBe(3);
// Should have added 1 OTHER reward day
const rewardDay = vacationDays.find((vd) => vd.type === SelectableDayType.OTHER);
expect(rewardDay).toBeDefined();
expect(rewardDay?.numberOfDays).toBe(1);
});
it('should apply time window rules with ANY type', () => {
const futureYear = new Date().getFullYear() + 2;
const vacationDays: VacDay[] = [
{
id: '1',
type: SelectableDayType.CP,
numberOfDays: 2,
expiryDate: new Date(futureYear, 11, 31),
},
{
id: '2',
type: SelectableDayType.RTT,
numberOfDays: 2,
expiryDate: new Date(futureYear - 1, 11, 31), // Expires earlier
},
];
const rules: TimeWindowRule[] = [
{
id: 'tw1',
name: 'Vacances',
type: RuleType.TIME_WINDOW,
enabled: true,
startDate: new Date(futureYear, 6, 1),
endDate: new Date(futureYear, 6, 15),
placeDays: 3,
placeType: SelectableDayType.ANY,
},
];
vi.spyOn(calendarService, 'monthsInPeriod').mockReturnValue([
[
[
new Date(futureYear, 6, 1),
new Date(futureYear, 6, 2),
new Date(futureYear, 6, 3),
new Date(futureYear, 6, 4),
new Date(futureYear, 6, 5),
],
],
]);
const startDate = new Date(futureYear, 6, 1);
const endDate = new Date(futureYear, 6, 15);
selectedDates.applyTimeWindowRules(
vacationDays,
rules,
calendarService,
startDate,
endDate,
);
// Should have placed 3 days total: 2 RTT (earlier expiry) and 1 CP
const placedRTT = selectedDates._datesSelected.filter(
(d) => d.type === DayType.RTT,
).length;
const placedCP = selectedDates._datesSelected.filter(
(d) => d.type === DayType.CP,
).length;
expect(placedRTT).toBe(2);
expect(placedCP).toBe(1);
// Should have consumed 2 RTT and 1 CP
expect(vacationDays[0].numberOfDays).toBe(1); // CP remaining
expect(vacationDays[1].numberOfDays).toBe(0); // RTT remaining
});
it('should apply if-then rules with ANY type', () => {
const futureYear = new Date().getFullYear() + 2;
const vacationDays: VacDay[] = [
{
id: '1',
type: SelectableDayType.CP,
numberOfDays: 2,
expiryDate: new Date(futureYear + 2, 11, 31),
},
{
id: '2',
type: SelectableDayType.RTT,
numberOfDays: 2,
expiryDate: new Date(futureYear + 1, 11, 31), // Expires earlier
},
];
const rules: IfThenRule[] = [
{
id: 'i1',
name: 'Rule',
type: RuleType.IF_THEN,
enabled: true,
dayOfWeek: 6, // Saturday
placeDays: 3,
placeType: SelectableDayType.ANY,
placement: 'auto',
rewardDays: 0,
rewardType: SelectableDayType.CP,
},
];
const saturdayHoliday = new Date(new Date().getFullYear() + 2, 11, 5);
while (saturdayHoliday.getDay() !== 6) {
saturdayHoliday.setDate(saturdayHoliday.getDate() + 1);
}
selectedDates._datesSelected.push(
new SelectedDate(
DayType.CLOSED_DAY,
new DateRange<Date>(saturdayHoliday, saturdayHoliday),
),
);
const startDate = new Date();
const endDate = new Date();
endDate.setFullYear(endDate.getFullYear() + 3);
selectedDates.applyIfThenRules(vacationDays, rules, startDate, endDate);
// Should have placed 3 days total: 2 RTT (earlier expiry) and 1 CP
const placedRTT = selectedDates._datesSelected.filter(
(d) => d.type === DayType.RTT,
).length;
const placedCP = selectedDates._datesSelected.filter(
(d) => d.type === DayType.CP,
).length;
expect(placedRTT).toBe(2);
expect(placedCP).toBe(1);
});
});
});
+448 -212
View File
@@ -4,8 +4,8 @@ import Holidays, { HolidaysTypes } from 'date-holidays';
import dayjs from 'dayjs';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import isoWeek from 'dayjs/plugin/isoWeek';
import { CalendarSettingsService } from './calendar-settings-service';
import { DateCacheService } from './date-cache.service';
import { VacationRule, RuleType, IfThenRule, TimeWindowRule } from './rule-service';
dayjs.extend(weekOfYear);
dayjs.extend(isoWeek);
@@ -22,22 +22,10 @@ const allTypesOptions: HolidaysTypes.Options = {
export class CalendarService {
private dateCache = inject(DateCacheService);
// Cache for holiday timestamps
private holidayTimestampsCache: Set<number> | null = null;
private holidayCacheYear: number | null = null;
// Get selected dates from current date to the next year
getSelectedDatesFromNow(): SelectedDates {
const holidays = this.getHolidays();
let selectedDates: SelectedDates = new SelectedDates();
// Logic to populate selectedDates from now until the next year
return selectedDates;
}
getSelectedDatesForYear(): SelectedDates {
const holidays = this.getHolidays();
const monthInAYear = this.monthsInAYearFromNow();
let selectedDates: SelectedDates = new SelectedDates();
getSelectedDatesForPeriod(startDate: Date, endDate: Date): SelectedDates {
const holidays = this.getHolidaysForPeriod(startDate, endDate);
const monthInPeriod = this.monthsInPeriod(startDate, endDate);
const selectedDates: SelectedDates = new SelectedDates();
// Use DateCacheService to get today's date components
const {
@@ -49,7 +37,7 @@ export class CalendarService {
// Create a Set of holiday timestamps for O(1) lookup instead of O(n)
const holidayTimestamps = new Set(holidays.map((h) => h.getTime()));
for (const weeksInAMonth of monthInAYear) {
for (const weeksInAMonth of monthInPeriod) {
for (const daysInAWeek of weeksInAMonth) {
for (const day of daysInAWeek) {
if (day) {
@@ -101,24 +89,16 @@ export class CalendarService {
}
}
getHolidays(): Date[] {
// Cache holidays to avoid recalculation
const currentYear = this.dateCache.getCurrentYear();
if (this.holidayCacheYear === currentYear && this.holidayTimestampsCache) {
// Return timestamps directly for efficient lookup
return Array.from(this.holidayTimestampsCache).map((ts) => new Date(ts));
}
// Logic to return holidays for the given year
getHolidaysForPeriod(startDate: Date, endDate: Date): Date[] {
const startYear = startDate.getFullYear();
const endYear = endDate.getFullYear();
const holidaysDate: Date[] = [];
const hd = new Holidays('FR', allTypesOptions);
const holidays = hd.getHolidays(currentYear, lang);
const holidaysPlusOne = hd.getHolidays(currentYear + 1, lang);
holidays.push(...holidaysPlusOne);
const holidaysDate: Date[] = holidays.map((holiday) => new Date(holiday.date));
// Update cache
this.holidayTimestampsCache = new Set(holidaysDate.map((d) => d.getTime()));
this.holidayCacheYear = currentYear;
for (let y = startYear; y <= endYear; y++) {
const yearHolidays = hd.getHolidays(y, lang);
holidaysDate.push(...yearHolidays.map((holiday) => new Date(holiday.date)));
}
return holidaysDate;
}
@@ -167,14 +147,15 @@ export class CalendarService {
return months;
}
monthsInAYearFromNow(): Year {
monthsInPeriod(startDate: Date, endDate: Date): Year {
const months: Year = [];
const currentMonthIndex = this.dateCache.getCurrentMonth();
const currentYear = this.dateCache.getCurrentYear();
for (let i = 0; i < 12; i++) {
const monthIndex = (currentMonthIndex + i) % 12;
const year = currentYear + Math.floor((currentMonthIndex + i) / 12);
months.push(CalendarService.weeksInMonth(monthIndex, year));
const start = dayjs(startDate).startOf('month');
const end = dayjs(endDate).startOf('month');
let current = start;
while (current.isBefore(end) || current.isSame(end, 'month')) {
months.push(CalendarService.weeksInMonth(current.month(), current.year()));
current = current.add(1, 'month');
}
return months;
}
@@ -184,10 +165,11 @@ export class CalendarService {
}
}
export type VacationsNumber = {
rtt: number;
cp: number;
other: number;
export type VacDay = {
id: string;
type: SelectableDayType;
numberOfDays: number;
expiryDate: Date;
};
export type Week = (Date | null)[];
@@ -203,13 +185,21 @@ export enum DayType {
TODAY = 'today',
}
export const tooltipTypeMapping: { [key in DayType]: string } = {
export enum SelectableDayType {
CP = DayType.CP,
RTT = DayType.RTT,
OTHER = DayType.OTHER,
ANY = 'any',
}
export const tooltipTypeMapping: { [key in DayType | SelectableDayType]: string } = {
[DayType.WEEKEND]: 'Weekend',
[DayType.CLOSED_DAY]: 'Jour Férié',
[DayType.RTT]: 'RTT',
[DayType.CP]: 'Congé Payé',
[DayType.OTHER]: 'Autre',
[DayType.TODAY]: "Aujourd'hui",
[SelectableDayType.ANY]: "N'importe",
};
export const monthNames: string[] = [
@@ -266,7 +256,7 @@ export class SelectedDate implements SelectedDateInterface {
getClasses(date: Date): string {
let returnType: DayType | string = '';
if (this.type && date >= this.range.start! && date <= this.range.end!) {
returnType = returnType += this.type + ' ';
returnType += this.type + ' ';
}
return returnType;
}
@@ -501,7 +491,7 @@ export class SelectedDates implements SelectedDateInterface {
// Check if adjacent (next day) - this is the best case!
if (nextStartTime - currentEndTime === oneDayInMs) {
totalScore += 10; // Big bonus for adjacency
totalScore += 15; // 50% increased bonus for adjacency; adjacency is highly valued in the scoring system
} else if (nextStartTime > currentEndTime) {
// Penalty based on days between the two dates
const daysBetween = Math.floor((nextStartTime - currentEndTime) / oneDayInMs) - 1;
@@ -519,7 +509,7 @@ export class SelectedDates implements SelectedDateInterface {
const daysInRange = selectedDate.getLengthInDays();
// For each day in the range, add it to the appropriate month
let currentDate = new Date(selectedDate.range.start);
const currentDate = new Date(selectedDate.range.start);
for (let i = 0; i < daysInRange; i++) {
const monthKey = currentDate.getFullYear() * 12 + currentDate.getMonth();
monthCounts.set(monthKey, (monthCounts.get(monthKey) ?? 0) + 1);
@@ -535,204 +525,449 @@ export class SelectedDates implements SelectedDateInterface {
return totalScore;
}
optimizeVacations(
vacationsNumber: VacationsNumber,
calendarSettingsService: CalendarSettingsService,
async optimizeVacations(
vacationDays: VacDay[],
rules: VacationRule[],
calendarService: CalendarService,
): void {
if (calendarSettingsService.samediMalin()) this.samediMalin(vacationsNumber);
while (vacationsNumber.cp > 0) {
const result = this.lookForVacation(DayType.CP, calendarService);
if (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {
startDate: Date,
endDate: Date,
progressCallback?: (progress: number) => void,
): Promise<VacDay[]> {
// Calculate total days to process
const totalDays = vacationDays.reduce((sum, vd) => sum + vd.numberOfDays, 0);
let processedDays = 0;
// Execute custom rules sequentially in the exact order they appear in the list
for (const rule of rules) {
if (!rule.enabled) continue;
if (rule.type === RuleType.TIME_WINDOW) {
this.applyTimeWindowRules(
vacationDays,
[rule as TimeWindowRule],
calendarService,
startDate,
endDate,
);
} else if (rule.type === RuleType.IF_THEN) {
this.applyIfThenRules(vacationDays, [rule as IfThenRule], startDate, endDate);
}
}
// Helper function to update progress, throttled to every 5%
let lastReportedProgress = 0;
const PROGRESS_STEP = 5; // percent
const updateProgress = async () => {
if (progressCallback && totalDays > 0) {
const progress = Math.round((processedDays / totalDays) * 100);
if (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {
progressCallback(progress);
lastReportedProgress = progress;
// Allow UI to update with a microtask
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
};
await updateProgress();
// Track VacDays that can't find valid placements
const exhaustedVacDayIds = new Set<string>();
// Process vacation days ordered by expiration date (earliest first)
while (vacationDays.some((vd) => vd.numberOfDays > 0 && !exhaustedVacDayIds.has(vd.id))) {
// Sort by expiration date to use days that expire first, excluding exhausted ones
const sortedVacDays = [...vacationDays]
.filter((vd) => vd.numberOfDays > 0 && !exhaustedVacDayIds.has(vd.id))
.sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime());
if (sortedVacDays.length === 0) {
break;
}
vacationsNumber.cp -= 1;
}
while (vacationsNumber.rtt > 0) {
const result = this.lookForVacation(DayType.RTT, calendarService);
const vacDayToUse = sortedVacDays[0];
const result = this.lookForVacation(vacDayToUse, calendarService, startDate, endDate);
if (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {
break;
}
vacationsNumber.rtt -= 1;
}
while (vacationsNumber.other > 0) {
const result = this.lookForVacation(DayType.OTHER, calendarService);
if (result.heuristic === SelectedDates.NO_VALID_STRATEGY_HEURISTIC) {
break;
}
vacationsNumber.other -= 1;
}
}
samediMalin(vacationsNumber: VacationsNumber): void {
const now = new Date();
now.setHours(0, 0, 0, 0);
const nowTime = now.getTime();
const samedi_ferie = this._datesSelected.filter(
(date) => date.type === DayType.CLOSED_DAY && date.range.start?.getDay() === 6,
);
for (const holiday of samedi_ferie) {
if (!holiday.range.start) continue;
// Skip if no CP days available
if (vacationsNumber.cp < 2) {
// Mark this VacDay as exhausted and try other vacation days
exhaustedVacDayIds.add(vacDayToUse.id);
continue;
}
// Get the Saturday date (holiday) and the following Sunday
const saturdayDate = new Date(holiday.range.start);
const sundayDate = new Date(saturdayDate);
sundayDate.setDate(saturdayDate.getDate() + 1);
vacDayToUse.numberOfDays -= 1;
processedDays++;
await updateProgress();
}
// Calculate dates for all three strategies (2 CP days total each)
// Strategy 1: 2 days before (Thursday + Friday)
const twoDaysBefore1 = new Date(saturdayDate);
twoDaysBefore1.setDate(saturdayDate.getDate() - 2);
const twoDaysBefore2 = new Date(saturdayDate);
twoDaysBefore2.setDate(saturdayDate.getDate() - 1);
// Final progress update (100%)
if (progressCallback) {
progressCallback(100);
}
// Strategy 2: 2 days after (Monday + Tuesday)
const twoDaysAfter1 = new Date(sundayDate);
twoDaysAfter1.setDate(sundayDate.getDate() + 1);
const twoDaysAfter2 = new Date(sundayDate);
twoDaysAfter2.setDate(sundayDate.getDate() + 2);
// Inform user if not all days were placed
const remainingDays = vacationDays.reduce((sum, vd) => sum + vd.numberOfDays, 0);
if (remainingDays > 0) {
console.warn(
`Optimization stopped: ${remainingDays} of ${totalDays} vacation days remain unplaced. ` +
`Too many days have equivalent placement quality - please place remaining days manually.`,
);
}
// Strategy 3: 1 day before + 1 day after (Friday + Monday)
const oneDayBefore = new Date(saturdayDate);
oneDayBefore.setDate(saturdayDate.getDate() - 1);
const oneDayAfter = new Date(sundayDate);
oneDayAfter.setDate(sundayDate.getDate() + 1);
return vacationDays;
}
// Check if we can apply each strategy (and not in the past)
const canApplyTwoBeforeStrategy =
twoDaysBefore1.getTime() >= nowTime &&
!this.isSelected(twoDaysBefore1) &&
!this.isSelected(twoDaysBefore2);
applyTimeWindowRules(
vacationDays: VacDay[],
rules: TimeWindowRule[],
calendarService: CalendarService,
startDate: Date,
endDate: Date,
): void {
for (const rule of rules) {
let daysToPlace = rule.placeDays;
const canApplyTwoAfterStrategy =
twoDaysAfter1.getTime() >= nowTime &&
!this.isSelected(twoDaysAfter1) &&
!this.isSelected(twoDaysAfter2);
const consumeDays = (count: number): boolean => {
let needed = count;
const availableDays = vacationDays
.filter(
(vd) =>
(rule.placeType === SelectableDayType.ANY ||
vd.type === rule.placeType) &&
vd.numberOfDays > 0,
)
.sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime());
const canApplyMixedStrategy =
oneDayBefore.getTime() >= nowTime &&
!this.isSelected(oneDayBefore) &&
!this.isSelected(oneDayAfter);
for (const vd of availableDays) {
if (needed === 0) break;
const take = Math.min(vd.numberOfDays, needed);
vd.numberOfDays -= take;
needed -= take;
}
return needed === 0;
};
// Test all applicable strategies and choose the one with the best heuristic score
const strategies: { apply: () => void; heuristic: number }[] = [];
const tempDatesBackup = [...this._datesSelected];
const totalAvailable = vacationDays
.filter(
(vd) => rule.placeType === SelectableDayType.ANY || vd.type === rule.placeType,
)
.reduce((sum, vd) => sum + vd.numberOfDays, 0);
if (canApplyTwoBeforeStrategy) {
this.push(
new SelectedDate(
DayType.CP,
new DateRange<Date>(twoDaysBefore1, twoDaysBefore2),
),
if (totalAvailable < daysToPlace) {
console.warn(
`Insufficient ${rule.placeType} days for Time Window Rule "${rule.name}". Needs ${daysToPlace}, has ${totalAvailable}. Placing ${totalAvailable} days instead.`,
);
const heuristic = this.computeHeuristics();
strategies.push({
apply: () => {
this._datesSelected = [...tempDatesBackup];
this.push(
new SelectedDate(
DayType.CP,
new DateRange<Date>(twoDaysBefore1, twoDaysBefore2),
),
);
vacationsNumber.cp -= 2;
vacationsNumber.other += 1; // Reward: gain 1 other day
},
heuristic: heuristic,
});
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
daysToPlace = totalAvailable;
}
if (canApplyTwoAfterStrategy) {
this.push(
new SelectedDate(DayType.CP, new DateRange<Date>(twoDaysAfter1, twoDaysAfter2)),
);
const heuristic = this.computeHeuristics();
strategies.push({
apply: () => {
this._datesSelected = [...tempDatesBackup];
this.push(
new SelectedDate(
DayType.CP,
new DateRange<Date>(twoDaysAfter1, twoDaysAfter2),
),
);
vacationsNumber.cp -= 2;
vacationsNumber.other += 1; // Reward: gain 1 other day
},
heuristic: heuristic,
});
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
if (daysToPlace <= 0) continue;
const getNextAvailableType = (): DayType => {
const sorted = vacationDays
.filter(
(vd) =>
(rule.placeType === SelectableDayType.ANY ||
vd.type === rule.placeType) &&
vd.numberOfDays > 0,
)
.sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime());
return sorted.length > 0
? (sorted[0].type as unknown as DayType)
: ((rule.placeType === SelectableDayType.ANY
? SelectableDayType.CP
: rule.placeType) as unknown as DayType);
};
const candidates: Date[] = [];
const daysOfYear = calendarService.monthsInPeriod(startDate, endDate);
const start = new Date(rule.startDate).getTime();
const end = new Date(rule.endDate).getTime();
const startLimit = startDate.getTime();
const endLimit = endDate.getTime();
for (const month of daysOfYear) {
for (const week of month) {
for (const day of week) {
if (day) {
const dayTime = day.getTime();
if (
dayTime >= start &&
dayTime <= end &&
dayTime >= startLimit &&
dayTime <= endLimit &&
!this.isSelected(day)
) {
candidates.push(day);
}
}
}
}
}
if (canApplyMixedStrategy) {
this.push(
new SelectedDate(DayType.CP, new DateRange<Date>(oneDayBefore, oneDayBefore)),
);
this.push(
new SelectedDate(DayType.CP, new DateRange<Date>(oneDayAfter, oneDayAfter)),
);
const heuristic = this.computeHeuristics();
strategies.push({
apply: () => {
this._datesSelected = [...tempDatesBackup];
this.push(
new SelectedDate(
DayType.CP,
new DateRange<Date>(oneDayBefore, oneDayBefore),
),
);
this.push(
new SelectedDate(
DayType.CP,
new DateRange<Date>(oneDayAfter, oneDayAfter),
),
);
vacationsNumber.cp -= 2;
vacationsNumber.other += 1; // Reward: gain 1 other day
},
heuristic: heuristic,
});
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
}
for (let i = 0; i < daysToPlace; i++) {
if (candidates.length === 0) break;
// Apply the strategy with the best heuristic score
if (strategies.length > 0) {
const bestStrategy = strategies.reduce((best, current) =>
current.heuristic > best.heuristic ? current : best,
);
bestStrategy.apply();
const currentPlaceType = getNextAvailableType();
let bestCandidate: Date | null = null;
let bestHeuristic = -Infinity;
let bestCandidateIndex = -1;
for (let j = 0; j < candidates.length; j++) {
const day = candidates[j];
if (this.isSelected(day)) continue;
const tempDatesBackup = [...this._datesSelected];
this._datesSelected.push(
new SelectedDate(currentPlaceType, new DateRange<Date>(day, day)),
);
this.datesSelected = this._datesSelected.slice();
this.grouping();
const heuristic = this.computeHeuristics();
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
if (heuristic > bestHeuristic) {
bestHeuristic = heuristic;
bestCandidate = day;
bestCandidateIndex = j;
}
}
if (bestCandidate) {
this.push(
new SelectedDate(
currentPlaceType,
new DateRange<Date>(bestCandidate, bestCandidate),
),
);
consumeDays(1);
candidates.splice(bestCandidateIndex, 1);
}
}
}
}
lookForVacation(vacType: DayType, calendarService: CalendarService): { apply: () => void; heuristic: number } {
const daysOfYear = calendarService.monthsInAYearFromNow();
applyIfThenRules(
vacationDays: VacDay[],
rules: IfThenRule[],
startDate: Date,
endDate: Date,
): void {
const nowTime = new Date().setHours(0, 0, 0, 0);
const startLimit = Math.max(nowTime, startDate.getTime());
const endLimit = endDate.getTime();
const consumeDays = (count: number, type: SelectableDayType, useByDate: Date): boolean => {
let daysToConsume = count;
const useByTime = useByDate.getTime();
const availableDays = vacationDays
.filter(
(vd) =>
(type === SelectableDayType.ANY || vd.type === type) &&
vd.numberOfDays > 0 &&
vd.expiryDate.getTime() >= useByTime,
)
.sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime());
for (const vd of availableDays) {
if (daysToConsume === 0) break;
const take = Math.min(vd.numberOfDays, daysToConsume);
vd.numberOfDays -= take;
daysToConsume -= take;
}
return daysToConsume === 0;
};
const checkAvailableDays = (
count: number,
type: SelectableDayType,
useByDate: Date,
): boolean => {
const useByTime = useByDate.getTime();
const total = vacationDays
.filter(
(vd) =>
(type === SelectableDayType.ANY || vd.type === type) &&
vd.expiryDate.getTime() >= useByTime,
)
.reduce((sum, vd) => sum + vd.numberOfDays, 0);
return total >= count;
};
const addRewardDays = (count: number, type: SelectableDayType): void => {
if (count <= 0) return;
const existing = vacationDays.find((vd) => vd.type === type);
if (existing) {
existing.numberOfDays += count;
} else {
vacationDays.push({
id: crypto.randomUUID(),
type: type,
numberOfDays: count,
expiryDate: new Date(new Date().getFullYear() + 10, 11, 31),
});
}
};
const getWorkingDays = (startDate: Date, direction: 1 | -1, count: number): Date[] => {
const result: Date[] = [];
const current = new Date(startDate);
while (result.length < count) {
current.setDate(current.getDate() + direction);
const isWeekend = current.getDay() === 0 || current.getDay() === 6;
const isHoliday = this._datesSelected.some(
(d) =>
d.type === DayType.CLOSED_DAY &&
d.range.start?.getTime() === current.getTime(),
);
if (!isWeekend && !isHoliday) {
result.push(new Date(current));
}
}
return result;
};
for (const rule of rules) {
const matchingHolidays = this._datesSelected.filter(
(date) =>
date.type === DayType.CLOSED_DAY &&
date.range.start?.getDay() === rule.dayOfWeek,
);
for (const holiday of matchingHolidays) {
if (!holiday.range.start) continue;
const holidayDate = new Date(holiday.range.start);
const N = rule.placeDays;
if (N <= 0) continue;
const placementOptions: Date[][] = [];
if (rule.placement === 'before' || rule.placement === 'auto') {
placementOptions.push(getWorkingDays(holidayDate, -1, N));
}
if (rule.placement === 'after' || rule.placement === 'auto') {
placementOptions.push(getWorkingDays(holidayDate, 1, N));
}
if (rule.placement === 'around' || rule.placement === 'auto') {
const beforeCount = Math.ceil(N / 2);
const afterCount = Math.floor(N / 2);
const aroundDates = [
...getWorkingDays(holidayDate, -1, beforeCount),
...getWorkingDays(holidayDate, 1, afterCount),
];
placementOptions.push(aroundDates);
}
const strategies: { apply: () => void; heuristic: number }[] = [];
const tempDatesBackup = [...this._datesSelected];
for (const dates of placementOptions) {
const allValid = dates.every(
(d) =>
d.getTime() >= startLimit &&
d.getTime() <= endLimit &&
!this.isSelected(d),
);
if (!allValid) continue;
const maxDate = new Date(Math.max(...dates.map((d) => d.getTime())));
if (!checkAvailableDays(N, rule.placeType, maxDate)) continue;
const simulatedDays = vacationDays.map((vd) => ({ ...vd }));
const datesWithTypes: { date: Date; type: DayType }[] = [];
for (const d of dates) {
const sorted = simulatedDays
.filter(
(vd) =>
(rule.placeType === SelectableDayType.ANY ||
vd.type === rule.placeType) &&
vd.numberOfDays > 0 &&
vd.expiryDate.getTime() >= maxDate.getTime(),
)
.sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime());
let chosenType = (rule.placeType === SelectableDayType.ANY
? SelectableDayType.CP
: rule.placeType) as unknown as DayType;
if (sorted.length > 0) {
sorted[0].numberOfDays--;
chosenType = sorted[0].type as unknown as DayType;
}
datesWithTypes.push({ date: d, type: chosenType });
}
for (const dt of datesWithTypes) {
this.push(new SelectedDate(dt.type, new DateRange<Date>(dt.date, dt.date)));
}
const heuristic = this.computeHeuristics();
strategies.push({
apply: () => {
this._datesSelected = [...tempDatesBackup];
for (const dt of datesWithTypes) {
this.push(
new SelectedDate(
dt.type,
new DateRange<Date>(dt.date, dt.date),
),
);
}
consumeDays(N, rule.placeType, maxDate);
addRewardDays(rule.rewardDays, rule.rewardType);
},
heuristic: heuristic,
});
this._datesSelected = [...tempDatesBackup];
this.datesSelected = this._datesSelected.slice();
this.grouping();
}
if (strategies.length > 0) {
const bestStrategy = strategies.reduce((best, current) =>
current.heuristic > best.heuristic ? current : best,
);
bestStrategy.apply();
} else {
console.warn(
`Could not apply If-Then Rule "${rule.name}" for holiday on ${holidayDate.toLocaleDateString()}. Candidates invalid or insufficient days.`,
);
}
}
}
}
lookForVacation(
vacDay: VacDay,
calendarService: CalendarService,
startDate: Date,
endDate: Date,
): { apply: () => void; heuristic: number } {
const daysOfYear = calendarService.monthsInPeriod(startDate, endDate);
const now = new Date();
now.setHours(0, 0, 0, 0);
const nowTime = now.getTime();
const startLimit = Math.max(nowTime, startDate.getTime());
const endLimit = endDate.getTime();
const expiryTime = vacDay.expiryDate.getTime();
// Preprocess: filter out past days and already selected days in one pass
// Preprocess: filter out past days, already selected days, and days after expiry date
const candidateDays: Date[] = [];
for (const month of daysOfYear) {
for (const week of month) {
for (const day of week) {
if (day) {
day.setHours(0, 0, 0, 0);
if (day.getTime() >= nowTime && !this.isSelected(day)) {
const dayTime = day.getTime();
if (
dayTime >= startLimit &&
dayTime <= endLimit &&
dayTime <= expiryTime &&
!this.isSelected(day)
) {
candidateDays.push(day);
}
}
@@ -745,6 +980,7 @@ export class SelectedDates implements SelectedDateInterface {
return { apply: () => {}, heuristic: SelectedDates.NO_VALID_STRATEGY_HEURISTIC };
}
const vacType = vacDay.type as unknown as DayType;
const tempDatesBackup = [...this._datesSelected];
let bestStrategy: { apply: () => void; heuristic: number; value: Date } | null = null;
let bestHeuristic = -Infinity;
@@ -17,17 +17,21 @@ describe('CalendarSettingsService', () => {
});
it('should initialize with default values', () => {
expect(service.showWeekNumbers()).toBe(false);
expect(service.samediMalin()).toBe(true);
expect(service.get('showWeekNumbers')()).toBe(false);
});
it('should allow updating showWeekNumbers', () => {
service.showWeekNumbers.set(true);
expect(service.showWeekNumbers()).toBe(true);
service.get('showWeekNumbers').set(true);
expect(service.get('showWeekNumbers')()).toBe(true);
});
it('should allow updating samediMalin', () => {
service.samediMalin.set(false);
expect(service.samediMalin()).toBe(false);
it('should provide settings array for UI iteration', () => {
const settingsArray = service.settingsArray;
expect(settingsArray.length).toBe(1);
expect(settingsArray[0].id).toBe('showWeekNumbers');
});
it('should return correct descriptions', () => {
expect(service.getDescription('showWeekNumbers')).toBe('Afficher les numéros de semaine');
});
});
+55 -3
View File
@@ -1,9 +1,61 @@
import { Injectable, signal } from '@angular/core';
import { Injectable, WritableSignal, inject, Injector } from '@angular/core';
import { storedSignal } from '../shared/utils/stored-signal';
interface SettingDefinition {
defaultValue: boolean;
description: string;
}
@Injectable({
providedIn: 'root',
})
export class CalendarSettingsService {
readonly showWeekNumbers = signal(false);
readonly samediMalin = signal(true);
private readonly injector = inject(Injector);
private readonly definitions = new Map<string, SettingDefinition>([
[
'showWeekNumbers',
{
defaultValue: false,
description: 'Afficher les numéros de semaine',
},
],
]);
private readonly signals = new Map<string, WritableSignal<boolean>>();
readonly settingsArray: ReadonlyArray<{
id: string;
signal: WritableSignal<boolean>;
description: string;
}>;
constructor() {
for (const [id, def] of this.definitions) {
const storageKey = `calendarSettings.${id}`;
this.signals.set(id, storedSignal(storageKey, def.defaultValue, this.injector));
}
this.settingsArray = Array.from(this.definitions.entries()).map(([id, def]) => ({
id,
signal: this.signals.get(id)!,
description: def.description,
}));
}
/**
* Returns the signal for the given setting ID.
* @throws Error if the setting ID doesn't exist
*/
get(id: string): WritableSignal<boolean> {
const signal = this.signals.get(id);
if (!signal) {
throw new Error(`Setting with id "${id}" does not exist`);
}
return signal;
}
getDescription(id: string): string {
return this.definitions.get(id)?.description ?? '';
}
}
+98
View File
@@ -0,0 +1,98 @@
import { TestBed } from '@angular/core/testing';
import { RuleService, RuleType, IfThenRule, TimeWindowRule } from './rule-service';
import { SelectableDayType } from './calendar-service';
describe('RuleService', () => {
let service: RuleService;
beforeEach(() => {
// Clear localStorage before each test to ensure a clean state
localStorage.clear();
TestBed.configureTestingModule({});
service = TestBed.inject(RuleService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with default rules', () => {
const rules = service.rules;
expect(rules.length).toBe(2);
expect(rules[0].id).toBe('default-samedi-malin');
expect(rules[1].id).toBe('default-christmas');
});
it('should parse dates correctly for TimeWindowRules', () => {
const rules = service.rules;
const christmasRule = rules.find((r) => r.type === RuleType.TIME_WINDOW) as TimeWindowRule;
expect(christmasRule).toBeDefined();
expect(christmasRule.startDate instanceof Date).toBe(true);
expect(christmasRule.endDate instanceof Date).toBe(true);
});
it('should add a new rule', () => {
const newRule: IfThenRule = {
id: 'test-rule',
name: 'Test Rule',
type: RuleType.IF_THEN,
enabled: true,
dayOfWeek: 1, // Monday
placeDays: 1,
placeType: SelectableDayType.RTT,
placement: 'after',
rewardDays: 0,
rewardType: SelectableDayType.OTHER,
};
service.addRule(newRule);
const rules = service.rules;
expect(rules.length).toBe(3);
expect(rules.find((r) => r.id === 'test-rule')).toBeDefined();
});
it('should update an existing rule', () => {
const rules = service.rules;
const targetRule = { ...rules[0] } as IfThenRule;
targetRule.name = 'Updated Name';
service.updateRule(targetRule);
const updatedRules = service.rules;
const updated = updatedRules.find((r) => r.id === targetRule.id);
expect(updated?.name).toBe('Updated Name');
});
it('should delete a rule', () => {
const rules = service.rules;
const idToDelete = rules[0].id;
service.deleteRule(idToDelete);
const updatedRules = service.rules;
expect(updatedRules.length).toBe(1);
expect(updatedRules.find((r) => r.id === idToDelete)).toBeUndefined();
});
it('should toggle rule enabled state', () => {
const targetId = service.rules[0].id;
const initialState = service.rules[0].enabled;
service.toggleRule(targetId);
expect(service.rules.find((r) => r.id === targetId)?.enabled).toBe(!initialState);
});
it('should reorder rules correctly', () => {
const rulesBefore = service.rules;
const firstRuleId = rulesBefore[0].id;
const secondRuleId = rulesBefore[1].id;
// Move first rule to second position
service.reorderRules(0, 1);
const rulesAfter = service.rules;
expect(rulesAfter[0].id).toBe(secondRuleId);
expect(rulesAfter[1].id).toBe(firstRuleId);
});
});
+139
View File
@@ -0,0 +1,139 @@
import { Injectable, WritableSignal, Injector, inject } from '@angular/core';
import { SelectableDayType } from './calendar-service';
import { storedSignal } from '../shared/utils/stored-signal';
export enum RuleType {
IF_THEN = 'if_then',
TIME_WINDOW = 'time_window',
}
export interface BaseRule {
id: string;
name: string;
type: RuleType;
enabled: boolean;
}
export type PlacementStrategy = 'before' | 'after' | 'around' | 'auto';
export interface IfThenRule extends BaseRule {
type: RuleType.IF_THEN;
dayOfWeek: number; // 0-6, where 0 is Sunday, 6 is Saturday
placeDays: number;
placeType: SelectableDayType;
placement: PlacementStrategy;
rewardDays: number;
rewardType: SelectableDayType;
}
export interface TimeWindowRule extends BaseRule {
type: RuleType.TIME_WINDOW;
startDate: Date;
endDate: Date;
placeDays: number;
placeType: SelectableDayType;
}
export type VacationRule = IfThenRule | TimeWindowRule;
interface SerializedRule {
id: string;
name: string;
type: RuleType;
enabled: boolean;
dayOfWeek?: number;
placeDays: number;
placeType: SelectableDayType;
placement?: PlacementStrategy;
rewardDays?: number;
rewardType?: SelectableDayType;
startDate?: string | Date;
endDate?: string | Date;
}
@Injectable({
providedIn: 'root',
})
export class RuleService {
private readonly injector = inject(Injector);
private readonly defaultRules: VacationRule[] = [
{
id: 'default-samedi-malin',
name: 'Samedi Malin',
type: RuleType.IF_THEN,
enabled: true,
dayOfWeek: 6, // Saturday
placeDays: 2,
placeType: SelectableDayType.CP,
placement: 'auto',
rewardDays: 1,
rewardType: SelectableDayType.OTHER,
},
{
id: 'default-christmas',
name: 'Vacances de Noël',
type: RuleType.TIME_WINDOW,
enabled: true,
startDate: new Date(new Date().getFullYear(), 11, 20), // Dec 20
endDate: new Date(new Date().getFullYear() + 1, 0, 3), // Jan 3 next year
placeDays: 5,
placeType: SelectableDayType.ANY,
},
];
private rawRulesSignal: WritableSignal<SerializedRule[]>;
constructor() {
this.rawRulesSignal = storedSignal<SerializedRule[]>(
'vacationRules',
this.defaultRules as unknown as SerializedRule[],
this.injector,
);
}
public get rules(): VacationRule[] {
const raw = this.rawRulesSignal();
return raw.map((rule) => {
if (rule.type === RuleType.TIME_WINDOW) {
return {
...rule,
startDate: new Date(rule.startDate as string),
endDate: new Date(rule.endDate as string),
} as TimeWindowRule;
}
return rule as IfThenRule;
});
}
public addRule(rule: VacationRule): void {
this.rawRulesSignal.update((rules) => [...rules, rule as unknown as SerializedRule]);
}
public updateRule(updatedRule: VacationRule): void {
this.rawRulesSignal.update((rules) =>
rules.map((rule) =>
rule.id === updatedRule.id ? (updatedRule as unknown as SerializedRule) : rule,
),
);
}
public deleteRule(id: string): void {
this.rawRulesSignal.update((rules) => rules.filter((rule) => rule.id !== id));
}
public toggleRule(id: string): void {
this.rawRulesSignal.update((rules) =>
rules.map((rule) => (rule.id === id ? { ...rule, enabled: !rule.enabled } : rule)),
);
}
public reorderRules(previousIndex: number, currentIndex: number): void {
this.rawRulesSignal.update((rules) => {
const updatedRules = [...rules];
const [movedRule] = updatedRules.splice(previousIndex, 1);
updatedRules.splice(currentIndex, 0, movedRule);
return updatedRules;
});
}
}
+30 -30
View File
@@ -3,40 +3,40 @@ import { provideZonelessChangeDetection } from '@angular/core';
import { ThemeService } from './theme-service';
describe('ThemeService', () => {
let service: ThemeService;
let service: ThemeService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(ThemeService);
});
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(ThemeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with system preference', () => {
expect(service.isLightMode()).toBeDefined();
});
it('should initialize with system preference', () => {
expect(service.isLightMode()).toBeDefined();
});
it('should toggle theme', () => {
const initialMode = service.isLightMode();
service.toggleTheme();
expect(service.isLightMode()).toBe(!initialMode);
});
it('should toggle theme', () => {
const initialMode = service.isLightMode();
service.toggleTheme();
expect(service.isLightMode()).toBe(!initialMode);
});
it('should force light mode when specified', () => {
service.toggleTheme(true);
expect(service.isLightMode()).toBe(true);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
});
it('should force light mode when specified', () => {
service.toggleTheme(true);
expect(service.isLightMode()).toBe(true);
expect(document.body.classList.contains('light-mode')).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(false);
});
it('should force dark mode when specified', () => {
service.toggleTheme(false);
expect(service.isLightMode()).toBe(false);
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
});
it('should force dark mode when specified', () => {
service.toggleTheme(false);
expect(service.isLightMode()).toBe(false);
expect(document.body.classList.contains('light-mode')).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(true);
});
});
@@ -0,0 +1,67 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { TranslationService } from './translation-service';
describe('TranslationService', () => {
let service: TranslationService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
service = TestBed.inject(TranslationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get correct locale', () => {
expect(service.locale()).toBeDefined();
});
it('should set language if available', () => {
service.setLanguage('en');
expect(service.language()).toBe('en');
service.setLanguage('invalid-lang');
expect(service.language()).toBe('en'); // Should not change
});
it('should get language name', () => {
const enName = service.getLanguageName('en');
expect(enName).toBe('English');
});
it('should get language name fallback', () => {
const origIntl = global.Intl;
Object.defineProperty(global, 'Intl', {
value: {
DisplayNames: class {
of() {
return undefined;
}
},
},
writable: true,
});
expect(service.getLanguageName('test')).toBe('TEST');
Object.defineProperty(global, 'Intl', {
value: origIntl,
writable: true,
});
});
it('should get day type label', () => {
expect(service.getDayTypeLabel('cp')).toBeDefined();
expect(service.getDayTypeLabel('rtt')).toBeDefined();
expect(service.getDayTypeLabel('weekend')).toBeDefined();
expect(service.getDayTypeLabel('closed-day')).toBeDefined();
expect(service.getDayTypeLabel('other')).toBeDefined();
expect(service.getDayTypeLabel('today')).toBeDefined();
expect(service.getDayTypeLabel('any')).toBeDefined();
expect(service.getDayTypeLabel('unknown-type')).toBe('unknown-type');
});
});
+182
View File
@@ -0,0 +1,182 @@
/// <reference types="vite/client" />
import { Injectable, inject, Injector, computed } from '@angular/core';
import { storedSignal } from '../shared/utils/stored-signal';
export interface AppTranslations {
locale: string;
app: {
title: string;
description: string;
};
userInput: {
addVacationDay: string;
calculate: string;
remainingDaysWarning: string;
remainingDaysManualPlace: string;
chooseDate: string;
daySuffix: string;
daysSuffix: string;
expires: string;
choosePeriod: string;
startDate: string;
endDate: string;
};
dayTypes: {
weekend: string;
closedDay: string;
rtt: string;
cp: string;
other: string;
today: string;
any: string;
};
settings: {
title: string;
description: string;
showWeekNumbers: string;
close: string;
selectLanguage: string;
};
rules: {
title: string;
subtitle: string;
priorityInfo: string;
ifThenTemplate: string;
timeWindowTemplate: string;
editRule: string;
addRule: string;
place: string;
ruleName: string;
holidayFalls: string;
days: string;
type: string;
position: string;
getBonus: string;
startDate: string;
endDate: string;
cancel: string;
save: string;
add: string;
addRuleButton: string;
newIfThenRule: string;
newTimeWindowRule: string;
defaultNewRuleName: string;
defaultNewPeriodName: string;
daysOfWeek: {
1: string;
2: string;
3: string;
4: string;
5: string;
6: string;
0: string;
};
placement: {
before: string;
after: string;
around: string;
auto: string;
};
};
loading: string;
weekdaysShort: string[];
}
import { translationsMap, availableLanguagesList } from '../i18n-registry';
@Injectable({
providedIn: 'root',
})
export class TranslationService {
private readonly injector = inject(Injector);
public readonly availableLanguages = availableLanguagesList;
// Detect browser language, fallback to the first available language
private getBrowserLanguage(): string {
if (typeof navigator !== 'undefined' && navigator.language) {
const primary = navigator.language.split('-')[0].toLowerCase();
if (this.availableLanguages.includes(primary)) {
return primary;
}
}
return this.availableLanguages[0] || '';
}
public readonly language = storedSignal<string>(
'app.language',
this.getBrowserLanguage(),
this.injector,
);
public readonly tr = computed<AppTranslations>(() => {
const currentLang = this.language();
const fallbackLang = this.availableLanguages[0] || '';
return (
(translationsMap[currentLang] as AppTranslations) ||
(translationsMap[fallbackLang] as AppTranslations) ||
({} as AppTranslations)
);
});
public readonly locale = computed<string>(() => {
const tr = this.tr();
if (tr && tr.locale) {
return tr.locale;
}
const lang = this.language();
if (lang) {
return `${lang}-${lang.toUpperCase()}`;
}
const fallbackLang = this.availableLanguages[0];
if (fallbackLang) {
return `${fallbackLang}-${fallbackLang.toUpperCase()}`;
}
return '';
});
public setLanguage(lang: string): void {
if (this.availableLanguages.includes(lang)) {
this.language.set(lang);
}
}
public getLanguageName(langCode: string): string {
try {
// Using native Intl.DisplayNames to get name of language in its own language
const displayNames = new Intl.DisplayNames([langCode], { type: 'language' });
const name = displayNames.of(langCode);
if (name) {
return name.charAt(0).toUpperCase() + name.slice(1);
}
} catch {
// Fallback to uppercase language code
}
return langCode.toUpperCase();
}
public getDayTypeLabel(type: string): string {
const tr = this.tr();
if (!tr || !tr.dayTypes) {
return type;
}
switch (type) {
case 'weekend':
return tr.dayTypes.weekend || type;
case 'closed-day':
return tr.dayTypes.closedDay || type;
case 'rtt':
return tr.dayTypes.rtt || type;
case 'cp':
return tr.dayTypes.cp || type;
case 'other':
return tr.dayTypes.other || type;
case 'today':
return tr.dayTypes.today || type;
case 'any':
return tr.dayTypes.any || type;
default:
return type;
}
}
}
+52 -9
View File
@@ -1,8 +1,9 @@
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { UserInputService } from './user-input-service';
import { SelectableDayType, VacDay } from './calendar-service';
describe('UserInput', () => {
describe('UserInputService', () => {
let service: UserInputService;
beforeEach(() => {
@@ -16,19 +17,61 @@ describe('UserInput', () => {
expect(service).toBeTruthy();
});
it('should initialize with random vacation numbers', () => {
it('should initialize with vacation days array', () => {
const vacations = service.vacationNumberSignal();
expect(vacations.cp).toBeGreaterThanOrEqual(1);
expect(vacations.cp).toBeLessThanOrEqual(9);
expect(vacations.rtt).toBeGreaterThanOrEqual(1);
expect(vacations.rtt).toBeLessThanOrEqual(9);
expect(vacations.other).toBeGreaterThanOrEqual(1);
expect(vacations.other).toBeLessThanOrEqual(9);
expect(Array.isArray(vacations)).toBe(true);
expect(vacations.length).toBeGreaterThan(0);
});
it('should have vacation days with valid properties', () => {
const vacations = service.vacationNumberSignal();
vacations.forEach((day) => {
expect(day.id).toBeDefined();
expect(day.type).toBeDefined();
expect(day.numberOfDays).toBeGreaterThanOrEqual(1);
expect(day.numberOfDays).toBeLessThanOrEqual(9);
expect(day.expiryDate).toBeInstanceOf(Date);
});
});
it('should update vacation number signal', () => {
const newVacations = { cp: 5, rtt: 3, other: 2 };
const newVacations: VacDay[] = [
{
id: 'test-id',
type: SelectableDayType.CP,
numberOfDays: 5,
expiryDate: new Date(),
},
];
service.vacationNumberSignal.set(newVacations);
expect(service.vacationNumberSignal()).toEqual(newVacations);
});
it('should create a new vacation day', () => {
const newDay = service.createVacationDay();
expect(newDay.id).toBeDefined();
expect(newDay.type).toBe(SelectableDayType.CP);
expect(newDay.numberOfDays).toBe(1);
expect(newDay.expiryDate).toBeInstanceOf(Date);
});
it('should set start date', () => {
const newDate = new Date();
newDate.setFullYear(newDate.getFullYear() + 2);
service.setStartDate(newDate);
expect(service.startDate().getFullYear()).toBe(newDate.getFullYear());
});
it('should set end date', () => {
const newDate = new Date();
newDate.setFullYear(newDate.getFullYear() + 3);
service.setEndDate(newDate);
expect(service.endDate().getFullYear()).toBe(newDate.getFullYear());
});
it('should request computation', () => {
const initialValue = service.computeTriggerSignal();
service.requestComputation();
expect(service.computeTriggerSignal()).toBe(initialValue + 1);
});
});
+82 -7
View File
@@ -1,5 +1,6 @@
import { Injectable, signal, PLATFORM_ID, inject } from '@angular/core';
import { VacationsNumber } from './calendar-service';
import { Injectable, signal, computed, inject, Injector } from '@angular/core';
import { SelectableDayType, VacDay } from './calendar-service';
import { storedSignal } from '../shared/utils/stored-signal';
@Injectable({
providedIn: 'root',
@@ -9,13 +10,87 @@ export class UserInputService {
return Math.floor(Math.random() * 9) + 1;
}
private getInitialValues(): VacationsNumber {
private getInitialValues(): VacDay[] {
return [
{
id: crypto.randomUUID(),
type: SelectableDayType.CP,
numberOfDays: this.getRandomNumber(),
expiryDate: UserInputService.getOneYearFromTodayMidnight(),
},
{
id: crypto.randomUUID(),
type: SelectableDayType.RTT,
numberOfDays: this.getRandomNumber(),
expiryDate: UserInputService.getOneYearFromTodayMidnight(),
},
{
id: crypto.randomUUID(),
type: SelectableDayType.OTHER,
numberOfDays: this.getRandomNumber(),
expiryDate: UserInputService.getOneYearFromTodayMidnight(),
},
];
}
public createVacationDay(): VacDay {
return {
cp: this.getRandomNumber(),
rtt: this.getRandomNumber(),
other: this.getRandomNumber(),
id: crypto.randomUUID(),
type: SelectableDayType.CP,
numberOfDays: 1,
expiryDate: this.endDate(),
};
}
readonly vacationNumberSignal = signal<VacationsNumber>(this.getInitialValues());
readonly vacationNumberSignal = signal<VacDay[]>(this.getInitialValues());
readonly remainingVacationDaysSignal = signal<VacDay[]>([]);
readonly computeTriggerSignal = signal(0);
private readonly injector = inject(Injector);
private static getTodayMidnight(): Date {
const d = new Date();
d.setHours(0, 0, 0, 0);
return d;
}
private static getOneYearFromTodayMidnight(): Date {
const d = new Date();
d.setFullYear(d.getFullYear() + 1);
d.setHours(0, 0, 0, 0);
return d;
}
readonly startDateString = storedSignal<string>(
'timeframe.startDate',
UserInputService.getTodayMidnight().toISOString(),
this.injector,
);
readonly endDateString = storedSignal<string>(
'timeframe.endDate',
UserInputService.getOneYearFromTodayMidnight().toISOString(),
this.injector,
);
readonly startDate = computed(() => new Date(this.startDateString()));
readonly endDate = computed(() => new Date(this.endDateString()));
public setStartDate(date: Date | null): void {
if (!date) return;
const d = new Date(date);
d.setHours(0, 0, 0, 0);
this.startDateString.set(d.toISOString());
}
public setEndDate(date: Date | null): void {
if (!date) return;
const d = new Date(date);
d.setHours(0, 0, 0, 0);
this.endDateString.set(d.toISOString());
}
public requestComputation(): void {
this.computeTriggerSignal.update((count) => count + 1);
}
}
@@ -0,0 +1,58 @@
import { TestBed } from '@angular/core/testing';
import { Injector, provideZonelessChangeDetection } from '@angular/core';
import { storedSignal } from './stored-signal';
import { vi } from 'vitest';
describe('storedSignal', () => {
let injector: Injector;
const testKey = 'test.key';
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection()],
});
injector = TestBed.inject(Injector);
localStorage.clear();
});
afterEach(() => {
localStorage.clear();
vi.restoreAllMocks();
});
it('should return initial value when no local storage value exists', () => {
const signal = storedSignal(testKey, 'initial', injector);
expect(signal()).toBe('initial');
});
it('should read from local storage if available', () => {
localStorage.setItem(testKey, JSON.stringify('stored'));
const signal = storedSignal(testKey, 'initial', injector);
expect(signal()).toBe('stored');
});
it('should fallback to initial value and remove key on invalid JSON', () => {
localStorage.setItem(testKey, 'invalid-json');
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const signal = storedSignal(testKey, 'initial', injector);
expect(signal()).toBe('initial');
expect(consoleSpy).toHaveBeenCalled();
expect(localStorage.getItem(testKey)).toBeNull();
});
it('should handle missing local storage gracefully', () => {
const originalLocalStorage = global.localStorage;
Object.defineProperty(global, 'localStorage', { value: undefined, writable: true });
const signal = storedSignal(testKey, 'initial', injector);
expect(signal()).toBe('initial');
// Restore
Object.defineProperty(global, 'localStorage', {
value: originalLocalStorage,
writable: true,
});
});
});
+111
View File
@@ -0,0 +1,111 @@
import { signal, effect, WritableSignal, Injector, runInInjectionContext } from '@angular/core';
/**
* Checks if localStorage is available in the current environment.
* @returns true if localStorage is accessible, false otherwise
*/
function isLocalStorageAvailable(): boolean {
try {
return typeof localStorage !== 'undefined' && localStorage !== null;
} catch {
return false;
}
}
/**
* Creates a signal that automatically persists its value to localStorage.
*
* The signal will:
* - Initialize with the stored value from localStorage if available
* - Fall back to the provided initial value if no stored value exists or parsing fails
* - Automatically sync changes to localStorage with debouncing (300ms delay)
* - Work safely in SSR and environments where localStorage is unavailable
*
* @template T - The type of value stored in the signal (must be JSON-serializable)
* @param key - The localStorage key used to store the value
* @param initialValue - The default value used when no stored value exists
* @param injector - The Angular injector (use inject(Injector) to get current context)
* @param debounceMs - Optional debounce delay in milliseconds (default: 300ms)
* @returns A writable signal that persists to localStorage
*
* @example
* ```typescript
* const injector = inject(Injector);
* const darkMode = storedSignal('theme.darkMode', false, injector);
* darkMode.set(true); // Automatically saved to localStorage after 300ms
* console.log(darkMode()); // true
* ```
*
* @remarks
* - Requires an Injector to create the effect in the proper context
* - Values must be JSON-serializable (no functions, circular references, etc.)
* - In SSR or when localStorage is unavailable, the signal works normally but won't persist
* - Corrupted localStorage data is automatically cleared and reset to initial value
* - localStorage writes are debounced to improve performance with frequent updates
*/
export function storedSignal<T>(
key: string,
initialValue: T,
injector: Injector,
debounceMs = 300,
): WritableSignal<T> {
let parsedValue = initialValue;
if (isLocalStorageAvailable()) {
try {
const storedValue = localStorage.getItem(key);
if (storedValue) {
parsedValue = JSON.parse(storedValue) as T;
}
} catch (error) {
console.warn(
`Failed to parse stored value for key "${key}". Using initial value.`,
error,
);
try {
localStorage.removeItem(key);
} catch {
// Ignore removal errors
}
}
}
const s = signal(parsedValue);
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let isFirstRun = true;
runInInjectionContext(injector, () => {
effect(() => {
const value = s();
// Skip the first execution to avoid writing back what we just read
if (isFirstRun) {
isFirstRun = false;
return;
}
// Clear any pending timeout
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
// Debounce the localStorage write
timeoutId = setTimeout(() => {
if (isLocalStorageAvailable()) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.warn(
`Failed to save value for key "${key}" to localStorage.`,
error,
);
}
}
timeoutId = null;
}, debounceMs);
});
});
return s;
}
+79
View File
@@ -0,0 +1,79 @@
{
"locale": "de-DE",
"app": {
"title": "vac-optimizer",
"description": "Optimieren Sie Ihre Urlaubstage basierend auf Feiertagen und Wochenenden!"
},
"userInput": {
"addVacationDay": "Urlaubstag hinzufügen",
"calculate": "Berechnen",
"remainingDaysWarning": "{count} nicht platziert:",
"remainingDaysManualPlace": "Bitte manuell platzieren.",
"chooseDate": "Datum auswählen",
"daySuffix": "Tag",
"daysSuffix": "Tage",
"expires": "abl.",
"choosePeriod": "Optimierungszeitraum",
"startDate": "Startdatum",
"endDate": "Enddatum"
},
"dayTypes": {
"weekend": "Wochenende",
"closedDay": "Feiertag",
"rtt": "RTT",
"cp": "CP",
"other": "Andere",
"today": "Heute",
"any": "Beliebig"
},
"settings": {
"title": "Einstellungen",
"description": "Einstellungen anpassen",
"showWeekNumbers": "Wochennummern anzeigen",
"close": "Schließen",
"selectLanguage": "Sprache auswählen"
},
"rules": {
"title": "Regeln",
"subtitle": "Urlaubsregeln konfigurieren",
"priorityInfo": "Regeln werden von oben nach unten angewendet",
"ifThenTemplate": "Wenn {type} dann {placement}",
"timeWindowTemplate": "Zeitfenster {startDate} bis {endDate}",
"editRule": "Regel bearbeiten",
"addRule": "Regel hinzufügen",
"place": "platzieren",
"ruleName": "Regelname",
"holidayFalls": "Feiertag fällt auf",
"days": "Tage",
"type": "Typ",
"position": "Position",
"getBonus": "Bonus erhalten",
"startDate": "Startdatum",
"endDate": "Enddatum",
"cancel": "Abbrechen",
"save": "Speichern",
"add": "Hinzufügen",
"addRuleButton": "Regel hinzufügen",
"newIfThenRule": "Neue Wenn-Dann-Regel",
"newTimeWindowRule": "Neue Zeitfenster-Regel",
"defaultNewRuleName": "Neue Regel",
"defaultNewPeriodName": "Neuer Zeitraum",
"daysOfWeek": {
"1": "Montag",
"2": "Dienstag",
"3": "Mittwoch",
"4": "Donnerstag",
"5": "Freitag",
"6": "Samstag",
"0": "Sonntag"
},
"placement": {
"before": "davor",
"after": "danach",
"around": "darum herum",
"auto": "automatisch"
}
},
"loading": "Wird geladen...",
"weekdaysShort": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"]
}
+79
View File
@@ -0,0 +1,79 @@
{
"locale": "en-GB",
"app": {
"title": "vac-optimizer",
"description": "Optimize your vacation days based on holidays and weekends!"
},
"userInput": {
"addVacationDay": "Add a vacation day",
"calculate": "Calculate",
"remainingDaysWarning": "{count} vacation day{plural} not placed:",
"remainingDaysManualPlace": "Please place them manually.",
"chooseDate": "Choose a date",
"daySuffix": "day",
"daysSuffix": "days",
"expires": "exp.",
"choosePeriod": "Optimization Period",
"startDate": "Start date",
"endDate": "End date"
},
"dayTypes": {
"weekend": "Weekend",
"closedDay": "Public Holiday",
"rtt": "RTT",
"cp": "Paid Leave",
"other": "Other",
"today": "Today",
"any": "Any"
},
"settings": {
"title": "Calendar Settings",
"description": "Configure your calendar settings here.",
"showWeekNumbers": "Show week numbers",
"close": "Close",
"selectLanguage": "Language"
},
"rules": {
"title": "Custom Rules",
"subtitle": "Manage optimization rules",
"priorityInfo": "The order of rules defines their execution priority (from top to bottom). Drag and drop to reorder.",
"ifThenTemplate": "If a holiday falls on a <strong>{day}</strong>, place <strong>{placeDays}</strong> day(s) <strong>{placeType}</strong> <strong>{placement}</strong> and get <strong>{rewardDays}</strong> day(s) <strong>{rewardType}</strong> as a bonus.",
"timeWindowTemplate": "Place <strong>{placeDays}</strong> day(s) <strong>{placeType}</strong> between <strong>{startDate}</strong> and <strong>{endDate}</strong>.",
"editRule": "Edit rule",
"addRule": "Add a rule",
"place": "Place",
"ruleName": "Rule name",
"holidayFalls": "Holiday falls on a",
"days": "Days",
"type": "Type",
"position": "Position",
"getBonus": "Get as bonus",
"startDate": "Start date",
"endDate": "End date",
"cancel": "Cancel",
"save": "Save",
"add": "Add",
"addRuleButton": "Add Rule",
"newIfThenRule": "New \"If ... Then\" Rule",
"newTimeWindowRule": "New Imposed Period",
"defaultNewRuleName": "New Rule",
"defaultNewPeriodName": "New Period",
"daysOfWeek": {
"1": "Monday",
"2": "Tuesday",
"3": "Wednesday",
"4": "Thursday",
"5": "Friday",
"6": "Saturday",
"0": "Sunday"
},
"placement": {
"before": "Before",
"after": "After",
"around": "Around",
"auto": "Auto (Best choice)"
}
},
"loading": "Calculating...",
"weekdaysShort": ["M", "T", "W", "T", "F", "S", "S"]
}
+79
View File
@@ -0,0 +1,79 @@
{
"locale": "es-ES",
"app": {
"title": "vac-optimizer",
"description": "¡Optimice sus días de vacaciones según los días festivos y los fines de semana!"
},
"userInput": {
"addVacationDay": "Añadir un día de vacaciones",
"calculate": "Calcular",
"remainingDaysWarning": "{count} día{plural} de vacaciones no colocado{plural}:",
"remainingDaysManualPlace": "Por favor, colóquelos manualmente.",
"chooseDate": "Elija una fecha",
"daySuffix": "día",
"daysSuffix": "días",
"expires": "exp.",
"choosePeriod": "Período de optimización",
"startDate": "Fecha de inicio",
"endDate": "Fecha de fin"
},
"dayTypes": {
"weekend": "Fin de semana",
"closedDay": "Día festivo",
"rtt": "RTT",
"cp": "Vacaciones pagadas",
"other": "Otro",
"today": "Hoy",
"any": "Cualquier"
},
"settings": {
"title": "Ajustes del calendario",
"description": "Configure sus ajustes del calendario aquí.",
"showWeekNumbers": "Mostrar números de semana",
"close": "Cerrar",
"selectLanguage": "Idioma"
},
"rules": {
"title": "Reglas personalizadas",
"subtitle": "Gestionar reglas de optimización",
"priorityInfo": "El orden de las reglas define su prioridad de ejecución (de arriba a abajo). Arrastre y suelte para reorganizar.",
"ifThenTemplate": "Si un día festivo cae en <strong>{day}</strong>, colocar <strong>{placeDays}</strong> día(s) <strong>{placeType}</strong> <strong>{placement}</strong> y obtener <strong>{rewardDays}</strong> día(s) <strong>{rewardType}</strong> de bonificación.",
"timeWindowTemplate": "Colocar <strong>{placeDays}</strong> día(s) <strong>{placeType}</strong> entre el <strong>{startDate}</strong> y el <strong>{endDate}</strong>.",
"editRule": "Editar regla",
"addRule": "Añadir una regla",
"place": "Colocar",
"ruleName": "Nombre de la regla",
"holidayFalls": "Día festivo cae en",
"days": "Días",
"type": "Tipo",
"position": "Posición",
"getBonus": "Obtener en bonificación",
"startDate": "Fecha de inicio",
"endDate": "Fecha de fin",
"cancel": "Cancelar",
"save": "Guardar",
"add": "Añadir",
"addRuleButton": "Añadir",
"newIfThenRule": "Nueva regla \"Si ... Entonces\"",
"newTimeWindowRule": "Nuevo período impuesto",
"defaultNewRuleName": "Nueva regla",
"defaultNewPeriodName": "Nuevo período",
"daysOfWeek": {
"1": "Lunes",
"2": "Martes",
"3": "Miércoles",
"4": "Jueves",
"5": "Viernes",
"6": "Sábado",
"0": "Domingo"
},
"placement": {
"before": "Antes",
"after": "Después",
"around": "Alrededor",
"auto": "Auto (Mejor opción)"
}
},
"loading": "Calculando...",
"weekdaysShort": ["L", "M", "M", "J", "V", "S", "D"]
}
+79
View File
@@ -0,0 +1,79 @@
{
"locale": "fr-FR",
"app": {
"title": "vac-optimizer",
"description": "Optimisez vos jours de vacances en fonction des jours fériés et des week-ends !"
},
"userInput": {
"addVacationDay": "Ajouter un jour de vacances",
"calculate": "Calculer",
"remainingDaysWarning": "{count} jour{plural} de vacances non placé{plural}:",
"remainingDaysManualPlace": "Veuillez les placer manuellement.",
"chooseDate": "Choisissez une date",
"daySuffix": "jour",
"daysSuffix": "jours",
"expires": "exp.",
"choosePeriod": "Période d'optimisation",
"startDate": "Date de début",
"endDate": "Date de fin"
},
"dayTypes": {
"weekend": "Weekend",
"closedDay": "Jour Férié",
"rtt": "RTT",
"cp": "Congé Payé",
"other": "Autre",
"today": "Aujourd'hui",
"any": "N'importe"
},
"settings": {
"title": "Paramètres du calendrier",
"description": "Configurez vos paramètres de calendrier ici.",
"showWeekNumbers": "Afficher les numéros de semaine",
"close": "Fermer",
"selectLanguage": "Langue"
},
"rules": {
"title": "Règles Personnalisées",
"subtitle": "Gérer les règles d'optimisation",
"priorityInfo": "L'ordre des règles définit leur priorité d'exécution (de haut en bas). Glissez-déposez pour réorganiser.",
"ifThenTemplate": "Si un jour férié tombe un <strong>{day}</strong>, placer <strong>{placeDays}</strong> jour(s) <strong>{placeType}</strong> <strong>{placement}</strong> et obtenir <strong>{rewardDays}</strong> jour(s) <strong>{rewardType}</strong> en bonus.",
"timeWindowTemplate": "Placer <strong>{placeDays}</strong> jour(s) <strong>{placeType}</strong> entre le <strong>{startDate}</strong> et le <strong>{endDate}</strong>.",
"editRule": "Modifier la règle",
"addRule": "Ajouter une règle",
"place": "Placer",
"ruleName": "Nom de la règle",
"holidayFalls": "Jour férié tombe un",
"days": "Jours",
"type": "Type",
"position": "Position",
"getBonus": "Obtenir en bonus",
"startDate": "Date de début",
"endDate": "Date de fin",
"cancel": "Annuler",
"save": "Enregistrer",
"add": "Ajouter",
"addRuleButton": "Ajouter",
"newIfThenRule": "Nouvelle Règle \"Si ... Alors\"",
"newTimeWindowRule": "Nouvelle Période Imposée",
"defaultNewRuleName": "Nouvelle Règle",
"defaultNewPeriodName": "Nouvelle Période",
"daysOfWeek": {
"1": "Lundi",
"2": "Mardi",
"3": "Mercredi",
"4": "Jeudi",
"5": "Vendredi",
"6": "Samedi",
"0": "Dimanche"
},
"placement": {
"before": "Avant",
"after": "Après",
"around": "Autour",
"auto": "Auto (Meilleur choix)"
}
},
"loading": "Calcul en cours...",
"weekdaysShort": ["L", "M", "M", "J", "V", "S", "D"]
}
+10 -9
View File
@@ -1,18 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>VacOptimizer</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
rel="stylesheet"
/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
</head>
<body>
<app-root></app-root>
</body>
</html>
</html>
+1 -2
View File
@@ -2,7 +2,6 @@ import { BootstrapContext, bootstrapApplication } from '@angular/platform-browse
import { App } from './app/app';
import { config } from './app/app.config.server';
const bootstrap = (context: BootstrapContext) =>
bootstrapApplication(App, config, context);
const bootstrap = (context: BootstrapContext) => bootstrapApplication(App, config, context);
export default bootstrap;
+1 -2
View File
@@ -2,5 +2,4 @@ import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
+1 -3
View File
@@ -41,9 +41,7 @@ app.use(
app.use((req, res, next) => {
angularApp
.handle(req)
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.then((response) => (response ? writeResponseToNodeResponse(response, res) : next()))
.catch(next);
});
+21
View File
@@ -0,0 +1,21 @@
import '@angular/compiler';
import { vi } from 'vitest';
import '@analogjs/vitest-angular/setup-snapshots';
import '@analogjs/vitest-angular/setup-serializers';
import { setupTestBed } from '@analogjs/vitest-angular/setup-testbed';
setupTestBed();
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
+3 -9
View File
@@ -4,14 +4,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": [
"node"
]
"types": ["node"]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
]
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.spec.ts"]
}
+1 -1
View File
@@ -31,4 +31,4 @@
"path": "./tsconfig.spec.json"
}
]
}
}
+5 -9
View File
@@ -1,14 +1,10 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
"types": ["vitest/globals"],
"target": "es2022"
},
"include": [
"src/**/*.ts"
]
}
"include": ["src/**/*.ts"],
"files": ["src/test-setup.ts"]
}
+27
View File
@@ -0,0 +1,27 @@
/// <reference types="vitest" />
import angular from '@analogjs/vite-plugin-angular';
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
return {
plugins: [angular()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['src/test-setup.ts'],
include: ['**/*.spec.ts'],
reporters: ['default'],
pool: 'forks',
coverage: {
provider: 'istanbul',
reporter: ['text', 'html', 'cobertura'],
},
},
define: {
'import.meta.vitest': mode !== 'production',
},
};
});