feat(events): add competitor number (bib) support

Allow participants to set their bib/competitor number per event.
Display as badge next to username in participant lists.

- Add competitorNumber field to EventParticipant model
- Add PUT /events/:slug/competitor-number endpoint
- Include competitorNumber in heats/me and heats/all responses
- Add input field in HeatsBanner component
- Display badge in UserListItem component
- Add unit tests for competitor number feature
This commit is contained in:
Radosław Gierwiało
2025-11-23 17:55:25 +01:00
parent a2279662dc
commit edf68f2489
9 changed files with 323 additions and 10 deletions

View File

@@ -43,7 +43,9 @@ const EventChatPage = () => {
// Heats state
const [myHeats, setMyHeats] = useState([]);
const [myCompetitorNumber, setMyCompetitorNumber] = useState(null);
const [userHeats, setUserHeats] = useState(new Map());
const [userCompetitorNumbers, setUserCompetitorNumbers] = useState(new Map());
const [showHeatsBanner, setShowHeatsBanner] = useState(false);
const [hideMyHeats, setHideMyHeats] = useState(false);
const [showHeatsModal, setShowHeatsModal] = useState(false);
@@ -87,18 +89,25 @@ const EventChatPage = () => {
const loadData = async () => {
try {
// Load my heats
const myHeatsData = await heatsAPI.getMyHeats(slug);
// Load my heats and competitor number
const myHeatsResponse = await heatsAPI.getMyHeats(slug);
const myHeatsData = myHeatsResponse.data || [];
setMyHeats(myHeatsData);
setMyCompetitorNumber(myHeatsResponse.competitorNumber);
setShowHeatsBanner(myHeatsData.length === 0);
// Load all users' heats
// Load all users' heats and competitor numbers
const allHeatsData = await heatsAPI.getAllHeats(slug);
const heatsMap = new Map();
const competitorNumbersMap = new Map();
allHeatsData.forEach((userHeat) => {
heatsMap.set(userHeat.userId, userHeat.heats);
if (userHeat.competitorNumber) {
competitorNumbersMap.set(userHeat.userId, userHeat.competitorNumber);
}
});
setUserHeats(heatsMap);
setUserCompetitorNumbers(competitorNumbersMap);
// Load all checked-in users (participants)
const eventDetails = await eventsAPI.getDetails(slug);
@@ -384,6 +393,7 @@ const EventChatPage = () => {
slug={slug}
onSave={handleHeatsSave}
onDismiss={() => setShowHeatsBanner(false)}
existingCompetitorNumber={myCompetitorNumber}
/>
)}
@@ -392,6 +402,7 @@ const EventChatPage = () => {
users={getAllDisplayUsers().filter(u => !shouldHideUser(u.userId))}
activeUsers={activeUsers}
userHeats={userHeats}
userCompetitorNumbers={userCompetitorNumbers}
myHeats={myHeats}
hideMyHeats={hideMyHeats}
onHideMyHeatsChange={setHideMyHeats}
@@ -457,6 +468,7 @@ const EventChatPage = () => {
onSave={handleHeatsSave}
onDismiss={() => setShowHeatsModal(false)}
existingHeats={myHeats}
existingCompetitorNumber={myCompetitorNumber}
/>
</Modal>
</div>