website refactor
This commit is contained in:
255
apps/website/client-wrapper/LeagueAdminSchedulePageClient.tsx
Normal file
255
apps/website/client-wrapper/LeagueAdminSchedulePageClient.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
createRaceAction,
|
||||
deleteRaceAction,
|
||||
publishScheduleAction,
|
||||
unpublishScheduleAction,
|
||||
updateRaceAction
|
||||
} from '@/app/actions/leagueScheduleActions';
|
||||
import { PageWrapper } from '@/ui/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',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user