diff --git a/apps/dispatch-server/modules/chron.ts b/apps/dispatch-server/modules/chron.ts index 3ad776fe..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 120 minutes ago - if (lastAlertTime && now.getTime() - lastAlertTime.getTime() > 120 * 60 * 1000) { - const log: MissionLog = { - type: "completed-log", - auto: true, - timeStamp: new Date().toISOString(), - data: {}, - }; + // change State to closed if last alert was more than 180 minutes ago + 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.`); }); }; 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..d4cce20e 100644 --- a/apps/dispatch-server/socket-events/connect-dispatch.ts +++ b/apps/dispatch-server/socket-events/connect-dispatch.ts @@ -70,8 +70,9 @@ export const handleConnectDispatch = socket.join("dispatchers"); // Dem Dispatcher-Raum beitreten socket.join(`user:${user.id}`); // Dem User-Raum beitreten - io.to("dispatchers").emit("dispatchers-update"); - io.to("pilots").emit("dispatchers-update"); + io.to(`user:${user.id}`).emit("dispatchers-update", connectedDispatcherEntry); + 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-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..5ed3dddc 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,11 @@ dispatchSocket.on("force-disconnect", (reason: string) => { message: reason, }); }); +dispatchSocket.on("dispatchers-update", (dispatch: ConnectedDispatcher) => { + console.log("dispatchers-update", dispatch); + 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..cdb98ab8 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,41 @@ 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 für die Logoff-Zeit + useEffect(() => { + 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!"); + modalRef.current?.close(); + }, 2000); + + setLogoffDebounce(timeout); + + // Cleanup function + return () => { + if (logoffDebounce) clearTimeout(logoffDebounce); + }; + }, [form.logoffTime, connection.connectedDispatcher]); + return (
{connection.message.length > 0 && ( @@ -65,11 +98,6 @@ export const ConnectionBtn = () => { ...form, logoffTime: value, }); - if (logoffDebounce) clearTimeout(logoffDebounce); - const timeout = setTimeout(() => { - 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 896fd326..42e9ca66 100644 --- a/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/_components/Connection.tsx @@ -43,6 +43,42 @@ export const ConnectionBtn = () => { } }, [stations, form.selectedStationId]); + useEffect(() => { + // Disconnect the socket when the component unmounts + return () => { + connection.disconnect(); + }; + }, [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, + }, + }); + modalRef.current?.close(); + 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; @@ -119,20 +155,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"