256 lines
7.7 KiB
TypeScript
256 lines
7.7 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
createRaceAction,
|
|
deleteRaceAction,
|
|
publishScheduleAction,
|
|
unpublishScheduleAction,
|
|
updateRaceAction
|
|
} from '@/app/actions/leagueScheduleActions';
|
|
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
|
import { ConfirmDialog } from '@/ui/ConfirmDialog';
|
|
import {
|
|
useLeagueAdminSchedule,
|
|
useLeagueAdminStatus,
|
|
useLeagueSeasons
|
|
} from "@/hooks/league/useLeagueScheduleAdminPageData";
|
|
import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId";
|
|
import { RaceScheduleCommandModel } from '@/lib/command-models/leagues/RaceScheduleCommandModel';
|
|
import { LeagueAdminScheduleTemplate } from '@/templates/LeagueAdminScheduleTemplate';
|
|
import { Box } from '@/ui/Box';
|
|
import { Card } from '@/ui/Card';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
import { useParams, useRouter } from 'next/navigation';
|
|
import { useState } from 'react';
|
|
|
|
export function LeagueAdminSchedulePageClient() {
|
|
const params = useParams();
|
|
const leagueId = params.id as string;
|
|
const currentDriverId = useEffectiveDriverId() || '';
|
|
const router = useRouter();
|
|
|
|
// Form state
|
|
const [seasonId, setSeasonId] = useState<string>('');
|
|
const [form, setForm] = useState(() => new RaceScheduleCommandModel());
|
|
const [editingRaceId, setEditingRaceId] = useState<string | null>(null);
|
|
|
|
// Action state
|
|
const [isPublishing, setIsPublishing] = useState(false);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [deletingRaceId, setDeletingRaceId] = useState<string | null>(null);
|
|
const [raceToDelete, setRaceToDelete] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Check admin status using domain hook
|
|
const { data: isAdmin, isLoading: isAdminLoading } = useLeagueAdminStatus(leagueId, currentDriverId);
|
|
|
|
// Load seasons using domain hook
|
|
const { data: seasonsData, isLoading: seasonsLoading } = useLeagueSeasons(leagueId, !!isAdmin);
|
|
|
|
// Auto-select season
|
|
const selectedSeasonId = seasonId || (seasonsData && seasonsData.length > 0
|
|
? (seasonsData.find((s) => s.status === 'active') ?? seasonsData[0])?.seasonId || ''
|
|
: '');
|
|
|
|
// Load schedule using domain hook
|
|
const { data: schedule, isLoading: scheduleLoading } = useLeagueAdminSchedule(leagueId, selectedSeasonId, !!isAdmin);
|
|
|
|
// Handlers
|
|
const handleSeasonChange = (newSeasonId: string) => {
|
|
setSeasonId(newSeasonId);
|
|
setEditingRaceId(null);
|
|
setForm(new RaceScheduleCommandModel());
|
|
};
|
|
|
|
const handlePublishToggle = async () => {
|
|
if (!schedule || !selectedSeasonId) return;
|
|
|
|
setIsPublishing(true);
|
|
setError(null);
|
|
try {
|
|
const result = schedule.published
|
|
? await unpublishScheduleAction(leagueId, selectedSeasonId)
|
|
: await publishScheduleAction(leagueId, selectedSeasonId);
|
|
|
|
if (result.isOk()) {
|
|
router.refresh();
|
|
} else {
|
|
setError(result.getError());
|
|
}
|
|
} finally {
|
|
setIsPublishing(false);
|
|
}
|
|
};
|
|
|
|
const handleAddOrSave = async () => {
|
|
if (!selectedSeasonId) return;
|
|
|
|
const validationErrors = form.validate();
|
|
if (Object.keys(validationErrors).length > 0) {
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
const result = !editingRaceId
|
|
? await createRaceAction(leagueId, selectedSeasonId, form.toCommand())
|
|
: await updateRaceAction(leagueId, selectedSeasonId, editingRaceId, form.toCommand());
|
|
|
|
if (result.isOk()) {
|
|
// Reset form
|
|
setForm(new RaceScheduleCommandModel());
|
|
setEditingRaceId(null);
|
|
router.refresh();
|
|
} else {
|
|
setError(result.getError());
|
|
}
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleEdit = (raceId: string) => {
|
|
if (!schedule) return;
|
|
const race = schedule.races.find((r) => r.id === raceId);
|
|
if (!race) return;
|
|
|
|
setEditingRaceId(raceId);
|
|
setForm(new RaceScheduleCommandModel({
|
|
track: race.track || '',
|
|
car: race.car || '',
|
|
scheduledAtIso: race.scheduledAt.toISOString(),
|
|
}));
|
|
};
|
|
|
|
const handleDelete = (raceId: string) => {
|
|
setRaceToDelete(raceId);
|
|
};
|
|
|
|
const confirmDelete = async () => {
|
|
if (!selectedSeasonId || !raceToDelete) return;
|
|
|
|
setDeletingRaceId(raceToDelete);
|
|
setError(null);
|
|
try {
|
|
const result = await deleteRaceAction(leagueId, selectedSeasonId, raceToDelete);
|
|
if (result.isOk()) {
|
|
router.refresh();
|
|
setRaceToDelete(null);
|
|
} else {
|
|
setError(result.getError());
|
|
}
|
|
} finally {
|
|
setDeletingRaceId(null);
|
|
}
|
|
};
|
|
|
|
const handleCancelEdit = () => {
|
|
setEditingRaceId(null);
|
|
setForm(new RaceScheduleCommandModel());
|
|
};
|
|
|
|
// Derived states
|
|
const isLoading = isAdminLoading || seasonsLoading || scheduleLoading;
|
|
|
|
// Prepare template data
|
|
const templateData = schedule && seasonsData && selectedSeasonId
|
|
? {
|
|
published: schedule.published,
|
|
races: schedule.races.map(r => ({
|
|
id: r.id,
|
|
name: r.name,
|
|
track: r.track || '',
|
|
car: r.car || '',
|
|
scheduledAt: r.scheduledAt.toISOString(),
|
|
})),
|
|
seasons: seasonsData.map(s => ({
|
|
seasonId: s.seasonId,
|
|
name: s.name,
|
|
})),
|
|
seasonId: selectedSeasonId,
|
|
}
|
|
: undefined;
|
|
|
|
// Render admin access required if not admin
|
|
if (!isLoading && !isAdmin) {
|
|
return (
|
|
<Stack gap={6}>
|
|
<Card>
|
|
<Box p={6} textAlign="center">
|
|
<Heading level={3}>Admin Access Required</Heading>
|
|
<Box mt={2}>
|
|
<Text size="sm" color="text-gray-400">Only league admins can manage the schedule.</Text>
|
|
</Box>
|
|
</Box>
|
|
</Card>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
// Template component that wraps the actual template with all props
|
|
const TemplateWrapper = ({ data }: { data: typeof templateData }) => {
|
|
if (!data) return null;
|
|
|
|
return (
|
|
<>
|
|
<LeagueAdminScheduleTemplate
|
|
viewData={data}
|
|
onSeasonChange={handleSeasonChange}
|
|
onPublishToggle={handlePublishToggle}
|
|
onAddOrSave={handleAddOrSave}
|
|
onEdit={handleEdit}
|
|
onDelete={handleDelete}
|
|
onCancelEdit={handleCancelEdit}
|
|
track={form.track}
|
|
car={form.car}
|
|
scheduledAtIso={form.scheduledAtIso}
|
|
editingRaceId={editingRaceId}
|
|
isPublishing={isPublishing}
|
|
isSaving={isSaving}
|
|
isDeleting={deletingRaceId}
|
|
error={error}
|
|
setTrack={(val) => {
|
|
form.track = val;
|
|
setForm(new RaceScheduleCommandModel(form.toCommand()));
|
|
}}
|
|
setCar={(val) => {
|
|
form.car = val;
|
|
setForm(new RaceScheduleCommandModel(form.toCommand()));
|
|
}}
|
|
setScheduledAtIso={(val) => {
|
|
form.scheduledAtIso = val;
|
|
setForm(new RaceScheduleCommandModel(form.toCommand()));
|
|
}}
|
|
/>
|
|
<ConfirmDialog
|
|
isOpen={!!raceToDelete}
|
|
onClose={() => setRaceToDelete(null)}
|
|
onConfirm={confirmDelete}
|
|
title="Delete Race"
|
|
description="Are you sure you want to delete this race? This will remove it from the schedule and cannot be undone."
|
|
confirmLabel="Delete Race"
|
|
variant="danger"
|
|
isLoading={!!deletingRaceId}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<PageWrapper
|
|
data={templateData}
|
|
isLoading={isLoading}
|
|
error={null}
|
|
Template={TemplateWrapper}
|
|
loading={{ variant: 'full-screen', message: 'Loading schedule admin...' }}
|
|
empty={{
|
|
title: 'No schedule data available',
|
|
description: 'Unable to load schedule administration data',
|
|
}}
|
|
/>
|
|
);
|
|
}
|