added DME, fixed sync and bugs. Rewrote setDisplay logic

This commit is contained in:
PxlLoewe
2025-05-19 22:23:15 -07:00
parent 46c58ae127
commit 61e7caf6c8
23 changed files with 776 additions and 242 deletions

View File

@@ -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) {

View File

@@ -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"],
});
};

View File

@@ -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",
};

View File

@@ -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<ConnectionStore>((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,
});
});

View File

@@ -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: <P extends Page>(page: P, pageData?: PageData[P]) => void;
setPage: (pageData: SetPageParams) => void;
setLines: (lines: MrtStore["lines"]) => void;
}
@@ -30,14 +41,93 @@ export const useMrtStore = create<MrtStore>(
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

View File

@@ -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",
});
});

View File

@@ -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<MrtStore>(
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
},
),
);

View File

@@ -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]);

View File

@@ -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 (
<div className="p-4 text-base-content">
@@ -66,6 +86,7 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
<Flag /> Einsatzdetails
</h2>
{mission.state !== "draft" && (
<div className="space-x-2">
<div
className="tooltip tooltip-primary tooltip-left font-semibold"
data-tip="Einsatzdaten übernehmen"
@@ -89,6 +110,25 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
<Repeat2 size={16} />
</button>
</div>
<div
className="tooltip tooltip-warning tooltip-left font-semibold z-[9999]"
data-tip="Einsatz abschließen"
>
<button
className="btn btn-xs btn-warning flex items-center gap-2"
onClick={() => {
editMissionMutation.mutate({
id: mission.id,
mission: {
state: "finished",
},
});
}}
>
<CheckCheck size={16} />
</button>
</div>
</div>
)}
</div>
<ul className="text-base-content font-semibold">
@@ -177,46 +217,84 @@ const Patientdetails = ({ mission }: { mission: Mission }) => {
};
const Rettungsmittel = ({ mission }: { mission: Mission }) => {
/* const [stations, setStations] = useState<Station[]>([]);
useEffect(() => {
getStations().then((data) => {
setStations(data);
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);
},
});
}, []); */
// Mockup data
const mockupData = [
{ bosCallsign: "Christoph 31", FMSstatus: 2, min: 6 },
{ bosCallsign: "RTW", FMSstatus: 3, min: 2 },
{ bosCallsign: "Polizei", FMSstatus: 4, min: 0 },
];
return (
<div className="p-4 text-base-content">
<div className="flex items-center w-full justify-between">
<h2 className="flex items-center gap-2 text-lg font-bold mb-3">
<SmartphoneNfc /> Rettungsmittel
</h2>
<div
className="tooltip tooltip-primary tooltip-left font-semibold"
data-tip="Einsatz erneut alarmieren"
>
<button
className="btn btn-xs btn-primary btn-outline"
onClick={() => {
sendAlertMutation.mutate(mission.id);
}}
>
<BellRing size={16} />
</button>
</div>
</div>
<ul className="space-y-2 max-h-[300px] overflow-y-auto overflow-x-auto">
{mockupData.map((item, index) => (
{stations?.map((station, index) => {
const connectedAircraft = conenctedAircrafts?.find(
(aircraft) => aircraft.stationId === station.id,
);
return (
<li key={index} className="flex items-center gap-2">
{connectedAircraft && (
<span
className="font-bold text-base"
style={{
color: FMS_STATUS_TEXT_COLORS[item.FMSstatus],
color: FMS_STATUS_TEXT_COLORS[connectedAircraft.fmsStatus],
}}
>
{item.FMSstatus}
{connectedAircraft.fmsStatus}
</span>
)}
<span className="text-base-content">
<span className="font-bold">{item.bosCallsign}</span>
{item.min > 0 && (
<div>
<span className="font-bold">{station.bosCallsign}</span>
{/* {item.min > 0 && (
<>
<br />
Ankunft in ca. {item.min} min
</>
)}
)} */}
</div>
</span>
</li>
))}
);
})}
</ul>
<div className="divider mt-0 mb-0" />
<div className="flex items-center gap-2">

View File

@@ -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 (
<div
style={{
fontFamily: "Famirids",
display: "flex",
flexWrap: "wrap",
...style,
}}
>
<span style={INNER_TEXT_PARTS}>{textLeft}</span>
<span style={{ textAlign: "center", ...INNER_TEXT_PARTS }}>
{textMid}
</span>
<span style={{ textAlign: "end", ...INNER_TEXT_PARTS }}>{textRight}</span>
</div>
);
};
export const Dme = () => {
useSounds();
const { handleButton } = useButtons();
const lines = useDmeStore((state) => state.lines);
return (
<div
style={{
display: "grid",
aspectRatio: "1037 / 800",
color: "black",
height: "auto",
width: "auto",
maxHeight: "100%",
maxWidth: "100%",
overflow: "hidden",
gridTemplateColumns:
"25.84% 6.08% 10.99% 11.57% 11.28% 10.90% 18.71% 4.63%",
gridTemplateRows: "2.66% 9.3% 8.9% 7.2% 45.05% 27%",
}}
>
<Image
src={sQuadImageNoReflections}
alt="sQuadImage"
style={{
zIndex: 0,
height: "100%",
width: "100%",
gridArea: "1 / 1 / 10 / 10",
}}
/>
<button
onClick={handleButton("main")}
style={{ gridArea: "2 / 7 / 4 / 8", ...DME_BUTTON_STYLES }}
/>
<button
onClick={handleButton("menu")}
style={{ gridArea: "3 / 2 / 4 / 3", ...DME_BUTTON_STYLES }}
/>
{/* <button
onClick={handleButton('left')}
style={{ gridArea: '3 / 4 / 4 / 5', ...DME_BUTTON_STYLES }}
/>
<button
onClick={handleButton('right')}
style={{ gridArea: '3 / 5 / 4 / 6', ...DME_BUTTON_STYLES }}
/> */}
<Image
src={sQuadReflection}
alt="sQuadImage"
style={{
zIndex: 1,
height: "100%",
width: "100%",
gridArea: "1 / 1 / 10 / 10",
}}
/>
<div
id="display"
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
zIndex: "2",
gridArea: "5 / 2 / 6 / 6",
padding: "5px",
overflowX: "hidden",
overflowY: "auto",
}}
>
{lines.map((l, i) => {
return <DisplayLine key={`line-${i}`} {...l} />;
})}
</div>
</div>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

View File

@@ -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 };
};

View File

@@ -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<HTMLAudioElement | null>(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]);
};

