Planner Component
planner is a grouped, ordered planning grid. Each resource is a row and its work items occupy ordinal slots (Move 1, Move 2, and so on). Unlike timeline-grid, its columns represent position rather than time.
Data pipeline
The component extracts items from queries, groups them with rows.groupBy, sorts each row with columns.orderBy, and places each sorted item by index. rows.fromQuery can supply empty resources; unmatched items appear in an optional Unassigned row.
component: planner
name: driverPlanner
props:
height: 700
queries:
- name: moves
query:
command: 'query Moves($date: DateTime!) { orders { items { orderMoves { orderMoveId } } } }'
variables: { date: '{{ plannerDate }}' }
path: orders.items
itemsPath: orderMoves
parentAs: order
- name: drivers
query:
command: 'query Drivers { contacts { items { contactId firstName lastName } } }'
path: contacts.items
rows:
groupBy: driver.contactId
fromQuery: drivers
key: contactId
label: '{{ item.firstName }} {{ item.lastName }}'
headerWidth: 260
columns:
appendEmpty: true
orderBy:
- field: startDate
direction: asc
label: 'Move {{ index }}'
width: 420
itemTemplate:
component: card
itemsPath automatically flattens arrays along a dot path; parentAs attaches the owner of the final collection and mapping can add or replace item fields. Column and row sorting is stable and supports multiple fields. Explicit rows.headerWidth and columns.width keep virtualized rows aligned.
Templates include rowHeaderTemplate, itemTemplate, emptyCellTemplate, summaryComponent, and emptyStateTemplate. Events include onItemClick, onCellClick, onRowClick, and onItemsLoaded. Set options.enableVirtualization and virtualizeThreshold for large boards, refreshHandler for action-driven reloads, and debug: true for extraction/grouping diagnostics.
Card actions
dotsMenu adds the same top-right menu used by dataGrid rows. Menu-item templates and actions receive the complete item context, including item. Opening the menu or selecting an action does not trigger onItemClick.
dotsMenu:
items:
- label: { en-US: Open order }
permission: Orders.View
disabled: '{{ item.isLocked }}'
onClick:
- navigate:
route: '/orders/{{ item.orderId }}'
Set columns.appendEmpty: true to keep one trailing empty slot on every lane. The board then renders one more ordinal column than the largest group, which is useful for drop-to-append workflows even when the fullest lane would otherwise have no empty cell.
Drop targets
Planner cells accept cards from a DataGrid collection view when props.drop and events.onItemDrop are both configured. accept matches the source type; onlyEmptyCells defaults to true; canDrop can veto a row; and autoScroll defaults to true.
drop:
accept: [orderMove]
onlyEmptyCells: true
canDrop: '{{ eval !row.isUnassigned }}'
events:
onItemDrop:
- workflow:
workflowId: assign-move
inputs: { moveId: '{{ dragItem.orderMoveId }}', driverId: '{{ row.record.contactId }}' }
The event receives dragItem, dragType, row, column, rowIndex, and columnIndex. Eligible cells may expose isDragArmed and isDragOver to emptyCellTemplate. Native drag is desktop-only, so retain an onCellClick equivalent for touch and keyboard users.