# Instruções do Projeto - Exent Dashboard

## Stack
- **Backend:** Laravel 11 + Inertia.js
- **Frontend:** React (JSX) + Tailwind CSS + shadcn/ui
- **Banco:** PostgreSQL via Supabase (RLS ativo)
- **Build:** Vite

## Deploy
Após qualquer edição, rodar `npm run build` para atualizar os assets em produção.

---

## Padrões de UI

### Tabelas com overflow horizontal em layouts flex

**Problema:** `overflow-auto` numa tabela não funciona se o flex item pai tiver `min-width: auto` (padrão do CSS). O flex item expande para acomodar o conteúdo em vez de criar scroll - resultando em scroll da página inteira.

**Solução:** Adicionar `min-w-0` ao flex item que contém a tabela (geralmente o wrapper principal do layout).

```jsx
// AppLayout.jsx - garante que a área de conteúdo não expanda além do viewport
<SidebarInset className="min-w-0">
```

> `min-w-0` anula o `min-width: auto` padrão dos flex items, permitindo que o item respeite
> sua largura atribuída e que o `overflow-auto` da tabela funcione como scroll container.

**Checklist ao usar tabelas em páginas:**
- O wrapper da página deve estar dentro de um flex item com `min-w-0`
- A tabela já tem `overflow-auto` via componente `<Table>` (shadcn)
- O `<div className="rounded-md border">` em volta da tabela não precisa de overflow extra

---

### Header de tabelas

`TableHeader` (shadcn) tem fundo `bg-muted/50` por padrão (alterado no componente global em `resources/js/Components/ui/table.jsx`). Isso vale para todas as tabelas - não é preciso passar classe extra.

### Status badges (padrão shadcn dashboard)

Padrão visual para qualquer label de status, origem, integração ou tag dentro de tabelas e listagens. Inspirado em https://ui.shadcn.com/examples/dashboard.

```jsx
<Badge variant="outline" className="gap-1 px-1.5 rounded-full font-normal text-muted-foreground">
  <CheckCircle2 className="h-3.5 w-3.5 fill-emerald-500 text-white dark:text-background" />
  Label
</Badge>
```

Regras:
- Badge sempre `variant="outline"` + `rounded-full` (formato pílula).
- Texto sempre `text-muted-foreground` e `font-normal`; tamanho default `text-xs`.
- Padding compacto `px-1.5` e `gap-1` entre ícone e texto.
- O ícone carrega a cor semântica/de marca - é o único elemento colorido.
- Para sucesso, usar `CheckCircle2` com `fill-emerald-500 text-white` (círculo cheio verde, check branco no interior). Tamanho `h-3.5 w-3.5`.
- Para alertas/erros, usar ícone com cor: `text-amber-500` (alerta), `text-destructive` (erro). Tamanho `h-3 w-3`.
- Para badges de marca, usar a cor da marca no ícone (ex.: Meta `#0866FF`, WhatsApp `#25D366`). Tamanho `h-3 w-3`.

### Listagens (padrão obrigatório)

Toda página de listagem deve ter:
- **Busca** por texto (campo `<Input>` com ícone `Search`)
- **Filtros** por select (status, vertical, papel, etc.)
- **Paginação** client-side com `ITEMS_PER_PAGE = 10`
- **Ações** em ícones (`<Button variant="ghost" size="icon">`)

### Layout responsivo de listagens

```jsx
{/* Cabeçalho: empilha no mobile */}
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
  <div>
    <h1 className="text-2xl font-bold">Título</h1>
    <p className="text-sm text-muted-foreground">Descrição</p>
  </div>
  <Button className="w-full sm:w-auto">Ação</Button>
</div>

{/* Filtros: empilham no mobile */}
<div className="flex flex-col sm:flex-row gap-2">
  <div className="relative flex-1 sm:flex-none">
    <Input className="w-full sm:w-56" />
  </div>
  <Select><SelectTrigger className="w-full sm:w-44" /></Select>
</div>

{/* Paginação: "X / Y" no mobile, botões individuais no desktop */}
<span className="px-2 text-sm sm:hidden">{currentPage} / {totalPages}</span>
<div className="hidden sm:flex items-center gap-1">
  {/* botões numerados */}
</div>
```

### Colunas de tabela no mobile (Integrações - 8 colunas)

Ocultar colunas secundárias em telas pequenas:
```jsx
<TableHead className="hidden sm:table-cell">Autenticação</TableHead>
<TableHead className="hidden sm:table-cell">Última validação</TableHead>
<TableHead className="hidden md:table-cell">Ativos</TableHead>
```
Manter sempre visível: Nome, Provedor/Status principal, Ações.

---

## Tema claro/escuro

