Skip to content

Commit

Permalink
ui: refactor search page and actions
Browse files Browse the repository at this point in the history
Signed-off-by: pamfilos <[email protected]>
  • Loading branch information
pamfilos committed Apr 5, 2024
1 parent 936bcd6 commit 31f1511
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 127 deletions.
45 changes: 30 additions & 15 deletions ui/src/components/search/CheckboxFacet.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,47 @@
/* 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";
import { Country, Journal, Params } from "@/types";
import { Country, Journal } from "@/types";

interface CheckboxFacetProps {
type: "country" | "journal";
title: string;
params: Params;
data: Country[] | Journal[];
}

const CheckboxFacet: React.FC<CheckboxFacetProps> = ({
type,
title,
params,
data,
}) => {
const [filters, setFilters] = useState<any[]>([]);
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);
params.delete("page");

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<string, string> = {
Expand All @@ -46,12 +57,16 @@ const CheckboxFacet: React.FC<CheckboxFacetProps> = ({
};

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.push(pathname + '?' + createQueryString(type, updated_filters))
};

return (
Expand Down
12 changes: 9 additions & 3 deletions ui/src/components/search/SearchPagination.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { Pagination } from "antd";
import { useRouter } from "next/navigation";
import { usePathname, useRouter, useSearchParams } from "next/navigation";

import { getSearchUrl } from "@/utils/utils";
import { Params } from "@/types";
Expand All @@ -12,9 +12,15 @@ interface SearchPagination {

const SearchPagination: React.FC<SearchPagination> = ({ count, params }) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const currentPage = searchParams.get("page") || 1

const onPageChange = (page: number) => {
router.push(getSearchUrl({ ...params, page }));
const params = new URLSearchParams(searchParams)
params.set("page", `${page}`);

router.push(pathname + (params.toString() ? `?${params.toString()}` : ''));
};

return (
Expand All @@ -24,7 +30,7 @@ const SearchPagination: React.FC<SearchPagination> = ({ count, params }) => {
total={count}
onChange={(page) => onPageChange(page)}
showSizeChanger={false}
current={Number(params?.page) || 1}
current={Number(currentPage) || 1}
hideOnSinglePage
className="md:mb-0 mb-3"
/>
Expand Down
136 changes: 56 additions & 80 deletions ui/src/components/search/YearFacet.tsx
Original file line number Diff line number Diff line change
@@ -1,113 +1,76 @@
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";
import { PublicationYear, YearFacetData } from "@/types";

interface YearFacetProps {
data: PublicationYear[];
params: Params;
}

const YearFacet: React.FC<YearFacetProps> = ({ data, params }) => {
const mapInitialDataToYears = (
initial: PublicationYear[]
): YearFacetData[] => {
return initial?.map((item) => ({
x: new Date(item?.key)?.getFullYear(),
y: item?.doc_count,
}));
};

const YearFacet = ({ data }: any) => {
const [hoveredBar, setHoveredBar] = useState<any>(null);
const [filters, setFilters] = useState<YearFacetData[]>([]);
const [initialData, setInitialData] = useState<YearFacetData[]>([]);
const [initialEndpoints, setInitialEndpoints] = useState<number[]>([]);
const [sliderEndpoints, setSliderEndpoints] = useState<number[]>([]);
const [marks, setMarks] = useState<SliderMarks>(undefined);
const [reset, setReset] = useState<boolean>(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: number[]) => {
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.delete("page");
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));
setSliderEndpoints(data)
const params = createQueryString('publication_year__range', [{x:data[0]}, {x:data[1]}]);
router.push(pathname + (params ? `?${params.toString()}` : ""))
};

const onBarClick = (value: YearFacetData) => {
updateStateAndMarks([value, value]);
setFilters([value, value]);
const params = createQueryString('publication_year__range', [value, value]);
router.push(pathname + (params ? `?${params.toString()}` : ""))
};

const onBarMouseHover = (bar: YearFacetData) => {
Expand All @@ -117,15 +80,25 @@ const YearFacet: React.FC<YearFacetProps> = ({ data, params }) => {
const onBarMouseOut = () => setHoveredBar(null);

const resetFilters = () => {
setReset(!reset);
updateStateAndMarks(initialData);
setFilters(initialData);
const params = new URLSearchParams(searchParams);
params.delete('publication_year__range');
params.delete('page');
router.push(pathname + (params.toString() ? `?${params.toString()}` : ""))
};

let marks: any = {}
filters.map(
(i, idx) => {
marks[`${i.x}`] = {
label: idx == 0 || idx == filters.length-1 ? `${i.x}` : ` `
};
}
);

return (
<Card title="Year" className="search-facets-facet mb-5">
<div>
{!isEqual(initialData, filters) && (
{searchParams.get('publication_year__range') && (
<div className="text-right mb-3">
<Button
onClick={resetFilters}
Expand Down Expand Up @@ -157,10 +130,13 @@ const YearFacet: React.FC<YearFacetProps> = ({ data, params }) => {
</div>
<Slider
range
disabled={Object.keys(marks).length <= 1 }
step={null}
className="year-facet-slider"
onChange={onSliderChange}
onAfterChange={onSliderAfterChange}
value={sliderEndpoints}
defaultValue={initialEndpoints}
min={initialEndpoints[0]}
max={initialEndpoints[1]}
marks={marks}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/shared/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
<>
{!hide && (
<Search
onSearch={() => router.push(getSearchUrl({ search: val }, true))}
onSearch={() => router.push(getSearchUrl({...(val ? {search: val} : {})} , true))}
placeholder={placeholder}
enterButton
className={className}
Expand Down
3 changes: 2 additions & 1 deletion ui/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ const HomePage: React.FC<HomePageProps> = ({ count, facets }) => {

export const getServerSideProps: GetServerSideProps = async () => {
const query = { search: "" };
const res = await fetch(`${getApiUrl() + getSearchUrl(query)}`, authToken);
const url = `${getApiUrl() + getSearchUrl(query)}`;
const res = await fetch(url, authToken);
const { count, facets } = (await res?.json()) as Response;
const countValue = { count: count || 0 };
const facetsValue = { facets: facets || null };
Expand Down
29 changes: 24 additions & 5 deletions ui/src/pages/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SearchResults from "@/components/search/SearchResults";
import YearFacet from "@/components/search/YearFacet";
import { authToken, getApiUrl, getSearchUrl } from "@/utils/utils";
import CheckboxFacet from "@/components/search/CheckboxFacet";
import { encode } from 'querystring'

interface SearchPageProps {
results: Result[];
Expand Down Expand Up @@ -40,7 +41,6 @@ const SearchPage: React.FC<SearchPageProps> = ({
data={countries}
title="Country / Region / Territory"
type={"country"}
params={query}
/>
)}

Expand All @@ -49,7 +49,6 @@ const SearchPage: React.FC<SearchPageProps> = ({
data={journals}
title="Journal"
type={"journal"}
params={query}
/>
)}
</>
Expand All @@ -68,10 +67,30 @@ const SearchPage: React.FC<SearchPageProps> = ({
export const getServerSideProps: GetServerSideProps = async (context) => {
const query = context?.query as unknown as Params;

const res = await fetch(getApiUrl() + getSearchUrl({ ...query }), authToken);
const { results, count, facets } = (await res.json()) as Response;
const searchParams = new URLSearchParams(encode(query))
const params = searchParams ? `?${searchParams}` : "";
const url = getApiUrl() + params
let results = [], count = 0 , facets =[];

return { props: { results, count, query, facets } };
try {
const res = await fetch(url, authToken);

const contentType = res.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") !== -1) {
const data = await res.json();
results = data.results;
count = data.count;
facets = data.facets;
}
} catch (err) {
console.error("Error fetching or parsing data:", err);
}



return {
props: { results, count, query, facets },
};
};

export default SearchPage;
Loading

0 comments on commit 31f1511

Please sign in to comment.