Merge pull request #731 from fsolla/plans/plan-issue-43

plan(C139): revisão do rascunho UI — coluna CEP, Apagar no mobile, inputs sem moldura
This commit is contained in:
Francisco Solla 2026-08-13 16:06:54 +00:00 committed by GitHub
commit ace2e5682d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 103 additions and 59 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -11,7 +11,7 @@
<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.
único = CTA primário (vermelho do app). Inputs sem borda — "somem" na célula.
</p>
<!-- ============ CENA: DESKTOP ============ -->
@ -106,7 +106,7 @@
</div>
</div>
<!-- tabela: Nome, Gênero, Telefone, Email, Cidade, Estado, Ações -->
<!-- tabela: Nome, Gênero, Telefone, Email, Cidade, Estado, CEP, Ações -->
<div class="overflow-x-auto bg-white">
<table class="w-full text-left text-sm">
<thead>
@ -117,6 +117,7 @@
<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 font-medium">CEP</th>
<th class="px-4 py-2 text-right font-medium">Ações</th>
</tr>
</thead>
@ -125,40 +126,46 @@
<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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="BA"
/>
</td>
<td class="px-4 py-2">
<input
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="40000-000"
/>
</td>
<td class="px-4 py-2">
<div class="flex justify-end gap-1">
<span
@ -180,46 +187,52 @@
<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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="Masculino"
/>
</td>
<td class="px-4 py-2">
<div class="flex flex-col gap-1">
<div class="flex flex-col gap-0.5">
<input
class="w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm outline-none"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
placeholder="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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="BA"
/>
</td>
<td class="px-4 py-2">
<input
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="44000-000"
/>
</td>
<td class="px-4 py-2">
<div class="flex justify-end gap-1">
<span
@ -237,44 +250,50 @@
</div>
</td>
</tr>
<!-- linha 3: linkada a liderança + dobradinha, sem telefone -->
<!-- linha 3: 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
placeholder="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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 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"
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="BA"
/>
</td>
<td class="px-4 py-2">
<input
class="w-full bg-transparent text-sm text-zinc-900 outline-none"
value="40000-000"
/>
</td>
<td class="px-4 py-2">
<div class="flex justify-end gap-1">
<span
@ -337,48 +356,55 @@
<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 font-medium">CEP</th>
<th class="px-4 py-2 text-right font-medium">Ações</th>
</tr>
</thead>
<tbody>
<!-- linha nova em rascunho -->
<!-- linha nova em rascunho: inputs invisíveis, célula destacada -->
<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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
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"
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
placeholder="UF"
/>
</td>
<td class="px-4 py-2">
<input
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
placeholder="CEP"
/>
</td>
<td class="px-4 py-2">
<div class="flex justify-end gap-1">
<button
@ -401,6 +427,7 @@
<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 text-zinc-700">40000-000</td>
<td class="px-4 py-2.5">
<div class="flex justify-end gap-1">
<span
@ -469,6 +496,9 @@
<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">
@ -490,6 +520,9 @@
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">
@ -513,6 +546,9 @@
<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">
@ -554,45 +590,53 @@
<div class="h-4 w-1/2 rounded bg-zinc-300"></div>
</div>
</div>
<!-- sheet -->
<!-- sheet: inputs sem moldura, divisórias full-bleed -->
<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">
<div class="flex flex-col">
<input
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 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"
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 text-sm outline-none"
value="Feminino"
/>
<input
class="rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm outline-none"
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 text-sm outline-none"
value="(71) 99999-0001"
/>
<p class="text-xs font-medium text-zinc-500">+ Adicionar telefone</p>
<p class="py-1.5 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"
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 text-sm outline-none"
value="maria@email.com"
/>
<div class="grid grid-cols-2 gap-2">
<input
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 text-sm outline-none"
value="Salvador"
/>
<div class="flex items-center 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"
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 text-sm outline-none"
value="BA"
/>
<input
class="w-full border-b border-zinc-200 bg-transparent px-0 py-2.5 text-sm outline-none"
value="40000-000"
placeholder="CEP"
/>
</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="mt-4 flex h-10 w-full items-center justify-center gap-1 rounded-lg border border-red-600 text-sm font-medium text-red-600"
>
Apagar contato
</button>
<p class="mt-1.5 text-center text-xs text-zinc-400">
Alerta confirma o contato e as entidades linkadas
</p>
<div class="mt-3 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"
>

View file

@ -25,25 +25,25 @@ O padrão que deu certo — **toda entidade de pessoa tem uma ficha `Contact`**
## 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.
- **A página é da ficha `Contact`, pura.** Colunas desktop, nesta ordem: **Nome, Gênero, Telefone, E-mail, Cidade, Estado, CEP**. 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).
- **Edição onde se vê (desktop):** **as células de dados são inputs sem moldura** — sem borda, sem fundo, sem destaque; "somem" na célula e só mostram o valor (salva no blur ou Enter); placeholder discreto quando vazio ("Sem telefone"/"Sem e-mail"); 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), inputs full-bleed com divisórias. 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.**
- **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.** **Apagar contato presente no mobile:** no card (ícone junto de WhatsApp/e-mail) e no sheet de edição (botão "Apagar contato" em vermelho) — sempre com o alerta de confirmação listando entidades linkadas.
## 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.
- Edição where-you-see: desktop células são inputs **sem moldura** (somem na célula; 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.
- Ações por linha: WhatsApp (só com telefone), e-mail (só com e-mail), Apagar contato com alerta listando as entidades linkadas — **no desktop e no mobile** (card + sheet de edição).
- 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.
@ -93,7 +93,7 @@ _Resolvidas no gate 2026-08-13:_ rota = **A** (leader tool move para `/campanha/
## 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 — tabela (Nome, Gênero, Telefone, E-mail, Cidade, Estado, CEP), células = inputs sem moldura, chips dentro do input, "+ Criar contato" ao lado de "Colunas":
![Rascunho UI — desktop](contatos-pagina-da-entidade-contact-ui-draft-desktop.png)
@ -101,10 +101,10 @@ Desktop — linha nova após "+ Criar contato" (Descartar/Salvar):
![Rascunho UI — desktop, linha nova](contatos-pagina-da-entidade-contact-ui-draft-desktop-novo.png)
Mobile — cards edge-to-edge, omnibox sem borda, só telefone principal, "Cidade · Estado", FAB de criação (IA permanece):
Mobile — cards edge-to-edge, omnibox sem borda, só telefone principal, "Cidade · Estado", Apagar no card, FAB de criação (IA permanece):
![Rascunho UI — mobile](contatos-pagina-da-entidade-contact-ui-draft-mobile.png)
Mobile — bottom sheet de edição (toque no card):
Mobile — bottom sheet de edição (toque no card; Apagar contato no rodapé):
![Rascunho UI — mobile, sheet de edição](contatos-pagina-da-entidade-contact-ui-draft-mobile-edicao.png)