website refactor
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { LeagueAdminScheduleViewModel } from '@/lib/view-models/LeagueAdminScheduleViewModel';
|
||||
import type { LeagueSeasonSummaryViewModel } from '@/lib/view-models/LeagueSeasonSummaryViewModel';
|
||||
import Card from '@/components/ui/Card';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
import React, { useMemo } 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 { GridItem } from '@/ui/GridItem';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import type { LeagueAdminScheduleViewData } from '@/lib/view-data/LeagueAdminScheduleViewData';
|
||||
|
||||
interface LeagueAdminScheduleTemplateProps {
|
||||
data: {
|
||||
schedule: LeagueAdminScheduleViewModel;
|
||||
seasons: LeagueSeasonSummaryViewModel[];
|
||||
seasonId: string;
|
||||
};
|
||||
viewData: LeagueAdminScheduleViewData;
|
||||
onSeasonChange: (seasonId: string) => void;
|
||||
onPublishToggle: () => void;
|
||||
onAddOrSave: () => void;
|
||||
@@ -39,12 +40,8 @@ interface LeagueAdminScheduleTemplateProps {
|
||||
setScheduledAtIso: (value: string) => void;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN TEMPLATE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
export function LeagueAdminScheduleTemplate({
|
||||
data,
|
||||
viewData,
|
||||
onSeasonChange,
|
||||
onPublishToggle,
|
||||
onAddOrSave,
|
||||
@@ -62,10 +59,10 @@ export function LeagueAdminScheduleTemplate({
|
||||
setCar,
|
||||
setScheduledAtIso,
|
||||
}: LeagueAdminScheduleTemplateProps) {
|
||||
const { schedule, seasons, seasonId } = data;
|
||||
const { races, seasons, seasonId, published } = viewData;
|
||||
|
||||
const isEditing = editingRaceId !== null;
|
||||
const publishedLabel = schedule.published ? 'Published' : 'Unpublished';
|
||||
const publishedLabel = published ? 'Published' : 'Unpublished';
|
||||
|
||||
const selectedSeasonLabel = useMemo(() => {
|
||||
const selected = seasons.find((s) => s.seasonId === seasonId);
|
||||
@@ -73,162 +70,142 @@ export function LeagueAdminScheduleTemplate({
|
||||
}, [seasons, seasonId]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Stack gap={6}>
|
||||
<Card>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Schedule Admin</h1>
|
||||
<p className="text-sm text-gray-400">Create, edit, and publish season races.</p>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm text-gray-300" htmlFor="seasonId">
|
||||
Season
|
||||
</label>
|
||||
{seasons.length > 0 ? (
|
||||
<select
|
||||
id="seasonId"
|
||||
value={seasonId}
|
||||
onChange={(e) => onSeasonChange(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
>
|
||||
{seasons.map((s) => (
|
||||
<option key={s.seasonId} value={s.seasonId}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
id="seasonId"
|
||||
value={seasonId}
|
||||
onChange={(e) => onSeasonChange(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
placeholder="season-id"
|
||||
/>
|
||||
)}
|
||||
<p className="text-xs text-gray-500">Selected: {selectedSeasonLabel}</p>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-sm text-gray-300">
|
||||
Status: <span className="font-medium text-white">{publishedLabel}</span>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
<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={!schedule || isPublishing}
|
||||
className="px-3 py-1.5 rounded bg-primary-blue text-white disabled:opacity-50"
|
||||
disabled={isPublishing}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
{isPublishing ? 'Processing...' : (schedule?.published ? 'Unpublish' : 'Publish')}
|
||||
</button>
|
||||
</div>
|
||||
{isPublishing ? 'Processing...' : (published ? 'Unpublish' : 'Publish')}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<div className="border-t border-charcoal-outline pt-4 space-y-3">
|
||||
<h2 className="text-lg font-semibold text-white">{isEditing ? 'Edit race' : 'Add race'}</h2>
|
||||
<Box pt={6} style={{ borderTop: '1px solid #262626' }}>
|
||||
<Box mb={4}>
|
||||
<Heading level={2}>{isEditing ? 'Edit race' : 'Add race'}</Heading>
|
||||
</Box>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="track" className="text-sm text-gray-300">
|
||||
Track
|
||||
</label>
|
||||
<input
|
||||
id="track"
|
||||
<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)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
placeholder="Track name"
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="car" className="text-sm text-gray-300">
|
||||
Car
|
||||
</label>
|
||||
<input
|
||||
id="car"
|
||||
<Box>
|
||||
<Text size="sm" color="text-gray-300" block mb={2}>Car</Text>
|
||||
<Input
|
||||
value={car}
|
||||
onChange={(e) => setCar(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
placeholder="Car name"
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor="scheduledAtIso" className="text-sm text-gray-300">
|
||||
Scheduled At (ISO)
|
||||
</label>
|
||||
<input
|
||||
id="scheduledAtIso"
|
||||
<Box>
|
||||
<Text size="sm" color="text-gray-300" block mb={2}>Scheduled At (ISO)</Text>
|
||||
<Input
|
||||
value={scheduledAtIso}
|
||||
onChange={(e) => setScheduledAtIso(e.target.value)}
|
||||
className="bg-iron-gray text-white px-3 py-2 rounded"
|
||||
placeholder="2025-01-01T12:00:00.000Z"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
<Stack direction="row" gap={3} mt={6}>
|
||||
<Button
|
||||
onClick={onAddOrSave}
|
||||
disabled={isSaving}
|
||||
className="px-3 py-1.5 rounded bg-primary-blue text-white"
|
||||
variant="primary"
|
||||
>
|
||||
{isSaving ? 'Processing...' : (isEditing ? 'Save' : 'Add race')}
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
{isEditing && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
onClick={onCancelEdit}
|
||||
className="px-3 py-1.5 rounded bg-iron-gray text-gray-200"
|
||||
variant="secondary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<div className="border-t border-charcoal-outline pt-4 space-y-3">
|
||||
<h2 className="text-lg font-semibold text-white">Races</h2>
|
||||
<Box pt={6} style={{ borderTop: '1px solid #262626' }}>
|
||||
<Box mb={4}>
|
||||
<Heading level={2}>Races</Heading>
|
||||
</Box>
|
||||
|
||||
{schedule?.races.length ? (
|
||||
<div className="space-y-2">
|
||||
{schedule.races.map((race) => (
|
||||
<div
|
||||
{races.length > 0 ? (
|
||||
<Stack gap={3}>
|
||||
{races.map((race) => (
|
||||
<Surface
|
||||
key={race.id}
|
||||
className="flex items-center justify-between gap-3 bg-deep-graphite border border-charcoal-outline rounded p-3"
|
||||
variant="muted"
|
||||
rounded="lg"
|
||||
border
|
||||
padding={4}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="text-white font-medium truncate">{race.name}</p>
|
||||
<p className="text-xs text-gray-400 truncate">{race.scheduledAt.toISOString()}</p>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onEdit(race.id)}
|
||||
className="px-3 py-1.5 rounded bg-iron-gray text-gray-200"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDelete(race.id)}
|
||||
disabled={isDeleting === race.id}
|
||||
className="px-3 py-1.5 rounded bg-iron-gray text-gray-200"
|
||||
>
|
||||
{isDeleting === race.id ? 'Deleting...' : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<div className="py-4 text-sm text-gray-500">No races yet.</div>
|
||||
<Box py={4}>
|
||||
<Text size="sm" color="text-gray-500" block>No races yet.</Text>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Card>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user