copied aircraft marker to mission marker

This commit is contained in:
PxlLoewe
2025-04-15 23:24:07 -07:00
parent 166b78bfc3
commit d74181c78f
5 changed files with 227 additions and 71 deletions

View File

@@ -9,6 +9,7 @@ interface MissionStore {
export const useMissionsStore = create<MissionStore>((set) => ({
missions: [
{
state: "draft",
id: "01250325",
addressLat: 52.520008,
addressLng: 13.404954,

View File

@@ -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>
);

View File

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