Skip to content

Commit

Permalink
Merge pull request #376 from dataforgoodfr/enhancement/frontend/excur…
Browse files Browse the repository at this point in the history
…sion-amp-details

Added MPA zones details in excursion
  • Loading branch information
HenriChabert authored Dec 17, 2024
2 parents b4f66ee + 895b4d1 commit 035c4d1
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 148 deletions.
7 changes: 6 additions & 1 deletion frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) {
return (
<>
<html lang="en" className="light" data-theme="light" suppressHydrationWarning>
<html
lang="en"
className="light"
data-theme="light"
suppressHydrationWarning
>
<head>
<meta charSet="UTF-8" />
<meta
Expand Down
97 changes: 62 additions & 35 deletions frontend/app/map/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ import {
getVessels,
getVesselSegments,
getVesselsLatestPositions,
getVesselTimeByZone,
} from "@/services/backend-rest-client"
import useSWR from "swr"
import { useShallow } from 'zustand/react/shallow'
import { useShallow } from "zustand/react/shallow"

import { Vessel, VesselPosition } from "@/types/vessel"
import { ZoneWithGeometry } from "@/types/zone"
import { ZoneCategory, ZoneWithGeometry } from "@/types/zone"
import { useLoaderStore } from "@/libs/stores/loader-store"
import { useMapStore } from "@/libs/stores/map-store"
import { useTrackModeOptionsStore } from "@/libs/stores/track-mode-options-store"
import { useVesselsStore } from "@/libs/stores/vessels-store"
import LeftPanel from "@/components/core/left-panel"
import MapControls from "@/components/core/map-controls"
import Map from "@/components/core/map/main-map"
import { useMapStore } from "@/libs/stores/map-store"
import { useVesselsStore } from "@/libs/stores/vessels-store"
import { useLoaderStore } from "@/libs/stores/loader-store"
import { useTrackModeOptionsStore } from "@/libs/stores/track-mode-options-store"

const fetcher = async (url: string) => {
const response = await fetch(url, {
Expand All @@ -30,17 +31,26 @@ const fetcher = async (url: string) => {
export default function MapPage() {
const setVessels = useVesselsStore((state) => state.setVessels)

const { setZonesLoading, setPositionsLoading, setVesselsLoading, setExcursionsLoading } = useLoaderStore(useShallow((state) => ({
setZonesLoading: state.setZonesLoading,
setPositionsLoading: state.setPositionsLoading,
setVesselsLoading: state.setVesselsLoading,
setExcursionsLoading: state.setExcursionsLoading,
})))
const {
setZonesLoading,
setPositionsLoading,
setVesselsLoading,
setExcursionsLoading,
} = useLoaderStore(
useShallow((state) => ({
setZonesLoading: state.setZonesLoading,
setPositionsLoading: state.setPositionsLoading,
setVesselsLoading: state.setVesselsLoading,
setExcursionsLoading: state.setExcursionsLoading,
}))
)

const { mode: mapMode, setLatestPositions } = useMapStore(useShallow((state) => ({
mode: state.mode,
setLatestPositions: state.setLatestPositions,
})))
const { mode: mapMode, setLatestPositions } = useMapStore(
useShallow((state) => ({
mode: state.mode,
setLatestPositions: state.setLatestPositions,
}))
)

const { data: vessels = [], isLoading: isLoadingVessels } = useSWR<Vessel[]>(
"/api/vessels",
Expand All @@ -54,9 +64,9 @@ export default function MapPage() {

useEffect(() => {
if (!isLoadingVessels) {
setVessels(vessels);
setVessels(vessels)
}
}, [vessels, isLoadingVessels]);
}, [vessels, isLoadingVessels])

const { data: zones = [], isLoading: isLoadingZones } = useSWR<
ZoneWithGeometry[]
Expand All @@ -80,12 +90,15 @@ export default function MapPage() {
setLatestPositions(latestPositions)
}, [latestPositions])

const { startDate, endDate, trackedVesselIDs, setVesselExcursions } = useTrackModeOptionsStore(useShallow((state) => ({
startDate: state.startDate,
endDate: state.endDate,
trackedVesselIDs: state.trackedVesselIDs,
setVesselExcursions: state.setVesselExcursions,
})))
const { startDate, endDate, trackedVesselIDs, setVesselExcursions } =
useTrackModeOptionsStore(
useShallow((state) => ({
startDate: state.startDate,
endDate: state.endDate,
trackedVesselIDs: state.trackedVesselIDs,
setVesselExcursions: state.setVesselExcursions,
}))
)

useEffect(() => {
setZonesLoading(isLoadingZones)
Expand All @@ -95,28 +108,42 @@ export default function MapPage() {

useEffect(() => {
const resetExcursions = async () => {
setExcursionsLoading(true);
setExcursionsLoading(true)
for (const vesselID of trackedVesselIDs) {
const vesselExcursions = await getVesselExcursions(vesselID, startDate, endDate);
const vesselExcursions = await getVesselExcursions(
vesselID,
startDate,
endDate
)
for (const excursion of vesselExcursions.data) {
const segments = await getVesselSegments(vesselID, excursion.id);
excursion.segments = segments.data;
const segments = await getVesselSegments(vesselID, excursion.id)
excursion.segments = segments.data

const timeByMPAZone = await getVesselTimeByZone({
vesselId: vesselID,
category: ZoneCategory.AMP,
startAt: excursion.departure_at
? new Date(excursion.departure_at)
: undefined,
endAt: excursion.arrival_at
? new Date(excursion.arrival_at)
: undefined,
})
excursion.timeByMPAZone = timeByMPAZone
}
setVesselExcursions(vesselID, vesselExcursions.data);
setVesselExcursions(vesselID, vesselExcursions.data)
}
setExcursionsLoading(false);
setExcursionsLoading(false)
}
if (mapMode === "track") {
resetExcursions();
resetExcursions()
}
}, [startDate, endDate, mapMode, trackedVesselIDs])

return (
<>
<LeftPanel/>
<Map
zones={zones}
/>
<LeftPanel />
<Map zones={zones} />
<MapControls zoneLoading={isLoadingZones} />
</>
)
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/core/left-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default function LeftPanel() {
name="Dashboard"
wide={leftPanelOpened}
>
<ChartBarIcon className="size-8 stroke-[0.75] hover:text-primary text-color-panel" />
<ChartBarIcon className="size-8 stroke-[0.75] text-color-panel hover:text-primary" />
</NavigationLink>
</div>
<div className="flex flex-col gap-3 bg-color-3 p-5">
Expand All @@ -142,7 +142,7 @@ export default function LeftPanel() {
</>
)}
<div
className={`flex flex-col gap-3 overflow-auto bg-color-2 p-5 rounded-br-lg ${leftPanelOpened ? "cursor-default" : "cursor-pointer"}`}
className={`flex flex-col gap-3 overflow-auto rounded-br-lg bg-color-2 p-5 ${leftPanelOpened ? "cursor-default" : "cursor-pointer"}`}
onClick={() => !leftPanelOpened && setLeftPanelOpened(true)}
>
<TrackedVesselsPanel wideMode={leftPanelOpened} />
Expand Down
3 changes: 1 addition & 2 deletions frontend/components/core/map-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import { useShallow } from "zustand/react/shallow"
import { useMapStore } from "@/libs/stores/map-store"
import IconButton from "@/components/ui/icon-button"

import ZoneFilterModal from "./map/zone-filter-modal"
import { TrackModeOptionsModal } from "./map/track-mode-options-modal"

import ZoneFilterModal from "./map/zone-filter-modal"

interface MapControlsProps {
zoneLoading: boolean
Expand Down
16 changes: 8 additions & 8 deletions frontend/components/core/map/deck-gl-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ const getOpacityFromTimestamp = (timestamp: string) => {
return 102 // 40% opacity
}

export default function DeckGLMap({
zones,
onHover,
}: DeckGLMapProps) {
export default function DeckGLMap({ zones, onHover }: DeckGLMapProps) {
const {
trackedVesselIDs,
vesselsIDsHidden,
Expand Down Expand Up @@ -188,11 +185,15 @@ export default function DeckGLMap({
],
getAngle: (vp: VesselPosition) =>
vp.heading ? 365 - Math.round(vp.heading) : 0,
getIcon: (vp : VesselPosition) => {
getIcon: (vp: VesselPosition) => {
if (vp.heading) {
return vp.vessel.id === activePosition?.vessel.id ? "selectedWithHeading" : "withHeading"
return vp.vessel.id === activePosition?.vessel.id
? "selectedWithHeading"
: "withHeading"
} else {
return vp.vessel.id === activePosition?.vessel.id ? "selectedNoHeading" : "noHeading"
return vp.vessel.id === activePosition?.vessel.id
? "selectedNoHeading"
: "noHeading"
}
},
iconAtlas: "../../../img/vessel_atlas.png",
Expand Down Expand Up @@ -629,4 +630,3 @@ export default function DeckGLMap({
</DeckGL>
)
}

51 changes: 23 additions & 28 deletions frontend/components/core/map/main-map.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
"use client"

import { useCallback, useEffect, useMemo, useState } from "react"
import React, { useCallback, useEffect, useMemo, useState } from "react"
import type { PickingInfo } from "@deck.gl/core"
import { useMapStore } from "@/libs/stores/map-store"
import { useTrackModeOptionsStore } from "@/libs/stores"
import { useShallow } from "zustand/react/shallow"

import { VesselPosition, VesselPositions } from "@/types/vessel"
import { ZoneWithGeometry } from "@/types/zone"

import DeckGLMap from "./deck-gl-map"
import React from "react"
import { useShallow } from "zustand/react/shallow"
import { useTrackModeOptionsStore } from "@/libs/stores"
import { useMapStore } from "@/libs/stores/map-store"
import MapVesselTooltip from "@/components/ui/map-vessel-tooltip"
import MapZoneTooltip from "@/components/ui/map-zone-tooltip"

import DeckGLMap from "./deck-gl-map"
import { getPickObjectType } from "./utils"

type MainMapProps = {
Expand All @@ -27,7 +26,7 @@ function CoordonatesIndicator({ coordinates }: { coordinates: string }) {
)
}

const MemoizedDeckGLMap = React.memo(DeckGLMap);
const MemoizedDeckGLMap = React.memo(DeckGLMap)

export default function MainMap({ zones }: MainMapProps) {
const { activePosition, setActivePosition } = useMapStore(
Expand All @@ -38,15 +37,14 @@ export default function MainMap({ zones }: MainMapProps) {
}))
)

const {
addTrackedVessel,
trackedVesselIDs,
removeTrackedVessel,
} = useTrackModeOptionsStore(useShallow((state) => ({
addTrackedVessel: state.addTrackedVessel,
trackedVesselIDs: state.trackedVesselIDs,
removeTrackedVessel: state.removeTrackedVessel,
})))
const { addTrackedVessel, trackedVesselIDs, removeTrackedVessel } =
useTrackModeOptionsStore(
useShallow((state) => ({
addTrackedVessel: state.addTrackedVessel,
trackedVesselIDs: state.trackedVesselIDs,
removeTrackedVessel: state.removeTrackedVessel,
}))
)

const [tooltipPosition, setTooltipPosition] = useState<{
top: number
Expand Down Expand Up @@ -93,33 +91,30 @@ export default function MainMap({ zones }: MainMapProps) {
}

const hoverTooltip = useMemo(() => {
if (!hoverInfo) return;
if (!hoverInfo) return

const { object, x, y } = hoverInfo;
const { object, x, y } = hoverInfo
const objectType = getPickObjectType(hoverInfo)

let element: React.ReactNode = null;
let element: React.ReactNode = null

if (objectType === "vessel") {
const vesselInfo = object as VesselPosition
const vesselId = vesselInfo.vessel.id
if (activePosition?.vessel.id !== vesselId) {
element = <MapVesselTooltip vesselInfo={vesselInfo} top={y} left={x}/>
element = <MapVesselTooltip vesselInfo={vesselInfo} top={y} left={x} />
}
} else if (objectType === "zone") {
const zoneInfo = object as ZoneWithGeometry
element = <MapZoneTooltip zoneInfo={zoneInfo} top={y} left={x}/>
element = <MapZoneTooltip zoneInfo={zoneInfo} top={y} left={x} />
}

return element;
}, [hoverInfo, activePosition]);
return element
}, [hoverInfo, activePosition])

return (
<div className="relative size-full">
<MemoizedDeckGLMap
zones={zones}
onHover={onMapHover}
/>
<MemoizedDeckGLMap zones={zones} onHover={onMapHover} />
<CoordonatesIndicator coordinates={coordinates} />
{tooltipPosition && activePosition && (
<MapVesselTooltip
Expand Down
11 changes: 4 additions & 7 deletions frontend/components/core/map/position-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
"use client"

import {
AnimatePresence,
motion,
} from "framer-motion"
import { AnimatePresence, motion } from "framer-motion"

import PreviewCard from "@/components/core/map/preview-card"
import { useMapStore } from "@/libs/stores/map-store"
import PreviewCard from "@/components/core/map/preview-card"

export interface PositionPreviewTypes {}

const PositionPreview: React.FC<PositionPreviewTypes> = () => {
const activePosition = useMapStore((state) => state.activePosition);
const activePosition = useMapStore((state) => state.activePosition)

return (
<AnimatePresence>
Expand All @@ -22,7 +19,7 @@ const PositionPreview: React.FC<PositionPreviewTypes> = () => {
animate={{ y: 0, opacity: 1 }}
exit={{ y: 300, opacity: 0 }}
transition={{ type: "spring", stiffness: 66 }}
className="fixed bottom-0 right-0 mr-10 mb-2 w-auto -translate-x-1/2"
className="fixed bottom-0 right-0 mb-2 mr-10 w-auto -translate-x-1/2"
>
<PreviewCard vesselInfo={activePosition} />
</motion.div>
Expand Down
Loading

0 comments on commit 035c4d1

Please sign in to comment.