fix: label "Veille" pour scores low 15-30, marqueur ambre, ETA sous surveillance

- scoreLabel() : low <15 → "OK", low ≥15 → "Veille", cohérence avec valeur numérique
- etaLabel() : score ≥15 sans ETA → "Sargasses en surveillance" (eta--watch)
- SpotMarker : étoile ambre pour level=low + scoreValue≥15 vs étoile verte pure OK
- spotScores stocke {level, value} au lieu de level seul

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-08 01:58:11 -04:00
parent 5dfa000247
commit 364bb5dfcc
3 changed files with 36 additions and 23 deletions

View File

@@ -98,7 +98,7 @@ const makeLayerStyles = (isPrediction, opacity) => ({
}); });
// ── SVG Markers ─────────────────────────────────────────────────────────── // ── SVG Markers ───────────────────────────────────────────────────────────
function SpotMarker({ level, isSelected, name }) { function SpotMarker({ level, scoreValue, isSelected, name }) {
const size = isSelected ? 36 : 28; const size = isSelected ? 36 : 28;
const shadow = 'filter: drop-shadow(2px 3px 0px rgba(0,0,0,0.35))'; const shadow = 'filter: drop-shadow(2px 3px 0px rgba(0,0,0,0.35))';
@@ -122,15 +122,18 @@ function SpotMarker({ level, isSelected, name }) {
</div> </div>
); );
if (level === 'low') return ( if (level === 'low') {
<div className={`smap-marker smap-marker--low${isSelected ? ' smap-marker--selected' : ''}`} const isWatch = (scoreValue ?? 0) >= 15;
return (
<div className={`smap-marker smap-marker--${isWatch ? 'watch' : 'low'}${isSelected ? ' smap-marker--selected' : ''}`}
title={name} style={{ width: size, height: size }}> title={name} style={{ width: size, height: size }}>
<svg width={size} height={size} viewBox="0 0 28 28" style={{ filter: 'drop-shadow(2px 3px 0px rgba(0,0,0,0.35))' }}> <svg width={size} height={size} viewBox="0 0 28 28" style={{ filter: 'drop-shadow(2px 3px 0px rgba(0,0,0,0.35))' }}>
<circle cx="14" cy="14" r="12" fill="#22c55e" stroke="#fff" strokeWidth="2.5"/> <circle cx="14" cy="14" r="12" fill={isWatch ? '#f59e0b' : '#22c55e'} stroke="#fff" strokeWidth="2.5"/>
<text x="14" y="19" textAnchor="middle" fontSize="14" fill="white" fontFamily="sans-serif"></text> <text x="14" y="19" textAnchor="middle" fontSize="14" fill="white" fontFamily="sans-serif"></text>
</svg> </svg>
</div> </div>
); );
}
// No score yet // No score yet
const s = isSelected ? 22 : 14; const s = isSelected ? 22 : 14;
@@ -191,7 +194,8 @@ export default function SargassesMap({ onSpotSelect, selectedSpotId, activeStep,
api.spots.score(s.id) api.spots.score(s.id)
.then(d => { .then(d => {
const level = d?.score?.level; const level = d?.score?.level;
if (level) setSpotScores(prev => ({ ...prev, [s.id]: level })); const value = d?.score?.value ?? 0;
if (level) setSpotScores(prev => ({ ...prev, [s.id]: { level, value } }));
}) })
.catch(() => {}); .catch(() => {});
}); });
@@ -292,7 +296,9 @@ export default function SargassesMap({ onSpotSelect, selectedSpotId, activeStep,
const coords = getCoords(spot); const coords = getCoords(spot);
if (!coords) return null; if (!coords) return null;
const isSelected = spot.id === selectedSpotId; const isSelected = spot.id === selectedSpotId;
const level = spotScores[spot.id]; const sc = spotScores[spot.id];
const level = sc?.level;
const scoreValue = sc?.value ?? 0;
return ( return (
<Marker <Marker
@@ -302,7 +308,7 @@ export default function SargassesMap({ onSpotSelect, selectedSpotId, activeStep,
anchor="center" anchor="center"
onClick={() => onSpotSelect(spot)} onClick={() => onSpotSelect(spot)}
> >
<SpotMarker level={level} isSelected={isSelected} name={spot.name} /> <SpotMarker level={level} scoreValue={scoreValue} isSelected={isSelected} name={spot.name} />
</Marker> </Marker>
); );
})} })}

View File

