Added appointment table

This commit is contained in:
PxlLoewe
2025-02-22 17:39:22 +01:00
parent 8e1e9a67be
commit 00efe207b2
7 changed files with 192 additions and 38 deletions

View File

@@ -16,7 +16,11 @@ export default async ({ params }: { params: Promise<{ id: string }> }) => {
publicId: true, publicId: true,
}, },
}); });
console.log(users); const appointments = await prisma.eventAppointment.findMany({
where: {
eventId: parseInt(id),
},
});
if (!event) return <div>Event not found</div>; if (!event) return <div>Event not found</div>;
return <Form event={event} users={users} />; return <Form event={event} users={users} appointments={appointments} />;
}; };

View File

@@ -1,26 +1,32 @@
'use client'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { import {
EventAppointmentOptionalDefaults,
EventAppointmentOptionalDefaultsSchema, EventAppointmentOptionalDefaultsSchema,
EventOptionalDefaults, EventOptionalDefaults,
EventOptionalDefaultsSchema, EventOptionalDefaultsSchema,
ParticipantOptionalDefaultsSchema, ParticipantOptionalDefaultsSchema,
} from '@repo/db/zod'; } from '@repo/db/zod';
import { set, useForm } from 'react-hook-form'; import { set, useForm } from 'react-hook-form';
import { BADGES, Event, User } from '@repo/db'; import { BADGES, Event, EventAppointment, User } from '@repo/db';
import { Bot, Calendar, FileText, UserIcon } from 'lucide-react'; import { Bot, Calendar, FileText, UserIcon } from 'lucide-react';
import { Input } from '../../../../_components/ui/Input'; import { Input } from '../../../../_components/ui/Input';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { deleteEvent, upsertEvent } from '../action'; import { deleteEvent, upsertAppointment, upsertEvent } from '../action';
import { Button } from '../../../../_components/ui/Button'; import { Button } from '../../../../_components/ui/Button';
import { redirect } from 'next/navigation'; import { redirect, useRouter } from 'next/navigation';
import { Switch } from '../../../../_components/ui/Switch'; import { Switch } from '../../../../_components/ui/Switch';
import { PaginatedTable } from '../../../../_components/PaginatedTable'; import {
PaginatedTable,
PaginatedTableRef,
} from '../../../../_components/PaginatedTable';
import { Select } from '../../../../_components/ui/Select'; import { Select } from '../../../../_components/ui/Select';
import { useSession } from 'next-auth/react';
export const Form = ({ export const Form = ({
event, event,
users, users,
appointments = [],
}: { }: {
event?: Event; event?: Event;
users: { users: {
@@ -29,15 +35,21 @@ export const Form = ({
lastname: string; lastname: string;
publicId: string; publicId: string;
}[]; }[];
appointments?: EventAppointment[];
}) => { }) => {
const { data: session } = useSession();
const form = useForm({ const form = useForm({
resolver: zodResolver(EventOptionalDefaultsSchema), resolver: zodResolver(EventOptionalDefaultsSchema),
defaultValues: event, defaultValues: event,
}); });
const appointmentForm = useForm({ const appointmentForm = useForm<EventAppointmentOptionalDefaults>({
resolver: zodResolver(EventAppointmentOptionalDefaultsSchema), resolver: zodResolver(EventAppointmentOptionalDefaultsSchema),
defaultValues: {
eventId: event?.id,
presenterId: session?.user?.id,
},
}); });
console.log(appointmentForm.formState.errors); const appointmentsTableRef = useRef<PaginatedTableRef>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false);
const addParticipantModal = useRef<HTMLDialogElement>(null); const addParticipantModal = useRef<HTMLDialogElement>(null);
@@ -51,20 +63,26 @@ export const Form = ({
</button> </button>
</form> </form>
<h3 className="font-bold text-lg">Teilnehmer hinzufügen</h3> <h3 className="font-bold text-lg">Termin hinzufügen</h3>
<form <form
onSubmit={appointmentForm.handleSubmit(async (values) => { onSubmit={appointmentForm.handleSubmit(async (values) => {
console.log(values); if (!event) return;
const createdAppointment = await upsertAppointment({
appointmentDate: values.appointmentDate,
eventId: event?.id,
presenterId: values.presenterId,
});
console.log(createdAppointment);
addParticipantModal.current?.close();
appointmentsTableRef.current?.refresh();
})} })}
className="flex flex-col"
> >
<Select <Input
form={appointmentForm} form={appointmentForm}
name="userId" label="Datum"
label="Teilnehmer" name="appointmentDate"
options={users.map((user) => ({ type="date"
label: `${user.firstname} ${user.lastname} (${user.publicId})`,
value: user.id,
}))}
/> />
<div className="modal-action"> <div className="modal-action">
<Button type="submit" className="btn btn-primary"> <Button type="submit" className="btn btn-primary">
@@ -165,16 +183,66 @@ export const Form = ({
</div> </div>
<PaginatedTable <PaginatedTable
ref={appointmentsTableRef}
prismaModel={'eventAppointment'} prismaModel={'eventAppointment'}
filter={{ filter={{
eventId: event?.id, eventId: event?.id,
}} }}
include={[ include={{
Presenter: true,
Participants: true,
}}
columns={[
{ {
user: true, header: 'Datum',
accessorKey: 'appointmentDate',
accessorFn: (date) =>
new Date(date.appointmentDate).toLocaleDateString(),
},
{
header: 'Presenter',
accessorKey: 'presenter',
cell: ({ row }) => (
<div className="flex items-center">
<span className="ml-2">
{(row.original as any).Presenter.firstname}{' '}
{(row.original as any).Presenter.lastname}
</span>
</div>
),
},
{
header: 'Teilnehmer',
accessorKey: 'Participants',
cell: ({ row }) => (
<div className="flex items-center">
<UserIcon className="w-5 h-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={() => {
console.log(row.original);
}}
className="btn btn-sm btn-outline"
>
Bearbeiten
</button>
</div>
);
},
}, },
]} ]}
columns={[]}
/> />
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
'use server'; 'use server';
import { prisma, Prisma, Event, Participant } from '@repo/db'; import { prisma, Prisma, Event, Participant, EventAppointment } from '@repo/db';
export const upsertEvent = async ( export const upsertEvent = async (
event: Prisma.EventCreateInput, event: Prisma.EventCreateInput,
@@ -19,6 +19,26 @@ export const deleteEvent = async (id: Event['id']) => {
await prisma.event.delete({ where: { id: id } }); await prisma.event.delete({ where: { id: id } });
}; };
export const upsertAppointment = async (
eventAppointment: Prisma.XOR<
Prisma.EventAppointmentCreateInput,
Prisma.EventAppointmentUncheckedCreateInput
>,
id?: EventAppointment['id']
) => {
const newEventAppointment = id
? await prisma.eventAppointment.update({
where: { id: id },
data: eventAppointment,
})
: await prisma.eventAppointment.create({ data: eventAppointment });
return newEventAppointment;
};
export const deleteAppoinement = async (id: Event['id']) => {
await prisma.eventAppointment.delete({ where: { id: id } });
};
export const upsertParticipant = async ( export const upsertParticipant = async (
participant: Prisma.ParticipantCreateInput, participant: Prisma.ParticipantCreateInput,
id?: Participant['id'] id?: Participant['id']

View File

@@ -1,9 +1,19 @@
'use client'; 'use client';
import { useEffect, useState, useCallback } from 'react'; import {
useEffect,
useState,
useCallback,
Ref,
useImperativeHandle,
} from 'react';
import SortableTable, { Pagination, SortableTableProps } from './Table'; import SortableTable, { Pagination, SortableTableProps } from './Table';
import { PrismaClient } from '@repo/db'; import { PrismaClient } from '@repo/db';
import { getData } from './pagiantedTableActions'; import { getData } from './pagiantedTableActions';
export interface PaginatedTableRef {
refresh: () => void;
}
interface PaginatedTableProps<TData> interface PaginatedTableProps<TData>
extends Omit<SortableTableProps<TData>, 'data'> { extends Omit<SortableTableProps<TData>, 'data'> {
prismaModel: keyof PrismaClient; prismaModel: keyof PrismaClient;
@@ -11,7 +21,8 @@ interface PaginatedTableProps<TData>
rowsPerPage?: number; rowsPerPage?: number;
showEditButton?: boolean; showEditButton?: boolean;
searchFields?: string[]; searchFields?: string[];
include?: Record<string, boolean>[]; include?: Record<string, boolean>;
ref?: Ref<PaginatedTableRef>;
} }
export function PaginatedTable<TData>({ export function PaginatedTable<TData>({
@@ -21,6 +32,7 @@ export function PaginatedTable<TData>({
searchFields = [], searchFields = [],
filter, filter,
include, include,
ref,
...restProps ...restProps
}: PaginatedTableProps<TData>) { }: PaginatedTableProps<TData>) {
const [data, setData] = useState<TData[]>([]); const [data, setData] = useState<TData[]>([]);
@@ -29,6 +41,30 @@ export function PaginatedTable<TData>({
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
const RefreshTableData = async () => {
getData(
prismaModel,
rowsPerPage,
page * rowsPerPage,
debouncedSearchTerm,
searchFields,
filter,
include
).then((result) => {
if (result) {
setData(result.data);
setTotal(result.total);
}
});
};
useImperativeHandle(ref, () => ({
refresh: () => {
console.log('refresh');
RefreshTableData();
},
}));
const debounce = (func: Function, delay: number) => { const debounce = (func: Function, delay: number) => {
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
return (...args: any[]) => { return (...args: any[]) => {
@@ -45,19 +81,7 @@ export function PaginatedTable<TData>({
); );
useEffect(() => { useEffect(() => {
getData( RefreshTableData();
prismaModel,
rowsPerPage,
page * rowsPerPage,
debouncedSearchTerm,
searchFields,
filter
).then((result) => {
if (result) {
setData(result.data);
setTotal(result.total);
}
});
}, [page, debouncedSearchTerm]); }, [page, debouncedSearchTerm]);
return ( return (

View File

@@ -10,7 +10,7 @@ export async function getData(
searchTerm: string, searchTerm: string,
searchFields: string[], searchFields: string[],
filter?: Record<string, any>, filter?: Record<string, any>,
include?: Record<string, boolean>[] include?: Record<string, boolean>
) { ) {
if (!model || !prisma[model]) { if (!model || !prisma[model]) {
return { data: [], total: 0 }; return { data: [], total: 0 };

View File

@@ -0,0 +1,38 @@
/*
Warnings:
- You are about to drop the `_EventAppointmentToUser` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `presenterId` to the `EventAppointment` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "_EventAppointmentToUser" DROP CONSTRAINT "_EventAppointmentToUser_A_fkey";
-- DropForeignKey
ALTER TABLE "_EventAppointmentToUser" DROP CONSTRAINT "_EventAppointmentToUser_B_fkey";
-- AlterTable
ALTER TABLE "EventAppointment" ADD COLUMN "presenterId" TEXT NOT NULL;
-- DropTable
DROP TABLE "_EventAppointmentToUser";
-- CreateTable
CREATE TABLE "_EventAppointmentUser" (
"A" INTEGER NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_EventAppointmentUser_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_EventAppointmentUser_B_index" ON "_EventAppointmentUser"("B");
-- AddForeignKey
ALTER TABLE "EventAppointment" ADD CONSTRAINT "EventAppointment_presenterId_fkey" FOREIGN KEY ("presenterId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_EventAppointmentUser" ADD CONSTRAINT "_EventAppointmentUser_A_fkey" FOREIGN KEY ("A") REFERENCES "EventAppointment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_EventAppointmentUser" ADD CONSTRAINT "_EventAppointmentUser_B_fkey" FOREIGN KEY ("B") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -17,7 +17,7 @@ model EventAppointment {
presenterId String presenterId String
// relations: // relations:
Users User[] @relation("EventAppointmentUser") Users User[] @relation("EventAppointmentUser")
participants Participant[] Participants Participant[]
Event Event @relation(fields: [eventId], references: [id]) Event Event @relation(fields: [eventId], references: [id])
Presenter User @relation(fields: [presenterId], references: [id]) Presenter User @relation(fields: [presenterId], references: [id])
} }