🚀 Auto-deploy: BrainWind atualizado em 27/07/2026 18:39:28
This commit is contained in:
+187
-225
@@ -1,7 +1,6 @@
|
||||
import React, { useMemo, useState } 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 { Button } from '@/components/ui/button';
|
||||
import { useWindStore } from '@/store/appStore';
|
||||
@@ -11,14 +10,14 @@ import {
|
||||
type OpeningPosition,
|
||||
} from '@/lib/nbr-tables/table-shelters';
|
||||
import Shelter3D from '@/components/three/Shelter3D';
|
||||
import SceneCanvas from '@/components/SceneCanvas';
|
||||
import { WindFlowGrid2D } from '@/components/WindFlowGrid2D';
|
||||
import SceneCapturePanel from '../components/SceneCapturePanel';
|
||||
import ExportMenu from '../components/ExportMenu';
|
||||
import { EducationalManual } from '@/components/EducationalManual';
|
||||
import { WindParametersSummary } from '@/components/WindParametersSummary';
|
||||
import { SaveModuleDialog } from '@/components/SaveModuleDialog';
|
||||
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
|
||||
import { Eye, Box, Layers, ShieldCheck, ArrowUpRight, CheckCircle2 } from 'lucide-react';
|
||||
import { Eye, Box, Layers, ArrowUpRight, CheckCircle2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const conditionLabels: Record<ShelterCondition, string> = {
|
||||
@@ -35,28 +34,41 @@ const ShelterModule: React.FC = () => {
|
||||
const [width, setWidth] = useState(10);
|
||||
const [height, setHeight] = useState(4.8);
|
||||
const [theta, setTheta] = useState(5);
|
||||
const [backClosure, setBackClosure] = useState(85);
|
||||
const [backClosure, setBackClosure] = useState(75);
|
||||
const [backRange, setBackRange] = useState<[number, number]>([0, 75]);
|
||||
const [leftClosure, setLeftClosure] = useState(100);
|
||||
const [rightClosure, setRightClosure] = useState(100);
|
||||
const [openingPos, setOpeningPos] = useState<OpeningPosition>('top');
|
||||
const [viewMode, setViewMode] = useState<'3d' | 'elevation'>('3d');
|
||||
|
||||
// Ajusta percentuais ao trocar as 4 condições rápidas
|
||||
// Determinação automática da posição da fresta/vão a partir dos cursores (base e topo)
|
||||
const bottomGap = backRange[0];
|
||||
const topGap = 100 - backRange[1];
|
||||
const openingPos: OpeningPosition =
|
||||
topGap > 2 && topGap >= bottomGap
|
||||
? 'top'
|
||||
: bottomGap > 2 && bottomGap > topGap
|
||||
? 'bottom'
|
||||
: 'distributed';
|
||||
|
||||
const handleConditionChange = (cond: ShelterCondition) => {
|
||||
setCondition(cond);
|
||||
if (cond === 'cond1') {
|
||||
setBackRange([0, 100]);
|
||||
setBackClosure(100);
|
||||
setLeftClosure(100);
|
||||
setRightClosure(100);
|
||||
} else if (cond === 'cond2') {
|
||||
setBackRange([0, 100]);
|
||||
setBackClosure(100);
|
||||
setLeftClosure(0);
|
||||
setRightClosure(0);
|
||||
} else if (cond === 'cond3') {
|
||||
setBackRange([0, 0]);
|
||||
setBackClosure(0);
|
||||
setLeftClosure(100);
|
||||
setRightClosure(100);
|
||||
} else if (cond === 'cond4') {
|
||||
setBackRange([0, 0]);
|
||||
setBackClosure(0);
|
||||
setLeftClosure(0);
|
||||
setRightClosure(0);
|
||||
@@ -64,65 +76,42 @@ const ShelterModule: React.FC = () => {
|
||||
};
|
||||
|
||||
const result = useMemo(() => {
|
||||
return calculateShelterWind(
|
||||
{
|
||||
condition,
|
||||
depth,
|
||||
width,
|
||||
height,
|
||||
theta,
|
||||
backClosure,
|
||||
leftClosure,
|
||||
rightClosure,
|
||||
openingPos,
|
||||
},
|
||||
q
|
||||
);
|
||||
}, [condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos, q]);
|
||||
return calculateShelterWind({
|
||||
condition,
|
||||
depth,
|
||||
width,
|
||||
height,
|
||||
theta,
|
||||
backClosure,
|
||||
backRange,
|
||||
leftClosure,
|
||||
rightClosure,
|
||||
openingPos,
|
||||
}, q);
|
||||
}, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos]);
|
||||
|
||||
const handleExportPDF = () => {
|
||||
const sections: GenericPDFSection[] = [
|
||||
{
|
||||
title: 'Geometria do Abrigo e Fechamentos',
|
||||
title: 'Geometria e Fechamentos do Abrigo',
|
||||
type: 'grid',
|
||||
gridItems: [
|
||||
{ label: 'Condição Topológica', value: conditionLabels[condition] },
|
||||
{ label: 'Comprimento (d / balanço)', value: `${depth} m` },
|
||||
{ label: 'Largura (b)', value: `${width} m` },
|
||||
{ label: 'Altura (h)', value: `${height} m` },
|
||||
{ label: 'Inclinação (θ)', value: `${theta}°` },
|
||||
{ label: 'Fechamento Fundo', value: `${backClosure}%` },
|
||||
{ label: 'Fechamento Lateral Esq / Dir', value: `${leftClosure}% / ${rightClosure}%` },
|
||||
{ label: 'Posição da Abertura', value: openingPos === 'top' ? 'Fresta Topo (Alívio)' : openingPos === 'bottom' ? 'Vão Inferior' : 'Distribuída' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Coeficientes Aerodinâmicos (NBR 6123)',
|
||||
type: 'grid',
|
||||
gridItems: [
|
||||
{ label: 'Ce Superior (Cobertura)', value: result.cpeTop.toString() },
|
||||
{ label: 'Cpi Inferior (Efetivo)', value: result.cpiBottom.toString() },
|
||||
{ label: 'Cnf Líquido (Arrancamento)', value: result.cnfVertical.toString() },
|
||||
{ label: 'Cf Parede Fundo (Tab. 23)', value: result.cfBack.toString() },
|
||||
{ label: 'Alívio por Fresta Superior', value: `${result.reliefPercentage}%` },
|
||||
{ label: 'Posição da Abertura', value: openingPos },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Forças Resultantes (kN)',
|
||||
type: 'grid',
|
||||
gridItems: [
|
||||
{ label: 'Força Vertical (Fz - Arrancamento)', value: `${result.forces.fzUp} kN` },
|
||||
{ label: 'Empuxo Frontal Fundo (Fx)', value: `${result.forces.fxBack} kN` },
|
||||
{ label: 'Empuxo nas Laterais (Fy)', value: `${result.forces.fySide} kN` },
|
||||
{ label: 'Atrito na Cobertura', value: `${result.forces.fFriction} kN` },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Carga Linear nos Pórticos (kN/m)',
|
||||
type: 'grid',
|
||||
gridItems: [
|
||||
{ label: 'Carga no Pilar (ex: Tubo 150x150)', value: `${result.lineLoads.columnLoad} kN/m` },
|
||||
{ label: 'Carga na Viga em Balanço', value: `${result.lineLoads.beamLoad} kN/m` },
|
||||
{ label: 'Força Vertical (Fz)', value: `${result.forces.fzUp} kN` },
|
||||
{ label: 'Empuxo Frontal (Fx)', value: `${result.forces.fxBack} kN` },
|
||||
{ label: 'Empuxo Laterais (Fy)', value: `${result.forces.fySide} kN` },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -130,9 +119,9 @@ const ShelterModule: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row h-full w-full gap-4 p-4 lg:p-6 bg-muted/30">
|
||||
<div className="h-[calc(100vh-4rem)] flex flex-col lg:flex-row gap-4 p-4 overflow-hidden">
|
||||
{/* Coluna Esquerda: Controles do Abrigo */}
|
||||
<div className="w-full lg:w-96 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
|
||||
<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 flex flex-row items-start justify-between space-y-0 pr-6">
|
||||
<div>
|
||||
@@ -140,9 +129,7 @@ const ShelterModule: React.FC = () => {
|
||||
<Layers className="size-5 text-primary" />
|
||||
Abrigos e Pórticos
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Pórticos em balanço, marquises e garagens com fechamentos (Tab. 23-25 e sec. 6.3).
|
||||
</CardDescription>
|
||||
<CardDescription>Pórticos em balanço (Tab. 23-25 e sec. 6.3).</CardDescription>
|
||||
</div>
|
||||
<SaveModuleDialog
|
||||
moduleType="shelter"
|
||||
@@ -152,76 +139,72 @@ const ShelterModule: React.FC = () => {
|
||||
<CardContent className="space-y-5">
|
||||
<WindParametersSummary />
|
||||
|
||||
{/* Seletor Rápido de 4 Condições Topológicas */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
Condições Topológicas
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{(['cond1', 'cond2', 'cond3', 'cond4'] as ShelterCondition[]).map((c) => (
|
||||
<Button
|
||||
key={c}
|
||||
type="button"
|
||||
variant={condition === c ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
className="h-auto py-2 px-2 text-xs text-left justify-start font-normal leading-tight"
|
||||
className="h-auto py-2 px-2.5 text-xs text-left justify-start font-normal leading-tight w-full"
|
||||
onClick={() => handleConditionChange(c)}
|
||||
>
|
||||
<CheckCircle2 className={cn("size-3.5 mr-1.5 shrink-0", condition === c ? "text-primary-foreground" : "text-muted-foreground")} />
|
||||
<span>{conditionLabels[c].split(' (')[0]}</span>
|
||||
<CheckCircle2 className={cn("size-3.5 mr-2 shrink-0", condition === c ? "text-primary-foreground" : "text-muted-foreground")} />
|
||||
<span className="truncate">{conditionLabels[c]}</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controle de Posição da Abertura (Topo / Base / Distribuída) */}
|
||||
<div className="space-y-2 pt-1 border-t">
|
||||
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
Posição da Abertura no Fundo
|
||||
</label>
|
||||
<Select value={openingPos} onValueChange={(v) => setOpeningPos(v as OpeningPosition)}>
|
||||
<SelectTrigger className="text-xs h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="top">Fresta no Topo (Alívio de Arrancamento)</SelectItem>
|
||||
<SelectItem value="bottom">Vão na Base (Comportamento Placa Suspensa)</SelectItem>
|
||||
<SelectItem value="distributed">Abertura Uniformemente Distribuída</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Sliders de Fechamento Parcial (%) */}
|
||||
<div className="space-y-3 pt-1 border-t">
|
||||
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
Permeabilidade / Fechamento (%)
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex justify-between items-center text-xs">
|
||||
<span>Parede de Fundo:</span>
|
||||
<span className="font-mono font-medium">{backClosure}%</span>
|
||||
<span className="font-mono font-medium">
|
||||
{backClosure}% {backRange[0] > 0 || backRange[1] < 100 ? `(Base ${backRange[0]}% | Topo ${100 - backRange[1]}%)` : ''}
|
||||
</span>
|
||||
</div>
|
||||
<Slider min={0} max={100} step={5} value={[backClosure]} onValueChange={(v) => setBackClosure(v[0])} />
|
||||
<Slider
|
||||
min={0}
|
||||
max={100}
|
||||
step={5}
|
||||
value={backRange}
|
||||
onValueChange={(val) => {
|
||||
if (Array.isArray(val) && val.length === 2) {
|
||||
const [start, end] = val;
|
||||
if (end >= start) {
|
||||
setBackRange([start, end]);
|
||||
setBackClosure(end - start);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
<div className="grid grid-cols-2 gap-3 pt-1">
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-[11px]">
|
||||
<span>Lateral Esq:</span>
|
||||
<span className="font-mono">{leftClosure}%</span>
|
||||
</div>
|
||||
<Slider min={0} max={100} step={5} value={[leftClosure]} onValueChange={(v) => setLeftClosure(v[0])} />
|
||||
<Slider min={0} max={100} step={10} value={[leftClosure]} onValueChange={(v) => setLeftClosure(v[0])} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-[11px]">
|
||||
<span>Lateral Dir:</span>
|
||||
<span className="font-mono">{rightClosure}%</span>
|
||||
</div>
|
||||
<Slider min={0} max={100} step={5} value={[rightClosure]} onValueChange={(v) => setRightClosure(v[0])} />
|
||||
<Slider min={0} max={100} step={10} value={[rightClosure]} onValueChange={(v) => setRightClosure(v[0])} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sliders Geométricos */}
|
||||
<div className="space-y-3 pt-1 border-t">
|
||||
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
Dimensões e Inclinação
|
||||
@@ -229,23 +212,23 @@ const ShelterModule: React.FC = () => {
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span>Vão do Balanço (d):</span>
|
||||
<span className="font-mono font-medium">{depth} m</span>
|
||||
<span className="font-mono font-medium">{depth.toFixed(1)} m</span>
|
||||
</div>
|
||||
<Slider min={3} max={20} step={0.5} value={[depth]} onValueChange={(v) => setDepth(v[0])} />
|
||||
<Slider min={2} max={15} step={0.1} value={[depth]} onValueChange={(v) => setDepth(v[0])} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span>Altura do Pilar (h):</span>
|
||||
<span className="font-mono font-medium">{height} m</span>
|
||||
</div>
|
||||
<Slider min={2.5} max={12} step={0.1} value={[height]} onValueChange={(v) => setHeight(v[0])} />
|
||||
<Slider min={2.5} max={12} step={0.2} value={[height]} onValueChange={(v) => setHeight(v[0])} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span>Largura do Abrigo (b):</span>
|
||||
<span className="font-mono font-medium">{width} m</span>
|
||||
</div>
|
||||
<Slider min={4} max={30} step={1} value={[width]} onValueChange={(v) => setWidth(v[0])} />
|
||||
<Slider min={4} max={40} step={1} value={[width]} onValueChange={(v) => setWidth(v[0])} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs">
|
||||
@@ -255,151 +238,130 @@ const ShelterModule: React.FC = () => {
|
||||
<Slider min={0} max={30} step={1} value={[theta]} onValueChange={(v) => setTheta(v[0])} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status Normativo */}
|
||||
<div className="rounded-lg border bg-secondary/30 p-3 text-xs space-y-1.5">
|
||||
<div className="flex items-center gap-1.5 font-semibold text-foreground">
|
||||
<ShieldCheck className="size-4 text-emerald-500 shrink-0" />
|
||||
<span>Norma NBR 6123:2023 — Sec. 6.3 & Tab. 23-25</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground leading-relaxed">{result.statusText}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Coluna Direita: 3D, Corte A-A e Quadro de Ações */}
|
||||
<div className="flex-1 flex flex-col gap-4">
|
||||
{/* Visualizador 3D com trava, manual (i) e corte em elevação */}
|
||||
<div className="flex-1 flex flex-col min-h-[460px] bg-background rounded-xl border border-border shadow-sm overflow-hidden relative">
|
||||
<div className="absolute top-4 left-4 z-10 flex flex-wrap gap-2">
|
||||
<Badge variant="secondary" className="bg-background/85 text-foreground border border-border backdrop-blur-sm">
|
||||
θ = {theta}°
|
||||
<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 flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary" className="bg-background/85 text-foreground border border-border backdrop-blur-sm font-mono">
|
||||
θ = {theta}°
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="bg-background/85 text-foreground border border-border backdrop-blur-sm">
|
||||
Fundo: {backClosure}% ({openingPos === 'top' ? 'Fresta Topo' : openingPos === 'bottom' ? 'Vão Base' : 'Distrib.'})
|
||||
</Badge>
|
||||
{result.reliefPercentage > 0 && (
|
||||
<Badge className="bg-emerald-600/90 hover:bg-emerald-600 text-white border-none shadow-sm flex items-center gap-1">
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
Alívio Arrancamento: -{result.reliefPercentage}%
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="bg-background/85 text-foreground border border-border backdrop-blur-sm">
|
||||
Fundo: {backClosure}% ({openingPos === 'top' ? 'Fresta Topo' : openingPos === 'bottom' ? 'Vão Base' : 'Distrib.'})
|
||||
</Badge>
|
||||
{result.reliefPercentage > 0 && (
|
||||
<Badge className="bg-emerald-600/90 hover:bg-emerald-600 text-white border-none shadow-sm flex items-center gap-1">
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
Alívio Arrancamento: -{result.reliefPercentage}%
|
||||
</Badge>
|
||||
)}
|
||||
)}
|
||||
<div className="bg-background/90 border border-border rounded-full p-0.5 shadow-sm flex items-center ml-1">
|
||||
<Button size="sm" variant={viewMode === '3d' ? 'default' : 'ghost'} className="h-6 px-2.5 text-xs rounded-full" onClick={() => setViewMode('3d')}>
|
||||
<Box className="size-3 mr-1" /> Vista 3D
|
||||
</Button>
|
||||
<Button size="sm" variant={viewMode === 'elevation' ? 'default' : 'ghost'} className="h-6 px-2.5 text-xs rounded-full" onClick={() => setViewMode('elevation')}>
|
||||
<Eye className="size-3 mr-1" /> Corte A-A
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-4 right-4 z-10 flex items-center gap-2">
|
||||
{/* Botão de Corte em Elevação (Corte A-A) vs 3D */}
|
||||
<div className="bg-background/90 border border-border rounded-full p-1 shadow-sm flex items-center">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={viewMode === '3d' ? 'default' : 'ghost'}
|
||||
className="h-7 px-3 text-xs rounded-full"
|
||||
onClick={() => setViewMode('3d')}
|
||||
>
|
||||
<Box className="size-3.5 mr-1" />
|
||||
Vista 3D
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={viewMode === 'elevation' ? 'default' : 'ghost'}
|
||||
className="h-7 px-3 text-xs rounded-full"
|
||||
onClick={() => setViewMode('elevation')}
|
||||
>
|
||||
<Eye className="size-3.5 mr-1" />
|
||||
Corte A-A (Frestas)
|
||||
</Button>
|
||||
<div className="absolute top-4 right-4 z-10">
|
||||
<EducationalManual type="shelter" params={{ condition, backClosure, openingPos, theta }} />
|
||||
</div>
|
||||
|
||||
{viewMode === '3d' ? (
|
||||
<Shelter3D
|
||||
condition={condition}
|
||||
depth={depth}
|
||||
width={width}
|
||||
height={height}
|
||||
theta={theta}
|
||||
backClosure={backClosure}
|
||||
backRange={backRange}
|
||||
leftClosure={leftClosure}
|
||||
rightClosure={rightClosure}
|
||||
openingPos={openingPos}
|
||||
viewMode={viewMode}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center p-6 bg-slate-950 overflow-auto">
|
||||
<div className="max-w-4xl w-full">
|
||||
<WindFlowGrid2D type="shelter" params={{ condition, backClosure, openingPos, theta }} />
|
||||
</div>
|
||||
|
||||
{/* Ícone (i) Manual Didático NBR 6123 */}
|
||||
<EducationalManual type="shelter" params={{ condition, backClosure, openingPos, theta }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SceneCanvas
|
||||
moduleId="shelter"
|
||||
camera={{ position: [12, 6, 14], fov: 45 }}
|
||||
fallback={<div className="flex items-center justify-center h-full text-muted-foreground">3D indisponível</div>}
|
||||
>
|
||||
<ambientLight intensity={0.7} />
|
||||
<directionalLight position={[10, 20, 15]} intensity={1.2} />
|
||||
<Shelter3D
|
||||
condition={condition}
|
||||
depth={depth}
|
||||
width={width}
|
||||
height={height}
|
||||
theta={theta}
|
||||
backClosure={backClosure}
|
||||
leftClosure={leftClosure}
|
||||
rightClosure={rightClosure}
|
||||
openingPos={openingPos}
|
||||
viewMode={viewMode}
|
||||
/>
|
||||
</SceneCanvas>
|
||||
</div>
|
||||
|
||||
{/* Quadro Superior de Resultados na Base do 3D */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Card className="bg-card border-border">
|
||||
<CardContent className="p-4 flex flex-col justify-between h-full">
|
||||
<span className="text-xs text-muted-foreground font-medium">F. Vertical (Fz - Arrancamento)</span>
|
||||
<div className="flex items-baseline justify-between mt-2">
|
||||
<span className="text-xl font-bold font-mono text-primary">{result.forces.fzUp} kN</span>
|
||||
<span className="text-xs text-muted-foreground font-mono">Cnf = {result.cnfVertical}</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground mt-1">
|
||||
{result.reliefPercentage > 0 ? `Com ${result.reliefPercentage}% de alívio por fresta` : 'Sem alívio por fresta superior'}
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border">
|
||||
<CardContent className="p-4 flex flex-col justify-between h-full">
|
||||
<span className="text-xs text-muted-foreground font-medium">Empuxo no Fundo (Fx Traseira)</span>
|
||||
<div className="flex items-baseline justify-between mt-2">
|
||||
<span className="text-xl font-bold font-mono text-foreground">{result.forces.fxBack} kN</span>
|
||||
<span className="text-xs text-muted-foreground font-mono">Cf = {result.cfBack}</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground mt-1">
|
||||
Parede com {backClosure}% de estanqueidade
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border">
|
||||
<CardContent className="p-4 flex flex-col justify-between h-full">
|
||||
<span className="text-xs text-muted-foreground font-medium">Carga no Pilar (ex: Tubo 150x150)</span>
|
||||
<div className="flex items-baseline justify-between mt-2">
|
||||
<span className="text-xl font-bold font-mono text-emerald-600 dark:text-emerald-400">
|
||||
{result.lineLoads.columnLoad} kN/m
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground font-mono">h = {height} m</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground mt-1">
|
||||
Ação direta + reação horizontal por pilar
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border">
|
||||
<CardContent className="p-4 flex flex-col justify-between h-full">
|
||||
<span className="text-xs text-muted-foreground font-medium">Carga na Viga em Balanço</span>
|
||||
<div className="flex items-baseline justify-between mt-2">
|
||||
<span className="text-xl font-bold font-mono text-sky-600 dark:text-sky-400">
|
||||
{result.lineLoads.beamLoad} kN/m
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground font-mono">d = {depth} m</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground mt-1">
|
||||
Sucção distribuída por braço principal
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Rodapé com Exportação e Captura */}
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 border-t pt-4">
|
||||
<SceneCapturePanel sceneId="shelter-module" />
|
||||
<ExportMenu onExportPDF={handleExportPDF} />
|
||||
<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-4">
|
||||
<CardTitle className="text-base flex items-center justify-between">
|
||||
Forças Calculadas
|
||||
<Badge variant="secondary" className="font-mono">{result.forces.fzUp.toFixed(1)} kN (Arrancamento)</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 text-sm">
|
||||
<div className="space-y-2 border-b pb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">F. Vertical (Fz - Cobertura)</span>
|
||||
<span className="font-mono font-bold text-foreground">{result.forces.fzUp.toFixed(2)} kN</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-xs text-muted-foreground">
|
||||
<span>Coeficiente Cnf</span>
|
||||
<span className="font-mono">{result.cnfVertical}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 border-b pb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Empuxo no Fundo (Fx)</span>
|
||||
<span className="font-mono font-bold text-foreground">{result.forces.fxBack.toFixed(2)} kN</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-xs text-muted-foreground">
|
||||
<span>Cf / Estanqueidade</span>
|
||||
<span className="font-mono">Cf = {result.cfBack} ({backClosure}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-2 border-b">
|
||||
<span className="text-muted-foreground">Empuxo Lateral (Fy)</span>
|
||||
<span className="font-mono font-bold text-foreground">{result.forces.fySide.toFixed(2)} kN</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-2 border-b">
|
||||
<span className="text-primary font-medium">Carga no Pilar (Pórtico)</span>
|
||||
<span className="font-mono font-bold text-emerald-600 dark:text-emerald-400">{result.lineLoads.columnLoad.toFixed(2)} kN/m</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-4 border-b">
|
||||
<span className="text-primary font-medium">Carga na Viga (Balanço)</span>
|
||||
<span className="font-mono font-bold text-sky-600 dark:text-sky-400">{result.lineLoads.beamLoad.toFixed(2)} kN/m</span>
|
||||
</div>
|
||||
<div className="pt-2 space-y-2">
|
||||
<div className="font-bold text-sm text-primary">Parâmetros Adicionais (NBR 6123)</div>
|
||||
<div className="grid grid-cols-1 gap-2 bg-muted/30 p-3 rounded-md border text-xs">
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>Permeabilidade (μ):</span>
|
||||
<span className="font-mono font-medium text-foreground">{100 - backClosure}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>Alívio por Fresta Superior:</span>
|
||||
<span className="font-mono font-medium text-emerald-600 dark:text-emerald-400">{result.reliefPercentage}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>Força de Atrito (Ff):</span>
|
||||
<span className="font-mono font-medium text-foreground">{result.forces.fFriction.toFixed(2)} kN</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground leading-tight">
|
||||
* Para a visualização didática dos esquemas aerodinâmicos e linhas de corrente, utilize a aba <strong>Corte A-A</strong> no painel central.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex justify-center bg-background rounded-xl border border-border shadow-sm p-4">
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<ExportMenu onExportPDF={handleExportPDF} />
|
||||
</div>
|
||||
</div>
|
||||
<SceneCapturePanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user