feat: tooltip carte + tri/group alertes + safelist couleurs + legal Nocleus
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run

- FranceMap : tooltip riche au hover (HTML overlay), liste les phénomènes
  + niveaux du département. Touch-friendly (1er tap = preview, 2e = clic).
- index.astro : layout refactored, carte toujours visible full-width centrée,
  liste par région en details collapsible sous (plus de side-by-side cassé sur PC).
- Alertes actives groupées par département, triées par numéro asc (2A/2B après 19).
- Tailwind safelist vigilance-chip-{1..4} : les classes générées dynamiquement
  n'étaient pas captées par le scanner statique → CSS absent en prod.
- Mentions légales : distinction explicite entre Nocleus (micro-entreprise
  commerciale) et Info Canicule (projet perso non lucratif, hors cadre pro).
- Liens code source git.nocleus.com retirés partout (autres repos privés y sont
  visibles) → code "disponible sur demande" par mail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian 2026-05-25 20:23:02 +02:00
parent 58053b72ed
commit 89e48c18e4
8 changed files with 287 additions and 81 deletions

View file

@ -6,7 +6,7 @@ import VigilanceLegend from '../components/VigilanceLegend.astro';
import VigilanceChip from '../components/VigilanceChip.astro';
import { getVigilanceSnapshot, maxColorByDepartement, activeAlerts } from '../lib/vigilance';
import { getDepartement } from '../lib/departements';
import { PHENOMENA, COLORS } from '../lib/phenomena';
import type { VigilanceAlert } from '../lib/vigilance';
export const prerender = false;
@ -20,6 +20,29 @@ try {
const colorsByDept = snapshot ? maxColorByDepartement(snapshot, 'J') : new Map();
const alertsToday = snapshot ? activeAlerts(snapshot, 2) : [];
// Group all today's alerts (any level) by department, for tooltip + active alerts list.
const alertsByDept = new Map<string, VigilanceAlert[]>();
if (snapshot) {
for (const a of snapshot.alerts) {
if (a.echeance !== 'J') continue;
if (a.colorId < 2) continue;
const list = alertsByDept.get(a.departement) ?? [];
list.push(a);
alertsByDept.set(a.departement, list);
}
}
// Tri des départements actifs par code (2A/2B inséré après 19).
function deptSortKey(code: string): string {
if (code === '2A') return '19.1';
if (code === '2B') return '19.2';
return code;
}
const activeDeptCodes = [...alertsByDept.keys()].sort((a, b) =>
deptSortKey(a).localeCompare(deptSortKey(b), 'en', { numeric: true }),
);
const canicule = alertsToday.filter((a) => a.phenomenonId === 6).length;
const orages = alertsToday.filter((a) => a.phenomenonId === 3).length;
const orange = alertsToday.filter((a) => a.colorId >= 3).length;
@ -82,44 +105,54 @@ const productDate = snapshot?.productDatetime
<h2 class="text-xl font-semibold text-slate-900">Niveau par département (aujourd'hui)</h2>
<VigilanceLegend />
</div>
<div class="grid gap-8 lg:grid-cols-[2fr_1fr]">
<div class="flex justify-center">
<FranceMap colorsByDept={colorsByDept} />
</div>
<details class="rounded border border-slate-200 bg-white p-4">
<summary class="cursor-pointer font-medium text-slate-700">
Vue par région (liste)
</summary>
<div class="mt-4">
<DepartementGrid colorsByDept={colorsByDept} />
</div>
</details>
<p class="mb-3 text-sm text-slate-500">
Survolez un département pour voir le détail des alertes, cliquez pour la page complète.
</p>
<div class="flex justify-center">
<FranceMap colorsByDept={colorsByDept} alertsByDept={alertsByDept} />
</div>
<details class="mt-6 rounded border border-slate-200 bg-white p-4">
<summary class="cursor-pointer font-medium text-slate-700">
Vue par région (liste)
</summary>
<div class="mt-4">
<DepartementGrid colorsByDept={colorsByDept} />
</div>
</details>
</section>
)
}
{
!error && alertsToday.length > 0 && (
!error && activeDeptCodes.length > 0 && (
<section class="border-t border-slate-200 bg-white">
<div class="container-tight py-8">
<h2 class="mb-4 text-xl font-semibold text-slate-900">Alertes actives</h2>
<h2 class="mb-4 text-xl font-semibold text-slate-900">
Départements en alerte ({activeDeptCodes.length})
</h2>
<p class="mb-4 text-sm text-slate-500">
Triés par numéro de département. Un département peut cumuler plusieurs phénomènes (ex: canicule + orages).
</p>
<ul class="space-y-2">
{alertsToday
.sort((a, b) => b.colorId - a.colorId)
.slice(0, 50)
.map((a) => {
const dept = getDepartement(a.departement);
if (!dept) return null;
return (
<li class="flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 px-3 py-2">
<a href={`/departement/${a.departement}`} class="font-medium no-underline">
{dept.name} ({a.departement})
{activeDeptCodes.map((code) => {
const dept = getDepartement(code);
if (!dept) return null;
const alerts = (alertsByDept.get(code) ?? []).slice().sort((a, b) => b.colorId - a.colorId);
return (
<li class="rounded border border-slate-200 px-3 py-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<a href={`/departement/${code}`} class="font-medium no-underline">
<span class="font-mono text-slate-500">{code}</span> · {dept.name}
</a>
<VigilanceChip colorId={a.colorId} phenomenonId={a.phenomenonId} />
</li>
);
})}
<div class="flex flex-wrap gap-1.5">
{alerts.map((a) => (
<VigilanceChip colorId={a.colorId} phenomenonId={a.phenomenonId} />
))}
</div>
</div>
</li>
);
})}
</ul>
</div>
</section>