fix: resolve loading infinito na navegação entre módulos
- Adiciona ErrorBoundary para capturar erros do React.lazy() - LoadingFallback com timeout: aviso aos 3s, erro aos 10s - Atualiza documentação (SPEC_NAVBAR.md, AGENTS.md) O Suspense não captura falhas no lazy loading. Agora o ErrorBoundary envolve o Suspense e exibe UI de fallback quando há erros.
This commit is contained in:
parent
317926f600
commit
b037512250
|
|
@ -0,0 +1,26 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- region "Notifications (F8)":
|
||||||
|
- list
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- img "Arcadia" [ref=e7]
|
||||||
|
- heading "Arcádia Suite" [level=1] [ref=e8]
|
||||||
|
- generic [ref=e9]:
|
||||||
|
- tablist [ref=e10]:
|
||||||
|
- tab "Login" [selected] [ref=e11]
|
||||||
|
- tab "Criar Conta" [ref=e12]
|
||||||
|
- tabpanel "Login" [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- generic [ref=e16]: Bem-vindo de volta
|
||||||
|
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]:
|
||||||
|
- text: Usuário
|
||||||
|
- textbox "Usuário" [ref=e21]:
|
||||||
|
- /placeholder: Digite seu usuário
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- text: Senha
|
||||||
|
- textbox "Senha" [ref=e23]:
|
||||||
|
- /placeholder: Digite sua senha
|
||||||
|
- button "Entrar" [ref=e24]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,34 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- region "Notifications (F8)":
|
||||||
|
- list [ref=e26]:
|
||||||
|
- status [ref=e27]:
|
||||||
|
- generic [ref=e28]:
|
||||||
|
- generic [ref=e29]: Login failed
|
||||||
|
- generic [ref=e30]: Invalid username or password
|
||||||
|
- button [ref=e31]:
|
||||||
|
- img [ref=e32]
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- img "Arcadia" [ref=e7]
|
||||||
|
- heading "Arcádia Suite" [level=1] [ref=e8]
|
||||||
|
- generic [ref=e9]:
|
||||||
|
- tablist [ref=e10]:
|
||||||
|
- tab "Login" [selected] [ref=e11]
|
||||||
|
- tab "Criar Conta" [ref=e12]
|
||||||
|
- tabpanel "Login" [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- generic [ref=e16]: Bem-vindo de volta
|
||||||
|
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]:
|
||||||
|
- text: Usuário
|
||||||
|
- textbox "Usuário" [ref=e21]:
|
||||||
|
- /placeholder: Digite seu usuário
|
||||||
|
- text: admin
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- text: Senha
|
||||||
|
- textbox "Senha" [ref=e23]:
|
||||||
|
- /placeholder: Digite sua senha
|
||||||
|
- text: admin
|
||||||
|
- button "Entrar" [ref=e24]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -0,0 +1,28 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- region "Notifications (F8)":
|
||||||
|
- list
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- img "Arcadia" [ref=e7]
|
||||||
|
- heading "Arcádia Suite" [level=1] [ref=e8]
|
||||||
|
- generic [ref=e9]:
|
||||||
|
- tablist [ref=e10]:
|
||||||
|
- tab "Login" [selected] [ref=e11]
|
||||||
|
- tab "Criar Conta" [ref=e12]
|
||||||
|
- tabpanel "Login" [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- generic [ref=e16]: Bem-vindo de volta
|
||||||
|
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]:
|
||||||
|
- text: Usuário
|
||||||
|
- textbox "Usuário" [ref=e21]:
|
||||||
|
- /placeholder: Digite seu usuário
|
||||||
|
- text: admin
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- text: Senha
|
||||||
|
- textbox "Senha" [ref=e23]:
|
||||||
|
- /placeholder: Digite sua senha
|
||||||
|
- text: admin
|
||||||
|
- button "Entrar" [ref=e24]
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- region "Notifications (F8)":
|
||||||
|
- list
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- img "Arcadia" [ref=e7]
|
||||||
|
- heading "Arcádia Suite" [level=1] [ref=e8]
|
||||||
|
- generic [ref=e9]:
|
||||||
|
- tablist [ref=e10]:
|
||||||
|
- tab "Login" [selected] [ref=e11]
|
||||||
|
- tab "Criar Conta" [ref=e12]
|
||||||
|
- tabpanel "Login" [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- generic [ref=e16]: Bem-vindo de volta
|
||||||
|
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]:
|
||||||
|
- text: Usuário
|
||||||
|
- textbox "Usuário" [ref=e21]:
|
||||||
|
- /placeholder: Digite seu usuário
|
||||||
|
- text: admin
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- text: Senha
|
||||||
|
- textbox "Senha" [ref=e23]:
|
||||||
|
- /placeholder: Digite sua senha
|
||||||
|
- text: admin
|
||||||
|
- button "Entrando..." [disabled]:
|
||||||
|
- img
|
||||||
|
- text: Entrando...
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
- generic [ref=e2]:
|
||||||
|
- region "Notifications (F8)":
|
||||||
|
- list
|
||||||
|
- generic [ref=e5]:
|
||||||
|
- generic [ref=e6]:
|
||||||
|
- img "Arcadia" [ref=e7]
|
||||||
|
- heading "Arcádia Suite" [level=1] [ref=e8]
|
||||||
|
- generic [ref=e9]:
|
||||||
|
- tablist [ref=e10]:
|
||||||
|
- tab "Login" [selected] [ref=e11]
|
||||||
|
- tab "Criar Conta" [ref=e12]
|
||||||
|
- tabpanel "Login" [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- generic [ref=e16]: Bem-vindo de volta
|
||||||
|
- generic [ref=e17]: Entre com suas credenciais para acessar o sistema
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]:
|
||||||
|
- text: Usuário
|
||||||
|
- textbox "Usuário" [ref=e21]:
|
||||||
|
- /placeholder: Digite seu usuário
|
||||||
|
- text: admin
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- text: Senha
|
||||||
|
- textbox "Senha" [ref=e23]:
|
||||||
|
- /placeholder: Digite sua senha
|
||||||
|
- text: admin
|
||||||
|
- button "Entrar" [ref=e24]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -0,0 +1,203 @@
|
||||||
|
# 🤖 Referência Rápida para Agentes - Arcádia Suite
|
||||||
|
|
||||||
|
> Guia de referência para desenvolvimento autônomo no projeto Arcádia Suite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Documentação Técnica por Componente
|
||||||
|
|
||||||
|
### Navegação (Navbar)
|
||||||
|
- **Especificação Completa:** [`docs/SPEC_NAVBAR.md`](docs/SPEC_NAVBAR.md)
|
||||||
|
- **Arquivo Fonte:** `client/src/components/Browser/BrowserFrame.tsx`
|
||||||
|
- **Hook de Tracking:** `client/src/hooks/use-navigation-tracking.ts`
|
||||||
|
- **Error Boundary:** `client/src/components/ErrorBoundary.tsx` (tratamento de erros lazy loading)
|
||||||
|
|
||||||
|
### Banco de Dados (Schema)
|
||||||
|
- **Arquivo Principal:** `shared/schema.ts` (7.000+ linhas)
|
||||||
|
- **Tabelas Principais:**
|
||||||
|
- `users`, `profiles`, `roles` - Autenticação e RBAC
|
||||||
|
- `tenants`, `tenantUsers` - Multi-tenancy
|
||||||
|
- `workspacePages`, `pageBlocks` - Produtividade (estilo Notion)
|
||||||
|
- `conversations`, `messages` - Chat/Agente
|
||||||
|
- `whatsappSessions`, `whatsappMessages` - WhatsApp
|
||||||
|
- `crmPartners`, `crmContracts` - CRM
|
||||||
|
- `fiscalNcms`, `fiscalGruposTributacao` - Fiscal
|
||||||
|
- `retailStores`, `posSales`, `mobileDevices` - Retail
|
||||||
|
- `manusRuns`, `manusSteps` - Agente Manus
|
||||||
|
- `biDatasets`, `biCharts`, `biDashboards` - BI
|
||||||
|
|
||||||
|
### Backend (API Routes)
|
||||||
|
- **Registro de Rotas:** `server/routes.ts`
|
||||||
|
- **Autenticação:** `server/auth.ts`
|
||||||
|
- **Agente Manus:** `server/manus/service.ts` + `server/manus/tools.ts`
|
||||||
|
- **WhatsApp:** `server/whatsapp/routes.ts` + `server/whatsapp/service.ts`
|
||||||
|
- **CRM:** `server/crm/routes.ts`
|
||||||
|
- **Fiscal:** `server/fisco/routes.ts`
|
||||||
|
- **Retail:** `server/retail/routes.ts`
|
||||||
|
- **Blackboard (Agentes):** `server/blackboard/routes.ts`
|
||||||
|
|
||||||
|
### Frontend (Páginas)
|
||||||
|
- **Router:** `client/src/App.tsx` (inclui ErrorBoundary + Suspense)
|
||||||
|
- **Cockpit (Home):** `client/src/pages/Cockpit.tsx`
|
||||||
|
- **Agent:** `client/src/pages/Agent.tsx`
|
||||||
|
- **DevCenter:** `client/src/pages/DevCenter.tsx`
|
||||||
|
- **Crm:** `client/src/pages/Crm.tsx`
|
||||||
|
- **Fisco:** `client/src/pages/Fisco.tsx`
|
||||||
|
- **Retail:** `client/src/pages/ArcadiaRetail.tsx`
|
||||||
|
- **WhatsApp:** `client/src/pages/WhatsApp.tsx`
|
||||||
|
- **ErrorBoundary:** `client/src/components/ErrorBoundary.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Arquitetura do Sistema
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAMADA DE APRESENTAÇÃO │
|
||||||
|
│ React 18 + TypeScript + Tailwind CSS + shadcn/ui │
|
||||||
|
│ 66 páginas / Interface tipo browser com abas │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAMADA DE ORQUESTRAÇÃO │
|
||||||
|
│ Express.js + Socket.IO + Manus Agent │
|
||||||
|
│ API REST + WebSocket em tempo real │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAMADA DE INTELIGÊNCIA │
|
||||||
|
│ FastAPI (Contábil 8003, BI 8004, Automação 8005) │
|
||||||
|
│ OpenAI GPT-4o + LiteLLM Gateway + Ollama │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ CAMADA DE DADOS │
|
||||||
|
│ PostgreSQL 16 + Drizzle ORM + pgvector │
|
||||||
|
│ Session Store + Multi-tenant │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Stack Tecnológico
|
||||||
|
|
||||||
|
| Camada | Tecnologia |
|
||||||
|
|--------|------------|
|
||||||
|
| **Frontend** | React 18, TypeScript, Tailwind CSS, shadcn/ui |
|
||||||
|
| **Backend** | Express.js, Socket.IO, Passport.js |
|
||||||
|
| **Banco** | PostgreSQL 16, Drizzle ORM, pgvector |
|
||||||
|
| **Python** | FastAPI (microserviços nas portas 8001-8005) |
|
||||||
|
| **IA** | OpenAI GPT-4o, LiteLLM, Ollama |
|
||||||
|
| **Deploy** | Docker Compose, Coolify, PM2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Comandos Úteis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Desenvolvimento
|
||||||
|
npm run dev # Inicia servidor de desenvolvimento
|
||||||
|
npm run dev:client # Apenas frontend (porta 5000)
|
||||||
|
|
||||||
|
# Banco de dados
|
||||||
|
npm run db:push # Aplica migrations Drizzle
|
||||||
|
npm run db:apply-rls # Aplica políticas RLS
|
||||||
|
|
||||||
|
# Build
|
||||||
|
npm run build # Build para produção
|
||||||
|
npm run start # Inicia em produção
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker compose up -d # Sobe todos os serviços
|
||||||
|
docker compose --profile ai up # Sobe com IA (Ollama + LiteLLM)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Convenções de Código
|
||||||
|
|
||||||
|
### Nomenclatura
|
||||||
|
- **Componentes:** PascalCase (ex: `BrowserFrame.tsx`)
|
||||||
|
- **Hooks:** camelCase com prefixo `use` (ex: `useAuth.tsx`)
|
||||||
|
- **Utilitários:** camelCase (ex: `navigation-tracking.ts`)
|
||||||
|
- **Rotas API:** kebab-case (ex: `/api/whatsapp/connect`)
|
||||||
|
|
||||||
|
### Estrutura de Pastas
|
||||||
|
```
|
||||||
|
client/src/
|
||||||
|
components/ # Componentes reutilizáveis
|
||||||
|
pages/ # Páginas da aplicação
|
||||||
|
hooks/ # Custom hooks
|
||||||
|
lib/ # Utilitários e configurações
|
||||||
|
|
||||||
|
server/
|
||||||
|
[modulo]/ # Um diretório por módulo
|
||||||
|
routes.ts # Rotas da API
|
||||||
|
service.ts # Lógica de negócio
|
||||||
|
storage.ts # Acesso a dados
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Test IDs
|
||||||
|
|
||||||
|
Sempre adicione `data-testid` em elementos interativos:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<button
|
||||||
|
data-testid="button-submit"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Enviar
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentação Adicional
|
||||||
|
|
||||||
|
- **Documentação Técnica Completa:** [`DOCUMENTATION.md`](DOCUMENTATION.md)
|
||||||
|
- **Mapa do Sistema:** [`MAPA_SISTEMA_ARCADIA.md`](MAPA_SISTEMA_ARCADIA.md)
|
||||||
|
- **Contexto para Claude:** [`CLAUDE.md`](CLAUDE.md)
|
||||||
|
- **README:** [`README.md`](README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Precisa de Ajuda?
|
||||||
|
|
||||||
|
1. Consulte a especificação técnica específica em `docs/`
|
||||||
|
2. Verifique exemplos similares no código existente
|
||||||
|
3. Siga os padrões já estabelecidos no projeto
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Última atualização: 2026-04-08*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Tratamento de Erros - Lazy Loading
|
||||||
|
|
||||||
|
### Problema Resolvido: Loading Infinito na Navegação
|
||||||
|
|
||||||
|
**Sintoma:** Ao clicar em itens da navbar, se a página tivesse erro (import quebrado, sintaxe inválida), o loading ficava infinito.
|
||||||
|
|
||||||
|
**Causa:** `React.lazy()` falha silenciosamente e `Suspense` não tem como capturar o erro.
|
||||||
|
|
||||||
|
**Solução:**
|
||||||
|
|
||||||
|
1. **ErrorBoundary** (`client/src/components/ErrorBoundary.tsx`):
|
||||||
|
- Captura erros de renderização, incluindo lazy load
|
||||||
|
- Mostra UI de fallback com botão de retry
|
||||||
|
|
||||||
|
2. **LoadingFallback com Timeout** (`client/src/App.tsx`):
|
||||||
|
- 3s: mensagem "Isso está demorando..."
|
||||||
|
- 10s: alerta amarelo com botão recarregar
|
||||||
|
|
||||||
|
3. **Integração:**
|
||||||
|
```tsx
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
|
<Switch>{/* rotas */}</Switch>
|
||||||
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
|
```
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Switch, Route, useLocation } from "wouter";
|
import { Switch, Route, useLocation } from "wouter";
|
||||||
import { lazy, Suspense, useEffect } from "react";
|
import { lazy, Suspense, useEffect, useState } from "react";
|
||||||
import { queryClient } from "./lib/queryClient";
|
import { queryClient } from "./lib/queryClient";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
|
@ -10,6 +10,7 @@ import { ProtectedRoute } from "@/lib/protected-route";
|
||||||
import { CommandPalette } from "@/components/CommandPalette";
|
import { CommandPalette } from "@/components/CommandPalette";
|
||||||
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
|
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
|
||||||
import { OpenClawWidget } from "@/components/openclaw/OpenClawWidget";
|
import { OpenClawWidget } from "@/components/openclaw/OpenClawWidget";
|
||||||
|
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
||||||
import NotFound from "@/pages/not-found";
|
import NotFound from "@/pages/not-found";
|
||||||
import AuthPage from "@/pages/auth-page";
|
import AuthPage from "@/pages/auth-page";
|
||||||
|
|
||||||
|
|
@ -72,13 +73,56 @@ const XosSupervisor = lazy(() => import("@/pages/XosSupervisor"));
|
||||||
const XosReports = lazy(() => import("@/pages/XosReports"));
|
const XosReports = lazy(() => import("@/pages/XosReports"));
|
||||||
const XosProtocols = lazy(() => import("@/pages/XosProtocols"));
|
const XosProtocols = lazy(() => import("@/pages/XosProtocols"));
|
||||||
|
|
||||||
|
|
||||||
function LoadingFallback() {
|
function LoadingFallback() {
|
||||||
|
const [showSlowLoading, setShowSlowLoading] = useState(false);
|
||||||
|
const [showError, setShowError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Show "slow loading" message after 3 seconds
|
||||||
|
const slowTimer = setTimeout(() => setShowSlowLoading(true), 3000);
|
||||||
|
// Show error hint after 10 seconds
|
||||||
|
const errorTimer = setTimeout(() => setShowError(true), 10000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(slowTimer);
|
||||||
|
clearTimeout(errorTimer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
|
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh", flexDirection: "column" }}>
|
||||||
<div style={{ textAlign: "center" }}>
|
<div style={{ textAlign: "center" }}>
|
||||||
<div style={{ width: 40, height: 40, border: "3px solid #e5e7eb", borderTopColor: "#3b82f6", borderRadius: "50%", animation: "spin 1s linear infinite", margin: "0 auto" }} />
|
<div style={{ width: 40, height: 40, border: "3px solid #e5e7eb", borderTopColor: "#3b82f6", borderRadius: "50%", animation: "spin 1s linear infinite", margin: "0 auto" }} />
|
||||||
<p style={{ marginTop: 16, color: "#6b7280" }}>Carregando...</p>
|
<p style={{ marginTop: 16, color: "#6b7280" }}>Carregando...</p>
|
||||||
|
|
||||||
|
{showSlowLoading && (
|
||||||
|
<p style={{ marginTop: 8, color: "#9ca3af", fontSize: 14 }}>
|
||||||
|
Isso está demorando mais que o esperado...
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showError && (
|
||||||
|
<div style={{ marginTop: 16, padding: 12, backgroundColor: "#fef3c7", borderRadius: 6, maxWidth: 300 }}>
|
||||||
|
<p style={{ color: "#92400e", fontSize: 13, margin: 0 }}>
|
||||||
|
A página pode ter um erro. Tente recarregar ou volte para a página anterior.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
style={{
|
||||||
|
marginTop: 8,
|
||||||
|
padding: "6px 12px",
|
||||||
|
backgroundColor: "#f59e0b",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: 13
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Recarregar Página
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
|
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -87,72 +131,74 @@ function LoadingFallback() {
|
||||||
|
|
||||||
function Router() {
|
function Router() {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<LoadingFallback />}>
|
<ErrorBoundary>
|
||||||
<Switch>
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
<ProtectedRoute path="/" component={Cockpit} />
|
<Switch>
|
||||||
<ProtectedRoute path="/agent" component={Agent} />
|
<ProtectedRoute path="/" component={Cockpit} />
|
||||||
<ProtectedRoute path="/admin" component={Admin} />
|
<ProtectedRoute path="/agent" component={Agent} />
|
||||||
<ProtectedRoute path="/chat" component={Chat} />
|
<ProtectedRoute path="/admin" component={Admin} />
|
||||||
<ProtectedRoute path="/whatsapp" component={WhatsApp} />
|
<ProtectedRoute path="/chat" component={Chat} />
|
||||||
<ProtectedRoute path="/comunicacao" component={XosInbox} />
|
<ProtectedRoute path="/whatsapp" component={WhatsApp} />
|
||||||
<ProtectedRoute path="/automations" component={Automations} />
|
<ProtectedRoute path="/comunicacao" component={XosInbox} />
|
||||||
<ProtectedRoute path="/insights" component={BiWorkspace} />
|
<ProtectedRoute path="/automations" component={Automations} />
|
||||||
<ProtectedRoute path="/compass" component={ProcessCompass} />
|
<ProtectedRoute path="/insights" component={BiWorkspace} />
|
||||||
<ProtectedRoute path="/crm" component={Crm} />
|
<ProtectedRoute path="/compass" component={ProcessCompass} />
|
||||||
<ProtectedRoute path="/production" component={Production} />
|
<ProtectedRoute path="/crm" component={Crm} />
|
||||||
<ProtectedRoute path="/support" component={Support} />
|
<ProtectedRoute path="/production" component={Production} />
|
||||||
<ProtectedRoute path="/valuation" component={Valuation} />
|
<ProtectedRoute path="/support" component={Support} />
|
||||||
<ProtectedRoute path="/canvas" component={Canvas} />
|
<ProtectedRoute path="/valuation" component={Valuation} />
|
||||||
<ProtectedRoute path="/ide" component={IDE} />
|
<ProtectedRoute path="/canvas" component={Canvas} />
|
||||||
<ProtectedRoute path="/scientist" component={Scientist} />
|
<ProtectedRoute path="/ide" component={IDE} />
|
||||||
<ProtectedRoute path="/knowledge" component={Knowledge} />
|
<ProtectedRoute path="/scientist" component={Scientist} />
|
||||||
<ProtectedRoute path="/central-apis" component={CentralApis} />
|
<ProtectedRoute path="/knowledge" component={Knowledge} />
|
||||||
<ProtectedRoute path="/api-tester" component={ApiTesterPage} />
|
<ProtectedRoute path="/central-apis" component={CentralApis} />
|
||||||
<ProtectedRoute path="/api-hub" component={ApiHub} />
|
<ProtectedRoute path="/api-tester" component={ApiTesterPage} />
|
||||||
<ProtectedRoute path="/fisco" component={Fisco} />
|
<ProtectedRoute path="/api-hub" component={ApiHub} />
|
||||||
<ProtectedRoute path="/people" component={People} />
|
<ProtectedRoute path="/fisco" component={Fisco} />
|
||||||
<ProtectedRoute path="/contabil" component={Contabil} />
|
<ProtectedRoute path="/people" component={People} />
|
||||||
<ProtectedRoute path="/soe" component={SOE} />
|
<ProtectedRoute path="/contabil" component={Contabil} />
|
||||||
<ProtectedRoute path="/erp" component={SOE} />
|
<ProtectedRoute path="/soe" component={SOE} />
|
||||||
<ProtectedRoute path="/financeiro" component={Financeiro} />
|
<ProtectedRoute path="/erp" component={SOE} />
|
||||||
<ProtectedRoute path="/communities" component={Communities} />
|
<ProtectedRoute path="/financeiro" component={Financeiro} />
|
||||||
<ProtectedRoute path="/quality" component={QualityModule} />
|
<ProtectedRoute path="/communities" component={Communities} />
|
||||||
<ProtectedRoute path="/commercial-env" component={CommercialEnv} />
|
<ProtectedRoute path="/quality" component={QualityModule} />
|
||||||
<ProtectedRoute path="/field-ops" component={FieldOperations} />
|
<ProtectedRoute path="/commercial-env" component={CommercialEnv} />
|
||||||
<ProtectedRoute path="/technical" component={TechnicalModule} />
|
<ProtectedRoute path="/field-ops" component={FieldOperations} />
|
||||||
<ProtectedRoute path="/suppliers" component={SuppliersPortal} />
|
<ProtectedRoute path="/technical" component={TechnicalModule} />
|
||||||
<ProtectedRoute path="/nps" component={NPSSurvey} />
|
<ProtectedRoute path="/suppliers" component={SuppliersPortal} />
|
||||||
<ProtectedRoute path="/engineering" component={EngineeringHub} />
|
<ProtectedRoute path="/nps" component={NPSSurvey} />
|
||||||
<ProtectedRoute path="/development" component={DevelopmentModule} />
|
<ProtectedRoute path="/engineering" component={EngineeringHub} />
|
||||||
<ProtectedRoute path="/retail" component={ArcadiaRetail} />
|
<ProtectedRoute path="/development" component={DevelopmentModule} />
|
||||||
<ProtectedRoute path="/plus" component={() => { const [, nav] = useLocation(); useEffect(() => nav("/soe"), []); return null; }} />
|
<ProtectedRoute path="/retail" component={ArcadiaRetail} />
|
||||||
<ProtectedRoute path="/super-admin" component={SuperAdmin} />
|
<ProtectedRoute path="/plus" component={() => { const [, nav] = useLocation(); useEffect(() => nav("/soe"), []); return null; }} />
|
||||||
<ProtectedRoute path="/marketplace" component={Marketplace} />
|
<ProtectedRoute path="/super-admin" component={SuperAdmin} />
|
||||||
<ProtectedRoute path="/lms" component={LMS} />
|
<ProtectedRoute path="/marketplace" component={Marketplace} />
|
||||||
<ProtectedRoute path="/apps" component={AppCenter} />
|
<ProtectedRoute path="/lms" component={LMS} />
|
||||||
<ProtectedRoute path="/xos" component={XosCentral} />
|
<ProtectedRoute path="/apps" component={AppCenter} />
|
||||||
<ProtectedRoute path="/xos/crm" component={XosCrm} />
|
<ProtectedRoute path="/xos" component={XosCentral} />
|
||||||
<ProtectedRoute path="/xos/inbox" component={XosInbox} />
|
<ProtectedRoute path="/xos/crm" component={XosCrm} />
|
||||||
<ProtectedRoute path="/xos/tickets" component={XosTickets} />
|
<ProtectedRoute path="/xos/inbox" component={XosInbox} />
|
||||||
<ProtectedRoute path="/xos/campaigns" component={XosCampaigns} />
|
<ProtectedRoute path="/xos/tickets" component={XosTickets} />
|
||||||
<ProtectedRoute path="/xos/automations" component={XosAutomations} />
|
<ProtectedRoute path="/xos/campaigns" component={XosCampaigns} />
|
||||||
<ProtectedRoute path="/xos/sites" component={XosSites} />
|
<ProtectedRoute path="/xos/automations" component={XosAutomations} />
|
||||||
<ProtectedRoute path="/xos/governance" component={XosGovernance} />
|
<ProtectedRoute path="/xos/sites" component={XosSites} />
|
||||||
<ProtectedRoute path="/xos/pipeline" component={XosPipeline} />
|
<ProtectedRoute path="/xos/governance" component={XosGovernance} />
|
||||||
<ProtectedRoute path="/xos/supervisor" component={XosSupervisor} />
|
<ProtectedRoute path="/xos/pipeline" component={XosPipeline} />
|
||||||
<ProtectedRoute path="/xos/reports" component={XosReports} />
|
<ProtectedRoute path="/xos/supervisor" component={XosSupervisor} />
|
||||||
<ProtectedRoute path="/xos/protocols" component={XosProtocols} />
|
<ProtectedRoute path="/xos/reports" component={XosReports} />
|
||||||
|
<ProtectedRoute path="/xos/protocols" component={XosProtocols} />
|
||||||
|
|
||||||
<ProtectedRoute path="/doctype-builder" component={DocTypeBuilder} />
|
<ProtectedRoute path="/doctype-builder" component={DocTypeBuilder} />
|
||||||
<ProtectedRoute path="/page-builder" component={PageBuilder} />
|
<ProtectedRoute path="/page-builder" component={PageBuilder} />
|
||||||
<ProtectedRoute path="/migration" component={Migration} />
|
<ProtectedRoute path="/migration" component={Migration} />
|
||||||
<ProtectedRoute path="/dev-center" component={DevCenter} />
|
<ProtectedRoute path="/dev-center" component={DevCenter} />
|
||||||
<ProtectedRoute path="/page/:id" component={WorkspacePage} />
|
<ProtectedRoute path="/page/:id" component={WorkspacePage} />
|
||||||
<ProtectedRoute path="/app/:id" component={AppViewer} />
|
<ProtectedRoute path="/app/:id" component={AppViewer} />
|
||||||
<Route path="/auth" component={AuthPage} />
|
<Route path="/auth" component={AuthPage} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { Component, ReactNode } from "react";
|
||||||
|
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
fallback?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
hasError: boolean;
|
||||||
|
error?: Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorBoundary extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error): State {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
|
console.error("[ErrorBoundary] Erro capturado:", error);
|
||||||
|
console.error("[ErrorBoundary] Info:", errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRetry = () => {
|
||||||
|
this.setState({ hasError: false, error: undefined });
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleGoHome = () => {
|
||||||
|
this.setState({ hasError: false, error: undefined });
|
||||||
|
window.location.href = "/";
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
// Fallback customizado ou padrão
|
||||||
|
if (this.props.fallback) {
|
||||||
|
return this.props.fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-slate-50 p-4">
|
||||||
|
<Card className="max-w-lg w-full">
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<div className="mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4">
|
||||||
|
<AlertCircle className="w-8 h-8 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-xl">Ops! Algo deu errado</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Não foi possível carregar esta página. O módulo pode estar incompleto ou com erro.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{this.state.error && (
|
||||||
|
<div className="bg-slate-100 p-3 rounded text-sm font-mono text-slate-600 overflow-auto max-h-32">
|
||||||
|
{this.state.error.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-3 justify-center">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={this.handleGoHome}
|
||||||
|
>
|
||||||
|
Voltar ao Início
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={this.handleRetry}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-4 h-4" />
|
||||||
|
Tentar Novamente
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,485 @@
|
||||||
|
# Especificação Técnica - Navbar do Arcádia Suite
|
||||||
|
|
||||||
|
> **Versão:** 1.0
|
||||||
|
> **Data:** 2026-04-08
|
||||||
|
> **Arquivo Fonte:** `client/src/components/Browser/BrowserFrame.tsx`
|
||||||
|
> **Status:** Implementado e em produção
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Visão Geral
|
||||||
|
|
||||||
|
A navbar do Arcádia Suite é uma barra de navegação compacta, horizontal e fixa no topo da tela. Ela implementa um padrão de "navegação por bookmarks" onde cada item representa um módulo principal do sistema.
|
||||||
|
|
||||||
|
### 1.1 Objetivos
|
||||||
|
- Acesso rápido aos módulos principais
|
||||||
|
- Navegação intuitiva estilo "browser"
|
||||||
|
- Rastreamento de uso para analytics
|
||||||
|
- Suporte a múltiplos perfis de usuário
|
||||||
|
|
||||||
|
### 1.2 Princípios de Design
|
||||||
|
- **Minimalista:** Apenas ícone + texto curto
|
||||||
|
- **Escalável:** Scroll horizontal permite muitos itens
|
||||||
|
- **Responsiva:** Adapta-se a diferentes telas
|
||||||
|
- **Consistente:** Cores e ícones padronizados
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Estrutura do Componente
|
||||||
|
|
||||||
|
```
|
||||||
|
BrowserFrame (Container)
|
||||||
|
└── CompactNavigationBar (Navbar principal)
|
||||||
|
├── Área de Bookmarks (scroll horizontal)
|
||||||
|
│ └── BookmarkItem[] (itens de navegação)
|
||||||
|
└── Área do Usuário (fixa à direita)
|
||||||
|
└── UserDropdown (menu do usuário)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 Hierarquia de Componentes
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// BrowserFrame.tsx
|
||||||
|
interface BrowserFrameProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CompactNavigationBar() {
|
||||||
|
// Hook de navegação
|
||||||
|
const [location, setLocation] = useLocation(); // wouter
|
||||||
|
const { user, logoutMutation } = useAuth();
|
||||||
|
const { trackPageView } = useNavigationTracking();
|
||||||
|
|
||||||
|
// Handler de navegação
|
||||||
|
const navigateTo = (path: string, pageName: string) => {
|
||||||
|
trackPageView(pageName, path);
|
||||||
|
setLocation(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-10 bg-background border-b...">
|
||||||
|
{/* Bookmarks */}
|
||||||
|
{/* User Menu */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BrowserFrame({ children }: BrowserFrameProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-screen w-screen...">
|
||||||
|
<CompactNavigationBar />
|
||||||
|
<div className="flex-1...">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Especificação Visual
|
||||||
|
|
||||||
|
### 3.1 Dimensões e Layout
|
||||||
|
|
||||||
|
| Propriedade | Valor | Tailwind |
|
||||||
|
|-------------|-------|----------|
|
||||||
|
| Altura | 40px | `h-10` |
|
||||||
|
| Padding horizontal | 12px | `px-3` |
|
||||||
|
| Gap entre itens | 8px | `gap-2` |
|
||||||
|
| Tamanho do ícone | 16x16px | `w-4 h-4` |
|
||||||
|
| Tamanho do ícone interno | 10x10px | `w-2.5 h-2.5` |
|
||||||
|
| Padding do bookmark | 6px 8px | `px-2 py-1.5` |
|
||||||
|
| Border radius | 4px | `rounded` |
|
||||||
|
|
||||||
|
### 3.2 Cores e Estilos
|
||||||
|
|
||||||
|
```
|
||||||
|
Fundo: bg-background (varia com tema claro/escuro)
|
||||||
|
Borda inferior: border-border
|
||||||
|
Texto: text-muted-foreground
|
||||||
|
Hover: hover:bg-muted
|
||||||
|
Sombra: shadow-xs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Estados dos Bookmarks
|
||||||
|
|
||||||
|
| Estado | Estilo |
|
||||||
|
|--------|--------|
|
||||||
|
| **Default** | `text-muted-foreground` |
|
||||||
|
| **Hover** | `hover:bg-muted` + cursor pointer |
|
||||||
|
| **Ativo** | Não há estado visual de "ativo" (intencional) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Itens de Navegação (Bookmarks)
|
||||||
|
|
||||||
|
### 4.1 Lista Completa de Itens
|
||||||
|
|
||||||
|
| Ordem | Nome | Rota | Ícone | Cor do Gradient | Visibilidade |
|
||||||
|
|-------|------|------|-------|-----------------|--------------|
|
||||||
|
| 1 | Administração | `/admin` | Settings | slate-700 → slate-900 | Apenas admin |
|
||||||
|
| 2 | Início | `/` | Arcádia Icon | - (imagem) | Todos |
|
||||||
|
| 3 | Agent | `/agent` | Bot | primary → blue-600 | Todos |
|
||||||
|
| 4 | Inbox | `/xos/inbox` | MessageCircle | #00a884 → #25D366 | Todos |
|
||||||
|
| 5 | Automações | `/automations` | Zap | #c89b3c → #d4a94a | Todos |
|
||||||
|
| 6 | Insights | `/insights` | LayoutDashboard | #1f334d → #2d4a6f | Todos |
|
||||||
|
| 7 | Compass | `/compass` | Compass | #c89b3c → #1f334d | Todos |
|
||||||
|
| 8 | Produção | `/production` | Users | indigo-500 → indigo-700 | Todos |
|
||||||
|
| 9 | Suporte | `/support` | Ticket | rose-500 → rose-700 | Todos |
|
||||||
|
| 10 | ERP | `/erp` | Package | blue-600 → blue-800 | Todos |
|
||||||
|
| 11 | Retail | `/retail` | Store | cyan-500 → blue-600 | Todos |
|
||||||
|
| 12 | Plus | `/plus` | Layers | purple-500 → purple-700 | Todos |
|
||||||
|
| 13 | Fisco | `/fisco` | Receipt | emerald-600 → emerald-800 | Todos |
|
||||||
|
| 14 | Engenharia | `/engineering` | Compass | teal-500 → green-700 | Todos |
|
||||||
|
|
||||||
|
### 4.2 Estrutura de um Bookmark
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-1 hover:bg-muted px-2 py-1.5 rounded cursor-pointer transition-colors flex-shrink-0"
|
||||||
|
onClick={() => navigateTo("/rota", "Nome")}
|
||||||
|
data-testid="bookmark-nome"
|
||||||
|
>
|
||||||
|
{/* Container do ícone com gradiente */}
|
||||||
|
<div className="w-4 h-4 bg-gradient-to-br from-[COR1] to-[COR2] rounded-sm flex items-center justify-center">
|
||||||
|
<Icon className="w-2.5 h-2.5 text-white" />
|
||||||
|
</div>
|
||||||
|
{/* Texto (oculto em mobile) */}
|
||||||
|
<span className="hidden md:inline">Nome</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Menu do Usuário (Dropdown)
|
||||||
|
|
||||||
|
### 5.1 Estrutura
|
||||||
|
|
||||||
|
```
|
||||||
|
[Avatar + Nome] ▼
|
||||||
|
│
|
||||||
|
├── Informações do Usuário
|
||||||
|
│ ├── Nome completo
|
||||||
|
│ ├── @username
|
||||||
|
│ └── Badge "Administrador" (se aplicável)
|
||||||
|
│
|
||||||
|
├── Meu Perfil
|
||||||
|
│
|
||||||
|
├── Plataforma (apenas admin)
|
||||||
|
│ └── Centro de Desenvolvimento
|
||||||
|
│
|
||||||
|
└── Sair (texto vermelho)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Componentes
|
||||||
|
|
||||||
|
- **Trigger:** Botão com avatar circular + nome
|
||||||
|
- **Avatar:** Inicial do nome em círculo colorido (5x5)
|
||||||
|
- **Dropdown:** `DropdownMenu` do shadcn/ui
|
||||||
|
- **Largura:** 224px (`w-56`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Comportamento e Interações
|
||||||
|
|
||||||
|
### 6.1 Fluxo de Navegação
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Usuário clica │
|
||||||
|
│ em bookmark │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ trackPageView │ ← Registra no backend via POST /api/learning/navigation
|
||||||
|
│ (pageName, path)│
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ setLocation │ ← wouter atualiza URL
|
||||||
|
│ (path) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ React.lazy() │ ← Code splitting carrega chunk
|
||||||
|
│ import() │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ <Suspense> │ ← Mostra LoadingFallback
|
||||||
|
│ fallback │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Página carrega │ ← Componente renderizado
|
||||||
|
│ e renderiza │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Rastreamento (Analytics)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// use-navigation-tracking.ts
|
||||||
|
const trackPageView = (pageName: string, route?: string) => {
|
||||||
|
track({
|
||||||
|
module: pageName, // Ex: "Agent", "Fisco"
|
||||||
|
action: "page_view",
|
||||||
|
metadata: {
|
||||||
|
route, // Ex: "/agent", "/fisco"
|
||||||
|
timestamp: "2026-04-08T15:30:00Z"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debounce:** 2000ms para evitar spam de eventos
|
||||||
|
|
||||||
|
### 6.3 Responsividade
|
||||||
|
|
||||||
|
| Breakpoint | Comportamento |
|
||||||
|
|------------|---------------|
|
||||||
|
| **Mobile** (< 768px) | Texto dos bookmarks oculto (`hidden md:inline`), apenas ícones |
|
||||||
|
| **Desktop** (≥ 768px) | Texto visível ao lado do ícone |
|
||||||
|
|
||||||
|
### 6.4 Scroll Horizontal
|
||||||
|
|
||||||
|
- A área de bookmarks tem `overflow-x-auto`
|
||||||
|
- Scrollbar oculta (`scrollbar-hide`)
|
||||||
|
- Itens não quebram linha (`flex-shrink-0`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Dependências
|
||||||
|
|
||||||
|
### 7.1 Bibliotecas
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"wouter": "^3.3.5", // Router
|
||||||
|
"lucide-react": "^0.545.0", // Ícones
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.16" // Dropdown
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Hooks Customizados
|
||||||
|
|
||||||
|
| Hook | Arquivo | Propósito |
|
||||||
|
|------|---------|-----------|
|
||||||
|
| `useLocation` | `wouter` | Navegação de rotas |
|
||||||
|
| `useAuth` | `@/hooks/use-auth` | Dados do usuário e logout |
|
||||||
|
| `useNavigationTracking` | `@/hooks/use-navigation-tracking` | Analytics |
|
||||||
|
|
||||||
|
### 7.3 Componentes UI
|
||||||
|
|
||||||
|
- `Button` (shadcn/ui)
|
||||||
|
- `DropdownMenu`, `DropdownMenuContent`, `DropdownMenuItem`, etc. (shadcn/ui)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Test IDs (data-testid)
|
||||||
|
|
||||||
|
Usados para testes automatizados:
|
||||||
|
|
||||||
|
```
|
||||||
|
bookmark-admin
|
||||||
|
bookmark-home
|
||||||
|
bookmark-agent
|
||||||
|
bookmark-xos-inbox
|
||||||
|
bookmark-automations
|
||||||
|
bookmark-insights
|
||||||
|
bookmark-compass
|
||||||
|
bookmark-production
|
||||||
|
bookmark-support
|
||||||
|
bookmark-erp
|
||||||
|
bookmark-retail
|
||||||
|
bookmark-plus
|
||||||
|
bookmark-fisco
|
||||||
|
bookmark-engineering
|
||||||
|
button-user-menu
|
||||||
|
button-logout
|
||||||
|
menu-development
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Como Modificar
|
||||||
|
|
||||||
|
### 9.1 Adicionar Novo Bookmark
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Em CompactNavigationBar, adicionar antes do </div> da área de bookmarks:
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-1 hover:bg-muted px-2 py-1.5 rounded cursor-pointer transition-colors flex-shrink-0"
|
||||||
|
onClick={() => navigateTo("/nova-rota", "Nome")}
|
||||||
|
data-testid="bookmark-novo"
|
||||||
|
>
|
||||||
|
<div className="w-4 h-4 bg-gradient-to-br from-[COR1] to-[COR2] rounded-sm flex items-center justify-center">
|
||||||
|
<NovoIcon className="w-2.5 h-2.5 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="hidden md:inline">Novo</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Alterar Ordem
|
||||||
|
|
||||||
|
Reorganizar os elementos `<div>` na ordem desejada dentro da área de bookmarks.
|
||||||
|
|
||||||
|
### 9.3 Condicional por Permissão
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
{user?.role === "admin" && (
|
||||||
|
<div ...>...</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
// Ou por módulo permitido:
|
||||||
|
{(user as any)?.allowedModules?.includes("modulo") && (
|
||||||
|
<div ...>...</div>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Integração com Sistema
|
||||||
|
|
||||||
|
### 10.1 Uso nas Páginas
|
||||||
|
|
||||||
|
Todas as páginas que usam a navbar devem ser envolvidas por `BrowserFrame`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Exemplo: client/src/pages/Agent.tsx
|
||||||
|
import { BrowserFrame } from "@/components/Browser/BrowserFrame";
|
||||||
|
|
||||||
|
export default function Agent() {
|
||||||
|
return (
|
||||||
|
<BrowserFrame>
|
||||||
|
<div className="h-full...">
|
||||||
|
{/* Conteúdo da página */}
|
||||||
|
</div>
|
||||||
|
</BrowserFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Páginas que NÃO usam BrowserFrame
|
||||||
|
|
||||||
|
- `AuthPage` (tela de login)
|
||||||
|
- `NotFound` (página 404)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Considerações Técnicas
|
||||||
|
|
||||||
|
### 11.1 Performance
|
||||||
|
- Code splitting via `React.lazy()` em todas as páginas
|
||||||
|
- Suspense com fallback de loading
|
||||||
|
- Debounce no rastreamento (2s)
|
||||||
|
|
||||||
|
### 11.2 Acessibilidade
|
||||||
|
- `cursor-pointer` em todos os bookmarks
|
||||||
|
- Estrutura semântica do dropdown
|
||||||
|
- Contraste adequado nos ícones (branco sobre gradiente)
|
||||||
|
|
||||||
|
### 11.3 Limitações Conhecidas
|
||||||
|
- Não há indicador visual de "página ativa" (design intencional)
|
||||||
|
- Scroll horizontal pode esconder itens em telas muito pequenas
|
||||||
|
- Não suporta drag-and-drop para reordenar
|
||||||
|
|
||||||
|
### 11.4 Tratamento de Erros (Resolvido)
|
||||||
|
|
||||||
|
**Problema:** Se uma página carregada via `React.lazy()` tivesse erro (import quebrado, sintaxe inválida), o `Suspense` ficava preso no estado de loading infinito.
|
||||||
|
|
||||||
|
**Solução Implementada:**
|
||||||
|
|
||||||
|
1. **ErrorBoundary** (`client/src/components/ErrorBoundary.tsx`):
|
||||||
|
- Captura erros em componentes filhos, incluindo falhas no lazy loading
|
||||||
|
- Exibe UI de fallback com detalhes do erro
|
||||||
|
- Botão "Tentar Novamente" para recarregar
|
||||||
|
|
||||||
|
2. **LoadingFallback Aprimorado** (`client/src/App.tsx`):
|
||||||
|
- Após 3s: mostra mensagem "Isso está demorando mais que o esperado..."
|
||||||
|
- Após 10s: mostra alerta amarelo com botão de recarregar
|
||||||
|
|
||||||
|
3. **Integração no Router**:
|
||||||
|
```tsx
|
||||||
|
function Router() {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary> {/* ← Captura erros */}
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
|
<Switch>
|
||||||
|
<ProtectedRoute path="/" component={Cockpit} />
|
||||||
|
{/* ... outras rotas ... */}
|
||||||
|
</Switch>
|
||||||
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fluxo de Erro:**
|
||||||
|
```
|
||||||
|
Usuário clica → lazy() falha → Suspense não resolve → ErrorBoundary captura
|
||||||
|
↓
|
||||||
|
LoadingFallback mostra
|
||||||
|
aviso após 3s/10s
|
||||||
|
↓
|
||||||
|
ErrorBoundary renderiza
|
||||||
|
UI de erro com retry
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Diagrama de Sequência
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
|
||||||
|
│ Usuário│ │ Navbar │ │ wouter │ │ React │ │ Backend │
|
||||||
|
└───┬────┘ └──────┬──────┘ └────┬─────┘ └────┬────┘ └────┬─────┘
|
||||||
|
│ │ │ │ │
|
||||||
|
│──clica─────────▶│ │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │──trackPageView────────────────────│──────────────▶│
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │──setLocation───▶│ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │──atualiza URL──│ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │◀─URL changed───│ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │──lazy import──▶│ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │◀────────────────│──chunk loaded──│ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │──renderiza nova página────────────▶│ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│◀────────────────│ │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Changelog
|
||||||
|
|
||||||
|
| Versão | Data | Alterações |
|
||||||
|
|--------|------|------------|
|
||||||
|
| 1.0 | 2026-04-08 | Especificação inicial criada |
|
||||||
|
| 1.1 | 2026-04-08 | Adicionado tratamento de erros (ErrorBoundary + LoadingFallback com timeout) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Referências
|
||||||
|
|
||||||
|
- **Arquivo Fonte:** `client/src/components/Browser/BrowserFrame.tsx`
|
||||||
|
- **Hook de Tracking:** `client/src/hooks/use-navigation-tracking.ts`
|
||||||
|
- **Rotas:** `client/src/App.tsx`
|
||||||
|
- **Autenticação:** `client/src/hooks/use-auth.tsx`
|
||||||
|
- **UI Components:** `client/src/components/ui/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Nota para Agentes:**
|
||||||
|
> Ao modificar a navbar, mantenha a consistência visual com os bookmarks existentes. Sempre adicione `data-testid` para testes e atualize esta especificação se houver mudanças significativas na estrutura ou comportamento.
|
||||||
Loading…
Reference in New Issue