plan(C139): nome obrigatório e único na criação, com aviso de conflito
This commit is contained in:
parent
ace2e5682d
commit
69044a04bc
4 changed files with 11 additions and 2 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
|
@ -366,7 +366,7 @@
|
|||
<td class="px-4 py-2">
|
||||
<input
|
||||
class="w-full bg-transparent text-sm outline-none placeholder:text-zinc-400"
|
||||
placeholder="Nome"
|
||||
value="Maria dos Santos"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
|
|
@ -420,6 +420,12 @@
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- conflito de nome: mensagem amigável, o que foi digitado permanece -->
|
||||
<tr class="bg-red-50">
|
||||
<td colspan="8" class="px-4 pb-2.5 text-xs font-medium text-red-700">
|
||||
⚠ Já existe um contato com este nome — confira a lista antes de salvar.
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ O padrão que deu certo — **toda entidade de pessoa tem uma ficha `Contact`**
|
|||
- **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 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.
|
||||
- **Nome obrigatório e único na criação.** O nome é obrigatório (Salvar inativo sem nome) e **deve ser único entre as fichas** — comparação normalizada (sem espaços duplicados, sem caixa). Conflito na criação → **aviso claro ao usuário** ("Já existe um contato com este nome"), mantendo tudo o que foi digitado na linha nova / sheet.
|
||||
- **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.
|
||||
|
|
@ -43,6 +44,7 @@ O padrão que deu certo — **toda entidade de pessoa tem uma ficha `Contact`**
|
|||
- 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 **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.
|
||||
- Criação exige nome (Salvar inativo sem nome); nome único (comparação normalizada); conflito → mensagem amigável mantendo o que foi digitado.
|
||||
- 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.
|
||||
|
|
@ -55,7 +57,7 @@ O padrão que deu certo — **toda entidade de pessoa tem uma ficha `Contact`**
|
|||
## 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).
|
||||
- **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), `assertStateDeputyNameAvailable` (`src/utilities/stateDeputy/nameInvariant.ts` — padrão de checagem de disponibilidade de nome normalizado, reusar para o conflito de nome na criação; `Contact.name` já é `required` no schema, a unicidade é checagem de app, sem índice — mantém "nenhuma migration"), **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
|
||||
|
|
@ -73,6 +75,7 @@ O padrão que deu certo — **toda entidade de pessoa tem uma ficha `Contact`**
|
|||
## 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 unicidade de nome aplicada ao import CSV.** A base real já pode ter duas "Maria Silva" com telefones diferentes (import dedupa por telefone); bloquear nome no import quebraria o lote. **Corte:** a regra vale só para a criação/edição manual na página; sem dedupe retroativo.
|
||||
- **"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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue