78ede01d11
ci/woodpecker/push/woodpecker Pipeline was successful
- 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>
137 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|