Files
gridpilot.gg/apps/website/templates/LeagueAdminScheduleTemplate.tsx
Marc Mintel 09632d004d
Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
code quality
2026-01-26 22:16:33 +01:00

220 lines
6.8 KiB
TypeScript

import type { LeagueAdminScheduleViewData } from '@/lib/view-data/LeagueAdminScheduleViewData';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Card } from '@/ui/Card';
import { Grid } from '@/ui/Grid';
import { Heading } from '@/ui/Heading';
import { InlineNotice } from '@/ui/InlineNotice';
import { Input } from '@/ui/Input';
import { Select } from '@/ui/Select';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
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;
error: 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,
error,
setTrack,
setCar,
setScheduledAtIso,
}: LeagueAdminScheduleTemplateProps) {
const typedViewData = viewData as LeagueAdminScheduleViewData & {
seasons: Array<{ seasonId: string; name: string }>;
};
const { races, seasons, seasonId, published } = typedViewData;
const isEditing = editingRaceId !== null;
const publishedLabel = published ? 'Published' : 'Unpublished';
const selectedSeasonLabel = seasons.find((s: {seasonId: string; name: string}) => s.seasonId === seasonId)?.name ?? seasonId;
return (
<Stack gap={6}>
{error && (
<InlineNotice
variant="error"
title="Action Failed"
message={error}
/>
)}
<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: {seasonId: string; name: string}) => ({ 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="white">{publishedLabel}</Text>
</Text>
<Button
onClick={onPublishToggle}
disabled={isPublishing}
variant="primary"
size="sm"
>
{isPublishing ? 'Processing...' : (published ? 'Unpublish' : 'Publish')}
</Button>
</Stack>
<Box pt={6} borderTop borderColor="rgba(255,255,255,0.1)">
<Box mb={4}>
<Heading level={2}>{isEditing ? 'Edit race' : 'Add race'}</Heading>
</Box>
<Grid responsiveGridCols={{ base: 1, md: 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 borderColor="rgba(255,255,255,0.1)">
<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="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>
);
}