diff --git a/packages/client/src/api/hooks/resultHooks.ts b/packages/client/src/api/hooks/resultHooks.ts index 4a74d19..1587399 100644 --- a/packages/client/src/api/hooks/resultHooks.ts +++ b/packages/client/src/api/hooks/resultHooks.ts @@ -1,4 +1,4 @@ -import { AllSeasonsAndOne, EventResultList, EventWithResults, PontozoError } from '@pontozo/common' +import { AllSeasonsAndOne, EventMessages, EventResultList, EventWithResults, PontozoError } from '@pontozo/common' import { useMutation, useQuery } from '@tanstack/react-query' import { EventFilter } from 'src/pages/results/types/EventFilter' import { functionAxios } from 'src/util/axiosConfig' @@ -42,3 +42,14 @@ export const useFetchEventResults = (eventId: number) => { { retry: false, enabled: !isNaN(eventId) && eventId > 0 } ) } + +export const useFetchEventMessages = (eventId: number) => { + return useQuery( + ['fetchEventMessages', eventId], + async () => { + const res = await functionAxios.get(`results/${eventId}/messages`) + return res.data + }, + { retry: false, enabled: !isNaN(eventId) && eventId > 0 } + ) +} diff --git a/packages/client/src/pages/results/ResultDetails.page.tsx b/packages/client/src/pages/results/ResultDetails.page.tsx index 1acb199..ea2c7f2 100644 --- a/packages/client/src/pages/results/ResultDetails.page.tsx +++ b/packages/client/src/pages/results/ResultDetails.page.tsx @@ -1,13 +1,17 @@ -import { FormLabel, Heading, HStack, Link as ChakraLink, Select, Stack, Text, VStack } from '@chakra-ui/react' -import { ALL_ROLES } from '@pontozo/common' +import { Badge, Box, Card, CardBody, FormLabel, Heading, HStack, Link as ChakraLink, Select, Stack, Text, VStack } from '@chakra-ui/react' +import { ALL_ROLES, PublicEventMessage, RatingRole } from '@pontozo/common' import { useEffect, useState } from 'react' +import { FaUserCircle } from 'react-icons/fa' import { useParams } from 'react-router-dom' -import { useFetchEventResults } from 'src/api/hooks/resultHooks' +import { useResultTableContext } from 'src/api/contexts/useResultTableContext' +import { useFetchEventMessages, useFetchEventResults } from 'src/api/hooks/resultHooks' import { HelmetTitle } from 'src/components/commons/HelmetTitle' import { LoadingSpinner } from 'src/components/commons/LoadingSpinner' import { NavigateWithError } from 'src/components/commons/NavigateWithError' import { formatDateRange } from 'src/util/dateHelpers' +import { ageGroupColor, ratingRoleColor, translateAgeGroup, translateRole } from 'src/util/enumHelpers' import { PATHS } from 'src/util/paths' +import { filterEventMessages } from 'src/util/resultItemHelpers' import { EventRankBadge } from '../events/components/EventRankBadge' import { AgeGroupRoleSelector } from './components/AgeGroupRoleSelector' import { CategoryBarChart } from './components/CategoriesBarChart' @@ -16,6 +20,9 @@ import { CriteriaBarChart } from './components/CriteriaBarChart' export const ResultDetailsPage = () => { const { eventId } = useParams() const { data: event, isLoading, error } = useFetchEventResults(+eventId!) + const { data: messageData, isLoading: messagesLoading, error: messagesError } = useFetchEventMessages(+eventId!) + const [filteredMessages, setFilteredMessages] = useState([]) + const { selectedAgeGroups, selectedRoles } = useResultTableContext() const [selectedCategoryId, setSelectedCategoryId] = useState() const [ratingCount, setRatingCount] = useState() @@ -38,13 +45,19 @@ export const ResultDetailsPage = () => { } }, [event]) + useEffect(() => { + if (messageData?.messages) { + setFilteredMessages(filterEventMessages(messageData.messages, selectedRoles, selectedAgeGroups)) + } + }, [messageData, selectedAgeGroups, selectedRoles]) + if (isLoading) { return } - if (error || !event) { + if (error || !event || messagesError) { return ( ) @@ -58,24 +71,23 @@ export const ResultDetailsPage = () => { {formatDateRange(event.startDate, event.endDate)} - - Rendező{event.organisers.length > 1 && 'k'}: {event.organisers.map((o) => o.shortName).join(', ')} + + + Rendező{event.organisers.length > 1 && 'k'}: {event.organisers.map((o) => o.shortName).join(', ')} + {ratingCount && ( Összesen {ratingCount} felhasználó értékelte a versenyt. )} - - + MTFSZ Adatbank esemény - Kategóriák szerinti eredmények - Szempontok szerinti eredmények @@ -88,8 +100,38 @@ export const ResultDetailsPage = () => { ))} - + Szöveges visszajelzések + {(filteredMessages.length === 0 || messagesLoading) && ( + + Erre a versenyre nem érkezett a szűrőknek megfelelő szöveges visszajelzés + + )} + {filteredMessages.map((pem) => ( + + + + + + + Névtelen felhasználó + + + {translateRole[pem.role]} + + {pem.role === RatingRole.COMPETITOR && ( + + {translateAgeGroup[pem.ageGroup]} + + )} + + + + {pem.message} + + + + ))} ) } diff --git a/packages/client/src/util/enumHelpers.ts b/packages/client/src/util/enumHelpers.ts index 342d9ea..8379c76 100644 --- a/packages/client/src/util/enumHelpers.ts +++ b/packages/client/src/util/enumHelpers.ts @@ -15,6 +15,13 @@ export const translateRole: RoleDict = { [RatingRole.JURY]: 'MTFSZ Zsűri', } +export const ratingRoleColor: RoleDict = { + [RatingRole.COACH]: 'blue', + [RatingRole.COMPETITOR]: 'brand', + [RatingRole.JURY]: 'orange', + [RatingRole.ORGANISER]: 'red', +} + export const getRoleDescription: RoleDict = { [RatingRole.COMPETITOR]: 'A versenyen elindult versenyző. Nem kell, hogy érvényes eredményed legyen, elég ha neveztél és rajthoz álltál.', [RatingRole.COACH]: @@ -49,6 +56,12 @@ export const translateAgeGroup: AgeGroupDictionary = { [AgeGroup.MASTER]: 'Szenior', } +export const ageGroupColor: AgeGroupDictionary = { + [AgeGroup.YOUTH]: 'purple', + [AgeGroup.ELITE]: 'red', + [AgeGroup.MASTER]: 'brand', +} + export const translateUR: UserRoleDictionary = { [UserRole.COACH]: 'Edző', [UserRole.SITE_ADMIN]: 'Admin', diff --git a/packages/client/src/util/resultItemHelpers.ts b/packages/client/src/util/resultItemHelpers.ts index 214906b..09489fb 100644 --- a/packages/client/src/util/resultItemHelpers.ts +++ b/packages/client/src/util/resultItemHelpers.ts @@ -1,5 +1,14 @@ -import { AgeGroup, ALL_AGE_GROUPS, ALL_ROLES, EventResult, RatingResult, RatingResultItem, RatingRole } from '@pontozo/common' -import { SortOrder } from 'src/pages/results/components/table/EventResultTable' +import { + AgeGroup, + ALL_AGE_GROUPS, + ALL_ROLES, + EventResult, + PublicEventMessage, + RatingResult, + RatingResultItem, + RatingRole, +} from '@pontozo/common' +import { SortOrder } from 'src/api/contexts/ResultTableContext' export const getResultItem = ( resultItems: RatingResultItem[], @@ -57,3 +66,13 @@ const generateResultItem = (count: number, sum: number): RatingResultItem => { count: count, } } + +export const filterEventMessages = (messages: PublicEventMessage[], roles: RatingRole[], ageGroups: AgeGroup[]): PublicEventMessage[] => { + if (roles.length === ALL_ROLES.length && ageGroups.length === ALL_AGE_GROUPS.length) { + return messages + } else if (roles.length < ALL_ROLES.length) { + return messages.filter((m) => roles.includes(m.role)) + } else { + return messages.filter((m) => ageGroups.includes(m.ageGroup)) + } +} diff --git a/packages/common/src/lib/types/ratingResult.ts b/packages/common/src/lib/types/ratingResult.ts index d6eeb56..8cca95b 100644 --- a/packages/common/src/lib/types/ratingResult.ts +++ b/packages/common/src/lib/types/ratingResult.ts @@ -1,7 +1,9 @@ import { Category } from './categories' import { Criterion } from './criteria' import { DbEvent, DbStage } from './dbEvents' +import { RatingRole } from './eventRatings' import { Season } from './seasons' +import { AgeGroup } from './users' export interface RatingResult { id: number @@ -50,6 +52,17 @@ export interface EventResult { stages: StageResult[] } +export interface PublicEventMessage { + eventRatingId: number + message: string + role: RatingRole + ageGroup: AgeGroup +} + +export interface EventMessages { + messages: PublicEventMessage[] +} + export interface StageResult { stageId: number stageName: string diff --git a/packages/common/src/lib/util/enumHelpers.ts b/packages/common/src/lib/util/enumHelpers.ts index 01c3551..d1e39c7 100644 --- a/packages/common/src/lib/util/enumHelpers.ts +++ b/packages/common/src/lib/util/enumHelpers.ts @@ -32,3 +32,9 @@ export const ageGroupFilterDict: { [G in AgeGroup]: (er: EventRating) => boolean ELITE: (er) => er.raterAge > 20 && er.raterAge < 35, MASTER: (er) => er.raterAge > 34, } + +export const getAgeGroupFromAge = (age: number): AgeGroup => { + if (age < 21) return AgeGroup.YOUTH + if (age < 34) return AgeGroup.ELITE + return AgeGroup.MASTER +} diff --git a/packages/functions/src/functions/ratings/getEventInfo.ts b/packages/functions/src/functions/ratings/getEventInfo.ts index 2e013df..813940f 100644 --- a/packages/functions/src/functions/ratings/getEventInfo.ts +++ b/packages/functions/src/functions/ratings/getEventInfo.ts @@ -6,17 +6,14 @@ import EventRating from '../../typeorm/entities/EventRating' import { getAppDataSource } from '../../typeorm/getConfig' import { handleException } from '../../util/handleException' import { PontozoResponse } from '../../util/pontozoResponse' +import { validateId } from '../../util/validation' /** * Called after the users starts the rating of an event to get all the rating categories and criteria. */ export const getEventInfo = async (req: HttpRequest, context: InvocationContext): Promise> => { try { - const ratingId = parseInt(req.params.id) - - if (isNaN(ratingId)) { - throw new PontozoException('Érvénytelen azonosító!', 400) - } + const ratingId = validateId(req) const user = getUserFromHeader(req) const ratingRepo = (await getAppDataSource(context)).getRepository(EventRating) const eventRatingAndEvent = await ratingRepo.findOne({ diff --git a/packages/functions/src/functions/ratings/getEventRating.ts b/packages/functions/src/functions/ratings/getEventRating.ts index 114c328..228e7ef 100644 --- a/packages/functions/src/functions/ratings/getEventRating.ts +++ b/packages/functions/src/functions/ratings/getEventRating.ts @@ -1,19 +1,16 @@ import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions' -import { PontozoException } from '@pontozo/common' import { getUserFromHeader } from '../../service/auth.service' import EventRating from '../../typeorm/entities/EventRating' import { getAppDataSource } from '../../typeorm/getConfig' import { handleException } from '../../util/handleException' +import { validateId } from '../../util/validation' /** * Called when a user visits an events page to get their rating of that event and also the event data. */ export const getEventRating = async (req: HttpRequest, context: InvocationContext): Promise => { try { - const eventId = parseInt(req.params.eventId) - if (isNaN(eventId)) { - throw new PontozoException('Érvénytelen azonosító!', 400) - } + const eventId = validateId(req) const user = getUserFromHeader(req) const ratingRepo = (await getAppDataSource(context)).getRepository(EventRating) @@ -31,6 +28,6 @@ export const getEventRating = async (req: HttpRequest, context: InvocationContex app.http('ratings-getEventRating', { methods: ['GET'], - route: 'ratings/event/{eventId}', + route: 'ratings/event/{id}', handler: getEventRating, }) diff --git a/packages/functions/src/functions/results/getMessages.ts b/packages/functions/src/functions/results/getMessages.ts new file mode 100644 index 0000000..5e021a3 --- /dev/null +++ b/packages/functions/src/functions/results/getMessages.ts @@ -0,0 +1,40 @@ +import { app, HttpRequest, InvocationContext } from '@azure/functions' +import { EventMessages, getAgeGroupFromAge, RatingStatus } from '@pontozo/common' +import { IsNull, Not } from 'typeorm' +import EventRating from '../../typeorm/entities/EventRating' +import { getAppDataSource } from '../../typeorm/getConfig' +import { handleException } from '../../util/handleException' +import { PontozoResponse } from '../../util/pontozoResponse' +import { validateId } from '../../util/validation' + +/** + * Called when a user visits an event result page to get the extra messages to the event. + */ +export const getMessages = async (req: HttpRequest, context: InvocationContext): Promise> => { + try { + const eventId = validateId(req) + + const ratingRepo = (await getAppDataSource(context)).getRepository(EventRating) + const ratingsWithMessages = await ratingRepo.find({ + where: { eventId, message: Not(IsNull()), status: RatingStatus.SUBMITTED }, + }) + return { + jsonBody: { + messages: ratingsWithMessages.map((er) => ({ + eventRatingId: er.id, + message: er.message, + role: er.role, + ageGroup: getAgeGroupFromAge(er.raterAge), + })), + }, + } + } catch (error) { + return handleException(req, context, error) + } +} + +app.http('results-getMessages', { + methods: ['GET'], + route: 'results/{id}/messages', + handler: getMessages, +})