added select form

This commit is contained in:
PxlLoewe
2025-02-21 22:51:10 +01:00
parent 30af30bd1d
commit 3fd0e991fd
13 changed files with 716 additions and 53 deletions

View File

@@ -1,28 +1,47 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { EventOptionalDefaultsSchema } from '@repo/db/zod';
import {
EventOptionalDefaults,
EventOptionalDefaultsSchema,
ParticipantOptionalDefaultsSchema,
} from '@repo/db/zod';
import { set, useForm } from 'react-hook-form';
import { z } from 'zod';
import { BosUse, Country, Event, prisma } from '@repo/db';
import { FileText, LocateIcon, PlaneIcon, UserIcon } from 'lucide-react';
import { BADGES, Event } from '@repo/db';
import { Bot, FileText, UserIcon } from 'lucide-react';
import { Input } from '../../../../_components/ui/Input';
import { useState } from 'react';
import { useRef, useState } from 'react';
import { deleteEvent, upsertEvent } from '../action';
import { Button } from '../../../../_components/ui/Button';
import { redirect } from 'next/navigation';
import { Switch } from '../../../../_components/ui/Switch';
import { PaginatedTable } from '../../../../_components/PaginatedTable';
import { Select } from '../../../../_components/ui/Select';
export const Form = ({ event }: { event?: Event }) => {
const form = useForm<z.infer<typeof EventOptionalDefaultsSchema>>({
const form = useForm({
resolver: zodResolver(EventOptionalDefaultsSchema),
defaultValues: event,
});
const participantForm = useForm({
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
});
const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
console.log(form.formState.errors);
const addParticipantModal = useRef<HTMLDialogElement>(null);
return (
<>
<dialog ref={addParticipantModal} className="modal">
<div className="modal-box">
<h3 className="font-bold text-lg">Teilnehmer</h3>
<div className="modal-action">
<form method="dialog">
{/* if there is a button in form, it will close the modal */}
<button className="btn">Close</button>
</form>
</div>
</div>
</dialog>
<form
onSubmit={form.handleSubmit(async (values) => {
setLoading(true);
@@ -44,14 +63,74 @@ export const Form = ({ event }: { event?: Event }) => {
name="description"
className="input-sm"
/>
<Input
form={form}
label="Maximale Teilnehmer (Nur für live Events)"
className="input-sm"
{...form.register('maxParticipants', {
valueAsNumber: true,
})}
/>
<Switch form={form} name="hidden" label="Versteckt" />
</div>
</div>
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
<div className="card-body">
<h2 className="card-title">
<UserIcon className="w-5 h-5" /> Teilnehmer
<Bot className="w-5 h-5" /> Automation
</h2>
<Input
form={form}
name="starterMoodleCourseId"
label="Moodle Anmelde Kurs ID"
className="input-sm"
/>
<Input
name="finisherMoodleCourseId"
form={form}
label="Moodle Abschluss Kurs ID"
className="input-sm"
/>
<Select
isMulti
form={form}
name="finishedBadges"
label="Badges bei Abschluss"
options={Object.entries(BADGES).map(([key, value]) => ({
label: value,
value: key,
}))}
/>
<Select
isMulti
form={form}
name="requiredBadges"
label="Benötigte Badges"
options={Object.entries(BADGES).map(([key, value]) => ({
label: value,
value: key,
}))}
/>
<Switch
form={form}
name="hasPresenceEvents"
label="Hat Live Event"
/>
</div>
</div>
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
<div className="card-body">
<div className="flex justify-between">
<h2 className="card-title">
<UserIcon className="w-5 h-5" /> Teilnehmer
</h2>
<button
className="btn btn-primary btn-outline"
onClick={() => addParticipantModal.current?.showModal()}
>
Teilnehmer hinzufügen
</button>
</div>
<PaginatedTable
prismaModel={'participant'}

View File

@@ -1,6 +1,6 @@
'use server';
import { prisma, Prisma, Event } from '@repo/db';
import { prisma, Prisma, Event, Participant } from '@repo/db';
export const upsertEvent = async (
event: Prisma.EventCreateInput,
@@ -18,3 +18,20 @@ export const upsertEvent = async (
export const deleteEvent = async (id: Event['id']) => {
await prisma.event.delete({ where: { id: id } });
};
export const upsertParticipant = async (
participant: Prisma.ParticipantCreateInput,
id?: Participant['id']
) => {
const newParticipant = id
? await prisma.participant.update({
where: { id: id },
data: participant,
})
: await prisma.participant.create({ data: participant });
return newParticipant;
};
export const deleteParticipant = async (id: Participant['id']) => {
await prisma.participant.delete({ where: { id: id } });
};

View File

@@ -158,6 +158,7 @@ export const SocialForm = ({
});
if (!user) return null;
console.log('Dirty', form.formState.isDirty);
return (
<form
className="card-body"
@@ -212,7 +213,11 @@ export const SocialForm = ({
</Button>
) : (
<a href={process.env.NEXT_PUBLIC_DISCORD_URL}>
<button className="btn btn-primary btn-block">
<button
type="button"
className="btn btn-primary btn-block"
onSubmit={() => false}
>
<DiscordLogoIcon className="w-5 h-5" /> Mit Discord verbinden
</button>
</a>
@@ -269,7 +274,6 @@ export const PasswordForm = ({ user }: { user: User }) => {
defaultValues: {},
resolver: zodResolver(schema),
});
console.log(form.formState.errors);
return (
<form
className="card-body"

View File

@@ -2,7 +2,7 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { redirect, useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { Toaster, toast } from 'react-hot-toast';
@@ -29,10 +29,10 @@ export const Login = () => {
setIsLoading(true);
const data = await signIn('credentials', {
redirect: false,
callbackUrl: searchParams.get('redirect') || '/',
email: form.getValues('email'),
password: form.getValues('password'),
});
setIsLoading(false);
if (!data || data.error) {
toast.error('E-Mail / Passwort ist falsch!', {
style: {
@@ -42,8 +42,9 @@ export const Login = () => {
'var(--fallback-nc, oklch(var(--nc) / var(--tw-text-opacity, 1)))',
},
});
return;
}
setIsLoading(false);
redirect(searchParams.get('redirect') || '/');
})}
>
<div>

View File

@@ -0,0 +1,58 @@
'use client';
import {
FieldValues,
Path,
RegisterOptions,
UseFormReturn,
} from 'react-hook-form';
import SelectTemplate, { Props as SelectTemplateProps } from 'react-select';
import { cn } from '../../../helper/cn';
import dynamic from 'next/dynamic';
interface SelectProps<T extends FieldValues> {
name: Path<T>;
form: UseFormReturn<T>;
formOptions?: RegisterOptions<T>;
label?: string;
placeholder?: string;
className?: string;
}
const SelectCom = <T extends FieldValues>({
name,
label = name,
placeholder = label,
form,
formOptions,
className,
...inputProps
}: SelectProps<T>) => {
return (
<div>
<span className="label-text text-lg flex items-center gap-2">
{label}
</span>
<SelectTemplate
{...form.register(name, formOptions)}
onChange={(newValue: any) => {
form.setValue(name, newValue);
form.trigger(name);
}}
className={cn('w-full placeholder:text-neutral-600', className)}
placeholder={placeholder}
{...inputProps}
/>
{form.formState.errors[name] && (
<p className="text-error">
{form.formState.errors[name].message as string}
</p>
)}
</div>
);
};
const SelectWrapper = (props: any) => <SelectCom {...props} />;
export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
ssr: false,
});

View File

@@ -11,8 +11,8 @@
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@next-auth/prisma-adapter": "^1.0.7",
"@repo/ui": "*",
"@repo/db": "*",
"@repo/ui": "*",
"@tanstack/react-table": "^8.20.6",
"axios": "^1.7.9",
"bcryptjs": "^2.4.3",
@@ -26,6 +26,7 @@
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.5.1",
"react-select": "^5.10.0",
"tailwind-merge": "^2.6.0",
"zod": "^3.24.1"
},