Merge branch 'main' of github.com:VAR-Virtual-Air-Rescue/var-monorepo

This commit is contained in:
Nicolas
2025-07-01 11:58:44 +02:00
9 changed files with 250 additions and 158 deletions

View 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,
}))}
/>
);
}

View File

@@ -28,6 +28,7 @@ import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
import { selectRandomHPGMissionSzenery } from "_helpers/selectRandomHPGMission"; import { selectRandomHPGMissionSzenery } from "_helpers/selectRandomHPGMission";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { cn } from "@repo/shared-components"; import { cn } from "@repo/shared-components";
import { StationsSelect } from "(app)/dispatch/_components/StationSelect";
export const MissionForm = () => { export const MissionForm = () => {
const { editingMissionId, setEditingMission } = usePannelStore(); const { editingMissionId, setEditingMission } = usePannelStore();
@@ -168,10 +169,6 @@ export const MissionForm = () => {
if (createNewMission) { if (createNewMission) {
newMission = await createMissionMutation.mutateAsync({ newMission = await createMissionMutation.mutateAsync({
...(mission as unknown as Prisma.MissionCreateInput), ...(mission as unknown as Prisma.MissionCreateInput),
missionAdditionalInfo:
!mission.missionAdditionalInfo.length && hpgSzenario
? `HPG-Szenario: ${hpgSzenario}`
: mission.missionAdditionalInfo,
hpgSelectedMissionString: szenarioCode, hpgSelectedMissionString: szenarioCode,
}); });
if (validationRequired) { if (validationRequired) {
@@ -205,7 +202,7 @@ export const MissionForm = () => {
} }
return newMission; return newMission;
}; };
console.log(form.formState.errors);
return ( return (
<form className="space-y-4"> <form className="space-y-4">
{/* Koorinaten Section */} {/* Koorinaten Section */}
@@ -262,33 +259,23 @@ export const MissionForm = () => {
className="input input-primary input-bordered w-full mt-4" className="input input-primary input-bordered w-full mt-4"
/> />
</div> </div>
{/* Rettungsmittel Section */} {/* Rettungsmittel Section */}
<div className="form-control"> <div className="form-control">
<h2 className="text-lg font-bold mb-2">Rettungsmittel</h2> <h2 className="text-lg font-bold mb-2">Rettungsmittel</h2>
<Select <StationsSelect
name="missionStationIds"
label={""}
placeholder="Wähle ein oder mehrere Rettungsmittel aus"
isMulti isMulti
form={form} selectedStations={form.watch("missionStationIds")}
options={stations onChange={(v) => {
?.sort((a, b) => { form.setValue("missionStationIds", v.selectedStationIds);
const aHasAircraft = aircrafts?.some((ac) => ac.stationId === a.id) ? 0 : 1; form.setValue("hpgAmbulanceState", v.hpgAmbulanceState);
const bHasAircraft = aircrafts?.some((ac) => ac.stationId === b.id) ? 0 : 1; form.setValue("hpgFireEngineState", v.hpgFireEngineState);
return aHasAircraft - bHasAircraft; form.setValue("hpgPoliceState", v.hpgPoliceState);
}) }}
.map((s) => ({ vehicleStates={{
label: ( hpgAmbulanceState: form.watch("hpgAmbulanceState"),
<span className="flex items-center gap-2"> hpgFireEngineState: form.watch("hpgFireEngineState"),
{aircrafts?.some((a) => a.stationId === s.id) && ( hpgPoliceState: form.watch("hpgPoliceState"),
<Radio className="text-success" size={15} /> }}
)}
{s.bosCallsign}
</span>
),
value: s.id,
}))}
/> />
</div> </div>
@@ -371,11 +358,12 @@ export const MissionForm = () => {
form.setValue("hpgMissionString", e.target.value); form.setValue("hpgMissionString", e.target.value);
const [name] = e.target.value.split(":"); const [name] = e.target.value.split(":");
const allHpgMissionTypes = keywords?.map((k) => k.hpgMissionTypes).flat(); const allHpgMissionTypes = keywords?.map((k) => k.hpgMissionTypes).flat();
const missionAdditionalInfo = form.watch("missionAdditionalInfo");
if ( if (
!form.watch("missionAdditionalInfo") || !missionAdditionalInfo ||
allHpgMissionTypes?.find((t) => { allHpgMissionTypes?.find((t) => {
const [hpgName] = t.split(":"); const [hpgName] = t.split(":");
return hpgName === form.watch("missionAdditionalInfo"); return hpgName === missionAdditionalInfo;
}) })
) { ) {
form.setValue("missionAdditionalInfo", name || ""); form.setValue("missionAdditionalInfo", name || "");
@@ -444,7 +432,7 @@ export const MissionForm = () => {
try { try {
console.log("Saving mission", mission.addressOSMways); console.log("Saving mission", mission.addressOSMways);
const newMission = await saveMission(mission); const newMission = await saveMission(mission);
toast.success(`Einsatz ${newMission.id} erfolgreich aktualisiert`); toast.success(`Einsatz ${newMission.publicId} aktualisiert`);
setSearchElements([]); // Reset search elements setSearchElements([]); // Reset search elements
setEditingMission(null); // Reset editing state setEditingMission(null); // Reset editing state
form.reset(); // Reset the form form.reset(); // Reset the form

View File

@@ -1,16 +1,8 @@
"use client"; "use client";
import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form"; import { FieldValues, Path } from "react-hook-form";
import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select"; import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select";
import { cn } from "@repo/shared-components"; import { cn } from "@repo/shared-components";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { CSSProperties } from "react";
interface SelectProps<T extends FieldValues> extends Omit<SelectTemplateProps, "form"> {
label?: any;
name: Path<T>;
form: UseFormReturn<T> | any;
formOptions?: RegisterOptions<T>;
}
const customStyles: StylesConfig<any, false> = { const customStyles: StylesConfig<any, false> = {
control: (provided) => ({ control: (provided) => ({
@@ -28,7 +20,7 @@ const customStyles: StylesConfig<any, false> = {
...provided, ...provided,
backgroundColor: state.isSelected ? "hsl(var(--p))" : "hsl(var(--b1))", backgroundColor: state.isSelected ? "hsl(var(--p))" : "hsl(var(--b1))",
color: "var(--color-primary-content)", color: "var(--color-primary-content)",
"&:hover": { backgroundColor: "var(--color-base-200)" }, // DaisyUI secondary color "&:hover": { backgroundColor: "var(--color-base-200)" },
}), }),
multiValueLabel: (provided) => ({ multiValueLabel: (provided) => ({
...provided, ...provided,
@@ -46,53 +38,55 @@ const customStyles: StylesConfig<any, false> = {
...provided, ...provided,
backgroundColor: "var(--color-base-100)", backgroundColor: "var(--color-base-100)",
borderRadius: "0.5rem", borderRadius: "0.5rem",
zIndex: 9999,
}), }),
}; };
const SelectCom = <T extends FieldValues>({ interface SelectProps extends Omit<SelectTemplateProps, "form" | "value" | "onChange"> {
name, label?: any;
label = name, value: any;
onChange: (value: any) => void;
error?: string;
}
const SelectCom = ({
label,
placeholder = label, placeholder = label,
form, value,
formOptions, onChange,
error,
className, className,
...inputProps ...inputProps
}: SelectProps<T>) => { }: SelectProps) => {
return ( return (
<div> <div className="relative">
<span className="label-text text-lg flex items-center gap-2">{label}</span> <span className="label-text text-lg flex items-center gap-2">{label}</span>
<SelectTemplate <SelectTemplate
onChange={(newValue: any) => { onChange={(newValue: any) => {
if (Array.isArray(newValue)) { if ((inputProps as any)?.isMulti) {
form.setValue(name, newValue.map((v: any) => v.value) as any, { onChange(Array.isArray(newValue) ? newValue.map((v: any) => v.value) : []);
shouldDirty: true,
});
} else { } else {
form.setValue(name, newValue.value, { onChange(newValue ? newValue.value : null);
shouldDirty: true,
});
} }
form.trigger(name);
form.Dirty;
}} }}
value={ value={
(inputProps as any)?.isMulti (inputProps as any)?.isMulti
? (inputProps as any).options?.filter((o: any) => form.watch(name)?.includes(o.value)) ? (inputProps as any).options?.filter(
: (inputProps as any).options?.find((o: any) => o.value === form.watch(name)) (o: any) => Array.isArray(value) && value.includes(o.value),
)
: (inputProps as any).options?.find((o: any) => o.value === value)
} }
styles={customStyles as any} styles={customStyles as any}
className={cn("w-full placeholder:text-neutral-600", className)} className={cn("w-full placeholder:text-neutral-600", className)}
placeholder={placeholder} placeholder={placeholder}
{...inputProps} {...inputProps}
/> />
{form.formState.errors[name]?.message && ( {error && <p className="text-error">{error}</p>}
<p className="text-error">{form.formState.errors[name].message as string}</p>
)}
</div> </div>
); );
}; };
const SelectWrapper = <T extends FieldValues>(props: SelectProps<T>) => <SelectCom {...props} />; const SelectWrapper = <T extends FieldValues>(props: SelectProps) => <SelectCom {...props} />;
export const Select = dynamic(() => Promise.resolve(SelectWrapper), { export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
ssr: false, ssr: false,

View File

@@ -68,7 +68,7 @@ export const Chat = () => {
{chatOpen && ( {chatOpen && (
<div <div
tabIndex={0} tabIndex={0}
className="dropdown-content card bg-base-200 w-150 shadow-md z-[1100] max-h-[400px] ml-2 border-1 border-primary" className="dropdown-content card bg-base-200 w-150 shadow-md z-[1100] max-h-[480px] ml-2 border-1 border-primary"
> >
<div className="card-body relative"> <div className="card-body relative">
<button <button
@@ -140,6 +140,7 @@ export const Chat = () => {
{chat.notification && <span className="indicator-item status status-info" />} {chat.notification && <span className="indicator-item status status-info" />}
</a> </a>
<div className="tab-content bg-base-100 border-base-300 p-6 overflow-y-auto max-h-[250px]"> <div className="tab-content bg-base-100 border-base-300 p-6 overflow-y-auto max-h-[250px]">
{/* So macht man kein overflow handeling, weiß ich. Aber es funktioniert... */}
{chat.messages.map((chatMessage) => { {chat.messages.map((chatMessage) => {
const isSender = chatMessage.senderId === session.data?.user.id; const isSender = chatMessage.senderId === session.data?.user.id;
return ( return (

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { usePannelStore } from "_store/pannelStore"; import { usePannelStore } from "_store/pannelStore";
import { Control, Icon, LatLngExpression } from "leaflet"; import { Control, Icon, LatLngExpression } from "leaflet";
import { useEffect, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { import {
LayerGroup, LayerGroup,
LayersControl, LayersControl,
@@ -73,9 +73,9 @@ 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 [stationsWithIcon, setStationsWithIcon] = useState<(Station & { icon?: string })[]>([]); // Zustand für die Stationen mit Icon
const attributionText = ""; const attributionText = "";
const resetSelection = () => { const resetSelection = () => {
@@ -94,27 +94,29 @@ const StationsLayer = ({ attribution }: { attribution: Control.Attribution }) =>
} }
}; };
useEffect(() => { const [stationsWithIcon, setStationsWithIcon] = useState<(Station & { icon: string })[]>([]);
// Erstelle die Icons für alle Stationen
useEffect(() => {
if (!stations) {
setStationsWithIcon([]);
return;
}
const fetchIcons = async () => { const fetchIcons = async () => {
if (!stations) return;
const urls = await Promise.all( const urls = await Promise.all(
stations.map(async (station) => { stations.map(async (station) => {
return createCustomMarker(station.operator); return await createCustomMarker(station.operator);
}), }),
); );
setStationsWithIcon(stations.map((station, index) => ({ ...station, icon: urls[index] }))); setStationsWithIcon(stations.map((station, index) => ({ ...station, icon: urls[index]! })));
}; };
fetchIcons(); fetchIcons();
}, [stations]); }, [stations]);
return ( return (
<FeatureGroup> <FeatureGroup>
{stationsWithIcon.map((station) => { {stationsWithIcon?.map((station) => {
const coordinates: LatLngExpression = [station.latitude, station.longitude]; const coordinates: LatLngExpression = [station.latitude, station.longitude];
const typeLabel = station.bosUse.charAt(0).toUpperCase(); const typeLabel = station.bosUse?.charAt(0).toUpperCase();
return ( return (
<Marker <Marker

View File

@@ -48,6 +48,7 @@ import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
import { getOsmAddress } from "_querys/osm"; import { getOsmAddress } from "_querys/osm";
import { hpgStateToFMSStatus } from "_helpers/hpgStateToFmsStatus"; import { hpgStateToFMSStatus } from "_helpers/hpgStateToFmsStatus";
import { cn } from "@repo/shared-components"; import { cn } from "@repo/shared-components";
import { StationsSelect } from "(app)/dispatch/_components/StationSelect";
const Einsatzdetails = ({ const Einsatzdetails = ({
mission, mission,
@@ -420,39 +421,6 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
}, },
}); });
const stationsOptions = [
...(allStations
?.filter((s) => !mission.missionStationIds.includes(s.id))
?.map((station) => ({
label: station.bosCallsign,
value: station.id,
type: "station" as const,
isOnline: !!connectedAircrafts?.find((a) => a.stationId === station.id),
})) || []),
...(!mission.hpgFireEngineState || mission.hpgFireEngineState === "NOT_REQUESTED"
? [{ label: "Feuerwehr", value: "FW", type: "vehicle" as const }]
: []),
...(!mission.hpgAmbulanceState || mission.hpgAmbulanceState === "NOT_REQUESTED"
? [{ label: "Rettungsdienst", value: "RTW", type: "vehicle" as const }]
: []),
...(!mission.hpgPoliceState || mission.hpgPoliceState === "NOT_REQUESTED"
? [{ label: "Polizei", value: "POL", 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);
});
const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected"; const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
const HPGVehicle = ({ state, name }: { state: HpgState; name: string }) => ( const HPGVehicle = ({ state, name }: { state: HpgState; name: string }) => (
@@ -503,70 +471,57 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
const connectedAircraft = connectedAircrafts?.find( const connectedAircraft = connectedAircrafts?.find(
(aircraft) => aircraft.stationId === station.id, (aircraft) => aircraft.stationId === station.id,
); );
console.log("connectedAircraft", connectedAircraft);
return ( return (
<li key={index} className="flex items-center gap-2"> <li key={index} className="flex items-center gap-2">
{connectedAircraft && ( <span
<span className="font-bold text-base"
className="font-bold text-base" style={{
style={{ color: FMS_STATUS_TEXT_COLORS[connectedAircraft?.fmsStatus || "6"],
color: FMS_STATUS_TEXT_COLORS[connectedAircraft.fmsStatus], }}
}} >
> {connectedAircraft?.fmsStatus || "6"}
{connectedAircraft.fmsStatus} </span>
</span> <span className="text-base-content flex flex-col ">
)} <span className="font-bold">{station.bosCallsign}</span>
<span className="text-base-content"> {!connectedAircraft && (
<div> <span className="text-gray-400 text-xs">Kein Benutzer verbunden</span>
<span className="font-bold">{station.bosCallsign}</span> )}
{/* {item.min > 0 && (
<>
<br />
Ankunft in ca. {item.min} min
</>
)} */}
</div>
</span> </span>
</li> </li>
); );
})} })}
{mission.hpgAmbulanceState && <HPGVehicle state={mission.hpgAmbulanceState} name="RTW" />} {mission.hpgAmbulanceState && mission.hpgAmbulanceState !== "NOT_REQUESTED" && (
{mission.hpgFireEngineState && ( <HPGVehicle state={mission.hpgAmbulanceState} name="RTW" />
)}
{mission.hpgFireEngineState && mission.hpgFireEngineState !== "NOT_REQUESTED" && (
<HPGVehicle state={mission.hpgFireEngineState} name="Feuerwehr" /> <HPGVehicle state={mission.hpgFireEngineState} name="Feuerwehr" />
)} )}
{mission.hpgPoliceState && <HPGVehicle state={mission.hpgPoliceState} name="Polizei" />} {mission.hpgPoliceState && mission.hpgPoliceState !== "NOT_REQUESTED" && (
<HPGVehicle state={mission.hpgPoliceState} name="Polizei" />
)}
</ul> </ul>
{dispatcherConnected && ( {dispatcherConnected && (
<div> <div>
<div className="divider mt-0 mb-0" /> <div className="divider mt-0 mb-0" />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* TODO: make it a small multiselect */} {/* TODO: make it a small multiselect */}
<select <StationsSelect
className="select select-sm select-primary select-bordered flex-1" menuPlacement="top"
onChange={(e) => { className="min-w-[320px] flex-1"
const value = e.target.value; isMulti={false}
const parsedValue = !isNaN(Number(value)) ? parseInt(value, 10) : value; onChange={(v: any) => {
setSelectedStation(parsedValue as number | "RTW" | "POL" | "FW" | null); setSelectedStation(v);
}} }}
value={selectedStation || "default"} selectedStations={mission.missionStationIds}
> filterSelected
<option disabled value={"default"}> vehicleStates={{
Rettungsmittel auswählen hpgAmbulanceState: mission.hpgAmbulanceState || undefined,
</option> hpgFireEngineState: mission.hpgFireEngineState || undefined,
{stationsOptions.map((option) => ( hpgPoliceState: mission.hpgPoliceState || undefined,
<option }}
key={option.value} />
value={option.value}
className={cn(
"flex gap-2",
"isOnline" in option && option?.isOnline && "text-green-500",
)}
>
{option.label}
{"isOnline" in option && option?.isOnline && " (Online)"}
</option>
))}
</select>
<button <button
className="btn btn-sm btn-primary btn-outline" className="btn btn-sm btn-primary btn-outline"
onClick={async () => { onClick={async () => {

View File

@@ -58,8 +58,8 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
/> />
<Input <Input
form={form} form={form}
label="zunehmende Atembeschwerden" label="Beschreibung"
placeholder="Beschreibung" placeholder="zunehmende Atembeschwerden"
name="description" name="description"
className="input-sm" className="input-sm"
/> />

View File

@@ -21,6 +21,7 @@ export const ListInput = <T extends FieldValues>({
const [value, setValue] = useState<string>(""); const [value, setValue] = useState<string>("");
return ( return (
<Controller <Controller
defaultValue={[] as any}
control={control} control={control}
name={name} name={name}
render={({ field }) => { render={({ field }) => {

View File

@@ -10,7 +10,7 @@ remote_write:
scrape_configs: scrape_configs:
- job_name: core-server - job_name: core-server
static_configs: static_configs:
- targets: ["host.docker.internal:3000"] - targets: ["host.docker.internal:3005"]
- job_name: Kuma-Status - job_name: Kuma-Status
static_configs: static_configs:
- targets: ["status.virtualairrescue.com"] - targets: ["status.virtualairrescue.com"]