From b16b719c740053181bf5dc6dcd26f3080ab04047 Mon Sep 17 00:00:00 2001
From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com>
Date: Sat, 27 Dec 2025 15:33:00 +0100
Subject: [PATCH] Redesigned Search, removed Unused Admin Route
---
.../app/(app)/_components/RecentFlights.tsx | 20 +++---
apps/hub/app/(app)/admin/changelog/page.tsx | 12 +++-
.../event/_components/AppointmentModal.tsx | 10 +--
.../(app)/admin/event/_components/Form.tsx | 30 +++++---
apps/hub/app/(app)/admin/heliport/page.tsx | 14 +++-
apps/hub/app/(app)/admin/keyword/page.tsx | 17 +++--
.../(app)/admin/report/_components/form.tsx | 10 +--
.../(app)/admin/station/_components/Form.tsx | 17 +++--
apps/hub/app/(app)/admin/station/page.tsx | 16 +++--
.../admin/user/[id]/_components/forms.tsx | 33 +++++----
apps/hub/app/(app)/admin/user/page.tsx | 18 ++++-
apps/hub/app/(app)/logbook/page.tsx | 22 +++---
apps/hub/app/_components/PaginatedTable.tsx | 42 ++++++------
.../app/_components/pagiantedTableActions.ts | 68 ++++++-------------
apps/hub/app/api/admin/user/search/route.ts | 33 ---------
apps/hub/app/vatsim/page.tsx | 25 +++++--
16 files changed, 209 insertions(+), 178 deletions(-)
delete mode 100644 apps/hub/app/api/admin/user/search/route.ts
diff --git a/apps/hub/app/(app)/_components/RecentFlights.tsx b/apps/hub/app/(app)/_components/RecentFlights.tsx
index 9999c414..d9be2342 100644
--- a/apps/hub/app/(app)/_components/RecentFlights.tsx
+++ b/apps/hub/app/(app)/_components/RecentFlights.tsx
@@ -1,5 +1,5 @@
"use client";
-import { Mission, MissionAlertLog, MissionLog, Station } from "@repo/db";
+import { Mission, MissionAlertLog, MissionLog, Prisma, Station } from "@repo/db";
import { ColumnDef } from "@tanstack/react-table";
import { PaginatedTable } from "_components/PaginatedTable";
import { ArrowRight, NotebookText } from "lucide-react";
@@ -12,20 +12,22 @@ export const RecentFlights = () => {
+ ({
+ OR: [
+ { bosCallsign: { contains: searchField, mode: "insensitive" } },
+ { operator: { contains: searchField, mode: "insensitive" } },
+ ],
+ }) as Prisma.StationWhereInput
+ }
stickyHeaders
columns={
[
@@ -44,11 +52,11 @@ const page = () => {
}
leftOfSearch={
- Stationen
+ Stationen
}
rightOfSearch={
-
+
diff --git a/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx b/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx
index 6829b137..1b56101d 100644
--- a/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx
+++ b/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx
@@ -8,6 +8,7 @@ import {
DiscordAccount,
Penalty,
PERMISSION,
+ Prisma,
Station,
User,
} from "@repo/db";
@@ -281,9 +282,11 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
+ ({
+ userId: user.id,
+ }) as Prisma.ConnectedDispatcherWhereInput
+ }
prismaModel={"connectedDispatcher"}
initialOrderBy={[
{
@@ -349,9 +352,11 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
+ ({
+ userId: user.id,
+ }) as Prisma.ConnectedAircraftWhereInput
+ }
prismaModel={"connectedAircraft"}
include={{ Station: true }}
initialOrderBy={[
@@ -505,9 +510,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
CreatedUser: true,
Report: true,
}}
- filter={{
- userId: user.id,
- }}
+ getFilter={() => ({ userId: user.id }) as Prisma.PenaltyWhereInput}
columns={penaltyColumns}
/>
@@ -529,9 +532,11 @@ export const UserReports = ({ user }: { user: User }) => {
+ ({
+ reportedUserId: user.id,
+ }) as Prisma.ReportWhereInput
+ }
initialOrderBy={[
{
id: "timestamp",
@@ -720,9 +725,7 @@ export const AdminForm = ({
)}
-
- Achtung! Dieser Account ist als Duplikat markiert oder hat Duplikate!
-
+ {user.duplicateReason || "Keine Grund angegeben"}
)}
{(!!openBans.length || !!openTimebans.length) && (
diff --git a/apps/hub/app/(app)/admin/user/page.tsx b/apps/hub/app/(app)/admin/user/page.tsx
index 91faa09d..cdeb8b28 100644
--- a/apps/hub/app/(app)/admin/user/page.tsx
+++ b/apps/hub/app/(app)/admin/user/page.tsx
@@ -3,7 +3,7 @@ import { User2 } from "lucide-react";
import { PaginatedTable } from "../../../_components/PaginatedTable";
import Link from "next/link";
import { ColumnDef } from "@tanstack/react-table";
-import { DiscordAccount, User } from "@repo/db";
+import { DiscordAccount, Prisma, User } from "@repo/db";
import { useSession } from "next-auth/react";
const AdminUserPage = () => {
@@ -14,7 +14,21 @@ const AdminUserPage = () => {
{
+ return {
+ OR: [
+ { firstname: { contains: searchTerm, mode: "insensitive" } },
+ { lastname: { contains: searchTerm, mode: "insensitive" } },
+ { email: { contains: searchTerm, mode: "insensitive" } },
+ {
+ discordAccounts: {
+ some: { username: { contains: searchTerm, mode: "insensitive" } },
+ },
+ },
+ ],
+ } as Prisma.UserWhereInput;
+ }}
include={{
discordAccounts: true,
}}
diff --git a/apps/hub/app/(app)/logbook/page.tsx b/apps/hub/app/(app)/logbook/page.tsx
index 9418a114..fd4647f8 100644
--- a/apps/hub/app/(app)/logbook/page.tsx
+++ b/apps/hub/app/(app)/logbook/page.tsx
@@ -1,5 +1,5 @@
"use client";
-import { Mission, MissionAlertLog, MissionLog, Station } from "@repo/db";
+import { Mission, MissionAlertLog, MissionLog, Prisma, Station } from "@repo/db";
import { ColumnDef } from "@tanstack/react-table";
import { Error } from "_components/Error";
import { PaginatedTable } from "_components/PaginatedTable";
@@ -14,19 +14,21 @@ const Page = () => {
return (
-
- Einsatzhistorie
+
+ Einsatzhistorie
-
+
+ ({
+ userId: session.data?.user?.id ?? "",
+ Mission: {
+ state: "finished",
+ },
+ }) as Prisma.MissionOnStationUsersWhereInput
+ }
include={{
Station: true,
User: true,
diff --git a/apps/hub/app/_components/PaginatedTable.tsx b/apps/hub/app/_components/PaginatedTable.tsx
index 87bc9fff..af4aedc0 100644
--- a/apps/hub/app/_components/PaginatedTable.tsx
+++ b/apps/hub/app/_components/PaginatedTable.tsx
@@ -9,12 +9,13 @@ export interface PaginatedTableRef {
refresh: () => void;
}
-interface PaginatedTableProps extends Omit, "data"> {
+interface PaginatedTableProps
+ extends Omit, "data"> {
prismaModel: keyof PrismaClient;
stickyHeaders?: boolean;
- filter?: Record;
initialRowsPerPage?: number;
- searchFields?: string[];
+ showSearch?: boolean;
+ getFilter?: (searchTerm: string) => TWhere;
include?: Record;
strictQuery?: boolean;
leftOfSearch?: React.ReactNode;
@@ -24,11 +25,11 @@ interface PaginatedTableProps extends Omit, "da
ref?: Ref;
}
-export function PaginatedTable({
+export function PaginatedTable({
prismaModel,
initialRowsPerPage = 30,
- searchFields = [],
- filter,
+ getFilter,
+ showSearch = false,
include,
ref,
strictQuery = false,
@@ -38,7 +39,7 @@ export function PaginatedTable({
leftOfPagination,
supressQuery,
...restProps
-}: PaginatedTableProps) {
+}: PaginatedTableProps) {
const [data, setData] = useState([]);
const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage);
const [page, setPage] = useState(0);
@@ -63,16 +64,14 @@ export function PaginatedTable({
return;
}
setLoading(true);
- getData(
- prismaModel,
- rowsPerPage,
- page * rowsPerPage,
- searchTerm,
- searchFields,
- filter,
+ getData({
+ model: prismaModel,
+ limit: rowsPerPage,
+ offset: page * rowsPerPage,
+ where: getFilter ? getFilter(searchTerm) : undefined,
include,
orderBy,
- strictQuery
+ select: strictQuery
? restProps.columns
.filter(
(col): col is { accessorKey: string } =>
@@ -84,7 +83,7 @@ export function PaginatedTable({
return acc;
}, {})
: undefined,
- )
+ })
.then((result) => {
if (result) {
setData(result.data);
@@ -100,8 +99,7 @@ export function PaginatedTable({
rowsPerPage,
page,
searchTerm,
- searchFields,
- filter,
+ getFilter,
include,
orderBy,
strictQuery,
@@ -119,19 +117,19 @@ export function PaginatedTable({
if (supressQuery) return;
setLoading(true);
- }, [searchTerm, page, rowsPerPage, orderBy, filter, setLoading, supressQuery]);
+ }, [searchTerm, page, rowsPerPage, orderBy, getFilter, setLoading, supressQuery]);
useDebounce(
() => {
refreshTableData();
},
500,
- [searchTerm, page, rowsPerPage, orderBy, filter],
+ [searchTerm, page, rowsPerPage, orderBy, getFilter],
);
return (
- {(rightOfSearch || leftOfSearch || searchFields.length > 0) && (
+ {(rightOfSearch || leftOfSearch || showSearch) && (
({
{leftOfSearch}
{loading && }
- {searchFields.length > 0 && (
+ {showSearch && (
,
- include?: Record
,
- orderBy?: Record,
- select?: Record,
-) {
- if (!model || !prisma[model]) {
+export async function getData({
+ model,
+ limit,
+ offset,
+ where,
+ include,
+ orderBy,
+ select,
+}: {
+ model: keyof PrismaClient;
+ limit: number;
+ offset: number;
+ where: Twhere;
+ include?: Record;
+ orderBy?: Record;
+ select?: Record;
+}) {
+ if (!model || !(prisma as any)[model]) {
return { data: [], total: 0 };
}
- const formattedId = searchTerm.match(/^VAR(\d+)$/)?.[1];
+ const delegate = (prisma as any)[model];
- const where = searchTerm
- ? {
- OR: [
- formattedId ? { id: formattedId } : undefined,
- ...searchFields.map((field) => {
- if (field.includes(".")) {
- const parts: string[] = field.split(".");
-
- // Helper function to build nested object
- const buildNestedFilter = (parts: string[], index = 0): any => {
- if (index === parts.length - 1) {
- // Reached the last part - add the contains filter
- return { [parts[index] as string]: { contains: searchTerm } };
- }
-
- // For intermediate levels, nest the next level
- return { [parts[index] as string]: buildNestedFilter(parts, index + 1) };
- };
-
- return buildNestedFilter(parts);
- }
-
- return { [field]: { contains: searchTerm } };
- }),
- ].filter(Boolean),
- ...filter,
- }
- : { ...filter };
-
- if (!prisma[model]) {
- return { data: [], total: 0 };
- }
- const data = await (prisma[model] as any).findMany({
+ const data = await delegate.findMany({
where,
orderBy,
take: limit,
@@ -60,7 +34,7 @@ export async function getData(
select,
});
- const total = await (prisma[model] as any).count({ where });
+ const total = await delegate.count({ where });
return { data, total };
}
diff --git a/apps/hub/app/api/admin/user/search/route.ts b/apps/hub/app/api/admin/user/search/route.ts
deleted file mode 100644
index 73571057..00000000
--- a/apps/hub/app/api/admin/user/search/route.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@repo/db";
-
-export async function GET(req: Request) {
- const { searchParams } = new URL(req.url);
- const publicId = searchParams.get("publicId")?.trim();
- const email = searchParams.get("email")?.trim()?.toLowerCase();
-
- if (!publicId && !email) {
- return NextResponse.json({ error: "Missing query" }, { status: 400 });
- }
-
- try {
- const user = await prisma.user.findFirst({
- where: {
- OR: [publicId ? { publicId } : undefined, email ? { email } : undefined].filter(
- Boolean,
- ) as any,
- },
- select: {
- id: true,
- publicId: true,
- firstname: true,
- lastname: true,
- isBanned: true,
- },
- });
- if (!user) return NextResponse.json({ user: null }, { status: 200 });
- return NextResponse.json({ user }, { status: 200 });
- } catch (e) {
- return NextResponse.json({ error: "Server error" }, { status: 500 });
- }
-}
diff --git a/apps/hub/app/vatsim/page.tsx b/apps/hub/app/vatsim/page.tsx
index 83d885ca..1f6edb18 100644
--- a/apps/hub/app/vatsim/page.tsx
+++ b/apps/hub/app/vatsim/page.tsx
@@ -1,5 +1,5 @@
"use client";
-import { User } from "@repo/db";
+import { Prisma, User } from "@repo/db";
import { ColumnDef } from "@tanstack/react-table";
import { PaginatedTable } from "_components/PaginatedTable";
@@ -7,13 +7,24 @@ export default function () {
return (
+ ({
+ AND: [
+ {
+ vatsimCid: {
+ not: "",
+ },
+ OR: [
+ { firstname: { contains: searchTerm, mode: "insensitive" } },
+ { lastname: { contains: searchTerm, mode: "insensitive" } },
+ { vatsimCid: { contains: searchTerm, mode: "insensitive" } },
+ ],
+ },
+ ],
+ }) as Prisma.UserWhereInput
+ }
leftOfSearch={Vatsim-Nutzer
}
columns={
[