added Mission Closed Toeast, enhanced logic
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { MissionLog, prisma } from "@repo/db";
|
import { MissionLog, NotificationPayload, prisma } from "@repo/db";
|
||||||
|
import { io } from "index";
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
|
|
||||||
const removeClosedMissions = async () => {
|
const removeClosedMissions = async () => {
|
||||||
@@ -11,6 +12,7 @@ const removeClosedMissions = async () => {
|
|||||||
const lastAlert = (mission.missionLog as unknown as MissionLog[]).find((l) => {
|
const lastAlert = (mission.missionLog as unknown as MissionLog[]).find((l) => {
|
||||||
return l.type === "alert-log";
|
return l.type === "alert-log";
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null;
|
const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null;
|
||||||
|
|
||||||
const aircraftsInMission = await prisma.connectedAircraft.findMany({
|
const aircraftsInMission = await prisma.connectedAircraft.findMany({
|
||||||
@@ -21,17 +23,65 @@ const removeClosedMissions = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const allConnectedAircraftsInIdleStatus = aircraftsInMission.every((a) =>
|
||||||
|
["1", "2", "6"].includes(a.fmsStatus),
|
||||||
|
);
|
||||||
|
|
||||||
|
const allStationsInMissionChangedFromStatus4to1Or8to1 = mission.missionStationIds.every(
|
||||||
|
(stationId) => {
|
||||||
|
const status4Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => {
|
||||||
|
return (
|
||||||
|
l.type === "station-log" &&
|
||||||
|
l.data?.stationId === stationId &&
|
||||||
|
l.data?.newFMSstatus === "4"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const status8Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => {
|
||||||
|
return (
|
||||||
|
l.type === "station-log" &&
|
||||||
|
l.data?.stationId === stationId &&
|
||||||
|
l.data?.newFMSstatus === "8"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const status1Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => {
|
||||||
|
return (
|
||||||
|
l.type === "station-log" &&
|
||||||
|
l.data?.stationId === stationId &&
|
||||||
|
l.data?.newFMSstatus === "1"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
status4Log !== -1 &&
|
||||||
|
status1Log !== -1 &&
|
||||||
|
(status4Log < status1Log || status8Log < status1Log)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const missionHastManualReactivation = (mission.missionLog as unknown as MissionLog[]).some(
|
||||||
|
(l) => l.type === "reopened-log",
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
aircraftsInMission.length > 0 && // Check if any aircraft is still active
|
!allConnectedAircraftsInIdleStatus // If some aircrafts are still active, do not close the mission
|
||||||
!aircraftsInMission.some((a) => ["1", "2", "6"].includes(a.fmsStatus)) // Check if any aircraft is in a status that indicates it's not inactive
|
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
if (!lastAlertTime) return;
|
if (!lastAlertTime) return;
|
||||||
|
|
||||||
// change State to closed if last alert was more than 180 minutes ago
|
// Case 1: Forgotten Mission, last alert more than 3 Hours ago
|
||||||
if (now.getTime() - lastAlertTime.getTime() < 30 * 60 * 1000) return;
|
// Case 2: All stations in mission changed from status 4 to 1 or from status 8 to 1
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
now.getTime() - lastAlertTime.getTime() > 1000 * 60 * 180 ||
|
||||||
|
allStationsInMissionChangedFromStatus4to1Or8to1
|
||||||
|
) ||
|
||||||
|
missionHastManualReactivation
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
const log: MissionLog = {
|
const log: MissionLog = {
|
||||||
type: "completed-log",
|
type: "completed-log",
|
||||||
auto: true,
|
auto: true,
|
||||||
@@ -39,7 +89,7 @@ const removeClosedMissions = async () => {
|
|||||||
data: {},
|
data: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
await prisma.mission.update({
|
const updatedMission = await prisma.mission.update({
|
||||||
where: {
|
where: {
|
||||||
id: mission.id,
|
id: mission.id,
|
||||||
},
|
},
|
||||||
@@ -50,6 +100,16 @@ const removeClosedMissions = async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
io.to("dispatchers").emit("new-mission", { updatedMission });
|
||||||
|
io.to("dispatchers").emit("notification", {
|
||||||
|
type: "mission-auto-close",
|
||||||
|
status: "chron",
|
||||||
|
message: `Einsatz ${updatedMission.publicId} wurde aufgrund von Inaktivität geschlossen.`,
|
||||||
|
data: {
|
||||||
|
missionId: updatedMission.id,
|
||||||
|
publicMissionId: updatedMission.publicId,
|
||||||
|
},
|
||||||
|
} as NotificationPayload);
|
||||||
console.log(`Mission ${mission.id} closed due to inactivity.`);
|
console.log(`Mission ${mission.id} closed due to inactivity.`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -74,11 +134,11 @@ const removeConnectedAircrafts = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
cron.schedule("*/5 * * * *", async () => {
|
cron.schedule("*/1 * * * *", async () => {
|
||||||
try {
|
try {
|
||||||
await removeClosedMissions();
|
await removeClosedMissions();
|
||||||
await removeConnectedAircrafts();
|
await removeConnectedAircrafts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing closed missions:", error);
|
console.error("Error on cron job:", error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ router.patch("/:id", async (req, res) => {
|
|||||||
where: { id: Number(id) },
|
where: { id: Number(id) },
|
||||||
data: req.body,
|
data: req.body,
|
||||||
});
|
});
|
||||||
io.to("dispatchers").emit("update-mission", updatedMission);
|
io.to("dispatchers").emit("update-mission", { updatedMission });
|
||||||
res.json(updatedMission);
|
res.json(updatedMission);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useMapStore } from "_store/mapStore";
|
|||||||
import { AdminMessageToast } from "_components/customToasts/AdminMessage";
|
import { AdminMessageToast } from "_components/customToasts/AdminMessage";
|
||||||
import { pilotSocket } from "(app)/pilot/socket";
|
import { pilotSocket } from "(app)/pilot/socket";
|
||||||
import { QUICK_RESPONSE, StatusToast } from "_components/customToasts/StationStatusToast";
|
import { QUICK_RESPONSE, StatusToast } from "_components/customToasts/StationStatusToast";
|
||||||
|
import { MissionAutoCloseToast } from "_components/customToasts/MissionAutoClose";
|
||||||
|
|
||||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||||
const mapStore = useMapStore((s) => s);
|
const mapStore = useMapStore((s) => s);
|
||||||
@@ -79,6 +80,14 @@ export function QueryProvider({ children }: { children: ReactNode }) {
|
|||||||
duration: 60000,
|
duration: 60000,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "mission-auto-close":
|
||||||
|
toast.custom(
|
||||||
|
(t) => <MissionAutoCloseToast event={notification} t={t} mapStore={mapStore} />,
|
||||||
|
{
|
||||||
|
duration: 60000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
toast("unbekanntes Notification-Event");
|
toast("unbekanntes Notification-Event");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { getPublicUser, MissionAutoClose, Prisma } from "@repo/db";
|
||||||
|
import { JsonValueType } from "@repo/db/zod";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { BaseNotification } from "_components/customToasts/BaseNotification";
|
||||||
|
import { editMissionAPI } from "_querys/missions";
|
||||||
|
import { MapStore } from "_store/mapStore";
|
||||||
|
import { Clock, X } from "lucide-react";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import toast, { Toast } from "react-hot-toast";
|
||||||
|
|
||||||
|
export const MissionAutoCloseToast = ({
|
||||||
|
event,
|
||||||
|
t,
|
||||||
|
mapStore,
|
||||||
|
}: {
|
||||||
|
event: MissionAutoClose;
|
||||||
|
t: Toast;
|
||||||
|
mapStore: MapStore;
|
||||||
|
}) => {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const editMissionMutation = useMutation({
|
||||||
|
mutationFn: ({ id, mission }: { id: number; mission: Partial<Prisma.MissionUpdateInput> }) =>
|
||||||
|
editMissionAPI(id, mission),
|
||||||
|
mutationKey: ["missions"],
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["missions"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseNotification icon={<Clock />} className="flex flex-row">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h1 className="text-warning font-bold">Inaktiver Einsatz wurde automatisch geschlossen</h1>
|
||||||
|
<p>{event.message}</p>
|
||||||
|
</div>
|
||||||
|
<div className="ml-11">
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!session?.user) return;
|
||||||
|
const mission = await editMissionMutation.mutateAsync({
|
||||||
|
id: event.data.missionId,
|
||||||
|
mission: {
|
||||||
|
state: "running",
|
||||||
|
missionLog: {
|
||||||
|
push: {
|
||||||
|
type: "reopened-log",
|
||||||
|
timeStamp: new Date().toISOString(),
|
||||||
|
data: {
|
||||||
|
user: getPublicUser(session?.user, {
|
||||||
|
ignorePrivacy: true,
|
||||||
|
}) as unknown as JsonValueType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mapStore.setMap({
|
||||||
|
zoom: 14,
|
||||||
|
center: {
|
||||||
|
lat: mission.addressLat,
|
||||||
|
lng: mission.addressLng,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mapStore.setOpenMissionMarker({
|
||||||
|
open: [
|
||||||
|
{
|
||||||
|
id: mission.id,
|
||||||
|
tab: "home",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
close: [],
|
||||||
|
});
|
||||||
|
toast.dismiss(t.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
schließen widerrufen
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-ghost btn-sm" onClick={() => toast.remove(t.id)}>
|
||||||
|
<X size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</BaseNotification>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -735,10 +735,15 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => {
|
|||||||
<span className="text-base-content">{entry.data.message}</span>
|
<span className="text-base-content">{entry.data.message}</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
if (entry.type === "alert-log") {
|
if (
|
||||||
const alertReceiver = entry.auto
|
entry.type === "alert-log" ||
|
||||||
? null
|
entry.type === "completed-log" ||
|
||||||
: entry.data.station?.bosCallsignShort || entry.data.vehicle;
|
entry.type === "reopened-log"
|
||||||
|
) {
|
||||||
|
const alertReceiver =
|
||||||
|
entry.auto || entry.type !== "alert-log"
|
||||||
|
? null
|
||||||
|
: entry.data.station?.bosCallsignShort || entry.data.vehicle;
|
||||||
return (
|
return (
|
||||||
<li key={index} className="flex items-center gap-2">
|
<li key={index} className="flex items-center gap-2">
|
||||||
<span className="text-base-content">
|
<span className="text-base-content">
|
||||||
@@ -755,8 +760,8 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => {
|
|||||||
>
|
>
|
||||||
{!entry.auto && (
|
{!entry.auto && (
|
||||||
<>
|
<>
|
||||||
{entry.data.user.firstname?.[0]?.toUpperCase() ?? "?"}
|
{entry.data.user?.firstname?.[0]?.toUpperCase() ?? "?"}
|
||||||
{entry.data.user.lastname?.[0]?.toUpperCase() ?? "?"}
|
{entry.data.user?.lastname?.[0]?.toUpperCase() ?? "?"}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{entry.auto && "AUTO"}
|
{entry.auto && "AUTO"}
|
||||||
@@ -781,7 +786,15 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-base-content">Einsatz alarmiert</span>
|
{entry.type === "alert-log" && (
|
||||||
|
<span className="text-base-content">Einsatz alarmiert</span>
|
||||||
|
)}
|
||||||
|
{entry.type === "completed-log" && (
|
||||||
|
<span className="text-base-content">Einsatz abgeschlossen</span>
|
||||||
|
)}
|
||||||
|
{entry.type === "reopened-log" && (
|
||||||
|
<span className="text-base-content">Einsatz wiedereröffnet</span>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,15 @@ export interface MissionCompletedLog {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MissionReopenedLog {
|
||||||
|
type: "reopened-log";
|
||||||
|
auto: false;
|
||||||
|
timeStamp: string;
|
||||||
|
data: {
|
||||||
|
user?: PublicUser;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type MissionLog =
|
export type MissionLog =
|
||||||
| MissionStationLog
|
| MissionStationLog
|
||||||
| MissionMessageLog
|
| MissionMessageLog
|
||||||
@@ -80,4 +89,5 @@ export type MissionLog =
|
|||||||
| MissionAlertLog
|
| MissionAlertLog
|
||||||
| MissionAlertLogAuto
|
| MissionAlertLogAuto
|
||||||
| MissionCompletedLog
|
| MissionCompletedLog
|
||||||
| MissionVehicleLog;
|
| MissionVehicleLog
|
||||||
|
| MissionReopenedLog;
|
||||||
|
|||||||
@@ -39,8 +39,19 @@ export interface StationStatus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MissionAutoClose = {
|
||||||
|
type: "mission-auto-close";
|
||||||
|
status: "chron";
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
missionId: number;
|
||||||
|
publicMissionId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type NotificationPayload =
|
export type NotificationPayload =
|
||||||
| ValidationFailed
|
| ValidationFailed
|
||||||
| ValidationSuccess
|
| ValidationSuccess
|
||||||
| AdminMessage
|
| AdminMessage
|
||||||
| StationStatus;
|
| StationStatus
|
||||||
|
| MissionAutoClose;
|
||||||
|
|||||||
Reference in New Issue
Block a user