diff --git a/apps/dispatch-server/routes/livekit.ts b/apps/dispatch-server/routes/livekit.ts index 93b9b803..f48b97cd 100644 --- a/apps/dispatch-server/routes/livekit.ts +++ b/apps/dispatch-server/routes/livekit.ts @@ -32,7 +32,6 @@ const router = Router(); router.get("/token", async (req, res) => { const roomName = req.query.roomName as string; - console.log("roomName", roomName); res.send({ token: await createToken(roomName), }); diff --git a/apps/dispatch-server/routes/mission.ts b/apps/dispatch-server/routes/mission.ts index 0bb31872..e2251feb 100644 --- a/apps/dispatch-server/routes/mission.ts +++ b/apps/dispatch-server/routes/mission.ts @@ -80,4 +80,86 @@ router.delete("/:id", async (req, res) => { } }); +// Send mission + +router.post("/:id/send-alert", async (req, res) => { + const { id } = req.params; + try { + const mission = await prisma.mission.findUnique({ + where: { id: Number(id) }, + }); + const Stations = await prisma.station.findMany({ + where: { + id: { + in: mission?.missionStationIds, + }, + }, + }); + + if (!mission) { + res.status(404).json({ error: "Mission not found" }); + return; + } + const connectedAircrafts = await prisma.connectedAircraft.findMany({ + where: { + stationId: { + in: mission.missionStationIds, + }, + logoutTime: null, + }, + }); + + for (const aircraft of connectedAircrafts) { + console.log(`Sending mission to: station:${aircraft.stationId}`); + io.to(`station:${aircraft.stationId}`).emit("mission-alert", { + ...mission, + Stations, + }); + const existingMissionOnStationUser = + await prisma.missionOnStationUsers.findFirst({ + where: { + missionId: mission.id, + userId: aircraft.userId, + stationId: aircraft.stationId, + }, + }); + if (!existingMissionOnStationUser) + await prisma.missionOnStationUsers.create({ + data: { + missionId: mission.id, + userId: aircraft.userId, + stationId: aircraft.stationId, + }, + }); + } + + // for statistics only + await prisma.missionsOnStations + .createMany({ + data: mission.missionStationIds.map((stationId) => ({ + missionId: mission.id, + stationId, + })), + }) + .catch(() => { + // Ignore if the entry already exists + }); + + await prisma.mission.update({ + where: { id: Number(id) }, + data: { + state: "running", + }, + }); + + res.status(200).json({ + message: `Einsatz gesendet (${connectedAircrafts.length} Nutzer) `, + }); + io.to("dispatchers").emit("update-mission", mission); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to send mission" }); + } +}); + export default router; diff --git a/apps/dispatch-server/socket-events/connect-pilot.ts b/apps/dispatch-server/socket-events/connect-pilot.ts index 5135195a..5bdb62a0 100644 --- a/apps/dispatch-server/socket-events/connect-pilot.ts +++ b/apps/dispatch-server/socket-events/connect-pilot.ts @@ -14,6 +14,8 @@ export const handleConnectPilot = const user = socket.data.user; // User ID aus dem JWT-Token const userId = socket.data.user.id; // User ID aus dem JWT-Token + console.log("Pilot connected:", userId); + if (!user) return Error("User not found"); const existingConnection = await prisma.connectedAircraft.findFirst({ @@ -62,8 +64,6 @@ export const handleConnectPilot = userId: userId, loginTime: new Date().toISOString(), stationId: parseInt(stationId), - /* user: { connect: { id: userId } }, // Ensure the user relationship is set - station: { connect: { id: stationId } }, // Ensure the station relationship is set */ }, }); @@ -71,6 +71,8 @@ export const handleConnectPilot = socket.join(`user:${userId}`); // Join the user-specific room socket.join(`station:${stationId}`); // Join the station-specific room + console.log(`Pilot in: station:${stationId}`); + io.to("dispatchers").emit("pilots-update"); io.to("pilots").emit("pilots-update"); @@ -83,14 +85,16 @@ export const handleConnectPilot = socket.on("disconnect", async () => { console.log("Disconnected from dispatch server"); - await prisma.connectedAircraft.update({ - where: { - id: connectedAircraftEntry.id, - }, - data: { - logoutTime: new Date().toISOString(), - }, - }); + await prisma.connectedAircraft + .update({ + where: { + id: connectedAircraftEntry.id, + }, + data: { + logoutTime: new Date().toISOString(), + }, + }) + .catch(console.error); io.to("dispatchers").emit("pilots-update"); io.to("pilots").emit("pilots-update"); }); diff --git a/apps/dispatch/app/_store/pilot/connectionStore.ts b/apps/dispatch/app/_store/pilot/connectionStore.ts index a90bbe9c..1bf5679e 100644 --- a/apps/dispatch/app/_store/pilot/connectionStore.ts +++ b/apps/dispatch/app/_store/pilot/connectionStore.ts @@ -1,12 +1,17 @@ import { create } from "zustand"; import { dispatchSocket } from "../../dispatch/socket"; -import { Station } from "@repo/db"; +import { Mission, Station } from "@repo/db"; import { pilotSocket } from "pilot/socket"; interface ConnectionStore { status: "connected" | "disconnected" | "connecting" | "error"; message: string; selectedStation: Station | null; + activeMission: + | (Mission & { + Stations: Station[]; + }) + | null; connect: ( uid: string, stationId: string, @@ -20,13 +25,14 @@ export const usePilotConnectionStore = create((set) => ({ status: "disconnected", message: "", selectedStation: null, + activeMission: null, connect: async (uid, stationId, logoffTime, station) => new Promise((resolve) => { set({ status: "connecting", message: "", selectedStation: station }); - dispatchSocket.auth = { uid }; - dispatchSocket.connect(); - dispatchSocket.once("connect", () => { - dispatchSocket.emit("connect-pilot", { + pilotSocket.auth = { uid }; + pilotSocket.connect(); + pilotSocket.once("connect", () => { + pilotSocket.emit("connect-pilot", { logoffTime, stationId, }); @@ -34,30 +40,36 @@ export const usePilotConnectionStore = create((set) => ({ }); }), disconnect: () => { - dispatchSocket.disconnect(); + pilotSocket.disconnect(); }, })); -dispatchSocket.on("connect", () => { - pilotSocket.disconnect(); +pilotSocket.on("connect", () => { + dispatchSocket.disconnect(); usePilotConnectionStore.setState({ status: "connected", message: "" }); }); -dispatchSocket.on("connect_error", (err) => { +pilotSocket.on("connect_error", (err) => { usePilotConnectionStore.setState({ status: "error", message: err.message, }); }); -dispatchSocket.on("disconnect", () => { +pilotSocket.on("disconnect", () => { usePilotConnectionStore.setState({ status: "disconnected", message: "" }); }); -dispatchSocket.on("force-disconnect", (reason: string) => { +pilotSocket.on("force-disconnect", (reason: string) => { console.log("force-disconnect", reason); usePilotConnectionStore.setState({ status: "disconnected", message: reason, }); }); + +pilotSocket.on("mission-alert", (data) => { + usePilotConnectionStore.setState({ + activeMission: data, + }); +}); diff --git a/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx b/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx index 9740ecfe..a63f2705 100644 --- a/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx +++ b/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx @@ -1,9 +1,7 @@ "use client"; import React, { useState } from "react"; import { FMS_STATUS_TEXT_COLORS } from "../AircraftMarker"; -/* import { Select } from "_components/Select"; -import { Station } from "@repo/db"; -import { getStations } from "dispatch/_components/pannel/action"; */ +import { toast } from "react-hot-toast"; import { Ban, BellRing, @@ -29,7 +27,11 @@ import { import { usePannelStore } from "_store/pannelStore"; import { useSession } from "next-auth/react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { deleteMissionAPI, editMissionAPI } from "querys/missions"; +import { + deleteMissionAPI, + editMissionAPI, + sendMissionAPI, +} from "querys/missions"; const Einsatzdetails = ({ mission }: { mission: Mission }) => { const queryClient = useQueryClient(); @@ -42,6 +44,20 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => { }); }, }); + const sendAlertMutation = useMutation({ + mutationKey: ["missions"], + mutationFn: sendMissionAPI, + onError: (error) => { + console.error(error); + toast.error("Fehler beim Alarmieren"); + }, + onSuccess: (data) => { + toast.success(data.message); + queryClient.invalidateQueries({ + queryKey: ["missions"], + }); + }, + }); const { setMissionFormValues, setOpen } = usePannelStore((state) => state); return (
@@ -76,7 +92,10 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
-
diff --git a/apps/dispatch/app/dispatch/socket.ts b/apps/dispatch/app/dispatch/socket.ts index 77065b55..ca6f998e 100644 --- a/apps/dispatch/app/dispatch/socket.ts +++ b/apps/dispatch/app/dispatch/socket.ts @@ -1,4 +1,4 @@ -import { pilotSocket } from "pilot/socket"; +"use client"; import { io } from "socket.io-client"; export const dispatchSocket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, { diff --git a/apps/dispatch/app/pilot/page.tsx b/apps/dispatch/app/pilot/page.tsx index a80aa343..aa609cf5 100644 --- a/apps/dispatch/app/pilot/page.tsx +++ b/apps/dispatch/app/pilot/page.tsx @@ -1,14 +1,12 @@ "use client"; -import { Pannel } from "dispatch/_components/pannel/Pannel"; -import { usePannelStore } from "_store/pannelStore"; -import { cn } from "helpers/cn"; -import dynamic from "next/dynamic"; import { Chat } from "../_components/left/Chat"; import { Report } from "../_components/left/Report"; +import { usePilotConnectionStore } from "_store/pilot/connectionStore"; const DispatchPage = () => { - const { isOpen } = usePannelStore(); + const { activeMission } = usePilotConnectionStore(); + return (
{/* */} @@ -20,14 +18,7 @@ const DispatchPage = () => {
-
- -
+
{JSON.stringify(activeMission)}
); }; diff --git a/apps/dispatch/app/pilot/socket.ts b/apps/dispatch/app/pilot/socket.ts index 1be19645..76059009 100644 --- a/apps/dispatch/app/pilot/socket.ts +++ b/apps/dispatch/app/pilot/socket.ts @@ -1,5 +1,6 @@ +"use client"; + import { io } from "socket.io-client"; -import { dispatchSocket } from "dispatch/socket"; export const pilotSocket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, { autoConnect: false, diff --git a/apps/dispatch/app/querys/missions.ts b/apps/dispatch/app/querys/missions.ts index a0d92ba7..f1015ec7 100644 --- a/apps/dispatch/app/querys/missions.ts +++ b/apps/dispatch/app/querys/missions.ts @@ -27,6 +27,13 @@ export const editMissionAPI = async ( return respone.data; }; +export const sendMissionAPI = async (id: number) => { + const respone = await serverApi.post<{ + message: string; + }>(`/mission/${id}/send-alert`); + return respone.data; +}; + export const deleteMissionAPI = async (id: number) => { await serverApi.delete(`/mission/${id}`); }; diff --git a/apps/hub/app/(app)/_components/PilotDispoStats.tsx b/apps/hub/app/(app)/_components/PilotDispoStats.tsx deleted file mode 100644 index 456677ee..00000000 --- a/apps/hub/app/(app)/_components/PilotDispoStats.tsx +++ /dev/null @@ -1,191 +0,0 @@ -"use server"; - -import { prisma } from "@repo/db"; -import { getServerSession } from "api/auth/[...nextauth]/auth"; -import { PlaneIcon } from "lucide-react"; - -export const PilotStats = async () => { - return ( -
-
-
- - - -
-
Einsätze geflogen
-
127
-
Du bist damit unter den top 5%!
-
- -
-
- - - -
-
Pilot Login Zeit
-
35h 12m
-
Mehr als 58% aller anderen User!
-
- -
-
- -
-
Christoph 31
-
- War bisher dein Rettungsmittel der Wahl -
-
- 87 Stationen warten noch auf dich! -
-
-
- ); -}; - -export const DispoStats = async () => { - const session = await getServerSession(); - if (!session) return null; - const user = await prisma.user.findUnique({ - where: { id: session.user.id }, - }); - - const dispoSessions = await prisma.connectedDispatcher.findMany({ - where: { - userId: user?.id, - logoutTime: { - not: null, - }, - }, - select: { - loginTime: true, - logoutTime: true, - }, - }); - - const mostDispatchedStationIds = await prisma.mission.groupBy({ - where: { - createdUserId: user?.id, - }, - by: ["missionStationIds"], - orderBy: { - _count: { - missionStationIds: "desc", - }, - }, - take: 1, - _count: { - missionStationIds: true, - }, - }); - - let mostDispatchedStation = null; - - if (mostDispatchedStationIds[0]?.missionStationIds[0]) { - mostDispatchedStation = await prisma.station.findUnique({ - where: { - id: parseInt(mostDispatchedStationIds[0]?.missionStationIds[0]), - }, - }); - } - - const totalDispatchedMissions = await prisma.mission.count({ - where: { - createdUserId: user?.id, - }, - }); - - 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 hours = Math.floor(totalDispoTime / (1000 * 60 * 60)); - const minutes = Math.floor((totalDispoTime % (1000 * 60 * 60)) / (1000 * 60)); - - return ( -
-
-
- - - -
-
Einsätze disponiert
-
{totalDispatchedMissions}
-
Du bist damit unter den top 9%!
-
- -
-
- - - -
-
Disponent Login Zeit
-
- {hours}h {minutes}m -
-
Mehr als 69% aller anderen User!
-
- - {mostDispatchedStation && ( -
-
- -
-
- {mostDispatchedStation?.bosCallsign} -
-
Wurde von dir am meisten Disponiert
-
- {mostDispatchedStationIds[0]?._count.missionStationIds} Einsätze -
-
- )} -
- ); -}; diff --git a/apps/hub/app/(app)/_components/stats.tsx b/apps/hub/app/(app)/_components/stats.tsx index f54918f0..3aa7b424 100644 --- a/apps/hub/app/(app)/_components/stats.tsx +++ b/apps/hub/app/(app)/_components/stats.tsx @@ -1,7 +1,292 @@ -/* import { useState } from "react"; -import { StatsToggle } from "./StatsToggle"; */ import { StatsToggle } from "(app)/_components/StatsToggle"; -import { DispoStats, PilotStats } from "./PilotDispoStats"; + +import { prisma } from "@repo/db"; +import { getServerSession } from "api/auth/[...nextauth]/auth"; +import { PlaneIcon } from "lucide-react"; + +export const PilotStats = async () => { + const session = await getServerSession(); + if (!session) return null; + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + }); + + const mostFlownStationsIds = await prisma.missionOnStationUsers.groupBy({ + where: { + userId: user?.id, + }, + by: ["stationId"], + orderBy: { + _count: { + stationId: "desc", + }, + }, + take: 1, + _count: { + stationId: true, + }, + }); + + let mostFlownStation = null; + + if (mostFlownStationsIds[0]?.stationId) { + mostFlownStation = await prisma.station.findUnique({ + where: { + id: mostFlownStationsIds[0]?.stationId, + }, + }); + } + + const dispoSessions = await prisma.connectedAircraft.findMany({ + where: { + userId: user?.id, + logoutTime: { + not: null, + }, + }, + select: { + loginTime: true, + logoutTime: true, + }, + }); + + const totalFlownMissions = await prisma.missionOnStationUsers.count({ + where: { + userId: user?.id, + }, + }); + + // Get the user's rank by missions flown + const missionsFlownRanks = await prisma.missionOnStationUsers.groupBy({ + by: ["userId"], + _count: { userId: true }, + orderBy: { _count: { userId: "desc" } }, + }); + + const ownRankMissionsFlown = + missionsFlownRanks.findIndex((rank) => rank.userId === user?.id) + 1; + + const totalUserCount = await prisma.user.count({ + where: { + NOT: { + id: user?.id, + }, + }, + }); + + const totalStationsCount = await prisma.station.count(); + + const unflownStationsCount = totalStationsCount - mostFlownStationsIds.length; + + const totalPilotTime = dispoSessions.reduce((acc, session) => { + const logoffTime = new Date(session.logoutTime!).getTime(); + const logonTime = new Date(session.loginTime).getTime(); + return acc + (logoffTime - logonTime); + }, 0); + + const hours = Math.floor(totalPilotTime / (1000 * 60 * 60)); + const minutes = Math.floor((totalPilotTime % (1000 * 60 * 60)) / (1000 * 60)); + + return ( +
+
+
+ + + +
+
Einsätze geflogen
+
{totalFlownMissions}
+
+ Du bist damit unter den top{" "} + {((ownRankMissionsFlown * 100) / totalUserCount).toFixed(0)}%! +
+
+ +
+
+ + + +
+
Pilot Login Zeit
+
+ {hours}h {minutes}min +
+
+ + {mostFlownStation && ( +
+
+ +
+
+ {mostFlownStation?.bosCallsign} +
+
+ War bisher dein Rettungsmittel der Wahl +
+ {unflownStationsCount > 0 && ( +
+ {unflownStationsCount}{" "} + {unflownStationsCount > 1 ? "Stationen" : "Station"} warten noch + auf dich! +
+ )} + {unflownStationsCount === 0 && ( +
+ Du hast alle Stationen geflogen! Krass... +
+ )} +
+ )} +
+ ); +}; + +export const DispoStats = async () => { + const session = await getServerSession(); + if (!session) return null; + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + }); + + const dispoSessions = await prisma.connectedDispatcher.findMany({ + where: { + userId: user?.id, + logoutTime: { + not: null, + }, + }, + select: { + loginTime: true, + logoutTime: true, + }, + }); + + const mostDispatchedStationIds = await prisma.mission.groupBy({ + where: { + createdUserId: user?.id, + }, + by: ["missionStationIds"], + orderBy: { + _count: { + missionStationIds: "desc", + }, + }, + take: 1, + _count: { + missionStationIds: true, + }, + }); + + let mostDispatchedStation = null; + + if (mostDispatchedStationIds[0]?.missionStationIds[0]) { + mostDispatchedStation = await prisma.station.findUnique({ + where: { + id: mostDispatchedStationIds[0]?.missionStationIds[0], + }, + }); + } + + const totalDispatchedMissions = await prisma.mission.count({ + where: { + createdUserId: user?.id, + }, + }); + + 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 hours = Math.floor(totalDispoTime / (1000 * 60 * 60)); + const minutes = Math.floor((totalDispoTime % (1000 * 60 * 60)) / (1000 * 60)); + + return ( +
+
+
+ + + +
+
Einsätze disponiert
+
{totalDispatchedMissions}
+
Du bist damit unter den top 9%!
+
+ +
+
+ + + +
+
Disponent Login Zeit
+
+ {hours}h {minutes}m +
+
+ + {mostDispatchedStation && ( +
+
+ +
+
+ {mostDispatchedStation?.bosCallsign} +
+
Wurde von dir am meisten Disponiert
+
+ {mostDispatchedStationIds[0]?._count.missionStationIds} Einsätze +
+
+ )} +
+ ); +}; export const Stats = ({ stats }: { stats: "pilot" | "dispo" }) => { return ( 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 95534a3d..db268997 100644 --- a/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx +++ b/apps/hub/app/(app)/admin/user/[id]/_components/forms.tsx @@ -1,9 +1,22 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { BADGES, PERMISSION, Report, User } from "@repo/db"; +import { + BADGES, + ConnectedAircraft, + ConnectedDispatcher, + PERMISSION, + Report, + Station, + User, +} from "@repo/db"; import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; -import { deleteDispoHistory, editUser, resetPassword } from "../../action"; +import { + deleteDispoHistory, + deletePilotHistory, + editUser, + resetPassword, +} from "../../action"; import { toast } from "react-hot-toast"; import { PersonIcon, @@ -166,59 +179,60 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ userId: user.id, }} prismaModel={"connectedDispatcher"} - columns={[ - { - accessorKey: "loginTime", - header: "Login", - cell: ({ row }) => { - return new Date(row.getValue("loginTime")).toLocaleString( - "de-DE", - ); + columns={ + [ + { + accessorKey: "loginTime", + header: "Login", + cell: ({ row }) => { + 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; + { + header: "Time Online", + cell: ({ row }) => { + if (row.original.logoutTime == null) { + return Online; + } + const loginTime = new Date(row.original.loginTime).getTime(); + const logoutTime = new Date( + row.original.logoutTime, + ).getTime(); - const hours = Math.floor(timeOnline / 1000 / 60 / 60); - const minutes = Math.floor((timeOnline / 1000 / 60) % 60); + const timeOnline = logoutTime - loginTime; - if ((row.original as any).logoutTime == null) { - return Online; - } + const hours = Math.floor(timeOnline / 1000 / 60 / 60); + const minutes = Math.floor((timeOnline / 1000 / 60) % 60); - return ( - 2 && "text-error")}> - {hours}h {minutes}min - - ); + return ( + 2 && "text-error")}> + {hours}h {minutes}min + + ); + }, }, - }, - { - header: "Aktionen", - cell: ({ row }) => { - return ( -
- -
- ); + { + header: "Aktionen", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, }, - }, - ]} + ] as ColumnDef[] + } />
@@ -232,73 +246,73 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ }} prismaModel={"connectedAircraft"} include={{ Station: true }} - columns={[ - { - accessorKey: "Station.bosCallsign", - header: "Station", - cell: ({ row }) => { - return ( - - {(row.original as any).Station.bosCallsign} - - ); - }, - }, - { - accessorKey: "loginTime", - header: "Login", - cell: ({ row }) => { - 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 ( -
- -
- ); + {row.original.Station.bosCallsign} + + ); + }, }, - }, - ]} + { + accessorKey: "loginTime", + header: "Login", + cell: ({ row }) => { + return new Date(row.getValue("loginTime")).toLocaleString( + "de-DE", + ); + }, + }, + { + header: "Time Online", + cell: ({ row }) => { + if (!row.original.logoutTime) { + return Online; + } + const loginTime = new Date(row.original.loginTime).getTime(); + const logoutTime = new Date( + row.original.logoutTime, + ).getTime(); + const timeOnline = logoutTime - loginTime; + + const hours = Math.floor(timeOnline / 1000 / 60 / 60); + const minutes = Math.floor((timeOnline / 1000 / 60) % 60); + + return ( + 2 && "text-error")}> + {hours}h {minutes}min + + ); + }, + }, + { + header: "Aktionen", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, + ] as ColumnDef[] + } />
diff --git a/apps/hub/app/(app)/admin/user/action.ts b/apps/hub/app/(app)/admin/user/action.ts index a511f8e3..b47213d3 100644 --- a/apps/hub/app/(app)/admin/user/action.ts +++ b/apps/hub/app/(app)/admin/user/action.ts @@ -44,3 +44,11 @@ export const deleteDispoHistory = async (id: number) => { }, }); }; + +export const deletePilotHistory = async (id: number) => { + return await prisma.connectedAircraft.delete({ + where: { + id: id, + }, + }); +}; diff --git a/grafana/grafana.db b/grafana/grafana.db index 8d29f836..bca4afb0 100644 Binary files a/grafana/grafana.db and b/grafana/grafana.db differ diff --git a/packages/database/prisma/schema/mission.prisma b/packages/database/prisma/schema/mission.prisma index d3bbc7dc..cb33c107 100644 --- a/packages/database/prisma/schema/mission.prisma +++ b/packages/database/prisma/schema/mission.prisma @@ -13,7 +13,7 @@ model Mission { missionKeywordAbbreviation String? missionPatientInfo String missionAdditionalInfo String - missionStationIds String[] @default([]) + missionStationIds Int[] @default([]) missionStationUserIds String[] @default([]) missionLog Json[] @default([]) hpgMissionString String?