removed event-chronjobs, used Events in hub-app insteand, added admin Btn to set Discord-User and run Event-completed-workflow. Fixed Bug of wrong participants-count in Event-Modal
This commit is contained in:
@@ -14,53 +14,32 @@ const page = async () => {
|
||||
if (!user) return null;
|
||||
|
||||
const events = await prisma.event.findMany({
|
||||
where: {
|
||||
type: "OBLIGATED_COURSE",
|
||||
},
|
||||
include: {
|
||||
appointments: {
|
||||
where: {
|
||||
appointmentDate: {
|
||||
gte: new Date(),
|
||||
},
|
||||
},
|
||||
},
|
||||
participants: {
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const userAppointments = await prisma.eventAppointment.findMany({
|
||||
where: {
|
||||
Participants: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const appointments = await prisma.eventAppointment.findMany({
|
||||
where: {
|
||||
appointmentDate: {
|
||||
gte: new Date(),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Participants: {
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
Participants: true,
|
||||
appointments: {
|
||||
include: {
|
||||
Participants: {
|
||||
where: {
|
||||
appointmentCancelled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const filteredEvents = events.filter((event) => {
|
||||
if (eventCompleted(event, event.participants[0])) return false;
|
||||
const userParticipant = event.participants.find(
|
||||
(participant) => participant.userId === user.id,
|
||||
);
|
||||
if (eventCompleted(event, userParticipant)) return false;
|
||||
if (event.type === "OBLIGATED_COURSE" && !eventCompleted(event, event.participants[0]))
|
||||
return true;
|
||||
return false;
|
||||
@@ -78,8 +57,10 @@ const page = async () => {
|
||||
{filteredEvents.map((event) => {
|
||||
return (
|
||||
<KursItem
|
||||
appointments={appointments}
|
||||
selectedAppointments={userAppointments}
|
||||
appointments={event.appointments}
|
||||
selectedAppointments={event.appointments.filter((a) =>
|
||||
a.Participants.find((p) => p.userId == user.id),
|
||||
)}
|
||||
user={user}
|
||||
event={event}
|
||||
key={event.id}
|
||||
@@ -9,6 +9,7 @@ import { Button } from "../../../../_components/ui/Button";
|
||||
import { DateInput } from "../../../../_components/ui/DateInput";
|
||||
import { upsertParticipant } from "../../../events/actions";
|
||||
import { deleteAppoinement, upsertAppointment } from "../action";
|
||||
import { handleParticipantFinished } from "../../../../../helper/events";
|
||||
|
||||
interface AppointmentModalProps {
|
||||
event?: Event;
|
||||
@@ -127,6 +128,9 @@ export const AppointmentModal = ({
|
||||
attended: true,
|
||||
appointmentCancelled: false,
|
||||
});
|
||||
if (!event.finisherMoodleCourseId) {
|
||||
await handleParticipantFinished(row.original.id.toString());
|
||||
}
|
||||
participantTableRef.current?.refresh();
|
||||
}}
|
||||
className="btn btn-outline btn-info btn-sm"
|
||||
|
||||
@@ -5,6 +5,9 @@ import { UseFormReturn } from "react-hook-form";
|
||||
import { upsertParticipant } from "../../../events/actions";
|
||||
import { RefObject } from "react";
|
||||
import { deleteParticipant } from "../action";
|
||||
import { handleParticipantFinished } from "../../../../../helper/events";
|
||||
import { AxiosError } from "axios";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface ParticipantModalProps {
|
||||
participantForm: UseFormReturn<Participant>;
|
||||
@@ -30,6 +33,27 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
|
||||
})}
|
||||
className="space-y-1"
|
||||
>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (!participantForm.watch("id")) return;
|
||||
|
||||
const participant = participantForm.getValues();
|
||||
await handleParticipantFinished(participant.id.toString()).catch((e) => {
|
||||
const error = e as AxiosError;
|
||||
if (
|
||||
error.response?.data &&
|
||||
typeof error.response.data === "object" &&
|
||||
"error" in error.response.data
|
||||
) {
|
||||
toast.error(`Fehler: ${error.response.data.error}`);
|
||||
} else {
|
||||
toast.error("Unbekannter Fehler beim ausführen des Workflows");
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Event-abgeschlossen Workflow ausführen
|
||||
</Button>
|
||||
<Switch form={participantForm} name="attended" label="Termin Teilgenommen" />
|
||||
<Switch form={participantForm} name="appointmentCancelled" label="Termin abgesagt" />
|
||||
<Switch
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
BADGES,
|
||||
ConnectedAircraft,
|
||||
ConnectedDispatcher,
|
||||
DiscordAccount,
|
||||
PERMISSION,
|
||||
Report,
|
||||
Station,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
LockOpen1Icon,
|
||||
HobbyKnifeIcon,
|
||||
ExclamationTriangleIcon,
|
||||
DiscordLogoIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { Button } from "../../../../../_components/ui/Button";
|
||||
import { Select } from "../../../../../_components/ui/Select";
|
||||
@@ -34,6 +36,7 @@ import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Error } from "_components/Error";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { setStandardName } from "../../../../../../helper/discord";
|
||||
|
||||
interface ProfileFormProps {
|
||||
user: User;
|
||||
@@ -362,6 +365,7 @@ export const UserReports = ({ user }: { user: User }) => {
|
||||
};
|
||||
|
||||
interface AdminFormProps {
|
||||
discordAccount?: DiscordAccount;
|
||||
user: User;
|
||||
dispoTime: {
|
||||
hours: number;
|
||||
@@ -380,7 +384,13 @@ interface AdminFormProps {
|
||||
};
|
||||
}
|
||||
|
||||
export const AdminForm = ({ user, dispoTime, pilotTime, reports }: AdminFormProps) => {
|
||||
export const AdminForm = ({
|
||||
user,
|
||||
dispoTime,
|
||||
pilotTime,
|
||||
reports,
|
||||
discordAccount,
|
||||
}: AdminFormProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
@@ -444,6 +454,27 @@ export const AdminForm = ({ user, dispoTime, pilotTime, reports }: AdminFormProp
|
||||
<HobbyKnifeIcon /> User Entperren
|
||||
</Button>
|
||||
)}
|
||||
{discordAccount && (
|
||||
<div className="tooltip flex-1" data-tip={`Name: ${discordAccount.username}`}>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await setStandardName({
|
||||
memberId: discordAccount.discordId,
|
||||
userId: user.id,
|
||||
});
|
||||
toast.success("Standard Name wurde gesetzt!", {
|
||||
style: {
|
||||
background: "var(--color-base-100)",
|
||||
color: "var(--color-base-content)",
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="btn-sm w-full btn-outline btn-info"
|
||||
>
|
||||
<DiscordLogoIcon /> Name und Berechtigungen setzen
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="card-title">
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { PersonIcon } from "@radix-ui/react-icons";
|
||||
import { prisma, User } from "@repo/db";
|
||||
import { prisma } from "@repo/db";
|
||||
import { AdminForm, ConnectionHistory, ProfileForm, UserReports } from "./_components/forms";
|
||||
import { Error } from "../../../../_components/Error";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
|
||||
const user: User | null = await prisma.user.findUnique({
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
include: {
|
||||
discordAccounts: true,
|
||||
},
|
||||
});
|
||||
|
||||
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
||||
@@ -88,7 +91,6 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||
open: totalReportsOpen,
|
||||
total60Days: totalReports60Days,
|
||||
};
|
||||
|
||||
if (!user) return <Error statusCode={404} title="User not found" />;
|
||||
return (
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
@@ -102,7 +104,13 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||
<ProfileForm user={user} />
|
||||
</div>
|
||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
||||
<AdminForm user={user} dispoTime={dispoTime} pilotTime={pilotTime} reports={reports} />
|
||||
<AdminForm
|
||||
user={user}
|
||||
dispoTime={dispoTime}
|
||||
pilotTime={pilotTime}
|
||||
reports={reports}
|
||||
discordAccount={user.discordAccounts[0]}
|
||||
/>
|
||||
</div>
|
||||
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6">
|
||||
<UserReports user={user} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { DrawingPinFilledIcon, EnterIcon } from "@radix-ui/react-icons";
|
||||
import { DrawingPinFilledIcon } from "@radix-ui/react-icons";
|
||||
import { Event, Participant, EventAppointment, User } from "@repo/db";
|
||||
import ModalBtn from "./modalBtn";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
@@ -26,14 +26,10 @@ export const KursItem = ({
|
||||
<h2 className="card-title">{event.name}</h2>
|
||||
<div className="absolute top-0 right-0 m-4">
|
||||
{event.type === "COURSE" && (
|
||||
<span className="badge badge-info badge-outline">
|
||||
Zusatzqualifikation
|
||||
</span>
|
||||
<span className="badge badge-info badge-outline">Zusatzqualifikation</span>
|
||||
)}
|
||||
{event.type === "OBLIGATED_COURSE" && (
|
||||
<span className="badge badge-secondary badge-outline">
|
||||
Verpflichtend
|
||||
</span>
|
||||
<span className="badge badge-secondary badge-outline">Verpflichtend</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
@@ -65,10 +61,7 @@ export const KursItem = ({
|
||||
<b className="text-gray-600 text-left mr-2">Abzeichen:</b>
|
||||
<div className="flex gap-2">
|
||||
{event.requiredBadges.map((badge) => (
|
||||
<div
|
||||
className="badge badge-secondary badge-outline"
|
||||
key={badge}
|
||||
>
|
||||
<div className="badge badge-secondary badge-outline" key={badge}>
|
||||
{badge}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Select } from "../../../_components/ui/Select";
|
||||
import toast from "react-hot-toast";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { eventCompleted } from "../../../../helper/events";
|
||||
import { eventCompleted, handleParticipantEnrolled } from "../../../../helper/events";
|
||||
|
||||
interface ModalBtnProps {
|
||||
title: string;
|
||||
@@ -123,13 +123,12 @@ const ModalBtn = ({
|
||||
<div className="flex items-center gap-2 justify-center">
|
||||
<CalendarIcon />
|
||||
{!!dates.length && (
|
||||
// TODO: Prevent users from selecting an appointment that is full
|
||||
<Select
|
||||
form={selectAppointmentForm as any}
|
||||
form={selectAppointmentForm}
|
||||
options={dates.map((date) => ({
|
||||
label: `${new Date(
|
||||
date.appointmentDate,
|
||||
).toLocaleString()} - (${(date as any)._count.Participants}/${event.maxParticipants})`,
|
||||
).toLocaleString()} - (${(date as any).Participants.length}/${event.maxParticipants})`,
|
||||
value: date.id,
|
||||
}))}
|
||||
name="eventAppointmentId"
|
||||
@@ -226,12 +225,14 @@ const ModalBtn = ({
|
||||
const data = selectAppointmentForm.getValues();
|
||||
if (!data.eventAppointmentId) return;
|
||||
|
||||
await upsertParticipant({
|
||||
const participant = await upsertParticipant({
|
||||
...data,
|
||||
enscriptionDate: new Date(),
|
||||
statusLog: data.statusLog?.filter((log) => log !== null),
|
||||
appointmentCancelled: false,
|
||||
});
|
||||
await handleParticipantEnrolled(participant.id.toString());
|
||||
|
||||
router.refresh();
|
||||
closeModal();
|
||||
}}
|
||||
|
||||
@@ -33,7 +33,11 @@ export default async function RootLayout({
|
||||
>
|
||||
<div className="hero-overlay bg-opacity-30"></div>
|
||||
<div>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
<Toaster
|
||||
containerStyle={{ zIndex: "999999999" }}
|
||||
position="top-center"
|
||||
reverseOrder={false}
|
||||
/>
|
||||
</div>
|
||||
{/* Card */}
|
||||
<div className="hero-content text-neutral-content text-center w-full max-w-full h-full m-10">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Events from "./_components/Events";
|
||||
import Events from "./_components/FeaturedEvents";
|
||||
import { Stats } from "./_components/Stats";
|
||||
import { Badges } from "./_components/Badges";
|
||||
import { RecentFlights } from "(app)/_components/RecentFlights";
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import {
|
||||
ButtonHTMLAttributes,
|
||||
DetailedHTMLProps,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { ButtonHTMLAttributes, DetailedHTMLProps, useEffect, useState } from "react";
|
||||
import { cn } from "../../../helper/cn";
|
||||
|
||||
export const Button = ({
|
||||
isLoading,
|
||||
...props
|
||||
}: DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & {
|
||||
}: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> & {
|
||||
isLoading?: boolean;
|
||||
}) => {
|
||||
const [isLoadingState, setIsLoadingState] = useState(isLoading);
|
||||
@@ -34,9 +26,7 @@ export const Button = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isLoadingState && (
|
||||
<span className="loading loading-spinner loading-sm"></span>
|
||||
)}
|
||||
{isLoadingState && <span className="loading loading-spinner loading-sm"></span>}
|
||||
{props.children as any}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import axios, { AxiosError } from "axios";
|
||||
import axios from "axios";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { DISCORD_ROLES, DiscordAccount, getPublicUser, prisma, PrismaClient } from "@repo/db";
|
||||
import { DiscordAccount, prisma } from "@repo/db";
|
||||
import { getServerSession } from "../auth/[...nextauth]/auth";
|
||||
import { addRolesToMember, removeRolesFromMember, renameMember } from "../../../helper/discord";
|
||||
import { setStandardName } from "../../../helper/discord";
|
||||
|
||||
export const GET = async (req: NextRequest) => {
|
||||
const session = await getServerSession();
|
||||
const code = req.nextUrl.searchParams.get("code");
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.redirect(`${process.env.NEXTAUTH_URL}/login`);
|
||||
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_HUB_URL}/login`);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -72,17 +72,10 @@ export const GET = async (req: NextRequest) => {
|
||||
where: { id: session.user.id },
|
||||
});
|
||||
if (user) {
|
||||
await renameMember(discordUser.id, `${getPublicUser(user).fullName} - ${user?.publicId}`);
|
||||
}
|
||||
if (user?.permissions.includes("PILOT")) {
|
||||
await addRolesToMember(discordUser.id, [DISCORD_ROLES.PILOT]);
|
||||
} else {
|
||||
await removeRolesFromMember(discordUser.id, [DISCORD_ROLES.PILOT]);
|
||||
}
|
||||
if (user?.permissions.includes("DISPO")) {
|
||||
await addRolesToMember(discordUser.id, [DISCORD_ROLES.ONLINE_DISPATCHER]);
|
||||
} else {
|
||||
await removeRolesFromMember(discordUser.id, [DISCORD_ROLES.PILOT]);
|
||||
await setStandardName({
|
||||
memberId: discordUser.id,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_HUB_URL}/settings`);
|
||||
|
||||
Reference in New Issue
Block a user