diff --git a/.github/workflows/angular-ci.yml b/.github/workflows/angular-ci.yml index 81436b6..f00dc0a 100644 --- a/.github/workflows/angular-ci.yml +++ b/.github/workflows/angular-ci.yml @@ -1,12 +1,6 @@ name: Angular CI on: - push: - branches: - - '**' # Run on all branches - paths: - - 'ui/**' - - '.github/workflows/angular-ci.yml' pull_request: branches: - main @@ -51,7 +45,6 @@ jobs: # Run linting - name: Run ESLint run: npm run lint - continue-on-error: true # Don't fail build on lint warnings # Check code formatting - name: Check Prettier formatting @@ -65,10 +58,9 @@ jobs: - name: Build (Production) run: npm run build -- --configuration=production - # Run unit tests (when available) + # Run unit tests - name: Run unit tests run: npm run test -- --watch=false --browsers=ChromeHeadless --code-coverage - continue-on-error: true # Don't fail yet (no tests implemented) # Upload coverage (when tests exist) - name: Upload coverage reports diff --git a/ui/.gitignore b/ui/.gitignore index 4090cbd..84c200e 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -60,8 +60,6 @@ testem.log .env .env.local .env.*.local -#src/environments/environment.prod.ts -# Keep environment.ts as template # Build artifacts *.tgz diff --git a/ui/README.md b/ui/README.md index 654d9d2..cd0657b 100644 --- a/ui/README.md +++ b/ui/README.md @@ -57,17 +57,17 @@ npm run e2e # E2E tests (Cypress) ### Development - API: `http://localhost:8080` -- WebSocket: `ws://localhost:8080` +- WebSocket: `ws://localhost:8080/ws` ### Production -- API: `https://e2eeda.hes-so.ch/api` -- WebSocket: `wss://e2eeda.hes-so.ch/ws` +- API: `https://ui.e.kb28.ch/api` +- WebSocket: `wss://ui.e.kb28.ch/ws` ## Conventions - Components: kebab-case (`room-map`) - Services: camelCase with suffix (`analytics.service.ts`) -- Models: PascalCase (`RoomData.ts`) +- Models: kebab-case with `.model.ts` suffix (`room-data.model.ts`) - Standalone components only - Signals for state management @@ -112,5 +112,5 @@ docker run -p 80:80 e2eeda/ui:latest **WebSocket connection failed:** ```typescript // Check URL in environment.ts -wsUrl: 'ws://localhost:8080/ws' // port can change +wsUrl: 'ws://localhost:8080/ws' // port can change ``` diff --git a/ui/src/app/components/header/header.component.html b/ui/src/app/components/header/header.component.html index 1bb66b6..cead6fc 100644 --- a/ui/src/app/components/header/header.component.html +++ b/ui/src/app/components/header/header.component.html @@ -1,6 +1,6 @@
-

🌡️ PI_E2EEDA

+

🌡️ PI_E2EEDA

