mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Merge pull request #687 from fsolla/C124-atividades-tarefa-so-com-texto-responsavel-e-concluida-
feat(C124): tarefa de atividade só com texto, responsável e concluída (sem prazo)
This commit is contained in:
commit
e68e4f6eda
12 changed files with 13597 additions and 51 deletions
|
|
@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-11):** **C124** (Issue #668) — tarefa de atividade fica só com **texto, responsável e concluída**: o campo `due` (Prazo, datetime) sai do modelo e da UI. Remoção em cadeia num único commit simétrico: campo do array `tasks` da `activity` (o guardrail de liderança perde a comparação de `due` no mesmo item — continua fail-closed sobre título/responsável/contagem), `taskSchema` zod, `parseTasksFormData`/`ParsedActivityTask` (formData), `ActivityFormTaskViewModel`/`ActivityTaskViewModel`, input `datetime-local` do `ActivityTaskFields` (grid `2fr_1fr`) e linha "Prazo: …" do `ActivityTaskChecklist` (subtítulo condicional + `break-words` no título — P2 dos reviewers). `doneAt` ("concluída em") permanece como derivado invisível; `taskTotal`/`taskDoneCount` intocados. **Migration** `20260811_183016_drop_activity_task_due` (gerada; diff do gerador tinha no-op de `google_calendar_sync` por snapshot stale — limpa à mão para só o `DROP COLUMN "due"` de `activity_tasks`; dados descartados conforme decidido no gate). Grep pós: zero `due` fora das migrations (histórico frozen); nenhum consumidor fora da vertical (notificações, Google sync, quick actions). Gates: unit+int 736/736, e2e atividade 9/9 (1 flake ECONNRESET no run paralelo — rerun limpo), build OK. Intenção [`atividades-tarefas-sem-prazo.md`](plans/atividades-tarefas-sem-prazo.md); impl [`atividades-tarefas-sem-prazo-impl.md`](plans/atividades-tarefas-sem-prazo-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-11):** **B197+** (Issue #642) — o menu do picker de colunas de assessores nasce da **mesma fonte** que os `<th>` do `AdvisorsTable` (fim do espelho): o módulo client declara `advisorTableColumns` (id/label/mandatory + `headerClassName` + `visuallyHiddenLabel` para o `sr-only` de Ações) e exporta `advisorPickerColumns = toCampaignColumnPickerColumns(...)` (precedente `supporterPickerColumns`); o header renderiza por `resolveVisibleColumns(advisorTableColumns, hiddenColumnIds)` — mesmo helper B17 das outras listas — e `emailHidden` deriva do mesmo `visibleColumns`. **Descoberta do /simplify incorporada:** o corpo só condiciona a coluna `email` (células à mão + exceção do draft), então `phone`/`municipalities`/`actions` são `mandatory: true` — esconder um deles (cookie velho do toggle inerte do B197) derrubaria o `<th>` sobre um corpo de 5 células; os checkboxes viram "sempre visível" (honestos). `assessores/page.tsx` consome a fonte exportada e perdeu o array espelho + imports órfãos. Comportamento da tabela idêntico (ids/labels/ordem, exceção do draft intacta); e2e `campaignColumnPicker` ganhou pin dos mandatory. Sem migration/access/Consent/UI/cache. Observações de ambiente (não da mudança): o e2e do picker falha no harness dev sob carga desta máquina (painel do popover não monta; falha idêntica na base e em lideranças — intocado; conjunto falho deriva com a carga; verificado funcional em Chromium real); warning React de key pré-existente no assessores (`CampaignListOmnibox` — base também). Intenção [`escala-dry-pos-b197.md`](plans/escala-dry-pos-b197.md); impl [`escala-dry-pos-b197-impl.md`](plans/escala-dry-pos-b197-impl.md).
|
||||
**Recently resolved (2026-08-11):** **C116** (Issue #655) — `/campanha/pessoas` desktop vira **edit-where-you-see**: toda célula (exceto Ações) é um input permanente sem destaque — salva no blur/Enter, Esc descarta, vazio mostra "—" apagado. **Nome** mantém o valor como link para a ficha (`/campanha/liderancas/[id]`) com o mecanismo travado no gate: input invisível (`text-transparent`, z-0) sobre o link (z-10, `pointer-events-auto`, `w-auto` — cobre só o texto; o resto da célula é a área clicável do input), draft renderizado **no texto do link** (o valor real do input nunca é o display), clique no link navega, teclado/área fora do texto edita. **Células de municípios** (Assessora, Lidera, Aliada em) e **Assessorado** usam chips internos com: grupo novo **"Salvador (19)"** (`buildMunicipalityPortfolioChips` ganha kind `city`, checado antes do território — senão as 19 ZEs colapsariam como "Metropolitano de Salvador"), **chip batch clicável expande in loco** (estado de apresentação do wrapper `PeopleMunicipalityCell` — clique expande, mouse-leave colapsa se todos os membros seguem atribuídos; X num membro remove e impede o recolapso), **"+X"** no toggle de overflow do B170, `quiet` (sem tint de hover/foco; indicador de foco discreto só no input), commit por **match exato** no Enter/espaço (digitar "salvador-ze-2"+Enter adiciona a ZE certa, não a pre-selecionada — regra da intenção). Escrita: `updatePersonContactRecord` (escopo "edita o que vê" — assessor só com entidade da pessoa na carteira; `contactFieldUpdateSchema` ganhou variante `city`, convivendo com `phones` do C112), `setPersonAssessoraMembershipRecord` (unrestricted-only, single-account — multi-conta nega com mensagem segura e célula read-only; toggle extraído para `toggleMunicipalityAdvisorMembership` em `municipality.ts`, reusado pelo record B27) e `setPersonAdvisorMembershipRecord` (union person-centric: delta de assessor nas DUAS entidades quando ambas existem). Matriz por papel na página: assessor edita Lidera/Aliada com `addableIds`=carteira e **read-only** em Assessora/Assessorado (regras das collections donas); tooltips de label (WhatsApp/Convidar/Apagar) via `CampaignHoverTooltip`; `CampaignListSheetProvider` na tabela (guard miss #52 — wrapper de composição registrado no `sharedSheetHostConventions`). Mobile intacto. Sem migration. Testes: unit (grupo Salvador + expand + match exato; guard do sheet host), int novo `campaignPersonCellEditing` (11 — escopo por papel, assessora single/multi/sem-conta, union nas duas entidades), e2e `campaignPeople` 4/4. Intenção [`pessoas-edicao-inplace-lista.md`](plans/pessoas-edicao-inplace-lista.md); impl [`pessoas-edicao-inplace-lista-impl.md`](plans/pessoas-edicao-inplace-lista-impl.md).
|
||||
|
||||
|
|
|
|||
73
docs/plans/atividades-tarefas-sem-prazo-impl.md
Normal file
73
docs/plans/atividades-tarefas-sem-prazo-impl.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Impl: C124 — Atividades: tarefa só com texto, responsável e concluída (sem data/hora)
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-08-11
|
||||
Issue: #668
|
||||
Intenção: docs/plans/atividades-tarefas-sem-prazo.md
|
||||
Appetite restante: herdado (~0,5–1 dia eng; mudança é remoção pura, sem migração de dados)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** tarefa de atividade passa a ter exatamente três controles — título (texto), responsável (pessoa) e "concluída" (checkbox); o prazo (data/hora) sai do schema, da UI e do guardrail de liderança; `doneAt` permanece.
|
||||
- **O que NÃO negociar:** guardrail de liderança fail-closed (só marca concluída, comparação perde `due`); dados existentes com prazo são **descartados** (drop da coluna, decidido no gate); anti-goal: prazo não migra para outro campo.
|
||||
- **O que reavaliar:** a hipótese de direção da intenção estava completa — confirmei por grep que `due` de tarefa só existe em 6 arquivos (collections, zod, formData, viewModels, 2 componentes) + payload-types gerado + migration antiga (frozen). Nenhum consumidor fora da vertical de atividades (notificações, Google sync, activityUi, quick actions: nenhum lê `task.due`).
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Activity.ts<br/>campo due sai do array tasks<br/>guardrail líder perde comparação due] --> M[migration:<br/>DROP COLUMN due<br/>em activity_tasks]
|
||||
C[lib/schemas/activity.ts<br/>taskSchema: due sai] --> D[activityFormData.ts<br/>ParsedActivityTask + parsing due saem]
|
||||
D --> E[activityViewModels.ts<br/>due sai dos dois view models]
|
||||
E --> F[ActivityTaskFields.tsx<br/>input datetime sai<br/>grid 2fr_1fr]
|
||||
E --> G[ActivityTaskChecklist.tsx<br/>linha Prazo sai]
|
||||
M --> T[generate:types<br/>payload-types refresca]
|
||||
T --> G2[Gates: tsc lint format knip cycles unit int e2e build]
|
||||
```
|
||||
|
||||
**Opções consideradas:** A) remoção completa (campo + coluna + UI) · B) manter coluna morta (`due` nullable, nunca escrita) · C) manter campo no schema, só esconder da UI
|
||||
**Recomendação:** A — a intenção e o gate decidiram "descartar e remover"; remover na origem elimina a coluna fantasma, o peso no guardrail e a tentação de re-exibir. Zero risco de perda: nenhum consumidor lê o valor (verificado por grep em `src/` e `tests/`), e dado de campanha interno sem valor operacional.
|
||||
**Rejeitadas:** B (anti-goal "limpeza real, sem coluna fantasma" — matéria do gate) · C (mantém API e guardrail poluídos com campo morto; anti-goal: campo sai do modelo, não só da UI).
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`Activity`** (`src/collections/Activity.ts`): remove o field `due` do array `tasks` (linhas 575–583); remove `(taskRecord.due ?? null) !== (previousTask.due ?? null)` do guardrail de liderança em `deriveActivityFields` (linha 361) — mesmo item, senão a comparação lê propriedade inexistente e o fail-closed perde a cobertura. `doneAt`/`taskTotal`/`taskDoneCount` intocados.
|
||||
- **`src/lib/schemas/activity.ts`**: `taskSchema` perde `due: z.string().datetime().optional().nullable()` (linha 74). Types `ActivityCreateInput`/`ActivityUpdateInput` derivam de zod — atualizam sozinhos.
|
||||
- **`src/utilities/activityFormData.ts`**: `ParsedActivityTask` perde `due?: string`; `parseTasksFormData` perde o parsing/validação de prazo (linhas 81–85) e a propagação `...(due ? { due } : {})` (linha 91). O resto do parse fica intacto.
|
||||
- **`src/utilities/activityViewModels.ts`**: `ActivityFormTaskViewModel` e `ActivityTaskViewModel` perdem `due` (linhas 244, 346) e os mapeamentos (linhas 294, 395). `doneAt` permanece no checklist VM.
|
||||
- **`ActivityTaskFields.tsx`**: `ActivityTaskFieldValue` e `initialTasks` perdem `due`; `serializeTasks` deixa de emitir `due`; estado inicial e `addTask` perdem o default; o input `datetime-local` do prazo sai; grid `sm:grid-cols-[2fr_1fr_1fr]` → `sm:grid-cols-[2fr_1fr]`; import `formatIsoAsBahiaDateTimeInput` sai.
|
||||
- **`ActivityTaskChecklist.tsx`**: a entrada `Prazo: …` do subtítulo sai (linha 63); import `formatBahiaDateTimeLabel` sai (o helper continua usado em outras superfícies — ActivityCard/agenda — knip valida).
|
||||
- **Migration:** `pnpm migrate:create drop_activity_task_due` — gera `ALTER TABLE "activity_tasks" DROP COLUMN IF EXISTS "due";` (+ down). A coluna é nullable e sem índice próprio, então o drop é barato. Aplica em prod via build (Vercel) — revisar o SQL gerado antes.
|
||||
- **Access / Consent:** nenhuma mudança — mesmo access, mesma coleção, sem Consent.
|
||||
- **UI:** Impeccable B — encaixe nas superfícies existentes; remoção pura, sem shape novo.
|
||||
|
||||
### Dados → forma (se aplicável)
|
||||
|
||||
- Não se aplica — sem dado novo para apresentar; o contador `taskDoneCount` segue exatamente como está.
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Schema + server** — `Activity.ts` (campo + guardrail), `taskSchema` zod, `parseTasksFormData`. Verificar: `tsc --noEmit` + int spec `campaignActivity.int.spec.ts` (cobre o guardrail de liderança com `tasks` sem `due`).
|
||||
2. **UI + view models** — `activityViewModels.ts`, `ActivityTaskFields.tsx`, `ActivityTaskChecklist.tsx`. Verificar: `tsc`, `lint`, unit specs de UI (activity\*) e `knip` (orfandade de helpers).
|
||||
3. **Migration + tipos** — `pnpm migrate:create drop_activity_task_due`, revisar SQL, `pnpm migrate` local (teqo_wt<slot>), `pnpm generate:types`. Verificar: `migrate:status` nas duas DBs (dev + test).
|
||||
4. **Gates** — `tsc --noEmit`, `lint` (0 warnings), `format:check`, `knip`, `check:cycles`, `pnpm test`, `pnpm test:e2e` (specs de atividade: `campaignActivity`, `campaignAgenda*`), `pnpm build` contra DB local.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- **Migração de conteúdo (preservar `due` em outra coluna):** corte — nada consome; drop limpo.
|
||||
- **Prazo em demandas/atividades em geral:** fora de escopo (não criar equivalente).
|
||||
- **Tocar `doneAt`/`taskDoneCount`:** fora de escopo.
|
||||
- **C123 (overlay da agenda):** roda depois, nasce já com o modelo simplificado — nada a preparar aqui além do modelo limpo.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Guardrail de liderança quebrado ao mudar o shape:** mitigado — a comparação de `due` é removida no mesmo commit do field; o int spec `denies leaders from updating activities` e o de toggle de tarefa da liderança continuam exercitando o caminho fail-closed.
|
||||
- **Migração destrutiva em prod (via build):** coluna nullable, dados já sem consumidor (grep `src/`+`tests/`); SQL revisado no passo 3 antes de qualquer deploy.
|
||||
- **`payload-types.ts` desatualizado no meio do caminho:** `due` some do tipo gerado no passo 3 (`generate:types`) — até lá `tsc` pega qualquer uso remanescente.
|
||||
- **Serialização client × parse server fora de sincronia:** `serializeTasks` para de emitir `due` e o parse para de ler — mudança simétrica num único commit; nenhum cliente antigo persiste (formas stateless).
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [ ] Aceite de produto da intenção ainda coberto: 3 controles por tarefa; checklist sem "Prazo: …"; schema sem prazo; dados descartados; guardrail de liderança intacto; `doneAt` permanece
|
||||
- [ ] Invariantes AGENTS/engineering-standards: sem toque em access/Consent; transações intactas; copy pt-BR; identificadores em inglês
|
||||
- [ ] Testes de domínio previstos: int spec existente do guardrail continua verde (sem novo teste — remoção pura, superfície coberta)
|
||||
|
|
@ -357,8 +357,7 @@ const deriveActivityFields: CollectionBeforeChangeHook = ({
|
|||
const taskRecord = task as Record<string, unknown>
|
||||
if (
|
||||
trimmedText(taskRecord.title) !== trimmedText(previousTask.title) ||
|
||||
relationshipId(taskRecord.responsible) !== relationshipId(previousTask.responsible) ||
|
||||
(taskRecord.due ?? null) !== (previousTask.due ?? null)
|
||||
relationshipId(taskRecord.responsible) !== relationshipId(previousTask.responsible)
|
||||
) {
|
||||
throw new APIError('Lideranças só podem marcar tarefas como concluídas.', 403)
|
||||
}
|
||||
|
|
@ -571,16 +570,6 @@ export const Activity: CollectionConfig = {
|
|||
relationTo: 'contact',
|
||||
label: 'Responsável',
|
||||
},
|
||||
{
|
||||
name: 'due',
|
||||
type: 'date',
|
||||
label: 'Prazo',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'done',
|
||||
type: 'checkbox',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { toast } from 'sonner'
|
|||
|
||||
import { toggleActivityTaskAction } from '@/app/(campaign)/campanha/(app)/atividades/[slug]/taskActions'
|
||||
import { Checkbox } from '@/components/ui/Checkbox'
|
||||
import { formatBahiaDateTimeLabel } from '@/lib/campaignTime'
|
||||
import type { ActivityTaskViewModel } from '@/utilities/activityViewModels'
|
||||
|
||||
type ActivityTaskChecklistProps = {
|
||||
|
|
@ -53,18 +52,15 @@ export const ActivityTaskChecklist = ({ activityId, tasks }: ActivityTaskCheckli
|
|||
aria-label={`Marcar tarefa "${task.title}" como ${task.done ? 'pendente' : 'concluída'}`}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-0.5">
|
||||
<span className={task.done ? 'line-through text-muted-foreground' : undefined}>
|
||||
<div className="min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span
|
||||
className={`break-words ${task.done ? 'line-through text-muted-foreground' : ''}`}
|
||||
>
|
||||
{task.title}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{[
|
||||
task.responsibleName ? `Resp: ${task.responsibleName}` : null,
|
||||
task.due ? `Prazo: ${formatBahiaDateTimeLabel(task.due)}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
</span>
|
||||
{task.responsibleName ? (
|
||||
<span className="text-xs text-muted-foreground">Resp: {task.responsibleName}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,11 @@ import {
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { formatIsoAsBahiaDateTimeInput } from '@/lib/campaignTime'
|
||||
|
||||
export type ActivityTaskFieldValue = {
|
||||
key: string
|
||||
title: string
|
||||
responsible: ContactComboboxOption | null
|
||||
due: string
|
||||
done: boolean
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +23,6 @@ type ActivityTaskFieldsProps = {
|
|||
id: string | null
|
||||
title: string
|
||||
responsible: ContactComboboxOption | null
|
||||
due: string | null
|
||||
done: boolean
|
||||
}>
|
||||
searchContacts: (query: string) => Promise<ContactComboboxOption[]>
|
||||
|
|
@ -45,7 +42,6 @@ const serializeTasks = (tasks: ActivityTaskFieldValue[]) =>
|
|||
.map((task) => ({
|
||||
title: task.title.trim(),
|
||||
...(task.responsible ? { responsible: task.responsible.id } : {}),
|
||||
...(task.due ? { due: task.due } : {}),
|
||||
done: task.done,
|
||||
})),
|
||||
)
|
||||
|
|
@ -61,7 +57,6 @@ export const ActivityTaskFields = ({
|
|||
key: task.id ?? nextTaskKey(),
|
||||
title: task.title,
|
||||
responsible: task.responsible,
|
||||
due: task.due ? formatIsoAsBahiaDateTimeInput(task.due) : '',
|
||||
done: task.done,
|
||||
})),
|
||||
)
|
||||
|
|
@ -73,7 +68,7 @@ export const ActivityTaskFields = ({
|
|||
const addTask = () => {
|
||||
setTasks((current) => [
|
||||
...current,
|
||||
{ key: nextTaskKey(), title: '', responsible: null, due: '', done: false },
|
||||
{ key: nextTaskKey(), title: '', responsible: null, done: false },
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +86,7 @@ export const ActivityTaskFields = ({
|
|||
key={task.key}
|
||||
className="flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-start"
|
||||
>
|
||||
<div className="grid flex-1 gap-3 sm:grid-cols-[2fr_1fr_1fr]">
|
||||
<div className="grid flex-1 gap-3 sm:grid-cols-[2fr_1fr]">
|
||||
<Input
|
||||
aria-label={`Título da tarefa ${index + 1}`}
|
||||
placeholder="Título da tarefa"
|
||||
|
|
@ -106,13 +101,6 @@ export const ActivityTaskFields = ({
|
|||
search={searchContacts}
|
||||
onChange={(contact) => updateTask(task.key, { responsible: contact })}
|
||||
/>
|
||||
<Input
|
||||
aria-label={`Prazo da tarefa ${index + 1}`}
|
||||
type="datetime-local"
|
||||
value={task.due}
|
||||
className="min-h-11"
|
||||
onChange={(event) => updateTask(task.key, { due: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ export type ActivityDemandDraft = z.input<typeof activityDemandDraftSchema>
|
|||
const taskSchema = z.object({
|
||||
title: z.string().trim().min(1).max(200),
|
||||
responsible: positiveRelationshipId.optional(),
|
||||
due: z.string().datetime().optional().nullable(),
|
||||
done: z.boolean().optional(),
|
||||
})
|
||||
|
||||
|
|
|
|||
13495
src/migrations/20260811_183016_drop_activity_task_due.json
Normal file
13495
src/migrations/20260811_183016_drop_activity_task_due.json
Normal file
File diff suppressed because it is too large
Load diff
11
src/migrations/20260811_183016_drop_activity_task_due.ts
Normal file
11
src/migrations/20260811_183016_drop_activity_task_due.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "activity_tasks" DROP COLUMN "due";`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "activity_tasks" ADD COLUMN "due" timestamp(3) with time zone;`)
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ import * as migration_20260810_010844_add_activity_all_day from './20260810_0108
|
|||
import * as migration_20260811_111828_add_contact_phones_array from './20260811_111828_add_contact_phones_array';
|
||||
import * as migration_20260811_133332_add_google_calendar_sync from './20260811_133332_add_google_calendar_sync';
|
||||
import * as migration_20260811_155116_add_lembranca_support_status from './20260811_155116_add_lembranca_support_status';
|
||||
import * as migration_20260811_183016_drop_activity_task_due from './20260811_183016_drop_activity_task_due';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -312,4 +313,9 @@ export const migrations = [
|
|||
down: migration_20260811_155116_add_lembranca_support_status.down,
|
||||
name: '20260811_155116_add_lembranca_support_status',
|
||||
},
|
||||
{
|
||||
up: migration_20260811_183016_drop_activity_task_due.up,
|
||||
down: migration_20260811_183016_drop_activity_task_due.down,
|
||||
name: '20260811_183016_drop_activity_task_due'
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -780,7 +780,6 @@ export interface Activity {
|
|||
| {
|
||||
title: string;
|
||||
responsible?: (number | null) | Contact;
|
||||
due?: string | null;
|
||||
done?: boolean | null;
|
||||
doneAt?: string | null;
|
||||
id?: string | null;
|
||||
|
|
@ -1847,7 +1846,6 @@ export interface ActivitySelect<T extends boolean = true> {
|
|||
| {
|
||||
title?: T;
|
||||
responsible?: T;
|
||||
due?: T;
|
||||
done?: T;
|
||||
doneAt?: T;
|
||||
id?: T;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import { MAX_TOUR_NAME_LENGTH, TOUR_EMPTY_MESSAGE } from '@/utilities/visit/visi
|
|||
type ParsedActivityTask = {
|
||||
title: string
|
||||
responsible?: number
|
||||
due?: string
|
||||
done?: boolean
|
||||
}
|
||||
|
||||
|
|
@ -78,17 +77,11 @@ const parseTasksFormData = (formData: FormData): ParsedActivityTask[] => {
|
|||
record.responsible > 0
|
||||
? record.responsible
|
||||
: undefined
|
||||
const dueRaw = typeof record.due === 'string' ? record.due : undefined
|
||||
const due = dueRaw ? (parseBahiaDateTimeInput(dueRaw) ?? undefined) : undefined
|
||||
if (dueRaw && !due) {
|
||||
throw new FormDataBoundaryError('tasksJson', `Prazo inválido na tarefa ${index + 1}.`)
|
||||
}
|
||||
const done = typeof record.done === 'boolean' ? record.done : undefined
|
||||
|
||||
return {
|
||||
title,
|
||||
...(responsible ? { responsible } : {}),
|
||||
...(due ? { due } : {}),
|
||||
...(done !== undefined ? { done } : {}),
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -241,7 +241,6 @@ type ActivityFormTaskViewModel = {
|
|||
id: string | null
|
||||
title: string
|
||||
responsible: { id: number; name: string; phone: string | null } | null
|
||||
due: string | null
|
||||
done: boolean
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +290,6 @@ export const toActivityFormViewModel = (activity: Activity): ActivityFormViewMod
|
|||
phone: primaryPhoneOf(task.responsible.phones),
|
||||
}
|
||||
: null,
|
||||
due: task.due ?? null,
|
||||
done: Boolean(task.done),
|
||||
})),
|
||||
})
|
||||
|
|
@ -343,7 +341,6 @@ export type ActivityTaskViewModel = {
|
|||
title: string
|
||||
responsibleId: number | null
|
||||
responsibleName: string | null
|
||||
due: string | null
|
||||
done: boolean
|
||||
doneAt: string | null
|
||||
}
|
||||
|
|
@ -392,7 +389,6 @@ const mapActivityTasks = (activity: Activity): ActivityTaskViewModel[] =>
|
|||
title: task.title,
|
||||
responsibleId: relationshipId(task.responsible),
|
||||
responsibleName: relationshipName(task.responsible),
|
||||
due: task.due ?? null,
|
||||
done: Boolean(task.done),
|
||||
doneAt: task.doneAt ?? null,
|
||||
}))
|
||||
|
|
|
|||
Loading…
Reference in a new issue