refactor: Open Day schedule — hall selector instead of full grid
Replace wide multi-column hall grid with hall selector tabs + single column time slots. Each hall tab shows class count badge. Scales better as more halls are added.
This commit is contained in:
@@ -292,25 +292,32 @@ function ScheduleGrid({
|
||||
styles: string[];
|
||||
onClassesChange: () => void;
|
||||
}) {
|
||||
const [selectedHall, setSelectedHall] = useState(halls[0] ?? "");
|
||||
const timeSlots = generateTimeSlots(10, 22);
|
||||
|
||||
// Build lookup: hall -> time -> class
|
||||
const grid = useMemo(() => {
|
||||
const map: Record<string, Record<string, OpenDayClass>> = {};
|
||||
for (const hall of halls) map[hall] = {};
|
||||
// Build lookup: time -> class for selected hall
|
||||
const hallClasses = useMemo(() => {
|
||||
const map: Record<string, OpenDayClass> = {};
|
||||
for (const cls of classes) {
|
||||
if (!map[cls.hall]) map[cls.hall] = {};
|
||||
map[cls.hall][cls.startTime] = cls;
|
||||
if (cls.hall === selectedHall) map[cls.startTime] = cls;
|
||||
}
|
||||
return map;
|
||||
}, [classes, selectedHall]);
|
||||
|
||||
// Count classes per hall for the tab badges
|
||||
const hallCounts = useMemo(() => {
|
||||
const counts: Record<string, number> = {};
|
||||
for (const hall of halls) counts[hall] = 0;
|
||||
for (const cls of classes) counts[cls.hall] = (counts[cls.hall] || 0) + 1;
|
||||
return counts;
|
||||
}, [classes, halls]);
|
||||
|
||||
async function addClass(hall: string, startTime: string) {
|
||||
async function addClass(startTime: string) {
|
||||
const endTime = addHour(startTime);
|
||||
await adminFetch("/api/admin/open-day/classes", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ eventId, hall, startTime, endTime, trainer: "—", style: "—" }),
|
||||
body: JSON.stringify({ eventId, hall: selectedHall, startTime, endTime, trainer: "—", style: "—" }),
|
||||
});
|
||||
onClassesChange();
|
||||
}
|
||||
@@ -342,26 +349,37 @@ function ScheduleGrid({
|
||||
{halls.length === 0 ? (
|
||||
<p className="text-sm text-neutral-500">Нет залов в расписании. Добавьте локации в разделе «Расписание».</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse min-w-[500px]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left text-xs text-neutral-500 font-medium pb-2 w-16">Время</th>
|
||||
<>
|
||||
{/* Hall selector */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{halls.map((hall) => (
|
||||
<th key={hall} className="text-left text-xs text-neutral-400 font-medium pb-2 px-1">
|
||||
<button
|
||||
key={hall}
|
||||
onClick={() => setSelectedHall(hall)}
|
||||
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
|
||||
selectedHall === hall
|
||||
? "bg-gold/20 text-gold border border-gold/40"
|
||||
: "bg-neutral-800 text-neutral-400 border border-white/10 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{hall}
|
||||
</th>
|
||||
{hallCounts[hall] > 0 && (
|
||||
<span className={`ml-1.5 ${selectedHall === hall ? "text-gold/60" : "text-neutral-600"}`}>
|
||||
{hallCounts[hall]}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timeSlots.map((time) => (
|
||||
<tr key={time} className="border-t border-white/5">
|
||||
<td className="text-xs text-neutral-500 py-1 pr-2 align-top pt-2">{time}</td>
|
||||
{halls.map((hall) => {
|
||||
const cls = grid[hall]?.[time];
|
||||
</div>
|
||||
|
||||
{/* Time slots for selected hall */}
|
||||
<div className="space-y-1">
|
||||
{timeSlots.map((time) => {
|
||||
const cls = hallClasses[time];
|
||||
return (
|
||||
<td key={hall} className="py-1 px-1 align-top">
|
||||
<div key={time} className="flex items-start gap-3 border-t border-white/5 py-1.5">
|
||||
<span className="text-xs text-neutral-500 w-12 pt-1.5 shrink-0">{time}</span>
|
||||
<div className="flex-1">
|
||||
{cls ? (
|
||||
<ClassCell
|
||||
cls={cls}
|
||||
@@ -374,20 +392,18 @@ function ScheduleGrid({
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => addClass(hall, time)}
|
||||
onClick={() => addClass(time)}
|
||||
className="w-full rounded-lg border border-dashed border-white/5 p-2 text-neutral-600 hover:text-gold hover:border-gold/20 transition-colors"
|
||||
>
|
||||
<Plus size={12} className="mx-auto" />
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user