Skip to content

Commit

Permalink
Solution
Browse files Browse the repository at this point in the history
  • Loading branch information
Wita-Shchurko committed Dec 5, 2023
1 parent a7bec0e commit 89679fd
Show file tree
Hide file tree
Showing 16 changed files with 523 additions and 777 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ implement the ability to filter and sort people in the table.
- Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline).
- Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript).
- Open one more terminal and run tests with `npm test` to ensure your solution is correct.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://<your_account>.github.io/react_people-table-advanced/) and add it to the PR description.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://Wita-Shchurko.github.io/react_people-table-advanced/) and add it to the PR description.
19 changes: 10 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { PeoplePage } from './components/PeoplePage';
import { Outlet } from 'react-router-dom';
import { Navbar } from './components/Navbar';

import './App.scss';
import { GlobalProvider } from './components/GeneralContext';

export const App = () => {
return (
<div data-cy="app">
<Navbar />
<GlobalProvider>
<div data-cy="app">
<Navbar />

<div className="section">
<div className="container">
<h1 className="title">Home Page</h1>
<h1 className="title">Page not found</h1>
<PeoplePage />
<div className="section">
<div className="container">
<Outlet />
</div>
</div>
</div>
</div>
</GlobalProvider>
);
};
45 changes: 45 additions & 0 deletions src/components/GeneralContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Context } from '../types/Context';
import { Person } from '../types';

const State: Context = {
people: [],
setPeople: () => {},
filteredPeople: [],
setFilteredPeople: () => {},
sortedPeople: [],
setSortedPeople: () => {},
searchParams: new URLSearchParams(),
setSearchParams: () => {},
};

export const GlobalContext = React.createContext<Context>(State);

type Props = {
children: React.ReactNode
};

export const GlobalProvider: React.FC<Props> = ({ children }) => {
const [people, setPeople] = useState<Person[]>([]);
const [filteredPeople, setFilteredPeople] = useState<Person[]>([]);
const [sortedPeople, setSortedPeople] = useState<Person[]>([]);
const [searchParams, setSearchParams] = useSearchParams();

const value = {
people,
setPeople,
filteredPeople,
setFilteredPeople,
searchParams,
setSearchParams,
sortedPeople,
setSortedPeople,
};

return (
<GlobalContext.Provider value={value}>
{children}
</GlobalContext.Provider>
);
};
3 changes: 3 additions & 0 deletions src/components/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const HomePage: React.FC = () => (
<h1 className="title">Home Page</h1>
);
24 changes: 19 additions & 5 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import classNames from 'classnames';
import { NavLink } from 'react-router-dom';

const getLinkClass = ({ isActive }: { isActive: boolean }) => {
return classNames('navbar-item', {
'has-background-grey-lighter': isActive,
});
};