View File

@@ -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";

View File

@@ -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 };
};

View File

@@ -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<NodeJS.Timeout>(null);
const setPage = useMrtStore((state) => state.setPage);
const MRTstatusSoundRef = useRef<HTMLAudioElement>(null);
const MrtMessageReceivedSoundRef = useRef<HTMLAudioElement>(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,
});
};
const playSound = (sound: HTMLAudioElement) => {
sound.play();
sound.addEventListener("ended", handleSoundEnd);
MrtMessageReceivedSoundRef.current.onended = () => {
if (!selectedStation || !connectedAircraft?.fmsStatus) return;
setPage({
page: "home",
station: selectedStation,
fmsStatus: connectedAircraft?.fmsStatus,
});
};
}
}, [connectedAircraft?.fmsStatus, selectedStation, setPage]);
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]);
};

View File

@@ -67,7 +67,7 @@ export const ConnectionBtn = () => {
</span>
</h3>
) : (
<h3 className="text-lg font-bold mb-5">Als Disponent anmelden</h3>
<h3 className="text-lg font-bold mb-5">Als Pilot anmelden</h3>
)}
<fieldset className="fieldset w-full">
<label className="floating-label w-full text-base">

View File

@@ -4,10 +4,9 @@ import { Mrt } from "pilot/_components/mrt/Mrt";
import { Chat } from "../_components/left/Chat";
import { Report } from "../_components/left/Report";
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
import { Dme } from "pilot/_components/dme/Dme";
const DispatchPage = () => {
const { activeMission } = usePilotConnectionStore();
return (
<div className="relative flex-1 flex transition-all duration-500 ease w-full">
{/* <MapToastCard2 /> */}
@@ -19,9 +18,9 @@ const DispatchPage = () => {
</div>
</div>
</div>
<div>{JSON.stringify(activeMission)}</div>
<div>
<div className="flex flex-col">
<Mrt />
<Dme />
</div>
</div>
);

Binary file not shown.

Binary file not shown.

View File

@@ -29,6 +29,9 @@ model User {
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([])