193 lines
9.7 KiB
TypeScript
193 lines
9.7 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 { CpiSettingsCard } from '@/components/CpiSettingsCard';
|
|
import { calculateDome, type DomeType } from '@/lib/modules/dome';
|
|
import Dome3DViewer from '@/components/three/Dome3D';
|
|
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 DomeModule: React.FC = () => {
|
|
const { vk, cpi } = useWindStore();
|
|
const [diameter, setDiameter] = React.useState(20);
|
|
const [rise, setRise] = React.useState(5);
|
|
const [wallHeight, setWallHeight] = React.useState(8);
|
|
const [type, setType] = React.useState<DomeType>('on-ground');
|
|
|
|
const result = useMemo(() => calculateDome({ d: diameter, f: rise, h: wallHeight, vk, type, cpi }), [diameter, rise, wallHeight, vk, type, cpi]);
|
|
|
|
const handleExportPDF = () => {
|
|
const sections: GenericPDFSection[] = [
|
|
{
|
|
title: 'Geometria da Cúpula',
|
|
type: 'grid',
|
|
gridItems: [
|
|
{ label: 'Tipo', value: type === 'on-ground' ? 'Sobre terreno' : 'Sobre parede cilíndrica' },
|
|
{ label: 'Diâmetro (d)', value: `${diameter} m` },
|
|
{ label: 'Flecha (f)', value: `${rise} m` },
|
|
...(type === 'on-cylinder' ? [{ label: 'Altura da parede', value: `${wallHeight} m` }] : []),
|
|
{ label: 'f/d', value: result.fOverD.toFixed(3) },
|
|
],
|
|
},
|
|
{
|
|
title: 'Coeficientes de Pressão (Cpe)',
|
|
type: 'grid',
|
|
gridItems: [
|
|
{ label: 'Barlavento', value: result.cpeBarlavento.toFixed(2) },
|
|
{ label: 'Topo', value: result.cpeTopo.toFixed(2) },
|
|
{ label: 'Lateral', value: result.cpeLateral.toFixed(2) },
|
|
],
|
|
},
|
|
{
|
|
title: 'Pressões p (kN/m²)',
|
|
type: 'grid',
|
|
gridItems: [
|
|
{ label: 'Barlavento', value: (result.q * (result.cpeBarlavento - cpi)).toFixed(3) },
|
|
{ label: 'Topo', value: (result.q * (result.cpeTopo - cpi)).toFixed(3) },
|
|
{ label: 'Lateral', value: (result.q * (result.cpeLateral - cpi)).toFixed(3) },
|
|
],
|
|
},
|
|
...(result.liftCoefficient > 0 ? [{
|
|
title: 'Força de Sustentação',
|
|
type: 'grid' as const,
|
|
gridItems: [
|
|
{ label: 'Coeficiente', value: result.liftCoefficient.toFixed(2) },
|
|
{ label: 'Força', value: `${result.liftForceKN.toFixed(2)} kN` },
|
|
],
|
|
}] : []),
|
|
];
|
|
exportGenericToPDF('Cúpula', 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">Cúpula</CardTitle>
|
|
<CardDescription>Sec. 6.2.4 — silos, reservatórios, ginásios.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium">Tipo</label>
|
|
<Select value={type} onValueChange={(v) => setType(v as DomeType)}>
|
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="on-ground">Sobre terreno (Tab. 21)</SelectItem>
|
|
<SelectItem value="on-cylinder">Sobre parede cilíndrica (Tab. 22)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<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={5} max={80} step={1} value={[diameter]} onValueChange={(v) => setDiameter(v[0])} />
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between"><label className="text-sm font-medium">Flecha (f)</label><span className="font-mono text-sm">{rise} m</span></div>
|
|
<Slider min={0.5} max={Math.min(diameter / 4, 25)} step={0.5} value={[rise]} onValueChange={(v) => setRise(v[0])} />
|
|
</div>
|
|
{type === 'on-cylinder' && (
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between"><label className="text-sm font-medium">Altura da parede</label><span className="font-mono text-sm">{wallHeight} m</span></div>
|
|
<Slider min={0} max={30} step={0.5} value={[wallHeight]} onValueChange={(v) => setWallHeight(v[0])} />
|
|
</div>
|
|
)}
|
|
<Separator />
|
|
<div className="rounded-md border bg-muted/40 p-3 text-xs space-y-1">
|
|
<div className="flex justify-between"><span>f/d:</span><span className="font-mono">{result.fOverD.toFixed(3)}</span></div>
|
|
<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">{result.q.toFixed(4)} kN/m²</span></div>
|
|
{result.liftCoefficient > 0 && (
|
|
<div className="flex justify-between"><span>F sustentação:</span><span className="font-mono font-semibold">{result.liftForceKN.toFixed(2)} kN</span></div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<CpiSettingsCard />
|
|
</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">f/d = {result.fOverD.toFixed(2)}</Badge>
|
|
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
|
Cpi = {cpi.toFixed(2)}
|
|
</Badge>
|
|
</div>
|
|
<div className="absolute top-4 right-4 z-10">
|
|
<EducationalManual type="dome" params={{ rise, diameter, cpi }} />
|
|
</div>
|
|
<div className="w-full h-full bg-muted/20">
|
|
<Dome3DViewer
|
|
diameter={diameter}
|
|
rise={rise}
|
|
wallHeight={wallHeight}
|
|
cpi={cpi}
|
|
cpeBarlavento={result.cpeBarlavento}
|
|
cpeTopo={result.cpeTopo}
|
|
cpeLateral={result.cpeLateral}
|
|
/>
|
|
</div>
|
|
</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">Coeficientes (Cpe)</CardTitle>
|
|
<CardDescription>Por zona da cúpula</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div className="grid grid-cols-3 gap-2 text-xs">
|
|
<div className="rounded-md border bg-muted/40 p-2 text-center">
|
|
<div className="text-muted-foreground text-[10px]">Barlavento</div>
|
|
<div className="font-mono font-medium text-lg">{result.cpeBarlavento.toFixed(2)}</div>
|
|
</div>
|
|
<div className="rounded-md border bg-muted/40 p-2 text-center">
|
|
<div className="text-muted-foreground text-[10px]">Topo</div>
|
|
<div className="font-mono font-medium text-lg">{result.cpeTopo.toFixed(2)}</div>
|
|
</div>
|
|
<div className="rounded-md border bg-muted/40 p-2 text-center">
|
|
<div className="text-muted-foreground text-[10px]">Lateral</div>
|
|
<div className="font-mono font-medium text-lg">{result.cpeLateral.toFixed(2)}</div>
|
|
</div>
|
|
</div>
|
|
<Separator />
|
|
<div>
|
|
<h4 className="text-xs font-semibold uppercase text-muted-foreground mb-2">Pressões p (kN/m²)</h4>
|
|
<div className="grid grid-cols-3 gap-2 text-xs">
|
|
<div className="rounded-md border bg-muted/40 p-2 text-center">
|
|
<div className="text-[10px] text-muted-foreground">Barlavento</div>
|
|
<div className="font-mono font-medium">{(result.q * (result.cpeBarlavento - cpi)).toFixed(3)}</div>
|
|
</div>
|
|
<div className="rounded-md border bg-muted/40 p-2 text-center">
|
|
<div className="text-[10px] text-muted-foreground">Topo</div>
|
|
<div className="font-mono font-medium">{(result.q * (result.cpeTopo - cpi)).toFixed(3)}</div>
|
|
</div>
|
|
<div className="rounded-md border bg-muted/40 p-2 text-center">
|
|
<div className="text-[10px] text-muted-foreground">Lateral</div>
|
|
<div className="font-mono font-medium">{(result.q * (result.cpeLateral - cpi)).toFixed(3)}</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 DomeModule; |