This commit is contained in:
nocnico
2025-07-24 03:24:28 +02:00
9 changed files with 112 additions and 74 deletions

View File

@@ -34,7 +34,7 @@ export const handleParticipantFinished = async (
}, },
}); });
if (event.discordRoleId && discordAccount) { if (discordAccount) {
await setStandardName({ await setStandardName({
memberId: discordAccount.discordId, memberId: discordAccount.discordId,
userId: user.id, userId: user.id,

View File

@@ -25,6 +25,7 @@ import { AppointmentModal } from "./AppointmentModal";
import { ParticipantModal } from "./ParticipantModal"; import { ParticipantModal } from "./ParticipantModal";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import Link from "next/link";
export const Form = ({ event }: { event?: Event }) => { export const Form = ({ event }: { event?: Event }) => {
const { data: session } = useSession(); const { data: session } = useSession();
@@ -72,10 +73,10 @@ export const Form = ({ event }: { event?: Event }) => {
})} })}
className="grid grid-cols-6 gap-3" className="grid grid-cols-6 gap-3"
> >
<div className="card bg-base-200 shadow-xl col-span-3 max-xl:col-span-6"> <div className="card bg-base-200 col-span-3 shadow-xl max-xl:col-span-6">
<div className="card-body"> <div className="card-body">
<h2 className="card-title"> <h2 className="card-title">
<FileText className="w-5 h-5" /> Allgemeines <FileText className="h-5 w-5" /> Allgemeines
</h2> </h2>
<Select <Select
form={form} form={form}
@@ -91,10 +92,10 @@ export const Form = ({ event }: { event?: Event }) => {
<MarkdownEditor form={form} name="descriptionShort" /> <MarkdownEditor form={form} name="descriptionShort" />
</div> </div>
</div> </div>
<div className="card bg-base-200 shadow-xl col-span-3 max-xl:col-span-6"> <div className="card bg-base-200 col-span-3 shadow-xl max-xl:col-span-6">
<div className="card-body"> <div className="card-body">
<h2 className="card-title"> <h2 className="card-title">
<Bot className="w-5 h-5" /> Automation <Bot className="h-5 w-5" /> Automation
</h2> </h2>
<Input <Input
name="finisherMoodleCourseId" name="finisherMoodleCourseId"
@@ -153,7 +154,7 @@ export const Form = ({ event }: { event?: Event }) => {
</div> </div>
</div> </div>
{form.watch("hasPresenceEvents") ? ( {form.watch("hasPresenceEvents") ? (
<div className="card bg-base-200 shadow-xl col-span-6"> <div className="card bg-base-200 col-span-6 shadow-xl">
<div className="card-body"> <div className="card-body">
<PaginatedTable <PaginatedTable
ref={appointmentsTableRef} ref={appointmentsTableRef}
@@ -167,7 +168,7 @@ export const Form = ({ event }: { event?: Event }) => {
}} }}
leftOfSearch={ leftOfSearch={
<h2 className="card-title"> <h2 className="card-title">
<Calendar className="w-5 h-5" /> Termine <Calendar className="h-5 w-5" /> Termine
</h2> </h2>
} }
rightOfSearch={ rightOfSearch={
@@ -210,7 +211,7 @@ export const Form = ({ event }: { event?: Event }) => {
accessorKey: "Participants", accessorKey: "Participants",
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center"> <div className="flex items-center">
<UserIcon className="w-5 h-5" /> <UserIcon className="h-5 w-5" />
<span className="ml-2">{row.original.Participants.length}</span> <span className="ml-2">{row.original.Participants.length}</span>
</div> </div>
), ),
@@ -247,15 +248,15 @@ export const Form = ({ event }: { event?: Event }) => {
</div> </div>
) : null} ) : null}
{!form.watch("hasPresenceEvents") ? ( {!form.watch("hasPresenceEvents") ? (
<div className="card bg-base-200 shadow-xl col-span-6"> <div className="card bg-base-200 col-span-6 shadow-xl">
<div className="card-body"> <div className="card-body">
<PaginatedTable <PaginatedTable
leftOfSearch={ leftOfSearch={
<h2 className="card-title"> <h2 className="card-title">
<Calendar className="w-5 h-5" /> Teilnehmer <Calendar className="h-5 w-5" /> Teilnehmer
</h2> </h2>
} }
searchFields={["User.firstname", "User.lastname"]} searchFields={["User.firstname", "User.lastname", "User.publicId"]}
ref={appointmentsTableRef} ref={appointmentsTableRef}
prismaModel={"participant"} prismaModel={"participant"}
filter={{ filter={{
@@ -264,46 +265,82 @@ export const Form = ({ event }: { event?: Event }) => {
include={{ include={{
User: true, User: true,
}} }}
columns={[ columns={
{ [
header: "Vorname", {
accessorKey: "User.firstname", header: "Vorname",
}, accessorKey: "User.firstname",
{ cell: ({ row }) => {
header: "Nachname", return (
accessorKey: "User.lastname", <Link
}, className="hover:underline"
{ href={`/admin/user/${row.original.User.id}`}
header: "Moodle Kurs abgeschlossen",
accessorKey: "finisherMoodleCurseCompleted",
},
{
header: "Aktionen",
cell: ({ row }) => {
return (
<div className="flex gap-2">
<button
onSubmit={() => false}
type="button"
onClick={() => {
participantForm.reset(row.original);
participantModal.current?.showModal();
}}
className="btn btn-sm btn-outline"
> >
Bearbeiten {row.original.User.firstname}
</button> </Link>
</div> );
); },
}, },
}, {
]} header: "Nachname",
accessorKey: "User.lastname",
cell: ({ row }) => {
return (
<Link
className="hover:underline"
href={`/admin/user/${row.original.User.id}`}
>
{row.original.User.lastname}
</Link>
);
},
},
{
header: "VAR-Nummer",
accessorKey: "User.publicId",
cell: ({ row }) => {
return (
<Link
className="hover:underline"
href={`/admin/user/${row.original.User.id}`}
>
{row.original.User.publicId}
</Link>
);
},
},
{
header: "Moodle Kurs abgeschlossen",
accessorKey: "finisherMoodleCurseCompleted",
},
{
header: "Aktionen",
cell: ({ row }) => {
return (
<div className="flex gap-2">
<button
onSubmit={() => false}
type="button"
onClick={() => {
participantForm.reset(row.original);
participantModal.current?.showModal();
}}
className="btn btn-sm btn-outline"
>
Bearbeiten
</button>
</div>
);
},
},
] as ColumnDef<Participant & { User: User }>[]
}
/> />
</div> </div>
</div> </div>
) : null} ) : null}
<div className="card bg-base-200 shadow-xl col-span-6"> <div className="card bg-base-200 col-span-6 shadow-xl">
<div className="card-body "> <div className="card-body">
<div className="flex w-full gap-4"> <div className="flex w-full gap-4">
<Button <Button
isLoading={form.formState.isSubmitting} isLoading={form.formState.isSubmitting}

View File

@@ -36,7 +36,7 @@ export const EventCard = ({
</div> </div>
<div className="grid grid-cols-6 gap-4"> <div className="grid grid-cols-6 gap-4">
<div className="col-span-4"> <div className="col-span-4">
<div className="text-left text-balance"> <div className="text-left text-balance" data-color-mode="dark">
<MDEditor.Markdown <MDEditor.Markdown
source={event.descriptionShort} source={event.descriptionShort}
style={{ style={{

View File

@@ -148,13 +148,13 @@ const ModalBtn = ({
<form method="dialog"> <form method="dialog">
<button className="btn btn-sm btn-circle btn-ghost absolute right-3 top-3"></button> <button className="btn btn-sm btn-circle btn-ghost absolute right-3 top-3"></button>
</form> </form>
<h3 className="font-bold text-lg text-left">{title}</h3> <h3 className="text-left text-lg font-bold">{title}</h3>
<div className="flex flex-wrap gap-4 mt-4"> <div className="mt-4 flex flex-wrap gap-4">
<div className="flex flex-col gap-2 flex-1 p-3 bg-base-300 min-w-[300px] shadow rounded-lg"> <div className="bg-base-300 flex min-w-[300px] flex-1 flex-col gap-2 rounded-lg p-3 shadow">
<h2 className="flex gap-2 text-lg font-bold"> <h2 className="flex gap-2 text-lg font-bold">
<Info /> Details <Info /> Details
</h2> </h2>
<div className="text-left text-balance"> <div className="text-balance text-left" data-color-mode="dark">
<MDEditor.Markdown <MDEditor.Markdown
source={event.description} source={event.description}
style={{ style={{
@@ -164,14 +164,14 @@ const ModalBtn = ({
</div> </div>
</div> </div>
{event.hasPresenceEvents && ( {event.hasPresenceEvents && (
<div className="flex flex-col gap-2 flex-1 p-3 bg-base-300 min-w-[300px] shadow rounded-lg"> <div className="bg-base-300 flex min-w-[300px] flex-1 flex-col gap-2 rounded-lg p-3 shadow">
<h2 className="flex gap-2 text-lg font-bold"> <h2 className="flex gap-2 text-lg font-bold">
<Calendar /> Termine <Calendar /> Termine
</h2> </h2>
<div className="flex flex-1 flex-col justify-center items-center"> <div className="flex flex-1 flex-col items-center justify-center">
{!!dates.length && !selectedDate && ( {!!dates.length && !selectedDate && (
<> <>
<p className="text-center text-info">Melde dich zu einem Termin an</p> <p className="text-info text-center">Melde dich zu einem Termin an</p>
<Select <Select
form={selectAppointmentForm} form={selectAppointmentForm}
options={dates.map((date) => ({ options={dates.map((date) => ({
@@ -190,7 +190,7 @@ const ModalBtn = ({
<span>Dein ausgewählter Termin (Deutsche Zeit)</span> <span>Dein ausgewählter Termin (Deutsche Zeit)</span>
<div> <div>
<button <button
className="input input-border min-w-[250px] pointer-events-none" className="input input-border pointer-events-none min-w-[250px]"
style={{ anchorName: "--rdp" } as React.CSSProperties} style={{ anchorName: "--rdp" } as React.CSSProperties}
> >
{new Date(selectedAppointment.appointmentDate).toLocaleString("de-DE", { {new Date(selectedAppointment.appointmentDate).toLocaleString("de-DE", {
@@ -203,12 +203,12 @@ const ModalBtn = ({
</button> </button>
</div> </div>
{participant?.attended ? ( {participant?.attended ? (
<p className="py-4 flex items-center gap-2 justify-center"> <p className="flex items-center justify-center gap-2 py-4">
<CheckCircledIcon className="text-success" /> <CheckCircledIcon className="text-success" />
Du hast an dem Presenztermin teilgenommen Du hast an dem Presenztermin teilgenommen
</p> </p>
) : ( ) : (
<p className="py-4 flex items-center gap-2 justify-center"> <p className="flex items-center justify-center gap-2 py-4">
Bitte erscheine ~5 minuten vor dem Termin im Discord Bitte erscheine ~5 minuten vor dem Termin im Discord
</p> </p>
)} )}
@@ -216,7 +216,7 @@ const ModalBtn = ({
)} )}
{!dates.length && ( {!dates.length && (
<p className="text-center text-error">Aktuell sind keine Termine verfügbar</p> <p className="text-error text-center">Aktuell sind keine Termine verfügbar</p>
)} )}
{!!selectedDate && {!!selectedDate &&
@@ -225,7 +225,7 @@ const ModalBtn = ({
!!(ownPlaceInParticipantList > event.maxParticipants) && ( !!(ownPlaceInParticipantList > event.maxParticipants) && (
<p <p
role="alert" role="alert"
className="py-4 my-5 flex items-center gap-2 justify-center border alert alert-error alert-outline" className="alert alert-error alert-outline my-5 flex items-center justify-center gap-2 border py-4"
> >
<TriangleAlert className="h-6 w-6 shrink-0 stroke-current" fill="none" /> <TriangleAlert className="h-6 w-6 shrink-0 stroke-current" fill="none" />
Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht teilnehmen Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht teilnehmen
@@ -237,11 +237,11 @@ const ModalBtn = ({
</div> </div>
)} )}
{event.finisherMoodleCourseId && ( {event.finisherMoodleCourseId && (
<div className="flex flex-col gap-2 flex-1 p-3 bg-base-300 min-w-[300px] shadow rounded-lg"> <div className="bg-base-300 flex min-w-[300px] flex-1 flex-col gap-2 rounded-lg p-3 shadow">
<h2 className="flex gap-2 text-lg font-bold"> <h2 className="flex gap-2 text-lg font-bold">
<BookCheck /> Moodle-Kurs <BookCheck /> Moodle-Kurs
</h2> </h2>
<div className="flex flex-col items-center justify-center h-full"> <div className="flex h-full flex-col items-center justify-center">
<MoodleCourseIndicator <MoodleCourseIndicator
participant={participant} participant={participant}
user={user} user={user}
@@ -254,9 +254,9 @@ const ModalBtn = ({
)} )}
</div> </div>
<div className="flex justify-between items-end mt-5"> <div className="mt-5 flex items-end justify-between">
<div> <div>
<p className="text-gray-600 text-left flex items-center gap-2"> <p className="flex items-center gap-2 text-left text-gray-600">
<DrawingPinFilledIcon /> <b>Teilnahmevoraussetzungen: </b> <DrawingPinFilledIcon /> <b>Teilnahmevoraussetzungen: </b>
{!event.requiredBadges.length && "Keine"} {!event.requiredBadges.length && "Keine"}
</p> </p>
@@ -347,23 +347,27 @@ const MoodleCourseIndicator = ({
const courseUrl = `${process.env.NEXT_PUBLIC_MOODLE_URL}/course/view.php?id=${moodleCourseId}`; const courseUrl = `${process.env.NEXT_PUBLIC_MOODLE_URL}/course/view.php?id=${moodleCourseId}`;
if (event.hasPresenceEvents && !participant?.attended) if (event.hasPresenceEvents && !participant?.attended)
return ( return (
<p className="py-4 flex items-center gap-2 justify-center"> <p className="flex items-center justify-center gap-2 py-4">
<Clock10Icon className="text-error" /> <Clock10Icon className="text-error" />
Abschlusstest erst nach Teilnahme verfügbar Abschlusstest erst nach Teilnahme verfügbar
</p> </p>
); );
if (completed) if (completed)
return ( return (
<p className="py-4 flex items-center gap-2 justify-center"> <p className="flex items-center justify-center gap-2 py-4">
<CheckCircledIcon className="text-success" /> <CheckCircledIcon className="text-success" />
Moodle Kurs abgeschlossen Moodle Kurs abgeschlossen
</p> </p>
); );
return ( return (
<div className="flex flex-col items-center justify-center gap-2"> <div className="flex flex-col items-center justify-center gap-2">
<p className="text-sm flex items-center gap-2"> <p className="flex items-center gap-2 text-sm">
<CirclePlay className="text-warning" /> Moodle-Kurs bereit <CirclePlay className="text-warning" /> Moodle-Kurs bereit
</p> </p>
<p className="font-bold text-gray-400">
Wenn du nach dem Anmelden den moodle Kurs nicht siehst, warte ein paar Sekunden und lade die
Seite neu.
</p>
<button <button
className="btn btn-sm btn-outline btn-info ml-2" className="btn btn-sm btn-outline btn-info ml-2"
onClick={async () => { onClick={async () => {
@@ -386,10 +390,6 @@ const MoodleCourseIndicator = ({
<ExternalLink size={16} /> <ExternalLink size={16} />
Zum Moodle Kurs Zum Moodle Kurs
</button> </button>
<p className="text-xs text-gray-400">
Wenn du nach dem Anmelden den moodle Kurs nicht siehst, warte ein paar Sekunden und lade die
Seite neu.
</p>
</div> </div>
); );
}; };

View File

@@ -13,7 +13,8 @@ export default function () {
BtnIcon={<DiscordLogoIcon />} BtnIcon={<DiscordLogoIcon />}
btnHref="https://discord.com/invite/x6FAMY7DW6" btnHref="https://discord.com/invite/x6FAMY7DW6"
btnLabel="Beitreten" btnLabel="Beitreten"
description="Tritt unserem Discord-Server bei, um mit der Community in Kontakt zu bleiben, Unterstützung zu erhalten und über die neuesten Updates informiert zu werden. Wenn du beigetreten bist, kannst du in deinen Einstellungen dein VAR-Konto mit deinem Discord-Konto verknüpfen und eine Rolle erhalten." description="Tritt unserem Discordserver bei, um mit der Community in Kontakt zu bleiben, Unterstützung zu erhalten und über die neuesten Updates informiert zu werden. Wenn du beigetreten bist kannst du in deinen Einstellungen dein VAR-Konto mit deinem Discordkonto verknüpfen und eine Rolle zu erhalten.
"
/> />
<ResourceCard <ResourceCard
image={Desktop} image={Desktop}

View File

@@ -169,7 +169,7 @@ export const ProfileForm = ({
{...form.register("settingsHideLastname")} {...form.register("settingsHideLastname")}
className="checkbox" className="checkbox"
/> />
Anfangsbuchstaben des Nachnamens verstecken Immer nur den Vornamen anzeigen ("Max" statt "Max M.")
</label> </label>
<label className="floating-label mt-4 w-full"> <label className="floating-label mt-4 w-full">
<span className="flex items-center gap-2 text-lg"> <span className="flex items-center gap-2 text-lg">

View File

@@ -13,7 +13,7 @@ export const resetPassword = async (email: string) => {
email, email,
}, },
}); });
const oldUser = (OLD_USER as OldUser[]).find((u) => u.email === email); const oldUser = (OLD_USER as OldUser[]).find((u) => u.email.toLowerCase() === email.toLowerCase());
if (!user) { if (!user) {
if (oldUser) { if (oldUser) {
user = await createNewUserFromOld(oldUser); user = await createNewUserFromOld(oldUser);

View File

@@ -29,7 +29,7 @@ export const register = async ({ password, ...user }: Omit<Prisma.UserCreateInpu
}, },
}); });
const existingOldUser = (OLD_USER as OldUser[]).find((u) => u.email === user.email); const existingOldUser = (OLD_USER as OldUser[]).find((u) => u.email.toLocaleLowerCase() === user.email.toLocaleLowerCase());
if (existingUser) { if (existingUser) {
return { return {

View File

@@ -20,7 +20,7 @@ export const options: AuthOptions = {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { email: credentials.email }, where: { email: credentials.email },
}); });
const v1User = (oldUser as OldUser[]).find((u) => u.email === credentials.email); const v1User = (oldUser as OldUser[]).find((u) => u.email.toLowerCase() === credentials.email.toLowerCase());
if (!user && v1User) { if (!user && v1User) {
if (bcrypt.compareSync(credentials.password, v1User.password)) { if (bcrypt.compareSync(credentials.password, v1User.password)) {
const newUser = await createNewUserFromOld(v1User); const newUser = await createNewUserFromOld(v1User);