From 606379d151c1445c956c60721b72724ae68cb5d2 Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:01:15 +0100 Subject: [PATCH 1/3] Remove Event-Appointment --- .../[id]/appointment/[appointmentId]/page.tsx | 34 +++ .../[id]/participant/[participantId]/page.tsx | 50 ++++ .../event/_components/AppointmentForm.tsx | 260 ++++++++++++++++++ .../event/_components/ParticipantForm.tsx | 218 +++++++++++++++ 4 files changed, 562 insertions(+) create mode 100644 apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx create mode 100644 apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx create mode 100644 apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx create mode 100644 apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx diff --git a/apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx b/apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx new file mode 100644 index 00000000..b3373878 --- /dev/null +++ b/apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx @@ -0,0 +1,34 @@ +import { AppointmentForm } from "(app)/admin/event/_components/AppointmentForm"; +import { prisma } from "@repo/db"; + +export default async function Page({ + params, +}: { + params: Promise<{ id: string; appointmentId: string }>; +}) { + const { id: eventId, appointmentId } = await params; + + const event = await prisma.event.findUnique({ + where: { id: parseInt(eventId) }, + }); + + if (!event) return
Event nicht gefunden
; + + let appointment = null; + if (appointmentId !== "new") { + appointment = await prisma.eventAppointment.findUnique({ + where: { id: parseInt(appointmentId) }, + include: { + Presenter: true, + Participants: { + include: { User: true }, + }, + }, + }); + if (!appointment) return
Termin nicht gefunden
; + } + + return ( + + ); +} diff --git a/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx b/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx new file mode 100644 index 00000000..1cfe0d6c --- /dev/null +++ b/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx @@ -0,0 +1,50 @@ +import { prisma } from "@repo/db"; +import { ParticipantForm } from "../../../_components/ParticipantForm"; +import { Error } from "_components/Error"; +import Link from "next/link"; +import { PersonIcon } from "@radix-ui/react-icons"; +import { ArrowLeft } from "lucide-react"; + +export default async function Page({ + params, +}: { + params: Promise<{ id: string; participantId: string }>; +}) { + const { id: eventId, participantId } = await params; + + const event = await prisma.event.findUnique({ + where: { id: parseInt(eventId) }, + }); + + const participant = await prisma.participant.findUnique({ + where: { id: parseInt(participantId) }, + }); + + const user = await prisma.user.findUnique({ + where: { id: participant?.userId }, + }); + + if (!event) return
Event nicht gefunden
; + + if (!participant || !user) { + return ; + } + + return ( +
+
+
+ + + Zurück zum Event + +
+

+ Event-übersicht für{" "} + {`${user.firstname} ${user.lastname} #${user.publicId}`} +

+
+ +
+ ); +} diff --git a/apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx b/apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx new file mode 100644 index 00000000..cae6f8c3 --- /dev/null +++ b/apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx @@ -0,0 +1,260 @@ +"use client"; +import { + EventAppointmentOptionalDefaults, + EventAppointmentOptionalDefaultsSchema, +} from "@repo/db/zod"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useRef } from "react"; +import { ColumnDef } from "@tanstack/react-table"; +import { Event, EventAppointment, Participant, Prisma } from "@repo/db"; +import { ArrowLeft, Calendar, Users } from "lucide-react"; +import toast from "react-hot-toast"; +import Link from "next/link"; +import { PaginatedTable, PaginatedTableRef } from "../../../_components/PaginatedTable"; +import { Button } from "../../../_components/ui/Button"; +import { DateInput } from "../../../_components/ui/DateInput"; +import { upsertParticipant } from "../../events/actions"; +import { upsertAppointment, deleteAppoinement } from "../action"; +import { InputJsonValueType } from "@repo/db/zod"; + +interface AppointmentFormProps { + event: Event; + initialAppointment: + | (EventAppointment & { + Presenter?: any; + Participants?: (Participant & { User?: any })[]; + }) + | null; + appointmentId: string; +} + +export const AppointmentForm = ({ + event, + initialAppointment, + appointmentId, +}: AppointmentFormProps) => { + const router = useRouter(); + const participantTableRef = useRef(null); + + const form = useForm({ + resolver: zodResolver(EventAppointmentOptionalDefaultsSchema), + defaultValues: initialAppointment + ? { + ...initialAppointment, + eventId: event.id, + presenterId: initialAppointment.presenterId, + } + : undefined, + }); + + const onSubmit = async (values: EventAppointmentOptionalDefaults) => { + try { + await upsertAppointment({ + ...values, + eventId: event.id, + }); + toast.success("Termin erfolgreich gespeichert"); + router.back(); + } catch { + toast.error("Fehler beim Speichern des Termins"); + } + }; + + const handleDelete = async () => { + if (!confirm("Wirklich löschen?")) return; + try { + await deleteAppoinement(parseInt(appointmentId)); + toast.success("Termin gelöscht"); + router.back(); + } catch { + toast.error("Fehler beim Löschen"); + } + }; + + return ( +
+
+ {/* Header */} +
+ + Zurück + +

+ {appointmentId === "new" ? "Neuer Termin" : "Termin bearbeiten"} +

+
+ + {/* Main Content */} +
+ {/* Form Card */} +
+
+
+

+ Termin Details +

+ +
+ + form.setValue("appointmentDate", date)} + /> + {form.formState.errors.appointmentDate && ( + + {form.formState.errors.appointmentDate.message} + + )} +
+ +
+ +
+ + {appointmentId !== "new" && ( + + )} +
+
+
+ + + {/* Participants Table */} +
+
+
+

+ Teilnehmer +

+ + {appointmentId !== "new" ? ( + + ({ + eventAppointmentId: appointmentId, + }) as Prisma.ParticipantWhereInput + } + include={{ User: true }} + columns={ + [ + { + accessorKey: "User.firstname", + header: "Vorname", + }, + { + accessorKey: "User.lastname", + header: "Nachname", + }, + { + accessorKey: "enscriptionDate", + header: "Einschreibedatum", + cell: ({ row }) => { + return ( + {new Date(row.original.enscriptionDate).toLocaleString()} + ); + }, + }, + { + header: "Status", + cell: ({ row }) => { + if (row.original.attended) { + return Anwesend; + } else if (row.original.appointmentCancelled) { + return Abgesagt; + } else { + return Offen; + } + }, + }, + { + header: "Aktionen", + cell: ({ row }) => { + return ( +
+ + Anzeigen + + {!row.original.attended && ( + + )} + {!row.original.appointmentCancelled && ( + + )} +
+ ); + }, + }, + ] as ColumnDef[] + } + /> + ) : ( +
+ Speichern Sie den Termin um Teilnehmer hinzuzufügen +
+ )} +
+
+
+
+
+
+ ); +}; diff --git a/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx b/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx new file mode 100644 index 00000000..39dc62e3 --- /dev/null +++ b/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx @@ -0,0 +1,218 @@ +"use client"; +import { Participant, Event, User, ParticipantLog, Prisma } from "@repo/db"; +import { Users, Activity, Bug } from "lucide-react"; +import toast from "react-hot-toast"; +import { InputJsonValueType, ParticipantOptionalDefaultsSchema } from "@repo/db/zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Switch } from "_components/ui/Switch"; +import { useState } from "react"; +import { Button } from "_components/ui/Button"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { upsertParticipant } from "(app)/events/actions"; +import { deleteParticipant } from "../action"; +import { redirect } from "next/navigation"; + +interface ParticipantFormProps { + event: Event; + participant: Participant; + user: User; +} + +const checkEventCompleted = (participant: Participant, event: Event): boolean => { + if (event.hasPresenceEvents) { + return event.finisherMoodleCourseId + ? participant.finisherMoodleCurseCompleted && participant.attended + : !!participant.attended; + } + return event.finisherMoodleCourseId ? participant.finisherMoodleCurseCompleted : false; +}; + +export const ParticipantForm = ({ event, participant, user }: ParticipantFormProps) => { + const [isLoading, setIsLoading] = useState(false); + const queryClient = useQueryClient(); + + const upsertParticipantMutation = useMutation({ + mutationKey: ["upsertParticipant"], + mutationFn: async (newData: Prisma.ParticipantUncheckedCreateInput) => { + const data = await upsertParticipant(newData); + await queryClient.invalidateQueries({ queryKey: ["participants", event.id] }); + return data; + }, + }); + + const deleteParticipantMutation = useMutation({ + mutationKey: ["deleteParticipant"], + mutationFn: async (participantId: number) => { + await deleteParticipant(participantId); + await queryClient.invalidateQueries({ queryKey: ["participants", event.id] }); + }, + }); + + const eventCompleted = checkEventCompleted(participant, event); + + const form = useForm({ + resolver: zodResolver(ParticipantOptionalDefaultsSchema), + defaultValues: participant, + }); + + const handleEventFinished = async () => { + setIsLoading(true); + try { + await upsertParticipantMutation.mutateAsync({ + eventId: event.id, + userId: participant.userId, + }); + toast.success("Event als beendet markiert"); + } finally { + setIsLoading(false); + } + }; + + const handleCheckMoodle = async () => { + setIsLoading(true); + try { + toast.success("Moodle-Check durchgeführt"); + } finally { + setIsLoading(false); + } + }; + + return ( +
{ + const data = await upsertParticipantMutation.mutateAsync({ + ...formData, + statusLog: participant.statusLog as unknown as Prisma.ParticipantCreatestatusLogInput, + eventId: event.id, + userId: participant.userId, + }); + form.reset(data); + toast.success("Teilnehmer aktualisiert"); + })} + className="bg-base-100 flex flex-wrap gap-6 p-6" + > + {/* Status Section */} +
+
+

