Skip to content

Organization tasks and projects

The panel section at /tasks manages projects and tasks with live data from the Nest API (global api prefix, module routed under operations).

Views and filters

Each tab (Projects / Tasks) offers:

  • Boardcolumns per status in a single horizontal scroll row inside the board only (overflow-x-auto on the column container; w-max + ~320px columns). Card delete (board): a trash control on each task / project card is shown on hover or when the control has keyboard focus (opacity-0group-hover / focus-visible); it opens an AlertDialog before calling deleteTask / deleteProject (use-tasks-module.ts). Project delete uses DELETE /operations/projects/:id, updates the projects cache via removeProjectFromProjectsCache, and invalidates the aggregated tasks query so linked rows refresh. Edge auto-scroll: when the pointer is in a small band at the left or right edge of that rail (including while dragging a card), the board scrolls horizontally very slowly via requestAnimationFrame (use-kanban-horizontal-edge-scroll.ts + kanban-horizontal-edge-scroll.ts); prefers-reduced-motion: reduce turns it off. The dashboard layout uses overflow-x-hidden / overflow-x-clip on the page region, overflow-x: clip on html / body / #root (with width / max-width: 100% on #root), and min-w-0 on the flex chain (including TabsContent in tabs.tsx) so width does not “pull” the whole window. Board columns use min(320px, 100cqw) relative to the rail (@container), avoiding 100vw (includes the vertical scrollbar and can force overflow). Horizontal scroll stays on the rail (overscroll-x-contain). Progress bar and done / total text below: for projects based on project tasks; for tasks based on checklist rows loaded via GET /operations/checklists?taskId= (finishedAt = done; board shows top-level tasks only). Implementation: kanban-card-progress.ts. In the preview under the bar, the circle for each project task row calls PUT /operations/tasks/:id (updateBodyForToggleTaskCompletion); each task checklist line calls PUT /operations/checklists/:id; clicks use stopPropagation so they do not interfere with dragging. On the task card, only checklist lines that are done show struck-through, muted text; the main task description does not. The task card uses slightly more padding (p-5 / sm:p-6) and the checklist list a bit more spacing.
  • Listtable (shadcn Table) with a styled header; columns depend on the entity (projects: title, creator (creator / creatorId), status, description, cost, end date; tasks: description, creator, status, project, start, end); rows are clickable / keyboard (Enter or space) to open the same edit dialog as on the board; rounded border wrapper and horizontal scroll on narrow screens (min-width on the table).

Toolbar filters: All, My projects / My tasks (by authenticated user creatorId), Recent (sorted by updatedAt).

When creating a status (column), the global task list is refetched with a new queryKey; the hook uses placeholderData: keepPreviousData and computeTasksModuleLoadingShell so the whole board is not replaced by skeletons: existing columns keep showing their cards; the new column appears with its header and empty area until refetch completes.

API usage

The panel HTTP client (apiClient, VITE_API_URL pointing at the origin that includes the /api prefix where applicable) calls:

ResourceMethods
/operations/kanban-statusesGET list, POST create
/operations/kanban-statuses/:idPUT update name/color, DELETE delete status
/operations/projectsGET list, POST create
/operations/projects/:idPATCH update (fields and statusId), DELETE delete project
/operations/tagsGET list, POST create (name, colorHex)
/operations/tasks?statusId=<uuid v7>GET tasks by status (parallel requests per status)
/operations/tasksPOST create (board tasks are top-level; no checklist lines here)
/operations/tasks/:idPUT update
/operations/tasks/:idDELETE delete (board card, etc.)
/operations/checklists?taskId=<uuid v7>GET checklist rows for a task
/operations/checklistsPOST create checklist row
/operations/checklists/:idPUT update (text / finishedAt), DELETE delete

Create payloads follow @repo/schemas (CreateKanbanStatus, CreateProject, CreateTask). In the UI, the main task field is labeled Description because the backend uses required description. Task and project dialogs use React Hook Form + zod (task-dialog-form-schema.ts, project-dialog-form-schema.ts): Guardar / Crear runs validation (mode: 'onSubmit'); missing título (proyecto), plain-text description from HTML, or (when editing a task) columna shows Spanish formState.errors next to the field (and outlines). New tasks do not show a column control — the column is inferred (see Create / edit task dialog).

Rich descriptions: in project and task dialogs, description is edited with TipTap (HTML: bold, lists, links, etc.). When not editing, the control matches create event: dashed full pill, Pencil icon, muted placeholder “Agregar descripción sugerida o notas adicionales…” (dialog-description-collapsed-trigger-classes.ts); tap expands the editor (Listo collapses). Links and images use dialogs: link with URL + visible text; image with URL only. The API stores description as HTML; board, list, and search use plain text (htmlToPlainText).

