- | {{ entry.timestamp | date:'HH:mm' }} |
+ {{ entry.timestamp | date: 'HH:mm' }} |
{{ entry.co2 }} ppm
@@ -151,10 +198,8 @@
No history available.
}
-
} @else {
Room not found.
}
-
diff --git a/ui/src/app/components/room-details-panel/room-details-panel.component.scss b/ui/src/app/components/room-details-panel/room-details-panel.component.scss
index f97a505..64cf88d 100644
--- a/ui/src/app/components/room-details-panel/room-details-panel.component.scss
+++ b/ui/src/app/components/room-details-panel/room-details-panel.component.scss
@@ -1,19 +1,19 @@
// ── Tokens (aligned with room-map) ───────────────────────────────────────────
-$navy-deep: #0d1b2a;
-$navy: #1a3a6b;
-$navy-mid: #1f4e8c;
-$navy-light: #cfe0f2;
-$accent: #00b4d8;
-$surface: #f4f7fb;
-$border: #d1dce8;
-$text-main: #0d1b2a;
-$text-muted: #5a7694;
-$success: #22c55e;
+$navy-deep: #0d1b2a;
+$navy: #1a3a6b;
+$navy-mid: #1f4e8c;
+$navy-light: #cfe0f2;
+$accent: #00b4d8;
+$surface: #f4f7fb;
+$border: #d1dce8;
+$text-main: #0d1b2a;
+$text-muted: #5a7694;
+$success: #22c55e;
$success-border: #4ade80;
-$success-bg: #f0fdf4;
-$radius-lg: 10px;
-$radius-md: 7px;
-$radius-sm: 5px;
+$success-bg: #f0fdf4;
+$radius-lg: 10px;
+$radius-md: 7px;
+$radius-sm: 5px;
// Breakpoints (identical to room-map)
$bp-md: 768px;
@@ -72,13 +72,21 @@ $t-fast: 0.15s ease;
gap: 6px;
color: $text-muted;
text-decoration: none;
- font: 500 13px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 500 13px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
transition: color 0.15s;
white-space: nowrap;
- svg { width: 14px; height: 14px; }
+ svg {
+ width: 14px;
+ height: 14px;
+ }
- &:hover { color: $navy; }
+ &:hover {
+ color: $navy;
+ }
}
.header-sep {
@@ -87,7 +95,9 @@ $t-fast: 0.15s ease;
background: $border;
flex-shrink: 0;
- @media (max-width: #{$bp-sm}) { display: none; }
+ @media (max-width: #{$bp-sm}) {
+ display: none;
+ }
}
.room-identity {
@@ -97,7 +107,10 @@ $t-fast: 0.15s ease;
}
.room-eyebrow {
- font: 600 9px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 9px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
letter-spacing: 0.14em;
text-transform: uppercase;
color: $accent;
@@ -105,7 +118,10 @@ $t-fast: 0.15s ease;
.room-name {
margin: 0;
- font: 700 16px/1.2 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 700 16px/1.2 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $navy;
white-space: nowrap;
}
@@ -115,33 +131,53 @@ $t-fast: 0.15s ease;
align-items: center;
padding: 4px 12px;
border-radius: 20px;
- font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 12px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: rgba(0, 0, 0, 0.72);
white-space: nowrap;
letter-spacing: 0.01em;
// La pill est redondante sur mobile (visible dans panel-left)
- @media (max-width: #{$bp-sm}) { display: none; }
+ @media (max-width: #{$bp-sm}) {
+ display: none;
+ }
}
.header-spacer {
flex: 1;
- @media (max-width: #{$bp-md}) { display: none; }
+ @media (max-width: #{$bp-md}) {
+ display: none;
+ }
}
.last-updated {
display: flex;
align-items: center;
gap: 5px;
- font: 400 12px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 12px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
white-space: nowrap;
- svg { width: 12px; height: 12px; flex-shrink: 0; color: $accent; }
- strong { font-weight: 600; color: $text-main; }
+ svg {
+ width: 12px;
+ height: 12px;
+ flex-shrink: 0;
+ color: $accent;
+ }
+ strong {
+ font-weight: 600;
+ color: $text-main;
+ }
- @media (max-width: #{$bp-sm}) { font-size: 11px; }
+ @media (max-width: #{$bp-sm}) {
+ font-size: 11px;
+ }
}
// ── Body ──────────────────────────────────────────────────────────────────────
@@ -200,7 +236,9 @@ $t-fast: 0.15s ease;
&::before {
content: '';
position: absolute;
- top: 0; left: 0; right: 0;
+ top: 0;
+ left: 0;
+ right: 0;
height: 4px;
background: var(--co2, #{$accent});
border-radius: $radius-lg $radius-lg 0 0;
@@ -209,7 +247,10 @@ $t-fast: 0.15s ease;
.co2-hero-label {
display: block;
- font: 600 10px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 10px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
letter-spacing: 0.14em;
text-transform: uppercase;
color: $text-muted;
@@ -226,7 +267,10 @@ $t-fast: 0.15s ease;
}
.co2-hero-unit {
- font: 500 14px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 500 14px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
margin-top: 4px;
}
@@ -237,8 +281,11 @@ $t-fast: 0.15s ease;
padding: 3px 12px;
border-radius: 20px;
background: var(--co2, #{$accent});
- color: rgba(0,0,0,0.72);
- font: 600 11px/1.6 ui-sans-serif, system-ui, sans-serif;
+ color: rgba(0, 0, 0, 0.72);
+ font:
+ 600 11px/1.6 ui-sans-serif,
+ system-ui,
+ sans-serif;
letter-spacing: 0.03em;
}
@@ -269,19 +316,27 @@ $t-fast: 0.15s ease;
}
.metric-label {
- font: 400 11px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 11px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.metric-value {
- font: 700 26px/1.1 'Courier New', monospace;
+ font:
+ 700 26px/1.1 'Courier New',
+ monospace;
color: $navy;
}
.metric-unit {
- font: 500 13px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 500 13px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
margin-left: 2px;
font-family: ui-sans-serif, system-ui, sans-serif;
@@ -296,7 +351,9 @@ $t-fast: 0.15s ease;
display: flex;
align-items: center;
justify-content: space-between;
- transition: border-color $t-fast, background $t-fast;
+ transition:
+ border-color $t-fast,
+ background $t-fast;
&.open {
border-color: $success-border;
@@ -318,14 +375,20 @@ $t-fast: 0.15s ease;
}
.window-label {
- font: 400 11px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 11px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.window-state {
- font: 600 15px/1.4 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 15px/1.4 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $navy;
text-transform: capitalize;
}
@@ -337,7 +400,9 @@ $t-fast: 0.15s ease;
background: $border;
transition: background $t-fast;
- &.open { background: $success; }
+ &.open {
+ background: $success;
+ }
}
// ── Right panel ───────────────────────────────────────────────────────────────
@@ -373,12 +438,18 @@ $t-fast: 0.15s ease;
.history-title {
margin: 0;
- font: 700 15px/1.2 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 700 15px/1.2 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $navy;
}
.history-sub {
- font: 400 12px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 12px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
}
@@ -400,9 +471,15 @@ $t-fast: 0.15s ease;
background: #fff;
color: $text-muted;
cursor: pointer;
- transition: background $t-fast, color $t-fast, border-color $t-fast;
+ transition:
+ background $t-fast,
+ color $t-fast,
+ border-color $t-fast;
- svg { width: 12px; height: 12px; }
+ svg {
+ width: 12px;
+ height: 12px;
+ }
&:hover:not(:disabled) {
background: $navy;
@@ -417,7 +494,9 @@ $t-fast: 0.15s ease;
}
.page-info {
- font: 600 12px/1 'Courier New', monospace;
+ font:
+ 600 12px/1 'Courier New',
+ monospace;
color: $text-muted;
min-width: 44px;
text-align: center;
@@ -443,20 +522,28 @@ $t-fast: 0.15s ease;
.history-table {
width: 100%;
border-collapse: collapse;
- font: 400 13px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 13px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
thead {
position: sticky;
top: 0;
z-index: 1;
- @media (max-width: #{$bp-md}) { position: static; }
+ @media (max-width: #{$bp-md}) {
+ position: static;
+ }
}
th {
background: $surface;
color: $text-muted;
- font: 600 11px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 11px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
text-transform: uppercase;
letter-spacing: 0.07em;
padding: 11px 16px;
@@ -464,7 +551,10 @@ $t-fast: 0.15s ease;
white-space: nowrap;
border-bottom: 1px solid $border;
- @media (max-width: #{$bp-sm}) { padding: 9px 10px; font-size: 10px; }
+ @media (max-width: #{$bp-sm}) {
+ padding: 9px 10px;
+ font-size: 10px;
+ }
}
td {
@@ -474,7 +564,10 @@ $t-fast: 0.15s ease;
vertical-align: middle;
white-space: nowrap;
- @media (max-width: #{$bp-sm}) { padding: 8px 10px; font-size: 12px; }
+ @media (max-width: #{$bp-sm}) {
+ padding: 8px 10px;
+ font-size: 12px;
+ }
}
tr:hover td {
@@ -483,7 +576,9 @@ $t-fast: 0.15s ease;
}
.td-time {
- font: 500 13px/1 'Courier New', monospace;
+ font:
+ 500 13px/1 'Courier New',
+ monospace;
color: $text-muted;
}
@@ -491,17 +586,25 @@ $t-fast: 0.15s ease;
display: inline-block;
padding: 3px 10px;
border-radius: 20px;
- font: 600 12px/1.6 'Courier New', monospace;
+ font:
+ 600 12px/1.6 'Courier New',
+ monospace;
color: rgba(0, 0, 0, 0.72);
white-space: nowrap;
}
.win-state {
- font: 500 12px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 500 12px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
text-transform: capitalize;
- &.open { color: #16a34a; font-weight: 600; }
+ &.open {
+ color: #16a34a;
+ font-weight: 600;
+ }
}
// ── States ────────────────────────────────────────────────────────────────────
@@ -512,7 +615,10 @@ $t-fast: 0.15s ease;
padding: 40px 24px;
text-align: center;
color: $text-muted;
- font: 400 14px/1.5 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 14px/1.5 ui-sans-serif,
+ system-ui,
+ sans-serif;
font-style: italic;
background: #fff;
border-radius: $radius-lg;
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 ba13c64..d6e8569 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
@@ -30,19 +30,19 @@ const PAGE_SIZE = 8;
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RoomDetailsPanelComponent implements OnInit {
- private route = inject(ActivatedRoute);
- private roomService = inject(RoomService);
+ private route = inject(ActivatedRoute);
+ private roomService = inject(RoomService);
private sensorService = inject(SensorService);
- private destroyRef = inject(DestroyRef);
+ private destroyRef = inject(DestroyRef);
- room = signal(undefined);
+ room = signal(undefined);
latestReading = signal(undefined);
- history = signal([]);
- lastUpdated = signal(null);
+ history = signal([]);
+ lastUpdated = signal(null);
currentPage = signal(0);
- totalPages = computed(() => Math.ceil(this.history().length / PAGE_SIZE));
+ totalPages = computed(() => Math.ceil(this.history().length / PAGE_SIZE));
pagedHistory = computed(() => {
const start = this.currentPage() * PAGE_SIZE;
return this.history().slice(start, start + PAGE_SIZE);
@@ -54,26 +54,34 @@ export class RoomDetailsPanelComponent implements OnInit {
ngOnInit(): void {
const interval = environment.polling.detailIntervalMs;
- this.route.paramMap.pipe(
- map(params => params.get('id') ?? ''),
- switchMap(id => combineLatest([
- this.roomService.getRoomById(id),
- concat(
- this.sensorService.getLatestReadingForRoom(id),
- timer(interval, interval).pipe(switchMap(() => this.sensorService.getLatestReadingForRoom(id))),
+ this.route.paramMap
+ .pipe(
+ map(params => params.get('id') ?? ''),
+ switchMap(id =>
+ combineLatest([
+ this.roomService.getRoomById(id),
+ concat(
+ this.sensorService.getLatestReadingForRoom(id),
+ timer(interval, interval).pipe(
+ switchMap(() => this.sensorService.getLatestReadingForRoom(id))
+ )
+ ),
+ concat(
+ this.sensorService.getHistoryForRoom(id),
+ timer(interval, interval).pipe(
+ switchMap(() => this.sensorService.getHistoryForRoom(id))
+ )
+ ),
+ ])
),
- concat(
- this.sensorService.getHistoryForRoom(id),
- timer(interval, interval).pipe(switchMap(() => this.sensorService.getHistoryForRoom(id))),
- ),
- ])),
- takeUntilDestroyed(this.destroyRef),
- ).subscribe(([room, reading, history]) => {
- this.room.set(room);
- this.latestReading.set(reading);
- this.history.set(history ?? []);
- if (reading) this.lastUpdated.set(new Date());
- });
+ takeUntilDestroyed(this.destroyRef)
+ )
+ .subscribe(([room, reading, history]) => {
+ this.room.set(room);
+ this.latestReading.set(reading);
+ this.history.set(history ?? []);
+ if (reading) this.lastUpdated.set(new Date());
+ });
}
prevPage(): void {
diff --git a/ui/src/app/components/room-map/room-map.component.html b/ui/src/app/components/room-map/room-map.component.html
index 97e344b..ef70a3a 100644
--- a/ui/src/app/components/room-map/room-map.component.html
+++ b/ui/src/app/components/room-map/room-map.component.html
@@ -1,10 +1,8 @@
-
-
+
+
+
\ No newline at end of file
+
+
diff --git a/ui/src/app/components/room-map/room-map.component.scss b/ui/src/app/components/room-map/room-map.component.scss
index 92e3824..fbcfb49 100644
--- a/ui/src/app/components/room-map/room-map.component.scss
+++ b/ui/src/app/components/room-map/room-map.component.scss
@@ -5,28 +5,28 @@
// ── Tokens ───────────────────────────────────────────────────────────────────
-$navy-deep: #0d1b2a;
-$navy: #1a3a6b;
-$navy-mid: #1f4e8c;
-$navy-light: #cfe0f2;
-$accent: #00b4d8;
-$surface: #f4f7fb;
-$border: #d1dce8;
-$text-main: #0d1b2a;
-$text-muted: #5a7694;
-$radius-lg: 10px;
-$radius-md: 7px;
-$radius-sm: 5px;
-$ctrl-h: 34px;
+$navy-deep: #0d1b2a;
+$navy: #1a3a6b;
+$navy-mid: #1f4e8c;
+$navy-light: #cfe0f2;
+$accent: #00b4d8;
+$surface: #f4f7fb;
+$border: #d1dce8;
+$text-main: #0d1b2a;
+$text-muted: #5a7694;
+$radius-lg: 10px;
+$radius-md: 7px;
+$radius-sm: 5px;
+$ctrl-h: 34px;
// Breakpoints
$bp-lg: 1100px;
-$bp-md: 768px;
-$bp-sm: 480px;
+$bp-md: 768px;
+$bp-sm: 480px;
// Transitions
-$t-fast: 0.12s ease;
-$t-normal: 0.20s ease;
+$t-fast: 0.12s ease;
+$t-normal: 0.2s ease;
// ── Layout global ─────────────────────────────────────────────────────────────
@@ -84,7 +84,10 @@ $t-normal: 0.20s ease;
}
.sidebar-eyebrow {
- font: 600 10px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 10px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
letter-spacing: 0.14em;
text-transform: uppercase;
color: $accent;
@@ -92,7 +95,10 @@ $t-normal: 0.20s ease;
.sidebar-title {
margin: 0;
- font: 700 15px/1.2 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 700 15px/1.2 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $navy;
}
@@ -146,7 +152,10 @@ $t-normal: 0.20s ease;
}
.title-eyebrow {
- font: 600 10px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 600 10px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
letter-spacing: 0.14em;
text-transform: uppercase;
color: $accent;
@@ -154,7 +163,10 @@ $t-normal: 0.20s ease;
.title {
margin: 0;
- font: 700 18px/1.2 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 700 18px/1.2 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $navy;
}
@@ -164,13 +176,23 @@ $t-normal: 0.20s ease;
display: flex;
align-items: center;
gap: 5px;
- font: 500 11px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 500 11px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
letter-spacing: 0.02em;
- svg { width: 12px; height: 12px; flex-shrink: 0; color: $accent; }
+ svg {
+ width: 12px;
+ height: 12px;
+ flex-shrink: 0;
+ color: $accent;
+ }
- @media (max-width: 480px) { display: none; }
+ @media (max-width: 480px) {
+ display: none;
+ }
}
// ── Barre de contrôles ────────────────────────────────────────────────────────
@@ -195,28 +217,40 @@ $t-normal: 0.20s ease;
background: transparent;
color: #8ab0cc;
cursor: pointer;
- transition: background $t-fast, color $t-fast;
+ transition:
+ background $t-fast,
+ color $t-fast;
&:hover {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
- &:active { transform: scale(0.93); }
+ &:active {
+ transform: scale(0.93);
+ }
- svg { display: block; }
+ svg {
+ display: block;
+ }
}
.ctrl-zoom {
width: $ctrl-h;
- svg { width: 14px; height: 14px; }
+ svg {
+ width: 14px;
+ height: 14px;
+ }
}
.ctrl-action {
width: $ctrl-h;
- svg { width: 15px; height: 15px; }
+ svg {
+ width: 15px;
+ height: 15px;
+ }
}
.ctrl-divider {
@@ -227,7 +261,9 @@ $t-normal: 0.20s ease;
}
.scale-badge {
- font: 600 12px/1 'Courier New', monospace;
+ font:
+ 600 12px/1 'Courier New',
+ monospace;
color: $accent;
min-width: 42px;
text-align: center;
@@ -259,19 +295,29 @@ $t-normal: 0.20s ease;
linear-gradient(45deg, transparent 75%, #e2eaf4 75%),
linear-gradient(-45deg, transparent 75%, #e2eaf4 75%);
background-size: 24px 24px;
- background-position: 0 0, 0 12px, 12px -12px, -12px 0;
+ background-position:
+ 0 0,
+ 0 12px,
+ 12px -12px,
+ -12px 0;
- &.panning { cursor: grabbing; }
+ &.panning {
+ cursor: grabbing;
+ }
// Hauteur explicite sur mobile (flex:1 ne suffit pas en colonne avec height:auto)
- @media (max-width: 1100px) { min-height: 55vh; }
- @media (max-width: 480px) { min-height: 50vh; border-radius: $radius-md; }
+ @media (max-width: 1100px) {
+ min-height: 55vh;
+ }
+ @media (max-width: 480px) {
+ min-height: 50vh;
+ border-radius: $radius-md;
+ }
}
// ── Canvas transformable ──────────────────────────────────────────────────────
.canvas {
-
position: absolute;
top: 0;
left: 0;
@@ -279,7 +325,6 @@ $t-normal: 0.20s ease;
will-change: transform;
// Ombre extérieure pour matérialiser les bords du plan
filter: drop-shadow(0 4px 24px rgba($navy-deep, 0.18));
-
}
// ── Hôte du SVG injecté ───────────────────────────────────────────────────────
@@ -322,7 +367,9 @@ $t-normal: 0.20s ease;
cursor: pointer;
.badge-bg {
- transition: filter 0.15s ease, fill-opacity 0.15s ease;
+ transition:
+ filter 0.15s ease,
+ fill-opacity 0.15s ease;
}
&:hover .badge-bg {
@@ -357,7 +404,10 @@ $t-normal: 0.20s ease;
p {
margin: 0;
- font: 400 14px/1.5 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 14px/1.5 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
text-align: center;
}
@@ -392,7 +442,9 @@ $t-normal: 0.20s ease;
}
@keyframes spin {
- to { transform: rotate(360deg); }
+ to {
+ transform: rotate(360deg);
+ }
}
// ── Hint viewport ─────────────────────────────────────────────────────────────
@@ -408,7 +460,10 @@ $t-normal: 0.20s ease;
background: rgba($navy-deep, 0.62);
backdrop-filter: blur(4px);
color: rgba(255, 255, 255, 0.7);
- font: 400 10px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 10px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
letter-spacing: 0.04em;
padding: 5px 12px;
border-radius: 20px;
@@ -423,7 +478,11 @@ $t-normal: 0.20s ease;
// Cache les spans "Mouse wheel" et "Drag" sur mobile, garde juste "Click"
@media (max-width: 480px) {
- .hint-mouse, .hint-drag, .hint-sep:not(:last-of-type) { display: none; }
+ .hint-mouse,
+ .hint-drag,
+ .hint-sep:not(:last-of-type) {
+ display: none;
+ }
}
}
@@ -446,19 +505,31 @@ $t-normal: 0.20s ease;
animation: tt-in 0.1s ease;
@keyframes tt-in {
- from { opacity: 0; transform: translateY(4px); }
- to { opacity: 1; transform: translateY(0); }
+ from {
+ opacity: 0;
+ transform: translateY(4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
}
.tt-name {
- font: 700 13px/1.2 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 700 13px/1.2 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $navy-light;
margin-bottom: 2px;
}
.tt-type {
- font: 400 10px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 10px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: $text-muted;
text-transform: capitalize;
margin-bottom: 8px;
@@ -475,17 +546,30 @@ $t-normal: 0.20s ease;
justify-content: space-between;
align-items: center;
gap: 16px;
- font: 400 11px/1.8 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 11px/1.8 ui-sans-serif,
+ system-ui,
+ sans-serif;
- span { color: #6a90b0; }
- strong { color: #fff; font-weight: 600; }
+ span {
+ color: #6a90b0;
+ }
+ strong {
+ color: #fff;
+ font-weight: 600;
+ }
- strong.open { color: #69e09a; }
+ strong.open {
+ color: #69e09a;
+ }
}
.tt-empty {
- font: 400 11px/1 ui-sans-serif, system-ui, sans-serif;
+ font:
+ 400 11px/1 ui-sans-serif,
+ system-ui,
+ sans-serif;
color: #5a7694;
font-style: italic;
margin-top: 4px;
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/room-map/room-map.component.ts b/ui/src/app/components/room-map/room-map.component.ts
index a6514f7..d644917 100644
--- a/ui/src/app/components/room-map/room-map.component.ts
+++ b/ui/src/app/components/room-map/room-map.component.ts
@@ -40,9 +40,9 @@ export interface RoomMapEntry {
// Les badges se chevauchent légèrement à l'échelle globale ; zoomer > 3× les sépare.
// ─────────────────────────────────────────────────────────────────────────────
const SVG_ROOM_CENTERS: Record = {
- A8: { cx: 155500, cy: 9000 },
- A9: { cx: 177200, cy: 57000 },
- A7: { cx: 134800, cy: 84700 },
+ A8: { cx: 155500, cy: 9000 },
+ A9: { cx: 177200, cy: 57000 },
+ A7: { cx: 134800, cy: 84700 },
A6: { cx: 166500, cy: 81200 },
A5: { cx: 134800, cy: 130200 },
A4: { cx: 166500, cy: 141500 },
@@ -51,10 +51,10 @@ const SVG_ROOM_CENTERS: Record = {
A1: { cx: 110000, cy: 225000 },
// B rooms : labels hors de la zone visible analysée du SVG (section tronquée).
// Décommentez et ajustez après inspection du fichier complet.
- B4: { cx: 17000, cy: 138000 },
- B3: { cx: 17000, cy: 170000 },
- B2: { cx: 17000, cy: 205000 },
- B1: { cx: 52000, cy: 225000 },
+ B4: { cx: 17000, cy: 138000 },
+ B3: { cx: 17000, cy: 170000 },
+ B2: { cx: 17000, cy: 205000 },
+ B1: { cx: 52000, cy: 225000 },
};
// Dimensions du SVG original (attributs width/height du fichier Inkscape)
@@ -74,21 +74,21 @@ const SCALE = 100;
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RoomMapComponent implements OnInit, AfterViewInit, OnDestroy {
- private http = inject(HttpClient);
- private sanitizer = inject(DomSanitizer);
- private router = inject(Router);
- private roomService = inject(RoomService);
+ private http = inject(HttpClient);
+ private sanitizer = inject(DomSanitizer);
+ private router = inject(Router);
+ private roomService = inject(RoomService);
private sensorService = inject(SensorService);
- private zone = inject(NgZone);
+ private zone = inject(NgZone);
@ViewChild('viewport') viewportRef!: ElementRef;
// ── Données ──────────────────────────────────────────────────────────────
- rooms = signal([]);
- svgHtml = signal(null);
- isLoading = signal(true);
- svgError = signal(false);
- tooltip = signal<{ room: RoomMapEntry; x: number; y: number } | null>(null);
+ rooms = signal([]);
+ svgHtml = signal(null);
+ isLoading = signal(true);
+ svgError = signal(false);
+ tooltip = signal<{ room: RoomMapEntry; x: number; y: number } | null>(null);
lastUpdated = signal(null);
lastUpdatedStr = computed(() => {
@@ -98,36 +98,36 @@ export class RoomMapComponent implements OnInit, AfterViewInit, OnDestroy {
});
private pollSub?: Subscription;
- private svgSub?: Subscription;
+ private svgSub?: Subscription;
private fitTimer?: ReturnType;
private roomLayouts: RoomLayout[] = [];
// ── État pan / zoom ──────────────────────────────────────────────────────
// Variables privées pour les mutations hors zone (wheel)
- private _s = 1;
+ private _s = 1;
private _tx = 0;
private _ty = 0;
- scale = signal(1);
- tx = signal(0);
- ty = signal(0);
+ scale = signal(1);
+ tx = signal(0);
+ ty = signal(0);
isPanning = signal(false);
- scalePct = computed(() => Math.round(this.scale() * 100));
+ scalePct = computed(() => Math.round(this.scale() * 100));
transform = computed(() => `translate(${this.tx()}px,${this.ty()}px) scale(${this.scale()})`);
// ── Constantes exposées au template ──────────────────────────────────────
- readonly SVG_W = SVG_W;
- readonly SVG_H = SVG_H;
- readonly CANVAS_W = Math.round(SVG_W / SCALE); // 1843 px
- readonly CANVAS_H = Math.round(SVG_H / SCALE); // 2457 px
+ readonly SVG_W = SVG_W;
+ readonly SVG_H = SVG_H;
+ readonly CANVAS_W = Math.round(SVG_W / SCALE); // 1843 px
+ readonly CANVAS_H = Math.round(SVG_H / SCALE); // 2457 px
readonly MIN_SCALE = 0.04;
readonly MAX_SCALE = 25;
readonly NO_SENSOR_COLOR = '#94a3b8';
// ── Pan ──────────────────────────────────────────────────────────────────
- private panStart = { x: 0, y: 0 };
+ private panStart = { x: 0, y: 0 };
private panOrigin = { tx: 0, ty: 0 };
// Référence stable pour removeEventListener
@@ -140,7 +140,7 @@ export class RoomMapComponent implements OnInit, AfterViewInit, OnDestroy {
ngOnInit(): void {
// Chargement du plan SVG depuis public/
this.svgSub = this.http.get('/plan.svg', { responseType: 'text' }).subscribe({
- next: (raw) => {
+ next: raw => {
const cleaned = raw
.replace(/( |