added MRT sds image
This commit is contained in:
@@ -327,9 +327,6 @@ router.post("/:id/validate-hpg", async (req, res) => {
|
|||||||
res.json({
|
res.json({
|
||||||
message: "HPG validierung gestartet",
|
message: "HPG validierung gestartet",
|
||||||
});
|
});
|
||||||
console.log(
|
|
||||||
`HPG Validation for ${user?.publicId} (${mission?.hpgSelectedMissionString}) started`,
|
|
||||||
);
|
|
||||||
io.to(`desktop:${activeAircraftinMission?.userId}`).emit("hpg-validation", {
|
io.to(`desktop:${activeAircraftinMission?.userId}`).emit("hpg-validation", {
|
||||||
missionId: parseInt(id),
|
missionId: parseInt(id),
|
||||||
userId: req.user?.id,
|
userId: req.user?.id,
|
||||||
|
|||||||
@@ -55,14 +55,35 @@ export const handleConnectPilot =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set "now" to 2 hours in the future
|
||||||
|
const nowPlus2h = new Date();
|
||||||
|
nowPlus2h.setHours(nowPlus2h.getHours() + 2);
|
||||||
|
|
||||||
|
// Generate a random position in Germany (approximate bounding box)
|
||||||
|
function getRandomGermanPosition() {
|
||||||
|
const minLat = 47.2701;
|
||||||
|
const maxLat = 55.0581;
|
||||||
|
const minLng = 5.8663;
|
||||||
|
const maxLng = 15.0419;
|
||||||
|
const lat = Math.random() * (maxLat - minLat) + minLat;
|
||||||
|
const lng = Math.random() * (maxLng - minLng) + minLng;
|
||||||
|
return { lat, lng };
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomPos =
|
||||||
|
process.env.environment === "development" ? getRandomGermanPosition() : undefined;
|
||||||
|
|
||||||
const connectedAircraftEntry = await prisma.connectedAircraft.create({
|
const connectedAircraftEntry = await prisma.connectedAircraft.create({
|
||||||
data: {
|
data: {
|
||||||
publicUser: getPublicUser(user) as any,
|
publicUser: getPublicUser(user) as any,
|
||||||
esimatedLogoutTime: parsedLogoffDate?.toISOString() || null,
|
esimatedLogoutTime: parsedLogoffDate?.toISOString() || null,
|
||||||
lastHeartbeat: new Date().toISOString(),
|
|
||||||
userId: userId,
|
userId: userId,
|
||||||
loginTime: new Date().toISOString(),
|
loginTime: nowPlus2h.toISOString(),
|
||||||
stationId: parseInt(stationId),
|
stationId: parseInt(stationId),
|
||||||
|
lastHeartbeat:
|
||||||
|
process.env.environment === "development" ? nowPlus2h.toISOString() : undefined,
|
||||||
|
posLat: randomPos?.lat,
|
||||||
|
posLng: randomPos?.lng,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { ConnectedAircraft, Station } from "@repo/db";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getConnectedAircraftPositionLogAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
import { getConnectedAircraftPositionLogAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { getMissionsAPI } from "_querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { checkSimulatorConnected } from "_helpers/simulatorConnected";
|
|
||||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
||||||
|
|
||||||
const AircraftPopupContent = ({
|
const AircraftPopupContent = ({
|
||||||
@@ -384,16 +383,14 @@ export const AircraftLayer = () => {
|
|||||||
const { data: aircrafts } = useQuery({
|
const { data: aircrafts } = useQuery({
|
||||||
queryKey: ["aircrafts"],
|
queryKey: ["aircrafts"],
|
||||||
queryFn: getConnectedAircraftsAPI,
|
queryFn: getConnectedAircraftsAPI,
|
||||||
refetchInterval: 10000,
|
refetchInterval: 10_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{aircrafts
|
{aircrafts?.map((aircraft) => {
|
||||||
?.filter((a) => checkSimulatorConnected(a.lastHeartbeat))
|
return <AircraftMarker key={aircraft.id} aircraft={aircraft} />;
|
||||||
?.map((aircraft) => {
|
})}
|
||||||
return <AircraftMarker key={aircraft.id} aircraft={aircraft} />;
|
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
||||||
import {
|
import {
|
||||||
ConnectedAircraft,
|
ConnectedAircraft,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Station,
|
Station,
|
||||||
} from "@repo/db";
|
} from "@repo/db";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { editConnectedAircraftAPI } from "_querys/aircrafts";
|
import { editConnectedAircraftAPI } from "_querys/aircrafts";
|
||||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||||
import { cn } from "_helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
@@ -39,7 +39,9 @@ import {
|
|||||||
TextSearch,
|
TextSearch,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { editMissionAPI, sendSdsMessageAPI } from "_querys/missions";
|
import { sendSdsMessageAPI } from "_querys/missions";
|
||||||
|
import { getLivekitRooms } from "_querys/livekit";
|
||||||
|
import { findLeitstelleForPosition } from "_helpers/findLeitstelleinPoint";
|
||||||
|
|
||||||
const FMSStatusHistory = ({
|
const FMSStatusHistory = ({
|
||||||
aircraft,
|
aircraft,
|
||||||
@@ -216,14 +218,35 @@ const RettungsmittelTab = ({
|
|||||||
aircraft: ConnectedAircraft & { Station: Station };
|
aircraft: ConnectedAircraft & { Station: Station };
|
||||||
}) => {
|
}) => {
|
||||||
const station = aircraft.Station;
|
const station = aircraft.Station;
|
||||||
|
const { data: livekitRooms } = useQuery({
|
||||||
|
queryKey: ["livekit-rooms"],
|
||||||
|
queryFn: () => getLivekitRooms(),
|
||||||
|
refetchInterval: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const participants =
|
||||||
|
livekitRooms?.flatMap((room) =>
|
||||||
|
room.participants.map((p) => ({
|
||||||
|
...p,
|
||||||
|
roomName: room.room.name,
|
||||||
|
})),
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const livekitUser = participants.find((p) => (p.attributes.userId = aircraft.userId));
|
||||||
|
|
||||||
|
const lstName = useMemo(() => {
|
||||||
|
if (!aircraft.posLng || !aircraft.posLat) return;
|
||||||
|
return findLeitstelleForPosition(aircraft.posLng, aircraft.posLat);
|
||||||
|
}, [aircraft]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-base-content">
|
<div className="p-4 text-base-content">
|
||||||
<ul className="text-base-content font-semibold">
|
<ul className="text-base-content font-semibold">
|
||||||
<li className="flex items-center gap-2 mb-1">
|
<li className="flex items-center gap-2 mb-1">
|
||||||
<Component size={16} /> Aktuelle Rufgruppe: LST_01
|
<Component size={16} /> Aktuelle Rufgruppe: {livekitUser?.roomName || "Nicht verbunden"}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2 mb-1">
|
<li className="flex items-center gap-2 mb-1">
|
||||||
<RadioTower size={16} /> Leitstellenbereich: Florian Berlin
|
<RadioTower size={16} /> Leitstellenbereich: {lstName || station.bosRadioArea}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="divider mt-0 mb-0" />
|
<div className="divider mt-0 mb-0" />
|
||||||
@@ -348,7 +371,7 @@ const SDSTab = ({
|
|||||||
onClick={() => setIsChatOpen(true)}
|
onClick={() => setIsChatOpen(true)}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Plus size={18} /> Notiz hinzufügen
|
<Plus size={18} /> SDS senden
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
@@ -379,6 +402,7 @@ const SDSTab = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
toast.success("SDS-Nachricht gesendet");
|
||||||
setIsChatOpen(false);
|
setIsChatOpen(false);
|
||||||
setNote("");
|
setNote("");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useMapStore } from "_store/mapStore";
|
|||||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
||||||
import { MISSION_STATUS_COLORS, MISSION_STATUS_TEXT_COLORS } from "_components/map/MissionMarkers";
|
import { MISSION_STATUS_COLORS, MISSION_STATUS_TEXT_COLORS } from "_components/map/MissionMarkers";
|
||||||
import { cn } from "_helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { checkSimulatorConnected } from "_helpers/simulatorConnected";
|
|
||||||
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { getMissionsAPI } from "_querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
@@ -96,41 +95,39 @@ const PopupContent = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{aircrafts
|
{aircrafts.map((aircraft) => (
|
||||||
.filter((a) => checkSimulatorConnected(a.lastHeartbeat))
|
<div
|
||||||
.map((aircraft) => (
|
key={aircraft.id}
|
||||||
<div
|
className="relative w-auto inline-flex items-center gap-2 text-nowrap cursor-pointer"
|
||||||
key={aircraft.id}
|
style={{
|
||||||
className="relative w-auto inline-flex items-center gap-2 text-nowrap cursor-pointer"
|
backgroundColor: FMS_STATUS_COLORS[aircraft.fmsStatus],
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setOpenAircraftMarker({
|
||||||
|
open: [
|
||||||
|
{
|
||||||
|
id: aircraft.id,
|
||||||
|
tab: "aircraft",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
close: [],
|
||||||
|
});
|
||||||
|
map.setView([aircraft.posLat!, aircraft.posLng!], 12, {
|
||||||
|
animate: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="mx-2 my-0.5 text-gt font-bold"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: FMS_STATUS_COLORS[aircraft.fmsStatus],
|
color: FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus],
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setOpenAircraftMarker({
|
|
||||||
open: [
|
|
||||||
{
|
|
||||||
id: aircraft.id,
|
|
||||||
tab: "aircraft",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
close: [],
|
|
||||||
});
|
|
||||||
map.setView([aircraft.posLat!, aircraft.posLng!], 12, {
|
|
||||||
animate: true,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
{aircraft.fmsStatus}
|
||||||
className="mx-2 my-0.5 text-gt font-bold"
|
</span>
|
||||||
style={{
|
<span>{aircraft.Station.bosCallsign}</span>
|
||||||
color: FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus],
|
</div>
|
||||||
}}
|
))}
|
||||||
>
|
|
||||||
{aircraft.fmsStatus}
|
|
||||||
</span>
|
|
||||||
<span>{aircraft.Station.bosCallsign}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -141,6 +138,7 @@ export const MarkerCluster = () => {
|
|||||||
const { data: aircrafts } = useQuery({
|
const { data: aircrafts } = useQuery({
|
||||||
queryKey: ["aircrafts"],
|
queryKey: ["aircrafts"],
|
||||||
queryFn: getConnectedAircraftsAPI,
|
queryFn: getConnectedAircraftsAPI,
|
||||||
|
refetchInterval: 10_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
|
const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
|
||||||
@@ -178,38 +176,36 @@ export const MarkerCluster = () => {
|
|||||||
lat: number;
|
lat: number;
|
||||||
lng: number;
|
lng: number;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
aircrafts
|
aircrafts?.forEach((aircraft) => {
|
||||||
?.filter((a) => checkSimulatorConnected(a.lastHeartbeat))
|
const lat = aircraft.posLat!;
|
||||||
.forEach((aircraft) => {
|
const lng = aircraft.posLng!;
|
||||||
const lat = aircraft.posLat!;
|
|
||||||
const lng = aircraft.posLng!;
|
|
||||||
|
|
||||||
const existingClusterIndex = newCluster.findIndex(
|
const existingClusterIndex = newCluster.findIndex(
|
||||||
(c) => Math.abs(c.lat - lat) < 1 && Math.abs(c.lng - lng) < 1,
|
(c) => Math.abs(c.lat - lat) < 1 && Math.abs(c.lng - lng) < 1,
|
||||||
);
|
);
|
||||||
const existingCluster = newCluster[existingClusterIndex];
|
const existingCluster = newCluster[existingClusterIndex];
|
||||||
if (existingCluster) {
|
if (existingCluster) {
|
||||||
newCluster = [...newCluster].map((c, i) => {
|
newCluster = [...newCluster].map((c, i) => {
|
||||||
if (i === existingClusterIndex) {
|
if (i === existingClusterIndex) {
|
||||||
return {
|
return {
|
||||||
...c,
|
...c,
|
||||||
aircrafts: [...c.aircrafts, aircraft],
|
aircrafts: [...c.aircrafts, aircraft],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newCluster = [
|
newCluster = [
|
||||||
...newCluster,
|
...newCluster,
|
||||||
{
|
{
|
||||||
aircrafts: [aircraft],
|
aircrafts: [aircraft],
|
||||||
missions: [],
|
missions: [],
|
||||||
lat,
|
lat,
|
||||||
lng,
|
lng,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
filteredMissions?.forEach((mission) => {
|
filteredMissions?.forEach((mission) => {
|
||||||
const lat = mission.addressLat;
|
const lat = mission.addressLat;
|
||||||
const lng = mission.addressLng;
|
const lng = mission.addressLng;
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ export default function AdminPanel() {
|
|||||||
refetchInterval: 10000,
|
refetchInterval: 10000,
|
||||||
});
|
});
|
||||||
const { data: livekitRooms } = useQuery({
|
const { data: livekitRooms } = useQuery({
|
||||||
queryKey: ["connected-audio-users"],
|
queryKey: ["livekit-rooms"],
|
||||||
queryFn: () => getLivekitRooms(),
|
queryFn: () => getLivekitRooms(),
|
||||||
refetchInterval: 10000,
|
refetchInterval: 10000,
|
||||||
});
|
});
|
||||||
const kickLivekitParticipantMutation = useMutation({
|
const kickLivekitParticipantMutation = useMutation({
|
||||||
mutationFn: kickLivekitParticipant,
|
mutationFn: kickLivekitParticipant,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["connected-audio-users"] });
|
queryClient.invalidateQueries({ queryKey: ["livekit-rooms"] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const editUSerMutation = useMutation({
|
const editUSerMutation = useMutation({
|
||||||
@@ -95,8 +95,6 @@ export default function AdminPanel() {
|
|||||||
return !pilot && !fDispatcher;
|
return !pilot && !fDispatcher;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Livekit Rooms", livekitRooms);
|
|
||||||
|
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
18
apps/dispatch/app/_helpers/findLeitstelleinPoint.ts
Normal file
18
apps/dispatch/app/_helpers/findLeitstelleinPoint.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { point, multiPolygon, booleanPointInPolygon } from "@turf/turf";
|
||||||
|
import leitstellenGeoJSON from "../_components/map/_geojson/Leitstellen.json"; // Pfad anpassen
|
||||||
|
|
||||||
|
export function findLeitstelleForPosition(lat: number, lng: number) {
|
||||||
|
const heliPoint = point([lat, lng]);
|
||||||
|
|
||||||
|
for (const feature of (leitstellenGeoJSON as any).features) {
|
||||||
|
if (feature.geometry.type === "MultiPolygon") {
|
||||||
|
const polygon = multiPolygon(feature.geometry.coordinates);
|
||||||
|
if (booleanPointInPolygon(heliPoint, polygon)) {
|
||||||
|
console.log("Point is inside polygon:", feature.properties.name);
|
||||||
|
return feature.properties.name ?? "Unbenannte Leitstelle";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Keine passende Leitstelle gefunden
|
||||||
|
}
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
export const checkSimulatorConnected = (date: Date) =>
|
import { ConnectedAircraft } from "@repo/db";
|
||||||
date && Date.now() - new Date(date).getTime() <= 3000_000;
|
|
||||||
|
export const checkSimulatorConnected = (a: ConnectedAircraft) => {
|
||||||
|
if (!a.lastHeartbeat || Date.now() - new Date(a.lastHeartbeat).getTime() > 30_000) return false; // 30 seconds
|
||||||
|
if (!a.posLat || !a.posLng) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { ConnectedAircraft, PositionLog, Prisma, PublicUser, Station } from "@repo/db";
|
import { ConnectedAircraft, PositionLog, Prisma, PublicUser, Station } from "@repo/db";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { serverApi } from "_helpers/axios";
|
import { serverApi } from "_helpers/axios";
|
||||||
|
import { checkSimulatorConnected } from "_helpers/simulatorConnected";
|
||||||
|
|
||||||
export const getConnectedAircraftsAPI = async () => {
|
export const getConnectedAircraftsAPI = async () => {
|
||||||
const res = await axios.get<(ConnectedAircraft & { Station: Station })[]>("/api/aircrafts"); // return only connected aircrafts
|
const res = await axios.get<(ConnectedAircraft & { Station: Station })[]>("/api/aircrafts"); // return only connected aircrafts
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error("Failed to fetch stations");
|
throw new Error("Failed to fetch stations");
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data.filter((a) => checkSimulatorConnected(a));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editConnectedAircraftAPI = async (
|
export const editConnectedAircraftAPI = async (
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const useMrtStore = create<MrtStore>(
|
|||||||
},
|
},
|
||||||
{ textLeft: "ILS VAR#", textSize: "3" },
|
{ textLeft: "ILS VAR#", textSize: "3" },
|
||||||
{
|
{
|
||||||
textLeft: "new status received",
|
textLeft: "empfangen",
|
||||||
style: { fontWeight: "bold" },
|
style: { fontWeight: "bold" },
|
||||||
textSize: "4",
|
textSize: "4",
|
||||||
},
|
},
|
||||||
@@ -136,7 +136,7 @@ export const useMrtStore = create<MrtStore>(
|
|||||||
page: "sds",
|
page: "sds",
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
textLeft: `neue SDS-Nachricht`,
|
textLeft: `SDS-Nachricht`,
|
||||||
style: { fontWeight: "bold" },
|
style: { fontWeight: "bold" },
|
||||||
textSize: "2",
|
textSize: "2",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ export const GET = async (request: NextRequest) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user || !user.permissions.includes("AUDIO_ADMIN"))
|
|
||||||
return Response.json({ message: "Missing permissions" }, { status: 401 });
|
|
||||||
|
|
||||||
const rooms = await RoomManager.listRooms();
|
const rooms = await RoomManager.listRooms();
|
||||||
|
|
||||||
const roomsWithParticipants = rooms.map(async (room) => {
|
const roomsWithParticipants = rooms.map(async (room) => {
|
||||||
|
|||||||
BIN
apps/dispatch/app/pilot/_components/mrt/MRT_MESSAGE.png
Normal file
BIN
apps/dispatch/app/pilot/_components/mrt/MRT_MESSAGE.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 322 KiB |
@@ -1,5 +1,6 @@
|
|||||||
import { CSSProperties } from "react";
|
import { CSSProperties } from "react";
|
||||||
import MrtImage from "./MRT.png";
|
import MrtImage from "./MRT.png";
|
||||||
|
import MrtMessageImage from "./MRT_MESSAGE.png";
|
||||||
import { useButtons } from "./useButtons";
|
import { useButtons } from "./useButtons";
|
||||||
import { useSounds } from "./useSounds";
|
import { useSounds } from "./useSounds";
|
||||||
import "./mrt.css";
|
import "./mrt.css";
|
||||||
@@ -18,6 +19,7 @@ const MRT_DISPLAYLINE_STYLES: CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface DisplayLineProps {
|
export interface DisplayLineProps {
|
||||||
|
lineStyle?: CSSProperties;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
textLeft?: string;
|
textLeft?: string;
|
||||||
textMid?: string;
|
textMid?: string;
|
||||||
@@ -31,12 +33,14 @@ const DisplayLine = ({
|
|||||||
textMid,
|
textMid,
|
||||||
textRight,
|
textRight,
|
||||||
textSize,
|
textSize,
|
||||||
|
lineStyle,
|
||||||
}: DisplayLineProps) => {
|
}: DisplayLineProps) => {
|
||||||
const INNER_TEXT_PARTS: CSSProperties = {
|
const INNER_TEXT_PARTS: CSSProperties = {
|
||||||
fontFamily: "Melder",
|
fontFamily: "Melder",
|
||||||
flex: "1",
|
flex: "1",
|
||||||
flexBasis: "auto",
|
flexBasis: "auto",
|
||||||
overflowWrap: "break-word",
|
overflowWrap: "break-word",
|
||||||
|
...lineStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -46,13 +50,12 @@ const DisplayLine = ({
|
|||||||
fontFamily: "Famirids",
|
fontFamily: "Famirids",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
|
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={INNER_TEXT_PARTS}>{textLeft}</span>
|
<span style={INNER_TEXT_PARTS}>{textLeft}</span>
|
||||||
<span style={{ textAlign: "center", ...INNER_TEXT_PARTS }}>
|
<span style={{ textAlign: "center", ...INNER_TEXT_PARTS }}>{textMid}</span>
|
||||||
{textMid}
|
|
||||||
</span>
|
|
||||||
<span style={{ textAlign: "end", ...INNER_TEXT_PARTS }}>{textRight}</span>
|
<span style={{ textAlign: "end", ...INNER_TEXT_PARTS }}>{textRight}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -61,7 +64,7 @@ const DisplayLine = ({
|
|||||||
export const Mrt = () => {
|
export const Mrt = () => {
|
||||||
useSounds();
|
useSounds();
|
||||||
const { handleButton } = useButtons();
|
const { handleButton } = useButtons();
|
||||||
const lines = useMrtStore((state) => state.lines);
|
const { lines, page } = useMrtStore((state) => state);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -75,21 +78,38 @@ export const Mrt = () => {
|
|||||||
maxHeight: "100%",
|
maxHeight: "100%",
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
color: "white",
|
color: "white",
|
||||||
gridTemplateColumns:
|
gridTemplateColumns: "21.83% 4.43% 24.42% 18.08% 5.93% 1.98% 6.00% 1.69% 6.00% 9.35%",
|
||||||
"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%",
|
||||||
gridTemplateRows:
|
|
||||||
"21.58% 11.87% 3.55% 5.00% 6.84% 0.53% 3.03% 11.84% 3.55% 11.84% 20.39%",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
{page !== "sds" && (
|
||||||
src={MrtImage}
|
<Image
|
||||||
alt="MrtImage"
|
src={MrtImage}
|
||||||
style={{
|
alt="MrtImage"
|
||||||
zIndex: 0,
|
style={{
|
||||||
height: "100%",
|
zIndex: 0,
|
||||||
width: "100%",
|
height: "100%",
|
||||||
gridArea: "1 / 1 / 13 / 13",
|
width: "100%",
|
||||||
}}
|
gridArea: "1 / 1 / 13 / 13",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{page === "sds" && (
|
||||||
|
<Image
|
||||||
|
src={MrtMessageImage}
|
||||||
|
alt="MrtImage-Message"
|
||||||
|
style={{
|
||||||
|
zIndex: 0,
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
gridArea: "1 / 1 / 13 / 13",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleButton("home")}
|
||||||
|
style={{ gridArea: "2 / 4 / 3 / 5", ...MRT_BUTTON_STYLES }}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleButton("1")}
|
onClick={handleButton("1")}
|
||||||
@@ -135,25 +155,50 @@ export const Mrt = () => {
|
|||||||
{lines[0] && (
|
{lines[0] && (
|
||||||
<DisplayLine
|
<DisplayLine
|
||||||
{...lines[0]}
|
{...lines[0]}
|
||||||
style={{
|
style={
|
||||||
gridArea: "4 / 3 / 5 / 4",
|
page === "sds"
|
||||||
marginLeft: "9px",
|
? {
|
||||||
marginTop: "auto",
|
gridArea: "2 / 3 / 3 / 4",
|
||||||
...MRT_DISPLAYLINE_STYLES,
|
marginLeft: "9px",
|
||||||
...lines[0]?.style,
|
marginTop: "auto",
|
||||||
}}
|
|
||||||
|
...MRT_DISPLAYLINE_STYLES,
|
||||||
|
...lines[0]?.style,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
gridArea: "4 / 3 / 5 / 4",
|
||||||
|
marginLeft: "9px",
|
||||||
|
marginTop: "auto",
|
||||||
|
...MRT_DISPLAYLINE_STYLES,
|
||||||
|
...lines[0]?.style,
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{lines[1] && (
|
{lines[1] && (
|
||||||
<DisplayLine
|
<DisplayLine
|
||||||
{...lines[1]}
|
lineStyle={{
|
||||||
style={{
|
overflowX: "hidden",
|
||||||
gridArea: "5 / 3 / 7 / 4",
|
maxHeight: "100%",
|
||||||
marginLeft: "3px",
|
overflowY: "auto",
|
||||||
marginTop: "auto",
|
|
||||||
...MRT_DISPLAYLINE_STYLES,
|
|
||||||
...lines[1].style,
|
|
||||||
}}
|
}}
|
||||||
|
{...lines[1]}
|
||||||
|
style={
|
||||||
|
page === "sds"
|
||||||
|
? {
|
||||||
|
gridArea: "4 / 2 / 10 / 4",
|
||||||
|
marginLeft: "3px",
|
||||||
|
...MRT_DISPLAYLINE_STYLES,
|
||||||
|
...lines[1].style,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
gridArea: "5 / 3 / 7 / 4",
|
||||||
|
marginLeft: "3px",
|
||||||
|
marginTop: "auto",
|
||||||
|
...MRT_DISPLAYLINE_STYLES,
|
||||||
|
...lines[1].style,
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{lines[2] && (
|
{lines[2] && (
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const useButtons = () => {
|
|||||||
const { page, setPage } = useMrtStore((state) => state);
|
const { page, setPage } = useMrtStore((state) => state);
|
||||||
|
|
||||||
const handleButton =
|
const handleButton =
|
||||||
(button: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0") => () => {
|
(button: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0" | "home") => () => {
|
||||||
if (connectionStatus !== "connected") return;
|
if (connectionStatus !== "connected") return;
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
if (!connectedAircraft?.id) return;
|
if (!connectedAircraft?.id) return;
|
||||||
@@ -40,7 +40,6 @@ export const useButtons = () => {
|
|||||||
button === "9" ||
|
button === "9" ||
|
||||||
button === "0"
|
button === "0"
|
||||||
) {
|
) {
|
||||||
if (page !== "home") return;
|
|
||||||
setPage({ page: "sending-status", station });
|
setPage({ page: "sending-status", station });
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
@@ -56,6 +55,8 @@ export const useButtons = () => {
|
|||||||
fmsStatus: button,
|
fmsStatus: button,
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
setPage({ page: "home", fmsStatus: connectedAircraft.fmsStatus || "6", station });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,15 @@
|
|||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@tailwindcss/postcss": "^4.1.8",
|
"@tailwindcss/postcss": "^4.1.8",
|
||||||
"@tanstack/react-query": "^5.79.0",
|
"@tanstack/react-query": "^5.79.0",
|
||||||
|
"@turf/turf": "^7.2.0",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
|
"@types/leaflet": "^1.9.18",
|
||||||
|
"@types/node": "^22.15.29",
|
||||||
|
"@types/react": "^19.1.6",
|
||||||
|
"@types/react-dom": "^19.1.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"daisyui": "^5.0.43",
|
||||||
"geojson": "^0.5.0",
|
"geojson": "^0.5.0",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
@@ -47,15 +53,9 @@
|
|||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.3.0",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.8",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
"zod": "^3.25.46",
|
"zod": "^3.25.46",
|
||||||
"zustand": "^5.0.5",
|
"zustand": "^5.0.5",
|
||||||
"zustand-sync-tabs": "^0.2.2",
|
"zustand-sync-tabs": "^0.2.2"
|
||||||
"@types/leaflet": "^1.9.18",
|
}
|
||||||
"@types/node": "^22.15.29",
|
|
||||||
"@types/react": "^19.1.6",
|
|
||||||
"@types/react-dom": "^19.1.5",
|
|
||||||
"daisyui": "^5.0.43",
|
|
||||||
"typescript": "^5.8.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
1617
pnpm-lock.yaml
generated
1617
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user