Made Aircrafts fetch from Server. Added OSM Objects to mission
This commit is contained in:
73
apps/dispatch-server/routes/aircraft.ts
Normal file
73
apps/dispatch-server/routes/aircraft.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { prisma } from "@repo/db";
|
||||
import { Router } from "express";
|
||||
import { io } from "../index";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Get all connectedAircrafts
|
||||
router.post("/", async (req, res) => {
|
||||
try {
|
||||
const filter = req.body?.filter || {};
|
||||
const connectedAircrafts = await prisma.connectedAircraft.findMany({
|
||||
where: filter,
|
||||
});
|
||||
res.json(connectedAircrafts);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Failed to fetch connectedAircrafts" });
|
||||
}
|
||||
});
|
||||
|
||||
// Get a single connectedAircraft by ID
|
||||
router.get("/:id", async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const connectedAircraft = await prisma.connectedAircraft.findUnique({
|
||||
where: { id: Number(id) },
|
||||
});
|
||||
if (connectedAircraft) {
|
||||
res.json(connectedAircraft);
|
||||
} else {
|
||||
res.status(404).json({ error: "ConnectedAircraft not found" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Failed to fetch connectedAircraft" });
|
||||
}
|
||||
});
|
||||
|
||||
// Update a connectedAircraft by ID
|
||||
router.patch("/:id", async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const updatedConnectedAircraft = await prisma.connectedAircraft.update({
|
||||
where: { id: Number(id) },
|
||||
data: req.body,
|
||||
});
|
||||
io.to("dispatchers").emit(
|
||||
"update-connectedAircraft",
|
||||
updatedConnectedAircraft,
|
||||
);
|
||||
res.json(updatedConnectedAircraft);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Failed to update connectedAircraft" });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a connectedAircraft by ID
|
||||
router.delete("/:id", async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
await prisma.connectedAircraft.delete({
|
||||
where: { id: Number(id) },
|
||||
});
|
||||
io.to("dispatchers").emit("delete-connectedAircraft", id);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Failed to delete connectedAircraft" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -42,7 +42,7 @@ router.put("/", async (req, res) => {
|
||||
const newMission = await prisma.mission.create({
|
||||
data: req.body,
|
||||
});
|
||||
io.to("missions").emit("new-mission", newMission);
|
||||
io.to("dispatchers").emit("new-mission", newMission);
|
||||
res.status(201).json(newMission);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to create mission" });
|
||||
@@ -57,7 +57,7 @@ router.patch("/:id", async (req, res) => {
|
||||
where: { id: Number(id) },
|
||||
data: req.body,
|
||||
});
|
||||
io.to("missions").emit("update-mission", updatedMission);
|
||||
io.to("dispatchers").emit("update-mission", updatedMission);
|
||||
res.json(updatedMission);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -72,7 +72,7 @@ router.delete("/:id", async (req, res) => {
|
||||
await prisma.mission.delete({
|
||||
where: { id: Number(id) },
|
||||
});
|
||||
io.to("missions").emit("delete-mission", id);
|
||||
io.to("dispatchers").emit("delete-mission", id);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -3,6 +3,7 @@ import livekitRouter from "./livekit";
|
||||
import dispatcherRotuer from "./dispatcher";
|
||||
import missionRouter from "./mission";
|
||||
import statusRouter from "./status";
|
||||
import aircraftsRouter from "./aircraft";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -10,5 +11,6 @@ router.use("/livekit", livekitRouter);
|
||||
router.use("/dispatcher", dispatcherRotuer);
|
||||
router.use("/mission", missionRouter);
|
||||
router.use("/status", statusRouter);
|
||||
router.use("/aircrafts", aircraftsRouter);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -79,7 +79,6 @@ export const handleConnectDispatch =
|
||||
|
||||
socket.join("dispatchers"); // Dem Dispatcher-Raum beitreten
|
||||
socket.join(`user:${user.id}`); // Dem User-Raum beitreten
|
||||
socket.join("missions");
|
||||
|
||||
io.to("dispatchers").emit("dispatchers-update");
|
||||
io.to("pilots").emit("dispatchers-update");
|
||||
|
||||
@@ -30,7 +30,13 @@ export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const invalidateConnectedUsers = () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["connected-users"],
|
||||
queryKey: ["connected-users", "aircrafts"],
|
||||
});
|
||||
};
|
||||
|
||||
const invalidateConenctedAircrafts = () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["aircrafts"],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,6 +45,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
dispatchSocket.on("new-mission", invalidateMission);
|
||||
dispatchSocket.on("dispatchers-update", invalidateConnectedUsers);
|
||||
dispatchSocket.on("pilots-update", invalidateConnectedUsers);
|
||||
dispatchSocket.on("update-connectedAircraft", invalidateConenctedAircrafts);
|
||||
}, [queryClient]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -86,7 +86,12 @@ export const Chat = () => {
|
||||
</option>
|
||||
)}
|
||||
|
||||
{connectedUser?.map((user) => (
|
||||
{[
|
||||
...(connectedUser?.filter(
|
||||
(user, idx, arr) =>
|
||||
arr.findIndex((u) => u.userId === user.userId) === idx,
|
||||
) || []),
|
||||
].map((user) => (
|
||||
<option key={user.userId} value={user.userId}>
|
||||
{asPublicUser(user.publicUser).fullName}
|
||||
</option>
|
||||
|
||||
@@ -75,9 +75,14 @@ export const Report = () => {
|
||||
Chatpartner auswählen
|
||||
</option>
|
||||
)}
|
||||
{connectedUser?.map((user) => (
|
||||
{[
|
||||
...(connectedUser?.filter(
|
||||
(user, idx, arr) =>
|
||||
arr.findIndex((u) => u.userId === user.userId) === idx,
|
||||
) || []),
|
||||
].map((user) => (
|
||||
<option key={user.userId} value={user.userId}>
|
||||
{asPublicUser(user).fullName}
|
||||
{asPublicUser(user.publicUser).fullName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export interface Aircraft {
|
||||
id: string;
|
||||
bosName: string;
|
||||
bosNameShort: string;
|
||||
bosNutzung: string;
|
||||
fmsStatus: string;
|
||||
fmsLog: {
|
||||
status: string;
|
||||
timestamp: string;
|
||||
user: string;
|
||||
}[];
|
||||
location: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
altitude: number;
|
||||
speed: number;
|
||||
};
|
||||
locationHistory: {
|
||||
lat: number;
|
||||
lon: number;
|
||||
altitude: number;
|
||||
speed: number;
|
||||
timestamp: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface AircraftStore {
|
||||
aircrafts: Aircraft[];
|
||||
setAircrafts: (aircrafts: Aircraft[]) => void;
|
||||
setAircraft: (aircraft: Aircraft) => void;
|
||||
}
|
||||
|
||||
export const useAircraftsStore = create<AircraftStore>((set) => ({
|
||||
aircrafts: [
|
||||
{
|
||||
id: "1",
|
||||
bosName: "Christoph 31",
|
||||
bosNameShort: "CHX31",
|
||||
bosNutzung: "RTH",
|
||||
fmsStatus: "1",
|
||||
fmsLog: [],
|
||||
location: {
|
||||
lat: 52.546781040592776,
|
||||
lng: 13.369535209542219,
|
||||
altitude: 0,
|
||||
speed: 0,
|
||||
},
|
||||
locationHistory: [],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
bosName: "Christoph Berlin",
|
||||
bosNameShort: "CHX83",
|
||||
bosNutzung: "ITH",
|
||||
fmsStatus: "2",
|
||||
fmsLog: [],
|
||||
location: {
|
||||
lat: 52.54588546048977,
|
||||
lng: 13.46470691054384,
|
||||
altitude: 0,
|
||||
speed: 0,
|
||||
},
|
||||
locationHistory: [],
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
bosName: "Christoph 100",
|
||||
bosNameShort: "CHX100",
|
||||
bosNutzung: "RTH",
|
||||
fmsStatus: "7",
|
||||
fmsLog: [],
|
||||
location: {
|
||||
lat: 52.497519717230155,
|
||||
lng: 13.342040806552554,
|
||||
altitude: 0,
|
||||
speed: 0,
|
||||
},
|
||||
locationHistory: [],
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
bosName: "Christophorus 1",
|
||||
bosNameShort: "A4",
|
||||
bosNutzung: "RTH",
|
||||
fmsStatus: "6",
|
||||
fmsLog: [],
|
||||
location: {
|
||||
lat: 52.50175041192073,
|
||||
lng: 13.478628701227349,
|
||||
altitude: 0,
|
||||
speed: 0,
|
||||
},
|
||||
locationHistory: [],
|
||||
},
|
||||
],
|
||||
setAircrafts: (aircrafts) => set({ aircrafts }),
|
||||
setAircraft: (aircraft) =>
|
||||
set((state) => {
|
||||
const existingAircraftIndex = state.aircrafts.findIndex(
|
||||
(a) => a.id === aircraft.id,
|
||||
);
|
||||
if (existingAircraftIndex !== -1) {
|
||||
const updatedAircrafts = [...state.aircrafts];
|
||||
updatedAircrafts[existingAircraftIndex] = aircraft;
|
||||
return { aircrafts: updatedAircrafts };
|
||||
} else {
|
||||
return { aircrafts: [...state.aircrafts, aircraft] };
|
||||
}
|
||||
}),
|
||||
}));
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OSMWay } from "@repo/db";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface MapStore {
|
||||
@@ -18,30 +19,14 @@ interface MapStore {
|
||||
close: number[];
|
||||
}) => void;
|
||||
openAircraftMarker: {
|
||||
id: string;
|
||||
id: number;
|
||||
tab: "home" | "fms" | "aircraft" | "mission" | "chat";
|
||||
}[];
|
||||
setOpenAircraftMarker: (aircraft: {
|
||||
open: MapStore["openAircraftMarker"];
|
||||
close: string[];
|
||||
close: number[];
|
||||
}) => void;
|
||||
searchElements: {
|
||||
id: number;
|
||||
nodes: {
|
||||
lat: number;
|
||||
lon: number;
|
||||
}[];
|
||||
tags?: {
|
||||
"addr:country"?: string;
|
||||
"addr:city"?: string;
|
||||
"addr:housenumber"?: string;
|
||||
"addr:postcode"?: string;
|
||||
"addr:street"?: string;
|
||||
"addr:suburb"?: string;
|
||||
building?: string;
|
||||
};
|
||||
type: string;
|
||||
}[];
|
||||
searchElements: OSMWay[];
|
||||
setSearchElements: (elements: MapStore["searchElements"]) => void;
|
||||
setContextMenu: (popup: MapStore["contextMenu"]) => void;
|
||||
searchPopup: {
|
||||
@@ -54,8 +39,8 @@ interface MapStore {
|
||||
[aircraftId: string]: "home" | "fms" | "aircraft" | "mission" | "chat";
|
||||
};
|
||||
setAircraftTab: (
|
||||
aircraftId: string,
|
||||
tab: MapStore["aircraftTabs"][string],
|
||||
aircraftId: number,
|
||||
tab: MapStore["aircraftTabs"][number],
|
||||
) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ interface ConnectionStore {
|
||||
disconnect: () => void;
|
||||
}
|
||||
|
||||
export const useDispatchConnectionStore = create<ConnectionStore>((set) => ({
|
||||
export const usePilotConnectionStore = create<ConnectionStore>((set) => ({
|
||||
status: "disconnected",
|
||||
message: "",
|
||||
selectedStation: null,
|
||||
@@ -40,23 +40,23 @@ export const useDispatchConnectionStore = create<ConnectionStore>((set) => ({
|
||||
|
||||
dispatchSocket.on("connect", () => {
|
||||
pilotSocket.disconnect();
|
||||
useDispatchConnectionStore.setState({ status: "connected", message: "" });
|
||||
usePilotConnectionStore.setState({ status: "connected", message: "" });
|
||||
});
|
||||
|
||||
dispatchSocket.on("connect_error", (err) => {
|
||||
useDispatchConnectionStore.setState({
|
||||
usePilotConnectionStore.setState({
|
||||
status: "error",
|
||||
message: err.message,
|
||||
});
|
||||
});
|
||||
|
||||
dispatchSocket.on("disconnect", () => {
|
||||
useDispatchConnectionStore.setState({ status: "disconnected", message: "" });
|
||||
usePilotConnectionStore.setState({ status: "disconnected", message: "" });
|
||||
});
|
||||
|
||||
dispatchSocket.on("force-disconnect", (reason: string) => {
|
||||
console.log("force-disconnect", reason);
|
||||
useDispatchConnectionStore.setState({
|
||||
usePilotConnectionStore.setState({
|
||||
status: "disconnected",
|
||||
message: reason,
|
||||
});
|
||||
|
||||
25
apps/dispatch/app/api/aircrafts/route.ts
Normal file
25
apps/dispatch/app/api/aircrafts/route.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@repo/db";
|
||||
|
||||
export async function GET(): Promise<NextResponse> {
|
||||
try {
|
||||
const connectedAircraft = await prisma.connectedAircraft.findMany({
|
||||
where: {
|
||||
logoutTime: null,
|
||||
},
|
||||
include: {
|
||||
Station: true,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(connectedAircraft, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch Aircrafts" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Aircraft, useAircraftsStore } from "_store/aircraftsStore";
|
||||
import { Marker, useMap } from "react-leaflet";
|
||||
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
@@ -26,6 +25,9 @@ import FMSStatusHistory, {
|
||||
FMSStatusSelector,
|
||||
RettungsmittelTab,
|
||||
} from "./_components/AircraftMarkerTabs";
|
||||
import { ConnectedAircraft, Station } from "@repo/db";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
||||
|
||||
export const FMS_STATUS_COLORS: { [key: string]: string } = {
|
||||
"0": "rgb(140,10,10)",
|
||||
@@ -54,7 +56,11 @@ export const FMS_STATUS_TEXT_COLORS: { [key: string]: string } = {
|
||||
N: "rgb(153,153,153)",
|
||||
};
|
||||
|
||||
const AircraftPopupContent = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
const AircraftPopupContent = ({
|
||||
aircraft,
|
||||
}: {
|
||||
aircraft: ConnectedAircraft & { Station: Station };
|
||||
}) => {
|
||||
const setAircraftTab = useMapStore((state) => state.setAircraftTab);
|
||||
const currentTab = useMapStore(
|
||||
(state) => state.aircraftTabs[aircraft.id] || "home",
|
||||
@@ -182,9 +188,9 @@ const AircraftPopupContent = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
onClick={() => handleTabChange("aircraft")}
|
||||
>
|
||||
<span className="text-white text-base font-medium">
|
||||
{aircraft.bosName}
|
||||
{aircraft.Station.bosCallsign}
|
||||
<br />
|
||||
{aircraft.bosNutzung}
|
||||
{aircraft.Station.bosUse}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -225,7 +231,11 @@ const AircraftPopupContent = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
const AircraftMarker = ({
|
||||
aircraft,
|
||||
}: {
|
||||
aircraft: ConnectedAircraft & { Station: Station };
|
||||
}) => {
|
||||
const [hideMarker, setHideMarker] = useState(false);
|
||||
const map = useMap();
|
||||
const markerRef = useRef<LMarker>(null);
|
||||
@@ -293,7 +303,7 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
}, [map, openAircraftMarker, handleConflict]);
|
||||
|
||||
const getMarkerHTML = (
|
||||
aircraft: Aircraft,
|
||||
aircraft: ConnectedAircraft & { Station: Station },
|
||||
anchor: "topleft" | "topright" | "bottomleft" | "bottomright",
|
||||
) => {
|
||||
return `<div
|
||||
@@ -327,12 +337,12 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
${aircraft.fmsStatus}
|
||||
</span>
|
||||
<span class="text-white text-[15px] text-nowrap">
|
||||
${aircraft.bosName}
|
||||
${aircraft.Station.bosCallsign}
|
||||
</span>
|
||||
<div
|
||||
data-id="${aircraft.id}"
|
||||
data-anchor-lat="${aircraft.location.lat}"
|
||||
data-anchor-lng="${aircraft.location.lng}"
|
||||
data-anchor-lat="${aircraft.posLat}"
|
||||
data-anchor-lng="${aircraft.posLng}"
|
||||
id="marker-domain-aircraft-${aircraft.id}"
|
||||
class="${cn(
|
||||
"map-collision absolute w-[200%] h-[200%] top-0 left-0 transform pointer-events-none",
|
||||
@@ -345,16 +355,18 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
|
||||
return (
|
||||
<Fragment key={aircraft.id}>
|
||||
<Marker
|
||||
ref={markerRef}
|
||||
position={[aircraft.location.lat, aircraft.location.lng]}
|
||||
icon={
|
||||
new DivIcon({
|
||||
iconAnchor: [0, 0],
|
||||
html: getMarkerHTML(aircraft, anchor),
|
||||
})
|
||||
}
|
||||
/>
|
||||
{aircraft.simulatorConnected && (
|
||||
<Marker
|
||||
ref={markerRef}
|
||||
position={[aircraft.posLat!, aircraft.posLng!]}
|
||||
icon={
|
||||
new DivIcon({
|
||||
iconAnchor: [0, 0],
|
||||
html: getMarkerHTML(aircraft, anchor),
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{openAircraftMarker.some((m) => m.id === aircraft.id) && !hideMarker && (
|
||||
<SmartPopup
|
||||
options={{
|
||||
@@ -362,7 +374,7 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
}}
|
||||
id={`aircraft-${aircraft.id}`}
|
||||
ref={popupRef}
|
||||
position={[aircraft.location.lat, aircraft.location.lng]}
|
||||
position={[aircraft.posLat!, aircraft.posLng!]}
|
||||
autoClose={false}
|
||||
closeOnClick={false}
|
||||
autoPan={false}
|
||||
@@ -379,12 +391,14 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
};
|
||||
|
||||
export const AircraftLayer = () => {
|
||||
const aircrafts = useAircraftsStore((state) => state.aircrafts);
|
||||
const { data: aircrafts } = useQuery({
|
||||
queryKey: ["aircrafts"],
|
||||
queryFn: getConnectedAircraftsAPI,
|
||||
});
|
||||
|
||||
// IDEA: Add Marker to Map Layer / LayerGroup
|
||||
return (
|
||||
<>
|
||||
{aircrafts.map((aircraft) => {
|
||||
{aircrafts?.map((aircraft) => {
|
||||
return <AircraftMarker key={aircraft.id} aircraft={aircraft} />;
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { OSMWay } from "@repo/db";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { usePannelStore } from "_store/pannelStore";
|
||||
import { MapPinned, Search } from "lucide-react";
|
||||
@@ -29,6 +31,43 @@ export const ContextMenu = () => {
|
||||
|
||||
if (!contextMenu) return null;
|
||||
|
||||
const addOSMobjects = async () => {
|
||||
const res = await fetch(
|
||||
`https://overpass-api.de/api/interpreter?data=${encodeURIComponent(`
|
||||
[out:json];
|
||||
(
|
||||
way["building"](around:100, ${contextMenu.lat}, ${contextMenu.lng});
|
||||
relation["building"](around:100, ${contextMenu.lat}, ${contextMenu.lng});
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
`)}`,
|
||||
);
|
||||
const data = await res.json();
|
||||
const parsed: OSMWay[] = data.elements
|
||||
.filter((e: any) => e.type === "way")
|
||||
.map((e: any) => {
|
||||
return {
|
||||
wayID: e.id,
|
||||
nodes: e.nodes.map((nodeId: string) => {
|
||||
const node = data.elements.find(
|
||||
(element: any) => element.id === nodeId,
|
||||
);
|
||||
return {
|
||||
lat: node.lat,
|
||||
lon: node.lon,
|
||||
};
|
||||
}),
|
||||
tags: e.tags,
|
||||
type: e.type,
|
||||
} as OSMWay;
|
||||
});
|
||||
|
||||
setSearchElements(parsed);
|
||||
return parsed;
|
||||
};
|
||||
|
||||
return (
|
||||
<Popup
|
||||
position={[contextMenu.lat, contextMenu.lng]}
|
||||
@@ -70,6 +109,31 @@ export const ContextMenu = () => {
|
||||
place_rank: number;
|
||||
type: string;
|
||||
};
|
||||
const objects = await addOSMobjects();
|
||||
const exactAddress = objects.find((object) => {
|
||||
return (
|
||||
object.tags["addr:street"] == data.address.road &&
|
||||
object.tags["addr:housenumber"]?.includes(
|
||||
data.address.house_number,
|
||||
)
|
||||
);
|
||||
});
|
||||
const closestToContext = objects.reduce((prev, curr) => {
|
||||
const prevLat = prev.nodes?.[0]?.lat ?? 0;
|
||||
const prevLon = prev.nodes?.[0]?.lon ?? 0;
|
||||
const currLat = curr.nodes?.[0]?.lat ?? 0;
|
||||
const currLon = curr.nodes?.[0]?.lon ?? 0;
|
||||
const prevDistance = Math.sqrt(
|
||||
Math.pow(prevLat - contextMenu.lat, 2) +
|
||||
Math.pow(prevLon - contextMenu.lng, 2),
|
||||
);
|
||||
const currDistance = Math.sqrt(
|
||||
Math.pow(currLat - contextMenu.lat, 2) +
|
||||
Math.pow(currLon - contextMenu.lng, 2),
|
||||
);
|
||||
return prevDistance < currDistance ? prev : curr;
|
||||
});
|
||||
|
||||
setOpen(true);
|
||||
setMissionFormValues({
|
||||
addressLat: contextMenu.lat,
|
||||
@@ -78,6 +142,7 @@ export const ContextMenu = () => {
|
||||
addressStreet: `${data.address.road}, ${data.address.house_number || "keine HN"}`,
|
||||
addressZip: data.address.postcode,
|
||||
state: "draft",
|
||||
addressOSMways: [(exactAddress || closestToContext) as any],
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -86,39 +151,7 @@ export const ContextMenu = () => {
|
||||
<button
|
||||
className="btn btn-sm rounded-full bg-rescuetrack aspect-square"
|
||||
onClick={async () => {
|
||||
const res = await fetch(
|
||||
`https://overpass-api.de/api/interpreter?data=${encodeURIComponent(`
|
||||
[out:json];
|
||||
(
|
||||
way["building"](around:100, ${contextMenu.lat}, ${contextMenu.lng});
|
||||
relation["building"](around:100, ${contextMenu.lat}, ${contextMenu.lng});
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
`)}`,
|
||||
);
|
||||
const data = await res.json();
|
||||
setSearchElements(
|
||||
data.elements
|
||||
.filter((e: any) => e.type === "way")
|
||||
.map((e: any) => {
|
||||
return {
|
||||
id: e.id,
|
||||
nodes: e.nodes.map((nodeId: string) => {
|
||||
const node = data.elements.find(
|
||||
(element: any) => element.id === nodeId,
|
||||
);
|
||||
return {
|
||||
lat: node.lat,
|
||||
lon: node.lon,
|
||||
};
|
||||
}),
|
||||
tags: e.tags,
|
||||
type: e.type,
|
||||
};
|
||||
}),
|
||||
);
|
||||
addOSMobjects();
|
||||
}}
|
||||
>
|
||||
<Search size={20} />
|
||||
|
||||
@@ -1,22 +1,45 @@
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { Marker as LMarker } from "leaflet";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Marker, Polygon, Polyline, Popup } from "react-leaflet";
|
||||
import { Fragment, useEffect, useRef } from "react";
|
||||
import { Marker, Polygon, Popup } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getMissionsAPI } from "querys/missions";
|
||||
import { OSMWay } from "@repo/db";
|
||||
|
||||
export const SearchElements = () => {
|
||||
const { searchElements, searchPopup, setSearchPopup, setContextMenu } =
|
||||
useMapStore();
|
||||
const {
|
||||
searchElements,
|
||||
searchPopup,
|
||||
setSearchPopup,
|
||||
setContextMenu,
|
||||
openMissionMarker,
|
||||
} = useMapStore();
|
||||
const missions = useQuery({
|
||||
queryKey: ["missions"],
|
||||
queryFn: () =>
|
||||
getMissionsAPI({
|
||||
OR: [
|
||||
{
|
||||
state: "draft",
|
||||
},
|
||||
{
|
||||
state: "running",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
const poppupRef = useRef<LMarker>(null);
|
||||
const intervalRef = useRef<NodeJS.Timeout>(null);
|
||||
const searchPopupElement = searchElements.find(
|
||||
(element) => element.id === searchPopup?.elementId,
|
||||
(element) => element.wayID === searchPopup?.elementId,
|
||||
);
|
||||
|
||||
const SearchElement = ({
|
||||
element,
|
||||
isActive = false,
|
||||
}: {
|
||||
element: (typeof searchElements)[1];
|
||||
isActive?: boolean;
|
||||
}) => {
|
||||
const ref = useRef<any>(null);
|
||||
|
||||
@@ -24,11 +47,11 @@ export const SearchElements = () => {
|
||||
if (ref.current) {
|
||||
ref.current.on("click", () => {
|
||||
const center = ref.current.getBounds().getCenter();
|
||||
if (searchPopup?.elementId !== element.id) {
|
||||
if (searchPopup?.elementId !== element.wayID) {
|
||||
setSearchPopup({
|
||||
lat: center.lat,
|
||||
lng: center.lng,
|
||||
elementId: element.id,
|
||||
elementId: element.wayID,
|
||||
});
|
||||
} else {
|
||||
setSearchPopup(null);
|
||||
@@ -40,9 +63,12 @@ export const SearchElements = () => {
|
||||
|
||||
return (
|
||||
<Polygon
|
||||
key={element.id}
|
||||
positions={element.nodes.map((node) => [node.lat, node.lon])}
|
||||
color={searchPopup?.elementId === element.id ? "#ff4500" : "#46b7a3"}
|
||||
color={
|
||||
searchPopup?.elementId === element.wayID || isActive
|
||||
? "#ff4500"
|
||||
: "#46b7a3"
|
||||
}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
@@ -86,8 +112,30 @@ export const SearchElements = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{searchElements.map((element) => {
|
||||
return <SearchElement key={element.id} element={element} />;
|
||||
{openMissionMarker.map(({ id }) => {
|
||||
const mission = missions.data?.find((m) => m.id === id);
|
||||
if (!mission) return null;
|
||||
return (
|
||||
<Fragment key={`mission-osm-${mission.id}`}>
|
||||
{(mission.addressOSMways as (OSMWay | null)[])
|
||||
.filter((element): element is OSMWay => element !== null)
|
||||
.map((element: OSMWay, i) => (
|
||||
<SearchElement
|
||||
key={`mission-elem-${element.wayID}-${i}`}
|
||||
element={element}
|
||||
isActive
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{searchElements.map((element, i) => {
|
||||
return (
|
||||
<SearchElement
|
||||
key={`mission-elem-${element.wayID}-${i}`}
|
||||
element={element}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{searchPopup && (
|
||||
<Marker
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "../AircraftMarker";
|
||||
import { Aircraft } from "_store/aircraftsStore";
|
||||
import { ConnectedAircraft, Prisma, Station } from "@repo/db";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { editConnectedAircraftAPI } from "querys/aircrafts";
|
||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||
import { cn } from "helpers/cn";
|
||||
|
||||
const FMSStatusHistory = () => {
|
||||
const dummyData = [
|
||||
@@ -33,17 +38,42 @@ const FMSStatusHistory = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const FMSStatusSelector = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
const FMSStatusSelector = ({
|
||||
aircraft,
|
||||
}: {
|
||||
aircraft: ConnectedAircraft & { Station: Station };
|
||||
}) => {
|
||||
const dispatcherConnected =
|
||||
useDispatchConnectionStore((s) => s.status) === "connected";
|
||||
const [hoveredStatus, setHoveredStatus] = useState<string | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const changeAircraftMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
id,
|
||||
update,
|
||||
}: {
|
||||
id: number;
|
||||
update: Prisma.ConnectedAircraftUpdateInput;
|
||||
}) => {
|
||||
await editConnectedAircraftAPI(id, update);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["aircrafts"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 p-4 justify-center items-center min-h-[136px] h-full">
|
||||
{Array.from({ length: 9 }, (_, i) => (i + 1).toString())
|
||||
.filter((status) => status !== "5") // Exclude status 5
|
||||
.map((status) => (
|
||||
<div
|
||||
<button
|
||||
disabled={!dispatcherConnected}
|
||||
key={status}
|
||||
className="flex justify-center items-center min-w-13 min-h-13 cursor-pointer text-4xl font-bold"
|
||||
className={cn(
|
||||
"flex justify-center items-center min-w-13 min-h-13 cursor-pointer text-4xl font-bold",
|
||||
!dispatcherConnected && "cursor-not-allowed",
|
||||
)}
|
||||
style={{
|
||||
backgroundColor:
|
||||
hoveredStatus === status
|
||||
@@ -60,15 +90,19 @@ const FMSStatusSelector = ({ aircraft }: { aircraft: Aircraft }) => {
|
||||
}}
|
||||
onMouseEnter={() => setHoveredStatus(status)}
|
||||
onMouseLeave={() => setHoveredStatus(null)}
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
// Handle status change logic here
|
||||
console.log(
|
||||
`Status changed to: ${status} for aircraft ${aircraft.id}`,
|
||||
);
|
||||
await changeAircraftMutation.mutateAsync({
|
||||
id: aircraft.id,
|
||||
update: {
|
||||
fmsStatus: status,
|
||||
},
|
||||
});
|
||||
toast.success(`Status changed to ${status}`);
|
||||
}}
|
||||
>
|
||||
{status}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Mission } from "@repo/db";
|
||||
import { ConnectedAircraft, Mission, Station } from "@repo/db";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { SmartPopup, useSmartPopup } from "_components/SmartPopup";
|
||||
import { Aircraft, useAircraftsStore } from "_store/aircraftsStore";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import {
|
||||
FMS_STATUS_COLORS,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
MISSION_STATUS_TEXT_COLORS,
|
||||
} from "dispatch/_components/map/MissionMarkers";
|
||||
import { cn } from "helpers/cn";
|
||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
||||
import { getMissionsAPI } from "querys/missions";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMap } from "react-leaflet";
|
||||
@@ -20,7 +20,7 @@ const PopupContent = ({
|
||||
aircrafts,
|
||||
missions,
|
||||
}: {
|
||||
aircrafts: Aircraft[];
|
||||
aircrafts: (ConnectedAircraft & { Station: Station })[];
|
||||
missions: Mission[];
|
||||
}) => {
|
||||
const { anchor } = useSmartPopup();
|
||||
@@ -101,39 +101,41 @@ const PopupContent = ({
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{aircrafts.map((aircraft) => (
|
||||
<div
|
||||
key={aircraft.id}
|
||||
className="relative w-auto inline-flex items-center gap-2 text-nowrap cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: FMS_STATUS_COLORS[aircraft.fmsStatus],
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpenAircraftMarker({
|
||||
open: [
|
||||
{
|
||||
id: aircraft.id,
|
||||
tab: "aircraft",
|
||||
},
|
||||
],
|
||||
close: [],
|
||||
});
|
||||
map.setView([aircraft.location.lat, aircraft.location.lng], 12, {
|
||||
animate: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="mx-2 my-0.5 text-gt font-bold"
|
||||
{aircrafts
|
||||
.filter((a) => a.simulatorConnected)
|
||||
.map((aircraft) => (
|
||||
<div
|
||||
key={aircraft.id}
|
||||
className="relative w-auto inline-flex items-center gap-2 text-nowrap cursor-pointer"
|
||||
style={{
|
||||
color: FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus],
|
||||
backgroundColor: FMS_STATUS_COLORS[aircraft.fmsStatus],
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpenAircraftMarker({
|
||||
open: [
|
||||
{
|
||||
id: aircraft.id,
|
||||
tab: "aircraft",
|
||||
},
|
||||
],
|
||||
close: [],
|
||||
});
|
||||
map.setView([aircraft.posLat!, aircraft.posLng!], 12, {
|
||||
animate: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{aircraft.fmsStatus}
|
||||
</span>
|
||||
<span>{aircraft.bosName}</span>
|
||||
</div>
|
||||
))}
|
||||
<span
|
||||
className="mx-2 my-0.5 text-gt font-bold"
|
||||
style={{
|
||||
color: FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus],
|
||||
}}
|
||||
>
|
||||
{aircraft.fmsStatus}
|
||||
</span>
|
||||
<span>{aircraft.Station.bosCallsign}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -141,7 +143,11 @@ const PopupContent = ({
|
||||
|
||||
export const MarkerCluster = () => {
|
||||
const map = useMap();
|
||||
const aircrafts = useAircraftsStore((state) => state.aircrafts);
|
||||
const { data: aircrafts } = useQuery({
|
||||
queryKey: ["aircrafts"],
|
||||
queryFn: getConnectedAircraftsAPI,
|
||||
});
|
||||
|
||||
const { data: missions } = useQuery({
|
||||
queryKey: ["missions"],
|
||||
queryFn: () =>
|
||||
@@ -151,7 +157,7 @@ export const MarkerCluster = () => {
|
||||
});
|
||||
const [cluster, setCluster] = useState<
|
||||
{
|
||||
aircrafts: Aircraft[];
|
||||
aircrafts: (ConnectedAircraft & { Station: Station })[];
|
||||
missions: Mission[];
|
||||
lat: number;
|
||||
lng: number;
|
||||
@@ -162,36 +168,38 @@ export const MarkerCluster = () => {
|
||||
const handleZoom = () => {
|
||||
const zoom = map.getZoom();
|
||||
let newCluster: typeof cluster = [];
|
||||
aircrafts.forEach((aircraft) => {
|
||||
const lat = aircraft.location.lat;
|
||||
const lng = aircraft.location.lng;
|
||||
aircrafts
|
||||
?.filter((a) => a.simulatorConnected)
|
||||
.forEach((aircraft) => {
|
||||
const lat = aircraft.posLat!;
|
||||
const lng = aircraft.posLng!;
|
||||
|
||||
const existingClusterIndex = newCluster.findIndex(
|
||||
(c) => Math.abs(c.lat - lat) < 1 && Math.abs(c.lng - lng) < 1,
|
||||
);
|
||||
const existingCluster = newCluster[existingClusterIndex];
|
||||
if (existingCluster) {
|
||||
newCluster = [...newCluster].map((c, i) => {
|
||||
if (i === existingClusterIndex) {
|
||||
return {
|
||||
...c,
|
||||
aircrafts: [...c.aircrafts, aircraft],
|
||||
};
|
||||
}
|
||||
return c;
|
||||
});
|
||||
} else {
|
||||
newCluster = [
|
||||
...newCluster,
|
||||
{
|
||||
aircrafts: [aircraft],
|
||||
missions: [],
|
||||
lat,
|
||||
lng,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
const existingClusterIndex = newCluster.findIndex(
|
||||
(c) => Math.abs(c.lat - lat) < 1 && Math.abs(c.lng - lng) < 1,
|
||||
);
|
||||
const existingCluster = newCluster[existingClusterIndex];
|
||||
if (existingCluster) {
|
||||
newCluster = [...newCluster].map((c, i) => {
|
||||
if (i === existingClusterIndex) {
|
||||
return {
|
||||
...c,
|
||||
aircrafts: [...c.aircrafts, aircraft],
|
||||
};
|
||||
}
|
||||
return c;
|
||||
});
|
||||
} else {
|
||||
newCluster = [
|
||||
...newCluster,
|
||||
{
|
||||
aircrafts: [aircraft],
|
||||
missions: [],
|
||||
lat,
|
||||
lng,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
missions?.forEach((mission) => {
|
||||
const lat = mission.addressLat;
|
||||
const lng = mission.addressLng;
|
||||
@@ -224,10 +232,7 @@ export const MarkerCluster = () => {
|
||||
});
|
||||
|
||||
const clusterWithAvgPos = newCluster.map((c) => {
|
||||
const aircraftPos = c.aircrafts.map((a) => [
|
||||
a.location.lat,
|
||||
a.location.lng,
|
||||
]);
|
||||
const aircraftPos = c.aircrafts.map((a) => [a.posLat, a.posLng]);
|
||||
const missionPos = c.missions.map((m) => [m.addressLat, m.addressLng]);
|
||||
const allPos = [...aircraftPos, ...missionPos];
|
||||
|
||||
|
||||
@@ -16,11 +16,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { createMissionAPI, editMissionAPI } from "querys/missions";
|
||||
import { getKeywordsAPI } from "querys/keywords";
|
||||
import { getStationsAPI } from "querys/stations";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
|
||||
export const MissionForm = () => {
|
||||
const { isEditingMission, editingMissionId, setEditingMission } =
|
||||
usePannelStore();
|
||||
const queryClient = useQueryClient();
|
||||
const setSeachOSMElements = useMapStore((s) => s.setSearchElements);
|
||||
|
||||
const { data: keywords } = useQuery({
|
||||
queryKey: ["keywords"],
|
||||
@@ -90,28 +92,10 @@ export const MissionForm = () => {
|
||||
}
|
||||
}, [session.data?.user.id, form]);
|
||||
|
||||
/* const formValues = form.watch();
|
||||
useEffect(() => {
|
||||
if (formValues) {
|
||||
setMissionFormValues(formValues);
|
||||
}
|
||||
}, [formValues, setMissionFormValues]); */
|
||||
|
||||
useEffect(() => {
|
||||
if (missionFormValues) {
|
||||
if (Object.keys(missionFormValues).length === 0) {
|
||||
console.log("resetting form");
|
||||
form.reset(/* {
|
||||
addressStreet: "",
|
||||
addressCity: "",
|
||||
addressZip: "",
|
||||
missionStationIds: [],
|
||||
missionKeywordName: "",
|
||||
missionKeywordAbbreviation: "",
|
||||
missionKeywordCategory: "",
|
||||
|
||||
...defaultFormValues,
|
||||
} */);
|
||||
form.reset();
|
||||
return;
|
||||
}
|
||||
for (const key in missionFormValues) {
|
||||
@@ -123,7 +107,6 @@ export const MissionForm = () => {
|
||||
}
|
||||
}, [missionFormValues, form, defaultFormValues]);
|
||||
|
||||
console.log(form.formState.errors);
|
||||
return (
|
||||
<form className="space-y-4">
|
||||
{/* Koorinaten Section */}
|
||||
@@ -313,9 +296,13 @@ export const MissionForm = () => {
|
||||
className="textarea textarea-primary textarea-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-error">
|
||||
Du musst noch ein Gebäude auswählen, um den Einsatz zu erstellen.
|
||||
</p>
|
||||
|
||||
{missionFormValues?.addressOSMways?.length && (
|
||||
<p className="text-sm text-info">
|
||||
In diesem Einsatz gibt es {missionFormValues?.addressOSMways?.length}{" "}
|
||||
Gebäude
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="form-control min-h-[140px]">
|
||||
<div className="flex gap-2">
|
||||
@@ -334,6 +321,7 @@ export const MissionForm = () => {
|
||||
toast.success(
|
||||
`Einsatz ${newMission.id} erfolgreich aktualisiert`,
|
||||
);
|
||||
setSeachOSMElements([]); // Reset search elements
|
||||
setEditingMission(false, null); // Reset editing state
|
||||
form.reset(); // Reset the form
|
||||
setOpen(false);
|
||||
@@ -359,6 +347,7 @@ export const MissionForm = () => {
|
||||
await createMissionMutation.mutateAsync(
|
||||
mission as unknown as Prisma.MissionCreateInput,
|
||||
);
|
||||
setSeachOSMElements([]); // Reset search elements
|
||||
toast.success(`Einsatz ${newMission.id} erstellt`);
|
||||
// TODO: Einsatz alarmieren
|
||||
setOpen(false);
|
||||
@@ -382,6 +371,7 @@ export const MissionForm = () => {
|
||||
await createMissionMutation.mutateAsync(
|
||||
mission as unknown as Prisma.MissionCreateInput,
|
||||
);
|
||||
setSeachOSMElements([]); // Reset search elements
|
||||
|
||||
toast.success(`Einsatz ${newMission.id} erstellt`);
|
||||
form.reset();
|
||||
|
||||
@@ -6,6 +6,16 @@
|
||||
@theme {
|
||||
--color-rescuetrack: #46b7a3;
|
||||
--color-rescuetrack-highlight: #ff4500;
|
||||
--color-fms-0: rgb(140, 10, 10);
|
||||
--color-fms-1: rgb(10, 134, 25);
|
||||
--color-fms-2: rgb(10, 134, 25);
|
||||
--color-fms-3: rgb(140, 10, 10);
|
||||
--color-fms-4: rgb(140, 10, 10);
|
||||
--color-fms-5: rgb(231, 77, 22);
|
||||
--color-fms-6: rgb(85, 85, 85);
|
||||
--color-fms-7: rgb(140, 10, 10);
|
||||
--color-fms-8: rgb(186, 105, 0);
|
||||
--color-fms-9: rgb(10, 134, 25);
|
||||
}
|
||||
|
||||
.leaflet-popup-tip-container {
|
||||
|
||||
25
apps/dispatch/app/querys/aircrafts.ts
Normal file
25
apps/dispatch/app/querys/aircrafts.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ConnectedAircraft, Prisma, Station } from "@repo/db";
|
||||
import axios from "axios";
|
||||
import { serverApi } from "helpers/axios";
|
||||
|
||||
export const getConnectedAircraftsAPI = async () => {
|
||||
const res =
|
||||
await axios.get<(ConnectedAircraft & { Station: Station })[]>(
|
||||
"/api/aircrafts",
|
||||
); // return only connected aircrafts
|
||||
if (res.status !== 200) {
|
||||
throw new Error("Failed to fetch stations");
|
||||
}
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const editConnectedAircraftAPI = async (
|
||||
id: number,
|
||||
mission: Prisma.ConnectedAircraftUpdateInput,
|
||||
) => {
|
||||
const respone = await serverApi.patch<ConnectedAircraft>(
|
||||
`/aircrafts/${id}`,
|
||||
mission,
|
||||
);
|
||||
return respone.data;
|
||||
};
|
||||
Binary file not shown.
@@ -1,8 +1,8 @@
|
||||
export interface MissionVehicleLog {
|
||||
wayID: string;
|
||||
tags: string[];
|
||||
export interface OSMWay {
|
||||
wayID: number;
|
||||
tags: Record<string, string>;
|
||||
nodes: {
|
||||
lat: number;
|
||||
lon: number;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./ParticipantLog";
|
||||
export * from "./MissionVehicleLog";
|
||||
export * from "./User";
|
||||
export * from "./OSMway";
|
||||
|
||||
@@ -3,6 +3,16 @@ model ConnectedAircraft {
|
||||
userId String
|
||||
publicUser Json
|
||||
lastHeartbeat DateTime @default(now())
|
||||
fmsStatus String @default("6")
|
||||
// position:
|
||||
posLat Float?
|
||||
posLng Float?
|
||||
posAlt Int?
|
||||
posSpeed Int?
|
||||
posHeading Int?
|
||||
simulator String?
|
||||
simulatorConnected Boolean @default(false)
|
||||
posH145active Boolean @default(false)
|
||||
stationId Int
|
||||
loginTime DateTime @default(now())
|
||||
esimatedLogoutTime DateTime?
|
||||
|
||||
Reference in New Issue
Block a user