fix(dept): vert ≠ vigilance, conseils sur chaque alerte active
Some checks failed
Deploy info-canicule / deploy (push) Failing after 5s
Some checks failed
Deploy info-canicule / deploy (push) Failing after 5s
Le flux Météo France renvoie parfois des phénomènes explicitement au vert (colorId 1) : ils étaient traités comme des alertes, d'où un « Que faire maintenant ? » affiché sur un phénomène vert et un dropdown limité aux phénomènes absents du flux. - "Active" = vigilance jaune ou plus (colorId >= 2) ; le vert n'est jamais une alerte - Tout au vert → un seul encadré « Aucune vigilance active » - Dropdown regroupe désormais tous les phénomènes au vert (présents au vert dans le flux + absents) - Une vigilance présente → bloc complet « Que faire maintenant ? » sur chacune des alertes actives (au lieu de la première seulement) - Tableau Aujourd'hui vs Demain et suite : inchangés Au passage, fix typage FranceMap : Map par défaut typée pour ne pas perdre VigilanceAlert (5 erreurs astro check), imports inutilisés retirés. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9db5d4c204
commit
c170bbca7d
2 changed files with 55 additions and 67 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import franceMap from '../data/france-map.json';
|
import franceMap from '../data/france-map.json';
|
||||||
import { COLORS, COLOR_LABEL, PHENOMENA } from '../lib/phenomena';
|
import { COLORS, PHENOMENA } from '../lib/phenomena';
|
||||||
import type { ColorId, PhenomenonId } from '../lib/phenomena';
|
import type { ColorId } from '../lib/phenomena';
|
||||||
import type { VigilanceAlert } from '../lib/vigilance';
|
import type { VigilanceAlert } from '../lib/vigilance';
|
||||||
import { getDepartement } from '../lib/departements';
|
import { getDepartement } from '../lib/departements';
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ interface Props {
|
||||||
alertsByDept?: Map<string, VigilanceAlert[]>;
|
alertsByDept?: Map<string, VigilanceAlert[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { colorsByDept, alertsByDept = new Map() } = Astro.props;
|
const { colorsByDept, alertsByDept = new Map<string, VigilanceAlert[]>() } = Astro.props;
|
||||||
const entries = Object.entries(franceMap.paths) as [string, { d: string; name: string }][];
|
const entries = Object.entries(franceMap.paths) as [string, { d: string; name: string }][];
|
||||||
|
|
||||||
// Construire un objet { code: { name, color, alerts: [{phenLabel, colorName, colorId}] } }
|
// Construire un objet { code: { name, color, alerts: [{phenLabel, colorName, colorId}] } }
|
||||||
|
|
|
||||||
|
|
@ -147,16 +147,20 @@ const j1Available = snapshot ? (ech === 'J' && hasJ1Period(snapshot)) : false;
|
||||||
const today = snapshot ? alertsForDepartement(snapshot, dept.code, ech) : [];
|
const today = snapshot ? alertsForDepartement(snapshot, dept.code, ech) : [];
|
||||||
const tomorrow = j1Available ? alertsForDepartement(snapshot!, dept.code, 'J1') : [];
|
const tomorrow = j1Available ? alertsForDepartement(snapshot!, dept.code, 'J1') : [];
|
||||||
|
|
||||||
// Sort by severity desc, pick top alert for the hero block
|
// Sort by severity desc. "Active" = vigilance jaune ou plus (colorId >= 2).
|
||||||
|
// Le flux MF renvoie parfois des phénomènes explicitement au vert (colorId 1) —
|
||||||
|
// ce ne sont PAS des alertes : ils rejoignent le dropdown "tous au vert", au même
|
||||||
|
// titre que les phénomènes absents du flux. topAlert = la plus sévère des actives
|
||||||
|
// (sert aux numéros d'urgence + carte "Kit" en bas de page).
|
||||||
const sortedToday = [...today].sort((a, b) => b.colorId - a.colorId);
|
const sortedToday = [...today].sort((a, b) => b.colorId - a.colorId);
|
||||||
const topAlert = sortedToday[0] ?? null;
|
const activeToday = sortedToday.filter((a) => a.colorId >= 2);
|
||||||
const otherAlerts = sortedToday.slice(1);
|
const topAlert = activeToday[0] ?? null;
|
||||||
|
|
||||||
const adviceFor = topAlert ? ADVICE[topAlert.phenomenonId] : null;
|
// First 3 action items, par phénomène, pour le bloc "Que faire maintenant ?"
|
||||||
// First 3 action items as quick bullets inside the hero block
|
function quickActionsFor(phenId: PhenomenonId): string[] {
|
||||||
const quickActions = adviceFor
|
const advice = ADVICE[phenId];
|
||||||
? adviceFor.blocks.flatMap((b) => b.items).slice(0, 3)
|
return advice ? advice.blocks.flatMap((b) => b.items).slice(0, 3) : [];
|
||||||
: [];
|
}
|
||||||
|
|
||||||
// Per-phenomenon color maps for the comparison table
|
// Per-phenomenon color maps for the comparison table
|
||||||
const PHENOM_IDS: PhenomenonId[] = [1, 2, 3, 5, 6, 8, 9];
|
const PHENOM_IDS: PhenomenonId[] = [1, 2, 3, 5, 6, 8, 9];
|
||||||
|
|
@ -179,9 +183,10 @@ const comparisonRows = PHENOM_IDS.map((id) => ({
|
||||||
changed: j1Available && (todayByPhenom.get(id) ?? 1) !== (tomorrowByPhenom.get(id) ?? 1),
|
changed: j1Available && (todayByPhenom.get(id) ?? 1) !== (tomorrowByPhenom.get(id) ?? 1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Phenomena with no active alert today (green)
|
// Phénomènes au vert aujourd'hui = ceux sans alerte active (jaune+). Inclut aussi
|
||||||
const activePhenomIds = new Set(today.map((a) => a.phenomenonId));
|
// les phénomènes absents du flux MF (réputés verts). Tous regroupés dans le dropdown.
|
||||||
const inactivePhenomIds = PHENOM_IDS.filter((id) => !activePhenomIds.has(id));
|
const activePhenomIds = new Set(activeToday.map((a) => a.phenomenonId));
|
||||||
|
const greenPhenomIds = PHENOM_IDS.filter((id) => !activePhenomIds.has(id));
|
||||||
|
|
||||||
const productDate = snapshot?.productDatetime
|
const productDate = snapshot?.productDatetime
|
||||||
? new Date(snapshot.productDatetime).toLocaleString('fr-FR', {
|
? new Date(snapshot.productDatetime).toLocaleString('fr-FR', {
|
||||||
|
|
@ -204,10 +209,6 @@ const tomorrowLabel = tomorrow[0] ? labelDate(tomorrow[0].beginTime) : '';
|
||||||
|
|
||||||
// Glyphs matching the pill levels
|
// Glyphs matching the pill levels
|
||||||
const GLYPHS: Record<ColorId, string> = { 1: '●', 2: '▲', 3: '◆', 4: '■' };
|
const GLYPHS: Record<ColorId, string> = { 1: '●', 2: '▲', 3: '◆', 4: '■' };
|
||||||
|
|
||||||
// Pre-computed display values for the hero alert block
|
|
||||||
const topColorName = topAlert ? COLORS[topAlert.colorId].name : null;
|
|
||||||
const topPhen = topAlert ? PHENOMENA[topAlert.phenomenonId] : null;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base
|
<Base
|
||||||
|
|
@ -266,80 +267,67 @@ const topPhen = topAlert ? PHENOMENA[topAlert.phenomenonId] : null;
|
||||||
|
|
||||||
{!drom && !error && (
|
{!drom && !error && (
|
||||||
<>
|
<>
|
||||||
{/* All-green state */}
|
{/* All-green state — aucune vigilance active (jaune+) */}
|
||||||
{sortedToday.length === 0 && (
|
{activeToday.length === 0 && (
|
||||||
<div class="v-block v-vert" style="margin-bottom: 24px;">
|
<div class="v-block v-vert" style="margin-bottom: 24px;">
|
||||||
<h2 style="color: var(--v-vert-ink); font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem);">
|
<h2 style="color: var(--v-vert-ink); font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem);">
|
||||||
● Aucune vigilance active {todayLabel ? `pour ${todayLabel}` : "aujourd'hui"}
|
● Aucune vigilance active {todayLabel ? `pour ${todayLabel}` : "aujourd'hui"}
|
||||||
</h2>
|
</h2>
|
||||||
<p style="color: var(--v-vert-ink); margin-top: 10px; line-height: 1.5;">
|
<p style="color: var(--v-vert-ink); margin-top: 10px; line-height: 1.5;">
|
||||||
Tous les phénomènes sont au vert pour {dept.name}. Restez attentif aux mises à jour Météo France.
|
Tous les phénomènes sont au vert pour {dept.name} — rien de particulier à signaler.
|
||||||
|
Restez attentif aux mises à jour Météo France.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hero — top alert */}
|
{/* Active alerts (jaune+) — bloc complet "Que faire maintenant ?" sur chacune */}
|
||||||
{topAlert && topColorName && topPhen && (
|
{activeToday.map((a) => {
|
||||||
<div class={`v-block v-${topColorName}`} style="margin-bottom: 24px;">
|
|
||||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap;">
|
|
||||||
<span style="font-size: 2rem; line-height: 1;" aria-hidden="true">{topPhen.emoji}</span>
|
|
||||||
<h2 style={`color: var(--v-${topColorName}-ink); font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem);`}>
|
|
||||||
{GLYPHS[topAlert.colorId]} {topPhen.label} — {COLOR_LABEL[topAlert.colorId]}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{adviceFor && (
|
|
||||||
<div style="margin-top: 4px; padding: 16px 20px; background: var(--paper-2); border-radius: var(--r-md); color: var(--ink);">
|
|
||||||
<strong style="font-size: 0.95rem;">Que faire maintenant ?</strong>
|
|
||||||
<ul style="margin-top: 10px; padding-left: 18px; color: var(--ink-2); line-height: 1.6;">
|
|
||||||
{quickActions.map((a) => <li>{a}</li>)}
|
|
||||||
</ul>
|
|
||||||
<a
|
|
||||||
href={`/conseils/${topPhen.slug}`}
|
|
||||||
style="display: inline-flex; align-items: center; gap: 6px; margin-top: 12px; font-size: 0.88rem; font-weight: 600; color: var(--brand-deep); text-decoration: none;"
|
|
||||||
>
|
|
||||||
Voir le kit complet {topPhen.label} →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p style={`color: var(--v-${topColorName}-ink); margin-top: 14px; font-size: 0.85rem; opacity: 0.85;`}>
|
|
||||||
Valide du {fmtTime(topAlert.beginTime)} au {fmtTime(topAlert.endTime)} — heure de Paris.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Other active alerts */}
|
|
||||||
{otherAlerts.map((a) => {
|
|
||||||
const colorName = COLORS[a.colorId].name;
|
const colorName = COLORS[a.colorId].name;
|
||||||
const phen = PHENOMENA[a.phenomenonId];
|
const phen = PHENOMENA[a.phenomenonId];
|
||||||
|
const advice = ADVICE[a.phenomenonId];
|
||||||
|
const quickActions = quickActionsFor(a.phenomenonId);
|
||||||
return (
|
return (
|
||||||
<div class={`v-block v-${colorName}`} style="margin-bottom: 12px; padding: 16px;">
|
<div class={`v-block v-${colorName}`} style="margin-bottom: 24px;">
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;">
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap;">
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
<span style="font-size: 2rem; line-height: 1;" aria-hidden="true">{phen.emoji}</span>
|
||||||
<span style="font-size: 1.3rem;" aria-hidden="true">{phen.emoji}</span>
|
<h2 style={`color: var(--v-${colorName}-ink); font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem);`}>
|
||||||
<strong style={`color: var(--v-${colorName}-ink);`}>{phen.label}</strong>
|
{GLYPHS[a.colorId]} {phen.label} — {COLOR_LABEL[a.colorId]}
|
||||||
</div>
|
</h2>
|
||||||
<VigilanceChip colorId={a.colorId} showLevel />
|
|
||||||
</div>
|
</div>
|
||||||
<p style={`color: var(--v-${colorName}-ink); margin-top: 8px; font-size: 0.82rem; opacity: 0.8;`}>
|
|
||||||
Du {fmtTime(a.beginTime)} au {fmtTime(a.endTime)}
|
{advice && (
|
||||||
|
<div style="margin-top: 4px; padding: 16px 20px; background: var(--paper-2); border-radius: var(--r-md); color: var(--ink);">
|
||||||
|
<strong style="font-size: 0.95rem;">Que faire maintenant ?</strong>
|
||||||
|
<ul style="margin-top: 10px; padding-left: 18px; color: var(--ink-2); line-height: 1.6;">
|
||||||
|
{quickActions.map((item) => <li>{item}</li>)}
|
||||||
|
</ul>
|
||||||
|
<a
|
||||||
|
href={`/conseils/${phen.slug}`}
|
||||||
|
style="display: inline-flex; align-items: center; gap: 6px; margin-top: 12px; font-size: 0.88rem; font-weight: 600; color: var(--brand-deep); text-decoration: none;"
|
||||||
|
>
|
||||||
|
Voir le kit complet {phen.label} →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p style={`color: var(--v-${colorName}-ink); margin-top: 14px; font-size: 0.85rem; opacity: 0.85;`}>
|
||||||
|
Valide du {fmtTime(a.beginTime)} au {fmtTime(a.endTime)} — heure de Paris.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Collapsible: inactive phenomena (all green) */}
|
{/* Collapsible: phénomènes au vert (sans vigilance) */}
|
||||||
{inactivePhenomIds.length > 0 && (
|
{greenPhenomIds.length > 0 && (
|
||||||
<details class="ic-card" style="margin-bottom: 28px; padding: 16px;">
|
<details class="ic-card" style="margin-bottom: 28px; padding: 16px;">
|
||||||
<summary style="cursor: pointer; font-weight: 600; color: var(--ink-2); display: flex; align-items: center; justify-content: space-between; list-style: none;">
|
<summary style="cursor: pointer; font-weight: 600; color: var(--ink-2); display: flex; align-items: center; justify-content: space-between; list-style: none;">
|
||||||
<span>
|
<span>
|
||||||
{inactivePhenomIds.length} phénomène{inactivePhenomIds.length > 1 ? 's' : ''} — tous au vert
|
{greenPhenomIds.length} phénomène{greenPhenomIds.length > 1 ? 's' : ''} au vert — aucune vigilance
|
||||||
</span>
|
</span>
|
||||||
<span aria-hidden="true" style="color: var(--ink-mute); font-size: 0.8rem;">▼</span>
|
<span aria-hidden="true" style="color: var(--ink-mute); font-size: 0.8rem;">▼</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="grid gap-2" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); margin-top: 14px;">
|
<div class="grid gap-2" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); margin-top: 14px;">
|
||||||
{inactivePhenomIds.map((id) => (
|
{greenPhenomIds.map((id) => (
|
||||||
<div class="v-block v-vert" style="padding: 10px 14px; font-size: 0.9rem; display: flex; align-items: center; gap: 8px;">
|
<div class="v-block v-vert" style="padding: 10px 14px; font-size: 0.9rem; display: flex; align-items: center; gap: 8px;">
|
||||||
<span aria-hidden="true">{PHENOMENA[id].emoji}</span>
|
<span aria-hidden="true">{PHENOMENA[id].emoji}</span>
|
||||||
<span style="color: var(--v-vert-ink);">{PHENOMENA[id].label}</span>
|
<span style="color: var(--v-vert-ink);">{PHENOMENA[id].label}</span>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue