From 152b3d46899f9d439d863a70b21f42eb36a616c3 Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Sun, 4 May 2025 11:40:40 -0700 Subject: [PATCH] Added Pilot Stats in Admin view --- .../app/(app)/_components/PilotDispoStats.tsx | 4 + .../admin/user/[id]/_components/forms.tsx | 203 +++++++++++++++++- apps/hub/app/(app)/admin/user/[id]/page.tsx | 61 +++++- apps/hub/app/(app)/admin/user/action.ts | 8 + grafana/grafana.db | Bin 1122304 -> 1122304 bytes 5 files changed, 267 insertions(+), 9 deletions(-) diff --git a/apps/hub/app/(app)/_components/PilotDispoStats.tsx b/apps/hub/app/(app)/_components/PilotDispoStats.tsx index c67c58f4..456677ee 100644 --- a/apps/hub/app/(app)/_components/PilotDispoStats.tsx +++ b/apps/hub/app/(app)/_components/PilotDispoStats.tsx @@ -79,6 +79,10 @@ export const DispoStats = async () => { not: null, }, }, + select: { + loginTime: true, + logoutTime: true, + }, }); const mostDispatchedStationIds = await prisma.mission.groupBy({ 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 17e36423..032f45aa 100644 --- a/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx +++ b/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx @@ -1,10 +1,9 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { BADGES, PERMISSION, User } from "@repo/db"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { editUser, resetPassword } from "../../action"; +import { deleteDispoHistory, editUser, resetPassword } from "../../action"; import { toast } from "react-hot-toast"; import { PersonIcon, @@ -14,18 +13,23 @@ import { LightningBoltIcon, LockOpen1Icon, HobbyKnifeIcon, - HeartIcon, } from "@radix-ui/react-icons"; import { Button } from "../../../../../_components/ui/Button"; import { Select } from "../../../../../_components/ui/Select"; import { UserSchema } from "@repo/db/zod"; import { useRouter } from "next/navigation"; +import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable"; +import { min } from "date-fns"; +import { cn } from "../../../../../../helper/cn"; +import { ChartBarBigIcon, PlaneIcon } from "lucide-react"; interface ProfileFormProps { user: User; } -export const ProfileForm: React.FC = ({ user }) => { +export const ProfileForm: React.FC = ({ + user, +}: ProfileFormProps) => { const [isLoading, setIsLoading] = useState(false); const form = useForm({ defaultValues: user, @@ -135,7 +139,163 @@ export const ProfileForm: React.FC = ({ user }) => { ); }; -export const AdminForm: React.FC = ({ user }) => { +export const ConnectionHistory: React.FC<{ user: User }> = ({ + user, +}: { + user: User; +}) => { + const dispoTableRef = useRef(null); + return ( +
+
+

+ Dispo-Verbindungs Historie +

+ { + return new Date(row.getValue("loginTime")).toLocaleString( + "de-DE", + ); + }, + }, + { + header: "Time Online", + cell: ({ row }) => { + console.log(row.original); + const loginTime = new Date(row.original.loginTime).getTime(); + const logoutTime = new Date( + (row.original as any).logoutTime, + ).getTime(); + const timeOnline = logoutTime - loginTime; + + const hours = Math.floor(timeOnline / 1000 / 60 / 60); + const minutes = Math.floor((timeOnline / 1000 / 60) % 60); + + if ((row.original as any).logoutTime == null) { + return Online; + } + + return ( + 2 && "text-error")}> + {hours}h {minutes}min + + ); + }, + }, + { + header: "Aktionen", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, + ]} + /> +
+
+

+ Pilot-Verbindungs Historie +

