wip
This commit is contained in:
@@ -15,9 +15,11 @@ import {
|
||||
loadLeagueProtests,
|
||||
removeLeagueMember as removeLeagueMemberCommand,
|
||||
updateLeagueMemberRole as updateLeagueMemberRoleCommand,
|
||||
loadLeagueSeasons,
|
||||
type LeagueJoinRequestViewModel,
|
||||
type LeagueOwnerSummaryViewModel,
|
||||
type LeagueAdminProtestsViewModel,
|
||||
type LeagueSeasonSummaryViewModel,
|
||||
} from '@/lib/presenters/LeagueAdminPresenter';
|
||||
import type { LeagueConfigFormModel } from '@gridpilot/racing/application';
|
||||
import type { LeagueSummaryViewModel } from '@/lib/presenters/LeagueAdminPresenter';
|
||||
@@ -51,13 +53,15 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
|
||||
const [ownerSummary, setOwnerSummary] = useState<LeagueOwnerSummaryViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<'members' | 'requests' | 'races' | 'settings' | 'protests' | 'sponsorships' | 'fees' | 'wallet' | 'prizes' | 'liveries'>('members');
|
||||
const [activeTab, setActiveTab] = useState<'members' | 'requests' | 'races' | 'seasons' | 'settings' | 'protests' | 'sponsorships' | 'fees' | 'wallet' | 'prizes' | 'liveries'>('members');
|
||||
const [downloadingLiveryPack, setDownloadingLiveryPack] = useState(false);
|
||||
const [rejectReason, setRejectReason] = useState('');
|
||||
const [configForm, setConfigForm] = useState<LeagueConfigFormModel | null>(null);
|
||||
const [configLoading, setConfigLoading] = useState(false);
|
||||
const [protestsViewModel, setProtestsViewModel] = useState<LeagueAdminProtestsViewModel | null>(null);
|
||||
const [protestsLoading, setProtestsLoading] = useState(false);
|
||||
const [seasons, setSeasons] = useState<LeagueSeasonSummaryViewModel[]>([]);
|
||||
const [seasonsLoading, setSeasonsLoading] = useState(false);
|
||||
|
||||
const loadJoinRequests = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -104,6 +108,22 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
|
||||
loadConfig();
|
||||
}, [league.id]);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadSeasonsVm() {
|
||||
setSeasonsLoading(true);
|
||||
try {
|
||||
const items = await loadLeagueSeasons(league.id);
|
||||
setSeasons(items);
|
||||
} catch (err) {
|
||||
console.error('Failed to load seasons:', err);
|
||||
} finally {
|
||||
setSeasonsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadSeasonsVm();
|
||||
}, [league.id]);
|
||||
|
||||
// Load protests for this league's races
|
||||
useEffect(() => {
|
||||
async function loadProtests() {
|
||||
@@ -257,6 +277,16 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
|
||||
>
|
||||
Races
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('seasons')}
|
||||
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
|
||||
activeTab === 'seasons'
|
||||
? 'text-primary-blue border-b-2 border-primary-blue'
|
||||
: 'text-gray-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
Seasons
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('sponsorships')}
|
||||
className={`pb-3 px-1 font-medium transition-colors whitespace-nowrap ${
|
||||
@@ -429,6 +459,101 @@ export default function LeagueAdmin({ league, onLeagueUpdate }: LeagueAdminProps
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{activeTab === 'seasons' && (
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-white">Seasons</h2>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
Plan, run, and review seasons inside this league.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{seasonsLoading ? (
|
||||
<div className="text-center py-8 text-gray-400">Loading seasons…</div>
|
||||
) : seasons.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 mx-auto mb-3 rounded-full bg-iron-gray/50 flex items-center justify-center">
|
||||
<Calendar className="w-6 h-6 text-gray-500" />
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">No seasons yet.</p>
|
||||
<p className="text-gray-500 text-xs mt-1">
|
||||
Your first season is created when you set up the league.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{(() => {
|
||||
const activeCount = seasons.filter((s) => s.status === 'active').length;
|
||||
return seasons.map((season) => {
|
||||
const start = season.startDate ? new Date(season.startDate) : null;
|
||||
const end = season.endDate ? new Date(season.endDate) : null;
|
||||
const hasParallel = activeCount > 1 && season.status === 'active';
|
||||
|
||||
const statusConfig: { label: string; className: string } = (() => {
|
||||
switch (season.status) {
|
||||
case 'planned':
|
||||
return { label: 'Planned', className: 'bg-charcoal-outline/40 text-gray-300 border-charcoal-outline/60' };
|
||||
case 'active':
|
||||
return { label: 'Active', className: 'bg-performance-green/15 text-performance-green border-performance-green/40' };
|
||||
case 'completed':
|
||||
return { label: 'Completed', className: 'bg-primary-blue/15 text-primary-blue border-primary-blue/40' };
|
||||
case 'archived':
|
||||
return { label: 'Archived', className: 'bg-gray-600/20 text-gray-300 border-gray-600/40' };
|
||||
case 'cancelled':
|
||||
case 'canceled':
|
||||
return { label: 'Cancelled', className: 'bg-red-500/10 text-red-400 border-red-500/40' };
|
||||
default:
|
||||
return { label: season.status, className: 'bg-charcoal-outline/40 text-gray-300 border-charcoal-outline/60' };
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<div
|
||||
key={season.seasonId}
|
||||
className="rounded-lg border border-charcoal-outline bg-deep-graphite/70 p-4 flex items-start justify-between gap-4"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-sm font-semibold text-white truncate">
|
||||
{season.name}
|
||||
</h3>
|
||||
{season.isPrimary && (
|
||||
<span className="px-2 py-0.5 rounded-full text-[10px] font-medium bg-primary-blue/10 text-primary-blue border border-primary-blue/40">
|
||||
Primary
|
||||
</span>
|
||||
)}
|
||||
{hasParallel && (
|
||||
<span className="px-2 py-0.5 rounded-full text-[10px] font-medium bg-warning-amber/10 text-warning-amber border border-warning-amber/40">
|
||||
Parallel
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
{start
|
||||
? end
|
||||
? `${start.toLocaleDateString()} – ${end.toLocaleDateString()}`
|
||||
: `Starts ${start.toLocaleDateString()}`
|
||||
: 'No dates configured yet'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<span
|
||||
className={`px-2 py-0.5 rounded-full text-[10px] font-medium border ${statusConfig.className}`}
|
||||
>
|
||||
{statusConfig.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{activeTab === 'protests' && (
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
|
||||
Reference in New Issue
Block a user