CourseKitExamples
React Schedule App
Frontend example with TimetableGrid, hooks, and conflict checking
Overview
This example builds a complete schedule viewer with event creation, conflict checking, and availability display using @hfu.digital/coursekit-react.
Provider Setup
// App.tsx
import { CourseKitProvider } from '@hfu.digital/coursekit-react';
function App() {
const token = useAuthToken();
return (
<CourseKitProvider
apiUrl="/api/coursekit"
fetch={(url, init) => fetch(url, {
...init,
headers: { ...init?.headers, Authorization: `Bearer ${token}` },
})}
>
<SchedulePage />
</CourseKitProvider>
);
}Week Schedule View
// SchedulePage.tsx
import { useState } from 'react';
import {
useTimetable,
useAvailability,
TimetableGrid,
EventCard,
AvailabilityOverlay,
} from '@hfu.digital/coursekit-react';
function SchedulePage() {
const [weekStart, setWeekStart] = useState('2026-03-02');
const weekEnd = getWeekEnd(weekStart); // helper to add 5 days
const { data: occurrences, loading } = useTimetable({
dateRange: { start: weekStart, end: weekEnd },
});
const { slots } = useAvailability({
entityType: 'instructor',
entityId: 'current-user',
dateRange: { start: weekStart, end: weekEnd },
});
if (loading) return <div>Loading schedule...</div>;
// Map occurrences to grid events
const gridEvents = occurrences.map(occ => ({
id: `${occ.eventId}-${occ.occurrenceDate}`,
title: occ.originalEvent.title,
startTime: occ.startTime,
durationMin: occ.durationMin,
dayIndex: getDayIndex(occ.startTime, weekStart), // 0=Mon, 4=Fri
}));
// Map availability to overlay blocks
const availabilityBlocks = slots
.filter(s => s.type === 'blocked')
.map(s => ({
startTime: s.startTime,
endTime: s.endTime,
dayIndex: getDayIndex(s.startTime, weekStart),
type: s.type as 'blocked',
hardness: s.hardness as 'hard' | 'soft',
}));
return (
<div>
<WeekNav weekStart={weekStart} onChange={setWeekStart} />
<div style={{ position: 'relative' }}>
<TimetableGrid
weekStart={weekStart}
startHour={8}
endHour={18}
events={gridEvents}
renderEvent={(event) => {
const occ = occurrences.find(
o => `${o.eventId}-${o.occurrenceDate}` === event.id,
);
return (
<EventCard
title={event.title}
startTime={event.startTime}
durationMin={event.durationMin}
isException={occ?.isException}
exceptionType={occ?.exceptionType}
/>
);
}}
/>
<AvailabilityOverlay blocks={availabilityBlocks} startHour={8} />
</div>
</div>
);
}Event Creator with Conflict Preview
// EventCreator.tsx
import { useState } from 'react';
import { useConflictCheck, useMutation, ConflictBadge } from '@hfu.digital/coursekit-react';
function EventCreator() {
const [title, setTitle] = useState('');
const [startTime, setStartTime] = useState('');
const [durationMin, setDurationMin] = useState(90);
const [roomId, setRoomId] = useState('');
const { result: conflicts, loading: checking, check } = useConflictCheck();
const { createEvent, loading: saving } = useMutation({
onSuccess: () => alert('Event created!'),
});
const handlePreview = async () => {
await check(
{ title, startTime, durationMin, roomId: roomId || null },
{ start: startTime, end: addMonths(startTime, 4) },
);
};
const handleCreate = async () => {
await createEvent({
title,
startTime,
durationMin,
roomId: roomId || null,
});
};
return (
<form onSubmit={(e) => { e.preventDefault(); handleCreate(); }}>
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="Event title" />
<input type="datetime-local" value={startTime} onChange={e => setStartTime(e.target.value)} />
<input type="number" value={durationMin} onChange={e => setDurationMin(+e.target.value)} />
<input value={roomId} onChange={e => setRoomId(e.target.value)} placeholder="Room ID" />
<button type="button" onClick={handlePreview} disabled={checking}>
Check Conflicts
</button>
{conflicts && (
<div>
{conflicts.conflicts.map(c => (
<ConflictBadge
key={c.id}
severity={c.severity}
message={c.message}
type={c.type}
/>
))}
{conflicts.conflicts.length === 0 && <p>No conflicts detected</p>}
</div>
)}
<button type="submit" disabled={saving || conflicts?.hasErrors}>
Create Event
</button>
</form>
);
}Room Finder
// RoomFinder.tsx
import { useRoomSearch } from '@hfu.digital/coursekit-react';
function RoomFinder({ startTime, endTime }: { startTime: string; endTime: string }) {
const { rooms, loading, search } = useRoomSearch();
return (
<div>
<button onClick={() => search({
minCapacity: 30,
availableAt: { start: startTime, end: endTime },
})}>
Find Available Rooms
</button>
{loading && <p>Searching...</p>}
{rooms.map(room => (
<div key={room.id}>
<strong>{room.name}</strong>
{room.building && <span> — {room.building}</span>}
<span> (capacity: {room.capacity})</span>
</div>
))}
</div>
);
}Helper Functions
function getWeekEnd(weekStart: string): string {
const date = new Date(weekStart);
date.setDate(date.getDate() + 5);
return date.toISOString().split('T')[0];
}
function getDayIndex(dateStr: string, weekStart: string): number {
const date = new Date(dateStr);
const start = new Date(weekStart);
return Math.floor((date.getTime() - start.getTime()) / (24 * 60 * 60 * 1000));
}
function addMonths(dateStr: string, months: number): string {
const date = new Date(dateStr);
date.setMonth(date.getMonth() + months);
return date.toISOString();
}