react-resource-view
Calendar & timeline
Laying a collection out over time.
Both lay the same collection out along a time axis, and both need to be told which fields carry the dates — there is no naming convention to guess at.
Calendar#
A day, a week or a month. Records with an end date are drawn as blocks spanning their duration; without one they sit at a point in time.
import { calendarViewOptionFactory } from "react-resource-view"
viewVariants: [
calendarViewOptionFactory({
dateKey: "startAt", // when it starts
endDateKey: "endAt", // when it ends — omit for point-in-time events
titleKey: "title", // what is written on the event
colorKey: "track", // one colour per distinct value
mode: "week", // "day" | "week" | "month"
hourStart: 8,
hourEnd: 18,
}),
]A week of conference sessions
| Name | Type | Default | Description |
|---|---|---|---|
dateKey | string | "dueDate" | The field the event is placed by. Almost always worth setting. |
endDateKey | string | — | Makes the event a block rather than a point. |
titleKey | string | — | What is written on the event. |
colorKey | string | — | One colour per distinct value of this field. |
mode | "day" | "week" | "month" | "month" | The span on screen. |
hourStart / hourEnd | number | 7 / 21 | The vertical window in day and week modes — no point drawing the small hours. |
getIcon | (row) => LucideIcon | undefined | — | An icon per event, resolved by the resource so the package stays generic. |
Timeline#
Time along the top, one band per resource down the side: rooms, staff, vehicles, machines. Where a calendar answers “what is happening on Tuesday”, a timeline answers “is room B free on Tuesday”.
import { timelineViewOptionFactory } from "react-resource-view"
viewVariants: [
timelineViewOptionFactory<Session>({
startDateKey: "startAt",
endDateKey: "endAt",
titleKey: "title",
groupKey: "room", // one band per room
groupsLabel: "Rooms", // header of the left-hand column
statusKey: "status",
colorByStatus: { confirmed: "#3b82f6", hold: "#f59e0b" },
daysToShow: 5,
showUnassigned: false,
}),
]The same sessions, banded by room
| Name | Type | Default | Description |
|---|---|---|---|
startDateKey / endDateKey | string | "startDate" / "endDate" | The span each band segment covers. |
groupKey | string | — | The field whose distinct values become the bands. |
groupsLabel | string | — | Header of the left-hand column — “Rooms”, “Staff”. |
statusKey / colorByStatus | string / Record<string, string> | — | Colours a segment by a status field. |
daysToShow | number | 14 | How wide the window is. |
showUnassigned | boolean | true | A band collecting records with no group. unassignedLabel names it. |
resolveGroups / resolveGroupForRow | functions | — | Bands computed rather than derived. See below. |
Bands that are not just a key#
groupKey derives the bands from the records, which means an empty room does not exist. When the bands are a known set — every room, every engineer, whether or not anything is booked — resolve them instead.
timelineViewOptionFactory<Booking>({
// The bands, when they are not simply the distinct values of a key —
// every room, including those with nothing booked in them.
resolveGroups: () => rooms.map((room) => ({
id: room["@id"],
label: room.name,
sublabel: `${room.capacity} seats`,
})),
// Which band a record belongs in.
resolveGroupForRow: (booking) => ({
id: booking.room,
label: roomsById[booking.room]?.name ?? "Unknown",
}),
})An empty band is information
A scheduling screen is read to find the gap. Derived bands hide exactly the rows a planner is looking for.
Locale#
Month names, day headers and the day a week starts on all come from the dateLocale port — Sunday in en-US, Monday in French — so one call settles both layouts.
import { fr } from "date-fns/locale"
import { configurePorts } from "react-resource-view"
// Month names, day headers, and the day a week starts on.
configurePorts({ dateLocale: fr })Which one, when#
- Calendar when the question is about a moment: what is on today, is that week busy.
- Timeline when the question is about a resource: is this room free, is that person double-booked.
- Declare both. They are two entries in
viewVariantsand the reader switches between them — as with any other layout.