208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { Card } from '@/ui/Card';
|
|
import { Box } from '@/ui/Box';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Button } from '@/ui/Button';
|
|
import { Input } from '@/ui/Input';
|
|
import { Select } from '@/ui/Select';
|
|
import { Grid } from '@/ui/Grid';
|
|
import { Surface } from '@/ui/Surface';
|
|
import type { LeagueAdminScheduleViewData } from '@/lib/view-data/LeagueAdminScheduleViewData';
|
|
|
|
interface LeagueAdminScheduleTemplateProps {
|
|
viewData: LeagueAdminScheduleViewData;
|
|
onSeasonChange: (seasonId: string) => void;
|
|
onPublishToggle: () => void;
|
|
onAddOrSave: () => void;
|
|
onEdit: (raceId: string) => void;
|
|
onDelete: (raceId: string) => void;
|
|
onCancelEdit: () => void;
|
|
|
|
// Form state
|
|
track: string;
|
|
car: string;
|
|
scheduledAtIso: string;
|
|
editingRaceId: string | null;
|
|
|
|
// Mutation states
|
|
isPublishing: boolean;
|
|
isSaving: boolean;
|
|
isDeleting: string | null;
|
|
|
|
// Form setters
|
|
setTrack: (value: string) => void;
|
|
setCar: (value: string) => void;
|
|
setScheduledAtIso: (value: string) => void;
|
|
}
|
|
|
|
export function LeagueAdminScheduleTemplate({
|
|
viewData,
|
|
onSeasonChange,
|
|
onPublishToggle,
|
|
onAddOrSave,
|
|
onEdit,
|
|
onDelete,
|
|
onCancelEdit,
|
|
track,
|
|
car,
|
|
scheduledAtIso,
|
|
editingRaceId,
|
|
isPublishing,
|
|
isSaving,
|
|
isDeleting,
|
|
setTrack,
|
|
setCar,
|
|
setScheduledAtIso,
|
|
}: LeagueAdminScheduleTemplateProps) {
|
|
const { races, seasons, seasonId, published } = viewData;
|
|
|
|
const isEditing = editingRaceId !== null;
|
|
const publishedLabel = published ? 'Published' : 'Unpublished';
|
|
|
|
const selectedSeasonLabel = seasons.find((s) => s.seasonId === seasonId)?.name ?? seasonId;
|
|
|
|
return (
|
|
<Stack gap={6}>
|
|
<Card>
|
|
<Stack gap={6}>
|
|
<Box>
|
|
<Heading level={1}>Schedule Admin</Heading>
|
|
<Text size="sm" color="text-gray-400" block mt={1}>Create, edit, and publish season races.</Text>
|
|
</Box>
|
|
|
|
<Box>
|
|
<Text size="sm" color="text-gray-300" block mb={2}>Season</Text>
|
|
<Select
|
|
value={seasonId}
|
|
onChange={(e) => onSeasonChange(e.target.value)}
|
|
options={seasons.map(s => ({ value: s.seasonId, label: s.name }))}
|
|
/>
|
|
<Text size="xs" color="text-gray-500" block mt={1}>Selected: {selectedSeasonLabel}</Text>
|
|
</Box>
|
|
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Text size="sm" color="text-gray-300">
|
|
Status: <Text weight="medium" color="text-white">{publishedLabel}</Text>
|
|
</Text>
|
|
<Button
|
|
onClick={onPublishToggle}
|
|
disabled={isPublishing}
|
|
variant="primary"
|
|
size="sm"
|
|
>
|
|
{isPublishing ? 'Processing...' : (published ? 'Unpublish' : 'Publish')}
|
|
</Button>
|
|
</Stack>
|
|
|
|
<Box pt={6} borderTop="1px solid" borderColor="border-neutral-800">
|
|
<Box mb={4}>
|
|
<Heading level={2}>{isEditing ? 'Edit race' : 'Add race'}</Heading>
|
|
</Box>
|
|
|
|
<Grid cols={3} gap={4}>
|
|
<Box>
|
|
<Text size="sm" color="text-gray-300" block mb={2}>Track</Text>
|
|
<Input
|
|
value={track}
|
|
onChange={(e) => setTrack(e.target.value)}
|
|
placeholder="Track name"
|
|
/>
|
|
</Box>
|
|
|
|
<Box>
|
|
<Text size="sm" color="text-gray-300" block mb={2}>Car</Text>
|
|
<Input
|
|
value={car}
|
|
onChange={(e) => setCar(e.target.value)}
|
|
placeholder="Car name"
|
|
/>
|
|
</Box>
|
|
|
|
<Box>
|
|
<Text size="sm" color="text-gray-300" block mb={2}>Scheduled At (ISO)</Text>
|
|
<Input
|
|
value={scheduledAtIso}
|
|
onChange={(e) => setScheduledAtIso(e.target.value)}
|
|
placeholder="2025-01-01T12:00:00.000Z"
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
|
|
<Stack direction="row" gap={3} mt={6}>
|
|
<Button
|
|
onClick={onAddOrSave}
|
|
disabled={isSaving}
|
|
variant="primary"
|
|
>
|
|
{isSaving ? 'Processing...' : (isEditing ? 'Save' : 'Add race')}
|
|
</Button>
|
|
|
|
{isEditing && (
|
|
<Button
|
|
onClick={onCancelEdit}
|
|
variant="secondary"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
</Stack>
|
|
</Box>
|
|
|
|
<Box pt={6} borderTop="1px solid" borderColor="border-neutral-800">
|
|
<Box mb={4}>
|
|
<Heading level={2}>Races</Heading>
|
|
</Box>
|
|
|
|
{races.length > 0 ? (
|
|
<Stack gap={3}>
|
|
{races.map((race) => (
|
|
<Surface
|
|
key={race.id}
|
|
variant="muted"
|
|
rounded="lg"
|
|
border
|
|
padding={4}
|
|
>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Box>
|
|
<Text weight="medium" color="text-white" block>{race.name}</Text>
|
|
<Text size="xs" color="text-gray-400" block mt={1}>{race.scheduledAt}</Text>
|
|
</Box>
|
|
|
|
<Stack direction="row" gap={2}>
|
|
<Button
|
|
onClick={() => onEdit(race.id)}
|
|
variant="secondary"
|
|
size="sm"
|
|
>
|
|
Edit
|
|
</Button>
|
|
<Button
|
|
onClick={() => onDelete(race.id)}
|
|
disabled={isDeleting === race.id}
|
|
variant="secondary"
|
|
size="sm"
|
|
>
|
|
{isDeleting === race.id ? 'Deleting...' : 'Delete'}
|
|
</Button>
|
|
</Stack>
|
|
</Stack>
|
|
</Surface>
|
|
))}
|
|
</Stack>
|
|
) : (
|
|
<Box py={4}>
|
|
<Text size="sm" color="text-gray-500" block>No races yet.</Text>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</Stack>
|
|
</Card>
|
|
</Stack>
|
|
);
|
|
}
|