250 lines
12 KiB
TypeScript
250 lines
12 KiB
TypeScript
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; |