diff --git a/apps/dispatch-server/routes/aircraft.ts b/apps/dispatch-server/routes/aircraft.ts index c3ff5d44..fa665438 100644 --- a/apps/dispatch-server/routes/aircraft.ts +++ b/apps/dispatch-server/routes/aircraft.ts @@ -2,6 +2,7 @@ import { AdminMessage, getPublicUser, MissionLog, + MissionSdsStatusLog, NotificationPayload, Prisma, prisma, @@ -130,6 +131,44 @@ router.patch("/:id", async (req, res) => { } }); +router.post("/:id/send-sds-message", async (req, res) => { + const { id } = req.params; + const { sdsMessage } = req.body as { sdsMessage: MissionSdsStatusLog }; + + if (!sdsMessage.data.stationId || !id) { + res.status(400).json({ error: "Missing aircraftId or stationId" }); + return; + } + + await prisma.mission.updateMany({ + where: { + state: "running", + missionStationIds: { + has: sdsMessage.data.stationId, + }, + }, + data: { + missionLog: { + push: sdsMessage as unknown as Prisma.InputJsonValue, + }, + }, + }); + + io.to( + sdsMessage.data.direction === "to-lst" ? "dispatchers" : `station:${sdsMessage.data.stationId}`, + ).emit(sdsMessage.data.direction === "to-lst" ? "notification" : "sds-status", { + type: "station-status", + status: sdsMessage.data.status, + message: "SDS Status Message", + data: { + aircraftId: parseInt(id), + stationId: sdsMessage.data.stationId, + }, + } as NotificationPayload); + + res.sendStatus(204); +}); + // Kick a connectedAircraft by ID router.delete("/:id", async (req, res) => { const { id } = req.params; diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx index f1450f19..039d7ac2 100644 --- a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx @@ -14,7 +14,7 @@ export const ConnectionBtn = () => { const connection = useDispatchConnectionStore((state) => state); const [form, setForm] = useState({ logoffTime: "", - selectedZone: "LST_01", + selectedZone: "VAR_LST_RD_01", ghostMode: false, }); const changeDispatcherMutation = useMutation({ diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/Base.tsx b/apps/dispatch/app/(app)/pilot/_components/mrt/Base.tsx new file mode 100644 index 00000000..7624c177 --- /dev/null +++ b/apps/dispatch/app/(app)/pilot/_components/mrt/Base.tsx @@ -0,0 +1,15 @@ +import { useMrtStore } from "_store/pilot/MrtStore"; +import Image from "next/image"; +import DAY_BASE_IMG from "./images/Base_NoScreen_Day.png"; +import NIGHT_BASE_IMG from "./images/Base_NoScreen_Night.png"; + +export const MrtBase = () => { + const { nightMode } = useMrtStore((state) => state); + return ( + + ); +}; diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT.png b/apps/dispatch/app/(app)/pilot/_components/mrt/MRT.png deleted file mode 100644 index a9c552b1..00000000 Binary files a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT.png and /dev/null differ diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT_MESSAGE.png b/apps/dispatch/app/(app)/pilot/_components/mrt/MRT_MESSAGE.png deleted file mode 100644 index a0e80ae6..00000000 Binary files a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT_MESSAGE.png and /dev/null differ diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx b/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx index 5d271921..0fda6281 100644 --- a/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx +++ b/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx @@ -1,22 +1,9 @@ import { CSSProperties } from "react"; -import MrtImage from "./MRT.png"; -import MrtMessageImage from "./MRT_MESSAGE.png"; -import { useButtons } from "./useButtons"; -import { useSounds } from "./useSounds"; import "./mrt.css"; -import Image from "next/image"; -import { useMrtStore } from "_store/pilot/MrtStore"; - -const MRT_BUTTON_STYLES: CSSProperties = { - cursor: "pointer", - zIndex: "9999", - backgroundColor: "transparent", - border: "none", -}; -const MRT_DISPLAYLINE_STYLES: CSSProperties = { - color: "white", - zIndex: 1, -}; +import { MrtBase } from "./Base"; +import { MrtDisplay } from "./MrtDisplay"; +import { MrtButtons } from "./MrtButtons"; +import { MrtPopups } from "./MrtPopups"; export interface DisplayLineProps { lineStyle?: CSSProperties; @@ -27,45 +14,7 @@ export interface DisplayLineProps { textSize: "1" | "2" | "3" | "4"; } -const DisplayLine = ({ - style = {}, - textLeft, - textMid, - textRight, - textSize, - lineStyle, -}: DisplayLineProps) => { - const INNER_TEXT_PARTS: CSSProperties = { - fontFamily: "Melder", - flex: "1", - flexBasis: "auto", - overflowWrap: "break-word", - ...lineStyle, - }; - - return ( -
- {textLeft} - {textMid} - {textRight} -
- ); -}; - export const Mrt = () => { - useSounds(); - const { handleButton } = useButtons(); - const { lines, page } = useMrtStore((state) => state); - return (
{ maxHeight: "100%", maxWidth: "100%", color: "white", - gridTemplateColumns: "21.83% 4.43% 24.42% 18.08% 5.93% 1.98% 6.00% 1.69% 6.00% 9.35%", - gridTemplateRows: "21.58% 11.87% 3.55% 5.00% 6.84% 0.53% 3.03% 11.84% 3.55% 11.84% 20.39%", + gridTemplateColumns: + "9.75% 4.23% 8.59% 7.30% 1.16% 7.30% 1.23% 7.16% 1.09% 7.30% 3.68% 4.23% 5.59% 6.07% 1.91% 6.07% 1.84% 6.21% 9.28%", + gridTemplateRows: + "21.55% 11.83% 3.55% 2.50% 9.46% 2.76% 0.66% 4.99% 6.83% 3.55% 1.97% 9.99% 4.20% 11.04% 5.12%", }} > - {page !== "sds" && ( - MrtImage - )} - {page === "sds" && ( - MrtImage-Message - )} - -
); }; diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/MrtButtons.tsx b/apps/dispatch/app/(app)/pilot/_components/mrt/MrtButtons.tsx new file mode 100644 index 00000000..d6e67f59 --- /dev/null +++ b/apps/dispatch/app/(app)/pilot/_components/mrt/MrtButtons.tsx @@ -0,0 +1,110 @@ +import { CSSProperties, useRef } from "react"; +import { useButtons } from "./useButtons"; +import { useSounds } from "./useSounds"; + +const MRT_BUTTON_STYLES: CSSProperties = { + cursor: "pointer", + zIndex: "9999", + backgroundColor: "transparent", + border: "none", +}; + +interface MrtButtonProps { + onClick: () => void; + onHold?: () => void; + style: CSSProperties; +} + +const MrtButton = ({ onClick, onHold, style }: MrtButtonProps) => { + const timeoutRef = useRef(null); + + const handleMouseDown = () => { + if (!onHold) return; + timeoutRef.current = setTimeout(onHold, 500); + }; + + const handleMouseUp = () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + onClick(); + } + }; + + return ( + ))} diff --git a/apps/dispatch/app/_components/QueryProvider.tsx b/apps/dispatch/app/_components/QueryProvider.tsx index d5b2799a..345d2fb1 100644 --- a/apps/dispatch/app/_components/QueryProvider.tsx +++ b/apps/dispatch/app/_components/QueryProvider.tsx @@ -63,14 +63,15 @@ export function QueryProvider({ children }: { children: ReactNode }) { }; const handleNotification = (notification: NotificationPayload) => { + console.log("Received notification:", notification); const playNotificationSound = () => { if (notificationSound.current) { - notificationSound.current.currentTime = 0; - notificationSound.current - .play() - .catch((e) => console.error("Notification sound error:", e)); - } - } + notificationSound.current.currentTime = 0; + notificationSound.current + .play() + .catch((e) => console.error("Notification sound error:", e)); + } + }; switch (notification.type) { case "hpg-validation": @@ -90,6 +91,7 @@ export function QueryProvider({ children }: { children: ReactNode }) { }); break; case "station-status": + console.log("station Status", QUICK_RESPONSE[notification.status]); if (!QUICK_RESPONSE[notification.status]) return; toast.custom((e) => , { duration: 60000, @@ -106,7 +108,7 @@ export function QueryProvider({ children }: { children: ReactNode }) { break; case "mission-closed": toast("Dein aktueller Einsatz wurde geschlossen."); - + break; default: toast("unbekanntes Notification-Event"); diff --git a/apps/dispatch/app/_components/customToasts/StationStatusToast.tsx b/apps/dispatch/app/_components/customToasts/StationStatusToast.tsx index 0b163e79..ec73555d 100644 --- a/apps/dispatch/app/_components/customToasts/StationStatusToast.tsx +++ b/apps/dispatch/app/_components/customToasts/StationStatusToast.tsx @@ -1,14 +1,16 @@ -import { Prisma, StationStatus } from "@repo/db"; +import { getPublicUser, MissionSdsStatusLog, Prisma, StationStatus } from "@repo/db"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { BaseNotification } from "_components/customToasts/BaseNotification"; import { FMS_STATUS_COLORS } from "_helpers/fmsStatusColors"; import { editConnectedAircraftAPI, getConnectedAircraftsAPI } from "_querys/aircrafts"; import { getLivekitRooms } from "_querys/livekit"; +import { sendSdsStatusMessageAPI } from "_querys/missions"; import { getStationsAPI } from "_querys/stations"; import { useAudioStore } from "_store/audioStore"; import { useMapStore } from "_store/mapStore"; import { X } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { useSession } from "next-auth/react"; +import { useEffect, useRef } from "react"; import { Toast, toast } from "react-hot-toast"; export const QUICK_RESPONSE: Record = { @@ -22,6 +24,8 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) => const status5Sounds = useRef(null); const status9Sounds = useRef(null); + const session = useSession(); + const { data: livekitRooms } = useQuery({ queryKey: ["livekit-rooms"], queryFn: () => getLivekitRooms(), @@ -46,7 +50,7 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) => status9Sounds.current = new Audio("/sounds/status-9.mp3"); } }, []); - const [aircraftDataAcurate, setAircraftDataAccurate] = useState(false); + //const mapStore = useMapStore((s) => s); const { setOpenAircraftMarker, setMap } = useMapStore((store) => store); @@ -65,29 +69,16 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) => const station = stations?.find((s) => s.id === event.data?.stationId); const queryClient = useQueryClient(); - const changeAircraftMutation = useMutation({ - mutationFn: async ({ - id, - update, - }: { - id: number; - update: Prisma.ConnectedAircraftUpdateInput; - }) => { - await editConnectedAircraftAPI(id, update); + const sendSdsStatusMutation = useMutation({ + mutationFn: async ({ sdsMessage }: { sdsMessage: MissionSdsStatusLog }) => { + if (!connectedAircraft?.id) throw new Error("No connected aircraft"); + await sendSdsStatusMessageAPI({ sdsMessage, aircraftId: connectedAircraft?.id }); queryClient.invalidateQueries({ - queryKey: ["aircrafts"], + queryKey: ["missions"], }); }, }); - useEffect(() => { - if (event.status !== connectedAircraft?.fmsStatus && aircraftDataAcurate) { - toast.remove(t.id); - } else if (event.status == connectedAircraft?.fmsStatus && !aircraftDataAcurate) { - setAircraftDataAccurate(true); - } - }, [aircraftDataAcurate, connectedAircraft, event.status, t.id]); - useEffect(() => { let soundRef: React.RefObject | null = null; switch (event.status) { @@ -103,7 +94,8 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) => default: soundRef = null; } - if (audioRoom !== livekitUser?.roomName) { + + if (audioRoom && livekitUser?.roomName && audioRoom !== livekitUser?.roomName) { toast.remove(t.id); return; } @@ -121,7 +113,8 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) => }; }, [event.status, livekitUser?.roomName, audioRoom, t.id]); - if (!connectedAircraft || !station) return null; + console.log(connectedAircraft, station); + if (!connectedAircraft || !station || !session.data) return null; return (
@@ -162,10 +155,18 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) => toast.error("Keine Flugzeug-ID gefunden"); return; } - await changeAircraftMutation.mutateAsync({ - id: event.data?.aircraftId, - update: { - fmsStatus: status, + await sendSdsStatusMutation.mutateAsync({ + sdsMessage: { + type: "sds-status-log", + auto: false, + data: { + direction: "to-aircraft", + stationId: event.data.stationId!, + station: station, + user: getPublicUser(session.data?.user), + status, + }, + timeStamp: new Date().toISOString(), }, }); toast.remove(t.id); diff --git a/apps/dispatch/app/_components/map/AircraftMarker.tsx b/apps/dispatch/app/_components/map/AircraftMarker.tsx index 9c0adaf3..445babda 100644 --- a/apps/dispatch/app/_components/map/AircraftMarker.tsx +++ b/apps/dispatch/app/_components/map/AircraftMarker.tsx @@ -396,27 +396,11 @@ const AircraftMarker = ({ aircraft }: { aircraft: ConnectedAircraft & { Station: }; export const AircraftLayer = () => { - const [aircrafts, setAircrafts] = useState<(ConnectedAircraft & { Station: Station })[]>([]); - - useEffect(() => { - const fetchAircrafts = async () => { - try { - const res = await fetch("/api/aircrafts"); - if (!res.ok) { - throw new Error("Failed to fetch aircrafts"); - } - const data: (ConnectedAircraft & { Station: Station })[] = await res.json(); - setAircrafts(data.filter((a) => checkSimulatorConnected(a))); - } catch (error) { - console.error("Failed to fetch aircrafts:", error); - } - }; - - fetchAircrafts(); - const interval = setInterval(fetchAircrafts, 10_000); - - return () => clearInterval(interval); - }, []); + const { data: aircrafts } = useQuery({ + queryKey: ["connected-aircrafts", "map"], + queryFn: () => getConnectedAircraftsAPI(), + refetchInterval: 15000, + }); const { setMap } = useMapStore((state) => state); const map = useMap(); const { diff --git a/apps/dispatch/app/_components/map/_components/AircraftMarkerTabs.tsx b/apps/dispatch/app/_components/map/_components/AircraftMarkerTabs.tsx index 97e529ab..0dae2e20 100644 --- a/apps/dispatch/app/_components/map/_components/AircraftMarkerTabs.tsx +++ b/apps/dispatch/app/_components/map/_components/AircraftMarkerTabs.tsx @@ -9,6 +9,7 @@ import { Mission, MissionLog, MissionSdsLog, + MissionSdsStatusLog, MissionStationLog, Prisma, PublicUser, @@ -40,7 +41,7 @@ import { TextSearch, } from "lucide-react"; import { useSession } from "next-auth/react"; -import { sendSdsMessageAPI } from "_querys/missions"; +import { sendSdsMessageAPI, sendSdsStatusMessageAPI } from "_querys/missions"; import { getLivekitRooms } from "_querys/livekit"; import { findLeitstelleForPosition } from "_helpers/findLeitstelleinPoint"; import { formatDistance } from "date-fns"; @@ -54,9 +55,13 @@ const FMSStatusHistory = ({ mission?: Mission; }) => { const log = ((mission?.missionLog as unknown as MissionLog[]) || []) - .filter((entry) => entry.type === "station-log" && entry.data.stationId === aircraft.Station.id) + .filter( + (entry) => + (entry.type === "station-log" || entry.type == "sds-status-log") && + entry.data.stationId === aircraft.Station.id, + ) .reverse() - .splice(0, 6) as MissionStationLog[]; + .splice(0, 6) as (MissionStationLog | MissionSdsStatusLog)[]; const aircraftUser: PublicUser = typeof aircraft.publicUser === "string" ? JSON.parse(aircraft.publicUser) : aircraft.publicUser; @@ -103,10 +108,13 @@ const FMSStatusHistory = ({ - {entry.data.newFMSstatus} + {entry.type === "sds-status-log" ? entry.data.status : entry.data.newFMSstatus} {new Date(entry.timeStamp).toLocaleTimeString([], { @@ -126,6 +134,7 @@ const FMSStatusSelector = ({ }: { aircraft: ConnectedAircraft & { Station: Station }; }) => { + const session = useSession(); const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected"; const [hoveredStatus, setHoveredStatus] = useState(null); const queryClient = useQueryClient(); @@ -144,6 +153,20 @@ const FMSStatusSelector = ({ }, }); + const sendSdsStatusMutation = useMutation({ + mutationFn: async ({ sdsMessage }: { sdsMessage: MissionSdsStatusLog }) => { + if (!aircraft?.id) throw new Error("No connected aircraft"); + await sendSdsStatusMessageAPI({ sdsMessage, aircraftId: aircraft?.id }); + queryClient.invalidateQueries({ + queryKey: ["missions"], + }); + }, + }); + + if (!session.data?.user) { + return null; + } + return (
@@ -213,12 +236,21 @@ const FMSStatusSelector = ({ onMouseEnter={() => setHoveredStatus(status)} onMouseLeave={() => setHoveredStatus(null)} onClick={async () => { - await changeAircraftMutation.mutateAsync({ - id: aircraft.id, - update: { - fmsStatus: status, + await sendSdsStatusMutation.mutateAsync({ + sdsMessage: { + type: "sds-status-log", + auto: false, + timeStamp: new Date().toISOString(), + data: { + status: status, + direction: "to-aircraft", + stationId: aircraft.Station.id, + station: aircraft.Station, + user: getPublicUser(session.data?.user), + }, }, }); + toast.success(`SDS Status ${status} gesendet`); }} > {status} @@ -378,7 +410,9 @@ const SDSTab = ({ ?.slice() .reverse() .filter( - (entry) => entry.type === "sds-log" && entry.data.stationId === aircraft.Station.id, + (entry) => + (entry.type === "sds-log" || entry.type == "sds-status-log") && + entry.data.stationId === aircraft.Station.id, ) || [], [mission?.missionLog, aircraft.Station.id], ); @@ -471,7 +505,7 @@ const SDSTab = ({ )}
    {log.map((entry, index) => { - const sdsEntry = entry as MissionSdsLog; + const sdsEntry = entry as MissionSdsLog | MissionSdsStatusLog; return (
  • @@ -489,7 +523,9 @@ const SDSTab = ({ {sdsEntry.data.user.firstname?.[0]?.toUpperCase() ?? "?"} {sdsEntry.data.user.lastname?.[0]?.toUpperCase() ?? "?"} - {sdsEntry.data.message} + + {sdsEntry.type == "sds-log" ? sdsEntry.data.message : sdsEntry.data.status} +
  • ); })} diff --git a/apps/dispatch/app/_components/map/_components/MissionMarkerTabs.tsx b/apps/dispatch/app/_components/map/_components/MissionMarkerTabs.tsx index dcb042e1..a02ef87c 100644 --- a/apps/dispatch/app/_components/map/_components/MissionMarkerTabs.tsx +++ b/apps/dispatch/app/_components/map/_components/MissionMarkerTabs.tsx @@ -726,7 +726,11 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => { {entry.data.station.bosCallsign} ); - if (entry.type === "message-log" || entry.type === "sds-log") + if ( + entry.type === "message-log" || + entry.type === "sds-log" || + entry.type === "sds-status-log" + ) return (
  • @@ -741,9 +745,10 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => { color: FMS_STATUS_TEXT_COLORS[6], }} > - {entry.data.user.firstname?.[0]?.toUpperCase() ?? "?"} - {entry.data.user.lastname?.[0]?.toUpperCase() ?? "?"} - {entry.type === "sds-log" && ( + {entry.type == "sds-status-log" && entry.data.direction == "to-lst" + ? entry.data.station.bosCallsignShort + : `${entry.data.user.firstname?.[0]?.toUpperCase() ?? "?"}${entry.data.user.lastname?.[0]?.toUpperCase() ?? "?"}`} + {(entry.type === "sds-log" || entry.type === "sds-status-log") && ( <> { /> - {entry.data.station.bosCallsignShort} + {entry.type == "sds-status-log" && entry.data.direction == "to-aircraft" + ? entry.data.station.bosCallsignShort + : "LST"} )} - {entry.data.message} + + {entry.type === "sds-log" || entry.type === "message-log" + ? entry.data.message + : entry.data.status} +
  • ); if ( diff --git a/apps/dispatch/app/_data/fmsStatusDescription.ts b/apps/dispatch/app/_data/fmsStatusDescription.ts index 3ca69b0c..c83c4551 100644 --- a/apps/dispatch/app/_data/fmsStatusDescription.ts +++ b/apps/dispatch/app/_data/fmsStatusDescription.ts @@ -24,3 +24,30 @@ export const fmsStatusDescription: { [key: string]: string } = { o: "Warten, alle Abfrageplätze belegt", u: "Verstanden", }; + +export const fmsStatusDescriptionShort: { [key: string]: string } = { + NaN: "Keine D.", + "0": "Prio. Sprechen", + "1": "E.-bereit Funk", + "2": "E.-bereit Wache", + "3": "E.-übernahme", + "4": "Einsatzort", + "5": "Sprechwunsch", + "6": "Nicht e.-bereit", + "7": "Einsatzgeb.", + "8": "Bed. Verfügbar", + "9": "F-anmeldung", + E: "Einsatzabbruch", + C: "Melden Sie Einsatzübernahme", + F: "Kommen Sie über Draht", + H: "Fahren Sie Wache an", + J: "Sprechen Sie", + L: "Geben Sie Lagemeldung", + P: "Einsatz mit Polizei", + U: "Ungültige Statusfolge", + c: "Status korrigieren", + d: "Nennen Sie Transportziel", + h: "Zielklinik verständigt", + o: "Warten, alle Abfrageplätze belegt", + u: "Verstanden", +}; diff --git a/apps/dispatch/app/_data/livekitRooms.ts b/apps/dispatch/app/_data/livekitRooms.ts index 1bbb1ae0..0ed6c0b5 100644 --- a/apps/dispatch/app/_data/livekitRooms.ts +++ b/apps/dispatch/app/_data/livekitRooms.ts @@ -1 +1,7 @@ -export const ROOMS = ["LST_01", "LST_02", "LST_03", "LST_04", "LST_05"]; +export const ROOMS = [ + { name: "VAR_LST_RD_01", id: 2201 }, + { name: "VAR_LST_RD_02", id: 2202 }, + { name: "VAR_LST_RD_03", id: 2203 }, + { name: "VAR_LST_RD_04", id: 2204 }, + { name: "VAR_LST_RD_05", id: 2205 }, +]; diff --git a/apps/dispatch/app/_querys/missions.ts b/apps/dispatch/app/_querys/missions.ts index 240591c1..bab144c4 100644 --- a/apps/dispatch/app/_querys/missions.ts +++ b/apps/dispatch/app/_querys/missions.ts @@ -1,4 +1,4 @@ -import { Mission, MissionSdsLog, Prisma } from "@repo/db"; +import { Mission, MissionSdsLog, MissionSdsStatusLog, Prisma } from "@repo/db"; import axios from "axios"; import { serverApi } from "_helpers/axios"; @@ -29,6 +29,20 @@ export const editMissionAPI = async (id: number, mission: Prisma.MissionUpdateIn const respone = await serverApi.patch(`/mission/${id}`, mission); return respone.data; }; + +export const sendSdsStatusMessageAPI = async ({ + sdsMessage, + aircraftId, +}: { + aircraftId: number; + sdsMessage: MissionSdsStatusLog; +}) => { + const respone = await serverApi.post(`/aircrafts/${aircraftId}/send-sds-message`, { + sdsMessage, + }); + return respone.data; +}; + export const sendSdsMessageAPI = async ({ missionId, sdsMessage, diff --git a/apps/dispatch/app/_store/dispatch/connectionStore.ts b/apps/dispatch/app/_store/dispatch/connectionStore.ts index c7a646aa..2e5bb4c1 100644 --- a/apps/dispatch/app/_store/dispatch/connectionStore.ts +++ b/apps/dispatch/app/_store/dispatch/connectionStore.ts @@ -27,7 +27,7 @@ export const useDispatchConnectionStore = create((set) => ({ setHideDraftMissions: (hide) => set({ hideDraftMissions: hide }), connectedDispatcher: null, message: "", - selectedZone: "LST_01", + selectedZone: "VAR_LST_RD_01", logoffTime: "", ghostMode: false, connect: async (uid, selectedZone, logoffTime, ghostMode) => @@ -48,7 +48,7 @@ export const useDispatchConnectionStore = create((set) => ({ dispatchSocket.on("connect", () => { const { logoffTime, selectedZone, ghostMode } = useDispatchConnectionStore.getState(); - useAudioStore.getState().connect("LST_01", selectedZone || "Leitstelle"); + useAudioStore.getState().connect("VAR_LST_RD_01", selectedZone || "Leitstelle"); dispatchSocket.emit("connect-dispatch", { logoffTime, selectedZone, diff --git a/apps/dispatch/app/_store/pilot/MrtStore.ts b/apps/dispatch/app/_store/pilot/MrtStore.ts index f5d32dc6..0bf9eaf1 100644 --- a/apps/dispatch/app/_store/pilot/MrtStore.ts +++ b/apps/dispatch/app/_store/pilot/MrtStore.ts @@ -1,165 +1,82 @@ -import { MissionSdsLog, Station } from "@repo/db"; -import { fmsStatusDescription } from "_data/fmsStatusDescription"; -import { DisplayLineProps } from "(app)/pilot/_components/mrt/Mrt"; import { create } from "zustand"; -interface SetSdsPageParams { - page: "sds"; - station: Station; - sdsMessage: MissionSdsLog; +interface SetOffPageParams { + page: "off"; +} + +interface SetStartupPageParams { + page: "startup"; } interface SetHomePageParams { page: "home"; - station: Station; - fmsStatus: string; } -interface SetSendingStatusPageParams { - page: "sending-status"; - station: Station; +interface SetVoicecallPageParams { + page: "voice-call"; +} +interface SetSdsReceivedPopupParams { + popup: "sds-received"; } -interface SetNewStatusPageParams { - page: "new-status"; - station: Station; +interface SetStatusSentPopupParams { + popup: "status-sent"; } -type SetPageParams = +interface SetLoginPopupParams { + popup: "login"; +} + +interface SetSdsSentPopupParams { + popup: "sds-sent"; +} + +export type SetPageParams = | SetHomePageParams - | SetSendingStatusPageParams - | SetSdsPageParams - | SetNewStatusPageParams; + | SetOffPageParams + | SetStartupPageParams + | SetVoicecallPageParams; + +export type SetPopupParams = + | SetStatusSentPopupParams + | SetSdsSentPopupParams + | SetSdsReceivedPopupParams + | SetLoginPopupParams; + +interface StringifiedData { + sdsText?: string; + sentSdsText?: string; +} interface MrtStore { page: SetPageParams["page"]; + popup?: SetPopupParams["popup"]; - lines: DisplayLineProps[]; + stringifiedData: StringifiedData; + setStringifiedData: (data: Partial) => void; setPage: (pageData: SetPageParams) => void; - setLines: (lines: MrtStore["lines"]) => void; + setPopup: (popupData: SetPopupParams | null) => void; + + // internal + updateIntervall?: number; + nightMode: boolean; + setNightMode: (nightMode: boolean) => void; } export const useMrtStore = create((set) => ({ - page: "home", - pageData: { - message: "", + page: "off", + nightMode: false, + stringifiedData: {}, + setNightMode: (nightMode) => set({ nightMode }), + setStringifiedData: (data) => + set((state) => ({ + stringifiedData: { ...state.stringifiedData, ...data }, + })), + setPopup: (popupData) => { + set({ popup: popupData ? popupData.popup : undefined }); }, - lines: [ - { - textLeft: "VAR.#", - textSize: "2", - }, - { - textLeft: "No Data", - textSize: "3", - }, - ], - setLines: (lines) => set({ lines }), setPage: (pageData) => { - switch (pageData.page) { - case "home": { - const { station, fmsStatus } = pageData as SetHomePageParams; - set({ - page: "home", - lines: [ - { - textLeft: `${station?.bosCallsign}`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { textLeft: "ILS VAR#", textSize: "3" }, - { - textLeft: fmsStatus, - style: { fontWeight: "extrabold" }, - textSize: "4", - }, - { - textLeft: fmsStatusDescription[fmsStatus], - textSize: "1", - }, - ], - }); - break; - } - - case "sending-status": { - const { station } = pageData as SetSendingStatusPageParams; - set({ - page: "sending-status", - lines: [ - { - textLeft: `${station?.bosCallsign}`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { textLeft: "ILS VAR#", textSize: "3" }, - { - textMid: "sending...", - style: { fontWeight: "bold" }, - textSize: "4", - }, - { - textLeft: "Status wird gesendet...", - textSize: "1", - }, - ], - }); - break; - } - case "new-status": { - const { station } = pageData as SetNewStatusPageParams; - set({ - page: "new-status", - lines: [ - { - textLeft: `${station?.bosCallsign}`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { textLeft: "ILS VAR#", textSize: "3" }, - { - textLeft: "empfangen", - style: { fontWeight: "bold" }, - textSize: "4", - }, - ], - }); - break; - } - case "sds": { - const { sdsMessage } = pageData as SetSdsPageParams; - const msg = sdsMessage.data.message; - set({ - page: "sds", - lines: [ - { - textLeft: `SDS-Nachricht`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { - textLeft: msg, - style: { - whiteSpace: "normal", - overflowWrap: "break-word", - wordBreak: "break-word", - display: "block", - maxWidth: "100%", - maxHeight: "100%", - overflow: "auto", - textOverflow: "ellipsis", - lineHeight: "1.2em", - }, - textSize: "2", - }, - ], - }); - break; - } - default: - set({ page: "home" }); - break; - } + set({ page: pageData.page }); }, })); diff --git a/apps/dispatch/app/_store/pilot/connectionStore.ts b/apps/dispatch/app/_store/pilot/connectionStore.ts index e9fc29f8..1976dd40 100644 --- a/apps/dispatch/app/_store/pilot/connectionStore.ts +++ b/apps/dispatch/app/_store/pilot/connectionStore.ts @@ -86,7 +86,7 @@ pilotSocket.on("connect", () => { usePilotConnectionStore.setState({ status: "connected", message: "" }); const { logoffTime, selectedStation, debug } = usePilotConnectionStore.getState(); dispatchSocket.disconnect(); - useAudioStore.getState().connect("LST_01", selectedStation?.bosCallsignShort || "pilot"); + useAudioStore.getState().connect("VAR_LST_RD_01", selectedStation?.bosCallsignShort || "pilot"); pilotSocket.emit("connect-pilot", { logoffTime, @@ -142,11 +142,13 @@ pilotSocket.on("mission-alert", (data: Mission & { Stations: Station[] }) => { }); pilotSocket.on("sds-message", (sdsMessage: MissionSdsLog) => { + console.log("Received sds-message via socket:", sdsMessage); const station = usePilotConnectionStore.getState().selectedStation; if (!station) return; - useMrtStore.getState().setPage({ - page: "sds", - station, - sdsMessage, + useMrtStore.getState().setPopup({ + popup: "sds-received", + }); + useMrtStore.getState().setStringifiedData({ + sdsText: sdsMessage.data.message, }); }); diff --git a/apps/dispatch/app/globals.css b/apps/dispatch/app/globals.css index 876c3519..979f4a4d 100644 --- a/apps/dispatch/app/globals.css +++ b/apps/dispatch/app/globals.css @@ -9,6 +9,11 @@ src: url("/fonts/MelderV2.ttf") format("truetype"); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */ } +@font-face { + font-family: "Bahnschrift"; + src: url("/fonts/bahnschrift.ttf") format("truetype"); /* Chrome 4+, Firefox 3.5, Opera 10+, Safari 3—5 */ +} + @theme { --color-rescuetrack: #46b7a3; --color-rescuetrack-highlight: #ff4500; diff --git a/apps/dispatch/next-env.d.ts b/apps/dispatch/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/apps/dispatch/next-env.d.ts +++ b/apps/dispatch/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/dispatch/public/fonts/bahnschrift.ttf b/apps/dispatch/public/fonts/bahnschrift.ttf new file mode 100644 index 00000000..2c121b3d Binary files /dev/null and b/apps/dispatch/public/fonts/bahnschrift.ttf differ diff --git a/apps/dispatch/public/sounds/1504.wav b/apps/dispatch/public/sounds/1504.wav new file mode 100644 index 00000000..edde45b8 Binary files /dev/null and b/apps/dispatch/public/sounds/1504.wav differ diff --git a/apps/dispatch/public/sounds/403.wav b/apps/dispatch/public/sounds/403.wav new file mode 100644 index 00000000..3d855dc6 Binary files /dev/null and b/apps/dispatch/public/sounds/403.wav differ diff --git a/apps/dispatch/public/sounds/775.wav b/apps/dispatch/public/sounds/775.wav new file mode 100644 index 00000000..bb406ab5 Binary files /dev/null and b/apps/dispatch/public/sounds/775.wav differ diff --git a/packages/database/prisma/json/MissionVehicleLog.ts b/packages/database/prisma/json/MissionVehicleLog.ts index 7bceda67..c7de840c 100644 --- a/packages/database/prisma/json/MissionVehicleLog.ts +++ b/packages/database/prisma/json/MissionVehicleLog.ts @@ -1,4 +1,5 @@ import { Station } from "../../generated/client"; +import { StationStatus } from "./SocketEvents"; import { PublicUser } from "./User"; export interface MissionVehicleLog { @@ -37,6 +38,19 @@ export interface MissionSdsLog { }; } +export interface MissionSdsStatusLog { + type: "sds-status-log"; + auto: false; + timeStamp: string; + data: { + direction: "to-lst" | "to-aircraft"; + stationId: number; + station: Station; + user: PublicUser; + status: string; + }; +} + export interface MissionMessageLog { type: "message-log"; auto: false; @@ -90,4 +104,5 @@ export type MissionLog = | MissionAlertLogAuto | MissionCompletedLog | MissionVehicleLog - | MissionReopenedLog; + | MissionReopenedLog + | MissionSdsStatusLog;