init
This commit is contained in:
122
frontend/components/weather/threshold-controls.tsx
Normal file
122
frontend/components/weather/threshold-controls.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
'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]
|
||||
}
|
||||
Reference in New Issue
Block a user