+ { + return new Date(row.getValue("loginTime")).toLocaleString( + "de-DE", + ); + }, + }, + { + header: "Time Online", + cell: ({ row }) => { + console.log(row.original); + const loginTime = new Date(row.original.loginTime).getTime(); + const logoutTime = new Date( + (row.original as any).logoutTime, + ).getTime(); + const timeOnline = logoutTime - loginTime; + + const hours = Math.floor(timeOnline / 1000 / 60 / 60); + const minutes = Math.floor((timeOnline / 1000 / 60) % 60); + + if ((row.original as any).logoutTime == null) { + return Online; + } + + return ( + 2 && "text-error")}> + {hours}h {minutes}min + + ); + }, + }, + { + header: "Aktionen", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, + ]} + /> +
+
+ ); +}; + +interface AdminFormProps { + user: User; + dispoTime: { + hours: number; + minutes: number; + lastLogin?: Date; + }; + pilotTime: { + hours: number; + minutes: number; + lastLogin?: Date; + }; +} + +export const AdminForm = ({ user, dispoTime, pilotTime }: AdminFormProps) => { const router = useRouter(); return ( @@ -201,6 +361,37 @@ export const AdminForm: React.FC = ({ user }) => { )} +

+ Aktivität +

+
+
+
+ +
+
+ {dispoTime.hours}h {dispoTime.minutes}min +
+
Dispo Zeit
+
+ {dispoTime.lastLogin && + new Date(dispoTime.lastLogin).toLocaleString("de-DE")} +
+
+
+
+ +
+
+ {pilotTime.hours}h {pilotTime.minutes}min +
+
Pilot Zeit
+
+ {pilotTime.lastLogin && + new Date(pilotTime.lastLogin).toLocaleString("de-DE")} +
+
+
); }; diff --git a/apps/hub/app/(app)/admin/user/[id]/page.tsx b/apps/hub/app/(app)/admin/user/[id]/page.tsx index 38265af8..c7d25742 100644 --- a/apps/hub/app/(app)/admin/user/[id]/page.tsx +++ b/apps/hub/app/(app)/admin/user/[id]/page.tsx @@ -1,9 +1,9 @@ import { PersonIcon } from "@radix-ui/react-icons"; import { PrismaClient, User } from "@repo/db"; -import { AdminForm, ProfileForm } from "./_components/forms"; +import { AdminForm, ConnectionHistory, ProfileForm } from "./_components/forms"; import { Error } from "../../../../_components/Error"; -export default async ({ params }: { params: { id: string } }) => { +const Page = async ({ params }: { params: { id: string } }) => { const prisma = new PrismaClient(); const { id } = await params; @@ -12,6 +12,56 @@ export default async ({ params }: { params: { id: string } }) => { id: id, }, }); + + const dispoSessions = await prisma.connectedDispatcher.findMany({ + where: { + userId: user?.id, + logoutTime: { + not: null, + }, + }, + select: { + loginTime: true, + logoutTime: true, + }, + }); + const totalDispoTime = dispoSessions.reduce((acc, session) => { + const logoffTime = new Date(session.logoutTime!).getTime(); + const logonTime = new Date(session.loginTime).getTime(); + return acc + (logoffTime - logonTime); + }, 0); + + const dispoTime = { + hours: Math.floor(totalDispoTime / (1000 * 60 * 60)), + minutes: Math.floor((totalDispoTime % (1000 * 60 * 60)) / (1000 * 60)), + lastLogin: dispoSessions[dispoSessions.length - 1]?.loginTime, + }; + + const pilotSessions = await prisma.connectedAircraft.findMany({ + where: { + userId: user?.id, + logoutTime: { + not: null, + }, + }, + select: { + loginTime: true, + logoutTime: true, + }, + }); + + const totalPilotTime = pilotSessions.reduce((acc, session) => { + const logoffTime = new Date(session.logoutTime!).getTime(); + const logonTime = new Date(session.loginTime).getTime(); + return acc + (logoffTime - logonTime); + }, 0); + + const pilotTime = { + hours: Math.floor(totalPilotTime / (1000 * 60 * 60)), + minutes: Math.floor((totalPilotTime % (1000 * 60 * 60)) / (1000 * 60)), + lastLogin: pilotSessions[pilotSessions.length - 1]?.loginTime, + }; + if (!user) return ; return (
@@ -25,8 +75,13 @@ export default async ({ params }: { params: { id: string } }) => {
- + +
+
+
); }; + +export default Page; diff --git a/apps/hub/app/(app)/admin/user/action.ts b/apps/hub/app/(app)/admin/user/action.ts index 0c55e489..062c18e4 100644 --- a/apps/hub/app/(app)/admin/user/action.ts +++ b/apps/hub/app/(app)/admin/user/action.ts @@ -36,3 +36,11 @@ export const resetPassword = async (id: string) => { return { password }; }; + +export const deleteDispoHistory = async (id: string) => { + return await prisma.connectedDispatcher.deleteMany({ + where: { + userId: id, + }, + }); +}; diff --git a/grafana/grafana.db b/grafana/grafana.db index 4a6dee7579ff0728e6d29e8e327bfe8437fda476..958024c3e1cc079ed5fcb9ae2b6d371cda2b1db2 100644 GIT binary patch delta 189 zcmZoT;L>owWr8&0s);hrjH@;#eA8u9kP~Be=A7QRnNxVWfE5=zzFD6`iPb7YeACzI zxAZyWS%pE&?f>*Smh-bJ$S~V7PJgJ+A<=%wo&$(Ew;!_SvJPMYYUJI@$jB_v&a!|D Xh`E872Z(urm=B2gx3eq|_)`x6#&9~z delta 189 zcmZoT;L>owWr8&0%!xA2j59YTeA8u*Smh-d9%P`wAPJgJ+A<=%wo&$(Ew;!_SvJPMYYUJI@$jB_v&a!|D Xh`E872Z(urm=B2gx3eq|_)`x6`cXKP