Skip to content

Commit

Permalink
show messages on results page
Browse files Browse the repository at this point in the history
  • Loading branch information
Tschonti committed Oct 13, 2024
1 parent dba8484 commit 815b638
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 26 deletions.
13 changes: 12 additions & 1 deletion packages/client/src/api/hooks/resultHooks.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -42,3 +42,14 @@ export const useFetchEventResults = (eventId: number) => {
{ retry: false, enabled: !isNaN(eventId) && eventId > 0 }
)
}

export const useFetchEventMessages = (eventId: number) => {
return useQuery<EventMessages, PontozoError>(
['fetchEventMessages', eventId],
async () => {
const res = await functionAxios.get(`results/${eventId}/messages`)
return res.data
},
{ retry: false, enabled: !isNaN(eventId) && eventId > 0 }
)
}
66 changes: 54 additions & 12 deletions packages/client/src/pages/results/ResultDetails.page.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<PublicEventMessage[]>([])
const { selectedAgeGroups, selectedRoles } = useResultTableContext()
const [selectedCategoryId, setSelectedCategoryId] = useState<number>()
const [ratingCount, setRatingCount] = useState<number>()

Expand All @@ -38,13 +45,19 @@ export const ResultDetailsPage = () => {
}
}, [event])

useEffect(() => {
if (messageData?.messages) {
setFilteredMessages(filterEventMessages(messageData.messages, selectedRoles, selectedAgeGroups))
}
}, [messageData, selectedAgeGroups, selectedRoles])

if (isLoading) {
return <LoadingSpinner />
}
if (error || !event) {
if (error || !event || messagesError) {
return (
<NavigateWithError
error={error || { message: 'Nem sikerült betölteni a verseny értékelési eredményeit', statusCode: 500 }}
error={error || messagesError || { message: 'Nem sikerült betölteni a verseny értékelési eredményeit', statusCode: 500 }}
to={PATHS.INDEX}
/>
)
Expand All @@ -58,24 +71,23 @@ export const ResultDetailsPage = () => {
<Heading size="md">{formatDateRange(event.startDate, event.endDate)}</Heading>
<EventRankBadge event={event} fontSize="1rem" />
</HStack>
<Text>
<b>Rendező{event.organisers.length > 1 && 'k'}:</b> {event.organisers.map((o) => o.shortName).join(', ')}
<Box>
<Text>
<b>Rendező{event.organisers.length > 1 && 'k'}:</b> {event.organisers.map((o) => o.shortName).join(', ')}
</Text>
{ratingCount && (
<Text>
Összesen <b>{ratingCount}</b> felhasználó értékelte a versenyt.
</Text>
)}
</Text>

</Box>
<ChakraLink color="brand.500" fontWeight="bold" href={`http://adatbank.mtfsz.hu/esemeny/show/esemeny_id/${event.id}`} target="_blank">
MTFSZ Adatbank esemény
</ChakraLink>
<Stack direction={['column', 'column', 'row']} gap={2} w="100%">
<AgeGroupRoleSelector />
</Stack>

<Heading size="md">Kategóriák szerinti eredmények</Heading>

<CategoryBarChart event={event} setSelectedCategoryId={setSelectedCategoryId} />
<Heading size="md">Szempontok szerinti eredmények</Heading>
<VStack gap={0.5} alignItems="flex-start" width={['100%', '100%', '33%']}>
Expand All @@ -88,8 +100,38 @@ export const ResultDetailsPage = () => {
))}
</Select>
</VStack>

<CriteriaBarChart event={event} selectedCategoryId={selectedCategoryId} />
<Heading size="md">Szöveges visszajelzések</Heading>
{(filteredMessages.length === 0 || messagesLoading) && (
<Text align="center" fontStyle="italic">
Erre a versenyre nem érkezett a szűrőknek megfelelő szöveges visszajelzés
</Text>
)}
{filteredMessages.map((pem) => (
<Card key={pem.eventRatingId} width="100%">
<CardBody>
<VStack alignItems="flex-start">
<HStack>
<FaUserCircle fontSize={42} />
<VStack alignItems="flex-start" gap={1}>
<Text fontWeight="bold">Névtelen felhasználó</Text>
<HStack>
<Badge colorScheme={ratingRoleColor[pem.role]} variant="solid">
{translateRole[pem.role]}
</Badge>
{pem.role === RatingRole.COMPETITOR && (
<Badge colorScheme={ageGroupColor[pem.ageGroup]} variant="solid">
{translateAgeGroup[pem.ageGroup]}
</Badge>
)}
</HStack>
</VStack>
</HStack>
<Text>{pem.message}</Text>
</VStack>
</CardBody>
</Card>
))}
</VStack>
)
}
13 changes: 13 additions & 0 deletions packages/client/src/util/enumHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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',
Expand Down
23 changes: 21 additions & 2 deletions packages/client/src/util/resultItemHelpers.ts
Original file line number Diff line number Diff line change
@@ -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[],
Expand Down Expand Up @@ -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))
}
}
13 changes: 13 additions & 0 deletions packages/common/src/lib/types/ratingResult.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/common/src/lib/util/enumHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
7 changes: 2 additions & 5 deletions packages/functions/src/functions/ratings/getEventInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PontozoResponse<EventRatingInfo>> => {
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({
Expand Down
9 changes: 3 additions & 6 deletions packages/functions/src/functions/ratings/getEventRating.ts
Original file line number Diff line number Diff line change
@@ -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<HttpResponseInit> => {
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)
Expand All @@ -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,
})
40 changes: 40 additions & 0 deletions packages/functions/src/functions/results/getMessages.ts
Original file line number Diff line number Diff line change
@@ -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<PontozoResponse<EventMessages>> => {
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,
})

0 comments on commit 815b638

Please sign in to comment.