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

197 lines
10 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 { calculateSign } from '@/lib/nbr-tables/table-23';
import Sign3DViewer from '@/components/three/Sign3D';
import SceneCapturePanel from '../components/SceneCapturePanel';
import ExportMenu from '../components/ExportMenu';
import { EducationalManual } from '@/components/EducationalManual';
import { PressureLegend } from '@/components/PressureLegend';
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
const SignModule: React.FC = () => {
const { q } = useWindStore();
const [length, setLength] = React.useState(8);
const [height, setHeight] = React.useState(3);
const [alpha, setAlpha] = React.useState<90 | 50>(90);
const [hasEndPlates, setHasEndPlates] = React.useState(true);
const [groundClearance, setGroundClearance] = React.useState(0.5);
const result = useMemo(
() =>
calculateSign({ length, height, alpha, hasEndPlates, groundClearance }, q),
[length, height, alpha, hasEndPlates, groundClearance, q],
);
const handleExportPDF = () => {
const sections: GenericPDFSection[] = [
{
title: 'Geometria do Muro/Placa',
type: 'grid',
gridItems: [
{ label: 'Comprimento ()', value: `${length} m` },
{ label: 'Altura (hₐ)', value: `${height} m` },
{ label: 'Distância do solo', value: `${groundClearance} m` },
{ label: '/hₐ', value: result.lhRatio.toFixed(2) },
{ label: 'Placas de extremidade', value: hasEndPlates ? 'Sim' : 'Não' },
{ label: 'Ângulo de incidência (α)', value: `${alpha}°` },
],
},
{
title: 'Força Resultante',
type: 'grid',
gridItems: [
{ label: 'Área Efetiva', value: `${result.areaEffective.toFixed(1)} m²` },
{ label: 'Coeficiente de Força (Cf)', value: result.cf.toFixed(2) },
{ label: 'Excentricidade (e)', value: `${result.applicationPoint.toFixed(2)} m` },
{ label: 'Força F', value: `${result.forceKN.toFixed(2)} kN` },
],
},
{
title: 'Momento de Tombamento (Estimado)',
type: 'grid',
gridItems: [
{ label: 'Em relação à base da placa', value: `${result.momentBaseKNm.toFixed(2)} kNm` },
{ label: 'Em relação ao solo', value: `${result.momentGroundKNm.toFixed(2)} kNm` },
],
},
];
exportGenericToPDF('Muros e Placas', 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">Muros e Placas</CardTitle>
<CardDescription>Sec. 7.1 vento perpendicular à face.</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<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={2} max={30} step={0.5} value={[length]} onValueChange={(v) => setLength(v[0])} />
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Altura (hₐ)</label><span className="font-mono text-sm">{height} m</span></div>
<Slider min={1} max={10} step={0.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">Distância do solo</label><span className="font-mono text-sm">{groundClearance} m</span></div>
<Slider min={0} max={5} step={0.1} value={[groundClearance]} onValueChange={(v) => setGroundClearance(v[0])} />
</div>
<Separator />
<div className="space-y-2">
<label className="text-sm font-medium">Ângulo de Incidência</label>
<Select value={alpha.toString()} onValueChange={(v) => setAlpha(Number(v) as 90 | 50)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="90">90° (perpendicular)</SelectItem>
<SelectItem value="50">50° (oblíquo)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Placas de Extremidade</label>
<Select value={hasEndPlates ? 'yes' : 'no'} onValueChange={(v) => setHasEndPlates(v === 'yes')}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="yes">Sim (/hₐ &lt; 60)</SelectItem>
<SelectItem value="no">Não (escoamento 2D)</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 className="flex justify-between"><span>/hₐ:</span><span className="font-mono">{result.lhRatio.toFixed(2)}</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 text-foreground border border-border/80 shadow-sm">F = {result.forceKN.toFixed(2)} kN</Badge>
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm text-foreground border border-border/80 shadow-sm">Cf = {result.cf.toFixed(2)}</Badge>
</div>
<div className="absolute top-4 right-4 z-10">
<EducationalManual type="sign" params={{ length, height, alpha, hasEndPlates }} />
</div>
<PressureLegend />
<Sign3DViewer
length={length}
height={height}
groundClearance={groundClearance}
alpha={alpha}
cf={result.cf}
forceKN={result.forceKN}
applicationPoint={result.applicationPoint}
/>
</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ça Resultante</CardTitle>
<CardDescription>F = Cf · q · A</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-muted-foreground text-[10px]">Cf</div>
<div className="font-mono font-medium text-lg">{result.cf.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-muted-foreground text-[10px]">e (m)</div>
<div className="font-mono font-medium text-lg">{result.applicationPoint.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-muted-foreground text-[10px]">Área efetiva</div>
<div className="font-mono font-medium">{result.areaEffective.toFixed(1)} m²</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-muted-foreground text-[10px]">Força F</div>
<div className="font-mono font-medium text-lg">{result.forceKN.toFixed(2)} kN</div>
</div>
</div>
<Separator />
<h4 className="text-xs font-semibold uppercase text-muted-foreground">Momento de Tombamento</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-muted-foreground text-[10px]">M (base da placa)</div>
<div className="font-mono font-medium">{result.momentBaseKNm.toFixed(2)} kNm</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-muted-foreground text-[10px]">M (solo)</div>
<div className="font-mono font-medium text-destructive">{result.momentGroundKNm.toFixed(2)} kNm</div>
</div>
</div>
<Separator />
<div className="text-xs text-muted-foreground">
A força F atua perpendicularmente ao plano do muro ou placa, com excentricidade e medida a partir do centro geométrico.
</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 SignModule;