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

221 lines
11 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 { useWindStore } from '@/store/appStore';
import { calculateTower, type TowerSection, type TowerBarType } from '@/lib/modules/tower';
import Tower3DViewer from '@/components/three/Tower3D';
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 TowerModule: React.FC = () => {
const { q } = useWindStore();
const [section, setSection] = React.useState<TowerSection>('square');
const [barType, setBarType] = React.useState<TowerBarType>('flat');
const [baseWidth, setBaseWidth] = React.useState(3);
const [height, setHeight] = React.useState(30);
const [panels, setPanels] = React.useState(6);
const [phi, setPhi] = React.useState(0.3);
const [alphaWind, setAlphaWind] = React.useState<0 | 45 | 90>(0);
const result = useMemo(() => {
const aFace = baseWidth * height;
return calculateTower({
section,
barType,
phi,
aFace,
alphaWind,
q,
});
}, [section, barType, phi, baseWidth, height, alphaWind, q]);
const handleExportPDF = () => {
const sections: GenericPDFSection[] = [
{
title: 'Geometria da Torre',
type: 'grid',
gridItems: [
{ label: 'Seção', value: section === 'square' ? 'Quadrada' : 'Triangular' },
{ label: 'Tipo de barra', value: barType === 'flat' ? 'Faces planas' : 'Circular' },
{ label: 'Largura da base', value: `${baseWidth} m` },
{ label: 'Altura', value: `${height} m` },
{ label: 'Tramos', value: String(panels) },
{ label: 'φ (área exposta)', value: phi.toFixed(2) },
{ label: 'Ângulo do vento', value: `${alphaWind}°` },
],
},
{
title: 'Coeficientes',
type: 'grid',
gridItems: [
{ label: 'Ca', value: result.ca.toFixed(2) },
{ label: 'Ca efetivo', value: result.caEff.toFixed(2) },
{ label: 'Kα', value: result.kAlpha.toFixed(2) },
],
},
{
title: 'Forças',
type: 'grid',
gridItems: [
{ label: 'Força Total', value: `${result.forceKN.toFixed(2)} kN` },
{ label: 'Face I', value: `${result.faceComponents.faceI.toFixed(2)} kN` },
{ label: 'Face II', value: `${result.faceComponents.faceII.toFixed(2)} kN` },
{ label: 'Face III', value: `${result.faceComponents.faceIII.toFixed(2)} kN` },
{ label: 'Face IV', value: `${result.faceComponents.faceIV.toFixed(2)} kN` },
],
},
];
exportGenericToPDF('Torre Reticulada', 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">Torre Reticulada</CardTitle>
<CardDescription>Sec. 8.5 faces planas ou circulares.</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium">Seção</label>
<Select value={section} onValueChange={(v) => setSection(v as TowerSection)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="square">Quadrada</SelectItem>
<SelectItem value="triangular">Triangular equilátera</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Tipo de Barra</label>
<Select value={barType} onValueChange={(v) => setBarType(v as TowerBarType)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="flat">Faces planas</SelectItem>
<SelectItem value="circular">Circular</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Largura da base</label><span className="font-mono text-sm">{baseWidth} m</span></div>
<Slider min={1} max={10} step={0.5} value={[baseWidth]} onValueChange={(v) => setBaseWidth(v[0])} />
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Altura</label><span className="font-mono text-sm">{height} m</span></div>
<Slider min={5} max={100} step={1} value={[height]} onValueChange={(v) => setHeight(v[0])} />
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Tramos</label><span className="font-mono text-sm">{panels}</span></div>
<Slider min={2} max={20} step={1} value={[panels]} onValueChange={(v) => setPanels(v[0])} />
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">φ (área exposta)</label><span className="font-mono text-sm">{phi.toFixed(2)}</span></div>
<Slider min={0.05} max={1} step={0.05} value={[phi]} onValueChange={(v) => setPhi(v[0])} />
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Ângulo do vento</label>
<Select value={String(alphaWind)} onValueChange={(v) => setAlphaWind(Number(v) as 0 | 45 | 90)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="0">0° ( face)</SelectItem>
<SelectItem value="45">45° (oblíquo)</SelectItem>
<SelectItem value="90">90° (diagonal)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="rounded-md border bg-muted/40 p-3 text-xs space-y-1">
<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">{section === 'square' ? 'Quadrada' : 'Triangular'}</Badge>
<Badge variant="secondary" className="bg-background/80 text-foreground border border-border/50 backdrop-blur-sm shadow-sm">α = {alphaWind}°</Badge>
</div>
<div className="absolute top-4 right-4 z-10">
<EducationalManual type="tower" params={{ section, alphaWind }} />
</div>
<Tower3DViewer
section={section}
barType={barType}
baseWidth={baseWidth}
height={height}
panels={panels}
phi={phi}
alphaWind={alphaWind}
forceKN={result.forceKN}
/>
</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">Resultados</CardTitle>
<CardDescription>Coeficientes e forças na torre</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<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">Ca</div>
<div className="font-mono font-medium">{result.ca.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Ca efetivo</div>
<div className="font-mono font-medium">{result.caEff.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">{result.kAlpha.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Força total</div>
<div className="font-mono font-medium">{result.forceKN.toFixed(2)} kN</div>
</div>
</div>
<div className="space-y-2">
<h4 className="text-xs font-semibold uppercase text-muted-foreground">Componentes por face</h4>
<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">Face I</div>
<div className="font-mono font-medium">{result.faceComponents.faceI.toFixed(2)} kN</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Face II</div>
<div className="font-mono font-medium">{result.faceComponents.faceII.toFixed(2)} kN</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Face III</div>
<div className="font-mono font-medium">{result.faceComponents.faceIII.toFixed(2)} kN</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Face IV</div>
<div className="font-mono font-medium">{result.faceComponents.faceIV.toFixed(2)} kN</div>
</div>
</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 TowerModule;