Create / edit task dialog

  • Column (statusId) matches projects: on create, the column is not shown as a dropdown. + on a kanban column opens the dialog with that column; Nueva tarea in the toolbar uses the first column (useTaskDialog.openCreate). The Columna del tablero selector is shown only when editing an existing task.
  • Shell matches calendar → Create event (CreateEventDialog): DialogContent sm:max-w-[650px], max-h-[85vh], header border-b + pr-14 for the default close control, body overflow-y-auto overscroll-contain px-6 py-5, footer DialogFooter with outline Cancelar and bold uppercase primary save. Section toggles use the same pill styles as event sections (dialog-section-pill-classes.ts: active bg-primary/10 text-primary, muted inactive with hover).
  • Fixed header (title + section bar), scrollable body, fixed footer. Help copy lives inside the scroll region.
  • Descripción: same dashed pill + pencil collapsed control and Listo / rich editor pattern as the project dialog. The bar includes Tags, Dates, Checklist, and Attachments (icon + label). Tags: colored chips and a small + that opens a portal overlay to toggle operation tags (checkmark when selected), Crear etiqueta nueva (expands inline name + color + Crear / Cancelar at the bottom of the same overlay — no second modal), and Listo (n) — not a dropdown. Dates: summary row (Spanish short range via formatTasksDateSummary, Vencida when the due date is before today) opens a popup titled Fechas with a month grid (Monday-first, buildCalendarWeeksForMonth + date-fns / locale es for the title), chevron month navigation, Spanish weekday initials (lu–do), and tap-to-set for the focused date (start vs end follows the last focused checkbox row or date input). Checkboxes and inputs edit draft values until Guardar applies them to the parent; Cancelar, the X, and the backdrop discard drafts and close. Maps to startedAt / finishedAt on task/project save (start of day / end of day). Checklist: each item is an operations checklist row for the task (plan-checklist-sync.ts diffs form lines vs listChecklistsByTask on save: POST / PUT / DELETE on /operations/checklists). The circle toggles done vs pending via PUT /operations/checklists/:id (finishedAt at the current instant when done, null when pending; if startedAt was missing, it is backfilled when marking done). For a new task, checklist lines only persist after Save creates the parent task, then each line is created. Attachments uses the same storage flow as projects.

Create / edit project dialog

  • Shell matches calendar → Create event: same DialogContent width/height, header/body/footer layout, showCloseButton, and section pill styling as the task dialog (dialog-section-pill-classes.ts). Project name is the first visible control: borderless bold line (text-base). Column (statusId) is not chosen in the dialog — it comes from the board column when creating or from the existing project when editing. Descripción: dashed pill + pencil when collapsed (shared with create event); tap opens TipTap with toolbar; Listo returns to the pill (plain text when there is content). Optional blocks (Cost, Tags, Dates, Attachments) use the section bar. Tags and dates use OperationTagsField and TasksDatesField (project end date label Entrega).
  • Section bar at the top: Cost, Tags, Dates, Attachments — pills only show or hide blocks. Attachments uses the storage file flow described below.
  • Scrollable body (overflow-y-auto overscroll-contain); footer — outline Cancelar + primary Guardar cambios / Crear proyecto (bold uppercase), consistent with Agendar Evento.
  • On save with API: name (title), description, column (statusId), cost/currency, dates (startedAt / finishedAt), tagIds, teamMemberIds, and attachmentIds (storage document id list).
  • Attachments: same flow as file pick in agents: Storage opens StorageLibraryDialog (useFilesLibraryDialogStore + startPickSession / openDialog). Each file adds its **documentId** to linkedFilesin dialog state; the UI shows **name, type, size (when the picker provides it)** and the **id** in monospace. **Remove** drops the id from the list before save. In edit mode, metadata comes fromproject.attachmentswhen the backend hydrates them; if onlyattachmentIds` exist, a short generic name is shown.
  • Tags: useTasksModule loads tags via operationsApiService.listTags; OperationTagsField (+ → overlay) lists all operation tags with toggle selection, Crear etiqueta nueva (inline create in the overlay), and Listo. OperationTagCreateFields renders the shared name + color presets; useOperationTagCreate owns draft state + POST /operations/tags + append to the open form (tasks-view.tsx uses one hook instance per dialog). The same OperationTagsField + hook pattern is used in calendar create/edit event dialogs. Projects and tasks persist tagIds on submit.

Implementation notes

  • Data layer: useTasksModule (TanStack Query) + operationsApiService.
  • Cache after mutations: create / update / delete for projects and tasks applies API responses into the query cache (merge-operations-cache.ts: upsertProjectInCache, mergeCreatedTaskIntoTasksCache, upsertTaskInCache, removeTaskFromTasksCache). Checklist mutations update or invalidate ['operations','checklists', taskId] (use-checklists-by-task-ids.ts). Mutation callbacks do not await a full listAllTasks refetch (that helper still issues one GET per status + orphans in parallel). Creating a project updates only the projects query. Bulk or column-wide changes still invalidateQueries on the aggregated tasks key ['operations','tasks', statusKey] so refetches run in the background without blocking dialogs.
  • Reorder kanban columns (horizontal drag): reorderKanbanStatusMutation applies an optimistic reorder to ['operations','kanban-statuses'] in onMutate (reorder-kanban-statuses-in-cache.ts), then merges the returned row’s sortIndex in onSuccess, so useKanbanDnd can drop statusColumnOrderLive without a visible snap-back to stale order; no refetch of statuses/projects/tasks solely for column reorder.
  • Column grouping: groupItemsByOrderedStatuses (with unit tests).
  • Board: @dnd-kit/core + @dnd-kit/sortable (SortableContext, verticalListSortingStrategy, closestCorners), per-column local order while dragging (applyKanbanSortableDragOver in kanban-dnd.ts) to animate gap closure; DragOverlay for the card following the pointer; column changes still persist with PATCH/PUT and statusId.
  • ⋮ menu on column header: Rename status (dialog) and Delete status (confirmation), via operationsApiService.updateKanbanStatus / deleteKanbanStatus.
  • Board card: drag from anywhere on the card between columns; click on project title or main task description opens edit (updateProject / updateTask) without starting drag (the sensor uses a movement threshold and those areas stop pointer propagation).
  • UI copy is in Spanish; code and identifiers are in English.