Skip to content

Commit

Permalink
Implement event attendance MVP (#804)
Browse files Browse the repository at this point in the history
Co-authored-by: Mats Jun <[email protected]>
Co-authored-by: Jo Gramnæs Tjernshaugen <[email protected]>
  • Loading branch information
3 people authored Apr 9, 2024
1 parent 404fa3d commit 8d86626
Show file tree
Hide file tree
Showing 122 changed files with 4,456 additions and 1,331 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ WEB_AUTH0_ISSUER=https://dev.id.online.ntnu.no
GTX_AUTH0_CLIENT_ID=
GTX_AUTH0_CLIENT_SECRET=
GTX_AUTH0_ISSUER=https://dev.id.online.ntnu.no
GTX_AUTH0_DOMAIN=onlineweb.eu.auth0.com

# Private secret for signing calendar generation and NextAuth JWTs
# Generate these yourself with `openssl rand -base64 32` or similar tool
Expand Down
179 changes: 82 additions & 97 deletions apps/dashboard/src/app/(dashboard)/event/[id]/attendance-page.tsx
Original file line number Diff line number Diff line change
@@ -1,114 +1,99 @@
import type { Attendee, User } from "@dotkomonline/types"
import { Button } from "@dotkomonline/ui"
import { Box, Checkbox, Title } from "@mantine/core"
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"
import React, { useMemo, useState, type FC } from "react"
import GenericSearch from "../../../../components/GenericSearch"
import { GenericTable } from "../../../../components/GenericTable"
import { useDeregisterForEventMutation } from "../../../../modules/event/mutations/use-deregister-for-event-mutation"
import { useRegisterForEventMutation } from "../../../../modules/event/mutations/use-register-for-event-mutation"
import { useUpdateEventAttendanceMutation } from "../../../../modules/event/mutations/use-update-event-attendance-mutation"
import { useEventAttendanceGetQuery } from "../../../../modules/event/queries/use-event-attendance-get-query"
import { useUserSearchQuery } from "../../../../modules/user/queries/use-user-search-query"
import type { Attendance } from "@dotkomonline/types"
import { Divider } from "@mantine/core"
import { Box, Title } from "@mantine/core"
import type { FC } from "react"
import { useAttendanceForm } from "../../../../modules/attendance/components/attendance-page/AttendanceForm"
import { InfoBox } from "../../../../modules/attendance/components/attendance-page/InfoBox"
import { PoolBox } from "../../../../modules/attendance/components/attendance-page/PoolsBox"
import { usePoolsForm } from "../../../../modules/attendance/components/attendance-page/PoolsForm"
import {
useAddAttendanceMutation,
useUpdateAttendanceMutation,
} from "../../../../modules/attendance/mutations/use-attendance-mutations"
import { usePoolsGetQuery } from "../../../../modules/attendance/queries/use-get-queries"
import { useEventDetailsContext } from "./provider"

interface CustomCheckboxProps {
userId: string
attendanceId: string
defaultChecked?: boolean
}
const CustomCheckbox = React.memo(({ attendanceId, userId, defaultChecked }: CustomCheckboxProps) => {
const updateAttendance = useUpdateEventAttendanceMutation()
export const AttendancePage: FC = () => {
const { attendance } = useEventDetailsContext()
const { event } = useEventDetailsContext()

const toggleAttendance = (userId: string, attendanceId: string, currentCheckedState: boolean) => {
updateAttendance.mutate({ userId, attendanceId, attended: currentCheckedState })
if (!attendance) {
return <NoAttendanceFallback eventId={event.id} />
}
return (
<Checkbox
onChange={(event) => {
toggleAttendance(userId, attendanceId, event.currentTarget.checked)
}}
defaultChecked={defaultChecked}
/>
)
})

CustomCheckbox.displayName = "attendanceToggle"
return <_AttendancePage attendance={attendance} />
}

export const EventAttendancePage: FC = () => {
const { event } = useEventDetailsContext()
const { eventAttendance } = useEventAttendanceGetQuery(event.id)
const [searchQuery, setSearchQuery] = useState("")
const { users } = useUserSearchQuery(searchQuery)
const registerForEvent = useRegisterForEventMutation()
const deregisterForEvent = useDeregisterForEventMutation()
const NoAttendanceFallback: FC<{ eventId: string }> = ({ eventId }) => {
const mutation = useAddAttendanceMutation()
const AttendanceForm = useAttendanceForm({
defaultValues: {
registerStart: new Date(),
registerEnd: new Date(),
deregisterDeadline: new Date(),
extras: null,
},
label: "Opprett",
onSubmit: (values) => {
mutation.mutate({ eventId, obj: values })
},
})

const columnHelper = createColumnHelper<Attendee>()
const columns = useMemo(
() => [
columnHelper.accessor("userId", {
header: () => "Bruker",
}),
columnHelper.accessor((attendee) => attendee, {
id: "attended",
header: () => "Møtt",
cell: (info) => (
<CustomCheckbox
userId={info.getValue().userId}
attendanceId={info.getValue().attendanceId}
defaultChecked={info.getValue().attended}
/>
),
}),
columnHelper.accessor((attendee) => attendee, {
id: "deregsiter",
header: () => "Meld av",
cell: (info) => (
<Button
onClick={() =>
deregisterForEvent.mutate({ attendanceId: info.getValue().attendanceId, userId: info.getValue().userId })
}
>
X
</Button>
),
}),
],
[columnHelper, deregisterForEvent]
return (
<Box>
<Title order={5}>Lag påmelding</Title>
<AttendanceForm />
</Box>
)
}

const table = useReactTable({
data: useMemo(() => eventAttendance.flatMap((attendance) => attendance.attendees), [eventAttendance]),
getCoreRowModel: getCoreRowModel(),
columns,
})
interface EventAttendanceProps {
attendance: Attendance
}
const _AttendancePage: FC<EventAttendanceProps> = ({ attendance }) => {
const { pools } = usePoolsGetQuery(attendance.id)

const handleUserSearch = (query: string) => {
setSearchQuery(query)
}
const updateAttendanceMut = useUpdateAttendanceMutation()

const handleUserClick = (user: User) => {
registerForEvent.mutate({ eventId: event.id, userId: user.id.toString() })
}
const AttendanceForm = useAttendanceForm({
defaultValues: attendance,
label: "Oppdater",
onSubmit: (values) => {
updateAttendanceMut.mutate({
id: attendance.id,
attendance: {
registerStart: values.registerStart,
registerEnd: values.registerEnd,
deregisterDeadline: values.deregisterDeadline,
},
})
},
})

const PoolsForm = usePoolsForm({
attendanceId: attendance.id,
pools,
})

return (
<Box>
<Title order={3}>Meld på</Title>
<GenericSearch
onSearch={handleUserSearch}
onSubmit={handleUserClick}
items={users}
dataMapper={(item: User) => item.id.toString()}
placeholder="Søk etter bruker..."
/>
{eventAttendance.map((attendance) => (
<Box key={attendance.id} mb="sm">
<Title order={4}>
{attendance.id} {`(${attendance.attendees.length}/${attendance.limit})`}
</Title>
<GenericTable table={table} />
</Box>
))}
<Box>
<Title mb={10} order={3}>
Generelt
</Title>
<AttendanceForm />
</Box>
<Divider my={32} />
<Box>
<Title mb={10} order={3}>
Reserverte plasser
</Title>
<InfoBox pools={pools || []} />
</Box>
<Box>
<PoolsForm />
<PoolBox pools={pools || []} attendanceId={attendance.id} />
</Box>
</Box>
)
}
51 changes: 51 additions & 0 deletions apps/dashboard/src/app/(dashboard)/event/[id]/attendees-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Attendance } from "@dotkomonline/types"
import { Box, Divider, Title } from "@mantine/core"
import type { FC } from "react"
import { UserSearch } from "../../../../components/molecules/UserSearch/UserSearch"
import { openCreateManualUserAttendModal } from "../../../../modules/attendance/modals/manual-user-attend-modal"
import { useEventAttendeesGetQuery } from "../../../../modules/attendance/queries/use-get-queries"
import { AllAttendeesTable } from "../all-users-table"
import { useEventDetailsContext } from "./provider"

export const AttendeesPage: FC = () => {
const { attendance } = useEventDetailsContext()

if (!attendance) {
return <div>Arrangementet har ikke påmelding</div>
}

return <Page attendance={attendance} />
}

interface Props {
attendance: Attendance
}

const Page: FC<Props> = ({ attendance }) => {
const { attendees } = useEventAttendeesGetQuery(attendance.id)

return (
<Box>
<Box>
<Title mb={10} order={3}>
Meld på bruker
</Title>
<UserSearch
onSubmit={(values) => {
openCreateManualUserAttendModal({
attendanceId: attendance.id,
userId: values.id,
})
}}
/>
</Box>
<Divider my={32} />
<Box>
<Title mb={10} order={3}>
Alle påmeldte
</Title>
<AllAttendeesTable users={attendees} attendanceId={attendance.id} />
</Box>
</Box>
)
}
11 changes: 7 additions & 4 deletions apps/dashboard/src/app/(dashboard)/event/[id]/edit-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export const EventEditCard: FC = () => {
const { event, eventCommittees } = useEventDetailsContext()
const edit = useEditEventWithCommitteesMutation()
const { committees } = useCommitteeAllQuery()

const defaultValues = {
...event,
committeeIds: eventCommittees.map((committee) => committee.id),
}

const FormComponent = useEventEditForm({
label: "Oppdater arrangement",
committees,
Expand All @@ -19,10 +25,7 @@ export const EventEditCard: FC = () => {
committees: committeeIds,
})
},
defaultValues: {
...event,
committeeIds: eventCommittees.map((committee) => committee.committeeId),
},
defaultValues,
})
return <FormComponent />
}
68 changes: 52 additions & 16 deletions apps/dashboard/src/app/(dashboard)/event/[id]/extras-page.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,77 @@
import type { Attendance } from "@dotkomonline/types"
import { Icon } from "@iconify/react"
import { ActionIcon, Box, Button, Paper, Title } from "@mantine/core"
import type { FC } from "react"
import { useCreateEventExtrasModal } from "../../../../modules/event/modals/create-event-extras-modal"
import { useEditEventExtrasModal } from "../../../../modules/event/modals/edit-event-extras-modal"
import { useEditEventMutation } from "../../../../modules/event/mutations/use-edit-event-mutation"
import useAttendanceForm from "../../../../modules/attendance/components/attendance-page/AttendanceForm"
import {
useAddAttendanceMutation,
useUpdateExtrasMutation,
} from "../../../../modules/attendance/mutations/use-attendance-mutations"
import { useCreateAttendanceExtrasModal } from "../../../../modules/event/modals/create-event-extras-modal"
import { useEditExtrasModal } from "../../../../modules/event/modals/edit-event-extras-modal"
import { useEventDetailsContext } from "./provider"

export const ExtrasPage: FC = () => {
const { attendance } = useEventDetailsContext()
const { event } = useEventDetailsContext()

const openCreate = useCreateEventExtrasModal({
event,
if (!attendance) {
return <NoAttendanceFallback eventId={event.id} />
}

return <_ExtrasPage attendance={attendance} />
}

const NoAttendanceFallback: FC<{ eventId: string }> = ({ eventId }) => {
const mutation = useAddAttendanceMutation()
const AttendanceForm = useAttendanceForm({
defaultValues: {
registerStart: new Date(),
registerEnd: new Date(),
deregisterDeadline: new Date(),
extras: [],
},
label: "Opprett",
onSubmit: (values) => {
mutation.mutate({ eventId, obj: values })
},
})

return (
<Box>
<Title order={5}>Ingen påmelding</Title>
<AttendanceForm />
</Box>
)
}
interface Props {
attendance: Attendance
}
export const _ExtrasPage: FC<Props> = ({ attendance }) => {
const openCreate = useCreateAttendanceExtrasModal({
attendance,
})

const openEdit = useEditEventExtrasModal({
event,
const openEdit = useEditExtrasModal({
attendance,
})

const edit = useEditEventMutation()
const edit = useUpdateExtrasMutation()

const deleteAlternative = (id: string) => {
const newChoices = event.extras?.filter((alt) => alt.id !== id)
const newChoices = attendance.extras?.filter((alt) => alt.id !== id)
edit.mutate({
id: event.id,
event: {
...event,
extras: newChoices ?? [],
},
id: attendance.id,
extras: newChoices ?? [],
})
}

return (
<Box>
<Title order={3}>Valg</Title>
{!event.extras?.length && <p>Ingen valg er lagt til</p>}
{!attendance.extras?.length && <p>Ingen valg er lagt til</p>}
<Box>
{event.extras?.map((extra) => (
{attendance.extras?.map((extra) => (
<Paper key={extra.id} withBorder p={"md"} mt={"md"}>
<ActionIcon variant="outline" onClick={() => openEdit(extra)} mr="md">
<Icon icon="tabler:edit" />
Expand Down
Loading

1 comment on commit 8d86626

@vercel
Copy link

@vercel vercel bot commented on 8d86626 Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

invoicification – ./apps/invoicification

invoicification.vercel.app
invoicification-git-main-dotkom.vercel.app
invoicification-dotkom.vercel.app
faktura.online.ntnu.no

Please sign in to comment.