added basic data for event list, md editor and viewer
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
|||||||
} from '../../../../_components/PaginatedTable';
|
} from '../../../../_components/PaginatedTable';
|
||||||
import { Select } from '../../../../_components/ui/Select';
|
import { Select } from '../../../../_components/ui/Select';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { MarkdownEditor } from '../../../../_components/ui/MDEditor';
|
||||||
|
|
||||||
export const Form = ({
|
export const Form = ({
|
||||||
event,
|
event,
|
||||||
@@ -107,12 +108,7 @@ export const Form = ({
|
|||||||
<FileText className="w-5 h-5" /> Allgemeines
|
<FileText className="w-5 h-5" /> Allgemeines
|
||||||
</h2>
|
</h2>
|
||||||
<Input form={form} label="Name" name="name" className="input-sm" />
|
<Input form={form} label="Name" name="name" className="input-sm" />
|
||||||
<Input
|
<MarkdownEditor form={form} name="description" />
|
||||||
form={form}
|
|
||||||
label="Beschreibung"
|
|
||||||
name="description"
|
|
||||||
className="input-sm"
|
|
||||||
/>
|
|
||||||
<Input
|
<Input
|
||||||
form={form}
|
form={form}
|
||||||
label="Maximale Teilnehmer (Nur für live Events)"
|
label="Maximale Teilnehmer (Nur für live Events)"
|
||||||
@@ -174,12 +170,14 @@ export const Form = ({
|
|||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<Calendar className="w-5 h-5" /> Termine
|
<Calendar className="w-5 h-5" /> Termine
|
||||||
</h2>
|
</h2>
|
||||||
|
{event && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary btn-outline"
|
className="btn btn-primary btn-outline"
|
||||||
onClick={() => addParticipantModal.current?.showModal()}
|
onClick={() => addParticipantModal.current?.showModal()}
|
||||||
>
|
>
|
||||||
Hinzufügen
|
Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
@@ -233,6 +231,7 @@ export const Form = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log(row.original);
|
console.log(row.original);
|
||||||
|
// TODO: open modal to edit appointment
|
||||||
}}
|
}}
|
||||||
className="btn btn-sm btn-outline"
|
className="btn btn-sm btn-outline"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { DrawingPinFilledIcon, EnterIcon } from "@radix-ui/react-icons";
|
import { DrawingPinFilledIcon, EnterIcon } from '@radix-ui/react-icons';
|
||||||
import { User } from "@repo/db";
|
import { Event, User } from '@repo/db';
|
||||||
import ModalBtn from "./modalBtn";
|
import ModalBtn from './modalBtn';
|
||||||
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
|
|
||||||
export const KursItem = ({
|
export const KursItem = ({ user, event }: { user: User; event: Event }) => {
|
||||||
user,
|
|
||||||
title,
|
|
||||||
type,
|
|
||||||
beschreibung,
|
|
||||||
badge,
|
|
||||||
moodleReq,
|
|
||||||
}: {
|
|
||||||
user: User;
|
|
||||||
title: string;
|
|
||||||
type: string;
|
|
||||||
beschreibung: string;
|
|
||||||
badge: string;
|
|
||||||
moodleReq: boolean;
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
<div className="card bg-base-200 shadow-xl mb-4">
|
<div className="card bg-base-200 shadow-xl mb-4">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">{title}</h2>
|
<h2 className="card-title">{event.name}</h2>
|
||||||
<div className="absolute top-0 right-0 m-4">
|
<div className="absolute top-0 right-0 m-4">
|
||||||
<span className="badge badge-info badge-outline">
|
<span className="badge badge-info badge-outline">
|
||||||
Zusatzqualifikation
|
Zusatzqualifikation
|
||||||
@@ -30,21 +17,48 @@ export const KursItem = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<p className="text-left text-balance">{beschreibung}</p>
|
<div className="text-left text-balance">
|
||||||
|
<MDEditor.Markdown
|
||||||
|
source={event.description}
|
||||||
|
className="whitespace-pre-wrap"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">{badge}</div>
|
</div>
|
||||||
|
<div className="col-span-2">{event.finishedBadges}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-actions flex justify-between items-center mt-5">
|
<div className="card-actions flex justify-between items-center mt-5">
|
||||||
|
<div>
|
||||||
<p className="text-gray-600 text-left flex items-center gap-2">
|
<p className="text-gray-600 text-left flex items-center gap-2">
|
||||||
<DrawingPinFilledIcon /> <b>Teilnahmevoraussetzungen: </b>
|
<DrawingPinFilledIcon /> <b>Teilnahmevoraussetzungen: </b>
|
||||||
|
{(!event.starterMoodleCourseId ||
|
||||||
|
!event.requiredBadges.length) &&
|
||||||
|
'Keine'}
|
||||||
|
{event.starterMoodleCourseId && (
|
||||||
<a className="link link-info" href="">
|
<a className="link link-info" href="">
|
||||||
Moodle Kurs /MOODLEKURSTITLE\
|
Moodle Kurs {event.starterMoodleCourseId}
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
{!!event.requiredBadges.length && (
|
||||||
|
<div className="flex ml-6">
|
||||||
|
<b className="text-gray-600 text-left">Abzeichen:</b>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{event.requiredBadges.map((badge) => (
|
||||||
|
<div className="badge badge-secondary badge-outline">
|
||||||
|
{badge}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ModalBtn
|
<ModalBtn
|
||||||
title={title}
|
title={event.name}
|
||||||
dates={["Dienstag, 25 Februar 2025", "Mittwoch, 26 Februar 2025"]}
|
dates={['Dienstag, 25 Februar 2025', 'Mittwoch, 26 Februar 2025']}
|
||||||
modalId={title + "_modal" + Math.random()}
|
modalId={`${event.name}_modal.${event.id}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
import { getServerSession } from '../../api/auth/[...nextauth]/auth';
|
||||||
import { PrismaClient } from "@repo/db";
|
import { PrismaClient } from '@repo/db';
|
||||||
import { PilotKurs, KursItem } from "./_components/item";
|
import { PilotKurs, KursItem } from './_components/item';
|
||||||
import {
|
import { RocketIcon } from '@radix-ui/react-icons';
|
||||||
RocketIcon,
|
|
||||||
DrawingPinFilledIcon,
|
|
||||||
EnterIcon,
|
|
||||||
} from "@radix-ui/react-icons";
|
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
if (!session) return null;
|
if (!session) return null;
|
||||||
const user = await prisma.user.findFirst({
|
const user = session.user;
|
||||||
where: {
|
const events = await prisma.event.findMany();
|
||||||
id: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
@@ -25,21 +19,9 @@ export default async () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<PilotKurs user={user} />
|
<PilotKurs user={user} />
|
||||||
<KursItem
|
{events.map((event) => (
|
||||||
user={user}
|
<KursItem user={user} event={event} key={event.id} />
|
||||||
title="Einsteigerkurs für Disponenten"
|
))}
|
||||||
type="1"
|
|
||||||
beschreibung="In diesem Kurs lernen Teilnehmer die Aufgaben eines
|
|
||||||
Disponenten in der Rettungsfliegerei kennen. Dazu gehören die
|
|
||||||
Koordination von Notfalleinsätzen, die effiziente Planung von
|
|
||||||
Ressourcen und die Kommunikation mit Piloten sowie
|
|
||||||
Rettungsdiensten. Der Kurs vermittelt praxisnahe Kenntnisse
|
|
||||||
für die schnelle und präzise Entscheidungsfindung unter
|
|
||||||
Zeitdruck, um eine reibungslose Abwicklung von
|
|
||||||
Rettungseinsätzen zu gewährleisten."
|
|
||||||
badge="Badge"
|
|
||||||
moodleReq={true}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ export function PaginatedTable<TData>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 m-4">
|
<div className="space-y-4 m-4">
|
||||||
{searchFields.length > 0 && (
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex-1">{leftOfSearch}</div>
|
<div className="flex-1">{leftOfSearch}</div>
|
||||||
|
{searchFields.length > 0 && (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Suchen..."
|
placeholder="Suchen..."
|
||||||
@@ -103,9 +103,9 @@ export function PaginatedTable<TData>({
|
|||||||
}}
|
}}
|
||||||
className="input input-bordered w-full max-w-xs justify-end"
|
className="input input-bordered w-full max-w-xs justify-end"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<div className="flex justify-center">{rightOfSearch}</div>
|
<div className="flex justify-center">{rightOfSearch}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<SortableTable
|
<SortableTable
|
||||||
data={data}
|
data={data}
|
||||||
prismaModel={prismaModel}
|
prismaModel={prismaModel}
|
||||||
|
|||||||
46
apps/hub/app/_components/ui/MDEditor.tsx
Normal file
46
apps/hub/app/_components/ui/MDEditor.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
'use client';
|
||||||
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
|
import {
|
||||||
|
FieldValues,
|
||||||
|
Path,
|
||||||
|
RegisterOptions,
|
||||||
|
UseFormReturn,
|
||||||
|
} from 'react-hook-form';
|
||||||
|
import { cn } from '../../../helper/cn';
|
||||||
|
|
||||||
|
interface MarkdownEditorProps<T extends FieldValues> {
|
||||||
|
name: Path<T>;
|
||||||
|
form: UseFormReturn<T>;
|
||||||
|
formOptions?: RegisterOptions<T>;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MarkdownEditor = <T extends FieldValues>({
|
||||||
|
name,
|
||||||
|
label = name,
|
||||||
|
placeholder = label,
|
||||||
|
form,
|
||||||
|
className,
|
||||||
|
}: MarkdownEditorProps<T>) => {
|
||||||
|
return (
|
||||||
|
<label className="floating-label w-full mt-5">
|
||||||
|
<span className="text-lg flex items-center gap-2">{label}</span>
|
||||||
|
<div className={cn('border rounded-lg p-2 w-full', className)}>
|
||||||
|
<MDEditor
|
||||||
|
value={form.watch(name)}
|
||||||
|
onChange={(value) =>
|
||||||
|
form.setValue(name, value as any, { shouldValidate: true })
|
||||||
|
}
|
||||||
|
textareaProps={{ placeholder }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{form.formState.errors[name] && (
|
||||||
|
<p className="text-error">
|
||||||
|
{form.formState.errors[name].message as string}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -24,8 +24,10 @@ export const Switch = <T extends FieldValues>({
|
|||||||
}: InputProps<T>) => {
|
}: InputProps<T>) => {
|
||||||
return (
|
return (
|
||||||
<div className="form-control ">
|
<div className="form-control ">
|
||||||
<label className="label cursor-pointer">
|
<label className="label cursor-pointer w-full">
|
||||||
<span className={cn('label-text', className)}>{label}</span>
|
<span className={cn('label-text text-left w-full', className)}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
<input type="checkbox" className="toggle" {...form.register(name)} />
|
<input type="checkbox" className="toggle" {...form.register(name)} />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {};
|
const removeImports = require('next-remove-imports')();
|
||||||
|
const nextConfig = removeImports({});
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -14,14 +14,18 @@
|
|||||||
"@repo/db": "*",
|
"@repo/db": "*",
|
||||||
"@repo/ui": "*",
|
"@repo/ui": "*",
|
||||||
"@tanstack/react-table": "^8.20.6",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
|
"@uiw/react-md-editor": "^4.0.5",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"i": "^0.3.7",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.474.0",
|
"lucide-react": "^0.474.0",
|
||||||
"next": "15.1.4",
|
"next": "15.1.4",
|
||||||
"next-auth": "^4.24.11",
|
"next-auth": "^4.24.11",
|
||||||
|
"next-remove-imports": "^1.0.12",
|
||||||
|
"npm": "^11.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-day-picker": "^9.5.1",
|
"react-day-picker": "^9.5.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
5687
package-lock.json
generated
5687
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user