export const Navbar = () => {
return (
<nav
Expand All @@ -8,15 +17,20 @@ export const Navbar = () => {
>
<div className="container">
<div className="navbar-brand">
<a className="navbar-item" href="#/">Home</a>
<NavLink
className={getLinkClass}
to="/"
>
Home
</NavLink>

<a
<NavLink
aria-current="page"
className="navbar-item has-background-grey-lighter"
href="#/people"
className={getLinkClass}
to="/people"
>
People
</a>
</NavLink>
</div>
</div>
</nav>
Expand Down
3 changes: 3 additions & 0 deletions src/components/PageNotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const PageNotFound: React.FC = () => (
<h1 className="title">Page not found</h1>
);
192 changes: 143 additions & 49 deletions src/components/PeopleFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,117 @@
export const PeopleFilters = () => {
import classNames from 'classnames';
import { useEffect, useContext } from 'react';
import { Link } from 'react-router-dom';
import { getSearchWith } from '../utils/searchHelper';
import { areArraysEqual } from '../utils/areArraysEqual';
import { GlobalContext } from './GeneralContext';

const centuriesList = ['16', '17', '18', '19', '20'];

export const PeopleFilters: React.FC = () => {
const {
people,
filteredPeople,
setFilteredPeople,
searchParams,
setSearchParams,
} = useContext(GlobalContext);

const query = searchParams.get('query') || '';
const centuries = searchParams.getAll('centuries') || [];
const sex = searchParams.get('sex') || '';

const isAllCenturiesSelected = centuries.length === 0;

useEffect(() => {
let resultOfFiltration = [...people];

if (query) {
resultOfFiltration = resultOfFiltration.filter(person => {
return person.name.toLowerCase().includes(query.toLowerCase())
|| person.motherName?.toLowerCase().includes(query.toLowerCase())
|| person.fatherName?.toLowerCase().includes(query.toLowerCase());
});
}

if (sex) {
resultOfFiltration
= resultOfFiltration.filter(person => person.sex === sex);
}

if (centuries.length > 0) {
const filtrationCopy = [...resultOfFiltration];

resultOfFiltration = [];

centuries.forEach(century => {
resultOfFiltration = resultOfFiltration.concat(
filtrationCopy.filter(person => person.born < +century * 100
&& person.born >= (+century - 1) * 100),
);
});
}

if (!areArraysEqual(resultOfFiltration, filteredPeople)) {
setFilteredPeople(resultOfFiltration);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [people, query, sex, centuries]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setSearchWith = (params: any) => {
const search = getSearchWith(params, searchParams);

setSearchParams(search);
};

const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchWith({ query: e.target.value || null });
};

return (
<nav className="panel">
<p className="panel-heading">Filters</p>

<p className="panel-tabs" data-cy="SexFilter">
<a className="is-active" href="#/people">All</a>
<a className="" href="#/people?sex=m">Male</a>
<a className="" href="#/people?sex=f">Female</a>
<Link
to={{
search: getSearchWith({ sex: '' || null }, searchParams),
}}
className={classNames({
'is-active': sex === '',
})}
>
All
</Link>

<Link
to={{
search: getSearchWith({ sex: 'm' || null }, searchParams),
}}
className={classNames({
'is-active': sex === 'm',
})}
>
Male
</Link>

<Link
to={{
search: getSearchWith({ sex: 'f' || null }, searchParams),
}}
className={classNames({
'is-active': sex === 'f',
})}
>
Female
</Link>
</p>

<div className="panel-block">
<p className="control has-icons-left">
<input
value={query}
onChange={handleQueryChange}
data-cy="NameFilter"
type="search"
className="input"
Expand All @@ -27,66 +127,60 @@ export const PeopleFilters = () => {
<div className="panel-block">
<div className="level is-flex-grow-1 is-mobile" data-cy="CenturyFilter">
<div className="level-left">
<a
data-cy="century"
className="button mr-1"
href="#/people?centuries=16"
>
16
</a>

<a
data-cy="century"
className="button mr-1 is-info"
href="#/people?centuries=17"
>
17
</a>

<a
data-cy="century"
className="button mr-1 is-info"
href="#/people?centuries=18"
>
18
</a>

<a
data-cy="century"
className="button mr-1 is-info"
href="#/people?centuries=19"
>
19
</a>

<a
data-cy="century"
className="button mr-1"
href="#/people?centuries=20"
>
20
</a>
{centuriesList.map(century => (
<Link
key={century}
to={{
search: getSearchWith({
centuries: centuries.includes(century)
? centuries.filter(newCentury => century !== newCentury)
: [...centuries, century],
}, searchParams),
}}
data-cy="century"
className={classNames(
'button', 'mr-1', {
'is-info': centuries.includes(century),
},
)}
>
{century}
</Link>
))}
</div>

<div className="level-right ml-4">
<a
<Link
to={{
search: getSearchWith({
centuries: null,
}, searchParams),
}}
data-cy="centuryALL"
className="button is-success is-outlined"
href="#/people"
className={classNames('button', 'is-success', {
'is-outlined': !isAllCenturiesSelected,
})}
>
All
</a>
</Link>
</div>
</div>
</div>

<div className="panel-block">
<a
<Link
to={{
search: getSearchWith({
query: null,
centuries: null,
sex: null,
}, searchParams),
}}
className="button is-link is-outlined is-fullwidth"
href="#/people"
>
Reset all filters
</a>
</Link>
</div>
</nav>
);
Expand Down
Loading

0 comments on commit 89679fd

Please sign in to comment.