diff --git a/apps/dispatch-server/socket-events/connect-pilot.ts b/apps/dispatch-server/socket-events/connect-pilot.ts index 5d82a755..f67ed868 100644 --- a/apps/dispatch-server/socket-events/connect-pilot.ts +++ b/apps/dispatch-server/socket-events/connect-pilot.ts @@ -100,7 +100,7 @@ export const handleConnectPilot = }, }) .catch(console.error); - io.to("dispatchers").emit("pilots-update"); + io.to("dispatchers").emit("update-connectedAircraft"); io.to("pilots").emit("pilots-update"); }); } catch (error) { diff --git a/apps/dispatch/app/_components/QueryProvider.tsx b/apps/dispatch/app/_components/QueryProvider.tsx index b47b0b53..6e4c9a21 100644 --- a/apps/dispatch/app/_components/QueryProvider.tsx +++ b/apps/dispatch/app/_components/QueryProvider.tsx @@ -30,7 +30,10 @@ export function QueryProvider({ children }: { children: ReactNode }) { const invalidateConnectedUsers = () => { queryClient.invalidateQueries({ - queryKey: ["connected-users", "aircrafts"], + queryKey: ["connected-users"], + }); + queryClient.invalidateQueries({ + queryKey: ["aircrafts"], }); }; diff --git a/apps/dispatch/app/_data/fmsStatusDescription.ts b/apps/dispatch/app/_data/fmsStatusDescription.ts new file mode 100644 index 00000000..75b77e28 --- /dev/null +++ b/apps/dispatch/app/_data/fmsStatusDescription.ts @@ -0,0 +1,26 @@ +export const fmsStatusDescription: { [key: string]: string } = { + NaN: "Keine Daten", + "0": "Prio. Sprechwunsch", + "1": "Frei auf Funk", + "2": "Einsatzbereit am LRZ", + "3": "Auf dem Weg", + "4": "Am Einsatzort", + "5": "Sprechwunsch", + "6": "Nicht einsatzbereit", + "7": "Patient aufgenommen", + "8": "Am Transportziel", + "9": "Fremdanmeldung", + E: "Indent/Abbruch/Einsatzbefehl abgebrochen", + C: "Anmelden zur Übernahme des Einsatzes", + F: "Kommen über Draht", + H: "Fahren auf Wache", + J: "Sprechaufforderung", + L: "Lagebericht abgeben", + P: "Einsatz mit Polizei/Pause machen", + U: "Ungültiger Status", + c: "Status korrigieren", + d: "Transportziel angeben", + h: "Zielklinik verständigt", + o: "Warten, alle Abfrageplätze belegt", + u: "Verstanden", +}; diff --git a/apps/dispatch/app/_store/dispatch/connectionStore.ts b/apps/dispatch/app/_store/dispatch/connectionStore.ts index 411dbd05..daf7f225 100644 --- a/apps/dispatch/app/_store/dispatch/connectionStore.ts +++ b/apps/dispatch/app/_store/dispatch/connectionStore.ts @@ -5,6 +5,7 @@ interface ConnectionStore { status: "connected" | "disconnected" | "connecting" | "error"; message: string; selectedZone: string; + logoffTime: string; connect: ( uid: string, selectedZone: string, @@ -17,6 +18,7 @@ export const useDispatchConnectionStore = create((set) => ({ status: "disconnected", message: "", selectedZone: "LST_01", + logoffTime: "", connect: async (uid, selectedZone, logoffTime) => new Promise((resolve) => { set({ status: "connecting", message: "" }); @@ -58,3 +60,12 @@ dispatchSocket.on("force-disconnect", (reason: string) => { message: reason, }); }); + +dispatchSocket.on("reconnect", () => { + const { logoffTime, selectedZone } = useDispatchConnectionStore.getState(); + + 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 43d7abc9..55aac686 100644 --- a/apps/dispatch/app/_store/pilot/MrtStore.ts +++ b/apps/dispatch/app/_store/pilot/MrtStore.ts @@ -1,25 +1,36 @@ +import { Station } from "@repo/db"; +import { fmsStatusDescription } from "_data/fmsStatusDescription"; import { DisplayLineProps } from "pilot/_components/mrt/Mrt"; import { create } from "zustand"; import { syncTabs } from "zustand-sync-tabs"; -type Page = "home" | "sending-status" | "new-status" | "error"; +interface SetHomePageParams { + page: "home"; + station: Station; + fmsStatus: string; +} -type PageData = { - home: undefined; - "sending-status": undefined; - "new-status": undefined; - error: { - message: string; - }; -}; +interface SetSendingStatusPageParams { + page: "sending-status"; + station: Station; +} + +interface SetNewStatusPageParams { + page: "new-status"; + station: Station; +} + +type SetPageParams = + | SetHomePageParams + | SetSendingStatusPageParams + | SetNewStatusPageParams; interface MrtStore { - page: Page; - pageData: PageData[Page]; + page: SetPageParams["page"]; lines: DisplayLineProps[]; - setPage:

(page: P, pageData?: PageData[P]) => void; + setPage: (pageData: SetPageParams) => void; setLines: (lines: MrtStore["lines"]) => void; } @@ -30,14 +41,93 @@ export const useMrtStore = create( pageData: { message: "", }, - lines: Array.from(Array(10).keys()).map(() => ({ - textLeft: "", - textMid: "", - textRight: "", - textSize: "1", - })), + lines: [ + { + textLeft: "VAR.#", + textSize: "2", + }, + { + textLeft: "No Data", + textSize: "3", + }, + ], setLines: (lines) => set({ lines }), - setPage: (page, pageData) => set({ page, pageData }), + setPage: (pageData) => { + switch (pageData.page) { + case "home": { + const { station, fmsStatus } = pageData as SetHomePageParams; + set({ + page: "home", + lines: [ + { + textLeft: `VAR#.${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: `VAR#.${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: `VAR#.${station?.bosCallsign}`, + style: { fontWeight: "bold" }, + textSize: "2", + }, + { textLeft: "ILS VAR#", textSize: "3" }, + { + textLeft: "new status received", + style: { fontWeight: "bold" }, + textSize: "4", + }, + ], + }); + break; + } + default: + set({ page: "home" }); + break; + } + }, }), { name: "mrt-store", // unique name diff --git a/apps/dispatch/app/_store/pilot/connectionStore.ts b/apps/dispatch/app/_store/pilot/connectionStore.ts index 7259b945..67b69dc2 100644 --- a/apps/dispatch/app/_store/pilot/connectionStore.ts +++ b/apps/dispatch/app/_store/pilot/connectionStore.ts @@ -2,7 +2,7 @@ import { create } from "zustand"; import { dispatchSocket } from "../../dispatch/socket"; import { ConnectedAircraft, Mission, Station } from "@repo/db"; import { pilotSocket } from "pilot/socket"; -import { useMrtStore } from "_store/pilot/MrtStore"; +import { useDmeStore } from "_store/pilot/dmeStore"; interface ConnectionStore { status: "connected" | "disconnected" | "connecting" | "error"; @@ -75,11 +75,14 @@ pilotSocket.on("aircraft-update", (data) => { usePilotConnectionStore.setState({ connectedAircraft: data, }); - useMrtStore.getState().setLines(getNew); + /* useMrtStore.getState().setLines(getNew); */ }); -pilotSocket.on("mission-alert", (data) => { +pilotSocket.on("mission-alert", (data: Mission & { Stations: Station[] }) => { usePilotConnectionStore.setState({ activeMission: data, }); + useDmeStore.getState().setPage({ + page: "new-mission", + }); }); diff --git a/apps/dispatch/app/_store/pilot/dmeStore.ts b/apps/dispatch/app/_store/pilot/dmeStore.ts new file mode 100644 index 00000000..e7a20bcb --- /dev/null +++ b/apps/dispatch/app/_store/pilot/dmeStore.ts @@ -0,0 +1,195 @@ +import { Mission, Station, User } from "@repo/db"; +import { DisplayLineProps } from "pilot/_components/dme/Dme"; +import { create } from "zustand"; +import { syncTabs } from "zustand-sync-tabs"; + +interface SetHomePageParams { + page: "home"; + user: User; + station: Station; +} + +interface SetErrorPageParams { + page: "error"; + error: string; +} + +interface SetNewMissionPageParams { + page: "new-mission"; +} + +interface SetMissionPageParams { + page: "mission"; + mission: Mission & { Stations: Station[] }; +} + +interface SetAcknowledgePageParams { + page: "acknowledge"; +} + +type SetPageParams = + | SetHomePageParams + | SetNewMissionPageParams + | SetMissionPageParams + | SetErrorPageParams + | SetAcknowledgePageParams; + +interface MrtStore { + page: SetPageParams["page"]; + + lines: DisplayLineProps[]; + + setPage: (pageData: SetPageParams) => void; + setLines: (lines: MrtStore["lines"]) => void; +} + +export const useDmeStore = create( + syncTabs( + (set) => ({ + page: "home", + pageData: { + message: "", + }, + lines: [ + { + textLeft: "VAR.#", + textSize: "2", + }, + { + textLeft: "No Data", + }, + ], + setLines: (lines) => set({ lines }), + setPage: (pageData) => { + switch (pageData.page) { + case "home": { + set({ + page: "home", + lines: [ + { textMid: "⠀" }, + { + textMid: pageData.station.bosCallsign + ? `VAR#.${pageData.station.bosCallsign}` + : "no Data", + style: { fontWeight: "bold" }, + }, + { textMid: "⠀" }, + { + textMid: new Date().toLocaleDateString(), + }, + { + textMid: new Date().toLocaleTimeString(), + style: { fontWeight: "bold" }, + }, + { textMid: "⠀" }, + { + textMid: `${pageData.user.lastname} ${pageData.user.firstname}`, + }, + { textMid: "⠀" }, + ], + }); + break; + } + + case "new-mission": { + set({ + page: "new-mission", + lines: [ + { textMid: "⠀" }, + { + textMid: "new mission received", + style: { fontWeight: "bold" }, + }, + { textMid: "⠀" }, + ], + }); + break; + } + case "mission": { + set({ + page: "mission", + lines: [ + { + textLeft: `${pageData.mission.missionKeywordAbbreviation}`, + textRight: pageData.mission.Stations.map( + (s) => s.bosCallsignShort, + ).join(","), + style: { fontWeight: "bold" }, + }, + { + textMid: `${pageData.mission.missionKeywordName}`, + style: { fontWeight: "bold" }, + }, + { textLeft: `${pageData.mission.addressStreet}` }, + { + textLeft: `${pageData.mission.addressZip} ${pageData.mission.addressCity}`, + }, + { + textMid: "Patienteninfos:", + style: { fontWeight: "bold" }, + }, + { + textLeft: + pageData.mission.missionPatientInfo || "keine Daten", + }, + { + textMid: "Weitere Infos:", + style: { fontWeight: "bold" }, + }, + { + textLeft: + pageData.mission.missionAdditionalInfo || "keine Daten", + }, + ], + }); + break; + } + case "error": { + set({ + page: "error", + lines: [ + { textMid: "Fehler:" }, + { + textMid: pageData.error, + style: { fontWeight: "bold" }, + }, + { textMid: "⠀" }, + ], + }); + break; + } + case "acknowledge": { + set({ + page: "acknowledge", + lines: [ + { textMid: "⠀" }, + { + textMid: "Einsatz angenommen", + style: { fontWeight: "bold" }, + }, + { textMid: "⠀" }, + ], + }); + break; + } + default: + set({ + page: "error", + lines: [ + { textMid: "Fehler:" }, + { + textMid: `Unbekannte Seite`, + style: { fontWeight: "bold" }, + }, + { textMid: "⠀" }, + ], + }); + break; + } + }, + }), + { + name: "dme-store", // unique name + }, + ), +); diff --git a/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx b/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx index 6e390301..a2568240 100644 --- a/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx +++ b/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx @@ -107,7 +107,7 @@ const AircraftPopupContent = ({ return missions?.find( (m) => (m.state === "running" || m.state === "draft") && - m.missionStationIds.includes(aircraft.Station.id.toString()), + m.missionStationIds.includes(aircraft.Station.id), ); }, [missions, aircraft.Station.id]); diff --git a/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx b/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx index ed5fcd90..61ccee40 100644 --- a/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx +++ b/apps/dispatch/app/dispatch/_components/map/_components/MissionMarkerTabs.tsx @@ -16,6 +16,7 @@ import { Trash, User, SmartphoneNfc, + CheckCheck, } from "lucide-react"; import { getPublicUser, @@ -23,15 +24,18 @@ import { MissionLog, MissionMessageLog, Prisma, + Station, } from "@repo/db"; import { usePannelStore } from "_store/pannelStore"; import { useSession } from "next-auth/react"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { deleteMissionAPI, editMissionAPI, sendMissionAPI, } from "querys/missions"; +import { getConnectedAircraftsAPI } from "querys/aircrafts"; +import { getStationsAPI } from "querys/stations"; const Einsatzdetails = ({ mission }: { mission: Mission }) => { const queryClient = useQueryClient(); @@ -58,6 +62,22 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => { }); }, }); + const editMissionMutation = useMutation({ + mutationKey: ["missions"], + mutationFn: ({ + id, + mission, + }: { + id: number; + mission: Prisma.MissionUpdateInput; + }) => editMissionAPI(id, mission), + onSuccess: () => { + toast.success("Gespeichert"); + queryClient.invalidateQueries({ + queryKey: ["missions"], + }); + }, + }); const { setMissionFormValues, setOpen } = usePannelStore((state) => state); return (

@@ -66,28 +86,48 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => { Einsatzdetails {mission.state !== "draft" && ( -
- + +
+
+ +
)} @@ -177,46 +217,84 @@ const Patientdetails = ({ mission }: { mission: Mission }) => { }; const Rettungsmittel = ({ mission }: { mission: Mission }) => { - /* const [stations, setStations] = useState([]); - useEffect(() => { - getStations().then((data) => { - setStations(data); - }); - }, []); */ - // Mockup data - const mockupData = [ - { bosCallsign: "Christoph 31", FMSstatus: 2, min: 6 }, - { bosCallsign: "RTW", FMSstatus: 3, min: 2 }, - { bosCallsign: "Polizei", FMSstatus: 4, min: 0 }, - ]; + const { data: conenctedAircrafts } = useQuery({ + queryKey: ["aircrafts"], + queryFn: getConnectedAircraftsAPI, + }); + const { data: stations } = useQuery({ + queryKey: ["mission", "stations-mission", mission.id], + queryFn: () => + getStationsAPI({ + id: { + in: mission.missionStationIds, + }, + }), + }); + + const sendAlertMutation = useMutation({ + mutationKey: ["missions"], + mutationFn: sendMissionAPI, + onError: (error) => { + console.error(error); + toast.error("Fehler beim Alarmieren"); + }, + onSuccess: (data) => { + toast.success(data.message); + }, + }); return (
-

- Rettungsmittel -

+
+

+ Rettungsmittel +

+
+ +
+
    - {mockupData.map((item, index) => ( -
  • - - {item.FMSstatus} - - - {item.bosCallsign} - {item.min > 0 && ( - <> -
    - Ankunft in ca. {item.min} min - + {stations?.map((station, index) => { + const connectedAircraft = conenctedAircrafts?.find( + (aircraft) => aircraft.stationId === station.id, + ); + + return ( +
  • + {connectedAircraft && ( + + {connectedAircraft.fmsStatus} + )} - -
  • - ))} + +
    + {station.bosCallsign} + {/* {item.min > 0 && ( + <> +
    + Ankunft in ca. {item.min} min + + )} */} +
    +
    + + ); + })}
diff --git a/apps/dispatch/app/pilot/_components/dme/Dme.tsx b/apps/dispatch/app/pilot/_components/dme/Dme.tsx new file mode 100644 index 00000000..7e9e9888 --- /dev/null +++ b/apps/dispatch/app/pilot/_components/dme/Dme.tsx @@ -0,0 +1,130 @@ +import { CSSProperties } from "react"; +import sQuadImageNoReflections from "./Melder_NoReflections.png"; +import sQuadReflection from "./reflektion.png"; +import { useButtons } from "./useButtons"; +import { useSounds } from "./useSounds"; +import Image from "next/image"; +import { useDmeStore } from "_store/pilot/dmeStore"; + +const DME_BUTTON_STYLES: CSSProperties = { + cursor: "pointer", + zIndex: "9999", + backgroundColor: "transparent", + border: "none", +}; + +export interface DisplayLineProps { + style?: CSSProperties; + textLeft?: string; + textMid?: string; + textRight?: string; +} + +const DisplayLine = ({ + style = {}, + textLeft, + textMid, + textRight, +}: DisplayLineProps) => { + const INNER_TEXT_PARTS: CSSProperties = { + fontFamily: "Melder", + flex: "1", + flexBasis: "auto", + overflowWrap: "break-word", + }; + + return ( +
+ {textLeft} + + {textMid} + + {textRight} +
+ ); +}; + +export const Dme = () => { + useSounds(); + const { handleButton } = useButtons(); + const lines = useDmeStore((state) => state.lines); + + return ( +
+ sQuadImage +
+ ); +}; diff --git a/apps/dispatch/app/pilot/_components/dme/Melder_NoReflections.png b/apps/dispatch/app/pilot/_components/dme/Melder_NoReflections.png new file mode 100644 index 00000000..d7adff64 Binary files /dev/null and b/apps/dispatch/app/pilot/_components/dme/Melder_NoReflections.png differ diff --git a/apps/dispatch/app/pilot/_components/dme/reflektion.png b/apps/dispatch/app/pilot/_components/dme/reflektion.png new file mode 100644 index 00000000..63eca3d1 Binary files /dev/null and b/apps/dispatch/app/pilot/_components/dme/reflektion.png differ diff --git a/apps/dispatch/app/pilot/_components/dme/squad-x15.jpg b/apps/dispatch/app/pilot/_components/dme/squad-x15.jpg new file mode 100644 index 00000000..e90769c4 Binary files /dev/null and b/apps/dispatch/app/pilot/_components/dme/squad-x15.jpg differ diff --git a/apps/dispatch/app/pilot/_components/dme/useButtons.ts b/apps/dispatch/app/pilot/_components/dme/useButtons.ts new file mode 100644 index 00000000..b3064c4e --- /dev/null +++ b/apps/dispatch/app/pilot/_components/dme/useButtons.ts @@ -0,0 +1,33 @@ +import { usePilotConnectionStore } from "_store/pilot/connectionStore"; +import { useDmeStore } from "_store/pilot/dmeStore"; + +import { useSession } from "next-auth/react"; +export const useButtons = () => { + const { page, setPage } = useDmeStore((state) => state); + const user = useSession().data?.user; + const station = usePilotConnectionStore((state) => state.selectedStation); + + const handleButton = (button: "main" | "menu" | "left" | "right") => () => { + switch (button) { + case "main": + if (page === "mission") { + setPage({ page: "acknowledge" }); + } + break; + case "menu": + console.log("home", page, { station, user }); + if (station && user) { + setPage({ page: "home", station, user }); + } else { + setPage({ page: "error", error: "No station or user found" }); + } + break; + default: + setPage({ page: "error", error: "Button now allowed" }); + + break; + } + }; + + return { handleButton }; +}; diff --git a/apps/dispatch/app/pilot/_components/dme/useSounds.ts b/apps/dispatch/app/pilot/_components/dme/useSounds.ts new file mode 100644 index 00000000..19840e68 --- /dev/null +++ b/apps/dispatch/app/pilot/_components/dme/useSounds.ts @@ -0,0 +1,52 @@ +"use client"; +import { usePilotConnectionStore } from "_store/pilot/connectionStore"; +import { useDmeStore } from "_store/pilot/dmeStore"; +import { useEffect, useRef } from "react"; + +export const useSounds = () => { + const { page, setPage } = useDmeStore((state) => state); + const mission = usePilotConnectionStore((state) => state.activeMission); + + const newMissionSound = useRef(null); + + useEffect(() => { + if (typeof window !== "undefined") { + newMissionSound.current = new Audio("/sounds/Melder3.wav"); + } + }, []); + + useEffect(() => { + const timeouts: NodeJS.Timeout[] = []; + + if (page === "new-mission" && newMissionSound.current) { + console.log("new-mission", mission); + newMissionSound.current.currentTime = 0; + newMissionSound.current.volume = 0.3; + newMissionSound.current.play(); + if (mission) { + timeouts.push( + setTimeout(() => setPage({ page: "mission", mission }), 500), + ); + } + } else if (page === "acknowledge") { + newMissionSound.current?.pause(); + if (mission) { + timeouts.push( + setTimeout(() => setPage({ page: "mission", mission }), 500), + ); + } else { + timeouts.push( + setTimeout( + () => setPage({ page: "error", error: "Einsatz nicht gebunden" }), + 500, + ), + ); + } + } + return () => { + timeouts.forEach((t) => { + clearTimeout(t); + }); + }; + }, [page, setPage, mission]); +}; diff --git a/apps/dispatch/app/pilot/_components/mrt/Mrt.tsx b/apps/dispatch/app/pilot/_components/mrt/Mrt.tsx index 046c0554..711c0eff 100644 --- a/apps/dispatch/app/pilot/_components/mrt/Mrt.tsx +++ b/apps/dispatch/app/pilot/_components/mrt/Mrt.tsx @@ -1,4 +1,4 @@ -import { CSSProperties } from "react"; +import { CSSProperties, useEffect } from "react"; import MrtImage from "./MRT.png"; import { useButtons } from "./useButtons"; import { useSounds } from "./useSounds"; diff --git a/apps/dispatch/app/pilot/_components/mrt/useButtons.ts b/apps/dispatch/app/pilot/_components/mrt/useButtons.ts index eaf8fc42..471536f8 100644 --- a/apps/dispatch/app/pilot/_components/mrt/useButtons.ts +++ b/apps/dispatch/app/pilot/_components/mrt/useButtons.ts @@ -1,115 +1,23 @@ -import { useSession } from "next-auth/react"; +import { ConnectedAircraft } from "@repo/db"; import { usePilotConnectionStore } from "_store/pilot/connectionStore"; import { useMrtStore } from "_store/pilot/MrtStore"; +import { pilotSocket } from "pilot/socket"; import { editConnectedAircraftAPI } from "querys/aircrafts"; -import { Station } from "@repo/db"; -import { DisplayLineProps } from "pilot/_components/mrt/Mrt"; - -export const fmsStatusDescription: { [key: string]: string } = { - NaN: "Keine Daten", - "0": "Prio. Sprechwunsch", - "1": "Frei auf Funk", - "2": "Einsatzbereit am LRZ", - "3": "Auf dem Weg", - "4": "Am Einsatzort", - "5": "Sprechwunsch", - "6": "Nicht einsatzbereit", - "7": "Patient aufgenommen", - "8": "Am Transportziel", - "9": "Fremdanmeldung", - E: "Indent/Abbruch/Einsatzbefehl abgebrochen", - C: "Anmelden zur Übernahme des Einsatzes", - F: "Kommen über Draht", - H: "Fahren auf Wache", - J: "Sprechaufforderung", - L: "Lagebericht abgeben", - P: "Einsatz mit Polizei/Pause machen", - U: "Ungültiger Status", - c: "Status korrigieren", - d: "Transportziel angeben", - h: "Zielklinik verständigt", - o: "Warten, alle Abfrageplätze belegt", - u: "Verstanden", -}; - -export const getSendingLines = (station: Station): DisplayLineProps[] => { - return [ - { - textLeft: `VAR#.${station?.bosCallsign}123`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { textLeft: "ILS VAR#", textSize: "3" }, - { - textMid: "sending...", - style: { fontWeight: "bold" }, - textSize: "4", - }, - { - textLeft: "Status wird gesendet...", - textSize: "1", - }, - ]; -}; - -export const getHomeLines = ( - station: Station, - fmsStatus: string, -): DisplayLineProps[] => { - return [ - { - textLeft: `VAR#.${station?.bosCallsign}`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { textLeft: "ILS VAR#", textSize: "3" }, - { - textLeft: fmsStatus, - style: { fontWeight: "extrabold" }, - textSize: "4", - }, - { - textLeft: fmsStatusDescription[fmsStatus], - textSize: "1", - }, - ]; -}; - -export const getNewStatusLines = ( - station: Station, - fmsStatus: string, -): DisplayLineProps[] => { - return [ - { - textLeft: `VAR#.${station?.bosCallsign}`, - style: { fontWeight: "bold" }, - textSize: "2", - }, - { textLeft: "ILS VAR#", textSize: "3" }, - { - textLeft: fmsStatus, - style: { fontWeight: "extrabold" }, - textSize: "4", - }, - { - textLeft: fmsStatusDescription[fmsStatus], - textSize: "1", - }, - ]; -}; +import { useEffect } from "react"; export const useButtons = () => { - const user = useSession().data?.user; const station = usePilotConnectionStore((state) => state.selectedStation); const connectedAircraft = usePilotConnectionStore( (state) => state.connectedAircraft, ); + const connectionStatus = usePilotConnectionStore((state) => state.status); - const { page, setLines } = useMrtStore((state) => state); + const { page, setPage } = useMrtStore((state) => state); const handleButton = (button: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0") => () => { + if (connectionStatus !== "connected") return; if (!station) return; if (!connectedAircraft?.id) return; if ( @@ -125,16 +33,32 @@ export const useButtons = () => { button === "0" ) { if (page !== "home") return; - setLines(getSendingLines(station)); + setPage({ page: "sending-status", station }); setTimeout(async () => { await editConnectedAircraftAPI(connectedAircraft!.id, { fmsStatus: button, }); - setLines(getNewStatusLines(station, button)); + setPage({ + page: "home", + station, + fmsStatus: button, + }); }, 1000); } }; + useEffect(() => { + pilotSocket.on("connect", () => { + if (!station) return; + setPage({ page: "home", fmsStatus: "6", station }); + }); + + pilotSocket.on("aircraft-update", () => { + if (!station) return; + setPage({ page: "new-status", station }); + }); + }, [setPage, station]); + return { handleButton }; }; diff --git a/apps/dispatch/app/pilot/_components/mrt/useSounds.ts b/apps/dispatch/app/pilot/_components/mrt/useSounds.ts index 0198b1e2..e4bc7e5e 100644 --- a/apps/dispatch/app/pilot/_components/mrt/useSounds.ts +++ b/apps/dispatch/app/pilot/_components/mrt/useSounds.ts @@ -1,66 +1,53 @@ "use client"; import { usePilotConnectionStore } from "_store/pilot/connectionStore"; import { useMrtStore } from "_store/pilot/MrtStore"; -import { editConnectedAircraftAPI } from "querys/aircrafts"; import { useEffect, useRef } from "react"; -const MRTstatusSound = new Audio("/sounds/MRT-status.mp3"); -const MrtMessageReceivedSound = new Audio("/sounds/MRT-message-received.mp3"); - export const useSounds = () => { const mrtState = useMrtStore((state) => state); const { connectedAircraft, selectedStation } = usePilotConnectionStore( (state) => state, ); - const fmsStatus = connectedAircraft?.fmsStatus || "NaN"; - - const previousFmsStatus = useRef(fmsStatus || "6"); - const timeout = useRef(null); + const setPage = useMrtStore((state) => state.setPage); + const MRTstatusSoundRef = useRef(null); + const MrtMessageReceivedSoundRef = useRef(null); useEffect(() => { - const handleSoundEnd = () => { - mrtState.setPage("home"); - }; + if (typeof window !== "undefined") { + MRTstatusSoundRef.current = new Audio("/sounds/MRT-status.mp3"); + MrtMessageReceivedSoundRef.current = new Audio( + "/sounds/MRT-message-received.mp3", + ); + MRTstatusSoundRef.current.onended = () => { + if (!selectedStation || !connectedAircraft?.fmsStatus) return; + setPage({ + page: "home", + station: selectedStation, + fmsStatus: connectedAircraft?.fmsStatus, + }); + }; + MrtMessageReceivedSoundRef.current.onended = () => { + if (!selectedStation || !connectedAircraft?.fmsStatus) return; + setPage({ + page: "home", + station: selectedStation, + fmsStatus: connectedAircraft?.fmsStatus, + }); + }; + } + }, [connectedAircraft?.fmsStatus, selectedStation, setPage]); - const playSound = (sound: HTMLAudioElement) => { - sound.play(); - sound.addEventListener("ended", handleSoundEnd); - }; + const fmsStatus = connectedAircraft?.fmsStatus || "NaN"; + useEffect(() => { if (!connectedAircraft) return; if (mrtState.page === "new-status") { - if (fmsStatus === "J") { - playSound(MrtMessageReceivedSound); - - timeout.current = setTimeout(() => { - editConnectedAircraftAPI(connectedAircraft.id, { - fmsStatus: previousFmsStatus.current, - }); - }, 5000); - } else if (previousFmsStatus.current !== fmsStatus) { - playSound(MRTstatusSound); + if (fmsStatus === "J" || fmsStatus === "c") { + MrtMessageReceivedSoundRef.current?.play(); } else { - handleSoundEnd(); - } - if (!timeout.current) { - previousFmsStatus.current = fmsStatus || "6"; + MRTstatusSoundRef.current?.play(); } } - return () => { - if (timeout.current) clearTimeout(timeout.current); - [MRTstatusSound, MrtMessageReceivedSound].forEach((sound) => { - sound.removeEventListener("ended", handleSoundEnd); - sound.pause(); - sound.currentTime = 0; - }); - }; - }, [ - mrtState, - fmsStatus, - connectedAircraft, - selectedStation, - previousFmsStatus, - timeout, - ]); + }, [mrtState, fmsStatus, connectedAircraft, selectedStation]); }; diff --git a/apps/dispatch/app/pilot/_components/navbar/Connection.tsx b/apps/dispatch/app/pilot/_components/navbar/Connection.tsx index 099dc21e..1765dcbb 100644 --- a/apps/dispatch/app/pilot/_components/navbar/Connection.tsx +++ b/apps/dispatch/app/pilot/_components/navbar/Connection.tsx @@ -67,7 +67,7 @@ export const ConnectionBtn = () => { ) : ( -

Als Disponent anmelden

+

Als Pilot anmelden

)}
-
{JSON.stringify(activeMission)}
-
+
+
); diff --git a/apps/dispatch/public/sounds/Melder3.wav b/apps/dispatch/public/sounds/Melder3.wav new file mode 100644 index 00000000..018f6b40 Binary files /dev/null and b/apps/dispatch/public/sounds/Melder3.wav differ diff --git a/grafana/grafana.db b/grafana/grafana.db index 82d639ca..bb578f1b 100644 Binary files a/grafana/grafana.db and b/grafana/grafana.db differ diff --git a/packages/database/prisma/schema/user.prisma b/packages/database/prisma/schema/user.prisma index 1003128a..c1a8a350 100644 --- a/packages/database/prisma/schema/user.prisma +++ b/packages/database/prisma/schema/user.prisma @@ -20,15 +20,18 @@ enum PERMISSION { } model User { - id String @id @default(uuid()) - publicId String @unique - firstname String - lastname String - email String @unique - password String - vatsimCid Int? @map(name: "vatsim_cid") - moodleId Int? @map(name: "moodle_id") - emailVerified DateTime? @map(name: "email_verified") + id String @id @default(uuid()) + publicId String @unique + firstname String + lastname String + email String @unique + password String + vatsimCid Int? @map(name: "vatsim_cid") + moodleId Int? @map(name: "moodle_id") + emailVerified DateTime? @map(name: "email_verified") + + // Settings: + image String? badges BADGES[] @default([]) permissions PERMISSION[] @default([])