@@ -140,6 +140,7 @@
.eta--now { background: #fee2e2; color: #b91c1c; border-color: #fca5a5; } .eta--now { background: #fee2e2; color: #b91c1c; border-color: #fca5a5; }
.eta--soon { background: #fef9c3; color: #92400e; border-color: #fde68a; } .eta--soon { background: #fef9c3; color: #92400e; border-color: #fde68a; }
.eta--later { background: #fff7ed; color: #9a3412; border-color: #fed7aa; } .eta--later { background: #fff7ed; color: #9a3412; border-color: #fed7aa; }
.eta--watch { background: #fef3c7; color: #78350f; border-color: #fde68a; }
.eta--safe { background: #dcfce7; color: #15803d; border-color: #86efac; } .eta--safe { background: #dcfce7; color: #15803d; border-color: #86efac; }
.spot-panel__eta-icon { font-size: 20px; line-height: 1; flex-shrink: 0; } .spot-panel__eta-icon { font-size: 20px; line-height: 1; flex-shrink: 0; }

View File

@@ -3,7 +3,12 @@ import { api } from '../../api/client';
import usePushSubscription from '../../hooks/usePushSubscription'; import usePushSubscription from '../../hooks/usePushSubscription';
import './SpotPanel.css'; import './SpotPanel.css';
const LEVEL_LABEL = { low: 'OK', medium: 'Risque', high: 'Impact' }; function scoreLabel(value, level) {
if (level === 'high') return 'Impact';
if (level === 'medium') return 'Risque';
if (value >= 15) return 'Veille';
return 'OK';
}
const TYPE_ICON = { beach: '🏖', port: '⚓', surf: '🏄', fishing: '🎣' }; const TYPE_ICON = { beach: '🏖', port: '⚓', surf: '🏄', fishing: '🎣' };
const TREND_CONFIG = { const TREND_CONFIG = {
@@ -23,12 +28,13 @@ function computeEta(score, horizons) {
return null; return null;
} }
function etaLabel(etaHours) { function etaLabel(etaHours, score) {
if (etaHours === 0) return { icon: '🚨', label: 'Impact en cours', cls: 'eta--now' }; if (etaHours === 0) return { icon: '🚨', label: 'Impact en cours', cls: 'eta--now' };
if (etaHours === 6) return { icon: '⚠️', label: 'Impact prévu dans ~6h', cls: 'eta--soon' }; if (etaHours === 6) return { icon: '⚠️', label: 'Impact prévu dans ~6h', cls: 'eta--soon' };
if (etaHours === 12) return { icon: '⚠️', label: 'Impact prévu dans ~12h', cls: 'eta--soon' }; if (etaHours === 12) return { icon: '⚠️', label: 'Impact prévu dans ~12h', cls: 'eta--soon' };
if (etaHours === 24) return { icon: '🕐', label: 'Impact prévu dans ~24h', cls: 'eta--later' }; if (etaHours === 24) return { icon: '🕐', label: 'Impact prévu dans ~24h', cls: 'eta--later' };
if (etaHours === 48) return { icon: '🕐', label: 'Impact prévu dans ~48h', cls: 'eta--later' }; if (etaHours === 48) return { icon: '🕐', label: 'Impact prévu dans ~48h', cls: 'eta--later' };
if ((score ?? 0) >= 15) return { icon: '👁', label: 'Sargasses en surveillance', cls: 'eta--watch' };
return { icon: '✅', label: 'Aucun impact prévu (48h)', cls: 'eta--safe' }; return { icon: '✅', label: 'Aucun impact prévu (48h)', cls: 'eta--safe' };
} }
@@ -94,7 +100,7 @@ export default function SpotPanel({ spot, activeStep, onClose }) {
: ( : (
<> <>
{activeStep === 'now' && (() => { {activeStep === 'now' && (() => {
const eta = etaLabel(computeEta(data?.score, data?.horizons)); const eta = etaLabel(computeEta(data?.score, data?.horizons), data?.score?.value);
return ( return (
<div className={`spot-panel__eta ${eta.cls}`}> <div className={`spot-panel__eta ${eta.cls}`}>
<span className="spot-panel__eta-icon">{eta.icon}</span> <span className="spot-panel__eta-icon">{eta.icon}</span>
@@ -105,7 +111,7 @@ export default function SpotPanel({ spot, activeStep, onClose }) {
<div className={`spot-panel__gauge gauge--${lvl}`}> <div className={`spot-panel__gauge gauge--${lvl}`}>
<div className="spot-panel__gauge-top"> <div className="spot-panel__gauge-top">
<span className="spot-panel__gauge-label">{LEVEL_LABEL[lvl] ?? lvl}</span> <span className="spot-panel__gauge-label">{scoreLabel(displayScore.value, lvl)}</span>
<span className="spot-panel__gauge-score"> <span className="spot-panel__gauge-score">
{displayScore.value} {displayScore.value}
<span className="spot-panel__gauge-max">/100</span> <span className="spot-panel__gauge-max">/100</span>