358 lines
18 KiB
TypeScript
358 lines
18 KiB
TypeScript
import React from 'react';
|
|
import Warehouse3DViewer from '../components/Warehouse3D';
|
|
import Heatmap2D from '../components/Heatmap2D';
|
|
import LinearLoadsTable from '../components/LinearLoadsTable';
|
|
import SceneCapturePanel from '../components/SceneCapturePanel';
|
|
import FtoolExportCard from '../components/FtoolExportCard';
|
|
import { CpiSettingsCard } from '../components/CpiSettingsCard';
|
|
import { useGalpaoStore } from '../store/galpaoStore';
|
|
import { useWindStore } from '../store/appStore';
|
|
import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } from '../lib/coefficients';
|
|
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 { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
|
import { Button } from '@/components/ui/button';
|
|
import { AlertCircle, Lightbulb, Box, Wind } from 'lucide-react';
|
|
import ExportMenu from '../components/ExportMenu';
|
|
import { EducationalManual } from '@/components/EducationalManual';
|
|
import { PressureLegend } from '@/components/PressureLegend';
|
|
import { SaveModuleDialog } from '@/components/SaveModuleDialog';
|
|
import { WindParametersSummary } from '@/components/WindParametersSummary';
|
|
import { useHydrationStore } from '@/store/hydrationStore';
|
|
|
|
const GalpaoModule: React.FC = () => {
|
|
const {
|
|
width,
|
|
length,
|
|
height,
|
|
roofPitch,
|
|
template,
|
|
extraHeight,
|
|
skirtHeight,
|
|
setTemplate,
|
|
setExtraHeight,
|
|
setSkirtHeight,
|
|
setWidth,
|
|
setLength,
|
|
setHeight,
|
|
setRoofPitch,
|
|
viewMode,
|
|
setViewMode,
|
|
} = useGalpaoStore();
|
|
const {
|
|
q,
|
|
cpi,
|
|
windAngle,
|
|
setWindAngle,
|
|
} = useWindStore();
|
|
|
|
const pendingLoad = useHydrationStore((s) => s.pendingLoad);
|
|
const clearPendingLoad = useHydrationStore((s) => s.clearPendingLoad);
|
|
|
|
React.useEffect(() => {
|
|
if (pendingLoad && pendingLoad.module === 'galpao') {
|
|
const p = pendingLoad.inputs as any;
|
|
if (p.width !== undefined) setWidth(p.width);
|
|
if (p.length !== undefined) setLength(p.length);
|
|
if (p.height !== undefined) setHeight(p.height);
|
|
if (p.roofPitch !== undefined) setRoofPitch(p.roofPitch);
|
|
clearPendingLoad();
|
|
}
|
|
}, [pendingLoad, clearPendingLoad, setWidth, setLength, setHeight, setRoofPitch]);
|
|
|
|
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-96 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
|
|
<Tabs defaultValue="geometria" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2 mb-4">
|
|
<TabsTrigger value="geometria">1. Geometria</TabsTrigger>
|
|
<TabsTrigger value="permeabilidade">2. Permeabilidade</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="geometria" className="space-y-4 m-0">
|
|
<Card className="shadow-sm border-border">
|
|
<CardHeader className="pb-3 flex flex-row items-start justify-between space-y-0 pr-6">
|
|
<div>
|
|
<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">
|
|
<WindParametersSummary />
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<label className="text-sm font-medium text-foreground">Template Visual</label>
|
|
</div>
|
|
<Tabs value={template} onValueChange={(v) => setTemplate(v as any)} className="w-full">
|
|
<TabsList className="w-full grid grid-cols-2 md:grid-cols-4 h-auto p-1">
|
|
<TabsTrigger value="standard" className="text-xs py-1.5">Padrão</TabsTrigger>
|
|
<TabsTrigger value="parapet" className="text-xs py-1.5">Platibanda</TabsTrigger>
|
|
<TabsTrigger value="skirt" className="text-xs py-1.5">Saia</TabsTrigger>
|
|
<TabsTrigger value="skirt_parapet" className="text-xs py-1.5">Saia + Plat.</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
|
|
{(template === 'parapet' || template === 'skirt_parapet') && (
|
|
<div className="bg-amber-500/10 border border-amber-500/20 rounded-md p-3 mt-3">
|
|
<div className="flex gap-2">
|
|
<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">
|
|
A platibanda bloqueia o fluxo. Calcule os esforços nela no módulo de <b>Muros/Placas</b>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{(template === 'skirt' || template === 'skirt_parapet') && (
|
|
<div className="bg-blue-500/10 border border-blue-500/20 rounded-md p-3 mt-3">
|
|
<div className="flex gap-2">
|
|
<Lightbulb className="w-4 h-4 text-blue-500 shrink-0 mt-0.5" />
|
|
<p className="text-xs text-blue-600 dark:text-blue-400">
|
|
Galpão aberto funciona como uma Cobertura Isolada.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{template !== 'standard' && (
|
|
<div className="space-y-4 bg-muted/50 p-3 rounded-lg border border-border">
|
|
{(template === 'parapet' || template === 'skirt_parapet') && (
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">
|
|
Altura da Platibanda
|
|
</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Altura adicional modelada visualmente no topo.</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<span className="text-sm text-muted-foreground font-mono">{extraHeight} m</span>
|
|
</div>
|
|
<Slider min={0.5} max={5} step={0.1} value={[extraHeight]} onValueChange={(vals) => setExtraHeight(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
)}
|
|
|
|
{(template === 'skirt' || template === 'skirt_parapet') && (
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">
|
|
Altura da Saia
|
|
</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Altura da saia fechada lateral (de cima para baixo).</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<span className="text-sm text-muted-foreground font-mono">{skirtHeight} m</span>
|
|
</div>
|
|
<Slider min={0.5} max={height} step={0.1} value={[skirtHeight]} onValueChange={(vals) => setSkirtHeight(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<Separator />
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Largura (X)</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Determina as proporções em planta (a/b)<br/>usadas nas <b>Tabelas 4 e 5</b> da NBR 6123.</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<span className="text-sm text-muted-foreground font-mono">{width} m</span>
|
|
</div>
|
|
<Slider min={5} max={80} step={1} value={[width]} onValueChange={(vals) => setWidth(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Comprimento (Z)</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Usado nas proporções a/b e b/a (Tabelas 4 e 5).<br/>Importante também para o cálculo de Atrito.</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<span className="text-sm text-muted-foreground font-mono">{length} m</span>
|
|
</div>
|
|
<Slider min={5} max={150} step={1} value={[length]} onValueChange={(vals) => setLength(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Altura (Y)</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Altura total. Define a proporção h/b para S₂ (Tabela 1).<br/>Influencia nas <b>Tabelas 4 e 5</b> para cálculo do Cpe.</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<span className="text-sm text-muted-foreground font-mono">{height} m</span>
|
|
</div>
|
|
<Slider min={3} max={40} step={0.5} value={[height]} onValueChange={(vals) => setHeight(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between items-center">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Inclinação (θ)</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Ângulo do telhado (<b>Tabela 5</b>).<br/>Acima de 60° interpola para Cpe de paredes.</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<span className="text-sm text-muted-foreground font-mono">{roofPitch}°</span>
|
|
</div>
|
|
<Slider min={0} max={60} step={1} value={[roofPitch]} onValueChange={(vals) => setRoofPitch(vals[0])} className="py-1 cursor-pointer" />
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div className="space-y-2">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Direção do Vento</label>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">
|
|
<p className="text-sm">Define a face barlavento. Tabelas da NBR são lidas para 0° e 90° separadamente.</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<Select value={windAngle.toString()} onValueChange={(val) => setWindAngle(Number(val) as 0 | 90)}>
|
|
<SelectTrigger className="w-full">
|
|
<SelectValue placeholder="Selecione a direção" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="0">0° (Perpendicular ao comprimento)</SelectItem>
|
|
<SelectItem value="90">90° (Perpendicular à largura)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="permeabilidade" className="space-y-4 m-0">
|
|
<CpiSettingsCard />
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
|
|
{/* 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">
|
|
<Tabs defaultValue="3d" className="w-full h-full flex flex-col">
|
|
<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">
|
|
<div className="flex flex-wrap gap-2 pointer-events-auto">
|
|
<Badge variant="secondary" className="bg-background/80 backdrop-blur-sm border-muted-foreground/20 text-foreground shadow-sm">
|
|
Vento a {windAngle}°
|
|
</Badge>
|
|
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
|
Cpi = {cpi.toFixed(2)}
|
|
</Badge>
|
|
<Badge variant="outline" className="bg-background/80 backdrop-blur-sm">
|
|
q = {q.toFixed(3)} kN/m²
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="flex pointer-events-auto bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border">
|
|
<TabsList className="h-7 px-1">
|
|
<TabsTrigger value="3d" className="text-xs h-6 px-3">Modelo 3D</TabsTrigger>
|
|
<TabsTrigger value="heatmap" className="text-xs h-6 px-3">Heatmap 2D</TabsTrigger>
|
|
<TabsTrigger value="compare" className="text-xs h-6 px-3">Modo A/B</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<TabsContent value="3d" className="flex-1 m-0 h-full w-full relative">
|
|
<div className="absolute bottom-20 left-4 z-20 flex bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border pointer-events-auto">
|
|
<Button
|
|
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 />
|
|
<div className="w-full h-full cursor-grab active:cursor-grabbing bg-muted/20">
|
|
<Warehouse3DViewer />
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="heatmap" className="flex-1 m-0 h-full w-full bg-muted/10 overflow-auto flex items-center justify-center">
|
|
<Heatmap2D />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="compare" className="flex-1 m-0 h-full w-full bg-muted/10 overflow-auto p-4 flex flex-col">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 flex-1">
|
|
<Heatmap2D
|
|
overrideAngle={0}
|
|
overrideWallCpe={getWallCpe(length, width, height, 0)}
|
|
overrideRoofCpe={getRoofCpe(length, width, height, roofPitch, 0)}
|
|
/>
|
|
<Heatmap2D
|
|
overrideAngle={90}
|
|
overrideWallCpe={getWallCpe(length, width, height, 90)}
|
|
overrideRoofCpe={getRoofCpe(length, width, height, roofPitch, 90)}
|
|
/>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
|
|
{/* Coluna Direita-Inferior: Cargas Lineares (M9.2) */}
|
|
<div className="w-full lg:w-96 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
|
|
<LinearLoadsTable />
|
|
|
|
<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 />
|
|
</div>
|
|
</div>
|
|
|
|
<SceneCapturePanel />
|
|
<FtoolExportCard />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default GalpaoModule; |