From d4971da6288bfc8e32811bd85d8938a06a79048e Mon Sep 17 00:00:00 2001 From: dijith-481 Date: Sat, 27 Dec 2025 18:04:53 +0530 Subject: [PATCH 1/4] add view result --- src/App.tsx | 11 ++ .../Events/EventDesc/EventData/EventData.tsx | 7 + .../Events/EventDesc/ToolBar/ToolBar.tsx | 12 +- .../Event/create-update/eventValidation.tsx | 9 +- src/Hooks/Event/eventTypes.tsx | 13 ++ src/Hooks/Event/useEventDesc.tsx | 16 +- src/Pages/Events/EventDesc.tsx | 2 +- src/Pages/Events/EventResults.tsx | 181 ++++++++++++++++++ 8 files changed, 245 insertions(+), 6 deletions(-) create mode 100644 src/Pages/Events/EventResults.tsx diff --git a/src/App.tsx b/src/App.tsx index bc6b399..208e5c0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,6 +43,7 @@ import EventStatsPage from 'Pages/Events/EventStats'; import TicketUserList from './Pages/Ticket/TicketUserList'; import TicketDescPage from './Pages/Ticket/TicketDesc'; import EventScheduleCreate from 'Pages/Events/EventScheduleCreate'; +import EventResults from 'Pages/Events/EventResults'; function App() { return ( @@ -168,6 +169,16 @@ function EventsRoutes() { } />, + + + + } + />, {event?.eventStatus} + + Results Published + + + {event?.results && event.results.length > 0 ? 'YES' : 'NO'} + + Number of Rounds diff --git a/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx b/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx index 530bb72..d94d71d 100644 --- a/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx +++ b/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx @@ -8,7 +8,13 @@ import { Link as RouterLink } from 'react-router-dom'; import './ToolBar.css'; import { useNavigate } from 'react-router-dom'; -export default function ToolBar({ eventId }: { eventId: number }) { +export default function ToolBar({ + eventId, + resultsPublished, +}: { + eventId: number; + resultsPublished?: boolean; +}) { const navigate = useNavigate(); return ( @@ -38,11 +44,11 @@ export default function ToolBar({ eventId }: { eventId: number }) { + + + + + + {event.results && event.results.length > 0 ? ( + <> + + Results + + + + + + {/* 1st Place */} + + 1st + + + + {event.results.length >= 1 ? event.results[0].teamName : ''} + + + + + {event.results.length >= 1 ? event.results[0].teamMembers : ''} + + + + {/* 2nd Place */} + + 2nd + + + + {event.results.length >= 2 ? event.results[1].teamName : ''} + + + + + {event.results.length >= 2 ? event.results[1].teamMembers : ''} + + + + {/* 3rd Place */} + + 3rd + + + + {event.results.length >= 3 ? event.results[2].teamName : ''} + + + + + {event.results.length >= 3 ? event.results[2].teamMembers : ''} + + + + ) : ( + + No results declared yet. + + )} + + + + + ); +} From edd3243c38120df9bc8bfd3287a102d9b6150c85 Mon Sep 17 00:00:00 2001 From: dijith-481 Date: Sat, 27 Dec 2025 18:11:23 +0530 Subject: [PATCH 2/4] fix infinite rerenders caused by validate schedule function not wrapped inside a callback --- src/Hooks/Event/useScheduleList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Hooks/Event/useScheduleList.tsx b/src/Hooks/Event/useScheduleList.tsx index 7bb4256..6eb24a4 100644 --- a/src/Hooks/Event/useScheduleList.tsx +++ b/src/Hooks/Event/useScheduleList.tsx @@ -1,4 +1,4 @@ -import { useContext, useState } from 'react'; +import { useCallback, useContext, useState } from 'react'; import { ApiContext } from 'Contexts/Api/ApiContext'; import { getErrMsg, @@ -209,7 +209,7 @@ export function useScheduleList() { } }; - function validateSchedule(): boolean { + const validateSchedule = useCallback((): boolean => { try { createEventScheduleValidationSchema.validateSync(newEvent, { abortEarly: false, @@ -227,7 +227,7 @@ export function useScheduleList() { } return false; } - } + }, [newEvent]); async function createSchedule(): Promise { try { From 425e7009a95253973af97dd99c812e5f09c708ad Mon Sep 17 00:00:00 2001 From: dijith-481 Date: Sun, 28 Dec 2025 11:23:54 +0530 Subject: [PATCH 3/4] resut add,edit,delete --- .../Events/EventDesc/ToolBar/ToolBar.tsx | 10 +- src/Hooks/Event/results/resultValidation.tsx | 31 ++ .../Event/results/useEventResultsCrud.tsx | 88 ++++ src/Pages/Events/EventDesc.tsx | 2 +- src/Pages/Events/EventResults.tsx | 376 +++++++++++++++--- 5 files changed, 443 insertions(+), 64 deletions(-) create mode 100644 src/Hooks/Event/results/resultValidation.tsx create mode 100644 src/Hooks/Event/results/useEventResultsCrud.tsx diff --git a/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx b/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx index d94d71d..04bdccb 100644 --- a/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx +++ b/src/Components/Events/EventDesc/ToolBar/ToolBar.tsx @@ -8,13 +8,7 @@ import { Link as RouterLink } from 'react-router-dom'; import './ToolBar.css'; import { useNavigate } from 'react-router-dom'; -export default function ToolBar({ - eventId, - resultsPublished, -}: { - eventId: number; - resultsPublished?: boolean; -}) { +export default function ToolBar({ eventId }: { eventId: number }) { const navigate = useNavigate(); return ( @@ -44,7 +38,7 @@ export default function ToolBar({ - + + {canEdit && ( + + )} + + {canEdit && ( + + )} + {crudError && ( + + {crudError} + + )} + {event.results && event.results.length > 0 ? ( - <> - - Results - - - - - - {/* 1st Place */} - - 1st + event.results.map((res) => ( + + + + + {res.position} + + + + {res.teamName} + + + + {res.teamMembers} + + + + ID: {res.excelId} | Team: {res.teamId} + + + {canEdit && ( + + handleOpenDialog(res)} + size="small" + > + + + { + setResultToDelete(res.id); + setDeleteConfirmationOpen(true); + }} + size="small" + > + + + + )} + + - - - {event.results.length >= 1 ? event.results[0].teamName : ''} - - - - - {event.results.length >= 1 ? event.results[0].teamMembers : ''} - - - - {/* 2nd Place */} - - 2nd - - - - {event.results.length >= 2 ? event.results[1].teamName : ''} - - - - - {event.results.length >= 2 ? event.results[1].teamMembers : ''} - - - - {/* 3rd Place */} - - 3rd - - - - {event.results.length >= 3 ? event.results[2].teamName : ''} - - - - - {event.results.length >= 3 ? event.results[2].teamMembers : ''} - - - + )) ) : ( No results declared yet. @@ -175,6 +311,136 @@ export default function EventResults() { )} + + {/* Add/Edit Dialog */} + + {editingResult ? 'Edit Result' : 'Add Result'} + + {crudError && ( + + {crudError} + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Delete Confirmation Dialog */} + setDeleteConfirmationOpen(false)}> + Delete Result + + Are you sure you want to delete this result? + + + + + + + + {/* Delete All Confirmation Dialog */} + setDeleteAllConfirmationOpen(false)} + > + Delete All Results + + + Are you sure you want to delete ALL results for this event? This action cannot be + undone. + + + Please type delete to confirm. + + setDeleteConfirmationText(e.target.value)} + /> + + + + + + ); From 87f1de94b096b63c2fe6c355506da8a4061e7458 Mon Sep 17 00:00:00 2001 From: dijith-481 Date: Sun, 28 Dec 2025 22:09:43 +0530 Subject: [PATCH 4/4] autopick result based on username or team name --- src/Hooks/Event/eventTypes.tsx | 6 +- src/Hooks/Event/results/resultValidation.tsx | 10 +- .../Event/results/useEventResultsCrud.tsx | 20 +++- src/Pages/Events/EventResults.tsx | 98 ++++++++++++++++++- 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/src/Hooks/Event/eventTypes.tsx b/src/Hooks/Event/eventTypes.tsx index fe4d2d4..4796831 100644 --- a/src/Hooks/Event/eventTypes.tsx +++ b/src/Hooks/Event/eventTypes.tsx @@ -47,12 +47,12 @@ export interface IResult { id: number; eventId: number; excelId: number; - teamId: number; + teamId?: number | null; event?: IEvent; position: number; name: string; - teamName: string; - teamMembers: string; + teamName?: string | null; + teamMembers?: string | null; } export interface IEventHead { diff --git a/src/Hooks/Event/results/resultValidation.tsx b/src/Hooks/Event/results/resultValidation.tsx index f808c81..679bc32 100644 --- a/src/Hooks/Event/results/resultValidation.tsx +++ b/src/Hooks/Event/results/resultValidation.tsx @@ -8,15 +8,11 @@ export const resultValidationSchema: ObjectSchema (isNaN(val) ? undefined : val)) - .required('Team ID is required'), + .notRequired(), position: number().required('Position is required').min(1, 'Position must be at least 1'), name: string().required('Name is required'), - teamName: string() - .required('Team Name is required') - .min(2, 'Team Name must be at least 2 characters'), - teamMembers: string() - .required('Team Members are required') - .min(2, 'Team Members must be at least 2 characters'), + teamName: string().notRequired().min(2, 'Team Name must be at least 2 characters'), + teamMembers: string().notRequired().min(2, 'Team Members must be at least 2 characters'), }); export interface IValidateResult extends InferType {} diff --git a/src/Hooks/Event/results/useEventResultsCrud.tsx b/src/Hooks/Event/results/useEventResultsCrud.tsx index 23508c3..9c5917b 100644 --- a/src/Hooks/Event/results/useEventResultsCrud.tsx +++ b/src/Hooks/Event/results/useEventResultsCrud.tsx @@ -1,7 +1,6 @@ import { useContext, useState } from 'react'; import { ApiContext } from 'Contexts/Api/ApiContext'; import { getErrMsg } from 'Hooks/errorParser'; -import { IResult } from '../eventTypes'; import { IValidateResult, resultValidationSchema } from './resultValidation'; import { ValidationError } from 'yup'; @@ -15,7 +14,14 @@ export function useEventResultsCrud() { setLoading(true); setError(''); await resultValidationSchema.validate(result, { abortEarly: false }); - await axiosEventsPrivate.post('/api/Result', { ...result, eventId }); + const payload = { + ...result, + eventId, + teamId: result.teamId ?? 0, + teamName: result.teamName ?? '', + teamMembers: result.teamMembers ?? '', + }; + await axiosEventsPrivate.post('/api/Result', payload); return true; } catch (err: any) { if (err instanceof ValidationError) { @@ -34,7 +40,15 @@ export function useEventResultsCrud() { setLoading(true); setError(''); await resultValidationSchema.validate(result, { abortEarly: false }); - await axiosEventsPrivate.put('/api/Result', { ...result, id: resultId, eventId }); + const payload = { + ...result, + id: resultId, + eventId, + teamId: result.teamId ?? 0, + teamName: result.teamName ?? '', + teamMembers: result.teamMembers ?? '', + }; + await axiosEventsPrivate.put('/api/Result', payload); return true; } catch (err: any) { if (err instanceof ValidationError) { diff --git a/src/Pages/Events/EventResults.tsx b/src/Pages/Events/EventResults.tsx index 3ca378f..5ec1776 100644 --- a/src/Pages/Events/EventResults.tsx +++ b/src/Pages/Events/EventResults.tsx @@ -1,4 +1,5 @@ import { + Autocomplete, Box, Button, Dialog, @@ -27,6 +28,8 @@ import { import { useEventResultsCrud } from 'Hooks/Event/results/useEventResultsCrud'; import { defaultResult, IValidateResult } from 'Hooks/Event/results/resultValidation'; import { IResult } from 'Hooks/Event/eventTypes'; +import { useEventRegList } from 'Hooks/Event/registrations/useEventReg'; +import { IRegistration, ITeam } from 'Hooks/Event/registrationTypes'; export default function EventResults() { const { event, fetchEvent, loading, error, setError } = useEventDesc(); @@ -43,6 +46,14 @@ export default function EventResults() { setError: setCrudError, } = useEventResultsCrud(); + const { + fetchEventRegList, + eventRegsIndividual, + eventRegsTeam, + individualRegsLoading, + teamRegsLoading, + } = useEventRegList(); + const [openDialog, setOpenDialog] = useState(false); const [editingResult, setEditingResult] = useState(null); const [formData, setFormData] = useState(defaultResult); @@ -55,8 +66,10 @@ export default function EventResults() { useEffect(() => { if (!Number.isInteger(Number(id))) { setError('Invalid Event ID'); + } else { + fetchEvent(Number(id)); + fetchEventRegList(Number(id)); } - fetchEvent(Number(id)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); @@ -156,6 +169,45 @@ export default function EventResults() { })); }; + const handleRegistrationSelect = (_event: any, newValue: IRegistration | ITeam | null) => { + if (!newValue) return; + + if ('user' in newValue) { + const reg = newValue as IRegistration; + const excelIdVal = + typeof reg.excelId === 'object' && reg.excelId !== null && 'id' in reg.excelId + ? (reg.excelId as any).id + : reg.excelId; + + setFormData((prev) => ({ + ...prev, + excelId: Number(excelIdVal) || 0, + name: reg.user.name, + teamId: reg.teamId?.id ?? reg.user.id, + teamName: reg.user.name, + teamMembers: reg.user.name, + })); + } else { + const team = newValue as ITeam; + const firstMemberExcelId = team.registrations[0]?.excelId; + const excelIdVal = + typeof firstMemberExcelId === 'object' && + firstMemberExcelId !== null && + 'id' in firstMemberExcelId + ? (firstMemberExcelId as any).id + : firstMemberExcelId; + + setFormData((prev) => ({ + ...prev, + excelId: Number(excelIdVal) || 0, + name: team.name, + teamId: team.id, + teamName: team.name, + teamMembers: team.registrations.map((r) => r.user.name).join(', '), + })); + } + }; + if (error) { return {error}; } @@ -312,8 +364,7 @@ export default function EventResults() { - {/* Add/Edit Dialog */} - + {editingResult ? 'Edit Result' : 'Add Result'} {crudError && ( @@ -322,6 +373,41 @@ export default function EventResults() { )} + + { + if ('user' in option) { + const excelIdVal = + typeof option.excelId === 'object' && + option.excelId !== null && + 'id' in option.excelId + ? (option.excelId as any).id + : option.excelId; + return `${option.user.name} (${excelIdVal})`; + } else { + // ITeam + return `${option.name} (ID: ${option.id})`; + } + }} + onChange={handleRegistrationSelect} + loading={event.isTeam ? teamRegsLoading : individualRegsLoading} + renderInput={(params) => ( + + )} + /> + + @@ -340,6 +427,7 @@ export default function EventResults() { fullWidth value={formData.excelId} onChange={handleChange} + InputLabelProps={{ shrink: true }} /> @@ -350,6 +438,7 @@ export default function EventResults() { fullWidth value={formData.teamId} onChange={handleChange} + InputLabelProps={{ shrink: true }} /> @@ -359,6 +448,7 @@ export default function EventResults() { fullWidth value={formData.name} onChange={handleChange} + InputLabelProps={{ shrink: true }} /> @@ -368,6 +458,7 @@ export default function EventResults() { fullWidth value={formData.teamName} onChange={handleChange} + InputLabelProps={{ shrink: true }} /> @@ -379,6 +470,7 @@ export default function EventResults() { onChange={handleChange} placeholder="e.g. Peter Griffin, Brian Griffin" helperText="Enter team members separated by commas" + InputLabelProps={{ shrink: true }} />