Skip to main content

Timeline Grid Component

Introduction

The Timeline Grid Component renders a scheduling grid with time on the horizontal axis and resources on the vertical axis. It is designed for allocation, dispatch, and capacity planning scenarios where you need to place and manipulate time spans (items) across multiple resources.

Unlike timeline, which focuses on a linear sequence of events, timelineGrid provides a matrix-like layout with rows (resources such as drivers, vehicles, docks, lanes, or teams) and time columns (hours/days/weeks). It supports virtualization for large datasets, range-based server loading, and rich interactions such as drag, drop, resize, and reassignment between resources.

Common use cases in CargoXplorer TMS:

  • Vehicle/driver assignment board
  • Dock and door scheduling by shift
  • Warehouse lane/zone utilization planning
  • Yard slot allocation
  • Technician/maintenance work orders by bay

YAML Structure

component: timelineGrid
name: vehicleScheduler
props:
view: "day" # hour | day | week | month (see timescale below)
startDate: "{{ startDate }}" # optional; initial lower bound (ISO or expression)
endDate: "{{ endDate }}" # optional; initial upper bound (ISO or expression)
defaultDate: "{{ today() }}" # initial focal date when no explicit range
options:
height: "600px" # px, %, vh
rowHeight: 44 # px per row
timescale: "hour" # hour | day | week | month
timeStepMinutes: 30 # grid cell resolution when timescale=hour
snapToMinutes: 15 # drag/resize snap increment
showNowMarker: true # show current time line
showNonWorking: true # shade non-working hours/days
workingHours: ["06:00", "22:00"] # daily working window (local time)
weekendDays: [6, 7] # 1=Mon ... 7=Sun
allowOverlap: false # allow items to overlap in the same row
editable:
move: true # drag to move
resize: true # resize start/end
reassign: true # drag between rows to reassign resource
virtualization: true # virtualize rows for large datasets
stickyHeaders: true # keep time header and row headers sticky
rowTemplate: # optional; custom render for row header cell
component: layout
props:
orientation: horizontal
children:
- component: text
props: { type: "strong", value: "{{ row.label }}" }
- component: badge
if: "{{ row.status }}"
props: { value: "{{ row.status }}" }
itemTemplate: # how each time span renders in the grid
component: card
props:
title: "{{ item.title }}"
description: "{{ item.description }}"
backgroundColor: "{{ item.color || '#3788d8' }}"
textColor: "white"

# Data sources
rowSources: # load resources (rows) for the visible range
- query:
command: >-
query GetVehicles($organizationId: Int!) {
vehicles(organizationId: $organizationId) { id code status type }
}
variables:
organizationId: "{{ number organizationId }}"
path: vehicles
mapping:
rowId: "{{ item.id }}" # required
label: "{{ item.code }}" # required
status: "{{ item.status }}" # optional
type: "{{ item.type }}" # optional

itemSources: # load items (spans) for the visible window
- query:
command: >-
query VehicleAssignments($organizationId: Int!, $start: String!, $end: String!) {
vehicleAssignments(organizationId: $organizationId, start: $start, end: $end) {
id vehicleId start end shipmentId status priority
}
}
variables:
organizationId: "{{ number organizationId }}"
start: "{{ startDate }}"
end: "{{ endDate }}"
path: vehicleAssignments
mapping:
id: "{{ item.id }}" # required
rowId: "{{ item.vehicleId }}" # required; resource association
start: "{{ item.start }}" # required ISO
end: "{{ item.end }}" # required ISO
title: "Shipment {{ item.shipmentId }}" # required title
status: "{{ item.status }}"
priority: "{{ item.priority }}"
color: "{{ eval item.status === 'delayed' ? '#e53e3e' : (item.priority === 'high' ? '#ffa726' : '#4ecdc4') }}"

# Interaction events
events:
onItemClick:
- navigate:
to: "/shipments/{{ event.item.shipmentId }}"
onItemMove: # after drag/drop move (same row)
- mutation:
command: >-
mutation UpdateSchedule($id: ID!, $start: String!, $end: String!) {
updateVehicleAssignment(id: $id, start: $start, end: $end) { id start end }
}
variables:
id: "{{ event.item.id }}"
start: "{{ event.newStart }}"
end: "{{ event.newEnd }}"
onSuccess:
- notification: { message: "Schedule updated" }
onError:
- notification:
{ message: "Failed to update schedule", variant: "error" }
onItemReassign: # after drag to a different row
- mutation:
command: >-
mutation Reassign($id: ID!, $vehicleId: ID!, $start: String!, $end: String!) {
reassignVehicle(id: $id, vehicleId: $vehicleId, start: $start, end: $end) { id vehicleId start end }
}
variables:
id: "{{ event.item.id }}"
vehicleId: "{{ event.newRowId }}"
start: "{{ event.newStart }}"
end: "{{ event.newEnd }}"
onSuccess:
- notification: { message: "Reassigned" }
onError:
- notification: { message: "Failed to reassign", variant: "error" }
onRangeChange: # visible window changed by scroll/zoom/nav
- setVariables:
windowStart: "{{ event.start }}"
windowEnd: "{{ event.end }}"
onEmptySlotClick: # click on empty time cell
- dialog:
name: "createAssignment"
props:
rowId: "{{ event.row.rowId }}"
start: "{{ event.start }}"
end: "{{ event.end }}"

