cleaned code, added hpg validation in Mission Marker

This commit is contained in:
PxlLoewe
2025-05-22 01:10:08 -07:00
parent 8a4b42f02b
commit 05e74077a5
7 changed files with 209 additions and 296 deletions

View File

@@ -235,6 +235,7 @@ router.post("/:id/validate-hpg", async (req, res) => {
} }
}, },
); */ ); */
// TODO: remove this after testing
setTimeout(() => { setTimeout(() => {
io.to(`user:${req.user?.id}`).emit("notification", { io.to(`user:${req.user?.id}`).emit("notification", {
type: "hpg-validation", type: "hpg-validation",

View File

@@ -4,13 +4,7 @@ import { Server, Socket } from "socket.io";
export const handleConnectPilot = export const handleConnectPilot =
(socket: Socket, io: Server) => (socket: Socket, io: Server) =>
async ({ async ({ logoffTime, stationId }: { logoffTime: string; stationId: string }) => {
logoffTime,
stationId,
}: {
logoffTime: string;
stationId: string;
}) => {
try { try {
const user = socket.data.user; // User ID aus dem JWT-Token const user = socket.data.user; // User ID aus dem JWT-Token
const userId = socket.data.user.id; // User ID aus dem JWT-Token const userId = socket.data.user.id; // User ID aus dem JWT-Token
@@ -32,9 +26,7 @@ export const handleConnectPilot =
}); });
if (existingConnection) { if (existingConnection) {
await io await io.to(`user:${user.id}`).emit("force-disconnect", "double-connection");
.to(`user:${user.id}`)
.emit("force-disconnect", "double-connection");
await prisma.connectedAircraft.updateMany({ await prisma.connectedAircraft.updateMany({
where: { where: {
userId: user.id, userId: user.id,
@@ -72,6 +64,7 @@ export const handleConnectPilot =
stationId: parseInt(stationId), stationId: parseInt(stationId),
posLat: 51.45, posLat: 51.45,
posLng: 9.77, posLng: 9.77,
posH145active: true,
}, },
}); });

View File

@@ -2,29 +2,11 @@ import { Marker, useMap } from "react-leaflet";
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet"; import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
import { useMapStore } from "_store/mapStore"; import { useMapStore } from "_store/mapStore";
import { usePannelStore } from "_store/pannelStore"; import { usePannelStore } from "_store/pannelStore";
import { import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
Fragment,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { cn } from "helpers/cn"; import { cn } from "helpers/cn";
import { import { ClipboardList, Cross, House, Minimize2, SmartphoneNfc, PencilLine } from "lucide-react";
ClipboardList, import { calculateAnchor, SmartPopup, useSmartPopup } from "_components/SmartPopup";
Cross, import { Mission, MissionState } from "@repo/db";
House,
Minimize2,
SmartphoneNfc,
PencilLine,
} from "lucide-react";
import {
calculateAnchor,
SmartPopup,
useSmartPopup,
} from "_components/SmartPopup";
import { HpgValidationState, Mission, MissionState } from "@repo/db";
import Einsatzdetails, { import Einsatzdetails, {
FMSStatusHistory, FMSStatusHistory,
Patientdetails, Patientdetails,
@@ -33,14 +15,15 @@ import Einsatzdetails, {
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { getMissionsAPI } from "querys/missions"; import { getMissionsAPI } from "querys/missions";
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore"; import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
import { HPGValidationRequired } from "helpers/hpgValidationRequired";
import { getConnectedAircraftsAPI } from "querys/aircrafts";
export const MISSION_STATUS_COLORS: Record<MissionState | "attention", string> = export const MISSION_STATUS_COLORS: Record<MissionState | "attention", string> = {
{ draft: "#0092b8",
draft: "#0092b8", running: "#155dfc",
running: "#155dfc", finished: "#155dfc",
finished: "#155dfc", attention: "rgb(186,105,0)",
attention: "rgb(186,105,0)", };
};
export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = { export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = {
draft: "#00d3f2", draft: "#00d3f2",
@@ -48,12 +31,17 @@ export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = {
finished: "#50a2ff", finished: "#50a2ff",
}; };
const MissionPopupContent = ({ mission }: { mission: Mission }) => { const MissionPopupContent = ({
mission,
hpgNeedsAttention,
}: {
mission: Mission;
hpgNeedsAttention?: boolean;
}) => {
const { setEditingMission } = usePannelStore(); const { setEditingMission } = usePannelStore();
const setMissionMarker = useMapStore((state) => state.setOpenMissionMarker); const setMissionMarker = useMapStore((state) => state.setOpenMissionMarker);
const currentTab = useMapStore( const currentTab = useMapStore(
(state) => (state) => state.openMissionMarker.find((m) => m.id === mission.id)?.tab ?? "home",
state.openMissionMarker.find((m) => m.id === mission.id)?.tab ?? "home",
); );
const handleTabChange = useCallback( const handleTabChange = useCallback(
@@ -75,7 +63,7 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => {
const renderTabContent = useMemo(() => { const renderTabContent = useMemo(() => {
switch (currentTab) { switch (currentTab) {
case "home": case "home":
return <Einsatzdetails mission={mission} />; return <Einsatzdetails mission={mission} hpgNeedsAttention={hpgNeedsAttention} />;
case "details": case "details":
return <Rettungsmittel mission={mission} />; return <Rettungsmittel mission={mission} />;
case "patient": case "patient":
@@ -87,9 +75,7 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => {
} }
}, [currentTab, mission]); }, [currentTab, mission]);
const setOpenMissionMarker = useMapStore( const setOpenMissionMarker = useMapStore((state) => state.setOpenMissionMarker);
(state) => state.setOpenMissionMarker,
);
const { anchor } = useSmartPopup(); const { anchor } = useSmartPopup();
const { setMissionFormValues, setOpen } = usePannelStore((state) => state); const { setMissionFormValues, setOpen } = usePannelStore((state) => state);
@@ -224,9 +210,17 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
const markerRef = useRef<LMarker>(null); const markerRef = useRef<LMarker>(null);
const popupRef = useRef<LPopup>(null); const popupRef = useRef<LPopup>(null);
const { openMissionMarker, setOpenMissionMarker } = useMapStore( const { data: aircrafts } = useQuery({
(store) => store, queryKey: ["aircrafts"],
); queryFn: getConnectedAircraftsAPI,
refetchInterval: 10000,
});
const { openMissionMarker, setOpenMissionMarker } = useMapStore((store) => store);
const needsAction =
HPGValidationRequired(mission.missionStationIds, aircrafts, mission.hpgMissionString) &&
mission.hpgValidationState !== "VALID";
useEffect(() => { useEffect(() => {
const handleClick = () => { const handleClick = () => {
@@ -255,15 +249,12 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
}; };
}, [mission.id, openMissionMarker, setOpenMissionMarker]); }, [mission.id, openMissionMarker, setOpenMissionMarker]);
const [anchor, setAnchor] = useState< const [anchor, setAnchor] = useState<"topleft" | "topright" | "bottomleft" | "bottomright">(
"topleft" | "topright" | "bottomleft" | "bottomright" "topleft",
>("topleft"); );
const handleConflict = useCallback(() => { const handleConflict = useCallback(() => {
const newAnchor = calculateAnchor( const newAnchor = calculateAnchor(`mission-${mission.id.toString()}`, "marker");
`mission-${mission.id.toString()}`,
"marker",
);
setAnchor(newAnchor); setAnchor(newAnchor);
}, [mission.id]); }, [mission.id]);
@@ -292,15 +283,13 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
mission: Mission, mission: Mission,
anchor: "topleft" | "topright" | "bottomleft" | "bottomright", anchor: "topleft" | "topright" | "bottomleft" | "bottomright",
) => { ) => {
const markerColor = console.log(
mission.hpgValidationState === HPGValidationRequired(mission.missionStationIds, aircrafts, mission.hpgMissionString),
(HpgValidationState.POSITION_AMANDED || );
HpgValidationState.INVALID ||
HpgValidationState.HPG_DISCONNECT || const markerColor = needsAction
HpgValidationState.HPG_BUSY || ? MISSION_STATUS_COLORS["attention"]
HpgValidationState.HPG_INVALID_MISSION) : MISSION_STATUS_COLORS[mission.state];
? MISSION_STATUS_COLORS["attention"]
: MISSION_STATUS_COLORS[mission.state];
return `<div return `<div
id="marker-mission-${mission.id}" id="marker-mission-${mission.id}"
class="${cn( class="${cn(
@@ -369,7 +358,7 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
className="w-[502px]" className="w-[502px]"
> >
<div style={{ height: "auto", maxHeight: "90vh", overflowY: "auto" }}> <div style={{ height: "auto", maxHeight: "90vh", overflowY: "auto" }}>
<MissionPopupContent mission={mission} /> <MissionPopupContent mission={mission} hpgNeedsAttention={needsAction} />
</div> </div>
</SmartPopup> </SmartPopup>
)} )}
@@ -378,8 +367,7 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
}; };
export const MissionLayer = () => { export const MissionLayer = () => {
const dispatcherConnected = const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
useDispatchConnectionStore((s) => s.status) === "connected";
const { data: missions = [] } = useQuery({ const { data: missions = [] } = useQuery({
queryKey: ["missions"], queryKey: ["missions"],
queryFn: () => queryFn: () =>

View File

@@ -31,16 +31,18 @@ import {
import { usePannelStore } from "_store/pannelStore"; import { usePannelStore } from "_store/pannelStore";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { import { deleteMissionAPI, editMissionAPI, sendMissionAPI } from "querys/missions";
deleteMissionAPI,
editMissionAPI,
sendMissionAPI,
} from "querys/missions";
import { getConnectedAircraftsAPI } from "querys/aircrafts"; import { getConnectedAircraftsAPI } from "querys/aircrafts";
import { getStationsAPI } from "querys/stations"; import { getStationsAPI } from "querys/stations";
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore"; import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
const Einsatzdetails = ({ mission }: { mission: Mission }) => { const Einsatzdetails = ({
mission,
hpgNeedsAttention,
}: {
mission: Mission;
hpgNeedsAttention?: boolean;
}) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const deleteMissionMutation = useMutation({ const deleteMissionMutation = useMutation({
mutationKey: ["missions"], mutationKey: ["missions"],
@@ -67,13 +69,8 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
}); });
const editMissionMutation = useMutation({ const editMissionMutation = useMutation({
mutationKey: ["missions"], mutationKey: ["missions"],
mutationFn: ({ mutationFn: ({ id, mission }: { id: number; mission: Prisma.MissionUpdateInput }) =>
id, editMissionAPI(id, mission),
mission,
}: {
id: number;
mission: Prisma.MissionUpdateInput;
}) => editMissionAPI(id, mission),
onSuccess: () => { onSuccess: () => {
toast.success("Gespeichert"); toast.success("Gespeichert");
queryClient.invalidateQueries({ queryClient.invalidateQueries({
@@ -81,9 +78,9 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
}); });
}, },
}); });
const dispatcherConnected = const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
useDispatchConnectionStore((s) => s.status) === "connected";
const { setMissionFormValues, setOpen } = usePannelStore((state) => state); const { setMissionFormValues, setOpen } = usePannelStore((state) => state);
const [ignoreHpg, setIgnoreHpg] = useState(false);
return ( return (
<div className="p-4 text-base-content"> <div className="p-4 text-base-content">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
@@ -170,6 +167,8 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
<input <input
type="checkbox" type="checkbox"
className="checkbox checkbox-sm checkbox-primary" className="checkbox checkbox-sm checkbox-primary"
checked={ignoreHpg}
onChange={(e) => setIgnoreHpg(e.target.checked)}
/> />
<span className="label-text font-semibold leading-6"> <span className="label-text font-semibold leading-6">
Ohne HPG-Mission alarmieren Ohne HPG-Mission alarmieren
@@ -178,9 +177,7 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
</div> </div>
<div className="flex items-center gap-2 w-full"> <div className="flex items-center gap-2 w-full">
{(mission.hpgValidationState === HpgValidationState.VALID || {(!hpgNeedsAttention || ignoreHpg) && (
mission.hpgValidationState ===
HpgValidationState.NOT_VALIDATED) && (
<button <button
className="btn btn-sm btn-info btn-outline flex-3" className="btn btn-sm btn-info btn-outline flex-3"
onClick={() => sendAlertMutation.mutate(mission.id)} onClick={() => sendAlertMutation.mutate(mission.id)}
@@ -190,45 +187,31 @@ const Einsatzdetails = ({ mission }: { mission: Mission }) => {
</span> </span>
</button> </button>
)} )}
{(mission.hpgValidationState === HpgValidationState.PENDING || {hpgNeedsAttention && (
mission.hpgValidationState === HpgValidationState.HPG_BUSY || <button
mission.hpgValidationState === className="btn btn-sm btn-info btn-outline flex-3"
HpgValidationState.HPG_DISCONNECT || onClick={() => sendAlertMutation.mutate(mission.id)}
mission.hpgValidationState === HpgValidationState.INVALID || disabled
HpgValidationState.HPG_INVALID_MISSION) && >
mission.hpgValidationState !== HpgValidationState.NOT_VALIDATED && <span className="flex items-center gap-2">
mission.hpgValidationState !== {mission.hpgValidationState === HpgValidationState.PENDING && (
HpgValidationState.POSITION_AMANDED && <div>
mission.hpgValidationState !== HpgValidationState.VALID && ( <span className="loading loading-spinner loading-md"></span> HPG-Validierung
<button läuft...
className="btn btn-sm btn-info btn-outline flex-3" </div>
onClick={() => sendAlertMutation.mutate(mission.id)} )}
disabled {mission.hpgValidationState === HpgValidationState.HPG_BUSY && "HPG-Client busy"}
> {mission.hpgValidationState === HpgValidationState.HPG_DISCONNECT &&
<span className="flex items-center gap-2"> "HPG-Client nicht verbunden"}
{mission.hpgValidationState === {mission.hpgValidationState === HpgValidationState.INVALID &&
HpgValidationState.PENDING && ( "HPG-Client fehlerhaft"}
<div> {mission.hpgValidationState === HpgValidationState.HPG_INVALID_MISSION &&
<span className="loading loading-spinner loading-md"></span>{" "} "Fehlerhafte HPG-Mission"}
HPG-Validierung läuft... </span>
</div> </button>
)} )}
{mission.hpgValidationState ===
HpgValidationState.HPG_BUSY && "HPG-Client busy"}
{mission.hpgValidationState ===
HpgValidationState.HPG_DISCONNECT &&
"HPG-Client nicht verbunden"}
{mission.hpgValidationState ===
HpgValidationState.INVALID && "HPG-Client fehlerhaft"}
{mission.hpgValidationState ===
HpgValidationState.HPG_INVALID_MISSION &&
"Fehlerhafte HPG-Mission"}
</span>
</button>
)}
{mission.hpgValidationState === {mission.hpgValidationState === HpgValidationState.POSITION_AMANDED && (
HpgValidationState.POSITION_AMANDED && (
<button <button
className="btn btn-sm btn-warning btn-outline flex-3" className="btn btn-sm btn-warning btn-outline flex-3"
onClick={() => sendAlertMutation.mutate(mission.id)} onClick={() => sendAlertMutation.mutate(mission.id)}
@@ -278,16 +261,12 @@ const Patientdetails = ({ mission }: { mission: Mission }) => {
<h2 className="flex items-center gap-2 text-lg font-bold mb-3"> <h2 className="flex items-center gap-2 text-lg font-bold mb-3">
<User /> Patientendetails <User /> Patientendetails
</h2> </h2>
<p className="text-base-content font-semibold"> <p className="text-base-content font-semibold">{mission.missionPatientInfo}</p>
{mission.missionPatientInfo}
</p>
<div className="divider my-2" /> <div className="divider my-2" />
<h2 className="flex items-center gap-2 text-lg font-bold mb-3"> <h2 className="flex items-center gap-2 text-lg font-bold mb-3">
<Cross /> Einsatzinformationen <Cross /> Einsatzinformationen
</h2> </h2>
<p className="text-base-content font-semibold"> <p className="text-base-content font-semibold">{mission.missionAdditionalInfo}</p>
{mission.missionAdditionalInfo}
</p>
</div> </div>
); );
}; };
@@ -303,13 +282,8 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
}); });
const updateMissionMutation = useMutation({ const updateMissionMutation = useMutation({
mutationKey: ["missions", "stations-mission", mission.id], mutationKey: ["missions", "stations-mission", mission.id],
mutationFn: ({ mutationFn: ({ id, missionEdit }: { id: number; missionEdit: Prisma.MissionUpdateInput }) =>
id, editMissionAPI(id, missionEdit),
missionEdit,
}: {
id: number;
missionEdit: Prisma.MissionUpdateInput;
}) => editMissionAPI(id, missionEdit),
onError: (error) => { onError: (error) => {
console.error(error); console.error(error);
@@ -323,17 +297,15 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
}, },
}); });
const { data: missionStations, refetch: refetchMissionStationIds } = useQuery( const { data: missionStations, refetch: refetchMissionStationIds } = useQuery({
{ queryKey: ["stations-mission", mission.id],
queryKey: ["stations-mission", mission.id], queryFn: () =>
queryFn: () => getStationsAPI({
getStationsAPI({ id: {
id: { in: mission.missionStationIds,
in: mission.missionStationIds, },
}, }),
}), });
},
);
useEffect(() => { useEffect(() => {
refetchMissionStationIds(); refetchMissionStationIds();
}, [mission.missionStationIds, refetchMissionStationIds]); }, [mission.missionStationIds, refetchMissionStationIds]);
@@ -426,22 +398,14 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
<select <select
className="select select-sm select-primary select-bordered flex-1" className="select select-sm select-primary select-bordered flex-1"
onChange={(e) => { onChange={(e) => {
const selected = allStations?.find( const selected = allStations?.find((s) => s.id.toString() === e.target.value);
(s) => s.id.toString() === e.target.value,
);
if (selected) { if (selected) {
setSelectedStation(selected); setSelectedStation(selected);
} else { } else {
setSelectedStation( setSelectedStation(e.target.value as "ambulance" | "police" | "firebrigade");
e.target.value as "ambulance" | "police" | "firebrigade",
);
} }
}} }}
value={ value={typeof selectedStation === "string" ? selectedStation : selectedStation?.id}
typeof selectedStation === "string"
? selectedStation
: selectedStation?.id
}
> >
{allStations {allStations
?.filter((s) => !mission.missionStationIds.includes(s.id)) ?.filter((s) => !mission.missionStationIds.includes(s.id))
@@ -498,17 +462,11 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => {
const [note, setNote] = useState(""); const [note, setNote] = useState("");
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const dispatcherConnected = const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
useDispatchConnectionStore((s) => s.status) === "connected";
const editMissionMutation = useMutation({ const editMissionMutation = useMutation({
mutationFn: ({ mutationFn: ({ id, mission }: { id: number; mission: Partial<Prisma.MissionUpdateInput> }) =>
id, editMissionAPI(id, mission),
mission,
}: {
id: number;
mission: Partial<Prisma.MissionUpdateInput>;
}) => editMissionAPI(id, mission),
mutationKey: ["missions"], mutationKey: ["missions"],
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["missions"] }); queryClient.invalidateQueries({ queryKey: ["missions"] });
@@ -608,9 +566,7 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => {
> >
{entry.data.newFMSstatus} {entry.data.newFMSstatus}
</span> </span>
<span className="text-base-content"> <span className="text-base-content">{entry.data.station.bosCallsign}</span>
{entry.data.station.bosCallsign}
</span>
</li> </li>
); );
if (entry.type === "message-log" || entry.type === "sds-log") if (entry.type === "message-log" || entry.type === "sds-log")
@@ -651,9 +607,7 @@ const FMSStatusHistory = ({ mission }: { mission: Mission }) => {
</> </>
)} )}
</span> </span>
<span className="text-base-content"> <span className="text-base-content">{entry.data.message}</span>
{entry.data.message}
</span>
</li> </li>
); );

View File

@@ -5,10 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { BellRing, BookmarkPlus } from "lucide-react"; import { BellRing, BookmarkPlus } from "lucide-react";
import { Select } from "_components/Select"; import { Select } from "_components/Select";
import { KEYWORD_CATEGORY, missionType, Prisma } from "@repo/db"; import { KEYWORD_CATEGORY, missionType, Prisma } from "@repo/db";
import { import { MissionOptionalDefaults, MissionOptionalDefaultsSchema } from "@repo/db/zod";
MissionOptionalDefaults,
MissionOptionalDefaultsSchema,
} from "@repo/db/zod";
import { usePannelStore } from "_store/pannelStore"; import { usePannelStore } from "_store/pannelStore";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@@ -23,10 +20,10 @@ import { getKeywordsAPI } from "querys/keywords";
import { getStationsAPI } from "querys/stations"; import { getStationsAPI } from "querys/stations";
import { useMapStore } from "_store/mapStore"; import { useMapStore } from "_store/mapStore";
import { getConnectedAircraftsAPI } from "querys/aircrafts"; import { getConnectedAircraftsAPI } from "querys/aircrafts";
import { HPGValidationRequired } from "helpers/hpgValidationRequired";
export const MissionForm = () => { export const MissionForm = () => {
const { isEditingMission, editingMissionId, setEditingMission } = const { isEditingMission, editingMissionId, setEditingMission } = usePannelStore();
usePannelStore();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const setSeachOSMElements = useMapStore((s) => s.setSearchElements); const setSeachOSMElements = useMapStore((s) => s.setSearchElements);
@@ -57,13 +54,8 @@ export const MissionForm = () => {
}); });
const editMissionMutation = useMutation({ const editMissionMutation = useMutation({
mutationFn: ({ mutationFn: ({ id, mission }: { id: number; mission: Partial<Prisma.MissionUpdateInput> }) =>
id, editMissionAPI(id, mission),
mission,
}: {
id: number;
mission: Partial<Prisma.MissionUpdateInput>;
}) => editMissionAPI(id, mission),
mutationKey: ["missions"], mutationKey: ["missions"],
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
@@ -113,11 +105,11 @@ export const MissionForm = () => {
}); });
const { missionFormValues, setOpen } = usePannelStore((state) => state); const { missionFormValues, setOpen } = usePannelStore((state) => state);
const validationRequired = /* form.watch("missionStationIds")?.some((id) => { const validationRequired = HPGValidationRequired(
const aircraft = aircrafts?.find((a) => a.stationId === id); form.watch("missionStationIds"),
aircrafts,
return aircraft?.posH145active; form.watch("hpgMissionString"),
}) && form.watch("hpgMissionString")?.length !== 0; */ true; );
useEffect(() => { useEffect(() => {
if (session.data?.user.id) { if (session.data?.user.id) {
@@ -159,8 +151,7 @@ export const MissionForm = () => {
disabled disabled
/> />
</div> </div>
{(form.formState.errors.addressLat || {(form.formState.errors.addressLat || form.formState.errors.addressLng) && (
form.formState.errors.addressLng) && (
<p className="text-error"> <p className="text-error">
Bitte wähle eine Postion übder das Context-Menu über der Karte aus. Bitte wähle eine Postion übder das Context-Menu über der Karte aus.
</p> </p>
@@ -236,10 +227,7 @@ export const MissionForm = () => {
{...form.register("missionKeywordCategory")} {...form.register("missionKeywordCategory")}
className="select select-primary select-bordered w-full mb-4" className="select select-primary select-bordered w-full mb-4"
onChange={(e) => { onChange={(e) => {
form.setValue( form.setValue("missionKeywordCategory", e.target.value as string);
"missionKeywordCategory",
e.target.value as string,
);
form.setValue("missionKeywordName", null); form.setValue("missionKeywordName", null);
form.setValue("missionKeywordAbbreviation", ""); form.setValue("missionKeywordAbbreviation", "");
form.setValue("hpgMissionString", ""); form.setValue("hpgMissionString", "");
@@ -259,14 +247,9 @@ export const MissionForm = () => {
{...form.register("missionKeywordAbbreviation")} {...form.register("missionKeywordAbbreviation")}
className="select select-primary select-bordered w-full mb-4" className="select select-primary select-bordered w-full mb-4"
onChange={(e) => { onChange={(e) => {
const keyword = keywords?.find( const keyword = keywords?.find((k) => k.abreviation === e.target.value);
(k) => k.abreviation === e.target.value,
);
form.setValue("missionKeywordName", keyword?.name || null); form.setValue("missionKeywordName", keyword?.name || null);
form.setValue( form.setValue("missionKeywordAbbreviation", keyword?.abreviation || null);
"missionKeywordAbbreviation",
keyword?.abreviation || null,
);
form.setValue("hpgMissionString", "default"); form.setValue("hpgMissionString", "default");
}} }}
defaultValue="default" defaultValue="default"
@@ -276,9 +259,7 @@ export const MissionForm = () => {
</option> </option>
{keywords && {keywords &&
keywords keywords
.filter( .filter((k) => k.category === form.watch("missionKeywordCategory"))
(k) => k.category === form.watch("missionKeywordCategory"),
)
.map((keyword) => ( .map((keyword) => (
<option key={keyword.id} value={keyword.abreviation}> <option key={keyword.id} value={keyword.abreviation}>
{keyword.name} {keyword.name}
@@ -308,9 +289,7 @@ export const MissionForm = () => {
})} })}
</select> </select>
{validationRequired && ( {validationRequired && (
<p className="text-sm text-warning"> <p className="text-sm text-warning">Szenario wird vor Alarmierung HPG-Validiert.</p>
Szenario wird vor Alarmierung HPG-Validiert.
</p>
)} )}
</div> </div>
</> </>
@@ -339,8 +318,7 @@ export const MissionForm = () => {
{missionFormValues?.addressOSMways?.length && ( {missionFormValues?.addressOSMways?.length && (
<p className="text-sm text-info"> <p className="text-sm text-info">
In diesem Einsatz gibt es {missionFormValues?.addressOSMways?.length}{" "} In diesem Einsatz gibt es {missionFormValues?.addressOSMways?.length} Gebäude
Gebäude
</p> </p>
)} )}
@@ -350,37 +328,33 @@ export const MissionForm = () => {
<button <button
type="button" type="button"
className="btn btn-primary flex-1" className="btn btn-primary flex-1"
onClick={form.handleSubmit( onClick={form.handleSubmit(async (mission: MissionOptionalDefaults) => {
async (mission: MissionOptionalDefaults) => { try {
try { const hpgSzenario = mission.hpgMissionString?.split(":")[0];
const hpgSzenario = mission.hpgMissionString?.split(":")[0]; const newMission = await editMissionMutation.mutateAsync({
const newMission = await editMissionMutation.mutateAsync({ id: Number(editingMissionId),
id: Number(editingMissionId), mission: {
mission: { ...(mission as unknown as Prisma.MissionCreateInput),
...(mission as unknown as Prisma.MissionCreateInput), missionAdditionalInfo:
missionAdditionalInfo: !mission.missionAdditionalInfo.length && hpgSzenario
!mission.missionAdditionalInfo.length && hpgSzenario ? `HPG-Szenario: ${hpgSzenario}`
? `HPG-Szenario: ${hpgSzenario}` : mission.missionAdditionalInfo,
: mission.missionAdditionalInfo, },
}, });
}); if (validationRequired) {
if (validationRequired) { await startHpgValidation(newMission.id);
await startHpgValidation(newMission.id);
}
toast.success(
`Einsatz ${newMission.id} erfolgreich aktualisiert`,
);
setSeachOSMElements([]); // Reset search elements
setEditingMission(false, null); // Reset editing state
form.reset(); // Reset the form
setOpen(false);
} catch (error) {
toast.error(
`Fehler beim Aktualisieren des Einsatzes: ${(error as Error).message}`,
);
} }
}, toast.success(`Einsatz ${newMission.id} erfolgreich aktualisiert`);
)} setSeachOSMElements([]); // Reset search elements
setEditingMission(false, null); // Reset editing state
form.reset(); // Reset the form
setOpen(false);
} catch (error) {
toast.error(
`Fehler beim Aktualisieren des Einsatzes: ${(error as Error).message}`,
);
}
})}
> >
Änderungen speichern Änderungen speichern
</button> </button>
@@ -389,66 +363,54 @@ export const MissionForm = () => {
<button <button
type="submit" type="submit"
className="btn btn-warning" className="btn btn-warning"
onClick={form.handleSubmit( onClick={form.handleSubmit(async (mission: MissionOptionalDefaults) => {
async (mission: MissionOptionalDefaults) => { try {
try { const hpgSzenario = mission.hpgMissionString?.split(":")[0];
const hpgSzenario = const newMission = await createMissionMutation.mutateAsync({
mission.hpgMissionString?.split(":")[0]; ...(mission as unknown as Prisma.MissionCreateInput),
const newMission = missionAdditionalInfo:
await createMissionMutation.mutateAsync({ !mission.missionAdditionalInfo.length && hpgSzenario
...(mission as unknown as Prisma.MissionCreateInput), ? `HPG-Szenario: ${hpgSzenario}`
missionAdditionalInfo: : mission.missionAdditionalInfo,
!mission.missionAdditionalInfo.length && hpgSzenario });
? `HPG-Szenario: ${hpgSzenario}` if (validationRequired) {
: mission.missionAdditionalInfo, await startHpgValidation(newMission.id, {
}); alertWhenValid: true,
if (validationRequired) { });
await startHpgValidation(newMission.id, { } else {
alertWhenValid: true, await sendAlertMutation.mutateAsync(newMission.id);
});
} else {
await sendAlertMutation.mutateAsync(newMission.id);
}
setSeachOSMElements([]); // Reset search elements
setOpen(false);
} catch (error) {
toast.error(
`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`,
);
} }
}, setSeachOSMElements([]); // Reset search elements
)} setOpen(false);
} catch (error) {
toast.error(`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`);
}
})}
> >
<BellRing className="h-4 w-4" /> Alarmieren <BellRing className="h-4 w-4" /> Alarmieren
</button> </button>
<button <button
type="submit" type="submit"
className="btn btn-primary flex-1" className="btn btn-primary flex-1"
onClick={form.handleSubmit( onClick={form.handleSubmit(async (mission: MissionOptionalDefaults) => {
async (mission: MissionOptionalDefaults) => { try {
try { const hpgSzenario = mission.hpgMissionString?.split(":")[0];
const hpgSzenario = const newMission = await createMissionMutation.mutateAsync({
mission.hpgMissionString?.split(":")[0]; ...(mission as unknown as Prisma.MissionCreateInput),
const newMission = missionAdditionalInfo:
await createMissionMutation.mutateAsync({ !mission.missionAdditionalInfo.length && hpgSzenario
...(mission as unknown as Prisma.MissionCreateInput), ? `HPG-Szenario: ${hpgSzenario}`
missionAdditionalInfo: : mission.missionAdditionalInfo,
!mission.missionAdditionalInfo.length && hpgSzenario });
? `HPG-Szenario: ${hpgSzenario}` setSeachOSMElements([]); // Reset search elements
: mission.missionAdditionalInfo, await startHpgValidation(newMission.id);
}); toast.success(`Einsatz ${newMission.publicId} erstellt`);
setSeachOSMElements([]); // Reset search elements form.reset();
await startHpgValidation(newMission.id); setOpen(false);
toast.success(`Einsatz ${newMission.publicId} erstellt`); } catch (error) {
form.reset(); toast.error(`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`);
setOpen(false); }
} catch (error) { })}
toast.error(
`Fehler beim Erstellen des Einsatzes: ${(error as Error).message}`,
);
}
},
)}
> >
<BookmarkPlus className="h-5 w-5" /> Einsatz vorbereiten <BookmarkPlus className="h-5 w-5" /> Einsatz vorbereiten
</button> </button>

View File

@@ -0,0 +1,15 @@
import { ConnectedAircraft } from "@repo/db";
export const HPGValidationRequired = (
missionStationIds?: number[],
aircrafts?: ConnectedAircraft[],
hpgMissionString?: string | null,
) => {
return (
missionStationIds?.some((id) => {
const aircraft = aircrafts?.find((a) => a.stationId === id);
return aircraft?.posH145active;
}) && hpgMissionString?.length !== 0
);
};

Binary file not shown.