Merge pull request #1 from VAR-Virtual-Air-Rescue/tailwind-+-daisyUI-update

Tailwind + daisy UI update
This commit was merged in pull request #1.
This commit is contained in:
Nicolas
2025-02-23 16:26:54 +01:00
committed by GitHub
22 changed files with 498 additions and 1081 deletions

View File

@@ -16,6 +16,11 @@ export default async ({ params }: { params: Promise<{ id: string }> }) => {
publicId: true,
},
});
const appointments = await prisma.eventAppointment.findMany({
where: {
eventId: parseInt(id),
},
});
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';
import { zodResolver } from '@hookform/resolvers/zod';
import {
EventAppointmentOptionalDefaults,
EventAppointmentOptionalDefaultsSchema,
EventOptionalDefaults,
EventOptionalDefaultsSchema,
ParticipantOptionalDefaultsSchema,
} from '@repo/db/zod';
import { set, useForm } from 'react-hook-form';
import { BADGES, Event, User } from '@repo/db';
import { Bot, FileText, UserIcon } from 'lucide-react';
import { BADGES, Event, EventAppointment, User } from '@repo/db';
import { Bot, Calendar, FileText, UserIcon } from 'lucide-react';
import { Input } from '../../../../_components/ui/Input';
import { useRef, useState } from 'react';
import { deleteEvent, upsertEvent } from '../action';
import { deleteEvent, upsertAppointment, upsertEvent } from '../action';
import { Button } from '../../../../_components/ui/Button';
import { redirect } from 'next/navigation';
import { redirect, useRouter } from 'next/navigation';
import { Switch } from '../../../../_components/ui/Switch';
import { PaginatedTable } from '../../../../_components/PaginatedTable';
import {
PaginatedTable,
PaginatedTableRef,
} from '../../../../_components/PaginatedTable';
import { Select } from '../../../../_components/ui/Select';
import { useSession } from 'next-auth/react';
export const Form = ({
event,
users,
appointments = [],
}: {
event?: Event;
users: {
@@ -29,15 +35,21 @@ export const Form = ({
lastname: string;
publicId: string;
}[];
appointments?: EventAppointment[];
}) => {
const { data: session } = useSession();
const form = useForm({
resolver: zodResolver(EventOptionalDefaultsSchema),
defaultValues: event,
});
const appointmentForm = useForm({
const appointmentForm = useForm<EventAppointmentOptionalDefaults>({
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 [deleteLoading, setDeleteLoading] = useState(false);
const addParticipantModal = useRef<HTMLDialogElement>(null);
@@ -51,20 +63,26 @@ export const Form = ({
</button>
</form>
<h3 className="font-bold text-lg">Teilnehmer hinzufügen</h3>
<h3 className="font-bold text-lg">Termin hinzufügen</h3>
<form
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}
name="userId"
label="Teilnehmer"
options={users.map((user) => ({
label: `${user.firstname} ${user.lastname} (${user.publicId})`,
value: user.id,
}))}
label="Datum"
name="appointmentDate"
type="date"
/>
<div className="modal-action">
<Button type="submit" className="btn btn-primary">
@@ -154,7 +172,7 @@ export const Form = ({
<div className="card-body">
<div className="flex justify-between">
<h2 className="card-title">
<UserIcon className="w-5 h-5" /> Termine
<Calendar className="w-5 h-5" /> Termine
</h2>
<button
className="btn btn-primary btn-outline"
@@ -165,16 +183,66 @@ export const Form = ({
</div>
<PaginatedTable
ref={appointmentsTableRef}
prismaModel={'eventAppointment'}
filter={{
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>

View File

@@ -1,6 +1,6 @@
'use server';
import { prisma, Prisma, Event, Participant } from '@repo/db';
import { prisma, Prisma, Event, Participant, EventAppointment } from '@repo/db';
export const upsertEvent = async (
event: Prisma.EventCreateInput,
@@ -19,6 +19,34 @@ export const deleteEvent = async (id: Event['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 } });
prisma.eventAppointment.findMany({
where: {
eventId: id,
},
orderBy: {
// TODO: add order by in relation to table selected column
},
});
};
export const upsertParticipant = async (
participant: Prisma.ParticipantCreateInput,
id?: Participant['id']

View File

@@ -1,5 +1,14 @@
import { prisma } from '@repo/db';
import { Form } from '../_components/Form';
export default () => {
return <Form />;
export default async () => {
const users = await prisma.user.findMany({
select: {
id: true,
firstname: true,
lastname: true,
publicId: true,
},
});
return <Form users={users} />;
};

View File

@@ -5,16 +5,6 @@ import Link from 'next/link';
export default () => {
return (
<>
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
<span className="flex items-center gap-2">
<PartyPopperIcon className="w-5 h-5" /> Events
</span>
<Link href={'/admin/event/new'}>
<button className="btn btn-sm btn-outline btn-primary">
Erstellen
</button>
</Link>
</p>
<PaginatedTable
showEditButton
prismaModel="event"
@@ -28,6 +18,18 @@ export default () => {
accessorKey: 'hidden',
},
]}
leftOfSearch={
<span className="flex items-center gap-2">
<PartyPopperIcon className="w-5 h-5" /> Events
</span>
}
rightOfSearch={
<Link href={'/admin/event/new'}>
<button className="btn btn-sm btn-outline btn-primary">
Erstellen
</button>
</Link>
}
/>
</>
);

View File

@@ -1,42 +1,46 @@
import { DatabaseBackupIcon } from "lucide-react";
import { PaginatedTable } from "../../../_components/PaginatedTable";
import Link from "next/link";
import { DatabaseBackupIcon } from 'lucide-react';
import { PaginatedTable } from '../../../_components/PaginatedTable';
import Link from 'next/link';
export default () => {
return (
<>
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
<PaginatedTable
showEditButton
prismaModel="station"
searchFields={['bosCallsign', 'bosUse', 'country', 'operator']}
columns={[
{
header: 'BOS Name',
accessorKey: 'bosCallsign',
},
{
header: 'Bos Use',
accessorKey: 'bosUse',
},
{
header: 'Country',
accessorKey: 'country',
},
{
header: 'operator',
accessorKey: 'operator',
},
]}
leftOfSearch={
<span className="flex items-center gap-2">
<DatabaseBackupIcon className="w-5 h-5" /> Stationen
</span>
<Link href={"/admin/station/new"}>
}
rightOfSearch={
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
<Link href={'/admin/station/new'}>
<button className="btn btn-sm btn-outline btn-primary">
Erstellen
</button>
</Link>
</p>
<PaginatedTable
showEditButton
prismaModel="station"
searchFields={["bosCallsign", "bosUse", "country", "operator"]}
columns={[
{
header: "BOS Name",
accessorKey: "bosCallsign",
},
{
header: "Bos Use",
accessorKey: "bosUse",
},
{
header: "Country",
accessorKey: "country",
},
{
header: "operator",
accessorKey: "operator",
},
]}
}
/>
</>
);

View File

@@ -1,34 +1,36 @@
import { User2 } from "lucide-react";
import { PaginatedTable } from "../../../_components/PaginatedTable";
import { User2 } from 'lucide-react';
import { PaginatedTable } from '../../../_components/PaginatedTable';
export default async () => {
return (
<>
<p className="text-2xl font-semibold text-left flex items-center gap-2">
<User2 className="w-5 h-5" /> Benutzer
</p>
<PaginatedTable
showEditButton
prismaModel="user"
searchFields={["publicId", "firstname", "lastname", "email"]}
searchFields={['publicId', 'firstname', 'lastname', 'email']}
columns={[
{
header: "ID",
accessorKey: "publicId",
header: 'ID',
accessorKey: 'publicId',
},
{
header: "Vorname",
accessorKey: "firstname",
header: 'Vorname',
accessorKey: 'firstname',
},
{
header: "Nachname",
accessorKey: "lastname",
header: 'Nachname',
accessorKey: 'lastname',
},
{
header: "Email",
accessorKey: "email",
header: 'Email',
accessorKey: 'email',
},
]}
leftOfSearch={
<p className="text-2xl font-semibold text-left flex items-center gap-2">
<User2 className="w-5 h-5" /> Benutzer
</p>
}
/>
</>
);

View File

@@ -4,8 +4,11 @@ import {
InstagramLogoIcon,
ReaderIcon,
} from "@radix-ui/react-icons";
import { HorizontalNav, VerticalNav } from "../_components/ui/Nav";
import { HorizontalNav, VerticalNav } from "../_components/Nav";
import { Toaster } from "react-hot-toast";
import { redirect } from "next/navigation";
import { getServerSession } from "../api/auth/[...nextauth]/auth";
import { headers } from "next/headers";
export const metadata: Metadata = {
title: "Create Next App",
@@ -17,6 +20,10 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession();
if (!session) redirect(`/login`);
return (
<div
className="hero min-h-screen"
@@ -35,7 +42,7 @@ export default async function RootLayout({
<HorizontalNav />
{/* Hauptlayout: Sidebar + Content (nimmt Resthöhe ein) */}
<div className="flex flex-grow overflow-hidden">
<div className="flex grow overflow-hidden">
{/* Linke Sidebar */}
<VerticalNav />

View File

@@ -13,10 +13,10 @@ const AuthLayout: NextPage<
'url(https://img.daisyui.com/images/stock/photo-1507358522600-9f71e620c44e.webp)',
}}
>
<div className="hero-overlay bg-opacity-60"></div>
<div className="hero-content text-neutral-content text-center ">
<div className="hero-overlay bg-neutral/60"></div>
<div className="hero-content text-center ">
<div className="max-w-lg">
<div className="card bg-base-100 w-full min-w-[500px] shadow-2xl max-md:min-w-[400px]">
<div className="card rounded-2xl bg-base-100 w-full min-w-[500px] shadow-2xl max-md:min-w-[400px]">
{children}
</div>
</div>

View File

@@ -1,12 +1,12 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { redirect, useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { Toaster, toast } from 'react-hot-toast';
import { z } from 'zod';
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { signIn } from "next-auth/react";
import Link from "next/link";
import { redirect, useSearchParams } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { Toaster, toast } from "react-hot-toast";
import { z } from "zod";
export const Login = () => {
const [isLoading, setIsLoading] = useState(false);
@@ -27,24 +27,22 @@ export const Login = () => {
className="card-body"
onSubmit={form.handleSubmit(async () => {
setIsLoading(true);
const data = await signIn('credentials', {
const data = await signIn("credentials", {
redirect: false,
email: form.getValues('email'),
password: form.getValues('password'),
email: form.getValues("email"),
password: form.getValues("password"),
});
setIsLoading(false);
if (!data || data.error) {
toast.error('E-Mail / Passwort ist falsch!', {
toast.error("E-Mail / Passwort ist falsch!", {
style: {
background:
'var(--fallback-b1, oklch(var(--b1) / var(--tw-bg-opacity, 1)))',
color:
'var(--fallback-nc, oklch(var(--nc) / var(--tw-text-opacity, 1)))',
background: "var(--color-base-100)",
color: "var(--color-base-content)",
},
});
return;
}
redirect(searchParams.get('redirect') || '/');
redirect(searchParams.get("redirect") || "/");
})}
>
<div>
@@ -52,12 +50,12 @@ export const Login = () => {
</div>
<h1 className="text-3xl font-bold">Login</h1>
<span className="text-sm font-medium">
Noch keinen Account? Zur{' '}
Noch keinen Account? Zur{" "}
<Link href="/register" className="link link-accent link-hover">
Registrierung
</Link>
</span>
<label className="input input-bordered flex items-center gap-2">
<label className="input input-bordered flex items-center gap-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -70,16 +68,16 @@ export const Login = () => {
<input
type="text"
className="grow"
{...form.register('email')}
{...form.register("email")}
placeholder="Email"
/>
</label>
<p className="text-error">
{typeof form.formState.errors.email?.message === 'string'
{typeof form.formState.errors.email?.message === "string"
? form.formState.errors.email.message
: ''}
: ""}
</p>
<label className="input input-bordered flex items-center gap-2 mt-2">
<label className="input input-bordered flex items-center gap-2 mt-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -95,21 +93,17 @@ export const Login = () => {
<input
autoComplete="current-password"
type="password"
{...form.register('password')}
{...form.register("password")}
placeholder="Passwort"
className="grow"
/>
</label>
<div className="form-control mt-6">
<button
className="btn btn-primary"
name="loginBtn"
disabled={isLoading}
>
<div className="card-actions mt-6">
<button className="btn btn-primary btn-block" disabled={isLoading}>
{isLoading && (
<span className="loading loading-spinner loading-sm"></span>
)}
Login{isLoading && '...'}
Login{isLoading && "..."}
</button>
</div>
</form>

View File

@@ -71,7 +71,7 @@ export const Register = () => {
</Link>
</span>
<div className="mt-5 mb-2">
<label className="input input-bordered flex items-center gap-2 mt-2">
<label className="input input-bordered flex items-center gap-2 mt-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -92,7 +92,7 @@ export const Register = () => {
? form.formState.errors.firstname.message
: ''}
</p>
<label className="input input-bordered flex items-center gap-2 mt-2">
<label className="input input-bordered flex items-center gap-2 mt-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -113,8 +113,8 @@ export const Register = () => {
? form.formState.errors.lastname.message
: ''}
</p>
<div className="divider divider-neutral">Account</div>
<label className="input input-bordered flex items-center gap-2">
<div className="divider">Account</div>
<label className="input input-bordered flex items-center gap-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -137,7 +137,7 @@ export const Register = () => {
? form.formState.errors.email.message
: ''}
</p>
<label className="input input-bordered flex items-center gap-2 mt-2">
<label className="input input-bordered flex items-center gap-2 mt-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -163,7 +163,7 @@ export const Register = () => {
? form.formState.errors.password.message
: ''}
</p>
<label className="input input-bordered flex items-center gap-2 mt-2">
<label className="input input-bordered flex items-center gap-2 mt-2 w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@@ -189,9 +189,9 @@ export const Register = () => {
? form.formState.errors.passwordConfirm.message
: ''}
</p>
<div className="form-control mt-6">
<div className="card-actions mt-6">
<button
className="btn btn-primary"
className="btn btn-primary btn-block"
name="registerBtn"
disabled={isLoading}
>

View File

@@ -0,0 +1,78 @@
import {
HomeIcon,
PersonIcon,
GearIcon,
ExitIcon,
LockClosedIcon,
} from "@radix-ui/react-icons";
import Link from "next/link";
export const VerticalNav = () => {
return (
<ul className="menu w-64 bg-base-300 p-4 rounded-lg shadow-md">
<li>
<Link href="/">
<HomeIcon /> Dashboard
</Link>
</li>
<li>
<Link href="/profile">
<PersonIcon /> Profile
</Link>
</li>
<li>
<details open>
<summary>
<LockClosedIcon />
Admin
</summary>
<ul>
<li>
<Link href="/admin/user">Benutzer</Link>
</li>
<li>
<Link href="/admin/station">Stationen</Link>
</li>
<li>
<Link href="/admin/event">Events</Link>
</li>
</ul>
</details>
</li>
<li>
<Link href="/settings">
<GearIcon />
Einstellungen
</Link>
</li>
</ul>
);
};
export const HorizontalNav = () => (
<div className="navbar bg-base-200 shadow-md rounded-lg mb-4">
<div className="flex items-center">
<a className="btn btn-ghost normal-case text-xl">
Virtual Air Rescue - HUB
</a>
</div>
<div className="flex items-center ml-auto">
<ul className="flex space-x-2 px-1">
<li>
<Link href="/">
<button className="btn btn-sm btn-outline btn-primary">
Zur Leitstelle
</button>
</Link>
</li>
<li>
<Link href="/logout">
<button className="btn btn-sm btn-ghost">
<ExitIcon /> Logout
</button>
</Link>
</li>
</ul>
</div>
</div>
);

View File

@@ -1,9 +1,19 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import {
useEffect,
useState,
useCallback,
Ref,
useImperativeHandle,
} from 'react';
import SortableTable, { Pagination, SortableTableProps } from './Table';
import { PrismaClient } from '@repo/db';
import { getData } from './pagiantedTableActions';
export interface PaginatedTableRef {
refresh: () => void;
}
interface PaginatedTableProps<TData>
extends Omit<SortableTableProps<TData>, 'data'> {
prismaModel: keyof PrismaClient;
@@ -11,7 +21,10 @@ interface PaginatedTableProps<TData>
rowsPerPage?: number;
showEditButton?: boolean;
searchFields?: string[];
include?: Record<string, boolean>[];
include?: Record<string, boolean>;
leftOfSearch?: React.ReactNode;
rightOfSearch?: React.ReactNode;
ref?: Ref<PaginatedTableRef>;
}
export function PaginatedTable<TData>({
@@ -21,6 +34,9 @@ export function PaginatedTable<TData>({
searchFields = [],
filter,
include,
ref,
leftOfSearch,
rightOfSearch,
...restProps
}: PaginatedTableProps<TData>) {
const [data, setData] = useState<TData[]>([]);
@@ -29,6 +45,30 @@ export function PaginatedTable<TData>({
const [searchTerm, setSearchTerm] = useState('');
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) => {
let timer: NodeJS.Timeout;
return (...args: any[]) => {
@@ -45,25 +85,14 @@ export function PaginatedTable<TData>({
);
useEffect(() => {
getData(
prismaModel,
rowsPerPage,
page * rowsPerPage,
debouncedSearchTerm,
searchFields,
filter
).then((result) => {
if (result) {
setData(result.data);
setTotal(result.total);
}
});
RefreshTableData();
}, [page, debouncedSearchTerm]);
return (
<div className="space-y-4 m-4">
{searchFields.length > 0 && (
<div className="flex justify-end">
<div className="flex items-center gap-2">
<div className="flex-1">{leftOfSearch}</div>
<input
type="text"
placeholder="Suchen..."
@@ -74,6 +103,7 @@ export function PaginatedTable<TData>({
}}
className="input input-bordered w-full max-w-xs justify-end"
/>
<div className="flex justify-center">{rightOfSearch}</div>
</div>
)}
<SortableTable

View File

@@ -116,7 +116,7 @@ export const Pagination = ({
<ArrowLeft size={16} />
</button>
<select
className="select join-item"
className="select join-item w-16"
value={page}
onChange={(e) => setPage(Number(e.target.value))}
>

View File

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

View File

@@ -1,11 +1,29 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@plugin "daisyui";
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
:root {
--background: #ffffff;
--foreground: #171717;
/* --p: 47.67% 0.2484 267.02; */
--nc: #a6adbb;
}
body.modal-open {

View File

@@ -33,16 +33,17 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4.0.8",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.8",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"daisyui": "^4.12.23",
"daisyui": "^5.0.0-beta.8",
"eslint": "^9",
"eslint-config-next": "15.1.4",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"tailwindcss": "^4.0.8",
"typescript": "^5"
}
}

View File

@@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
'@tailwindcss/postcss': {},
},
};

View File

@@ -1,10 +0,0 @@
import type { Config } from 'tailwindcss';
export default {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
plugins: [require('daisyui')],
} satisfies Config;

1005
package-lock.json generated

File diff suppressed because it is too large Load Diff

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
// relations:
Users User[] @relation("EventAppointmentUser")
participants Participant[]
Participants Participant[]
Event Event @relation(fields: [eventId], references: [id])
Presenter User @relation(fields: [presenterId], references: [id])
}