Finalized Event modal
This commit is contained in:
@@ -81,6 +81,7 @@ router.put("/", async (req, res) => {
|
|||||||
router.patch("/:id", async (req, res) => {
|
router.patch("/:id", async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
try {
|
try {
|
||||||
|
console.log("Updating mission with ID:", id, req.body);
|
||||||
const updatedMission = await prisma.mission.update({
|
const updatedMission = await prisma.mission.update({
|
||||||
where: { id: Number(id) },
|
where: { id: Number(id) },
|
||||||
data: req.body,
|
data: req.body,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
||||||
import { prisma } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { EventCard } from "../events/_components/item";
|
import { EventCard } from "../events/_components/EventCard";
|
||||||
import { RocketIcon } from "lucide-react";
|
import { RocketIcon } from "lucide-react";
|
||||||
import { eventCompleted } from "@repo/shared-components";
|
import { eventCompleted } from "@repo/shared-components";
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useSession } from "next-auth/react";
|
|||||||
import { useRef, useEffect, useState } from "react";
|
import { useRef, useEffect, useState } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getEvents } from "../../../helper/events";
|
import { getEvents } from "../../../helper/events";
|
||||||
import { EventCard } from "(app)/events/_components/item";
|
import { EventCard } from "(app)/events/_components/EventCard";
|
||||||
|
|
||||||
const PathsOptions = ({
|
const PathsOptions = ({
|
||||||
selected,
|
selected,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const StatsToggle = () => {
|
|||||||
const session = useSession();
|
const session = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const user = session.data?.user;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const statsPage = searchParams.get("stats") || "pilot";
|
const statsPage = searchParams.get("stats") || "pilot";
|
||||||
if (statsPage === "dispo") {
|
if (statsPage === "dispo") {
|
||||||
@@ -30,11 +30,7 @@ export const StatsToggle = () => {
|
|||||||
return (
|
return (
|
||||||
<header className="flex justify-between items-center pb-4">
|
<header className="flex justify-between items-center pb-4">
|
||||||
<h1 className="text-2xl font-bold">
|
<h1 className="text-2xl font-bold">
|
||||||
Hallo,{" "}
|
Hallo, {user?.firstname} <span className="text-gray-500">{" #" + user?.publicId}</span>!
|
||||||
{session.status === "authenticated"
|
|
||||||
? session.data?.user.firstname + " <" + session.data?.user.publicId + ">"
|
|
||||||
: "<username>"}
|
|
||||||
{"!"}
|
|
||||||
</h1>
|
</h1>
|
||||||
<div>
|
<div>
|
||||||
<div className="tooltip tooltip-left" data-tip="Disponent / Pilot">
|
<div className="tooltip tooltip-left" data-tip="Disponent / Pilot">
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const AppointmentModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog ref={ref} className="modal ">
|
<dialog ref={ref} className="modal ">
|
||||||
<div className="modal-box min-w-[900px]">
|
<div className="modal-box min-w-[900px] min-h-[500px]">
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
{/* if there is a button in form, it will close the modal */}
|
{/* if there is a button in form, it will close the modal */}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -155,11 +155,23 @@ export const Form = ({ event }: { event?: Event }) => {
|
|||||||
{form.watch("hasPresenceEvents") ? (
|
{form.watch("hasPresenceEvents") ? (
|
||||||
<div className="card bg-base-200 shadow-xl col-span-6">
|
<div className="card bg-base-200 shadow-xl col-span-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="flex justify-between">
|
<PaginatedTable
|
||||||
|
ref={appointmentsTableRef}
|
||||||
|
prismaModel={"eventAppointment"}
|
||||||
|
filter={{
|
||||||
|
eventId: event?.id,
|
||||||
|
}}
|
||||||
|
include={{
|
||||||
|
Presenter: true,
|
||||||
|
Participants: true,
|
||||||
|
}}
|
||||||
|
leftOfSearch={
|
||||||
<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 && (
|
}
|
||||||
|
rightOfSearch={
|
||||||
|
event && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary btn-outline"
|
className="btn btn-primary btn-outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -173,19 +185,8 @@ export const Form = ({ event }: { event?: Event }) => {
|
|||||||
>
|
>
|
||||||
Hinzufügen
|
Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
)}
|
)
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<PaginatedTable
|
|
||||||
ref={appointmentsTableRef}
|
|
||||||
prismaModel={"eventAppointment"}
|
|
||||||
filter={{
|
|
||||||
eventId: event?.id,
|
|
||||||
}}
|
|
||||||
include={{
|
|
||||||
Presenter: true,
|
|
||||||
Participants: true,
|
|
||||||
}}
|
|
||||||
columns={
|
columns={
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { DrawingPinFilledIcon } from "@radix-ui/react-icons";
|
import { DrawingPinFilledIcon } from "@radix-ui/react-icons";
|
||||||
import { Event, Participant, EventAppointment, User } from "@repo/db";
|
import { Event, Participant, EventAppointment, User } from "@repo/db";
|
||||||
import ModalBtn from "./modalBtn";
|
import ModalBtn from "./Modal";
|
||||||
import MDEditor from "@uiw/react-md-editor";
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
import { Badge } from "@repo/shared-components";
|
import { Badge } from "@repo/shared-components";
|
||||||
|
|
||||||
@@ -4,7 +4,17 @@ import { CheckCircledIcon, EnterIcon, DrawingPinFilledIcon } from "@radix-ui/rea
|
|||||||
import { Event, EventAppointment, Participant, User } from "@repo/db";
|
import { Event, EventAppointment, Participant, User } from "@repo/db";
|
||||||
import { cn } from "@repo/shared-components";
|
import { cn } from "@repo/shared-components";
|
||||||
import { inscribeToMoodleCourse, upsertParticipant } from "../actions";
|
import { inscribeToMoodleCourse, upsertParticipant } from "../actions";
|
||||||
import { Check, Clock10Icon, ExternalLink, EyeIcon, TriangleAlert } from "lucide-react";
|
import {
|
||||||
|
BookCheck,
|
||||||
|
Calendar,
|
||||||
|
Check,
|
||||||
|
CirclePlay,
|
||||||
|
Clock10Icon,
|
||||||
|
ExternalLink,
|
||||||
|
EyeIcon,
|
||||||
|
Info,
|
||||||
|
TriangleAlert,
|
||||||
|
} from "lucide-react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { ParticipantOptionalDefaults, ParticipantOptionalDefaultsSchema } from "@repo/db/zod";
|
import { ParticipantOptionalDefaults, ParticipantOptionalDefaultsSchema } from "@repo/db/zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -15,6 +25,7 @@ import { eventCompleted } from "@repo/shared-components";
|
|||||||
import MDEditor from "@uiw/react-md-editor";
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
import { DayPicker } from "react-day-picker";
|
import { DayPicker } from "react-day-picker";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { se } from "date-fns/locale";
|
||||||
|
|
||||||
interface ModalBtnProps {
|
interface ModalBtnProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -131,8 +142,11 @@ const ModalBtn = ({
|
|||||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-3 top-3">✕</button>
|
<button className="btn btn-sm btn-circle btn-ghost absolute right-3 top-3">✕</button>
|
||||||
</form>
|
</form>
|
||||||
<h3 className="font-bold text-lg text-left">{title}</h3>
|
<h3 className="font-bold text-lg text-left">{title}</h3>
|
||||||
<div className="grid grid-cols-6 gap-4 mt-4">
|
<div className="flex flex-wrap gap-4 mt-4">
|
||||||
<div className="col-span-4">
|
<div className="flex flex-col gap-2 flex-1 p-3 bg-base-300 min-w-[300px] shadow rounded-lg">
|
||||||
|
<h2 className="flex gap-2 text-lg font-bold">
|
||||||
|
<Info /> Details
|
||||||
|
</h2>
|
||||||
<div className="text-left text-balance">
|
<div className="text-left text-balance">
|
||||||
<MDEditor.Markdown
|
<MDEditor.Markdown
|
||||||
source={event.description}
|
source={event.description}
|
||||||
@@ -144,10 +158,12 @@ const ModalBtn = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{event.hasPresenceEvents && (
|
{event.hasPresenceEvents && (
|
||||||
<div className="flex col-span-2 justify-end">
|
<div className="flex flex-col gap-2 flex-1 p-3 bg-base-300 min-w-[300px] shadow rounded-lg">
|
||||||
{canSelectDate && (
|
<h2 className="flex gap-2 text-lg font-bold">
|
||||||
<div className="flex flex-col items-center justify-center gap-2">
|
<Calendar /> Termine
|
||||||
{!!dates.length && (
|
</h2>
|
||||||
|
<div className="flex flex-1 flex-col justify-center items-center">
|
||||||
|
{!!dates.length && !selectedDate && (
|
||||||
<>
|
<>
|
||||||
<p className="text-center text-info">Melde dich zu einem Termin an</p>
|
<p className="text-center text-info">Melde dich zu einem Termin an</p>
|
||||||
<Select
|
<Select
|
||||||
@@ -169,17 +185,40 @@ const ModalBtn = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!dates.length && (
|
{selectedAppointment && !participant?.appointmentCancelled && (
|
||||||
<p className="text-center text-error">Aktuell sind keine Termine verfügbar</p>
|
<div className="flex flex-col items-center justify-center gap-2">
|
||||||
)}
|
<span>Dein ausgewählter Termin (Deutsche Zeit)</span>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="input input-border min-w-[250px] pointer-events-none"
|
||||||
|
style={{ anchorName: "--rdp" } as React.CSSProperties}
|
||||||
|
>
|
||||||
|
{new Date(selectedAppointment.appointmentDate).toLocaleString("de-DE", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{participant?.attended ? (
|
||||||
{!canSelectDate && participant?.attended && (
|
|
||||||
<p className="py-4 flex items-center gap-2 justify-center">
|
<p className="py-4 flex items-center gap-2 justify-center">
|
||||||
<CheckCircledIcon className="text-success" />
|
<CheckCircledIcon className="text-success" />
|
||||||
Du hast an dem Presenztermin teilgenommen
|
Du hast an dem Presenztermin teilgenommen
|
||||||
</p>
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="py-4 flex items-center gap-2 justify-center">
|
||||||
|
Bitte erscheine ~5 minuten vor dem Termin im Discord
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!dates.length && (
|
||||||
|
<p className="text-center text-error">Aktuell sind keine Termine verfügbar</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{!!selectedDate &&
|
{!!selectedDate &&
|
||||||
!!event.maxParticipants &&
|
!!event.maxParticipants &&
|
||||||
!!ownPlaceInParticipantList &&
|
!!ownPlaceInParticipantList &&
|
||||||
@@ -189,50 +228,20 @@ const ModalBtn = ({
|
|||||||
className="py-4 my-5 flex items-center gap-2 justify-center border alert alert-error alert-outline"
|
className="py-4 my-5 flex items-center gap-2 justify-center border alert alert-error alert-outline"
|
||||||
>
|
>
|
||||||
<TriangleAlert className="h-6 w-6 shrink-0 stroke-current" fill="none" />
|
<TriangleAlert className="h-6 w-6 shrink-0 stroke-current" fill="none" />
|
||||||
Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht teilnehmen können.
|
Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht teilnehmen
|
||||||
(Listenplatz: {ownPlaceInParticipantList} , max. {event.maxParticipants})
|
können. (Listenplatz: {ownPlaceInParticipantList} , max.{" "}
|
||||||
|
{event.maxParticipants})
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{selectedAppointment && !participant?.appointmentCancelled && (
|
|
||||||
<div className="flex flex-col items-center justify-center gap-2">
|
|
||||||
<span>Dein ausgewählter Termin (Deutsche Zeit)</span>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
popoverTarget="rdp-popover"
|
|
||||||
className="input input-border min-w-[250px]"
|
|
||||||
style={{ anchorName: "--rdp" } as React.CSSProperties}
|
|
||||||
>
|
|
||||||
{selectedAppointment.appointmentDate
|
|
||||||
? new Date(selectedAppointment.appointmentDate).toLocaleString("de-DE", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})
|
|
||||||
: "Keinen Termin ausgewählt"}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
popover="auto"
|
|
||||||
id="rdp-popover"
|
|
||||||
className="dropdown"
|
|
||||||
style={{ positionAnchor: "--rdp" } as React.CSSProperties}
|
|
||||||
>
|
|
||||||
<DayPicker
|
|
||||||
className="react-day-picker"
|
|
||||||
mode="single"
|
|
||||||
selected={selectedAppointment.appointmentDate}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span>Bitte erscheine pünktlich im Discord.</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{event.finisherMoodleCourseId && (
|
{event.finisherMoodleCourseId && (
|
||||||
<div className="flex col-span-2 justify-end">
|
<div className="flex flex-col gap-2 flex-1 p-3 bg-base-300 min-w-[300px] shadow rounded-lg">
|
||||||
|
<h2 className="flex gap-2 text-lg font-bold">
|
||||||
|
<BookCheck /> Moodle-Kurs
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-col items-center justify-center h-full">
|
||||||
<MoodleCourseIndicator
|
<MoodleCourseIndicator
|
||||||
participant={participant}
|
participant={participant}
|
||||||
user={user}
|
user={user}
|
||||||
@@ -241,6 +250,7 @@ const ModalBtn = ({
|
|||||||
event={event}
|
event={event}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -278,7 +288,9 @@ const ModalBtn = ({
|
|||||||
<EnterIcon /> Anmelden
|
<EnterIcon /> Anmelden
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{selectedAppointment && !participant?.appointmentCancelled && (
|
{selectedAppointment &&
|
||||||
|
!participant?.appointmentCancelled &&
|
||||||
|
!participant?.attended && (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await upsertParticipant({
|
await upsertParticipant({
|
||||||
@@ -349,7 +361,9 @@ const MoodleCourseIndicator = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center gap-2">
|
<div className="flex flex-col items-center justify-center gap-2">
|
||||||
<p>Moodle-Kurs Benötigt</p>
|
<p className="text-sm flex items-center gap-2">
|
||||||
|
<CirclePlay className="text-warning" /> Moodle-Kurs bereit
|
||||||
|
</p>
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-outline btn-info ml-2"
|
className="btn btn-sm btn-outline btn-info ml-2"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -372,6 +386,10 @@ const MoodleCourseIndicator = ({
|
|||||||
<ExternalLink size={16} />
|
<ExternalLink size={16} />
|
||||||
Zum Moodle Kurs
|
Zum Moodle Kurs
|
||||||
</button>
|
</button>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Wenn du nach dem Anmelden den moodle Kurs nicht siehst, warte ein paar Sekunden und lade die
|
||||||
|
Seite neu.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { prisma } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
||||||
import { EventCard } from "./_components/item";
|
import { EventCard } from "./_components/EventCard";
|
||||||
import { RocketIcon } from "@radix-ui/react-icons";
|
import { RocketIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
const page = async () => {
|
const page = async () => {
|
||||||
@@ -38,6 +38,9 @@ const page = async () => {
|
|||||||
id: true,
|
id: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
},
|
},
|
||||||
|
where: {
|
||||||
|
appointmentCancelled: false,
|
||||||
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
enscriptionDate: "asc",
|
enscriptionDate: "asc",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user