diff --git a/playwright/tests/search_page/test_map.py b/playwright/tests/search_page/test_map.py index 33184a23..f104221e 100644 --- a/playwright/tests/search_page/test_map.py +++ b/playwright/tests/search_page/test_map.py @@ -161,7 +161,7 @@ def test_map_spider( search_page.wait_for_search_to_complete() search_page.map.center_map(head_lng, head_lat) - page_mock.wait_for_timeout(1000) + page_mock.wait_for_timeout(5000) # Try to find and click a cluster cluster_found = execute_js(page_mock, 'findAndClickCluster') diff --git a/src/components/common/buttons/DetailSubtabBtn.tsx b/src/components/common/buttons/DetailSubtabBtn.tsx index 70eeb9c5..d973f99b 100644 --- a/src/components/common/buttons/DetailSubtabBtn.tsx +++ b/src/components/common/buttons/DetailSubtabBtn.tsx @@ -5,14 +5,14 @@ import { color } from "../../../styles/constants"; interface DetailSubtabProps { id?: string; title: string; - navigate: () => void; + onClick: () => void; isBordered: boolean; } const DetailSubtabBtn: React.FC = ({ id, title, - navigate, + onClick, isBordered, }) => { const theme = useTheme(); @@ -42,7 +42,7 @@ const DetailSubtabBtn: React.FC = ({ textAlign: "center", backgroundColor: "#fff", }} - onClick={navigate} + onClick={onClick} > {title} diff --git a/src/components/common/buttons/__test__/DynamicResultCardButton.test.tsx b/src/components/common/buttons/__test__/DynamicResultCardButton.test.tsx index 7d3235aa..30105b5d 100644 --- a/src/components/common/buttons/__test__/DynamicResultCardButton.test.tsx +++ b/src/components/common/buttons/__test__/DynamicResultCardButton.test.tsx @@ -8,18 +8,18 @@ import DynamicResultCardButton from "../DynamicResultCardButton"; import { rgbToHex } from "@mui/material"; import AppTheme from "../../../../utils/AppTheme"; import { ThemeProvider } from "@mui/material/styles"; -beforeAll(() => { - server.listen(); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); -}); -afterAll(() => { - server.close(); -}); describe("DynamicResultCardButton", async () => { + beforeAll(() => { + server.listen(); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + }); + afterAll(() => { + server.close(); + }); const theme = AppTheme; test("Button color and text should be shown properly when status is ongoing", async () => { render( diff --git a/src/components/common/charts/TimeRangeBarChart.tsx b/src/components/common/charts/TimeRangeBarChart.tsx index 34625702..52e66271 100644 --- a/src/components/common/charts/TimeRangeBarChart.tsx +++ b/src/components/common/charts/TimeRangeBarChart.tsx @@ -1,7 +1,6 @@ import React from "react"; import { BarChart } from "@mui/x-charts/BarChart"; -import { BarSeriesType } from "@mui/x-charts"; -import { useTheme } from "@mui/material"; +import { axisClasses, BarSeriesType } from "@mui/x-charts"; import { OGCCollections } from "../store/OGCCollectionDefinitions"; import { color } from "../../../styles/constants"; @@ -20,9 +19,9 @@ interface Bucket { } enum DividedBy { - day, - month, - year, + day = "Day", + month = "Month", + year = "Year", } const TimeRangeBarChart: React.FC = ({ @@ -175,7 +174,7 @@ const TimeRangeBarChart: React.FC = ({ type: "bar", valueFormatter: seriesFormatter, stack: "total", - label: "IMOS Data", + label: "IMOS Records", data: buckets.flatMap((m) => m.imosOnlyCount), color: color.blue.dark, }; @@ -184,7 +183,7 @@ const TimeRangeBarChart: React.FC = ({ type: "bar", valueFormatter: seriesFormatter, stack: "total", - label: "All Data", + label: "All Records", data: buckets.flatMap((m) => m.total - m.imosOnlyCount), color: color.blue.darkSemiTransparent, }; @@ -203,8 +202,10 @@ const TimeRangeBarChart: React.FC = ({ = ({ { data: xValues, scaleType: "band", - // valueFormatter: (date: Date) => date.toLocaleDateString(), valueFormatter: xAxisLabelFormatter, - tickMinStep: 3600 * 1000 * 48, // min step: 48h, - // If you want the label rotate - // tickLabelStyle: { - // angle: 45, - // dominantBaseline: 'hanging', - // textAnchor: 'start', - // }, + tickMinStep: 3600 * 1000 * 48, // min step: 48h + label: determineChartUnit().toString(), // x-axis label + labelStyle: { + fontSize: 12, + fontWeight: "bold", + }, + }, + ]} + yAxis={[ + { + label: "Count of Records", // y-axis label + labelStyle: { + fontSize: 12, + fontWeight: "bold", + }, + tickLabelStyle: { + fontSize: 12, + }, }, ]} + sx={{ + [`.${axisClasses.left} .${axisClasses.label}`]: { + transform: "translate(-20px, 0)", + }, + }} series={series} /> ); diff --git a/src/components/common/filters/AdvanceFilters.tsx b/src/components/common/filters/AdvanceFilters.tsx index 48cf7801..e9ee1c5e 100644 --- a/src/components/common/filters/AdvanceFilters.tsx +++ b/src/components/common/filters/AdvanceFilters.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useEffect, useMemo, useState } from "react"; +import { FC, useCallback, useEffect, useState } from "react"; import { ParameterState, updateParameterVocabs, @@ -165,17 +165,28 @@ const AdvanceFilters: FC = ({ }} > - + - + - + = ({ - + = ({ filter, setFilter }) => { min={dateToValue(initialMinDate)} max={dateToValue(initialMaxDate)} onChange={handleSliderChange} - valueLabelDisplay="on" + valueLabelDisplay="auto" valueLabelFormat={(value: number) => valueToDate(value).format("MM/YYYY") } diff --git a/src/components/common/filters/FilterSection.tsx b/src/components/common/filters/FilterSection.tsx index 57138e58..75e7b8b1 100644 --- a/src/components/common/filters/FilterSection.tsx +++ b/src/components/common/filters/FilterSection.tsx @@ -1,18 +1,20 @@ import { FC, ReactNode } from "react"; import StyledFilterSectionGrid from "./StyledFilterSectionGrid"; -import { Typography } from "@mui/material"; +import { Tooltip, Typography } from "@mui/material"; import { gap, padding } from "../../../styles/constants"; interface FilterSectionContainerProps { title: string; children: ReactNode; isTitleOnlyHeader?: boolean; + toolTip?: string; } const FilterSection: FC = ({ title, children, isTitleOnlyHeader = false, + toolTip, }) => { return ( = ({ paddingTop: isTitleOnlyHeader ? padding.triple : 0, }} > - - {title} - + + + {title} + + {children} ); diff --git a/src/components/common/filters/ImosOnlySwitch.tsx b/src/components/common/filters/ImosOnlySwitch.tsx index ab236eca..923af1eb 100644 --- a/src/components/common/filters/ImosOnlySwitch.tsx +++ b/src/components/common/filters/ImosOnlySwitch.tsx @@ -1,4 +1,4 @@ -import { Grid, Switch, SwitchProps } from "@mui/material"; +import { Grid, Switch, SwitchProps, Tooltip } from "@mui/material"; import React, { FC, useCallback } from "react"; import imos_logo from "@/assets/logos/imos-logo-transparent.png"; import { ParameterState } from "../store/componentParamReducer"; @@ -33,17 +33,20 @@ const ImosOnlySwitch: FC = ({ filter, setFilter }) => { height: "100%", }} > - - imos_logo - + + + imos_logo + + + = ({ }, [dispatch]); return ( - + c.label) || []} + value={filter.parameterVocabs?.map((vocab) => vocab.label) || []} onChange={handleChange} aria-label="parameter vocab selection" > diff --git a/src/components/common/hover-tip/BasicMapHoverTip.tsx b/src/components/common/hover-tip/BasicMapHoverTip.tsx index 0184ba2d..d73ae372 100644 --- a/src/components/common/hover-tip/BasicMapHoverTip.tsx +++ b/src/components/common/hover-tip/BasicMapHoverTip.tsx @@ -11,7 +11,6 @@ import { fontColor, fontSize, fontWeight } from "../../../styles/constants"; export interface BasicMapHoverTipProps { content?: string | undefined | null; sx?: SxProps; - onNavigateToDetail?: () => void; onDatasetSelected?: () => void; } diff --git a/src/components/common/hover-tip/ComplexMapHoverTip.tsx b/src/components/common/hover-tip/ComplexMapHoverTip.tsx index c560117b..e827401a 100644 --- a/src/components/common/hover-tip/ComplexMapHoverTip.tsx +++ b/src/components/common/hover-tip/ComplexMapHoverTip.tsx @@ -9,6 +9,7 @@ import { padding, } from "../../../styles/constants"; import ResultCardButtonGroup from "../../result/ResultCardButtonGroup"; +import useTabNavigation from "../../../hooks/useTabNavigation"; interface ComplexMapHoverTipProps extends BasicMapHoverTipProps { collection: OGCCollection; @@ -16,10 +17,11 @@ interface ComplexMapHoverTipProps extends BasicMapHoverTipProps { const ComplexMapHoverTip: FC = ({ collection, - onNavigateToDetail = () => {}, onDatasetSelected = () => {}, sx, }) => { + const navigateToDetailPage = useTabNavigation(); + return ( @@ -61,14 +63,12 @@ const ComplexMapHoverTip: FC = ({ - + - + navigateToDetailPage(collection.id, "abstract")} + > void; + truncateCount?: number; + showMoreStr?: string; } -const truncateCount = 430; +const defaultTruncateCount = 430; const ExpandableTextArea: React.FC = ({ text, isClickable = false, onClick = () => {}, + truncateCount = defaultTruncateCount, + showMoreStr = "Show More", }) => { const decodedText = decodeHtmlEntities(text); const truncatedText = truncateText(decodedText, truncateCount); @@ -50,7 +54,7 @@ const ExpandableTextArea: React.FC = ({ {doesNeedTruncation && ( )} diff --git a/src/components/map/mapbox/Map.tsx b/src/components/map/mapbox/Map.tsx index 17935a4c..c6efb6d2 100644 --- a/src/components/map/mapbox/Map.tsx +++ b/src/components/map/mapbox/Map.tsx @@ -57,12 +57,12 @@ const styles = [ // Add more styles as needed ]; +const { WEST_LON, EAST_LON, NORTH_LAT, SOUTH_LAT } = + MapDefaultConfig.BBOX_ENDPOINTS; + const ReactMap = ({ panelId, - centerLongitude = MapDefaultConfig.CENTER_LONGITUDE, - centerLatitude = MapDefaultConfig.CENTER_LATITUDE, bbox, - zoom = MapDefaultConfig.ZOOM, minZoom = MapDefaultConfig.MIN_ZOOM, maxZoom = MapDefaultConfig.MAX_ZOOM, projection = MapDefaultConfig.PROJECTION, @@ -104,8 +104,7 @@ const ReactMap = ({ container: panelId, accessToken: import.meta.env.VITE_MAPBOX_ACCESS_TOKEN, style: styles[MapDefaultConfig.DEFAULT_STYLE].style, - center: [centerLongitude, centerLatitude], - zoom: zoom, + bounds: [WEST_LON, SOUTH_LAT, EAST_LON, NORTH_LAT], minZoom: minZoom, maxZoom: maxZoom, testMode: import.meta.env.MODE === "dev", @@ -148,10 +147,7 @@ const ReactMap = ({ }; } }, [ - centerLatitude, - centerLongitude, panelId, - zoom, projection, map, onZoomEvent, diff --git a/src/components/map/mapbox/component/MapPopup.tsx b/src/components/map/mapbox/component/MapPopup.tsx index e7d88c22..bafc0e30 100644 --- a/src/components/map/mapbox/component/MapPopup.tsx +++ b/src/components/map/mapbox/component/MapPopup.tsx @@ -14,7 +14,7 @@ import { ThemeProvider } from "@mui/material/styles"; import { Box, Card, CardContent, CircularProgress } from "@mui/material"; import { MapLayerMouseEvent, Popup } from "mapbox-gl"; import MapContext from "../MapContext"; -import { Point, Feature } from "geojson"; +import { Feature, Point } from "geojson"; import { fetchResultNoStore, SearchParameters, @@ -32,7 +32,6 @@ interface MapPopupProps { layerId: string; popupType?: PopupType; onDatasetSelected?: (uuid: Array) => void; - onNavigateToDetail?: (uuid: string) => void; } export interface MapPopupRef { forceRemovePopup: () => void; @@ -88,15 +87,6 @@ const renderLoadingBox = ({ ); -const handleNavigateToDetailPage = ( - uuid: string, - onNavigateToDetail?: (uuid: string) => void -) => { - if (onNavigateToDetail) { - onNavigateToDetail(uuid); - } -}; - const handleDatasetSelect = ( uuid: string, onDatasetSelected?: (uuid: Array) => void @@ -107,12 +97,7 @@ const handleDatasetSelect = ( }; const MapPopup: ForwardRefRenderFunction = ( - { - layerId, - onDatasetSelected, - onNavigateToDetail, - popupType = PopupType.Basic, - }, + { layerId, onDatasetSelected, popupType = PopupType.Basic }, ref ) => { const dispatch = useAppDispatch(); @@ -177,12 +162,6 @@ const MapPopup: ForwardRefRenderFunction = ( {popupType === PopupType.Complex && ( - handleNavigateToDetailPage( - collection.id, - onNavigateToDetail - ) - } onDatasetSelected={() => handleDatasetSelect(collection.id, onDatasetSelected) } @@ -193,7 +172,7 @@ const MapPopup: ForwardRefRenderFunction = ( ); }, - [onDatasetSelected, onNavigateToDetail, popupHeight, popupType, popupWidth] + [onDatasetSelected, popupHeight, popupType, popupWidth] ); const removePopup = useCallback(() => { diff --git a/src/components/map/mapbox/component/SpiderDiagram.tsx b/src/components/map/mapbox/component/SpiderDiagram.tsx index e07835bb..82265262 100644 --- a/src/components/map/mapbox/component/SpiderDiagram.tsx +++ b/src/components/map/mapbox/component/SpiderDiagram.tsx @@ -3,6 +3,7 @@ import { useCallback, useContext, useEffect, + useMemo, useRef, useState, } from "react"; @@ -97,22 +98,22 @@ const SpiderDiagram: FC = ({ const [spiderifiedCluster, setSpiderifiedCluster] = useState(null); - const config = mergeWithDefaults( - defaultSpiderDiagramConfig, - spiderDiagramConfig + const config = useMemo( + () => mergeWithDefaults(defaultSpiderDiagramConfig, spiderDiagramConfig), + [spiderDiagramConfig] ); const { spiderifyFromZoomLevel, maxSpiderLegs } = config; // Util function to check if a cluster can spiderify or not const shouldCreateSpiderDiagram = useCallback( - (features: any[]): boolean => { + (features: any[], spiderifyFromZoomLevel: number): boolean => { const zoom = map?.getZoom() || 0; const clusterCount = features[0].properties.point_count; return ( (!clusterCount && features.length > 1) || zoom >= spiderifyFromZoomLevel ); }, - [map, spiderifyFromZoomLevel] + [map] ); // Util function to un-spiderify a spiderified cluster given its id @@ -152,7 +153,9 @@ const SpiderDiagram: FC = ({ ( coordinate: [number, number], datasets: Feature[], - expansionZoom: number + expansionZoom: number, + spiderifyFromZoomLevel: number, + config: SpiderDiagramConfig ) => { const clusterCircleId = getClusterCircleId(coordinate); const spiderPinsSourceId = getSpiderPinsSourceId(clusterCircleId); @@ -309,7 +312,7 @@ const SpiderDiagram: FC = ({ expansionZoom, }); }, - [spiderifiedCluster, map, config, unspiderify, spiderifyFromZoomLevel] + [spiderifiedCluster, map, unspiderify] ); // Util function for checking if need unspiderify on every zoom end @@ -391,7 +394,7 @@ const SpiderDiagram: FC = ({ const source = map?.getSource(clusterSourceId) as GeoJSONSource; if ( - shouldCreateSpiderDiagram(features) && + shouldCreateSpiderDiagram(features, spiderifyFromZoomLevel) && clusterCount <= maxSpiderLegs ) { // get first up to maxSpiderLegs(default = 50) datasets in the cluster to avoid huge spider @@ -406,7 +409,13 @@ const SpiderDiagram: FC = ({ // Store the clicked cluster info source.getClusterExpansionZoom(clusterId, (err, zoom) => { if (err) return; - spiderify(coordinate, datasets, zoom); + spiderify( + coordinate, + datasets, + zoom, + spiderifyFromZoomLevel, + config + ); }); }); } else { @@ -425,7 +434,13 @@ const SpiderDiagram: FC = ({ return; } const datasets = leaves as Feature[]; - spiderify(coordinate, datasets, zoom); + spiderify( + coordinate, + datasets, + zoom, + spiderifyFromZoomLevel, + config + ); } ); return; @@ -447,9 +462,10 @@ const SpiderDiagram: FC = ({ clusterLayer, clusterSourceId, shouldCreateSpiderDiagram, + spiderifyFromZoomLevel, maxSpiderLegs, spiderify, - config.maxSpiderLegs, + config, ] ); @@ -475,6 +491,7 @@ const SpiderDiagram: FC = ({ map, onClusterClick, onEmptySpaceClick, + spiderifyFromZoomLevel, ]); return ( diff --git a/src/components/map/mapbox/constants.ts b/src/components/map/mapbox/constants.ts index 9a134f14..7483d0e5 100644 --- a/src/components/map/mapbox/constants.ts +++ b/src/components/map/mapbox/constants.ts @@ -1,11 +1,14 @@ export const MapDefaultConfig = { // Magic number, try and error by experience DEBOUNCE_BEFORE_EVENT_FIRE: 700, - CENTER_LONGITUDE: 134.0470865301421, - CENTER_LATITUDE: -27.609351801462687, - ZOOM: 4, MIN_ZOOM: 1, MAX_ZOOM: 12, PROJECTION: "equirectangular", DEFAULT_STYLE: 3, + BBOX_ENDPOINTS: { + WEST_LON: 104, + EAST_LON: 163, + NORTH_LAT: -8, + SOUTH_LAT: -43, + }, }; diff --git a/src/components/map/mapbox/layers/ClusterLayer.tsx b/src/components/map/mapbox/layers/ClusterLayer.tsx index 5f48f317..d34806805 100644 --- a/src/components/map/mapbox/layers/ClusterLayer.tsx +++ b/src/components/map/mapbox/layers/ClusterLayer.tsx @@ -100,7 +100,6 @@ const ClusterLayer: FC = ({ collections, selectedUuids, onDatasetSelected, - onNavigateToDetail, clusterLayerConfig, showFullMap, }: ClusterLayerProps) => { @@ -283,7 +282,6 @@ const ClusterLayer: FC = ({ layerId={unclusterPointLayer} popupType={showFullMap ? PopupType.Complex : PopupType.Basic} onDatasetSelected={onDatasetSelected} - onNavigateToDetail={onNavigateToDetail} /> = ({ selectedUuids, showFullMap, onDatasetSelected, - onNavigateToDetail, heatmapLayerConfig, }: HeatmapLayerProps) => { const { map } = useContext(MapContext); @@ -338,7 +337,6 @@ const HeatmapLayer: FC = ({ layerId={unClusterPointLayer} popupType={showFullMap ? PopupType.Complex : PopupType.Basic} onDatasetSelected={onDatasetSelected} - onNavigateToDetail={onNavigateToDetail} /> void; } // Use to create static layer on map, you need to add menu item to select those layers, // refer to map section diff --git a/src/components/result/GridResultCard.tsx b/src/components/result/GridResultCard.tsx index 63260c41..a1c9bece 100644 --- a/src/components/result/GridResultCard.tsx +++ b/src/components/result/GridResultCard.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from "react"; +import { FC, useCallback, useState } from "react"; import { Box, Card, @@ -20,21 +20,31 @@ import { import OrganizationLogo from "../logo/OrganizationLogo"; import ResultCardButtonGroup from "./ResultCardButtonGroup"; import MapSpatialExtents from "@/assets/icons/map-spatial-extents.png"; -import { ResultCard } from "./ResultCards"; +import { OGCCollection } from "../common/store/OGCCollectionDefinitions"; +import { useNavigate } from "react-router-dom"; +import { pageDefault } from "../common/constants"; -interface GridResultCardProps extends ResultCard { +interface GridResultCardProps { + content?: OGCCollection; + onClickCard?: (uuid: string) => void; isSelectedDataset?: boolean; } const GridResultCard: FC = ({ content, - onDownload = () => {}, - onLink = () => {}, - onDetail = () => {}, onClickCard = () => {}, isSelectedDataset, }) => { const [showButtons, setShowButtons] = useState(false); + const navigate = useNavigate(); + const onDetail = useCallback( + (uuid: string) => { + const searchParams = new URLSearchParams(); + searchParams.append("uuid", uuid); + navigate(pageDefault.details + "?" + searchParams.toString()); + }, + [navigate] + ); if (!content) return; const { id: uuid, title, findIcon, findThumbnail } = content; @@ -154,14 +164,7 @@ const GridResultCard: FC = ({ paddingRight: padding.extraSmall, }} /> - onDownload(uuid, "abstract", "download-section")} - onDetail={() => onDetail(uuid)} - onLink={() => onLink(uuid, "links")} - shouldHideText - isGridView - /> + )} diff --git a/src/components/result/ListResultCard.tsx b/src/components/result/ListResultCard.tsx index d10bbcee..1d603e01 100644 --- a/src/components/result/ListResultCard.tsx +++ b/src/components/result/ListResultCard.tsx @@ -20,23 +20,25 @@ import { import { FC, useState } from "react"; import OrganizationLogo from "../logo/OrganizationLogo"; import ResultCardButtonGroup from "./ResultCardButtonGroup"; -import { ResultCard } from "./ResultCards"; +import useTabNavigation from "../../hooks/useTabNavigation"; +import { OGCCollection } from "../common/store/OGCCollectionDefinitions"; -interface ListResultCardProps extends ResultCard { +interface ListResultCardProps { + content?: OGCCollection; + onClickCard?: (uuid: string) => void; isSelectedDataset?: boolean; } // links here may need to be changed, because only html links are wanted const ListResultCard: FC = ({ content, - onDownload = () => {}, - onLink = () => {}, - onDetail = () => {}, onClickCard = () => {}, isSelectedDataset, }) => { const [showButtons, setShowButtons] = useState(false); + const goToDetailPage = useTabNavigation(); + if (!content) return; const { id: uuid, title, description, findIcon, findThumbnail } = content; @@ -72,7 +74,7 @@ const ListResultCard: FC = ({ mr={gap.sm} > - onDetail(uuid)}> + goToDetailPage(uuid, "abstract")}> = ({ {(isSelectedDataset || showButtons) && ( - onDownload(uuid, "abstract", "download-section")} - onDetail={() => onDetail(uuid)} - onLink={() => onLink(uuid, "links")} - shouldHideText - /> + )} diff --git a/src/components/result/ResultCardButtonGroup.tsx b/src/components/result/ResultCardButtonGroup.tsx index a0a408cd..ea116066 100644 --- a/src/components/result/ResultCardButtonGroup.tsx +++ b/src/components/result/ResultCardButtonGroup.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode, useCallback } from "react"; +import { FC, ReactNode } from "react"; import { Grid, SxProps } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import InfoIcon from "@mui/icons-material/Info"; @@ -9,12 +9,10 @@ import QuestionMarkIcon from "@mui/icons-material/QuestionMark"; import { OGCCollection } from "../common/store/OGCCollectionDefinitions"; import ResultCardButton from "../common/buttons/ResultCardButton"; import { color } from "../../styles/constants"; +import useTabNavigation from "../../hooks/useTabNavigation"; interface ResultCardButtonGroupProps { content: OGCCollection; - onDownload?: () => void; - onDetail?: () => void; - onLink?: () => void; isGridView?: boolean; shouldHideText?: boolean; } @@ -74,12 +72,11 @@ const renderStatusButton = ( const ResultCardButtonGroup: FC = ({ content, - onDetail, - onLink, - onDownload, isGridView, shouldHideText = false, }) => { + const goToDetailPanel = useTabNavigation(); + const ButtonContainer: FC = ({ children, sx }) => ( = ({ startIcon={LinkIcon} text={generateLinkText(links.length)} shouldHideText={shouldHideText} - onClick={onLink} + onClick={() => goToDetailPanel(content.id, "links")} resultCardButtonConfig={{ color: links.length > 0 ? color.blue.dark : color.gray.light, }} - disable={!!(links.length === 0)} + disable={links.length === 0} /> )} @@ -120,7 +117,9 @@ const ResultCardButtonGroup: FC = ({ startIcon={DownloadIcon} text="Download" shouldHideText={shouldHideText} - onClick={onDownload} + onClick={() => + goToDetailPanel(content.id, "abstract", "download-section") + } /> @@ -128,7 +127,7 @@ const ResultCardButtonGroup: FC = ({ startIcon={InfoIcon} text="More details ..." shouldHideText={shouldHideText} - onClick={onDetail} + onClick={() => goToDetailPanel(content.id, "abstract")} /> diff --git a/src/components/result/ResultCards.tsx b/src/components/result/ResultCards.tsx index 6ce71ed2..c08ebac1 100644 --- a/src/components/result/ResultCards.tsx +++ b/src/components/result/ResultCards.tsx @@ -1,5 +1,10 @@ -import { FC, useCallback, useRef } from "react"; -import { CollectionsQueryType } from "../common/store/searchReducer"; +import { useCallback, useRef } from "react"; +import { + CollectionsQueryType, + createSearchParamFrom, + DEFAULT_SEARCH_PAGE, + fetchResultAppendStore, +} from "../common/store/searchReducer"; import { FixedSizeList, ListChildComponentProps } from "react-window"; import { Box, Grid, ListItem, SxProps, Theme } from "@mui/material"; import GridResultCard from "./GridResultCard"; @@ -8,94 +13,48 @@ import { OGCCollection } from "../common/store/OGCCollectionDefinitions"; import AutoSizer, { Size } from "react-virtualized-auto-sizer"; import DetailSubtabBtn from "../common/buttons/DetailSubtabBtn"; import { SearchResultLayoutEnum } from "../common/buttons/MapViewButton"; -import { GRID_CARD_HEIGHT, LIST_CARD_GAP, LIST_CARD_HEIGHT } from "./constants"; -import { gap, padding } from "../../styles/constants"; - -export interface ResultCard { +import { GRID_CARD_HEIGHT, LIST_CARD_HEIGHT } from "./constants"; +import { padding } from "../../styles/constants"; +import SelectedListCard from "./SelectedListCard"; +import SelectedGridCard from "./SelectedGridCard"; +import { ParameterState } from "../common/store/componentParamReducer"; +import store, { getComponentState } from "../common/store/store"; +import { useAppDispatch } from "../common/store/hooks"; + +interface ResultCardsProps { content?: OGCCollection; onClickCard?: (uuid: string) => void; - onDetail?: (uuid: string) => void; - onDownload?: (uuid: string, tab: string, section?: string) => void; - onLink?: (uuid: string, tab: string, section?: string) => void; -} - -export interface ResultCardsList extends ResultCard { contents: CollectionsQueryType; layout?: SearchResultLayoutEnum; - onFetchMore?: (() => void) | undefined; sx?: SxProps; datasetsSelected?: OGCCollection[]; } -interface ResultCardsProps extends ResultCardsList {} - -interface SelectedCardsProps extends ResultCard { - hasSelectedDatasets: boolean; -} -const renderSelectedListCards: FC = ({ - hasSelectedDatasets, - content, - onDownload, - onLink, - onDetail, - onClickCard, -}) => { - if (!hasSelectedDatasets) return; - return ( - - - - ); -}; - -// For now only one dataset can be selected at a time, so use datasetsSelected[0] for selected list/grid card -const renderSelectedGridCards: FC = ({ - hasSelectedDatasets, - content, - onDownload, - onLink, - onClickCard, - onDetail, -}) => { - if (!hasSelectedDatasets) return; - return ( - - - - ); -}; - const ResultCards = ({ contents, layout, sx, datasetsSelected, - onClickCard, - onDownload, - onLink, - onDetail, - onFetchMore, + onClickCard = () => {}, }: ResultCardsProps) => { const componentRef = useRef(null); + // Get contents from redux + const dispatch = useAppDispatch(); + const fetchMore = useCallback(async () => { + // This is very specific to how elastic works and then how to construct the query + const componentParam: ParameterState = getComponentState(store.getState()); + // Use standard param to get fields you need, record is stored in redux, + // set page so that it return fewer records and append the search_after + // to go the next batch of record. + const paramPaged = createSearchParamFrom(componentParam, { + pagesize: DEFAULT_SEARCH_PAGE, + searchafter: contents.result.search_after, + }); + // Must use await so that record updated before you exit this call + await dispatch(fetchResultAppendStore(paramPaged)); + }, [dispatch, contents.result.search_after]); + const hasSelectedDatasets = datasetsSelected && datasetsSelected.length > 0; const count = contents.result.collections.length; @@ -107,18 +66,16 @@ const ResultCards = ({ id="result-card-load-more-btn" title="Show more results" isBordered={false} - navigate={() => { - onFetchMore && onFetchMore(); - }} + onClick={fetchMore} /> ); - }, [onFetchMore]); + }, [fetchMore]); const renderCells = useCallback( ( count: number, total: number, - { contents, onClickCard, onDownload, onLink, onDetail }: ResultCardsProps, + { contents, onClickCard }: ResultCardsProps, child: ListChildComponentProps ) => { const { index, style } = child; @@ -137,7 +94,7 @@ const ResultCards = ({ }} style={style} > - {renderLoadMoreButton()} + {count !== total ? renderLoadMoreButton() : null} ); } else { @@ -147,20 +104,14 @@ const ResultCards = ({ {rightIndex < contents.result.collections.length && ( )} @@ -176,7 +127,7 @@ const ResultCards = ({ ( count: number, total: number, - { contents, onClickCard, onDownload, onLink, onDetail }: ResultCardsProps, + { contents, onClickCard }: ResultCardsProps, child: ListChildComponentProps ) => { // The style must pass to the listitem else incorrect rendering @@ -202,9 +153,6 @@ const ResultCards = ({ @@ -225,15 +173,12 @@ const ResultCards = ({ ref={componentRef} data-testid="resultcard-result-list" > - {hasSelectedDatasets && - renderSelectedListCards({ - hasSelectedDatasets, - content: datasetsSelected[0], - onDownload, - onLink, - onClickCard, - onDetail, - })} + {hasSelectedDatasets && ( + + )} {({ height, width }: Size) => ( - {hasSelectedDatasets && - renderSelectedGridCards({ - hasSelectedDatasets, - content: datasetsSelected[0], - onDownload, - onLink, - onClickCard, - onDetail, - })} + {hasSelectedDatasets && ( + + )} {({ height, width }: Size) => ( void; +} + +const SelectedGridCard: React.FC = ({ + content, + onClickCard, +}) => { + return ( + + + + ); +}; + +export default SelectedGridCard; diff --git a/src/components/result/SelectedListCard.tsx b/src/components/result/SelectedListCard.tsx new file mode 100644 index 00000000..d1bf7f5c --- /dev/null +++ b/src/components/result/SelectedListCard.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { LIST_CARD_GAP, LIST_CARD_HEIGHT } from "./constants"; +import { gap } from "../../styles/constants"; +import ListResultCard from "./ListResultCard"; +import { Box } from "@mui/material"; +import { OGCCollection } from "../common/store/OGCCollectionDefinitions"; + +interface SelectedListCardProps { + content: OGCCollection; + onClickCard: (uuid: string) => void; +} + +const SelectedListCard: React.FC = ({ + content, + onClickCard, +}) => { + return ( + + + + ); +}; + +export default SelectedListCard; diff --git a/src/components/search/__test__/ComplexTextSearch.test.tsx b/src/components/search/__test__/ComplexTextSearch.test.tsx index 6871c1a5..d2f22e43 100644 --- a/src/components/search/__test__/ComplexTextSearch.test.tsx +++ b/src/components/search/__test__/ComplexTextSearch.test.tsx @@ -13,18 +13,17 @@ vi.mock("../../common/filters/AdvanceFilters.tsx", () => { return { default: mockAdvanceFilters }; }); -beforeAll(() => { - server.listen(); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); -}); -afterAll(() => { - server.close(); -}); - describe("ComplexTextSearch Component", () => { + beforeAll(() => { + server.listen(); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { render( diff --git a/src/components/search/__test__/InputWithSuggester.test.tsx b/src/components/search/__test__/InputWithSuggester.test.tsx index e33f0777..06885d3e 100644 --- a/src/components/search/__test__/InputWithSuggester.test.tsx +++ b/src/components/search/__test__/InputWithSuggester.test.tsx @@ -7,7 +7,6 @@ import { describe, expect, test, - vi, } from "vitest"; import { Provider } from "react-redux"; import store from "../../common/store/store"; @@ -15,22 +14,20 @@ import InputWithSuggester from "../InputWithSuggester"; import { server } from "../../../__mocks__/server"; import { userEvent } from "@testing-library/user-event"; -beforeAll(() => { - server.listen(); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); -}); -afterAll(() => { - server.close(); -}); - // TODO: skip this test suite for now cause it will have weird failure (local all passed // , but in github action failed) describe.skip("inputwithsuggester", async () => { let rendered: RenderResult; - + beforeAll(() => { + server.listen(); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { rendered = render( diff --git a/src/hooks/useTabNavigation.tsx b/src/hooks/useTabNavigation.tsx index 143e4cc6..57b01a0c 100644 --- a/src/hooks/useTabNavigation.tsx +++ b/src/hooks/useTabNavigation.tsx @@ -5,7 +5,7 @@ import { pageDefault } from "../components/common/constants"; const useTabNavigation = () => { const navigate = useNavigate(); - const navigateToTabAndSection = useCallback( + return useCallback( (uuid: string, tab: string, section?: string) => { console.log("is called navi,tab=", tab); const searchParams = new URLSearchParams(); @@ -26,8 +26,6 @@ const useTabNavigation = () => { }, [navigate] ); - - return navigateToTabAndSection; }; export default useTabNavigation; diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index 8127fbb0..cbc9a61a 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -44,6 +44,7 @@ import { StaticLayersDef } from "../../../../components/map/mapbox/layers/Static import { MapboxWorldLayersDef } from "../../../../components/map/mapbox/layers/MapboxWorldLayer"; import useScrollToSection from "../../../../hooks/useScrollToSection"; import { decodeHtmlEntities } from "../../../../utils/StringUtils"; +import ExpandableTextArea from "../../../../components/list/listItem/subitem/ExpandableTextArea"; interface DownloadSelect { label?: string; @@ -137,6 +138,8 @@ const renderSelect = (select: DownloadSelect) => ( const DOWNLOAD_SECTION_ID = "download-section"; +const TRUNCATE_COUNT = 800; + const AbstractAndDownloadPanel: FC = () => { const { collection } = useDetailPageContext(); const downloadSectionRef = useScrollToSection({ @@ -154,9 +157,11 @@ const AbstractAndDownloadPanel: FC = () => { - - {decodeHtmlEntities(abstract)} - + = ({ key={index} title={child.title} isBordered={isPositionInsideBlock(position, index)} - navigate={onNavigate(index)} + onClick={onNavigate(index)} /> ); })} diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/AboutPanel.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/AboutPanel.test.tsx index 132e0257..330286d2 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/AboutPanel.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/AboutPanel.test.tsx @@ -9,37 +9,35 @@ import { DetailPageProvider } from "../../../context/detail-page-provider"; import { Provider } from "react-redux"; import AboutPanel from "../AboutPanel"; -beforeAll(() => { - server.listen(); -}); - -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); - - vi.mocked(useLocation).mockReturnValue({ - state: null, - hash: "111", - key: "default", - pathname: "/details", - search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", +describe("AboutPanel", async () => { + const theme = AppTheme; + beforeAll(() => { + server.listen(); }); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); -describe("AboutPanel", async () => { - const theme = AppTheme; + vi.mocked(useLocation).mockReturnValue({ + state: null, + hash: "111", + key: "default", + pathname: "/details", + search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + }); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { render( diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/AssociatedRecordsPanel.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/AssociatedRecordsPanel.test.tsx index e636c38f..5c6086dd 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/AssociatedRecordsPanel.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/AssociatedRecordsPanel.test.tsx @@ -10,37 +10,37 @@ import store from "../../../../../components/common/store/store"; import { Provider } from "react-redux"; import { userEvent } from "@testing-library/user-event"; -beforeAll(() => { - server.listen(); -}); +describe("AssociatedRecordsPanel", async () => { + const theme = AppTheme; + let openSpy: any; -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); - - vi.mocked(useLocation).mockReturnValue({ - state: null, - hash: "111", - key: "default", - pathname: "/details", - search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + beforeAll(() => { + server.listen(); }); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); + + vi.mocked(useLocation).mockReturnValue({ + state: null, + hash: "111", + key: "default", + pathname: "/details", + search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + }); + }); -describe("AssociatedRecordsPanel", async () => { - const theme = AppTheme; - let openSpy: any; + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { openSpy = vi diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/CitationPanel.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/CitationPanel.test.tsx index a33ca8b9..3c4a5ea5 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/CitationPanel.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/CitationPanel.test.tsx @@ -9,36 +9,35 @@ import { server } from "../../../../../__mocks__/server"; import { useLocation } from "react-router-dom"; import CitationPanel from "../CitationPanel"; -beforeAll(() => { - server.listen(); -}); +describe("CitationPanel", async () => { + const theme = AppTheme; + beforeAll(() => { + server.listen(); + }); -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); - vi.mocked(useLocation).mockReturnValue({ - state: null, - hash: "111", - key: "default", - pathname: "/details", - search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + vi.mocked(useLocation).mockReturnValue({ + state: null, + hash: "111", + key: "default", + pathname: "/details", + search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + }); }); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); - -describe("CitationPanel", async () => { - const theme = AppTheme; + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { render( diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/EmptyInfoDisplay.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/EmptyInfoDisplay.test.tsx index c56317c5..3c181f46 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/EmptyInfoDisplay.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/EmptyInfoDisplay.test.tsx @@ -13,28 +13,28 @@ import CitationPanel from "../CitationPanel"; import LineagePanel from "../LineagePanel"; import AssociatedRecordsPanel from "../AssociatedRecordsPanel"; -beforeAll(() => { - server.listen(); -}); +describe("empty area display", async () => { + const theme = AppTheme; -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); -}); + beforeAll(() => { + server.listen(); + }); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); + }); -describe("empty area display", async () => { - const theme = AppTheme; + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); test("about panel", async () => { vi.mocked(useLocation).mockReturnValue({ diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/LineagePanel.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/LineagePanel.test.tsx index 8c61f839..3b15b491 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/LineagePanel.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/LineagePanel.test.tsx @@ -9,37 +9,35 @@ import { server } from "../../../../../__mocks__/server"; import { useLocation } from "react-router-dom"; import LineagePanel from "../LineagePanel"; -beforeAll(() => { - server.listen(); -}); - -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); - - vi.mocked(useLocation).mockReturnValue({ - state: null, - hash: "111", - key: "default", - pathname: "/details", - search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", +describe("LineagePanel", async () => { + const theme = AppTheme; + beforeAll(() => { + server.listen(); }); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); -describe("LineagePanel", async () => { - const theme = AppTheme; + vi.mocked(useLocation).mockReturnValue({ + state: null, + hash: "111", + key: "default", + pathname: "/details", + search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + }); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { render( diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/LinksPanel.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/LinksPanel.test.tsx index 458a37ef..87778d40 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/LinksPanel.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/LinksPanel.test.tsx @@ -10,37 +10,35 @@ import { Provider } from "react-redux"; import { userEvent } from "@testing-library/user-event"; import LinksPanel from "../LinksPanel"; -beforeAll(() => { - server.listen(); -}); - -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); - - vi.mocked(useLocation).mockReturnValue({ - state: null, - hash: "111", - key: "default", - pathname: "/details", - search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", +describe("LinksPanel", async () => { + const theme = AppTheme; + beforeAll(() => { + server.listen(); }); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); -describe("LinksPanel", async () => { - const theme = AppTheme; + vi.mocked(useLocation).mockReturnValue({ + state: null, + hash: "111", + key: "default", + pathname: "/details", + search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + }); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { render( diff --git a/src/pages/detail-page/subpages/tab-panels/__test__/MetadataInformationPanel.test.tsx b/src/pages/detail-page/subpages/tab-panels/__test__/MetadataInformationPanel.test.tsx index 0643bd41..d8aa0628 100644 --- a/src/pages/detail-page/subpages/tab-panels/__test__/MetadataInformationPanel.test.tsx +++ b/src/pages/detail-page/subpages/tab-panels/__test__/MetadataInformationPanel.test.tsx @@ -9,37 +9,35 @@ import { DetailPageProvider } from "../../../context/detail-page-provider"; import { server } from "../../../../../__mocks__/server"; import { useLocation } from "react-router-dom"; -beforeAll(() => { - server.listen(); -}); - -beforeEach(() => { - vi.mock("react-router-dom", () => ({ - ...vi.importActual("react-router-dom"), - useLocation: vi.fn(), - })); - - vi.mocked(useLocation).mockReturnValue({ - state: null, - hash: "111", - key: "default", - pathname: "/details", - search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", +describe("MetadataInformationPanel", async () => { + const theme = AppTheme; + beforeAll(() => { + server.listen(); }); -}); -afterEach(() => { - cleanup(); - server.resetHandlers(); - vi.restoreAllMocks(); -}); -afterAll(() => { - server.close(); -}); + beforeEach(() => { + vi.mock("react-router-dom", () => ({ + ...vi.importActual("react-router-dom"), + useLocation: vi.fn(), + })); -describe("MetadataInformationPanel", async () => { - const theme = AppTheme; + vi.mocked(useLocation).mockReturnValue({ + state: null, + hash: "111", + key: "default", + pathname: "/details", + search: "?uuid=5fc91100-4ade-11dc-8f56-00008a07204e", + }); + }); + afterEach(() => { + cleanup(); + server.resetHandlers(); + vi.restoreAllMocks(); + }); + afterAll(() => { + server.close(); + }); beforeEach(() => { render( diff --git a/src/pages/search-page/SearchPage.tsx b/src/pages/search-page/SearchPage.tsx index 2bc9b8ec..c9b11710 100644 --- a/src/pages/search-page/SearchPage.tsx +++ b/src/pages/search-page/SearchPage.tsx @@ -45,10 +45,8 @@ import { OGCCollections, } from "../../components/common/store/OGCCollectionDefinitions"; import { useAppDispatch } from "../../components/common/store/hooks"; -import useTabNavigation from "../../hooks/useTabNavigation"; const SEARCH_BAR_HEIGHT = 56; -const RESULT_SECTION_WIDTH = 500; const isLoading = (count: number): boolean => { if (count > 0) { @@ -69,7 +67,6 @@ const SearchPage = () => { const location = useLocation(); const navigate = useNavigate(); const dispatch = useAppDispatch(); - const navigateToTabAndSection = useTabNavigation(); // Layers contains record with uuid and bbox only const [layers, setLayers] = useState>([]); const [visibility, setVisibility] = useState( @@ -252,15 +249,6 @@ const SearchPage = () => { } }, [location, dispatch, doSearch]); - const handleNavigateToDetailPage = useCallback( - (uuid: string) => { - const searchParams = new URLSearchParams(); - searchParams.append("uuid", uuid); - navigate(pageDefault.details + "?" + searchParams.toString()); - }, - [navigate] - ); - const onChangeSorting = useCallback( (v: SortResultEnum) => { switch (v) { @@ -342,17 +330,10 @@ const SearchPage = () => { {visibility === SearchResultLayoutEnum.VISIBLE && ( @@ -370,7 +351,6 @@ const SearchPage = () => { onToggleClicked={onToggleDisplay} onDatasetSelected={handleDatasetSelecting} isLoading={isLoading(loadingThreadCount)} - onNavigateToDetail={handleNavigateToDetailPage} /> diff --git a/src/pages/search-page/__test__/SearchPage.test.tsx b/src/pages/search-page/__test__/SearchPage.test.tsx index 42addfe6..2e4f1c85 100644 --- a/src/pages/search-page/__test__/SearchPage.test.tsx +++ b/src/pages/search-page/__test__/SearchPage.test.tsx @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, expect, describe, vi } from "vitest"; +import { afterAll, beforeAll, describe, expect, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; import { server } from "../../../__mocks__/server"; @@ -8,7 +8,6 @@ import { Provider } from "react-redux"; import AppTheme from "../../../utils/AppTheme"; import SearchPage from "../SearchPage"; import { BrowserRouter as Router } from "react-router-dom"; -import _ from "lodash"; const theme = AppTheme; @@ -20,27 +19,26 @@ vi.mock("../../../components/map/mapbox/Map", () => { }; }); -beforeAll(() => { - // With use of AutoSizer component in ResultCard, it will fail in non-UI env like vitest - // here we mock it so to give some screen size to let the test work. - vi.mock("react-virtualized-auto-sizer", () => { - return { - __esModule: true, - default: ({ - children, - }: { - children: (size: { width: number; height: number }) => JSX.Element; - }) => children({ width: 800, height: 600 }), // Provide fixed dimensions - }; +describe("SearchPage", () => { + beforeAll(() => { + // With use of AutoSizer component in ResultCard, it will fail in non-UI env like vitest + // here we mock it so to give some screen size to let the test work. + vi.mock("react-virtualized-auto-sizer", () => { + return { + __esModule: true, + default: ({ + children, + }: { + children: (size: { width: number; height: number }) => JSX.Element; + }) => children({ width: 800, height: 600 }), // Provide fixed dimensions + }; + }); + server.listen(); }); - server.listen(); -}); - -afterAll(() => { - server.close(); -}); -describe("SearchPage", () => { + afterAll(() => { + server.close(); + }); it("The map should be able to expand properly", async () => { const user = userEvent.setup(); render( @@ -119,7 +117,7 @@ describe("SearchPage", () => { it("Change sort order load correct record", async () => { const user = userEvent.setup(); - const { findByTestId, findAllByTestId } = render( + const { findByTestId } = render( diff --git a/src/pages/search-page/subpages/MapSection.tsx b/src/pages/search-page/subpages/MapSection.tsx index d6d918e1..82564922 100644 --- a/src/pages/search-page/subpages/MapSection.tsx +++ b/src/pages/search-page/subpages/MapSection.tsx @@ -34,7 +34,6 @@ interface MapSectionProps { ) => void; onToggleClicked: (v: boolean) => void; onDatasetSelected?: (uuids: Array) => void; - onNavigateToDetail?: (uuid: string) => void; isLoading: boolean; } @@ -47,7 +46,6 @@ const MapSection: React.FC = ({ showFullMap, sx, selectedUuids, - onNavigateToDetail, isLoading, }) => { const [selectedLayer, setSelectedLayer] = useState("heatmap"); @@ -63,7 +61,6 @@ const MapSection: React.FC = ({ selectedUuids={selectedUuids} showFullMap={showFullMap} onDatasetSelected={onDatasetSelected} - onNavigateToDetail={onNavigateToDetail} /> ); @@ -74,18 +71,11 @@ const MapSection: React.FC = ({ selectedUuids={selectedUuids} showFullMap={showFullMap} onDatasetSelected={onDatasetSelected} - onNavigateToDetail={onNavigateToDetail} /> ); } }, - [ - collections, - onDatasetSelected, - onNavigateToDetail, - selectedUuids, - showFullMap, - ] + [collections, onDatasetSelected, selectedUuids, showFullMap] ); return ( diff --git a/src/pages/search-page/subpages/ResultSection.tsx b/src/pages/search-page/subpages/ResultSection.tsx index 08033b1f..77c740d7 100644 --- a/src/pages/search-page/subpages/ResultSection.tsx +++ b/src/pages/search-page/subpages/ResultSection.tsx @@ -1,69 +1,43 @@ import ResultPanelSimpleFilter from "../../../components/common/filters/ResultPanelSimpleFilter"; import { Box } from "@mui/material"; -import { - CollectionsQueryType, - createSearchParamFrom, - DEFAULT_SEARCH_PAGE, - fetchResultAppendStore, -} from "../../../components/common/store/searchReducer"; +import { CollectionsQueryType } from "../../../components/common/store/searchReducer"; import { FC, useCallback, useState } from "react"; -import ResultCards, { - ResultCardsList, -} from "../../../components/result/ResultCards"; +import ResultCards from "../../../components/result/ResultCards"; import { useSelector } from "react-redux"; -import store, { - getComponentState, +import { RootState, searchQueryResult, } from "../../../components/common/store/store"; -import { ParameterState } from "../../../components/common/store/componentParamReducer"; import { SortResultEnum } from "../../../components/common/buttons/ResultListSortButton"; import { SearchResultLayoutEnum } from "../../../components/common/buttons/MapViewButton"; import CircleLoader from "../../../components/loading/CircleLoader"; -import { useAppDispatch } from "../../../components/common/store/hooks"; +import { OGCCollection } from "../../../components/common/store/OGCCollectionDefinitions"; -interface ResultSectionProps extends Partial { +interface ResultSectionProps { onVisibilityChanged?: (v: SearchResultLayoutEnum) => void; onChangeSorting: (v: SortResultEnum) => void; isLoading: boolean; + onClickCard?: (uuid: string) => void; + datasetsSelected?: OGCCollection[]; } +const RESULT_SECTION_WIDTH = 500; + const ResultSection: FC = ({ datasetsSelected, - sx, onVisibilityChanged, onChangeSorting, onClickCard, - onDetail, - onDownload, - onLink, isLoading, }) => { - // Get contents from redux - const dispatch = useAppDispatch(); const reduxContents = useSelector( searchQueryResult ); - // Use to remember last layout, it is either LIST or GRID at the moment const [currentLayout, setCurrentLayout] = useState< SearchResultLayoutEnum.LIST | SearchResultLayoutEnum.GRID >(SearchResultLayoutEnum.LIST); - const fetchMore = useCallback(async () => { - // This is very specific to how elastic works and then how to construct the query - const componentParam: ParameterState = getComponentState(store.getState()); - // Use standard param to get fields you need, record is stored in redux, - // set page so that it return fewer records and append the search_after - // to go the next batch of record. - const paramPaged = createSearchParamFrom(componentParam, { - pagesize: DEFAULT_SEARCH_PAGE, - searchafter: reduxContents.result.search_after, - }); - // Must use await so that record updated before you exit this call - await dispatch(fetchResultAppendStore(paramPaged)); - }, [dispatch, reduxContents.result.search_after]); - const onChangeLayout = useCallback( (layout: SearchResultLayoutEnum) => { if ( @@ -86,7 +60,8 @@ const ResultSection: FC = ({ reduxContents && ( = ({