feat: tooltip carte + tri/group alertes + safelist couleurs + legal Nocleus
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
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:
parent
58053b72ed
commit
89e48c18e4
8 changed files with 287 additions and 81 deletions
|
|
@ -77,9 +77,9 @@ export const prerender = false;
|
|||
|
||||
<h2>Code source</h2>
|
||||
<p>
|
||||
Le site est entièrement open source. Le code est disponible sur
|
||||
<a href="https://git.nocleus.com/florian/info-canicule" rel="noopener">git.nocleus.com/florian/info-canicule</a>
|
||||
(instance Forgejo personnelle). Contributions, signalements de bugs et améliorations bienvenus.
|
||||
Le code est disponible sur demande à
|
||||
<a href="mailto:florian@nocleus.com">florian@nocleus.com</a> (contributions, signalements de
|
||||
bugs et améliorations bienvenus).
|
||||
</p>
|
||||
|
||||
<h2>Contact</h2>
|
||||
|
|
|
|||
|
|
@ -206,10 +206,8 @@ const INFRA = [
|
|||
</div>
|
||||
|
||||
<p class="text-sm text-slate-500">
|
||||
Code source du site :
|
||||
<a href="https://git.nocleus.com/florian/info-canicule" rel="noopener">
|
||||
git.nocleus.com/florian/info-canicule
|
||||
</a>
|
||||
Code source du site disponible sur demande à
|
||||
<a href="mailto:florian@nocleus.com">florian@nocleus.com</a>.
|
||||
</p>
|
||||
</section>
|
||||
</Base>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -18,16 +18,22 @@ export const prerender = false;
|
|||
<section class="container-tight py-8 prose prose-slate max-w-none">
|
||||
<h2>Éditeur du site</h2>
|
||||
<p>
|
||||
Site édité à titre personnel, <strong>sans but lucratif</strong>, par :
|
||||
Site édité <strong>à titre personnel, sans but lucratif</strong>, par :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Florian Bouchet — développeur indépendant</li>
|
||||
<li>Florian Bouchet — personne physique</li>
|
||||
<li>Contact : <a href="mailto:florian@nocleus.com">florian@nocleus.com</a></li>
|
||||
<li>Statut : personne physique (pas d'entreprise immatriculée, pas d'association)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Le site n'a aucune vocation commerciale. Aucun chiffre d'affaires, aucune publicité, aucune
|
||||
collecte de données à des fins de monétisation.
|
||||
Info Canicule n'a <strong>aucune vocation commerciale</strong> : aucune publicité, aucun chiffre
|
||||
d'affaires, aucune collecte de données à des fins de monétisation.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Distinction importante</strong> : l'éditeur exerce par ailleurs une activité de
|
||||
développement sous le statut de micro-entreprise « <em>Nocleus</em> ». Cette activité commerciale
|
||||
est <strong>totalement indépendante</strong> du site Info Canicule, qui est édité hors cadre
|
||||
professionnel, sur fonds propres, à titre purement personnel et bénévole. Aucun service de la
|
||||
micro-entreprise n'est financé, promu ou rattaché à ce site.
|
||||
</p>
|
||||
|
||||
<h2>Hébergement</h2>
|
||||
|
|
@ -100,10 +106,10 @@ export const prerender = false;
|
|||
|
||||
<h2>Propriété intellectuelle</h2>
|
||||
<p>
|
||||
Le code source du site est sous licence libre (
|
||||
<a href="https://git.nocleus.com/florian/info-canicule" rel="noopener">repo Forgejo</a>
|
||||
). Les données affichées sont sous Licence Ouverte 2.0 et réutilisables librement, y compris
|
||||
commercialement, à condition de citer Météo France et la licence.
|
||||
Le code source du site est disponible sur demande à
|
||||
<a href="mailto:florian@nocleus.com">florian@nocleus.com</a>. Les données affichées sont sous
|
||||
Licence Ouverte 2.0 et réutilisables librement, y compris commercialement, à condition de citer
|
||||
Météo France et la licence.
|
||||
</p>
|
||||
<p>
|
||||
L'endpoint <code>/api/vigilance</code> diffuse le snapshot courant en JSON (CORS *), pour
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ export const prerender = false;
|
|||
<ul>
|
||||
<li>
|
||||
<strong>Signaler un bug ou une typo</strong> : par mail à
|
||||
<a href="mailto:florian@nocleus.com">florian@nocleus.com</a> ou via une issue sur le
|
||||
<a href="https://git.nocleus.com/florian/info-canicule" rel="noopener">repo Forgejo</a>.
|
||||
<a href="mailto:florian@nocleus.com">florian@nocleus.com</a>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Partager le site</strong> autour de vous, en particulier auprès de personnes fragiles
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue