diff --git a/app/components/UserAttendance/Attendance.tsx b/app/components/UserAttendance/Attendance.tsx index 70f8707aef..53b0fd27e5 100644 --- a/app/components/UserAttendance/Attendance.tsx +++ b/app/components/UserAttendance/Attendance.tsx @@ -4,16 +4,14 @@ import AttendanceStatus from 'app/components/UserAttendance/AttendanceStatus'; import UserGrid from 'app/components/UserGrid'; import { useIsLoggedIn } from 'app/reducers/auth'; import RegisteredSummary from 'app/routes/events/components/RegisteredSummary'; -import type { - Pool, - Registration, -} from 'app/components/UserAttendance/AttendanceModalContent'; +import type { Pool } from 'app/components/UserAttendance/AttendanceModalContent'; import type { SummaryRegistration } from 'app/routes/events/components/RegisteredSummary'; +import type { PaymentRegistration } from 'app/store/models/Registration'; type Props = { pools: Pool[]; registrations?: SummaryRegistration[]; - currentRegistration?: Registration; + currentRegistration?: PaymentRegistration; minUserGridRows?: number; maxUserGridRows?: number; isMeeting?: boolean; diff --git a/app/routes/events/components/EventDetail/AttendeeSection.tsx b/app/routes/events/components/EventDetail/AttendeeSection.tsx new file mode 100644 index 0000000000..ccbdd9b680 --- /dev/null +++ b/app/routes/events/components/EventDetail/AttendeeSection.tsx @@ -0,0 +1,82 @@ +import { Flex } from '@webkom/lego-bricks'; +import moment from 'moment-timezone'; +import Attendance from 'app/components/UserAttendance/Attendance'; +import { useIsLoggedIn } from 'app/reducers/auth'; +import { selectRegistrationsFromPools } from 'app/reducers/events'; +import RegistrationMeta from 'app/routes/events/components/RegistrationMeta'; +import { getEventSemesterFromStartTime } from 'app/routes/events/utils'; +import { useAppSelector } from 'app/store/hooks'; +import type { UserDetailedEvent } from 'app/store/models/Event'; +import type { + PaymentRegistration, + ReadRegistration, +} from 'app/store/models/Registration'; + +const MIN_USER_GRID_ROWS = 2; +const MAX_USER_GRID_ROWS = 2; + +interface Props { + showSkeleton: boolean; + event: UserDetailedEvent; + currentRegistration?: PaymentRegistration; + pools: any; + currentPool: any; +} + +export const AttendeeSection = ({ + showSkeleton, + event, + currentRegistration, + pools, + currentPool, +}: Props) => { + const loggedIn = useIsLoggedIn(); + const fetching = useAppSelector((state) => state.events.fetching); + const registrations: ReadRegistration[] | undefined = useAppSelector( + (state) => selectRegistrationsFromPools(state, { eventId: event.id }), + ); + + const currentMoment = moment(); + const hasSimpleWaitingList = + pools.filter((p) => p.name != 'Venteliste').length <= 1; + const waitingListIndex = + currentPool?.registrations.indexOf(currentRegistration); + + // The UserGrid is expanded when there's less than 5 minutes till activation + const minUserGridRows = currentMoment.isAfter( + moment(event.activationTime).subtract(5, 'minutes'), + ) + ? MIN_USER_GRID_ROWS + : 0; + + return ( + +

Påmeldte

+ + + + {loggedIn && ( + + )} +
+ ); +}; diff --git a/app/routes/events/components/EventDetail/UnansweredSurveys.tsx b/app/routes/events/components/EventDetail/UnansweredSurveys.tsx new file mode 100644 index 0000000000..25120f7308 --- /dev/null +++ b/app/routes/events/components/EventDetail/UnansweredSurveys.tsx @@ -0,0 +1,28 @@ +import { Card } from '@webkom/lego-bricks'; +import { Link } from 'react-router-dom'; +import type { AuthUserDetailedEvent } from 'app/store/models/Event'; +import type { ReadRegistration } from 'app/store/models/Registration'; + +interface Props { + event: AuthUserDetailedEvent; + currentRegistration: ReadRegistration | null; +} + +export const UnansweredSurveys = ({ event, currentRegistration }: Props) => { + return ( + +

+ Du kan ikke melde deg {currentRegistration ? 'av' : 'på'} dette + arrangementet fordi du har ubesvarte spørreundersøkelser. Gå til lenkene + under for å svare: +

+ +
+ ); +}; diff --git a/app/routes/events/components/EventDetail/index.tsx b/app/routes/events/components/EventDetail/index.tsx index d710c025ab..975ab818bf 100644 --- a/app/routes/events/components/EventDetail/index.tsx +++ b/app/routes/events/components/EventDetail/index.tsx @@ -1,4 +1,4 @@ -import { Card, Flex, Page, Skeleton } from '@webkom/lego-bricks'; +import { Flex, Page, Skeleton } from '@webkom/lego-bricks'; import { usePreparedEffect } from '@webkom/react-prepare'; import { isEmpty } from 'lodash'; import { CircleHelp, FilePenLine } from 'lucide-react'; @@ -19,7 +19,6 @@ import Tag from 'app/components/Tags/Tag'; import TextWithIcon from 'app/components/TextWithIcon'; import { FormatTime } from 'app/components/Time'; import Tooltip from 'app/components/Tooltip'; -import Attendance from 'app/components/UserAttendance/Attendance'; import config from 'app/config'; import { useCurrentUser, useIsLoggedIn } from 'app/reducers/auth'; import { @@ -30,18 +29,18 @@ import { selectPoolsForEvent, selectPoolsWithRegistrationsForEvent, selectRegistrationForEventByUserId, - selectRegistrationsFromPools, selectWaitingRegistrationsForEvent, } from 'app/reducers/events'; import { resolveGroupLink } from 'app/reducers/groups'; import { selectPenaltyByUserId } from 'app/reducers/penalties'; import { selectUserWithGroups } from 'app/reducers/users'; +import { AttendeeSection } from 'app/routes/events/components/EventDetail/AttendeeSection'; import { InterestedButton } from 'app/routes/events/components/EventDetail/InterestedButton'; import { SidebarInfo } from 'app/routes/events/components/EventDetail/SidebarInfo'; +import { UnansweredSurveys } from 'app/routes/events/components/EventDetail/UnansweredSurveys'; import { colorForEventType, penaltyHours, - getEventSemesterFromStartTime, registrationCloseTime, displayNameForEventType, } from 'app/routes/events/utils'; @@ -49,17 +48,12 @@ import YoutubeCover from 'app/routes/pages/components/YoutubeCover'; import { useAppDispatch, useAppSelector } from 'app/store/hooks'; import Admin from '../Admin'; import JoinEventForm from '../JoinEventForm'; -import RegistrationMeta from '../RegistrationMeta'; import styles from './EventDetail.css'; import type { PropertyGenerator } from 'app/components/PropertyHelmet'; import type { AuthUserDetailedEvent, UserDetailedEvent, } from 'app/store/models/Event'; -import type { ReadRegistration } from 'app/store/models/Registration'; - -const MIN_USER_GRID_ROWS = 2; -const MAX_USER_GRID_ROWS = 2; const Line = () =>
; @@ -115,7 +109,7 @@ const EventDetail = () => { const fetching = useAppSelector((state) => state.events.fetching); const showSkeleton = fetching && isEmpty(event); const actionGrant = event?.actionGrant || []; - const hasFullAccess = Boolean(event?.waitingRegistrations); + const hasFullAccess = Boolean('waitingRegistrations' in event); const loggedIn = useIsLoggedIn(); const currentUser = useCurrentUser(); @@ -136,9 +130,6 @@ const EventDetail = () => { ? selectMergedPoolWithRegistrations(state, { eventId }) : selectPoolsWithRegistrationsForEvent(state, { eventId }), ); - const registrations: ReadRegistration[] | undefined = useAppSelector( - (state) => selectRegistrationsFromPools(state, { eventId }), - ); const waitingRegistrations = useAppSelector((state) => selectWaitingRegistrationsForEvent(state, { eventId }), ); @@ -175,17 +166,10 @@ const EventDetail = () => { ), ); - let currentRegistration; - let currentRegistrationIndex; - - if (currentPool) { - currentRegistrationIndex = currentPool.registrations.findIndex( - (registration) => registration.user?.id === currentUser?.id, - ); - currentRegistration = currentPool.registrations[currentRegistrationIndex]; - } + const currentRegistration = currentPool?.registrations.find( + (registration) => registration.user?.id === currentUser?.id, + ); - const hasSimpleWaitingList = poolsWithRegistrations.length <= 1; const pendingRegistration = useAppSelector( (state) => currentUser && @@ -225,13 +209,6 @@ const EventDetail = () => { const registrationCloseTimeMoment = registrationCloseTime(event); - // The UserGrid is expanded when there's less than 5 minutes till activation - const minUserGridRows = currentMoment.isAfter( - moment(activationTimeMoment).subtract(5, 'minutes'), - ) - ? MIN_USER_GRID_ROWS - : 0; - const deadlines = [ event.activationTime && currentMoment.isBefore(activationTimeMoment) ? { @@ -436,58 +413,21 @@ const EventDetail = () => { ) : ( <> - -

Påmeldte

- - - - {loggedIn && ( - - )} -
+ {'unansweredSurveys' in event && event.unansweredSurveys?.length > 0 && !event.isAdmitted ? ( - -

- Du kan ikke melde deg {currentRegistration ? 'av' : 'på'}{' '} - dette arrangementet fordi du har ubesvarte - spørreundersøkelser. Gå til lenkene under for å svare: -

-
    - {event.unansweredSurveys.map((surveyId, i) => ( -
  • - - Undersøkelse {i + 1} - -
  • - ))} -
-
+ ) : ( !showSkeleton && ( ; + registration?: PaymentRegistration; isPriced: boolean; registrationIndex: number; hasSimpleWaitingList: boolean; @@ -47,7 +50,7 @@ const ConsentStatus = ({ eventSemester, }: { useConsent: boolean; - LEGACY_photoConsent: LEGACY_EventRegistrationPhotoConsent; + LEGACY_photoConsent?: LEGACY_EventRegistrationPhotoConsent; hasEnded: boolean; photoConsents?: Array; eventSemester: EventSemester; @@ -184,7 +187,7 @@ const PaymentStatus = ({ paymentStatus, isPriced, }: { - paymentStatus: EventRegistrationPaymentStatus; + paymentStatus?: EventRegistrationPaymentStatus | null; isPriced: boolean; }) => { if (!isPriced) return null; diff --git a/app/store/models/Event.ts b/app/store/models/Event.ts index 81bcf90c4d..c139b1cc02 100644 --- a/app/store/models/Event.ts +++ b/app/store/models/Event.ts @@ -4,7 +4,11 @@ import type { Dateish } from 'app/models'; import type { AutocompleteContentType } from 'app/store/models/Autocomplete'; import type { ListCompany } from 'app/store/models/Company'; import type ObjectPermissionsMixin from 'app/store/models/ObjectPermissionsMixin'; -import type { DetailedUser, PublicUser } from 'app/store/models/User'; +import type { + DetailedUser, + PhotoConsent, + PublicUser, +} from 'app/store/models/User'; import type { ContentTarget } from 'app/store/utils/contentTarget'; export enum EventType { @@ -55,6 +59,7 @@ interface Event { useStripe: boolean; paymentDueDate?: Dateish; useCaptcha: boolean; + waitingRegistrations?: EntityId[]; waitingRegistrationCount?: number; tags: string[]; isMerged: boolean; @@ -69,6 +74,7 @@ interface Event { pinned: boolean; responsibleUsers: DetailedUser[]; isForeignLanguage: boolean; + unregistered: EntityId[]; // for survey attendedCount: number; @@ -80,7 +86,7 @@ interface Event { following: false | EntityId; spotsLeft: number; pendingRegistration?: EntityId; - photoConsents: EntityId[]; + photoConsents: PhotoConsent[]; unansweredSurveys: EntityId[]; } @@ -181,6 +187,7 @@ export type EventForSurvey = Pick< ListEvent; // User specific event serializer that appends data based on request.user +// Used when an authenticated user who CANNOT register is viewing an event export type UserDetailedEvent = Pick< Event, | 'price' @@ -193,12 +200,14 @@ export type UserDetailedEvent = Pick< > & DetailedEvent; +// Used when an authenticated user who CAN register is viewing an event export type AuthUserDetailedEvent = Pick< Event, 'waitingRegistrations' | 'unansweredSurveys' > & UserDetailedEvent; +// Used in /administrate endpoint export type AdministrateEvent = Pick< Event, | 'pools' diff --git a/app/store/models/Registration.ts b/app/store/models/Registration.ts index 91bbc2c10f..daeb35cd3e 100644 --- a/app/store/models/Registration.ts +++ b/app/store/models/Registration.ts @@ -1,5 +1,9 @@ import type { EntityId } from '@reduxjs/toolkit'; -import type { Dateish } from 'app/models'; +import type { + Dateish, + EventRegistrationPaymentStatus, + LEGACY_EventRegistrationPhotoConsent, +} from 'app/models'; import type { DetailedUser, PhotoConsent, @@ -29,10 +33,10 @@ interface Registration { unregistrationDate: Dateish; adminRegistrationReason: string; paymentIntentId: string | null; - paymentStatus: string | null; //TODO: enum + paymentStatus: EventRegistrationPaymentStatus | null; paymentAmount: number; paymentAmountRefunded: number; - LEGACYPhotoConsent: string; //TODO: enum + LEGACYPhotoConsent: LEGACY_EventRegistrationPhotoConsent; photoConsents: PhotoConsent[]; }