Merge branch 'main' of https://github.com/VAR-Virtual-Air-Rescue/var-monorepo
This commit is contained in:
119
apps/dispatch/app/_components/Select.tsx
Normal file
119
apps/dispatch/app/_components/Select.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
FieldValues,
|
||||||
|
Path,
|
||||||
|
RegisterOptions,
|
||||||
|
UseFormReturn,
|
||||||
|
} from "react-hook-form";
|
||||||
|
import SelectTemplate, {
|
||||||
|
Props as SelectTemplateProps,
|
||||||
|
StylesConfig,
|
||||||
|
} from "react-select";
|
||||||
|
import { cn } from "helpers/cn";
|
||||||
|
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>;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
}
|
||||||
|
|
||||||
|
const customStyles: StylesConfig<any, false> = {
|
||||||
|
control: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
backgroundColor: "var(--color-base-100)",
|
||||||
|
borderColor: "color-mix(in oklab, var(--color-base-content) 20%, #0000);",
|
||||||
|
borderRadius: "0.5rem",
|
||||||
|
padding: "0.25rem",
|
||||||
|
boxShadow: "none",
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "color-mix(in oklab, var(--color-base-content) 20%, #0000);",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
option: (provided, state) => ({
|
||||||
|
...provided,
|
||||||
|
backgroundColor: state.isSelected ? "hsl(var(--p))" : "hsl(var(--b1))",
|
||||||
|
color: "var(--color-primary-content)",
|
||||||
|
"&:hover": { backgroundColor: "var(--color-base-200)" }, // DaisyUI secondary color
|
||||||
|
}),
|
||||||
|
multiValueLabel: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
color: "var(--color-primary-content)",
|
||||||
|
}),
|
||||||
|
singleValue: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
color: "var(--color-primary-content)",
|
||||||
|
}),
|
||||||
|
multiValue: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
backgroundColor: "var(--color-base-300)",
|
||||||
|
}),
|
||||||
|
menu: (provided) => ({
|
||||||
|
...provided,
|
||||||
|
backgroundColor: "var(--color-base-100)",
|
||||||
|
borderRadius: "0.5rem",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectCom = <T extends FieldValues>({
|
||||||
|
name,
|
||||||
|
label = name,
|
||||||
|
placeholder = label,
|
||||||
|
form,
|
||||||
|
formOptions,
|
||||||
|
className,
|
||||||
|
...inputProps
|
||||||
|
}: SelectProps<T>) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span className="label-text text-lg flex items-center gap-2">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<SelectTemplate
|
||||||
|
onChange={(newValue: any) => {
|
||||||
|
if (Array.isArray(newValue)) {
|
||||||
|
form.setValue(name, newValue.map((v: any) => v.value) as any, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
form.setValue(name, newValue.value, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
form.trigger(name);
|
||||||
|
form.Dirty;
|
||||||
|
}}
|
||||||
|
value={
|
||||||
|
(inputProps as any)?.isMulti
|
||||||
|
? (inputProps as any).options?.filter((o: any) =>
|
||||||
|
form.watch(name)?.includes(o.value),
|
||||||
|
)
|
||||||
|
: (inputProps as any).options?.find(
|
||||||
|
(o: any) => o.value === form.watch(name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
styles={customStyles as any}
|
||||||
|
className={cn("w-full placeholder:text-neutral-600", className)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
{form.formState.errors[name]?.message && (
|
||||||
|
<p className="text-error">
|
||||||
|
{form.formState.errors[name].message as string}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectWrapper = <T extends FieldValues>(props: SelectProps<T>) => (
|
||||||
|
<SelectCom {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
@@ -58,6 +58,13 @@ interface MapStore {
|
|||||||
aircraftId: string,
|
aircraftId: string,
|
||||||
tab: MapStore["aircraftTabs"][string],
|
tab: MapStore["aircraftTabs"][string],
|
||||||
) => void;
|
) => void;
|
||||||
|
missionTabs: {
|
||||||
|
[missionId: string]: "home" | "details" | "chat";
|
||||||
|
};
|
||||||
|
setMissionTab: (
|
||||||
|
missionId: string,
|
||||||
|
tab: MapStore["missionTabs"][string],
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMapStore = create<MapStore>((set, get) => ({
|
export const useMapStore = create<MapStore>((set, get) => ({
|
||||||
@@ -106,4 +113,12 @@ export const useMapStore = create<MapStore>((set, get) => ({
|
|||||||
[aircraftId]: tab,
|
[aircraftId]: tab,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
missionTabs: {},
|
||||||
|
setMissionTab: (missionId, tab) =>
|
||||||
|
set((state) => ({
|
||||||
|
missionTabs: {
|
||||||
|
...state.missionTabs,
|
||||||
|
[missionId]: tab,
|
||||||
|
},
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface MissionStore {
|
|||||||
missions: MissionOptionalDefaults[];
|
missions: MissionOptionalDefaults[];
|
||||||
setMissions: (missions: MissionOptionalDefaults[]) => void;
|
setMissions: (missions: MissionOptionalDefaults[]) => void;
|
||||||
getMissions: () => Promise<undefined>;
|
getMissions: () => Promise<undefined>;
|
||||||
|
setMission: (mission: MissionOptionalDefaults) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMissionsStore = create<MissionStore>((set) => ({
|
export const useMissionsStore = create<MissionStore>((set) => ({
|
||||||
@@ -47,4 +48,17 @@ export const useMissionsStore = create<MissionStore>((set) => ({
|
|||||||
set({ missions: data });
|
set({ missions: data });
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
setMission: (mission) =>
|
||||||
|
set((state) => {
|
||||||
|
const existingMissionIndex = state.missions.findIndex(
|
||||||
|
(m) => m.id === mission.id,
|
||||||
|
);
|
||||||
|
if (existingMissionIndex !== -1) {
|
||||||
|
const updatedMissions = [...state.missions];
|
||||||
|
updatedMissions[existingMissionIndex] = mission;
|
||||||
|
return { missions: updatedMissions };
|
||||||
|
} else {
|
||||||
|
return { missions: [...state.missions, mission] };
|
||||||
|
}
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ interface PannelStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const usePannelStore = create<PannelStore>((set) => ({
|
export const usePannelStore = create<PannelStore>((set) => ({
|
||||||
isOpen: false,
|
isOpen: true, // DEBUG, REMOVE LATER FOR PROD
|
||||||
setOpen: (isOpen) => set({ isOpen }),
|
setOpen: (isOpen) => set({ isOpen }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -2,11 +2,19 @@ import { useMissionsStore } from "_store/missionsStore";
|
|||||||
import { Marker, Popup, useMap } from "react-leaflet";
|
import { Marker, Popup, 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 { Fragment, useCallback, useEffect, useRef, useState } from "react";
|
import {
|
||||||
|
Fragment,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "helpers/cn";
|
||||||
import { Cross, House, Minimize2, Route } from "lucide-react";
|
import { Cross, House, Minimize2, Route } from "lucide-react";
|
||||||
import { SmartPopup, useConflict, useSmartPopup } from "_components/SmartPopup";
|
import { SmartPopup, useConflict, useSmartPopup } from "_components/SmartPopup";
|
||||||
import { Mission, MissionState } from "@repo/db";
|
import { Mission, MissionState } from "@repo/db";
|
||||||
|
import FMSStatusHistory from "./_components/MissionMarkerTabs";
|
||||||
|
|
||||||
export const MISSION_STATUS_COLORS: Record<MissionState, string> = {
|
export const MISSION_STATUS_COLORS: Record<MissionState, string> = {
|
||||||
draft: "#0092b8",
|
draft: "#0092b8",
|
||||||
@@ -21,10 +29,38 @@ export const MISSION_STATUS_TEXT_COLORS: Record<MissionState, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MissionPopupContent = ({ mission }: { mission: Mission }) => {
|
const MissionPopupContent = ({ mission }: { mission: Mission }) => {
|
||||||
|
const setMissionTab = useMapStore((state) => state.setMissionTab);
|
||||||
|
const currentTab = useMapStore(
|
||||||
|
(state) => state.missionTabs[mission.id] || "home",
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabChange = useCallback(
|
||||||
|
(tab: "home" | "details" | "chat") => {
|
||||||
|
if (currentTab !== tab) {
|
||||||
|
setMissionTab(mission.id, tab);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentTab, mission.id, setMissionTab],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderTabContent = useMemo(() => {
|
||||||
|
switch (currentTab) {
|
||||||
|
case "home":
|
||||||
|
return <FMSStatusHistory />;
|
||||||
|
case "details":
|
||||||
|
return <div>Details Content</div>;
|
||||||
|
case "chat":
|
||||||
|
return <div>Chat Content</div>;
|
||||||
|
default:
|
||||||
|
return <span className="text-gray-100">Error</span>;
|
||||||
|
}
|
||||||
|
}, [currentTab]);
|
||||||
|
|
||||||
const setOpenMissionMarker = useMapStore(
|
const setOpenMissionMarker = useMapStore(
|
||||||
(state) => state.setOpenMissionMarker,
|
(state) => state.setOpenMissionMarker,
|
||||||
);
|
);
|
||||||
const { anchor } = useSmartPopup();
|
const { anchor } = useSmartPopup();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@@ -36,7 +72,7 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Minimize2 className="text-white " size={15} />
|
<Minimize2 className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -68,40 +104,47 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="p-2 flex justify-center items-center"
|
className="p-2 px-3 flex justify-center items-center cursor-pointer"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
backgroundColor: `${MISSION_STATUS_COLORS[mission.state]}`,
|
||||||
|
borderBottom:
|
||||||
|
currentTab === "home"
|
||||||
|
? `5px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||||
|
: "5px solid transparent",
|
||||||
}}
|
}}
|
||||||
|
onClick={() => handleTabChange("home")}
|
||||||
>
|
>
|
||||||
<House className="text-sm " />
|
<House className="text-sm" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="p-2 flex justify-center items-center"
|
className="p-2 px-4 flex justify-center items-center cursor-pointer"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
backgroundColor: `${MISSION_STATUS_COLORS[mission.state]}`,
|
||||||
|
borderBottom:
|
||||||
|
currentTab === "details"
|
||||||
|
? `5px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||||
|
: "5px solid transparent",
|
||||||
}}
|
}}
|
||||||
|
onClick={() => handleTabChange("details")}
|
||||||
>
|
>
|
||||||
<Route className="text-sm " />
|
<Route className="text-sm" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="flex justify-center items-center text-2xl p-2"
|
className="p-2 px-4 flex justify-center items-center cursor-pointer"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
backgroundColor: `${MISSION_STATUS_COLORS[mission.state]}`,
|
||||||
color: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
borderBottom:
|
||||||
|
currentTab === "chat"
|
||||||
|
? `5px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||||
|
: "5px solid transparent",
|
||||||
}}
|
}}
|
||||||
|
onClick={() => handleTabChange("chat")}
|
||||||
>
|
>
|
||||||
{mission.state}
|
<Cross className="text-sm" />
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="p-2 flex-1 flex justify-center items-center"
|
|
||||||
style={{
|
|
||||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-sm text-white">Einsatz 250411</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>{renderTabContent}</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -231,9 +274,11 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
|
|||||||
closeOnClick={false}
|
closeOnClick={false}
|
||||||
autoPan={false}
|
autoPan={false}
|
||||||
wrapperClassName="relative"
|
wrapperClassName="relative"
|
||||||
className="w-[300px] h-[150px]"
|
className="w-[502px]"
|
||||||
>
|
>
|
||||||
<MissionPopupContent mission={mission} />
|
<div style={{ height: "auto", maxHeight: "90vh", overflowY: "auto" }}>
|
||||||
|
<MissionPopupContent mission={mission} />
|
||||||
|
</div>
|
||||||
</SmartPopup>
|
</SmartPopup>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import { FMS_STATUS_TEXT_COLORS } from "../AircraftMarker";
|
||||||
|
|
||||||
|
const FMSStatusHistory = () => {
|
||||||
|
const dummyData = [
|
||||||
|
{ status: "3", time: "12:00", unit: "RTW", unitshort: "RTW" },
|
||||||
|
{ status: "3", time: "12:01", unit: "Christoph 31", unitshort: "CHX31" },
|
||||||
|
{ status: "4", time: "12:09", unit: "RTW", unitshort: "RTW" },
|
||||||
|
{ status: "4", time: "12:11", unit: "Christoph 31", unitshort: "CHX31" },
|
||||||
|
{ status: "7", time: "12:34", unit: "RTW", unitshort: "RTW" },
|
||||||
|
{ status: "1", time: "12:38", unit: "Christoph 31", unitshort: "CHX31" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{[
|
||||||
|
...new Map(dummyData.map((entry) => [entry.unit, entry])).values(),
|
||||||
|
].map((entry, index, array) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="font-bold text-base"
|
||||||
|
style={{
|
||||||
|
color: FMS_STATUS_TEXT_COLORS[entry.status],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.status}
|
||||||
|
</span>
|
||||||
|
<span className="text-base-content">{entry.unitshort}</span>
|
||||||
|
</div>
|
||||||
|
{index < array.length - 1 && <span className="mx-1">|</span>}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="divider m-0" />
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{dummyData.map((entry, index) => (
|
||||||
|
<li key={index} className="flex items-center gap-2">
|
||||||
|
<span className="text-base-content">{entry.time}</span>
|
||||||
|
<span
|
||||||
|
className="font-bold text-base"
|
||||||
|
style={{
|
||||||
|
color: FMS_STATUS_TEXT_COLORS[entry.status],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.status}
|
||||||
|
</span>
|
||||||
|
<span className="text-base-content">{entry.unit}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FMSStatusHistory;
|
||||||
215
apps/dispatch/app/dispatch/_components/pannel/MissionForm.tsx
Normal file
215
apps/dispatch/app/dispatch/_components/pannel/MissionForm.tsx
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { MissionSchema } from "@repo/db/zod";
|
||||||
|
import { BellRing, BookmarkPlus, Trash2 } from "lucide-react";
|
||||||
|
import { Select } from "_components/Select";
|
||||||
|
|
||||||
|
const clearBtn = () => {
|
||||||
|
return (
|
||||||
|
<button className="btn btn-sm btn-circle btn-info">
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const missionFormSchema = MissionSchema.pick({
|
||||||
|
addressLat: true,
|
||||||
|
addressLng: true,
|
||||||
|
addressStreet: true,
|
||||||
|
addressCity: true,
|
||||||
|
addressZip: true,
|
||||||
|
missionCategory: true,
|
||||||
|
missionKeyword: true,
|
||||||
|
missionAdditionalInfo: true,
|
||||||
|
missionPatientInfo: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
type MissionFormValues = z.infer<typeof missionFormSchema>;
|
||||||
|
|
||||||
|
const dummyRettungsmittel = [
|
||||||
|
"RTW",
|
||||||
|
"Feuerwehr",
|
||||||
|
"Polizei",
|
||||||
|
"Christoph 31",
|
||||||
|
"Christoph 100",
|
||||||
|
"Christoph Berlin",
|
||||||
|
"Christophorus 1",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MissionForm: React.FC = () => {
|
||||||
|
const [missionCategory, setMissionCategory] = useState<"PRIMÄR" | "SEKUNDÄR">(
|
||||||
|
"PRIMÄR",
|
||||||
|
);
|
||||||
|
const [missionKeyword, setMissionKeyword] = useState<
|
||||||
|
"AB_ATMUNG" | "C_BLUTUNG"
|
||||||
|
>("AB_ATMUNG");
|
||||||
|
const [missionType, setMissionType] = useState<"typ1" | "typ2" | "typ3">(
|
||||||
|
"typ1",
|
||||||
|
);
|
||||||
|
const [selectedRettungsmittel, setSelectedRettungsmittel] = useState<
|
||||||
|
{ label: string; value: string }[]
|
||||||
|
>([]);
|
||||||
|
const form = useForm<MissionFormValues>({
|
||||||
|
resolver: zodResolver(missionFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
addressLat: 0,
|
||||||
|
addressLng: 0,
|
||||||
|
missionCategory: "PRIMÄR",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data: MissionFormValues) => {
|
||||||
|
console.log({
|
||||||
|
...data,
|
||||||
|
rettungsmittel: selectedRettungsmittel.map((item) => item.value),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
{/* Koorinaten Section */}
|
||||||
|
<div className="form-control">
|
||||||
|
<h2 className="text-lg font-bold mb-2">Koordinaten</h2>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...form.register("addressLat")}
|
||||||
|
className="input input-sm input-neutral input-bordered w-full"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...form.register("addressLng")}
|
||||||
|
className="input input-sm input-neutral input-bordered w-full"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Adresse Section */}
|
||||||
|
<div className="form-control">
|
||||||
|
<h2 className="text-lg font-bold mb-2">Adresse</h2>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...form.register("addressStreet")}
|
||||||
|
placeholder="Straße"
|
||||||
|
className="input input-primary input-bordered w-full mb-4"
|
||||||
|
/>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...form.register("addressCity")}
|
||||||
|
placeholder="Stadt"
|
||||||
|
className="input input-primary input-bordered w-full"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...form.register("addressZip")}
|
||||||
|
placeholder="PLZ"
|
||||||
|
className="input input-primary input-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...form.register("missionAdditionalInfo")}
|
||||||
|
placeholder="Zusätzliche Adressinformationen"
|
||||||
|
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="rettungsmittel"
|
||||||
|
label={""}
|
||||||
|
isMulti
|
||||||
|
form={form}
|
||||||
|
options={dummyRettungsmittel.map((key, val) => ({
|
||||||
|
label: key,
|
||||||
|
value: val,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Einsatzdaten Section */}
|
||||||
|
<div className="form-control">
|
||||||
|
<h2 className="text-lg font-bold mb-2">Einsatzdaten</h2>
|
||||||
|
<select
|
||||||
|
{...form.register("missionCategory")}
|
||||||
|
className="select select-primary select-bordered w-full mb-4"
|
||||||
|
onChange={(e) =>
|
||||||
|
setMissionCategory(e.target.value as "PRIMÄR" | "SEKUNDÄR")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="PRIMÄR">PRIMÄR</option>
|
||||||
|
<option value="SEKUNDÄR">SEKUNDÄR</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
{...form.register("missionKeyword")}
|
||||||
|
className="select select-primary select-bordered w-full mb-4"
|
||||||
|
onChange={(e) =>
|
||||||
|
setMissionKeyword(e.target.value as "AB_ATMUNG" | "C_BLUTUNG")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="AB_ATMUNG">AB_ATMUNG</option>
|
||||||
|
<option value="C_BLUTUNG">C_BLUTUNG</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
/* {...form.register("missionKeyword")} */
|
||||||
|
className="select select-primary select-bordered w-full mb-4"
|
||||||
|
onChange={(e) =>
|
||||||
|
setMissionType(e.target.value as "typ1" | "typ2" | "typ3")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option defaultChecked disabled value="">
|
||||||
|
Einsatz Szenerie auswählen...
|
||||||
|
</option>
|
||||||
|
<option value="typ1">typ1</option>
|
||||||
|
<option value="typ2">typ2</option>
|
||||||
|
<option value="typ3">typ3</option>
|
||||||
|
</select>
|
||||||
|
<textarea
|
||||||
|
{...form.register("missionAdditionalInfo")}
|
||||||
|
placeholder="Einsatzinformationen"
|
||||||
|
className="textarea textarea-primary textarea-bordered w-full mb-4"
|
||||||
|
/>
|
||||||
|
{missionCategory === "SEKUNDÄR" && (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Zielkrankenhaus"
|
||||||
|
className="input input-primary input-bordered w-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Patienteninformationen Section */}
|
||||||
|
<div className="form-control">
|
||||||
|
<h2 className="text-lg font-bold mb-2">Patienteninformationen</h2>
|
||||||
|
<textarea
|
||||||
|
{...form.register("missionPatientInfo")}
|
||||||
|
placeholder="Patienteninformationen"
|
||||||
|
className="textarea textarea-primary textarea-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-error">
|
||||||
|
Du musst noch ein Gebäude auswählen, um den Einsatz zu erstellen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="form-control min-h-[140px] max-w-[320px]">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button type="submit" className="btn btn-warning">
|
||||||
|
<BellRing className="h-4 w-4" /> Alarmieren
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-primary btn-block">
|
||||||
|
<BookmarkPlus className="h-5 w-5" /> Einsatz vorbereiten
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { useMissionsStore } from "_store/missionsStore";
|
|
||||||
|
|
||||||
export const Missions = () => {
|
|
||||||
const { missions } = useMissionsStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<table className="table table-xs">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Einsatzmittel</th>
|
|
||||||
<th>Ort</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{missions.map((mission) => (
|
|
||||||
<tr key={mission.id}>
|
|
||||||
<td>{mission.id}</td>
|
|
||||||
<td>{mission.missionCategory}</td>
|
|
||||||
<td>
|
|
||||||
{mission.addressStreet}, {mission.addressCity}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button className="btn btn-sm">Details</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -14,7 +14,7 @@ export const OpenButton = () => {
|
|||||||
isOpen && "transform translate-x-full",
|
isOpen && "transform translate-x-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Open
|
Neuer Einsatz
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
import { Missions } from "dispatch/_components/pannel/Missions";
|
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "helpers/cn";
|
||||||
|
import { MissionForm } from "./MissionForm";
|
||||||
|
import { Rss } from "lucide-react";
|
||||||
|
|
||||||
export const Pannel = () => {
|
export const Pannel = () => {
|
||||||
const { isOpen, setOpen } = usePannelStore();
|
const { isOpen, setOpen } = usePannelStore();
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex-1 max-w-[400px] z-9999999")}>
|
<div className={cn("flex-1 max-w-[600px] z-9999999")}>
|
||||||
<div className="bg-base-100 h-full w-full">
|
<div className="bg-base-100 min-h-screen h-full max-h-screen w-full overflow-auto">
|
||||||
<div className="flex justify-between items-center p-4">
|
<div className="flex flex-row justify-between items-center p-4">
|
||||||
<h1 className="text-xl font-bold">Pannel</h1>
|
<h1 className="text-xl font-bold flex items-center gap-2">
|
||||||
|
<Rss /> Neuer Einsatz
|
||||||
|
</h1>
|
||||||
<button className="btn" onClick={() => setOpen(false)}>
|
<button className="btn" onClick={() => setOpen(false)}>
|
||||||
Close
|
Abbrechen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Missions />
|
<div className="divider m-0" />
|
||||||
|
<div className="p-4">
|
||||||
|
<MissionForm />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { OpenButton } from "dispatch/_components/pannel/OpenButton";
|
import { OpenButton } from "dispatch/_components/pannel/OpenButton";
|
||||||
import { Pannel } from "dispatch/_components/pannel/Pannel";
|
import { Pannel } from "dispatch/_components/pannel/Pannel";
|
||||||
import MapToastCard2 from "dispatch/_components/toast/ToastCard";
|
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "helpers/cn";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
@@ -11,19 +10,20 @@ const Map = dynamic(() => import("./_components/map/Map"), { ssr: false });
|
|||||||
export default () => {
|
export default () => {
|
||||||
const { isOpen } = usePannelStore();
|
const { isOpen } = usePannelStore();
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="relative flex-1 flex transition-all duration-500 ease w-full">
|
||||||
className={cn(
|
|
||||||
"relative flex-1 flex transition-all duration-500 ease",
|
|
||||||
!isOpen && "w-[calc(100%+400px)]",
|
|
||||||
isOpen && "w-[100%]",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* <MapToastCard2 /> */}
|
{/* <MapToastCard2 /> */}
|
||||||
<div className="flex-1 relative flex">
|
<div className="flex flex-1 relative">
|
||||||
<OpenButton />
|
<OpenButton />
|
||||||
<Map />
|
<Map />
|
||||||
</div>
|
</div>
|
||||||
<Pannel />
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute right-0 w-[500px] z-999 transition-transform",
|
||||||
|
isOpen ? "translate-x-0" : "translate-x-full",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Pannel />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user