Attribute Description

Root

  • component (string, required): Must be timelineGrid.
  • name (string, optional): Component instance name for referencing in actions/variables.
  • props (object, required): Grid configuration.
  • rowSources (array, required): Data providers for resources (rows).
  • itemSources (array, required): Data providers for time spans (items).
  • events (object, optional): Interaction handlers.

Props

AttributeTypeRequiredDefaultDescription
viewenumNodayZoom level preset. Works with timescale.
startDatetemplateExpressionNoInitial lower bound for visible window (ISO or expression).
endDatetemplateExpressionNoInitial upper bound for visible window.
defaultDatetemplateExpressionNo{{ today() }}Initial focal date when range not provided.
optionsobjectNosee belowVisual/behavioral options.
rowTemplatecomponentDefinitionNodefault labelTemplate for row header cell. Receives row.
itemTemplatecomponentDefinitionNocardTemplate for items. Receives item.

Options

AttributeTypeDefaultDescription
heightstring|number600pxGrid height.
rowHeightnumber44Row height in pixels.
timescaleenumhourTime unit on X-axis: hour, day, week, month.
timeStepMinutesnumber30Cell width resolution when timescale=hour.
snapToMinutesnumber15Drag/resize snap step.
showNowMarkerbooleantrueDisplays current time marker line.
showNonWorkingbooleantrueShades non-working periods.
workingHours[string,string]["06:00","22:00"]Daily working period.
weekendDaysnumber[][6,7]Weekend day numbers (1=Mon..7=Sun).
allowOverlapbooleanfalseAllow overlaps within the same row.
editable.movebooleantrueEnable drag move.
editable.resizebooleantrueEnable resize.
editable.reassignbooleantrueEnable row reassignment by drag.
virtualizationbooleantrueVirtualize rows for performance.
stickyHeadersbooleantrueSticky time header and row headers.

Row Sources

Each entry defines how to fetch rows (resources) and map them to the grid.

AttributeTypeRequiredDescription
queryobjectYesGraphQL query configuration.
query.commandstringYesGraphQL document string.
query.variablesobjectNoVariables for query. Template expressions supported.
pathstringYesDot path to array of rows in response.
mappingobjectYesMapping from source to row fields.

Required row mapping fields:

  • rowId (string|number): Unique row identifier.
  • label (string): Row display label.

Common optional row fields:

  • group (string): Group name for collapsible grouping.
  • status (string), type (string), color (string): Extra row metadata available to rowTemplate.

Item Sources

Each entry describes how to load time spans within the current visible window.

AttributeTypeRequiredDescription
queryobjectYesGraphQL query configuration.
query.commandstringYesGraphQL document string.
query.variablesobjectYesShould include start and end for window scoping.
pathstringYesDot path to array of items in response.
mappingobjectYesMapping from source to item fields.

Required item mapping fields:

  • id (string|number): Unique item identifier.
  • rowId (string|number): Row association.
  • start (string, ISO): Start datetime.
  • end (string, ISO): End datetime.
  • title (string): Display title.

Common optional item fields:

  • description (string), status (string), priority (string|number)
  • color (string), textColor (string), className (string)
  • Any mapped fields are available via {{ item.<prop> }} in itemTemplate.

Events

Available event handlers with typical payloads:

  • onItemClick(event)
    • Payload: { item }
  • onItemMove(event)
    • When an item is moved within the same row
    • Payload: { item, oldStart, oldEnd, newStart, newEnd }
  • onItemResize(event)
    • Payload: { item, oldStart, oldEnd, newStart, newEnd }
  • onItemReassign(event)
    • When an item is dragged to a different row
    • Payload: { item, oldRowId, newRowId, oldStart, oldEnd, newStart, newEnd }
  • onRangeChange(event)
    • Visible window changed by navigation/zoom/scroll
    • Payload: { start, end, timescale, view }
  • onEmptySlotClick(event)
    • Clicked an empty cell to create new item
    • Payload: { row, start, end }
  • onLoading(event)
    • Data loading state changed
    • Payload: { isLoading }

Examples

1) Daily Vehicle Schedule with Drag/Drop

