Some checks failed
app-ci-deploy / test-build-deploy (push) Has been cancelled
GET /database/timetable/timetables returns 404 (endpoint not implemented). Treat any fetch error as empty state so the page renders gracefully rather than showing error UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
import React, { useEffect } from 'react';
|
||
import { Link } from 'react-router-dom';
|
||
import { CalendarToday, KeyboardArrowRight } from '@mui/icons-material';
|
||
import useTimetableStore from '../../stores/timetableStore';
|
||
import { format, parseISO } from 'date-fns';
|
||
|
||
const TimetableListPage: React.FC = () => {
|
||
const { timetables, timetablesLoading, timetablesError, fetchTimetables } = useTimetableStore();
|
||
|
||
useEffect(() => {
|
||
fetchTimetables();
|
||
}, [fetchTimetables]);
|
||
|
||
if (timetablesLoading) {
|
||
return (
|
||
<div className="flex justify-center items-center h-64">
|
||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Treat fetch errors (including 404 — endpoint not yet available) as empty state
|
||
const showEmpty = timetablesError || timetables.length === 0;
|
||
|
||
return (
|
||
<div className="container mx-auto px-4 py-6 max-w-4xl">
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div>
|
||
<h1 className="text-3xl font-bold text-gray-900">Timetables</h1>
|
||
<p className="mt-1 text-gray-500">Your scheduled timetables</p>
|
||
</div>
|
||
</div>
|
||
|
||
{showEmpty ? (
|
||
<div className="text-center py-16">
|
||
<CalendarToday sx={{ fontSize: 48 }} className="mx-auto mb-4 text-gray-300" />
|
||
<h3 className="text-lg font-medium text-gray-900">No timetables yet</h3>
|
||
<p className="mt-2 text-gray-500">
|
||
Timetables are created from a class. Go to{' '}
|
||
<Link to="/classes" className="text-blue-600 hover:underline">Classes</Link>{' '}
|
||
to get started.
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{timetables.map((timetable) => (
|
||
<Link
|
||
key={timetable.id}
|
||
to={`/timetable/${timetable.id}`}
|
||
className="flex items-center gap-4 p-4 bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-shadow"
|
||
>
|
||
<CalendarToday className="text-blue-500 flex-shrink-0" />
|
||
<div className="flex-1 min-w-0">
|
||
<h3 className="font-semibold text-gray-900 truncate">{timetable.name}</h3>
|
||
<p className="text-sm text-gray-500">
|
||
{format(parseISO(timetable.start_date), 'MMM d, yyyy')}
|
||
{timetable.end_date
|
||
? ` – ${format(parseISO(timetable.end_date), 'MMM d, yyyy')}`
|
||
: ' (ongoing)'}
|
||
</p>
|
||
</div>
|
||
{timetable.recurrence_rule && (
|
||
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs font-medium rounded-full flex-shrink-0">
|
||
Recurring
|
||
</span>
|
||
)}
|
||
<KeyboardArrowRight className="text-gray-400 flex-shrink-0" />
|
||
</Link>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default TimetableListPage;
|