Hooks

useDocyrusKanban

One-call wiring of a Docyrus data source to a fully configured Kanban board with select/status/radio-group, user, and date columns, drag-to-update persistence, final-zone integration, and a standard Docyrus card layout.

Installation

pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-kanban
Required Packages(6 packages)
pnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query @dnd-kit/core @dnd-kit/sortable react-querybuilder

This hook is distributed as source. It expects an authenticated RestApiClient from @docyrus/api-client and a QueryClientProvider from @tanstack/react-query somewhere above your component tree.

Overview

useDocyrusKanban is the kanban-first companion to useDocyrusDataGrid. It wires a Docyrus data source to a fully configured <Kanban> board with:

  • saved-view filters, sorting, and search wiring (via useDocyrusDataViewSelect)
  • automatic column derivation from the field type you point it at
  • drag-to-update persistence (PATCH /items/:id)
  • final-zone integration when the group-by field is a field-status
  • a ready-to-render standard card layout (avatar, title, description, footer with user + audit info)

Supported group-by field types

The groupByFieldSlug you pass must point to one of these field types:

Field typeColumn derivationToolbar control
field-select, field-radioGroupOne column per enum option (using the option's slug/id as the key, color/icon for column accent)"Show all" switch (default true)
field-statusSame as above, plus a <KanbanFinalZone> is rendered for any enum option whose is_final_option === true"Show all" switch (default true)
field-userSelectOne column per distinct user (or team) referenced by the rows"Group by" picker — User / Team
field-dateOne column per bucketed date — time portion is always ignored"Group by" picker — Day / Week / Month
field-dateTimeSame as field-date — time portion is dropped before bucketing"Group by" picker — Day / Week / Month

For enum-backed fields, the hook automatically appends the field slug to the request expand array so each row's value carries the option's color and icon. The same is done for field-userSelect so the user payload (photo, name, team) is available for column headers and card avatars.

Card contract

Every card is rendered with the same standard layout — only the field bindings change:

┌─────────────────────────────────────────────┐
│ ◯  Title                              ⋮     │ ← avatarColumn + titleColumn + menu
│    Description                              │ ← descriptionColumn
│                                             │
│    {cardContent}                            │ ← free-form children
│                                             │
│ ◎                                       ℹ︎  │ ← userColumn + audit tooltip
└─────────────────────────────────────────────┘
SlotBound byNotes
avatarColumnA field whose value resolves to { icon, color, image } (or a string icon/color)Rendered with <AvatarThumbnail> so icon, color, and image inputs all work consistently
titleColumnField slugSingle-line, truncated, bold
descriptionColumnField slugTwo-line clamp, muted
userColumnField slug pointing to a field-userSelect payload (or any object with firstname/lastname/photo)Rendered as a circular avatar in the footer-left with a name tooltip
cardContentRender prop receiving { row, column }Free-form region between header and footer — render whatever extra fields the app needs
Footer info iconcreated_on, last_modified_on, created_by, last_modified_byAlways shown when at least one is present; rendered as an <Info> icon with a tooltip
Card menu (top-right)cardMenuItems (or default Open / Edit / Delete)Default actions wire onCardOpen, onCardEdit, and a built-in delete confirmation dialog

Override the menu either by listing items directly or by passing a function that receives the defaults — the function form lets you keep Open/Edit/Delete and append additional entries.

Usage

Status-driven board (enum + final zone)

'use client';

import { useDocyrusAuth } from '@docyrus/signin';
import { useDocyrusKanban } from '@docyrus/ui/library/hooks/use-docyrus-kanban';

type LeadRow = {
  id: string;
  name: string;
  description?: string;
  status: { id: string; name: string };
  assigned_to?: { id: string; firstname: string; lastname: string; photo?: string };
  icon?: string;
  color?: string;
};

export function LeadsKanban() {
  const { client } = useDocyrusAuth();

  if (!client) return null;

  const { toolbar, board } = useDocyrusKanban<LeadRow>({
    client,
    appSlug: 'crm',
    dataSourceSlug: 'lead',
    groupByFieldSlug: 'status',
    avatarColumn: 'icon', // any field whose value is an icon/color/image bag
    titleColumn: 'name',
    descriptionColumn: 'description',
    userColumn: 'assigned_to',
    onCardOpen: row => router.push(`/crm/leads/${row.id}`),
    onCardEdit: row => openEditDialog(row.id)
  });

  return (
    <div className="flex h-full flex-col">
      {toolbar}
      <div className="flex-1 overflow-hidden">{board}</div>
    </div>
  );
}

Date board with the Group By picker

const { toolbar, board } = useDocyrusKanban<TaskRow>({
  client,
  appSlug: 'project',
  dataSourceSlug: 'task',
  groupByFieldSlug: 'due_date',     // field-date or field-dateTime
  dateGroupBy: 'week',              // Day | Week | Month — switchable via toolbar
  titleColumn: 'subject',
  descriptionColumn: 'summary',
  userColumn: 'assigned_to'
});

When the group-by field is a date or datetime field the toolbar automatically renders a "Group by" Select with Day, Week, and Month. The time portion is always ignored — buckets are computed from the UTC date components only.

User board with User/Team toggle

const { toolbar, board } = useDocyrusKanban<TaskRow>({
  client,
  appSlug: 'project',
  dataSourceSlug: 'task',
  groupByFieldSlug: 'assigned_to',  // field-userSelect
  userGroupBy: 'team',              // User | Team — switchable via toolbar
  avatarColumn: 'icon',
  titleColumn: 'subject',
  descriptionColumn: 'description',
  userColumn: 'assigned_to'
});

userGroupBy: 'team' reads team_id (or a nested team.id) off the user payload returned by expand=assigned_to. Cards remain draggable in 'user' mode and become read-only in 'team' mode (since teams aren't a writable field on the user reference).

Show all columns (enum) toggle

For enum-backed fields the toolbar renders a Show all switch (default true) that mirrors the showAllColumns option:

  • true (default): every enum option is rendered as a column even when no record currently maps to it. Useful when you want users to be able to drop a card into a fresh stage.
  • false: only columns with at least one record are rendered.
const { toolbar, board, showAllColumns, setShowAllColumns } = useDocyrusKanban({
  /* … */
  showAllColumns: false    // initial value — the toolbar switch still controls it
});

Final zone (status fields)

When the group-by field is field-status, every enum option flagged is_final_option is rendered inside a <KanbanFinalZone> below the main board instead of as a board column:

const { board } = useDocyrusKanban({
  /* … */
  groupByFieldSlug: 'status'
});

Dragging a card into a final-zone column issues the same PATCH /items/:id as dragging between board columns — the only difference is that final columns aren't rendered side-by-side with the active stages.

API Reference

Parameters

The hook accepts every option from useDocyrusDataViewSelect (for view CRUD + filter fields), plus:

DB-free metadata. Because this hook forwards into useDocyrusDataViewSelect, it also accepts dataSource (inject a pre-resolved schema → skips the getBySlug fetch), enableDataViews: false (skip the /views fetch), and dataSourceExpand (tune/drop the schema expand param). Pass dataSource together with data (pre-resolved rows) to render the board with no metadata requests. See DB-free metadata.

OptionTypeDefaultDescription
groupByFieldSlugstring—Required. Slug of the field whose values become kanban columns.
dataArray<TData>—Pre-resolved rows. When provided, the hook skips its internal items query.
collectionDocyrusKanbanCollection<TData>—TanStack DB collection. Optional update and remove methods are wired to drag-move and the default delete action.
listParamsDocyrusKanbanListParams—Extra query params merged on top of the view-derived payload.
defaultLimitnumber200Default page size when no limit is supplied via listParams.
enableItemsQuerybooleantrue when no dataToggle the internal items query.
dateGroupBy'day' | 'week' | 'month''day'Initial date bucket granularity. The toolbar switch overrides this at runtime.
userGroupBy'user' | 'team''user'Initial user grouping mode for field-userSelect.
showAllColumnsbooleantrueInitial state of the "Show all" switch (enum group-by only).
avatarColumnstring—Field slug bound to the card avatar (icon/color/image).
titleColumnstring—Field slug bound to the card title.
descriptionColumnstring—Field slug bound to the card description.
userColumnstring—Field slug bound to the user shown in the card footer.
cardContent(ctx) => ReactNode—Free-form card body. Receives { row, column }.
cardMenuItemsArray<DocyrusKanbanCardMenuItem<TData>> | (row, defaults) => Array<...>—Override the action menu. The function form keeps the default Open/Edit/Delete entries available.
cardActionsArray<'open' | 'edit' | 'delete'>['open', 'edit', 'delete']Whitelist of the default actions.
onCardOpen(row) => void—Wired to the default Open menu item.
onCardEdit(row) => void—Wired to the default Edit menu item.
onCardDelete(row) => Promise<void> | void—Custom delete handler. When omitted the hook calls collection.remove(id) or DELETE /items/:id after a confirmation dialog.
onCardClick(row) => void—Click handler fired when the card body is clicked.
enableViewSelectbooleantrueShow the saved-view picker in the toolbar.
enableSearchInputbooleantrueShow the search input.
enableDateGroupMenubooleantrueShow the Day/Week/Month picker when grouping by date.
enableUserGroupMenubooleantrueShow the User/Team picker when grouping by user.
enableShowAllColumnsSwitchbooleantrueShow the "Show all" switch when grouping by enum.
enableReloadButtonbooleantrueShow the reload button.
onReload() => void—Called when the reload button is clicked, after the internal refetch.
searchPlaceholderstring'Search…'Placeholder for the toolbar search input.
searchDebounceMsnumber300Debounce in ms before the search input is sent as filterKeyword.
toolbarClassNamestring—Extra className for the toolbar root.
toolbarStartContentReactNode—Extra node prepended to the left side of the toolbar.
toolbarEndContentReactNode—Extra node appended to the right side of the toolbar.
onItemMove(params) => void—Fired when a card is dropped into a different column. Runs alongside the built-in mutation.
onItemMoveCommit(params) => Promise<void> | void—Replace the built-in PATCH with a custom handler. Throw to abort the move.

Return Value

PropertyTypeDescription
toolbarReactNodePre-wired toolbar element ready to render above the board.
boardReactNodePre-wired kanban board element. Render directly.
itemsArray<TData>Resolved rows passed to the board.
resolvedListParamsDocyrusKanbanListParamsThe list params actually sent to the backend.
groupByFieldDataSourceField | undefinedThe field metadata used to derive columns.
columnsArray<DocyrusKanbanColumnMeta>Ordered metadata for every rendered column (including counts).
columnsItemsRecord<string, Array<TData>>Items grouped by column.id.
dateGroupBy'day' | 'week' | 'month'Active date grouping.
setDateGroupBy(value) => voidProgrammatically switch date grouping.
userGroupBy'user' | 'team'Active user grouping.
setUserGroupBy(value) => voidProgrammatically switch user grouping.
showAllColumnsbooleanActive state of the "Show all" switch.
setShowAllColumns(value) => voidProgrammatically toggle the switch.
viewsArray<SavedDataGridView>Saved views mapped from the backend shape.
fieldsArray<FullField>Fields mapped for react-querybuilder.
dataSourceDataSource | undefinedRaw data source metadata response.
activeViewIdstringId of the currently active view.
setActiveViewId(viewId) => voidProgrammatically switch views.
reload() => voidTriggers refetch of the data source, views, and items queries plus the optional onReload callback.
isLoadingbooleantrue until all queries have resolved.
errorError | nullFirst error from any of the queries.
refetch() => voidAlias for reload.

Drag-and-drop persistence

When a card is dropped into a different column the hook computes a payload based on the field type and either:

  • calls collection.update(id, payload) if the consumer passed a TanStack DB collection with an update method, or
  • issues a PATCH /v1/apps/:appSlug/data-sources/:dataSourceSlug/items/:id with the RestApiClient from @docyrus/signin.
Group-by fieldPayload sent on drop
field-select, field-radioGroup, field-status{ [fieldSlug]: <enumOptionId> }
field-userSelect (userGroupBy: 'user'){ [fieldSlug]: <userId> }
field-userSelect (userGroupBy: 'team')(no-op — teams aren't writable on the user reference)
field-date, field-dateTime(no-op — bucketing is derived, not stored)

Pass onItemMoveCommit to skip the built-in PATCH entirely (e.g. for optimistic updates with rollback) and onItemMove to mirror the change in local state without replacing the mutation.

Field type → column derivation matrix

Docyrus typeColumn idColumn labelColor/icon sourceFinal-zone integration
field-selectenum option id (or slug)enum option nameenum option color / icon—
field-radioGroupenum option id (or slug)enum option nameenum option color / icon—
field-statusenum option id (or slug)enum option nameenum option color / iconoptions where is_final_option === true
field-userSelect (user)user idfull name (or email)user photo—
field-userSelect (team)team idteam name——
field-date, field-dateTimebucket key (YYYY-MM-DD / YYYY-Www / YYYY-MM)localized label——

Out of scope

  • field-relation, field-multiSelect, and field-tagSelect are intentionally not surfaced as group-by candidates — relation fields would require additional metadata to render meaningful column headers and multi-value fields don't map cleanly onto a single column. If you need to group by one of these, render your own board with the same useDocyrusDataViewSelect wiring.

On this page