Room booking
Rooms are the rows, half hours are the columns. Selecting free time opens your own form, and booked time cannot be selected, moved onto or resized into.
The code
Section titled “The code”export function Rooms() { const [rooms, setRooms] = useState<Resource[]>(initialRooms); const [draft, setDraft] = useState<SlotSelection | null>(null);
return ( <> {draft && ( <BookingForm slot={draft} onCancel={() => setDraft(null)} onBook={(title) => { setRooms((prev) => prev.map((r) => r.id === draft.resourceId ? { ...r, events: [...r.events, { id: crypto.randomUUID(), title, startDate: draft.start, endDate: draft.end }] } : r ) ); setDraft(null); }} /> )}
<ResourceScheduler resources={rooms} initialView={ViewType.Day} allowViewChange={false} slotDuration={30} dayStartHour={8} dayEndHour={18} ariaLabel="Meeting room bookings" eventOverlap={false} // no double booking, anywhere onSlotSelect={setDraft} // your form, not ours onEventDrop={moveBooking} onEventResize={resizeBooking} /> </> );}Make it real
Section titled “Make it real”- Enforce on the server too. The browser check is a courtesy. Run
isPlacementAllowed(or your own query) before saving, and return an error if someone else booked first. - Business hours.
dayStartHouranddayEndHourhide the night, andisValidDropcan reject anything outside opening hours or on holidays. - Capacity or equipment. Put the rule in
isValidDrop: it receives the event and the targetresourceId. Equipment rental shows a stock count enforced this way. - Recurring bookings are built in: give an event a
recurrenceand the scheduler draws each occurrence. Shift roster uses it for a weekly rota.