🚀 Auto-deploy: BrainWind atualizado em 26/08/2026 10:50:25
This commit is contained in:
@@ -15,6 +15,7 @@ const TowerModule = lazy(() => import('./pages/TowerModule'));
|
|||||||
const PiperackModule = lazy(() => import('./pages/PiperackModule'));
|
const PiperackModule = lazy(() => import('./pages/PiperackModule'));
|
||||||
const ShelterModule = lazy(() => import('./pages/ShelterModule'));
|
const ShelterModule = lazy(() => import('./pages/ShelterModule'));
|
||||||
const CertificatePage = lazy(() => import('./pages/CertificatePage'));
|
const CertificatePage = lazy(() => import('./pages/CertificatePage'));
|
||||||
|
const CompareModule = lazy(() => import('./pages/Compare'));
|
||||||
import {
|
import {
|
||||||
Home, Settings, Menu, Cylinder, Church, CircleDot,
|
Home, Settings, Menu, Cylinder, Church, CircleDot,
|
||||||
Square, Layers, BarChart3, Activity, Warehouse,
|
Square, Layers, BarChart3, Activity, Warehouse,
|
||||||
@@ -376,6 +377,7 @@ function App() {
|
|||||||
<Route path="/calc-ca" element={<CalcCaModule />} />
|
<Route path="/calc-ca" element={<CalcCaModule />} />
|
||||||
<Route path="/settings" element={<SettingsModule />} />
|
<Route path="/settings" element={<SettingsModule />} />
|
||||||
<Route path="/certificado" element={<CertificatePage />} />
|
<Route path="/certificado" element={<CertificatePage />} />
|
||||||
|
<Route path="/compare" element={<CompareModule />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useGalpaoStore } from '../store/galpaoStore';
|
||||||
|
import { useWindStore } from '../store/appStore';
|
||||||
|
import type { WallCoefficients, RoofCoefficients } from '../lib/coefficients';
|
||||||
|
|
||||||
|
interface Heatmap2DProps {
|
||||||
|
overrideAngle?: 0 | 90;
|
||||||
|
overrideWallCpe?: WallCoefficients;
|
||||||
|
overrideRoofCpe?: RoofCoefficients;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapeia o valor da pressão (Cpe - Cpi) para uma cor.
|
||||||
|
* Sucção (negativo) = Tons de vermelho/laranja.
|
||||||
|
* Pressão (positivo) = Tons de azul.
|
||||||
|
*/
|
||||||
|
const getPressureColor = (cpe: number, cpi: number): string => {
|
||||||
|
const p = cpe - cpi;
|
||||||
|
if (p < 0) {
|
||||||
|
const intensity = Math.min(Math.abs(p) / 1.5, 1); // Normaliza sucção até -1.5
|
||||||
|
return `rgba(239, 68, 68, ${0.2 + intensity * 0.7})`; // Vermelho
|
||||||
|
} else if (p > 0) {
|
||||||
|
const intensity = Math.min(p / 1.0, 1); // Normaliza pressão até +1.0
|
||||||
|
return `rgba(59, 130, 246, ${0.2 + intensity * 0.7})`; // Azul
|
||||||
|
}
|
||||||
|
return 'rgba(200, 200, 200, 0.3)'; // Neutro
|
||||||
|
};
|
||||||
|
|
||||||
|
const Heatmap2D: React.FC<Heatmap2DProps> = ({ overrideAngle, overrideWallCpe, overrideRoofCpe }) => {
|
||||||
|
const storeGalpao = useGalpaoStore();
|
||||||
|
const storeWind = useWindStore();
|
||||||
|
|
||||||
|
const width = storeGalpao.width;
|
||||||
|
const length = storeGalpao.length;
|
||||||
|
const wallCpe = overrideWallCpe || storeGalpao.wallCpe;
|
||||||
|
const roofCpe = overrideRoofCpe || storeGalpao.roofCpe;
|
||||||
|
const windAngle = overrideAngle !== undefined ? overrideAngle : storeWind.windAngle;
|
||||||
|
const cpi = storeWind.cpi;
|
||||||
|
|
||||||
|
// Dimensões base do SVG
|
||||||
|
const svgW = 600;
|
||||||
|
const svgH = 600;
|
||||||
|
const margin = 100;
|
||||||
|
|
||||||
|
// Fator de escala para caber a planta (width x length) na tela
|
||||||
|
const maxDim = Math.max(width, length);
|
||||||
|
const scale = (svgW - margin * 2) / maxDim;
|
||||||
|
|
||||||
|
// Tamanhos desenhados
|
||||||
|
const drawW = width * scale;
|
||||||
|
const drawL = length * scale;
|
||||||
|
|
||||||
|
// Centro do desenho
|
||||||
|
const cx = svgW / 2;
|
||||||
|
const cy = svgH / 2;
|
||||||
|
const x0 = cx - drawW / 2;
|
||||||
|
const y0 = cy - drawL / 2;
|
||||||
|
|
||||||
|
// Determinar larguras das zonas do telhado (baseado na NBR 6123, onde as bordas têm distâncias específicas)
|
||||||
|
// Para simplificar graficamente o Heatmap, usaremos proporções visuais para E, F, G, H
|
||||||
|
const zoneSizeX = Math.min(drawW * 0.2, 40); // Representação genérica da zona E/F
|
||||||
|
const zoneSizeY = Math.min(drawL * 0.2, 40);
|
||||||
|
|
||||||
|
// A NBR 6123 rotaciona as zonas dependendo de windAngle (0° vs 90°)
|
||||||
|
// Vento 0° = perpendicular ao comprimento (Z). A=Frente, B=Fundo, C=Lado, D=Lado
|
||||||
|
// Vento 90° = perpendicular à largura (X). A=Lado, B=Lado, C=Frente, D=Fundo
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex flex-col items-center justify-center p-4">
|
||||||
|
<div className="bg-background rounded-xl p-4 shadow-sm border border-border w-full max-w-2xl">
|
||||||
|
<h3 className="text-center font-semibold mb-2">Planta Baixa - Telhado e Paredes</h3>
|
||||||
|
<p className="text-xs text-center text-muted-foreground mb-4">Vento incidindo a {windAngle}°</p>
|
||||||
|
|
||||||
|
<svg width="100%" height="100%" viewBox={`0 0 ${svgW} ${svgH}`} className="max-w-[500px] mx-auto overflow-visible">
|
||||||
|
<defs>
|
||||||
|
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor" className="text-foreground" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{/* Vento (Setas Dinâmicas) */}
|
||||||
|
{windAngle === 90 && (
|
||||||
|
<g className="text-blue-500" stroke="currentColor" strokeWidth="3" markerEnd="url(#arrow)">
|
||||||
|
<line x1={x0 - 60} y1={cy} x2={x0 - 20} y2={cy} />
|
||||||
|
<line x1={x0 - 60} y1={cy - 40} x2={x0 - 20} y2={cy - 40} />
|
||||||
|
<line x1={x0 - 60} y1={cy + 40} x2={x0 - 20} y2={cy + 40} />
|
||||||
|
<text x={x0 - 70} y={cy} textAnchor="end" className="text-sm font-bold fill-blue-500" stroke="none">Vento (90°)</text>
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{windAngle === 0 && (
|
||||||
|
<g className="text-blue-500" stroke="currentColor" strokeWidth="3" markerEnd="url(#arrow)">
|
||||||
|
<line x1={cx} y1={y0 - 60} x2={cx} y2={y0 - 20} />
|
||||||
|
<line x1={cx - 40} y1={y0 - 60} x2={cx - 40} y2={y0 - 20} />
|
||||||
|
<line x1={cx + 40} y1={y0 - 60} x2={cx + 40} y2={y0 - 20} />
|
||||||
|
<text x={cx} y={y0 - 70} textAnchor="middle" className="text-sm font-bold fill-blue-500" stroke="none">Vento (0°)</text>
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Área Total do Telhado (Fundo) */}
|
||||||
|
<rect x={x0} y={y0} width={drawW} height={drawL} fill="white" stroke="#ccc" strokeWidth="2" />
|
||||||
|
|
||||||
|
{/* Zonas do Telhado (Simplificadas para Vento a 0 ou 90) */}
|
||||||
|
{windAngle === 90 ? (
|
||||||
|
// Vento 90° -> Zonas E, G na esquerda, F, H na direita etc. (Esquema simplificado de visualização)
|
||||||
|
<>
|
||||||
|
{/* Metade Barlavento (Esquerda) */}
|
||||||
|
<rect x={x0} y={y0} width={drawW/2} height={drawL} fill={getPressureColor(roofCpe.E || 0, cpi)} stroke="#666" />
|
||||||
|
{/* Metade Sotavento (Direita) */}
|
||||||
|
<rect x={x0 + drawW/2} y={y0} width={drawW/2} height={drawL} fill={getPressureColor(roofCpe.F || 0, cpi)} stroke="#666" />
|
||||||
|
{/* Zonas Críticas */}
|
||||||
|
<rect x={x0} y={y0} width={zoneSizeX} height={zoneSizeY} fill={getPressureColor(roofCpe.G || 0, cpi)} stroke="#333" />
|
||||||
|
<rect x={x0} y={y0 + drawL - zoneSizeY} width={zoneSizeX} height={zoneSizeY} fill={getPressureColor(roofCpe.G || 0, cpi)} stroke="#333" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
// Vento 0°
|
||||||
|
<>
|
||||||
|
{/* Metade Barlavento (Topo) */}
|
||||||
|
<rect x={x0} y={y0} width={drawW} height={drawL/2} fill={getPressureColor(roofCpe.E || 0, cpi)} stroke="#666" />
|
||||||
|
{/* Metade Sotavento (Baixo) */}
|
||||||
|
<rect x={x0} y={y0 + drawL/2} width={drawW} height={drawL/2} fill={getPressureColor(roofCpe.F || 0, cpi)} stroke="#666" />
|
||||||
|
{/* Zonas Críticas */}
|
||||||
|
<rect x={x0} y={y0} width={zoneSizeX} height={zoneSizeY} fill={getPressureColor(roofCpe.G || 0, cpi)} stroke="#333" />
|
||||||
|
<rect x={x0 + drawW - zoneSizeX} y={y0} width={zoneSizeX} height={zoneSizeY} fill={getPressureColor(roofCpe.G || 0, cpi)} stroke="#333" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Labels do Telhado (Simplificados) */}
|
||||||
|
<text x={cx} y={cy} textAnchor="middle" dominantBaseline="middle" className="text-sm font-bold fill-foreground" style={{ mixBlendMode: 'difference' }}>
|
||||||
|
TELHADO
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* PAREDES (Desenhadas como bordas expandidas) */}
|
||||||
|
{/* Parede A (Frente em 0°, Lado Esq em 90°) */}
|
||||||
|
{windAngle === 0 ? (
|
||||||
|
<rect x={x0} y={y0 - 15} width={drawW} height={15} fill={getPressureColor(wallCpe.A, cpi)} stroke="#111" />
|
||||||
|
) : (
|
||||||
|
<rect x={x0 - 15} y={y0} width={15} height={drawL} fill={getPressureColor(wallCpe.A, cpi)} stroke="#111" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Parede B (Fundo em 0°, Lado Dir em 90°) */}
|
||||||
|
{windAngle === 0 ? (
|
||||||
|
<rect x={x0} y={y0 + drawL} width={drawW} height={15} fill={getPressureColor(wallCpe.B, cpi)} stroke="#111" />
|
||||||
|
) : (
|
||||||
|
<rect x={x0 + drawW} y={y0} width={15} height={drawL} fill={getPressureColor(wallCpe.B, cpi)} stroke="#111" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Parede C (Lateral Dir em 0°, Frente em 90°) */}
|
||||||
|
{windAngle === 0 ? (
|
||||||
|
<rect x={x0 + drawW} y={y0} width={15} height={drawL} fill={getPressureColor(wallCpe.C, cpi)} stroke="#111" />
|
||||||
|
) : (
|
||||||
|
<rect x={x0} y={y0 + drawL} width={drawW} height={15} fill={getPressureColor(wallCpe.C, cpi)} stroke="#111" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Parede D (Lateral Esq em 0°, Fundo em 90°) */}
|
||||||
|
{windAngle === 0 ? (
|
||||||
|
<rect x={x0 - 15} y={y0} width={15} height={drawL} fill={getPressureColor(wallCpe.D, cpi)} stroke="#111" />
|
||||||
|
) : (
|
||||||
|
<rect x={x0} y={y0 - 15} width={drawW} height={15} fill={getPressureColor(wallCpe.D, cpi)} stroke="#111" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Indicadores de Paredes */}
|
||||||
|
{windAngle === 0 ? (
|
||||||
|
<>
|
||||||
|
<text x={cx} y={y0 - 25} textAnchor="middle" className="text-xs font-bold fill-foreground">A</text>
|
||||||
|
<text x={cx} y={y0 + drawL + 25} textAnchor="middle" dominantBaseline="hanging" className="text-xs font-bold fill-foreground">B</text>
|
||||||
|
<text x={x0 - 25} y={cy} textAnchor="end" dominantBaseline="middle" className="text-xs font-bold fill-foreground">D</text>
|
||||||
|
<text x={x0 + drawW + 25} y={cy} textAnchor="start" dominantBaseline="middle" className="text-xs font-bold fill-foreground">C</text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<text x={x0 - 25} y={cy} textAnchor="end" dominantBaseline="middle" className="text-xs font-bold fill-foreground">A</text>
|
||||||
|
<text x={x0 + drawW + 25} y={cy} textAnchor="start" dominantBaseline="middle" className="text-xs font-bold fill-foreground">B</text>
|
||||||
|
<text x={cx} y={y0 + drawL + 25} textAnchor="middle" dominantBaseline="hanging" className="text-xs font-bold fill-foreground">C</text>
|
||||||
|
<text x={cx} y={y0 - 25} textAnchor="middle" className="text-xs font-bold fill-foreground">D</text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div className="mt-6 flex flex-wrap justify-center gap-4 text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded" style={{ backgroundColor: getPressureColor(-1.0, cpi) }}></div>
|
||||||
|
<span>Sucção Máxima</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded border border-border" style={{ backgroundColor: getPressureColor(0, cpi) }}></div>
|
||||||
|
<span>Neutro (0)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded" style={{ backgroundColor: getPressureColor(1.0, cpi) }}></div>
|
||||||
|
<span>Sobrepressão Máxima</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Heatmap2D;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useGalpaoStore } from '../store/galpaoStore';
|
||||||
|
import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } from '../lib/coefficients';
|
||||||
|
import Heatmap2D from '../components/Heatmap2D';
|
||||||
|
|
||||||
|
const Compare: React.FC = () => {
|
||||||
|
const { length, width, height, roofPitch } = useGalpaoStore();
|
||||||
|
|
||||||
|
const wallCpe0 = getWallCpe(length, width, height, 0);
|
||||||
|
const roofCpe0 = getRoofCpe(length, width, height, roofPitch, 0);
|
||||||
|
|
||||||
|
const wallCpe90 = getWallCpe(length, width, height, 90);
|
||||||
|
const roofCpe90 = getRoofCpe(length, width, height, roofPitch, 90);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full w-full p-4 lg:p-6 bg-muted/30 overflow-auto">
|
||||||
|
<h1 className="text-2xl font-bold mb-2">Comparação A/B (0° vs 90°)</h1>
|
||||||
|
<p className="text-muted-foreground mb-6">Compare visualmente a distribuição de pressões (Cpe - Cpi) nas duas direções normativas simultaneamente.</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 flex-1 min-h-[600px]">
|
||||||
|
<Heatmap2D overrideAngle={0} overrideWallCpe={wallCpe0} overrideRoofCpe={roofCpe0} />
|
||||||
|
<Heatmap2D overrideAngle={90} overrideWallCpe={wallCpe90} overrideRoofCpe={roofCpe90} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Compare;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import Warehouse3DViewer from '../components/Warehouse3D';
|
import Warehouse3DViewer from '../components/Warehouse3D';
|
||||||
|
import Heatmap2D from '../components/Heatmap2D';
|
||||||
import LinearLoadsTable from '../components/LinearLoadsTable';
|
import LinearLoadsTable from '../components/LinearLoadsTable';
|
||||||
import SceneCapturePanel from '../components/SceneCapturePanel';
|
import SceneCapturePanel from '../components/SceneCapturePanel';
|
||||||
import FtoolExportCard from '../components/FtoolExportCard';
|
import FtoolExportCard from '../components/FtoolExportCard';
|
||||||
@@ -12,9 +14,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { AlertCircle, Lightbulb, Box, Wind } from 'lucide-react';
|
import { AlertCircle, Lightbulb, Box, Wind, Columns } from 'lucide-react';
|
||||||
import ExportMenu from '../components/ExportMenu';
|
import ExportMenu from '../components/ExportMenu';
|
||||||
import { EducationalManual } from '@/components/EducationalManual';
|
import { EducationalManual } from '@/components/EducationalManual';
|
||||||
import { PressureLegend } from '@/components/PressureLegend';
|
import { PressureLegend } from '@/components/PressureLegend';
|
||||||
@@ -65,18 +67,25 @@ const GalpaoModule: React.FC = () => {
|
|||||||
return (
|
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="flex flex-col lg:flex-row h-full w-full gap-4 p-4 lg:p-6 bg-muted/30">
|
||||||
{/* Coluna Esquerda: Controles */}
|
{/* 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">
|
<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">
|
<Tabs defaultValue="geometria" className="w-full">
|
||||||
<CardHeader className="pb-3 flex flex-row items-start justify-between space-y-0 pr-6">
|
<TabsList className="grid w-full grid-cols-2 mb-4">
|
||||||
<div>
|
<TabsTrigger value="geometria">1. Geometria</TabsTrigger>
|
||||||
<CardTitle className="text-lg">Geometria</CardTitle>
|
<TabsTrigger value="permeabilidade">2. Permeabilidade</TabsTrigger>
|
||||||
<CardDescription>Tabelas 4 e 5 — NBR 6123.</CardDescription>
|
</TabsList>
|
||||||
</div>
|
|
||||||
<SaveModuleDialog
|
<TabsContent value="geometria" className="space-y-4 m-0">
|
||||||
moduleType="galpao"
|
<Card className="shadow-sm border-border">
|
||||||
inputs={{ width, length, height, roofPitch }}
|
<CardHeader className="pb-3 flex flex-row items-start justify-between space-y-0 pr-6">
|
||||||
/>
|
<div>
|
||||||
</CardHeader>
|
<CardTitle className="text-lg">Geometria e Vento</CardTitle>
|
||||||
|
<CardDescription>Tabelas 4 e 5 — NBR 6123.</CardDescription>
|
||||||
|
</div>
|
||||||
|
<SaveModuleDialog
|
||||||
|
moduleType="galpao"
|
||||||
|
inputs={{ width, length, height, roofPitch }}
|
||||||
|
/>
|
||||||
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<WindParametersSummary />
|
<WindParametersSummary />
|
||||||
|
|
||||||
@@ -98,7 +107,7 @@ const GalpaoModule: React.FC = () => {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<AlertCircle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
|
<AlertCircle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
|
||||||
<p className="text-xs text-amber-600 dark:text-amber-400">
|
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||||||
A platibanda bloqueia o fluxo. Calcule os esforços sobre ela no módulo de <b>Muros/Placas</b>.
|
A platibanda bloqueia o fluxo. Calcule os esforços nela no módulo de <b>Muros/Placas</b>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,55 +252,83 @@ const GalpaoModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<CpiSettingsCard />
|
<TabsContent value="permeabilidade" className="space-y-4 m-0">
|
||||||
|
<CpiSettingsCard />
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Coluna Direita: Viewer 3D */}
|
{/* Coluna Central: Viewer 3D e Heatmap (Tab) */}
|
||||||
<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="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 right-4 z-10 flex flex-col sm:flex-row sm:justify-between gap-3 pointer-events-none">
|
<Tabs defaultValue="3d" className="w-full h-full flex flex-col">
|
||||||
<div className="flex flex-wrap gap-2 pointer-events-auto">
|
<div className="absolute top-4 left-4 right-4 z-10 flex flex-col sm:flex-row sm:justify-between gap-3 pointer-events-none">
|
||||||
<Badge variant="secondary" className="bg-background/80 backdrop-blur-sm border-muted-foreground/20 text-foreground shadow-sm">
|
<div className="flex flex-wrap gap-2 pointer-events-auto">
|
||||||
Vento a {windAngle}°
|
<Badge variant="secondary" className="bg-background/80 backdrop-blur-sm border-muted-foreground/20 text-foreground shadow-sm">
|
||||||
</Badge>
|
Vento a {windAngle}°
|
||||||
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
</Badge>
|
||||||
Cpi = {cpi.toFixed(2)}
|
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
||||||
</Badge>
|
Cpi = {cpi.toFixed(2)}
|
||||||
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
</Badge>
|
||||||
q = {q.toFixed(3)} kN/m²
|
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
||||||
</Badge>
|
q = {q.toFixed(3)} kN/m²
|
||||||
</div>
|
</Badge>
|
||||||
<div className="flex bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border pointer-events-auto ml-auto">
|
</div>
|
||||||
<Button
|
|
||||||
variant={viewMode === 'solid' ? 'secondary' : 'ghost'}
|
<div className="flex pointer-events-auto bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border">
|
||||||
size="sm"
|
<TabsList className="h-7 px-1">
|
||||||
className="h-7 px-3 text-xs"
|
<TabsTrigger value="3d" className="text-xs h-6 px-3">Modelo 3D</TabsTrigger>
|
||||||
onClick={() => setViewMode('solid')}
|
<TabsTrigger value="heatmap" className="text-xs h-6 px-3">Heatmap 2D</TabsTrigger>
|
||||||
>
|
</TabsList>
|
||||||
<Box className="w-3.5 h-3.5 mr-1.5" />
|
</div>
|
||||||
Sólido
|
|
||||||
</Button>
|
<div className="flex pointer-events-auto ml-1">
|
||||||
<Button
|
<Button asChild size="sm" variant="outline" className="h-7 px-2 text-xs bg-background/80 backdrop-blur-sm border-border shadow-sm">
|
||||||
variant={viewMode === 'airflow' ? 'secondary' : 'ghost'}
|
<Link to="/compare">
|
||||||
size="sm"
|
<Columns className="w-3.5 h-3.5 mr-1.5" />
|
||||||
className="h-7 px-3 text-xs"
|
Modo A/B
|
||||||
onClick={() => setViewMode('airflow')}
|
</Link>
|
||||||
>
|
</Button>
|
||||||
<Wind className="w-3.5 h-3.5 mr-1.5" />
|
</div>
|
||||||
Fluxo
|
|
||||||
</Button>
|
<div className="flex flex-wrap gap-2 pointer-events-auto justify-start sm:justify-end ml-auto">
|
||||||
|
<EducationalManual type="warehouse" params={{ windAngle, cpi }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2 pointer-events-auto justify-start sm:justify-end">
|
<TabsContent value="3d" className="flex-1 m-0 h-full w-full relative">
|
||||||
<EducationalManual type="warehouse" params={{ windAngle, cpi }} />
|
<div className="absolute top-4 left-4 z-20 flex bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border pointer-events-auto">
|
||||||
</div>
|
<Button
|
||||||
</div>
|
variant={viewMode === 'solid' ? 'secondary' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
className="h-7 px-3 text-xs"
|
||||||
|
onClick={() => setViewMode('solid')}
|
||||||
|
>
|
||||||
|
<Box className="w-3.5 h-3.5 mr-1.5" />
|
||||||
|
Sólido
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={viewMode === 'airflow' ? 'secondary' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
className="h-7 px-3 text-xs"
|
||||||
|
onClick={() => setViewMode('airflow')}
|
||||||
|
>
|
||||||
|
<Wind className="w-3.5 h-3.5 mr-1.5" />
|
||||||
|
Fluxo
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<PressureLegend />
|
<PressureLegend />
|
||||||
|
<div className="w-full h-full cursor-grab active:cursor-grabbing bg-muted/20">
|
||||||
|
<Warehouse3DViewer />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<div className="w-full h-full cursor-grab active:cursor-grabbing bg-muted/20">
|
<TabsContent value="heatmap" className="flex-1 m-0 h-full w-full bg-muted/10 overflow-auto flex items-center justify-center">
|
||||||
<Warehouse3DViewer />
|
<Heatmap2D />
|
||||||
</div>
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Coluna Direita-Inferior: Cargas Lineares (M9.2) */}
|
{/* Coluna Direita-Inferior: Cargas Lineares (M9.2) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user