Skip to content

TimeGrid Views

A TimeGrid is an editable spreadsheet-style entry grid: rows × navigable time-period columns, with a single numeric value typed into each cell. It’s the classic “type your hours/amounts against periods” surface — timesheets (task × day → hours), budgets (account × month → amount), demand forecasts (product × week → quantity).

Unlike a TimelineHeatmap (read-only, shaded) or a computed data_method board, every cell is a real record of the view’s model. The grid reads them with the standard record query and creates / updates / deletes them as you type — no read model, no data_method. It’s ordinary record CRUD laid out as a matrix, with period navigation (prev / today / next) and running row/column totals.

  • Rows — one per distinct value of a relation field on the model (row.field). New rows are added from an in-view picker of that relation’s records.
  • Columns — a date field (period.field) bucketed into day / week / month (period.bucket) over a fixed window, navigable by whole windows.
  • Cell — a single numeric measure (measure.field). Typing a value upserts one record for that (row, period); clearing it deletes the record.

Any extra dimension (whose timesheet? which warehouse’s forecast?) is not a column — it belongs in the action’s default filter (e.g. a “my timesheets” filter scoping to the current user), applied server-side like any other view.

- data_type: UiView
name: timesheet_grid_view
identifier: timesheet_grid_view
type: TimeGrid
model: Timesheet # the fact model — one record per non-empty cell
arch:
- row:
field: task # relation on the model; each value is a row
add_label: Add task # placeholder for the row picker
period:
field: date # date field bucketed into the columns
bucket: day # day | week | month
window: 7 # number of periods visible at once (7 days = a week)
measure:
field: hours # the numeric field typed into cells

A cell is read-only when any record in it is frozen by the model’s _controlled_edits rules, and its tooltip shows that rule’s message. There is nothing to declare in the view — the grid reads the same rules the backend enforces on write and that List and Form views already render read-only from, so a freeze is described once, on the model, and every surface agrees.

Put TimeGrid in the action’s modes (usually alongside List and Form) so users can switch between the grid and a plain list:

- data_type: WindowAction
name: Weekly Grid
identifier: timesheet_grid_action
model: Timesheet
default_view: timesheet_grid_view
modes: TimeGrid, List, Form
search_view: timesheet_search_view
action_ctx:
view:
filters: [my_timesheets_filter] # scope to the current user, applied server-side
views:
- - R
- - timesheet_grid_view
- timesheet_list_view
- timesheet_form_view

The component is generic — only the arch changes per use case:

Use caserow.fieldperiodmeasure.field
Timesheetstaskdate · day · window 7hours
Budgetingaccountperiod · month · window 12amount
Forecastingproductdate · week · window 13quantity
  • A cell shows the sum of its records; editing reconciles the cell to the single typed value. Cells with multiple underlying records are read-only in the grid (edit them on the record) so nothing is silently merged.
  • The grid participates in the standard search/filter system, so the action’s default filter (and any the user applies) scope which rows load — there is no Browse panel or grouping, since the row/period axes are fixed by the arch.