added Badges to Dashboard, warning for full events

This commit is contained in:
PxlLoewe
2025-03-10 23:29:02 -07:00
parent c01e618a56
commit 92dff8f3c9
20 changed files with 141 additions and 31 deletions

View File

@@ -80,7 +80,6 @@ const updateParticipantMoodleResults = async () => {
};
export const checkFinishedParticipants = async () => {
console.log("Checking finished participants");
const participantsPending = await prisma.participant.findMany({
where: {
completetionWorkflowFinished: false,
@@ -95,6 +94,9 @@ export const checkFinishedParticipants = async () => {
const completed = eventCompleted(p.Event, p);
if (!completed) return;
console.log(
`User ${p.User.firstname} ${p.User.lastname} - ${p.User.publicId} finished event ${p.Event.name}`,
);
handleParticipantFinished(p.Event, p, p.User);
});
};
@@ -103,7 +105,6 @@ CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
CronJob.from({
cronTime: "*/1 * * * *",
onTick: async () => {
console.log("Updating participant moodle results");
await updateParticipantMoodleResults();
await checkFinishedParticipants();
},

View File

@@ -12,16 +12,23 @@ export const handleParticipantFinished = async (
},
});
const badgedToAdd = event.finishedBadges.filter((badge) => {
return !user.badges.includes(badge);
});
const permissionsToAdd = event.finishedPermissions.filter((permission) => {
return !user.permissions.includes(permission);
});
await prisma.user.update({
where: {
id: user.id,
},
data: {
badges: {
push: event.finishedBadges,
push: badgedToAdd,
},
permissions: {
push: event.finishedPermissions,
push: permissionsToAdd,
},
},
});

View File

@@ -1,7 +1,6 @@
import { Badge } from "@repo/ui";
import { Award } from "lucide-react";
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
import { Badge } from "../../_components/Badge/Badge";
export const Badges = async () => {
const session = await getServerSession();
@@ -15,13 +14,11 @@ export const Badges = async () => {
<Award className="w-4 h-4" /> Verdiente Abzeichen
</span>
</h2>
{session.user.badges.map((badge) => {
return (
<div className="badge badge-primary badge-outline">
<Badge name={badge} />
</div>
);
})}
<div className="flex flex-wrap gap-2">
{session.user.badges.map((badge, i) => {
return <Badge name={badge} key={`${badge} - ${i}`} />;
})}
</div>
</div>
</div>
);

View File

@@ -41,6 +41,26 @@ export default async () => {
},
});
const appointments = await prisma.eventAppointment.findMany({
where: {
appointmentDate: {
gte: new Date(),
},
},
include: {
Participants: {
where: {
userId: user.id,
},
},
_count: {
select: {
Participants: true,
},
},
},
});
const filteredEvents = events.filter((event) => {
console.log;
if (eventCompleted(event, event.participants[0])) return false;
@@ -64,6 +84,7 @@ export default async () => {
{filteredEvents.map((event) => {
return (
<KursItem
appointments={appointments}
selectedAppointments={userAppointments}
user={user}
event={event}

View File

@@ -69,7 +69,13 @@ export const AppointmentModal = ({
})}
className="flex flex-col"
>
<DateInput control={appointmentForm.control} name="appointmentDate" />
<DateInput
control={appointmentForm.control}
name="appointmentDate"
showTimeInput
timeCaption="Uhrzeit"
showTimeCaption
/>
{/* <Input
form={appointmentForm}
type="datetime-local"

View File

@@ -81,7 +81,7 @@ export const Form = ({ event }: { event?: Event }) => {
onSubmit={form.handleSubmit(async (values) => {
setLoading(true);
const createdEvent = await upsertEvent(values, event?.id);
await upsertEvent(values, event?.id);
setLoading(false);
if (!event) redirect(`/admin/event`);
})}
@@ -201,7 +201,7 @@ export const Form = ({ event }: { event?: Event }) => {
header: "Datum",
accessorKey: "appointmentDate",
accessorFn: (date) =>
new Date(date.appointmentDate).toLocaleDateString(),
new Date(date.appointmentDate).toLocaleString(),
},
{
header: "Presenter",

View File

@@ -4,6 +4,7 @@ import { Participant, Prisma } from "@repo/db";
import { UseFormReturn } from "react-hook-form";
import { upsertParticipant } from "../../../events/actions";
import { RefObject } from "react";
import { deleteParticipant } from "../action";
interface ParticipantModalProps {
participantForm: UseFormReturn<Participant>;
@@ -59,6 +60,17 @@ export const ParticipantModal = ({
))}
</div>
<Button type="submit">Speichern</Button>
<Button
type="button"
onSubmit={() => false}
onClick={() => {
deleteParticipant(participantForm.watch("id"));
participantForm.reset();
ref.current?.close();
}}
>
Löschen
</Button>
</form>
</div>
</dialog>

View File

@@ -3,11 +3,13 @@ import { DrawingPinFilledIcon, EnterIcon } from "@radix-ui/react-icons";
import { Event, Participant, EventAppointment, User } from "@repo/db";
import ModalBtn from "./modalBtn";
import MDEditor from "@uiw/react-md-editor";
import { Badge } from "../../../_components/Badge/Badge";
export const KursItem = ({
user,
event,
selectedAppointments,
appointments,
}: {
user: User;
event: Event & {
@@ -15,6 +17,7 @@ export const KursItem = ({
participants: Participant[];
};
selectedAppointments: EventAppointment[];
appointments: EventAppointment[];
}) => {
return (
<div className="col-span-full">
@@ -45,7 +48,11 @@ export const KursItem = ({
/>
</div>
</div>
<div className="col-span-2">{event.finishedBadges}</div>
<div className="col-span-2">
{event.finishedBadges.map((b) => {
return <Badge name={b} key={b} />;
})}
</div>
</div>
<div className="card-actions flex justify-between items-center mt-5">
<div>
@@ -55,10 +62,13 @@ export const KursItem = ({
</p>
{!!event.requiredBadges.length && (
<div className="flex ml-6">
<b className="text-gray-600 text-left">Abzeichen:</b>
<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">
<div
className="badge badge-secondary badge-outline"
key={badge}
>
{badge}
</div>
))}
@@ -71,7 +81,7 @@ export const KursItem = ({
user={user}
event={event}
title={event.name}
dates={event.appointments}
dates={appointments}
participant={event.participants[0]}
modalId={`${event.name}_modal.${event.id}`}
/>

View File

@@ -8,20 +8,23 @@ import {
import { Event, EventAppointment, Participant, prisma, User } from "@repo/db";
import { cn } from "../../../../helper/cn";
import { inscribeToMoodleCourse, upsertParticipant } from "../actions";
import { Check, Clock10Icon, Cross, EyeIcon } from "lucide-react";
import {
Check,
Clock10Icon,
Cross,
EyeIcon,
MessageCircleWarning,
TriangleAlert,
} from "lucide-react";
import { useForm } from "react-hook-form";
import {
EventAppointmentOptionalDefaults,
EventAppointmentSchema,
ParticipantOptionalDefaults,
ParticipantOptionalDefaultsSchema,
ParticipantSchema,
} from "@repo/db/zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Select } from "../../../_components/ui/Select";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
import { JsonArray } from "../../../../../../packages/database/generated/client/runtime/library";
import { eventCompleted } from "@repo/ui";
interface ModalBtnProps {
@@ -85,6 +88,11 @@ const ModalBtn = ({
},
});
const selectedAppointment = selectedAppointments[0];
const selectedDate = dates.find(
(date) =>
date.id === selectAppointmentForm.watch("eventAppointmentId") ||
selectedAppointment?.id,
);
return (
<>
<button
@@ -124,7 +132,9 @@ const ModalBtn = ({
<Select
form={selectAppointmentForm as any}
options={dates.map((date) => ({
label: new Date(date.appointmentDate).toLocaleString(),
label: `${new Date(
date.appointmentDate,
).toLocaleString()} - (${(date as any)._count.Participants}/${event.maxParticipants})`,
value: date.id,
}))}
name="eventAppointmentId"
@@ -132,6 +142,7 @@ const ModalBtn = ({
className="min-w-[200px]"
/>
)}
{}
{!dates.length && (
<p className="text-center text-info">
Keine Termine verfügbar
@@ -145,6 +156,22 @@ const ModalBtn = ({
Du hast an dem Presenztermin teilgenommen
</p>
)}
{selectedDate &&
event.maxParticipants !== null &&
(selectedDate as any)._count.Participants >=
event.maxParticipants && (
<p
role="alert"
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"
/>
Dieser Termin ist ausgebucht, wahrscheinlich wirst du nicht
teilnehmen können
</p>
)}
{selectedAppointment && !participant?.appointmentCancelled && (
<>
<div className="flex items-center gap-2 justify-center">
@@ -182,6 +209,7 @@ const ModalBtn = ({
absagen
</button>
</div>
<p className="mt-3 text-center">
Bitte finde dich an diesem Termin in unserem Discord ein.
</p>

View File

@@ -30,6 +30,25 @@ export default async () => {
},
},
});
const appointments = await prisma.eventAppointment.findMany({
where: {
appointmentDate: {
gte: new Date(),
},
},
include: {
Participants: {
where: {
userId: user.id,
},
},
_count: {
select: {
Participants: true,
},
},
},
});
const userAppointments = await prisma.eventAppointment.findMany({
where: {
Participants: {
@@ -51,6 +70,7 @@ export default async () => {
{events.map((event) => {
return (
<KursItem
appointments={appointments}
selectedAppointments={userAppointments}
user={user}
event={event}

View File

@@ -6,6 +6,7 @@ import D1 from "./d-1.png";
import D2 from "./d-2.png";
import D3 from "./d-3.png";
import DAY1 from "./day-1-member.png";
import { cn } from "../../../helper/cn";
const BadgeImage = {
[BADGES.P1]: P1,
@@ -17,12 +18,20 @@ const BadgeImage = {
[BADGES.DAY1]: DAY1,
};
export const Badge = ({ name }: { name: BADGES }) => {
export const Badge = ({
name,
className,
}: {
name: BADGES;
className?: string;
}) => {
const image = BadgeImage[name];
return (
<span className="badge">
<img src={image} />
<span
className={cn("badge badge-secondary badge-outline h-fit p-1", className)}
>
<img src={image.src} alt={name} width={100} />
</span>
);
};

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -27,7 +27,7 @@ model User {
moodleId Int? @map(name: "moodle_id")
emailVerified DateTime? @map(name: "email_verified")
image String?
badges BADGES[] @default([])
badges BADGES[] @unique @default([])
permissions PERMISSION[] @default([])
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")

View File

@@ -1,2 +1 @@
export * from "./Badge/Badge";
export * from "./helper/event";