Finished Hub ESLINT rule enforcement
This commit is contained in:
@@ -6,8 +6,8 @@ export const addMessage = async (notam: Prisma.ConfigCreateInput) => {
|
|||||||
await prisma.config.create({
|
await prisma.config.create({
|
||||||
data: notam,
|
data: notam,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
throw new Error("Failed to add message");
|
throw new Error(`Failed to add message: ${e instanceof Error ? e.message : "Unknown error"}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export const disableMessage = async () => {
|
|||||||
await prisma.config.create({
|
await prisma.config.create({
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
throw new Error("Failed to disable message");
|
throw new Error(`Failed to add message: ${e instanceof Error ? e.message : "Unknown error"}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Check, MessageSquareWarning, Settings } from "lucide-react";
|
import { Check, Settings } from "lucide-react";
|
||||||
import { MessageForm } from "./_components/MessageForm";
|
import { MessageForm } from "./_components/MessageForm";
|
||||||
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Event, Participant } from "@repo/db";
|
import { Event, Participant } from "@repo/db";
|
||||||
import { EventAppointmentOptionalDefaults } from "@repo/db/zod";
|
import { EventAppointmentOptionalDefaults, InputJsonValueType } from "@repo/db/zod";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { RefObject, useRef } from "react";
|
import { RefObject, useRef } from "react";
|
||||||
@@ -45,7 +45,7 @@ export const AppointmentModal = ({
|
|||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<h3 className="font-bold text-lg">Termin {appointmentForm.watch("id")}</h3>
|
|
||||||
<form
|
<form
|
||||||
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
@@ -55,13 +55,13 @@ export const AppointmentModal = ({
|
|||||||
})}
|
})}
|
||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
>
|
>
|
||||||
|
<div className="flex justify-between mr-7">
|
||||||
|
<h3 className="font-bold text-lg">Termin {appointmentForm.watch("id")}</h3>
|
||||||
<DateInput
|
<DateInput
|
||||||
control={appointmentForm.control}
|
value={new Date(appointmentForm.watch("appointmentDate") || Date.now())}
|
||||||
name="appointmentDate"
|
onChange={(date) => appointmentForm.setValue("appointmentDate", date)}
|
||||||
showTimeInput
|
|
||||||
timeCaption="Uhrzeit"
|
|
||||||
showTimeCaption
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
hide={appointmentForm.watch("id") === undefined}
|
hide={appointmentForm.watch("id") === undefined}
|
||||||
@@ -150,7 +150,7 @@ export const AppointmentModal = ({
|
|||||||
attended: false,
|
attended: false,
|
||||||
appointmentCancelled: true,
|
appointmentCancelled: true,
|
||||||
statusLog: [
|
statusLog: [
|
||||||
...(row.original.statusLog as any),
|
...(row.original.statusLog as InputJsonValueType[]),
|
||||||
{
|
{
|
||||||
event: "Gefehlt an Event",
|
event: "Gefehlt an Event",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -169,7 +169,7 @@ export const AppointmentModal = ({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as ColumnDef<Participant, any>[]
|
] as ColumnDef<Participant>[]
|
||||||
}
|
}
|
||||||
prismaModel={"participant"}
|
prismaModel={"participant"}
|
||||||
filter={{
|
filter={{
|
||||||
|
|||||||
@@ -59,9 +59,7 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
|
|||||||
if (!participantForm.watch("id")) return;
|
if (!participantForm.watch("id")) return;
|
||||||
|
|
||||||
const participant = participantForm.getValues();
|
const participant = participantForm.getValues();
|
||||||
await handleParticipantFinished(participant.id.toString()).catch((e) => {
|
await handleParticipantFinished(participant.id.toString()).catch(() => {});
|
||||||
const error = e as AxiosError;
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.success("Workflow erfolgreich ausgeführt");
|
toast.success("Workflow erfolgreich ausgeführt");
|
||||||
router.refresh();
|
router.refresh();
|
||||||
@@ -119,10 +117,10 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h3 className="text-xl">Verlauf</h3>
|
<h3 className="text-xl">Verlauf</h3>
|
||||||
{(participantForm.watch("statusLog") as unknown as ParticipantLog[])?.map((s) => (
|
{(participantForm.watch("statusLog") as unknown as ParticipantLog[])?.map((s) => (
|
||||||
<div className="flex justify-between" key={(s as any).timestamp}>
|
<div className="flex justify-between" key={s.timestamp.toString()}>
|
||||||
<p>{s.event}</p>
|
<p>{s.event}</p>
|
||||||
<p>{s.user}</p>
|
<p>{s.user}</p>
|
||||||
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
|
<p>{new Date(s.timestamp).toLocaleString()}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { prisma, Prisma, Event, Participant, EventAppointment } from "@repo/db";
|
import { prisma, Prisma, Event, Participant } from "@repo/db";
|
||||||
|
|
||||||
export const upsertEvent = async (
|
export const upsertEvent = async (event: Prisma.EventCreateInput, id?: Event["id"]) => {
|
||||||
event: Prisma.EventCreateInput,
|
|
||||||
id?: Event["id"],
|
|
||||||
) => {
|
|
||||||
const newEvent = id
|
const newEvent = id
|
||||||
? await prisma.event.update({
|
? await prisma.event.update({
|
||||||
where: { id: id },
|
where: { id: id },
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { prisma } from "@repo/db";
|
|
||||||
import { Form } from "../_components/Form";
|
import { Form } from "../_components/Form";
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { PartyPopperIcon } from "lucide-react";
|
import { PartyPopperIcon } from "lucide-react";
|
||||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { Event } from "@repo/db";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
showEditButton
|
|
||||||
prismaModel="event"
|
prismaModel="event"
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
header: "Name",
|
header: "Name",
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
@@ -17,7 +19,18 @@ export default function Page() {
|
|||||||
header: "Versteckt",
|
header: "Versteckt",
|
||||||
accessorKey: "hidden",
|
accessorKey: "hidden",
|
||||||
},
|
},
|
||||||
]}
|
{
|
||||||
|
header: "Aktionen",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Link href={`/admin/event/${row.original.id}`}>
|
||||||
|
<button className="btn btn-sm">Edit</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as ColumnDef<Event>[]
|
||||||
|
}
|
||||||
leftOfSearch={
|
leftOfSearch={
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<PartyPopperIcon className="w-5 h-5" /> Events
|
<PartyPopperIcon className="w-5 h-5" /> Events
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { KeywordOptionalDefaultsSchema } from "@repo/db/zod";
|
import { KeywordOptionalDefaultsSchema } from "@repo/db/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
|
||||||
import { KEYWORD_CATEGORY, Keyword } from "@repo/db";
|
import { KEYWORD_CATEGORY, Keyword } from "@repo/db";
|
||||||
import { FileText } from "lucide-react";
|
import { FileText } from "lucide-react";
|
||||||
import { Input } from "../../../../_components/ui/Input";
|
import { Input } from "../../../../_components/ui/Input";
|
||||||
@@ -24,7 +23,7 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
|
|||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(async (values) => {
|
onSubmit={form.handleSubmit(async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const createdKeyword = await upsertKeyword(values, keyword?.id);
|
await upsertKeyword(values, keyword?.id);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (!keyword) redirect(`/admin/keyword`);
|
if (!keyword) redirect(`/admin/keyword`);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export default () => {
|
|||||||
<>
|
<>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
initialOrderBy={[{ id: "category", desc: true }]}
|
initialOrderBy={[{ id: "category", desc: true }]}
|
||||||
showEditButton
|
|
||||||
prismaModel="keyword"
|
prismaModel="keyword"
|
||||||
searchFields={["name", "abreviation", "description"]}
|
searchFields={["name", "abreviation", "description"]}
|
||||||
columns={
|
columns={
|
||||||
@@ -26,6 +25,16 @@ export default () => {
|
|||||||
header: "Name",
|
header: "Name",
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: "Aktionen",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Link href={`/admin/keyword/${row.original.id}`}>
|
||||||
|
<button className="btn btn-sm">bearbeiten</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
] as ColumnDef<Keyword>[]
|
] as ColumnDef<Keyword>[]
|
||||||
}
|
}
|
||||||
leftOfSearch={
|
leftOfSearch={
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { StationOptionalDefaultsSchema } from "@repo/db/zod";
|
import { StationOptionalDefaultsSchema } from "@repo/db/zod";
|
||||||
import { set, useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
|
||||||
import { BosUse, Country, Station } from "@repo/db";
|
import { BosUse, Country, Station } from "@repo/db";
|
||||||
import { FileText, LocateIcon, PlaneIcon } from "lucide-react";
|
import { FileText, LocateIcon, PlaneIcon } from "lucide-react";
|
||||||
import { Input } from "../../../../_components/ui/Input";
|
import { Input } from "../../../../_components/ui/Input";
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
|
"use client";
|
||||||
import { DatabaseBackupIcon } from "lucide-react";
|
import { DatabaseBackupIcon } from "lucide-react";
|
||||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { Station } from "@repo/db";
|
||||||
|
|
||||||
const page = () => {
|
const page = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
showEditButton
|
|
||||||
prismaModel="station"
|
prismaModel="station"
|
||||||
searchFields={["bosCallsign", "bosUse", "country", "operator"]}
|
searchFields={["bosCallsign", "bosUse", "country", "operator"]}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
header: "BOS Name",
|
header: "BOS Name",
|
||||||
accessorKey: "bosCallsign",
|
accessorKey: "bosCallsign",
|
||||||
@@ -26,7 +29,18 @@ const page = () => {
|
|||||||
header: "operator",
|
header: "operator",
|
||||||
accessorKey: "operator",
|
accessorKey: "operator",
|
||||||
},
|
},
|
||||||
]}
|
{
|
||||||
|
header: "Aktionen",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Link href={`/admin/event/${row.original.id}`}>
|
||||||
|
<button className="btn btn-sm">Edit</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as ColumnDef<Station>[]
|
||||||
|
}
|
||||||
leftOfSearch={
|
leftOfSearch={
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<DatabaseBackupIcon className="w-5 h-5" /> Stationen
|
<DatabaseBackupIcon className="w-5 h-5" /> Stationen
|
||||||
@@ -35,9 +49,7 @@ const page = () => {
|
|||||||
rightOfSearch={
|
rightOfSearch={
|
||||||
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
|
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
|
||||||
<Link href={"/admin/station/new"}>
|
<Link href={"/admin/station/new"}>
|
||||||
<button className="btn btn-sm btn-outline btn-primary">
|
<button className="btn btn-sm btn-outline btn-primary">Erstellen</button>
|
||||||
Erstellen
|
|
||||||
</button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,26 +39,23 @@ import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
|||||||
import { cn } from "@repo/shared-components";
|
import { cn } from "@repo/shared-components";
|
||||||
import {
|
import {
|
||||||
ChartBarBigIcon,
|
ChartBarBigIcon,
|
||||||
Check,
|
|
||||||
Eye,
|
Eye,
|
||||||
LockKeyhole,
|
LockKeyhole,
|
||||||
PlaneIcon,
|
PlaneIcon,
|
||||||
RedoDot,
|
|
||||||
ShieldUser,
|
ShieldUser,
|
||||||
Timer,
|
Timer,
|
||||||
Trash2,
|
Trash2,
|
||||||
Users,
|
Users,
|
||||||
X,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { Error } from "_components/Error";
|
import { Error } from "_components/Error";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { setStandardName } from "../../../../../../helper/discord";
|
import { setStandardName } from "(app)/../../helper/discord";
|
||||||
import { penaltyColumns } from "(app)/admin/penalty/columns";
|
import { penaltyColumns } from "(app)/admin/penalty/columns";
|
||||||
import { addPenalty, editPenaltys } from "(app)/admin/penalty/actions";
|
import { addPenalty, editPenaltys } from "(app)/admin/penalty/actions";
|
||||||
import { reportColumns } from "(app)/admin/report/columns";
|
import { reportColumns } from "(app)/admin/report/columns";
|
||||||
import { sendMail, sendMailByTemplate } from "../../../../../../helper/mail";
|
import { sendMailByTemplate } from "(app)/../../helper/mail";
|
||||||
|
|
||||||
interface ProfileFormProps {
|
interface ProfileFormProps {
|
||||||
user: User;
|
user: User;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
"use client";
|
||||||
import { User2 } from "lucide-react";
|
import { User2 } from "lucide-react";
|
||||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
import Link from "next/link";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { User } from "@repo/db";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
const AdminUserPage = async () => {
|
const AdminUserPage = () => {
|
||||||
const session = await getServerSession();
|
const { data: session } = useSession();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
showEditButton
|
|
||||||
prismaModel="user"
|
prismaModel="user"
|
||||||
searchFields={["publicId", "firstname", "lastname", "email"]}
|
searchFields={["publicId", "firstname", "lastname", "email"]}
|
||||||
initialOrderBy={[
|
initialOrderBy={[
|
||||||
@@ -16,7 +19,8 @@ const AdminUserPage = async () => {
|
|||||||
desc: false,
|
desc: false,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
columns={[
|
columns={
|
||||||
|
[
|
||||||
{
|
{
|
||||||
header: "ID",
|
header: "ID",
|
||||||
accessorKey: "publicId",
|
accessorKey: "publicId",
|
||||||
@@ -37,7 +41,18 @@ const AdminUserPage = async () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
]}
|
{
|
||||||
|
header: "Aktionen",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Link href={`/admin/event/${row.original.id}`}>
|
||||||
|
<button className="btn btn-sm">Anzeigen</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as ColumnDef<User>[]
|
||||||
|
} // Define the columns for the user table
|
||||||
leftOfSearch={
|
leftOfSearch={
|
||||||
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
||||||
<User2 className="w-5 h-5" /> Benutzer
|
<User2 className="w-5 h-5" /> Benutzer
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ export const EventCard = ({
|
|||||||
Participants: Participant[];
|
Participants: Participant[];
|
||||||
};
|
};
|
||||||
selectedAppointments: EventAppointment[];
|
selectedAppointments: EventAppointment[];
|
||||||
appointments: EventAppointment[];
|
appointments: (EventAppointment & {
|
||||||
|
Participants: { userId: string }[];
|
||||||
|
})[];
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ import {
|
|||||||
TriangleAlert,
|
TriangleAlert,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { ParticipantOptionalDefaults, ParticipantOptionalDefaultsSchema } from "@repo/db/zod";
|
import {
|
||||||
|
InputJsonValueType,
|
||||||
|
ParticipantOptionalDefaults,
|
||||||
|
ParticipantOptionalDefaultsSchema,
|
||||||
|
} from "@repo/db/zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Select } from "../../../_components/ui/Select";
|
import { Select } from "../../../_components/ui/Select";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -24,11 +28,14 @@ import { handleParticipantEnrolled } from "../../../../helper/events";
|
|||||||
import { eventCompleted } from "@repo/shared-components";
|
import { eventCompleted } from "@repo/shared-components";
|
||||||
import MDEditor from "@uiw/react-md-editor";
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { formatDate } from "date-fns";
|
||||||
|
|
||||||
interface ModalBtnProps {
|
interface ModalBtnProps {
|
||||||
title: string;
|
title: string;
|
||||||
event: Event;
|
event: Event;
|
||||||
dates: EventAppointment[];
|
dates: (EventAppointment & {
|
||||||
|
Participants: { userId: string }[];
|
||||||
|
})[];
|
||||||
selectedAppointments: EventAppointment[];
|
selectedAppointments: EventAppointment[];
|
||||||
participant?: Participant;
|
participant?: Participant;
|
||||||
user: User;
|
user: User;
|
||||||
@@ -88,14 +95,16 @@ const ModalBtn = ({
|
|||||||
(date) =>
|
(date) =>
|
||||||
date.id === selectAppointmentForm.watch("eventAppointmentId") || selectedAppointment?.id,
|
date.id === selectAppointmentForm.watch("eventAppointmentId") || selectedAppointment?.id,
|
||||||
);
|
);
|
||||||
const ownIndexInParticipantList = (selectedDate as any)?.Participants?.findIndex(
|
const ownIndexInParticipantList = selectedDate?.Participants?.findIndex(
|
||||||
(p: Participant) => p.userId === user.id,
|
(p) => p.userId === user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const ownPlaceInParticipantList =
|
const ownPlaceInParticipantList =
|
||||||
ownIndexInParticipantList === -1
|
typeof ownIndexInParticipantList === "number"
|
||||||
? (selectedDate as any)?.Participants?.length + 1
|
? ownIndexInParticipantList === -1
|
||||||
: ownIndexInParticipantList + 1;
|
? (selectedDate?.Participants?.length ?? 0) + 1
|
||||||
|
: ownIndexInParticipantList + 1
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const missingRequirements =
|
const missingRequirements =
|
||||||
event.requiredBadges?.length > 0 &&
|
event.requiredBadges?.length > 0 &&
|
||||||
@@ -167,13 +176,7 @@ const ModalBtn = ({
|
|||||||
<Select
|
<Select
|
||||||
form={selectAppointmentForm}
|
form={selectAppointmentForm}
|
||||||
options={dates.map((date) => ({
|
options={dates.map((date) => ({
|
||||||
label: `${new Date(date.appointmentDate).toLocaleString("de-DE", {
|
label: `${formatDate(date.appointmentDate, "dd.MM.yyyy HH:mm")} - (${date.Participants.length}/${event.maxParticipants})`,
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})} - (${(date as any).Participants.length}/${event.maxParticipants})`,
|
|
||||||
value: date.id,
|
value: date.id,
|
||||||
}))}
|
}))}
|
||||||
name="eventAppointmentId"
|
name="eventAppointmentId"
|
||||||
@@ -296,7 +299,7 @@ const ModalBtn = ({
|
|||||||
userId: participant!.userId,
|
userId: participant!.userId,
|
||||||
appointmentCancelled: true,
|
appointmentCancelled: true,
|
||||||
statusLog: [
|
statusLog: [
|
||||||
...(participant?.statusLog as any),
|
...(participant?.statusLog as unknown as InputJsonValueType[]),
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
appointmentId: selectedAppointment.id,
|
appointmentId: selectedAppointment.id,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Download } from "lucide-react";
|
|
||||||
import Image, { StaticImageData } from "next/image";
|
import Image, { StaticImageData } from "next/image";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Service } from "../page";
|
|||||||
import { generateToken } from "./action";
|
import { generateToken } from "./action";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useErrorBoundary } from "react-error-boundary";
|
import { useErrorBoundary } from "react-error-boundary";
|
||||||
import { se } from "date-fns/locale";
|
|
||||||
import { PERMISSION } from "@repo/db";
|
import { PERMISSION } from "@repo/db";
|
||||||
|
|
||||||
export const Authorize = ({ service }: { service: Service }) => {
|
export const Authorize = ({ service }: { service: Service }) => {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ export const CustomErrorBoundary = ({ children }: { children?: React.ReactNode }
|
|||||||
let errorTest;
|
let errorTest;
|
||||||
let errorCode = 500;
|
let errorCode = 500;
|
||||||
if ("statusCode" in error) {
|
if ("statusCode" in error) {
|
||||||
errorCode = (error as any).statusCode;
|
errorCode = error.statusCode;
|
||||||
}
|
}
|
||||||
if ("message" in error || error instanceof Error) {
|
if ("message" in error || error instanceof Error) {
|
||||||
errorTest = (error as any).message;
|
errorTest = error.message;
|
||||||
} else if (typeof error === "string") {
|
} else if (typeof error === "string") {
|
||||||
errorTest = error;
|
errorTest = error;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useState, useCallback, Ref, useImperativeHandle } from "react";
|
import { useState, Ref, useImperativeHandle } from "react";
|
||||||
import SortableTable, { Pagination, SortableTableProps } from "./Table";
|
import SortableTable, { Pagination, SortableTableProps } from "./Table";
|
||||||
import { PrismaClient } from "@repo/db";
|
import { PrismaClient } from "@repo/db";
|
||||||
import { getData } from "./pagiantedTableActions";
|
import { getData } from "./pagiantedTableActions";
|
||||||
|
import { useDebounce } from "@repo/shared-components";
|
||||||
|
|
||||||
export interface PaginatedTableRef {
|
export interface PaginatedTableRef {
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
@@ -10,9 +11,8 @@ export interface PaginatedTableRef {
|
|||||||
|
|
||||||
interface PaginatedTableProps<TData> extends Omit<SortableTableProps<TData>, "data"> {
|
interface PaginatedTableProps<TData> extends Omit<SortableTableProps<TData>, "data"> {
|
||||||
prismaModel: keyof PrismaClient;
|
prismaModel: keyof PrismaClient;
|
||||||
filter?: Record<string, any>;
|
filter?: Record<string, unknown>;
|
||||||
rowsPerPage?: number;
|
rowsPerPage?: number;
|
||||||
showEditButton?: boolean;
|
|
||||||
searchFields?: string[];
|
searchFields?: string[];
|
||||||
include?: Record<string, boolean>;
|
include?: Record<string, boolean>;
|
||||||
strictQuery?: boolean;
|
strictQuery?: boolean;
|
||||||
@@ -26,7 +26,6 @@ interface PaginatedTableProps<TData> extends Omit<SortableTableProps<TData>, "da
|
|||||||
export function PaginatedTable<TData>({
|
export function PaginatedTable<TData>({
|
||||||
prismaModel,
|
prismaModel,
|
||||||
rowsPerPage = 10,
|
rowsPerPage = 10,
|
||||||
showEditButton = false,
|
|
||||||
searchFields = [],
|
searchFields = [],
|
||||||
filter,
|
filter,
|
||||||
include,
|
include,
|
||||||
@@ -42,7 +41,6 @@ export function PaginatedTable<TData>({
|
|||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
|
|
||||||
const [orderBy, setOrderBy] = useState<Record<string, "asc" | "desc">>(
|
const [orderBy, setOrderBy] = useState<Record<string, "asc" | "desc">>(
|
||||||
restProps.initialOrderBy
|
restProps.initialOrderBy
|
||||||
? restProps.initialOrderBy.reduce(
|
? restProps.initialOrderBy.reduce(
|
||||||
@@ -60,16 +58,19 @@ export function PaginatedTable<TData>({
|
|||||||
prismaModel,
|
prismaModel,
|
||||||
rowsPerPage,
|
rowsPerPage,
|
||||||
page * rowsPerPage,
|
page * rowsPerPage,
|
||||||
debouncedSearchTerm,
|
searchTerm,
|
||||||
searchFields,
|
searchFields,
|
||||||
filter,
|
filter,
|
||||||
include,
|
include,
|
||||||
orderBy,
|
orderBy,
|
||||||
strictQuery
|
strictQuery
|
||||||
? restProps.columns
|
? restProps.columns
|
||||||
.filter((col: any) => "accessorKey" in col)
|
.filter(
|
||||||
.map((col: any) => col.accessorKey)
|
(col): col is { accessorKey: string } =>
|
||||||
.reduce((acc: Record<string, any>, key: string) => {
|
typeof (col as { accessorKey?: unknown }).accessorKey === "string",
|
||||||
|
)
|
||||||
|
.map((col) => col.accessorKey)
|
||||||
|
.reduce<Record<string, boolean>>((acc, key) => {
|
||||||
acc[key] = true;
|
acc[key] = true;
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
@@ -82,34 +83,19 @@ export function PaginatedTable<TData>({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
RefreshTableData();
|
|
||||||
}, [filter, orderBy]);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
refresh: () => {
|
refresh: () => {
|
||||||
RefreshTableData();
|
RefreshTableData();
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const debounce = (func: Function, delay: number) => {
|
useDebounce(
|
||||||
let timer: NodeJS.Timeout;
|
() => {
|
||||||
return (...args: any[]) => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => func(...args), delay);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchChange = useCallback(
|
|
||||||
debounce((value: string) => {
|
|
||||||
setDebouncedSearchTerm(value);
|
|
||||||
}, 500),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
RefreshTableData();
|
RefreshTableData();
|
||||||
}, [page, debouncedSearchTerm]);
|
},
|
||||||
|
500,
|
||||||
|
[searchTerm, page, rowsPerPage, orderBy, filter],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 m-4">
|
<div className="space-y-4 m-4">
|
||||||
@@ -122,7 +108,6 @@ export function PaginatedTable<TData>({
|
|||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSearchTerm(e.target.value);
|
setSearchTerm(e.target.value);
|
||||||
handleSearchChange(e.target.value);
|
|
||||||
setPage(0); // Reset to first page on search
|
setPage(0); // Reset to first page on search
|
||||||
}}
|
}}
|
||||||
className="input input-bordered w-full max-w-xs justify-end"
|
className="input input-bordered w-full max-w-xs justify-end"
|
||||||
@@ -134,7 +119,6 @@ export function PaginatedTable<TData>({
|
|||||||
<SortableTable
|
<SortableTable
|
||||||
data={data}
|
data={data}
|
||||||
prismaModel={prismaModel}
|
prismaModel={prismaModel}
|
||||||
showEditButton={showEditButton}
|
|
||||||
setOrderBy={setOrderBy}
|
setOrderBy={setOrderBy}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
|
|
||||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ import {
|
|||||||
flexRender,
|
flexRender,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { ArrowLeft, ArrowRight, ChevronDown, ChevronUp } from "lucide-react"; // Icons for sorting
|
import { ArrowLeft, ArrowRight, ChevronDown, ChevronUp } from "lucide-react"; // Icons for sorting
|
||||||
import Link from "next/link";
|
|
||||||
import { PrismaClient } from "@repo/db";
|
import { PrismaClient } from "@repo/db";
|
||||||
|
|
||||||
export interface SortableTableProps<TData> {
|
export interface SortableTableProps<TData> {
|
||||||
data: TData[];
|
data: TData[];
|
||||||
columns: ColumnDef<TData>[];
|
columns: ColumnDef<TData>[];
|
||||||
showEditButton?: boolean;
|
|
||||||
prismaModel?: keyof PrismaClient;
|
prismaModel?: keyof PrismaClient;
|
||||||
setOrderBy?: (orderBy: Record<string, "asc" | "desc">) => void;
|
setOrderBy?: (orderBy: Record<string, "asc" | "desc">) => void;
|
||||||
initialOrderBy?: SortingState;
|
initialOrderBy?: SortingState;
|
||||||
@@ -26,28 +24,13 @@ export default function SortableTable<TData>({
|
|||||||
columns,
|
columns,
|
||||||
initialOrderBy = [],
|
initialOrderBy = [],
|
||||||
prismaModel,
|
prismaModel,
|
||||||
showEditButton,
|
|
||||||
setOrderBy,
|
setOrderBy,
|
||||||
}: SortableTableProps<TData>) {
|
}: SortableTableProps<TData>) {
|
||||||
const [sorting, setSorting] = useState<SortingState>(initialOrderBy);
|
const [sorting, setSorting] = useState<SortingState>(initialOrderBy);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
columns: showEditButton
|
columns,
|
||||||
? [
|
|
||||||
...columns,
|
|
||||||
{
|
|
||||||
header: "Actions",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Link href={`/admin/${prismaModel as string}/${(row.original as any).id}`}>
|
|
||||||
<button className="btn btn-sm">Edit</button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: columns,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const Button = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
{...(props as any)}
|
{...props}
|
||||||
className={cn("btn", props.className)}
|
className={cn("btn", props.className)}
|
||||||
disabled={isLoadingState || props.disabled}
|
disabled={isLoadingState || props.disabled}
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
@@ -27,7 +27,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}
|
{props.children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,32 +1,24 @@
|
|||||||
import DatePicker, { DatePickerProps, registerLocale } from "react-datepicker";
|
import { formatDate } from "date-fns";
|
||||||
import { Control, Controller, FieldValues, Path } from "react-hook-form";
|
|
||||||
import { de } from "date-fns/locale";
|
|
||||||
registerLocale("de", de);
|
|
||||||
|
|
||||||
interface DateInputProps<T extends FieldValues>
|
export const DateInput = ({
|
||||||
extends Omit<DatePickerProps, "onChange" | "selected"> {
|
value,
|
||||||
control: Control<T>;
|
onChange,
|
||||||
name: Path<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DateInput = <T extends FieldValues>({
|
|
||||||
control,
|
|
||||||
name,
|
|
||||||
...props
|
...props
|
||||||
}: DateInputProps<T>) => {
|
}: Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange"> & {
|
||||||
|
value?: Date | null;
|
||||||
|
onChange?: (date: Date) => void;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Controller
|
<input
|
||||||
control={control}
|
type="datetime-local"
|
||||||
name={name}
|
className="input"
|
||||||
render={({ field }) => (
|
value={formatDate(value || new Date(), "yyyy-MM-dd hh:mm")}
|
||||||
<DatePicker
|
onChange={(e) => {
|
||||||
className="input input-bordered mt-2"
|
const date = e.target.value ? new Date(e.target.value) : null;
|
||||||
locale={"de"}
|
if (!date) return;
|
||||||
onChange={(date) => field.onChange(date)}
|
onChange?.(date);
|
||||||
selected={field.value}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,3 +2,5 @@ export * from "./cn";
|
|||||||
export * from "./event";
|
export * from "./event";
|
||||||
export * from "./dates";
|
export * from "./dates";
|
||||||
export * from "./simulatorConnected";
|
export * from "./simulatorConnected";
|
||||||
|
export * from "./useDebounce";
|
||||||
|
export * from "./useTimeout";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import { DependencyList, useEffect } from "react";
|
import { DependencyList, useEffect } from "react";
|
||||||
import useTimeout from "./useTimeout";
|
import useTimeout from "./useTimeout";
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
export default function useTimeout(callback: () => void, delay: number) {
|
export default function useTimeout(callback: () => void, delay: number) {
|
||||||
Reference in New Issue
Block a user