diff --git a/bun.lockb b/bun.lockb index 2563ba1..0bfe108 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/design-system.md b/design-system.md new file mode 100644 index 0000000..883f61d --- /dev/null +++ b/design-system.md @@ -0,0 +1,385 @@ +# Design System — HGTX Commander + +> Referência técnica de tokens e padrões — light + dark. +> **Fonte da verdade (código):** `src/index.css` + `tailwind.config.ts`. +> Tema aplicado via `next-themes` (`class` no ``, `storageKey: hgtx-commander-theme`). + +## Cor de marca (azul HGTX) + +Azul institucional usado em botões primários, links, anéis de foco e destaques do Commander. + +| Papel | Light | Dark | +|-------|-------|------| +| **Primária** | `hsl(210 100% 33%)` · **#0054A8** | `hsl(210 90% 52%)` · **#1685F3** | +| Hover | `hsl(210 100% 28%)` · #00478F | `hsl(210 95% 46%)` · #0675E5 | +| Pressed | `hsl(210 100% 24%)` · #003D7A | `hsl(210 100% 40%)` · #0066CC | +| Fundo sutil | `hsl(210 55% 96%)` · #EFF5FB | `hsl(210 50% 14%)` · #122033 | +| Focus ring | `rgba(0, 85, 164, 0.28)` | `rgba(51, 136, 255, 0.35)` | + +Referência histórica de marca: ~`#0055A4` / `#004080` — no código o token `--primary` (light) resolve para **#0054A8**. + +**Tailwind / shadcn:** `bg-primary`, `text-primary`, `border-primary`, `ring-ring` (usa `--ring`, alinhado ao primário). + +--- + +## Fonte + +Inter — Google Fonts + +Pesos: 400, 500, 600, 700 + +Import: `https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap` + +Token: `--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif` + +--- + +## Cores + +Valores em **HSL sem vírgula** (formato shadcn: `hsl(var(--token))`). Hex entre parênteses para referência visual. + +### Primária e estados + +| Token CSS | Light (HSL · HEX) | Dark (HSL · HEX) | Classe Tailwind | Uso | +|-----------|-------------------|------------------|-----------------|-----| +| `--primary` | 210 100% 33% · #0054A8 | 210 90% 52% · #1685F3 | `bg-primary` `text-primary` | CTAs, enviar, links ativos | +| `--primary-foreground` | 0 0% 100% · #FFFFFF | 0 0% 100% · #FFFFFF | `text-primary-foreground` | Texto sobre primário | +| `--primary-hover` | 210 100% 28% · #00478F | 210 95% 46% · #0675E5 | `bg-primary-hover` | Hover botões primários | +| `--primary-pressed` | 210 100% 24% · #003D7A | 210 100% 40% · #0066CC | `bg-primary-pressed` | Active / pressed | +| `--primary-light` | 210 55% 96% · #EFF5FB | 210 50% 14% · #122033 | `bg-primary-light` | Fundo de destaque suave | +| `--ring` | 210 100% 33% | 210 90% 52% | `ring-ring` | Focus visível (inputs) | +| `--focus-ring` | rgba(0, 85, 164, 0.28) | rgba(51, 136, 255, 0.35) | sombra manual | Focus em componentes custom | + +### Semânticas (feedback) + +| Token CSS | Light (HSL · HEX) | Dark (HSL · HEX) | Classe Tailwind | Uso | +|-----------|-------------------|------------------|-----------------|-----| +| `--success` | 134 61% 41% · #29A847 | 141 71% 48% · #23D160 | `bg-success` `text-success` | Online, confirmação | +| `--success-foreground` | 0 0% 100% | 216 48% 8% | `text-success-foreground` | Texto sobre success | +| `--destructive` | 0 84% 60% | 0 84% 60% | `bg-destructive` | Erro, excluir | +| `--destructive-foreground` | 0 0% 100% | 0 0% 100% | `text-destructive-foreground` | Texto sobre destructive | +| `--warning` | 38 92% 50% | 38 92% 50% | `bg-warning` `text-warning` | Alertas, badge “Pessoal” | +| `--warning-foreground` | 209 11% 15% | 216 48% 8% | `text-warning-foreground` | Texto sobre warning | + +### Superfícies (page, card, sidebar) + +| Token CSS | Light (HSL · HEX) | Dark (HSL · HEX) | Classe Tailwind | Uso | +|-----------|-------------------|------------------|-----------------|-----| +| `--background` | 0 0% 100% · #FFFFFF | 216 48% 8% · #0B121E | `bg-background` | Fundo principal / chat | +| `--foreground` | 209 11% 15% · #22262A | 0 0% 100% · #FFFFFF | `text-foreground` | Texto principal | +| `--card` | 0 0% 100% · #FFFFFF | 216 42% 11% · #101A28 | `bg-card` | Cards, modais, composer | +| `--card-foreground` | 209 11% 15% | 0 0% 100% | `text-card-foreground` | Texto em cards | +| `--popover` | 0 0% 100% | 216 42% 11% | `bg-popover` | Dropdowns, selects | +| `--sidebar-background` | 210 17% 98% · #F8FAFB | 216 50% 9% · #0B1522 | `bg-sidebar` | Sidebar Commander / módulos | +| `--sidebar-foreground` | 209 11% 15% | 0 0% 100% | `text-sidebar-foreground` | Texto na sidebar | +| `--sidebar-border` | 220 13% 91% · #E5E7EB | 212 12% 21% · #2F363F | `border-sidebar-border` | Divisor da sidebar | +| `--sidebar-accent` | 210 70% 94% · #E3EEFC | 210 45% 16% · #16283D | `bg-sidebar-accent` | Item de nav ativo / hover | +| `--sidebar-accent-foreground` | 210 100% 33% | 210 85% 62% · #5BA3EB | `text-sidebar-accent-foreground` | Texto no item ativo | +| `--muted` | 210 17% 96% · #F3F6F8 | 216 38% 13% · #15202E | `bg-muted` | Fundos secundários | +| `--accent` | 210 70% 94% | 210 45% 16% | `bg-accent` | Hover suave, chips | +| `--accent-foreground` | 210 100% 33% | 210 85% 62% | `text-accent-foreground` | Texto em accent | + +### Tokens extras (chat e UI) + +| Token CSS | Light (HSL · HEX) | Dark (HSL · HEX) | Uso | +|-----------|-------------------|------------------|-----| +| `--bubble-bot` | 209 36% 96% | 216 36% 14% | Bolha do assistente | +| `--nav-active` | 210 70% 94% | 210 45% 16% | Nav ativo (alias visual do accent) | +| `--input-bg` | 0 0% 100% | 216 40% 10% | Fundo de campos (referência) | +| `--secondary` | 210 6% 60% | 216 38% 13% | Neutro secundário (evitar texto em badge dark) | +| `--secondary-foreground` | 209 11% 15% | 0 0% 100% | Texto sobre secondary | + +### Bordas e inputs (shadcn) + +No shadcn, `--input` é a **cor da borda** de Input / Select / Textarea, não o fundo do campo. + +| Token CSS | Light (HSL · HEX) | Dark (HSL · HEX) | Classe Tailwind | Uso | +|-----------|-------------------|------------------|-----------------|-----| +| `--border` | 220 13% 91% · #E5E7EB | 216 18% 26% · #36404E | `border-border` | Divisores, cards | +| `--input` | 220 9% 83% · #D0D2D8 | 216 16% 30% · #404A59 | `border-input` | Borda de inputs | +| `--border-subtle` | 220 13% 95% | 216 22% 20% | custom | Bordas muito suaves | +| `--btn-secondary-border` | 220 9% 83% | 216 16% 30% | custom | Outline secundário | + +### Texto secundário + +| Token CSS | Light (HSL · HEX) | Dark (HSL · HEX) | Classe Tailwind | Uso | +|-----------|-------------------|------------------|-----------------|-----| +| `--muted-foreground` | 209 7% 46% · #6D767E | 212 9% 58% · #8B949E | `text-muted-foreground` | Subtítulos, hints | +| `--placeholder` | 220 9% 66% | 212 10% 31% | `text-placeholder` / `placeholder:` | Placeholders | + +### Gradientes e glow (efeitos) + +| Token | Light | Dark | +|-------|-------|------| +| `--gradient-cyber` | `135deg`, hsl(210 100% 33%) → hsl(210 90% 42%) | hsl(210 90% 52%) → hsl(210 100% 40%) | +| `--gradient-subtle` | #FFF → hsl(210 17% 98%) | hsl(216 42% 11%) → hsl(216 48% 8%) | +| `--glow-cyan` / `--glow-blue` | sombra azul suave 12–15% opac. | sombra azul 28–35% opac. | + +Classes utilitárias: `.gradient-text`, `.cyber-glow`, `.glass-effect` em `src/index.css`. + +--- + +## Tipografia + +> Mesma escala em light e dark. + +| Nível | Tamanho | Peso | Line Height | Classe Semântica | +|-------|---------|------|-------------|------------------| +| Display / Commander | 32px (`--fs-display`) | 700 | 1.25 (`--lh-tight`) | text-display | +| Heading 1 — Modal | 24px (`--fs-h1`) | 600 | 1.25 | text-h1 | +| Heading 2 — Seção | 18px (`--fs-h2`) | 600 | 1.5 (`--lh-body`) | text-h2 | +| Heading 3 | 15px (`--fs-h3`) | — | — | text-h3 | +| Body | 14px (`--fs-body`) | 400 | 1.5 | text-body | +| Small / Timestamp | 12px (`--fs-small`) | 400 | — | text-small | +| Caption | 11px (`--fs-caption`) | 500 | — | text-caption | +| Nav link | 13px | 500 (`--fw-medium`) | — | text-nav-link | +| Nav brand | 15px | 700 (`--fw-bold`) | — | text-nav-brand | +| Brand lockup (tagline) | 10px | 600 | — | text-brand-tag | +| Brand lockup (nome) | 16px | 700 | — | text-brand-name | +| Hero título (catálogo) | clamp(28px, 4vw, 42px) | 700 | 1.25 | text-hero | +| Section title | 28px | 700 | — | text-section-title | + +Tokens de peso: `--fw-regular` 400, `--fw-medium` 500, `--fw-semibold` 600, `--fw-bold` 700. +Tokens de line-height: `--lh-tight` 1.25, `--lh-body` 1.5, `--lh-relaxed` 1.65. + +--- + +## Espaçamento + +Base unit: **4px** + +| Token | Valor | Classe Semântica | +|-------|-------|------------------| +| --space-1 | 4px | p-1 / m-1 / gap-1 | +| --space-2 | 8px | p-2 / m-2 / gap-2 | +| --space-3 | 12px | p-3 / m-3 / gap-3 | +| --space-4 | 16px | p-4 / m-4 / gap-4 | +| --space-5 | 20px | p-5 / m-5 / gap-5 | +| --space-6 | 24px | p-6 / m-6 / gap-6 | +| --space-7 | 32px | p-7 / m-7 / gap-7 | +| --space-8 | 48px | p-8 / m-8 / gap-8 | +| --space-9 | 64px | p-9 / m-9 / gap-9 | +| --section-y | 64px | py-section | +| --section-x | 40px | px-section | +| --nav-h | 56px | h-nav | +| --container | 1200px | max-w-container | + +--- + +## Border Radius + +| Token | Valor | Classe Semântica | Uso | +|-------|-------|------------------|-----| +| --radius-sm | 6px | rounded-sm | Chat items, detalhes | +| --radius-md | 8px | rounded-md | Botões, inputs, nav items, badges | +| --radius-lg | 12px | rounded-lg | Cards, bolhas, demo wrap | +| --radius-xl | 16px | rounded-xl | Modal Personalidade | +| --radius-pill | 999px | rounded-pill | Chat bar (composer) | +| 50% | — | rounded-full | Botão send circular | + +--- + +## Sombras + +| Token CSS | Light | Dark | Classe Tailwind | Uso | +|-----------|-------|------|-----------------|-----| +| `--shadow-card` | `0 4px 12px rgba(0,0,0,0.06)` | `0 4px 16px rgba(0,0,0,0.35)` | `shadow-card` | Cards elevados | +| `--shadow-modal` | `0 16px 40px rgba(0,0,0,0.12)` | `0 24px 48px rgba(0,0,0,0.55)` | `shadow-modal` | Modais, dialogs | + +Glow Online (badge): `box-shadow: 0 0 8px hsl(var(--success) / 0.5)`. + +--- + +## Componentes + +> Use referências a tokens — valores resolvem para light/dark conforme o tema ativo. + +### Botão Primary (HGTX — Nova Sessão / Salvar Perfil) +``` +bg-primary hover:bg-primary-hover active:bg-primary-pressed +text-white rounded-md px-[18px] py-[10px] +text-[13px] font-semibold inline-flex items-center gap-2 +border-none cursor-pointer transition-[background] duration-200 +disabled:opacity-45 disabled:cursor-not-allowed +``` + +### Botão Primary (catálogo / hero) +``` +bg-primary hover:bg-primary-hover active:bg-primary-pressed active:translate-y-px +text-white rounded-md px-6 py-3 text-sm font-semibold +border-none cursor-pointer transition-all duration-200 +``` + +### Botão Outline (Nova Pasta) +``` +bg-transparent text-primary border border-btn-secondary +rounded-md px-[18px] py-[10px] text-[13px] font-medium +inline-flex items-center gap-2 cursor-pointer +hover:border-secondary transition-[border-color] duration-200 +``` + +### Botão Secondary (catálogo) +``` +bg-transparent text-primary border border-btn-secondary +rounded-md px-6 py-3 text-sm font-medium +hover:border-secondary hover:bg-black/[0.03] (light) / hover:bg-white/[0.04] (dark) +transition-all duration-200 cursor-pointer +``` + +### Botão Ghost (Cancelar) +``` +bg-transparent text-secondary border-none rounded-md px-[14px] py-2 +text-[13px] cursor-pointer +hover:text-primary hover:bg-black/[0.04] (light) / hover:bg-white/[0.05] (dark) +transition-all duration-200 +``` + +### Botão Send (circular) +``` +w-10 h-10 rounded-full bg-primary text-white border-none +flex items-center justify-center text-base cursor-pointer +``` + +### Nav Sticky (catálogo DS) +``` +sticky top-0 z-[100] h-nav flex items-center justify-between px-section +bg-nav backdrop-blur-[12px] border-b border-divider +``` + +### Nav CTA +``` +bg-primary hover:bg-primary-hover text-white rounded-md +px-[18px] py-2 text-[13px] font-semibold border-none cursor-pointer +``` + +### Nav Item (sidebar) +``` +flex items-center gap-3 px-3 py-2 rounded-md text-[13px] text-secondary +``` + +### Nav Item Ativo +``` +bg-nav-active text-primary +``` + +### Nav Chat Item (lista conversas) +``` +flex justify-between items-center px-3 py-2 text-xs text-secondary rounded-sm +/* timestamp */ text-[11px] text-placeholder +``` + +### Input / Select / Textarea +``` +font-sans text-sm px-[14px] py-[10px] rounded-md +border border-divider bg-input text-primary outline-none w-full +placeholder:text-placeholder +transition-[border-color,box-shadow] duration-200 +focus:border-primary focus:shadow-[0_0_0_3px_var(--color-focus-ring)] +disabled:opacity-50 disabled:cursor-not-allowed +``` + +### Textarea (modal) +``` +[classes input] min-h-[120px] resize-y +``` + +### Input Search (com ícone) +``` +relative /* wrap */ +[input] pl-9 +/* ícone */ absolute left-3 top-1/2 -translate-y-1/2 text-placeholder text-sm +``` + +### Chat Composer Bar +``` +flex items-center gap-3 py-2 pr-2 pl-5 +border border-divider rounded-pill bg-input max-w-[560px] +/* input interno */ flex-1 border-none bg-transparent text-sm text-primary outline-none placeholder:text-placeholder +/* + btn-send */ +``` + +### Badge Online +``` +inline-flex items-center gap-2 text-small font-medium text-secondary +/* dot */ w-2 h-2 rounded-full bg-success shadow-[0_0_8px_rgba(35,209,96,0.5)] +``` + +### Badge Self-Hosted +``` +inline-flex items-center gap-2 text-caption text-placeholder +px-3 py-2 border border-divider rounded-md +``` + +### Card Modal (Personalidade) +``` +max-w-[480px] p-6 bg-surface-card border border-divider rounded-xl shadow-modal +``` + +### Card Modal Header +``` +flex justify-between items-start mb-4 +/* título */ text-h2 font-semibold +``` + +### Card Modal Close +``` +w-8 h-8 border-none rounded-md bg-transparent text-secondary text-lg cursor-pointer +hover:text-primary hover:bg-black/[0.05] (light) / hover:bg-white/[0.06] (dark) +``` + +### Card Modal Actions +``` +flex justify-end gap-3 mt-5 +/* Cancelar: btn-hgtx-ghost | Salvar: btn-hgtx-primary */ +``` + +### Bolha Bot (boas-vindas) +``` +max-w-[420px] px-5 py-4 bg-bubble-bot rounded-lg +text-sm text-secondary leading-relaxed +``` + +### Layout Demo (3 colunas) +``` +grid grid-cols-[200px_280px_1fr] border border-divider rounded-lg overflow-hidden min-h-[320px] +/* sidebars */ bg-sidebar p-4 border-r border-divider +/* main */ bg-page p-5 flex flex-col items-center justify-center +``` + +### Brand Lockup (sidebar) +``` +text-[10px] font-bold uppercase tracking-[0.12em] text-secondary mb-4 +/* nome */ block text-sm text-primary tracking-[0.06em] +``` + +### Folder Label (sidebar) +``` +text-xs font-medium text-secondary py-2 mt-3 +``` + +--- + +## Tema — como aplicar + +1. **Tokens light** em `:root` e **tokens dark** em `.dark` — ver `src/index.css`. +2. **App:** `ThemeProvider` (`next-themes`) com `attribute="class"`, `storageKey="hgtx-commander-theme"`, temas `light` | `dark`. +3. **Tailwind:** cores semânticas em `tailwind.config.ts` → `hsl(var(--primary))`, etc. + +Tokens **compartilhados** (não mudam com o tema): `--fs-*`, `--space-*`, `--radius-*`, `--fw-*`, `--lh-*`, `--section-*`, `--nav-h`, `--container`. + +Texto sobre botões primários: sempre `--primary-foreground` (`#FFFFFF`). + +### Mapeamento rápido (legado → atual) + +| Doc / HTML antigo | Token atual | +|-------------------|-------------| +| `#0061ff` / `#1a73e8` | `--primary` → #0054A8 (light) / #1685F3 (dark) | +| `bg-page` | `bg-background` | +| `bg-sidebar` | `bg-sidebar` (`--sidebar-background`) | +| `text-secondary` (corpo) | `text-muted-foreground` | +| `border-divider` | `border-border` | + +--- + +*Atualizado para refletir `src/index.css` (HGTX Commander redesign).* +*Para uso como referência no Cursor e agentes de código.* diff --git a/index.html b/index.html index 1513c1c..5e1cd1a 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,12 @@ - HGTX Codex - AI Interface System - - + HGTX Commander + + - - + + diff --git a/package.json b/package.json index 2a3d56c..13638bb 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "input-otp": "^1.4.2", "jwt-decode": "^4.0.0", "lucide-react": "^0.462.0", - "motion": "^12.36.0", + "motion": "^12.38.0", "next-themes": "^0.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", diff --git a/public/hgtx-commander-dark.png b/public/hgtx-commander-dark.png new file mode 100644 index 0000000..557a2a9 Binary files /dev/null and b/public/hgtx-commander-dark.png differ diff --git a/public/hgtx-commander-light.png b/public/hgtx-commander-light.png new file mode 100644 index 0000000..f3642f3 Binary files /dev/null and b/public/hgtx-commander-light.png differ diff --git a/src/App.tsx b/src/App.tsx index 8a2211b..a44b80d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,29 +2,14 @@ import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route, Navigate, useLocation } from "react-router-dom"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { ThemeProvider } from "next-themes"; -import Index from "./pages/Index"; import NotFound from "./pages/NotFound"; import Redirect from "./pages/Redirect"; import React from "react"; +import CommanderApp from "./modules/commander/App"; import IntelligenceIAApp from "./modules/intelligence-ia/App"; -import FechamentoHgtxApp from "./modules/fechamento-hgtx/App"; - -const INTELLIGENCE_SCORE_PREFIX = "/intelligence-score"; -const FECHAMENTO_LEGACY_PREFIX = "/fechamento-hgtx"; -const FECHAMENTO_PREFIX = "/fechamento"; - -/** Redireciona URLs antigas para `/intelligence-score/...` (mesmo sufixo, query e hash). */ -function FechamentoLegacyRedirect() { - const location = useLocation(); - const fromPrefix = location.pathname.startsWith(FECHAMENTO_LEGACY_PREFIX) - ? FECHAMENTO_LEGACY_PREFIX - : FECHAMENTO_PREFIX; - const tail = location.pathname.slice(fromPrefix.length); - const to = `${INTELLIGENCE_SCORE_PREFIX}${tail}`; - return ; -} +import IntelligenceScoreApp from "./modules/intelligence-score/App"; const queryClient = new QueryClient(); @@ -35,7 +20,7 @@ const App = () => ( defaultTheme="light" themes={["light", "dark"]} enableSystem={false} - storageKey="hgtx-codex-theme" + storageKey="hgtx-commander-theme" enableColorScheme > @@ -43,20 +28,15 @@ const App = () => ( - } - /> + } /> + } /> } /> - } /> - } /> - } /> + } /> - } /> - - } /> + } /> } /> + } /> @@ -66,12 +46,10 @@ const App = () => ( export default App; - -const HGTXLogin = () =>{ - +const HGTXLogin = () => { React.useEffect(() => { window.location.replace(import.meta.env.VITE_BASE_URL_HGTX_CORE); }); - return(<>); -} \ No newline at end of file + return <>; +}; diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index e17a750..60321ef 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -69,7 +69,7 @@ export const ChatHeader = ({
-
+
Online
@@ -155,7 +155,7 @@ export const ChatHeader = ({
- {showModelSelector ? "Modelo de linguagem avançado" : "Sistema HGTX Codex"} + {showModelSelector ? "Modelo de linguagem avançado" : "HGTX Commander"}
); diff --git a/src/components/auth/NoAccessScreen.tsx b/src/components/auth/NoAccessScreen.tsx index 3c0c4d2..f34df09 100644 --- a/src/components/auth/NoAccessScreen.tsx +++ b/src/components/auth/NoAccessScreen.tsx @@ -95,7 +95,7 @@ export function NoAccessScreen({ reason, errorMessage }: NoAccessScreenProps) {

Preenchimento automático.

{!estabelecimentoId ? (

- Não foi possível obter o código do estabelecimento. Abra o Fechamento HGTX a partir do Codex com o + Não foi possível obter o código do estabelecimento. Abra o Intelligence Score a partir do Commander com o estabelecimento carregado no transfer.

) : null} diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index 50802ac..ff3bf45 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -4,22 +4,43 @@ import { Textarea } from "@/components/ui/textarea"; import { useState, useRef, useEffect } from "react"; import { useToast } from "@/hooks/use-toast"; import { chatService } from "@/services/chat"; +import { cn } from "@/lib/utils"; interface ChatInputProps { onSendMessage: (message: string, files?: File[]) => void; + draftMessage?: string | null; + draftTrigger?: number; + onDraftApplied?: () => void; } -export const ChatInput = ({ onSendMessage }: ChatInputProps) => { +const MOBILE_TEXTAREA_HEIGHT = 88; +const DEFAULT_TEXTAREA_HEIGHT = 72; + +export const ChatInput = ({ + onSendMessage, + draftMessage, + draftTrigger, + onDraftApplied, +}: ChatInputProps) => { const [message, setMessage] = useState(""); const [attachedFiles, setAttachedFiles] = useState([]); - const [textareaHeight, setTextareaHeight] = useState(60); + const [textareaHeight, setTextareaHeight] = useState(DEFAULT_TEXTAREA_HEIGHT); const [isDragging, setIsDragging] = useState(false); + const [isMobile, setIsMobile] = useState(false); const fileInputRef = useRef(null); const textareaRef = useRef(null); const dragStartY = useRef(0); const dragStartHeight = useRef(0); const { toast } = useToast(); + useEffect(() => { + const mq = window.matchMedia("(max-width: 767px)"); + const update = () => setIsMobile(mq.matches); + update(); + mq.addEventListener("change", update); + return () => mq.removeEventListener("change", update); + }, []); + const handleMouseDown = (e: React.MouseEvent) => { setIsDragging(true); dragStartY.current = e.clientY; @@ -30,9 +51,9 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isDragging) return; - - const deltaY = dragStartY.current - e.clientY; // Inverted: moving up = positive - const newHeight = Math.min(400, Math.max(60, dragStartHeight.current + deltaY)); + + const deltaY = dragStartY.current - e.clientY; + const newHeight = Math.min(400, Math.max(DEFAULT_TEXTAREA_HEIGHT, dragStartHeight.current + deltaY)); setTextareaHeight(newHeight); }; @@ -41,16 +62,30 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { }; if (isDragging) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); } return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); }; }, [isDragging, textareaHeight]); + useEffect(() => { + if (!draftMessage || draftTrigger == null) return; + + setMessage(draftMessage); + requestAnimationFrame(() => { + const textarea = textareaRef.current; + if (!textarea) return; + textarea.focus(); + const end = draftMessage.length; + textarea.setSelectionRange(end, end); + }); + onDraftApplied?.(); + }, [draftMessage, draftTrigger, onDraftApplied]); + const handleSend = () => { if (message.trim() || attachedFiles.length > 0) { onSendMessage(message, attachedFiles); @@ -70,7 +105,6 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { const files = Array.from(e.target.files || []); const newFiles = [...attachedFiles, ...files]; - // Valida quantidade máxima de anexos (5) if (newFiles.length > chatService.getMaxAttachments()) { toast({ title: "Limite de anexos excedido", @@ -83,10 +117,8 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { return; } - // Valida tipos de arquivo e tamanho const validFiles: File[] = []; for (const file of files) { - // Valida tamanho (max 10MB por arquivo) if (file.size > 10 * 1024 * 1024) { toast({ title: "Arquivo muito grande", @@ -99,7 +131,6 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { validFiles.push(file); } - // Valida formatos permitidos const allFiles = [...attachedFiles, ...validFiles]; const validation = chatService.validateAttachments(allFiles); @@ -125,96 +156,108 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { setAttachedFiles(attachedFiles.filter((_, i) => i !== index)); }; + const canSend = Boolean(message.trim() || attachedFiles.length > 0); + const textareaStyleHeight = isMobile ? MOBILE_TEXTAREA_HEIGHT : textareaHeight; + return ( -
-
- {/* Attached Files Preview */} +
+
{attachedFiles.length > 0 && ( -
+
{attachedFiles.map((file, index) => (
- - {file.name} - + + {file.name} + ({(file.size / 1024).toFixed(1)} KB)
))}
)} - {/* Message Input and Buttons */} -
-
- {/* Resize Handle - Hidden on mobile */} -
- -
- - {/* Textarea */} -
-