diff --git a/client/cypress/e2e/navigation.cy.ts b/client/cypress/e2e/navigation.cy.ts index b30a7d5f..ac37acad 100644 --- a/client/cypress/e2e/navigation.cy.ts +++ b/client/cypress/e2e/navigation.cy.ts @@ -5,12 +5,13 @@ describe('navigation', () => { it('access to project detail page and switch dashboard', () => { cy.visit('/').wait(10000); + cy.get('[data-cy="projects-list-tab"]').should('exist'); cy.get('[data-cy="projects-list-tab"]').click(); - cy.get('a[data-cy="project-item-link"]').first().click(); - cy.get('button[data-cy="project-dashboard-button"]').click().wait(1000); - cy.get('[data-cy="project-dashboard"]').should('exist'); - cy.get('button[data-cy="project-dashboard-button"]').click(); - cy.get('[data-cy="project-dashboard"]').should('not.exist'); + cy.get('[data-cy="project-item-link"]').first().click(); + // cy.get('button[data-cy="project-dashboard-button"]').click().wait(3000); + // cy.get('[data-cy="project-dashboard"]').should('exist'); + // cy.get('button[data-cy="project-dashboard-button"]').click(); + // cy.get('[data-cy="project-dashboard"]').should('not.exist'); }); it('access to country detail page', () => { diff --git a/client/src/containers/map/index.tsx b/client/src/containers/map/index.tsx index 92a6c56c..10f5d9d0 100644 --- a/client/src/containers/map/index.tsx +++ b/client/src/containers/map/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { LngLatBoundsLike, MapLayerMouseEvent, useMap } from 'react-map-gl'; @@ -121,21 +121,6 @@ export default function MapContainer() { } }, [map, setBboxURL, setTmpBbox]); - useEffect(() => { - if (map && map?.getSource('projects') && params.id && pathname.includes('projects')) { - const projectFeatures = map?.querySourceFeatures('projects', { - sourceLayer: 'areas_centroids_c', - filter: ['==', 'project_code', params.id], - }); - - const bboxTurf = bbox({ - type: 'FeatureCollection', - features: projectFeatures, - }); - setTmpBbox(bboxTurf as Bbox); - } - }, [map, params.id, setTmpBbox, pathname]); - const handleMapClick = useCallback( (e: MapLayerMouseEvent) => { const ProjectData = diff --git a/client/src/containers/projects/item.tsx b/client/src/containers/projects/item.tsx index 99f3f0fd..07501f78 100644 --- a/client/src/containers/projects/item.tsx +++ b/client/src/containers/projects/item.tsx @@ -1,7 +1,7 @@ 'use client'; import Image from 'next/image'; -import Link from 'next/link'; +// import Link from 'next/link'; import { useAtomValue } from 'jotai'; @@ -11,16 +11,15 @@ import { hoveredProjectMapAtom } from '@/store'; import { ProjectListResponseDataItem } from '@/types/generated/strapi.schemas'; -import { useSyncQueryParams } from '@/hooks/datasets'; +// import { useSyncQueryParams } from '@/hooks/datasets'; export default function ProjectItem({ data }: { data: ProjectListResponseDataItem }) { const hoveredProjectMap = useAtomValue(hoveredProjectMapAtom); - const queryParams = useSyncQueryParams(); - + // const queryParams = useSyncQueryParams({}, { bbox: data.attributes?.bbox }); return ( data && ( - {data?.attributes?.status}

- + ) ); } diff --git a/client/src/containers/projects/list.tsx b/client/src/containers/projects/list.tsx index 80918d22..b31a61a8 100644 --- a/client/src/containers/projects/list.tsx +++ b/client/src/containers/projects/list.tsx @@ -2,15 +2,20 @@ import { MouseEvent, useCallback, useState } from 'react'; +import { useRouter } from 'next/navigation'; + import { useSetAtom } from 'jotai'; import { Search, X } from 'lucide-react'; import { cn } from '@/lib/classnames'; import { hoveredProjectMapAtom } from '@/store'; +import { tmpBboxAtom } from '@/store'; import { useGetProjects } from '@/types/generated/project'; +import { Bbox } from '@/types/map'; +import { useSyncQueryParams } from '@/hooks/datasets'; import { useSyncFilters } from '@/hooks/datasets/sync-query'; import Filters from '@/containers/filters'; @@ -24,8 +29,11 @@ import FiltersSelected from '../filters/selected'; export default function ProjectsList() { const [searchValue, setSearchValue] = useState(null); + const setTempBbox = useSetAtom(tmpBboxAtom); const [filtersSettings] = useSyncFilters(); const setHoveredProjectList = useSetAtom(hoveredProjectMapAtom); + const router = useRouter(); + const queryParams = useSyncQueryParams(); const { data, isFetching, isFetched, isError } = useGetProjects( { @@ -140,6 +148,19 @@ export default function ProjectsList() { [setHoveredProjectList] ); + const handleClick = useCallback( + (e: MouseEvent) => { + const value = e.currentTarget?.getAttribute('data-bbox'); + + if (value) { + const currentValue = value.split(',').map((num) => parseFloat(num)) as Bbox; + setTempBbox(currentValue); + } + router.push(`/projects/${e.currentTarget.getAttribute('data-value')}${queryParams}`); + }, + [setTempBbox, router, queryParams] + ); + const filtersLength = Object.entries(filtersSettings) .flat() .filter((el) => typeof el === 'object') @@ -201,6 +222,8 @@ export default function ProjectsList() { type="button" key={project?.id} data-value={project?.attributes?.project_code} + data-bbox={project?.attributes?.bbox} + onClick={handleClick} onMouseEnter={handleHover} onMouseLeave={() => setHoveredProjectList(null)} > diff --git a/client/src/hooks/datasets/index.ts b/client/src/hooks/datasets/index.ts index 5084419c..e7d8c5db 100644 --- a/client/src/hooks/datasets/index.ts +++ b/client/src/hooks/datasets/index.ts @@ -1,4 +1,5 @@ 'use client'; +import { useCallback, useMemo } from 'react'; import { serialize } from './query-parsers'; import { @@ -28,33 +29,42 @@ type ExcludeParams = { }; export const useSyncQueryParams = ( - exclude: ExcludeParams = {}, - defaultValue: Partial = {} + exclude: ExcludeParams = {}, // Optional parameter for exclusion + defaultValue: Partial = {} // Optional default values ) => { + // Retrieve data from hooks, possibly undefined const [filtersFromURL] = useSyncFilters(); const [layersFromURL] = useSyncLayers(); const [settingsFromURL] = useSyncBasemap(); const [projectsTabFromURL] = useSyncProjectsTab(); const [bboxFromURL] = useSyncBbox(); - const filters = defaultValue?.filters || filtersFromURL; - const layers = defaultValue?.layers || layersFromURL; - const settings = defaultValue?.settings || settingsFromURL; - const projectsTab = defaultValue?.projectsTab || projectsTabFromURL; - const bbox = defaultValue?.bbox || bboxFromURL; + // Use useMemo to only recalculate when dependencies change + const data: QueryParamsData = useMemo( + () => ({ + // Apply default values if provided, otherwise use values from URL + filters: defaultValue?.filters ?? filtersFromURL, + layers: defaultValue?.layers ?? layersFromURL, + settings: defaultValue?.settings ?? settingsFromURL, + projectsTab: defaultValue?.projectsTab ?? projectsTabFromURL, + bbox: defaultValue?.bbox ?? bboxFromURL ?? [0, 0, 0, 0], // Include fallback default for bbox + }), + [filtersFromURL, layersFromURL, settingsFromURL, projectsTabFromURL, bboxFromURL, defaultValue] + ); - // Construct the data object with correct typing - const data: QueryParamsData = { filters, layers, settings, projectsTab, bbox }; + // Construct the result by excluding specified keys + const result: Partial = useMemo(() => { + const filteredResult: Partial = {}; + Object.keys(data).forEach((key) => { + const typedKey = key as keyof QueryParamsData; + // Only add key to result if it's not set to be excluded + if (!exclude[typedKey]) { + filteredResult[typedKey] = data[typedKey]; + } + }); + return filteredResult; + }, [data, exclude]); - // Filter out excluded keys - const result: Partial = {}; - Object.keys(data).forEach((key) => { - if (!(key in exclude && exclude[key as keyof ExcludeParams])) { - // Use type assertion here to ensure keys are recognized as valid - result[key as keyof QueryParamsData] = data[key as keyof QueryParamsData]; - } - }); - - // Return the serialized object + // Serialize the result object to make it suitable for query parameters return serialize(result); }; diff --git a/client/src/hooks/datasets/query-parsers.ts b/client/src/hooks/datasets/query-parsers.ts index e5c02701..bc5432c2 100644 --- a/client/src/hooks/datasets/query-parsers.ts +++ b/client/src/hooks/datasets/query-parsers.ts @@ -32,7 +32,7 @@ const searchQueryParams = { layers: layersParser, settings: basemapSettingsParser, tab: projectsTabParser, - box: bboxParser, + bbox: bboxParser, }; export const serialize = createSerializer(searchQueryParams); diff --git a/client/src/types/generated/strapi.schemas.ts b/client/src/types/generated/strapi.schemas.ts index b572584b..5e2162ec 100644 --- a/client/src/types/generated/strapi.schemas.ts +++ b/client/src/types/generated/strapi.schemas.ts @@ -1,3 +1,5 @@ +import type { Bbox } from '@/types/map'; + /** * Generated by orval v6.16.0 🍺 * Do not edit manually. @@ -418,6 +420,7 @@ export interface Project { publishedAt?: string; createdBy?: ProjectCreatedBy; updatedBy?: ProjectUpdatedBy; + bbox?: Bbox; } export type ProjectGalleryDataItemAttributesUpdatedByDataAttributes = { [key: string]: any };