Merge pull request #729 from fsolla/plans/plan-issue-43
plan(C139): Contatos - pagina da entidade Contact + cancelamento dos planos abertos de Pessoas
This commit is contained in:
commit
977dab044e
8 changed files with 721 additions and 145 deletions
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
611
docs/plans/contatos-pagina-da-entidade-contact-ui-draft.html
Normal file
611
docs/plans/contatos-pagina-da-entidade-contact-ui-draft.html
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>UI draft — Contatos: página da entidade Contact</title>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-100 antialiased">
|
||||
<main class="mx-auto max-w-5xl p-8">
|
||||
<p class="text-sm text-zinc-400">
|
||||
Mesa (coordenação/assessores) na lista de Contatos — atualizar ficha e contatar pessoas.
|
||||
Cenas: desktop 1280px (lista + linha nova) e mobile 390px (cards + sheet de edição). Acento
|
||||
único = CTA primário (vermelho do app). Chips dentro do input; células = inputs.
|
||||
</p>
|
||||
|
||||
<!-- ============ CENA: DESKTOP ============ -->
|
||||
<h2 class="mt-8 text-sm font-semibold uppercase tracking-wide text-zinc-500">
|
||||
Desktop — /campanha/contatos (1280px)
|
||||
</h2>
|
||||
<section
|
||||
data-shot="desktop"
|
||||
class="mt-4 overflow-hidden rounded-xl border border-zinc-800 bg-zinc-100 text-zinc-900"
|
||||
>
|
||||
<!-- app header -->
|
||||
<div class="flex h-14 items-center justify-between border-b border-zinc-300 bg-white px-4">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<p class="text-sm font-semibold">Contatos</p>
|
||||
<p class="text-xs text-zinc-500">
|
||||
Fichas da campanha — atualize dados e fale com as pessoas
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
class="flex size-9 items-center justify-center rounded-full bg-zinc-200 text-xs text-zinc-600"
|
||||
>SR</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- omnibox: chips DENTRO do input -->
|
||||
<div class="border-b border-zinc-300 bg-white px-4 py-2">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div
|
||||
class="flex min-h-9 flex-1 items-center gap-1 rounded-lg border border-zinc-300 px-2"
|
||||
>
|
||||
<span class="text-xs text-zinc-400">⌕</span>
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full bg-zinc-800 px-2 py-0.5 text-xs text-zinc-100"
|
||||
>
|
||||
Gênero: Feminino <span class="text-zinc-400">✕</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full bg-zinc-800 px-2 py-0.5 text-xs text-zinc-100"
|
||||
>
|
||||
Estado: BA <span class="text-zinc-400">✕</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full bg-zinc-800 px-2 py-0.5 text-xs text-zinc-100"
|
||||
>
|
||||
Sem e-mail <span class="text-zinc-400">✕</span>
|
||||
</span>
|
||||
<input
|
||||
class="min-h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-zinc-500"
|
||||
placeholder="Buscar contato — nome, e-mail ou telefone…"
|
||||
/>
|
||||
<span
|
||||
class="flex size-8 shrink-0 items-center justify-center rounded-full bg-zinc-200 text-xs text-zinc-500"
|
||||
>✕</span
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="flex h-9 shrink-0 items-center gap-1 rounded-lg bg-red-600 px-3 text-xs font-medium text-white"
|
||||
>
|
||||
+ Criar contato
|
||||
</button>
|
||||
<button
|
||||
class="flex h-9 shrink-0 items-center gap-1 rounded-lg bg-zinc-900 px-3 text-xs font-medium text-zinc-100"
|
||||
>
|
||||
Colunas
|
||||
</button>
|
||||
</div>
|
||||
<!-- sugestões (dropdown aberto) -->
|
||||
<div class="mt-2 rounded-lg border border-zinc-300 bg-white p-1 shadow-sm">
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">
|
||||
⌕ Buscar: “maria”
|
||||
</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">Lideranças: Sim</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">
|
||||
Dobradinhas: Sim
|
||||
</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">Assessores: Sim</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">Equipe: Sim</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">
|
||||
Gênero: Masculino
|
||||
</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">
|
||||
Gênero: Não binário
|
||||
</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">
|
||||
Gênero: Não informado
|
||||
</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">
|
||||
Cidade: Salvador
|
||||
</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">Sem telefone</p>
|
||||
<p class="flex items-center gap-1 px-2 py-1.5 text-xs text-zinc-600">Ordenar: Cidade</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tabela: Nome, Gênero, Telefone, Email, Cidade, Estado, Ações -->
|
||||
<div class="overflow-x-auto bg-white">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-zinc-300 text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="px-4 py-2 font-medium">Nome</th>
|
||||
<th class="px-4 py-2 font-medium">Gênero</th>
|
||||
<th class="px-4 py-2 font-medium">Telefone</th>
|
||||
<th class="px-4 py-2 font-medium">E-mail</th>
|
||||
<th class="px-4 py-2 font-medium">Cidade</th>
|
||||
<th class="px-4 py-2 font-medium">Estado</th>
|
||||
<th class="px-4 py-2 text-right font-medium">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-200">
|
||||
<!-- linha 1: telefone único -->
|
||||
<tr class="bg-white">
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Maria dos Santos"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Feminino"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="(71) 99999-0001"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="maria@email.com"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Salvador"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="BA"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex justify-end gap-1">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✆</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✉</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>🗑</span
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- linha 2: dois telefones empilhados, sem e-mail -->
|
||||
<tr class="bg-white">
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="João Pereira"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Masculino"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="(75) 98888-0002"
|
||||
/>
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="(75) 97777-0009"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-dashed border-zinc-300 bg-white px-2 py-1.5 text-sm text-zinc-400 outline-none"
|
||||
value="Sem e-mail"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Feira de Santana"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="BA"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex justify-end gap-1">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✆</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs text-zinc-300"
|
||||
>✉</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>🗑</span
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- linha 3: linkada a liderança + dobradinha, sem telefone -->
|
||||
<tr class="bg-white">
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Ana Lima"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Não binário"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-dashed border-zinc-300 bg-white px-2 py-1.5 text-sm text-zinc-400 outline-none"
|
||||
value="Sem telefone"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="ana@email.com"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="Salvador"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
value="BA"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex justify-end gap-1">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs text-zinc-300"
|
||||
>✆</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✉</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>🗑</span
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
class="flex items-center justify-between border-t border-zinc-300 px-4 py-2 text-xs text-zinc-500"
|
||||
>
|
||||
<span>Mostrando 3 de 435 contatos</span>
|
||||
<span>‹ 1 · 2 · 3 ›</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ CENA: DESKTOP — linha nova ============ -->
|
||||
<h2 class="mt-8 text-sm font-semibold uppercase tracking-wide text-zinc-500">
|
||||
Desktop — linha nova (após “+ Criar contato”)
|
||||
</h2>
|
||||
<section
|
||||
data-shot="desktop-novo"
|
||||
class="mt-4 overflow-hidden rounded-xl border border-zinc-800 bg-zinc-100 text-zinc-900"
|
||||
>
|
||||
<div class="flex h-14 items-center justify-between border-b border-zinc-300 bg-white px-4">
|
||||
<p class="text-sm font-semibold">Contatos</p>
|
||||
<span
|
||||
class="flex size-9 items-center justify-center rounded-full bg-zinc-200 text-xs text-zinc-600"
|
||||
>SR</span
|
||||
>
|
||||
</div>
|
||||
<div class="border-b border-zinc-300 bg-white px-4 py-2">
|
||||
<div class="flex min-h-9 items-center gap-1 rounded-lg border border-zinc-300 px-2">
|
||||
<span class="text-xs text-zinc-400">⌕</span>
|
||||
<input
|
||||
class="min-h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-zinc-500"
|
||||
placeholder="Buscar contato…"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto bg-white">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-zinc-300 text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="px-4 py-2 font-medium">Nome</th>
|
||||
<th class="px-4 py-2 font-medium">Gênero</th>
|
||||
<th class="px-4 py-2 font-medium">Telefone</th>
|
||||
<th class="px-4 py-2 font-medium">E-mail</th>
|
||||
<th class="px-4 py-2 font-medium">Cidade</th>
|
||||
<th class="px-4 py-2 font-medium">Estado</th>
|
||||
<th class="px-4 py-2 text-right font-medium">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- linha nova em rascunho -->
|
||||
<tr class="border-b-2 border-red-600 bg-red-50">
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-red-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
placeholder="Nome"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-red-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
placeholder="Gênero"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-red-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
placeholder="(71) 9…"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-red-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
placeholder="email@…"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-red-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
placeholder="Cidade"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full rounded-md border border-red-300 bg-white px-2 py-1.5 text-sm outline-none"
|
||||
placeholder="UF"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="flex justify-end gap-1">
|
||||
<button
|
||||
class="flex h-8 items-center rounded-lg bg-zinc-900 px-3 text-xs font-medium text-zinc-100"
|
||||
>
|
||||
Salvar
|
||||
</button>
|
||||
<button
|
||||
class="flex h-8 items-center rounded-lg border border-zinc-300 px-3 text-xs font-medium text-zinc-600"
|
||||
>
|
||||
Descartar
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-white">
|
||||
<td class="px-4 py-2.5 font-medium">Maria dos Santos</td>
|
||||
<td class="px-4 py-2.5 text-zinc-700">Feminino</td>
|
||||
<td class="px-4 py-2.5 text-zinc-700">(71) 99999-0001</td>
|
||||
<td class="px-4 py-2.5 text-zinc-700">maria@email.com</td>
|
||||
<td class="px-4 py-2.5 text-zinc-700">Salvador</td>
|
||||
<td class="px-4 py-2.5 text-zinc-700">BA</td>
|
||||
<td class="px-4 py-2.5">
|
||||
<div class="flex justify-end gap-1">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✆</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✉</span
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>🗑</span
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ CENA: MOBILE ============ -->
|
||||
<h2 class="mt-8 text-sm font-semibold uppercase tracking-wide text-zinc-500">
|
||||
Mobile — cards (390px, omnibox edge-to-edge sem borda)
|
||||
</h2>
|
||||
<section
|
||||
data-shot="mobile"
|
||||
class="mt-4 w-[390px] overflow-hidden rounded-xl border border-zinc-800 bg-zinc-100 text-zinc-900"
|
||||
>
|
||||
<!-- app header -->
|
||||
<div class="flex h-14 items-center justify-between border-b border-zinc-300 bg-white px-4">
|
||||
<p class="text-sm font-semibold">Contatos</p>
|
||||
<span
|
||||
class="flex size-9 items-center justify-center rounded-full bg-zinc-200 text-xs text-zinc-600"
|
||||
>SR</span
|
||||
>
|
||||
</div>
|
||||
<!-- omnibox sem moldura, edge-to-edge -->
|
||||
<div class="border-b border-zinc-300 bg-white px-3 py-1.5">
|
||||
<div class="flex min-h-9 items-center gap-1">
|
||||
<span class="text-xs text-zinc-400">⌕</span>
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full bg-zinc-800 px-2 py-0.5 text-xs text-zinc-100"
|
||||
>
|
||||
Gênero: Feminino <span class="text-zinc-400">✕</span>
|
||||
</span>
|
||||
<input
|
||||
class="min-h-9 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-zinc-500"
|
||||
placeholder="Buscar contato…"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- cards -->
|
||||
<ul class="flex flex-col divide-y divide-zinc-300 bg-zinc-100">
|
||||
<li class="flex flex-col gap-2 bg-white px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium">Maria dos Santos</p>
|
||||
<p class="truncate text-xs text-zinc-500">(71) 99999-0001</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-1">
|
||||
<span class="flex size-9 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✆</span
|
||||
>
|
||||
<span class="flex size-9 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✉</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 text-xs">
|
||||
<span class="rounded-full bg-zinc-200 px-2 py-0.5 text-zinc-700">Feminino</span>
|
||||
<span class="rounded-full bg-zinc-200 px-2 py-0.5 text-zinc-700">Salvador · BA</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex flex-col gap-2 bg-white px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium">João Pereira</p>
|
||||
<p class="truncate text-xs text-zinc-500">(75) 98888-0002</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-1">
|
||||
<span class="flex size-9 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✆</span
|
||||
>
|
||||
<span
|
||||
class="flex size-9 items-center justify-center rounded-lg bg-zinc-100 text-xs text-zinc-300"
|
||||
>✉</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 text-xs">
|
||||
<span class="rounded-full bg-zinc-200 px-2 py-0.5 text-zinc-700">Masculino</span>
|
||||
<span class="rounded-full bg-zinc-200 px-2 py-0.5 text-zinc-700"
|
||||
>Feira de Santana · BA</span
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex flex-col gap-2 bg-white px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium">Ana Lima</p>
|
||||
<p class="truncate text-xs text-zinc-500">ana@email.com</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-1">
|
||||
<span
|
||||
class="flex size-9 items-center justify-center rounded-lg bg-zinc-100 text-xs text-zinc-300"
|
||||
>✆</span
|
||||
>
|
||||
<span class="flex size-9 items-center justify-center rounded-lg bg-zinc-100 text-xs"
|
||||
>✉</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 text-xs">
|
||||
<span class="rounded-full bg-zinc-200 px-2 py-0.5 text-zinc-700">Não binário</span>
|
||||
<span class="rounded-full bg-zinc-200 px-2 py-0.5 text-zinc-700">Salvador · BA</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- FAB de criação (substitui o de ações rápidas; FAB de IA permanece) -->
|
||||
<div class="relative h-20 bg-zinc-100">
|
||||
<div
|
||||
class="absolute bottom-4 right-4 flex size-14 items-center justify-center rounded-full bg-red-600 text-2xl text-white"
|
||||
>
|
||||
+
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-4 right-20 flex size-14 items-center justify-center rounded-full bg-zinc-900 text-xs text-zinc-100"
|
||||
>
|
||||
IA
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ CENA: MOBILE — sheet de edição ============ -->
|
||||
<h2 class="mt-8 text-sm font-semibold uppercase tracking-wide text-zinc-500">
|
||||
Mobile — bottom sheet de edição (toque no card)
|
||||
</h2>
|
||||
<section
|
||||
data-shot="mobile-edicao"
|
||||
class="mt-4 w-[390px] overflow-hidden rounded-xl border border-zinc-800 bg-zinc-100 text-zinc-900"
|
||||
>
|
||||
<!-- lista desfocada atrás (representação) -->
|
||||
<div class="h-24 bg-zinc-100 opacity-40">
|
||||
<div class="flex h-14 items-center border-b border-zinc-300 bg-white px-4">
|
||||
<p class="text-sm font-semibold">Contatos</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5 px-4 pt-2">
|
||||
<div class="h-5 w-2/3 rounded bg-zinc-300"></div>
|
||||
<div class="h-4 w-1/2 rounded bg-zinc-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- sheet -->
|
||||
<div class="rounded-t-2xl border-t border-zinc-300 bg-white px-4 pb-4 pt-3">
|
||||
<div class="mx-auto mb-3 h-1 w-10 rounded-full bg-zinc-300"></div>
|
||||
<p class="mb-3 text-sm font-semibold">Editar contato</p>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="Maria dos Santos"
|
||||
/>
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="Feminino"
|
||||
/>
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="(71) 99999-0001"
|
||||
/>
|
||||
<p class="text-xs font-medium text-zinc-500">+ Adicionar telefone</p>
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="maria@email.com"
|
||||
/>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="Salvador"
|
||||
/>
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="BA"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
|
||||
value="40000-000"
|
||||
placeholder="CEP"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button
|
||||
class="flex h-10 flex-1 items-center justify-center rounded-lg border border-zinc-300 text-sm font-medium text-zinc-600"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
class="flex h-10 flex-1 items-center justify-center rounded-lg bg-red-600 text-sm font-medium text-white"
|
||||
>
|
||||
Salvar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
110
docs/plans/contatos-pagina-da-entidade-contact.md
Normal file
110
docs/plans/contatos-pagina-da-entidade-contact.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Contatos — página da entidade Contact (fichas da campanha)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-13
|
||||
Issue: #728
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: C — fluxo novo: rota nova + tabela desktop + cards mobile + omnibox
|
||||
Rascunho UI: docs/plans/contatos-pagina-da-entidade-contact-ui-draft.html
|
||||
Appetite: ~2–3 dias eng; uma rota nova com chassis existentes
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A página de Pessoas (`/campanha/pessoas`) nasceu de uma necessidade real — editar o contato de todo mundo rápido num lugar só — mas o que ela virou (lista all-featuring, com colunas de capacidade por território) não tem caso de uso: a mesa não navega por capacidades nela, e cada lista especializada já faz isso melhor. O que a mesa realmente precisa é **uma lista de fichas de contato**: ver os dados de contato de qualquer pessoa da campanha num lugar só, atualizar ficha (nome, e-mail, telefones, gênero, estado, cidade, CEP) e **contatar pessoas específicas** (WhatsApp/e-mail) sem saltar entre listas.
|
||||
|
||||
O padrão que deu certo — **toda entidade de pessoa tem uma ficha `Contact`** — é exatamente a fundação do caso de uso: a ficha é a fonte única de dados de contato. Esta entrega constrói a página da ficha. A página de Pessoas **fica no ar como referência** enquanto esta nasce (e cai depois, em decisão futura — ver Fora de escopo).
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenação e assessores na mesa, com um celular na mão ("preciso falar com fulana") ou editando a ficha de quem entrou na campanha.
|
||||
- **Job principal:** achar a pessoa por qualquer dado de contato (nome, e-mail, telefone, cidade…), ver a ficha completa e atualizar o que estiver errado, e partir dela para o contato (WhatsApp / e-mail).
|
||||
- **Fluxo desejado:** abre `/campanha/contatos` → vê a lista de fichas (tabela no desktop, cards no mobile) → filtra na omnibox por cada propriedade da ficha (gênero, estado, cidade, ausências como "sem telefone") e/ou busca geral → ordena → edita a ficha onde vê (nome, e-mail, telefones, gênero, estado, cidade, CEP) → contata pela ação da linha (WhatsApp / e-mail / copiar telefone).
|
||||
- **Anti-goals de produto:** **não** vira a página de Pessoas de novo — sem colunas de capacidade (assessora/lidera/dobra/apoiador), sem territórios, sem papel; **não** é segundo cadastro de pessoa (a ficha continua sendo `Contact`); **não** substitui as listas especializadas (`/liderancas`, `/dobradinhas`, `/apoiadores`, `/assessores` seguem intocadas); **não** é edição em massa / spreadsheet.
|
||||
|
||||
## Decisões de produto (travadas com o humano em 2026-08-13)
|
||||
|
||||
- **A página é da ficha `Contact`, pura.** Colunas desktop, nesta ordem: **Nome, Gênero, Telefone, E-mail, Cidade, Estado** (CEP fica dado da ficha, editável no mobile sheet, sem coluna). Sem coluna de vínculos — foi o que matou a página de Pessoas.
|
||||
- **Gênero (vocabulário de produto):** Masculino, Feminino, **Não binário**, Não informado — facet e edição usam exatamente esses 4 valores.
|
||||
- **Busca geral cobre telefone.** O `q` da omnibox casa nome, e-mail e **qualquer** telefone da ficha (multi-telefone, C112) — absorve o gatilho de C121 ("tenho um número na mão, acho a pessoa").
|
||||
- **Filtros por propriedade da ficha:** gênero, estado, cidade, ausências ("sem telefone", "sem e-mail"). **Filtros de vínculo** (só facet, nunca coluna): **Lideranças, Dobradinhas, Assessores, Equipe** (nome pt-BR que cobre assessores/coordenadores/candidato — "Equipe" assumido; facet booleano "Sim" para cada vínculo).
|
||||
- **Ordenação:** pelas colunas relevantes (nome default, cidade, estado, e-mail) — padrão C117 (header sortable + omnibox). Sem ordenação por telefone.
|
||||
- **Edição onde se vê (desktop):** **as células de dados são inputs** — salva no blur ou Enter; multi-telefones empilhados um sobre o outro na célula. Mobile: toque no card abre **bottom sheet com todos os campos** editáveis (nome, gênero, telefones + adicionar, e-mail, cidade, estado, CEP). Mesmo padrão de escopo do `updatePersonContact` (assessor só edita ficha da carteira dele).
|
||||
- **Criação de contato:** desktop botão **"+ Criar contato" ao lado de "Colunas"** → linha vazia no topo com ações **Descartar/Salvar**; mobile **FAB de criação** (substitui o FAB de ações rápidas nesta rota; FAB de IA permanece) → bottom sheet de criação.
|
||||
- **Ações por linha:** **Mensagem no WhatsApp** (`wa.me`, só com telefone — `whatsAppHrefForPhone`), **Enviar e-mail** (`mailto`, só com e-mail), **Apagar contato** — com **alerta de confirmação que lista o contato e todas as entidades linkadas a ele** (liderança, dobradinha, assessor, apoiador, conta de acesso…) que serão removidas.
|
||||
- **Escopo de acesso = `canReadContacts` existente:** assessor vê só fichas da carteira (via lideranças/dobradinhas/apoiadores do escopo); coordenador/candidato veem tudo. **Leader não acessa** (lockdown — a ferramenta "Meus contatos" do leader fica intocada).
|
||||
- **Nenhuma migration.** `Contact` já existe; nada no schema muda.
|
||||
- **Mobile: cards mostram só o telefone principal; "Cidade · Estado"** (nessa ordem); **sem rótulo "Sem e-mail"** — o que não existe simplesmente não aparece. **Omnibox sem bordas, edge-to-edge.**
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- `/campanha/contatos` lista as fichas que o ator pode ler, com **tabela no desktop e cards no mobile** (mesmo chassis das listas atuais).
|
||||
- Omnibox com facet por propriedade (gênero com os 4 valores, estado, cidade, ausências) + vínculos (Lideranças/Dobradinhas/Assessores/Equipe) + busca geral por nome/e-mail/qualquer telefone + ordenação (nome/cidade/estado/e-mail). **Chips do filtro dentro do input.**
|
||||
- Edição where-you-see: desktop células são inputs (salva no blur/Enter), telefones múltiplos empilhados; mobile sheet com todos os campos, CEP incluso. Escopo do ator respeitado.
|
||||
- Criação: desktop "+ Criar contato" ao lado de "Colunas" (linha vazia + Descartar/Salvar); mobile FAB (no lugar do de ações rápidas, IA permanece) → bottom sheet.
|
||||
- Ações por linha: WhatsApp (só com telefone), e-mail (só com e-mail), Apagar contato com alerta listando as entidades linkadas.
|
||||
- Mobile: card mostra só telefone principal; "Cidade · Estado"; sem "Sem e-mail"; omnibox edge-to-edge sem bordas.
|
||||
- Leader não acessa a rota; "Meus contatos" do leader intacta.
|
||||
- Nenhuma rota pública muda; nenhuma migration; `/campanha/pessoas` continua no ar como referência.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — as colunas expressam a ficha, não métricas. Nenhum agregado entra na superfície.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** rota nova `src/app/(campaign)/campanha/(app)/contatos/` (ver Questões — decidido: A, mover o leader tool); shells do Pass 2 W1 (URL state, omnibox, tabela, cards, paginação); utilitários de domínio já existentes (`canReadContacts` em `src/utilities/access/contacts.ts`, `contactIdentity.ts`, `contactPhoneLocks.ts`, `src/lib/phone.ts` com `whatsAppHrefForPhone`/`formatBrazilianPhoneInput`).
|
||||
- **Precedente a olhar:** C116/C130 (células editáveis in-place + célula de contato), C117 (ordenação header + omnibox), C125 (facet de ausência), C112 (multi-telefone), C100 (lista de pessoas como referência estrutural), B18 (filtros salvos — padrão disponível, não prometido no v1), `updatePersonContact` (`src/app/(campaign)/campanha/actions/person.ts` — padrão de escopo de edição de ficha), **apagar com manifest de vínculos** (precedente do `personDelete`/delete de pessoa de C100 — reusar o contrato "o que a remoção leva junto" para o alerta).
|
||||
- **Risco de acoplamento:** a edição da ficha deve preservar o escopo (assessor só a carteira) e os invariantes do `Contact` (normalização de telefone, nome de dobradinha vinculada); a rota `contatos` hoje pertence ao leader tool — **mover para `/campanha/meus-contatos`** exige atualizar `LEADER_CONTACTS_HOME` (`src/lib/campaignPaths.ts`) e o catálogo `campaignPageChrome`; leader lockdown é o limite da rota; facets de vínculo precisam dos joins existentes (leadership/stateDeputy/campaignUser por ficha) — não podem vazar escopo de assessor.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura. Soft: C111/C112 (telefone não único / múltiplos telefones — entregues) e C120 (race de telefone em `findOrCreateContactByPhone`, aberta — não bloqueia).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- **Derrubar a página de Pessoas** — fica no ar como referência durante esta entrega; a remoção é decisão futura do produto (a rota de Pessoas e seus planos abertos morrem juntos — planos cancelados neste lote).
|
||||
- Substituir as listas especializadas (lideranças/dobradinhas/apoiadores/assessores).
|
||||
- Colunas de capacidade/vínculos na lista de contatos (anti-goal explícito — vínculos entram **só** como facet).
|
||||
- Filtros salvos (padrão B18 existe e pode ser reusado depois se a mesa pedir).
|
||||
- Dedupe/merge de fichas duplicadas (pré-existente, fora do v1).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Só completar" com colunas de capacidade.** Se alguém adicionar "Lidera/Assessora/Dobra" como coluna, vira a página de Pessoas de novo. **Corte:** ficha pura; vínculos só como facet da omnibox.
|
||||
- **"Só completar" com edição em massa.** Editar várias fichas de uma vez = spreadsheet mode sem pedido. **Corte:** edição in-place de uma ficha por vez.
|
||||
- **"Só completar" com a busca virando filtro por município.** Município não é propriedade da ficha `Contact` (é do vínculo). **Corte:** facet de cidade/estado (texto da ficha), não de município de capacidade.
|
||||
- **"Só completar" com copiar telefone nas ações.** A mesa não pediu; WhatsApp + e-mail + apagar cobrem o job. **Corte:** sem ação extra no v1.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
_Resolvidas no gate 2026-08-13:_ rota = **A** (leader tool move para `/campanha/meus-contatos`; nova página toma `/campanha/contatos`); CEP editável = **A** (sim, no sheet mobile); ordenação por telefone = **não**; kill list = **confirmado**.
|
||||
|
||||
- **Nome do facet de staff?** **Opções:** A) "Equipe" | B) "Staff" | C) "Pessoal da campanha". **Recomendação:** A — pt-BR curto que cobre assessores, coordenadores e candidato. _(assumido — validar)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #728
|
||||
- Rascunho UI (gate): docs/plans/contatos-pagina-da-entidade-contact-ui-draft.html + PNGs embutidos abaixo
|
||||
- Planos cancelados neste lote (decisão "Pessoas vira referência"): C130+ (#717), C132 (#699), C135 (#714), C136 (#722), C137 (#724), C138 (#725), C121 (#664)
|
||||
- Abrir primeiro: `src/collections/Contact.ts`, `src/utilities/access/contacts.ts`, `src/app/(campaign)/campanha/(app)/pessoas/page.tsx`, `src/utilities/people/peopleData.ts`, `src/components/campaign/shared/CampaignListOmnibox.tsx`
|
||||
- `AGENTS.md` — `Contact` é a ficha de pessoa (convenção travada); leader lockdown; escopo de assessor por carteira
|
||||
|
||||
## Rascunho UI (gate)
|
||||
|
||||
Desktop — tabela (Nome, Gênero, Telefone, E-mail, Cidade, Estado), células = inputs, chips dentro do input, "+ Criar contato" ao lado de "Colunas":
|
||||
|
||||

|
||||
|
||||
Desktop — linha nova após "+ Criar contato" (Descartar/Salvar):
|
||||
|
||||

|
||||
|
||||
Mobile — cards edge-to-edge, omnibox sem borda, só telefone principal, "Cidade · Estado", FAB de criação (IA permanece):
|
||||
|
||||

|
||||
|
||||
Mobile — bottom sheet de edição (toque no card):
|
||||
|
||||

|
||||
|
|
@ -1,76 +0,0 @@
|
|||
# Escala e DRY pós-C130 (registro de facets da omnibox)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-11
|
||||
Item do roadmap: fill-in **C130+** pós-[#697](https://github.com/fsolla/teqo/issues/697)
|
||||
Impeccable: A — sem superfície UI nova (DRY/estrutura)
|
||||
Appetite: ~0,5–1 dia eng
|
||||
Responsável: —
|
||||
|
||||
## Contexto
|
||||
|
||||
O C130 adicionou o **5º facet** da lista de pessoas (`party`), cada um com o mesmo
|
||||
bloco em `peopleOmnibox.ts` (seeds com `createOmniboxSuggestionSeed`, chips,
|
||||
branches de prefixo em `applyPeopleOmniboxSuggestion`/`removePeopleOmniboxChip`)
|
||||
mais o toggle correspondente em `peopleListFilters.ts` e o param no
|
||||
`peopleListUrl.ts`. O padrão já era repetido para capacity/municipality/status/
|
||||
ausência — e a mesma forma existe nas outras listas (lideranças, municípios,
|
||||
apoiadores). A cada facet novo, o custo de adição é ~5 edições em 3 módulos, com
|
||||
risco de esquecer um branch (facet que aparece nas sugestões mas não filtra).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- **Registro declarativo de facet** (um lugar por facet: prefixo do id, grupo,
|
||||
label, keywords, toggle/parse): `peopleOmnibox.ts` e `peopleListFilters.ts`
|
||||
derivam seeds/chips/branches do registro; a adição de um facet novo vira uma
|
||||
entrada, não cinco edições.
|
||||
- Sem mudança de URL/contrato (params, chaves e labels pt-BR iguais).
|
||||
- Escopo mínimo: **pessoas** (os 5 facets). Generalizar para as outras listas
|
||||
só quando a segunda lista adotar (2º consumidor) — mesmo corte do C130.
|
||||
- **Hazard documentado no registro:** valores de facet de texto livre (partido)
|
||||
não podem conter `:` (separador do id de sugestão) — validação no registro.
|
||||
|
||||
## Já resolvido no simplify/critique (não reabrir)
|
||||
|
||||
- Parse do `party` simplificado para `allParamValues(...).filter(≤32)` (o
|
||||
helper já trima/deduplica) — redundância removida.
|
||||
- Comentários de sort key (`peopleListUrl`/`peopleData`) reconciliados com a
|
||||
exceção C130 (base/party invisíveis vs `email` oculta-por-toggle).
|
||||
- Comentários "Aliada em" → "Dobra em" (3 pontos).
|
||||
- Orfandade `field: 'city'` removida (type/schema/record/formAction).
|
||||
- Tooltip span-wrappers documentados por site (disabled vs ref-forwarding).
|
||||
|
||||
## Explicitamente fora
|
||||
|
||||
- **Flake `googleCalendarSync.int.spec.ts`** (falha intermitente, reproduz no
|
||||
main limpo) — classe de teste instável pré-existente, fora deste lote.
|
||||
- **Flakes e2e sob carga da máquina** (setup prewarm socket hang-up; picker
|
||||
"2022"/B34 em runs combinados com load ~60+; passam isolados e no main) —
|
||||
classe já documentada no changelog B197+; reavaliar com harness dev
|
||||
estabilizado.
|
||||
- **Tooltip `openOnTouch` nas ações da tabela de pessoas** — a tabela é
|
||||
desktop-only (`md:block`); touch não a alcança. Gatilho: se a tabela ganhar
|
||||
cobertura coarse, seguir o precedente B23 (`openOnTouch={false}` em
|
||||
triggers que abrem Popover/Dialog).
|
||||
- **Label mobile "Base:" vs 2ª linha desktop sem label** — decisão de gate
|
||||
(mobile muda só o nome da coluna; C130 anti-goal). Gatilho: redesign dos
|
||||
cards mobile.
|
||||
- **Janela do save já-despachado no Escape** (draft descartado localmente mas
|
||||
o save em voo persiste) — mitigada com `requestId` bump; janela
|
||||
completada-save irrecuperável client-side, documentada no código. Gatilho:
|
||||
se o autosave da célula virar transacional.
|
||||
|
||||
## Fases
|
||||
|
||||
1. **Registro de facets de pessoas** — declarar os 5 facets existentes; derivar
|
||||
seeds/chips/apply/remove/toggles do registro; unit tests do
|
||||
`peopleOmnibox`/`peopleListFilters` inalterados em comportamento.
|
||||
2. **Gates** — `pnpm gate:fast`, e2e `campaignPeople`.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Identificadores em inglês; labels pt-BR intactos (grupos "Capacidade",
|
||||
"Município", "Apoio", "Ausência", "Partido", "Ordenação").
|
||||
- Sem migration/access/Consent/URL nova.
|
||||
- Precedentes: `escala-dry-pos-c6.md` (F3 — DRY de filtros/forms),
|
||||
`escala-dry-pos-c8.md` (facet de município de apoiadores).
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# Pessoas: criação rápida de pessoa na lista (linha nova no topo com botão Salvar)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-11
|
||||
Issue: #699
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe na tabela/omnibox de `/campanha/pessoas` (botão ao lado do seletor de colunas + linha-draft)
|
||||
Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-Code-teqo/canvases/plan-pessoas-ajustes-ui-draft.canvas.tsx (seção "Criação rápida de pessoa")
|
||||
Appetite: ~0,5–1 dia eng; uma linha-draft + ação de criação
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
Cadastrar uma pessoa nova exige hoje sair da lista (fluxo de criação das listas especializadas). A mesa quer criar direto em `/campanha/pessoas`: um botão "Nova pessoa" ao lado do seletor de colunas abre uma **linha em branco no topo da lista**, onde nome/telefone/capacidades são preenchidos inline e um botão de **Salvar** (na coluna de ações) confirma a criação.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenador/candidato (e assessor na carteira dele) cadastrando contato novo durante atendimento.
|
||||
- **Job principal:** criar a ficha da pessoa e já amarrar a primeira capacidade (cidade assessorada/liderada/onde dobra) sem trocar de tela.
|
||||
- **Fluxo desejado:** clico em "Nova pessoa" → uma linha vazia aparece no topo da lista (nome e telefone em branco, capacidades vazias) → digito nome e telefone → adiciono a primeira cidade de uma capacidade (com C128, a pessoa nasce naquela tabela) → clico em "Salvar" → a linha vira uma pessoa de verdade na lista. "Descartar" (X) remove a linha sem salvar.
|
||||
- **Anti-goals de produto:** não é modal/formulário novo (a linha é a superfície); não é segundo cadastro paralelo de pessoa (ficha continua `Contact`); não substitui os fluxos ricos das listas especializadas.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Botão **"Nova pessoa"** visível ao lado do seletor de colunas (barra de filtros) para quem pode criar (staff; escopo de carteira preservado para assessor).
|
||||
- Clicar abre **uma linha-draft no topo da lista**, destacada, com: Nome e Contato (telefone) editáveis inline, as três colunas de capacidade vazias, e na coluna de ações um botão **Salvar** (+ descartar).
|
||||
- **Salvar** cria a ficha (e, com C128, os vínculos de capacidade já adicionados na linha) e a pessoa aparece na lista; **Descartar** fecha a linha sem escrever nada.
|
||||
- Salvar sem nome (ou só com nome, sem capacidade) não quebra a lista: pessoa sem capacidade continua fora da lista (regra C100) — a linha avisa quando isso acontecer em vez de "sumir" com a pessoa.
|
||||
- A linha-draft respeita os mesmos limites das células normais (escopo de acesso, chips de território, "Salvador" agregado de C131).
|
||||
- Sem regressão nas linhas normais: a linha-draft nunca conta como linha real (ordenação, filtros, paginação, facetas não a incluem).
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — cadastro, sem métrica.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/app/(campaign)/campanha/(app)/pessoas/page.tsx` (botão na barra de filtros, linha-draft no topo da tabela), células de capacidade em modo `draft` (as células de município já têm contrato de draft local), ação de criação de pessoa no estilo das ações de domínio existentes (`src/app/(campaign)/campanha/actions/…`), escrita transacional Contact + capacidades.
|
||||
- **Precedente a olhar:** C128 (criação de entidade pela primeira cidade), criação de liderança/dobradinha/contato nas listas especializadas (findOrCreate de Contact + entidade, transação), células `draft` (`MunicipalityPortfolioCell.draft`), C116 (células sempre-input).
|
||||
- **Risco de acoplamento:** a linha-draft lida com "pessoa que ainda não existe" — toda célula que hoje parte de `ownerId` existente precisa do caminho de criação; a regra C100 (sem capacidade não há linha) determina o que acontece ao salvar.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C128** (ordem/forte) — sem o ciclo de vida "primeira cidade cria a entidade", a linha-draft não consegue amarrar capacidades no momento da criação.
|
||||
- Suave com C130 (a linha-draft usa as mesmas colunas/larguras da tabela ajustada).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Wizard rico de criação (telefones múltiplos, partido, organizações, convite) — a linha é o cadastro mínimo; o resto segue nas fichas.
|
||||
- Edição de dados da linha-draft além de nome/telefone/capacidades (e-mail fica para depois da criação).
|
||||
- Criação a partir do mobile (cards) no v1 — a pedido é o botão ao lado de Colunas (desktop).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Linha-draft vira mini-wizard**: se cada capacidade pedir seu próprio fluxo de criação na linha, a superfície explode. **Corte:** a linha só repete o comportamento das células normais (com C128 criando as entidades); sem campos extras além de nome/telefone.
|
||||
- **"Salvar sem capacidade some a pessoa"**: a regra C100 confunde se a linha apenas "desaparece". **Corte:** aviso claro na linha ao salvar sem capacidade ("a pessoa não aparece na lista enquanto não tiver uma capacidade") + manter o cadastro feito (a ficha existe para o admin).
|
||||
- **Duas linhas-draft simultâneas**: só uma linha de criação por vez (abrir outra descarta/fecha a atual sem salvar).
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Salvar pessoa sem nenhuma capacidade?** **Opções:** (a) salva a ficha e avisa que ela não aparece na lista (regra C100 mantida); (b) bloqueia o Salvar até ter uma capacidade. **Recomendação:** (a) — cadastro não deve ser impedido, mas o destino fica explícito no aviso. _(assumido — validar no gate)_
|
||||
- **Quem pode usar "Nova pessoa"?** **Opções:** (a) todo staff da página (assessor cria na carteira dele); (b) só coordenação/candidato. **Recomendação:** (a) — com escopo de carteira igual às células; coordenação continua vendo tudo. _(assumido — validar no gate)_
|
||||
|
||||
## Referências
|
||||
|
||||
- Canvas UI (gate): plan-pessoas-ajustes-ui-draft.canvas.tsx (seção C132)
|
||||
- Planos: [pessoas-qualquer-capacidade-para-toda-pessoa.md](pessoas-qualquer-capacidade-para-toda-pessoa.md) (C128 — criação pela primeira cidade), [pessoas-lista-unificada.md](pessoas-lista-unificada.md) (C100 — regra de visibilidade por capacidade), [pessoas-edicao-inplace-lista.md](pessoas-edicao-inplace-lista.md) (C116)
|
||||
- `AGENTS.md` — transações multi-coleção, escopo de acesso por capacidade
|
||||
Loading…
Reference in a new issue