From 9820c92ecc1b3d61c96b63959d7ab682a6c63a58 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sat, 8 Aug 2026 13:58:23 -0300 Subject: [PATCH] =?UTF-8?q?docs(plans):=20C90=20=E2=80=94=20remodelar=20re?= =?UTF-8?q?spons=C3=A1veis=20da=20Atividade;=20C91=20=E2=80=94=20criar=20e?= =?UTF-8?q?vento=20inline=20na=20agenda?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/agenda-criar-evento-inline.md | 113 ++++++++++++++++++ .../plans/remodelar-responsaveis-atividade.md | 90 ++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 docs/plans/agenda-criar-evento-inline.md create mode 100644 docs/plans/remodelar-responsaveis-atividade.md diff --git a/docs/plans/agenda-criar-evento-inline.md b/docs/plans/agenda-criar-evento-inline.md new file mode 100644 index 00000000..f0268791 --- /dev/null +++ b/docs/plans/agenda-criar-evento-inline.md @@ -0,0 +1,113 @@ +# Criar evento inline no calendário da agenda (estilo Google Calendar) + +Status: rascunho +Atualizado em: 2026-08-08 +Issue: #428 +Priority: P1 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: C — fluxo novo de criação inline sobre uma tela existente (`/campanha/agenda`) +Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-Code-teqo/canvases/plan-c91-ui-draft.canvas.tsx +Appetite: ~1–1,5 dia eng; um outcome verificável — staff cria compromisso no slot certo sem sair da agenda +Responsável: — + +## Dependência rápida + +- **Dura: [C90 — remodelar `responsible` da Atividade](remodelar-responsaveis-atividade.md) (#426)** (campo único polimórfico multi-valor; remove `advisors`/`leadership`). O seletor de responsáveis do overlay **consome o campo remodelado**; este item não decide schema. + +## Intenção + +A agenda (C15) já mostra a semana e o mês, mas criar exige **sair** para `/campanha/atividades/nova` (o clique no slot só pré-preenche a URL). Quebra o ritmo de planejamento na mesa: “onde eu estava” se perde. Queremos criar **no lugar clicado**, como no Google Calendar — um popover com dia e horários já preenchidos do slot, editáveis, e o evento surgindo no calendário assim que salvar, sem reload. A mesa-alvo já vive no Google Calendar; a promessa é “mesma letra, no Teqo”. + +A criação rápida também leva o **seletor de responsáveis** do fluxo completo para o overlay (popover/drawer): o responsável de um compromisso pode ser **candidato**, **assessor** (staff `campaignUser`), **liderança** ou **dobradinha** — não só um Contact avulso. Na mesa, o coordenador decide o compromisso e quem o conduz no mesmo gesto, sem abrir o formulário grande só para isso. + +## Persona e fluxo + +- **Persona / contexto:** coordenador (ou assessor no recorte) planejando a semana do candidato, desktop e mobile, no meio de uma conversa — quer lançar compromissos rápido e seguir. +- **Job principal:** criar um compromisso no dia/horário certo da agenda **sem sair dela**. +- **Fluxo desejado:** + 1. Abre `/campanha/agenda`, aplica filtros se quiser. + 2. Clica num slot vazio (semana ou mês) no desktop → popover abre **naquele ponto**, com título vazio e **dia/horário já preenchidos do slot** (editáveis). + 3. Mobile → em vez de popover, um **bottom sheet** sobe com o mesmo conteúdo. + 4. Digita o mínimo obrigatório (título + município) e escolhe o **responsável** (candidato, assessor, liderança ou dobradinha), salva → o evento **aparece no calendário na hora**, sem reload. + 5. Precisa de mais campos? “Mais detalhes” leva ao formulário completo pré-preenchido. +- **Anti-goals de produto:** o popover **não** vira o formulário completo (tarefas, organizações, demandas, liderança vinculada e assessores responsáveis ficam no detalhe); salvar **não** navega (voltar à tela quebra o fluxo); **não** é mode de edição em massa; o seletor de responsável **não** cadastra pessoa nova — só escolhe entre entidades já existentes da campanha (candidato/assessor/liderança/dobradinha). + +### Esboço de fluxo (C) + +```text +[/campanha/agenda + filtros] + → clique em slot vazio + → [desktop] popover ancorado ao ponto / [mobile] bottom sheet + → título + horário (pré-preenchido do slot, editável) + município + local + → seletor de responsável (candidato · assessor · liderança · dobradinha) + → Salvar → evento insere no calendário (sem reload) + → “Mais detalhes” → /nova pré-preenchida (form completo) +``` + +## Objetivo e aceite + +- Clicar num slot vazio da agenda abre criação **inline** (popover no desktop, bottom sheet no mobile) em vez de navegar para `/campanha/atividades/nova`. +- O popover/sheet abre com **início e fim pré-preenchidos do slot clicado** (ex.: o intervalo do clique; clique no dia cheio “vira” 09:00–10:00, como o comportamento atual de `dateClick`), e os horários são **editáveis**. +- Campos mínimos para salvar: **título** e **município** (obrigatórios do modelo) — local e **responsável** opcionais no overlay. +- O overlay traz um **seletor de responsáveis** que escolhe **um ou mais** entre **candidato/assessor/coordenador (staff `campaignUser`), liderança (`leadership`) ou dobradinha (`stateDeputy`)** — entidades da campanha já existentes, buscadas por nome (e celular quando fizer sentido), com rótulo indicando o tipo (ex.: “Maria (Assessora)”). Usa o campo **`responsible` polimórfico multi-valor** do remodel (C90). +- Após salvar, o evento **aparece imediatamente** na agenda sem recarregar a página; falha → toast de erro e nada some. +- “Mais detalhes” leva ao formulário completo (`/atividades/nova`) já com o que foi digitado, sem perder o que já foi feito. +- Reusa componentes shadcn já existentes (popover, drawer/sheet, inputs) — não inventar primitiva paralela. +- Liderança não acessa a agenda (inalterado). + +## Dados (intenção) + +- **Vou apresentar dados?** Não. `Dados: N/A` — affordance de escrita sobre a agenda; não introduz métrica, série, ranking ou mapa. +- **Decisões desbloqueadas:** ator decide “que compromisso entra neste slot” **no momento do planejamento**, sem fila de navegação. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** + - `src/components/campaign/activity/ActivityAgenda.tsx` — `handleDateClick` hoje só monta `buildActivityCreateHref` e redireciona; vira abrir o popover/sheet com prefill. + - `src/utilities/activityUi.ts` — `buildActivityCreateHref` / `parseActivityCreatePrefill`: o prefill do slot já existe; a criação inline precisa de um caminho que **não navegue**. + - `src/app/(campaign)/campanha/actions/activity.ts` — `createActivity` já é uma action; criar inline via action que retorna o evento (sem `redirect`), e o calendário insere no estado local (mesma família do `rescheduleActivity`). + - `src/app/(campaign)/campanha/(app)/atividades/nova/page.tsx` + `ActivityForm.tsx` — forma atual (referência de campos/validação), reutilizada só no “Mais detalhes”. + - **Responsável:** hoje `responsible` é um `Contact` único (via `ContactCombobox`); `advisors` (hasMany `campaignUser`) e `leadership` (única) são campos separados. O seletor unificado do overlay precisa agregar opções de `leadership` + `stateDeputy` + staff `campaignUser` — precedentes: `searchActivityLeadershipOptions` (`activityLeadershipOptions`), `loadStateDeputyOptions` (`campaignRelationOptions`), `searchActivityContactOptions` (`contactSearchActions`). A direção de schema (`responsible` único vs lista) é questão em aberto de produto — não travada aqui (ver Rabbit holes / Questões). +- **Precedente a olhar:** `ActivityForm.tsx` (campos/validação), `rescheduleActivity`/`loadActivityAgendaEvents` (estado local do calendário), bottom drawer já usado em listas mobile (`CampaignListSheetHost` → `Drawer`), `criar-assessor-inline-popover-municipios` (B154 — criação inline em popover como precedente de pattern). +- **Risco de acoplamento:** estado local do FullCalendar (`events`/`reloadCount` em `ActivityAgenda`) — a criação inline deve seguir o mesmo padrão de otimismo/atualização do reschedule ou a célula pode dessincronizar; não quebrar o fluxo `redirect` do formulário completo. + +## Dependências + +- **Dura: C90 — remodelar `responsible` da Atividade ([#426](https://github.com/fsolla/teqo/issues/426))** (campo único polimórfico multi-valor; remove `advisors`/`leadership`). O seletor de responsáveis do overlay **consome o campo remodelado**. +- **Suaves:** `loadActivityAgendaEvents` (refetch da janela) — se a criação inline só insere localmente, revalidar a janela ao fechar. + +## Fora de escopo + +- Campos avançados inline (tarefas, organizações, demandas, liderança vinculada, assessores responsáveis) → formulário completo `/atividades/nova`. +- **Seletor de responsável não cadastra entidade nova** — só combina entidades já existentes (staff/candidato/liderança/dobradinha). Criar um `campaignUser` ou `leadership` na hora (padrão B154) **não** entra aqui, a menos que o produto peça explicitamente. +- Edição inline de evento existente (abrir continua indo ao detalhe). +- Recorrência, resources, sync Google (C16 já cobre import/sync). +- Mudanças no formulário completo de `/atividades/nova`. + +## Rabbit holes de produto + +- **“Vira o formulário inteiro no popover.”** Se alguém “só completar” campos, o popover vira uma segunda página de criação. **Corte neste item:** mínimo (título + município + horário + local) e um link “Mais detalhes”. +- **Responsável = vários campos.** O remodel (C90) unifica tudo num `responsible` polimórfico multi-valor; este item **não** soma campos — **Corte:** o overlay usa o campo remodelado (vários responsáveis de qualquer papel) e pronto. Se a mesa pedir algo além disso, vira novo item, não estica o popover. +- **“Criar a pessoa se não existir.”** B154 criou assessor inline na lista; aqui não. **Corte:** o seletor só escolhe dentre o que já existe; mensagem clara quando a busca não acha. +- **“Salvar e voltar pro detalhe.”** Navegar após salvar quebra o fluxo. **Corte neste item:** salvar fecha o overlay, evento aparece, usuário segue na agenda. +- **“Mostrar também nas células do mês.”** Mês é sprint de visão; criação no slot é o job. **Corte:** funcionar na semana e dia; no mês, clique no dia abre o popover com 09:00–10:00 (comportamento atual). + +## Questões em aberto (produto) + +- **Intervalo pré-preenchido:** **Opções:** A) usar o próprio intervalo do clique (meia-hora tipada no slot da semana) | B) fixo 1h a partir do horário do clique | C) 1h como hoje. **Recomendação: A** — é o que mais replica o Google Calendar; onde o intervalo do clique não dá (dia cheio do mês), usamos o comportamento atual (09:00–10:00). _(assumido — validar)_ +- **“Mais detalhes” preserva o rascunho?** **Opções:** A) prefere popular o form completo via URL (já existe) | B) mantém o texto do popover no `defaultValue`. **Recomendação: A** + preencher `title` no link (novo parâmetro). _(assumido — validar)_ +- **Após salvar, refetch ou inserção local?** **Opções (produto):** o evento deve **aparecer já** (inserção otimista/local) | ou **refetch da janela** (mais caro, mas “correto” com filtros). **Recomendação: refetch da janela** no fechamento — é o mesmo contrato dos filtros e evita estado duplicado; a sensação de “sem reload” vem de não navegar. _(decidir — se parecer lento no app, volta para inserção local)_ +- **Validação de município no popover:** **Opções:** A) combobox com opções do escopo do ator (já carregadas na página) | B) select nativo | C) em branco e erra ao salvar. **Recomendação: A** — combobox leve, com o município do filtro ativo como opção inicial se houver filtro. _(assumido — validar)_ +- **Seletor de responsáveis — decisão tomada no gate:** campo **`responsible` polimórfico multi-valor** (staff `campaignUser` nas funções assessor/candidato/coordenador, `leadership`, `stateDeputy`) — **remodel em C90**. Este item (C91) **consome** o campo remodelado no overlay; não decide schema. _Removido como questão em aberto — travada._ +- **Responsável obrigatório ou opcional no overlay?** **Opções:** A) opcional (como o form completo hoje) | B) obrigatório para criar rápido. **Recomendação: A** — obrigatório só `title` e `municipality`; responsável é preenchido na primeira chance, sem travar a criação. _(assumido — validar)_ + +## Referências + +- GitHub Issue: [#428](https://github.com/fsolla/teqo/issues/428) +- Canvas UI (gate): `/home/fsolla/.cursor/projects/home-fsolla-Code-teqo/canvases/plan-c91-ui-draft.canvas.tsx` +- `src/components/campaign/activity/ActivityAgenda.tsx` — `handleDateClick` (prefill do slot + redirect atual) +- `src/utilities/activityUi.ts` — `buildActivityCreateHref`, `parseActivityCreatePrefill` +- `src/app/(campaign)/campanha/actions/activity.ts` — `createActivity`, `rescheduleActivity`, `loadActivityAgendaEvents` +- `src/components/campaign/activity/ActivityForm.tsx` — campos e validação do form completo +- `docs/plans/criar-assessor-inline-popover-municipios.md` — precedente de criação inline em popover (B154) diff --git a/docs/plans/remodelar-responsaveis-atividade.md b/docs/plans/remodelar-responsaveis-atividade.md new file mode 100644 index 00000000..5b61f619 --- /dev/null +++ b/docs/plans/remodelar-responsaveis-atividade.md @@ -0,0 +1,90 @@ +# Remodelar responsáveis da Atividade — campo único polimórfico multi-valor + +Status: rascunho +Atualizado em: 2026-08-08 +Issue: #426 +Priority: P1 +Model: cursor-grok-4.5-medium +Model-local: deepseek-v4-high +Impeccable: B — muda o card "Pessoas e organizações" do form completo + detalhe (campos somem/somam) +Appetite: ~1–1,5 dia eng; um outcome verificável — um compromisso pode ter vários responsáveis de papéis distintos +Responsável: — + +## Intenção + +Hoje um compromisso tem **três campos separados**: `responsible` (um Contact avulso), `advisors` (assessores/campaignUser, vários) e `leadership` (uma liderança). Na mesa, o coordenador pensa em **“quem é responsável por este compromisso”** — e isso pode ser o candidato, um assessor, uma liderança ou uma dobradinha, **um ou vários ao mesmo tempo**. Três campos forçam a mesa a decidir arbitrariamente onde cada pessoa entra, e o candidato/coordenador não tem onde entrar (não são Contact nem cabem em assessor/liderança). Unificar num **campo único `responsible`, polimórfico (staff `campaignUser` nas funções assessor/candidato/coordenador, `leadership`, `stateDeputy`) e de múltiplos valores**, removendo `advisors` e `leadership`. + +## Persona e fluxo + +- **Persona / contexto:** coordenador (ou assessor com acesso) montando eventos na agenda ou no form completo; precisa registrar quem conduz, sem enquadrar pessoas erradas. +- **Job principal:** indicar **um ou mais responsáveis** de qualquer papel da campanha num único seletor. +- **Fluxo desejado:** form completo (ou o overlay da criação rápida na agenda) → seletor "Responsáveis" → escolhe vários (candidato, assessor, liderança, dobradinha) → salvar → detalhe mostra todos com seus tipos. +- **Anti-goals de produto:** não é segundo cadastro de pessoa (só escolhe entidades existentes); não é "responsável por município" já coberto por outra entidade; não introduz papel novo nem altera quem pode entrar. + +### Esboço de fluxo (B) + +```text +[form completo de atividade] → "Responsáveis" → busca por nome → marca 1+ de + qualquer papel (candidato/assessor/liderança/dobradinha) → salvar → detalhe lista +``` + +## Objetivo e aceite + +- A Atividade passa a ter **um campo `responsible`** que aceita **vários** valores e pode referenciar **pessoas de qualquer papel**: assessor, candidato ou coordenador (staff `campaignUser`), liderança (`leadership`), dobradinha (`stateDeputy`). +- Os campos `advisors` e `leadership` **saem do modelo e da UI** (form completo, detalhe, agenda); a informação que morava neles migra para `responsible`. +- Assessor continua podendo **ver/editar** os compromissos em que está listado como responsável (mesmo direito que `advisors` lhe dava, agora via `responsible`). +- Acesso de criação/edição segue as regras atuais (staff cria; assessor edita o que administra + o que lhe é imputado). +- `responsible` opcional no form completo (como hoje); obrigatório só título/município. +- Não cadastra entidade nova no seletor (sem padrão B154 aqui). + +## Dados (intenção) + +- **Vou apresentar dados?** Não. `Dados: N/A` — remodel de escrita/relacionamento; não introduz métrica, série, ranking nem mapa. +- **Decisões desbloqueadas:** coordenador define quem conduz o compromisso **sem enquadrar papel em campo errado**, com múltiplos responsáveis quando fizer sentido. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** + - `src/collections/Activity.ts` — campo `responsible` passa a relationship **polimórfica e hasMany** (`relationTo` staff/leadership/stateDeputy); remove `advisors` e `leadership`; hooks `validateActivityAdvisors`, `deriveActivityFields` (auto-inclusão de assessor), `validateDeputyPresentTimeGate` re-expressos sobre `responsible`. + - `src/utilities/access/activities.ts` — `canReadActivity` usa `advisors contains id` como escopo do assessor; passa a usar `responsible` (player id) + escopo de município. + - `src/utilities/activityViewModels.ts`, `src/utilities/activityFormData.ts`, `src/app/(campaign)/campanha/actions/activity.ts` — tipos de input/view e parse do formulário acompanham a mudança. + - `src/components/campaign/activity/ActivityForm.tsx` — card "Pessoas e organizações": um seletor multi-polimórfico `Responsáveis`; some o grupo de assessores e o combobox de liderança. + - `src/app/(campaign)/campanha/(app)/atividades/[slug]/ActivityOverviewTab.tsx` — detalhe lista os responsáveis com tipo. + - Busca: agregar opções de `leadership` + `stateDeputy` + staff `campaignUser` (precedentes: `searchActivityLeadershipOptions`, `loadStateDeputyOptions`, `searchActivityContactOptions`). +- **Precedente a olhar:** `criar-assessor-inline-popover` (B154), `remodelar-atividades-para-agenda` (C14), e2e `campaignActivity.e2e.spec.ts`. +- **Risco de acoplamento:** mudança de **schema** (migration) + **access** do assessor + **form completo** + **detalhe** + **e2e** — item serializado, sem paralelismo de agents; é pré-requisito do item "criar evento inline na agenda" (que consome o novo seletor). + +## Dependências + +- **Nenhuma dura** para si. Desbloqueia o item "Criar evento inline na agenda" (que depende deste). +- Chess com qualquer outra change que toque `Activity`/migrations (serialização). + +## Fora de escopo + +- Criar entidade nova no seletor (B154 fica para outro item, se o produto pedir). +- Mudar o acesso/regras do `deputyPresent` time gate (só re-expressar em `responsible`). +- Tarefas (`tasks[].responsible`) mantêm o próprio `responsible` de Contact — não coberto aqui. +- Rewrite do form completo além do card "Pessoas e organizações". + +## Rabbit holes de produto + +- **“Vira segundo cadastro de pessoa.”** Se a busca não achar, a tentação é criar inline (B154). **Corte:** só escolher entre existentes; sem criação no seletor. +- **“Polimorfismo é problema só de schema; produto já decidiu.”** Sim — produto decidiu (C). Este plano só garante que o **acesso do assessor** não quebre e que o **detalhe** mostre tipos. +- **“Vários de tudo.”** Multi-valor não significa "misturar assessores de outros municípios fora do escopo". **Corte:** a escolha respeta o escopo de leitura do ator (não busca quem o ator não pode ver). + +## Questões em aberto (produto) + +- **Exibição no detalhe/card da agenda:** **Opções:** A) lista "N resp do tipo X" colapsada com tooltip | B) chips individuais por responsável | C) contar só "Resp: N". **Recomendação: B** — chips com tipo, mesmo padrão do card de hoje ("Resp: nome") mas agora pode ser vários. _(assumido — validar)_ +- **Ordem/relação com `tasks[].responsible`:** campo de tarefa (Contact) fica como está? **Recomendação: sim** — tarefa tem responsável próprio de execução; não acoplar. _(assumido — validar)_ + +## Decisões travadas (gate 2026-08-08) + +- **(C)** Campo `responsible` **polimórfico multi-valor**: staff `campaignUser` (roles assessor/candidato/coordenador), `leadership`, `stateDeputy`. +- **Remover** `advisors` e `leadership` do modelo/form/detalhe; informação migra para `responsible`. +- Acesso do assessor via `responsible` (mantém o direito que `advisors` dava). + +## Referências + +- GitHub Issue: [#426](https://github.com/fsolla/teqo/issues/426) +- `src/collections/Activity.ts`, `src/utilities/access/activities.ts`, `src/components/campaign/activity/ActivityForm.tsx` +- Deps: item "Criar evento inline na agenda" ([#428](https://github.com/fsolla/teqo/issues/428), C91) consome este seletor.