Fixed StationSelectBug, Fixed HPGNotificationGuf, see #45

This commit is contained in:
PxlLoewe
2025-07-06 12:53:04 -07:00
parent 690f4876d6
commit 9b954e4053
8 changed files with 60 additions and 37 deletions

View File

@@ -257,6 +257,9 @@ router.post("/:id/hpg-validation-result", async (req, res) => {
type: "hpg-validation", type: "hpg-validation",
status: "success", status: "success",
message: `HPG Validierung erfolgreich`, message: `HPG Validierung erfolgreich`,
data: {
mission: newMission,
},
} as NotificationPayload); } as NotificationPayload);
if (result.alertWhenValid) { if (result.alertWhenValid) {
@@ -268,6 +271,9 @@ router.post("/:id/hpg-validation-result", async (req, res) => {
type: "hpg-validation", type: "hpg-validation",
status: "failed", status: "failed",
message: result.state, message: result.state,
data: {
mission: newMission,
},
} as NotificationPayload); } as NotificationPayload);
} }
res.json({ res.json({

View File

@@ -5,10 +5,10 @@ import { Select } from "_components/Select";
import { getConnectedAircraftsAPI } from "_querys/aircrafts"; import { getConnectedAircraftsAPI } from "_querys/aircrafts";
import { getStationsAPI } from "_querys/stations"; import { getStationsAPI } from "_querys/stations";
import { Ambulance, FireExtinguisher, Radio, Siren } from "lucide-react"; import { Ambulance, FireExtinguisher, Radio, Siren } from "lucide-react";
import { useState } from "react"; import { useEffect, useState } from "react";
import { FieldValues } from "react-hook-form"; import { FieldValues } from "react-hook-form";
type MissionStationsSelectProps<T extends FieldValues> = { type MissionStationsSelectProps = {
selectedStations?: number[]; selectedStations?: number[];
className?: string; className?: string;
menuPlacement?: "top" | "bottom" | "auto"; // Added menuPlacement prop for better control menuPlacement?: "top" | "bottom" | "auto"; // Added menuPlacement prop for better control
@@ -27,7 +27,7 @@ type MissionStationsSelectProps<T extends FieldValues> = {
isMulti?: boolean; isMulti?: boolean;
}; };
export function StationsSelect<T extends FieldValues>({ export function StationsSelect({
onChange, onChange,
selectedStations, selectedStations,
vehicleStates, vehicleStates,
@@ -35,7 +35,7 @@ export function StationsSelect<T extends FieldValues>({
isMulti = true, isMulti = true,
menuPlacement = "bottom", menuPlacement = "bottom",
filterSelected = false, filterSelected = false,
}: MissionStationsSelectProps<T>) { }: MissionStationsSelectProps) {
const { data: connectedAircrafts } = useQuery({ const { data: connectedAircrafts } = useQuery({
queryKey: ["aircrafts"], queryKey: ["aircrafts"],
queryFn: () => getConnectedAircraftsAPI(), queryFn: () => getConnectedAircraftsAPI(),
@@ -47,11 +47,21 @@ export function StationsSelect<T extends FieldValues>({
const [value, setValue] = useState<string[]>(selectedStations?.map((id) => String(id)) || []); const [value, setValue] = useState<string[]>(selectedStations?.map((id) => String(id)) || []);
useEffect(() => {
setValue([
...(selectedStations || []).map((id) => String(id)),
...(vehicleStates.hpgAmbulanceState !== HpgState.NOT_REQUESTED ? ["RTW"] : []),
...(vehicleStates.hpgFireEngineState !== HpgState.NOT_REQUESTED ? ["FW"] : []),
...(vehicleStates.hpgPoliceState !== HpgState.NOT_REQUESTED ? ["POL"] : []),
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedStations, vehicleStates]);
// Helper to check if a station is a vehicle and its state is NOT_REQUESTED // Helper to check if a station is a vehicle and its state is NOT_REQUESTED
const stationsOptions = [ const stationsOptions = [
...(stations?.map((station) => ({ ...(stations?.map((station) => ({
label: station.bosCallsign, label: station.bosCallsign,
value: station.id, value: String(station.id),
type: "station" as const, type: "station" as const,
isOnline: !!connectedAircrafts?.find((a) => a.stationId === station.id), isOnline: !!connectedAircrafts?.find((a) => a.stationId === station.id),
})) || []), })) || []),
@@ -78,7 +88,7 @@ export function StationsSelect<T extends FieldValues>({
if (!filterSelected) return true; // If filterSelected is false, include all stations if (!filterSelected) return true; // If filterSelected is false, include all stations
// Filter out selected stations if filterSelectedStations is true // Filter out selected stations if filterSelectedStations is true
if (s.type === "station") { if (s.type === "station") {
return !selectedStations?.includes(s.value); return !selectedStations?.map(String)?.includes(s.value);
} }
// If the station is a vehicle, we need to check its state // If the station is a vehicle, we need to check its state
if (s.type === "vehicle") { if (s.type === "vehicle") {

View File

@@ -21,7 +21,6 @@ import {
startHpgValidation, startHpgValidation,
} from "_querys/missions"; } from "_querys/missions";
import { getKeywordsAPI } from "_querys/keywords"; import { getKeywordsAPI } from "_querys/keywords";
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"; import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
@@ -197,7 +196,7 @@ export const MissionForm = () => {
} }
return newMission; return newMission;
}; };
console.log(form.formState.errors); console.log(form.watch("missionStationIds"));
return ( return (
<form className="space-y-4"> <form className="space-y-4">
{/* Koorinaten Section */} {/* Koorinaten Section */}
@@ -282,12 +281,12 @@ export const MissionForm = () => {
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("type", e.target.value as missionType); form.setValue("type", e.target.value as missionType);
if (e.target.value === "sekundär") { if (e.target.value === "primary") {
form.setValue("missionKeywordName", KEYWORD_CATEGORY.Z_SONSTIGES); form.setValue("missionKeywordName", null as any);
form.setValue("missionKeywordAbbreviation", "VL_S"); form.setValue("missionKeywordAbbreviation", null as any);
form.setValue("hpgMissionString", "Verlegung:4_1_1_1-4_1"); form.setValue("hpgMissionString", null);
} else { } else {
form.setValue("missionKeywordCategory", "V_VERLEGUNG"); form.setValue("missionKeywordCategory", KEYWORD_CATEGORY.V_VERLEGUNG);
form.setValue("missionKeywordAbbreviation", null as any); form.setValue("missionKeywordAbbreviation", null as any);
form.setValue("hpgMissionString", null); form.setValue("hpgMissionString", null);
} }

View File

@@ -16,15 +16,15 @@ const Map = dynamic(() => import("_components/map/Map"), {
ssr: false, ssr: false,
}); });
const DispatchPage = () => { const PilotPage = () => {
const { connectedAircraft, status } = usePilotConnectionStore((state) => state); const { connectedAircraft, status } = usePilotConnectionStore((state) => state);
const { data: ownAircraftArray = [] } = useQuery({ const { data: ownAircraftArray = [] } = useQuery({
queryKey: ["aircrafts", connectedAircraft?.id], queryKey: ["own-aircraft", connectedAircraft?.id],
queryFn: () => queryFn: () =>
getAircraftsAPI({ getAircraftsAPI({
id: connectedAircraft?.id, id: connectedAircraft?.id,
}), }),
refetchInterval: 1000, refetchInterval: 10000,
}); });
const ownAircraft = ownAircraftArray[0]; const ownAircraft = ownAircraftArray[0];
const simulatorConnected = ownAircraft ? checkSimulatorConnected(ownAircraft) : false; const simulatorConnected = ownAircraft ? checkSimulatorConnected(ownAircraft) : false;
@@ -73,6 +73,6 @@ const DispatchPage = () => {
); );
}; };
DispatchPage.displayName = "DispatchPage"; PilotPage.displayName = "DispatchPage";
export default DispatchPage; export default PilotPage;

View File

@@ -59,6 +59,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
const handleNotification = (notification: NotificationPayload) => { const handleNotification = (notification: NotificationPayload) => {
switch (notification.type) { switch (notification.type) {
case "hpg-validation": case "hpg-validation":
console.log("hpg-validation notification received", notification);
toast.custom( toast.custom(
(t) => <HPGnotificationToast event={notification} mapStore={mapStore} t={t} />, (t) => <HPGnotificationToast event={notification} mapStore={mapStore} t={t} />,
{ {

View File

@@ -110,7 +110,7 @@ const AircraftPopupContent = ({
/> />
<div> <div>
<div <div
className="flex gap-[2px] text-white pb-0.5" className="flex gap-[2px] text-white pb-0.5 overflow-auto"
style={{ style={{
backgroundColor: `${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]}`, backgroundColor: `${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]}`,
}} }}
@@ -152,21 +152,26 @@ const AircraftPopupContent = ({
{aircraft.fmsStatus} {aircraft.fmsStatus}
</div> </div>
<div <div
className="w-100 cursor-pointer px-2" className="cursor-pointer px-2 min-w-[130px]"
style={{ style={{
backgroundColor: `${FMS_STATUS_COLORS[aircraft.fmsStatus]}`, backgroundColor: `${FMS_STATUS_COLORS[aircraft.fmsStatus]}`,
borderBottom: borderBottom:
currentTab === "aircraft" currentTab === "aircraft"
? `5px solid ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]}` ? `5px solid ${FMS_STATUS_TEXT_COLORS[aircraft.fmsStatus]}`
: "5px solid transparent", : "5px solid transparent",
whiteSpace: "nowrap",
}} }}
onClick={() => handleTabChange("aircraft")} onClick={() => handleTabChange("aircraft")}
> >
<span className="text-white text-base font-medium"> <span className="text-white text-base font-medium truncate">
{aircraft.Station.bosCallsign} {aircraft.Station.bosCallsign.length > 20
? aircraft.Station.bosCallsignShort
: aircraft.Station.bosCallsign}
</span>
<span className="text-sm text-gray-400">
<br /> <br />
{aircraft.Station.bosUse} {aircraft.Station.bosUse === "DUAL_USE" && "(dual use)"}
{aircraft.Station.bosUse === "PRIMARY" && "(primär)"}
{aircraft.Station.bosUse === "SECONDARY" && "(sekundär)"}
</span> </span>
</div> </div>
<div <div
@@ -184,7 +189,7 @@ const AircraftPopupContent = ({
<span className="text-white text-base font-medium">Einsatz</span> <span className="text-white text-base font-medium">Einsatz</span>
<br /> <br />
<span className="text-white text-sm font-medium"> <span className="text-white text-sm font-medium">
{mission?.publicId || "Kein aktiver Einsatz"} {mission?.publicId || "kein Einsatz"}
</span> </span>
</div> </div>
<div <div

View File

@@ -73,7 +73,6 @@ const StationsLayer = ({ attribution }: { attribution: Control.Attribution }) =>
queryKey: ["stations"], queryKey: ["stations"],
queryFn: () => getStationsAPI(), queryFn: () => getStationsAPI(),
}); });
console.log("StationsLayer: stations", stations);
const [selectedStations, setSelectedStations] = useState<Station["id"][]>([]); const [selectedStations, setSelectedStations] = useState<Station["id"][]>([]);
const attributionText = ""; const attributionText = "";
@@ -306,14 +305,6 @@ const WindfarmOutlineLayer = () => {
export const BaseMaps = () => { export const BaseMaps = () => {
const map = useMap(); const map = useMap();
const isPannelOpen = usePannelStore((state) => state.isOpen);
useEffect(() => {
setTimeout(() => {
map.invalidateSize();
}, 600);
}, [isPannelOpen]);
return ( return (
<LayersControl position="topleft"> <LayersControl position="topleft">
<LayersControl.Overlay name={"Leitstellenbereiche"}> <LayersControl.Overlay name={"Leitstellenbereiche"}>

View File

@@ -3,6 +3,11 @@ import { Prisma, prisma } from "@repo/db";
export async function GET(request: NextRequest): Promise<NextResponse> { export async function GET(request: NextRequest): Promise<NextResponse> {
try { try {
const config = await prisma.config.findFirst({
orderBy: {
createdAt: "desc",
},
});
const filter = JSON.parse( const filter = JSON.parse(
new URL(request.url).searchParams.get("filter") || "{}", new URL(request.url).searchParams.get("filter") || "{}",
) as Prisma.ConnectedAircraftWhereInput; ) as Prisma.ConnectedAircraftWhereInput;
@@ -16,9 +21,15 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
Station: true, Station: true,
}, },
}); });
return NextResponse.json(connectedAircraft, { return NextResponse.json(
status: 200, connectedAircraft.map((a) => ({
}); ...a,
posH145active: config?.disableHPG ? false : a.posH145active,
})),
{
status: 200,
},
);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return NextResponse.json({ error: "Failed to fetch Aircrafts" }, { status: 500 }); return NextResponse.json({ error: "Failed to fetch Aircrafts" }, { status: 500 });