🚀 Auto-deploy: BrainWind atualizado em 13/07/2026 15:00:47
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user