Finished Hub ESLINT rule enforcement

This commit is contained in:
PxlLoewe
2025-07-09 23:26:09 -07:00
parent 98ed0cb5ca
commit eec72a51b8
26 changed files with 199 additions and 195 deletions

View File

@@ -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"}`);
} }
}; };

View File

@@ -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";

View File

@@ -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={{

View File

@@ -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>

View File

@@ -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 },

View File

@@ -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 () => {

View File

@@ -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

View File

@@ -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`);
})} })}

View File

@@ -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={

View File

@@ -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";

View File

@@ -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>
} }

View File

@@ -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;

View File

@@ -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

View File

@@ -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">

View File

@@ -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,

View File

@@ -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";

View File

@@ -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 }) => {

View File

@@ -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 {

View File

@@ -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}
/> />

View File

@@ -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(

View File

@@ -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,

View File

@@ -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>
); );
}; };

View File

@@ -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}
/> />
)}
/>
); );
}; };

View File

@@ -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";

View File

@@ -1,3 +1,4 @@
"use client";
import { DependencyList, useEffect } from "react"; import { DependencyList, useEffect } from "react";
import useTimeout from "./useTimeout"; import useTimeout from "./useTimeout";

View File

@@ -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) {