Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: refactor search page and actions #193

Merged
merged 1 commit into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]);

karolina-siemieniuk-morawska marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
router.push(
getSearchUrl({
...params,
page: 1,
publication_year__range: resolveYearQuery(),
pamfilos marked this conversation as resolved.
Show resolved Hide resolved
})
);
}, [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
Loading