diff --git a/src/App.tsx b/src/App.tsx index adcb8594e..5527a887a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ -import { PeoplePage } from './components/PeoplePage'; -import { Navbar } from './components/Navbar'; - +import { Outlet } from 'react-router-dom'; import './App.scss'; +import { Navbar } from './components/Navbar/Navbar'; + export const App = () => { return (
@@ -10,9 +10,7 @@ export const App = () => {
-

Home Page

-

Page not found

- +
diff --git a/src/Root.tsx b/src/Root.tsx new file mode 100644 index 000000000..163b181d8 --- /dev/null +++ b/src/Root.tsx @@ -0,0 +1,26 @@ +import { + Navigate, + Route, + HashRouter as Router, + Routes, +} from 'react-router-dom'; + +import { App } from './App'; +import { HomePage } from './pages/HomePage'; +import { PeoplePage } from './pages/PeoplePage'; +import { NotFoundPage } from './pages/NotFoundPage'; + +export const Root = () => ( + + + }> + } /> + } /> + + } /> + + } /> + + + +); diff --git a/src/api.ts b/src/api.ts index c799527b8..db5820724 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,15 +1,94 @@ +import { FilterType } from './types/FilterType'; import { Person } from './types/Person'; - -// eslint-disable-next-line max-len -const API_URL = 'https://mate-academy.github.io/react_people-table/api/people.json'; +import { SortParam } from './types/SortParam'; +import { getParent } from './utils/getParentHelper'; +import { API_URL, YEARS_IN_CENTURY } from './utils/variables'; function wait(delay: number) { return new Promise(resolve => setTimeout(resolve, delay)); } export async function getPeople(): Promise { - // keep this delay for testing purpose return wait(500) .then(() => fetch(API_URL)) .then(response => response.json()); } + +export function addParent(people: Person[]) { + return people.map(person => { + const { motherName, fatherName } = person; + + return { + ...person, + mother: motherName ? getParent(people, motherName) : undefined, + father: fatherName ? getParent(people, fatherName) : undefined, + }; + }); +} + +export function getSortedPeople( + people: Person[], + sortParam: SortParam | string, +) { + if (sortParam) { + return [...people].sort((a, b) => { + switch (sortParam) { + case (SortParam.Name): + case (SortParam.Sex): { + return a[sortParam].localeCompare(b[sortParam]); + } + + case (SortParam.Born): + case (SortParam.Died): { + return a[sortParam] - (b[sortParam]); + } + + default: { + return 0; + } + } + }); + } + + return [...people]; +} + +const getFilteredPeopleHelper = ( + name: string | null, + query: string, +) => { + return name?.toLowerCase().includes(query); +}; + +export function getFilteredPeople( + filterOption: FilterType, + people: Person[], +) { + let filteredPeople = people; + + if (filterOption.sex) { + filteredPeople = filteredPeople + .filter(person => person.sex === filterOption.sex); + } + + if (filterOption.centuries.length) { + filteredPeople = filteredPeople.filter(person => { + const personBirthCentury = Math.ceil(person.born / YEARS_IN_CENTURY); + + return filterOption.centuries.includes(personBirthCentury.toString()); + }); + } + + if (filterOption.query) { + filteredPeople = filteredPeople + .filter(person => { + const query = filterOption.query.toLowerCase(); + + return getFilteredPeopleHelper(person.name, query) + || getFilteredPeopleHelper(person.motherName, query) + || getFilteredPeopleHelper(person.fatherName, query); + }); + } + + return filteredPeople; +} diff --git a/src/components/ColumnNameItem/ColumnNameItem.tsx b/src/components/ColumnNameItem/ColumnNameItem.tsx new file mode 100644 index 000000000..8e25da3e5 --- /dev/null +++ b/src/components/ColumnNameItem/ColumnNameItem.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; + +import { SearchLink } from '../SearchLink'; +import { ColumnNames } from '../../types'; + +type Props = { + value: keyof typeof ColumnNames, +}; + +export const ColumnName: React.FC = ({ value }) => { + const [searchParams] = useSearchParams(); + + const lowerCasedValue = value.toLowerCase(); + + const sort = searchParams.get('sort') || ''; + const order = searchParams.get('order') || ''; + + const getSortParams = (sortOption: string) => { + if (sortOption !== sort) { + return { sort: sortOption, order: null }; + } + + if (sort === sortOption && !order) { + return { sort: sortOption, order: 'desc' }; + } + + return { sort: null, order: null }; + }; + + const hasFaSort = lowerCasedValue !== sort; + const hasFaSortUp = lowerCasedValue === sort && !order; + const hasFaSortDown = order && lowerCasedValue === sort; + + return ( + + + {value} + + + + + + + + ); +}; diff --git a/src/components/ColumnNameItem/index.ts b/src/components/ColumnNameItem/index.ts new file mode 100644 index 000000000..2150f01d0 --- /dev/null +++ b/src/components/ColumnNameItem/index.ts @@ -0,0 +1 @@ +export * from './ColumnNameItem'; diff --git a/src/components/FilterCenturyItem/FilterCenturyItem.tsx b/src/components/FilterCenturyItem/FilterCenturyItem.tsx new file mode 100644 index 000000000..64997b6b6 --- /dev/null +++ b/src/components/FilterCenturyItem/FilterCenturyItem.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; +import { SearchLink } from '../SearchLink'; + +type Props = { + value: string, +}; + +export const CenturyItem: React.FC = ({ value }) => { + const [searchParams] = useSearchParams(); + const centuries = searchParams.getAll('centuries') || []; + const isSelected = centuries.includes(value); + + const toggleCentury = (curCentury: string) => { + return centuries.includes(curCentury) + ? centuries.filter((age: string) => age !== curCentury) + : [...centuries, curCentury]; + }; + + return ( + + {value} + + ); +}; diff --git a/src/components/FilterCenturyItem/index.ts b/src/components/FilterCenturyItem/index.ts new file mode 100644 index 000000000..70f0e0f32 --- /dev/null +++ b/src/components/FilterCenturyItem/index.ts @@ -0,0 +1 @@ +export * from './FilterCenturyItem'; diff --git a/src/components/FilterSexItem/FilterSexItem.tsx b/src/components/FilterSexItem/FilterSexItem.tsx new file mode 100644 index 000000000..05cd5725b --- /dev/null +++ b/src/components/FilterSexItem/FilterSexItem.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; + +import { SearchLink } from '../SearchLink'; +import { PersonSex } from '../../types'; + +type Props = { + sexValue: keyof typeof PersonSex, +}; + +export const PersonSexItem: React.FC = ({ sexValue }) => { + const [searchParams] = useSearchParams(); + const sex = searchParams.get('sex') || ''; + let sexParam: string | null = PersonSex[sexValue]; + let isActive = sex === sexParam; + + if (sexValue === 'All') { + sexParam = null; + isActive = !sex; + } + + return ( + + {sexValue} + + ); +}; diff --git a/src/components/FilterSexItem/index.ts b/src/components/FilterSexItem/index.ts new file mode 100644 index 000000000..c746a4e92 --- /dev/null +++ b/src/components/FilterSexItem/index.ts @@ -0,0 +1 @@ +export * from './FilterSexItem'; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx deleted file mode 100644 index c2aa20f1c..000000000 --- a/src/components/Navbar.tsx +++ /dev/null @@ -1,24 +0,0 @@ -export const Navbar = () => { - return ( - - ); -}; diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx new file mode 100644 index 000000000..25a498bf5 --- /dev/null +++ b/src/components/Navbar/Navbar.tsx @@ -0,0 +1,39 @@ +import { NavLink, useSearchParams } from 'react-router-dom'; +import cn from 'classnames'; + +export const Navbar = () => { + const [searchParams] = useSearchParams(); + const getLinkClass = ({ isActive }: { isActive: boolean }) => cn( + 'navbar-item', + { 'has-background-grey-lighter': isActive }, + ); + + return ( + + ); +}; diff --git a/src/components/Navbar/index.ts b/src/components/Navbar/index.ts new file mode 100644 index 000000000..e8a656233 --- /dev/null +++ b/src/components/Navbar/index.ts @@ -0,0 +1 @@ +export * from './Navbar'; diff --git a/src/components/PeopleFilters.tsx b/src/components/PeopleFilters.tsx deleted file mode 100644 index 2f1608bef..000000000 --- a/src/components/PeopleFilters.tsx +++ /dev/null @@ -1,93 +0,0 @@ -export const PeopleFilters = () => { - return ( -