diff --git a/ui/src/app/components/header/header.component.scss b/ui/src/app/components/header/header.component.scss index ed13613..0d36cdf 100644 --- a/ui/src/app/components/header/header.component.scss +++ b/ui/src/app/components/header/header.component.scss @@ -16,10 +16,14 @@ header { h1 { margin: 0; font-size: 24px; - cursor: pointer; - &:hover { - opacity: 0.9; + a { + color: inherit; + text-decoration: none; + + &:hover { + opacity: 0.9; + } } } diff --git a/ui/src/app/components/legend/legend.component.ts b/ui/src/app/components/legend/legend.component.ts index b78e407..eb149c5 100644 --- a/ui/src/app/components/legend/legend.component.ts +++ b/ui/src/app/components/legend/legend.component.ts @@ -1,11 +1,6 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component } from '@angular/core'; - -interface CO2Level { - label: string; - range: string; - color: string; -} +import { CO2_LEVELS, CO2Level } from '../../config/co2-levels.config'; @Component({ selector: 'app-legend', @@ -16,12 +11,5 @@ interface CO2Level { changeDetection: ChangeDetectionStrategy.OnPush, }) export class LegendComponent { - levels: CO2Level[] = [ - { label: 'Excellent', range: '< 800 ppm', color: '#4caf50' }, - { label: 'Good', range: '800-1000 ppm', color: '#8bc34a' }, - { label: 'Moderate', range: '1000-1200 ppm', color: '#ffc107' }, - { label: 'Poor', range: '1200-1500 ppm', color: '#ff9800' }, - { label: 'Very Poor', range: '1500-2000 ppm', color: '#ff5722' }, - { label: 'Critical', range: '> 2000 ppm', color: '#f44336' }, - ]; + levels: CO2Level[] = CO2_LEVELS; } diff --git a/ui/src/app/components/room-details-panel/room-details-panel.component.ts b/ui/src/app/components/room-details-panel/room-details-panel.component.ts index 3857bc0..34506c3 100644 --- a/ui/src/app/components/room-details-panel/room-details-panel.component.ts +++ b/ui/src/app/components/room-details-panel/room-details-panel.component.ts @@ -1,6 +1,9 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, RouterModule } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map } from 'rxjs/operators'; + @Component({ selector: 'app-room-details-panel', standalone: true, @@ -11,5 +14,5 @@ import { ActivatedRoute, RouterModule } from '@angular/router'; }) export class RoomDetailsPanelComponent { private route = inject(ActivatedRoute); - roomId: string | null = this.route.snapshot.paramMap.get('id'); + roomId = toSignal(this.route.paramMap.pipe(map((params) => params.get('id')))); } diff --git a/ui/src/app/config/co2-levels.config.ts b/ui/src/app/config/co2-levels.config.ts new file mode 100644 index 0000000..f315780 --- /dev/null +++ b/ui/src/app/config/co2-levels.config.ts @@ -0,0 +1,19 @@ +/** + * CO2 Level Thresholds Configuration + * Defines air quality levels with their PPM ranges and display colors + */ + +export interface CO2Level { + label: string; + range: string; + color: string; +} + +export const CO2_LEVELS: CO2Level[] = [ + { label: 'Excellent', range: '< 800 ppm', color: '#4caf50' }, + { label: 'Good', range: '800-1000 ppm', color: '#8bc34a' }, + { label: 'Moderate', range: '1000-1200 ppm', color: '#ffc107' }, + { label: 'Poor', range: '1200-1500 ppm', color: '#ff9800' }, + { label: 'Very Poor', range: '1500-2000 ppm', color: '#ff5722' }, + { label: 'Critical', range: '> 2000 ppm', color: '#f44336' }, +]; diff --git a/ui/src/app/config/rooms-layout.config.ts b/ui/src/app/config/rooms-layout.config.ts new file mode 100644 index 0000000..fee19c9 --- /dev/null +++ b/ui/src/app/config/rooms-layout.config.ts @@ -0,0 +1,238 @@ +/** + * Room Layout Configuration + * Defines all rooms in Espace A and Espace B with their SVG positions and metadata + */ + +export interface RoomLayout { + id: string; + name: string; + espace: 'A' | 'B'; + floor: number; + type: string; + x: number; + y: number; + width: number; + height: number; + capacity?: number; + hasSensor: boolean; +} + +export const ROOM_LAYOUTS: RoomLayout[] = [ + // ======================================== + // ESPACE B (Left side - 4 rooms) + // ======================================== + { + id: 'B1', + name: 'B1 - Meeting Room', + espace: 'B', + floor: 1, + type: 'meeting', + x: 50, + y: 80, + width: 220, + height: 140, + capacity: 12, + hasSensor: true, + }, + { + id: 'B2', + name: 'B2 - Lab', + espace: 'B', + floor: 1, + type: 'lab', + x: 50, + y: 240, + width: 220, + height: 140, + capacity: 20, + hasSensor: true, + }, + { + id: 'B3', + name: 'B3 - Office', + espace: 'B', + floor: 1, + type: 'office', + x: 50, + y: 400, + width: 220, + height: 140, + capacity: 8, + hasSensor: true, + }, + { + id: 'B4', + name: 'B4 - Storage', + espace: 'B', + floor: 1, + type: 'storage', + x: 50, + y: 560, + width: 220, + height: 140, + capacity: 0, + hasSensor: false, + }, + + // ======================================== + // ESPACE A (Right side - 9 rooms) + // ======================================== + { + id: 'A1', + name: 'A1 - Auditorium', + espace: 'A', + floor: 1, + type: 'auditorium', + x: 410, + y: 240, + width: 220, + height: 300, + capacity: 150, + hasSensor: true, + }, + { + id: 'A2', + name: 'A2 - Lecture Hall', + espace: 'A', + floor: 1, + type: 'classroom', + x: 670, + y: 80, + width: 220, + height: 140, + capacity: 60, + hasSensor: true, + }, + { + id: 'A3', + name: 'A3 - Workshop', + espace: 'A', + floor: 1, + type: 'workshop', + x: 410, + y: 80, + width: 220, + height: 140, + capacity: 25, + hasSensor: true, + }, + { + id: 'A4', + name: 'A4 - Open Space', + espace: 'A', + floor: 1, + type: 'openspace', + x: 930, + y: 80, + width: 220, + height: 140, + capacity: 30, + hasSensor: true, + }, + { + id: 'A5', + name: 'A5 - Conference', + espace: 'A', + floor: 1, + type: 'conference', + x: 670, + y: 400, + width: 220, + height: 140, + capacity: 20, + hasSensor: true, + }, + { + id: 'A6', + name: 'A6 - Study Room', + espace: 'A', + floor: 1, + type: 'study', + x: 930, + y: 240, + width: 220, + height: 140, + capacity: 15, + hasSensor: true, + }, + { + id: 'A7', + name: 'A7 - Lab', + espace: 'A', + floor: 1, + type: 'lab', + x: 670, + y: 560, + width: 220, + height: 140, + capacity: 18, + hasSensor: true, + }, + { + id: 'A8', + name: 'A8 - Classroom', + espace: 'A', + floor: 1, + type: 'classroom', + x: 930, + y: 560, + width: 220, + height: 140, + capacity: 40, + hasSensor: true, + }, + { + id: 'A9', + name: 'A9 - Office', + espace: 'A', + floor: 1, + type: 'office', + x: 930, + y: 400, + width: 220, + height: 140, + capacity: 10, + hasSensor: true, + }, +]; + +/** + * Get room layout by ID + */ +export function getRoomById(roomId: string): RoomLayout | undefined { + return ROOM_LAYOUTS.find((room) => room.id === roomId); +} + +/** + * Get rooms by espace (A or B) + */ +export function getRoomsByEspace(espace: 'A' | 'B'): RoomLayout[] { + return ROOM_LAYOUTS.filter((room) => room.espace === espace); +} + +/** + * Get all rooms with sensors + */ +export function getRoomsWithSensors(): RoomLayout[] { + return ROOM_LAYOUTS.filter((room) => room.hasSensor); +} + +/** + * Get total number of rooms + */ +export function getTotalRooms(): number { + return ROOM_LAYOUTS.length; +} + +/** + * Get room center point (for tooltip positioning) + */ +export function getRoomCenter(roomId: string): { x: number; y: number } | null { + const room = getRoomById(roomId); + if (!room) return null; + + return { + x: room.x + room.width / 2, + y: room.y + room.height / 2, + }; +} diff --git a/ui/src/app/directives/.gitkeep b/ui/src/app/directives/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/app/guards/.gitkeep b/ui/src/app/guards/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/app/interceptors/.gitkeep b/ui/src/app/interceptors/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/app/models/.gitkeep b/ui/src/app/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/app/pipes/.gitkeep b/ui/src/app/pipes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/app/services/.gitkeep b/ui/src/app/services/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/app/utils/.gitkeep b/ui/src/app/utils/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/environments/environment.prod.ts b/ui/src/environments/environment.prod.ts index 8a25bdf..2204905 100644 --- a/ui/src/environments/environment.prod.ts +++ b/ui/src/environments/environment.prod.ts @@ -1,7 +1,7 @@ export const environment = { production: true, - apiUrl: 'https://e2eeda.hes-so.ch/api', - wsUrl: 'wss://e2eeda.hes-so.ch/ws', + apiUrl: 'https://ui.e.kb28.ch/api', + wsUrl: 'wss://ui.e.kb28.ch/ws', influxUrl: '', logLevel: 'error', }; diff --git a/ui/src/environments/environment.template.ts b/ui/src/environments/environment.template.ts index 77f4edf..ec5248f 100644 --- a/ui/src/environments/environment.template.ts +++ b/ui/src/environments/environment.template.ts @@ -1,6 +1,6 @@ /** - * Template pour configuration environment - * Copier vers environment.prod.ts et remplir valeurs réelles + * Production environment configuration template + * Copy to environment.prod.ts and fill in actual values */ export const environment = { production: true, diff --git a/ui/src/environments/environment.ts b/ui/src/environments/environment.ts index f012fcd..a3b48f0 100644 --- a/ui/src/environments/environment.ts +++ b/ui/src/environments/environment.ts @@ -1,7 +1,7 @@ export const environment = { production: false, apiUrl: 'http://localhost:8080', - wsUrl: 'ws://localhost:8080', + wsUrl: 'ws://localhost:8080/ws', influxUrl: '', logLevel: 'debug', };