feat: unified 0 and 90 degree PDF envelope and category descriptors
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
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 { 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 backdrop-blur-sm">{section === 'square' ? 'Quadrada' : 'Triangular'}</Badge>
|
||||
<Badge variant="secondary" className="bg-background/80 backdrop-blur-sm">α = {alphaWind}°</Badge>
|
||||
</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;
|
||||
Reference in New Issue
Block a user