Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f82633dd9 | |||
| cd0a6c16aa | |||
| 76c97352bc | |||
| fa00719a3e | |||
| 86c075db10 | |||
| 9ad8129321 | |||
| c8e2fba13e | |||
| 8f2632f456 | |||
| c575992d64 | |||
| 7215c01741 | |||
| dd8fdeb5ca | |||
| 9be9f191bb | |||
| e12bbf29ae | |||
| bdfef72124 | |||
| 8c898cd652 | |||
| be5f5775ef | |||
| 5400294d45 | |||
| b8eb8a9393 | |||
| b645f28daf | |||
| 8cdd03b28b | |||
| 246d420fe5 | |||
| e1969a2f83 | |||
| 879c7c68d6 | |||
| 8ed9b50054 | |||
| 917b90a26d | |||
| e1e703c2d8 | |||
| dbff41b9c8 | |||
| 3ce58faa1c | |||
| 2a3a03dd31 | |||
| 8da4b7cb28 | |||
| 16fc6b69a6 |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(grep \" FAILED$\")",
|
||||
"Bash(grep -A2 \" FAILED$\")",
|
||||
"Bash(grep -A3 \" FAILED$\")",
|
||||
"Bash(grep -v \"Executed\\\\|^--$\")",
|
||||
"Bash(grep -E \"TOTAL:|FAILED$\")",
|
||||
"Bash(node -e \"const c = require\\('chart.js'\\); console.log\\(Object.keys\\(c\\).filter\\(k => k.includes\\('Doughnut'\\) || k.includes\\('Controller'\\)\\)\\)\")",
|
||||
"Bash(npm list *)",
|
||||
"Bash(ng update *)",
|
||||
"Bash(npm run *)",
|
||||
"Bash(nvm ls *)",
|
||||
"Bash(grep -E \"\\\\.\\(js|ts\\)$\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
+12
-15
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": [
|
||||
"projects/**/*"
|
||||
],
|
||||
"overrides": [{
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"ignorePatterns": ["projects/**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@angular-eslint/recommended",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
|
||||
],
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
@@ -33,13 +35,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/template/recommended",
|
||||
"plugin:@angular-eslint/template/accessibility"
|
||||
],
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@angular-eslint/template/recommended", "plugin:@angular-eslint/template/accessibility"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -54,5 +54,11 @@ sonar.properties
|
||||
**/*.copy.scss
|
||||
**/*.copy.html
|
||||
|
||||
# Next.js (legacy, removed)
|
||||
.next/
|
||||
next-env.d.ts
|
||||
next.config.js
|
||||
|
||||
# Environment files (Angular uses src/environments/*.ts)
|
||||
.env
|
||||
.env.*
|
||||
@@ -0,0 +1,5 @@
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
nvm use 20.19.6
|
||||
npm test -- --watch=false
|
||||
npx lint-staged
|
||||
@@ -0,0 +1,6 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
|
||||
# auto generated files
|
||||
src/generated
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"printWidth": 120
|
||||
}
|
||||
@@ -1,27 +1,97 @@
|
||||
# Ad Astra
|
||||
# AdAstra App
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.2.
|
||||
Frontend of the AdAstra platform — a multi-domain Angular application covering e-commerce, CMS, skydive club management, and Hero Wars guild analytics.
|
||||
|
||||
## Development server
|
||||
## Tech stack
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
| Layer | Choice |
|
||||
| --------- | ------------------------------------------------------------------ |
|
||||
| Framework | Angular 21 (standalone components, no NgModules) |
|
||||
| Language | TypeScript 5.9 — strict mode + strictTemplates |
|
||||
| UI | Angular Material 21, Bootstrap 5 |
|
||||
| Charts | Chart.js 4 (ng2-charts), Chartist |
|
||||
| HTTP | Angular HttpClient with three interceptors (base URL, JWT, errors) |
|
||||
| Tests | Karma + Jasmine |
|
||||
| Linting | angular-eslint + Prettier (enforced via lint-staged) |
|
||||
| Node | `^20.19.0 \|\| ^22.12.0` |
|
||||
|
||||
## Code scaffolding
|
||||
## Related repository
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
The backend API lives in [`adastra_api`](../adastra_api) — an Express + Sequelize stack, routes grouped under `/api/ecommerce`, `/api/cms`, `/api/skydive`, `/api/herowars`.
|
||||
|
||||
## Build
|
||||
## Getting started
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
```bash
|
||||
npm install
|
||||
npm run local # dev server on :4400, uses environment.local.ts
|
||||
```
|
||||
|
||||
## Running unit tests
|
||||
Copy and configure your local environment:
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
```bash
|
||||
cp src/environments/environment.example.ts src/environments/environment.local.ts
|
||||
# fill in: apiBaseUrl, apiKey, googleMapApiKey, …
|
||||
```
|
||||
|
||||
## Running end-to-end tests
|
||||
## Commands
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
| Command | Description |
|
||||
| --------------- | ----------------------------------------------------- |
|
||||
| `npm start` | Dev server on :4200 with production config |
|
||||
| `npm run dev` | Dev server on :4300 with `environment.development.ts` |
|
||||
| `npm run local` | Dev server on :4400 with `environment.local.ts` |
|
||||
| `npm run build` | Production build → `dist/adastra_angular/` |
|
||||
| `npm run watch` | Incremental build in development configuration |
|
||||
| `npm run lint` | angular-eslint on `**/*.ts` and `**/*.html` |
|
||||
| `npm test` | Karma + Jasmine unit tests |
|
||||
|
||||
## Further help
|
||||
Run a single spec:
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
```bash
|
||||
ng test --include src/path/to/file.spec.ts
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Bootstrap
|
||||
|
||||
`AppComponent` renders a single `FullComponent` shell (Material sidenav + toolbar). Routes are split into two arrays in `app.routes.ts`:
|
||||
|
||||
- `auth.routes.ts` — protected by `authGuard`; admin sections additionally require `adminGuard`
|
||||
- `noauth.routes.ts` — public pages and auth entry points, blocked by `noauthGuard` when already authenticated
|
||||
|
||||
Data is pre-fetched by resolvers under `src/app/core/resolvers/` — new route components should follow this pattern rather than fetching in `ngOnInit`.
|
||||
|
||||
### HTTP pipeline
|
||||
|
||||
Three interceptors applied in order:
|
||||
|
||||
1. **`apiInterceptor`** — prepends `environment.apiBaseUrl` to every request. Services use relative paths (`/cms/user`, `/skydive/jumps`, …), never absolute URLs.
|
||||
2. **`tokenInterceptor`** — attaches `Authorization: Token <jwt>` when a token exists in `localStorage['jwtToken']`.
|
||||
3. **`errorInterceptor`** — unwraps `err.error` and rethrows.
|
||||
|
||||
### Feature domains
|
||||
|
||||
| Domain | Routes | Backend prefix |
|
||||
| ---------- | ------------------------------------------------------ | ---------------- |
|
||||
| E-commerce | `products`, `product/:slug`, `products/:category` | `/api/ecommerce` |
|
||||
| CMS | `pages`, `page/:slug`, `home`, profiles, articles | `/api/cms` |
|
||||
| Skydive | `skydive/*` — aeronefs, canopies, dropzones, jumps | `/api/skydive` |
|
||||
| Hero Wars | Guild analytics, weekly snapshots in `src/files-data/` | `/api/herowars` |
|
||||
|
||||
### Path aliases
|
||||
|
||||
Defined in `tsconfig.json`; always prefer them over relative imports:
|
||||
|
||||
```
|
||||
@components @services @models @guards
|
||||
@interceptors @resolvers @viewmodels @interfaces
|
||||
@constants @core/* @environments/*
|
||||
@data/* @assets/* @styles/*
|
||||
```
|
||||
|
||||
When adding a new file, export it from the appropriate `index.ts` barrel so the alias resolves correctly.
|
||||
|
||||
### Styling
|
||||
|
||||
SCSS with `includePaths: ["src/styles"]` — imports like `@use 'variables'` resolve globally. Material date locale is set to `fr-FR` with `DD/MM/YYYY` format.
|
||||
|
||||
+43
-31
@@ -4,9 +4,7 @@
|
||||
"cli": {
|
||||
"packageManager": "npm",
|
||||
"analytics": false,
|
||||
"schematicCollections": [
|
||||
"@angular-eslint/schematics"
|
||||
]
|
||||
"schematicCollections": ["@angular-eslint/schematics"]
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
@@ -32,10 +30,7 @@
|
||||
},
|
||||
"index": "src/index.html",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"polyfills": [
|
||||
"@angular/localize/init",
|
||||
"zone.js"
|
||||
],
|
||||
"polyfills": ["@angular/localize/init", "zone.js"],
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
@@ -47,12 +42,12 @@
|
||||
"output": "/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/chartist/dist/index.scss",
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"styles": ["src/styles/styles.scss"],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": ["src/styles"]
|
||||
"includePaths": ["src/styles"],
|
||||
"sass": {
|
||||
"silenceDeprecations": ["import"]
|
||||
}
|
||||
},
|
||||
"scripts": [],
|
||||
"browser": "src/main.ts"
|
||||
@@ -67,8 +62,8 @@
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
"maximumWarning": "3mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
@@ -140,33 +135,50 @@
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"polyfills": ["zone.js", "zone.js/testing"],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
|
||||
"styles": ["src/styles/styles.scss"],
|
||||
"scripts": [],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": ["src/styles"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"type": "component"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"type": "directive"
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"type": "service"
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:interceptor": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:module": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:resolver": {
|
||||
"typeSeparator": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Migrate frontend from Next.js to Angular
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The project was initially built with Next.js (React). The development team already maintained an existing Angular stack for other projects, creating a knowledge split between two frontend frameworks. Server-side rendering — Next.js's main differentiator — was not required: all data is user-specific and served via authenticated API calls.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Keep Next.js/React
|
||||
- Migrate to Angular
|
||||
- Migrate to Vue or Svelte
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Migrate to Angular", because the team already maintained an Angular stack for other projects, making it the path of least resistance with no additional knowledge split.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Single frontend framework across projects — reuse of existing expertise, libraries, and patterns.
|
||||
- No SSR complexity; the app is a pure SPA, which fits Angular's model naturally.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Complete rewrite — no incremental migration path exists between React and Angular.
|
||||
- Next.js artifacts (`.next/`, `src/modules/`, `src/middleware.ts`, etc.) required a dedicated cleanup pass after the migration.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Keep Next.js/React
|
||||
|
||||
- Good, because no rewrite cost.
|
||||
- Bad, because maintaining two frontend frameworks in parallel increases overhead.
|
||||
- Bad, because SSR provides no benefit for this authenticated, user-specific application.
|
||||
|
||||
### Migrate to Angular
|
||||
|
||||
- Good, because leverages existing team expertise.
|
||||
- Good, because consistent toolchain across projects.
|
||||
- Bad, because full rewrite required.
|
||||
|
||||
### Migrate to Vue or Svelte
|
||||
|
||||
- Good, because smaller bundle size.
|
||||
- Bad, because no existing team expertise — introduces a third framework.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Use Angular standalone components without NgModules
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Angular historically required all components, directives, and pipes to be declared inside NgModules. Angular 14 introduced standalone components as an opt-in. Angular 17+ made them the recommended default and they are required for the esbuild-based application builder. How should the application be structured?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Standalone components (no NgModules)
|
||||
- NgModule-based architecture
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Standalone components (no NgModules)", because they are the Angular-recommended default since v17, provide explicit per-component dependency declarations, and are required for the esbuild application builder.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Each component explicitly declares its own `imports` — dependencies are visible and not hidden in a shared module.
|
||||
- Better tree-shaking: unused imports in one component don't affect others.
|
||||
- Aligns with Angular's long-term direction.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Slightly more verbose component decorators — each component lists its own imports rather than inheriting from a module.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Standalone components (no NgModules)
|
||||
|
||||
- Good, because explicit dependency declarations per component.
|
||||
- Good, because required for the esbuild application builder.
|
||||
- Good, because Angular-recommended default since v17.
|
||||
- Bad, because more verbose decorators.
|
||||
|
||||
### NgModule-based architecture
|
||||
|
||||
- Good, because familiar pattern for developers coming from Angular < 14.
|
||||
- Bad, because deprecated direction; incompatible with the modern application builder.
|
||||
- Bad, because shared module patterns hide dependencies.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Coexist Angular Material and Bootstrap during UI library consolidation
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The application currently uses two UI libraries: Angular Material (interactive components) and Bootstrap 5 (layout, spacing utilities). Bootstrap was the original styling layer; Angular Material was introduced incrementally. Maintaining both increases bundle size, creates styling inconsistencies, and adds cognitive overhead. Which strategy should be adopted?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Keep both libraries and consolidate later
|
||||
- Consolidate to Angular Material only
|
||||
- Consolidate to Bootstrap only
|
||||
- Migrate to a different library (PrimeNG, Spartan/shadcn-angular, etc.)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Keep both libraries and consolidate later", because a big-bang UI rewrite is not justified at this stage and the final target library has not yet been chosen.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- No immediate rewrite required — development velocity is preserved.
|
||||
- Each library covers the other's gaps during the transition period.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Two styling systems in tension, leading to inconsistent UX.
|
||||
- Bootstrap's `@import`-based Sass is incompatible with the `@use` module system, requiring a deprecation workaround (see ADR 0012).
|
||||
- Larger CSS bundle than a single-library solution.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Keep both libraries and consolidate later
|
||||
|
||||
- Good, because no rewrite cost now.
|
||||
- Bad, because styling inconsistency and larger bundle persist until consolidation.
|
||||
|
||||
### Consolidate to Angular Material only
|
||||
|
||||
- Good, because single coherent design system.
|
||||
- Bad, because Bootstrap layout utilities would need to be replaced.
|
||||
|
||||
### Consolidate to Bootstrap only
|
||||
|
||||
- Good, because removes Angular Material dependency.
|
||||
- Bad, because Angular Material components (dialogs, date pickers, etc.) would need rebuilding.
|
||||
|
||||
### Migrate to a different library
|
||||
|
||||
- Good, because could address limitations of both current libraries.
|
||||
- Bad, because highest rewrite cost; unknown tradeoffs until evaluated.
|
||||
|
||||
## Links
|
||||
|
||||
- Related to [ADR 0012](0012-scss-bootstrap-import-deprecation.md)
|
||||
@@ -0,0 +1,46 @@
|
||||
# Handle API base URL, auth token, and errors via HTTP interceptors
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Every API call requires three cross-cutting concerns: prepending the API base URL, attaching the JWT bearer token, and unwrapping error responses into a consistent shape. How should these be applied across all HTTP calls without duplicating logic in each service?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Three chained HTTP interceptors
|
||||
- Per-service base URL and token handling
|
||||
- Single utility wrapper function
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Three chained HTTP interceptors", because they centralise cross-cutting concerns transparently and require no changes to individual services.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Services use relative paths (e.g. `/skydive/jumps`) — environment-agnostic.
|
||||
- Auth header injection is transparent to all services.
|
||||
- Uniform error payload shape across the entire application.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Services must always use relative paths starting with `/`. Passing an absolute URL would cause the base URL to be prepended twice.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Three chained HTTP interceptors
|
||||
|
||||
- Good, because zero duplication — logic defined once, applied everywhere.
|
||||
- Good, because services remain unaware of environment or auth details.
|
||||
- Bad, because interceptors apply globally — opting out requires explicit handling.
|
||||
|
||||
### Per-service base URL and token handling
|
||||
|
||||
- Good, because each service controls its own behaviour explicitly.
|
||||
- Bad, because duplicates the same logic across every service.
|
||||
|
||||
### Single utility wrapper function
|
||||
|
||||
- Good, because more explicit than interceptors.
|
||||
- Bad, because all service calls must go through the wrapper — easy to bypass accidentally.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Store JWT authentication token in localStorage
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The application requires persistent authentication across browser sessions. Where should the JWT token be stored client-side?
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Application is internal — used by a known, controlled user base; not publicly accessible.
|
||||
- Implementation simplicity.
|
||||
- Compatibility with Angular functional guards and the APP_INITIALIZER auth bootstrap.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Store JWT in localStorage
|
||||
- Store JWT in an httpOnly cookie
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Store JWT in localStorage", because the application is internal with a controlled user base, making the XSS risk acceptable, and localStorage avoids the additional CSRF complexity of cookie-based auth.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Simple implementation — no server-side session management.
|
||||
- Token persists across browser restarts without requiring re-login.
|
||||
- Works seamlessly with Angular functional guards and `APP_INITIALIZER`.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Accessible to JavaScript — an XSS attack could exfiltrate the token.
|
||||
- If the application ever becomes publicly accessible or handles sensitive data, this decision must be revisited.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Store JWT in localStorage
|
||||
|
||||
- Good, because simple to implement and operate.
|
||||
- Good, because survives browser restarts.
|
||||
- Bad, because XSS-vulnerable.
|
||||
|
||||
### Store JWT in an httpOnly cookie
|
||||
|
||||
- Good, because inaccessible to JavaScript — XSS-safe.
|
||||
- Bad, because requires CSRF protection (SameSite cookie policy or CSRF tokens).
|
||||
- Bad, because more complex server-side coordination.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Pre-fetch route data using Angular route resolvers
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Route components need data to render. Should that data be fetched before the route activates (resolver) or after the component renders (in-component)?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Angular route resolvers
|
||||
- In-component fetching (ngOnInit)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Angular route resolvers", because components receive data immediately via `ActivatedRoute.data`, eliminating the need for per-component loading states on initial render.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- No loading state needed in components for the initial data fetch.
|
||||
- Loading indication can be centralised at the router level via router events.
|
||||
- Data availability is guaranteed before any component lifecycle hook runs.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Navigation appears paused while the resolver fetches — there is no partial render before data arrives.
|
||||
- Resolver errors must be handled explicitly to avoid navigation hangs on API failure.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Angular route resolvers
|
||||
|
||||
- Good, because data is available immediately in the component.
|
||||
- Good, because loading logic is centralised outside the component.
|
||||
- Bad, because navigation is blocked until the resolver completes.
|
||||
|
||||
### In-component fetching (ngOnInit)
|
||||
|
||||
- Good, because navigation is instant — component renders immediately.
|
||||
- Bad, because each component must manage its own loading and error states.
|
||||
- Bad, because inconsistent UX across routes if not implemented uniformly.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Use the esbuild-based Angular application builder
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Angular 17 introduced a new application builder (`@angular/build:application`) based on esbuild and Vite, replacing the legacy webpack-based `@angular-devkit/build-angular:browser` builder. Which builder should be used for development and production builds?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- `@angular/build:application` — esbuild-based (Angular 17+)
|
||||
- `@angular-devkit/build-angular:browser` — webpack-based (legacy)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "`@angular/build:application`", because it is the Angular-recommended builder since v17, delivers significantly faster builds, and is required for certain schema-level features such as `stylePreprocessorOptions.sass.silenceDeprecations`.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Build times are 3–5× faster than the webpack builder for development and production.
|
||||
- Native ESM output with better code splitting.
|
||||
- Aligns with Angular's recommended toolchain for v17+.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Webpack-specific configuration options (custom plugins, loaders) no longer apply. Not an issue for this project.
|
||||
- The `sass` sub-option under `stylePreprocessorOptions` is specific to this builder's schema; the Karma test builder uses a stricter schema and does not support it.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### `@angular/build:application` (esbuild)
|
||||
|
||||
- Good, because significantly faster cold builds and incremental rebuilds.
|
||||
- Good, because Angular-recommended default since v17.
|
||||
- Good, because required for `sass.silenceDeprecations` support.
|
||||
- Bad, because webpack plugin ecosystem is not compatible.
|
||||
|
||||
### `@angular-devkit/build-angular:browser` (webpack)
|
||||
|
||||
- Good, because supports custom webpack configuration.
|
||||
- Bad, because deprecated direction; slower builds.
|
||||
- Bad, because does not support the `sass.silenceDeprecations` option.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Keep Karma + Jasmine as the test stack
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Angular's default test setup uses Karma as the test runner (executing tests in a real Chrome browser) and Jasmine as the spec framework. Faster alternatives exist. Should the test stack be replaced?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Karma + Jasmine (Angular default)
|
||||
- Jest (Node/jsdom, no real browser)
|
||||
- Vitest (ESM-native, no real browser)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Karma + Jasmine", because the test suite (54 specs) is small enough that Karma's startup overhead is not a bottleneck, and migration would require replacing test utilities and adjusting Angular `TestBed` setup with no functional gain at the current scale.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Zero migration effort — full compatibility with Angular's `TestBed` and `ComponentFixture` APIs.
|
||||
- Tests run in a real Chrome browser, catching browser-specific issues that jsdom misses.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Slower startup than Jest or Vitest due to Chrome launch. Acceptable at the current test suite size.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Karma + Jasmine
|
||||
|
||||
- Good, because zero migration cost from the Angular default.
|
||||
- Good, because tests run in a real browser environment.
|
||||
- Bad, because slower startup than headless alternatives.
|
||||
|
||||
### Jest
|
||||
|
||||
- Good, because fast — runs in Node via jsdom with no browser launch.
|
||||
- Bad, because requires `jest-preset-angular`, removal of `zone.js/testing`, and test utility changes.
|
||||
- Bad, because jsdom is not a real browser — some browser-specific behaviours are not caught.
|
||||
|
||||
### Vitest
|
||||
|
||||
- Good, because ESM-native, fastest option.
|
||||
- Bad, because Angular `TestBed` integration requires additional configuration.
|
||||
- Bad, because jsdom limitation applies here too.
|
||||
|
||||
## Links
|
||||
|
||||
- Future consideration: if the test suite grows significantly or CI performance becomes a concern, evaluate migration to Jest with `jest-preset-angular`.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Enforce quality gates at commit time with Husky and lint-staged
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Without automated enforcement, code quality rules (linting, formatting, tests) are discovered late — in CI, or not at all. Should quality checks be enforced at commit time?
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Issues should be caught before they enter the repository, not after.
|
||||
- Formatting should be applied automatically so developers don't need to run it manually.
|
||||
- The correct Node version must be active during hook execution regardless of the shell environment.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Husky + lint-staged pre-commit hook
|
||||
- CI-only enforcement (no local hooks)
|
||||
- No automated enforcement
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Husky + lint-staged pre-commit hook", because it prevents lint errors and failing tests from entering the repository, and auto-fixes formatting on commit without developer intervention.
|
||||
|
||||
The hook runs in order:
|
||||
|
||||
1. Full test suite (`npm test -- --watch=false`) — blocks commit on any failing spec.
|
||||
2. `lint-staged` on staged files: `prettier --write` on `*.{ts,html,scss,json,md}`, then `eslint --max-warnings=0` on `*.ts` and `*.html`.
|
||||
|
||||
nvm is initialised inside the hook script to ensure the correct Node version (pinned in `.nvmrc`) is active regardless of the shell environment that triggered the commit.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- No lint errors or failing tests can enter the repository via a normal commit.
|
||||
- Prettier auto-fixes formatting on commit — developers don't need to run it manually.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Commits are slower (~10–20 s for the full test suite). Acceptable trade-off for a solo project.
|
||||
- Requires nvm to be installed on the developer's machine. The hook sources `$HOME/.nvm/nvm.sh` directly.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Husky + lint-staged
|
||||
|
||||
- Good, because issues are caught before they enter the repository.
|
||||
- Good, because formatting is applied automatically.
|
||||
- Bad, because adds latency to every commit.
|
||||
- Bad, because requires nvm on the developer's machine.
|
||||
|
||||
### CI-only enforcement
|
||||
|
||||
- Good, because no local overhead.
|
||||
- Bad, because broken commits are discovered only after push.
|
||||
|
||||
### No automated enforcement
|
||||
|
||||
- Good, because zero overhead.
|
||||
- Bad, because quality degrades without consistent manual discipline.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Pin Node version with nvm and .nvmrc
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Angular 18 requires Node.js `^18.19.1 || ^20.11.1`. The system Node version on a development machine may not meet this requirement, causing silent build failures or CLI errors. How should the required Node version be enforced?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- nvm with `.nvmrc`
|
||||
- System Node (no version management)
|
||||
- Other version managers (fnm, volta)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "nvm with `.nvmrc`", because it pins the version explicitly in the repository, is already in use on the development machine, and integrates directly with the Husky pre-commit hook.
|
||||
|
||||
A `.nvmrc` file at the repository root pins Node to `20.19.6`. The Husky pre-commit hook explicitly calls `nvm use 20.19.6` before running tests to ensure the correct version is active in the hook's shell environment.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Consistent Node version across all commands: dev server, build, tests, pre-commit hook.
|
||||
- `.nvmrc` documents the required Node version explicitly in the repository.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Requires nvm. Developers using other version managers (fnm, volta) must align manually.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### nvm with `.nvmrc`
|
||||
|
||||
- Good, because explicit version pinning visible in the repository.
|
||||
- Good, because integrates with Husky hook via `nvm use`.
|
||||
- Bad, because tied to nvm — other version managers need manual alignment.
|
||||
|
||||
### System Node
|
||||
|
||||
- Good, because no tooling required.
|
||||
- Bad, because version is not pinned — any system upgrade can silently break the build.
|
||||
|
||||
### Other version managers (fnm, volta)
|
||||
|
||||
- Good, because faster and more ergonomic than nvm for some workflows.
|
||||
- Bad, because not the currently installed tool — switching would require migration.
|
||||
|
||||
## Links
|
||||
|
||||
- The `package.json` `engines` field (`^18.19.1 || ^20.11.1`) provides a secondary signal for tooling that reads it.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Organise code by functional domain
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The application covers four independent functional areas with distinct data models, API backends, and user audiences: Skydive, CMS, E-commerce, and Hero Wars. Without explicit organisational boundaries, code from different domains would intermingle, making each area harder to reason about independently. How should the codebase be structured?
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Domain-based organisation (one folder per functional area)
|
||||
- Flat feature-based organisation (one folder per component type)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Domain-based organisation", because it mirrors the backend's route grouping, making the full-stack data flow traceable, and allows each domain to be understood and modified independently.
|
||||
|
||||
Services are placed under `src/app/core/services/<domain>/`, API prefixes are `/skydive`, `/cms`, `/ecommerce`, `/herowars`, and routes are composed from `auth.routes.ts` and `noauth.routes.ts` with domain-specific guards.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Each domain can be understood and modified independently.
|
||||
- Consistent mapping between frontend service paths and backend API routes simplifies debugging.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Cross-domain features (shared auth, user profile) must be placed in `core/` to avoid circular dependencies.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Domain-based organisation
|
||||
|
||||
- Good, because domain boundaries are explicit and enforced by directory structure.
|
||||
- Good, because mirrors the backend route grouping — frontend `core/services/skydive/` maps to backend `src/routes/api/skydive/`.
|
||||
- Bad, because shared cross-domain concerns need a neutral `core/` layer.
|
||||
|
||||
### Flat feature-based organisation
|
||||
|
||||
- Good, because all components of the same type are co-located.
|
||||
- Bad, because domain boundaries are invisible — mixing domains is too easy.
|
||||
|
||||
## Links
|
||||
|
||||
- Hero Wars uses static JSON files (`src/files-data/`) rather than live API calls — weekly snapshots updated manually. This is intentional and not a gap in the domain structure.
|
||||
- Related to backend [ADR 0004](../../adastra_api/docs/decisions/0004-route-organisation-by-domain.md)
|
||||
@@ -0,0 +1,57 @@
|
||||
# Silence the Sass @import deprecation for the Bootstrap import chain
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Bootstrap 5 relies on Sass's `@import` rule and `!default` variable overrides: our custom `_variable.scss` must be loaded before Bootstrap's `variables.scss` so that Bootstrap picks up our values via the `!default` mechanism. Dart Sass 1.80+ deprecates `@import` in favour of the `@use` module system. How should the deprecation be addressed?
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Bootstrap 5's `!default` override mechanism is incompatible with `@use` module scopes — variables loaded via `@use` do not flow into Bootstrap's module scope.
|
||||
- Other Dart Sass deprecations (`color.red/green/blue()`, global `if()`) were fixed properly.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Silence the `@import` deprecation via `angular.json`
|
||||
- Migrate to `@use` with `@use "bootstrap" with (...)`
|
||||
- Wait for Bootstrap 6
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Silence the `@import` deprecation via `angular.json`", because the `@use` migration is blocked by Bootstrap 5's architecture and Bootstrap 6 (which adopts `@use` natively) is not yet released.
|
||||
|
||||
The deprecation is suppressed in `angular.json` via `stylePreprocessorOptions.sass.silenceDeprecations: ["import"]`. The existing `@import`-based Bootstrap loading chain is kept intact. All other Dart Sass deprecations are fixed properly.
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- Bootstrap customisation works correctly with no behaviour change.
|
||||
- No risk of introducing regressions by attempting an incompatible migration now.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Deprecation is silenced rather than fixed. `@import` will be removed in Dart Sass 3.0.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Silence the `@import` deprecation
|
||||
|
||||
- Good, because no behaviour change — existing variable overrides continue to work.
|
||||
- Bad, because the underlying issue is deferred, not resolved.
|
||||
|
||||
### Migrate to `@use` with `@use "bootstrap" with (...)`
|
||||
|
||||
- Good, because fully resolves the deprecation.
|
||||
- Bad, because requires rewriting all Bootstrap variable overrides using `with ()` syntax — significant effort.
|
||||
- Bad, because the `with ()` syntax has limitations compared to `!default` chaining.
|
||||
|
||||
### Wait for Bootstrap 6
|
||||
|
||||
- Good, because Bootstrap 6 adopts `@use` natively — migration path will be documented by the Bootstrap team.
|
||||
- Bad, because Bootstrap 6 release date is unknown; deferring means living with the silenced warning.
|
||||
|
||||
## Links
|
||||
|
||||
- Related to [ADR 0003](0003-dual-ui-library-progressive-migration.md)
|
||||
- Future action: when Bootstrap 6 is released with `@use` support, migrate the import chain, remove the `silenceDeprecations` entry, and supersede this ADR.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Migrate HeroWars static JSON imports to HttpClient assets
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-04-26
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Six large JSON files (36 KB–3 MB) were statically imported directly into Angular components and services via TypeScript `import` statements. This bundled all the data into the main JavaScript bundle, inflating the initial download regardless of whether the user navigated to the HeroWars section. The largest file (`hw-guild-raids.json`, 3 MB) alone would significantly delay first contentful paint for all users.
|
||||
|
||||
Additionally, a seventh file (`qcm-bpa.json`, 210 KB) was imported but never consumed — the component had switched to a route resolver but the dead import remained.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Static JSON imports are compiled into the bundle — they cannot be lazy-loaded or cached by the browser independently.
|
||||
- Updating the data files required a recompile and redeployment; serving them as assets allows hot-swapping without a rebuild.
|
||||
- The HeroWars section is used by a small subset of users; its data should not penalise all other users' load time.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- Keep static imports
|
||||
- Move JSON to assets and load via `HttpClient`
|
||||
- Fetch data from the API backend
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: "Move JSON to assets and load via HttpClient", because it removes the data from the bundle, enables browser-level HTTP caching, and requires no backend changes. The files are not secret (guild-internal analytics data) so serving them as static assets is appropriate.
|
||||
|
||||
Structure:
|
||||
|
||||
- JSON files moved from `src/files-data/` to `src/assets/files-data/`
|
||||
- `HWDataService` created at `src/app/core/services/herowars/hw-data.service.ts` — exposes one `readonly` observable per file, each piped through `shareReplay(1)` so the HTTP request fires at most once per app lifecycle
|
||||
- `HWClanService.loadClan()` and `HWMemberService.loadMembers()` converted from synchronous return values to `Observable<T>`
|
||||
- All consumer components inject `HWDataService` and subscribe in `ngOnInit()`; template-called methods that previously accessed `guildWarSlots.slots` inline now read from a `_slots` class property populated in the subscribe callback
|
||||
- Unused `import data from 'src/qcm-bpa.json'` and two dead import-helper methods removed from `qcm.component.ts`
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
- HeroWars JSON (~4 MB total) no longer shipped in the initial bundle.
|
||||
- Each JSON file is independently cacheable by the browser with standard HTTP cache headers.
|
||||
- Data files can be updated without recompiling the Angular application.
|
||||
- `shareReplay(1)` ensures a single HTTP request per session even when multiple components subscribe to the same observable.
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
- Components now initialize asynchronously — there is a brief render before data arrives (consistent with the rest of the app's HTTP-driven components).
|
||||
- `Object.entries()` on `any`-typed HTTP responses requires explicit `Record<string, T>` casts to satisfy strict TypeScript, adding minor verbosity.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Keep static imports
|
||||
|
||||
- Good, because synchronous — no async lifecycle complexity.
|
||||
- Bad, because adds up to 4 MB to the initial bundle.
|
||||
- Bad, because data updates require a full rebuild and redeployment.
|
||||
|
||||
### Move JSON to assets and load via HttpClient
|
||||
|
||||
- Good, because removes data from the bundle entirely.
|
||||
- Good, because browser-cacheable independently of the app JS.
|
||||
- Good, because hot-swappable without a recompile.
|
||||
- Bad, because converts synchronous service methods to observables, requiring component updates.
|
||||
|
||||
### Fetch from API backend
|
||||
|
||||
- Good, because data could be managed dynamically via the admin interface.
|
||||
- Bad, because requires significant backend work (new endpoints, data model) for data that is currently managed as flat files.
|
||||
- Bad, because adds latency and a failure mode not present with local assets.
|
||||
Generated
+6002
-3729
File diff suppressed because it is too large
Load Diff
+37
-24
@@ -17,51 +17,64 @@
|
||||
"test": "ng test"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1"
|
||||
"node": "^20.19.0 || ^22.12.0"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.2.14",
|
||||
"@angular/cdk": "^18.2.14",
|
||||
"@angular/common": "^18.2.14",
|
||||
"@angular/compiler": "^18.2.14",
|
||||
"@angular/core": "^18.2.14",
|
||||
"@angular/forms": "^18.2.14",
|
||||
"@angular/google-maps": "^17.3.8",
|
||||
"@angular/localize": "^18.2.14",
|
||||
"@angular/material": "^18.2.14",
|
||||
"@angular/platform-browser": "^18.2.14",
|
||||
"@angular/platform-browser-dynamic": "^18.2.14",
|
||||
"@angular/router": "^18.2.14",
|
||||
"@angular/animations": "^21.2.10",
|
||||
"@angular/cdk": "^21.2.8",
|
||||
"@angular/common": "^21.2.10",
|
||||
"@angular/compiler": "^21.2.10",
|
||||
"@angular/core": "^21.2.10",
|
||||
"@angular/forms": "^21.2.10",
|
||||
"@angular/google-maps": "^21.2.8",
|
||||
"@angular/localize": "^21.2.10",
|
||||
"@angular/material": "^21.2.8",
|
||||
"@angular/platform-browser": "^21.2.10",
|
||||
"@angular/platform-browser-dynamic": "^21.2.10",
|
||||
"@angular/router": "^21.2.10",
|
||||
"bootstrap": "^5.3.7",
|
||||
"chart.js": "^4.4.2",
|
||||
"chartist": "^1.3.0",
|
||||
"ng-chartist": "^8.2.0",
|
||||
"ng2-charts": "^6.0.1",
|
||||
"ngx-skeleton-loader": "^9.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "^0.14.10"
|
||||
"zone.js": "^0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.2.21",
|
||||
"@angular-eslint/builder": "18.4.3",
|
||||
"@angular-eslint/eslint-plugin": "18.4.3",
|
||||
"@angular-eslint/eslint-plugin-template": "18.4.3",
|
||||
"@angular-eslint/schematics": "18.4.3",
|
||||
"@angular-eslint/template-parser": "18.4.3",
|
||||
"@angular/cli": "^18.2.21",
|
||||
"@angular/compiler-cli": "^18.2.14",
|
||||
"@angular-devkit/build-angular": "^21.2.8",
|
||||
"@angular-eslint/builder": "21.3.1",
|
||||
"@angular-eslint/eslint-plugin": "21.3.1",
|
||||
"@angular-eslint/eslint-plugin-template": "21.3.1",
|
||||
"@angular-eslint/schematics": "21.3.1",
|
||||
"@angular-eslint/template-parser": "21.3.1",
|
||||
"@angular/cli": "^21.2.8",
|
||||
"@angular/compiler-cli": "^21.2.10",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.2.0",
|
||||
"@typescript-eslint/parser": "7.2.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"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",
|
||||
"typescript": "~5.5.4"
|
||||
"lint-staged": "^16.4.0",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,html,scss,json,md}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts": [
|
||||
"eslint --max-warnings=0"
|
||||
],
|
||||
"*.html": [
|
||||
"eslint --max-warnings=0"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideNativeDateAdapter } from '@angular/material/core';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
@@ -14,16 +24,16 @@ describe('AppComponent', () => {
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'adastra_angular' title`, () => {
|
||||
it(`should have the 'Ad Astra' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('adastra_angular');
|
||||
expect(app.title).toEqual('Ad Astra');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
it('should render the full layout', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, adastra_angular');
|
||||
expect(compiled.querySelector('app-full-layout')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, OnInit, AfterContentInit } from '@angular/core';
|
||||
import { Component, OnInit, AfterContentInit, inject } from '@angular/core';
|
||||
import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, provideNativeDateAdapter } from '@angular/material/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { FullComponent } from '@components/shared/layout';
|
||||
import { UserService } from "@services";
|
||||
import { UserService } from '@services';
|
||||
|
||||
export const MY_FORMATS = {
|
||||
parse: {
|
||||
@@ -19,24 +19,22 @@ export const MY_FORMATS = {
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FullComponent
|
||||
],
|
||||
imports: [FullComponent],
|
||||
providers: [
|
||||
{ provide: MAT_DATE_LOCALE, useValue: 'fr-FR' },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
||||
provideNativeDateAdapter(MY_FORMATS)
|
||||
provideNativeDateAdapter(MY_FORMATS),
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
styleUrl: './app.component.scss',
|
||||
})
|
||||
export class AppComponent implements OnInit, AfterContentInit {
|
||||
private userService = inject(UserService);
|
||||
private titleService = inject(Title);
|
||||
|
||||
public title = 'Ad Astra';
|
||||
public appClass = 'grayscale';
|
||||
|
||||
constructor(private userService: UserService, private titleService: Title) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.userService.populate();
|
||||
this.titleService.setTitle(this.title);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { AeronefsComponent } from './aeronefs.component';
|
||||
|
||||
describe('AeronefsComponent', () => {
|
||||
@@ -8,7 +12,13 @@ describe('AeronefsComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AeronefsComponent]
|
||||
imports: [AeronefsComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -22,30 +22,35 @@ import { AeronefByImat, AeronefByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aeronefs',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatGridListModule, MatIconModule, MatMenuModule,
|
||||
BarsChartComponent, HistoryTableComponent, PieChartComponent
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatGridListModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
BarsChartComponent,
|
||||
HistoryTableComponent,
|
||||
PieChartComponent,
|
||||
],
|
||||
templateUrl: './aeronefs.component.html',
|
||||
styleUrl: './aeronefs.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateX(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateX(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class AeronefsComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
private _aeronefsByImat!: Array<AeronefByImat>;
|
||||
private _aeronefsByYear!: Array<AeronefByYear>;
|
||||
@@ -59,23 +64,21 @@ export class AeronefsComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ aeronefsPageData: AeronefsPageData }> = this.route.data as Observable<{ aeronefsPageData: AeronefsPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { aeronefsPageData: AeronefsPageData }) => {
|
||||
const data$: Observable<{ aeronefsPageData: AeronefsPageData }> = this.route.data as Observable<{
|
||||
aeronefsPageData: AeronefsPageData;
|
||||
}>;
|
||||
this._data = data$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: { aeronefsPageData: AeronefsPageData }) => {
|
||||
const pageData: AeronefsPageData = data.aeronefsPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
this._loadAeronefByImat(pageData);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { AuthComponent } from './auth.component';
|
||||
|
||||
@@ -8,8 +11,13 @@ describe('AuthComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AuthComponent]
|
||||
});
|
||||
imports: [AuthComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(AuthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { UntypedFormBuilder, UntypedFormGroup, AbstractControl, Validators, ValidatorFn, ValidationErrors, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
UntypedFormBuilder,
|
||||
UntypedFormGroup,
|
||||
AbstractControl,
|
||||
Validators,
|
||||
ValidatorFn,
|
||||
ValidationErrors,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -12,10 +21,9 @@ import { take } from 'rxjs/operators';
|
||||
|
||||
import { Errors } from '@models';
|
||||
import { UserService } from '@services';
|
||||
import { ListErrorsComponent, ShowAuthedDirective } from '@components/shared';
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink,
|
||||
FormsModule,
|
||||
@@ -24,13 +32,18 @@ import { ListErrorsComponent, ShowAuthedDirective } from '@components/shared';
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
ListErrorsComponent,
|
||||
ShowAuthedDirective
|
||||
],
|
||||
selector: 'app-auth',
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrl: './auth.component.scss'
|
||||
styleUrl: './auth.component.scss',
|
||||
})
|
||||
export class AuthComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private titleService = inject(Title);
|
||||
private userService = inject(UserService);
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
|
||||
private _url: Subscription = new Subscription();
|
||||
private _user: Subscription = new Subscription();
|
||||
authType = '';
|
||||
@@ -41,13 +54,7 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
authForm!: UntypedFormGroup;
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService,
|
||||
private fb: UntypedFormBuilder
|
||||
) {
|
||||
constructor() {
|
||||
this._resetErrors();
|
||||
// use FormBuilder to create a form group
|
||||
const controlsConfig = {
|
||||
@@ -59,7 +66,7 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
licence: '',
|
||||
poids: '',
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ''
|
||||
confirmPassword: '',
|
||||
};
|
||||
this.authForm = this.fb.group(controlsConfig, { validators: this.checkPasswords });
|
||||
}
|
||||
@@ -68,8 +75,7 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
// you would normally unsubscribe from this observable subscription
|
||||
// the active route observables are exemptions from unsubribe always rule
|
||||
// see notes on: https://angular.io/guide/router#observable-parammap-and-component-reuse
|
||||
this.route.url.pipe(take(1))
|
||||
.subscribe(data => {
|
||||
this.route.url.pipe(take(1)).subscribe((data) => {
|
||||
// Get the last piece of the URL (it's either 'login' or 'register')
|
||||
this.authType = data[data.length - 1].path;
|
||||
// Set a title for the page accordingly
|
||||
@@ -104,14 +110,16 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
if (this.authForm.valid) {
|
||||
this._resetErrors();
|
||||
const credentials = this.authForm.value;
|
||||
const user$ = this.userService.attemptAuth(this.authType, credentials).pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const user$ = this.userService
|
||||
.attemptAuth(this.authType, credentials)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._user = user$.subscribe({
|
||||
next: () => void this.router.navigateByUrl('/'),
|
||||
error: (err) => {
|
||||
console.log(err);
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -157,12 +165,11 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
return null;
|
||||
}
|
||||
const pass = group.get('password')!.value;
|
||||
const confirmPass = group.get('confirmPassword')!.value
|
||||
return (pass === confirmPass && pass !== '') ? null : { notSame: true }
|
||||
}
|
||||
const confirmPass = group.get('confirmPassword')!.value;
|
||||
return pass === confirmPass && pass !== '' ? null : { notSame: true };
|
||||
};
|
||||
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { CalculatorComponent } from './calculator.component';
|
||||
|
||||
describe('CalculatorComponent', () => {
|
||||
@@ -8,8 +14,14 @@ describe('CalculatorComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CalculatorComponent]
|
||||
});
|
||||
imports: [CalculatorComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(CalculatorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Input, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Component, Input, OnInit, OnDestroy, ViewChild, inject } from '@angular/core';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
@@ -18,36 +18,57 @@ import { BaseChartDirective } from 'ng2-charts';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { CalculatorInfo, CalculatorResult, CalculatorState, InputParams, Jump, LineConfig, WeightSizeRange, TableHeader, User, WeightSize, weightSizes } from '@models';
|
||||
import {
|
||||
CalculatorInfo,
|
||||
CalculatorResult,
|
||||
CalculatorState,
|
||||
InputParams,
|
||||
Jump,
|
||||
LineConfig,
|
||||
WeightSizeRange,
|
||||
TableHeader,
|
||||
User,
|
||||
WeightSize,
|
||||
weightSizes,
|
||||
} from '@models';
|
||||
import { CalculatorService, JumpsService, UserService, UtilitiesService } from '@services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-calculator',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, FormsModule, RouterModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatFormFieldModule, MatInputModule, MatIconModule, MatMenuModule,
|
||||
MatPaginatorModule, MatProgressBarModule, MatSortModule, MatTableModule,
|
||||
BaseChartDirective
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
RouterModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
MatPaginatorModule,
|
||||
MatProgressBarModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
BaseChartDirective,
|
||||
],
|
||||
templateUrl: './calculator.component.html',
|
||||
styleUrl: './calculator.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
private _calculatorService = inject(CalculatorService);
|
||||
private _jumpsService = inject(JumpsService);
|
||||
private _userService = inject(UserService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
//private _lastjump!: Subscription; // = new Subscription();
|
||||
//private _currentUser!: Subscription; // = new Subscription();
|
||||
private _subscriptions: Array<Subscription> = [];
|
||||
@@ -57,7 +78,7 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
public currentRange: WeightSizeRange = {} as WeightSizeRange;
|
||||
public currentUser: User = {} as User;
|
||||
public displayedColumns: Array<string> = ['weight'];
|
||||
public tableHeader: Array<TableHeader> = [<TableHeader>{name: 'Poids nu en kg', active: ''}];
|
||||
public tableHeader: Array<TableHeader> = [<TableHeader>{ name: 'Poids nu en kg', active: '' }];
|
||||
public sizesHeader: Array<TableHeader> = [];
|
||||
public sizesValuesFeet: Array<number> = [];
|
||||
public sizesMinValuesFeet: Array<number> = [];
|
||||
@@ -71,41 +92,31 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
charges: <CalculatorResult>{ current: 0, min: 0, min11: 0 },
|
||||
sizesFeet: <CalculatorResult>{ current: 0, min: 0, min11: 0 },
|
||||
sizesMeter: <CalculatorResult>{ current: 0, min: 0, min11: 0 },
|
||||
state: <CalculatorState>{ color: 'danger' }
|
||||
}
|
||||
state: <CalculatorState>{ color: 'danger' },
|
||||
};
|
||||
|
||||
@Input() canopy_size_table!: MatTableDataSource<WeightSize>;
|
||||
@Input() inputs: InputParams = <InputParams>{
|
||||
@Input() inputs: InputParams = (<InputParams>{
|
||||
jumps: 0,
|
||||
weight: 60,
|
||||
gear: 10,
|
||||
current: 190
|
||||
} as InputParams;
|
||||
current: 190,
|
||||
}) as InputParams;
|
||||
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
|
||||
constructor(
|
||||
private _calculatorService: CalculatorService,
|
||||
private _jumpsService: JumpsService,
|
||||
private _userService: UserService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const currentUser$: Observable<User> = this._userService.currentUser;
|
||||
const lastjump$: Observable<Jump> = this._jumpsService.getLastJump();
|
||||
|
||||
weightSizes[0].ranges.forEach((range: WeightSizeRange) => {
|
||||
const header: TableHeader = {name: range.label, active: range.active};
|
||||
this.tableHeader.push(header)
|
||||
const header: TableHeader = { name: range.label, active: range.active };
|
||||
this.tableHeader.push(header);
|
||||
this.sizesHeader.push(header);
|
||||
this.displayedColumns.push(range.name);
|
||||
});
|
||||
this.chartConfig.lineChartData.labels = [...this.sizesHeader.map(
|
||||
(data: TableHeader): string => data.name
|
||||
)];
|
||||
this.chartConfig.lineChartData.labels = [...this.sizesHeader.map((data: TableHeader): string => data.name)];
|
||||
this.chartConfig.lineChartData.datasets = [
|
||||
{
|
||||
label: 'Tailles min -11%',
|
||||
@@ -114,7 +125,7 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
stepped: true,
|
||||
pointStyle: false,
|
||||
borderColor: 'rgba(241, 80, 80, 1)',
|
||||
backgroundColor: 'rgba(241, 80, 80, 0.3)'
|
||||
backgroundColor: 'rgba(241, 80, 80, 0.3)',
|
||||
},
|
||||
{
|
||||
label: 'Tailles min',
|
||||
@@ -123,8 +134,8 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
stepped: true,
|
||||
pointStyle: false,
|
||||
borderColor: 'rgba(32, 182, 252, 1)',
|
||||
backgroundColor: 'rgba(32, 182, 252, 0.3)'
|
||||
}
|
||||
backgroundColor: 'rgba(32, 182, 252, 0.3)',
|
||||
},
|
||||
];
|
||||
this._subscriptions.push(
|
||||
currentUser$.subscribe((userData: User) => {
|
||||
@@ -133,13 +144,13 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
lastjump$.subscribe((jump) => {
|
||||
this.lastjump = jump;
|
||||
this.inputs.jumps = jump.numero;
|
||||
this.inputs.weight = this.currentUser.poids!;
|
||||
this.inputs.weight = this.currentUser.poids ?? this.inputs.weight;
|
||||
this.inputs.current = jump.taille!;
|
||||
this.canopy_size_table = new MatTableDataSource<WeightSize>(weightSizes);
|
||||
this.refresh();
|
||||
})
|
||||
}),
|
||||
);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -149,7 +160,7 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public refresh():void {
|
||||
public refresh(): void {
|
||||
this._refreshActive();
|
||||
this._refreshSizes();
|
||||
}
|
||||
@@ -159,25 +170,25 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
if (this.inputs.weight < 60) {
|
||||
weight = 60;
|
||||
}
|
||||
weightSizes.forEach(element => {
|
||||
weightSizes.forEach((element) => {
|
||||
element.active = '';
|
||||
element.ranges.forEach(range => {
|
||||
element.ranges.forEach((range) => {
|
||||
range.active = '';
|
||||
});
|
||||
});
|
||||
this.tableHeader.forEach(element => {
|
||||
this.tableHeader.forEach((element) => {
|
||||
element.active = '';
|
||||
});
|
||||
if (weight >= 60 && weight <= 110) {
|
||||
weightSizes[(weight - 60)].active = 'active';
|
||||
weightSizes[weight - 60].active = 'active';
|
||||
}
|
||||
if (this.inputs.jumps >= 0) {
|
||||
const num = this._calculatorService.getRangeNum(this.inputs.jumps);
|
||||
this.tableHeader[num].active = 'active';
|
||||
weightSizes.forEach(element => {
|
||||
element.ranges[(num-1)].active = 'active';
|
||||
weightSizes.forEach((element) => {
|
||||
element.ranges[num - 1].active = 'active';
|
||||
});
|
||||
this.currentRange = weightSizes[0].ranges[(num-1)];
|
||||
this.currentRange = weightSizes[0].ranges[num - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,13 +211,13 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
this.info.sizesMeter = {
|
||||
current: this._calculatorService.convertFeet2Meters(this.inputs.current),
|
||||
min: this._calculatorService.convertFeet2Meters(this.info.sizesFeet.min),
|
||||
min11: this._calculatorService.convertFeet2Meters(this.info.sizesFeet.min11)
|
||||
}
|
||||
min11: this._calculatorService.convertFeet2Meters(this.info.sizesFeet.min11),
|
||||
};
|
||||
this.info.charges = {
|
||||
current: this._calculatorService.getCharge(this.inputs.current, this.inputs.weight, this.inputs.gear),
|
||||
min: this._calculatorService.getCharge(this.info.sizesFeet.min, this.inputs.weight, this.inputs.gear),
|
||||
min11: this._calculatorService.getCharge(this.info.sizesFeet.min11, this.inputs.weight, this.inputs.gear)
|
||||
}
|
||||
min11: this._calculatorService.getCharge(this.info.sizesFeet.min11, this.inputs.weight, this.inputs.gear),
|
||||
};
|
||||
this.info.state.color = this._calculatorService.getStateColor(this.info.sizesFeet);
|
||||
|
||||
this.chartConfig.lineChartData.datasets[0].data = this.sizesMinValuesFeet;
|
||||
@@ -218,5 +229,4 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
this.inputs.weight = weight;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { CanopiesComponent } from './canopies.component';
|
||||
|
||||
describe('CanopiesComponent', () => {
|
||||
@@ -8,7 +12,13 @@ describe('CanopiesComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CanopiesComponent]
|
||||
imports: [CanopiesComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
@@ -21,30 +21,34 @@ import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from
|
||||
|
||||
@Component({
|
||||
selector: 'app-canopies',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatGridListModule, MatIconModule, MatMenuModule,
|
||||
HistoryTableComponent, BarsChartComponent, PieChartComponent
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatGridListModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
HistoryTableComponent,
|
||||
BarsChartComponent,
|
||||
PieChartComponent,
|
||||
],
|
||||
templateUrl: './canopies.component.html',
|
||||
styleUrl: './canopies.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateX(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateX(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class CanopiesComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
private _canopiesModelBySize!: Array<CanopyModelBySize>;
|
||||
private _canopiesModelByYear!: Array<CanopyModelByYear>;
|
||||
@@ -64,23 +68,21 @@ export class CanopiesComponent implements OnInit, OnDestroy {
|
||||
public seriesSizeValue: number[] = [];
|
||||
public seriesSizeRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ canopiesPageData: CanopiesPageData }> = this.route.data as Observable<{ canopiesPageData: CanopiesPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { canopiesPageData: CanopiesPageData }) => {
|
||||
const data$: Observable<{ canopiesPageData: CanopiesPageData }> = this.route.data as Observable<{
|
||||
canopiesPageData: CanopiesPageData;
|
||||
}>;
|
||||
this._data = data$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: { canopiesPageData: CanopiesPageData }) => {
|
||||
const pageData: CanopiesPageData = data.canopiesPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
this._loadCanopyBySize(pageData);
|
||||
@@ -134,7 +136,9 @@ export class CanopiesComponent implements OnInit, OnDestroy {
|
||||
const voile: string = row.voile;
|
||||
const taille: string = row.taille.toString();
|
||||
const year: string = row.year.toString();
|
||||
this.seriesModelRow[this.seriesModelName.indexOf(`${taille} - ${voile}`)][this.seriesModelHeader.indexOf(year)] = row.count;
|
||||
this.seriesModelRow[this.seriesModelName.indexOf(`${taille} - ${voile}`)][
|
||||
this.seriesModelHeader.indexOf(year)
|
||||
] = row.count;
|
||||
return row;
|
||||
});
|
||||
this.displayCharts = true;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
|
||||
@@ -8,8 +11,13 @@ describe('DashboardComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DashboardComponent]
|
||||
});
|
||||
imports: [DashboardComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -7,31 +7,54 @@ import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { Aeronef, AeronefByImat, Canopy, CanopyModelBySize, DropZone, DropZoneByYear, Errors, Jump, User } from '@models';
|
||||
import {
|
||||
Aeronef,
|
||||
AeronefByImat,
|
||||
Canopy,
|
||||
CanopyModelBySize,
|
||||
DropZone,
|
||||
DropZoneByYear,
|
||||
Errors,
|
||||
Jump,
|
||||
User,
|
||||
} from '@models';
|
||||
//import { AeronefsService, CanopiesService, DropZonesService, UserService } from '@services';
|
||||
import { JumpsService, UserService } from '@services';
|
||||
import { ShowAuthedDirective } from '@components/shared/show-authed.directive';
|
||||
import {
|
||||
AeronefsPieComponent, AeronefsBarComponent,
|
||||
CanopiesModelsComponent, CanopiesSizesComponent,
|
||||
DropzonesPieComponent, DropzonesBarComponent,
|
||||
JumpsByMonthComponent
|
||||
AeronefsPieComponent,
|
||||
AeronefsBarComponent,
|
||||
CanopiesModelsComponent,
|
||||
CanopiesSizesComponent,
|
||||
DropzonesPieComponent,
|
||||
DropzonesBarComponent,
|
||||
JumpsByMonthComponent,
|
||||
} from '@components/shared/dashboard-components';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, ShowAuthedDirective, MatDividerModule,
|
||||
AeronefsPieComponent, AeronefsBarComponent,
|
||||
CanopiesModelsComponent, CanopiesSizesComponent,
|
||||
DropzonesPieComponent, DropzonesBarComponent,
|
||||
JumpsByMonthComponent
|
||||
DatePipe,
|
||||
ShowAuthedDirective,
|
||||
MatDividerModule,
|
||||
AeronefsPieComponent,
|
||||
AeronefsBarComponent,
|
||||
CanopiesModelsComponent,
|
||||
CanopiesSizesComponent,
|
||||
DropzonesPieComponent,
|
||||
DropzonesBarComponent,
|
||||
JumpsByMonthComponent,
|
||||
],
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrl: './dashboard.component.scss'
|
||||
styleUrl: './dashboard.component.scss',
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private titleService = inject(Title);
|
||||
private _userService = inject(UserService);
|
||||
private _jumpsService = inject(JumpsService);
|
||||
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
private _lastjump: Subscription = new Subscription();
|
||||
@@ -56,18 +79,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
public dropzonesCount = 0;
|
||||
public lastJump: Jump = {} as Jump;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private _userService: UserService,
|
||||
private _jumpsService: JumpsService
|
||||
/*
|
||||
private aeronefsService: AeronefsService,
|
||||
private canopiesService: CanopiesService,
|
||||
private dropzonesService: DropZonesService
|
||||
*/
|
||||
) {
|
||||
constructor() {
|
||||
this._resetErrors();
|
||||
}
|
||||
|
||||
@@ -95,7 +107,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
this._loadCanopies();
|
||||
this._loadDropzones();
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
import { DemoComponent } from './demo.component';
|
||||
|
||||
@@ -8,7 +10,11 @@ describe('DemoComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DemoComponent]
|
||||
imports: [DemoComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import {CdkDragDrop, CdkDropList, CdkDrag, CdkDragPlaceholder, moveItemInArray} from '@angular/cdk/drag-drop';
|
||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { CdkDragDrop, CdkDropList, CdkDrag, CdkDragPlaceholder, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -17,30 +17,88 @@ import { ListErrorsComponent } from '@components/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'app-demo',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CdkDropList, CdkDrag, CdkDragPlaceholder, RouterLink,
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatIconModule, MatProgressBarModule, MatSliderModule,
|
||||
ListErrorsComponent
|
||||
CdkDropList,
|
||||
CdkDrag,
|
||||
CdkDragPlaceholder,
|
||||
RouterLink,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatProgressBarModule,
|
||||
MatSliderModule,
|
||||
ListErrorsComponent,
|
||||
],
|
||||
templateUrl: './demo.component.html',
|
||||
styleUrl: './demo.component.scss'
|
||||
styleUrl: './demo.component.scss',
|
||||
})
|
||||
export class DemoComponent implements OnInit, OnDestroy {
|
||||
private titleService = inject(Title);
|
||||
private userService = inject(UserService);
|
||||
|
||||
private _user: Subscription = new Subscription();
|
||||
private _colors: string[] = [
|
||||
"primary", "accent", "warn", "secondary", "megna", "raspberry", "success", "info",
|
||||
"warning", "danger", "red", "orange", "yellow", "green", "teal", "turquoise", "cyan",
|
||||
"blue", "navy", "navy-light", "pink", "magenta", "purple", "purple-light", "grey", "muted"
|
||||
'primary',
|
||||
'accent',
|
||||
'warn',
|
||||
'secondary',
|
||||
'megna',
|
||||
'raspberry',
|
||||
'success',
|
||||
'info',
|
||||
'warning',
|
||||
'danger',
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'green',
|
||||
'teal',
|
||||
'turquoise',
|
||||
'cyan',
|
||||
'blue',
|
||||
'navy',
|
||||
'navy-light',
|
||||
'pink',
|
||||
'magenta',
|
||||
'purple',
|
||||
'purple-light',
|
||||
'grey',
|
||||
'muted',
|
||||
];
|
||||
private _icons: string[] = [
|
||||
'mode_edit', 'delete', 'bookmark', 'settings', 'home', 'person',
|
||||
'share', 'account_circle', 'public', 'search', 'favorite', 'done',
|
||||
'numbers', 'attach_file', 'insert_comment', 'format_list_bulleted',
|
||||
'filter', 'crop', 'edit_attributes', 'upload', 'download', 'expand_less',
|
||||
'expand_more', 'chevron_left', 'chevron_right', 'sync', 'power',
|
||||
'power_off', 'notifications', 'emoji_emotions', 'person_add', 'block'
|
||||
'mode_edit',
|
||||
'delete',
|
||||
'bookmark',
|
||||
'settings',
|
||||
'home',
|
||||
'person',
|
||||
'share',
|
||||
'account_circle',
|
||||
'public',
|
||||
'search',
|
||||
'favorite',
|
||||
'done',
|
||||
'numbers',
|
||||
'attach_file',
|
||||
'insert_comment',
|
||||
'format_list_bulleted',
|
||||
'filter',
|
||||
'crop',
|
||||
'edit_attributes',
|
||||
'upload',
|
||||
'download',
|
||||
'expand_less',
|
||||
'expand_more',
|
||||
'chevron_left',
|
||||
'chevron_right',
|
||||
'sync',
|
||||
'power',
|
||||
'power_off',
|
||||
'notifications',
|
||||
'emoji_emotions',
|
||||
'person_add',
|
||||
'block',
|
||||
];
|
||||
|
||||
title = 'Demo';
|
||||
@@ -49,11 +107,6 @@ export class DemoComponent implements OnInit, OnDestroy {
|
||||
errors: Errors = { errors: {} };
|
||||
btnSettingsTitle = 'Paramètres';
|
||||
|
||||
constructor(
|
||||
private titleService: Title,
|
||||
private userService: UserService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle(`Ad Astra - ${this.title}`);
|
||||
Object.assign(this.user, this.userService.getCurrentUser());
|
||||
@@ -79,5 +132,4 @@ export class DemoComponent implements OnInit, OnDestroy {
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
moveItemInArray(this._colors, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { GoogleMap, MapKmlLayer } from '@angular/google-maps';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { DropzonesComponent } from './dropzones.component';
|
||||
|
||||
describe('DropzonesComponent', () => {
|
||||
@@ -8,7 +14,17 @@ describe('DropzonesComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DropzonesComponent]
|
||||
imports: [DropzonesComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.overrideComponent(DropzonesComponent, {
|
||||
remove: { imports: [GoogleMap, MapKmlLayer] },
|
||||
add: { schemas: [CUSTOM_ELEMENTS_SCHEMA] },
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { GoogleMap, MapKmlLayer} from '@angular/google-maps';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { GoogleMap, MapKmlLayer } from '@angular/google-maps';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
@@ -23,31 +23,37 @@ import { UtilitiesService } from '@services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dropzones',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, GoogleMap, MapKmlLayer,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatGridListModule, MatIconModule, MatMenuModule,
|
||||
DatePipe,
|
||||
GoogleMap,
|
||||
MapKmlLayer,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatGridListModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
//ChartistModule,
|
||||
HistoryTableComponent, BarsChartComponent, PieChartComponent
|
||||
HistoryTableComponent,
|
||||
BarsChartComponent,
|
||||
PieChartComponent,
|
||||
],
|
||||
templateUrl: './dropzones.component.html',
|
||||
styleUrl: './dropzones.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateX(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateX(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class DropzonesComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
private _lastjump: Subscription = new Subscription();
|
||||
private _dropzonesByOaci!: Array<DropZoneByOaci>;
|
||||
@@ -62,16 +68,16 @@ export class DropzonesComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
//public chartBarConfig: Configuration = this._utilitiesService.getBarConfig();
|
||||
|
||||
public center: google.maps.LatLngLiteral = {lat: 44.596408, lng: -1.115958};
|
||||
public center: google.maps.LatLngLiteral = { lat: 44.596408, lng: -1.115958 };
|
||||
public zoom = 16;
|
||||
public kmlUrl = 'https://rampeur.com/assets/kmls/dropzones.kml?v=6';
|
||||
//public mapTypeId: google.maps.MapTypeId = google.maps.MapTypeId.SATELLITE;
|
||||
@@ -82,28 +88,16 @@ export class DropzonesComponent implements OnInit, OnDestroy {
|
||||
mapTypeId: 'satellite',
|
||||
disableDoubleClickZoom: true,
|
||||
maxZoom: 20,
|
||||
minZoom: 3
|
||||
minZoom: 3,
|
||||
};
|
||||
// Arcachon : 44.596408,-1.115958
|
||||
// La Réole : 44.566309,-0.054606
|
||||
// Soulac : 45.495395,-1.081039
|
||||
// Royan : 45.632660,-0.977096
|
||||
// Rochefort : 45.888477,-0.984551
|
||||
// Sables : 46.475393,-1.722160
|
||||
// Pamiers : 43.091951,1.698546
|
||||
// Pau : 43.426255,-0.288453
|
||||
// Cahors : 44.348635,1.478898
|
||||
// Béni : 32.394218,-6.327999
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ dropZonesPageData: DropZonesPageData }> = this.route.data as Observable<{ dropZonesPageData: DropZonesPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { dropZonesPageData: DropZonesPageData }) => {
|
||||
const data$: Observable<{ dropZonesPageData: DropZonesPageData }> = this.route.data as Observable<{
|
||||
dropZonesPageData: DropZonesPageData;
|
||||
}>;
|
||||
this._data = data$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: { dropZonesPageData: DropZonesPageData }) => {
|
||||
const pageData: DropZonesPageData = data.dropZonesPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
this._loadDropZoneByOaci(pageData);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { HerowarsGuildraidComponent } from './herowars-guildraid.component';
|
||||
|
||||
@@ -8,7 +9,10 @@ describe('HerowarsGuildraidComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HerowarsGuildraidComponent]
|
||||
imports: [HerowarsGuildraidComponent],
|
||||
providers: [
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@ import { MatStepperIntl, MatStepperModule } from '@angular/material/stepper';
|
||||
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
import { Errors } from '@models';
|
||||
import { HWGuildRaid, HWGuildRaidAttacker, HWGuildRaidAttackers, HWGuildRaidStage, HWMember } from '@models/herowars';
|
||||
|
||||
import guildRaids from 'src/files-data/hw-guild-raids.json'; // page Asgard -> Guild Raid -> Log
|
||||
import { HWGuildRaid, HWGuildRaidStage, HWMember } from '@models/herowars';
|
||||
|
||||
@Injectable()
|
||||
export class GuildRaidStepperIntl extends MatStepperIntl {
|
||||
@@ -25,7 +23,6 @@ export class GuildRaidStepperIntl extends MatStepperIntl {
|
||||
}
|
||||
@Component({
|
||||
selector: 'app-herowars-guildraid',
|
||||
standalone: true,
|
||||
providers: [
|
||||
{
|
||||
provide: STEPPER_GLOBAL_OPTIONS,
|
||||
@@ -33,27 +30,30 @@ export class GuildRaidStepperIntl extends MatStepperIntl {
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
DatePipe, FormsModule, ListErrorsComponent,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatFormFieldModule,
|
||||
MatIconModule, MatInputModule, MatStepperModule
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
ListErrorsComponent,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatStepperModule,
|
||||
],
|
||||
templateUrl: './herowars-guildraid.component.html',
|
||||
styleUrl: './herowars-guildraid.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class HerowarsGuildraidComponent implements OnInit {
|
||||
private _titleService = inject(Title);
|
||||
|
||||
public title = 'Guild Raids';
|
||||
public subtitle = 'Log';
|
||||
public description = 'Tools in working progress.';
|
||||
@@ -68,20 +68,16 @@ export class HerowarsGuildraidComponent implements OnInit {
|
||||
private _raidStages: HWGuildRaidStage[] = [
|
||||
{ num: 1, name: 'Stage 1', maxPower: 320000, duration: 8, startTime: 0, endTime: 0 },
|
||||
{ num: 2, name: 'Stage 2', maxPower: 500000, duration: 8, startTime: 0, endTime: 0 },
|
||||
{ num: 3, name: 'Stage 3', maxPower: 0, duration: 0, startTime: 0, endTime: 0 }
|
||||
{ num: 3, name: 'Stage 3', maxPower: 0, duration: 0, startTime: 0, endTime: 0 },
|
||||
];
|
||||
|
||||
constructor(
|
||||
private _titleService: Title
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._titleService.setTitle(this.title);
|
||||
this._guildMembers.map((data) => {
|
||||
data.raids = [];
|
||||
data.raidsInfo = {
|
||||
variationAvg: 0,
|
||||
variationSum: 0
|
||||
variationSum: 0,
|
||||
};
|
||||
return data;
|
||||
});
|
||||
@@ -90,5 +86,4 @@ export class HerowarsGuildraidComponent implements OnInit {
|
||||
getMembers(): HWMember[] {
|
||||
return this._guildMembers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { HerowarsGuildwarComponent } from './herowars-guildwar.component';
|
||||
|
||||
@@ -8,7 +10,11 @@ describe('HerowarsGuildwarComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HerowarsGuildwarComponent]
|
||||
imports: [HerowarsGuildwarComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -11,41 +11,44 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
|
||||
import { CardContainerComponent, ListErrorsComponent, GuildwarAttackComponent, GuildwarDefenceComponent } from '@components/shared';
|
||||
import { CardContainerComponent, ListErrorsComponent } from '@components/shared';
|
||||
import { CardColors, Errors, HWActivityStat, HWGuildClan, HWMember } from '@models';
|
||||
import { ClanViewModel, MembersViewModel } from '@viewmodels/herowars';
|
||||
import { HWClanService, HWMemberService, UtilitiesService } from '@services';
|
||||
import { MAX_CLAN_MEMBERS } from '@constants';
|
||||
|
||||
import guildData from '@data/hw-guild-data.json'; // page Membres
|
||||
import guildStatistics from '@data/hw-guild-statistics.json'; // page Overview -> Statistics
|
||||
|
||||
@Component({
|
||||
selector: 'app-herowars-guildwar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, DecimalPipe, FormsModule, CardContainerComponent, ListErrorsComponent,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatFormFieldModule,
|
||||
MatIconModule, MatInputModule, MatTabsModule,
|
||||
GuildwarAttackComponent, GuildwarDefenceComponent
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
FormsModule,
|
||||
CardContainerComponent,
|
||||
ListErrorsComponent,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatTabsModule,
|
||||
],
|
||||
templateUrl: './herowars-guildwar.component.html',
|
||||
styleUrl: './herowars-guildwar.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class HerowarsGuildwarComponent implements OnInit {
|
||||
private _titleService = inject(Title);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
private _clanService = inject(HWClanService);
|
||||
private _memberService = inject(HWMemberService);
|
||||
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public title = 'Guild War';
|
||||
public subtitle = 'Log';
|
||||
@@ -63,13 +66,6 @@ export class HerowarsGuildwarComponent implements OnInit {
|
||||
private _guildMembers: HWMember[] = [];
|
||||
private _guildInfo: HWActivityStat = {} as HWActivityStat;
|
||||
|
||||
constructor(
|
||||
private _titleService: Title,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
private _clanService: HWClanService,
|
||||
private _memberService: HWMemberService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._titleService.setTitle(this.title);
|
||||
}
|
||||
@@ -94,29 +90,35 @@ export class HerowarsGuildwarComponent implements OnInit {
|
||||
}
|
||||
|
||||
loadClan(): void {
|
||||
this._guildClan = this._clanService.loadClan();
|
||||
this._clanService.loadClan().subscribe((clan) => {
|
||||
this._guildClan = clan;
|
||||
this.clanViewModel = new ClanViewModel(this._guildClan);
|
||||
this.clanLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
loadData(): void {
|
||||
this._guildClan = this._clanService.loadClan();
|
||||
this._guildMembers = this._memberService.loadMembers(this._guildClan);
|
||||
this._clanService.loadClan().subscribe((clan) => {
|
||||
this._guildClan = clan;
|
||||
this._memberService.loadMembers(this._guildClan).subscribe((members) => {
|
||||
this._guildMembers = members;
|
||||
this._guildClan = this._clanService.loadClanStats(this._guildClan, this._guildMembers);
|
||||
this.daysToWarn = (parseInt(this._guildClan.daysToKick) / 2);
|
||||
this.daysToWarn = parseInt(this._guildClan.daysToKick) / 2;
|
||||
this.clanViewModel = new ClanViewModel(this._guildClan);
|
||||
this.clanLoaded = true;
|
||||
this.membersViewModel = new MembersViewModel(this._guildMembers);
|
||||
this.membersLoaded = true;
|
||||
console.log(this._guildClan);
|
||||
console.log(this._guildMembers);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadMembers(): void {
|
||||
this._guildMembers = this._memberService.loadMembers(this._guildClan);
|
||||
this._memberService.loadMembers(this._guildClan).subscribe((members) => {
|
||||
this._guildMembers = members;
|
||||
this.membersViewModel = new MembersViewModel(this._guildMembers);
|
||||
this._guildClan = this._clanService.loadClanStats(this._guildClan, this._guildMembers);
|
||||
this.membersLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
getMembers(): HWMember[] {
|
||||
@@ -125,12 +127,9 @@ export class HerowarsGuildwarComponent implements OnInit {
|
||||
|
||||
syncClan(): void {
|
||||
console.log(this._guildClan);
|
||||
console.log(guildData);
|
||||
}
|
||||
|
||||
syncMembers(): void {
|
||||
console.log(this._guildMembers);
|
||||
console.log(guildStatistics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideNativeDateAdapter } from '@angular/material/core';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
|
||||
import { HerowarsComponent } from './herowars.component';
|
||||
|
||||
@@ -8,9 +12,14 @@ describe('HerowarsComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HerowarsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [HerowarsComponent],
|
||||
providers: [
|
||||
provideAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HerowarsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -10,42 +10,46 @@ import { MatTabsModule } from '@angular/material/tabs';
|
||||
|
||||
import {
|
||||
ListErrorsComponent,
|
||||
GuildCardComponent, GuildraidsLogComponent, GuildwarChampionsComponent,
|
||||
GuildwarAttackComponent, GuildwarDefenceComponent, GuildwarTeamsComponent, MembersStatisticsComponent
|
||||
GuildCardComponent,
|
||||
GuildraidsLogComponent,
|
||||
GuildwarChampionsComponent,
|
||||
GuildwarDefenceComponent,
|
||||
GuildwarTeamsComponent,
|
||||
MembersStatisticsComponent,
|
||||
} from '@components/shared';
|
||||
import { Errors, HWMember } from '@models';
|
||||
import guildData from 'src/files-data/hw-guild-data.json'; // page Membres
|
||||
//import guildWarData from 'src/files-data/hw-guild-war.json'; // page Overview -> Statistics | page Guild War -> Guild War
|
||||
//import guildStatistics from 'src/files-data/hw-guild-statistics.json'; // page Overview -> Statistics
|
||||
//import guildWarSlots from 'src/files-data/hw-guild-war-slots.json'; // no pages
|
||||
import { HWDataService } from '@services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-herowars',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatCardModule, MatDividerModule, MatIconModule, MatTabsModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatTabsModule,
|
||||
ListErrorsComponent,
|
||||
GuildCardComponent, GuildraidsLogComponent, GuildwarChampionsComponent,
|
||||
GuildwarAttackComponent, GuildwarDefenceComponent, GuildwarTeamsComponent, MembersStatisticsComponent
|
||||
GuildCardComponent,
|
||||
GuildraidsLogComponent,
|
||||
GuildwarChampionsComponent,
|
||||
GuildwarDefenceComponent,
|
||||
GuildwarTeamsComponent,
|
||||
MembersStatisticsComponent,
|
||||
],
|
||||
templateUrl: './herowars.component.html',
|
||||
styleUrl: './herowars.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class HerowarsComponent implements OnInit {
|
||||
private _titleService = inject(Title);
|
||||
private _dataService = inject(HWDataService);
|
||||
|
||||
public title = 'HeroWars GM Tools';
|
||||
public description = 'Tools in working progress.';
|
||||
public errors: Errors = { errors: {} };
|
||||
@@ -53,32 +57,24 @@ export class HerowarsComponent implements OnInit {
|
||||
public now = new Date();
|
||||
private _guildMembers: HWMember[] = [];
|
||||
|
||||
constructor(
|
||||
private _titleService: Title
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
try {
|
||||
this._titleService.setTitle(this.title);
|
||||
this._dataService.guildData$.subscribe({
|
||||
next: (guildData) => {
|
||||
for (const data of Object.entries(guildData.clan.members)) {
|
||||
const member: HWMember = {} as HWMember;
|
||||
Object.assign(member, data[1]);
|
||||
if (guildData.clan.warriors.indexOf(parseInt(member.id)) !== -1) {
|
||||
member.champion = true;
|
||||
} else {
|
||||
member.champion = false;
|
||||
}
|
||||
member.heroes = { power: 0, teams: []};
|
||||
member.titans = { power: 0, teams: []};
|
||||
member.champion = guildData.clan.warriors.indexOf(parseInt(member.id)) !== -1;
|
||||
member.heroes = { power: 0, teams: [] };
|
||||
member.titans = { power: 0, teams: [] };
|
||||
this._guildMembers.push(member);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
error: (err) => console.error(err),
|
||||
});
|
||||
}
|
||||
|
||||
getMembers(): HWMember[] {
|
||||
return this._guildMembers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
@@ -8,7 +9,10 @@ describe('HomeComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HomeComponent]
|
||||
imports: [HomeComponent],
|
||||
providers: [
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -12,43 +12,27 @@ import { Errors } from '@models';
|
||||
//import { UserService } from '@services';
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatCardModule, MatDividerModule, MatIconModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
imports: [DatePipe, MatCardModule, MatDividerModule, MatIconModule, ListErrorsComponent],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
private _titleService = inject(Title);
|
||||
|
||||
public title = 'Shop bientôt disponible!';
|
||||
public description = 'Encore un peu de patience, notre shop sera mis en ligne d\'ici peu.';
|
||||
public description = "Encore un peu de patience, notre shop sera mis en ligne d'ici peu.";
|
||||
public errors: Errors = { errors: {} };
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public now = new Date();
|
||||
|
||||
constructor(
|
||||
private _titleService: Title,
|
||||
//private _utilitiesService: UtilitiesService
|
||||
//private _userService: UserService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
this._titleService.setTitle(this.title);
|
||||
/*
|
||||
type CreateArrayWithLengthX<
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideNativeDateAdapter } from '@angular/material/core';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { JumpComponent } from './jump.component';
|
||||
|
||||
describe('JumpComponent', () => {
|
||||
@@ -8,7 +13,14 @@ describe('JumpComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [JumpComponent]
|
||||
imports: [JumpComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { DatePipe, DecimalPipe } from '@angular/common';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -9,35 +9,36 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { ListErrorsComponent, MenuItems } from '@components/shared';
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { Errors, Jump, JumpPageData } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-jump',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, DecimalPipe, RouterLink,
|
||||
MatButtonModule, MatCardModule,
|
||||
MatDividerModule, MatIconModule, MatMenuModule,
|
||||
ListErrorsComponent
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
RouterLink,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
],
|
||||
templateUrl: './jump.component.html',
|
||||
styleUrl: './jump.component.scss'
|
||||
styleUrl: './jump.component.scss',
|
||||
})
|
||||
export class JumpComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
public title: string = '';
|
||||
public errors: Errors = { errors: {} };
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public jump: Jump = {} as Jump;
|
||||
public jump: Jump = { sautants: [], author: {} } as unknown as Jump;
|
||||
public prevSlug: string = '';
|
||||
public nextSlug: string = '';
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ pageData: JumpPageData }> = this.route.data as Observable<{ pageData: JumpPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpPageData }) => {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<div @flyInOut>
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<h1 class="mb-1">{{ title }}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
Dernier saut enregistré :
|
||||
<span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à
|
||||
<span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le
|
||||
<span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: "dd/MM/yyyy" }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
@@ -27,13 +30,24 @@
|
||||
<mat-card-title>{{ subtitle }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-2">
|
||||
@if(displayCharts) {
|
||||
@if (displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 col-xs-12">
|
||||
<app-linearea-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor"></app-linearea-chart>
|
||||
<app-linearea-chart
|
||||
[headers]="seriesHeader"
|
||||
[names]="seriesName"
|
||||
[values]="seriesRow"
|
||||
[colors]="seriesColor"
|
||||
></app-linearea-chart>
|
||||
</div>
|
||||
<div class="col-xxl-6 col-xs-12">
|
||||
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart>
|
||||
<app-bars-chart
|
||||
[headers]="seriesHeader"
|
||||
[names]="seriesName"
|
||||
[values]="seriesRow"
|
||||
[colors]="seriesColor"
|
||||
[legend]="true"
|
||||
></app-bars-chart>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -44,44 +58,70 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th>
|
||||
@for (name of seriesHeader; track name) {
|
||||
<th class="text-end">{{ name }}</th>
|
||||
}
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
|
||||
@for (values of seriesRow; track $index; let i = $index) {
|
||||
<tr>
|
||||
<th class="{{ seriesColorClass[i] }} text-end">● {{ seriesName[i] }}</th>
|
||||
@for (value of values; track $index) {
|
||||
<td class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value }}</span>
|
||||
}
|
||||
</td>
|
||||
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th>
|
||||
}
|
||||
<th class="{{ seriesColorClass[i] }} font-monospace text-end">
|
||||
{{ seriesRowTotal[i] }}
|
||||
</th>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th class="text-end">Total</th>
|
||||
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
|
||||
@for (value of seriesColTotal; track $index) {
|
||||
<th class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value }}</span>
|
||||
}
|
||||
</th>
|
||||
}
|
||||
<th class="font-monospace text-end">{{ grandTotal }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-end">Moyenne</th>
|
||||
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
|
||||
@for (value of seriesRowAvg; track $index) {
|
||||
<th class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
}
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
|
||||
}
|
||||
<th class="font-monospace text-end">{{ grandAvg | number: "1.0-1" }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-end">Moyenne <small>3 dernières années</small></th>
|
||||
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
|
||||
@for (value of seriesRowAvgLastYears; track $index) {
|
||||
<th class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
}
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
|
||||
}
|
||||
<th class="font-monospace text-end">{{ grandAvgLastYears | number: "1.0-1" }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -94,22 +134,41 @@
|
||||
<mat-card-title>Types de sauts</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-2">
|
||||
@if(displayCharts) {
|
||||
@if (displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true" [hideZero]="true"></app-line-chart>
|
||||
<app-line-chart
|
||||
[headers]="seriesTypesHeader"
|
||||
[names]="seriesTypesName"
|
||||
[values]="seriesTypesRowCumulated"
|
||||
[colors]="seriesTypesColor"
|
||||
[legend]="true"
|
||||
[hideZero]="true"
|
||||
></app-line-chart>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="mt-2 mb-3"></mat-divider>
|
||||
<div class="row">
|
||||
@for (item of getCategories(); track $index) {
|
||||
<div class="col-lg-2 col-sm-4 col-xs-6">
|
||||
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="$index" [label]="item.categorie"></app-circle-chart>
|
||||
<app-circle-chart
|
||||
[headers]="seriesHeader"
|
||||
[names]="seriesName"
|
||||
[values]="[item.count, lastJump.numero - item.count]"
|
||||
[color]="$index"
|
||||
[label]="item.categorie"
|
||||
></app-circle-chart>
|
||||
</div>
|
||||
}
|
||||
@for (item of getModules(); track $index) {
|
||||
<div class="col-lg-2 col-sm-4 col-xs-6">
|
||||
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="getModuleColor($index)" [label]="item.module"></app-circle-chart>
|
||||
<app-circle-chart
|
||||
[headers]="seriesHeader"
|
||||
[names]="seriesName"
|
||||
[values]="[item.count, lastJump.numero - item.count]"
|
||||
[color]="getModuleColor($index)"
|
||||
[label]="item.module"
|
||||
></app-circle-chart>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { JumpsComponent } from './jumps.component';
|
||||
|
||||
describe('JumpsComponent', () => {
|
||||
@@ -8,8 +12,14 @@ describe('JumpsComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [JumpsComponent]
|
||||
});
|
||||
imports: [JumpsComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(JumpsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { DatePipe, DecimalPipe } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -13,36 +13,46 @@ import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent } from '@components/shared/helpers-chart';
|
||||
import {
|
||||
BarsChartComponent,
|
||||
CircleChartComponent,
|
||||
LineChartComponent,
|
||||
LineAreaChartComponent,
|
||||
} from '@components/shared/helpers-chart';
|
||||
import { UtilitiesService } from '@services';
|
||||
import { Jump, JumpByCategorie, JumpByDate, JumpByDateByModule, JumpByModule, JumpsPageData, JumpYears } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-jumps',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
BarsChartComponent,
|
||||
CircleChartComponent,
|
||||
LineChartComponent,
|
||||
LineAreaChartComponent,
|
||||
],
|
||||
templateUrl: './jumps.component.html',
|
||||
styleUrl: './jumps.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateX(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateX(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class JumpsComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
private _jumpsByCategorie: Array<JumpByCategorie> = [];
|
||||
private _jumpsByModule: Array<JumpByModule> = [];
|
||||
@@ -61,22 +71,22 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
public seriesName: string[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesTypesHeader: string[] = [];
|
||||
public seriesTypesName: string[] = [];
|
||||
public seriesTypesRow: Array<Array<number>> = [];
|
||||
public seriesTypesRowCumulated: Array<Array<number>> = [];
|
||||
public seriesTypesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'pastels'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public seriesRowTotal: number[] = [];
|
||||
@@ -86,14 +96,10 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
public seriesColTotalClosed: number[] = [];
|
||||
public seriesColTotalLastYears: number[] = [];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ pageData: JumpsPageData }> = this.route.data as Observable<{ pageData: JumpsPageData }>;
|
||||
const data$: Observable<{ pageData: JumpsPageData }> = this.route.data as Observable<{
|
||||
pageData: JumpsPageData;
|
||||
}>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpsPageData }) => {
|
||||
const pageData: JumpsPageData = data.pageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
@@ -104,7 +110,7 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
const modules: Array<string> = [...verticals, ...others];
|
||||
const minoration = 2; // -2 pour ne pas compter la première année
|
||||
this.min = pageData.jumpsByYears[0].year;
|
||||
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
|
||||
this.max = pageData.jumpsByYears[pageData.jumpsByYears.length - 1].year;
|
||||
|
||||
this.seriesHeader = this._utilitiesService.getMonthsList();
|
||||
const values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
@@ -117,13 +123,13 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
|
||||
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
|
||||
this.seriesColTotal[(row.month-1)] += row.count;
|
||||
this.seriesRow[row.year - this.min][row.month - 1] = row.count;
|
||||
this.seriesColTotal[row.month - 1] += row.count;
|
||||
if (row.year < currentYear) {
|
||||
this.seriesColTotalClosed[(row.month-1)] += row.count;
|
||||
this.seriesColTotalClosed[row.month - 1] += row.count;
|
||||
}
|
||||
if (row.year >= (currentYear-3) && row.year < currentYear) {
|
||||
this.seriesColTotalLastYears[(row.month-1)] += row.count;
|
||||
if (row.year >= currentYear - 3 && row.year < currentYear) {
|
||||
this.seriesColTotalLastYears[row.month - 1] += row.count;
|
||||
}
|
||||
return row;
|
||||
});
|
||||
@@ -132,15 +138,18 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
this.seriesRowTotal.push(total);
|
||||
});
|
||||
this.seriesColTotalClosed.forEach((row: number) => {
|
||||
const value: number = (row/(this.seriesName.length-minoration));
|
||||
const value: number = row / (this.seriesName.length - minoration);
|
||||
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
|
||||
});
|
||||
this.seriesColTotalLastYears.forEach((row: number) => {
|
||||
const value: number = (row/3); // Les 3 dernières années
|
||||
const value: number = row / 3; // Les 3 dernières années
|
||||
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
|
||||
});
|
||||
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
|
||||
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
|
||||
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce(
|
||||
(partialSum, accumulated) => partialSum + accumulated,
|
||||
0,
|
||||
);
|
||||
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
|
||||
|
||||
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
|
||||
@@ -150,8 +159,8 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
|
||||
let verticalCount:number = 0;
|
||||
let otherCount:number = 0;
|
||||
let verticalCount: number = 0;
|
||||
let otherCount: number = 0;
|
||||
pageData.jumpsByModule.forEach((row: JumpByModule) => {
|
||||
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
|
||||
if (modules.indexOf(row.module) !== -1) {
|
||||
@@ -163,25 +172,25 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
if (verticalCount > 0) {
|
||||
const row:JumpByModule = {
|
||||
const row: JumpByModule = {
|
||||
categorie: 'FF',
|
||||
module: 'Vertical',
|
||||
count: verticalCount
|
||||
}
|
||||
count: verticalCount,
|
||||
};
|
||||
this._jumpsByModule.push(row);
|
||||
}
|
||||
if (otherCount > 0) {
|
||||
const row:JumpByModule = {
|
||||
const row: JumpByModule = {
|
||||
categorie: 'FF',
|
||||
module: 'Track/Trace',
|
||||
count: otherCount
|
||||
}
|
||||
count: otherCount,
|
||||
};
|
||||
this._jumpsByModule.push(row);
|
||||
}
|
||||
|
||||
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
|
||||
for (let index = 0; index < 12; index++) {
|
||||
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
|
||||
const month: string = index + 1 < 10 ? `0${index + 1}` : `${index + 1}`;
|
||||
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
|
||||
}
|
||||
}
|
||||
@@ -193,7 +202,7 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
|
||||
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
|
||||
if (indexOf !== -1) {
|
||||
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
|
||||
this.seriesTypesRow[indexOf][(row.year - this.min) * 12 + row.month - 1] += row.count;
|
||||
}
|
||||
});
|
||||
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
|
||||
@@ -203,12 +212,21 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
return accumulated;
|
||||
});
|
||||
});
|
||||
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
|
||||
this.seriesTypesHeader = this.seriesTypesHeader.slice(
|
||||
pageData.jumpsByDateByModule[0].month + 1,
|
||||
-pageData.jumpsByDateByModule[pageData.jumpsByDateByModule.length - 1].month,
|
||||
);
|
||||
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
|
||||
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
|
||||
return row.slice(
|
||||
pageData.jumpsByDateByModule[0].month + 1,
|
||||
-pageData.jumpsByDateByModule[pageData.jumpsByDateByModule.length - 1].month,
|
||||
);
|
||||
});
|
||||
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
|
||||
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
|
||||
return row.slice(
|
||||
pageData.jumpsByDateByModule[0].month + 1,
|
||||
-pageData.jumpsByDateByModule[pageData.jumpsByDateByModule.length - 1].month,
|
||||
);
|
||||
});
|
||||
/*
|
||||
console.log('this.min', this.min);
|
||||
@@ -244,6 +262,6 @@ export class JumpsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public getModuleColor(index: number): number {
|
||||
return (this._jumpsByCategorie.length + index);
|
||||
return this._jumpsByCategorie.length + index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, Inject, OnDestroy } from '@angular/core';
|
||||
import { AsyncPipe, DatePipe } from '@angular/common';
|
||||
import { Component, OnDestroy, inject } from '@angular/core';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -14,16 +14,24 @@ import { MatSelectModule } from '@angular/material/select';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { map, startWith, take } from 'rxjs/operators';
|
||||
|
||||
import { AeronefByImat, CanopyModelBySize, DropZoneByOaci, Errors, Jump, JumpAddParams, JumpByModule, JumpFile, JumpFileType } from '@models';
|
||||
import {
|
||||
AeronefByImat,
|
||||
CanopyModelBySize,
|
||||
DropZoneByOaci,
|
||||
Errors,
|
||||
Jump,
|
||||
JumpAddParams,
|
||||
JumpByModule,
|
||||
JumpFile,
|
||||
JumpFileType,
|
||||
} from '@models';
|
||||
import { AeronefsService, CanopiesService, DropZonesService, JumpsService } from '@services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-jump-add-dialog',
|
||||
templateUrl: 'jump-add.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatAutocompleteModule,
|
||||
@@ -35,10 +43,21 @@ import { AeronefsService, CanopiesService, DropZonesService, JumpsService } from
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule
|
||||
]
|
||||
MatOptionModule,
|
||||
],
|
||||
})
|
||||
export class JumpAddDialogComponent implements OnDestroy {
|
||||
dialogRef = inject<MatDialogRef<JumpAddDialogComponent>>(MatDialogRef);
|
||||
private fb = inject(FormBuilder);
|
||||
private _aeronefsService = inject(AeronefsService);
|
||||
private _canopiesService = inject(CanopiesService);
|
||||
private _dropZonesService = inject(DropZonesService);
|
||||
private _jumpsService = inject(JumpsService);
|
||||
data = inject<{
|
||||
jump: Jump;
|
||||
lastJump: Jump;
|
||||
}>(MAT_DIALOG_DATA);
|
||||
|
||||
private _formChanges: Array<Subscription> = [];
|
||||
private _subscriptions: Array<Subscription> = [];
|
||||
private _aeronefByImat: Subscription = new Subscription();
|
||||
@@ -46,7 +65,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
private _dropZoneByOaci: Subscription = new Subscription();
|
||||
private _jumpByModule: Subscription = new Subscription();
|
||||
public errors!: Errors;
|
||||
public tagField = new FormControl<string>('', { nonNullable: true});
|
||||
public tagField = new FormControl<string>('', { nonNullable: true });
|
||||
public jumpForm: FormGroup;
|
||||
public jump: Jump;
|
||||
public lastJump: Jump;
|
||||
@@ -73,7 +92,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
dropzone: false,
|
||||
groupe: false,
|
||||
programme: false,
|
||||
voile: false
|
||||
voile: false,
|
||||
};
|
||||
public inputOptions: {
|
||||
aeronefs: Array<AeronefByImat>;
|
||||
@@ -92,24 +111,18 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
modules: Observable<Array<JumpByModule>>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpAddDialogComponent>,
|
||||
private fb: FormBuilder,
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _canopiesService: CanopiesService,
|
||||
private _dropZonesService: DropZonesService,
|
||||
private _jumpsService: JumpsService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: {jump: Jump, lastJump: Jump}
|
||||
) {
|
||||
constructor() {
|
||||
const data = this.data;
|
||||
|
||||
this.inputOptions = { aeronefs: [], canopies: [], dropzones: [], categories: [] };
|
||||
//this.filteredOptions.aeronefs = this._aeronefsService.getAllByImat();
|
||||
this.jump = data.jump;
|
||||
this.lastJump = data.lastJump;
|
||||
this.nextJump = (this.lastJump.numero + 1);
|
||||
this.nextJump = this.lastJump.numero + 1;
|
||||
this.params = {
|
||||
numeros: this.nextJump.toString(),
|
||||
date: '',
|
||||
video: true
|
||||
video: true,
|
||||
};
|
||||
this.jump.numero = this.nextJump;
|
||||
const controlsConfig = {
|
||||
@@ -131,7 +144,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
accessoires: '',
|
||||
zone: '',
|
||||
dossier: '',
|
||||
video: ''
|
||||
video: '',
|
||||
};
|
||||
this.jumpForm = this.fb.group(controlsConfig);
|
||||
this.filename = this._computeFilename();
|
||||
@@ -142,7 +155,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
}),
|
||||
this.jumpForm.controls['numero'].valueChanges.subscribe(() => {
|
||||
this.filename = this._computeFilename();
|
||||
})
|
||||
}),
|
||||
);
|
||||
/*
|
||||
this.filteredAeronefOptions = this.jumpForm.controls['aeronef'].valueChanges.pipe(
|
||||
@@ -164,60 +177,60 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
this.filteredOptions = {
|
||||
aeronefs: this.jumpForm.controls['aeronef'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const aeronef = typeof value === 'string' ? value : value?.aeronef;
|
||||
return aeronef ? this._filterImat(aeronef as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
imats: this.jumpForm.controls['imat'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const imat = typeof value === 'string' ? value : value?.imat;
|
||||
return imat ? this._filterImat(imat as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
canopies: this.jumpForm.controls['voile'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const voile = typeof value === 'string' ? value : value?.voile;
|
||||
return voile ? this._filterTaille(voile as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
tailles: this.jumpForm.controls['taille'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const taille = typeof value === 'string' ? value : value?.taille.toString();
|
||||
return taille ? this._filterTaille(taille as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
dropzones: this.jumpForm.controls['lieu'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const lieu = typeof value === 'string' ? value : value?.lieu;
|
||||
return lieu ? this._filterOaci(lieu as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
oaci: this.jumpForm.controls['oaci'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const oaci = typeof value === 'string' ? value : value?.oaci;
|
||||
return oaci ? this._filterOaci(oaci as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
categories: this.jumpForm.controls['categorie'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const category = typeof value === 'string' ? value : value?.categorie;
|
||||
return category ? this._filterModule(category as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
),
|
||||
modules: this.jumpForm.controls['module'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const module = typeof value === 'string' ? value : value?.module;
|
||||
return module ? this._filterModule(module as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
)
|
||||
),
|
||||
};
|
||||
this._loadAeronefByImat();
|
||||
this._loadCanopyModelBySize();
|
||||
@@ -244,7 +257,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
const timestamp: number = Date.parse(this.jumpForm.controls['date'].value);
|
||||
if (isNaN(timestamp) == false) {
|
||||
const date: Date = new Date(timestamp);
|
||||
file += `${(date.getFullYear() + '').padStart(2, '0')}-${((date.getMonth() + 1) + '').padStart(2, '0')}-${(date.getDate() + '').padStart(2, '0')}_`;
|
||||
file += `${(date.getFullYear() + '').padStart(2, '0')}-${(date.getMonth() + 1 + '').padStart(2, '0')}-${(date.getDate() + '').padStart(2, '0')}_`;
|
||||
}
|
||||
if (numero !== undefined) {
|
||||
numero = (numero + '').padStart(5, '0');
|
||||
@@ -261,7 +274,10 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
let path: string = '/Volumes/Storage/Skydive/Videos/';
|
||||
if (this.jumpForm.controls['module'].value != '') {
|
||||
//path += `${this.jumpForm.controls['module'].value}/`;
|
||||
path += typeof this.jumpForm.controls['module'].value === 'string' ? `${this.jumpForm.controls['module'].value}/` : `${this.jumpForm.controls['module'].value?.module}/`;
|
||||
path +=
|
||||
typeof this.jumpForm.controls['module'].value === 'string'
|
||||
? `${this.jumpForm.controls['module'].value}/`
|
||||
: `${this.jumpForm.controls['module'].value?.module}/`;
|
||||
}
|
||||
this.jumpForm.controls['dossier'].setValue(path);
|
||||
|
||||
@@ -270,35 +286,48 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
|
||||
private _isLastForAllSections() {
|
||||
return (
|
||||
this.useLast.aeronef
|
||||
&& this.useLast.altitude
|
||||
&& this.useLast.discipline
|
||||
&& this.useLast.divers
|
||||
&& this.useLast.dropzone
|
||||
&& this.useLast.groupe
|
||||
&& this.useLast.programme
|
||||
&& this.useLast.voile
|
||||
this.useLast.aeronef &&
|
||||
this.useLast.altitude &&
|
||||
this.useLast.discipline &&
|
||||
this.useLast.divers &&
|
||||
this.useLast.dropzone &&
|
||||
this.useLast.groupe &&
|
||||
this.useLast.programme &&
|
||||
this.useLast.voile
|
||||
);
|
||||
}
|
||||
|
||||
private _filterImat(imat: string): Array<AeronefByImat> {
|
||||
const filterValue = imat.toLowerCase();
|
||||
return this.inputOptions.aeronefs.filter(option => (option.aeronef.toLowerCase().includes(filterValue) || option.imat.toLowerCase().includes(filterValue)));
|
||||
return this.inputOptions.aeronefs.filter(
|
||||
(option) =>
|
||||
option.aeronef.toLowerCase().includes(filterValue) || option.imat.toLowerCase().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _filterModule(module: string): Array<JumpByModule> {
|
||||
const filterValue = module.toLowerCase();
|
||||
return this.inputOptions.categories.filter(option => (option.categorie.toLowerCase().includes(filterValue) || option.module.toLowerCase().includes(filterValue)));
|
||||
return this.inputOptions.categories.filter(
|
||||
(option) =>
|
||||
option.categorie.toLowerCase().includes(filterValue) ||
|
||||
option.module.toLowerCase().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _filterOaci(oaci: string): Array<DropZoneByOaci> {
|
||||
const filterValue = oaci.toLowerCase();
|
||||
return this.inputOptions.dropzones.filter(option => (option.lieu.toLowerCase().includes(filterValue) || option.oaci.toLowerCase().includes(filterValue)));
|
||||
return this.inputOptions.dropzones.filter(
|
||||
(option) =>
|
||||
option.lieu.toLowerCase().includes(filterValue) || option.oaci.toLowerCase().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _filterTaille(taille: string): Array<CanopyModelBySize> {
|
||||
const filterValue = taille.toLowerCase();
|
||||
return this.inputOptions.canopies.filter(option => (option.voile.toLowerCase().includes(filterValue) || option.taille.toString().includes(filterValue)));
|
||||
return this.inputOptions.canopies.filter(
|
||||
(option) =>
|
||||
option.voile.toLowerCase().includes(filterValue) || option.taille.toString().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
@@ -376,7 +405,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
if (tag != null && tag.trim() !== '') {
|
||||
if (tag.includes(',') === true) {
|
||||
const tags = tag.split(',');
|
||||
tags.forEach(sautant => {
|
||||
tags.forEach((sautant) => {
|
||||
if (this.jump.sautants.indexOf(sautant.trim()) < 0) {
|
||||
this.jump.sautants.push(sautant.trim());
|
||||
}
|
||||
@@ -386,7 +415,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
this.tagField.reset('');
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
const participants = this.jump.sautants.length + 1;
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
}
|
||||
|
||||
@@ -562,7 +591,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
this.useLast.groupe = false;
|
||||
this.useLast.all = this._isLastForAllSections();
|
||||
this.jump.sautants = this.jump.sautants.filter((sautant) => sautant !== index);
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
const participants = this.jump.sautants.length + 1;
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
}
|
||||
|
||||
@@ -766,17 +795,17 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
const elements: string[] = this.jumpForm.controls['numero'].value.split(';');
|
||||
elements.forEach((element: string) => {
|
||||
if (element == '') {
|
||||
jumpNum ++;
|
||||
jumpNum++;
|
||||
element = jumpNum.toString();
|
||||
}
|
||||
const values: string[] = element.split('-');
|
||||
if (values.length == 2) {
|
||||
const start: number = parseInt(values[0]);
|
||||
const stop: number = parseInt(values[1]);
|
||||
const diff: number = (stop - start);
|
||||
const diff: number = stop - start;
|
||||
/* Ajout de saut par plage */
|
||||
for (let index = 0; index <= diff; index++) {
|
||||
jumpNum = (start + index);
|
||||
jumpNum = start + index;
|
||||
numeros.push(jumpNum);
|
||||
}
|
||||
} else {
|
||||
@@ -785,7 +814,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
/* Ajout de sauts par quantité */
|
||||
//const start: number = jumpNum;
|
||||
for (let index = 1; index <= parseInt(quantity[1]); index++) {
|
||||
jumpNum ++;
|
||||
jumpNum++;
|
||||
numeros.push(jumpNum);
|
||||
}
|
||||
} else {
|
||||
@@ -802,14 +831,38 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
|
||||
/* Valeurs communes aux 1 à n saut(s) à ajouter */
|
||||
jump.date = date.toISOString();
|
||||
jump.lieu = typeof this.jumpForm.controls['lieu'].value === 'string' ? this.jumpForm.controls['lieu'].value : this.jumpForm.controls['lieu'].value.lieu;
|
||||
jump.oaci = typeof this.jumpForm.controls['oaci'].value === 'string' ? this.jumpForm.controls['oaci'].value : this.jumpForm.controls['oaci'].value.oaci;
|
||||
jump.aeronef = typeof this.jumpForm.controls['aeronef'].value === 'string' ? this.jumpForm.controls['aeronef'].value : this.jumpForm.controls['aeronef'].value.aeronef;
|
||||
jump.imat = typeof this.jumpForm.controls['imat'].value === 'string' ? this.jumpForm.controls['imat'].value : this.jumpForm.controls['imat'].value.imat;
|
||||
jump.voile = typeof this.jumpForm.controls['voile'].value === 'string' ? this.jumpForm.controls['voile'].value : this.jumpForm.controls['voile'].value.voile;
|
||||
jump.taille = typeof this.jumpForm.controls['taille'].value === 'string' ? this.jumpForm.controls['taille'].value : this.jumpForm.controls['taille'].value.taille;
|
||||
jump.categorie = typeof this.jumpForm.controls['categorie'].value === 'string' ? this.jumpForm.controls['categorie'].value : this.jumpForm.controls['categorie'].value.categorie;
|
||||
jump.module = typeof this.jumpForm.controls['module'].value === 'string' ? this.jumpForm.controls['module'].value : this.jumpForm.controls['module'].value.module;
|
||||
jump.lieu =
|
||||
typeof this.jumpForm.controls['lieu'].value === 'string'
|
||||
? this.jumpForm.controls['lieu'].value
|
||||
: this.jumpForm.controls['lieu'].value.lieu;
|
||||
jump.oaci =
|
||||
typeof this.jumpForm.controls['oaci'].value === 'string'
|
||||
? this.jumpForm.controls['oaci'].value
|
||||
: this.jumpForm.controls['oaci'].value.oaci;
|
||||
jump.aeronef =
|
||||
typeof this.jumpForm.controls['aeronef'].value === 'string'
|
||||
? this.jumpForm.controls['aeronef'].value
|
||||
: this.jumpForm.controls['aeronef'].value.aeronef;
|
||||
jump.imat =
|
||||
typeof this.jumpForm.controls['imat'].value === 'string'
|
||||
? this.jumpForm.controls['imat'].value
|
||||
: this.jumpForm.controls['imat'].value.imat;
|
||||
jump.voile =
|
||||
typeof this.jumpForm.controls['voile'].value === 'string'
|
||||
? this.jumpForm.controls['voile'].value
|
||||
: this.jumpForm.controls['voile'].value.voile;
|
||||
jump.taille =
|
||||
typeof this.jumpForm.controls['taille'].value === 'string'
|
||||
? this.jumpForm.controls['taille'].value
|
||||
: this.jumpForm.controls['taille'].value.taille;
|
||||
jump.categorie =
|
||||
typeof this.jumpForm.controls['categorie'].value === 'string'
|
||||
? this.jumpForm.controls['categorie'].value
|
||||
: this.jumpForm.controls['categorie'].value.categorie;
|
||||
jump.module =
|
||||
typeof this.jumpForm.controls['module'].value === 'string'
|
||||
? this.jumpForm.controls['module'].value
|
||||
: this.jumpForm.controls['module'].value.module;
|
||||
jump.hauteur = this.jumpForm.controls['hauteur'].value;
|
||||
jump.deploiement = this.jumpForm.controls['deploiement'].value;
|
||||
jump.participants = this.jumpForm.controls['participants'].value;
|
||||
@@ -832,10 +885,11 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
const file: JumpFile = {
|
||||
name: item.video,
|
||||
path: item.dossier,
|
||||
type: JumpFileType.VIDEO
|
||||
type: JumpFileType.VIDEO,
|
||||
} as JumpFile;
|
||||
this._subscriptions.push(
|
||||
this._jumpsService.saveFile(item.slug, file)
|
||||
this._jumpsService
|
||||
.saveFile(item.slug, file)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (file) => {
|
||||
@@ -844,8 +898,8 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
jumps.push(item);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
@@ -9,17 +9,11 @@ import { Jump } from '@models';
|
||||
@Component({
|
||||
selector: 'app-jump-delete-dialog',
|
||||
templateUrl: 'jump-delete.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatDialogModule, MatButtonModule, MatIconModule
|
||||
]
|
||||
imports: [DatePipe, MatDialogModule, MatButtonModule, MatIconModule],
|
||||
})
|
||||
export class JumpDeleteDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpDeleteDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
dialogRef = inject<MatDialogRef<JumpDeleteDialogComponent>>(MatDialogRef);
|
||||
jump = inject<Jump>(MAT_DIALOG_DATA);
|
||||
|
||||
closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, Inject, OnDestroy } from '@angular/core';
|
||||
import { AsyncPipe, DatePipe } from '@angular/common';
|
||||
import { Component, OnDestroy, inject } from '@angular/core';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -20,10 +20,8 @@ import { AeronefsService, CanopiesService, DropZonesService, JumpsService } from
|
||||
@Component({
|
||||
selector: 'app-jump-edit-dialog',
|
||||
templateUrl: 'jump-edit.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatAutocompleteModule,
|
||||
@@ -35,15 +33,23 @@ import { AeronefsService, CanopiesService, DropZonesService, JumpsService } from
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule
|
||||
]
|
||||
MatOptionModule,
|
||||
],
|
||||
})
|
||||
export class JumpEditDialogComponent implements OnDestroy {
|
||||
dialogRef = inject<MatDialogRef<JumpEditDialogComponent>>(MatDialogRef);
|
||||
private fb = inject(FormBuilder);
|
||||
private _aeronefsService = inject(AeronefsService);
|
||||
private _canopiesService = inject(CanopiesService);
|
||||
private _dropZonesService = inject(DropZonesService);
|
||||
private _jumpsService = inject(JumpsService);
|
||||
jump = inject<Jump>(MAT_DIALOG_DATA);
|
||||
|
||||
private _aeronefByImat: Subscription = new Subscription();
|
||||
private _canopyModelBySize: Subscription = new Subscription();
|
||||
private _dropZoneByOaci: Subscription = new Subscription();
|
||||
private _jumpByModule: Subscription = new Subscription();
|
||||
public tagField = new FormControl<string>('', { nonNullable: true});
|
||||
public tagField = new FormControl<string>('', { nonNullable: true });
|
||||
public jumpForm: FormGroup;
|
||||
public inputOptions: {
|
||||
aeronefs: Array<AeronefByImat>;
|
||||
@@ -62,15 +68,7 @@ export class JumpEditDialogComponent implements OnDestroy {
|
||||
modules: Observable<Array<JumpByModule>>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpEditDialogComponent>,
|
||||
private fb: FormBuilder,
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _canopiesService: CanopiesService,
|
||||
private _dropZonesService: DropZonesService,
|
||||
private _jumpsService: JumpsService,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) {
|
||||
constructor() {
|
||||
this.inputOptions = { aeronefs: [], canopies: [], dropzones: [], categories: [] };
|
||||
const controlsConfig = {
|
||||
slug: [this.jump.slug, Validators.required],
|
||||
@@ -91,67 +89,67 @@ export class JumpEditDialogComponent implements OnDestroy {
|
||||
accessoires: this.jump.accessoires,
|
||||
zone: this.jump.zone,
|
||||
dossier: this.jump.dossier,
|
||||
video: this.jump.video
|
||||
video: this.jump.video,
|
||||
};
|
||||
this.jumpForm = this.fb.group(controlsConfig);
|
||||
|
||||
this.filteredOptions = {
|
||||
aeronefs: this.jumpForm.controls['aeronef'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const aeronef = typeof value === 'string' ? value : value?.aeronef;
|
||||
return aeronef ? this._filterImat(aeronef as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
imats: this.jumpForm.controls['imat'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const imat = typeof value === 'string' ? value : value?.imat;
|
||||
return imat ? this._filterImat(imat as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
canopies: this.jumpForm.controls['voile'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const voile = typeof value === 'string' ? value : value?.voile;
|
||||
return voile ? this._filterTaille(voile as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
tailles: this.jumpForm.controls['taille'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const taille = typeof value === 'string' ? value : value?.taille.toString();
|
||||
return taille ? this._filterTaille(taille as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
dropzones: this.jumpForm.controls['lieu'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const lieu = typeof value === 'string' ? value : value?.lieu;
|
||||
return lieu ? this._filterOaci(lieu as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
oaci: this.jumpForm.controls['oaci'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const oaci = typeof value === 'string' ? value : value?.oaci;
|
||||
return oaci ? this._filterOaci(oaci as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
categories: this.jumpForm.controls['categorie'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const category = typeof value === 'string' ? value : value?.categorie;
|
||||
return category ? this._filterModule(category as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
),
|
||||
modules: this.jumpForm.controls['module'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
map((value) => {
|
||||
const module = typeof value === 'string' ? value : value?.module;
|
||||
return module ? this._filterModule(module as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
)
|
||||
),
|
||||
};
|
||||
this._loadAeronefByImat();
|
||||
this._loadCanopyModelBySize();
|
||||
@@ -168,22 +166,35 @@ export class JumpEditDialogComponent implements OnDestroy {
|
||||
|
||||
private _filterImat(imat: string): Array<AeronefByImat> {
|
||||
const filterValue = imat.toLowerCase();
|
||||
return this.inputOptions.aeronefs.filter(option => (option.aeronef.toLowerCase().includes(filterValue) || option.imat.toLowerCase().includes(filterValue)));
|
||||
return this.inputOptions.aeronefs.filter(
|
||||
(option) =>
|
||||
option.aeronef.toLowerCase().includes(filterValue) || option.imat.toLowerCase().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _filterModule(module: string): Array<JumpByModule> {
|
||||
const filterValue = module.toLowerCase();
|
||||
return this.inputOptions.categories.filter(option => (option.categorie.toLowerCase().includes(filterValue) || option.module.toLowerCase().includes(filterValue)));
|
||||
return this.inputOptions.categories.filter(
|
||||
(option) =>
|
||||
option.categorie.toLowerCase().includes(filterValue) ||
|
||||
option.module.toLowerCase().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _filterOaci(oaci: string): Array<DropZoneByOaci> {
|
||||
const filterValue = oaci.toLowerCase();
|
||||
return this.inputOptions.dropzones.filter(option => (option.lieu.toLowerCase().includes(filterValue) || option.oaci.toLowerCase().includes(filterValue)));
|
||||
return this.inputOptions.dropzones.filter(
|
||||
(option) =>
|
||||
option.lieu.toLowerCase().includes(filterValue) || option.oaci.toLowerCase().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _filterTaille(taille: string): Array<CanopyModelBySize> {
|
||||
const filterValue = taille.toLowerCase();
|
||||
return this.inputOptions.canopies.filter(option => (option.voile.toLowerCase().includes(filterValue) || option.taille.toString().includes(filterValue)));
|
||||
return this.inputOptions.canopies.filter(
|
||||
(option) =>
|
||||
option.voile.toLowerCase().includes(filterValue) || option.taille.toString().includes(filterValue),
|
||||
);
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
@@ -219,7 +230,7 @@ export class JumpEditDialogComponent implements OnDestroy {
|
||||
if (tag != null && tag.trim() !== '' && this.jump.sautants.indexOf(tag) < 0) {
|
||||
this.jump.sautants.push(tag);
|
||||
}
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
const participants = this.jump.sautants.length + 1;
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
this.tagField.reset('');
|
||||
}
|
||||
@@ -382,21 +393,45 @@ export class JumpEditDialogComponent implements OnDestroy {
|
||||
|
||||
removeSautant(index: string): void {
|
||||
this.jump.sautants = this.jump.sautants.filter((sautant) => sautant !== index);
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
const participants = this.jump.sautants.length + 1;
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
}
|
||||
|
||||
submitForm(): void {
|
||||
// update the model
|
||||
this.updateJump(this.jumpForm.value);
|
||||
this.jump.lieu = typeof this.jumpForm.controls['lieu'].value === 'string' ? this.jumpForm.controls['lieu'].value : this.jumpForm.controls['lieu'].value.lieu;
|
||||
this.jump.oaci = typeof this.jumpForm.controls['oaci'].value === 'string' ? this.jumpForm.controls['oaci'].value : this.jumpForm.controls['oaci'].value.oaci;
|
||||
this.jump.aeronef = typeof this.jumpForm.controls['aeronef'].value === 'string' ? this.jumpForm.controls['aeronef'].value : this.jumpForm.controls['aeronef'].value.aeronef;
|
||||
this.jump.imat = typeof this.jumpForm.controls['imat'].value === 'string' ? this.jumpForm.controls['imat'].value : this.jumpForm.controls['imat'].value.imat;
|
||||
this.jump.voile = typeof this.jumpForm.controls['voile'].value === 'string' ? this.jumpForm.controls['voile'].value : this.jumpForm.controls['voile'].value.voile;
|
||||
this.jump.taille = typeof this.jumpForm.controls['taille'].value === 'string' ? this.jumpForm.controls['taille'].value : this.jumpForm.controls['taille'].value.taille;
|
||||
this.jump.categorie = typeof this.jumpForm.controls['categorie'].value === 'string' ? this.jumpForm.controls['categorie'].value : this.jumpForm.controls['categorie'].value.categorie;
|
||||
this.jump.module = typeof this.jumpForm.controls['module'].value === 'string' ? this.jumpForm.controls['module'].value : this.jumpForm.controls['module'].value.module;
|
||||
this.jump.lieu =
|
||||
typeof this.jumpForm.controls['lieu'].value === 'string'
|
||||
? this.jumpForm.controls['lieu'].value
|
||||
: this.jumpForm.controls['lieu'].value.lieu;
|
||||
this.jump.oaci =
|
||||
typeof this.jumpForm.controls['oaci'].value === 'string'
|
||||
? this.jumpForm.controls['oaci'].value
|
||||
: this.jumpForm.controls['oaci'].value.oaci;
|
||||
this.jump.aeronef =
|
||||
typeof this.jumpForm.controls['aeronef'].value === 'string'
|
||||
? this.jumpForm.controls['aeronef'].value
|
||||
: this.jumpForm.controls['aeronef'].value.aeronef;
|
||||
this.jump.imat =
|
||||
typeof this.jumpForm.controls['imat'].value === 'string'
|
||||
? this.jumpForm.controls['imat'].value
|
||||
: this.jumpForm.controls['imat'].value.imat;
|
||||
this.jump.voile =
|
||||
typeof this.jumpForm.controls['voile'].value === 'string'
|
||||
? this.jumpForm.controls['voile'].value
|
||||
: this.jumpForm.controls['voile'].value.voile;
|
||||
this.jump.taille =
|
||||
typeof this.jumpForm.controls['taille'].value === 'string'
|
||||
? this.jumpForm.controls['taille'].value
|
||||
: this.jumpForm.controls['taille'].value.taille;
|
||||
this.jump.categorie =
|
||||
typeof this.jumpForm.controls['categorie'].value === 'string'
|
||||
? this.jumpForm.controls['categorie'].value
|
||||
: this.jumpForm.controls['categorie'].value.categorie;
|
||||
this.jump.module =
|
||||
typeof this.jumpForm.controls['module'].value === 'string'
|
||||
? this.jumpForm.controls['module'].value
|
||||
: this.jumpForm.controls['module'].value.module;
|
||||
// close the dialog with result
|
||||
this.dialogRef.close(this.jump);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { DatePipe, DecimalPipe } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
@@ -10,19 +10,11 @@ import { Jump } from '@models';
|
||||
@Component({
|
||||
selector: 'app-jump-view-dialog',
|
||||
templateUrl: 'jump-view.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, DecimalPipe,
|
||||
MatButtonModule, MatDialogModule,
|
||||
MatDividerModule, MatIconModule
|
||||
]
|
||||
imports: [DatePipe, DecimalPipe, MatButtonModule, MatDialogModule, MatDividerModule, MatIconModule],
|
||||
})
|
||||
export class JumpViewDialogComponent {
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpViewDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
dialogRef = inject<MatDialogRef<JumpViewDialogComponent>>(MatDialogRef);
|
||||
jump = inject<Jump>(MAT_DIALOG_DATA);
|
||||
|
||||
closeDialog(): void {
|
||||
// close the dialog without result
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideNativeDateAdapter } from '@angular/material/core';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { LogbookComponent } from './logbook.component';
|
||||
|
||||
describe('LogbookComponent', () => {
|
||||
@@ -8,8 +13,15 @@ describe('LogbookComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [LogbookComponent]
|
||||
});
|
||||
imports: [LogbookComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
provideNativeDateAdapter(),
|
||||
MenuItems,
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(LogbookComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, AfterContentChecked, OnInit, OnDestroy, computed, signal, Signal } from '@angular/core';
|
||||
import { Component, AfterContentChecked, OnInit, OnDestroy, computed, signal, Signal, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -12,7 +12,13 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatSliderModule } from '@angular/material/slider';
|
||||
import { MatSnackBar, MatSnackBarConfig, MatSnackBarHorizontalPosition, MatSnackBarModule, MatSnackBarVerticalPosition } from '@angular/material/snack-bar';
|
||||
import {
|
||||
MatSnackBar,
|
||||
MatSnackBarConfig,
|
||||
MatSnackBarHorizontalPosition,
|
||||
MatSnackBarModule,
|
||||
MatSnackBarVerticalPosition,
|
||||
} from '@angular/material/snack-bar';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
@@ -20,17 +26,23 @@ import { JumpAddDialogComponent } from '@components/logbook/dialogs';
|
||||
import { MenuItems, JumpTableComponent } from '@components/shared';
|
||||
import { Errors, Jump, JumpFile, JumpFileType, JumpListConfig, Range, User, X2Data } from '@models';
|
||||
import { JumpsService, JumpTableService, UserService } from '@services';
|
||||
import data from 'src/jumps.json';
|
||||
//import data from 'src/jumps_02.json';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, RouterModule, FormsModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatIconModule, MatInputModule, MatMenuModule, MatDialogModule,
|
||||
MatSliderModule, MatSnackBarModule,
|
||||
JumpTableComponent
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatMenuModule,
|
||||
MatDialogModule,
|
||||
MatSliderModule,
|
||||
MatSnackBarModule,
|
||||
JumpTableComponent,
|
||||
],
|
||||
selector: 'app-logbook',
|
||||
templateUrl: './logbook.component.html',
|
||||
@@ -38,18 +50,21 @@ import data from 'src/jumps.json';
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private _jumpsService = inject(JumpsService);
|
||||
private _jumpTableService = inject(JumpTableService);
|
||||
private _userService = inject(UserService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _subscriptions: Array<Subscription> = [];
|
||||
private _data: Subscription = new Subscription();
|
||||
@@ -77,28 +92,20 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
hauteur: Range;
|
||||
year: Range;
|
||||
} = {
|
||||
numero: {start: 1, end: 100000},
|
||||
taille: {start: 1, end: 400},
|
||||
participants: {start: 1, end: 200},
|
||||
hauteur: {start: 0, end: 10000},
|
||||
year: {start: 1970, end: 2020}
|
||||
numero: { start: 1, end: 100000 },
|
||||
taille: { start: 1, end: 400 },
|
||||
participants: { start: 1, end: 200 },
|
||||
hauteur: { start: 0, end: 10000 },
|
||||
year: { start: 1970, end: 2020 },
|
||||
};
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private dialog: MatDialog,
|
||||
private snackBar: MatSnackBar,
|
||||
private _jumpsService: JumpsService,
|
||||
private _jumpTableService: JumpTableService,
|
||||
private _userService: UserService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
//this.importJumps();
|
||||
const data$: Observable<{isAuthenticated: boolean, lastjump: Jump}> = this.route.data as Observable<{isAuthenticated: boolean, lastjump: Jump}>;
|
||||
this._data = data$.subscribe((data: {isAuthenticated: boolean, lastjump: Jump}) => {
|
||||
const data$: Observable<{ isAuthenticated: boolean; lastjump: Jump }> = this.route.data as Observable<{
|
||||
isAuthenticated: boolean;
|
||||
lastjump: Jump;
|
||||
}>;
|
||||
this._data = data$.subscribe((data: { isAuthenticated: boolean; lastjump: Jump }) => {
|
||||
this.isAuthenticated = data.isAuthenticated;
|
||||
if (!this.isAuthenticated) {
|
||||
this.router.navigateByUrl('/login');
|
||||
@@ -114,24 +121,24 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
const currentYear: number = new Date().getFullYear();
|
||||
this.lastJump = data['lastjump'];
|
||||
this.rangesMinMax = {
|
||||
numero: {start: 1, end: data.lastjump.numero},
|
||||
taille: {start: data.lastjump.taille!, end: 230},
|
||||
participants: {start: 1, end: 20},
|
||||
hauteur: {start: 1000, end: 8000},
|
||||
year: {start: 2018, end: currentYear}
|
||||
}
|
||||
numero: { start: 1, end: data.lastjump.numero },
|
||||
taille: { start: data.lastjump.taille!, end: 230 },
|
||||
participants: { start: 1, end: 20 },
|
||||
hauteur: { start: 1000, end: 8000 },
|
||||
year: { start: 2018, end: currentYear },
|
||||
};
|
||||
this.listConfig.filters.numeroRangeStart = this.rangesMinMax.numero.start;
|
||||
this.listConfig.filters.numeroRangeEnd = this.rangesMinMax.numero.end;
|
||||
this.listConfig.filters.tailleRangeStart = this.rangesMinMax.taille.start;
|
||||
this.listConfig.filters.tailleRangeEnd = this.rangesMinMax.taille.end;
|
||||
this.listConfig.filters.participantsRangeStart = this.rangesMinMax.participants.start;
|
||||
this.listConfig.filters.participantsRangeEnd = (this.rangesMinMax.participants.end / 2);
|
||||
this.listConfig.filters.participantsRangeEnd = this.rangesMinMax.participants.end / 2;
|
||||
this.listConfig.filters.hauteurRangeStart = this.rangesMinMax.hauteur.start;
|
||||
this.listConfig.filters.hauteurRangeEnd = this.rangesMinMax.hauteur.end;
|
||||
this.listConfig.filters.yearRangeStart = this.rangesMinMax.year.start;
|
||||
this.listConfig.filters.yearRangeEnd = this.rangesMinMax.year.end;
|
||||
this.jumpsSinceHundred = computed(() => (this.lastJump.numero % 100));
|
||||
this.jumpsToHundred = computed(() => (100 - this.jumpsSinceHundred()));
|
||||
this.jumpsSinceHundred = computed(() => this.lastJump.numero % 100);
|
||||
this.jumpsToHundred = computed(() => 100 - this.jumpsSinceHundred());
|
||||
/*
|
||||
this._lastjump$ = this._jumpsService.getLastJump();
|
||||
this._lastjump = this._lastjump$.subscribe((jump) => {
|
||||
@@ -165,7 +172,7 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
this._data.unsubscribe();
|
||||
this._currentUser.unsubscribe();
|
||||
this._lastjump.unsubscribe();
|
||||
this._subscriptions.forEach(subscription => {
|
||||
this._subscriptions.forEach((subscription) => {
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
@@ -181,38 +188,19 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
}
|
||||
}
|
||||
|
||||
importJumps() {
|
||||
try {
|
||||
data.forEach((entry) => {
|
||||
Object.assign(this.jump, entry);
|
||||
this._jumpsService.save(this.jump)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log(`Le saut n'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
openAddDialog(): void {
|
||||
this.jump = <Jump>{};
|
||||
this.jump.numero = (this.lastJump.numero + 1);
|
||||
this.jump.numero = this.lastJump.numero + 1;
|
||||
this.jump.sautants = [];
|
||||
this.jump.participants = 1;
|
||||
const dialogRef = this.dialog.open(JumpAddDialogComponent, {
|
||||
width: '70vw',
|
||||
data: {jump: this.jump, lastJump: this.lastJump}
|
||||
data: { jump: this.jump, lastJump: this.lastJump },
|
||||
});
|
||||
dialogRef.afterClosed().pipe(take(1))
|
||||
.subscribe(result => {
|
||||
dialogRef
|
||||
.afterClosed()
|
||||
.pipe(take(1))
|
||||
.subscribe((result) => {
|
||||
if (result != undefined) {
|
||||
this._onEntry(result);
|
||||
}
|
||||
@@ -229,19 +217,30 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
config.filters.offset = 0;
|
||||
config.filters.dateRangeStart = `${jumps[0].date!.substring(0, 10)} 00:00:00`;
|
||||
config.filters.dateRangeEnd = `${jumps[0].date!.substring(0, 10)} 23:59:59`;
|
||||
const subscription: Subscription = this._jumpsService.getAllFromSkydiverIdApi(config)
|
||||
const subscription: Subscription = this._jumpsService
|
||||
.getAllFromSkydiverIdApi(config)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (data) => {
|
||||
if (data.items.length) {
|
||||
const diff = jumps.length - data.items.length;
|
||||
if (diff < 0) {
|
||||
console.error(`${diff} ${diff > 1 ? 'données' : 'donnée'} altimètre de trop le ${jumps[0].date!.substring(0, 10)}`);
|
||||
console.error(
|
||||
`${diff} ${diff > 1 ? 'données' : 'donnée'} altimètre de trop le ${jumps[0].date!.substring(0, 10)}`,
|
||||
);
|
||||
}
|
||||
if (diff > 0) {
|
||||
console.error(`${diff}/${jumps.length} ${diff > 1 ? 'données altimètre manquantes' : 'donnée altimètre manquante'} le ${jumps[0].date!.substring(0, 10)}`, jumps, data.items);
|
||||
console.error(
|
||||
`${diff}/${jumps.length} ${diff > 1 ? 'données altimètre manquantes' : 'donnée altimètre manquante'} le ${jumps[0].date!.substring(0, 10)}`,
|
||||
jumps,
|
||||
data.items,
|
||||
);
|
||||
}
|
||||
console.log(`${jumps.length} ${jumps.length > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, jumps.sort((a, b) => b.numero! - a.numero!), data.items);
|
||||
console.log(
|
||||
`${jumps.length} ${jumps.length > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`,
|
||||
jumps.sort((a, b) => b.numero! - a.numero!),
|
||||
data.items,
|
||||
);
|
||||
this._addX2Data(data.items, jumps);
|
||||
} else {
|
||||
console.info('Aucune données altimètre pour le ', jumps[0].date!.substring(0, 10));
|
||||
@@ -250,13 +249,14 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
error: (err) => {
|
||||
console.error('getAllFromSkydiverIdApi error');
|
||||
this.errors = err;
|
||||
}
|
||||
},
|
||||
});
|
||||
this._subscriptions.push(subscription);
|
||||
}
|
||||
|
||||
private _addJump(jumps: Array<Jump>, index: number = 0) {
|
||||
this._jumpsService.save(jumps[index])
|
||||
this._jumpsService
|
||||
.save(jumps[index])
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
@@ -265,16 +265,16 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
Object.assign(this.jump, jump);
|
||||
this._openSnackBar(`Le saut n°${jump.numero} a été ajouté !`, 'Annuler');
|
||||
this._resetErrors();
|
||||
if (jumps.length == (index + 1)) {
|
||||
if (jumps.length == index + 1) {
|
||||
this._loadSkydiverIdJumps(jumps);
|
||||
} else {
|
||||
this._addJump(jumps, (index + 1));
|
||||
this._addJump(jumps, index + 1);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -282,26 +282,26 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
const file: JumpFile = {
|
||||
name: x2data[index].name,
|
||||
path: `/Volumes/Storage/Skydive/X2_Logs/${x2data[index].date!.substring(0, 4)}/`,
|
||||
type: JumpFileType.CSV
|
||||
type: JumpFileType.CSV,
|
||||
} as JumpFile;
|
||||
this._subscriptions.push(
|
||||
this._jumpsService.saveX2Data(jumps[index].slug!, file, x2data[index])
|
||||
this._jumpsService
|
||||
.saveX2Data(jumps[index].slug!, file, x2data[index])
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
jumps[index] = jump;
|
||||
console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
|
||||
if (jumps.length == (index + 1) || x2data.length == (index + 1)) {
|
||||
if (jumps.length == index + 1 || x2data.length == index + 1) {
|
||||
this._addKmlFile(jumps);
|
||||
} else {
|
||||
this._addX2Data(x2data, jumps, (index + 1));
|
||||
this._addX2Data(x2data, jumps, index + 1);
|
||||
}
|
||||
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -311,15 +311,19 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
const file: JumpFile = {
|
||||
name: jumps[index].x2data.name.substring(0, -4) + '.kml',
|
||||
path: `/Volumes/Storage/Skydive/X2_Kmls/${jumps[index].date.substring(0, 4)}/`,
|
||||
type: JumpFileType.KML
|
||||
type: JumpFileType.KML,
|
||||
} as JumpFile;
|
||||
this._subscriptions.push(
|
||||
this._jumpsService.saveKml(jumps[index].slug, file)
|
||||
this._jumpsService
|
||||
.saveKml(jumps[index].slug, file)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (file) => {
|
||||
console.log(`Le fichier ${file.path}${file.name} a été ajouté au saut n°${jumps[index].numero} !`, 'OK');
|
||||
if (jumps.length == (index + 1)) {
|
||||
console.log(
|
||||
`Le fichier ${file.path}${file.name} a été ajouté au saut n°${jumps[index].numero} !`,
|
||||
'OK',
|
||||
);
|
||||
if (jumps.length == index + 1) {
|
||||
this._resetErrors();
|
||||
this.jumpRefresh = !this.jumpRefresh;
|
||||
this._jumpTableService.updateJumpRefresh(this.jumpRefresh);
|
||||
@@ -328,13 +332,13 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
this.lastJump = jump;
|
||||
});
|
||||
} else {
|
||||
this._addKmlFile(jumps, (index + 1));
|
||||
this._addKmlFile(jumps, index + 1);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -387,7 +391,7 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
const config: MatSnackBarConfig = {
|
||||
horizontalPosition: <MatSnackBarHorizontalPosition>'end',
|
||||
verticalPosition: <MatSnackBarVerticalPosition>'bottom',
|
||||
duration: 4000
|
||||
duration: 4000,
|
||||
};
|
||||
this.snackBar.open(content, title, config);
|
||||
}
|
||||
@@ -395,5 +399,4 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { PageComponent } from './page.component';
|
||||
|
||||
@@ -8,7 +11,12 @@ describe('PageComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PageComponent]
|
||||
imports: [PageComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,57 +1,48 @@
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { ListErrorsComponent, } from '@components/shared';
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
import { Errors, Article, ArticlePageData } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, RouterLink,
|
||||
MatCardModule, MatDividerModule, MatIconModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
imports: [DatePipe, MatCardModule, MatDividerModule, MatIconModule, ListErrorsComponent],
|
||||
templateUrl: './page.component.html',
|
||||
styleUrl: './page.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class PageComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private titleService = inject(Title);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
public title = 'Shop bientôt disponible!';
|
||||
public description = 'Encore un peu de patience, notre shop sera mis en ligne d\'ici peu.';
|
||||
public description = "Encore un peu de patience, notre shop sera mis en ligne d'ici peu.";
|
||||
public errors: Errors = { errors: {} };
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public article: Article = {} as Article;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private titleService: Title
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ pageData: ArticlePageData }> = this.route.data as Observable<{ pageData: ArticlePageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: ArticlePageData }) => {
|
||||
const data$: Observable<{ pageData: ArticlePageData }> = this.route.data as Observable<{
|
||||
pageData: ArticlePageData;
|
||||
}>;
|
||||
this._data = data$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: { pageData: ArticlePageData }) => {
|
||||
this.article = data.pageData.article;
|
||||
this.title = this.article.title;
|
||||
this.titleService.setTitle(this.article.title);
|
||||
|
||||
@@ -2,11 +2,8 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pages',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './pages.component.html',
|
||||
styleUrl: './pages.component.scss'
|
||||
styleUrl: './pages.component.scss',
|
||||
})
|
||||
export class PagesComponent {
|
||||
|
||||
}
|
||||
export class PagesComponent {}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { ProductComponent } from './product.component';
|
||||
|
||||
@@ -8,7 +11,12 @@ describe('ProductComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProductComponent]
|
||||
imports: [ProductComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,57 +1,48 @@
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { DatePipe, DecimalPipe } from '@angular/common';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { ListErrorsComponent, } from '@components/shared';
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
import { Errors, Product, ProductPageData } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, DecimalPipe, RouterLink,
|
||||
MatCardModule, MatDividerModule, MatIconModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
imports: [DatePipe, DecimalPipe, MatCardModule, MatDividerModule, MatIconModule, ListErrorsComponent],
|
||||
templateUrl: './product.component.html',
|
||||
styleUrl: './product.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class ProductComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private titleService = inject(Title);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
public title = 'Ad Astra - Produit';
|
||||
public description = 'Encore un peu de patience, notre shop sera mis en ligne d\'ici peu.';
|
||||
public description = "Encore un peu de patience, notre shop sera mis en ligne d'ici peu.";
|
||||
public errors: Errors = { errors: {} };
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public product: Product = {} as Product;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private titleService: Title
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ pageData: ProductPageData }> = this.route.data as Observable<{ pageData: ProductPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: ProductPageData }) => {
|
||||
const data$: Observable<{ pageData: ProductPageData }> = this.route.data as Observable<{
|
||||
pageData: ProductPageData;
|
||||
}>;
|
||||
this._data = data$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: { pageData: ProductPageData }) => {
|
||||
this.product = data.pageData.product;
|
||||
this.title = this.product.name;
|
||||
this.description = this.product.description;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { ProductsComponent } from './products.component';
|
||||
|
||||
@@ -8,7 +11,12 @@ describe('ProductsComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProductsComponent]
|
||||
imports: [ProductsComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,59 +1,50 @@
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { DatePipe, DecimalPipe } from '@angular/common';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { ListErrorsComponent, } from '@components/shared';
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
import { Errors, Product, ProductsPageData } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-products',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, DecimalPipe, RouterLink,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatIconModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
imports: [DecimalPipe, MatButtonModule, MatCardModule, MatDividerModule, MatIconModule, ListErrorsComponent],
|
||||
templateUrl: './products.component.html',
|
||||
styleUrl: './products.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]),
|
||||
transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]),
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
],
|
||||
})
|
||||
export class ProductsComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private titleService = inject(Title);
|
||||
|
||||
private _data: Subscription = new Subscription();
|
||||
public title = 'Ad Astra - Produit';
|
||||
public description = 'Encore un peu de patience, notre shop sera mis en ligne d\'ici peu.';
|
||||
public description = "Encore un peu de patience, notre shop sera mis en ligne d'ici peu.";
|
||||
public errors: Errors = { errors: {} };
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public products: Array<Product> = [];
|
||||
public productsCount = 0;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private titleService: Title
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ pageData: ProductsPageData }> = this.route.data as Observable<{ pageData: ProductsPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: ProductsPageData }) => {
|
||||
const data$: Observable<{ pageData: ProductsPageData }> = this.route.data as Observable<{
|
||||
pageData: ProductsPageData;
|
||||
}>;
|
||||
this._data = data$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: { pageData: ProductsPageData }) => {
|
||||
this.products = data.pageData.products;
|
||||
this.productsCount = data.pageData.count;
|
||||
//this.title = 'Fleurs CBD';
|
||||
@@ -68,5 +59,4 @@ export class ProductsComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy() {
|
||||
this._data.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
import { ProfileComponent } from './profile.component';
|
||||
|
||||
@@ -8,8 +10,12 @@ describe('ProfileComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ProfileComponent]
|
||||
});
|
||||
imports: [ProfileComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -13,17 +13,15 @@ import { UserService } from '@services';
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatIconModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
imports: [RouterLink, MatButtonModule, MatCardModule, MatDividerModule, MatIconModule, ListErrorsComponent],
|
||||
selector: 'app-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrl: './profile.component.scss'
|
||||
styleUrl: './profile.component.scss',
|
||||
})
|
||||
export class ProfileComponent implements OnInit, OnDestroy {
|
||||
private titleService = inject(Title);
|
||||
private userService = inject(UserService);
|
||||
|
||||
private _user: Subscription = new Subscription();
|
||||
title = 'Mon compte';
|
||||
bannerTitle = '';
|
||||
@@ -31,11 +29,6 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||
errors: Errors = { errors: {} };
|
||||
btnSettingsTitle = 'Paramètres';
|
||||
|
||||
constructor(
|
||||
private titleService: Title,
|
||||
private userService: UserService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle(`Ad Astra - ${this.title}`);
|
||||
Object.assign(this.user, this.userService.getCurrentUser());
|
||||
@@ -49,5 +42,4 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy() {
|
||||
this._user.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,506 +0,0 @@
|
||||
[{
|
||||
"_id": {
|
||||
"$oid": "646676e011a65b214af84215"
|
||||
},
|
||||
"favorites": [
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb18"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb1a"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb15"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb24"
|
||||
}
|
||||
],
|
||||
"following": [],
|
||||
"username": "jgautier",
|
||||
"email": "jgautier@aucoffre.com",
|
||||
"firstname": "Julien",
|
||||
"lastname": "Gautier",
|
||||
"phone": "0661655763",
|
||||
"society": "AuCoffre",
|
||||
"salt": "ddaa921b00889b72c53a443f379022f9",
|
||||
"hash": "0b767756b834d313c9f2d2addbc16a1c42ee15385d65c73ddbaa474da66239bbe77cbfbaf40f53a127bfedfd81403cbc252902ff6ac8f16b7d70fdfa1d20c98aab3b367dbeebbb76d9e74fdabbbbb06fda9b14b60add027042d5b582b2d259f58024069da79b018449bcf752c7ac8faa0580f0b85c14a0e49883bd4e5eebcf1803731997d7c5d2216ea80fb4463021483d5e8d196ccddcdc3b51bb8cbb85f563a2f51b616385a50fbbb5b7fb1bd4c25cae4b81eb7ccde0d89e302782590a26e2f769a5903d4649076f3df15f2a9c60a0180d143702b0259c090b52432fcfca4693a3b79d09d00f820c9ecc53984c41ea215ae032d89e4c089e7dddbfdf2352e6d429f3c896fe2b2c6a671f6fcd73134e8ebb082d3060aaa36cc40e6d5f24236b13d3643aeb4d03fad11cce434c95cf4f812186a9093117e3f3a57a978702f3a86beedea0aafbb1d0b04db6de42151f80a3c86dd55705e5ab8b70ca6fc2321e9960076674b93c097451bc0834a4be2e7f69881296c75432fd13127bbf6efc2fbcd4a6752164f03ba8079d30715b2d5a1d6f0d4ffa8670c593a2c96791e621617f139ad985f0843763691f6e6348ea6d4e07cadac5680b7037b30511372230cb65aa3c36cb4ea8154e852ea76da853c9b090a5e0e6f0e3b9fe568f35dc0b6902d75ae7d95a34f040f69add055fb375be86de1498f362e1265e38cfa20e402b7e80",
|
||||
"role": "Admin",
|
||||
"createdAt": {
|
||||
"$date": "2023-05-18T19:05:04.978Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-23T14:12:49.144Z"
|
||||
},
|
||||
"__v": 83,
|
||||
"image": "/assets/images/user_avatar.jpg",
|
||||
"pseudoAC": "LEPARA"
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64681bf16ff7b20feae78554"
|
||||
},
|
||||
"favorites": [
|
||||
{
|
||||
"$oid": "6469770267a3086eb994c62c"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb1a"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb32"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb48"
|
||||
},
|
||||
{
|
||||
"$oid": "646b78739cc692a8a4eb9970"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb36"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb38"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb16"
|
||||
}
|
||||
],
|
||||
"following": [],
|
||||
"username": "julien_gautier",
|
||||
"email": "rampeur@gmail.com",
|
||||
"firstname": "Julien",
|
||||
"lastname": "Gautier",
|
||||
"phone": "0661655763",
|
||||
"society": "AuCoffre",
|
||||
"salt": "4c87e9e9f899d03593faa1eb9e903bda",
|
||||
"hash": "e27251923c34a0bb886d8d21b2de3fc784002071865b4e69b4c8b1c3adca1bc1ed68e7387453612abdffaa9f24476d81e621d450346a77f6303c8fc2e69ad74a6a795de7f12b0520772a37cc70af166920beac1e57e7f837b4c995016e00e44e1638da5916b209ceab543c8f58bdc2ada8d3dbb6d91630f3923a0901719432410c040fd379ded1f2ed7cbc2015b36ae1b31357b99e6aec9e1179a74dbd2620684f66d6ed3d117144349277d488effd7ea897c6040b3a0c8068ba982a155c3b1d2c8f063e6e8c8590b2b0e8c489b28b48503ffd35c99f54b59f58522319abc81a1e5197ba0f6d2cbbb2affda309e87db48aa76228418ec6875a8f678c35e805ebbfe54a2bc23c429153a4c4e18aedd6b74cf61a237899a9c94c104a69f04323cce32c95890c5cbae84415dc44068072fb7bec14379d61b77c084f967f42de331421d31e36ab423925cce28ddcbd770223936c026af7d8a5ecbf6777f52845e9e85ed67e358fbe591f68231e8f658b140bcda358faa4dd028b1048679900314ec75fdfb5f64c96dd6abc39e3c60d22c67500da9883e2dedb508175a8cb4f66091ea142b3c3a4fa5e9784875a9e0a1aff659ddf8bdccaf1dc3524a5a83c094333afd29e6280bc7af93d0aa6f593b40f49c29739940a412aa0364bb21d6463318826267e4cd4825ca4baa7e1a526d62ceeadcf8d73969e0c490c3588f476058eb4e0",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-05-20T01:01:37.633Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-01T08:40:21.130Z"
|
||||
},
|
||||
"__v": 70,
|
||||
"image": "/assets/images/logo_user_002.jpg"
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64ba8c824ecc6ac7bb7629a3"
|
||||
},
|
||||
"favorites": [
|
||||
{
|
||||
"$oid": "646921ceae37e3f3574d9ab4"
|
||||
},
|
||||
{
|
||||
"$oid": "6468e67d3a6bccf2fe57fb15"
|
||||
},
|
||||
{
|
||||
"$oid": "647739a22ee14fec66329870"
|
||||
}
|
||||
],
|
||||
"following": [],
|
||||
"username": "abusco",
|
||||
"email": "abusco@aucoffre.com",
|
||||
"firstname": "Anthony",
|
||||
"lastname": "Busco",
|
||||
"phone": "0631902525",
|
||||
"society": "AuCoffre",
|
||||
"salt": "b8e33b5ae7ca938ab466ba80fdae6e5f",
|
||||
"hash": "37ee30a899b7fa6596555d2b538ca89ee5af42e7fbc9de0bea82657b0561c8dc747ffcd383776b97e56fc40513098a2860b13646b9112b1c0f0b086d718ae881e313153b0947b807485169931ed4ff99e8d70df7e0481b6a13f83cc2750dc3e1b3fde0e8a5de5cb939647250b677e1f5958c6a0cc8c64ebceffa009c13ee89b1c4acdba9e4b4dde8fd08201a813e320317806716adcd984959c29ef21e67b4acbc0f2a54f606a469a7e4708bd60b75229af03972a14eb1dd5c62533428ee1acb2d1e4e66d045689f5a4d31ddaeccebe2e9254dd87417490378ae568c0ff16708063de56754e3550ba1c6be498773b0e8fd5c260a8e9e97b284db8ec2694ccdd9290dba83111d1c894af57bc70ac11337b2df0cdcd3c44e2adbea2939b70ee3402c0bcdcbe13ca220406422e3ef43f1ba8e57018d461822adc234b44430db8079ddf9b32d926ac82db9a23169b79ada4482c7bf14e6b1a4f7a38840e578d0307f9acaae9d6c373f65415e6b5dbcdda94bf40331f7c39c9eb07da1a1f18ee7d2eae8773e8aa76c9cd822bf25daefbefcdc4d8d1576197cf5dbb2c5677f2a624b5aac549cccb53a6d9b4c37243dd865f150e042c798ab1904dc704bc62853253a7ca8a58f79c5b607b043a9a5efed2e86c8380c1fbdc3227b5c0288f49ad7cb005c015bf1785caaee41fdf37fdfcb6a5e35789339a36ed6d31fe5bcccecec04b9b3",
|
||||
"role": "Admin",
|
||||
"createdAt": {
|
||||
"$date": "2023-05-18T19:05:04.978Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-07-21T13:49:08.539Z"
|
||||
},
|
||||
"__v": 1,
|
||||
"image": "/assets/images/logo_user_001.jpg"
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d35f07f42bebe5d1625fef"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "christophe_marchesseau",
|
||||
"email": "info@oretcie.fr",
|
||||
"firstname": "Christophe",
|
||||
"lastname": "Marchesseau",
|
||||
"phone": "0671563041",
|
||||
"society": "Or Et Compagnie Eysines/Villenave/Fargue",
|
||||
"pseudoAC": "WYLERA",
|
||||
"salt": "10b786c0f3500268f59b72eeac903d00",
|
||||
"hash": "57d9a1600146152d274749f2a9237d3ca33eb6c2265ee2f89f557ce375a91f1699a14c0a57145d12cab55b96d5cea995a1a87ba768ee01c7717c6a622580c64719d2cffba1b6027ca27cfcdd1cdc2ddeeaa91fc5b6b533b89c453723d68a5611f2c01b23bc32860834b28e158984a96bcecd05c990a3f6ed0e3111f9619375f6a16e52c02999d3b58f815be115e9e49f30c54bf6b2416287b7cb46dbb90e1634ba545637b565eda680b543d041d41dcd0c79b8c32f2bc40446d634ba065ff44a5a2dd1e21cb1144066ff34a08d03b00e94b75c5c94f81cab9f352fc9b7456e5ce2741fccee92a7835dc4d117cabecf1307978c9e1507541245d4e629a251e730ad388a1bb42e06a3a0478f50914289a96bc226375cab8a61b6f840979d75b57a0821174914ae931805dea02af37c7d2f5a2cc75578ad4f4ea4684fb0047dbc2463f0164a543da271f8b3b743ed1d981af05d230398b9f4246d7030c983db81628639373d612bc9b49de703b7de84de812914f1214d958e3b737600a4f5a8ee0ec6cb4c03c4b96fba6d0f1ba58ec23880d16a9df072b8cf96df2430e183caf0f16955c409913adda2fad8942178a77fb31d6273b9bf49f24a219a2b7c6897cccad8d5d6e24a2b2e342bcfc510a74f9f43539cb69380e3a9bdb3da03169814aca1d37a942e2951ef9e1a13ee865ba1aabb6a2083f6c609a659fa242c17b1d82afb",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:40:23.133Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:40:23.133Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d35f98f42bebe5d1626001"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "joel_hourcourigaray",
|
||||
"email": "oap33170@outlook.fr",
|
||||
"firstname": "Joel",
|
||||
"lastname": "Hourcourigaray",
|
||||
"phone": "0619446471",
|
||||
"society": "Au Napoléon d'Or Gradignan",
|
||||
"pseudoAC": "JECUPU",
|
||||
"salt": "1078ee75bad68b2845bfae3c8ab2df4f",
|
||||
"hash": "4135d652f196e75d4e1a8e975ae559b05a65d05feaabf373a6844656437c03a6e7756dd0be696859bf8c877037aae1b41ac9919f84dd3ff55549a6de4ae5a9972662c668e163e1c97d182ca941f4821f99d0cb4c3e95b887793cf20b282ce491d55aad31e6f381a14f0919274b26122d7e548808276fbb003d03bcafbdc3315022d2047b2ce76f93cc324c1e5f3e30fb278d1f68b507469a35f43d0f416894f090a0b9ef1dbe7583be438b17c336f8728178437a24fa39d4e4bcd19b27c2c9ce24647575c3e1dca9d4488717901e555f920cfaf1cbe1761f9609747369f376ce135e9dfeb7439e66cd02ef66e62f59e96ab5c93292361bc8f2629ed35627f6c851b2f4c75154ec3246e1b2ed1f8a8b7746ca77014ce1cf1973a6059612dc108dde2bc1d46801e877a7690b24674f69f5e6df5361028966d7d2cc814afc44e1b661d9996c0b8c26cfe5a8d0f63a9be397cf37ecbcf541ab3ab296b4dc14ac8516aa929590d0d1110670182d3b998e0b6ec6b26cffbafd255dea6a738b65b3145df55aafbf983044ded11b2606517888af56665a2eeffbf767d1fb75cde2fe1d714ec15badab4e9bc57e963991c047cf997959255cd6d2e8ac4755afd28a1a393ebafbe3397a0493ed23f1c4c73e5ecd4cff07b664cf880d430409503801888d77b7afd1bf3e5e5ef83f92d9169c4bf9e751aed7f3adb2305d6dce60f7a8ab8bb2",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:42:48.377Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:42:48.377Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d36004f42bebe5d1626013"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "valentin_darteyre",
|
||||
"email": "aunapoleondor@outlook.fr",
|
||||
"firstname": "Valentin",
|
||||
"lastname": "Darteyre",
|
||||
"phone": "0637848630",
|
||||
"society": "Au Napoléon d'Or Langon",
|
||||
"pseudoAC": "UMIFER",
|
||||
"salt": "047815ff4983d8848ab14aa9af6ca054",
|
||||
"hash": "d0640f88e03e20e9b7fe84970226dde3346f6ce139a714b7b63e5d2c0dff3c60e6a36c2856f94c85578a8bf2f4410eb418ca28e9bad23e7cd45c583a79128d188a9d92b020f0c43175f94bf7c5fa72a65ac94d9b59bc558807177a3013167b4e3379305bab32808a2d64264c78950d242ff8ac6524cf08bc7d8ff28c97e79cea62cb414a04e1c7aa838a1d29121a92f4d574c45058352aa5c632384dff05f0f00b57295795bc2d2ae62612fe0b3ede0d0ed7860da86ad8eba5fa09a00bb0f7bba911a9659f8f8980946a5068f2e1077c864ba21b36d01f299269f4faf24ccedf2fc005f9e62ce7d1fbf2b322415fb9f0f3cf6c3f9edff1a2d45aefee8ae631a8dda425b8d5285637c96ae57911fdbf7179e6a5eed40dea89e1836545c1e1247734556a4d78ce69f65ab8a74ddc4a7ed3d5688afa1192b6383ceac427e1f79e99ec3db2dc700f847a9393e17f5aa9a267ce4a649f32d623f010e7d218b7c99624aacd2239760735c83e6f9f01ce38474aa7e07feb69aea69304725d8e9bfb9c8c7d0000a84a03cb4efa82629052437c352f27bdd356486eb9c498e9759abb15dfd6405b0b301913991dec68ae5ae4e649e30f738f535bbd95f9f4511e171eb978eae47b542c05156a9b7fcfa7987497a31a99538044bbba933d58cae7880da132dd083e2ca24c060be090608138e49893cbdf1b61b672046dc5a2acb30d8b49ac",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:44:36.505Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:44:36.505Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d36063f42bebe5d162602b"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "johann_abecassis",
|
||||
"email": "or-vintage@tutanota.com",
|
||||
"firstname": "Johann",
|
||||
"lastname": "Abecassis",
|
||||
"phone": "0623196292",
|
||||
"society": "Or Vintage",
|
||||
"pseudoAC": "ZUDYXE",
|
||||
"salt": "2f8976c3e7653ecfb64102303e8717bb",
|
||||
"hash": "3fa4e42ecff2d0a827e42667e59d6c75db79c7e4c8e4fb69028f46c6160d63255314a3878e60a7ba11b603d0be74ef786f241ab324a5d31852beaecead19843e753cdf45ff74ff25a0839ab5405807656633ab2a244c8d8e643e4eb8dc81e2b09ded66fdbc93867a12c110dc0a96de01702f88ce4d99521ed6974dfe483467c352110ec1b4f675cf4aba2094e9b755f897e8cf5cc6b45558663df91593bf5da889d37a81c4129e522576e2facd28513decd1a4c01fa879f75d33fbc315f70c8b8f2dc8fd2125dadd95653c6d266189a2e1624fcd3a333ed798b7f24b676f1493a8feba627334979a7ecadda07a3d13d0baa98499a795181c1b1b6233d6ac22bb7cc3ce19f069a72019ed7d27ad3ce01be0c308885bcd7685aa8eabcc2393e1a6b85ae883972eb662ba660f469d16663b728c3a3b3f6e0ae0eef92e08f205f63ed8bf86054766d3095cfeed1f462d66f826a71dd453db52c4212a70b319edc19c4b329b901b9fa5ffbf4a20e691ec3981d781ed94bafbc646443850d76629b346aa60f5b480a82ff79d487fb7462f33b9b9534baf1970027a8b6f5994b7655c00b5ee5bf0c2311b8bb1688167c5fef786b0e1cdf93f0742f3a6093272cea551312fb5ef431fb70a61f69a647205195094faddccbe55cb27f9be4b32c3fee326271fcf8fa586a1ebc65b43f00a23dabdb324ff3277f679ba9bd6d0dee920a51126",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:46:11.647Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:46:11.647Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d360b0f42bebe5d1626063"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "murat_aydin",
|
||||
"email": "eliyor4430@gmail.com",
|
||||
"firstname": "Murat",
|
||||
"lastname": "Aydin",
|
||||
"phone": "+32499344802",
|
||||
"society": "Eliyor (Belgique)",
|
||||
"pseudoAC": "FEZABA",
|
||||
"salt": "0137a9f77588bba98b10bb759b471045",
|
||||
"hash": "0a4ebdf614963e9a1deb53499faa716a1895ff228f763b799828f7fe1917bf1625f5c95b88ee103f9516d129ca8bfbcf821c18977ce583e293e02e210aa97f4239d10a30a36ef0af69377b0e5c8d565ecd64597774d0064b6855713703b12536ae631f333db68a108aab426c8888561a88dca6ddafcf7b0c61c6d76c0c623b18ba7c72fd380cefa83aacf37a9c16dbb866f8a86b933966b86145623b2cfda034a7e257f4847d7ae982cd439cb6ccbfd5b61856247819aa2de75a55cf308a0e0832d7fa94bdfee85bc883bb47020f16ff12d13882216bb4adb27bcf44c704454bddd0a200961b0acd8eb5e41a7afc127c678399dbe2b3a0677d6e81330669babfac481a3fde4db59e0e4bd36f8bc6deb01bbfa375959f666b5108acab37c40590bfcd7ea4093c2e04d7ba0c51b295d119813185abdf6b29e5c05b6b47ccc60a9661b997d753b9350c9e00e95d5a33398ba5bae3c5365f7d19699650001f19f70da874db812149d53d93d13ae8f51bb0af39fd23bd85f5d31e1c2362ff8e38c554dce4e263f70407f09d7f67eb2ac032da854232ff0329e16f375c322c3b1b9e1d8776af6cb2d0ed6cc6a81aa16c9cc602757a3447ebe684cbc2c986cf6921f289021f22732845928e006c8a96d3641fdffde9b4994bf77422667e9cb2e8f142f89e4b6376dbc4801a10d26cad368a22bab92d2ac30f5b0f19e61bba14af0c6e49",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:47:28.244Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:47:28.244Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d360def42bebe5d1626075"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "eric_buichon",
|
||||
"email": "eric.buichon@orange.fr",
|
||||
"firstname": "Eric",
|
||||
"lastname": "Buichon",
|
||||
"phone": "0683460137",
|
||||
"society": "ERBB / Aureus",
|
||||
"pseudoAC": "",
|
||||
"salt": "bd93626a314b3fb1a87c5a0ebd4bec60",
|
||||
"hash": "7660f16bac71d7c26645aa2cf050423c9e63d4ea3150ac383f54ea66268e12c8d1f3ab2263fae6f1b9a82961f4ac9dd5e24e6f4c6be5ac980ea3cb670448585c5f9a20c042d89e6c3353475d5ab190f273a032e9c7afe00fc9173ba9d9c4bada0eb862081c81e3d875ec36754b19d9581333c3d228cc1cb26add38b23a468b5d0ba524c3ecbfb338c4e4d07a8461fc98750c8afbc8ebbf7a672c7d188d078cb095751818250f93a6212fe9c1bb0201c19348f2e1fd56bc7e7f3df453be3b7b4762580c8c1a63d953ca79ed40b40ebc50aed1e338025952e54ecd9973f10336d2a93a771536c588b54727b70f31d435e9409075bf59246e46d7525fbea2371b05d3e93d8018ae0be1d5e6a925cd2160ac4d1a81f857c4b5a9f893f5fac93e00e52c07d7f0fe97c92e573c50fea3f7a01468bfddc3499a23dd4d59f1bfea6c0a6c01cebdb2beb06f14d128de067c1d8b1b876747701182c4946a05b78424e4a6953448ca676e7fb7db48144bd7958d0a491b06df92d7b44fa8c5906845999711c68fd4bb799f03d9f64dd3081ff8f564214c36846f446f74dd65c7a473bdb8ba5aea738b59fd60d69cbd62dd71dac41a2c55c0a372372cb5222b8ca932233f712908de4bd0b92e2b045aaaf9f9084d4a537ab5b90b2fe752e7fc16c18392daeb90a4378d9c8bc56280e1164c045cd9d7393b36506f7fa0943c19d0a131c384a693",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:48:14.532Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:48:14.532Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d3610bf42bebe5d1626086"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "sonia_yungcker",
|
||||
"email": "contact@numissima.com",
|
||||
"firstname": "Sonia",
|
||||
"lastname": "Yungcker",
|
||||
"phone": "0682751820",
|
||||
"society": "Numissima",
|
||||
"pseudoAC": "",
|
||||
"salt": "dd5ee0f64b1e69664ceaac107c56668f",
|
||||
"hash": "0efd56217b6a19fa841e7997bd34c3913df55a46d6310d44731bfc60f80ee0247503783ddc7a61adb14a9c71ccdaf6b02bce9f4b454c170a1b605bc21bcb30ad14886008193adf6b4f422e827f12f75d123aa8fec658b22de3cc0ed9331452a631af8d8a431a98bfc5310788dd4a2b58d36471f6608dc08f085eaf0f223a01e47738aad20c271fbd8575d6e22b82a6932749538b7dacd3b547634ed692df7a54d8571f8db24914f17954845de46df658a63c4cd25fca892b581c6cf5ae8f1871d9e36f3997dcb7190e284314331c018f573eb6bb72597e6406f1a88f5ec6d29d5288b8b8aa4a7d0a0184463e51b65dae5d37ced74939d4c1ea37c767e0f92a0cf8e6d990ad33abd331593d2722bfcf7276e5d55b879cfe726f759865151a3bd1f9c43e77aa09da765b05b6faebc512bb6071da5f66e4057c1d9161994769d53c3fd3c98637fc11a4d6b3a4e1263b9978b62a90761efbd4f6bc431c43f91eee6df1c9df1b259c6f232db794ed02b83d5d669965608dd6554a6f4e5613a6347520065d56d73e5184cf6a446ab13e652513fce973f8055642a547c3bfc0ca48652e8c030389203fb83d37358b0f1542900adf1c27c68fb9aaf7455ebf78d9f2419a9c6a264291ff06883025f78e1dccf4d1e7ed57893d601eec5a19b166c6496f8f4a770b3b78172f4c7fb5727d9dda7399f3c7b68af7f734a0109f0eadcceb7ae8",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:48:59.923Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:48:59.923Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d36205f42bebe5d162609d"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "mohamed_hamza",
|
||||
"email": "cbbo.33@wanadoo.fr",
|
||||
"firstname": "Mohamed",
|
||||
"lastname": "Hamza",
|
||||
"phone": "0643358437",
|
||||
"society": "CBBO",
|
||||
"pseudoAC": "",
|
||||
"salt": "e3d8602fae3ded82fa80a67b2b784630",
|
||||
"hash": "d2a0de718d8d2766130482264b3236af7612dd12607cb5bd1717c12b7ee6dea3f40c62ea3e5c7b1d537b5e47a536ca3a20b7d5805854384199d1249687be06f2d123f54b55cba52de343142287820a6d80786353bb8968a01e2bd8a7d7cbd62844fe710b85b837e0e272c02ddb634fb65c390f149cc0e75515ee69297c7a09dd3454e1e841931c400be0368271d85f3718714fc434bd35adf0503f040baeabf6de747ce54da2291aca4b5b75288e7e3d9d78bb3b0cf6486dfc01eeb23c3011f2fd0002fca3b9d227987dac6ff483d4e27da0ad42224a1b0b0e31a0c34b9a0e62c8c2cca00ff52391d8ac763e02bc88260dbe28d45b1f200494ab155d294021ade4e4829b708540371cc640d3d305d237067b81b130d49a8eea377f3aa5f69ce1d51ff7529d5e12f67d2b1b00e0efe6e5afb647e8a4e36863e9517b83ddddc93bd63392d385831edba06a870621cfed6f525eec80be8532ffef4b6026a39c90c68aadebb9417d3a4f9e84a31fe419e4a3af2301e559d9bc249c43c0c1c1b9db594dc36c542e5316868bc22c2a34062aeeb2c6049d8fd1d66796a88c7e679f5cd0a811e5a3c3626b7a6ec1d52158869696a22b8293c950f2d77d2a0324715386dfa50edd83f1e170520fea11e0a7ef04651523610ad984977542729206c709fe12a75c33535015d72d4ab08e832a335d3df8833fd6f9f833f9c4b960eec980a552",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:53:09.330Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:53:09.330Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d36234f42bebe5d16260af"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "bruno_abitbol",
|
||||
"email": "info@artetorbordeaux.fr",
|
||||
"firstname": "Bruno",
|
||||
"lastname": "Abitbol",
|
||||
"phone": "0608994303",
|
||||
"society": "Art et Or",
|
||||
"pseudoAC": "",
|
||||
"salt": "7ac0adf3e8cd029bffdae7972e1285f0",
|
||||
"hash": "aa4b30596df6d9ff74c9412587a469b6a90316bd7cf59f1d9a032f23e95a62cf8c73f1e3f97ad401bea1a74e3b987861fb929e99022bbff4ad18c3592412b32ebd7e0b387b4319060a624486488d9760cf98a306d49de61da87c26ad8640b3e297946663a7c04e7f2b7b6c015f601e1c7f180d7cade3e0a4e9f039d92f1cbf4f9ddea521aa638fd2d80c1cd6ed2dc8e085122446f14951a78728698ba67871746739cc330ab820f8200cb8be43f88ff2a8fbccaa60000c629af4bbf1251fac444169d92139d65e372ab946d46398e90976328bb55e4f5e741824614b50c8dbea7e67026983966d7bc5d2a10bb4b32cc2ba608557fb80efea74fb3dc5a08101087f08a1c35d3d5816d3065d79c9f46c55f761c49daeeac389f1ccb867a3416df29516524765ecdcfe39bb5fd7cc5b69c186d850c7ce25d1264445d368a02bcbfb1f822cd5335b99e1ff54fcded3c65a2ee3251fee30c02b5041c6f96a019c3107c327f9e170c090f23d0ed8412521ff1cbb2f0525612618024361394bd97c0b9488a66ac77bae86ebda0507b2994a0431284ddcd9543cb1d95b6ef0fb64fa3fead41d2d9f9bf818117dcd6f28578071460bb8e3d917ef73836c234f34d39f73d988c03647ed476ba8ce251d52a767b0790ff6596a0d0f6390d64c73d14b6100361661d3b10dfd56dc0a888dd6e1358b4256bb0802a66eb0df0ba8aeee8113c24c",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:53:56.395Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:53:56.395Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d3627ef42bebe5d16260bf"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "manuel_rodrigues",
|
||||
"email": "changeplus64@gmail.com",
|
||||
"firstname": "Manuel",
|
||||
"lastname": "Rodrigues",
|
||||
"phone": "0621745693",
|
||||
"society": "Change Plus",
|
||||
"pseudoAC": "",
|
||||
"salt": "07841463ed0da7f3e47f3a6faf414e93",
|
||||
"hash": "90b40e506332109636b0285eeb50f9e76b0ac37b9a9fe13b59fb3426754363e7f4c217fff07567f4a7c4ec14a2ad4e5e144863ca52fb1ba2d865d2a62335404b3c79fc19891fe9cbd858eb1d84d178a901ab0b03362f2685775fe5b18d8f51818a337f53b5700bf79b2f639d2c60309af164028fb84e86ca2559a531e62bbfb0aa31246d715f1ab50cffbd1c3687cff8f4d07db1b0345296ba78270216da444dc501ac452842aee292466843290a7d4c5393c633f922c98d075e80dfa888af75cc6db0c15efed3a931a9ea5f27c179c4f5983ef542063df7ea059bdda25d00069652c82d41fd8458d84bac59077d39494d2cf55b87147b0278705b5b871f4509dcfaa720a99a6bc54dd7cf754e9f080c2f2f7a1152904e4164c30487af18c449d5ff7df19c8ce66fba7372bc9c4c2dbe3f45027f6d97642deb7493c43e715c3b143b850c8636b2d2a5975999c6d2d0e4b85156e0f9788a67e391bb05ab796cee2f99e25c908269cc82ea95764437b92b596d22081403363103e305b8352748b77aea2690865dcd782921a6f198908ff8342076475565d50182a9cf0b6fe0d266d829ac9fa67b6f9fa33737aaf68c07c66094fb5efd813fe8ce2e711d17fd95cf1f82096d7314a9b3b19276e22f2f4367e1f3d1d86cde26fd245032ca1cdd02a39712aba6793167853ed3847a6461d56f2d41db4ef9f4b71ac0b78b0d4bd7583e",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:55:10.737Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:55:10.737Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d362a3f42bebe5d16260d1"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "frederic_cabon",
|
||||
"email": "fcabon.numis@yahoo.fr",
|
||||
"firstname": "Frederic",
|
||||
"lastname": "Cabon",
|
||||
"phone": "0556812159",
|
||||
"society": "Cabon Numismatique",
|
||||
"pseudoAC": "",
|
||||
"salt": "ac563ffe5abef7f6d4930d90b7c535d8",
|
||||
"hash": "1e665a577c9806386c66a6fcfbad5ee0340ca0ba2f3f0379f20d5ff171d3218dad85a6ed885148a2a96e45b3c5758217e49f0ae17260310bc1b21f09ff44bfe5434cd05b90aa4883ab31966a49de9231b46139bd337897610653ffdd39ffedf3bfcb5a31ce34e6ade723e847333bdd0369becf613593d88c4c48ba06aa6b35aabf1940846d5d1452f87ec603b625edd5566987564364e436c4f9cf65b0d849bb8b792216bad471c3e60382c893d37bb5f7c7f8ba0dd515e5062a4d92e85b4c2472a2cc25ee48d6b4e6d8370ad111933852732019c7a33e90fe0114b8b67709581920a5a9054806ec50b5fed7c42458bc3bb3f7c319bee23e7b8ffcd323ad16fe7beadc0a1d00d8ffd7575e4e2cc93410dd46679c7360627ddbc44c8fc0b62cd447dd3092482a43981160219d596911f4a7e99a4975904bb21d719524af2b36355fb7411f01e6e826dd6e98a37d83c1c29216aea24a15a29212f7265b379c4a1aea9afb74c43cf24ee11be2514af6305d7691e92911cd1c56d2e71af29be39bbb3e0653d7bc9d8f1d2f2ac099ead62962bb76f59308ead5ce861d61af8eadc7778954aa25b0ab321952f8cd38cff92ed725aa5cbed1b0a9f9ab35cd2d6194523ed2c6366dbe10ab47898d351fd0ea4bc487cf9f915672c093e7df5416d279af18cd94e5891e520fe092d2af19c223ede6fce3f7954547e2cbf6877a66876e2046",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:55:47.818Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:55:47.818Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d362d4f42bebe5d16260e1"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "sebastien_gheneyn",
|
||||
"email": "sebastien.gheneyn@gmail.com",
|
||||
"firstname": "Sebastien",
|
||||
"lastname": "Gheneyn",
|
||||
"phone": "0630765688",
|
||||
"society": "Euro GS Finance",
|
||||
"pseudoAC": "EREGAP",
|
||||
"salt": "82dc595495fc4f5ced00ffe5f62f0366",
|
||||
"hash": "311d477ef906c62dcb5a066ac2ef1c9e6521c0a16042a53cd83f73ec79d5a88917c71650bcfbd29d04c06d0cdb87c6cf0b944111b325584b050127000e14f13efea3dc9f105c22d7bf8596c211ba45c1406191dbe1ee8dac9fc9b60bcd0e794165179df46a6482e860bc6b6df2ea8eb3d87be173ec29478576d15e5faebacef158af1c6630df0a247c021bc3a720e7f9a11a368b56b7c69a8505ac70dbd38ab319a7ef314f231768612e421ff1f1f63797fd0704018086e05ecbf4f53db78433b7972d81cf5794d656475331b765f58698f019e523345d8f49d84abb781f0d831161438b946ccda0eecf9eb0b1da20dfa4c22c099ca14384a9ea48af8f01f26239d96713fcca5d48731de9931236708f4a101cd00d257b6e8828759c56c2218fa3f84641cbc62b30e8e05be416f950ce47627734e4d175735275b5c4a14a3724b4500c335e3c6704ddcdd8f8f15f5737c7a69fb3f6854b7f7256f193b98daf7c3fd367172d61b4294ce44962e8ffd7bf3ec362c4854d83ae6b075d27c3039c8bafb2d790410603642dd4f10c9b6a858fd46107dfc37d214e9461e239c27ff9791d9bc5b5a3d0ae6e65eee7aa33413f401deac1c10ae08f087d5c57105f45f3bcd2ace7727dd3819b384606453ff4c142e108bd7de4592dfb5bb283854d9871d6632d872cbd37881001e9e7003a050967d9dbe47a6c48fc77ded9a22493f89b7c",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:56:36.883Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:56:36.883Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d36325f42bebe5d16260f3"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "patrice_tassistro",
|
||||
"email": "sarlctmp@gmail.com",
|
||||
"firstname": "Patrice",
|
||||
"lastname": "Tassistro",
|
||||
"phone": "0688017403",
|
||||
"society": "CTMP (Comptoir Toulousain Métaux Précieux)",
|
||||
"pseudoAC": "",
|
||||
"salt": "f8946d23b20184c05ff9af9fd30f8ba0",
|
||||
"hash": "65b35ad6ca6240becb63826afe3942e062ab62ca7e2bea1b3a14ee67d558367bfe57d7461c88b1cf44fbeca65418500e3ab1bf4a95f2fe64549616108c2d7558c7e778ff486ec0c315a9cf22f21aefc2df706f1253b76fac39b54fc8cc2888c6ae447a20916148c6b75b6a0595b5cbe462357a64ec368864641615d3343df57440295704eaeb94fed45ad805b6124777700da5589c704105bafb7a0e7c12f5f84e30b12e467362cc94ea4f3e53c93319b5078c7fbdab1ac14f0ff2ce96aa54df444bda9505441eb2d9522913f5c7b41a287ca2ecb0db9ee5be1f92936bbd12b8d26ac2834971e1a2bcf0637fae03d39d6f59eea312291a8930e999ac337f8519589b1cc2cdf6e09c6fef736a6da58f0da4d3cb6553baaa37087a4f2f14e2cb235cb47b1dd3012ee073d3aa0cf21354c2bb8ae5fd2e152743af8ebb5bb9be7d6c7c72f8e4f5ab8bb24d6dbb97d2b77bd5a06d347dd4e1227599c483a288f390aa1ac2c70295bdfd1b70d3e03179750209987d5355aa1f04a53d1cc0d465a973859c7cd2101a85ee488d27636600c5ec225546145f22a9664f6db4a0c3ac7ab3faa84bcf428f882ee632270c1549cf8f103aafcc520af6e1d229f081453ec307535ded8f778088ed0af7bbcc370c17451443903c6705f488c79ce5ba155af5ef5ad8f021396c1e76f667045d8e264a880edb02e4324ddfd3ac1f8ee518a169a96d",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:57:57.795Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:57:57.795Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d3636bf42bebe5d1626105"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "franck_gicquel",
|
||||
"email": "bordeaux@fidso.com",
|
||||
"firstname": "Franck",
|
||||
"lastname": "Gicquel",
|
||||
"phone": "0557999999",
|
||||
"society": "Or Expertise Fidso",
|
||||
"pseudoAC": "",
|
||||
"salt": "d24d34f28257b974145cafe8d38e82c3",
|
||||
"hash": "6ebf5514b234a418500a03d916bb477b6c2859240ea2918ab38e8cd51c28cc9fb09932c00dad7bca97a5f65e2088d8da42b8863f0fcbfff950c799442f2d2fc8f69a282bcf9332926a6f6a0dbbcb24789793d64730c4f67abef4371af5b849afe8cfb9ba3c9d763557beb2fa13690d961c4310a0e8bc389b8344569412d4425d3eb3d6d1169a9d5b17298b3cb9c867acf966f577661fca549d2567a724d8b9ac2028b4c93c53e41ee3968758a9fb7e40c86953614419566f8ef5e0e9c5dbdc052d6e0c36e512b5a3de7df07134d2c98d01131d061aa2a6c4d497a06daf55ef2757908b29e32a284f70418fcfea1ad6cfc189c2c7ee22cf961b27593c46f2625d1493f339f2093a3aa713904847308fa53aa751ba5134f00555cc6018491db2bf35a25e96a0407c830c3dffbf85e2eda4d3fcfd47b4dc74995baad720d088c0eeee07890dc1907b145ebefa382811d92c1b03af4f1c2f9b246cbce70bad94ead2fcaaaa8b337a3839c51b9c43849e2a2278dfdc2e62319a10b2bbbf49615e66ff953c86e3a8ddb4bfd8bf1e2160a9d7da34b18bd67e6bd9be034d770a96855a026dc5badd2f16bf3175e45e196962a196101a013eb3ab8b8eb1c42b40194a93f9b1a403e5d468d2ebec6f534e9defadf815131a410baf8fd72b9b53b660faa6c70551450515ca28fa9800f66bf9076b6313551e8c3cd085a148672a5a596bb902",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T09:59:07.208Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T09:59:07.208Z"
|
||||
},
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d3641ff42bebe5d1626163"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "julien_dapsens_turquat",
|
||||
"email": "mail@galerie-des-monnaies.fr",
|
||||
"firstname": "Julien",
|
||||
"lastname": "Dapsens-Turquat",
|
||||
"phone": "0662012604",
|
||||
"society": "Galerie des Monnaies",
|
||||
"pseudoAC": "",
|
||||
"salt": "fc59f59219a8bdfdf87aa951b3154db5",
|
||||
"hash": "dc8a69c86415d84ec4dd16d95f04971f0deefd55ee83c32b362878cc959f25acc99d528b84f1a889c54b775f6be332010a8fa2f4ee95add677453c0654be7704e6ee0912bbf830655d95080260063f52672854f08b826ccfe6fe643458d9d7f4c79a849ce2045b1b6923ef725c0e42ca326631ae2ed8af43829331909da2a79e610d7397333954cd58c2bb76b0b1dae011af4c038442858134c0c7719c209dcb316ff1a477f52287a1821aea6c99163f4a118f36e2dd3a69f35fae8669dbf44ddc9229567b0e65de070138bc1a2191c2b4eae09ed0c89374549ef1999b8b336743921c517359f43e318a123579a30e13adc1b2669ee0c136c3590b60da555e6baf5d85913590d348c50013bd54f5d0a8d01f3b1dec975982089ed27645ac79b10dc2559efbe893226453b7e2f9d8f91e5d2bcd9f6d98bc67df021fb51454ea8c4dc240310f3655f150d30f0b421aee1723ca9d42d1e949577410111ed81fb36f5256f191e16090e0703754d01b5f22be7a78e1ef8982d04bf8c539714e1c8d084ae65cd0f11738a28da21ffed2f44000c4df19b84dd7a342fd2658ba3578085187a459800caa0761d65b2dcf24dcf4bdf13afefcbb1d2eb8c56047017d51eb2dcc9c7f02d1dbed594386ae188cf7658b3e4bb5dd65299d5ab6370285e0a21f1be43edd29f3803e8e7b8dbac04626a90fa94ac23831885ae7fa09db1b3028757e",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T10:02:08.050Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T10:02:38.743Z"
|
||||
},
|
||||
"__v": 0,
|
||||
"image": "/assets/images/user_avatar.jpg"
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "64d397d9f42bebe5d162619e"
|
||||
},
|
||||
"favorites": [],
|
||||
"following": [],
|
||||
"username": "antoine_vincent",
|
||||
"email": "avincent@aucoffre.com",
|
||||
"firstname": "Antoine",
|
||||
"lastname": "Vincent",
|
||||
"phone": "0788834747",
|
||||
"society": "AuCoffre",
|
||||
"pseudoAC": "",
|
||||
"salt": "fb310aa95696dcec3351d03c9a68855b",
|
||||
"hash": "2f00be603829a8c30f89b9d6a9bc0a0123aa921c31d10246e3000ca93ae329a0b12e54eec5091a5c30fec9e3c9bcfc9cdad2991786ed2f87a19618e1ac2521754d0329235709b86de82f655903f75d680fb0bc5836017f0a1d54c28212f4eb81f9255d3bc24815ba67a922498b1ef0e0c4ae125f5346d6abf0136663ff8df1c9eac8974fe9a9874bf699877652375c12fa2618ce96fed6a1eedf205a2311ada78d829d48fa7f688a6e820fe1dfa2762a430bf7132ef6826005fa186ce849fc6dbda112983d8504e53d5cf5a3bcd160e62905a72ece2c7e73d0b0c3163ecc09c217084d9009b0f301b9235961b7286a06ee235ab980d1eee37128bfd5b9b65d46377ca315eaeb283192d80e0d83fda5eac79cd9c39b9cdaf0cb11b553204a8cd27fb678550fb3d6676f94f5e8e1727d0d94914993d75c8142d6d8792da497ebb76a93d006850fd17bad826cfae6367dd315740293353b22f23553a43e4689678bcfe553500e6a49605a6f7668c6d03c05705a3365986119a57cfdcb9c4b6a58e73aa9cbbed1719673dc08b3a5da06e28cad2775fce88a5d844c9deb29bb23b50e36ae9fb813d95b6919eea975f38d47401cca8ff2e7320b5ead2b2a3481167129b97c4432fc9f4f0d658c5cc6be300a90fdcf4864291bafd09d209fbeac74ef80606f3c9a1b4249413e277067b31d9100cf7974c6c017f5597c0b442dc4e2dfed",
|
||||
"role": "User",
|
||||
"createdAt": {
|
||||
"$date": "2023-08-09T13:42:49.353Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"$date": "2023-08-09T13:42:49.353Z"
|
||||
},
|
||||
"__v": 0
|
||||
}]
|
||||
@@ -1,112 +0,0 @@
|
||||
[{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f4c"
|
||||
},
|
||||
"num": 1,
|
||||
"name": "Aéronefs",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f4d"
|
||||
},
|
||||
"num": 2,
|
||||
"name": "Altimètre",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f4e"
|
||||
},
|
||||
"num": 3,
|
||||
"name": "Déclencheurs et matériels",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f4f"
|
||||
},
|
||||
"num": 4,
|
||||
"name": "Largage",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f50"
|
||||
},
|
||||
"num": 5,
|
||||
"name": "Règlementation",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f51"
|
||||
},
|
||||
"num": 6,
|
||||
"name": "Pilotage et mécanique de vol",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f52"
|
||||
},
|
||||
"num": 7,
|
||||
"name": "Physiologie",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f53"
|
||||
},
|
||||
"num": 8,
|
||||
"name": "Connaissances générales",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f54"
|
||||
},
|
||||
"num": 9,
|
||||
"name": "Unités de mesure",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f55"
|
||||
},
|
||||
"num": 10,
|
||||
"name": "Altimétrie",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f56"
|
||||
},
|
||||
"num": 11,
|
||||
"name": "Météorologie et aérologie",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f57"
|
||||
},
|
||||
"num": 12,
|
||||
"name": "Sauts spéciaux",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f58"
|
||||
},
|
||||
"num": 13,
|
||||
"name": "Questions spécifiques au brevet C",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "652075758a2b41cd02ad2f59"
|
||||
},
|
||||
"num": 14,
|
||||
"name": "Questions spécifiques au brevet D",
|
||||
"questions": []
|
||||
}]
|
||||
@@ -1,143 +0,0 @@
|
||||
[
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "651f4b5beb17b73b9333b777"
|
||||
},
|
||||
"name": "bpa",
|
||||
"categories": [
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4c"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4d"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4e"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4f"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f50"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f51"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f52"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f53"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f54"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f55"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f56"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f57"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "651f4b91eb17b73b9333b778"
|
||||
},
|
||||
"name": "c",
|
||||
"categories": [
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4c"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4d"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4e"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4f"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f50"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f51"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f52"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f53"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f54"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f55"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f56"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f57"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f58"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": {
|
||||
"$oid": "651f4bb2eb17b73b9333b779"
|
||||
},
|
||||
"name": "d",
|
||||
"categories": [
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4c"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4d"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4e"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f4f"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f50"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f51"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f52"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f53"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f54"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f55"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f56"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f57"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f58"
|
||||
},
|
||||
{
|
||||
"$oid": "652075758a2b41cd02ad2f59"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { QcmComponent } from './qcm.component';
|
||||
|
||||
describe('QcmComponent', () => {
|
||||
@@ -8,7 +11,12 @@ describe('QcmComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [QcmComponent]
|
||||
imports: [QcmComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
MenuItems,
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(QcmComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { /*FormControl,*/ FormGroup, FormBuilder, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -18,22 +18,33 @@ import { MenuItems } from '@components/shared';
|
||||
import { ListErrorsComponent } from '@components/shared';
|
||||
import { Errors, Qcm, QcmCategory, QcmQuestion, QcmQuestionState, User } from '@models';
|
||||
import { QcmService, UserService } from '@services';
|
||||
import data from 'src/qcm-bpa.json';
|
||||
|
||||
@Component({
|
||||
selector: 'app-qcm',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatIconModule, MatMenuModule, MatProgressBarModule, MatRadioModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
MatProgressBarModule,
|
||||
MatRadioModule,
|
||||
MatSelectModule,
|
||||
ListErrorsComponent
|
||||
ListErrorsComponent,
|
||||
],
|
||||
templateUrl: './qcm.component.html',
|
||||
styleUrl: './qcm.component.scss'
|
||||
styleUrl: './qcm.component.scss',
|
||||
})
|
||||
export class QcmComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private fb = inject(FormBuilder);
|
||||
private _qcmService = inject(QcmService);
|
||||
private _userService = inject(UserService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
private _timer: Subscription = new Subscription();
|
||||
@@ -53,17 +64,11 @@ export class QcmComponent implements OnInit, OnDestroy {
|
||||
public totalAnswer = 0;
|
||||
public timeProgress = 0;
|
||||
public timeProgressColor = 'primary';
|
||||
public timeMax = (60*45);
|
||||
public timeMax = 60 * 45;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private fb: FormBuilder,
|
||||
private _qcmService: QcmService,
|
||||
private _userService: UserService,
|
||||
public menuItems: MenuItems
|
||||
) {
|
||||
constructor() {
|
||||
this.qcmForm = this.fb.group({
|
||||
questions: this.fb.array([])
|
||||
questions: this.fb.array([]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,9 +92,9 @@ export class QcmComponent implements OnInit, OnDestroy {
|
||||
this.subtitle = `Préparation au questionnaire du brevet ${this.qcm.name.toUpperCase()}`;
|
||||
}
|
||||
//console.log(this.title, this.qcm);
|
||||
this.qcm.categories.forEach(category => {
|
||||
this.qcm.categories.forEach((category) => {
|
||||
this.questionsCount += category.questions.length;
|
||||
category.questions.forEach(question => {
|
||||
category.questions.forEach((question) => {
|
||||
this.addQuestion(question);
|
||||
});
|
||||
});
|
||||
@@ -113,11 +118,14 @@ export class QcmComponent implements OnInit, OnDestroy {
|
||||
.pipe(take(this.timeMax))
|
||||
.subscribe(() => {
|
||||
this.timeProgress++;
|
||||
if (this.timeProgress > (this.timeMax/100*70) && this.timeProgress <= (this.timeMax/100*90)) {
|
||||
if (this.timeProgress > (this.timeMax / 100) * 70 && this.timeProgress <= (this.timeMax / 100) * 90) {
|
||||
this.timeProgressColor = 'warning';
|
||||
} else if (this.timeProgress > (this.timeMax/100*90) && this.timeProgress <= (this.timeMax/100*98)) {
|
||||
} else if (
|
||||
this.timeProgress > (this.timeMax / 100) * 90 &&
|
||||
this.timeProgress <= (this.timeMax / 100) * 98
|
||||
) {
|
||||
this.timeProgressColor = 'danger';
|
||||
} else if (this.timeProgress > (this.timeMax/100*98)) {
|
||||
} else if (this.timeProgress > (this.timeMax / 100) * 98) {
|
||||
this.timeProgressColor = 'purple';
|
||||
}
|
||||
if (this.timeProgress >= this.timeMax) {
|
||||
@@ -130,7 +138,7 @@ export class QcmComponent implements OnInit, OnDestroy {
|
||||
addQuestion(question: QcmQuestion) {
|
||||
const item = this.fb.group({
|
||||
id: [question.num],
|
||||
choice: ['']
|
||||
choice: [''],
|
||||
});
|
||||
this.answers.push(QcmQuestionState.UNANSWERED);
|
||||
this.questions.push(item);
|
||||
@@ -145,19 +153,19 @@ export class QcmComponent implements OnInit, OnDestroy {
|
||||
if (this.timeProgress === 0) {
|
||||
this._starTimer();
|
||||
}
|
||||
this.qcm.categories[(category.num-1)].questions[(question.num-1)].choices.forEach(choice => {
|
||||
this.qcm.categories[category.num - 1].questions[question.num - 1].choices.forEach((choice) => {
|
||||
if (choice.index === value) {
|
||||
if (choice.correct) {
|
||||
this.answers[(question.num-1)] = QcmQuestionState.VALID;
|
||||
this.answers[question.num - 1] = QcmQuestionState.VALID;
|
||||
console.log(`Réponse à la question ${question.num} de la catégorie ${category.num} : correcte`);
|
||||
} else {
|
||||
this.answers[(question.num-1)] = QcmQuestionState.ERROR;
|
||||
this.answers[question.num - 1] = QcmQuestionState.ERROR;
|
||||
console.log(`Réponse à la question ${question.num} de la catégorie ${category.num} : incorrecte`);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.totalAnswer++;
|
||||
this.questions.controls[(question.num-1)].disable();
|
||||
this.questions.controls[question.num - 1].disable();
|
||||
//console.log(this.questions.controls[(question.num-1)].disabled);
|
||||
}
|
||||
|
||||
@@ -165,53 +173,6 @@ export class QcmComponent implements OnInit, OnDestroy {
|
||||
console.log('onSubmit');
|
||||
}
|
||||
|
||||
private _importChoices() {
|
||||
try {
|
||||
data.categories.forEach((category) => {
|
||||
category.questions.forEach(question => {
|
||||
this._qcmService.saveChoices(question)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: (question) => {
|
||||
console.log('question : ', question);
|
||||
//console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log('err : ', err);
|
||||
//console.log(`Le saut n'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _importQuestions() {
|
||||
try {
|
||||
data.categories.forEach((category) => {
|
||||
//Object.assign(this.jump, category);
|
||||
this._qcmService.saveQuestions(category)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: (category) => {
|
||||
console.log('category : ', category);
|
||||
//console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log('err : ', err);
|
||||
//console.log(`Le saut n'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
[
|
||||
{
|
||||
"num": 1,
|
||||
"name": "Aéronefs",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 2,
|
||||
"name": "Altimètre",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 3,
|
||||
"name": "Déclencheurs et matériels",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 4,
|
||||
"name": "Largage",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 5,
|
||||
"name": "Règlementation",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 6,
|
||||
"name": "Pilotage et mécanique de vol",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 7,
|
||||
"name": "Physiologie",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 8,
|
||||
"name": "Connaissances générales",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 9,
|
||||
"name": "Unités de mesure",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 10,
|
||||
"name": "Altimétrie",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 11,
|
||||
"name": "Météorologie et aérologie",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 12,
|
||||
"name": "Sauts spéciaux",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 13,
|
||||
"name": "Questions spécifiques au brevet C",
|
||||
"questions": []
|
||||
},
|
||||
{
|
||||
"num": 14,
|
||||
"name": "Questions spécifiques au brevet D",
|
||||
"questions": []
|
||||
}
|
||||
]
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { CredentialsComponent } from './credentials.component';
|
||||
|
||||
@@ -8,7 +11,12 @@ describe('CredentialsComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CredentialsComponent]
|
||||
imports: [CredentialsComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { AbstractControl, FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators, ValidatorFn, ValidationErrors } from '@angular/forms';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormGroup,
|
||||
Validators,
|
||||
ValidatorFn,
|
||||
ValidationErrors,
|
||||
} from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -17,16 +26,26 @@ import { ListErrorsComponent } from '@components/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'app-credentials',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
ListErrorsComponent
|
||||
RouterLink,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatDividerModule,
|
||||
ListErrorsComponent,
|
||||
],
|
||||
templateUrl: './credentials.component.html',
|
||||
styleUrl: './credentials.component.scss'
|
||||
styleUrl: './credentials.component.scss',
|
||||
})
|
||||
export class CredentialsComponent implements OnInit, OnDestroy {
|
||||
private router = inject(Router);
|
||||
private titleService = inject(Title);
|
||||
private userService = inject(UserService);
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
|
||||
private _user: Subscription = new Subscription();
|
||||
title = 'Modifier le mot de passe';
|
||||
bannerTitle = '';
|
||||
@@ -38,16 +57,14 @@ export class CredentialsComponent implements OnInit, OnDestroy {
|
||||
isSubmitting = false;
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService,
|
||||
private fb: UntypedFormBuilder
|
||||
) {
|
||||
this.credentialsForm = this.fb.group({
|
||||
constructor() {
|
||||
this.credentialsForm = this.fb.group(
|
||||
{
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ['', Validators.required]
|
||||
}, { validators: this.checkPasswords });
|
||||
confirmPassword: ['', Validators.required],
|
||||
},
|
||||
{ validators: this.checkPasswords },
|
||||
);
|
||||
}
|
||||
|
||||
get f() {
|
||||
@@ -72,13 +89,15 @@ export class CredentialsComponent implements OnInit, OnDestroy {
|
||||
submitForm() {
|
||||
this.isSubmitting = true;
|
||||
this.updateUser(this.credentialsForm.value);
|
||||
const user$: Observable<{ user: User }> = this.userService.update(this.user).pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const user$: Observable<{ user: User }> = this.userService
|
||||
.update(this.user)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._user = user$.subscribe({
|
||||
next: ({ user }) => void this.router.navigateByUrl('/profile/' + user.username),
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,8 +107,7 @@ export class CredentialsComponent implements OnInit, OnDestroy {
|
||||
|
||||
checkPasswords: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
|
||||
const pass = group.get('password')!.value;
|
||||
const confirmPass = group.get('confirmPassword')!.value
|
||||
return pass === confirmPass ? null : { notSame: true }
|
||||
}
|
||||
|
||||
const confirmPass = group.get('confirmPassword')!.value;
|
||||
return pass === confirmPass ? null : { notSame: true };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { SettingsComponent } from './settings.component';
|
||||
|
||||
@@ -6,9 +9,14 @@ describe('SettingsComponent', () => {
|
||||
let component: SettingsComponent;
|
||||
let fixture: ComponentFixture<SettingsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SettingsComponent]
|
||||
imports: [SettingsComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
@@ -18,16 +18,27 @@ import { ListErrorsComponent } from '@components/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-page',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatIconModule, MatDividerModule, MatFormFieldModule, MatInputModule, MatSelectModule,
|
||||
ListErrorsComponent
|
||||
RouterLink,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
ListErrorsComponent,
|
||||
],
|
||||
styleUrl: './settings.component.scss',
|
||||
templateUrl: './settings.component.html'
|
||||
templateUrl: './settings.component.html',
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
private router = inject(Router);
|
||||
private titleService = inject(Title);
|
||||
private userService = inject(UserService);
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
|
||||
private _user: Subscription = new Subscription();
|
||||
title = 'Vos paramètres';
|
||||
bannerTitle = '';
|
||||
@@ -42,22 +53,25 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
imgPath = '/assets/images/avatars/';
|
||||
imgExt = '.jpg';
|
||||
avatars = [
|
||||
`${this.imgPath}animal_001${this.imgExt}`, `${this.imgPath}animal_002${this.imgExt}`,
|
||||
`${this.imgPath}animal_003${this.imgExt}`, `${this.imgPath}animal_004${this.imgExt}`,
|
||||
`${this.imgPath}animal_005${this.imgExt}`, `${this.imgPath}animal_006${this.imgExt}`,
|
||||
`${this.imgPath}animal_007${this.imgExt}`, `${this.imgPath}animal_008${this.imgExt}`,
|
||||
`${this.imgPath}animal_009${this.imgExt}`, `${this.imgPath}animal_010${this.imgExt}`,
|
||||
`${this.imgPath}animal_011${this.imgExt}`, `${this.imgPath}animal_012${this.imgExt}`,
|
||||
`${this.imgPath}animal_013${this.imgExt}`, `${this.imgPath}animal_014${this.imgExt}`,
|
||||
`${this.imgPath}animal_015${this.imgExt}`, `${this.imgPath}animal_016${this.imgExt}`
|
||||
`${this.imgPath}animal_001${this.imgExt}`,
|
||||
`${this.imgPath}animal_002${this.imgExt}`,
|
||||
`${this.imgPath}animal_003${this.imgExt}`,
|
||||
`${this.imgPath}animal_004${this.imgExt}`,
|
||||
`${this.imgPath}animal_005${this.imgExt}`,
|
||||
`${this.imgPath}animal_006${this.imgExt}`,
|
||||
`${this.imgPath}animal_007${this.imgExt}`,
|
||||
`${this.imgPath}animal_008${this.imgExt}`,
|
||||
`${this.imgPath}animal_009${this.imgExt}`,
|
||||
`${this.imgPath}animal_010${this.imgExt}`,
|
||||
`${this.imgPath}animal_011${this.imgExt}`,
|
||||
`${this.imgPath}animal_012${this.imgExt}`,
|
||||
`${this.imgPath}animal_013${this.imgExt}`,
|
||||
`${this.imgPath}animal_014${this.imgExt}`,
|
||||
`${this.imgPath}animal_015${this.imgExt}`,
|
||||
`${this.imgPath}animal_016${this.imgExt}`,
|
||||
];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService,
|
||||
private fb: UntypedFormBuilder
|
||||
) {
|
||||
constructor() {
|
||||
// create form group using the form builder
|
||||
this.settingsForm = this.fb.group({
|
||||
username: ['', Validators.required],
|
||||
@@ -65,7 +79,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
firstname: ['', Validators.required],
|
||||
lastname: ['', Validators.required],
|
||||
phone: ['', Validators.required],
|
||||
image: ''
|
||||
image: '',
|
||||
});
|
||||
// Optional: subscribe to changes on the form :
|
||||
// this.settingsForm.valueChanges.pipe(this._scavenger.collect()).subscribe(values => this.updateUser(values));
|
||||
@@ -110,18 +124,19 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.updateUser(this.settingsForm.value);
|
||||
|
||||
const user$: Observable<{ user: User }> = this.userService.update(this.user).pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const user$: Observable<{ user: User }> = this.userService
|
||||
.update(this.user)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._user = user$.subscribe({
|
||||
next: ({ user }) => void this.router.navigateByUrl('/profile/' + user.username),
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateUser(values: NonNullable<unknown>) {
|
||||
Object.assign(this.user, values);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Directive, AfterContentChecked } from '@angular/core';
|
||||
import { Directive, AfterContentChecked, inject } from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
|
||||
import { AccordionLinkDirective } from './accordionlink.directive';
|
||||
@@ -6,9 +6,11 @@ import { filter } from 'rxjs/operators';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordion]',
|
||||
standalone: true
|
||||
standalone: true,
|
||||
})
|
||||
export class AccordionDirective implements AfterContentChecked {
|
||||
private router = inject(Router);
|
||||
|
||||
protected navlinks: Array<AccordionLinkDirective> = [];
|
||||
|
||||
closeOtherLinks(selectedLink: AccordionLinkDirective): void {
|
||||
@@ -45,11 +47,11 @@ export class AccordionDirective implements AfterContentChecked {
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.pipe(filter((event) => event instanceof NavigationEnd))
|
||||
.subscribe(() => this.checkOpenLinks());
|
||||
}
|
||||
|
||||
constructor(private router: Router) {
|
||||
constructor() {
|
||||
setTimeout(() => this.checkOpenLinks());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { Directive, HostListener, Inject } from '@angular/core';
|
||||
import { Directive, HostListener, inject } from '@angular/core';
|
||||
|
||||
import { AccordionLinkDirective } from './accordionlink.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordionToggle]',
|
||||
standalone: true
|
||||
standalone: true,
|
||||
})
|
||||
export class AccordionAnchorDirective {
|
||||
protected navlink: AccordionLinkDirective;
|
||||
|
||||
constructor(@Inject(AccordionLinkDirective) navlink: AccordionLinkDirective) {
|
||||
constructor() {
|
||||
const navlink = inject<AccordionLinkDirective>(AccordionLinkDirective);
|
||||
|
||||
this.navlink = navlink;
|
||||
}
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
this.navlink.toggle();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import {
|
||||
Directive,
|
||||
HostBinding,
|
||||
Inject,
|
||||
Input,
|
||||
OnInit,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { Directive, HostBinding, Input, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
|
||||
import { AccordionDirective } from './accordion.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordionLink]',
|
||||
standalone: true
|
||||
standalone: true,
|
||||
})
|
||||
export class AccordionLinkDirective implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
@@ -33,7 +26,9 @@ export class AccordionLinkDirective implements OnInit, OnDestroy {
|
||||
protected _selected: boolean = false;
|
||||
protected nav: AccordionDirective;
|
||||
|
||||
constructor(@Inject(AccordionDirective) nav: AccordionDirective) {
|
||||
constructor() {
|
||||
const nav = inject<AccordionDirective>(AccordionDirective);
|
||||
|
||||
this.nav = nav;
|
||||
}
|
||||
|
||||
@@ -41,7 +36,7 @@ export class AccordionLinkDirective implements OnInit, OnDestroy {
|
||||
this.nav.addLink(this);
|
||||
}
|
||||
|
||||
ngOnDestroy(){
|
||||
ngOnDestroy() {
|
||||
this.nav.removeGroup(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { NgForOf, NgIf } from "@angular/common";
|
||||
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
|
||||
import { CardColors } from 'src/app/core';
|
||||
@@ -7,8 +7,7 @@ import { CardColors } from 'src/app/core';
|
||||
@Component({
|
||||
selector: 'app-card-container',
|
||||
templateUrl: './card-container.component.html',
|
||||
standalone: true,
|
||||
imports: [NgIf, NgForOf, MatCardModule]
|
||||
imports: [MatCardModule],
|
||||
})
|
||||
export class CardContainerComponent {
|
||||
errorList: string[] = [];
|
||||
|
||||
+12
-2
@@ -1,5 +1,10 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { AeronefsBarComponent } from './aeronefs-bar.component';
|
||||
|
||||
describe('AeronefsBarComponent', () => {
|
||||
@@ -8,8 +13,13 @@ describe('AeronefsBarComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AeronefsBarComponent]
|
||||
})
|
||||
imports: [AeronefsBarComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+25
-18
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -16,15 +16,23 @@ import { AeronefByImat, AeronefByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aeronefs-bar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
BarHorizontalChartComponent, HistoryTableComponent
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
BarHorizontalChartComponent,
|
||||
HistoryTableComponent,
|
||||
],
|
||||
templateUrl: './aeronefs-bar.component.html'
|
||||
templateUrl: './aeronefs-bar.component.html',
|
||||
})
|
||||
export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
private _aeronefsService = inject(AeronefsService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _aeronefByImat: Subscription = new Subscription();
|
||||
private _aeronefByYear: Subscription = new Subscription();
|
||||
private _aeronefsByImat!: Array<AeronefByImat>;
|
||||
@@ -36,22 +44,16 @@ export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public displayCharts = false;
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._loadAeronefByImat();
|
||||
}
|
||||
@@ -62,7 +64,9 @@ export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService.getAllByImat().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService
|
||||
.getAllByImat()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByImat = aeronefs$.subscribe((aggregate: Array<AeronefByImat>) => {
|
||||
this._aeronefsByImat = aggregate.map((row: AeronefByImat) => {
|
||||
//this.chartConfig.barChartData.labels!.push(`${row.aeronef} ${row.imat}`);
|
||||
@@ -80,13 +84,16 @@ export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService.getAllByImatByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService
|
||||
.getAllByImatByYear()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByYear = dropzones$.subscribe((aggregate: Array<AeronefByYear>) => {
|
||||
this._aeronefsByYear = aggregate.map((row: AeronefByYear) => {
|
||||
const aeronef: string = row.aeronef;
|
||||
const imat: string = row.imat;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] =
|
||||
row.count;
|
||||
return row;
|
||||
});
|
||||
this.displayCharts = true;
|
||||
|
||||
+12
-2
@@ -1,5 +1,10 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { AeronefsPieComponent } from './aeronefs-pie.component';
|
||||
|
||||
describe('AeronefsPieComponent', () => {
|
||||
@@ -8,8 +13,13 @@ describe('AeronefsPieComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AeronefsPieComponent]
|
||||
})
|
||||
imports: [AeronefsPieComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+26
-18
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
@@ -17,15 +17,24 @@ import { AeronefByImat, AeronefByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aeronefs-pie',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
HistoryTableComponent, PieChartComponent
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
HistoryTableComponent,
|
||||
PieChartComponent,
|
||||
],
|
||||
templateUrl: './aeronefs-pie.component.html'
|
||||
templateUrl: './aeronefs-pie.component.html',
|
||||
})
|
||||
export class AeronefsPieComponent implements OnInit, OnDestroy {
|
||||
private _aeronefsService = inject(AeronefsService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _aeronefByImat: Subscription = new Subscription();
|
||||
private _aeronefByYear: Subscription = new Subscription();
|
||||
private _aeronefsByImat!: Array<AeronefByImat>;
|
||||
@@ -37,21 +46,15 @@ export class AeronefsPieComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._loadAeronefByImat();
|
||||
}
|
||||
@@ -62,7 +65,9 @@ export class AeronefsPieComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService.getAllByImat().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService
|
||||
.getAllByImat()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByImat = aeronefs$.subscribe((aggregate: Array<AeronefByImat>) => {
|
||||
this._aeronefsByImat = aggregate.map((row: AeronefByImat) => {
|
||||
this.seriesName.push(`${row.aeronef} ${row.imat}`);
|
||||
@@ -79,13 +84,16 @@ export class AeronefsPieComponent implements OnInit, OnDestroy {
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService.getAllByImatByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService
|
||||
.getAllByImatByYear()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByYear = dropzones$.subscribe((aggregate: Array<AeronefByYear>) => {
|
||||
this._aeronefsByYear = aggregate.map((row: AeronefByYear) => {
|
||||
const aeronef: string = row.aeronef;
|
||||
const imat: string = row.imat;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] =
|
||||
row.count;
|
||||
return row;
|
||||
});
|
||||
});
|
||||
|
||||
+14
-4
@@ -1,15 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { CanopiesModelsComponent } from './canopies-models.component';
|
||||
|
||||
describe('CanopiesModelsComponent', () => {
|
||||
let component: CanopiesModelsComponent;
|
||||
let fixture: ComponentFixture<CanopiesModelsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CanopiesModelsComponent]
|
||||
})
|
||||
imports: [CanopiesModelsComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+26
-19
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy} from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -16,15 +16,23 @@ import { CanopyModelBySize, CanopyModelByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-canopies-models',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
PieChartComponent, HistoryTableComponent
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
PieChartComponent,
|
||||
HistoryTableComponent,
|
||||
],
|
||||
templateUrl: './canopies-models.component.html'
|
||||
templateUrl: './canopies-models.component.html',
|
||||
})
|
||||
export class CanopiesModelsComponent implements OnInit, OnDestroy {
|
||||
private _canopiesService = inject(CanopiesService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _canopyBySize: Subscription = new Subscription();
|
||||
private _canopyByYear: Subscription = new Subscription();
|
||||
private _canopiesBySize!: Array<CanopyModelBySize>;
|
||||
@@ -36,21 +44,15 @@ export class CanopiesModelsComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _canopiesService: CanopiesService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._loadCanopyModelBySize();
|
||||
}
|
||||
@@ -61,7 +63,9 @@ export class CanopiesModelsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadCanopyModelBySize(): void {
|
||||
const canopies$: Observable<Array<CanopyModelBySize>> = this._canopiesService.getAllBySizeByModel().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const canopies$: Observable<Array<CanopyModelBySize>> = this._canopiesService
|
||||
.getAllBySizeByModel()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._canopyBySize = canopies$.subscribe((aggregate: Array<CanopyModelBySize>) => {
|
||||
this._canopiesBySize = aggregate.map((row: CanopyModelBySize) => {
|
||||
this.seriesName.push(`${row.taille.toString()} - ${row.voile}`);
|
||||
@@ -78,13 +82,16 @@ export class CanopiesModelsComponent implements OnInit, OnDestroy {
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<CanopyModelByYear>> = this._canopiesService.getAllBySizeByModelByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<CanopyModelByYear>> = this._canopiesService
|
||||
.getAllBySizeByModelByYear()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._canopyByYear = dropzones$.subscribe((aggregate: Array<CanopyModelByYear>) => {
|
||||
this._canopiesByYear = aggregate.map((row: CanopyModelByYear) => {
|
||||
const voile: string = row.voile;
|
||||
const taille: string = row.taille.toString();
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${taille} - ${voile}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
this.seriesRow[this.seriesName.indexOf(`${taille} - ${voile}`)][this.seriesHeader.indexOf(year)] =
|
||||
row.count;
|
||||
return row;
|
||||
});
|
||||
});
|
||||
|
||||
+14
-4
@@ -1,15 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { CanopiesSizesComponent } from './canopies-sizes.component';
|
||||
|
||||
describe('CanopiesSizesComponent', () => {
|
||||
let component: CanopiesSizesComponent;
|
||||
let fixture: ComponentFixture<CanopiesSizesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CanopiesSizesComponent]
|
||||
})
|
||||
imports: [CanopiesSizesComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+23
-17
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -17,15 +17,23 @@ import { CanopyBySize, CanopyByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-canopies-sizes',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
PieChartComponent, HistoryTableComponent
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
PieChartComponent,
|
||||
HistoryTableComponent,
|
||||
],
|
||||
templateUrl: './canopies-sizes.component.html'
|
||||
templateUrl: './canopies-sizes.component.html',
|
||||
})
|
||||
export class CanopiesSizesComponent implements OnInit, OnDestroy {
|
||||
private _canopiesService = inject(CanopiesService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _canopyBySize: Subscription = new Subscription();
|
||||
private _canopyByYear: Subscription = new Subscription();
|
||||
private _canopiesBySize!: Array<CanopyBySize>;
|
||||
@@ -37,21 +45,15 @@ export class CanopiesSizesComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _canopiesService: CanopiesService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._loadCanopyBySize();
|
||||
}
|
||||
@@ -62,7 +64,9 @@ export class CanopiesSizesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadCanopyBySize(): void {
|
||||
const canopies$: Observable<Array<CanopyBySize>> = this._canopiesService.getAllBySize().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const canopies$: Observable<Array<CanopyBySize>> = this._canopiesService
|
||||
.getAllBySize()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._canopyBySize = canopies$.subscribe((aggregate: Array<CanopyBySize>) => {
|
||||
this._canopiesBySize = aggregate.map((row: CanopyBySize) => {
|
||||
this.seriesName.push(row.taille.toString());
|
||||
@@ -79,7 +83,9 @@ export class CanopiesSizesComponent implements OnInit, OnDestroy {
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<CanopyByYear>> = this._canopiesService.getAllBySizeByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<CanopyByYear>> = this._canopiesService
|
||||
.getAllBySizeByYear()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._canopyByYear = dropzones$.subscribe((aggregate: Array<CanopyByYear>) => {
|
||||
this._canopiesByYear = aggregate.map((row: CanopyByYear) => {
|
||||
const taille: string = row.taille.toString();
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"Bar": {
|
||||
"labels": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
"series": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 1, 17, 22, 12, 18, 0, 0],
|
||||
[0, 35, 8, 0, 0, 10, 31, 67, 26, 24, 0, 0],
|
||||
[0, 0, 0, 0, 0, 9, 8, 0, 11, 24, 17, 4],
|
||||
[0, 0, 0, 29, 37, 11, 22, 20, 17, 13, 13, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
"Pie": {
|
||||
"labels": ["261", "45", "178", "16", "13", "11", "4", "2"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2],
|
||||
"names": ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"]
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -18,9 +18,12 @@
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card-content>
|
||||
@if (displayCharts) {
|
||||
<div class="barchart position-relative w-100 my-1">
|
||||
<x-chartist [configuration]="barChartDropZones"></x-chartist>
|
||||
</div>
|
||||
<app-bars-chart
|
||||
[names]="seriesName"
|
||||
[values]="seriesRow"
|
||||
[headers]="seriesHeader"
|
||||
[colors]="seriesColor"
|
||||
></app-bars-chart>
|
||||
}
|
||||
</mat-card-content>
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
@@ -29,7 +32,13 @@
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Historique annuel</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
|
||||
<app-history-table
|
||||
[headers]="seriesHeader"
|
||||
[names]="seriesName"
|
||||
[values]="seriesValue"
|
||||
[rows]="seriesRow"
|
||||
[colors]="seriesColorClass"
|
||||
></app-history-table>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</mat-card>
|
||||
|
||||
+14
-4
@@ -1,15 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { DropzonesBarComponent } from './dropzones-bar.component';
|
||||
|
||||
describe('DropzonesBarComponent', () => {
|
||||
let component: DropzonesBarComponent;
|
||||
let fixture: ComponentFixture<DropzonesBarComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DropzonesBarComponent]
|
||||
})
|
||||
imports: [DropzonesBarComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+26
-68
@@ -1,33 +1,38 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { BaseChartDirective } from 'ng2-charts';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
import { AxisOptions, Label } from 'chartist';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { BarsChartComponent } from '@components/shared/helpers-chart';
|
||||
import { HistoryTableComponent } from '@components/shared/helpers-jump';
|
||||
import { UtilitiesService, DropZonesService } from '@services';
|
||||
import { DropZoneByOaci, DropZoneByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dropzones-bar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
BaseChartDirective, ChartistModule,
|
||||
HistoryTableComponent
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
BarsChartComponent,
|
||||
HistoryTableComponent,
|
||||
],
|
||||
templateUrl: './dropzones-bar.component.html'
|
||||
templateUrl: './dropzones-bar.component.html',
|
||||
})
|
||||
export class DropzonesBarComponent implements OnInit, OnDestroy {
|
||||
private _dropzonesService = inject(DropZonesService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _dropzoneByOaci: Subscription = new Subscription();
|
||||
private _dropzoneByYear: Subscription = new Subscription();
|
||||
private _dropzonesByOaci!: Array<DropZoneByOaci>;
|
||||
@@ -39,23 +44,16 @@ export class DropzonesBarComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public displayCharts = false;
|
||||
public barChartDropZones: Configuration = this._utilitiesService.getBarConfig();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _dropzonesService: DropZonesService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._loadDropZoneByOaci();
|
||||
}
|
||||
@@ -66,7 +64,9 @@ export class DropzonesBarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadDropZoneByOaci(): void {
|
||||
const dropzones$: Observable<Array<DropZoneByOaci>> = this._dropzonesService.getAllByOaci().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<DropZoneByOaci>> = this._dropzonesService
|
||||
.getAllByOaci()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._dropzoneByOaci = dropzones$.subscribe((aggregate: Array<DropZoneByOaci>) => {
|
||||
this._dropzonesByOaci = aggregate.map((row: DropZoneByOaci) => {
|
||||
this.seriesName.push(`${row.oaci} - ${row.lieu}`);
|
||||
@@ -83,60 +83,18 @@ export class DropzonesBarComponent implements OnInit, OnDestroy {
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<DropZoneByYear>> = this._dropzonesService.getAllByOaciByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<DropZoneByYear>> = this._dropzonesService
|
||||
.getAllByOaciByYear()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._dropzoneByYear = dropzones$.subscribe((aggregate: Array<DropZoneByYear>) => {
|
||||
this._dropzonesByYear = aggregate.map((row: DropZoneByYear) => {
|
||||
const lieu: string = row.lieu;
|
||||
const oaci: string = row.oaci;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${oaci} - ${lieu}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
this.seriesRow[this.seriesName.indexOf(`${oaci} - ${lieu}`)][this.seriesHeader.indexOf(year)] =
|
||||
row.count;
|
||||
return row;
|
||||
});
|
||||
const axisX: AxisOptions = {
|
||||
labelInterpolationFnc: function(value: Label, index: number): string {
|
||||
return index % 1 === 0 ? `${value.toString()}` : '';
|
||||
}
|
||||
}
|
||||
this.barChartDropZones = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
'labels': this.seriesHeader,
|
||||
'series': this.seriesRow
|
||||
},
|
||||
options: {
|
||||
seriesBarDistance: 15,
|
||||
horizontalBars: false,
|
||||
high: 180,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: true,
|
||||
offset: 40
|
||||
},
|
||||
height: 300
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: axisX
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 7,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: axisX
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: axisX
|
||||
}]
|
||||
]
|
||||
};
|
||||
this.displayCharts = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"Bar": {
|
||||
"labels": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
"series": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 1, 17, 22, 12, 18, 0, 0],
|
||||
[0, 35, 8, 0, 0, 10, 31, 67, 26, 24, 0, 0],
|
||||
[0, 0, 0, 0, 0, 9, 8, 0, 11, 24, 17, 4],
|
||||
[0, 0, 0, 29, 37, 11, 22, 20, 17, 13, 13, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
"Pie": {
|
||||
"labels": ["261", "45", "178", "16", "13", "11", "4", "2"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2],
|
||||
"names": ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"]
|
||||
}
|
||||
}
|
||||
+14
-4
@@ -1,15 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController } from 'chart.js';
|
||||
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ArcElement, LineElement, PointElement, DoughnutController, PieController);
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { DropzonesPieComponent } from './dropzones-pie.component';
|
||||
|
||||
describe('DropzonesPieComponent', () => {
|
||||
let component: DropzonesPieComponent;
|
||||
let fixture: ComponentFixture<DropzonesPieComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DropzonesPieComponent]
|
||||
})
|
||||
imports: [DropzonesPieComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+25
-18
@@ -1,5 +1,5 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -17,15 +17,23 @@ import { DropZoneByOaci, DropZoneByYear } from '@models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dropzones-pie',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
PieChartComponent, HistoryTableComponent
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
PieChartComponent,
|
||||
HistoryTableComponent,
|
||||
],
|
||||
templateUrl: './dropzones-pie.component.html'
|
||||
templateUrl: './dropzones-pie.component.html',
|
||||
})
|
||||
export class DropzonesPieComponent implements OnInit, OnDestroy {
|
||||
private _dropzonesService = inject(DropZonesService);
|
||||
private _utilitiesService = inject(UtilitiesService);
|
||||
menuItems = inject(MenuItems);
|
||||
|
||||
private _dropzoneByOaci: Subscription = new Subscription();
|
||||
private _dropzoneByYear: Subscription = new Subscription();
|
||||
private _dropzonesByOaci!: Array<DropZoneByOaci>;
|
||||
@@ -37,21 +45,15 @@ export class DropzonesPieComponent implements OnInit, OnDestroy {
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
backgroundColor: string[];
|
||||
borderColor: string[];
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all'),
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _dropzonesService: DropZonesService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._loadDropZoneByOaci();
|
||||
}
|
||||
@@ -62,7 +64,9 @@ export class DropzonesPieComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadDropZoneByOaci(): void {
|
||||
const dropzones$: Observable<Array<DropZoneByOaci>> = this._dropzonesService.getAllByOaci().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<DropZoneByOaci>> = this._dropzonesService
|
||||
.getAllByOaci()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._dropzoneByOaci = dropzones$.subscribe((aggregate: Array<DropZoneByOaci>) => {
|
||||
this._dropzonesByOaci = aggregate.map((row: DropZoneByOaci) => {
|
||||
this.seriesName.push(`${row.oaci} - ${row.lieu}`);
|
||||
@@ -79,13 +83,16 @@ export class DropzonesPieComponent implements OnInit, OnDestroy {
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<DropZoneByYear>> = this._dropzonesService.getAllByOaciByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
const dropzones$: Observable<Array<DropZoneByYear>> = this._dropzonesService
|
||||
.getAllByOaciByYear()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._dropzoneByYear = dropzones$.subscribe((aggregate: Array<DropZoneByYear>) => {
|
||||
this._dropzonesByYear = aggregate.map((row: DropZoneByYear) => {
|
||||
const lieu: string = row.lieu;
|
||||
const oaci: string = row.oaci;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${oaci} - ${lieu}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
this.seriesRow[this.seriesName.indexOf(`${oaci} - ${lieu}`)][this.seriesHeader.indexOf(year)] =
|
||||
row.count;
|
||||
return row;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"Bar": {
|
||||
"labels": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
"series": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 1, 17, 22, 12, 18, 0, 0],
|
||||
[0, 35, 8, 0, 0, 10, 31, 67, 26, 24, 0, 0],
|
||||
[0, 0, 0, 0, 0, 9, 8, 0, 11, 24, 17, 4],
|
||||
[0, 0, 0, 29, 37, 11, 22, 20, 17, 13, 13, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
"Pie": {
|
||||
"labels": ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2]
|
||||
}
|
||||
}
|
||||
+54
-34
@@ -1,34 +1,30 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ title }}</mat-card-title>
|
||||
<mat-card-subtitle>{{ subtitle }} de {{min}} à {{max}}</mat-card-subtitle>
|
||||
<mat-card-subtitle>{{ subtitle }} de {{ min }} à {{ max }}</mat-card-subtitle>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuJumpBar" aria-label="Menu Dropzone Bar">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuJumpBar="matMenu">
|
||||
<button mat-menu-item *ngFor="let menuitem of menuItems.getMenuPanel()">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</mat-card-header>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card-content>
|
||||
<!--
|
||||
<div class="text-center mb-3">
|
||||
<ul class="list-inline my-0">
|
||||
<li *ngFor='let name of seriesName; let i=index' class="list-inline-item me-lg-3 me-sm-2">
|
||||
<span class="{{seriesColor[i]}} fs-6 m-0">
|
||||
● {{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
-->
|
||||
<div *ngIf="displayCharts" class="barchart position-relative w-100 my-1">
|
||||
<x-chartist [configuration]="barChartJumps"></x-chartist>
|
||||
</div>
|
||||
@if (displayCharts) {
|
||||
<app-bars-chart
|
||||
[names]="seriesName"
|
||||
[values]="seriesRow"
|
||||
[headers]="seriesHeader"
|
||||
[colors]="seriesColor"
|
||||
></app-bars-chart>
|
||||
}
|
||||
</mat-card-content>
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<mat-accordion>
|
||||
@@ -40,44 +36,68 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th>
|
||||
@for (name of seriesHeader; track name) {
|
||||
<th class="text-end">{{ name }}</th>
|
||||
}
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
|
||||
@for (values of seriesRow; track $index; let i = $index) {
|
||||
<tr>
|
||||
<th class="{{ seriesColorClass[i] }} text-end">● {{ seriesName[i] }}</th>
|
||||
@for (value of values; track $index) {
|
||||
<td class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value }}</span>
|
||||
}
|
||||
</td>
|
||||
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th>
|
||||
}
|
||||
<th class="{{ seriesColorClass[i] }} font-monospace text-end">{{ seriesRowTotal[i] }}</th>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th class="text-end">Total</th>
|
||||
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
|
||||
@for (value of seriesColTotal; track $index) {
|
||||
<th class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value }}</span>
|
||||
}
|
||||
</th>
|
||||
}
|
||||
<th class="font-monospace text-end">{{ grandTotal }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-end">Moyenne</th>
|
||||
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
|
||||
@for (value of seriesRowAvg; track $index) {
|
||||
<th class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
}
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
|
||||
}
|
||||
<th class="font-monospace text-end">{{ grandAvg | number: "1.0-1" }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-end">Moyenne <small>3 dernières années</small></th>
|
||||
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
|
||||
@for (value of seriesRowAvgLastYears; track $index) {
|
||||
<th class="font-monospace text-end">
|
||||
@if (value) {
|
||||
<span class="pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
} @else {
|
||||
<span class="text-empty pr-1">{{ value | number: "1.0-1" }}</span>
|
||||
}
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
|
||||
}
|
||||
<th class="font-monospace text-end">{{ grandAvgLastYears | number: "1.0-1" }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
+12
-4
@@ -1,15 +1,23 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { MenuItems } from '@components/shared';
|
||||
import { JumpsByMonthComponent } from './jumps-by-month.component';
|
||||
|
||||
describe('JumpsByMonthComponent', () => {
|
||||
let component: JumpsByMonthComponent;
|
||||
let fixture: ComponentFixture<JumpsByMonthComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [JumpsByMonthComponent]
|
||||
})
|
||||
imports: [JumpsByMonthComponent],
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
MenuItems,
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user