🚀 Auto-deploy: BrainWind atualizado em 13/07/2026 15:00:47

This commit is contained in:
2026-07-13 15:00:47 +00:00
parent 314c9b7490
commit ab8f89e807
11 changed files with 567 additions and 10 deletions
+246
View File
@@ -0,0 +1,246 @@
import React, { useMemo, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Slider } from '@/components/ui/slider';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { useWindStore } from '@/store/appStore';
import { calculatePiperack, type PipeDef } from '@/lib/modules/piperack';
import { Piperack3D } from '@/components/three/Piperack3D';
import SceneCapturePanel from '../components/SceneCapturePanel';
import ExportMenu from '../components/ExportMenu';
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
import { Plus, Trash2, Layers } from 'lucide-react';
import { Input } from '@/components/ui/input';
const PiperackModule: React.FC = () => {
const { q } = useWindStore();
const [width, setWidth] = useState(6);
const [height, setHeight] = useState(2.5);
const [elevation, setElevation] = useState(8);
const [spacing, setSpacing] = useState(6);
const [numFrames, setNumFrames] = useState(3);
const [phiStruct, setPhiStruct] = useState(0.2);
const [pipes, setPipes] = useState<PipeDef[]>([]);
const addPipe = () => {
setPipes([...pipes, { id: Math.random().toString(36).substr(2, 9), diameter: 0.5 }]);
};
const removePipe = (id: string) => {
setPipes(pipes.filter(p => p.id !== id));
};
const updatePipe = (id: string, diam: number) => {
setPipes(pipes.map(p => p.id === id ? { ...p, diameter: diam } : p));
};
const result = useMemo(() => {
return calculatePiperack({
width,
height,
elevation,
spacing,
numFrames,
phiStruct,
pipes,
q,
});
}, [width, height, elevation, spacing, numFrames, phiStruct, pipes, q]);
const handleExportPDF = () => {
const sections: GenericPDFSection[] = [
{
title: 'Geometria do Pipe-Rack',
type: 'grid',
gridItems: [
{ label: 'Largura do pórtico (b)', value: `${width} m` },
{ label: 'Altura do pórtico (h)', value: `${height} m` },
{ label: 'Elevação média (z)', value: `${elevation} m` },
{ label: 'Espaçamento (e)', value: `${spacing} m` },
{ label: 'Número de pórticos (n)', value: String(numFrames) },
{ label: 'φ (estrutural)', value: phiStruct.toFixed(2) },
{ label: 'Quantidade de Tubos', value: String(pipes.length) },
],
},
{
title: 'Coeficientes Aerodinâmicos',
type: 'grid',
gridItems: [
{ label: 'φ total (estrutura + tubos)', value: result.phiTotal.toFixed(2) },
{ label: 'Ca frontal', value: result.caFrontal.toFixed(2) },
{ label: 'Fator proteção η', value: result.eta.toFixed(2) },
],
},
{
title: 'Forças e Cargas',
type: 'grid',
gridItems: [
{ label: 'Carga Linear Pórtico Frontal', value: `${result.linearLoadFront.toFixed(2)} kN/m` },
{ label: 'Carga Linear Pórtico Traseiros (cada)', value: `${result.linearLoadBack.toFixed(2)} kN/m` },
{ label: 'Força Global Total', value: `${result.globalForce.toFixed(2)} kN` },
],
}
];
exportGenericToPDF('Pipe-Racks Industriais', sections);
};
return (
<div className="flex flex-col h-[calc(100vh-3.5rem)] md:h-screen bg-background">
<header className="shrink-0 flex items-center justify-between px-6 py-4 border-b bg-card">
<div className="flex items-center gap-3">
<div className="p-2 bg-primary/10 rounded-lg">
<Layers className="w-5 h-5 text-primary" />
</div>
<div>
<h1 className="text-xl font-bold tracking-tight">Pipe-Racks Industriais</h1>
<p className="text-sm text-muted-foreground">Vento transversal (Sec. 7 - NBR 6123)</p>
</div>
</div>
<div className="flex items-center gap-3">
<ExportMenu onExportPDF={handleExportPDF} />
</div>
</header>
<div className="flex-1 overflow-hidden grid lg:grid-cols-[1fr_400px]">
{/* Painel Esquerdo: 3D */}
<div className="relative min-h-[400px] lg:min-h-0 bg-muted/30">
<Piperack3D
width={width}
height={height}
elevation={elevation}
spacing={spacing}
numFrames={numFrames}
pipes={pipes}
phiTotal={result.phiTotal}
eta={result.eta}
/>
<SceneCapturePanel />
</div>
{/* Painel Direito: Controles */}
<div className="flex flex-col border-l bg-card overflow-y-auto">
<div className="p-6 space-y-8">
<Card className="shadow-none border-primary/20">
<CardHeader className="pb-4">
<CardTitle className="text-base flex items-center justify-between">
Forças Calculadas
<Badge variant="secondary" className="font-mono">{result.globalForce.toFixed(1)} kN (Global)</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<div className="flex justify-between items-center pb-2 border-b">
<span className="text-muted-foreground">φ efetivo</span>
<span className="font-mono font-medium">{result.phiTotal.toFixed(2)}</span>
</div>
<div className="flex justify-between items-center pb-2 border-b">
<span className="text-muted-foreground">Ca frontal (Tab. 26 adaptada)</span>
<span className="font-mono font-medium">{result.caFrontal.toFixed(2)}</span>
</div>
<div className="flex justify-between items-center pb-2 border-b">
<span className="text-muted-foreground">Fator η (Tab. 28)</span>
<span className="font-mono font-medium">{result.eta.toFixed(2)}</span>
</div>
<div className="flex justify-between items-center pb-2 border-b">
<span className="text-muted-foreground text-primary font-medium">Carga Pórtico 1</span>
<span className="font-mono font-bold text-primary">{result.linearLoadFront.toFixed(2)} kN/m</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground text-primary font-medium">Carga Pórticos {numFrames > 1 ? `2 a ${numFrames}` : '-'}</span>
<span className="font-mono font-bold text-primary">{result.linearLoadBack.toFixed(2)} kN/m</span>
</div>
</CardContent>
</Card>
<div className="space-y-6">
<div>
<h3 className="font-medium mb-4">Geometria da Estrutura</h3>
<div className="space-y-5">
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium">Largura (b)</span>
<span className="text-sm text-muted-foreground">{width} m</span>
</div>
<Slider value={[width]} min={2} max={15} step={0.5} onValueChange={v => setWidth(v[0])} />
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium">Altura do pórtico (h)</span>
<span className="text-sm text-muted-foreground">{height} m</span>
</div>
<Slider value={[height]} min={1} max={5} step={0.1} onValueChange={v => setHeight(v[0])} />
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium">Elevação (z)</span>
<span className="text-sm text-muted-foreground">{elevation} m</span>
</div>
<Slider value={[elevation]} min={2} max={25} step={1} onValueChange={v => setElevation(v[0])} />
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium">Espaçamento (e)</span>
<span className="text-sm text-muted-foreground">{spacing} m</span>
</div>
<Slider value={[spacing]} min={2} max={12} step={0.5} onValueChange={v => setSpacing(v[0])} />
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium">Número de Pórticos (n)</span>
<span className="text-sm text-muted-foreground">{numFrames}</span>
</div>
<Slider value={[numFrames]} min={1} max={10} step={1} onValueChange={v => setNumFrames(v[0])} />
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium">Índice de Solidez (φ str)</span>
<span className="text-sm text-muted-foreground">{phiStruct.toFixed(2)}</span>
</div>
<Slider value={[phiStruct]} min={0.1} max={0.6} step={0.05} onValueChange={v => setPhiStruct(v[0])} />
</div>
</div>
</div>
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="font-medium">Tubulações (Bloqueio)</h3>
<Button variant="outline" size="sm" onClick={addPipe} className="h-7 text-xs">
<Plus className="w-3 h-3 mr-1" /> Adicionar
</Button>
</div>
{pipes.length === 0 ? (
<div className="text-center p-4 border border-dashed rounded-lg text-sm text-muted-foreground">
Nenhuma tubulação adicionada.
</div>
) : (
<div className="space-y-2">
{pipes.map((pipe, index) => (
<div key={pipe.id} className="flex items-center gap-2 p-2 rounded-md border bg-muted/20">
<label className="w-20 text-xs text-muted-foreground shrink-0">Tubo {index + 1}</label>
<Input
type="number"
value={pipe.diameter}
onChange={(e) => updatePipe(pipe.id, Number(e.target.value))}
step={0.1}
min={0.1}
className="h-7 text-xs font-mono"
/>
<span className="text-xs text-muted-foreground">m</span>
<Button variant="ghost" size="icon" className="h-7 w-7 text-destructive shrink-0" onClick={() => removePipe(pipe.id)}>
<Trash2 className="w-4 h-4" />
</Button>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default PiperackModule;