remove appointment from events
This commit is contained in:
@@ -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 <div>Event nicht gefunden</div>;
|
||||
|
||||
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 <div>Termin nicht gefunden</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppointmentForm event={event} initialAppointment={appointment} appointmentId={appointmentId} />
|
||||
);
|
||||
}
|
||||
@@ -40,11 +40,11 @@ export default async function Page({
|
||||
</Link>
|
||||
</div>
|
||||
<p className="text-left text-2xl font-semibold">
|
||||
<PersonIcon className="mr-2 inline h-5 w-5" /> Event-übersicht für{" "}
|
||||
<PersonIcon className="mr-2 inline h-5 w-5" /> Event-Übersicht für{" "}
|
||||
{`${user.firstname} ${user.lastname} #${user.publicId}`}
|
||||
</p>
|
||||
</div>
|
||||
<ParticipantForm event={event} user={user} participant={participant} />
|
||||
<ParticipantForm event={event} participant={participant} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<PaginatedTableRef>(null);
|
||||
|
||||
const form = useForm<EventAppointmentOptionalDefaults>({
|
||||
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 (
|
||||
<div className="bg-base-100 min-h-screen p-6">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<Link href={`/admin/event/${event.id}`} className="btn btn-ghost btn-sm mb-4">
|
||||
<ArrowLeft className="h-4 w-4" /> Zurück
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold">
|
||||
{appointmentId === "new" ? "Neuer Termin" : "Termin bearbeiten"}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
{/* Form Card */}
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 lg:col-span-1">
|
||||
<div className="card bg-base-200 shadow-lg">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title text-lg">
|
||||
<Calendar className="h-5 w-5" /> Termin Details
|
||||
</h2>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text font-semibold">Datum & Zeit</span>
|
||||
</label>
|
||||
<DateInput
|
||||
value={new Date(form.watch("appointmentDate") || Date.now())}
|
||||
onChange={(date) => form.setValue("appointmentDate", date)}
|
||||
/>
|
||||
{form.formState.errors.appointmentDate && (
|
||||
<span className="text-error mt-1 text-sm">
|
||||
{form.formState.errors.appointmentDate.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="divider" />
|
||||
|
||||
<div className="flex gap-2 pt-2">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={form.formState.isSubmitting}
|
||||
className="btn btn-primary flex-1"
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
{appointmentId !== "new" && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
isLoading={form.formState.isSubmitting}
|
||||
className="btn btn-error"
|
||||
>
|
||||
Löschen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Participants Table */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="card bg-base-200 shadow-lg">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title text-lg">
|
||||
<Users className="h-5 w-5" /> Teilnehmer
|
||||
</h2>
|
||||
|
||||
{appointmentId !== "new" ? (
|
||||
<PaginatedTable
|
||||
ref={participantTableRef}
|
||||
prismaModel={"participant"}
|
||||
getFilter={() =>
|
||||
({
|
||||
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 (
|
||||
<span>{new Date(row.original.enscriptionDate).toLocaleString()}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Status",
|
||||
cell: ({ row }) => {
|
||||
if (row.original.attended) {
|
||||
return <span className="badge badge-success">Anwesend</span>;
|
||||
} else if (row.original.appointmentCancelled) {
|
||||
return <span className="badge badge-error">Abgesagt</span>;
|
||||
} else {
|
||||
return <span className="badge badge-ghost">Offen</span>;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
href={`/admin/event/${event.id}/participant/${row.original.id}`}
|
||||
className="btn btn-sm btn-outline"
|
||||
>
|
||||
Anzeigen
|
||||
</Link>
|
||||
{!row.original.attended && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
await upsertParticipant({
|
||||
eventId: event.id,
|
||||
userId: row.original.userId,
|
||||
attended: true,
|
||||
appointmentCancelled: false,
|
||||
});
|
||||
participantTableRef.current?.refresh();
|
||||
}}
|
||||
className="btn btn-sm btn-info btn-outline"
|
||||
>
|
||||
✓
|
||||
</button>
|
||||
)}
|
||||
{!row.original.appointmentCancelled && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
await upsertParticipant({
|
||||
eventId: event.id,
|
||||
userId: row.original.userId,
|
||||
attended: false,
|
||||
appointmentCancelled: true,
|
||||
statusLog: [
|
||||
...(row.original.statusLog as InputJsonValueType[]),
|
||||
{
|
||||
event: "Gefehlt an Event",
|
||||
timestamp: new Date().toISOString(),
|
||||
user: "Admin",
|
||||
},
|
||||
],
|
||||
});
|
||||
participantTableRef.current?.refresh();
|
||||
}}
|
||||
className="btn btn-sm btn-error btn-outline"
|
||||
>
|
||||
✗
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
] as ColumnDef<Participant>[]
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="py-8 text-center text-gray-500">
|
||||
Speichern Sie den Termin um Teilnehmer hinzuzufügen
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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<HTMLDialogElement | null>;
|
||||
participantModal: RefObject<HTMLDialogElement | null>;
|
||||
appointmentsTableRef: React.RefObject<PaginatedTableRef | null>;
|
||||
appointmentForm: UseFormReturn<EventAppointmentOptionalDefaults>;
|
||||
participantForm: UseFormReturn<Participant>;
|
||||
}
|
||||
|
||||
export const AppointmentModal = ({
|
||||
event,
|
||||
ref,
|
||||
participantModal,
|
||||
appointmentsTableRef,
|
||||
appointmentForm,
|
||||
participantForm,
|
||||
}: AppointmentModalProps) => {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const participantTableRef = useRef<PaginatedTableRef>(null);
|
||||
|
||||
return (
|
||||
<dialog ref={ref} className="modal">
|
||||
<div className="modal-box min-h-[500px] min-w-[900px]">
|
||||
<form method="dialog">
|
||||
{/* if there is a button in form, it will close the modal */}
|
||||
<button
|
||||
className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||
onClick={() => ref.current?.close()}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form
|
||||
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
||||
if (!event) return;
|
||||
await upsertAppointment(values);
|
||||
ref.current?.close();
|
||||
appointmentsTableRef.current?.refresh();
|
||||
})}
|
||||
className="flex flex-col"
|
||||
>
|
||||
<div className="mr-7 flex justify-between">
|
||||
<h3 className="text-lg font-bold">Termin {appointmentForm.watch("id")}</h3>
|
||||
<DateInput
|
||||
value={new Date(appointmentForm.watch("appointmentDate") || Date.now())}
|
||||
onChange={(date) => appointmentForm.setValue("appointmentDate", date)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<PaginatedTable
|
||||
supressQuery={appointmentForm.watch("id") === undefined}
|
||||
ref={participantTableRef}
|
||||
columns={
|
||||
[
|
||||
{
|
||||
accessorKey: "User.firstname",
|
||||
header: "Vorname",
|
||||
},
|
||||
{
|
||||
accessorKey: "User.lastname",
|
||||
header: "Nachname",
|
||||
},
|
||||
{
|
||||
accessorKey: "enscriptionDate",
|
||||
header: "Einschreibedatum",
|
||||
cell: ({ row }) => {
|
||||
return <span>{new Date(row.original.enscriptionDate).toLocaleString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Anwesend",
|
||||
cell: ({ row }) => {
|
||||
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>?</span>;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Aktion",
|
||||
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
participantForm.reset(row.original);
|
||||
participantModal.current?.showModal();
|
||||
}}
|
||||
className="btn btn-outline btn-sm"
|
||||
>
|
||||
anzeigen
|
||||
</button>
|
||||
{!row.original.attended && event?.hasPresenceEvents && (
|
||||
<button
|
||||
type="button"
|
||||
onSubmit={() => {}}
|
||||
onClick={async () => {
|
||||
await upsertParticipant({
|
||||
eventId: event!.id,
|
||||
userId: row.original.userId,
|
||||
attended: true,
|
||||
appointmentCancelled: false,
|
||||
});
|
||||
if (!event.finisherMoodleCourseId?.length) {
|
||||
toast(
|
||||
"Teilnehmer hat das event abgeschlossen, workflow ausgeführt",
|
||||
);
|
||||
await handleParticipantFinished(row.original.id.toString());
|
||||
}
|
||||
participantTableRef.current?.refresh();
|
||||
}}
|
||||
className="btn btn-outline btn-info btn-sm"
|
||||
>
|
||||
Anwesend
|
||||
</button>
|
||||
)}
|
||||
{!row.original.appointmentCancelled && event?.hasPresenceEvents && (
|
||||
<button
|
||||
type="button"
|
||||
onSubmit={() => {}}
|
||||
onClick={async () => {
|
||||
await upsertParticipant({
|
||||
eventId: event!.id,
|
||||
userId: row.original.userId,
|
||||
attended: false,
|
||||
appointmentCancelled: true,
|
||||
statusLog: [
|
||||
...(row.original.statusLog as InputJsonValueType[]),
|
||||
{
|
||||
event: "Gefehlt an Event",
|
||||
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"
|
||||
>
|
||||
abwesend
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
] as ColumnDef<Participant>[]
|
||||
}
|
||||
prismaModel={"participant"}
|
||||
getFilter={() =>
|
||||
({
|
||||
eventAppointmentId: appointmentForm.watch("id")!,
|
||||
}) as Prisma.ParticipantWhereInput
|
||||
}
|
||||
include={{ User: true }}
|
||||
leftOfPagination={
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" className="btn btn-primary">
|
||||
Speichern
|
||||
</Button>
|
||||
{appointmentForm.watch("id") && (
|
||||
<Button
|
||||
type="button"
|
||||
onSubmit={() => {}}
|
||||
onClick={async () => {
|
||||
await deleteAppoinement(appointmentForm.watch("id")!);
|
||||
ref.current?.close();
|
||||
appointmentsTableRef.current?.refresh();
|
||||
}}
|
||||
className="btn btn-error btn-outline"
|
||||
>
|
||||
Löschen
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
@@ -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<EventOptionalDefaults>({
|
||||
resolver: zodResolver(EventOptionalDefaultsSchema),
|
||||
defaultValues: event
|
||||
@@ -40,31 +26,9 @@ export const Form = ({ event }: { event?: Event }) => {
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
const appointmentForm = useForm<EventAppointmentOptionalDefaults>({
|
||||
resolver: zodResolver(EventAppointmentOptionalDefaultsSchema),
|
||||
defaultValues: {
|
||||
eventId: event?.id,
|
||||
presenterId: session?.user?.id,
|
||||
},
|
||||
});
|
||||
const participantForm = useForm<Participant>({
|
||||
resolver: zodResolver(ParticipantSchema),
|
||||
});
|
||||
const appointmentsTableRef = useRef<PaginatedTableRef>(null);
|
||||
const appointmentModal = useRef<HTMLDialogElement>(null);
|
||||
const participantModal = useRef<HTMLDialogElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppointmentModal
|
||||
participantModal={participantModal}
|
||||
participantForm={participantForm}
|
||||
appointmentForm={appointmentForm}
|
||||
ref={appointmentModal}
|
||||
appointmentsTableRef={appointmentsTableRef}
|
||||
event={event}
|
||||
/>
|
||||
<ParticipantModal participantForm={participantForm} ref={participantModal} />
|
||||
<form
|
||||
onSubmit={form.handleSubmit(async (values) => {
|
||||
await upsertEvent(values, event?.id);
|
||||
@@ -147,216 +111,11 @@ export const Form = ({ event }: { event?: Event }) => {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
<Switch form={form} name="hasPresenceEvents" label="Hat Live Event" />
|
||||
<div className="divider w-full" />
|
||||
|
||||
<Switch form={form} name="hidden" label="Event verstecken" />
|
||||
</div>
|
||||
</div>
|
||||
{form.watch("hasPresenceEvents") ? (
|
||||
<div className="card bg-base-200 col-span-6 shadow-xl">
|
||||
<div className="card-body">
|
||||
<PaginatedTable
|
||||
ref={appointmentsTableRef}
|
||||
prismaModel={"eventAppointment"}
|
||||
getFilter={() =>
|
||||
({
|
||||
eventId: event?.id,
|
||||
}) as Prisma.EventAppointmentWhereInput
|
||||
}
|
||||
include={{
|
||||
Presenter: true,
|
||||
Participants: true,
|
||||
}}
|
||||
leftOfSearch={
|
||||
<h2 className="card-title">
|
||||
<Calendar className="h-5 w-5" /> Termine
|
||||
</h2>
|
||||
}
|
||||
rightOfSearch={
|
||||
event && (
|
||||
<button
|
||||
className="btn btn-primary btn-outline"
|
||||
onClick={() => {
|
||||
appointmentModal.current?.showModal();
|
||||
appointmentForm.reset({
|
||||
id: undefined,
|
||||
eventId: event.id,
|
||||
presenterId: session?.user?.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
)
|
||||
}
|
||||
columns={
|
||||
[
|
||||
{
|
||||
header: "Datum",
|
||||
accessorKey: "appointmentDate",
|
||||
accessorFn: (date) => new Date(date.appointmentDate).toLocaleString(),
|
||||
},
|
||||
{
|
||||
header: "Presenter",
|
||||
accessorKey: "presenter",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">
|
||||
{row.original.Presenter.firstname} {row.original.Presenter.lastname}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Teilnehmer",
|
||||
accessorKey: "Participants",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center">
|
||||
<UserIcon className="h-5 w-5" />
|
||||
<span className="ml-2">{row.original.Participants.length}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onSubmit={() => false}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
appointmentForm.reset(row.original);
|
||||
appointmentModal.current?.showModal();
|
||||
}}
|
||||
className="btn btn-sm btn-outline"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
] as ColumnDef<
|
||||
EventAppointmentOptionalDefaults & {
|
||||
Presenter: User;
|
||||
Participants: Participant[];
|
||||
}
|
||||
>[]
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{!form.watch("hasPresenceEvents") ? (
|
||||
<div className="card bg-base-200 col-span-6 shadow-xl">
|
||||
<div className="card-body">
|
||||
{
|
||||
<PaginatedTable
|
||||
leftOfSearch={
|
||||
<h2 className="card-title">
|
||||
<UserIcon className="h-5 w-5" /> Teilnehmer
|
||||
</h2>
|
||||
}
|
||||
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 (
|
||||
<Link
|
||||
className="hover:underline"
|
||||
href={`/admin/user/${row.original.User.id}`}
|
||||
>
|
||||
{row.original.User.firstname}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
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>
|
||||
) : null}
|
||||
<div className="card bg-base-200 col-span-6 shadow-xl">
|
||||
<div className="card-body">
|
||||
<div className="flex w-full gap-4">
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Info Card */}
|
||||
|
||||
@@ -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 } });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user