- Controlado por `ThemeProvider` em `resources/js/Components/ThemeProvider.jsx`
- Persiste em `localStorage` (chave `theme`)
- Script inline no `app.blade.php` aplica a classe `dark` antes do React renderizar (evita flash)
- Tailwind usa `darkMode: "class"` - variáveis CSS em `.dark { ... }` em `app.css`

---

## Sidebar

- Usa componente shadcn/ui (`resources/js/Components/ui/sidebar.jsx`)
- Desktop: sidebar fixa lateral
- Mobile (`< 768px`): converte para drawer (Sheet)
- Menu principal em `resources/js/config/menu.js` com campo `permission` para RBAC
- "Configurações" fica no `<SidebarFooter>`, separado dos itens de navegação

---

## URLs e rotas no frontend

**NUNCA usar URLs hardcoded** (ex: `` `/projetos/${id}/google-ads/campaigns` ``) em chamadas de API ou navegação no React.

**Sempre usar a função `route()` do Ziggy** (ex: `route("google-ads.campaigns", { project: id })`), que já gera o caminho completo incluindo subdiretório ou subdomínio.

> O app atualmente roda em subdiretório (`/st/8.3/dashboard/public/`), mas vai migrar para subdomínio em produção. A função `route()` do Ziggy abstrai isso - URLs hardcoded quebram dependendo do ambiente.

Isso vale para:
- `axios.get()` / `axios.post()` em hooks e componentes
- `router.visit()` / `router.put()` do Inertia
- Qualquer fetch direto a endpoints da API

---

## Padrões do Dashboard

### Ordem padrão de colunas em tabelas de drill-down de campanhas/anúncios

Tabelas de drill-down de anúncios (Google Ads, Meta Ads — qualquer nível: campanhas, conjuntos, anúncios, keywords) devem seguir esta **ordem fixa** de colunas de métricas:

1. **Nome** da entidade (com `ChevronRight` quando a linha for clicável)
2. **Objetivo/Tipo** (badge, se aplicável)
3. **Status** (badge)
4. **Tendência** (sparkline, apenas no nível de campanhas)
5. **Impressões**
6. **CTR**
7. **Cliques**
8. **Tx. Conv.** (taxa de conversão)
9. **Conversões** (usar este label, **nunca "Leads"** — "Leads" é reservado para o sistema Exent Hub/CRM)
10. **CPL** (Custo por Conversão / Custo por Lead)
11. **Invest.** (Investimento, `font-medium` para destaque)

Métricas extras específicas do provedor (ex.: CPC Médio para Google Ads, Alcance para Meta Ads) vão **ao final** da tabela como colunas opcionais. As métricas-tabela numéricas usam `text-right tabular-nums`.

**Responsividade:** ocultar no mobile via `hidden sm:table-cell` (Objetivo, Status, Tendência) e `hidden md:table-cell` (Tx. Conv., CPL, métricas extras). Sempre visíveis: Nome, Impressões, Cliques, Conversões, Invest.

**Sortable headers:** usar `<SortableHeader>` (`resources/js/Components/Dashboard/GoogleAdsDrilldown/SortableHeader.jsx`) em todas as colunas numéricas suportadas pelo backend. Colunas calculadas client-side (ex.: Tx. Conv.) ficam como `<TableHead>` simples.

A mesma ordem se aplica aos botões/toggles de gráficos diários (`DailyChart.jsx`).

### Nomenclatura de métricas de custo

Usar **"Investimento"** (nunca "Custo") como label de métricas de gasto total em todas as abas do dashboard (KPIs, gráficos, tooltips, tabelas).
- `Investimento Total`, `Investimento Google Ads`, `Investimento Meta Ads`
- Em colunas de tabela usar a abreviação `Invest.` quando houver restrição de espaço

Exceção: métricas derivadas como `CPL` (Custo por Lead) e `CPC` (Custo por Clique) mantêm o prefixo "Custo" por serem siglas padrão de mercado.

### Trend de métricas de custo (invertTrend)

Métricas onde **aumento é ruim** devem usar `invertTrend` para inverter a cor (verde/vermelho), mantendo a seta e o sinal na direção real da mudança:

| Métrica | invertTrend | Subiu → cor | Caiu → cor |
|---|---|---|---|
| CPL, CPC, CPA, Custo/Conversão, Spam | `true` | Vermelho | Verde |
| Conversões, Leads, Impressões, Cliques, CTR, Investimento | `false` | Verde | Vermelho |

Componentes que implementam: `KpiCard` (prop `invertTrend`), `TrendBadge` (prop `invert`), `integrationDataHelpers.js` (campo `invertTrend` no `METRIC_META`).

---

## Prevenção de zoom automático no iOS

Inputs com `font-size < 16px` disparam zoom automático no iOS Safari.
Corrigido globalmente em `app.css`:

```css
@media (max-width: 768px) {
  input, select, textarea { font-size: 16px; }
}
```
