Anzeige für lsitenplatz, sortierung nach Datum

This commit is contained in:
PxlLoewe
2025-03-11 00:25:42 -07:00
parent 92dff8f3c9
commit 07310907f1
6 changed files with 129 additions and 27 deletions

View File

@@ -45,8 +45,8 @@ export const AppointmentModal = ({
const participantTableRef = useRef<PaginatedTableRef>(null); const participantTableRef = useRef<PaginatedTableRef>(null);
return ( return (
<dialog ref={ref} className="modal"> <dialog ref={ref} className="modal ">
<div className="modal-box"> <div className="modal-box min-w-[900px]">
<form method="dialog"> <form method="dialog">
{/* if there is a button in form, it will close the modal */} {/* if there is a button in form, it will close the modal */}
<button <button
@@ -99,10 +99,40 @@ export const AppointmentModal = ({
header: "Nachname", header: "Nachname",
}, },
{ {
header: "Aktion", accessorKey: "enscriptionDate",
header: "Einschreibedatum",
cell: ({ row }: CellContext<Participant, any>) => { cell: ({ row }: CellContext<Participant, any>) => {
return ( return (
<> <span>
{new Date(
row.original.enscriptionDate,
).toLocaleString()}
</span>
);
},
},
{
header: "Anwesend",
cell: ({ row }: CellContext<Participant, any>) => {
if (row.original.attended) {
return <span className="text-green-500">Ja</span>;
} else if (row.original.appointmentCancelled) {
return (
<span className="text-red-500">
Nein (Termin abgesagt)
</span>
);
} else {
return <span>Abwarten</span>;
}
},
},
{
header: "Aktion",
cell: ({ row }: CellContext<Participant, any>) => {
return (
<div className="space-x-2">
<button <button
onClick={() => { onClick={() => {
participantForm.reset(row.original); participantForm.reset(row.original);
@@ -121,6 +151,7 @@ export const AppointmentModal = ({
eventId: event!.id, eventId: event!.id,
userId: participantForm.watch("userId"), userId: participantForm.watch("userId"),
attended: true, attended: true,
appointmentCancelled: false,
}); });
participantTableRef.current?.refresh(); participantTableRef.current?.refresh();
}} }}
@@ -129,7 +160,33 @@ export const AppointmentModal = ({
Anwesend Anwesend
</button> </button>
)} )}
</> {!row.original.attended && event?.hasPresenceEvents && (
<button
type="button"
onSubmit={() => {}}
onClick={async () => {
await upsertParticipant({
eventId: event!.id,
userId: participantForm.watch("userId"),
attended: false,
appointmentCancelled: true,
statusLog: [
...(row.original.statusLog as any),
{
event: "Gefehlt",
timestamp: new Date().toISOString(),
user: `${session?.user?.firstname} (${session?.user?.lastname} - ${session?.user?.publicId})`,
},
],
});
participantTableRef.current?.refresh();
}}
className="btn btn-outline btn-error btn-sm"
>
nicht da
</button>
)}
</div>
); );
}, },
}, },

View File

@@ -35,6 +35,11 @@ export const ParticipantModal = ({
})} })}
className="space-y-1" className="space-y-1"
> >
<Switch
form={participantForm}
name="attended"
label="Termin Teilgenommen"
/>
<Switch <Switch
form={participantForm} form={participantForm}
name="appointmentCancelled" name="appointmentCancelled"
@@ -50,6 +55,18 @@ export const ParticipantModal = ({
name="completetionWorkflowFinished" name="completetionWorkflowFinished"
label="Abgeschlossen (E-Mail-Benachrichtigung senden)" label="Abgeschlossen (E-Mail-Benachrichtigung senden)"
/> />
<div className="w-full">
<h3 className="text-xl">Termine</h3>
<p className="w-full flex justify-between">
<span>Termin ausgewählt</span>
<span>
{new Date(
participantForm.watch("enscriptionDate"),
).toLocaleString()}
</span>
</p>
</div>
<div className="flex flex-col"> <div className="flex flex-col">
<h3 className="text-xl">Verlauf</h3> <h3 className="text-xl">Verlauf</h3>
{participantForm.watch("statusLog")?.map((s) => ( {participantForm.watch("statusLog")?.map((s) => (
@@ -59,9 +76,12 @@ export const ParticipantModal = ({
</div> </div>
))} ))}
</div> </div>
<Button type="submit">Speichern</Button> <Button type="submit" className="btn btn-primary">
Speichern
</Button>
<Button <Button
type="button" type="button"
className="btn btn-error btn-outline"
onSubmit={() => false} onSubmit={() => false}
onClick={() => { onClick={() => {
deleteParticipant(participantForm.watch("id")); deleteParticipant(participantForm.watch("id"));

View File

@@ -26,6 +26,7 @@ import { Select } from "../../../_components/ui/Select";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { eventCompleted } from "@repo/ui"; import { eventCompleted } from "@repo/ui";
import { se } from "date-fns/locale";
interface ModalBtnProps { interface ModalBtnProps {
title: string; title: string;
@@ -93,6 +94,23 @@ const ModalBtn = ({
date.id === selectAppointmentForm.watch("eventAppointmentId") || date.id === selectAppointmentForm.watch("eventAppointmentId") ||
selectedAppointment?.id, selectedAppointment?.id,
); );
const ownIndexInParticipantList =
(selectedDate as any)?.Participants?.findIndex(
(p: Participant) => p.userId === user.id,
) || (selectedDate as any)?.Participants?.length + 1;
const ownPlaceInParticipantList =
ownIndexInParticipantList === -1
? (selectedDate as any)?.Participants?.length + 1
: ownIndexInParticipantList + 1;
console.log(
selectedDate,
ownPlaceInParticipantList > event.maxParticipants!,
ownPlaceInParticipantList,
event.maxParticipants,
);
return ( return (
<> <>
<button <button
@@ -156,10 +174,10 @@ const ModalBtn = ({
Du hast an dem Presenztermin teilgenommen Du hast an dem Presenztermin teilgenommen
</p> </p>
)} )}
{selectedDate && {!!selectedDate &&
event.maxParticipants !== null && !!event.maxParticipants &&
(selectedDate as any)._count.Participants >= !!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="py-4 my-5 flex items-center gap-2 justify-center border alert alert-error alert-outline"
@@ -169,7 +187,8 @@ const ModalBtn = ({
fill="none" fill="none"
/> />
Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht
teilnehmen können teilnehmen können. (Listenplatz: {ownPlaceInParticipantList}{" "}
, max. {event.maxParticipants})
</p> </p>
)} )}
{selectedAppointment && !participant?.appointmentCancelled && ( {selectedAppointment && !participant?.appointmentCancelled && (
@@ -237,13 +256,12 @@ const ModalBtn = ({
event.type === "OBLIGATED_COURSE" && "btn-secondary", event.type === "OBLIGATED_COURSE" && "btn-secondary",
)} )}
onClick={async () => { onClick={async () => {
console.log("submit", selectAppointmentForm.formState.errors);
const data = selectAppointmentForm.getValues(); const data = selectAppointmentForm.getValues();
if (!data.eventAppointmentId) return; if (!data.eventAppointmentId) return;
console.log("submit", data);
await upsertParticipant({ await upsertParticipant({
...data, ...data,
enscriptionDate: new Date(),
statusLog: data.statusLog?.filter((log) => log !== null), statusLog: data.statusLog?.filter((log) => log !== null),
appointmentCancelled: false, appointmentCancelled: false,
}); });

View File

@@ -38,8 +38,13 @@ export default async () => {
}, },
include: { include: {
Participants: { Participants: {
where: { select: {
userId: user.id, enscriptionDate: true,
id: true,
userId: true,
},
orderBy: {
enscriptionDate: "asc",
}, },
}, },
_count: { _count: {

View File

@@ -17,19 +17,21 @@ model EventAppointment {
} }
model Participant { model Participant {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
userId String @map(name: "user_id") userId String @map(name: "user_id")
finisherMoodleCurseCompleted Boolean @default(false) finisherMoodleCurseCompleted Boolean @default(false)
attended Boolean @default(false) attended Boolean @default(false)
appointmentCancelled Boolean @default(false) appointmentCancelled Boolean @default(false)
completetionWorkflowFinished Boolean @default(false) completetionWorkflowFinished Boolean @default(false)
eventAppointmentId Int? eventAppointmentId Int?
statusLog Json[] @default([]) enscriptionDate DateTime @default(now())
eventId Int
statusLog Json[] @default([])
eventId Int
// relations: // relations:
User User @relation(fields: [userId], references: [id]) User User @relation(fields: [userId], references: [id])
Event Event @relation(fields: [eventId], references: [id]) Event Event @relation(fields: [eventId], references: [id])
EventAppointment EventAppointment? @relation(fields: [eventAppointmentId], references: [id]) EventAppointment EventAppointment? @relation(fields: [eventAppointmentId], references: [id])
} }
model Event { model Event {

View File

@@ -27,7 +27,7 @@ model User {
moodleId Int? @map(name: "moodle_id") moodleId Int? @map(name: "moodle_id")
emailVerified DateTime? @map(name: "email_verified") emailVerified DateTime? @map(name: "email_verified")
image String? image String?
badges BADGES[] @unique @default([]) badges BADGES[] @default([])
permissions PERMISSION[] @default([]) permissions PERMISSION[] @default([])
createdAt DateTime @default(now()) @map(name: "created_at") createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at") updatedAt DateTime @default(now()) @map(name: "updated_at")