+ + Debug +

+ + + +
+
+ {/* Info Card */} +
+
+

+ Informationen +

+
+
+

Kurs-status

+

{eventCompleted ? "Abgeschlossen" : "In Bearbeitung"}

+

Einschreibedatum

+

+ {participant?.enscriptionDate + ? new Date(participant.enscriptionDate).toLocaleDateString() + : "-"} +

+
+ +
+ +
+ + +
+
+
+ + {/* Activity Log */} +
+
+

+ Aktivitätslog +

+ +
+ + + + + + + + + + {participant?.statusLog && + Array.isArray(participant.statusLog) && + (participant.statusLog as InputJsonValueType[]) + .slice() + .reverse() + .map((log: InputJsonValueType, index: number) => { + const logEntry = log as unknown as ParticipantLog; + return ( + + + + + + ); + })} + +
DatumEventUser
+ {logEntry.timestamp + ? new Date(logEntry.timestamp).toLocaleDateString() + : "-"} + {logEntry.event || "-"}{logEntry.user || "-"}
+
+
+
+
+
+
+ + {event && ( + + )} +
+
+
+ + ); +}; From 9129652912d588b173d86c403c2a2955dbe3d7fc Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:09:39 +0100 Subject: [PATCH 2/3] remove appointment from events --- apps/core-server/routes/helper.ts | 1 - .../app/(app)/_components/FeaturedEvents.tsx | 25 +- apps/hub/app/(app)/_components/FirstPath.tsx | 40 +-- apps/hub/app/(app)/_components/Footer.tsx | 8 +- .../[id]/appointment/[appointmentId]/page.tsx | 34 --- .../[id]/participant/[participantId]/page.tsx | 4 +- .../event/_components/AppointmentForm.tsx | 260 ------------------ .../event/_components/AppointmentModal.tsx | 203 -------------- .../(app)/admin/event/_components/Form.tsx | 247 +---------------- .../event/_components/ParticipantForm.tsx | 11 +- apps/hub/app/(app)/admin/event/action.ts | 31 +-- .../(app)/events/_components/EventCard.tsx | 27 +- .../app/(app)/events/_components/Modal.tsx | 198 +------------ apps/hub/app/(app)/events/page.tsx | 53 +--- apps/hub/app/api/event/route.ts | 9 - apps/hub/helper/events.ts | 6 +- packages/database/prisma/schema/event.prisma | 27 +- .../migration.sql | 35 +++ .../migration.sql | 8 + .../migration.sql | 8 + packages/database/prisma/schema/user.prisma | 2 - packages/shared-components/helper/event.ts | 1 - 22 files changed, 105 insertions(+), 1133 deletions(-) delete mode 100644 apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx delete mode 100644 apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx delete mode 100644 apps/hub/app/(app)/admin/event/_components/AppointmentModal.tsx create mode 100644 packages/database/prisma/schema/migrations/20260117233913_remove_event_appointments/migration.sql create mode 100644 packages/database/prisma/schema/migrations/20260117235335_remove_event_appointment/migration.sql create mode 100644 packages/database/prisma/schema/migrations/20260117235623_remove_max_permissions/migration.sql diff --git a/apps/core-server/routes/helper.ts b/apps/core-server/routes/helper.ts index d712ee86..8ab02909 100644 --- a/apps/core-server/routes/helper.ts +++ b/apps/core-server/routes/helper.ts @@ -7,7 +7,6 @@ const router: Router = Router(); export const eventCompleted = (event: Event, participant?: Participant) => { if (!participant) return false; if (event.finisherMoodleCourseId && !participant.finisherMoodleCurseCompleted) return false; - if (event.hasPresenceEvents && !participant.attended) return false; return true; }; diff --git a/apps/hub/app/(app)/_components/FeaturedEvents.tsx b/apps/hub/app/(app)/_components/FeaturedEvents.tsx index b3233bd5..0aa5782f 100644 --- a/apps/hub/app/(app)/_components/FeaturedEvents.tsx +++ b/apps/hub/app/(app)/_components/FeaturedEvents.tsx @@ -23,15 +23,6 @@ const page = async () => { userId: user.id, }, }, - Appointments: { - include: { - Participants: { - where: { - appointmentCancelled: false, - }, - }, - }, - }, }, }); @@ -47,23 +38,13 @@ const page = async () => { return (
-

- Laufende Events & Kurse +

+ Laufende Events & Kurse

{filteredEvents.map((event) => { - return ( - - a.Participants.find((p) => p.userId == user.id), - )} - user={user} - event={event} - key={event.id} - /> - ); + return ; })}
diff --git a/apps/hub/app/(app)/_components/FirstPath.tsx b/apps/hub/app/(app)/_components/FirstPath.tsx index b12301c5..3bda43c3 100644 --- a/apps/hub/app/(app)/_components/FirstPath.tsx +++ b/apps/hub/app/(app)/_components/FirstPath.tsx @@ -20,18 +20,18 @@ const PathsOptions = ({
{/* Disponent Card */}
setSelected("disponent")} role="button" tabIndex={0} aria-pressed={selected === "disponent"} > -

+

Disponent

-
+
Denkt sich realistische Einsatzszenarien aus, koordiniert deren Ablauf und ist die zentrale Schnittstelle zwischen Piloten und bodengebundenen Rettungsmitteln. Er trägt die Verantwortung für einen reibungslosen Ablauf und der erfolgreichen Durchführung der @@ -43,18 +43,18 @@ const PathsOptions = ({
{/* Pilot Card */}
setSelected("pilot")} role="button" tabIndex={0} aria-pressed={selected === "pilot"} > -

+

Pilot

-
+
Fliegt die vom Disponenten erstellten Einsätze und transportiert die Med-Crew sicher zum Einsatzort. Er übernimmt die navigatorische Vorbereitung, achtet auf Wetterentwicklungen und sorgt für die Sicherheit seiner Crew im Flug. @@ -76,17 +76,7 @@ const EventSelect = ({ pathSelected }: { pathSelected: "disponent" | "pilot" }) const user = useSession().data?.user; if (!user) return null; return events?.map((event) => { - return ( - - a.Participants.find((p) => p.userId == user.id), - )} - user={user} - event={event} - key={event.id} - /> - ); + return ; }); }; @@ -107,14 +97,14 @@ export const FirstPath = () => { return (
-

+

{session?.user.migratedFromV1 ? "Hallo, hier hat sich einiges geändert!" : "Wähle deinen Einstieg!"}

-

Willkommen bei Virtual Air Rescue!

+

Willkommen bei Virtual Air Rescue!

{session?.user.migratedFromV1 ? ( -

+

Dein Account wurde erfolgreich auf das neue System migriert. Herzlich Willkommen im neuen HUB! Um die Erfahrung für alle Nutzer zu steigern haben wir uns dazu entschlossen, dass alle Nutzer einen Test absolvieren müssen:{" "} @@ -129,12 +119,12 @@ export const FirstPath = () => { ausprobieren, wenn du möchtest.

)} -
+
{page === "path" && } {page === "event-select" && ( -
+
-

Wähle dein Einführungs-Event aus:

+

Wähle dein Einführungs-Event aus:

diff --git a/apps/hub/app/(app)/_components/Footer.tsx b/apps/hub/app/(app)/_components/Footer.tsx index 1c2a03db..ba60a6af 100644 --- a/apps/hub/app/(app)/_components/Footer.tsx +++ b/apps/hub/app/(app)/_components/Footer.tsx @@ -2,23 +2,17 @@ import Image from "next/image"; import { DiscordLogoIcon, InstagramLogoIcon, ReaderIcon } from "@radix-ui/react-icons"; import YoutubeSvg from "./youtube_wider.svg"; import FacebookSvg from "./facebook.svg"; -import { ChangelogModalBtn } from "@repo/shared-components"; -import { getServerSession } from "api/auth/[...nextauth]/auth"; -import { updateUser } from "(app)/settings/actions"; -import toast from "react-hot-toast"; + import { ChangelogWrapper } from "(app)/_components/ChangelogWrapper"; import { prisma } from "@repo/db"; export const Footer = async () => { - const session = await getServerSession(); const latestChangelog = await prisma.changelog.findFirst({ orderBy: { createdAt: "desc", }, }); - const autoOpen = !session?.user.changelogAck && !!latestChangelog; - return (
{/* Left: Impressum & Datenschutz */} diff --git a/apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx b/apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx deleted file mode 100644 index b3373878..00000000 --- a/apps/hub/app/(app)/admin/event/[id]/appointment/[appointmentId]/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { AppointmentForm } from "(app)/admin/event/_components/AppointmentForm"; -import { prisma } from "@repo/db"; - -export default async function Page({ - params, -}: { - params: Promise<{ id: string; appointmentId: string }>; -}) { - const { id: eventId, appointmentId } = await params; - - const event = await prisma.event.findUnique({ - where: { id: parseInt(eventId) }, - }); - - if (!event) return
Event nicht gefunden
; - - let appointment = null; - if (appointmentId !== "new") { - appointment = await prisma.eventAppointment.findUnique({ - where: { id: parseInt(appointmentId) }, - include: { - Presenter: true, - Participants: { - include: { User: true }, - }, - }, - }); - if (!appointment) return
Termin nicht gefunden
; - } - - return ( - - ); -} diff --git a/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx b/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx index 1cfe0d6c..e7bd207d 100644 --- a/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx +++ b/apps/hub/app/(app)/admin/event/[id]/participant/[participantId]/page.tsx @@ -40,11 +40,11 @@ export default async function Page({

- Event-übersicht für{" "} + Event-Übersicht für{" "} {`${user.firstname} ${user.lastname} #${user.publicId}`}

- +
); } diff --git a/apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx b/apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx deleted file mode 100644 index cae6f8c3..00000000 --- a/apps/hub/app/(app)/admin/event/_components/AppointmentForm.tsx +++ /dev/null @@ -1,260 +0,0 @@ -"use client"; -import { - EventAppointmentOptionalDefaults, - EventAppointmentOptionalDefaultsSchema, -} from "@repo/db/zod"; -import { useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useRef } from "react"; -import { ColumnDef } from "@tanstack/react-table"; -import { Event, EventAppointment, Participant, Prisma } from "@repo/db"; -import { ArrowLeft, Calendar, Users } from "lucide-react"; -import toast from "react-hot-toast"; -import Link from "next/link"; -import { PaginatedTable, PaginatedTableRef } from "../../../_components/PaginatedTable"; -import { Button } from "../../../_components/ui/Button"; -import { DateInput } from "../../../_components/ui/DateInput"; -import { upsertParticipant } from "../../events/actions"; -import { upsertAppointment, deleteAppoinement } from "../action"; -import { InputJsonValueType } from "@repo/db/zod"; - -interface AppointmentFormProps { - event: Event; - initialAppointment: - | (EventAppointment & { - Presenter?: any; - Participants?: (Participant & { User?: any })[]; - }) - | null; - appointmentId: string; -} - -export const AppointmentForm = ({ - event, - initialAppointment, - appointmentId, -}: AppointmentFormProps) => { - const router = useRouter(); - const participantTableRef = useRef(null); - - const form = useForm({ - resolver: zodResolver(EventAppointmentOptionalDefaultsSchema), - defaultValues: initialAppointment - ? { - ...initialAppointment, - eventId: event.id, - presenterId: initialAppointment.presenterId, - } - : undefined, - }); - - const onSubmit = async (values: EventAppointmentOptionalDefaults) => { - try { - await upsertAppointment({ - ...values, - eventId: event.id, - }); - toast.success("Termin erfolgreich gespeichert"); - router.back(); - } catch { - toast.error("Fehler beim Speichern des Termins"); - } - }; - - const handleDelete = async () => { - if (!confirm("Wirklich löschen?")) return; - try { - await deleteAppoinement(parseInt(appointmentId)); - toast.success("Termin gelöscht"); - router.back(); - } catch { - toast.error("Fehler beim Löschen"); - } - }; - - return ( -
-
- {/* Header */} -
- - Zurück - -

- {appointmentId === "new" ? "Neuer Termin" : "Termin bearbeiten"} -

-
- - {/* Main Content */} -
- {/* Form Card */} -
-
-
-

- Termin Details -

- -
- - form.setValue("appointmentDate", date)} - /> - {form.formState.errors.appointmentDate && ( - - {form.formState.errors.appointmentDate.message} - - )} -
- -
- -
- - {appointmentId !== "new" && ( - - )} -
-
-
- - - {/* Participants Table */} -
-
-
-

- Teilnehmer -

- - {appointmentId !== "new" ? ( - - ({ - eventAppointmentId: appointmentId, - }) as Prisma.ParticipantWhereInput - } - include={{ User: true }} - columns={ - [ - { - accessorKey: "User.firstname", - header: "Vorname", - }, - { - accessorKey: "User.lastname", - header: "Nachname", - }, - { - accessorKey: "enscriptionDate", - header: "Einschreibedatum", - cell: ({ row }) => { - return ( - {new Date(row.original.enscriptionDate).toLocaleString()} - ); - }, - }, - { - header: "Status", - cell: ({ row }) => { - if (row.original.attended) { - return Anwesend; - } else if (row.original.appointmentCancelled) { - return Abgesagt; - } else { - return Offen; - } - }, - }, - { - header: "Aktionen", - cell: ({ row }) => { - return ( -
- - Anzeigen - - {!row.original.attended && ( - - )} - {!row.original.appointmentCancelled && ( - - )} -
- ); - }, - }, - ] as ColumnDef[] - } - /> - ) : ( -
- Speichern Sie den Termin um Teilnehmer hinzuzufügen -
- )} -
-
-
-
-
-
- ); -}; diff --git a/apps/hub/app/(app)/admin/event/_components/AppointmentModal.tsx b/apps/hub/app/(app)/admin/event/_components/AppointmentModal.tsx deleted file mode 100644 index f2e69dcf..00000000 --- a/apps/hub/app/(app)/admin/event/_components/AppointmentModal.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { Event, Participant, Prisma } from "@repo/db"; -import { EventAppointmentOptionalDefaults, InputJsonValueType } from "@repo/db/zod"; -import { ColumnDef } from "@tanstack/react-table"; -import { useSession } from "next-auth/react"; -import { RefObject, useRef } from "react"; -import { UseFormReturn } from "react-hook-form"; -import { PaginatedTable, PaginatedTableRef } from "../../../../_components/PaginatedTable"; -import { Button } from "../../../../_components/ui/Button"; -import { DateInput } from "../../../../_components/ui/DateInput"; -import { upsertParticipant } from "../../../events/actions"; -import { deleteAppoinement, upsertAppointment } from "../action"; -import { handleParticipantFinished } from "../../../../../helper/events"; -import toast from "react-hot-toast"; - -interface AppointmentModalProps { - event?: Event; - ref: RefObject; - participantModal: RefObject; - appointmentsTableRef: React.RefObject; - appointmentForm: UseFormReturn; - participantForm: UseFormReturn; -} - -export const AppointmentModal = ({ - event, - ref, - participantModal, - appointmentsTableRef, - appointmentForm, - participantForm, -}: AppointmentModalProps) => { - const { data: session } = useSession(); - - const participantTableRef = useRef(null); - - return ( - -
-
- {/* if there is a button in form, it will close the modal */} - -
- -
{ - if (!event) return; - await upsertAppointment(values); - ref.current?.close(); - appointmentsTableRef.current?.refresh(); - })} - className="flex flex-col" - > -
-

Termin {appointmentForm.watch("id")}

- appointmentForm.setValue("appointmentDate", date)} - /> -
-
- { - return {new Date(row.original.enscriptionDate).toLocaleString()}; - }, - }, - { - header: "Anwesend", - cell: ({ row }) => { - if (row.original.attended) { - return Ja; - } else if (row.original.appointmentCancelled) { - return Nein (Termin abgesagt); - } else { - return ?; - } - }, - }, - { - header: "Aktion", - - cell: ({ row }) => { - return ( -
- - {!row.original.attended && event?.hasPresenceEvents && ( - - )} - {!row.original.appointmentCancelled && event?.hasPresenceEvents && ( - - )} -
- ); - }, - }, - ] as ColumnDef[] - } - prismaModel={"participant"} - getFilter={() => - ({ - eventAppointmentId: appointmentForm.watch("id")!, - }) as Prisma.ParticipantWhereInput - } - include={{ User: true }} - leftOfPagination={ -
- - {appointmentForm.watch("id") && ( - - )} -
- } - /> -
-
-
-
- ); -}; diff --git a/apps/hub/app/(app)/admin/event/_components/Form.tsx b/apps/hub/app/(app)/admin/event/_components/Form.tsx index f2726311..65bc5e54 100644 --- a/apps/hub/app/(app)/admin/event/_components/Form.tsx +++ b/apps/hub/app/(app)/admin/event/_components/Form.tsx @@ -1,34 +1,20 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { BADGES, Event, EVENT_TYPE, Participant, PERMISSION, Prisma, User } from "@repo/db"; -import { - EventAppointmentOptionalDefaults, - EventAppointmentOptionalDefaultsSchema, - EventOptionalDefaults, - EventOptionalDefaultsSchema, - ParticipantSchema, -} from "@repo/db/zod"; -import { Bot, Calendar, FileText, UserIcon } from "lucide-react"; -import { useSession } from "next-auth/react"; +import { BADGES, Event, EVENT_TYPE, PERMISSION } from "@repo/db"; +import { EventOptionalDefaults, EventOptionalDefaultsSchema } from "@repo/db/zod"; +import { Bot, FileText } from "lucide-react"; import { redirect } from "next/navigation"; -import { useRef } from "react"; import "react-datepicker/dist/react-datepicker.css"; import { useForm } from "react-hook-form"; -import { PaginatedTable, PaginatedTableRef } from "../../../../_components/PaginatedTable"; import { Button } from "../../../../_components/ui/Button"; import { Input } from "../../../../_components/ui/Input"; import { MarkdownEditor } from "../../../../_components/ui/MDEditor"; import { Select } from "../../../../_components/ui/Select"; import { Switch } from "../../../../_components/ui/Switch"; import { deleteEvent, upsertEvent } from "../action"; -import { AppointmentModal } from "./AppointmentModal"; -import { ParticipantModal } from "./ParticipantModal"; -import { ColumnDef } from "@tanstack/react-table"; import toast from "react-hot-toast"; -import Link from "next/link"; export const Form = ({ event }: { event?: Event }) => { - const { data: session } = useSession(); const form = useForm({ resolver: zodResolver(EventOptionalDefaultsSchema), defaultValues: event @@ -40,31 +26,9 @@ export const Form = ({ event }: { event?: Event }) => { } : undefined, }); - const appointmentForm = useForm({ - resolver: zodResolver(EventAppointmentOptionalDefaultsSchema), - defaultValues: { - eventId: event?.id, - presenterId: session?.user?.id, - }, - }); - const participantForm = useForm({ - resolver: zodResolver(ParticipantSchema), - }); - const appointmentsTableRef = useRef(null); - const appointmentModal = useRef(null); - const participantModal = useRef(null); return ( <> - -
{ await upsertEvent(values, event?.id); @@ -147,216 +111,11 @@ export const Form = ({ event }: { event?: Event }) => { valueAsNumber: true, })} /> -
- {form.watch("hasPresenceEvents") ? ( -
-
- - ({ - eventId: event?.id, - }) as Prisma.EventAppointmentWhereInput - } - include={{ - Presenter: true, - Participants: true, - }} - leftOfSearch={ -

- Termine -

- } - rightOfSearch={ - event && ( - - ) - } - columns={ - [ - { - header: "Datum", - accessorKey: "appointmentDate", - accessorFn: (date) => new Date(date.appointmentDate).toLocaleString(), - }, - { - header: "Presenter", - accessorKey: "presenter", - cell: ({ row }) => ( -
- - {row.original.Presenter.firstname} {row.original.Presenter.lastname} - -
- ), - }, - { - header: "Teilnehmer", - accessorKey: "Participants", - cell: ({ row }) => ( -
- - {row.original.Participants.length} -
- ), - }, - { - header: "Aktionen", - cell: ({ row }) => { - return ( -
- -
- ); - }, - }, - ] as ColumnDef< - EventAppointmentOptionalDefaults & { - Presenter: User; - Participants: Participant[]; - } - >[] - } - /> -
-
- ) : null} - {!form.watch("hasPresenceEvents") ? ( -
-
- { - - Teilnehmer - - } - ref={appointmentsTableRef} - prismaModel={"participant"} - showSearch - getFilter={(searchTerm) => - ({ - AND: [{ eventId: event?.id }], - OR: [ - { - User: { - OR: [ - { firstname: { contains: searchTerm, mode: "insensitive" } }, - { lastname: { contains: searchTerm, mode: "insensitive" } }, - { publicId: { contains: searchTerm, mode: "insensitive" } }, - ], - }, - }, - ], - }) as Prisma.ParticipantWhereInput - } - include={{ - User: true, - }} - supressQuery={!event} - columns={ - [ - { - header: "Vorname", - accessorKey: "User.firstname", - cell: ({ row }) => { - return ( - - {row.original.User.firstname} - - ); - }, - }, - { - header: "Nachname", - accessorKey: "User.lastname", - cell: ({ row }) => { - return ( - - {row.original.User.lastname} - - ); - }, - }, - { - header: "VAR-Nummer", - accessorKey: "User.publicId", - cell: ({ row }) => { - return ( - - {row.original.User.publicId} - - ); - }, - }, - { - header: "Moodle Kurs abgeschlossen", - accessorKey: "finisherMoodleCurseCompleted", - }, - { - header: "Aktionen", - cell: ({ row }) => { - return ( -
- -
- ); - }, - }, - ] as ColumnDef[] - } - /> - } -
-
- ) : null}
diff --git a/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx b/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx index 39dc62e3..0b69f86e 100644 --- a/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx +++ b/apps/hub/app/(app)/admin/event/_components/ParticipantForm.tsx @@ -1,5 +1,5 @@ "use client"; -import { Participant, Event, User, ParticipantLog, Prisma } from "@repo/db"; +import { Participant, Event, ParticipantLog, Prisma } from "@repo/db"; import { Users, Activity, Bug } from "lucide-react"; import toast from "react-hot-toast"; import { InputJsonValueType, ParticipantOptionalDefaultsSchema } from "@repo/db/zod"; @@ -16,19 +16,13 @@ import { redirect } from "next/navigation"; interface ParticipantFormProps { event: Event; participant: Participant; - user: User; } const checkEventCompleted = (participant: Participant, event: Event): boolean => { - if (event.hasPresenceEvents) { - return event.finisherMoodleCourseId - ? participant.finisherMoodleCurseCompleted && participant.attended - : !!participant.attended; - } return event.finisherMoodleCourseId ? participant.finisherMoodleCurseCompleted : false; }; -export const ParticipantForm = ({ event, participant, user }: ParticipantFormProps) => { +export const ParticipantForm = ({ event, participant }: ParticipantFormProps) => { const [isLoading, setIsLoading] = useState(false); const queryClient = useQueryClient(); @@ -106,6 +100,7 @@ export const ParticipantForm = ({ event, participant, user }: ParticipantFormPro name={"finisherMoodleCurseCompleted"} label="Moodle Kurs abgeschlossen" /> +
{/* Info Card */} diff --git a/apps/hub/app/(app)/admin/event/action.ts b/apps/hub/app/(app)/admin/event/action.ts index 0a3c6a3e..438135ce 100644 --- a/apps/hub/app/(app)/admin/event/action.ts +++ b/apps/hub/app/(app)/admin/event/action.ts @@ -2,6 +2,7 @@ import { prisma, Prisma, Event, Participant } from "@repo/db"; +//############# Event //############# export const upsertEvent = async (event: Prisma.EventCreateInput, id?: Event["id"]) => { const newEvent = id ? await prisma.event.update({ @@ -11,34 +12,22 @@ export const upsertEvent = async (event: Prisma.EventCreateInput, id?: Event["id : await prisma.event.create({ data: event }); return newEvent; }; - export const deleteEvent = async (id: Event["id"]) => { await prisma.event.delete({ where: { id: id } }); }; -export const upsertAppointment = async ( - eventAppointment: Prisma.EventAppointmentUncheckedCreateInput, -) => { - const newEventAppointment = eventAppointment.id - ? await prisma.eventAppointment.update({ - where: { id: eventAppointment.id }, - data: eventAppointment, +//############# Participant //############# + +export const upsertParticipant = async (participant: Prisma.ParticipantUncheckedCreateInput) => { + const newParticipant = participant.id + ? await prisma.participant.update({ + where: { id: participant.id }, + data: participant, }) - : await prisma.eventAppointment.create({ data: eventAppointment }); - return newEventAppointment; + : await prisma.participant.create({ data: participant }); + return newParticipant; }; -export const deleteAppoinement = async (id: Event["id"]) => { - await prisma.eventAppointment.delete({ where: { id: id } }); - prisma.eventAppointment.findMany({ - where: { - eventId: id, - }, - orderBy: { - // TODO: add order by in relation to table selected column - }, - }); -}; export const deleteParticipant = async (id: Participant["id"]) => { await prisma.participant.delete({ where: { id: id } }); }; diff --git a/apps/hub/app/(app)/events/_components/EventCard.tsx b/apps/hub/app/(app)/events/_components/EventCard.tsx index 0f66e28f..a64465c2 100644 --- a/apps/hub/app/(app)/events/_components/EventCard.tsx +++ b/apps/hub/app/(app)/events/_components/EventCard.tsx @@ -1,6 +1,6 @@ "use client"; import { DrawingPinFilledIcon } from "@radix-ui/react-icons"; -import { Event, Participant, EventAppointment, User } from "@repo/db"; +import { Event, Participant, User } from "@repo/db"; import ModalBtn from "./Modal"; import MDEditor from "@uiw/react-md-editor"; import { Badge } from "@repo/shared-components"; @@ -8,25 +8,18 @@ import { Badge } from "@repo/shared-components"; export const EventCard = ({ user, event, - selectedAppointments, - appointments, }: { user: User; event: Event & { - Appointments: EventAppointment[]; Participants: Participant[]; }; - selectedAppointments: EventAppointment[]; - appointments: (EventAppointment & { - Participants: { userId: string }[]; - })[]; }) => { return (
-
+

{event.name}

-
+
{event.type === "COURSE" && ( Zusatzqualifikation )} @@ -36,7 +29,7 @@ export const EventCard = ({
-
+
-
+
{event.finishedBadges.map((b) => { return ; })}
-
+
-

+

Teilnahmevoraussetzungen: {!event.requiredBadges.length && "Keine"}

{!!event.requiredBadges.length && ( -
- Abzeichen: +
+ Abzeichen:
{event.requiredBadges.map((badge) => (
@@ -71,11 +64,9 @@ export const EventCard = ({ )}
diff --git a/apps/hub/app/(app)/events/_components/Modal.tsx b/apps/hub/app/(app)/events/_components/Modal.tsx index bcf0e910..74b45364 100644 --- a/apps/hub/app/(app)/events/_components/Modal.tsx +++ b/apps/hub/app/(app)/events/_components/Modal.tsx @@ -1,56 +1,30 @@ "use client"; import { useEffect } from "react"; import { CheckCircledIcon, EnterIcon, DrawingPinFilledIcon } from "@radix-ui/react-icons"; -import { Event, EventAppointment, Participant, User } from "@repo/db"; +import { Event, Participant, User } from "@repo/db"; import { cn } from "@repo/shared-components"; import { inscribeToMoodleCourse, upsertParticipant } from "../actions"; import { BookCheck, - Calendar, Check, CirclePlay, - Clock10Icon, ExternalLink, EyeIcon, Info, TriangleAlert, } from "lucide-react"; -import { useForm } from "react-hook-form"; -import { - InputJsonValueType, - ParticipantOptionalDefaults, - ParticipantOptionalDefaultsSchema, -} from "@repo/db/zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Select } from "../../../_components/ui/Select"; -import { useRouter } from "next/navigation"; -import { handleParticipantEnrolled } from "../../../../helper/events"; import { eventCompleted } from "@repo/shared-components"; import MDEditor from "@uiw/react-md-editor"; -import toast from "react-hot-toast"; -import { formatDate } from "date-fns"; interface ModalBtnProps { title: string; event: Event; - dates: (EventAppointment & { - Participants: { userId: string }[]; - })[]; - selectedAppointments: EventAppointment[]; participant?: Participant; user: User; modalId: string; } -const ModalBtn = ({ - title, - dates, - modalId, - participant, - selectedAppointments, - event, - user, -}: ModalBtnProps) => { +const ModalBtn = ({ title, modalId, participant, event, user }: ModalBtnProps) => { useEffect(() => { const modal = document.getElementById(modalId) as HTMLDialogElement; const handleOpen = () => { @@ -66,12 +40,6 @@ const ModalBtn = ({ modal?.removeEventListener("close", handleClose); }; }, [modalId]); - const router = useRouter(); - - const canSelectDate = - event.hasPresenceEvents && - !participant?.attended && - (selectedAppointments.length === 0 || participant?.appointmentCancelled); const openModal = () => { const modal = document.getElementById(modalId) as HTMLDialogElement; @@ -82,29 +50,6 @@ const ModalBtn = ({ const modal = document.getElementById(modalId) as HTMLDialogElement; modal?.close(); }; - const selectAppointmentForm = useForm({ - resolver: zodResolver(ParticipantOptionalDefaultsSchema), - defaultValues: { - eventId: event.id, - userId: user.id, - ...participant, - }, - }); - const selectedAppointment = selectedAppointments[0]; - const selectedDate = dates.find( - (date) => - date.id === selectAppointmentForm.watch("eventAppointmentId") || selectedAppointment?.id, - ); - const ownIndexInParticipantList = selectedDate?.Participants?.findIndex( - (p) => p.userId === user.id, - ); - - const ownPlaceInParticipantList = - typeof ownIndexInParticipantList === "number" - ? ownIndexInParticipantList === -1 - ? (selectedDate?.Participants?.length ?? 0) + 1 - : ownIndexInParticipantList + 1 - : undefined; const missingRequirements = event.requiredBadges?.length > 0 && @@ -163,79 +108,6 @@ const ModalBtn = ({ />
- {event.hasPresenceEvents && ( -
-

- Termine -

-
- {!!dates.length && !selectedDate && ( - <> -

Melde dich zu einem Termin an

-
+
+
+ { + + Teilnehmer + + } + prismaModel={"participant"} + showSearch + getFilter={(searchTerm) => + ({ + AND: [{ eventId: event?.id }], + OR: [ + { + User: { + OR: [ + { firstname: { contains: searchTerm, mode: "insensitive" } }, + { lastname: { contains: searchTerm, mode: "insensitive" } }, + { publicId: { contains: searchTerm, mode: "insensitive" } }, + ], + }, + }, + ], + }) as Prisma.ParticipantWhereInput + } + include={{ + User: true, + }} + supressQuery={!event} + columns={ + [ + { + header: "Vorname", + accessorKey: "User.firstname", + cell: ({ row }) => { + return ( + + {row.original.User.firstname} + + ); + }, + }, + { + header: "Nachname", + accessorKey: "User.lastname", + cell: ({ row }) => { + return ( + + {row.original.User.lastname} + + ); + }, + }, + { + header: "VAR-Nummer", + accessorKey: "User.publicId", + cell: ({ row }) => { + return ( + + {row.original.User.publicId} + + ); + }, + }, + { + header: "Moodle Kurs abgeschlossen", + accessorKey: "finisherMoodleCurseCompleted", + }, + { + header: "Aktionen", + cell: ({ row }) => { + return ( + + + + ); + }, + }, + ] as ColumnDef[] + } + /> + } +
+
diff --git a/package.json b/package.json index da08da6d..a7352403 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node": ">=18", "pnpm": ">=10" }, - "packageManager": "pnpm@10.28.0", + "packageManager": "pnpm@10.28.2", "workspaces": [ "apps/*", "packages/*" diff --git a/packages/database/package.json b/packages/database/package.json index c197f985..a7eb3e99 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -25,6 +25,7 @@ "zod-prisma-types": "^3.2.4" }, "devDependencies": { + "@types/node": "^25.1.0", "prisma": "^6.12.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a37750f..14d8004b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,7 +130,7 @@ importers: version: 0.5.8(@types/dom-mediacapture-transform@0.1.11)(livekit-client@2.15.3(@types/dom-mediacapture-record@1.0.22)) '@next-auth/prisma-adapter': specifier: ^1.0.7 - version: 1.0.7(@prisma/client@6.12.0(prisma@6.12.0(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.13(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.0.7(@prisma/client@6.12.0(prisma@6.12.0(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.13(next@16.1.1(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@radix-ui/react-icons': specifier: ^1.3.2 version: 1.3.2(react@19.1.0) @@ -211,10 +211,10 @@ importers: version: 0.525.0(react@19.1.0) next: specifier: '>=15.4.9' - version: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 16.1.1(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-auth: specifier: '>=4.24.12' - version: 4.24.13(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 4.24.13(next@16.1.1(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) npm: specifier: ^11.4.2 version: 11.4.2 @@ -588,6 +588,9 @@ importers: specifier: ^3.2.4 version: 3.2.4(@prisma/client@6.12.0(prisma@6.12.0(typescript@5.9.3))(typescript@5.9.3))(prisma@6.12.0(typescript@5.9.3)) devDependencies: + '@types/node': + specifier: ^25.1.0 + version: 25.1.0 prisma: specifier: ^6.12.0 version: 6.12.0(typescript@5.9.3) @@ -2161,6 +2164,9 @@ packages: '@types/node@22.15.34': resolution: {integrity: sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==} + '@types/node@25.1.0': + resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==} + '@types/nodemailer@6.4.17': resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} @@ -5290,6 +5296,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@6.21.3: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} @@ -5603,7 +5612,7 @@ snapshots: '@babel/parser': 7.27.7 '@babel/template': 7.27.2 '@babel/types': 7.27.7 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5852,7 +5861,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -6091,11 +6100,6 @@ snapshots: '@prisma/client': 6.12.0(prisma@6.12.0(typescript@5.8.3))(typescript@5.8.3) next-auth: 4.24.13(next@16.1.1(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@next-auth/prisma-adapter@1.0.7(@prisma/client@6.12.0(prisma@6.12.0(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.13(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': - dependencies: - '@prisma/client': 6.12.0(prisma@6.12.0(typescript@5.8.3))(typescript@5.8.3) - next-auth: 4.24.13(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@next/env@16.1.1': {} '@next/eslint-plugin-next@15.4.2': @@ -7678,9 +7682,13 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@25.1.0': + dependencies: + undici-types: 7.16.0 + '@types/nodemailer@6.4.17': dependencies: - '@types/node': 22.15.29 + '@types/node': 22.15.34 '@types/parse-json@4.0.2': {} @@ -7749,7 +7757,7 @@ snapshots: '@typescript-eslint/types': 8.37.0 '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.37.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) eslint: 9.31.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: @@ -7759,7 +7767,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -7778,7 +7786,7 @@ snapshots: '@typescript-eslint/types': 8.37.0 '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) eslint: 9.31.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 @@ -7793,7 +7801,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.37.0(typescript@5.8.3) '@typescript-eslint/types': 8.37.0 '@typescript-eslint/visitor-keys': 8.37.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -8424,10 +8432,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.1(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -8770,7 +8774,7 @@ snapshots: eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.31.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1 + debug: 4.4.1(supports-color@5.5.0) eslint: 9.31.0(jiti@2.4.2) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 @@ -10223,23 +10227,6 @@ snapshots: optionalDependencies: nodemailer: 7.0.11 - next-auth@4.24.13(next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nodemailer@7.0.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@babel/runtime': 7.27.6 - '@panva/hkdf': 1.2.1 - cookie: 0.7.2 - jose: 4.15.9 - next: 16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - oauth: 0.9.15 - openid-client: 5.7.1 - preact: 10.28.2 - preact-render-to-string: 5.2.6(preact@10.28.2) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - uuid: 8.3.2 - optionalDependencies: - nodemailer: 7.0.11 - next-remove-imports@1.0.12(webpack@5.99.9): dependencies: '@babel/core': 7.27.7 @@ -10275,32 +10262,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.1.1(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@next/env': 16.1.1 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.9.14 - caniuse-lite: 1.0.30001726 - postcss: 8.4.31 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(react@19.1.0) - optionalDependencies: - '@next/swc-darwin-arm64': 16.1.1 - '@next/swc-darwin-x64': 16.1.1 - '@next/swc-linux-arm64-gnu': 16.1.1 - '@next/swc-linux-arm64-musl': 16.1.1 - '@next/swc-linux-x64-gnu': 16.1.1 - '@next/swc-linux-x64-musl': 16.1.1 - '@next/swc-win32-arm64-msvc': 16.1.1 - '@next/swc-win32-x64-msvc': 16.1.1 - '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.52.0 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - node-cron@4.2.1: {} node-releases@2.0.19: {} @@ -11238,11 +11199,6 @@ snapshots: optionalDependencies: '@babel/core': 7.27.7 - styled-jsx@5.1.6(react@19.1.0): - dependencies: - client-only: 0.0.1 - react: 19.1.0 - stylis@4.2.0: {} supports-color@5.5.0: @@ -11463,6 +11419,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.16.0: {} + undici@6.21.3: {} unified@11.0.5: