This commit is contained in:
2025-12-05 12:24:38 +01:00
parent fb509607c1
commit 5a9cd28d5b
47 changed files with 5456 additions and 228 deletions

View File

@@ -12,7 +12,14 @@ import {
getDriverStats,
getAllDriverRankings,
getDriverRepository,
getGetLeagueFullConfigQuery,
} from '@/lib/di-container';
import type { LeagueConfigFormModel } from '@gridpilot/racing/application';
import { LeagueBasicsSection } from './LeagueBasicsSection';
import { LeagueStructureSection } from './LeagueStructureSection';
import { LeagueScoringSection } from './LeagueScoringSection';
import { LeagueDropSection } from './LeagueDropSection';
import { LeagueTimingsSection } from './LeagueTimingsSection';
import { useEffectiveDriverId } from '@/lib/currentDriver';
import type { MembershipRole } from '@/lib/leagueMembership';
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
@@ -46,6 +53,8 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<'members' | 'requests' | 'races' | 'settings' | 'disputes'>('members');
const [rejectReason, setRejectReason] = useState('');
const [configForm, setConfigForm] = useState<LeagueConfigFormModel | null>(null);
const [configLoading, setConfigLoading] = useState(false);
const loadJoinRequests = useCallback(async () => {
setLoading(true);
@@ -93,6 +102,23 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
loadOwner();
}, [league.ownerId]);
useEffect(() => {
async function loadConfig() {
setConfigLoading(true);
try {
const query = getGetLeagueFullConfigQuery();
const form = await query.execute({ leagueId: league.id });
setConfigForm(form);
} catch (err) {
console.error('Failed to load league config:', err);
} finally {
setConfigLoading(false);
}
}
loadConfig();
}, [league.id]);
const handleApproveRequest = async (requestId: string) => {
try {
const membershipRepo = getLeagueMembershipRepository();
@@ -464,113 +490,74 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
<Card>
<h2 className="text-xl font-semibold text-white mb-4">League Settings</h2>
<div className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
League Name
</label>
<p className="text-white">{league.name}</p>
</div>
{configLoading && !configForm ? (
<div className="py-6 text-sm text-gray-400">Loading configuration</div>
) : configForm ? (
<div className="space-y-8">
<LeagueBasicsSection form={configForm} readOnly />
<LeagueStructureSection form={configForm} readOnly />
<LeagueTimingsSection form={configForm} readOnly />
<LeagueScoringSection form={configForm} presets={[]} readOnly />
<LeagueDropSection form={configForm} readOnly />
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Description
</label>
<p className="text-white">{league.description}</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 pt-2 border-t border-charcoal-outline">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-4">
<div>
<label className="text-sm text-gray-500">Season / Series</label>
<label className="block text-sm font-medium text-gray-300 mb-2">
Season / Series
</label>
<p className="text-white">Alpha Demo Season</p>
</div>
<div>
<label className="text-sm text-gray-500">Points System</label>
<p className="text-white">{league.settings.pointsSystem.toUpperCase()}</p>
</div>
<div>
<label className="text-sm text-gray-500">Qualifying Format</label>
<p className="text-white capitalize">{league.settings.qualifyingFormat}</p>
<div className="rounded-lg border border-charcoal-outline bg-iron-gray/60 p-4 space-y-2">
<h3 className="text-sm font-semibold text-gray-200 mb-1">
At a glance
</h3>
<p className="text-xs text-gray-300">
<span className="font-semibold text-gray-200">Structure:</span>{' '}
{configForm.structure.mode === 'solo'
? `Solo • ${configForm.structure.maxDrivers} drivers`
: `Teams • ${configForm.structure.maxTeams ?? '—'} × ${
configForm.structure.driversPerTeam ?? '—'
} drivers (${configForm.structure.maxDrivers ?? '—'} total)`}
</p>
<p className="text-xs text-gray-300">
<span className="font-semibold text-gray-200">Schedule:</span>{' '}
{`${configForm.timings.roundsPlanned ?? '?'} rounds • ${
configForm.timings.qualifyingMinutes
} min Qualifying • ${configForm.timings.mainRaceMinutes} min Race`}
</p>
<p className="text-xs text-gray-300">
<span className="font-semibold text-gray-200">Scoring:</span>{' '}
{league.settings.pointsSystem.toUpperCase()}
</p>
</div>
</div>
{league.socialLinks && (
<div className="pt-4 border-t border-charcoal-outline space-y-2">
<h3 className="text-sm font-medium text-gray-300">Social Links</h3>
<div className="space-y-1 text-sm">
{league.socialLinks.discordUrl && (
<div className="flex items-center justify-between gap-3">
<span className="text-gray-400">Discord</span>
<a
href={league.socialLinks.discordUrl}
target="_blank"
rel="noreferrer"
className="text-primary-blue hover:underline break-all"
>
{league.socialLinks.discordUrl}
</a>
</div>
)}
{league.socialLinks.youtubeUrl && (
<div className="flex items-center justify-between gap-3">
<span className="text-gray-400">YouTube</span>
<a
href={league.socialLinks.youtubeUrl}
target="_blank"
rel="noreferrer"
className="text-red-400 hover:underline break-all"
>
{league.socialLinks.youtubeUrl}
</a>
</div>
)}
{league.socialLinks.websiteUrl && (
<div className="flex items-center justify-between gap-3">
<span className="text-gray-400">Website</span>
<a
href={league.socialLinks.websiteUrl}
target="_blank"
rel="noreferrer"
className="text-gray-100 hover:underline break-all"
>
{league.socialLinks.websiteUrl}
</a>
</div>
)}
{!league.socialLinks.discordUrl &&
!league.socialLinks.youtubeUrl &&
!league.socialLinks.websiteUrl && (
<p className="text-gray-500">
No social links configured for this league in the alpha demo.
</p>
)}
</div>
</div>
)}
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-300">League Owner</h3>
{ownerSummary ? (
<DriverSummaryPill
driver={ownerSummary.driver}
rating={ownerSummary.rating}
rank={ownerSummary.rank}
/>
) : (
<p className="text-sm text-gray-500">Loading owner details...</p>
)}
</div>
</div>
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-300">League Owner</h3>
{ownerSummary ? (
<DriverSummaryPill
driver={ownerSummary.driver}
rating={ownerSummary.rating}
rank={ownerSummary.rank}
/>
) : (
<p className="text-sm text-gray-500">Loading owner details...</p>
)}
<div className="pt-4 border-t border-charcoal-outline">
<p className="text-sm text-gray-400">
League settings editing is alpha-only and changes are not persisted yet.
</p>
</div>
</div>
<div className="pt-4 border-t border-charcoal-outline">
<p className="text-sm text-gray-400">
League settings editing is alpha-only and changes are not persisted yet.
</p>
) : (
<div className="py-6 text-sm text-gray-500">
Unable to load league configuration for this demo league.
</div>
</div>
)}
</Card>
)}