From d1a5052ea80798f7f7e1da60ca7dcc1e14b6c67b Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 28 Oct 2024 15:53:15 +0000 Subject: [PATCH 1/9] Add population map --- src/app/api/areas/[id]/route.ts | 49 +++---- src/app/api/tiles/areas/[z]/[x]/[y]/route.ts | 1 + src/app/components/map/index.tsx | 5 +- src/app/components/map/layers/area.tsx | 94 ++++++++++--- src/app/components/map/state/machine.ts | 129 +++++++++++++++++- src/app/components/map/state/types/actions.ts | 17 ++- 6 files changed, 240 insertions(+), 55 deletions(-) diff --git a/src/app/api/areas/[id]/route.ts b/src/app/api/areas/[id]/route.ts index 6a29af5..2e36c2f 100644 --- a/src/app/api/areas/[id]/route.ts +++ b/src/app/api/areas/[id]/route.ts @@ -4,20 +4,19 @@ import prisma from "@/lib/prisma"; export interface FetchAreaResponse { id: string; name: string; + totalpop: number; boundingBox: GeoJSON.Feature; - destinationAreas: GeoJSON.FeatureCollection< - GeoJSON.Geometry, - DestinationAreaProps - >; + destinationAreas: GeoJSON.FeatureCollection; destinationPorts: GeoJSON.FeatureCollection< GeoJSON.Geometry, DestinationPortProps >; } -interface DestinationAreaProps { +interface AreaProps { id: string; name: string; + totalpop: number; } interface DestinationPortProps { @@ -26,7 +25,7 @@ interface DestinationPortProps { name: string; } -type DestinationAreaRow = DestinationAreaProps & { +type DestinationAreaRow = AreaProps & { geometry: string; }; @@ -41,13 +40,14 @@ export async function GET( const { id } = params; try { - const area = await prisma.area.findUnique({ - where: { id }, - select: { - id: true, - name: true, - }, - }); + const areaRows = await prisma.$queryRaw` + SELECT id, name, (meta->>'totalpop')::float as totalpop + FROM "Area" + WHERE id = ${id} + LIMIT 1; + `; + + const area = areaRows[0]; if (!area) { return NextResponse.json({ error: "Area not found" }, { status: 404 }); @@ -59,7 +59,7 @@ export async function GET( SELECT ST_AsGeoJSON(ST_Extent(ST_Transform(limits, 4326))) as geojson FROM "Area" WHERE id = ${id} `, prisma.$queryRaw` - select "Area"."id", "Area"."name", ST_AsGeoJSON(ST_Transform(limits, 4326)) as geometry from "Flow" LEFT JOIN "Area" ON "Flow"."toAreaId" = "Area"."id" where "fromAreaId" = ${id}; + select "Area"."id", "Area"."name", (meta->>'totalpop')::float as totalpop, ST_AsGeoJSON(ST_Transform(limits, 4326)) as geometry from "Flow" LEFT JOIN "Area" ON "Flow"."toAreaId" = "Area"."id" where "fromAreaId" = ${id}; `, // TODO this is a temporary query to get the 10 closest ports to the area until there is maritime data in the database prisma.$queryRaw` @@ -87,17 +87,20 @@ export async function GET( const destinationAreas: GeoJSON.FeatureCollection< GeoJSON.Geometry, - DestinationAreaProps + AreaProps > = { type: "FeatureCollection", - features: destinationAreasRows.map(({ id, name, geometry }) => ({ - type: "Feature", - properties: { - id, - name, - }, - geometry: JSON.parse(geometry), - })), + features: destinationAreasRows.map( + ({ id, name, totalpop, geometry }) => ({ + type: "Feature", + properties: { + id, + name, + totalpop, + }, + geometry: JSON.parse(geometry), + }) + ), }; const destinationPorts: GeoJSON.FeatureCollection< diff --git a/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts b/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts index c25e021..2ba9291 100644 --- a/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts +++ b/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts @@ -35,6 +35,7 @@ export async function GET( id, ((ctid::text::point)[0]::bigint << 32) | (ctid::text::point)[1]::bigint AS id_int, name, + (meta->>'totalpop')::float AS totalpop, ST_AsMVTGeom( limits, ST_TileEnvelope(${zNum}, ${xNum}, ${yNum}) diff --git a/src/app/components/map/index.tsx b/src/app/components/map/index.tsx index f957be7..777a0db 100644 --- a/src/app/components/map/index.tsx +++ b/src/app/components/map/index.tsx @@ -47,9 +47,6 @@ function GlobeInner() { const mapPopup = MachineContext.useSelector( (state) => state.context.mapPopup ); - const areaSelected = MachineContext.useSelector( - (state) => !!state.context.currentArea - ); const handleMouseMove = useCallback((event: MapMouseEvent) => { actorRef.send({ @@ -113,8 +110,8 @@ function GlobeInner() { style={{ width: "100%", height: "100%", flex: 1 }} mapStyle={mapboxStyleUrl} > - + diff --git a/src/app/components/map/layers/area.tsx b/src/app/components/map/layers/area.tsx index 71cbd1f..175525f 100644 --- a/src/app/components/map/layers/area.tsx +++ b/src/app/components/map/layers/area.tsx @@ -6,20 +6,13 @@ const appUrl = process.env.NEXT_PUBLIC_APP_URL; // Colors const AREA_HIGHLIGHT_OUTLINE_COLOR = "rgba(28, 25, 23, 0.6)"; const AREA_DEFAULT_OUTLINE_COLOR = "rgba(28, 25, 23, 0.02)"; +const AREA_POPULATION_COLOR = "#C60E08"; +const AREA_OVERLAY_COLOR = "rgba(255,255,255,0.6)"; export const areaDefaultStyle: FillLayerSpecification["paint"] = { "fill-color": "transparent", }; -export const areaStyle: FillLayerSpecification["paint"] = { - "fill-color": [ - "case", - ["boolean", ["feature-state", "selected"], false], - "transparent", - "rgba(255,255,255,0.6)", - ], -}; - export const lineStyle: LineLayerSpecification["paint"] = { "line-color": [ "case", @@ -27,29 +20,96 @@ export const lineStyle: LineLayerSpecification["paint"] = { AREA_HIGHLIGHT_OUTLINE_COLOR, ["boolean", ["feature-state", "selected"], false], AREA_HIGHLIGHT_OUTLINE_COLOR, - ["boolean", ["feature-state", "destination"], false], - AREA_HIGHLIGHT_OUTLINE_COLOR, AREA_DEFAULT_OUTLINE_COLOR, ], "line-width": ["interpolate", ["exponential", 1.99], ["zoom"], 3, 1, 7, 3], }; -interface IAreaLayer { - areaSelected?: boolean; -} - -function AreaLayer({ areaSelected }: IAreaLayer) { +function AreaLayer() { return ( + + + + + + + { + "action:enterProductionAreaView": assign(({ context }) => { const { mapRef } = context; if (mapRef) { const m = mapRef.getMap(); m.setLayoutProperty("foodgroups-layer", "visibility", "visible"); + m.setLayoutProperty("selected-area-overlay", "visibility", "visible"); + } + + return {}; + }), + "action:exitProductionAreaView": assign(({ context }) => { + const { mapRef } = context; + + if (mapRef) { + const m = mapRef.getMap(); + m.setLayoutProperty("foodgroups-layer", "visibility", "visible"); + m.setLayoutProperty("selected-area-overlay", "visibility", "none"); } return {}; @@ -392,7 +411,11 @@ export const globeViewMachine = createMachine( if (!mapRef || !currentArea) return {}; const m = mapRef.getMap(); + + // Disable unwanted layers m.setLayoutProperty("foodgroups-layer", "visibility", "none"); + m.setLayoutProperty("area-population-fill", "visibility", "none"); + m.setLayoutProperty("area-population-outline", "visibility", "none"); const destinationAreaIds = currentArea.destinationAreas.features.map( ({ properties }) => properties.id @@ -440,12 +463,28 @@ export const globeViewMachine = createMachine( ({ properties }) => parseInt(properties.id_int) ); + // Enable desired layers + m.setLayoutProperty( + "destination-areas-outline", + "visibility", + "visible" + ); + return { destinationPortsIds }; }), "action:exitTransportationAreaView": assign(({ context }) => { const { mapRef, currentArea } = context; if (mapRef && currentArea) { + const m = mapRef.getMap(); + + // Disable desired layers + m.setLayoutProperty( + "destination-areas-outline", + "visibility", + "none" + ); + const destinationAreaIds = currentArea.destinationAreas.features.map( ({ properties }) => properties.id ); @@ -471,12 +510,88 @@ export const globeViewMachine = createMachine( destinationPortsIds: [], }; }), - "action:setImpactAreaView": assign(({ context }) => { - const { mapRef } = context; + "action:enterImpactAreaView": assign(({ context }) => { + const { mapRef, currentArea } = context; - if (mapRef) { + if (mapRef && currentArea) { const m = mapRef.getMap(); m.setLayoutProperty("foodgroups-layer", "visibility", "none"); + + const destinationAreaIds = currentArea.destinationAreas.features.map( + ({ properties }) => properties.id + ); + const destinationAreaBbox = bbox(currentArea.destinationAreas); + const combinedBboxes = combineBboxes([ + destinationAreaBbox, + bbox(currentArea.boundingBox), + ]); + + mapRef.fitBounds( + [ + [combinedBboxes[0], combinedBboxes[1]], + [combinedBboxes[2], combinedBboxes[3]], + ], + { + padding: { + top: 100, + left: 100, + bottom: 100, + right: 100, + }, + } + ); + + // Build population scale + const maxPopulation = Math.max( + currentArea.totalpop, + ...currentArea.destinationAreas.features.map( + ({ properties }) => properties.totalpop + ) + ); + + const minPopulation = Math.min( + currentArea.totalpop, + ...currentArea.destinationAreas.features.map( + ({ properties }) => properties.totalpop + ) + ); + m.setPaintProperty("area-population-fill", "fill-opacity", [ + "interpolate", + ["linear"], + ["get", "totalpop"], + minPopulation, + 0.3, + maxPopulation, + 0.9, + ]); + m.setLayoutProperty("area-population-fill", "visibility", "visible"); + + // highlight destination areas + const features = mapRef.querySourceFeatures("area-tiles", { + filter: ["in", "id", ...destinationAreaIds], + sourceLayer: "default", + }); + for (let i = 0, len = features.length; i < len; i++) { + mapRef.setFeatureState( + { + source: "area-tiles", + sourceLayer: "default", + id: features[i].id ?? "", + }, + { destination: true } + ); + } + } + + return {}; + }), + "action:exitImpactAreaView": assign(({ context }) => { + const { mapRef, currentArea } = context; + + if (mapRef && currentArea) { + const m = mapRef.getMap(); + m.setLayoutProperty("area-population-fill", "visibility", "none"); + m.setLayoutProperty("area-population-outline", "visibility", "none"); } return {}; diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts index a57a3fc..9e45bcf 100644 --- a/src/app/components/map/state/types/actions.ts +++ b/src/app/components/map/state/types/actions.ts @@ -42,7 +42,10 @@ interface ActionSetAreaMapView { type: "action:fitMapToCurrentAreaBounds"; } interface ActionEnterProductionAreaView { - type: "action:setProductionAreaView"; + type: "action:enterProductionAreaView"; +} +interface ActionExitProductionAreaView { + type: "action:exitProductionAreaView"; } interface ActionEnterTransportationAreaView { @@ -52,8 +55,12 @@ interface ActionExitTransportationAreaView { type: "action:exitTransportationAreaView"; } -interface ActionSetImpactAreaView { - type: "action:setImpactAreaView"; +interface ActionEnterImpactAreaView { + type: "action:enterImpactAreaView"; +} + +interface ActionExitImpactAreaView { + type: "action:exitImpactAreaView"; } interface ActionEnterWorldMapView { @@ -73,9 +80,11 @@ export type StateActions = | ActionSetCurrentAreaId | ActionSetCurrentArea | ActionEnterProductionAreaView + | ActionExitProductionAreaView | ActionEnterTransportationAreaView | ActionExitTransportationAreaView - | ActionSetImpactAreaView + | ActionEnterImpactAreaView + | ActionExitImpactAreaView | ActionSetAreaMapView | ActionEnterWorldMapView | ActionEnterAreaView; From e519e0d341b495eece05ba03629e5606a0b3fa1f Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 29 Oct 2024 11:08:06 +0000 Subject: [PATCH 2/9] Include production area in population map --- src/app/components/map/layers/area.tsx | 26 +++++-------------------- src/app/components/map/state/machine.ts | 2 -- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/app/components/map/layers/area.tsx b/src/app/components/map/layers/area.tsx index 175525f..44ab576 100644 --- a/src/app/components/map/layers/area.tsx +++ b/src/app/components/map/layers/area.tsx @@ -39,7 +39,11 @@ function AreaLayer() { paint={{ "fill-color": [ "case", - ["boolean", ["feature-state", "destination"], false], + [ + "any", + ["boolean", ["feature-state", "destination"], false], + ["boolean", ["feature-state", "selected"], false], + ], AREA_POPULATION_COLOR, "transparent", ], @@ -48,26 +52,6 @@ function AreaLayer() { visibility: "none", }} /> - properties.id @@ -591,7 +590,6 @@ export const globeViewMachine = createMachine( if (mapRef && currentArea) { const m = mapRef.getMap(); m.setLayoutProperty("area-population-fill", "visibility", "none"); - m.setLayoutProperty("area-population-outline", "visibility", "none"); } return {}; From eefb98e4c284923412cdb6bf7af87e33f0c41f85 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 29 Oct 2024 11:27:50 +0000 Subject: [PATCH 3/9] Move legend to separate folder --- src/app/components/map/{legend.tsx => legend/index.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/app/components/map/{legend.tsx => legend/index.tsx} (100%) diff --git a/src/app/components/map/legend.tsx b/src/app/components/map/legend/index.tsx similarity index 100% rename from src/app/components/map/legend.tsx rename to src/app/components/map/legend/index.tsx From 33517504fd0972c93e5a60e7721f3c956b3c2cbb Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 29 Oct 2024 12:27:39 +0000 Subject: [PATCH 4/9] Add population legend --- src/app/components/map/legend/categories.tsx | 47 ++++++++++++++ src/app/components/map/legend/index.tsx | 63 ++++++------------- src/app/components/map/legend/population.tsx | 31 +++++++++ src/app/components/map/state/machine.ts | 28 ++++++++- src/app/components/map/state/types/actions.ts | 3 + 5 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 src/app/components/map/legend/categories.tsx create mode 100644 src/app/components/map/legend/population.tsx diff --git a/src/app/components/map/legend/categories.tsx b/src/app/components/map/legend/categories.tsx new file mode 100644 index 0000000..489bfb9 --- /dev/null +++ b/src/app/components/map/legend/categories.tsx @@ -0,0 +1,47 @@ +interface ILegendItem { + color: string; + label: string; +} + +function LegendItem({ color, label }: ILegendItem) { + return ( +
+ + {label} +
+ ); +} + +export default function CategoriesLegend() { + return ( + <> +
+

Categories

+
+ + + + + + + + + + +
+
+
+

Calories production

+
+ Low + + + + + + High +
+
+ + ); +} diff --git a/src/app/components/map/legend/index.tsx b/src/app/components/map/legend/index.tsx index 3efcec2..138bcc8 100644 --- a/src/app/components/map/legend/index.tsx +++ b/src/app/components/map/legend/index.tsx @@ -1,22 +1,25 @@ import { ArrowDown, ArrowUp } from "@phosphor-icons/react"; import { useState } from "react"; +import CategoriesLegend from "./categories"; +import PopulationLegend from "./population"; +import { MachineContext } from "../state"; -interface ILegendItem { - color: string; - label: string; -} - -function LegendItem({ color, label }: ILegendItem) { - return ( -
- - {label} -
- ); -} +export type Legend = + | { + type: "category"; + } + | { + type: "population"; + range: [number, number]; + }; function Legend() { const [isExpanded, setIsExpanded] = useState(true); + const legend = MachineContext.useSelector((state) => state.context.legend); + + if (!legend) { + return null; + } return (
-
-

Categories

-
- - - - - - - - - - -
-
-
-

Calories production

-
- Low - - - - - - High -
-
+ {legend.type === "category" && } + {legend.type === "population" && ( + + )}
); diff --git a/src/app/components/map/legend/population.tsx b/src/app/components/map/legend/population.tsx new file mode 100644 index 0000000..e606fd7 --- /dev/null +++ b/src/app/components/map/legend/population.tsx @@ -0,0 +1,31 @@ +import { formatKeyIndicator } from "../../../../utils/numbers"; +interface PopulationLegendProps { + range: [number, number]; +} + +function PopulationLegend({ range }: PopulationLegendProps) { + const [min, max] = range; + + return ( +
+ Population +
+ + {formatKeyIndicator(min, "metric", 0)} + +
+ + {formatKeyIndicator(max, "metric", 0)} + +
+
+ ); +} + +export default PopulationLegend; diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 0aa1f8c..1883929 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -10,6 +10,7 @@ import { EItemType } from "@/types/components"; import { FetchAreaResponse } from "@/app/api/areas/[id]/route"; import { worldViewState } from ".."; import { combineBboxes } from "@/utils/geometries"; +import { Legend } from "../legend"; export enum EViewType { world = "world", @@ -25,6 +26,7 @@ export enum EAreaViewType { interface StateContext { viewType: EViewType | null; mapRef: MapRef | null; + legend: Legend | null; highlightedArea: GeoJSONFeature | null; currentAreaId: string | null; currentArea: FetchAreaResponse | null; @@ -52,6 +54,7 @@ export const globeViewMachine = createMachine( context: { viewType: EViewType.world, mapRef: null, + legend: { type: "category" }, highlightedArea: null, currentAreaId: null, currentArea: null, @@ -391,7 +394,9 @@ export const globeViewMachine = createMachine( m.setLayoutProperty("selected-area-overlay", "visibility", "visible"); } - return {}; + return { + legend: { type: "category" } as Legend, + }; }), "action:exitProductionAreaView": assign(({ context }) => { const { mapRef } = context; @@ -469,7 +474,7 @@ export const globeViewMachine = createMachine( "visible" ); - return { destinationPortsIds }; + return { destinationPortsIds, legend: null }; }), "action:exitTransportationAreaView": assign(({ context }) => { const { mapRef, currentArea } = context; @@ -580,6 +585,15 @@ export const globeViewMachine = createMachine( { destination: true } ); } + + const legend: Legend = { + type: "population", + range: [minPopulation, maxPopulation], + }; + + return { + legend, + }; } return {}; @@ -592,7 +606,13 @@ export const globeViewMachine = createMachine( m.setLayoutProperty("area-population-fill", "visibility", "none"); } - return {}; + const legend: Legend = { + type: "category", + }; + + return { + legend, + }; }), "action:fitMapToCurrentAreaBounds": assign(({ context }) => { const { mapRef, currentArea } = context; @@ -630,6 +650,7 @@ export const globeViewMachine = createMachine( if (mapRef) { const m = mapRef.getMap(); m.setLayoutProperty("top-ports", "visibility", "visible"); + m.setLayoutProperty("foodgroups-layer", "visibility", "visible"); if (currentAreaFeature?.id) { mapRef.setFeatureState( @@ -650,6 +671,7 @@ export const globeViewMachine = createMachine( currentAreaId: null, currentArea: null, currentAreaFeature: null, + legend: { type: "category" } as Legend, }; }), "action:enterAreaView": ({ context }) => { diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts index 9e45bcf..0b49f06 100644 --- a/src/app/components/map/state/types/actions.ts +++ b/src/app/components/map/state/types/actions.ts @@ -2,6 +2,7 @@ import { MapRef } from "react-map-gl"; import { GeoJSONFeature } from "mapbox-gl"; import { IMapPopup } from "@/app/components/map-popup"; import { EViewType } from "../machine"; +import { Legend } from "../../legend"; interface ActionParseUrl { type: "action:parseUrl"; @@ -57,10 +58,12 @@ interface ActionExitTransportationAreaView { interface ActionEnterImpactAreaView { type: "action:enterImpactAreaView"; + legend: Legend; } interface ActionExitImpactAreaView { type: "action:exitImpactAreaView"; + legend: Legend; } interface ActionEnterWorldMapView { From 44534c0c6ec54cadb154f48ff206adb876b76cb4 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 29 Oct 2024 12:31:47 +0000 Subject: [PATCH 5/9] Reduce size of port icon --- src/app/components/map/layers/ports.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/map/layers/ports.tsx b/src/app/components/map/layers/ports.tsx index 0a85796..1d54ae5 100644 --- a/src/app/components/map/layers/ports.tsx +++ b/src/app/components/map/layers/ports.tsx @@ -42,7 +42,7 @@ const PortsLayer = () => { source-layer="default" layout={{ "icon-image": "port-icon", - "icon-size": 0.5, + "icon-size": 0.4, }} filter={["in", "$id", ...destinationPortsIds]} /> From 257fe9d98b1dcd4ce10c2b5a5c247682d783d42f Mon Sep 17 00:00:00 2001 From: Vitor George Date: Wed, 30 Oct 2024 15:39:29 +0000 Subject: [PATCH 6/9] Refactor clearing destination areas --- src/app/components/map/state/machine.ts | 112 +++++++++--------- src/app/components/map/state/types/actions.ts | 5 + 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 1883929..526a0d2 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -33,6 +33,7 @@ interface StateContext { currentAreaFeature: GeoJSONFeature | null; currentAreaViewType: EAreaViewType | null; destinationPortsIds: number[]; + destinationAreasFeatureIds: number[]; mapPopup: IMapPopup | null; mapBounds: BBox | null; eventHandlers: { @@ -42,7 +43,7 @@ interface StateContext { export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggLYCGADl2gCusMJzTMA2gAYAuolC80sXG1xoW8kAA9EARgAcAZiIB2AGz7dUswE4ArHbOGATDYA0IAJ6I7zoobNfQwAWYJCnKRtnAF9oj1RMHDoSckoaOkZmdi4+AWEwITZpOSQQRWVVdU0dBEM7KSIbfTMTZ10bFydg-Q9vBAMG3RcpXWdXYOsnG1j49Cw8QhSKKlpCTNYOQQoEDbAyYs1ylTUNUpqAWkNDfSJ9ZzMpMYfnQyl9dy9Ec9siA11dEx2GxNe4GYIzEAJebJHj8MSCdi4FhQdbZWF5dgHUpHSqnUAXMy6OxEZzmO5SOqjRwfPrfaxEUI2N5Ah6AuwmCFQpKLbhkMDcBAAMzAbAAxgALJEoiDqMBEJHUNAAazlXIWxF5-KFIolUoQCrQou4uOKWIUSmOVTOehGfnq13ewTM3QBhl6iGcUhMDJMXoCvuBhiJnLm3OIvG4MAQ6G4EHoZrKFtx1S+HVMDl8vl0wXqQLs7oQ52cdl0DMcJmCYxMNhMrSGIcS6qIEajMbjEl0JXNFROKYQrUGLOLNlGjzuBfOBm9w0rL3+vruDehixbYGjaFj8ecXcTPat+JthhsRBaR5c12zBh6n0LLSI5bCXv+umdBiXYebkbX8MRyNRHFXDEilkQ4k17a1bwBRoHWzOp9AcIwJ2aYkbBaatuidd4onfJtNQFVYCB2dg9ileMQOxMD920RATBCX4X2LUYX18d4CzsUIGUMWs7DuDpHBfHDkjw9JCCItgSL-DsdxxcCD37bNGmzZ1UMiSx2QLf4zD8T0rkrKl7laQSeT5fC6DEiSUQkbdQL3PFqP7ex-BHRwc2zExs2cDTbAaat7CMIFmiJQwjI1EyRMI3gyDQCBBFFXF-wQLYUHM-ZyO7S07IuK4bjuZ57keV59HgtjK1+Iq-KkKRHB4jk4khUNcLCgiEEi6LYvipgNhyOEhBEMRJDS3cMr7YJq38f5gnsAEjHudiC30J17yql4Rw6LDpjqtUhKaszWpiuKTgS9F4REQoExkqiamcO573ch4LAmIqC1sMxGhHJ1mOuJ0Yk2hrtq1Zq2DIbgWFgRQyDYY1Ds67IkpS87KMyr5stufKngKt5ipvf4gUadjJpLQIRnZEKiGEwHgdB8HIY6rIOGO3rRHEMAEdsvsnhPdijxsJ0R0sExrz6IYvV+O72WBBbJdJ8mzKBkGwfIGnobp7q8lOwRgOkxGRseW6gzeWj7qJMwNLCY9XAHdp6ngmtpZ20TcE4CM4oSuHdlSrW2Ygy5rlRvLnkKmk9AsG4HD9MwnCMJwzDtgGzMd522CO3ITqZgbPeGiCXG9HmeYj5pugrE3sfsYli0qocHBCYLfsbf7TIdp3uBdmH6ZTxmzsGi6kf6LTFK41D3gW4sTA03KfU0lpTzGfRYjqlhorgTQtsIGzM7k759B89iw5eCkml0CcrEse8gyKuoRikJ1SdIZZwrX5NvdGc37kwoqnlGwXUz8AWLB4wki7-FnrXZcxAGYIlUMiB+sl7J0m0lfR8PMAQGE8jec47ljztFGAXVCRJFwgI-MJYUYpJRQIol7OSVgr63HYkMLeIRQikgLMEUsLR4LD0mmEZ07FSaATbNAy6XwnA-x3vUPeR5LATjCN6IEIxgQMVJE4XhX4gJSgET3OkwQTzWFrO0UIYQv6FmBFoz0WEQjVleBHWODdCLuzUeQ9e9ldEMjkd0fWmEg61ArEQK+rwqpFWeI8cEBDGpx1EntdqMDu59m+MSIMzhRqjQFmyGsbEDD3hmlpIMtZJrANmHXYyYTCJyyporKGVFolP1GD4l09wxiPArCwjSRJvRD1aCCBcdhrHhX1E3OK6iYlFV+KEL0AUFqPGBF5Z0jRQhYXeFpHMtVYhAA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggLYCGADl2gCusMJzTMA2gAYAuolC80sXG1xoW8kAA9EARgAcAZiIB2AGz7dUswE4ArHbOGATDYA0IAJ6I7zoobNfQwAWYJCnKRtnAF9oj1RMHDoSckoaOkZmdi4+AWEwITZpOSQQRWVVdU0dBEM7KSIbfTMTZ10bFydg-Q9vBAMG3RcpXWdXYOsnG1j49Cw8QhSKKlpCTNYOQQoEDbAyYs1ylTUNUpqAWkNDfSJ9ZzMpMYfnQyl9dy9Ec9siA11dEx2GxNe4GYIzEAJebJHj8MSCdi4FhQdbZWF5dgHUpHSqnUAXMy6OxEZzmO5SOqjRwfPrfaxEUI2N5Ah6AuwmCFQpKLbhkMDcBAAMzAbAAxgALJEoiDqMBEJHUNAAazlXIWxF5-KFIolUoQCrQou4uOKWIUSmOVTOehGfnq13ewTM3QBhl6iGcUhMDJMXoCvuBhiJnLm3OIvG4MAQ6G4EHoZrKFtx1S+HVMDl8vl0wXqQLs7oQ52cdl0DMcJmCYxMNhMrSGIcS6qIEajMbjEl0JXNFROKYQrUGLOLNlGjzuBfOBm9w0rL3+vruDehixbYGjaFj8ecXcTPat+JthhsRBaR5c12zBh6n0LLSI5bCXv+umdBiXYebkbX8MRyNRHFXDEilkQ4k17a1bwBRoHWzOp9AcIwJ2aYkbBaatuidd4onfJtNQFVYCB2dg9ileMQOxMD920RATBCX4X2LUYX18d4CzsUIGUMWs7DuDpHBfHDkjw9JCCItgSL-DsdxxcCD37bNGmzZ1UMiSx2QLf4zD8T0rkrKl7laQSeT5fC6DEiSUQkbdQL3PFqP7ex-BHRwc2zExs2cDTbAaat7CMIFmiJQwjI1EyRMI3gyDQCBBFFXF-wQLYUHM-ZyO7S07IuK4bjuZ57keV59HgtjK1+Iq-KkKRHB4jk4khUNcLCgiEEi6LYvipgNhyOEhBEMRJDS3cMr7YJq38f5gnsAEjHudiC30J17yql4Rw6LDpjqtUhKaszWpiuKTgS9F4REQoExkqiamcO573ch4LAmIqC1sMxGhHJ1mOuJ0Yk2hrtq1Zq2DIbgWFgRQyDYY1Ds67IkpS87KMyr5stufKngKt5ipvf4gUadjJpLQIRnZEKiGEwHgdB8HIY6rIOGO3rRHEMAEdsvsnhPdijxsJ0R0sExrz6IYvV+O72WBBbJdJ8mzKBkGwfIGnobp7q8lOwRgOkxGRseW6gzeWj7qJMwNLCY9XAHdp6ngmtpZ20TcE4CM4oSuHdlSrW2Ygy5rlRvLnkKmk9AsG4HD9MwnCMJwzDtgGzMd522CO3ITqZgbPeGiCXG9HmeYj5pugrE3sfsYli0qocHBCYLfsbf7TIdp3uBdmH6ZTxmzsGi6kf6LTFK41D3gW4sTA03KfU0lpTzGfRYjqlhorgTQtsIGzM7k759B89iw5eCkml0CcrEse8gyMIMHiGH7ZjrxZSGWcK1+Tb3RnN+5MKKp5RsF1M-AFiweKEiLv8WetdlzEAZgiVQyIn6yXsnSbSUhQivB5gCAwnkbznHcsedoowC6oSJIuMBH5hLCjFJKGBFEvZySsEg247EhhbxCKEUkBZgilhaPBYek0wjOnYqTQCbZYGXS+E4P+O96h7yPJYCcYRvRAhGMCBipInACK-EBKUwie50mCCeawtZ2ihDCD-QswJdGeiwiEasrwI6xwboRd2miqHr3sgYhkijuj60wkHWoFYiBINeFVIqzxHjgmIY1OOok9rtTgd3Ps3xiRBmcKNUaAs2Q1jYgYe8M0tJBlrJNUBN9wFk3toROWVNFZQyonEl+ox-EunuGMR4FZ2EaSJN6IerQQQLjsHY8K+om5xS0fEoqvxQhegCgtR4wIvLOkaKELC7wtI5lqrEIAA */ id: "globeView", types: { @@ -60,6 +61,7 @@ export const globeViewMachine = createMachine( currentArea: null, currentAreaFeature: null, currentAreaViewType: null, + destinationAreasFeatureIds: [], destinationPortsIds: [], mapPopup: null, mapBounds: null, @@ -109,7 +111,7 @@ export const globeViewMachine = createMachine( }), onDone: { target: "area:view:entering", - actions: ["action:setCurrentArea"], + actions: ["action:resetAreaViewMap", "action:setCurrentArea"], reenter: true, }, }, @@ -348,13 +350,14 @@ export const globeViewMachine = createMachine( mapPopup: null, }; }), - "action:setCurrentArea": assign(({ event, context }) => { - assertEvent(event, "xstate.done.actor.0.globeView.area:fetching"); - const { mapRef, currentAreaFeature } = context; + "action:resetAreaViewMap": assign(({ context }) => { + const { mapRef, currentAreaFeature, destinationAreasFeatureIds } = + context; + + if (!mapRef) return {}; - // Reset the previous selected area, if any if (currentAreaFeature?.id) { - mapRef?.setFeatureState( + mapRef.setFeatureState( { source: "area-tiles", sourceLayer: "default", @@ -364,6 +367,26 @@ export const globeViewMachine = createMachine( ); } + const m = mapRef.getMap(); + for (const destinationAreaFeatureId of destinationAreasFeatureIds) { + m.setFeatureState( + { + source: "area-tiles", + sourceLayer: "default", + id: destinationAreaFeatureId, + }, + { destination: false } + ); + } + + return { destinationAreasFeatureIds }; + }), + "action:setCurrentArea": assign(({ event, context }) => { + assertEvent(event, "xstate.done.actor.0.globeView.area:fetching"); + const { mapRef } = context; + + if (!mapRef) return {}; + const features = mapRef?.querySourceFeatures("area-tiles", { filter: ["==", "id", event.output.id], sourceLayer: "default", @@ -380,9 +403,32 @@ export const globeViewMachine = createMachine( ); } + const destinationAreaIds = event.output.destinationAreas.features.map( + ({ properties }) => properties.id + ); + + const destinationAreasFeatureIds = mapRef + .querySourceFeatures("area-tiles", { + filter: ["in", "id", ...destinationAreaIds], + sourceLayer: "default", + }) + .map((feature) => feature.id as number); // we are sure that the id is a number, because we are not using promoteId from MapboxGL + + for (const destinationAreaFeatureId of destinationAreasFeatureIds) { + mapRef.setFeatureState( + { + source: "area-tiles", + sourceLayer: "default", + id: destinationAreaFeatureId ?? "", + }, + { destination: true } + ); + } + return { currentArea: event.output, currentAreaFeature: feature, + destinationAreasFeatureIds, }; }), "action:enterProductionAreaView": assign(({ context }) => { @@ -447,22 +493,6 @@ export const globeViewMachine = createMachine( } ); - // highlight destination areas - const features = mapRef.querySourceFeatures("area-tiles", { - filter: ["in", "id", ...destinationAreaIds], - sourceLayer: "default", - }); - for (let i = 0, len = features.length; i < len; i++) { - mapRef.setFeatureState( - { - source: "area-tiles", - sourceLayer: "default", - id: features[i].id ?? "", - }, - { destination: true } - ); - } - const destinationPortsIds = currentArea.destinationPorts.features.map( ({ properties }) => parseInt(properties.id_int) ); @@ -488,26 +518,6 @@ export const globeViewMachine = createMachine( "visibility", "none" ); - - const destinationAreaIds = currentArea.destinationAreas.features.map( - ({ properties }) => properties.id - ); - - const features = mapRef.querySourceFeatures("area-tiles", { - filter: ["in", "id", ...destinationAreaIds], - sourceLayer: "default", - }); - - for (let i = 0, len = features.length; i < len; i++) { - mapRef.setFeatureState( - { - source: "area-tiles", - sourceLayer: "default", - id: features[i].id ?? "", - }, - { destination: false } - ); - } } return { @@ -570,22 +580,6 @@ export const globeViewMachine = createMachine( ]); m.setLayoutProperty("area-population-fill", "visibility", "visible"); - // highlight destination areas - const features = mapRef.querySourceFeatures("area-tiles", { - filter: ["in", "id", ...destinationAreaIds], - sourceLayer: "default", - }); - for (let i = 0, len = features.length; i < len; i++) { - mapRef.setFeatureState( - { - source: "area-tiles", - sourceLayer: "default", - id: features[i].id ?? "", - }, - { destination: true } - ); - } - const legend: Legend = { type: "population", range: [minPopulation, maxPopulation], diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts index 0b49f06..7a58618 100644 --- a/src/app/components/map/state/types/actions.ts +++ b/src/app/components/map/state/types/actions.ts @@ -70,6 +70,10 @@ interface ActionEnterWorldMapView { type: "action:enterWorldMapView"; } +interface ActionResetAreaViewMap { + type: "action:resetAreaViewMap"; +} + interface ActionEnterAreaView { type: "action:enterAreaView"; } @@ -90,4 +94,5 @@ export type StateActions = | ActionExitImpactAreaView | ActionSetAreaMapView | ActionEnterWorldMapView + | ActionResetAreaViewMap | ActionEnterAreaView; From 65fe9330d91b7150dc77937271029dd083aa70ab Mon Sep 17 00:00:00 2001 From: Vitor George Date: Wed, 30 Oct 2024 16:23:41 +0000 Subject: [PATCH 7/9] Update file name --- src/app/components/map/index.tsx | 4 ++-- src/app/components/map/layers/{area.tsx => areas.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/app/components/map/layers/{area.tsx => areas.tsx} (98%) diff --git a/src/app/components/map/index.tsx b/src/app/components/map/index.tsx index 777a0db..c953755 100644 --- a/src/app/components/map/index.tsx +++ b/src/app/components/map/index.tsx @@ -10,7 +10,7 @@ import { MachineContext, MachineProvider } from "./state"; import EdgeLayer from "./layers/edges"; import Legend from "./legend"; import FoodGroupsLayer from "./layers/foodgroups"; -import AreaLayer from "./layers/area"; +import AreaLayers from "./layers/areas"; import PortsLayer from "./layers/ports"; // Environment variables used in this component @@ -111,7 +111,7 @@ function GlobeInner() { mapStyle={mapboxStyleUrl} > - + diff --git a/src/app/components/map/layers/area.tsx b/src/app/components/map/layers/areas.tsx similarity index 98% rename from src/app/components/map/layers/area.tsx rename to src/app/components/map/layers/areas.tsx index 44ab576..6f0a200 100644 --- a/src/app/components/map/layers/area.tsx +++ b/src/app/components/map/layers/areas.tsx @@ -25,7 +25,7 @@ export const lineStyle: LineLayerSpecification["paint"] = { "line-width": ["interpolate", ["exponential", 1.99], ["zoom"], 3, 1, 7, 3], }; -function AreaLayer() { +function AreaLayers() { return ( Date: Wed, 30 Oct 2024 17:03:47 +0000 Subject: [PATCH 8/9] Add empty state --- src/app/(home)/area/[areaId]/page.tsx | 289 +++++++++++++----------- src/app/components/map/state/machine.ts | 36 ++- 2 files changed, 187 insertions(+), 138 deletions(-) diff --git a/src/app/(home)/area/[areaId]/page.tsx b/src/app/(home)/area/[areaId]/page.tsx index 8ff2baf..99b64dc 100644 --- a/src/app/(home)/area/[areaId]/page.tsx +++ b/src/app/(home)/area/[areaId]/page.tsx @@ -10,6 +10,8 @@ import { FoodGroup } from "@prisma/client"; import { Arc, ListBars, Sankey } from "@/app/components/charts"; import { formatKeyIndicator } from "@/utils/numbers"; import { EAreaViewType } from "@/app/components/map/state/machine"; +import { Button } from "@nextui-org/react"; +import Link from "next/link"; interface IFoodGroupAgg extends FoodGroup { sum: number; @@ -139,146 +141,165 @@ const AreaPage = async ({ className={`w-[600px] bg-white h-screen grid grid-rows-[max-content_1fr]`} > - - - - - - - - - - - - - - ({ - label: name, - value: sum, - }))} - /> + {totalFlow === 0 ? ( + +

There is no food produced in this area or no data is available.

+
- - - - - + + + + + + + + + + + + + - - ({ - id: toAreaId, - label: name, - type: EItemType["node"], - }) - ), - ], - links: [ - ...(outboundFlows as ExportFlow[]) - .slice(0, 10) - .map(({ toAreaId, value }) => ({ - source: area.id, - target: toAreaId, - value, - popupData: [ - { - label: "Volume", - value: formatKeyIndicator(value, "weight", 0), - }, - ], - })), - { - source: area.id, - target: "other", - value: (outboundFlows as ExportFlow[]) - .slice(10) - .reduce((sum, { value }) => sum + value, 0), - }, - ], - }} - /> - - - - - ({ + label: name, + value: sum, + }))} /> - -
- {meta[IndicatorColumn.PCT_RURAL] !== undefined && ( - - )} - {meta[IndicatorColumn.PCT_ELDERLY] !== undefined && ( - + + + + - )} - {meta[IndicatorColumn.PCT_F_CHILDBEARING] !== undefined && ( - - )} - {meta[IndicatorColumn.PCT_UNDER5] !== undefined && ( - + ({ + id: toAreaId, + label: name, + type: EItemType["node"], + }) + ), + ], + links: [ + ...(outboundFlows as ExportFlow[]) + .slice(0, 10) + .map(({ toAreaId, value }) => ({ + source: area.id, + target: toAreaId, + value, + popupData: [ + { + label: "Volume", + value: formatKeyIndicator(value, "weight", 0), + }, + ], + })), + { + source: area.id, + target: "other", + value: (outboundFlows as ExportFlow[]) + .slice(10) + .reduce((sum, { value }) => sum + value, 0), + }, + ], + }} + /> + + + + + - )} -
-
-
+ +
+ {meta[IndicatorColumn.PCT_RURAL] !== undefined && ( + + )} + {meta[IndicatorColumn.PCT_ELDERLY] !== undefined && ( + + )} + {meta[IndicatorColumn.PCT_F_CHILDBEARING] !== undefined && ( + + )} + {meta[IndicatorColumn.PCT_UNDER5] !== undefined && ( + + )} +
+ + + )}
); }; diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 526a0d2..2679afe 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -43,7 +43,7 @@ interface StateContext { export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggLYCGADl2gCusMJzTMA2gAYAuolC80sXG1xoW8kAA9EARgAcAZiIB2AGz7dUswE4ArHbOGATDYA0IAJ6I7zoobNfQwAWYJCnKRtnAF9oj1RMHDoSckoaOkZmdi4+AWEwITZpOSQQRWVVdU0dBEM7KSIbfTMTZ10bFydg-Q9vBAMG3RcpXWdXYOsnG1j49Cw8QhSKKlpCTNYOQQoEDbAyYs1ylTUNUpqAWkNDfSJ9ZzMpMYfnQyl9dy9Ec9siA11dEx2GxNe4GYIzEAJebJHj8MSCdi4FhQdbZWF5dgHUpHSqnUAXMy6OxEZzmO5SOqjRwfPrfaxEUI2N5Ah6AuwmCFQpKLbhkMDcBAAMzAbAAxgALJEoiDqMBEJHUNAAazlXIWxF5-KFIolUoQCrQou4uOKWIUSmOVTOehGfnq13ewTM3QBhl6iGcUhMDJMXoCvuBhiJnLm3OIvG4MAQ6G4EHoZrKFtx1S+HVMDl8vl0wXqQLs7oQ52cdl0DMcJmCYxMNhMrSGIcS6qIEajMbjEl0JXNFROKYQrUGLOLNlGjzuBfOBm9w0rL3+vruDehixbYGjaFj8ecXcTPat+JthhsRBaR5c12zBh6n0LLSI5bCXv+umdBiXYebkbX8MRyNRHFXDEilkQ4k17a1bwBRoHWzOp9AcIwJ2aYkbBaatuidd4onfJtNQFVYCB2dg9ileMQOxMD920RATBCX4X2LUYX18d4CzsUIGUMWs7DuDpHBfHDkjw9JCCItgSL-DsdxxcCD37bNGmzZ1UMiSx2QLf4zD8T0rkrKl7laQSeT5fC6DEiSUQkbdQL3PFqP7ex-BHRwc2zExs2cDTbAaat7CMIFmiJQwjI1EyRMI3gyDQCBBFFXF-wQLYUHM-ZyO7S07IuK4bjuZ57keV59HgtjK1+Iq-KkKRHB4jk4khUNcLCgiEEi6LYvipgNhyOEhBEMRJDS3cMr7YJq38f5gnsAEjHudiC30J17yql4Rw6LDpjqtUhKaszWpiuKTgS9F4REQoExkqiamcO573ch4LAmIqC1sMxGhHJ1mOuJ0Yk2hrtq1Zq2DIbgWFgRQyDYY1Ds67IkpS87KMyr5stufKngKt5ipvf4gUadjJpLQIRnZEKiGEwHgdB8HIY6rIOGO3rRHEMAEdsvsnhPdijxsJ0R0sExrz6IYvV+O72WBBbJdJ8mzKBkGwfIGnobp7q8lOwRgOkxGRseW6gzeWj7qJMwNLCY9XAHdp6ngmtpZ20TcE4CM4oSuHdlSrW2Ygy5rlRvLnkKmk9AsG4HD9MwnCMJwzDtgGzMd522CO3ITqZgbPeGiCXG9HmeYj5pugrE3sfsYli0qocHBCYLfsbf7TIdp3uBdmH6ZTxmzsGi6kf6LTFK41D3gW4sTA03KfU0lpTzGfRYjqlhorgTQtsIGzM7k759B89iw5eCkml0CcrEse8gyMIMHiGH7ZjrxZSGWcK1+Tb3RnN+5MKKp5RsF1M-AFiweKEiLv8WetdlzEAZgiVQyIn6yXsnSbSUhQivB5gCAwnkbznHcsedoowC6oSJIuMBH5hLCjFJKGBFEvZySsEg247EhhbxCKEUkBZgilhaPBYek0wjOnYqTQCbZYGXS+E4P+O96h7yPJYCcYRvRAhGMCBipInACK-EBKUwie50mCCeawtZ2ihDCD-QswJdGeiwiEasrwI6xwboRd2miqHr3sgYhkijuj60wkHWoFYiBINeFVIqzxHjgmIY1OOok9rtTgd3Ps3xiRBmcKNUaAs2Q1jYgYe8M0tJBlrJNUBN9wFk3toROWVNFZQyonEl+ox-EunuGMR4FZ2EaSJN6IerQQQLjsHY8K+om5xS0fEoqvxQhegCgtR4wIvLOkaKELC7wtI5lqrEIAA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggLYCGADl2gCusMJzTMA2gAYAuolC80sXG1xoW8kAA9EARgAcAZiIB2AGz7dUswE4ArHbOGATDYA0IAJ6I7zoobNfQwAWYJCnKRtnAF9oj1RMHDoSckoaOkZmdi4+AWEwITZpOSQQRWVVdU0dBEM7KSIbfTMTZ10bFydg-Q9vBAMG3RcpXWdXYOsnG1j49Cw8QhSKKlpCTNYOQQoEDbAyYs1ylTUNUpqAWkNDfSJ9ZzMpMYfnQyl9dy9Ec9siA11dEx2GxNe4GYIzEAJebJHj8MSCdi4FhQdbZWF5dgHUpHSqnUAXMy6OxEZzmO5SOqjRwfPrfaxEUI2N5Ah6AuwmCFQpKLbhkMDcBAAMzAbAAxgALJEoiDqMBEJHUNAAazlXIWxF5-KFIolUoQCrQou4uOKWIUSmOVTOehGfnq13ewTM3QBhl6iGcUhMDJMXoCvuBhiJnLm3OIvG4MAQ6G4EHoZrKFtx1S+HVMDl8vl0wXqQLs7oQ52cdl0DMcJmCYxMNhMrSGIcS6qIEajMbjEl0JXNFROKYQrUGLOLNlGjzuBfOBm9w0rL3+vruDehixbYGjaFj8ecXcTPat+JthhsRBaR5c12zBh6n0LLSI5bCXv+umdBiXYebkbX8MRyNRHFXDEilkQ4k17a1bwBRoHWzOp9AcIwJ2aYkbBaatuidd4onfJtNQFVYCB2dg9ileMQOxMD920RAjAaEwAQMAIATMQIzALWwGgmCx7FtEcbHBOJIVDXC+XwugiLYEi-w7HccXAg8EH0elujsINnDuQlRhMAsrEcfxzGCCsiSPbocOSPD0kICSpJRCRt1Avc8Wo-t7H8EdHBzbN6MrHSLD8WxrkMQEvJcGJBLVczRMswjdjIUiJEMWTKKcmp9EBW4nCBJl7maIYCzGR5TAveilMeLozJ5KKCIQXgyDQCBBFFXF-wQLYUGs-ZyO7S0Uq+OpjE9VwgVCdkRn4nTHm9JoWlCIYgSGQwKo1KrxNq+rGuapgNhyOEhBEMRJC63cer7J1gmgnM7CMEIOnCHSrj8V5XmLFxgiJHMlqICzqrWhqmpOFr0XhERCgTOSqJqIMbikdk-gpBxs0CHT2mPbMmRrZwnWuMLZkbSKtWqtgyG4FhYEUMg2GNAGtuyNqOrB5K+0uVSSUeex+JzX12mCXyc3TMZLCG0lHk+77xKJkmyfISnNqyDggb20RxDABnHL7F5jweSaTGeytglQnTq0195IngqQpCdOxRZWqyJdJ8mZepuWdryEHBGApK1Ygh1flQhj3nuCx8xvdpiyISJ9f0JTLH+D7wuE-GxKs3BOAjJqWrp2LVZOiDmYGtnhs5saeZvArOPg30wjsEb+OtgnxJTtO2EB3JgaVw7PZzhTQm9BamiJdSgX9fLZtuEcWOr8YRjrpPCMb7h05p+XW8V0GjvB3qEHUm4rBaDTqRacbS5cUtnoti3gUJIKZ+ihAWDQAAxdACFgDPtiz9fGdz-rWfGK5WhMDHdiUdfjV1eFhI8pIwqCXvhAOAmgIqEAcl3Zy3wlLpkuo8J6-cJxWEsPeIM-Ecp3A6AJXGy5iCkGWNFZByZc6jGPE8TCUcmGAInFEUwzR4K5QrJpfQn0FYIlUMiWh8lUHPFZqEV4-F-bOAnPRVGI51LOj9ldHGQk8aVS1MKMUkoREUS9gpKwFtbjVyGEpEIoRSQFjeieQBV09b8QCDmMhGiKGflbBuCAoiIZfCcH4QEmCXgUhwTec4YRvRAjGuPKsThPqAR-MIqAPjN50nOg8NC3MpHXlpMCc6noIGGQ6JMG+1VYpShSX2WspYJjcyMFYTCNJEBBXOhbV4MMo7PEeK4xBy165WV+htMRG8mbVhPI6f4ZgxhozeBNExF4Lb6FCKVUIpTxbE3ttLKmVERn0PuAyYs1hXhOBYsFXmxhbCBCJPYHWR4OTx00X02e+pU4LzYJU+hhUsy2nqdWB4TSt5EjopWCwzxAjmytg89xYsrL3yfmgF+HyFLnDGdxCY1xzaekiACli50JiAmQoCQI1ZYixCAA */ id: "globeView", types: { @@ -149,6 +149,11 @@ export const globeViewMachine = createMachine( "area:view:entering": { always: [ + { + target: "area:view:production", + guard: "guard:areaHasNoFlows", + reenter: true, + }, { target: "area:view:production", guard: "guard:isAreaProductionView", @@ -239,6 +244,17 @@ export const globeViewMachine = createMachine( exit: "action:exitImpactAreaView", }, + + "area:view:noFlows": { + on: { + "event:url:enter": { + target: "page:load", + reenter: true, + }, + }, + + entry: ["action:fitMapToCurrentAreaBounds"], + }, }, initial: "page:mounting", @@ -431,6 +447,18 @@ export const globeViewMachine = createMachine( destinationAreasFeatureIds, }; }), + "action:displayFoodGroupsLayer": assign(({ context }) => { + const { mapRef } = context; + + if (mapRef) { + const m = mapRef.getMap(); + m.setLayoutProperty("foodgroups-layer", "visibility", "visible"); + } + + return { + legend: { type: "category" } as Legend, + }; + }), "action:enterProductionAreaView": assign(({ context }) => { const { mapRef } = context; @@ -531,9 +559,6 @@ export const globeViewMachine = createMachine( const m = mapRef.getMap(); m.setLayoutProperty("foodgroups-layer", "visibility", "none"); - const destinationAreaIds = currentArea.destinationAreas.features.map( - ({ properties }) => properties.id - ); const destinationAreaBbox = bbox(currentArea.destinationAreas); const combinedBboxes = combineBboxes([ destinationAreaBbox, @@ -681,6 +706,9 @@ export const globeViewMachine = createMachine( "guard:isWorldView": ({ context }) => { return context.viewType === EViewType.world; }, + "guard:areaHasNoFlows": ({ context }) => { + return context.currentArea?.destinationAreas.features.length === 0; + }, "guard:isAreaProductionView": () => { return window.location.hash === `#${EAreaViewType.production}`; }, From 69b4e29d0d825a7a23e8bd9758dd2e762583c29b Mon Sep 17 00:00:00 2001 From: Vitor George Date: Wed, 30 Oct 2024 17:06:54 +0000 Subject: [PATCH 9/9] Lint fixes --- src/app/components/map/state/machine.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 2679afe..c0ccda6 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -447,18 +447,6 @@ export const globeViewMachine = createMachine( destinationAreasFeatureIds, }; }), - "action:displayFoodGroupsLayer": assign(({ context }) => { - const { mapRef } = context; - - if (mapRef) { - const m = mapRef.getMap(); - m.setLayoutProperty("foodgroups-layer", "visibility", "visible"); - } - - return { - legend: { type: "category" } as Legend, - }; - }), "action:enterProductionAreaView": assign(({ context }) => { const { mapRef } = context; @@ -495,9 +483,6 @@ export const globeViewMachine = createMachine( m.setLayoutProperty("foodgroups-layer", "visibility", "none"); m.setLayoutProperty("area-population-fill", "visibility", "none"); - const destinationAreaIds = currentArea.destinationAreas.features.map( - ({ properties }) => properties.id - ); const destinationAreaBbox = bbox(currentArea.destinationAreas); const destinationPortsBbox = bbox(currentArea.destinationPorts); const combinedBboxes = combineBboxes([