Skip to content

Commit

Permalink
Introduce "too late" presence option to event attendees
Browse files Browse the repository at this point in the history
Neither ion-icons or font-awesome had a turtle icon, so I extracted one
from Lucide (which we're planning on migrating to). I'm very open to
design suggestions, as I'm not sure what I feel about this. The padding
feels a bit much, so I might refactor the Icon component - but that will
probably have to wait.
  • Loading branch information
ivarnakken committed Mar 9, 2024
1 parent 0edb9fe commit 46f30ef
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 58 deletions.
4 changes: 2 additions & 2 deletions app/actions/EventActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
} from 'app/reducers';
import createQueryString from 'app/utils/createQueryString';
import { Event } from './ActionTypes';
import type { EventRegistrationPresence } from 'app/models';
import type { AppDispatch } from 'app/store/createStore';
import type { ID } from 'app/store/models';
import type {
DetailedEvent,
ListEvent,
UnknownEvent,
} from 'app/store/models/Event';
import type { Presence } from 'app/store/models/Registration';
import type { Thunk, Action } from 'app/types';

export const waitinglistPoolId = -1;
Expand Down Expand Up @@ -310,7 +310,7 @@ export function markUsernamePresent(eventId: ID, username: string) {
export function updatePresence(
eventId: ID,
registrationId: ID,
presence: EventRegistrationPresence,
presence: Presence,
) {
return callAPI({
types: Event.UPDATE_REGISTRATION,
Expand Down
5 changes: 3 additions & 2 deletions app/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { EventType } from './store/models/Event';
import type { Presence } from './store/models/Registration';
import type Comment from 'app/store/models/Comment';
import type { ListCompany } from 'app/store/models/Company';
import type { ReactionsGrouped } from 'app/store/models/Reaction';
Expand Down Expand Up @@ -178,7 +179,7 @@ type EventBase = {
};

export type Permission = string;
export type EventRegistrationPresence = 'PRESENT' | 'NOT_PRESENT' | 'UNKNOWN';

export type LEGACY_EventRegistrationPhotoConsent =
| 'PHOTO_NOT_CONSENT'
| 'PHOTO_CONSENT'
Expand Down Expand Up @@ -208,7 +209,7 @@ export type EventRegistration = {
unregistrationDate: Dateish;
status: EventRegistrationStatus;
pool: number;
presence: EventRegistrationPresence;
presence: Presence;
paymentStatus: EventRegistrationPaymentStatus;
feedback: string;
sharedMemberships?: number;
Expand Down
39 changes: 22 additions & 17 deletions app/routes/events/components/EventAdministrate/Administrate.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,40 +62,45 @@
color: var(--color-green-7);
}

.greenIcon {
font-size: 1.4em;
color: var(--success-color);

&:hover {
color: var(--color-green-7);
}
}

.orangePill {
background-color: var(--color-orange-2);
color: var(--color-orange-7);
}

.questionIcon {
font-size: 1.4em;
.bluePill {
background-color: var(--color-blue-2);
color: var(--color-blue-6);
}

.greenIcon {
color: var(--success-color);

ion-icon {
color: var(--success-color);
}
}

.tooLateIcon {
width: 34px;
height: 34px;
padding: 0.375rem;
cursor: pointer;
border-radius: 50%;

&:hover {
color: var(--color-blue-7);
background-color: var(--additive-background);
}
}

.bluePill {
background-color: var(--color-blue-2);
.questionIcon ion-icon {
color: var(--color-blue-6);
}

.redIcon {
font-size: 1.4em;
color: var(--danger-color);

&:hover {
color: var(--color-red-7);
ion-icon {
color: var(--danger-color);
}
}

Expand Down
114 changes: 82 additions & 32 deletions app/routes/events/components/EventAdministrate/AttendeeElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,24 @@ import {
} from 'app/actions/EventActions';
import Tooltip from 'app/components/Tooltip';
import { useAppDispatch } from 'app/store/hooks';
import { Presence } from 'app/store/models/Registration';
import styles from './Administrate.css';
import type {
EventRegistration,
EventRegistrationPaymentStatus,
EventRegistrationPresence,
ID,
} from 'app/models';
import type { ID } from 'app/store/models';

type TooltipIconProps = {
onClick?: (arg0: React.SyntheticEvent<any>) => unknown;
content: string;
transparent?: boolean;
iconName?: string;
iconClass: string;
disabled?: boolean;
};
type PresenceProps = {
presence: EventRegistrationPresence;
presence: Presence;
registrationId: ID;
};
type UnregisterProps = {
Expand All @@ -45,18 +46,27 @@ export const TooltipIcon = ({
onClick,
content,
transparent,
iconName,
iconClass,
disabled = false,
}: TooltipIconProps) => {
const classNames = cx(iconClass, transparent && styles.transparent);

return (
<Tooltip content={content}>
<button
className={cx(transparent && styles.transparent)}
onClick={onClick}
disabled={disabled}
>
<i className={iconClass} />
</button>
<Tooltip content={content} className={styles.presenceIcon}>
{iconName ? (
<Icon
name={iconName}
className={classNames}
onClick={onClick}
disabled={disabled}
size={22}
/>
) : (
<button onClick={onClick} disabled={disabled}>
<i className={classNames} />
</button>
)}
</Tooltip>
);
};
Expand All @@ -66,29 +76,66 @@ export const PresenceIcons = ({ presence, registrationId }: PresenceProps) => {
const dispatch = useAppDispatch();

return (
<Flex justifyContent="center">
<Flex alignItems="center" justifyContent="center">
<TooltipIcon
content="Til stede"
iconClass={cx('fa fa-check', styles.greenIcon)}
iconName="checkmark"
iconClass={styles.greenIcon}
transparent={presence !== 'PRESENT'}
onClick={() =>
dispatch(updatePresence(eventId, registrationId, 'PRESENT'))
eventId &&
dispatch(updatePresence(eventId, registrationId, Presence.PRESENT))
}
/>
<Tooltip content="Møtte for sent opp (gir 1 prikk)">
<div
className={cx(
styles.tooLateIcon,
presence !== 'LATE' && styles.transparent,
)}
onClick={() => {
eventId &&
dispatch(updatePresence(eventId, registrationId, Presence.LATE));
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="var(--color-orange-6)"
strokeWidth="1.4"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m12 10 2 4v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3a8 8 0 1 0-16 0v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3l2-4h4Z" />
<path d="M4.82 7.9 8 10" />
<path d="M15.18 7.9 12 10" />
<path d="M16.93 10H20a2 2 0 0 1 0 4H2" />
</svg>
</div>
</Tooltip>
<TooltipIcon
content="Ukjent"
iconClass={cx('fa fa-question-circle', styles.questionIcon)}
iconClass={styles.questionIcon}
iconName="help-outline"
transparent={presence !== 'UNKNOWN'}
onClick={() =>
dispatch(updatePresence(eventId, registrationId, 'UNKNOWN'))
eventId &&
dispatch(updatePresence(eventId, registrationId, Presence.UNKNOWN))
}
/>
<TooltipIcon
content="Ikke til stede"
iconClass={cx('fa fa-times', styles.redIcon)}
content="Møtte ikke opp (gir 2 prikker)"
iconClass={styles.redIcon}
iconName="close-outline"
transparent={presence !== 'NOT_PRESENT'}
onClick={() =>
dispatch(updatePresence(eventId, registrationId, 'NOT_PRESENT'))
eventId &&
dispatch(
updatePresence(eventId, registrationId, Presence.NOT_PRESENT),
)
}
/>
</Flex>
Expand All @@ -103,27 +150,29 @@ export const StripeStatus = ({
const dispatch = useAppDispatch();

return (
<Flex justifyContent="center">
<Flex alignItems="center" justifyContent="center">
<TooltipIcon
content="Betalt via Stripe"
iconClass={cx('fa fa-cc-stripe', styles.greenIcon)}
iconClass={cx('fa fa-cc-stripe', styles.greenIcon, styles.stripeIcon)}
transparent={paymentStatus !== 'succeeded'}
disabled
/>
<TooltipIcon
content="Betalt manuelt"
transparent={paymentStatus !== 'manual'}
iconClass={cx('fa fa-money', styles.greenIcon)}
iconName="cash-outline"
iconClass={styles.greenIcon}
onClick={() =>
dispatch(updatePayment(eventId, registrationId, 'manual'))
eventId && dispatch(updatePayment(eventId, registrationId, 'manual'))
}
/>
<TooltipIcon
content="Ikke betalt"
transparent={['manual', 'succeeded'].includes(paymentStatus)}
iconClass={cx('fa fa-times', styles.redIcon)}
iconName="close-outline"
iconClass={styles.redIcon}
onClick={() =>
dispatch(updatePayment(eventId, registrationId, 'failed'))
eventId && dispatch(updatePayment(eventId, registrationId, 'failed'))
}
/>
</Flex>
Expand All @@ -143,13 +192,14 @@ export const Unregister = ({ fetching, registration }: UnregisterProps) => {
title="Bekreft avregistrering"
message={`Er du sikker på at du vil melde av "${registration.user.fullName}"?`}
onConfirm={() => {
dispatch(
unregister({
eventId,
registrationId: registration.id,
admin: true,
}),
);
eventId &&
dispatch(
unregister({
eventId,
registrationId: registration.id,
admin: true,
}),
);
}}
closeOnConfirm
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const ConsentIcons = ({
photoConsents,
);
return (
<Flex justifyContent="center" gap={5}>
<Flex alignItems="center" justifyContent="center">
<TooltipIcon
content={consentMessage(webConsent)}
iconClass={iconClass(webConsent)}
Expand Down Expand Up @@ -254,7 +254,7 @@ export const RegisteredTable = ({
},
},
{
title: 'Til stede',
title: 'Oppmøte',
dataIndex: 'presence',
visible: showPresence,
render: (presence, registration) => {
Expand Down
4 changes: 2 additions & 2 deletions app/routes/events/components/RegistrationMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
import styles from './EventDetail/EventDetail.css';
import type { TextWithIconProps } from 'app/components/TextWithIcon';
import type {
EventRegistrationPresence,
EventRegistrationPaymentStatus,
LEGACY_EventRegistrationPhotoConsent,
PhotoConsent,
EventSemester,
} from 'app/models';
import type { Presence } from 'app/store/models/Registration';

type Props = {
registration: Record<string, any>;
Expand Down Expand Up @@ -146,7 +146,7 @@ const PresenceStatus = ({
hasEnded,
}: {
hasEnded: boolean;
presence: EventRegistrationPresence;
presence: Presence;
}) => {
switch (presence) {
case 'NOT_PRESENT':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import type {
} from 'app/store/models/User';
import type { ID } from 'app/store/models/index';

export enum Presence {
PRESENT = 'PRESENT',
LATE = 'LATE',
NOT_PRESENT = 'NOT_PRESENT',
UNKNOWN = 'UNKNOWN',
}

interface Registration {
id: ID;
user: PublicUser;
Expand All @@ -14,7 +21,7 @@ interface Registration {
updatedBy: ID;
pool: ID;
event: ID;
presence: string; //TODO: enum
presence: Presence;
feedback: string;
sharedMemberships: unknown;
status: string; //TODO: enum
Expand Down

0 comments on commit 46f30ef

Please sign in to comment.