Files
g1flux/src/components/PeriodSelector.tsx
T
syoul 78ede01d11
ci/woodpecker/push/woodpecker Pipeline was successful
feat: déplacer bouton ℹ hors du PeriodSelector → bouton flottant isolé
- Sous ☰ sur mobile (top-16 left-4), top-4 left-4 sur desktop
- PeriodSelector : suppression prop onInfo + bouton ℹ intégré

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:12:24 +01:00

137 lines
4.5 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
interface PeriodSelectorProps {
value: number;
onChange: (days: number) => void;
animationActive: boolean;
onAnimate: () => void;
viewMode: 'heatmap' | 'flow';
onViewModeChange: (mode: 'heatmap' | 'flow') => void;
geoPercent?: number | null;
}
const PERIODS = [
{ label: '24h', days: 1 },
{ label: '7 jours', days: 7 },
{ label: '30 jours', days: 30 },
];
const PRESET_DAYS = new Set([1, 7, 30]);
export function PeriodSelector({ value, onChange, animationActive, onAnimate, viewMode, onViewModeChange, geoPercent }: PeriodSelectorProps) {
const [customOpen, setCustomOpen] = useState(false);
const [inputVal, setInputVal] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
// Ouvre le champ custom avec la valeur courante pré-remplie
const openCustom = () => {
setInputVal(PRESET_DAYS.has(value) ? '' : String(value));
setCustomOpen(true);
};
useEffect(() => {
if (customOpen) inputRef.current?.focus();
}, [customOpen]);
const commit = () => {
const n = parseInt(inputVal, 10);
if (n >= 1 && n <= 365) onChange(n);
setCustomOpen(false);
};
const isCustomActive = !PRESET_DAYS.has(value);
return (
<div className="flex flex-wrap gap-1 bg-[#0f1016] border border-[#2e2f3a] rounded-lg p-1 items-center max-w-[calc(100vw-2rem)]">
{PERIODS.map(({ label, days }) => (
<button
key={days}
onClick={() => { onChange(days); setCustomOpen(false); }}
className={`
px-3 py-2.5 sm:py-1.5 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer
${value === days && !customOpen
? 'bg-[#d4a843] text-[#0a0b0f] shadow-[0_0_12px_rgba(212,168,67,0.4)]'
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
}
`}
>
{label}
</button>
))}
<div className="w-px mx-1 bg-[#2e2f3a] self-stretch" />
{/* Bouton Personnaliser + champ inline */}
{customOpen ? (
<div className="flex items-center gap-1">
<input
ref={inputRef}
type="number"
min={1}
max={365}
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') commit();
if (e.key === 'Escape') setCustomOpen(false);
}}
onBlur={commit}
placeholder="jours"
className="w-16 px-2 py-1 text-sm bg-[#1a1b23] border border-[#d4a843] rounded-md text-[#d4a843] text-center focus:outline-none tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
/>
<span className="text-[#6b7280] text-xs">j</span>
</div>
) : (
<button
onClick={openCustom}
className={`
px-3 py-2.5 sm:py-1.5 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer
${isCustomActive
? 'bg-[#d4a843] text-[#0a0b0f] shadow-[0_0_12px_rgba(212,168,67,0.4)]'
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
}
`}
>
{isCustomActive ? `${value} jours` : 'Personnaliser'}
</button>
)}
<div className="w-px mx-1 bg-[#2e2f3a] self-stretch" />
<button
onClick={onAnimate}
className={`
px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer
${animationActive
? 'bg-[#d4a843] text-[#0a0b0f] shadow-[0_0_12px_rgba(212,168,67,0.4)]'
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
}
`}
>
Animer
</button>
<div className="w-px mx-1 bg-[#2e2f3a] self-stretch" />
<button
onClick={() => onViewModeChange(viewMode === 'heatmap' ? 'flow' : 'heatmap')}
className={`
px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer
${viewMode === 'flow'
? 'bg-[#d4a843] text-[#0a0b0f] shadow-[0_0_12px_rgba(212,168,67,0.4)]'
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
}
`}
>
{viewMode === 'flow' ? '⊙ Heatmap' : '◉ Flux'}
</button>
{geoPercent != null && (
<span className="text-[10px] font-mono text-white px-1 shrink-0">
{geoPercent}% Tx géoloc.
</span>
)}
</div>
);
}