fix(ui): address code review feedback
- Fix CI: remove redundant push trigger, keep only pull_request on main - Fix CI: remove continue-on-error on lint and test steps - Fix accessibility: wrap h1 routerLink in <a> element in header - Fix reactivity: use paramMap observable instead of route.snapshot - Extract CO2 thresholds to co2-levels.config.ts - Fix environment.template.ts comment to English - Fix wsUrl in environment.ts to include /ws path - Fix production URLs: hes-so.ch -> ui.e.kb28.ch - Fix README: align Models convention with kebab-case .model.ts suffix - Remove redundant .gitkeep files from directories with README.md
This commit is contained in:
10
.github/workflows/angular-ci.yml
vendored
10
.github/workflows/angular-ci.yml
vendored
@@ -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
|
||||
|
||||
2
ui/.gitignore
vendored
2
ui/.gitignore
vendored
@@ -60,8 +60,6 @@ testem.log
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
#src/environments/environment.prod.ts
|
||||
# Keep environment.ts as template
|
||||
|
||||
# Build artifacts
|
||||
*.tgz
|
||||
|
||||
10
ui/README.md
10
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
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<h1 routerLink="/dashboard">🌡️ PI_E2EEDA</h1>
|
||||
<h1><a routerLink="/dashboard">🌡️ PI_E2EEDA</a></h1>
|
||||
<nav>
|
||||
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
|
||||
</nav>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'))));
|
||||
}
|
||||
|
||||
19
ui/src/app/config/co2-levels.config.ts
Normal file
19
ui/src/app/config/co2-levels.config.ts
Normal file
@@ -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' },
|
||||
];
|
||||
238
ui/src/app/config/rooms-layout.config.ts
Normal file
238
ui/src/app/config/rooms-layout.config.ts
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user