207 lines
10 KiB
TypeScript
207 lines
10 KiB
TypeScript
import { useState, useMemo } from 'react';
|
|
import { useWindStore } from '@/store/appStore';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@/components/ui/dialog';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { Slider } from '@/components/ui/slider';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Input } from '@/components/ui/input';
|
|
import { searchStations } from '@/lib/stations-lookup';
|
|
import { Settings2 } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { IsopletasModal } from '@/components/IsopletasModal';
|
|
import { InfoV0Modal } from '@/components/InfoV0Modal';
|
|
import { InfoParamModal } from '@/components/InfoParamModal';
|
|
|
|
export function GlobalWindSettingsModal() {
|
|
const {
|
|
v0,
|
|
s1,
|
|
s3,
|
|
s3Group,
|
|
terrainCategory,
|
|
structureClass,
|
|
s2,
|
|
vk,
|
|
q,
|
|
setV0,
|
|
setS1,
|
|
setS3,
|
|
setS3Group,
|
|
setTerrainCategory,
|
|
} = useWindStore();
|
|
|
|
const [stationQuery, setStationQuery] = useState('');
|
|
const [v0Filter, setV0Filter] = useState<number | null>(null);
|
|
|
|
const filteredStations = useMemo(() => {
|
|
let stations = searchStations(stationQuery);
|
|
if (v0Filter !== null) {
|
|
stations = stations.filter((s) => s.v0 === v0Filter);
|
|
}
|
|
return stations;
|
|
}, [stationQuery, v0Filter]);
|
|
|
|
return (
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button variant="outline" size="sm" className="gap-2 bg-background shadow-sm hover:bg-muted/50 border-primary/20 text-primary">
|
|
<Settings2 className="w-4 h-4" />
|
|
<span className="hidden sm:inline">Parâmetros do Vento</span>
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="sm:max-w-[500px] p-4 sm:p-6">
|
|
<DialogHeader>
|
|
<DialogTitle>Parâmetros do Vento</DialogTitle>
|
|
<DialogDescription>
|
|
Defina os parâmetros do vento que afetam todas as estruturas do projeto.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Tabs defaultValue="norma" className="w-full mt-2 min-w-0">
|
|
<TabsList className="flex w-full mb-4 bg-muted p-1 text-xs min-w-0">
|
|
<TabsTrigger value="norma" className="text-xs py-1 px-1.5 flex-1">NBR 6123</TabsTrigger>
|
|
<TabsTrigger value="local" className="text-xs py-1 px-1.5 flex-1">Local</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="norma" className="space-y-4 min-w-0">
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex items-center gap-1.5">
|
|
<label className="text-xs font-medium text-foreground">Velocidade Básica (V₀)</label>
|
|
<InfoParamModal type="v0" />
|
|
</div>
|
|
<span className="text-xs text-muted-foreground font-mono">{v0} m/s</span>
|
|
</div>
|
|
<Slider min={25} max={55} step={1} value={[v0]} onValueChange={(vals) => setV0(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-1.5 mb-1">
|
|
<label className="text-xs font-medium text-foreground">Fator Topográfico (S₁)</label>
|
|
<InfoParamModal type="s1" />
|
|
</div>
|
|
<Select value={s1.toString()} onValueChange={(val) => setS1(Number(val))}>
|
|
<SelectTrigger className="w-full text-xs h-8"><SelectValue placeholder="S₁" /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="0.9">0,9 (Vale profissional protegido)</SelectItem>
|
|
<SelectItem value="1">1,0 (Terreno plano)</SelectItem>
|
|
<SelectItem value="1.1">1,1 (Talude)</SelectItem>
|
|
<SelectItem value="1.2">1,2 (Morro)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-1.5 mb-1">
|
|
<label className="text-xs font-medium text-foreground">Categoria do Terreno (S₂)</label>
|
|
<InfoParamModal type="s2" />
|
|
</div>
|
|
<Select value={terrainCategory} onValueChange={(val) => setTerrainCategory(val as typeof terrainCategory)}>
|
|
<SelectTrigger className="w-full text-xs h-8"><SelectValue placeholder="Categoria" /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="I">I — Superfícies lisas (Mar calmo, lagos, rios)</SelectItem>
|
|
<SelectItem value="II">II — Terrenos abertos em nível (Campos, pastos)</SelectItem>
|
|
<SelectItem value="III">III — Terrenos planos/ondulados c/ obstáculos (Granjas, subúrbios rurais)</SelectItem>
|
|
<SelectItem value="IV">IV — Obstáculos numerosos e próximos (Cidades pequenas/médias)</SelectItem>
|
|
<SelectItem value="V">V — Obstáculos numerosos e altos (Grandes cidades, centros industriais)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="rounded-md border bg-muted/40 p-2 text-[11px] space-y-1">
|
|
<div className="flex justify-between"><span>Classe (maior dimensão):</span><span className="font-mono font-medium">{structureClass}</span></div>
|
|
<div className="flex justify-between"><span>S₂:</span><span className="font-mono font-medium">{s2.toFixed(3)}</span></div>
|
|
<div className="flex justify-between"><span>Vₖ:</span><span className="font-mono font-medium">{vk.toFixed(2)} m/s</span></div>
|
|
<div className="flex justify-between"><span>q:</span><span className="font-mono font-medium">{q.toFixed(4)} kN/m²</span></div>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-1.5 mb-1">
|
|
<label className="text-xs font-medium text-foreground">Grupo Estatístico (S₃)</label>
|
|
<InfoParamModal type="s3" />
|
|
</div>
|
|
<Select value={s3Group.toString()} onValueChange={(val) => setS3Group(Number(val) as 1 | 2 | 3 | 4 | 5)}>
|
|
<SelectTrigger className="w-full text-xs h-8"><SelectValue placeholder="Grupo" /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="1">Grupo 1 — Risco à vida (Hospitais, quartéis) (S₃=1,11)</SelectItem>
|
|
<SelectItem value="2">Grupo 2 — Edificações comuns (Hotéis, residências) (S₃=1,06)</SelectItem>
|
|
<SelectItem value="3">Grupo 3 — Edificações de baixo risco (Comércio, indústrias) (S₃=1,00)</SelectItem>
|
|
<SelectItem value="4">Grupo 4 — Baixo fator humano (Silos, depósitos) (S₃=0,95)</SelectItem>
|
|
<SelectItem value="5">Grupo 5 — Estruturas temporárias (S₃=0,83)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between items-center">
|
|
<label className="text-xs font-medium text-foreground">S₃ customizado</label>
|
|
<span className="text-xs text-muted-foreground font-mono">{s3.toFixed(2)}</span>
|
|
</div>
|
|
<Slider min={0.83} max={1.10} step={0.01} value={[s3]} onValueChange={(vals) => setS3(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="local" className="space-y-3 min-w-0">
|
|
<div className="space-y-2">
|
|
<div className="flex gap-1.5 items-center">
|
|
<Input
|
|
placeholder="Buscar cidade ou estação..."
|
|
value={stationQuery}
|
|
onChange={(e) => setStationQuery(e.target.value)}
|
|
className="h-8 text-xs flex-1"
|
|
/>
|
|
<InfoV0Modal variant="button" buttonText="Divergências?" />
|
|
<IsopletasModal />
|
|
</div>
|
|
|
|
<div className="flex items-center gap-1.5 py-1 overflow-x-auto no-scrollbar">
|
|
<span className="text-[10px] text-muted-foreground mr-1 shrink-0">Filtrar V₀:</span>
|
|
{[null, 30, 35, 40, 45, 50].map((val) => (
|
|
<button
|
|
key={val ?? 'todos'}
|
|
onClick={() => setV0Filter(val)}
|
|
className={`h-6 px-2.5 text-[10px] rounded-full border transition-all font-mono font-medium shrink-0 ${
|
|
v0Filter === val
|
|
? 'bg-primary text-primary-foreground border-primary shadow-sm'
|
|
: 'bg-background text-muted-foreground hover:text-foreground border-border hover:bg-muted/50'
|
|
}`}
|
|
>
|
|
{val ? `${val} m/s` : 'Todos'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="max-h-[200px] overflow-y-auto rounded-md border divide-y">
|
|
{filteredStations.map((s) => (
|
|
<button
|
|
key={s.id}
|
|
onClick={() => setV0(s.v0)}
|
|
className="w-full text-left px-2 py-1.5 hover:bg-muted/60 transition-colors"
|
|
>
|
|
<div className="flex justify-between items-center">
|
|
<span className="font-medium text-xs truncate max-w-[150px]">{s.nome}</span>
|
|
<Badge variant="outline" className="font-mono text-[10px] py-0 px-1 font-semibold">V₀ = {s.v0} m/s</Badge>
|
|
</div>
|
|
<div className="text-[10px] text-muted-foreground">{s.latitude} · {s.longitude} · {s.altitude} m</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<p className="text-[10px] leading-tight text-muted-foreground">
|
|
Selecionar uma estação ajusta V₀. Você pode sobrescrever manualmente na aba NBR.
|
|
</p>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|