Skip to content

Commit

Permalink
Merge pull request #342 from dataforgoodfr/enhancement/frontend/left-…
Browse files Browse the repository at this point in the history
…bar-for-tracking

Enhancement/frontend/left bar for tracking
  • Loading branch information
HenriChabert authored Dec 10, 2024
2 parents 2766546 + b4dfb2d commit 2e5e5f1
Show file tree
Hide file tree
Showing 42 changed files with 1,912 additions and 598 deletions.
2 changes: 2 additions & 0 deletions docker/frontend/dev.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ WORKDIR ${APP_DIR}

# Install dependencies based on the preferred package manager
COPY frontend/package.json frontend/yarn.lock* frontend/package-lock.json* frontend/pnpm-lock.yaml* ./
RUN mkdir -p ./app

RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
Expand Down
3 changes: 2 additions & 1 deletion frontend/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"@next/next/no-page-custom-font": "off",
"react/jsx-key": "off",
"tailwindcss/no-custom-classname": "off",
"tailwindcss/classnames-order": "off"
"tailwindcss/classnames-order": "off",
"react-hooks/exhaustive-deps": "off"
},
"settings": {
"tailwindcss": {
Expand Down
2 changes: 0 additions & 2 deletions frontend/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export default function DashboardPage() {
isLoading,
} = useDashboardData(startAt, endAt)

console.log("totalVesselsTracked", totalVesselsTracked)

return (
<section className="flex h-full items-center justify-center overflow-auto bg-color-3 p-2 2xl:p-4">
<div className="flex size-full max-w-screen-xl flex-col gap-2 2xl:gap-4">
Expand Down
5 changes: 2 additions & 3 deletions frontend/app/details/amp/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import { useMemo, useState } from "react"
import { getZoneDetails } from "@/services/backend-rest-client"
import { swrOptions } from "@/services/swr"
import { getCountryNameFromIso3 } from "@/utils/vessel.utils"
import useSWR from "swr"

import { convertDurationInHours, getDateRange } from "@/libs/dateUtils"
import { convertDurationInHoursStr, getDateRange } from "@/libs/dateUtils"
import DetailsContainer from "@/components/details/details-container"

export default function AmpDetailsPage({ params }: { params: { id: string } }) {
Expand Down Expand Up @@ -42,7 +41,7 @@ export default function AmpDetailsPage({ params }: { params: { id: string } }) {
id: vessel.id.toString(),
title: `${vessel.ship_name} - ${getCountryNameFromIso3(vessel.country_iso3)}`,
description: `IMO: ${vessel.imo} - MMSI: ${vessel.mmsi} - Type: ${vessel.type} - Length: ${vessel.length} m`,
value: `${convertDurationInHours(zone_visiting_time_by_vessel)}h`,
value: `${convertDurationInHoursStr(zone_visiting_time_by_vessel)}h`,
type: "vessels",
}
}),
Expand Down
9 changes: 3 additions & 6 deletions frontend/app/details/vessel/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"use client"

import { useMemo, useState } from "react"
import {
getTimeByZone,
getTopZonesVisited,
} from "@/services/backend-rest-client"
import { getTimeByZone } from "@/services/backend-rest-client"
import { getCountryNameFromIso3 } from "@/utils/vessel.utils"
import useSWR from "swr"

import { convertDurationInHours, getDateRange } from "@/libs/dateUtils"
import { convertDurationInHoursStr, getDateRange } from "@/libs/dateUtils"
import DetailsContainer from "@/components/details/details-container"

export default function VesselDetailsPage({
Expand Down Expand Up @@ -51,7 +48,7 @@ export default function VesselDetailsPage({
id: zone.id.toString(),
title: zone.name,
description: zone.sub_category,
value: `${convertDurationInHours(vessel_visiting_time_by_zone)}h`,
value: `${convertDurationInHoursStr(vessel_visiting_time_by_zone)}h`,
type: "zones",
}
}),
Expand Down
1 change: 1 addition & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "@/styles/globals.css"
import "react-day-picker/dist/style.css"

import { Metadata } from "next"

Expand Down
8 changes: 1 addition & 7 deletions frontend/app/map/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import "@/styles/globals.css"
import { Metadata } from "next"

import { siteConfig } from "@/config/site"
import { MapStoreProvider } from "@/components/providers/map-store-provider"
import { VesselsStoreProvider } from "@/components/providers/vessels-store-provider"

export const metadata: Metadata = {
title: {
Expand All @@ -30,11 +28,7 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) {
return (
<section className="relative flex h-screen w-full flex-row">
<MapStoreProvider>
<VesselsStoreProvider>
{children}
</VesselsStoreProvider>
</MapStoreProvider>
{children}
</section>
)
}
72 changes: 63 additions & 9 deletions frontend/app/map/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
"use client"

import { useEffect, useState } from "react"
import { useEffect, useMemo, useState } from "react"
import {
getVesselExcursions,
getVessels,
getVesselSegments,
getVesselsLatestPositions,
} from "@/services/backend-rest-client"
import useSWR from "swr"
import { useShallow } from 'zustand/react/shallow'

import { Vessel, VesselPosition } from "@/types/vessel"
import { ZoneWithGeometry } from "@/types/zone"
import LeftPanel from "@/components/core/left-panel"
import MapControls from "@/components/core/map-controls"
import Map from "@/components/core/map/main-map"
import PositionPreview from "@/components/core/map/position-preview"
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 @@ -18,6 +29,19 @@ 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 { mode: mapMode } = useMapStore(useShallow((state) => ({
mode: state.mode
})))

const { data: vessels = [], isLoading: isLoadingVessels } = useSWR<Vessel[]>(
"/api/vessels",
fetcher,
Expand All @@ -28,6 +52,12 @@ export default function MapPage() {
}
)

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

const { data: zones = [], isLoading: isLoadingZones } = useSWR<
ZoneWithGeometry[]
>("/api/zones", fetcher, {
Expand All @@ -46,21 +76,45 @@ export default function MapPage() {
refreshInterval: 900000, // 15 minutes in milliseconds
})

const isLoading = isLoadingVessels || isLoadingPositions || isLoadingZones
const { startDate, endDate, trackedVesselIDs, setVesselExcursions } = useTrackModeOptionsStore(useShallow((state) => ({
startDate: state.startDate,
endDate: state.endDate,
trackedVesselIDs: state.trackedVesselIDs,
setVesselExcursions: state.setVesselExcursions,
})))

useEffect(() => {
setZonesLoading(isLoadingZones)
setPositionsLoading(isLoadingPositions)
setVesselsLoading(isLoadingVessels)
}, [isLoadingZones, isLoadingPositions, isLoadingVessels])

useEffect(() => {
const resetExcursions = async () => {
setExcursionsLoading(true);
for (const vesselID of trackedVesselIDs) {
const vesselExcursions = await getVesselExcursions(vesselID, startDate, endDate);
for (const excursion of vesselExcursions.data) {
const segments = await getVesselSegments(vesselID, excursion.id);
excursion.segments = segments.data;
}
setVesselExcursions(vesselID, vesselExcursions.data);
}
setExcursionsLoading(false);
}
if (mapMode === "track") {
resetExcursions();
}
}, [startDate, endDate, mapMode, trackedVesselIDs])

return (
<>
<LeftPanel vessels={vessels} isLoading={isLoadingVessels} />
<LeftPanel/>
<Map
vesselsPositions={latestPositions}
zones={zones}
isLoading={{
vessels: isLoadingVessels,
positions: isLoadingPositions,
zones: isLoadingZones,
}}
/>
<MapControls zoneLoading={isLoading} />
<MapControls zoneLoading={isLoadingZones} />
<PositionPreview />
</>
)
Expand Down
36 changes: 28 additions & 8 deletions frontend/components/core/command/vessel-finder.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import { useShallow } from "zustand/react/shallow"
import { useState } from "react"
import { getVesselFirstExcursionSegments } from "@/services/backend-rest-client"
import { FlyToInterpolator } from "deck.gl"
Expand All @@ -14,8 +15,9 @@ import {
CommandList,
CommandSeparator,
} from "@/components/ui/command"
import { useMapStore } from "@/components/providers/map-store-provider"
import { useVesselsStore } from "@/components/providers/vessels-store-provider"
import { useMapStore } from "@/libs/stores/map-store"
import { useTrackModeOptionsStore } from "@/libs/stores/track-mode-options-store"
import { useVesselsStore } from "@/libs/stores/vessels-store"

type Props = {
wideMode: boolean
Expand All @@ -26,21 +28,39 @@ const SEPARATOR = "___"
export function VesselFinderDemo({ wideMode }: Props) {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState<string>("")

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

const {
addTrackedVessel,
trackedVesselIDs,
setActivePosition,
viewState,
latestPositions,
setViewState,
} = useMapStore((state) => state)
const { vessels: allVessels } = useVesselsStore((state) => state)
const { latestPositions } = useMapStore((state) => state)
} = useMapStore(
useShallow((state) => ({
viewState: state.viewState,
latestPositions: state.latestPositions,
setActivePosition: state.setActivePosition,
setViewState: state.setViewState,
}))
)

const { vessels: allVessels } = useVesselsStore(
useShallow((state) => ({
vessels: state.vessels,
}))
)

const onSelectVessel = async (vesselIdentifier: string) => {
const vesselId = parseInt(vesselIdentifier.split(SEPARATOR)[3])
const response = await getVesselFirstExcursionSegments(vesselId)
if (vesselId && !trackedVesselIDs.includes(vesselId)) {
addTrackedVessel(vesselId, response)
addTrackedVessel(vesselId)
}
if (vesselId) {
const selectedVesselLatestPosition = latestPositions.find(
Expand Down
Loading

0 comments on commit 2e5e5f1

Please sign in to comment.