@@ -16,8 +65,9 @@ export const PeopleFilters = () => {
type="search"
className="input"
placeholder="Search"
+ value={params.query}
+ onChange={handleQueryChange}
/>
-
@@ -26,67 +76,28 @@ export const PeopleFilters = () => {
-
-
+
{centuries.map(renderCenturyFilter)}
);
diff --git a/src/components/PeoplePage.tsx b/src/components/PeoplePage.tsx
deleted file mode 100644
index 4c8713b16..000000000
--- a/src/components/PeoplePage.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { PeopleFilters } from './PeopleFilters';
-import { Loader } from './Loader';
-import { PeopleTable } from './PeopleTable';
-
-export const PeoplePage = () => {
- return (
- <>
-
People Page
-
-
-
-
-
-
-
-
-
-
Something went wrong
-
-
- There are no people on the server
-
-
-
There are no people matching the current search criteria
-
-
-
-
-
-
- >
- );
-};
diff --git a/src/components/PeopleTable.tsx b/src/components/PeopleTable.tsx
index 8ab5b90f7..e93775685 100644
--- a/src/components/PeopleTable.tsx
+++ b/src/components/PeopleTable.tsx
@@ -1,4 +1,37 @@
-export const PeopleTable = () => {
+import React, { useMemo } from 'react';
+import cn from 'classnames';
+import { useParams, useSearchParams } from 'react-router-dom';
+
+import { Person } from '../types';
+import { PersonLink } from './PersonLink';
+import { SearchLink } from './SearchLink';
+
+type Props = {
+ people: Person[];
+};
+
+type Sort = {
+ sortBy: string | null;
+ order: string | null;
+};
+
+export const PeopleTable: React.FC
= ({ people }) => {
+ const { selectedSlug } = useParams();
+ const [searchParams] = useSearchParams();
+
+ const currentSort: Sort = useMemo(() => {
+ return {
+ sortBy: searchParams.get('sort'),
+ order: searchParams.get('order'),
+ };
+ }, [searchParams]);
+
+ const getParent = (name: string | null): Person | undefined => {
+ return name ? people.find(person => person.name === name) : undefined;
+ };
+
+ const sorts = ['Name', 'Sex', 'Born', 'Died'];
+
return (
);
diff --git a/src/components/PersonLink.tsx b/src/components/PersonLink.tsx
new file mode 100644
index 000000000..ec664b158
--- /dev/null
+++ b/src/components/PersonLink.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { Link, useSearchParams } from 'react-router-dom';
+import cn from 'classnames';
+
+import { Person } from '../types';
+
+type Props = {
+ person?: Person;
+};
+
+export const PersonLink: React.FC = ({ person }) => {
+ const [searchParams] = useSearchParams();
+
+ return (
+
+ {person?.name}
+
+ );
+};
diff --git a/src/index.tsx b/src/index.tsx
index 8cb63aeff..71c713acb 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,14 +1,31 @@
import { createRoot } from 'react-dom/client';
-import { HashRouter as Router } from 'react-router-dom';
+import {
+ HashRouter,
+ Navigate,
+ Route,
+ Routes,
+} from 'react-router-dom';
import 'bulma/css/bulma.css';
import '@fortawesome/fontawesome-free/css/all.css';
import { App } from './App';
+import { HomePage } from './pages/HomePage';
+import { PeoplePage } from './pages/PeoplePage';
+import { NotFoundPage } from './pages/NotFoundPage';
createRoot(document.getElementById('root') as HTMLDivElement)
.render(
-
-
- ,
+
+
+ }>
+ } />
+ } />
+ }>
+
+
+ } />
+
+
+ ,
);
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
new file mode 100644
index 000000000..0b41a51df
--- /dev/null
+++ b/src/pages/HomePage.tsx
@@ -0,0 +1,5 @@
+export const HomePage = () => {
+ return (
+ Home Page
+ );
+};
diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx
new file mode 100644
index 000000000..c38405914
--- /dev/null
+++ b/src/pages/NotFoundPage.tsx
@@ -0,0 +1,5 @@
+export const NotFoundPage = () => {
+ return (
+ Page not found
+ );
+};
diff --git a/src/pages/PeoplePage.tsx b/src/pages/PeoplePage.tsx
new file mode 100644
index 000000000..b77fcef2c
--- /dev/null
+++ b/src/pages/PeoplePage.tsx
@@ -0,0 +1,121 @@
+import { useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+import { getPeople } from '../api';
+import { Person } from '../types';
+import { Loader } from '../components/Loader';
+import { PeopleFilters } from '../components/PeopleFilters';
+import { PeopleTable } from '../components/PeopleTable';
+import { filterPeople } from '../utils/filterPeople';
+import { sortPeople } from '../utils/sortPeople';
+
+const PAGE_TITLE = 'People Page';
+const NO_PEOPLE_MESSAGE = 'There are no people on the server';
+const SOMETHING_WENT_WRONG_MESSAGE = 'Something went wrong';
+
+const PARAM_KEYS = {
+ CENTURY: 'century',
+ SEX: 'sex',
+ QUERY: 'query',
+ SORT: 'sort',
+ ORDER: 'order',
+};
+
+interface Filters {
+ centuries: string[] | null;
+ sex: string | null;
+ query: string | null;
+}
+
+interface Sort {
+ sortBy: string | null;
+ order: string | null;
+}
+
+export const PeoplePage = () => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [people, setPeople] = useState([]);
+ const [error, setError] = useState(false);
+ const [filters, setFilters] = useState({
+ centuries: null,
+ sex: null,
+ query: null,
+ });
+ const [sorts, setSorts] = useState({
+ sortBy: null,
+ order: null,
+ });
+
+ const [searchParams] = useSearchParams();
+
+ useEffect(() => {
+ const initialLoad = async () => {
+ try {
+ const humans = await getPeople();
+
+ setPeople(humans.map(human => ({
+ ...human,
+ mother: humans.find(person => person.name === human.motherName),
+ father: humans.find(person => person.name === human.fatherName),
+ })));
+ } catch (e) {
+ setError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ initialLoad();
+ }, []);
+
+ useEffect(() => {
+ setFilters({
+ centuries: searchParams.getAll(PARAM_KEYS.CENTURY),
+ sex: searchParams.get(PARAM_KEYS.SEX),
+ query: searchParams.get(PARAM_KEYS.QUERY),
+ });
+
+ setSorts({
+ sortBy: searchParams.get(PARAM_KEYS.SORT),
+ order: searchParams.get(PARAM_KEYS.ORDER),
+ });
+ }, [searchParams]);
+
+ const filteredPeople = sortPeople(filterPeople(people, filters), sorts);
+
+ return (
+ <>
+ {PAGE_TITLE}
+
+
+
+ {isLoading ? null : (
+
+ )}
+
+
+
+ {isLoading ?
: (
+ <>
+ {error && (
+
+ {SOMETHING_WENT_WRONG_MESSAGE}
+
+ )}
+
+ {!people.length ? (
+
{NO_PEOPLE_MESSAGE}
+ ) : (
+
+ )}
+ >
+ )}
+
+
+
+
+ >
+ );
+};
diff --git a/src/utils/filterPeople.tsx b/src/utils/filterPeople.tsx
new file mode 100644
index 000000000..0a3c5adff
--- /dev/null
+++ b/src/utils/filterPeople.tsx
@@ -0,0 +1,41 @@
+import { Person } from '../types';
+
+interface Filters {
+ centuries: string[] | null;
+ sex: string | null;
+ query: string | null;
+}
+
+export function filterPeople(people: Person[], filters: Filters) {
+ let peopleCopy = [...people];
+
+ const normalizeQuery = () => filters.query?.toLowerCase().trim() as string;
+
+ const filterByCenturies = (person: Person) => (
+ filters.centuries?.includes(Math.ceil(person.born / 100).toString())
+ );
+
+ const filterBySex = (person: Person) => (
+ person.sex === filters.sex
+ );
+
+ const filterByQuery = (person: Person) => (
+ person.name.toLowerCase().includes(normalizeQuery())
+ || (person.fatherName?.toLowerCase().includes(normalizeQuery())
+ || person.motherName?.toLowerCase().includes(normalizeQuery()))
+ );
+
+ if (filters.centuries?.length !== 0) {
+ peopleCopy = peopleCopy.filter(filterByCenturies);
+ }
+
+ if (filters.sex) {
+ peopleCopy = peopleCopy.filter(filterBySex);
+ }
+
+ if (filters.query) {
+ peopleCopy = peopleCopy.filter(filterByQuery);
+ }
+
+ return peopleCopy;
+}
diff --git a/src/utils/sortPeople.tsx b/src/utils/sortPeople.tsx
new file mode 100644
index 000000000..555fcad71
--- /dev/null
+++ b/src/utils/sortPeople.tsx
@@ -0,0 +1,44 @@
+import { Person } from '../types';
+
+enum SortBy {
+ Died = 'died',
+ Born = 'born',
+ Sex = 'sex',
+ Name = 'name',
+}
+
+interface Sort {
+ sortBy: string | null,
+ order: string | null,
+}
+
+export function sortPeople(people: Person[], sort: Sort) {
+ const peopleCopy = [...people];
+
+ const direction = sort.order ? -1 : 1;
+
+ if (sort.sortBy) {
+ switch (sort.sortBy) {
+ case SortBy.Died:
+ return peopleCopy.sort((person1, person2) => (
+ (person1.died - person2.died) * direction
+ ));
+ case SortBy.Born:
+ return peopleCopy.sort((person1, person2) => (
+ (person1.born - person2.born) * direction
+ ));
+ case SortBy.Sex:
+ return peopleCopy.sort((person1, person2) => (
+ (person1.sex.localeCompare(person2.sex)) * direction
+ ));
+ case SortBy.Name:
+ return peopleCopy.sort((person1, person2) => (
+ (person1.name.localeCompare(person2.name)) * direction
+ ));
+ default:
+ return peopleCopy;
+ }
+ }
+
+ return peopleCopy;
+}