Fahrzeugauswahl überarbeitet
This commit is contained in:
151
apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx
Normal file
151
apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { HpgState } from "@repo/db";
|
||||
import { cn } from "@repo/shared-components";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Select } from "_components/Select";
|
||||
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||
import { getStationsAPI } from "_querys/stations";
|
||||
import { Ambulance, FireExtinguisher, Radio, Siren } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { FieldValues } from "react-hook-form";
|
||||
|
||||
type MissionStationsSelectProps<T extends FieldValues> = {
|
||||
selectedStations?: number[];
|
||||
className?: string;
|
||||
menuPlacement?: "top" | "bottom" | "auto"; // Added menuPlacement prop for better control
|
||||
onChange?: (value: {
|
||||
selectedStationIds: number[];
|
||||
hpgAmbulanceState?: HpgState;
|
||||
hpgFireEngineState?: HpgState;
|
||||
hpgPoliceState?: HpgState;
|
||||
}) => void;
|
||||
vehicleStates: {
|
||||
hpgAmbulanceState?: HpgState;
|
||||
hpgFireEngineState?: HpgState;
|
||||
hpgPoliceState?: HpgState;
|
||||
};
|
||||
filterSelected?: boolean; // If true, filter out selected stations from the options
|
||||
isMulti?: boolean;
|
||||
};
|
||||
|
||||
export function StationsSelect<T extends FieldValues>({
|
||||
onChange,
|
||||
selectedStations,
|
||||
vehicleStates,
|
||||
className = "",
|
||||
isMulti = true,
|
||||
menuPlacement = "bottom",
|
||||
filterSelected = false,
|
||||
}: MissionStationsSelectProps<T>) {
|
||||
const { data: connectedAircrafts } = useQuery({
|
||||
queryKey: ["aircrafts"],
|
||||
queryFn: () => getConnectedAircraftsAPI(),
|
||||
});
|
||||
const { data: stations } = useQuery({
|
||||
queryKey: ["stations"],
|
||||
queryFn: () => getStationsAPI(),
|
||||
});
|
||||
|
||||
const [value, setValue] = useState<string[]>(selectedStations?.map((id) => String(id)) || []);
|
||||
|
||||
// Helper to check if a station is a vehicle and its state is NOT_REQUESTED
|
||||
const stationsOptions = [
|
||||
...(stations?.map((station) => ({
|
||||
label: station.bosCallsign,
|
||||
value: station.id,
|
||||
type: "station" as const,
|
||||
isOnline: !!connectedAircrafts?.find((a) => a.stationId === station.id),
|
||||
})) || []),
|
||||
|
||||
{ label: "Feuerwehr", value: "FW", type: "vehicle" as const },
|
||||
{ label: "Polizei", value: "POL", type: "vehicle" as const },
|
||||
{ label: "RTW", value: "RTW", type: "vehicle" as const },
|
||||
]
|
||||
.sort((a, b) => {
|
||||
// 1. Vehicles first
|
||||
if (a.type === "vehicle" && b.type !== "vehicle") return -1;
|
||||
if (a.type !== "vehicle" && b.type === "vehicle") return 1;
|
||||
|
||||
// 2. Online stations before offline stations
|
||||
if (a.type === "station" && b.type === "station") {
|
||||
if (a.isOnline && !b.isOnline) return -1;
|
||||
if (!a.isOnline && b.isOnline) return 1;
|
||||
}
|
||||
|
||||
// 3. Otherwise, sort alphabetically by label
|
||||
return a.label.localeCompare(b.label);
|
||||
})
|
||||
.filter((s) => {
|
||||
if (!filterSelected) return true; // If filterSelected is false, include all stations
|
||||
// Filter out selected stations if filterSelectedStations is true
|
||||
if (s.type === "station") {
|
||||
return !selectedStations?.includes(s.value);
|
||||
}
|
||||
// If the station is a vehicle, we need to check its state
|
||||
if (s.type === "vehicle") {
|
||||
if (s.value === "FW" && vehicleStates.hpgFireEngineState !== HpgState.NOT_REQUESTED)
|
||||
return false;
|
||||
if (s.value === "POL" && vehicleStates.hpgPoliceState !== HpgState.NOT_REQUESTED)
|
||||
return false;
|
||||
if (s.value === "RTW" && vehicleStates.hpgAmbulanceState !== HpgState.NOT_REQUESTED)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<Select
|
||||
className={className}
|
||||
menuPlacement={menuPlacement}
|
||||
isMulti={isMulti}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
if (!isMulti) return onChange?.(v);
|
||||
const hpgAmbulanceState =
|
||||
vehicleStates.hpgAmbulanceState === "NOT_REQUESTED" && v.includes("RTW")
|
||||
? HpgState.DISPATCHED
|
||||
: vehicleStates.hpgAmbulanceState;
|
||||
const hpgFireEngineState =
|
||||
vehicleStates.hpgFireEngineState === "NOT_REQUESTED" && v.includes("FW")
|
||||
? HpgState.DISPATCHED
|
||||
: vehicleStates.hpgFireEngineState;
|
||||
const hpgPoliceState =
|
||||
vehicleStates.hpgPoliceState === "NOT_REQUESTED" && v.includes("POL")
|
||||
? HpgState.DISPATCHED
|
||||
: vehicleStates.hpgPoliceState;
|
||||
|
||||
onChange?.({
|
||||
selectedStationIds: v
|
||||
.filter((id: string) => !["FW", "POL", "RTW"].includes(id))
|
||||
.map(Number),
|
||||
hpgAmbulanceState,
|
||||
hpgFireEngineState,
|
||||
hpgPoliceState,
|
||||
});
|
||||
}}
|
||||
value={value}
|
||||
label=""
|
||||
placeholder={
|
||||
isMulti ? "Wähle ein oder mehrere Rettungsmittel aus" : "Wähle ein Rettungsmittel aus"
|
||||
}
|
||||
options={stationsOptions.map((s) => ({
|
||||
label: (
|
||||
<div
|
||||
className={cn(s.type === "vehicle" && isMulti && "tooltip tooltip-right")}
|
||||
data-tip={
|
||||
"Wenn kein Pilot mit HPG-Script im Einsatz ist, bleibt dieses Fahrzeug im Status 4."
|
||||
}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{s.type === "station" && s.isOnline && <Radio className="text-success" size={15} />}
|
||||
{s.type === "vehicle" && s.value === "FW" && <FireExtinguisher size={15} />}
|
||||
{s.type === "vehicle" && s.value === "POL" && <Siren size={15} />}
|
||||
{s.type === "vehicle" && s.value === "RTW" && <Ambulance size={15} />}
|
||||
{s.label}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
value: s.value,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||
import { selectRandomHPGMissionSzenery } from "_helpers/selectRandomHPGMission";
|
||||
import { AxiosError } from "axios";
|
||||
import { cn } from "@repo/shared-components";
|
||||
import { StationsSelect } from "(app)/dispatch/_components/StationSelect";
|
||||
|
||||
export const MissionForm = () => {
|
||||
const { editingMissionId, setEditingMission } = usePannelStore();
|
||||
@@ -168,10 +169,6 @@ export const MissionForm = () => {
|
||||
if (createNewMission) {
|
||||
newMission = await createMissionMutation.mutateAsync({
|
||||
...(mission as unknown as Prisma.MissionCreateInput),
|
||||
missionAdditionalInfo:
|
||||
!mission.missionAdditionalInfo.length && hpgSzenario
|
||||
? `HPG-Szenario: ${hpgSzenario}`
|
||||
: mission.missionAdditionalInfo,
|
||||
hpgSelectedMissionString: szenarioCode,
|
||||
});
|
||||
if (validationRequired) {
|
||||
@@ -205,7 +202,7 @@ export const MissionForm = () => {
|
||||
}
|
||||
return newMission;
|
||||
};
|
||||
|
||||
console.log(form.formState.errors);
|
||||
return (
|
||||
<form className="space-y-4">
|
||||
{/* Koorinaten Section */}
|
||||
@@ -262,33 +259,23 @@ export const MissionForm = () => {
|
||||
className="input input-primary input-bordered w-full mt-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Rettungsmittel Section */}
|
||||
<div className="form-control">
|
||||
<h2 className="text-lg font-bold mb-2">Rettungsmittel</h2>
|
||||
<Select
|
||||
name="missionStationIds"
|
||||
label={""}
|
||||
placeholder="Wähle ein oder mehrere Rettungsmittel aus"
|
||||
<StationsSelect
|
||||
isMulti
|
||||
form={form}
|
||||
options={stations
|
||||
?.sort((a, b) => {
|
||||
const aHasAircraft = aircrafts?.some((ac) => ac.stationId === a.id) ? 0 : 1;
|
||||
const bHasAircraft = aircrafts?.some((ac) => ac.stationId === b.id) ? 0 : 1;
|
||||
return aHasAircraft - bHasAircraft;
|
||||
})
|
||||
.map((s) => ({
|
||||
label: (
|
||||
<span className="flex items-center gap-2">
|
||||
{aircrafts?.some((a) => a.stationId === s.id) && (
|
||||
<Radio className="text-success" size={15} />
|
||||
)}
|
||||
{s.bosCallsign}
|
||||
</span>
|
||||
),
|
||||
value: s.id,
|
||||
}))}
|
||||
selectedStations={form.watch("missionStationIds")}
|
||||
onChange={(v) => {
|
||||
form.setValue("missionStationIds", v.selectedStationIds);
|
||||
form.setValue("hpgAmbulanceState", v.hpgAmbulanceState);
|
||||
form.setValue("hpgFireEngineState", v.hpgFireEngineState);
|
||||
form.setValue("hpgPoliceState", v.hpgPoliceState);
|
||||
}}
|
||||
vehicleStates={{
|
||||
hpgAmbulanceState: form.watch("hpgAmbulanceState"),
|
||||
hpgFireEngineState: form.watch("hpgFireEngineState"),
|
||||
hpgPoliceState: form.watch("hpgPoliceState"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -371,11 +358,12 @@ export const MissionForm = () => {
|
||||
form.setValue("hpgMissionString", e.target.value);
|
||||
const [name] = e.target.value.split(":");
|
||||
const allHpgMissionTypes = keywords?.map((k) => k.hpgMissionTypes).flat();
|
||||
const missionAdditionalInfo = form.watch("missionAdditionalInfo");
|
||||
if (
|
||||
!form.watch("missionAdditionalInfo") ||
|
||||
!missionAdditionalInfo ||
|
||||
allHpgMissionTypes?.find((t) => {
|
||||
const [hpgName] = t.split(":");
|
||||
return hpgName === form.watch("missionAdditionalInfo");
|
||||
return hpgName === missionAdditionalInfo;
|
||||
})
|
||||
) {
|
||||
form.setValue("missionAdditionalInfo", name || "");
|
||||
@@ -444,7 +432,7 @@ export const MissionForm = () => {
|
||||
try {
|
||||
console.log("Saving mission", mission.addressOSMways);
|
||||
const newMission = await saveMission(mission);
|
||||
toast.success(`Einsatz ${newMission.id} erfolgreich aktualisiert`);
|
||||
toast.success(`Einsatz ${newMission.publicId} aktualisiert`);
|
||||
setSearchElements([]); // Reset search elements
|
||||
setEditingMission(null); // Reset editing state
|
||||
form.reset(); // Reset the form
|
||||
|
||||
Reference in New Issue
Block a user