diff --git a/ui/src/components/search/CheckboxFacet.tsx b/ui/src/components/search/CheckboxFacet.tsx index d2b8cc82f..03d3db41d 100644 --- a/ui/src/components/search/CheckboxFacet.tsx +++ b/ui/src/components/search/CheckboxFacet.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import React, { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; +import React, { useState, useEffect, useCallback } from "react"; +import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { Card } from "antd"; import { getSearchUrl } from "@/utils/utils"; @@ -9,28 +9,40 @@ import { Country, Journal, Params } from "@/types"; interface CheckboxFacetProps { type: "country" | "journal"; title: string; - params: Params; data: Country[] | Journal[]; } const CheckboxFacet: React.FC = ({ type, title, - params, data, }) => { const [filters, setFilters] = useState([]); const [showMore, setShowMore] = useState(false); const displayedData = showMore ? data : data?.slice(0, 13); const router = useRouter(); + const searchParams = useSearchParams(); + const pathname = usePathname() - useEffect(() => { - setFilters([params[type]].flat()); - }, []); + const createQueryString = useCallback( + (name: string, value: any) => { + const params = new URLSearchParams(searchParams) + + params.delete(name); + + if (!Array.isArray(value)) value = [value]; + value.forEach((val: string) => { + params.append(name, val); + }); + + return params.toString() + }, + [searchParams] + ) useEffect(() => { - router.push(getSearchUrl({ ...params, page: 1, [type]: filters })); - }, [filters]); + setFilters(searchParams.getAll(type)); + }, []); const shortJournalName = (value: string) => { const journalMapping: Record = { @@ -46,12 +58,16 @@ const CheckboxFacet: React.FC = ({ }; const onCheckboxChange = (value: string) => { + let updated_filters = []; + if (filters.includes(value)) { - const newFilters = filters.filter((item: string) => item !== value); - setFilters(newFilters); + updated_filters = filters.filter((item: string) => item !== value); } else { - setFilters((oldFilters) => [...oldFilters, value]); + updated_filters = [...filters, value] } + + setFilters(updated_filters); + router.replace(pathname + '?' + createQueryString(type, updated_filters)) }; return ( diff --git a/ui/src/components/search/YearFacet.tsx b/ui/src/components/search/YearFacet.tsx index b2e2b1d2e..486f222d7 100644 --- a/ui/src/components/search/YearFacet.tsx +++ b/ui/src/components/search/YearFacet.tsx @@ -1,12 +1,9 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { XYPlot, VerticalBarSeries, Hint } from "react-vis"; import { Button, Card, Slider } from "antd"; -import { SliderMarks } from "antd/es/slider"; -import { useRouter } from "next/navigation"; -import isEqual from "lodash.isequal"; +import { useRouter, usePathname, useSearchParams } from "next/navigation"; import "react-vis/dist/style.css"; -import { getSearchUrl } from "@/utils/utils"; import { PublicationYear, YearFacetData, Params } from "@/types"; interface YearFacetProps { @@ -14,100 +11,67 @@ interface YearFacetProps { params: Params; } +const mapInitialDataToYears = ( + initial: PublicationYear[] +): YearFacetData[] => { + return initial?.map((item) => ({ + x: new Date(item?.key)?.getFullYear(), + y: item?.doc_count, + })); +}; + const YearFacet: React.FC = ({ data, params }) => { const [hoveredBar, setHoveredBar] = useState(null); const [filters, setFilters] = useState([]); - const [initialData, setInitialData] = useState([]); const [initialEndpoints, setInitialEndpoints] = useState([]); const [sliderEndpoints, setSliderEndpoints] = useState([]); - const [marks, setMarks] = useState(undefined); - const [reset, setReset] = useState(false); + const router = useRouter(); + const pathname = usePathname() + const searchParams = useSearchParams(); useEffect(() => { const initialData = mapInitialDataToYears(data); setFilters(initialData); - setInitialData(initialData); setSliderEndpoints(getSliderEndpoints(initialData)); setInitialEndpoints(getSliderEndpoints(initialData)); - setMarks(getMarks(initialData)); - }, []); + }, [data]); - useEffect(() => { - router.push( - getSearchUrl({ - ...params, - page: 1, - publication_year__range: resolveYearQuery(), - }) - ); - }, [filters]); - - const resolveYearQuery = () => { - if (sliderEndpoints[0] === sliderEndpoints[1]) { - return sliderEndpoints[0]?.toString(); + const resolveYearQuery = (range) => { + if (range[0] === range[1]) { + return range[0]?.toString(); } - return sliderEndpoints.join("__"); + return range.join("__"); }; - const mapInitialDataToYears = ( - initial: PublicationYear[] - ): YearFacetData[] => { - return initial?.map((item) => ({ - x: new Date(item?.key)?.getFullYear(), - y: item?.doc_count, - })); - }; + const createQueryString = useCallback( + (name: string, value: any) => { + const params = new URLSearchParams(searchParams) + + params.delete(name); + params.set(name, resolveYearQuery([value[0].x, value[1].x])); + + return params.toString() + }, + [searchParams] + ) const getSliderEndpoints = (initial: YearFacetData[]): number[] => { if (initial.length === 1) return [initial[0]?.x]; return [initial[0]?.x, initial[initial.length - 1]?.x]; }; - const getMarks = (initial: YearFacetData[]): SliderMarks => { - if (initial.length === 1) { - return { - [initial[0]?.x]: [initial[0]?.x], - }; - } - return { - [initial[0]?.x]: [initial[0]?.x], - [initial[initial.length - 1]?.x]: [initial[initial.length - 1]?.x], - }; - }; - - const updateStateAndMarks = (newFilters: YearFacetData[]) => { - setSliderEndpoints(getSliderEndpoints(newFilters)); - setMarks(getMarks(newFilters)); - }; - - const sliderDataToGraphData = (data: number[]) => { - const firstIndex = initialData.findIndex( - (item: YearFacetData) => item.x === data[0] - ); - const lastIndex = initialData.findIndex( - (item: YearFacetData) => item.x === data[data.length - 1] - ); - const range = initialData.slice(firstIndex, lastIndex + 1); - - if (range.length === 1) { - return [range, range].flat(); - } - - return range; - }; - const onSliderChange = (data: number[]) => { - updateStateAndMarks(sliderDataToGraphData(data)); + setSliderEndpoints(data) }; - const onSliderAfterChange = (data: number[]) => { - setFilters(sliderDataToGraphData(data)); + const onSliderChange2 = (data: number[]) => { + setSliderEndpoints(data) + router.replace(pathname + '?' + createQueryString('publication_year__range', [{x:data[0]}, {x:data[1]}])) }; const onBarClick = (value: YearFacetData) => { - updateStateAndMarks([value, value]); - setFilters([value, value]); + router.replace(pathname + '?' + createQueryString('publication_year__range', [value, value])) }; const onBarMouseHover = (bar: YearFacetData) => { @@ -117,15 +81,24 @@ const YearFacet: React.FC = ({ data, params }) => { const onBarMouseOut = () => setHoveredBar(null); const resetFilters = () => { - setReset(!reset); - updateStateAndMarks(initialData); - setFilters(initialData); + const params = new URLSearchParams(searchParams); + params.delete('publication_year__range'); + router.replace(pathname + params.toString()) }; + let marks = {} + filters.map( + (i, idx) => { + marks[`${i.x}`] = { + label: idx == 0 || idx == filters.length-1 ? `${i.x}` : ` ` + }; + } + ); + return (
- {!isEqual(initialData, filters) && ( + {searchParams.get('publication_year__range') && (
= ({ data={countries} title="Country / Region / Territory" type={"country"} - params={query} /> )} @@ -49,7 +48,6 @@ const SearchPage: React.FC = ({ data={journals} title="Journal" type={"journal"} - params={query} /> )} @@ -68,7 +66,8 @@ const SearchPage: React.FC = ({ export const getServerSideProps: GetServerSideProps = async (context) => { const query = context?.query as unknown as Params; - const res = await fetch(getApiUrl() + getSearchUrl({ ...query }), authToken); + const url = getApiUrl() + getSearchUrl(query); + const res = await fetch(url, authToken); const { results, count, facets } = (await res.json()) as Response; return { props: { results, count, query, facets } }; diff --git a/ui/src/utils/utils.tsx b/ui/src/utils/utils.tsx index bdfb1cfc4..d50700d97 100644 --- a/ui/src/utils/utils.tsx +++ b/ui/src/utils/utils.tsx @@ -25,21 +25,18 @@ const isValue = (value: any): boolean => value !== undefined && value !== null && value !== ""; const buildSearchParams = (q: Params): string => { - const query = { ...defaultQueryValues, ...q }; - - const values = Object.entries(query).flatMap(([key, value]) => { - if (queryTypes.includes(key as QueryType)) { - if (Array.isArray(value)) { - return value.filter(isValue).map((v) => `${key}=${v}`); - } else if (isValue(value)) { - return [`${key}=${value}`]; - } + const searchParams = new URLSearchParams(); + + Object.entries(q).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(item => searchParams.append(key, item)); + } else { + searchParams.set(key, value); } - return []; }); - return values.join("&"); -}; + return searchParams.toString(); +} const getSearchUrl = (query: Params, local?: boolean) => { const searchParams = buildSearchParams(query);