wip league admin tools
This commit is contained in:
@@ -5,11 +5,9 @@ import Card from '@/components/ui/Card';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { ProtestViewModel } from '@/lib/view-models/ProtestViewModel';
|
||||
import { RaceViewModel } from '@/lib/view-models/RaceViewModel';
|
||||
import type { ProtestDetailViewModel } from '@/lib/view-models/ProtestDetailViewModel';
|
||||
import { ProtestDriverViewModel } from '@/lib/view-models/ProtestDriverViewModel';
|
||||
import { ProtestDecisionCommandModel } from '@/lib/command-models/protests/ProtestDecisionCommandModel';
|
||||
import type { PenaltyTypesReferenceDTO, PenaltyValueKindDTO } from '@/lib/types/PenaltyTypesReferenceDTO';
|
||||
import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
@@ -99,54 +97,18 @@ const PENALTY_UI: Record<string, PenaltyUiConfig> = {
|
||||
},
|
||||
};
|
||||
|
||||
function getPenaltyValueLabel(valueKind: PenaltyValueKindDTO): string {
|
||||
switch (valueKind) {
|
||||
case 'seconds':
|
||||
return 'seconds';
|
||||
case 'grid_positions':
|
||||
return 'positions';
|
||||
case 'points':
|
||||
return 'points';
|
||||
case 'races':
|
||||
return 'races';
|
||||
case 'none':
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getFallbackDefaultValue(valueKind: PenaltyValueKindDTO): number {
|
||||
switch (valueKind) {
|
||||
case 'seconds':
|
||||
return 5;
|
||||
case 'grid_positions':
|
||||
return 3;
|
||||
case 'points':
|
||||
return 5;
|
||||
case 'races':
|
||||
return 1;
|
||||
case 'none':
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export default function ProtestReviewPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const leagueId = params.id as string;
|
||||
const protestId = params.protestId as string;
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const { protestService, leagueMembershipService, penaltyService } = useServices();
|
||||
const { leagueStewardingService, protestService, leagueMembershipService } = useServices();
|
||||
|
||||
const [protest, setProtest] = useState<ProtestViewModel | null>(null);
|
||||
const [race, setRace] = useState<RaceViewModel | null>(null);
|
||||
const [protestingDriver, setProtestingDriver] = useState<ProtestDriverViewModel | null>(null);
|
||||
const [accusedDriver, setAccusedDriver] = useState<ProtestDriverViewModel | null>(null);
|
||||
const [detail, setDetail] = useState<ProtestDetailViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const [penaltyTypesReference, setPenaltyTypesReference] = useState<PenaltyTypesReferenceDTO | null>(null);
|
||||
const [penaltyTypesLoading, setPenaltyTypesLoading] = useState(false);
|
||||
|
||||
// Decision state
|
||||
const [showDecisionPanel, setShowDecisionPanel] = useState(false);
|
||||
const [decision, setDecision] = useState<'uphold' | 'dismiss' | null>(null);
|
||||
@@ -156,24 +118,20 @@ export default function ProtestReviewPage() {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const penaltyTypes = useMemo(() => {
|
||||
const referenceItems = penaltyTypesReference?.penaltyTypes ?? [];
|
||||
const referenceItems = detail?.penaltyTypes ?? [];
|
||||
return referenceItems.map((ref) => {
|
||||
const ui = PENALTY_UI[ref.type] ?? {
|
||||
label: ref.type.replaceAll('_', ' '),
|
||||
description: '',
|
||||
icon: Gavel,
|
||||
color: 'text-gray-400 bg-gray-500/10 border-gray-500/20',
|
||||
defaultValue: getFallbackDefaultValue(ref.valueKind),
|
||||
};
|
||||
|
||||
return {
|
||||
...ref,
|
||||
...ui,
|
||||
valueLabel: getPenaltyValueLabel(ref.valueKind),
|
||||
defaultValue: ui.defaultValue ?? getFallbackDefaultValue(ref.valueKind),
|
||||
icon: ui.icon,
|
||||
color: ui.color,
|
||||
};
|
||||
});
|
||||
}, [penaltyTypesReference]);
|
||||
}, [detail?.penaltyTypes]);
|
||||
|
||||
const selectedPenalty = useMemo(() => {
|
||||
return penaltyTypes.find((p) => p.type === penaltyType);
|
||||
@@ -195,15 +153,14 @@ export default function ProtestReviewPage() {
|
||||
async function loadProtest() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const protestData = await protestService.getProtestById(leagueId, protestId);
|
||||
if (!protestData) {
|
||||
throw new Error('Protest not found');
|
||||
}
|
||||
const protestDetail = await leagueStewardingService.getProtestDetailViewModel(leagueId, protestId);
|
||||
|
||||
setProtest(protestData.protest);
|
||||
setRace(protestData.race);
|
||||
setProtestingDriver(protestData.protestingDriver);
|
||||
setAccusedDriver(protestData.accusedDriver);
|
||||
setDetail(protestDetail);
|
||||
|
||||
if (protestDetail.initialPenaltyType) {
|
||||
setPenaltyType(protestDetail.initialPenaltyType);
|
||||
setPenaltyValue(protestDetail.initialPenaltyValue);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load protest:', err);
|
||||
alert('Failed to load protest details');
|
||||
@@ -216,43 +173,18 @@ export default function ProtestReviewPage() {
|
||||
if (isAdmin) {
|
||||
loadProtest();
|
||||
}
|
||||
}, [protestId, leagueId, isAdmin, router, protestService]);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadPenaltyTypes() {
|
||||
if (!isAdmin) return;
|
||||
if (penaltyTypesReference) return;
|
||||
|
||||
setPenaltyTypesLoading(true);
|
||||
try {
|
||||
const ref = await penaltyService.getPenaltyTypesReference();
|
||||
setPenaltyTypesReference(ref);
|
||||
|
||||
const hasSelected = ref.penaltyTypes.some((p) => p.type === penaltyType);
|
||||
const [first] = ref.penaltyTypes;
|
||||
if (!hasSelected && first) {
|
||||
setPenaltyType(first.type);
|
||||
setPenaltyValue(PENALTY_UI[first.type]?.defaultValue ?? getFallbackDefaultValue(first.valueKind));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load penalty types reference:', err);
|
||||
} finally {
|
||||
setPenaltyTypesLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadPenaltyTypes();
|
||||
}, [isAdmin, penaltyService, penaltyTypesReference, penaltyType]);
|
||||
}, [protestId, leagueId, isAdmin, router, leagueStewardingService]);
|
||||
|
||||
|
||||
const handleSubmitDecision = async () => {
|
||||
if (!decision || !stewardNotes.trim() || !protest) return;
|
||||
if (penaltyTypesLoading) return;
|
||||
if (!decision || !stewardNotes.trim() || !detail) return;
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const defaultUpheldReason = penaltyTypesReference?.defaultReasons?.upheld;
|
||||
const defaultDismissedReason = penaltyTypesReference?.defaultReasons?.dismissed;
|
||||
const protest = detail.protest;
|
||||
|
||||
const defaultUpheldReason = detail.defaultReasons?.upheld;
|
||||
const defaultDismissedReason = detail.defaultReasons?.dismissed;
|
||||
|
||||
if (decision === 'uphold') {
|
||||
const requiresValue = selectedPenalty?.requiresValue ?? true;
|
||||
@@ -287,7 +219,7 @@ export default function ProtestReviewPage() {
|
||||
|
||||
await protestService.applyPenalty(penaltyCommand);
|
||||
} else {
|
||||
const warningRef = penaltyTypesReference?.penaltyTypes.find((p) => p.type === 'warning');
|
||||
const warningRef = detail.penaltyTypes.find((p) => p.type === 'warning');
|
||||
const requiresValue = warningRef?.requiresValue ?? false;
|
||||
|
||||
const commandModel = new ProtestDecisionCommandModel({
|
||||
@@ -330,12 +262,12 @@ export default function ProtestReviewPage() {
|
||||
};
|
||||
|
||||
const handleRequestDefense = async () => {
|
||||
if (!protest) return;
|
||||
if (!detail) return;
|
||||
|
||||
try {
|
||||
// Request defense
|
||||
await protestService.requestDefense({
|
||||
protestId: protest.id,
|
||||
protestId: detail.protest.id,
|
||||
stewardId: currentDriverId,
|
||||
});
|
||||
|
||||
@@ -379,7 +311,7 @@ export default function ProtestReviewPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || !protest || !race) {
|
||||
if (loading || !detail) {
|
||||
return (
|
||||
<Card>
|
||||
<div className="text-center py-12">
|
||||
@@ -389,6 +321,11 @@ export default function ProtestReviewPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const protest = detail.protest;
|
||||
const race = detail.race;
|
||||
const protestingDriver = detail.protestingDriver;
|
||||
const accusedDriver = detail.accusedDriver;
|
||||
|
||||
const statusConfig = getStatusConfig(protest.status);
|
||||
const StatusIcon = statusConfig.icon;
|
||||
const isPending = protest.status === 'pending';
|
||||
|
||||
Reference in New Issue
Block a user