From 37e1d0381b419b6428dfb911e9e9832dba1f5fbc Mon Sep 17 00:00:00 2001 From: Ivar Nakken Date: Thu, 12 Dec 2024 14:56:45 +0100 Subject: [PATCH 1/3] Remove unnecessary `ListEventWithUserRegistration` event model `userReg` is now included in the read event `ListEvent` model --- app/routes/users/components/UserProfile/index.tsx | 6 +++--- app/store/models/Event.ts | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/routes/users/components/UserProfile/index.tsx b/app/routes/users/components/UserProfile/index.tsx index c12adbad66..6027c6d478 100644 --- a/app/routes/users/components/UserProfile/index.tsx +++ b/app/routes/users/components/UserProfile/index.tsx @@ -44,7 +44,7 @@ import { GroupMemberships } from './GroupMemberships'; import Penalties from './Penalties'; import PhotoConsents from './PhotoConsents'; import styles from './UserProfile.module.css'; -import type { ListEventWithUserRegistration } from 'app/store/models/Event'; +import type { ListEvent } from 'app/store/models/Event'; import type { PublicGroup } from 'app/store/models/Group'; import type { CurrentUser, PublicUserWithGroups } from 'app/store/models/User'; import type { ExclusifyUnion } from 'app/types'; @@ -74,7 +74,7 @@ const UserProfile = () => { }), ); const upcomingEvents = useAppSelector((state) => - selectAllEvents(state, { + selectAllEvents(state, { pagination: upcomingEventsPagination, }), ); @@ -87,7 +87,7 @@ const UserProfile = () => { }), ); const previousEvents = useAppSelector((state) => - selectAllEvents(state, { + selectAllEvents(state, { pagination: previousEventsPagination, }), ).filter((e) => e.userReg); diff --git a/app/store/models/Event.ts b/app/store/models/Event.ts index 9986773c58..f01982f151 100644 --- a/app/store/models/Event.ts +++ b/app/store/models/Event.ts @@ -79,6 +79,7 @@ export interface CompleteEvent { responsibleUsers: PublicUser[]; isForeignLanguage: boolean; unregistered: EntityId[]; + userReg?: ReadRegistration; // for survey attendedCount: number; @@ -134,14 +135,10 @@ export type ListEvent = Pick< | 'survey' | 'responsibleUsers' | 'actionGrant' + | 'userReg' > & ObjectPermissionsMixin; -// Used for /upcoming and /previous endpoints -export type ListEventWithUserRegistration = ListEvent & { - userReg?: ReadRegistration; -}; - export type DetailedEvent = Pick< CompleteEvent, | 'id' @@ -282,7 +279,6 @@ export type AutocompleteEvent = Pick< export type UnknownEvent = ( | PublicEvent | ListEvent - | ListEventWithUserRegistration | DetailedEvent | EventForSurvey | UserDetailedEvent From 074860e6542da90decf2555a1bd65d43d14ef97b Mon Sep 17 00:00:00 2001 From: Ivar Nakken Date: Thu, 12 Dec 2024 14:59:37 +0100 Subject: [PATCH 2/3] Include registration statuses to event items Makes it easier to differentiate between the list of dates in the items. Besides, people prefer to use the event list on /events rather than the one on the user profile to view upcoming events. --- .../EventItem/RegistrationStatusTag.tsx | 87 +++++++++++++++++ app/components/EventItem/index.tsx | 95 +++++++++++-------- app/components/EventItem/styles.module.css | 12 +-- app/components/Tags/Tag.tsx | 3 +- app/store/models/Event.ts | 1 + 5 files changed, 149 insertions(+), 49 deletions(-) create mode 100644 app/components/EventItem/RegistrationStatusTag.tsx diff --git a/app/components/EventItem/RegistrationStatusTag.tsx b/app/components/EventItem/RegistrationStatusTag.tsx new file mode 100644 index 0000000000..c2cfb8145a --- /dev/null +++ b/app/components/EventItem/RegistrationStatusTag.tsx @@ -0,0 +1,87 @@ +import { Flex, Icon } from '@webkom/lego-bricks'; +import { AlarmClock } from 'lucide-react'; +import Tag from 'app/components/Tags/Tag'; +import Time from 'app/components/Time'; +import { useIsLoggedIn } from 'app/reducers/auth'; +import { EventStatusType } from 'app/store/models/Event'; +import utilities from 'app/styles/utilities.css'; +import type { ListEvent } from 'app/store/models/Event'; + +type Props = { + event: ListEvent; + isRegistrationOpen: boolean; + isRegistrationSameYear: boolean; +}; + +const RegistrationStatusTag = ({ + event, + isRegistrationOpen, + isRegistrationSameYear, +}: Props) => { + const loggedIn = useIsLoggedIn(); + + const getRegistrationStatus = () => { + if ( + event.eventStatusType === EventStatusType.OPEN || + event.eventStatusType === EventStatusType.INFINITE + ) { + return 'Åpent for alle'; + } + + if (event.isAdmitted) { + return 'Du er påmeldt'; + } + + if (event.userReg && event.eventStatusType === EventStatusType.NORMAL) { + return 'Du er på venteliste'; + } + + if (!!event.activationTime) { + return ( + + } + size={14} + className={utilities.hiddenOnMobile} + /> + + Påmelding {isRegistrationOpen ? 'åpnet' : 'åpner'}{' '} + + + ); + } + + return 'Påmelding er ikke bestemt'; + }; + + const getTagColor = () => { + if (event.isAdmitted) { + return 'green'; + } + + if (event.userReg && event.eventStatusType === EventStatusType.NORMAL) { + return 'orange'; + } + + if ( + event.eventStatusType === EventStatusType.OPEN || + event.eventStatusType === EventStatusType.INFINITE + ) { + return 'red'; + } + + return isRegistrationOpen ? 'red' : 'gray'; + }; + + if (!loggedIn) { + return null; + } + + return ; +}; + +export default RegistrationStatusTag; diff --git a/app/components/EventItem/index.tsx b/app/components/EventItem/index.tsx index 85fc569f20..91f991b33f 100644 --- a/app/components/EventItem/index.tsx +++ b/app/components/EventItem/index.tsx @@ -1,6 +1,5 @@ import { Flex, Icon, Image } from '@webkom/lego-bricks'; import { - AlarmClock, Calendar, CalendarClock, CircleAlert, @@ -8,6 +7,7 @@ import { Clock, Timer, } from 'lucide-react'; +import moment from 'moment-timezone'; import { Link } from 'react-router-dom'; import Pill from 'app/components/Pill'; import Tag from 'app/components/Tags/Tag'; @@ -16,6 +16,7 @@ import Tooltip from 'app/components/Tooltip'; import { colorForEventType } from 'app/routes/events/utils'; import { EventStatusType } from 'app/store/models/Event'; import { eventAttendanceAbsolute } from 'app/utils/eventStatus'; +import RegistrationStatusTag from './RegistrationStatusTag'; import styles from './styles.module.css'; import type { CompleteEvent, ListEvent } from 'app/store/models/Event'; import type { ReactNode } from 'react'; @@ -33,27 +34,34 @@ const getRegistrationIconOptions = ( ): RegistrationIconOptions => { const { isAdmitted, eventStatusType } = event; + if (isAdmitted) { + return { + icon: , + color: 'var(--success-color)', + tooltip: 'Du er påmeldt', + }; + } + switch (eventStatusType) { case EventStatusType.NORMAL: case EventStatusType.INFINITE: - if (isAdmitted) { - return { - icon: , - color: 'var(--success-color)', - tooltip: 'Du er påmeldt', - } satisfies RegistrationIconOptions; - } return { icon: , color: 'var(--color-orange-6)', tooltip: 'Du er på ventelisten', - } satisfies RegistrationIconOptions; + }; + case EventStatusType.OPEN: + return { + icon: , + color: 'var(--success-color)', + tooltip: 'Åpent for alle', + }; default: return { icon: , color: 'var(--danger-color)', tooltip: 'Det har oppstått en feil', - } satisfies RegistrationIconOptions; + }; } }; @@ -85,24 +93,6 @@ const TimeStamp = ({ event }: TimeStampProps) => { ); }; -const TimeStartAndRegistration = ({ event }: TimeStampProps) => ( - <> - - } size={18} /> - - - {!!event.activationTime && ( - - - } size={18} /> - - - )} - -); - const RegistrationIcon = ({ event }: TimeStampProps) => { const registrationIconOptions = getRegistrationIconOptions(event); return ( @@ -127,6 +117,12 @@ const EventItem = ({ showTags = true, eventStyle, }: EventItemProps): ReactNode => { + const isRegistrationOpen = moment(event.activationTime).isBefore(moment()); + // No need to show the year if it is the same year + const isRegistrationSameYear = + moment().year() === moment(event.activationTime).year(); + const isEventSameYear = moment().year() === moment(event.startTime).year(); + switch (eventStyle) { case 'extra-compact': return ( @@ -205,12 +201,20 @@ const EventItem = ({ }} className={styles.eventItem} > - -
-

{event.title}

- {event.totalCapacity > 0 && } -
- + +

{event.title}

+ {event.totalCapacity != null && event.totalCapacity > 0 && ( +
+ +
+ )} + + } size={16} /> + {showTags && ( {event.tags.map((tag, index) => ( @@ -220,14 +224,21 @@ const EventItem = ({ )} - - {event.cover && ( - Forsidebilde - )} + + + {event.cover && ( + Forsidebilde + )} + + ); diff --git a/app/components/EventItem/styles.module.css b/app/components/EventItem/styles.module.css index c9d4d35d21..3b5d4fbc84 100644 --- a/app/components/EventItem/styles.module.css +++ b/app/components/EventItem/styles.module.css @@ -1,13 +1,15 @@ .eventItem { min-height: 50px; - padding: var(--spacing-xs); - padding-left: var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); border-left: 4px solid transparent; display: flex; justify-content: space-between; + align-items: center; + gap: var(--spacing-md); line-height: 1.3; border-top-right-radius: var(--border-radius-lg); border-bottom-right-radius: var(--border-radius-lg); + color: var(--lego-font-color); transition: background-color var(--easing-fast); &:hover { @@ -30,7 +32,6 @@ color: var(--lego-font-color); margin: 0; word-break: break-word; - margin-bottom: var(--spacing-sm); } .eventTime { @@ -38,7 +39,7 @@ } .companyLogo { - width: 120px; + width: 140px; display: flex; justify-content: center; align-items: center; @@ -51,11 +52,10 @@ } .companyLogo img { - min-width: 120px; + min-width: 140px; max-height: 80px; object-fit: contain; background: white; - margin: var(--spacing-sm) var(--spacing-md) var(--spacing-sm) 0; } .companyLogoCompact img { diff --git a/app/components/Tags/Tag.tsx b/app/components/Tags/Tag.tsx index 38de5f0127..6ac8417310 100644 --- a/app/components/Tags/Tag.tsx +++ b/app/components/Tags/Tag.tsx @@ -2,6 +2,7 @@ import { Flex, Icon } from '@webkom/lego-bricks'; import cx from 'classnames'; import { Link } from 'react-router-dom'; import styles from './Tag.module.css'; +import type { ReactNode } from 'react'; const tagColors = [ 'red', @@ -18,7 +19,7 @@ const tagColors = [ export type TagColors = (typeof tagColors)[number]; type Props = { - tag: string; + tag: string | ReactNode; icon?: string; iconSize?: number; color?: TagColors; diff --git a/app/store/models/Event.ts b/app/store/models/Event.ts index f01982f151..40e35387c2 100644 --- a/app/store/models/Event.ts +++ b/app/store/models/Event.ts @@ -188,6 +188,7 @@ export type DetailedEvent = Pick< | 'responsibleUsers' | 'isForeignLanguage' | 'actionGrant' + | 'isAdmitted' > & ObjectPermissionsMixin; From 64ad6d012c795e9cd8327ae0d99889f8199a12f5 Mon Sep 17 00:00:00 2001 From: Ivar Nakken Date: Thu, 12 Dec 2024 15:01:39 +0100 Subject: [PATCH 3/3] Re-fetch events when logging in In case the user is on the /events page and logs in, the events should be re-fetched to get the user registration statuses. --- app/routes/events/components/EventList.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/routes/events/components/EventList.tsx b/app/routes/events/components/EventList.tsx index 1cabc3b3ce..55685cdc9f 100644 --- a/app/routes/events/components/EventList.tsx +++ b/app/routes/events/components/EventList.tsx @@ -17,7 +17,7 @@ import EmptyState from 'app/components/EmptyState'; import EventItem from 'app/components/EventItem'; import { CheckBox, SelectInput } from 'app/components/Form/'; import { EventTime } from 'app/models'; -import { useCurrentUser } from 'app/reducers/auth'; +import { useCurrentUser, useIsLoggedIn } from 'app/reducers/auth'; import { selectAllEvents } from 'app/reducers/events'; import { selectPaginationNext } from 'app/reducers/selectors'; import { useAppDispatch, useAppSelector } from 'app/store/hooks'; @@ -134,6 +134,7 @@ const EventList = () => { ); const icalToken = useCurrentUser()?.icalToken; + const loggedIn = useIsLoggedIn(); const fetchQuery = { date_after: moment().format('YYYY-MM-DD'), @@ -162,7 +163,7 @@ const EventList = () => { query: fetchQuery, }), ), - [], + [loggedIn], ); const fetchMore = () =>