Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions app/dashboard/crm/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export default async function CrmPage({

return (
<main
className="mx-auto px-8 py-8 flex flex-col"
style={{ maxWidth: 1400, height: "100vh" }}
className="crm-fullscreen mx-auto px-8 py-8 flex flex-col"
style={{ maxWidth: 1400 }}
>
<CrmRealtime />
<div className="flex items-center justify-between flex-wrap gap-4" style={{ flexShrink: 0 }}>
Expand Down Expand Up @@ -103,17 +103,9 @@ export default async function CrmPage({

<div style={{ flex: 1, minHeight: 0, marginTop: 20 }}>
{aba === "conversas" && (
<div
className="glass"
style={{
display: "grid",
gridTemplateColumns: "360px 1fr",
height: "100%",
overflow: "hidden",
}}
>
<div className={`glass crm-conversas-grid ${lead ? "tem-lead" : ""}`}>
<div
className="scrollbar-thin"
className="crm-sidebar-pane scrollbar-thin"
style={{
borderRight: "0.5px solid rgba(255,255,255,0.08)",
overflowY: "auto",
Expand All @@ -133,7 +125,7 @@ export default async function CrmPage({
/>
</div>

<div style={{ minWidth: 0 }}>
<div className="crm-thread-pane" style={{ minWidth: 0 }}>
{lead ? (
<Thread
key={lead.id}
Expand Down
46 changes: 46 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,52 @@ table {
}
}

/* ============ CRM — tela cheia responsiva ============
* .crm-fullscreen preenche a viewport inteira dentro do .app-main. Usa dvh
* (não vh) pra não quebrar quando o teclado/barra do navegador mobile muda
* a altura visível; no mobile o .app-main ganha padding-top:56px pro botão
* hambúrguer (regra acima) — dvh sozinho não desconta isso, então
* subtraímos aqui (senão o rodapé da tela — composer, fundo da lista —
* fica ~56px abaixo da dobra, "sem aparecer teclado"/"sem ver conversas").
*/
.crm-fullscreen {
height: 100dvh;
}
@media (max-width: 1023px) {
.crm-fullscreen {
height: calc(100dvh - 56px);
}
}

/* Conversas: sidebar + thread lado a lado no desktop. No mobile (<=768px,
* telas de celular) vira uma tela por vez, estilo WhatsApp — mostra OU a
* lista OU a conversa aberta; os 360px fixos da sidebar não cabem do lado
* de uma thread numa tela de telefone (as duas ficavam espremidas a ponto
* de sumir/travar). */
.crm-conversas-grid {
display: grid;
grid-template-columns: 360px 1fr;
height: 100%;
overflow: hidden;
}
.crm-back-mobile {
display: none;
}
@media (max-width: 768px) {
.crm-conversas-grid {
grid-template-columns: 1fr;
}
.crm-conversas-grid:not(.tem-lead) .crm-thread-pane {
display: none;
}
.crm-conversas-grid.tem-lead .crm-sidebar-pane {
display: none;
}
.crm-back-mobile {
display: inline-flex;
}
}

/* ============ Sino flutuante (botão) ============
* Botão fixo no canto superior direito, sempre visível.
* z-index 60 = mesmo do hambúrguer mobile (top-left); não conflitam.
Expand Down
6 changes: 5 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export default function RootLayout({
rel="stylesheet"
/>
</head>
<body className="min-h-screen">{children}</body>
{/* min-h-dvh (não min-h-screen/100vh): em mobile, vh usa a viewport
"grande" (barra do navegador recolhida) — com a barra visível
(o estado mais comum), min-height:100vh deixa o body mais alto do
que o conteúdo, sobrando espaço em branco rolável no fim da página. */}
<body className="min-h-dvh">{children}</body>
</html>
)
}
7 changes: 6 additions & 1 deletion components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,12 @@ export default function AppShell({
// CSS var permite que a regra mobile sobrescreva pra 0 sem
// que precisemos checar matchMedia no React.
["--rail-width" as string]: `${railWidth}px`,
minHeight: "100vh",
// dvh (não vh): em mobile, vh usa a viewport "grande" (barra do
// navegador recolhida) — quando a barra está visível (o estado
// mais comum), min-height:100vh deixa esse container mais alto do
// que o conteúdo (ex: a tela cheia do CRM, que já usa dvh),
// sobrando um espaço em branco rolável no fim da página.
minHeight: "100dvh",
}}
>
{children}
Expand Down
63 changes: 4 additions & 59 deletions components/crm/Etiquetas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
import { useState, useTransition } from "react"
import { useRouter } from "next/navigation"
import {
criarEtiquetaAction,
atribuirEtiquetaAction,
removerEtiquetaAction,
} from "@/lib/crm-etiquetas-actions"
import { CORES_ETIQUETA } from "@/lib/crm-cores"

interface EtiquetaResumo {
id: string
Expand Down Expand Up @@ -62,8 +60,9 @@ export function EtiquetaChip({
)
}

/** Popover de atribuição de etiquetas a um lead — checkboxes + criação
* inline de etiqueta nova (mesmo espírito dos labels do WhatsApp Business). */
/** Popover de atribuição de etiquetas a um lead — checkboxes das etiquetas
* sincronizadas com as etapas do Kanban (Fase 6: não dá mais pra criar
* etiqueta nova aqui, só em Kanban → elas viram "fases"). */
export default function EtiquetasPicker({
leadId,
etiquetasDoLead,
Expand All @@ -74,7 +73,6 @@ export default function EtiquetasPicker({
todasEtiquetas: EtiquetaResumo[]
}) {
const [aberto, setAberto] = useState(false)
const [novoNome, setNovoNome] = useState("")
const [pending, startTransition] = useTransition()
const router = useRouter()

Expand All @@ -91,26 +89,6 @@ export default function EtiquetasPicker({
})
}

function criarNova() {
const nome = novoNome.trim()
if (!nome) return
const cor = CORES_ETIQUETA[todasEtiquetas.length % CORES_ETIQUETA.length]
startTransition(async () => {
const fdCriar = new FormData()
fdCriar.set("nome", nome)
fdCriar.set("cor", cor)
const r = await criarEtiquetaAction(fdCriar)
if (r.ok && r.id) {
const fdAtribuir = new FormData()
fdAtribuir.set("lead_id", leadId)
fdAtribuir.set("etiqueta_id", r.id)
await atribuirEtiquetaAction(fdAtribuir)
}
setNovoNome("")
router.refresh()
})
}

return (
<div style={{ position: "relative", display: "inline-block" }}>
<button
Expand Down Expand Up @@ -188,43 +166,10 @@ export default function EtiquetasPicker({
))}
{todasEtiquetas.length === 0 && (
<p style={{ fontSize: 11, color: "var(--text-4)" }}>
Nenhuma etiqueta ainda.
Nenhuma etiqueta ainda. Crie fases no Kanban.
</p>
)}
</div>
<div
style={{
display: "flex",
gap: 4,
marginTop: 8,
borderTop: "0.5px solid rgba(255,255,255,0.08)",
paddingTop: 8,
}}
>
<input
value={novoNome}
onChange={(e) => setNovoNome(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
criarNova()
}
}}
placeholder="Nova etiqueta..."
maxLength={40}
className="glass-input"
style={{ flex: 1, fontSize: 11, padding: "4px 8px" }}
/>
<button
type="button"
onClick={criarNova}
disabled={pending || !novoNome.trim()}
className="btn-gold-filled"
style={{ fontSize: 11, padding: "4px 8px" }}
>
+
</button>
</div>
</div>
</>
)}
Expand Down
Loading