From af36b822218dba24ba14e5412a6c9aa835e75d21 Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:04:15 -0700 Subject: [PATCH 1/4] added auto disconnect when changing link --- apps/dispatch-server/routes/aircraft.ts | 4 ++- apps/dispatch-server/routes/dispatcher.ts | 16 +++++++++++- .../socket-events/connect-dispatch.ts | 1 + .../socket-events/connect-pilot.ts | 1 + apps/dispatch/app/_querys/connected-user.ts | 12 +++++++++ .../app/_store/dispatch/connectionStore.ts | 9 +++++++ .../app/_store/pilot/connectionStore.ts | 2 +- .../navbar/_components/Connection.tsx | 25 +++++++++++++++++-- .../navbar/_components/Connection.tsx | 7 ++++++ 9 files changed, 72 insertions(+), 5 deletions(-) diff --git a/apps/dispatch-server/routes/aircraft.ts b/apps/dispatch-server/routes/aircraft.ts index e3e97c21..576d3e5f 100644 --- a/apps/dispatch-server/routes/aircraft.ts +++ b/apps/dispatch-server/routes/aircraft.ts @@ -91,12 +91,14 @@ router.patch("/:id", async (req, res) => { }); } + res.json(updatedConnectedAircraft); + // When change is only the estimated logout time, we don't need to emit an event + if (Object.keys(aircraftUpdate).length === 1 && aircraftUpdate.esimatedLogoutTime) return; io.to("dispatchers").emit("update-connectedAircraft", updatedConnectedAircraft); io.to(`user:${updatedConnectedAircraft.userId}`).emit( "aircraft-update", updatedConnectedAircraft, ); - res.json(updatedConnectedAircraft); } catch (error) { console.error(error); res.status(500).json({ error: "Failed to update connectedAircraft" }); diff --git a/apps/dispatch-server/routes/dispatcher.ts b/apps/dispatch-server/routes/dispatcher.ts index ab38ab3a..a3c79915 100644 --- a/apps/dispatch-server/routes/dispatcher.ts +++ b/apps/dispatch-server/routes/dispatcher.ts @@ -1,4 +1,4 @@ -import { prisma } from "@repo/db"; +import { Prisma, prisma } from "@repo/db"; import { Router } from "express"; import { pubClient } from "modules/redis"; @@ -14,4 +14,18 @@ router.get("/", async (req, res) => { res.json(user); }); +router.patch("/:id", async (req, res) => { + const { id } = req.params; + const disaptcherUpdate = req.body as Prisma.ConnectedDispatcherUpdateInput; + + const newDispatcher = await prisma.connectedDispatcher.update({ + where: { id: Number(id) }, + data: { + ...disaptcherUpdate, + }, + }); + + res.json(newDispatcher); +}); + export default router; diff --git a/apps/dispatch-server/socket-events/connect-dispatch.ts b/apps/dispatch-server/socket-events/connect-dispatch.ts index ace95790..e4f0a9b0 100644 --- a/apps/dispatch-server/socket-events/connect-dispatch.ts +++ b/apps/dispatch-server/socket-events/connect-dispatch.ts @@ -70,6 +70,7 @@ export const handleConnectDispatch = socket.join("dispatchers"); // Dem Dispatcher-Raum beitreten socket.join(`user:${user.id}`); // Dem User-Raum beitreten + io.to(`user:${user.id}`).emit("dispatchers-update", connectedDispatcherEntry); io.to("dispatchers").emit("dispatchers-update"); io.to("pilots").emit("dispatchers-update"); diff --git a/apps/dispatch-server/socket-events/connect-pilot.ts b/apps/dispatch-server/socket-events/connect-pilot.ts index 44e98d77..c5e743f0 100644 --- a/apps/dispatch-server/socket-events/connect-pilot.ts +++ b/apps/dispatch-server/socket-events/connect-pilot.ts @@ -6,6 +6,7 @@ export const handleConnectPilot = (socket: Socket, io: Server) => async ({ logoffTime, stationId }: { logoffTime: string; stationId: string }) => { try { + if (!stationId) return Error("Station ID is required"); const user: User = socket.data.user; // User ID aus dem JWT-Token const userId = socket.data.user.id; // User ID aus dem JWT-Token diff --git a/apps/dispatch/app/_querys/connected-user.ts b/apps/dispatch/app/_querys/connected-user.ts index e96de52d..b3d86ab7 100644 --- a/apps/dispatch/app/_querys/connected-user.ts +++ b/apps/dispatch/app/_querys/connected-user.ts @@ -1,4 +1,5 @@ import { ConnectedAircraft, ConnectedDispatcher, Prisma } from "@repo/db"; +import { serverApi } from "_helpers/axios"; import axios from "axios"; export const getConnectedUserAPI = async () => { @@ -12,6 +13,17 @@ export const getConnectedUserAPI = async () => { return res.data; }; +export const changeDispatcherAPI = async ( + id: number, + data: Prisma.ConnectedDispatcherUpdateInput, +) => { + const res = await serverApi.patch(`/dispatcher/${id}`, data); + if (res.status !== 200) { + throw new Error("Failed to update Connected Dispatcher"); + } + return res.data; +}; + export const getConnectedDispatcherAPI = async (filter?: Prisma.ConnectedDispatcherWhereInput) => { const res = await axios.get("/api/dispatcher", { params: { diff --git a/apps/dispatch/app/_store/dispatch/connectionStore.ts b/apps/dispatch/app/_store/dispatch/connectionStore.ts index 6dbc3ab5..1fdfae26 100644 --- a/apps/dispatch/app/_store/dispatch/connectionStore.ts +++ b/apps/dispatch/app/_store/dispatch/connectionStore.ts @@ -1,9 +1,11 @@ import { create } from "zustand"; import { dispatchSocket } from "../../dispatch/socket"; import { useAudioStore } from "_store/audioStore"; +import { ConnectedDispatcher } from "@repo/db"; interface ConnectionStore { status: "connected" | "disconnected" | "connecting" | "error"; + connectedDispatcher: ConnectedDispatcher | null; message: string; selectedZone: string; logoffTime: string; @@ -13,6 +15,7 @@ interface ConnectionStore { export const useDispatchConnectionStore = create((set) => ({ status: "disconnected", + connectedDispatcher: null, message: "", selectedZone: "LST_01", logoffTime: "", @@ -51,6 +54,7 @@ dispatchSocket.on("connect_error", (err) => { dispatchSocket.on("disconnect", () => { useDispatchConnectionStore.setState({ status: "disconnected", message: "" }); + useAudioStore.getState().disconnect(); }); dispatchSocket.on("force-disconnect", (reason: string) => { @@ -60,5 +64,10 @@ dispatchSocket.on("force-disconnect", (reason: string) => { message: reason, }); }); +dispatchSocket.on("dispatchers-update", (dispatch: ConnectedDispatcher) => { + useDispatchConnectionStore.setState({ + connectedDispatcher: dispatch, + }); +}); dispatchSocket.on("reconnect", () => {}); diff --git a/apps/dispatch/app/_store/pilot/connectionStore.ts b/apps/dispatch/app/_store/pilot/connectionStore.ts index 2efffb14..1c3044d3 100644 --- a/apps/dispatch/app/_store/pilot/connectionStore.ts +++ b/apps/dispatch/app/_store/pilot/connectionStore.ts @@ -81,6 +81,7 @@ pilotSocket.on("connect_error", (err) => { pilotSocket.on("disconnect", () => { usePilotConnectionStore.setState({ status: "disconnected", message: "" }); + useAudioStore.getState().disconnect(); }); pilotSocket.on("force-disconnect", (reason: string) => { @@ -95,7 +96,6 @@ pilotSocket.on("aircraft-update", (data) => { usePilotConnectionStore.setState({ connectedAircraft: data, }); - /* useMrtStore.getState().setLines(getNew); */ }); pilotSocket.on("mission-alert", (data: Mission & { Stations: Station[] }) => { diff --git a/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx index c25f4581..f7639e21 100644 --- a/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx @@ -1,8 +1,11 @@ "use client"; import { useSession } from "next-auth/react"; import { useDispatchConnectionStore } from "../../../../_store/dispatch/connectionStore"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { toast } from "react-hot-toast"; +import { useMutation } from "@tanstack/react-query"; +import { Prisma } from "@repo/db"; +import { changeDispatcherAPI } from "_querys/connected-user"; export const ConnectionBtn = () => { const modalRef = useRef(null); @@ -11,11 +14,22 @@ export const ConnectionBtn = () => { logoffTime: "", selectedZone: "LST_01", }); + const changeDispatcherMutation = useMutation({ + mutationFn: ({ id, data }: { id: number; data: Prisma.ConnectedDispatcherUpdateInput }) => + changeDispatcherAPI(id, data), + }); const [logoffDebounce, setLogoffDebounce] = useState(null); const session = useSession(); const uid = session.data?.user?.id; if (!uid) return null; + useEffect(() => { + // Disconnect the socket when the component unmounts + return () => { + connection.disconnect(); + }; + }, [connection.disconnect]); + return (
{connection.message.length > 0 && ( @@ -66,7 +80,14 @@ export const ConnectionBtn = () => { logoffTime: value, }); if (logoffDebounce) clearTimeout(logoffDebounce); - const timeout = setTimeout(() => { + const timeout = setTimeout(async () => { + if (!connection.connectedDispatcher) return; + await changeDispatcherMutation.mutateAsync({ + id: connection.connectedDispatcher?.id, + data: { + esimatedLogoutTime: value, + }, + }); toast.success("Änderung gespeichert!"); }, 2000); setLogoffDebounce(timeout); diff --git a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx index 896fd326..ec456bea 100644 --- a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx @@ -43,6 +43,13 @@ export const ConnectionBtn = () => { } }, [stations, form.selectedStationId]); + useEffect(() => { + // Disconnect the socket when the component unmounts + return () => { + connection.disconnect(); + }; + }, [connection.disconnect]); + const session = useSession(); const uid = session.data?.user?.id; if (!uid) return null; From 41931fd276af9a0a0c65411337e5bea804478ad3 Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:32:09 -0700 Subject: [PATCH 2/4] Added time update from Dispatcher --- apps/dispatch-server/modules/chron.ts | 4 +- .../socket-events/connect-dispatch.ts | 4 +- .../app/_store/dispatch/connectionStore.ts | 1 + .../navbar/_components/Connection.tsx | 36 +++++++++------- .../navbar/_components/Connection.tsx | 42 ++++++++++++------- 5 files changed, 54 insertions(+), 33 deletions(-) diff --git a/apps/dispatch-server/modules/chron.ts b/apps/dispatch-server/modules/chron.ts index 3ad776fe..920b3d78 100644 --- a/apps/dispatch-server/modules/chron.ts +++ b/apps/dispatch-server/modules/chron.ts @@ -15,8 +15,8 @@ const removeClosedMissions = async () => { const now = new Date(); if (!lastAlertTime) return; - // change State to closed if last alert was more than 120 minutes ago - if (lastAlertTime && now.getTime() - lastAlertTime.getTime() > 120 * 60 * 1000) { + // change State to closed if last alert was more than 180 minutes ago + if (lastAlertTime && now.getTime() - lastAlertTime.getTime() > 180 * 60 * 1000) { const log: MissionLog = { type: "completed-log", auto: true, diff --git a/apps/dispatch-server/socket-events/connect-dispatch.ts b/apps/dispatch-server/socket-events/connect-dispatch.ts index e4f0a9b0..d4cce20e 100644 --- a/apps/dispatch-server/socket-events/connect-dispatch.ts +++ b/apps/dispatch-server/socket-events/connect-dispatch.ts @@ -71,8 +71,8 @@ export const handleConnectDispatch = socket.join(`user:${user.id}`); // Dem User-Raum beitreten io.to(`user:${user.id}`).emit("dispatchers-update", connectedDispatcherEntry); - io.to("dispatchers").emit("dispatchers-update"); - io.to("pilots").emit("dispatchers-update"); + io.to("dispatchers").emit("dispatchers-update", connectedDispatcherEntry); + io.to("pilots").emit("dispatchers-update", connectedDispatcherEntry); socket.on("stop-other-transmition", async ({ ownRole, otherRole }) => { const aircrafts = await prisma.connectedAircraft.findMany({ diff --git a/apps/dispatch/app/_store/dispatch/connectionStore.ts b/apps/dispatch/app/_store/dispatch/connectionStore.ts index 1fdfae26..5ed3dddc 100644 --- a/apps/dispatch/app/_store/dispatch/connectionStore.ts +++ b/apps/dispatch/app/_store/dispatch/connectionStore.ts @@ -65,6 +65,7 @@ dispatchSocket.on("force-disconnect", (reason: string) => { }); }); dispatchSocket.on("dispatchers-update", (dispatch: ConnectedDispatcher) => { + console.log("dispatchers-update", dispatch); useDispatchConnectionStore.setState({ connectedDispatcher: dispatch, }); diff --git a/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx index f7639e21..103432e6 100644 --- a/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx @@ -23,12 +23,30 @@ export const ConnectionBtn = () => { const uid = session.data?.user?.id; if (!uid) return null; + // useEffect für die Logoff-Zeit useEffect(() => { - // Disconnect the socket when the component unmounts + if (logoffDebounce) clearTimeout(logoffDebounce); + + const timeout = setTimeout(async () => { + if (!form.logoffTime || !connection.connectedDispatcher) return; + await changeDispatcherMutation.mutateAsync({ + id: connection.connectedDispatcher?.id, + data: { + esimatedLogoutTime: new Date( + new Date().toDateString() + " " + form.logoffTime, + ).toISOString(), + }, + }); + toast.success("Änderung gespeichert!"); + }, 2000); + + setLogoffDebounce(timeout); + + // Cleanup function return () => { - connection.disconnect(); + if (logoffDebounce) clearTimeout(logoffDebounce); }; - }, [connection.disconnect]); + }, [form.logoffTime, connection.connectedDispatcher]); return (
@@ -79,18 +97,6 @@ export const ConnectionBtn = () => { ...form, logoffTime: value, }); - if (logoffDebounce) clearTimeout(logoffDebounce); - const timeout = setTimeout(async () => { - if (!connection.connectedDispatcher) return; - await changeDispatcherMutation.mutateAsync({ - id: connection.connectedDispatcher?.id, - data: { - esimatedLogoutTime: value, - }, - }); - toast.success("Änderung gespeichert!"); - }, 2000); - setLogoffDebounce(timeout); }} value={form.logoffTime} type="time" diff --git a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx index ec456bea..55bdf536 100644 --- a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx @@ -50,6 +50,34 @@ export const ConnectionBtn = () => { }; }, [connection.disconnect]); + const logoffTime = form.logoffTime; + + useEffect(() => { + if (!logoffTime || !connection.connectedAircraft) return; + + if (logoffDebounce) clearTimeout(logoffDebounce); + + const timeout = setTimeout(async () => { + if (!connection.connectedAircraft?.id) return; + await aircraftMutation.mutateAsync({ + sessionId: connection.connectedAircraft.id, + change: { + esimatedLogoutTime: logoffTime + ? new Date(new Date().toDateString() + " " + logoffTime).toISOString() + : null, + }, + }); + toast.success("Änderung gespeichert!"); + }, 2000); + + setLogoffDebounce(timeout); + + // Cleanup function to clear timeout + return () => { + if (logoffDebounce) clearTimeout(logoffDebounce); + }; + }, [logoffTime, connection.connectedAircraft]); + const session = useSession(); const uid = session.data?.user?.id; if (!uid) return null; @@ -126,20 +154,6 @@ export const ConnectionBtn = () => { ...form, logoffTime: value, }); - if (logoffDebounce) clearTimeout(logoffDebounce); - const timeout = setTimeout(async () => { - if (!connection.connectedAircraft) return; - await aircraftMutation.mutateAsync({ - sessionId: connection.connectedAircraft.id, - change: { - esimatedLogoutTime: value - ? new Date(new Date().toDateString() + " " + value).toISOString() - : null, - }, - }); - toast.success("Änderung gespeichert!"); - }, 2000); - setLogoffDebounce(timeout); }} value={form.logoffTime ?? ""} type="time" From 00b04089bfb1c044daa9b41692494082da1d97a6 Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:43:12 -0700 Subject: [PATCH 3/4] added more restrictions to mission cleanup --- apps/dispatch-server/modules/chron.ts | 51 +++++++++++++++++---------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/apps/dispatch-server/modules/chron.ts b/apps/dispatch-server/modules/chron.ts index 920b3d78..f7747c0b 100644 --- a/apps/dispatch-server/modules/chron.ts +++ b/apps/dispatch-server/modules/chron.ts @@ -13,30 +13,43 @@ const removeClosedMissions = async () => { }); const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null; + const aircraftsInMission = await prisma.connectedAircraft.findMany({ + where: { + stationId: { + in: mission.missionStationIds, + }, + }, + }); + + if ( + !aircraftsInMission || + !aircraftsInMission.some((a) => ["1", "2", "6"].includes(a.fmsStatus)) + ) + return; + const now = new Date(); if (!lastAlertTime) return; // change State to closed if last alert was more than 180 minutes ago - if (lastAlertTime && now.getTime() - lastAlertTime.getTime() > 180 * 60 * 1000) { - const log: MissionLog = { - type: "completed-log", - auto: true, - timeStamp: new Date().toISOString(), - data: {}, - }; + if (now.getTime() - lastAlertTime.getTime() < 30 * 60 * 1000) return; + const log: MissionLog = { + type: "completed-log", + auto: true, + timeStamp: new Date().toISOString(), + data: {}, + }; - await prisma.mission.update({ - where: { - id: mission.id, + await prisma.mission.update({ + where: { + id: mission.id, + }, + data: { + state: "finished", + missionLog: { + push: log as any, }, - data: { - state: "finished", - missionLog: { - push: log as any, - }, - }, - }); - console.log(`Mission ${mission.id} closed due to inactivity.`); - } + }, + }); + console.log(`Mission ${mission.id} closed due to inactivity.`); }); }; From f752e5fa785c4d58a979ace82a1bf832017453bc Mon Sep 17 00:00:00 2001 From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:45:22 -0700 Subject: [PATCH 4/4] add outoclose modal when login time changed --- .../app/dispatch/_components/navbar/_components/Connection.tsx | 1 + .../app/pilot/_components/navbar/_components/Connection.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx index 103432e6..cdb98ab8 100644 --- a/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/dispatch/_components/navbar/_components/Connection.tsx @@ -38,6 +38,7 @@ export const ConnectionBtn = () => { }, }); toast.success("Änderung gespeichert!"); + modalRef.current?.close(); }, 2000); setLogoffDebounce(timeout); diff --git a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx index 55bdf536..42e9ca66 100644 --- a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx @@ -67,6 +67,7 @@ export const ConnectionBtn = () => { : null, }, }); + modalRef.current?.close(); toast.success("Änderung gespeichert!"); }, 2000);