Appearance
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:
- Board — columns per status in a single horizontal scroll row inside the board only (
overflow-x-autoon 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-0→group-hover/focus-visible); it opens anAlertDialogbefore callingdeleteTask/deleteProject(use-tasks-module.ts). Project delete usesDELETE /operations/projects/:id, updates the projects cache viaremoveProjectFromProjectsCache, 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 viarequestAnimationFrame(use-kanban-horizontal-edge-scroll.ts+kanban-horizontal-edge-scroll.ts); prefers-reduced-motion: reduce turns it off. The dashboard layout usesoverflow-x-hidden/overflow-x-clipon the page region,overflow-x: cliponhtml/body/#root(withwidth/max-width: 100%on#root), andmin-w-0on the flex chain (includingTabsContentintabs.tsx) so width does not “pull” the whole window. Board columns usemin(320px, 100cqw)relative to the rail (@container), avoiding100vw(includes the vertical scrollbar and can force overflow). Horizontal scroll stays on the rail (overscroll-x-contain). Progress bar anddone / totaltext below: for projects based on project tasks; for tasks based on checklist rows loaded viaGET /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 callsPUT /operations/tasks/:id(updateBodyForToggleTaskCompletion); each task checklist line callsPUT /operations/checklists/:id; clicks usestopPropagationso 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. - List — table (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-widthon 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:
| Resource | Methods |
|---|---|
/operations/kanban-statuses | GET list, POST create |
/operations/kanban-statuses/:id | PUT update name/color, DELETE delete status |
/operations/projects | GET list, POST create |
/operations/projects/:id | PATCH update (fields and statusId), DELETE delete project |
/operations/tags | GET list, POST create (name, colorHex) |
/operations/tasks?statusId=<uuid v7> | GET tasks by status (parallel requests per status) |
/operations/tasks | POST create (board tasks are top-level; no checklist lines here) |
/operations/tasks/:id | PUT update |
/operations/tasks/:id | DELETE delete (board card, etc.) |
/operations/checklists?taskId=<uuid v7> | GET checklist rows for a task |
/operations/checklists | POST create checklist row |
/operations/checklists/:id | PUT 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):DialogContentsm:max-w-[650px],max-h-[85vh], headerborder-b+pr-14for the default close control, bodyoverflow-y-auto overscroll-contain px-6 py-5, footerDialogFooterwith outline Cancelar and bold uppercase primary save. Section toggles use the same pill styles as event sections (dialog-section-pill-classes.ts: activebg-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/ localeesfor 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 tostartedAt/finishedAton task/project save (start of day / end of day). Checklist: each item is an operations checklist row for the task (plan-checklist-sync.tsdiffs form lines vslistChecklistsByTaskon save:POST/PUT/DELETEon/operations/checklists). The circle toggles done vs pending viaPUT /operations/checklists/:id(finishedAtat the current instant when done,nullwhen pending; ifstartedAtwas 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
DialogContentwidth/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 useOperationTagsFieldandTasksDatesField(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, andattachmentIds(storage document id list). - Attachments: same flow as file pick in agents: Storage opens
StorageLibraryDialog(useFilesLibraryDialogStore+startPickSession/openDialog). Each file adds its **documentId** tolinkedFilesin 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:
useTasksModuleloadstagsviaoperationsApiService.listTags;OperationTagsField(+ → overlay) lists all operation tags with toggle selection, Crear etiqueta nueva (inline create in the overlay), and Listo.OperationTagCreateFieldsrenders the shared name + color presets;useOperationTagCreateowns draft state +POST /operations/tags+ append to the open form (tasks-view.tsxuses one hook instance per dialog). The sameOperationTagsField+ hook pattern is used in calendar create/edit event dialogs. Projects and tasks persisttagIdson 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 notawaita fulllistAllTasksrefetch (that helper still issues one GET per status + orphans in parallel). Creating a project updates only the projects query. Bulk or column-wide changes stillinvalidateQuerieson the aggregated tasks key['operations','tasks', statusKey]so refetches run in the background without blocking dialogs. - Reorder kanban columns (horizontal drag):
reorderKanbanStatusMutationapplies an optimistic reorder to['operations','kanban-statuses']inonMutate(reorder-kanban-statuses-in-cache.ts), then merges the returned row’ssortIndexinonSuccess, souseKanbanDndcan dropstatusColumnOrderLivewithout 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 (applyKanbanSortableDragOverinkanban-dnd.ts) to animate gap closure;DragOverlayfor the card following the pointer; column changes still persist withPATCH/PUTandstatusId. - ⋮ 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.