Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
"Bash(where jq *)",
"Bash(powershell -NonInteractive -Command ' *)",
"Bash(powershell -NonInteractive -Command \"Get-Content '.claude/settings.json' | ConvertFrom-Json | ConvertTo-Json -Depth 10\")",
"Bash(node -e \"const rw = require\\('react-window'\\); console.log\\(Object.keys\\(rw\\)\\)\")"
"Bash(node -e \"const rw = require\\('react-window'\\); console.log\\(Object.keys\\(rw\\)\\)\")",
"Bash(git log *)",
"Bash(Get-Content .env.example)"
]
},
"hooks": {
Expand Down
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ npm run lint # ESLint

## Modelo de Dados

**Task:** `id, title, clickupLink?, assignee, status ('backlog'|'em andamento'|'bloqueado'|'concluído'), phases {design,approval,dev,qa} cada com {start,end} YYYY-MM-DD, createdAt`
**Task:** `id, title, clickupLink?, status {blocked, blockedAt?}, subtasks: Subtask[], createdAt, concludedAt?, concludedBy?, clientId?`

**Subtask:** `id, title (livre, obrigatório), status (SubtaskStatus), start (YYYY-MM-DD), end (YYYY-MM-DD), assignees (member ids), active, order` — substitui o antigo `Step`. Tabela: `task_subtasks` + `subtask_assignees`.

**SubtaskStatus (enum):** `analise-ux` | `analise-dev` | `design` | `aprovacao-design` | `desenvolvimento` | `homologacao` | `qa` | `publicacao` — controla a cor/ícone da subtask nas views; o título é livre.

**Member:** `id, name, role ('Designer'|'Developer'), avatar (iniciais), avatar_url?, email?, auth_user_id?, access_role ('admin'|'user'), is_active?, created_at?, deactivated_at?`

**UserPreferences:** `id, user_id (fk → members.id), theme ('light'|'dark'|'system'), language ('pt-BR'|'en'), notifications_enabled, default_view ('home'|'calendar'|'timeline'|'list'), client_order (text[]), notification_step_overdue (bool, def true), notification_task_stalled (bool, def true), notification_member_overloaded (bool, def true), stalled_days_threshold (int 1–30, def 5), overload_threshold (int 1–20, def 3), created_at, updated_at`

**Fases:** Design (5d, violeta) → Approval (3d, laranja) → Dev (7d, azul) → QA (3d, esmeralda). Cascata automática.

## Branding

- **Nome:** Run/Way
Expand Down
44 changes: 35 additions & 9 deletions docs/components/task-modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,44 @@

**Ficheiro:** `src/components/TaskModal.tsx`

## Cascata de Fases
Ao alterar o **início** de uma fase, a duração é preservada e fases seguintes avançam. Ao alterar o **fim**, fases seguintes arrastam (cascata bidirecional).
## Responsabilidade

### Restrições (garantidas após cada alteração)
- `approval.start` >= `nextBusinessDay(design.end)`
- `dev.start` >= `nextBusinessDay(approval.end)`
- `qa.start` >= `nextBusinessDay(dev.end)`
Modal de criação e edição de demandas. Gere o formulário de subtasks de forma dinâmica (N subtasks livres, sem lista fixa de 8 tipos).

## Subtasks

Cada subtask tem:
- **title** (obrigatório) — nome livre dado pelo usuário
- **status** (`SubtaskStatus`) — controla cor/ícone; dropdown com os 8 valores de `STEP_TYPES_ORDER`
- **progressStatus** (`SubtaskProgressStatus`) — andamento operacional: A fazer, Pronta, Em andamento, Em revisão, Aguardando, Bloqueada, Precisa de ajustes, Pausada, Concluída ou Cancelada
- **start / end** — datas obrigatórias quando subtask existe
- **assignees** — opcional; toggle por membro
- Botão de remover por subtask
- Botão "+ Adicionar subtask" no rodapé da lista

Nova demanda começa **sem subtasks**. O usuário adiciona livremente.

## Validação

- `title`: mín. 3 caracteres
- `clickupLink`: opcional, deve começar com `http`
- `clickupLink`: opcional, deve ser URL válida (`https://...`)
- Pelo menos 1 subtask com `title` preenchido e datas `start`/`end` válidas (`end >= start`)

## Confirmação de fim de semana / feriado

Se alguma subtask tiver datas em fim de semana ou feriado, exibe `ConfirmModal` com 3 opções:
- Salvar mesmo assim
- Prolongar para próximo dia útil (via `nextNonHolidayBusinessDay`)
- Cancelar

## Lógica de estado

O estado interno usa `SubtaskDraft` (Subtask + `_tempId` para identificação local antes de persistir). Subtasks novas têm `id: ''`; subtasks existentes mantêm o `id` do banco — o `updateTask` do CRUD diferencia novas (INSERT) de existentes (UPDATE/DELETE) por este campo.

## Dirty detection / submit

`useFormState` rastreia `isDirty` comparando snapshot inicial vs estado atual. Botão "Salvar" desabilitado enquanto `!isDirty || submitting`.

## Apagar Demanda
- Botão "Apagar" no footer (só visível ao editar)
- Confirmação via `confirm()` antes de eliminar

Botão "Apagar" no footer (só visível ao editar). Delega para prop `onDelete`.
26 changes: 26 additions & 0 deletions docs/components/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,30 @@ Wrapper sobre `<label>` com `text-sm font-medium text-slate-700` + `peer-disable
## Badge
Variants: `default` (cinza), `design` (azul), `approval` (amarelo), `dev` (roxo), `qa` (verde).

## Tabs
Wrapper sobre Radix `TabsPrimitive`. Exports: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`.

`TabsList` e `TabsTrigger` aceitam prop `variant`:
- `default` — fundo `bg-muted`, trigger com sombra quando ativo
- `underline` — borda inferior; trigger com `border-b-2` + hover suave no inativo
- `pills` — trigger com `bg-primary` quando ativo

`TabsTrigger` sempre tem `cursor-pointer` na base.

## ViewTabs
Componente leve **sem Radix** para grupos de botões estilo tab (`src/components/ui/ViewTabs.tsx`).

```tsx
import { ViewTabs, type ViewTab } from '@/components/ui/ViewTabs'

const TABS: readonly ViewTab<string>[] = [
{ value: 'a', label: 'A' },
{ value: 'b', label: 'B' },
]

<ViewTabs tabs={TABS} value={active} onChange={setActive} />
```

Usar quando o conteúdo a alternar **não precisa** de acessibilidade de tabs do Radix (ex: filtros rápidos inline). Para navegação de views principais, preferir `Tabs` + Radix.

**Regra:** Não criar novos componentes UI para uso único — usar Tailwind diretamente.
27 changes: 27 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,30 @@ Registro de decisões arquiteturais significativas do projeto Run/Way.
**Decisão:** a `AdminView` passa a ser acessada via `/:clientSlug/admin` em vez de `/admin` (rota global sem slug); `admin` foi removido de `GLOBAL_ROUTES` em `useAppNavigation`; a regra em `accessControl.ts` passou a ter `requiresClient: true`
**Racional:** admin gerencia dados (clientes, membros, notificações) que pertencem a uma empresa específica; ter o `clientSlug` na URL mantém consistência com o restante da aplicação, permite deep links contextuais e prepara a estrutura para um futuro multi-tenant onde cada empresa terá seu próprio escopo de admin
**Consequências:** a URL `/admin` deixa de existir (redireciona para `/` via wildcard); é necessário ter um cliente selecionado para acessar admin; `isGlobalView` em `App.tsx` não inclui mais `"admin"`, logo admin usa o `AppLayout` normal com sidebar

---

## ADR-019: Steps → Subtasks (modelo flexível por demanda)

**Status:** Aceito (Mai 2026)
**Decisão:** o modelo de `Step` (8 tipos fixos por task, identidade = tipo) foi substituído por `Subtask` (N subtasks livres por task, identidade = `id`, tipo expresso como campo `status`). Tabelas: `task_subtasks` + `subtask_assignees` (substituem `task_steps` + `step_assignees`). Tipo domínio: `Subtask` com campos `id, title, status: SubtaskStatus, start, end, assignees, active, order`. `StepType` passou a ser alias de `SubtaskStatus` para compatibilidade temporária.
**Racional:** o modelo fixo de 8 steps impedia nomear etapas de forma contextual (ex: "Homepage — Design" vs "Design"); uma demanda pode ter múltiplas subtasks do mesmo tipo em paralelo; a flexibilidade de N subtasks livres é mais adequada a diferentes tipos de projeto
**Consequências:** nova task nasce sem subtasks — usuário adiciona livremente via TaskModal; dados existentes migrados automaticamente (`title = type`, `status = type`); `Planning View` exibe a demanda em todos os grupos onde tiver subtask ativa (não apenas o grupo "atual"); drag/drop no Calendar e Timeline indexado por `subtaskId` em vez de `stepType`; `task_steps` e `step_assignees` mantidas no banco para rollback até uma migration de drop futura

---

## ADR-020: Prioridade manual das demandas-pai

**Status:** Aceito (Mai 2026)
**Decisão:** demandas têm `priority_order` persistido na tabela `tasks`; a subview `Demandas` ordena por esse campo e permite reordenar linhas-pai via drag-and-drop quando não há filtros ativos.
**Racional:** prioridade manual é uma decisão de planejamento da lista, não derivada apenas do prazo da subtask ativa; persistir a ordem no banco garante consistência entre sessões e usuários.
**Consequências:** novas demandas entram no fim da fila do cliente; reordenação aplica update otimista no cache TanStack Query e grava os novos índices no Supabase; filtros desativam o drag para evitar gravar uma ordem parcial acidental.

---

## ADR-021: Status de andamento separado da etapa da subtask

**Status:** Aceito (Mai 2026)
**Decisão:** `task_subtasks` passa a ter `progress_status`, separado de `status` (que continua representando a etapa/tipo: Design, QA, Publicação etc.). O domínio expõe `Subtask.progressStatus` com os valores `todo`, `ready`, `in-progress`, `in-review`, `waiting`, `blocked`, `needs-changes`, `paused`, `done` e `canceled`.
**Racional:** o campo `status` já era usado como categoria visual e filtro de etapa; reaproveitá-lo para andamento quebraria calendário, timeline e legenda. Separar andamento permite gerir subtasks esquecidas, bloqueadas ou concluídas sem perder a fase de entrega.
**Consequências:** a tabela de demandas ganhou uma coluna "Status" com popover de edição inline; o modal de demanda também salva o andamento; o progresso da demanda agora considera subtasks `done` e ignora subtasks `canceled`.
33 changes: 23 additions & 10 deletions docs/hooks/supabase.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Hook de **mutations apenas**. Não armazena estado — após cada operação usa

## Funções

- `createTask(data)` — insere tarefa + steps + assignees; invalida a query `['tasks', ...]` no fim
- `createTask(data)` — insere tarefa + subtasks + assignees; invalida a query `['tasks', ...]` no fim
- `updateTask(data)` — update otimista no cache do TanStack Query, depois persiste no DB; reverte em caso de erro
- `deleteTask(id)` — remove do DB e atualiza o cache local sem re-fetch

As três funções são envolvidas por `useThrottledMutation` (500ms) antes de serem expostas. Chamadas mais rápidas que o intervalo são rejeitadas com toast de aviso e retornam `false`.
`createTask` e `deleteTask` são envolvidas por `useThrottledMutation` (500ms) antes de serem expostas — chamadas mais rápidas que o intervalo são rejeitadas com toast de aviso e retornam `false`. `updateTask` é exposto sem throttle, pois é chamado de forma intencional (drag-and-drop, edição inline).

## Rate Limiting (client-side)

Expand All @@ -28,28 +28,41 @@ Aplicado em: `useSupabase` (500ms), `useTaskQuickActions` (500ms), `useUserClien

`useTaskQuickActions` (`src/hooks/useTaskQuickActions.ts`) centraliza os toggles rápidos de bloqueio e conclusão, usados por `ListView` e `TasksView` — elimina código duplicado e garante throttle consistente.

`useSubtaskQuickEdit` (`src/hooks/tasks/useSubtaskQuickEdit.ts`) — mutations granulares de subtask sem passar pela modal. Expõe:
- `updateSubtaskAssignees(task, subtaskId, assignees[])` — diff de adds/removes em `subtask_assignees` com update otimista no cache
- `updateSubtaskDates(task, subtaskId, start, end)` — UPDATE direto em `task_subtasks.start_date / end_date` com update otimista no cache
- `updateSubtaskProgressStatus(task, subtaskId, progressStatus)` — UPDATE direto em `task_subtasks.progress_status` com update otimista no cache

Usado por `PlanningView` (subview `demandas`) para alimentar os popovers inline de `TaskTable`.

## Update otimista (`updateTask`)

```
1. Snapshot de cachedTasks via queryClient.getQueryData
2. queryClient.setQueryData → aplica alteração localmente (UI actualiza imediatamente)
3. useTaskStore.applyOptimisticUpdate → sincroniza o store local (para rollback via clearOptimistic)
4. Persiste no DB (tasks + steps + assignees)
4. Persiste no DB (tasks + subtasks + assignees)
5. Se erro → queryClient.setQueryData(prev) + useTaskStore.clearOptimistic()
```

## Steps (`upsertSteps`)
## Subtasks (`createAllSubtasks` + diff em `updateTask`)

`createAllSubtasks` — função privada chamada em `createTask` e em `updateTask` (para subtasks novas):
- INSERT em `task_subtasks` (todas de uma vez), indexadas por `subtask_order` para associar IDs de volta
- INSERT em `subtask_assignees` para assignees não vazias

Função privada que faz insert/update de cada step e compara diff de assignees:
- Step existente: UPDATE em `task_steps` apenas se mudou
- Assignees adicionados: INSERT em `step_assignees`
- Assignees removidos: DELETE em `step_assignees`
`updateTask` faz diff por `subtask.id`:
- Subtasks com `id = ''` → novas → `createAllSubtasks`
- IDs presentes no prev mas ausentes no next → DELETE em `task_subtasks`
- IDs presentes em ambos → compara campos → UPDATE se mudou
- Assignees diff por subtask: INSERT/DELETE em `subtask_assignees`

## Tabelas Supabase

- `tasks` — dados da tarefa
- `task_steps` — fases/steps da tarefa
- `step_assignees` — relação step ↔ member
- `task_subtasks` — subtasks da tarefa (substitui `task_steps`)
- `subtask_assignees` — relação subtask ↔ member (substitui `step_assignees`)
- `task_steps` / `step_assignees` — mantidas temporariamente para rollback (migration drop pendente)

## Estado

Expand Down
2 changes: 1 addition & 1 deletion docs/todo/melhorias.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Todos implementados com `memo` e comparador customizado. Callbacks excluídos do
|---|---|
| `NotificationBell` | `unreadCount`, `notifications.length`, `selectedClientId` |
| `WeekRow` | `tasks` (ref + length), `week[0]`, `currentMonth`, `viewMode`, `weekIndex`, `dragPreview`, `holidays.length` |
| `StepBar` | `bar.{taskId,stepType,startCol,endCol,slot}`, `task.{concludedAt,status.blocked}`, `isFirst/LastBarOfStep`, `viewMode`, `demandColor`, `dragPreview` |
| `StepBar` | `bar.{taskId,subtaskId,startCol,endCol,slot}`, `task.{concludedAt,status.blocked}`, `isFirst/LastBarOfStep`, `viewMode`, `demandColor`, `dragPreview` |
| `PhaseBar` | `step.{type,start,end}`, `task.{id,concludedAt,status.blocked}`, `days.length`, `dragPreview` |
| `MemberCard` | `member.id`, `tasks` (ref + length), `today` |
| `TaskRow` | `task.{id,status.blocked,concludedAt}`, `stepType`, `members.length` |
Expand Down
Loading
Loading