diff --git a/src/app/components/pages/planning/planning.css b/src/app/components/pages/planning/planning.css index 229c75f5..4652dcf9 100644 --- a/src/app/components/pages/planning/planning.css +++ b/src/app/components/pages/planning/planning.css @@ -317,8 +317,6 @@ /* ── Événements dans la grille ──────────────────────────── */ .show-event { position: absolute; - left: 3px; - right: 3px; height: 42px; background: linear-gradient(135deg, #d4a574, #c49563); color: #fff; @@ -348,8 +346,6 @@ .truck-event { position: absolute; - left: 3px; - right: 3px; height: 42px; background: linear-gradient(135deg, #605DC8, #4e4aaa); color: #fff; @@ -873,3 +869,58 @@ opacity: 0.45; cursor: not-allowed; } + +/* ── Hint dans le formulaire ────────────────────────────── */ +.form-hint { + font-size: 10px; + color: #bbb; + margin-top: 5px; + font-style: italic; + line-height: 1.4; +} + +/* ── Badge statut camion dans truck-tag ─────────────────── */ +.truck-tag-statut { + font-size: 9px; + font-weight: 700; + padding: 1px 5px; + border-radius: 8px; + background: rgba(96, 93, 200, 0.12); + color: #605DC8; +} + +/* ── Couleurs selon statut ──────────────────────────────── */ +.statut-disponible { + color: #3aaa6e !important; +} + +.statut-indisponible { + color: #e05252 !important; +} + +.statut-en-déplacement { + color: #d4a574 !important; +} + +.statut-en-panne { + color: #e05252 !important; +} + +.statut-en-maintenance { + color: #888 !important; +} + +.show-delete-btn { + border: none; + background: none; + color: #ccc; + cursor: pointer; + font-size: 16px; + line-height: 1; + padding: 0 2px; + transition: color 0.15s; +} + +.show-delete-btn:hover { + color: #e05252; +} diff --git a/src/app/components/pages/planning/planning.html b/src/app/components/pages/planning/planning.html index 3c7efdf5..2885d69b 100644 --- a/src/app/components/pages/planning/planning.html +++ b/src/app/components/pages/planning/planning.html @@ -62,7 +62,6 @@
{{ date.getDate() }}
- @if (isToday(date)) {
} @@ -71,16 +70,20 @@ (click)="selectDay(date)"> @for (show of getShowsForSlot(date, hour); track show.id) {
+ [style.top.px]="show.date ? getEventTopPx(show.date) : 0" + [style.left]="'calc(' + (show.colIndex / show.colCount * 100) + '% + 3px)'" + [style.right]="'calc(' + ((show.colCount - show.colIndex - 1) / show.colCount * 100) + '% + 3px)'"> {{ show.name }} {{ formatShowTime(show.date) }}
} @for (truck of getTrucksForSlot(date, hour); track truck.id) {
+ [style.top.px]="truck.showId ? 0 : 0" + [style.left]="'calc(' + (truck.colIndex / truck.colCount * 100) + '% + 3px)'" + [style.right]="'calc(' + ((truck.colCount - truck.colIndex - 1) / truck.colCount * 100) + '% + 3px)'"> {{ truck.type || 'Camion' }} - {{ truck.statut }} + {{ getComputedTruckStatus(truck) }}
}
@@ -153,7 +156,10 @@
{{ show.name }} - {{ formatShowTime(show.date) }} +
+ {{ formatShowTime(show.date) }} + +

{{ show.place }}

{{ show.description }}

@@ -161,6 +167,9 @@ @for (truck of getTrucksForShow(show.id!); track truck.id) { {{ truck.type }} + + {{ getComputedTruckStatus(truck) }} + @@ -200,7 +209,9 @@
{{ truck.type || 'Camion' }}
- {{ truck.statut }} + + {{ getComputedTruckStatus(truck) }} +
@@ -212,7 +223,6 @@ }
- @@ -331,16 +341,15 @@ +
- + +

Les statuts Disponible, Indisponible et En déplacement sont calculés automatiquement selon le show assigné.

@@ -355,4 +364,4 @@
- \ No newline at end of file + diff --git a/src/app/components/pages/planning/planning.ts b/src/app/components/pages/planning/planning.ts index 364578de..e322d281 100644 --- a/src/app/components/pages/planning/planning.ts +++ b/src/app/components/pages/planning/planning.ts @@ -85,7 +85,7 @@ export class Planning implements OnInit, AfterViewInit { if (!this.newShow.name) return; const dto: PyroFetesDTOShowRequestCreateShowDto = { ...this.newShow, - date: this.newShowDate ? this.newShowDate.toISOString() : undefined, + date: this.newShowDate ? this.formatDateLocal(this.newShowDate) : undefined, }; await firstValueFrom(this.showsServices.pyroFetesEndpointsShowCreateShowEndpoint(dto)); await this.fetchShows(); @@ -94,6 +94,16 @@ export class Planning implements OnInit, AfterViewInit { this.isVisible.set(false); } + private formatDateLocal(date: Date): string { + const pad = (n: number) => n.toString().padStart(2, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:00`; + } + + async deleteShow(showId: number): Promise { + await firstValueFrom(this.showsServices.pyroFetesEndpointsShowDeleteShowEndpoint(showId)); + await this.fetchShows(); + } + handleCancel(): void { this.newShow = {}; this.newShowDate = null; @@ -207,15 +217,43 @@ export class Planning implements OnInit, AfterViewInit { this.selectedDay = date; } - // Both filters off → show everything. One active → only that type. Both active → both types. + // ── Filtres ────────────────────────────────────────────────── + // Aucun filtre actif → rien ne s'affiche. Chaque filtre affiche uniquement son type. get shouldShowShows(): boolean { - return !this.isCamionFilterActive || this.isShowFilterActive; + return this.isShowFilterActive; } get shouldShowCamions(): boolean { - return !this.isShowFilterActive || this.isCamionFilterActive; + return this.isCamionFilterActive; } + // ── Statut automatique des camions ─────────────────────────── + // Priorité : En panne / En maintenance (manuel) → toujours respectés + // Sinon calculé selon le show associé et l'heure actuelle + getComputedTruckStatus(truck: PyroFetesDTOTruckResponseReadTruckDto): string { + // Statuts manuels prioritaires, jamais écrasés + if (truck.statut === 'En panne' || truck.statut === 'En maintenance') { + return truck.statut; + } + + // Pas de show assigné → disponible + if (!truck.showId) return 'Disponible'; + + // Cherche le show associé + const show = this.shows().find(s => s.id === truck.showId); + if (!show?.date) return 'Disponible'; + + const showDate = new Date(show.date); + const now = new Date(); + const diffMs = showDate.getTime() - now.getTime(); + const diffH = diffMs / (1000 * 60 * 60); + + if (diffH < 0) return 'Disponible'; // show passé → camion libre + if (diffH < 1) return 'En déplacement'; // moins d'1h avant le show + return 'Indisponible'; // show futur (>= 1h) + } + + // ── Slots et grille ────────────────────────────────────────── getShowsForDay(date: Date): PyroFetesDTOShowResponseReadShowDto[] { if (!this.shouldShowShows) return []; return this.shows().filter(show => { @@ -227,9 +265,33 @@ export class Planning implements OnInit, AfterViewInit { }); } - getShowsForSlot(date: Date, hour: string): PyroFetesDTOShowResponseReadShowDto[] { + getShowsForSlot(date: Date, hour: string): (PyroFetesDTOShowResponseReadShowDto & { colIndex: number; colCount: number })[] { if (!this.shouldShowShows) return []; const h = hour === '' ? 0 : parseInt(hour); + const shows = this.shows().filter(show => { + if (!show.date) return false; + const d = new Date(show.date); + return d.getFullYear() === date.getFullYear() && + d.getMonth() === date.getMonth() && + d.getDate() === date.getDate() && + d.getHours() === h; + }); + const trucksPresent = this.shouldShowCamions && this.getTrucksRawForSlot(date, h).length > 0; + const colCount = trucksPresent ? 2 : 1; + return shows.map(s => ({ ...s, colIndex: 0, colCount })); + } + + getTrucksForSlot(date: Date, hour: string): (PyroFetesDTOTruckResponseReadTruckDto & { colIndex: number; colCount: number })[] { + if (!this.shouldShowCamions) return []; + const h = hour === '' ? 0 : parseInt(hour); + const trucks = this.getTrucksRawForSlot(date, h); + const showsPresent = this.shouldShowShows && this.getShowsRawForSlot(date, h).length > 0; + const colCount = showsPresent ? 2 : 1; + return trucks.map(t => ({ ...t, colIndex: showsPresent ? 1 : 0, colCount })); + } + + // helpers internes + private getShowsRawForSlot(date: Date, h: number): PyroFetesDTOShowResponseReadShowDto[] { return this.shows().filter(show => { if (!show.date) return false; const d = new Date(show.date); @@ -240,9 +302,7 @@ export class Planning implements OnInit, AfterViewInit { }); } - getTrucksForSlot(date: Date, hour: string): PyroFetesDTOTruckResponseReadTruckDto[] { - if (!this.shouldShowCamions) return []; - const h = hour === '' ? 0 : parseInt(hour); + private getTrucksRawForSlot(date: Date, h: number): PyroFetesDTOTruckResponseReadTruckDto[] { const showsInSlot = this.shows().filter(show => { if (!show.date) return false; const d = new Date(show.date); @@ -282,10 +342,12 @@ export class Planning implements OnInit, AfterViewInit { return this.trucks().filter(t => t.showId === showId); } + // Retourne uniquement les camions dont le statut calculé est "Disponible" getAvailableTrucks(): PyroFetesDTOTruckResponseReadTruckDto[] { - return this.trucks().filter(t => !t.showId); + return this.trucks().filter(t => this.getComputedTruckStatus(t) === 'Disponible'); } + // ── Actions camions ────────────────────────────────────────── async assignTruck(truckId: number, showId: number): Promise { await firstValueFrom(this.trucksService.pyroFetesEndpointsTruckUpdateTruckEndpoint( truckId, { showId } @@ -371,8 +433,8 @@ export class Planning implements OnInit, AfterViewInit { return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`; } - isCamionFilterActive = false; - isShowFilterActive = false; + isCamionFilterActive = true; + isShowFilterActive = true; camionFilter(): void { this.isCamionFilterActive = !this.isCamionFilterActive;