From aafeef5586c60877ed6a0a9fab62a2c19f6ae36a Mon Sep 17 00:00:00 2001 From: brage-andreas Date: Mon, 22 Sep 2025 15:19:45 +0200 Subject: [PATCH 1/2] wip: merge attendee and attendance page --- .../(internal)/event/[id]/attendance-page.tsx | 143 +++++++++++++++--- .../(internal)/event/[id]/attendees-page.tsx | 115 -------------- .../src/app/(internal)/event/[id]/page.tsx | 8 - 3 files changed, 126 insertions(+), 140 deletions(-) delete mode 100644 apps/dashboard/src/app/(internal)/event/[id]/attendees-page.tsx diff --git a/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx b/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx index 9b4673a649..545ebdd439 100644 --- a/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx +++ b/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx @@ -1,23 +1,42 @@ -import type { Attendance } from "@dotkomonline/types" -import { Box, Divider, Title } from "@mantine/core" +import { UserSearch } from "@/app/(internal)/user/components/user-search" +import type { Attendance, Event, FeedbackFormAnswer } from "@dotkomonline/types" +import { Anchor, Button, Divider, Group, List, ListItem, Space, Stack, Text, Title } from "@mantine/core" +import { skipToken } from "@tanstack/react-query" import type { FC } from "react" +import { AllAttendeesTable } from "../components/all-attendees-table" import { useAttendanceForm } from "../components/attendance-form" +import { openManualCreateUserAttendModal } from "../components/manual-create-user-attend-modal" import { PoolBox } from "../components/pools-box" import { usePoolsForm } from "../components/pools-form" +import { QrCodeScanner } from "../components/qr-code-scanner" import { useAddAttendanceMutation, useUpdateAttendanceMutation } from "../mutations" +import { useEventFeedbackFormGetQuery, useFeedbackAnswersGetQuery } from "../queries" import { useEventContext } from "./provider" +const getMailTo = (eventTitle: string, emails: (string | null)[]) => { + return `mailto:?bcc=${emails.filter(Boolean).join(",")}&subject=(${eventTitle}) Melding fra arrangør` +} + export const AttendancePage: FC = () => { const { event, attendance } = useEventContext() + const { data: feedbackForm } = useEventFeedbackFormGetQuery(event.id) + const { data: feedbackAnswers } = useFeedbackAnswersGetQuery(feedbackForm?.id ?? skipToken) + if (!attendance) { return } - return + return ( + + + + + ) } const NoAttendanceFallback: FC<{ eventId: string }> = ({ eventId }) => { const mutation = useAddAttendanceMutation() + const AttendanceForm = useAttendanceForm({ defaultValues: { registerStart: new Date(), @@ -32,17 +51,18 @@ const NoAttendanceFallback: FC<{ eventId: string }> = ({ eventId }) => { }) return ( - + Lag påmelding - + ) } -interface EventAttendanceProps { +interface AttendanceSectionProps { attendance: Attendance } -const AttendancePageDetail: FC = ({ attendance }) => { + +const AttendanceSection: FC = ({ attendance }) => { const updateAttendanceMut = useUpdateAttendanceMutation() const AttendanceForm = useAttendanceForm({ @@ -65,19 +85,108 @@ const AttendancePageDetail: FC = ({ attendance }) => { }) return ( - - - - Påmeldingstid - + + + Påmeldingstid - - - + + + + + Påmeldingsgrupper - - + + + ) +} + +interface AttendeesSectionProps { + event: Event + attendance: Attendance + feedbackAnswers?: FeedbackFormAnswer[] +} + +const AttendeesSection: FC = ({ event, attendance, feedbackAnswers }) => { + const attendees = attendance.attendees.filter((attendee) => attendee.user.email !== null) + const attendeesWithoutEmail = attendance.attendees.filter((attendee) => !attendee.user.email) + + return ( + + + Alle påmeldte + + + + + + + {attendeesWithoutEmail.length > 0 && ( + + + Følgene påmeldte brukere har ikke registrert e-postadresse: + + + {attendeesWithoutEmail.map((attendee) => ( + + + {attendee.user.name} ({attendee.user.profileSlug}) + + + ))} + + + )} + + + attendee.userId)} + onSubmit={(values) => { + openManualCreateUserAttendModal({ + attendanceId: attendance.id, + userId: values.id, + }) + }} + /> + + + + Oppmøteregistrering + + + + + Påmeldte + + + + + ) } diff --git a/apps/dashboard/src/app/(internal)/event/[id]/attendees-page.tsx b/apps/dashboard/src/app/(internal)/event/[id]/attendees-page.tsx deleted file mode 100644 index b880f4f4b0..0000000000 --- a/apps/dashboard/src/app/(internal)/event/[id]/attendees-page.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { UserSearch } from "@/app/(internal)/user/components/user-search" -import type { Attendance, Event, FeedbackFormAnswer } from "@dotkomonline/types" -import { Anchor, Button, Group, List, ListItem, Space, Stack, Text, Title } from "@mantine/core" -import { skipToken } from "@tanstack/react-query" -import type { FC } from "react" -import { AllAttendeesTable } from "../components/all-attendees-table" -import { openManualCreateUserAttendModal } from "../components/manual-create-user-attend-modal" -import { QrCodeScanner } from "../components/qr-code-scanner" -import { useEventFeedbackFormGetQuery, useFeedbackAnswersGetQuery } from "../queries" -import { useEventContext } from "./provider" - -const getMailTo = (eventTitle: string, emails: (string | null)[]) => { - return `mailto:?bcc=${emails.filter(Boolean).join(",")}&subject=(${eventTitle}) Melding fra arrangør` -} - -export const AttendeesPage: FC = () => { - const { event, attendance } = useEventContext() - const { data: feedbackForm } = useEventFeedbackFormGetQuery(event.id) - const { data: feedbackAnswers } = useFeedbackAnswersGetQuery(feedbackForm?.id ?? skipToken) - - if (!attendance) { - // TODO: Return something useful here - return null - } - return -} - -interface Props { - event: Event - attendance: Attendance - feedbackAnswers?: FeedbackFormAnswer[] -} - -const Page: FC = ({ event, attendance, feedbackAnswers }) => { - const attendees = attendance.attendees.filter((attendee) => attendee.user.email !== null) - const attendeesWithoutEmail = attendance.attendees.filter((attendee) => !attendee.user.email) - - return ( - - - Alle påmeldte - - - - - - - {attendeesWithoutEmail.length > 0 && ( - - - Følgene påmeldte brukere har ikke registrert e-postadresse: - - - {attendeesWithoutEmail.map((attendee) => ( - - - {attendee.user.name} ({attendee.user.profileSlug}) - - - ))} - - - )} - - - attendee.userId)} - onSubmit={(values) => { - openManualCreateUserAttendModal({ - attendanceId: attendance.id, - userId: values.id, - }) - }} - /> - - - - Oppmøteregistrering - - - - - Påmeldte - - - - - - ) -} diff --git a/apps/dashboard/src/app/(internal)/event/[id]/page.tsx b/apps/dashboard/src/app/(internal)/event/[id]/page.tsx index 659db46ca0..8ac93dbf47 100644 --- a/apps/dashboard/src/app/(internal)/event/[id]/page.tsx +++ b/apps/dashboard/src/app/(internal)/event/[id]/page.tsx @@ -15,12 +15,10 @@ import { IconListDetails, IconSelector, IconTrash, - IconUser, } from "@tabler/icons-react" import { useRouter, useSearchParams } from "next/navigation" import { useDeleteEventMutation } from "../mutations" import { AttendancePage } from "./attendance-page" -import { AttendeesPage } from "./attendees-page" import { EventEditCard } from "./edit-card" import { FeedbackPage } from "./feedback-page" import { PaymentPage } from "./payment-page" @@ -46,12 +44,6 @@ const SIDEBAR_LINKS = [ slug: "pamelding", component: AttendancePage, }, - { - icon: IconUser, - label: "Påmeldte", - slug: "pameldte", - component: AttendeesPage, - }, { icon: IconSelector, label: "Valg", From 12da95c160528830efd4f58697a638df1a4201ec Mon Sep 17 00:00:00 2001 From: brage-andreas Date: Wed, 24 Sep 2025 17:43:08 +0200 Subject: [PATCH 2/2] wip idk --- .../(internal)/event/[id]/attendance-page.tsx | 21 +++++---- .../(internal)/event/[id]/selections-page.tsx | 47 +++++++++++++++---- .../event/components/attendance-form.tsx | 46 ------------------ packages/types/src/attendance.ts | 20 ++++++++ 4 files changed, 68 insertions(+), 66 deletions(-) delete mode 100644 apps/dashboard/src/app/(internal)/event/components/attendance-form.tsx diff --git a/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx b/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx index 545ebdd439..c7130554a0 100644 --- a/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx +++ b/apps/dashboard/src/app/(internal)/event/[id]/attendance-page.tsx @@ -65,19 +65,20 @@ interface AttendanceSectionProps { const AttendanceSection: FC = ({ attendance }) => { const updateAttendanceMut = useUpdateAttendanceMutation() + const onAttendanceFormSubmit = (values) => { + updateAttendanceMut.mutate({ + id: attendance.id, + attendance: { + registerStart: values.registerStart, + registerEnd: values.registerEnd, + deregisterDeadline: values.deregisterDeadline, + }, + }) + } + 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({ diff --git a/apps/dashboard/src/app/(internal)/event/[id]/selections-page.tsx b/apps/dashboard/src/app/(internal)/event/[id]/selections-page.tsx index cd82c5bf46..6477090e82 100644 --- a/apps/dashboard/src/app/(internal)/event/[id]/selections-page.tsx +++ b/apps/dashboard/src/app/(internal)/event/[id]/selections-page.tsx @@ -1,10 +1,12 @@ +import { createDateTimeInput } from "@/components/forms/DateTimeInput" import { useTRPC } from "@/lib/trpc-client" -import type { Attendance } from "@dotkomonline/types" -import { ActionIcon, Box, Button, Divider, Paper, Table, Title } from "@mantine/core" +import { type Attendance,type AttendanceWrite,AttendanceWriteSchema } from "@dotkomonline/types" +import { zodResolver } from "@hookform/resolvers/zod" +import { ActionIcon, Box, Button, Divider, Group, Paper, Table, Title } from "@mantine/core" import { IconEdit, IconTrash } from "@tabler/icons-react" import { useQuery } from "@tanstack/react-query" import type { FC } from "react" -import { useAttendanceForm } from "../components/attendance-form" +import { useForm } from "react-hook-form" import { useCreateAttendanceSelectionsModal } from "../components/create-event-selections-modal" import { useEditSelectionsModal } from "../components/edit-event-selections-modal" import { useAddAttendanceMutation, useUpdateAttendanceMutation } from "../mutations" @@ -22,23 +24,48 @@ export const SelectionsPage: FC = () => { const NoAttendanceFallback: FC<{ eventId: string }> = ({ eventId }) => { const mutation = useAddAttendanceMutation() - const AttendanceForm = useAttendanceForm({ - label: "Opprett", + const form = useForm({ + resolver: zodResolver(AttendanceWriteSchema), + mode: "onBlur", defaultValues: { registerStart: new Date(), registerEnd: new Date(), deregisterDeadline: new Date(), - selections: [], - }, - onSubmit: (values) => { - mutation.mutate({ eventId, values }) }, }) + const Lol = createDateTimeInput({ label: "Påmeldingsstart" }) + const x = + return ( Ingen påmelding - + +
{ + e.preventDefault() + return form.handleSubmit((values) => { + return onSubmit(values, form) + })(e) + }} + > + + {x} + +
+ +
+
+
) } diff --git a/apps/dashboard/src/app/(internal)/event/components/attendance-form.tsx b/apps/dashboard/src/app/(internal)/event/components/attendance-form.tsx deleted file mode 100644 index adcffa158f..0000000000 --- a/apps/dashboard/src/app/(internal)/event/components/attendance-form.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { createDateTimeInput } from "@/components/forms/DateTimeInput" -import { useFormBuilder } from "@/components/forms/Form" -import { AttendanceWriteSchema } from "@dotkomonline/types" -import type { z } from "zod" - -// Define the schema without the omitted fields -const AttendanceFormSchema = AttendanceWriteSchema.superRefine((val, ctx) => { - if (val.registerStart > val.registerEnd) { - const message = "Påmeldingsstart må være før påmeldingsslutt" - const code = "custom" - ctx.addIssue({ message, code, path: ["registerEnd"] }) - } - - if (val.registerStart > val.deregisterDeadline) { - const message = "Påmeldingsstart må være før frist avmelding" - const code = "custom" - ctx.addIssue({ message, code, path: ["deregisterDeadline"] }) - } - - return true -}) - -interface AttendanceFormProps { - onSubmit(values: z.infer): void - defaultValues?: z.infer - label: string -} - -export const useAttendanceForm = ({ onSubmit, defaultValues, label }: AttendanceFormProps) => - useFormBuilder({ - schema: AttendanceFormSchema, - defaultValues, - onSubmit, // Directly use the onSubmit prop - label, - fields: { - registerStart: createDateTimeInput({ - label: "Påmeldingsstart", - }), - deregisterDeadline: createDateTimeInput({ - label: "Frist avmelding", - }), - registerEnd: createDateTimeInput({ - label: "Påmeldingsslutt", - }), - }, - }) diff --git a/packages/types/src/attendance.ts b/packages/types/src/attendance.ts index 4772ae4b99..6bf00909e4 100644 --- a/packages/types/src/attendance.ts +++ b/packages/types/src/attendance.ts @@ -1,5 +1,6 @@ import { schemas } from "@dotkomonline/db/schemas" import { compareAsc } from "date-fns" +import { isBefore } from "date-fns" import { z } from "zod" import { type User, UserSchema, findActiveMembership, getMembershipGrade } from "./user" @@ -97,6 +98,25 @@ export const AttendanceWriteSchema = AttendanceSchema.pick({ registerEnd: true, deregisterDeadline: true, selections: true, + attendancePrice: true, +}).superRefine((val, ctx) => { + if (isBefore(val.registerEnd, val.registerStart)) { + ctx.addIssue({ + code: "custom", + message: "Påmeldingsslutt kan ikke være før påmeldingsstart", + path: ["registerEnd"], + }) + } + + if (isBefore(val.deregisterDeadline, val.registerStart)) { + ctx.addIssue({ + code: "custom", + message: "Avmeldingsfrist kan ikke være før påmeldingsstart", + path: ["deregisterDeadline"], + }) + } + + return true }) export function getReservedAttendeeCount(attendance: Attendance, poolId?: AttendancePoolId): number {