123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
'use client'
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Slider } from '@/components/ui/slider'
|
|
import { CompassSelector } from '@/components/ui/compass-selector'
|
|
import { useThresholdStore } from '@/store/threshold-store'
|
|
import { useEffect } from 'react'
|
|
|
|
interface ThresholdControlsProps {
|
|
className?: string
|
|
}
|
|
|
|
export function ThresholdControls({ className }: ThresholdControlsProps) {
|
|
const {
|
|
speedMin,
|
|
speedMax,
|
|
dirCenter,
|
|
dirRange,
|
|
setSpeedRange,
|
|
setDirCenter,
|
|
setDirRange,
|
|
initFromURL,
|
|
} = useThresholdStore()
|
|
|
|
// Initialize from URL on mount
|
|
useEffect(() => {
|
|
initFromURL()
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [])
|
|
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<CardTitle>Threshold Controls</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-8">
|
|
{/* Wind Speed Range */}
|
|
<div className="space-y-4">
|
|
<div>
|
|
<div className="mb-2">
|
|
<label className="text-sm font-medium">Wind Speed Range</label>
|
|
</div>
|
|
<div className="relative">
|
|
<Slider
|
|
value={[speedMin, speedMax]}
|
|
onValueChange={(values) => setSpeedRange(values[0], values[1])}
|
|
min={0}
|
|
max={30}
|
|
step={0.5}
|
|
minStepsBetweenThumbs={1}
|
|
className="min-h-[44px] py-4"
|
|
aria-label="Wind speed range threshold"
|
|
/>
|
|
<div className="flex justify-between text-xs font-medium mt-1">
|
|
<span style={{ position: 'absolute', left: `${(speedMin / 30) * 100}%`, transform: 'translateX(-50%)' }}>
|
|
{speedMin} mph
|
|
</span>
|
|
<span style={{ position: 'absolute', left: `${(speedMax / 30) * 100}%`, transform: 'translateX(-50%)' }}>
|
|
{speedMax} mph
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Wind Direction Center */}
|
|
<div className="space-y-4">
|
|
<div>
|
|
<div className="mb-4">
|
|
<label className="text-sm font-medium">Direction Center</label>
|
|
</div>
|
|
<div className="flex justify-center">
|
|
<CompassSelector
|
|
value={dirCenter}
|
|
onChange={setDirCenter}
|
|
range={dirRange}
|
|
size={220}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Wind Direction Range */}
|
|
<div className="space-y-4">
|
|
<div>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<label className="text-sm font-medium">Direction Range</label>
|
|
<span className="text-sm text-muted-foreground">
|
|
±{dirRange}°
|
|
</span>
|
|
</div>
|
|
<Slider
|
|
value={[dirRange]}
|
|
onValueChange={(values) => setDirRange(values[0])}
|
|
min={5}
|
|
max={90}
|
|
step={5}
|
|
className="min-h-[44px] py-4"
|
|
aria-label="Wind direction range threshold"
|
|
/>
|
|
<div className="text-xs text-muted-foreground mt-1">
|
|
Acceptable range: {(dirCenter - dirRange + 360) % 360}° to {(dirCenter + dirRange) % 360}°
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-4 border-t">
|
|
<p className="text-xs text-muted-foreground">
|
|
Thresholds are saved to URL and applied to charts automatically.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
// Helper function to convert degrees to compass direction
|
|
function getCompassDirection(degrees: number): string {
|
|
const directions = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
|
|
const index = Math.round(degrees / 22.5) % 16
|
|
return directions[index]
|
|
}
|