Files
BrainWind/app/src/pages/BarSelectorModule.tsx
T

250 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useMemo } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Slider } from '@/components/ui/slider';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { useWindStore } from '@/store/appStore';
import { calculateFlatBarForce, type FlatBarSection } from '@/lib/nbr-tables/table-26';
import { calculateCircleBarForce, reynoldsBar } from '@/lib/nbr-tables/table-27';
import Bar3DViewer from '@/components/three/Bar3D';
import SceneCapturePanel from '../components/SceneCapturePanel';
import ExportMenu from '../components/ExportMenu';
import { EducationalManual } from '@/components/EducationalManual';
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
const BarSelectorModule: React.FC = () => {
const { vk, q } = useWindStore();
const [barType, setBarType] = React.useState<'flat' | 'circular'>('flat');
const [section, setSection] = React.useState<FlatBarSection>('l');
const [alpha, setAlpha] = React.useState(45);
const [width, setWidth] = React.useState(0.1);
const [length, setLength] = React.useState(5);
const [diameter, setDiameter] = React.useState(0.05);
const flatResult = useMemo(() => {
if (barType !== 'flat') return null;
return calculateFlatBarForce({ section, alpha, width, length, q });
}, [barType, section, alpha, width, length, q]);
const circleResult = useMemo(() => {
if (barType !== 'circular') return null;
return calculateCircleBarForce({ d: diameter, length, vk, q });
}, [barType, diameter, length, vk, q]);
const reCircle = useMemo(() => reynoldsBar(vk, diameter), [vk, diameter]);
const handleExportPDF = () => {
const sections: GenericPDFSection[] = [
{
title: 'Geometria da Barra',
type: 'grid',
gridItems: [
{ label: 'Tipo', value: barType === 'flat' ? 'Faces planas' : 'Circular' },
{ label: 'Comprimento ()', value: `${length} m` },
...(barType === 'flat'
? [
{ label: 'Seção transversal', value: section },
{ label: 'Ângulo (α)', value: `${alpha}°` },
{ label: 'Largura (c)', value: `${width} m` },
]
: [
{ label: 'Diâmetro (d)', value: `${diameter} m` },
{ label: 'Reynolds (Re)', value: reCircle.toExponential(2) },
{
label: 'Regime',
value: reCircle < 4.2e5 ? 'Subcrítico' : reCircle < 2.3e6 ? 'Crítico' : 'Supercrítico',
},
]),
],
},
{
title: 'Forças e Coeficientes',
type: 'grid',
gridItems:
barType === 'flat' && flatResult
? [
{ label: 'Coeficiente Cx', value: flatResult.cx.toFixed(2) },
{ label: 'Coeficiente Cy', value: flatResult.cy.toFixed(2) },
{ label: 'Fator K', value: flatResult.kFactor.toFixed(2) },
{ label: 'Força Fx', value: `${flatResult.fxKN.toFixed(3)} kN` },
{ label: 'Força Fy', value: `${flatResult.fyKN.toFixed(3)} kN` },
]
: circleResult
? [
{ label: 'Coeficiente Ca', value: circleResult.ca.toFixed(2) },
{ label: 'Regime (tabela)', value: circleResult.regime },
{ label: 'Fator K', value: circleResult.kFactor.toFixed(2) },
{ label: 'Força de Arrasto', value: `${circleResult.forceKN.toFixed(3)} kN` },
]
: [],
},
];
exportGenericToPDF('Barra Prismática', sections);
};
return (
<div className="flex flex-col lg:flex-row h-full w-full gap-4 p-4 lg:p-6 bg-muted/30">
{/* Coluna Esquerda: Controles */}
<div className="w-full lg:w-80 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
<Card className="shadow-sm border-border">
<CardHeader className="pb-3">
<CardTitle className="text-lg">Barra Prismática</CardTitle>
<CardDescription>Sec. 8.1 faces planas (Tab. 26) ou circulares (Tab. 27).</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium">Tipo de Barra</label>
<Select value={barType} onValueChange={(v) => setBarType(v as 'flat' | 'circular')}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="flat">Faces planas</SelectItem>
<SelectItem value="circular">Circular</SelectItem>
</SelectContent>
</Select>
</div>
{barType === 'flat' && (
<>
<div className="space-y-2">
<label className="text-sm font-medium">Seção Transversal</label>
<Select value={section} onValueChange={(v) => setSection(v as FlatBarSection)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="placa">Placa</SelectItem>
<SelectItem value="l">Perfil L (cantoneira)</SelectItem>
<SelectItem value="t">Perfil T</SelectItem>
<SelectItem value="i">Perfil I</SelectItem>
<SelectItem value="rectangle">Retângulo</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Ângulo α</label><span className="font-mono text-sm">{alpha}°</span></div>
<Slider min={0} max={180} step={5} value={[alpha]} onValueChange={(v) => setAlpha(v[0])} />
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Largura c</label><span className="font-mono text-sm">{width} m</span></div>
<Slider min={0.05} max={1} step={0.01} value={[width]} onValueChange={(v) => setWidth(v[0])} />
</div>
</>
)}
{barType === 'circular' && (
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Diâmetro d</label><span className="font-mono text-sm">{diameter} m</span></div>
<Slider min={0.01} max={0.5} step={0.01} value={[diameter]} onValueChange={(v) => setDiameter(v[0])} />
<div className="rounded-md border bg-muted/40 p-2 text-xs">
Re = {reCircle.toLocaleString('pt-BR')} · regime: {reCircle < 4.2e5 ? 'subcrítico' : reCircle < 2.3e6 ? 'crítico' : 'supercrítico'}
</div>
</div>
)}
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Comprimento </label><span className="font-mono text-sm">{length} m</span></div>
<Slider min={1} max={30} step={0.5} value={[length]} onValueChange={(v) => setLength(v[0])} />
</div>
<div className="rounded-md border bg-muted/40 p-3 text-xs space-y-1">
<div className="flex justify-between"><span>Vₖ:</span><span className="font-mono">{vk.toFixed(2)} m/s</span></div>
<div className="flex justify-between"><span>q:</span><span className="font-mono">{q.toFixed(4)} kN/m²</span></div>
</div>
</CardContent>
</Card>
</div>
{/* Coluna Central: 3D */}
<div className="flex-1 flex flex-col min-h-[500px] lg:min-h-0 bg-background rounded-xl border border-border shadow-sm overflow-hidden relative">
<div className="absolute top-4 left-4 z-10 flex gap-2">
<Badge variant="secondary" className="bg-background/80 text-foreground border border-border/50 backdrop-blur-sm shadow-sm">α = {alpha}°</Badge>
</div>
<div className="absolute top-4 right-4 z-10">
<EducationalManual type="bar" params={{ barType, section, alpha }} />
</div>
<Bar3DViewer
barType={barType}
section={barType === 'flat' ? section : undefined}
diameter={barType === 'circular' ? diameter : undefined}
width={barType === 'flat' ? width : undefined}
length={length}
alpha={alpha}
fxKN={flatResult?.fxKN ?? 0}
fyKN={flatResult?.fyKN ?? 0}
cx={flatResult?.cx ?? 0}
/>
</div>
{/* Coluna Direita: Resultados */}
<div className="w-full lg:w-96 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
<Card className="shadow-sm border-border">
<CardHeader className="pb-3">
<CardTitle className="text-lg">Forças Cx e Cy</CardTitle>
<CardDescription>Com fator K (Tab. 28) aplicado</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{flatResult && (
<>
<div className="grid grid-cols-3 gap-2 text-sm">
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Cx</div>
<div className="font-mono font-medium">{flatResult.cx.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Cy</div>
<div className="font-mono font-medium">{flatResult.cy.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">K</div>
<div className="font-mono font-medium">{flatResult.kFactor.toFixed(2)}</div>
</div>
</div>
<Separator />
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Fx</div>
<div className="font-mono font-medium">{flatResult.fxKN.toFixed(3)} kN</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Fy</div>
<div className="font-mono font-medium">{flatResult.fyKN.toFixed(3)} kN</div>
</div>
</div>
</>
)}
{circleResult && (
<>
<div className="grid grid-cols-3 gap-2 text-sm">
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Ca</div>
<div className="font-mono font-medium">{circleResult.ca.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Regime</div>
<div className="font-mono font-medium text-xs">{circleResult.regime}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">K</div>
<div className="font-mono font-medium">{circleResult.kFactor.toFixed(2)}</div>
</div>
</div>
<Separator />
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Força de arrasto</div>
<div className="font-mono font-medium text-lg">{circleResult.forceKN.toFixed(3)} kN</div>
</div>
</>
)}
</CardContent>
</Card>
<div className="flex justify-center bg-background rounded-xl border border-border shadow-sm p-4">
<ExportMenu onExportPDF={handleExportPDF} />
</div>
<SceneCapturePanel />
</div>
</div>
);
};
export default BarSelectorModule;