Files
var-monorepo/apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx
2025-07-04 20:29:46 -07:00

154 lines
5.0 KiB
TypeScript

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"
}
formatOptionLabel={(option: any) => option.component}
options={stationsOptions.map((s) => ({
label: s.label,
component: (
<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,
}))}
/>
);
}