copied aircraft marker to mission marker
This commit is contained in:
@@ -9,6 +9,7 @@ interface MissionStore {
|
||||
export const useMissionsStore = create<MissionStore>((set) => ({
|
||||
missions: [
|
||||
{
|
||||
state: "draft",
|
||||
id: "01250325",
|
||||
addressLat: 52.520008,
|
||||
addressLng: 13.404954,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useMapStore } from "_store/mapStore";
|
||||
import { MapContainer } from "react-leaflet";
|
||||
import { BaseMaps } from "dispatch/_components/map/BaseMaps";
|
||||
import { ContextMenu } from "dispatch/_components/map/ContextMenu";
|
||||
import { MissionMarkers } from "dispatch/_components/map/MissionMarkers";
|
||||
import { MissionLayer } from "dispatch/_components/map/MissionMarkers";
|
||||
import { SearchElements } from "dispatch/_components/map/SearchElements";
|
||||
import { AircraftLayer } from "dispatch/_components/map/AircraftMarker";
|
||||
|
||||
@@ -16,7 +16,7 @@ export default ({}) => {
|
||||
<BaseMaps />
|
||||
<SearchElements />
|
||||
<ContextMenu />
|
||||
<MissionMarkers />
|
||||
<MissionLayer />
|
||||
<AircraftLayer />
|
||||
</MapContainer>
|
||||
);
|
||||
|
||||
@@ -1,104 +1,252 @@
|
||||
import { MissionOptionalDefaults } from "@repo/db/zod";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { useMissionsStore } from "_store/missionsStore";
|
||||
import { Icon, Marker as LMarker } from "leaflet";
|
||||
import { House, Route } from "lucide-react";
|
||||
import { RefObject, useEffect, useRef, useState } from "react";
|
||||
import { Marker, Popup, useMap } from "react-leaflet";
|
||||
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { Fragment, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { cn } from "helpers/cn";
|
||||
import { Cross, House, Minimize2, Route } from "lucide-react";
|
||||
import { SmartPopup, useConflict, useSmartPopup } from "_components/SmartPopup";
|
||||
import { Mission } from "@repo/db";
|
||||
|
||||
export const MissionMarker = ({
|
||||
mission,
|
||||
}: {
|
||||
mission: MissionOptionalDefaults;
|
||||
}) => {
|
||||
const { openMissionMarker, setOpenMissionMarker } = useMapStore();
|
||||
const [zoom, setZoom] = useState(0);
|
||||
export const MISSION_STATUS_COLORS: { [key: string]: string } = {
|
||||
draft: "0092b8",
|
||||
running: "#155dfc",
|
||||
};
|
||||
|
||||
export const MISSION_STATUS_TEXT_COLORS: { [key: string]: string } = {
|
||||
draft: "00d3f2",
|
||||
running: "#50a2ff",
|
||||
};
|
||||
|
||||
const MissionPopupContent = ({ mission }: { mission: Mission }) => {
|
||||
const setOpenMissionMarker = useMapStore(
|
||||
(state) => state.setOpenMissionMarker,
|
||||
);
|
||||
const { anchor } = useSmartPopup();
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="absolute p-1 z-99 top-0 right-0 transform -translate-y-full bg-base-100 cursor-pointer"
|
||||
onClick={() => {
|
||||
setOpenMissionMarker({
|
||||
open: [],
|
||||
close: [mission.id],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Minimize2 className="text-white " size={15} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"absolute w-[calc(100%+2px)] h-4 z-99",
|
||||
anchor.includes("left") ? "-left-[2px]" : "-right-[2px]",
|
||||
anchor.includes("top") ? "-top-[2px]" : "-bottom-[2px]",
|
||||
)}
|
||||
style={{
|
||||
borderLeft: anchor.includes("left")
|
||||
? `3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||
: "",
|
||||
borderRight: anchor.includes("right")
|
||||
? `3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||
: "",
|
||||
borderTop: anchor.includes("top")
|
||||
? `3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||
: "",
|
||||
borderBottom: anchor.includes("bottom")
|
||||
? `3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]}`
|
||||
: "",
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="flex gap-[2px] text-white pb-0.5"
|
||||
style={{
|
||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="p-2 flex justify-center items-center"
|
||||
style={{
|
||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
||||
}}
|
||||
>
|
||||
<House className="text-sm " />
|
||||
</div>
|
||||
<div
|
||||
className="p-2 flex justify-center items-center"
|
||||
style={{
|
||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
||||
}}
|
||||
>
|
||||
<Route className="text-sm " />
|
||||
</div>
|
||||
<div
|
||||
className="flex justify-center items-center text-2xl p-2"
|
||||
style={{
|
||||
backgroundColor: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
||||
color: `${MISSION_STATUS_TEXT_COLORS[mission.state]}`,
|
||||
}}
|
||||
>
|
||||
{mission.state}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MissionMarker = ({ mission }: { mission: Mission }) => {
|
||||
const missions = useMissionsStore((state) => state.missions);
|
||||
const map = useMap();
|
||||
const markerRef = useRef<LMarker<any>>(null);
|
||||
const markerRef = useRef<LMarker>(null);
|
||||
const popupRef = useRef<LPopup>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// markerRef.current?.openPopup();
|
||||
|
||||
const handleZoom = () => {
|
||||
setZoom(map.getZoom());
|
||||
};
|
||||
map.on("zoom", handleZoom);
|
||||
return () => {
|
||||
map.off("zoom", handleZoom);
|
||||
};
|
||||
}, [map]);
|
||||
const { openMissionMarker, setOpenMissionMarker } = useMapStore(
|
||||
(store) => store,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = () => {
|
||||
if (!mission.id) return;
|
||||
if (!openMissionMarker.includes(mission.id)) {
|
||||
setOpenMissionMarker({
|
||||
open: [mission.id],
|
||||
close: [],
|
||||
});
|
||||
// setSearchPopup(null);
|
||||
} else {
|
||||
console.log("close", mission.id);
|
||||
const open = openMissionMarker.includes(mission.id);
|
||||
if (open) {
|
||||
setOpenMissionMarker({
|
||||
open: [],
|
||||
close: [mission.id],
|
||||
});
|
||||
// setSearchPopup(null);
|
||||
} else {
|
||||
setOpenMissionMarker({
|
||||
open: [mission.id],
|
||||
close: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
markerRef.current?.on("click", handleClick);
|
||||
return () => {
|
||||
markerRef.current?.off("click", handleClick);
|
||||
};
|
||||
}, [markerRef.current, mission.id, setOpenMissionMarker, openMissionMarker]);
|
||||
}, [markerRef.current, mission.id, openMissionMarker]);
|
||||
|
||||
const [anchor, setAnchor] = useState<
|
||||
"topleft" | "topright" | "bottomleft" | "bottomright"
|
||||
>("topleft");
|
||||
|
||||
const handleConflict = () => {
|
||||
const newAnchor = useConflict(mission.id, "marker");
|
||||
setAnchor(newAnchor);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleConflict();
|
||||
}, [missions, openMissionMarker]);
|
||||
|
||||
useEffect(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
handleConflict();
|
||||
}, 100);
|
||||
|
||||
map.on("zoom", handleConflict);
|
||||
return () => {
|
||||
map.off("zoom", handleConflict);
|
||||
};
|
||||
}, [map, openMissionMarker]);
|
||||
|
||||
const getMarkerHTML = (
|
||||
mission: Mission,
|
||||
anchor: "topleft" | "topright" | "bottomleft" | "bottomright",
|
||||
) => {
|
||||
return `<div
|
||||
class="${cn(
|
||||
"relative w-[140px] transform flex items-center gap-2 px-2 z-100",
|
||||
anchor.includes("right") && "-translate-x-full",
|
||||
anchor.includes("bottom") && "-translate-y-full",
|
||||
)}"
|
||||
style="
|
||||
background-color: ${MISSION_STATUS_COLORS[mission.state]};
|
||||
${openMissionMarker.includes(mission.id) ? "opacity: 0; pointer-events: none;" : ""}
|
||||
">
|
||||
|
||||
<div
|
||||
class="${cn(
|
||||
"absolute w-4 h-4 z-99",
|
||||
anchor.includes("left") ? "-left-[2px]" : "-right-[2px]",
|
||||
anchor.includes("top") ? "-top-[2px]" : "-bottom-[2px]",
|
||||
)}"
|
||||
style="
|
||||
${anchor.includes("left") ? `border-left: 3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]};` : `border-right: 3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]};`}
|
||||
${anchor.includes("top") ? `border-top: 3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]};` : `border-bottom: 3px solid ${MISSION_STATUS_TEXT_COLORS[mission.state]};`}
|
||||
"
|
||||
></div>
|
||||
<span
|
||||
class="font-semibold text-xl"
|
||||
style="color: ${MISSION_STATUS_TEXT_COLORS[mission.state]};"
|
||||
>
|
||||
${mission.state}
|
||||
</span>
|
||||
<span class="text-white text-[15px]">
|
||||
${mission.missionSummary}
|
||||
</span>
|
||||
<div
|
||||
data-id="${mission.id}"
|
||||
id="marker-${mission.id}"
|
||||
class="${cn(
|
||||
"map-collision absolute w-[200%] h-[200%] top-0 left-0 transform pointer-events-none",
|
||||
anchor.includes("left") && "-translate-x-1/2",
|
||||
anchor.includes("top") && "-translate-y-1/2",
|
||||
)}"
|
||||
></div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment key={mission.id}>
|
||||
<Marker
|
||||
ref={markerRef}
|
||||
position={[mission.addressLat, mission.addressLng]}
|
||||
icon={
|
||||
new Icon({
|
||||
iconUrl: "/icons/MissionIcon.png",
|
||||
iconSize: zoom < 8 ? [30, 30] : [50, 50],
|
||||
popupAnchor: [0, 0],
|
||||
new DivIcon({
|
||||
iconAnchor: [0, 0],
|
||||
html: getMarkerHTML(mission, anchor),
|
||||
})
|
||||
}
|
||||
></Marker>
|
||||
{mission.id && openMissionMarker.includes(mission.id) && (
|
||||
<Popup
|
||||
/>
|
||||
{openMissionMarker.includes(mission.id) && (
|
||||
<SmartPopup
|
||||
id={mission.id}
|
||||
ref={popupRef}
|
||||
position={[mission.addressLat, mission.addressLng]}
|
||||
autoClose={false}
|
||||
closeOnClick={false}
|
||||
autoPan={false}
|
||||
wrapperClassName="relative"
|
||||
className="w-[200px] h-[150px]"
|
||||
>
|
||||
<div className="absolute z-1000 opacity-100 pointer-events-auto w-[500px] text-white">
|
||||
<div className="bg-amber-600 pt-2 flex gap-1">
|
||||
<div className="p-2 bg-amber-700">
|
||||
<House className="text-sm" />
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<Route />
|
||||
</div>
|
||||
<div className="bg-red-600 flex justify-center items-center text-2xl p-2">
|
||||
{mission.missionCategory}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
<MissionPopupContent mission={mission} />
|
||||
</SmartPopup>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const MissionMarkers = () => {
|
||||
const { missions } = useMissionsStore();
|
||||
export const MissionLayer = () => {
|
||||
const missions = useMissionsStore((state) => state.missions);
|
||||
|
||||
// IDEA: Add Marker to Map Layer / LayerGroup
|
||||
return (
|
||||
<>
|
||||
{missions.map((mission) => (
|
||||
<MissionMarker key={mission.id} mission={mission} />
|
||||
))}
|
||||
{missions.map((mission) => {
|
||||
return <MissionMarker key={mission.id} mission={mission as Mission} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
model Mission {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
state MissionState @default(draft)
|
||||
addressLat Float
|
||||
addressLng Float
|
||||
addressStreet String
|
||||
@@ -10,11 +11,17 @@ model Mission {
|
||||
missionSummary String
|
||||
missionPatientInfo String
|
||||
missionAdditionalInfo String
|
||||
hpgAmbulanceState HpgState? @default(ready)
|
||||
hpgFireEngineState HpgState? @default(ready)
|
||||
hpgPoliceState HpgState? @default(ready)
|
||||
hpgLocationLat Float? @default(0)
|
||||
hpgLocationLng Float? @default(0)
|
||||
hpgAmbulanceState HpgState? @default(ready)
|
||||
hpgFireEngineState HpgState? @default(ready)
|
||||
hpgPoliceState HpgState? @default(ready)
|
||||
hpgLocationLat Float? @default(0)
|
||||
hpgLocationLng Float? @default(0)
|
||||
}
|
||||
|
||||
enum MissionState {
|
||||
running
|
||||
finished
|
||||
draft
|
||||
}
|
||||
|
||||
enum HpgState {
|
||||
|
||||
Reference in New Issue
Block a user