component: timelineGrid
name: dailyVehicleSchedule
props:
view: "day"
options:
height: "560px"
timescale: "hour"
timeStepMinutes: 30
snapToMinutes: 15
showNowMarker: true
editable: { move: true, resize: true, reassign: true }
rowSources:
- query:
command: "query($org: Int!){ vehicles(organizationId:$org){ id code status } }"
variables:
org: "{{ number organizationId }}"
path: vehicles
mapping:
rowId: "{{ item.id }}"
label: "{{ item.code }}"
status: "{{ item.status }}"
itemSources:
- query:
command: "query($org:Int!,$start:String!,$end:String!){ assignments(organizationId:$org,start:$start,end:$end){ id vehicleId start end shipmentId status } }"
variables:
org: "{{ number organizationId }}"
start: "{{ startDate }}"
end: "{{ endDate }}"
path: assignments
mapping:
id: "{{ item.id }}"
rowId: "{{ item.vehicleId }}"
start: "{{ item.start }}"
end: "{{ item.end }}"
title: "Shipment {{ item.shipmentId }}"
status: "{{ item.status }}"
color: "{{ eval item.status === 'delayed' ? '#e53e3e' : '#4ecdc4' }}"
events:
onItemMove:
- mutation:
command: "mutation($id:ID!,$start:String!,$end:String!){ updateAssignment(id:$id,start:$start,end:$end){ id } }"
variables:
id: "{{ event.item.id }}"
start: "{{ event.newStart }}"
end: "{{ event.newEnd }}"
onSuccess:
- notification: { message: "Updated" }

2) Weekly Dock Scheduling with Non-Working Shading

component: timelineGrid
props:
view: "week"
options:
timescale: "day"
showNonWorking: true
weekendDays: [6, 7]
rowSources:
- query:
command: "query($org:Int!){ docks(organizationId:$org){ id name } }"
variables:
org: "{{ number organizationId }}"
path: docks
mapping:
rowId: "{{ item.id }}"
label: "{{ item.name }}"
itemSources:
- query:
command: "query($org:Int!,$start:String!,$end:String!){ dockBookings(organizationId:$org,start:$start,end:$end){ id dockId start end reference } }"
variables:
org: "{{ number organizationId }}"
start: "{{ startDate }}"
end: "{{ endDate }}"
path: dockBookings
mapping:
id: "{{ item.id }}"
rowId: "{{ item.dockId }}"
start: "{{ item.start }}"
end: "{{ item.end }}"
title: "{{ item.reference }}"

3) Grouped Rows (Zones with Lanes)

component: timelineGrid
props:
options:
rowHeight: 38
rowSources:
- query:
command: >-
query($org:Int!){ zones(organizationId:$org){ id name lanes{ id name } } }
variables:
org: "{{ number organizationId }}"
path: zones
mapping:
# parent group rows
# Emit one row for zone label (optional) and then children rows for lanes
# Example: map lanes with group = zone.name
rows: "{{ eval item.lanes.map(l=>({ rowId:l.id, label:l.name, group:item.name })) }}"
itemSources:
- query:
command: "query($org:Int!,$start:String!,$end:String!){ laneTasks(organizationId:$org,start:$start,end:$end){ id laneId start end title } }"
variables:
org: "{{ number organizationId }}"
start: "{{ startDate }}"
end: "{{ endDate }}"
path: laneTasks
mapping:
id: "{{ item.id }}"
rowId: "{{ item.laneId }}"
start: "{{ item.start }}"
end: "{{ item.end }}"
title: "{{ item.title }}"

4) Create-on-Empty Slot

events:
onEmptySlotClick:
- dialog:
name: "createTask"
props:
rowId: "{{ event.row.rowId }}"
start: "{{ event.start }}"
end: "{{ event.end }}"
onClose:
- if: "{{ result.success }}"
then:
- refresh: "timelineGrid"

Best Practices

  • Performance
    • Scope queries by start/end (visible window) and use pagination where available.
    • Enable virtualization for large row counts; avoid overly complex itemTemplate trees.
  • Data modeling
    • Use stable rowId and id values to ensure smooth updates and re-rendering.
    • Normalize working hours and non-working periods on the server when possible.
  • Scheduling constraints
    • Enforce no-overlap rules on the server even if allowOverlap is false on the client.
    • Validate reassignments (permissions, capacity) in mutations; return errors with actionable messages.
  • Time and timezone
    • Store and transmit ISO 8601 strings; agree on UTC vs local strategy across services.
    • Be consistent with snapToMinutes and server rounding to prevent drift.
  • UX and accessibility
    • Use color and concise titles to communicate status; ensure sufficient contrast.
    • Keep row headers informative (code + key attributes) via rowTemplate.