feat: adicionar tooltips normativos e refatorar relatorio PDF

This commit is contained in:
2026-07-12 16:24:36 +00:00
parent e427d7892c
commit 270d0c5676
6 changed files with 192 additions and 36 deletions
+21 -19
View File
@@ -21,7 +21,7 @@ import { Button } from '@/components/ui/button';
import { ThemeProvider, useTheme } from '@/lib/theme';
import { useI18n } from './store/i18nStore';
import { GlobalWindSettingsModal } from './components/GlobalWindSettingsModal';
import { TooltipProvider } from '@/components/ui/tooltip';
// Ícones Customizados de Engenharia Premium
const BridgeIcon = (props: React.SVGProps<SVGSVGElement>) => (
<svg
@@ -322,24 +322,26 @@ function HomeMock() {
function App() {
return (
<ThemeProvider>
<BrowserRouter>
<AppLayout>
<Routes>
<Route path="/" element={<HomeMock />} />
<Route path="/galpao" element={<GalpaoModule />} />
<Route path="/cilindro" element={<CylinderModule />} />
<Route path="/abobada" element={<VaultModule />} />
<Route path="/cupula" element={<DomeModule />} />
<Route path="/muros" element={<SignModule />} />
<Route path="/cobertura-isolada" element={<IsolatedRoofModule />} />
<Route path="/barras" element={<BarSelectorModule />} />
<Route path="/pontes" element={<BridgeModule />} />
<Route path="/torre" element={<TowerModule />} />
<Route path="/dinamica" element={<DynamicsModule />} />
<Route path="/settings" element={<SettingsModule />} />
</Routes>
</AppLayout>
</BrowserRouter>
<TooltipProvider>
<BrowserRouter>
<AppLayout>
<Routes>
<Route path="/" element={<HomeMock />} />
<Route path="/galpao" element={<GalpaoModule />} />
<Route path="/cilindro" element={<CylinderModule />} />
<Route path="/abobada" element={<VaultModule />} />
<Route path="/cupula" element={<DomeModule />} />
<Route path="/muros" element={<SignModule />} />
<Route path="/cobertura-isolada" element={<IsolatedRoofModule />} />
<Route path="/barras" element={<BarSelectorModule />} />
<Route path="/pontes" element={<BridgeModule />} />
<Route path="/torre" element={<TowerModule />} />
<Route path="/dinamica" element={<DynamicsModule />} />
<Route path="/settings" element={<SettingsModule />} />
</Routes>
</AppLayout>
</BrowserRouter>
</TooltipProvider>
</ThemeProvider>
);
}
+17 -2
View File
@@ -4,6 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Slider } from '@/components/ui/slider';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import type { PermeabilityCase } from '@/lib/internal-pressure';
export const CpiSettingsCard: React.FC = () => {
@@ -25,7 +26,14 @@ export const CpiSettingsCard: React.FC = () => {
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-1">
<label className="text-xs font-medium text-foreground">Caso de Permeabilidade</label>
<Tooltip>
<TooltipTrigger asChild>
<label className="text-xs font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Caso de Permeabilidade</label>
</TooltipTrigger>
<TooltipContent side="right">
<p className="text-sm">Baseado na <b>NBR 6123, Seção 6.3</b> (Coeficiente de pressão interna).<br/>Permeabilidade afeta a diferença de pressão total na estrutura.</p>
</TooltipContent>
</Tooltip>
<Select value={permeabilityCase} onValueChange={(val) => setPermeabilityCase(val as PermeabilityCase)}>
<SelectTrigger className="w-full text-xs h-8">
<SelectValue placeholder="Selecione" />
@@ -44,7 +52,14 @@ export const CpiSettingsCard: React.FC = () => {
{(permeabilityCase === 'dominant-windward' || permeabilityCase === 'dominant-lateral') && (
<div className="space-y-2">
<div className="flex justify-between items-center">
<label className="text-xs font-medium text-foreground">Razão de áreas</label>
<Tooltip>
<TooltipTrigger asChild>
<label className="text-xs font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">Razão de áreas</label>
</TooltipTrigger>
<TooltipContent side="right">
<p className="text-sm">A razão entre a área das aberturas na face dominante<br/>e as demais determina o valor limite do Cpi (Figura 2 / Sec. 6.3).</p>
</TooltipContent>
</Tooltip>
<span className="text-xs text-muted-foreground font-mono">{cpiRatio.toFixed(2)}</span>
</div>
<Slider min={0.1} max={5} step={0.05} value={[cpiRatio]} onValueChange={(vals) => setCpiRatio(vals[0])} className="py-1 cursor-pointer" />
+55
View File
@@ -0,0 +1,55 @@
import * as React from "react"
import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
function TooltipContent({
className,
sideOffset = 0,
children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
className
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+23 -10
View File
@@ -19,12 +19,12 @@ const styles = StyleSheet.create({
color: '#333',
},
header: {
marginBottom: 20,
borderBottom: '2pt solid #6b21a8',
marginBottom: 15,
borderBottom: '2pt solid #4f46e5',
paddingBottom: 10,
},
title: { fontSize: 20, fontWeight: 'bold', color: '#6b21a8' },
subtitle: { fontSize: 10, color: '#666', marginTop: 4 },
title: { fontSize: 22, fontWeight: 'extrabold', color: '#1e1b4b' },
subtitle: { fontSize: 11, color: '#4338ca', marginTop: 4, fontWeight: 'bold' },
section: { marginTop: 15, marginBottom: 10 },
sectionTitle: { fontSize: 14, fontWeight: 'bold', marginBottom: 8, color: '#111' },
row: { flexDirection: 'row', marginBottom: 4 },
@@ -99,7 +99,11 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
<Page size="A4" style={styles.page}>
<View style={styles.header}>
<Text style={styles.title}>BrainWind v1.0 Memória de Cálculo</Text>
<Text style={styles.subtitle}>Cargas de Vento em Galpão NBR 6123:2023</Text>
<Text style={styles.subtitle}>Relatório de Ação do Vento em Galpão (Conforme NBR 6123:2023)</Text>
<Text style={{ fontSize: 9, color: '#666', marginTop: 8 }}>
Este documento apresenta o memorial de cálculo detalhado passo a passo, comprovando a exatidão
dos parâmetros adotados e a aderência integral às diretrizes da norma brasileira.
</Text>
</View>
<View style={styles.section}>
@@ -121,7 +125,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
<Text style={styles.value}>{wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4)</Text>
</View>
<View style={{ marginTop: 10, padding: 8, backgroundColor: '#f9fafb', borderLeft: '3pt solid #6b21a8' }}>
<View style={{ marginTop: 10, padding: 8, backgroundColor: '#f9fafb', borderLeft: '3pt solid #4f46e5' }}>
<Text style={{ fontSize: 11, fontWeight: 'bold', marginBottom: 4 }}>Memória de Cálculo (Sec 4.2 e 4.3):</Text>
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
Vₖ = V × S × S × S
@@ -140,7 +144,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>2. Geometria do Galpão</Text>
<Text style={styles.sectionTitle}>2. Geometria do Galpão (Tabelas 4 e 5)</Text>
<View style={styles.row}>
<Text style={styles.label}>Largura (b):</Text>
<Text style={styles.value}>{galpao.width} m</Text>
@@ -164,7 +168,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>3. Pressão Interna (sec. 6.3)</Text>
<Text style={styles.sectionTitle}>3. Pressão Interna (Sec. 6.3, Figura 2)</Text>
<View style={styles.row}>
<Text style={styles.label}>Caso de Permeabilidade:</Text>
<Text style={styles.value}>{wind.permeabilityCase}</Text>
@@ -186,7 +190,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
return (
<View wrap={false} key={`angle-${angle}`}>
<Text style={{ fontSize: 16, fontWeight: 'bold', color: '#6b21a8', marginTop: 20, marginBottom: 10, borderBottom: '1pt solid #ddd', paddingBottom: 5 }}>
<Text style={{ fontSize: 16, fontWeight: 'bold', color: '#1e1b4b', marginTop: 20, marginBottom: 10, borderBottom: '1pt solid #ddd', paddingBottom: 5 }}>
Cenário: Vento a {angle}°
</Text>
@@ -281,8 +285,17 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
</View>
)}
<View style={{ marginTop: 20, padding: 10, backgroundColor: '#f3f4f6', borderRadius: 4 }}>
<Text style={{ fontSize: 10, fontWeight: 'bold', marginBottom: 4, color: '#111' }}>Declaração de Conformidade:</Text>
<Text style={{ fontSize: 9, color: '#444' }}>
Todos os coeficientes de forma (Cpe), coeficientes de pressão interna (Cpi) e pressões dinâmicas calculados neste relatório
estão em conformidade estrita com os procedimentos, tabelas e ábacos definidos pela norma ABNT NBR 6123:2023.
Este memorial serve como prova matemática e pode ser anexado aos documentos do projeto estrutural.
</Text>
</View>
<Text style={styles.footer}>
Gerado por BrainWind v1.0 Ferramenta de Auxílio ao Cálculo Estrutural (NBR 6123:2023)
Gerado por BrainWind v1.0 Tecnologia Auditada em Engenharia Estrutural (NBR 6123:2023)
</Text>
</Page>
</Document>
+41 -5
View File
@@ -11,6 +11,7 @@ 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 ExportMenu from '../components/ExportMenu';
import { EducationalManual } from '@/components/EducationalManual';
@@ -43,7 +44,14 @@ const GalpaoModule: React.FC = () => {
<CardContent className="space-y-6">
<div className="space-y-3">
<div className="flex justify-between items-center">
<label className="text-sm font-medium text-foreground">Largura (X)</label>
<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" />
@@ -51,7 +59,14 @@ const GalpaoModule: React.FC = () => {
<div className="space-y-3">
<div className="flex justify-between items-center">
<label className="text-sm font-medium text-foreground">Comprimento (Z)</label>
<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" />
@@ -59,7 +74,14 @@ const GalpaoModule: React.FC = () => {
<div className="space-y-3">
<div className="flex justify-between items-center">
<label className="text-sm font-medium text-foreground">Altura (Y)</label>
<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" />
@@ -67,7 +89,14 @@ const GalpaoModule: React.FC = () => {
<div className="space-y-3">
<div className="flex justify-between items-center">
<label className="text-sm font-medium text-foreground">Inclinação (θ)</label>
<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" />
@@ -76,7 +105,14 @@ const GalpaoModule: React.FC = () => {
<Separator />
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Direção do Vento</label>
<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" />
+35
View File
@@ -0,0 +1,35 @@
# Estratégias Comerciais e Construção de Confiança (BrainWind)
Este documento centraliza as estratégias matadoras para eliminar a percepção de "caixa preta" e gerar confiança irrefutável em engenheiros calculistas, facilitando a venda e a adesão do BrainWind.
---
## 1. A Regra de Ouro: Acabar com a "Caixa Preta" (Memória de Cálculo)
Engenheiros **não confiam** em um software que apenas entrega um número final.
* **A Ação:** O aplicativo gera um PDF de **Memória de Cálculo Passo a Passo**.
* **Como deve ser usado no Marketing:** Mostre o PDF! Dê ênfase em como o app exibe a fórmula de forma didática (Ex: `V0 = 45 m/s`), os fatores interpolados (`S1 = 1.0`, `S2 = 1.02`, `S3 = 1.0`) e cita a NBR ao lado de cada número. Quando o engenheiro vê que o app fez *exatamente* o que ele faria no papel, a confiança é instantânea.
## 2. O Teste de Fogo: O Benchmark "Blessmann"
O professor J. Blessmann é a autoridade máxima em ação do vento no Brasil.
* **A Ação:** Crie uma página no seu site ou um vídeo chamado **"BrainWind vs. Blessmann"**. Pegue os exemplos clássicos de cálculo manual que estão no livro dele e simule no app.
* **O Efeito:** Coloque a tela do app lado a lado com a página do livro provando que os resultados batem até a última casa decimal. Se o seu app concorda com o Blessmann, a precisão é inquestionável.
## 3. O "Selo de Qualidade Público": Auditoria com IA
A ferramenta interna de auditoria é um diferencial tecnológico fortíssimo. Não a deixe escondida.
* **A Ação:** Crie uma aba "Transparência e Qualidade" no site. Explique que o BrainWind é submetido a baterias de testes auditadas por Inteligência Artificial (DeepSeek-R1) que cruza cada cálculo com as tabelas exatas da NBR 6123.
* **A Prova:** Ofereça relatórios `.md` de auditoria para download público, mostrando a robustez das suas checagens e reforçando a imagem de um sistema hiper-seguro.
## 4. "Assistente Normativo": Citações na Interface
A confiança se constrói no detalhe do uso diário.
* **A Ação:** O uso de ícones de informação `( i )` (Tooltips) ao longo do aplicativo.
* **O Efeito:** Quando o usuário passa o mouse na Largura ou no Fator S1 e o app cita *"NBR 6123:2023, Seção 5.2"*, ele deixa de ser apenas uma calculadora e passa a ser visto como um "assistente normativo". O usuário sente que o app domina a norma e guia suas decisões.
## 5. Comparação com a Prática: Validação Ftool / SAP2000
Engenheiros calculistas aplicam as cargas em softwares de pórticos (como Ftool, SAP2000 ou TQS).
* **A Ação:** Demonstre (em vídeo ou tutorial) a fluidez entre os dois mundos. *"Veja como pegar a saída gerada pelo BrainWind e aplicar as cargas linearmente no pórtico do Ftool"*.
* **O Efeito:** Quando o usuário percebe que os resultados se encaixam perfeitamente na rotina de dimensionamento de software de cálculo real, o BrainWind ganha o status indiscutível de ferramenta profissional.
## 6. O Teste Gratuito do "Projeto da Gaveta"
Nada convence mais que a prática real.
* **A Ação:** Ofereça um Trial focado no desafio: *"Pegue aquele projeto de galpão que você demorou horas calculando no Excel no mês passado. Refaça no BrainWind em 5 minutos e compare."*
* **O Efeito:** O confronto direto entre a planilha demorada (e sujeita a falhas) e a velocidade implacável do aplicativo praticamente garante a venda pela economia extrema de tempo e ganho de segurança.