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

Develop #1058

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Develop #1058

Show file tree
Hide file tree
Changes from 5 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
31 changes: 17 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { PeoplePage } from './components/PeoplePage';
import { Navbar } from './components/Navbar';
import React from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { HomePage } from './HomePage/HomePage';
import { PeoplePage } from './PeoplePage/PeoplePage';
import { NotFoundPage } from './NotFoundPage/NotFoundPage';
import { Navigation } from './Navigation/Navigation';

import './App.scss';

export const App = () => {
export const App: React.FC = () => {
return (
<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>
</div>
<Navigation />
<main className="section">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/home" element={<Navigate to="/" replace />} />
<Route path="/people" element={<PeoplePage />} />
<Route path="/people/:slug" element={<PeoplePage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</main>
</div>
);
};
7 changes: 7 additions & 0 deletions src/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

export const HomePage: React.FC = () => (
<div className="section">
<h1 className="title">Home Page</h1>
</div>
);
30 changes: 30 additions & 0 deletions src/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';

export const Navigation: React.FC = () => {
const location = useLocation();
const isHome = location.pathname === '/';
const isPeople = location.pathname.startsWith('/people');

return (
<nav className="navbar is-fixed-top has-shadow" data-cy="nav">
<div className="container">
<div className="navbar-brand">
<Link
to="/"
className={`navbar-item ${isHome ? 'has-background-grey-lighter' : ''}`}
>
Home
</Link>

<Link
to="/people"
className={`navbar-item ${isPeople ? 'has-background-grey-lighter' : ''}`}
>
People
</Link>
</div>
</div>
</nav>
);
};
9 changes: 9 additions & 0 deletions src/NotFoundPage/NotFoundPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export const NotFoundPage: React.FC = () => (
<div className="section">
<div className="container">
<h1 className="title">Page not found</h1>
</div>
</div>
);
108 changes: 108 additions & 0 deletions src/PeopleFilter/PeopleFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';
import { useSearchParams } from 'react-router-dom';
import { SearchLink } from '../components/SearchLink';
import classNames from 'classnames';

export const PeopleFilters = () => {
const [searchParams, setSearchParams] = useSearchParams();

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

function handleQueryChange(event: React.ChangeEvent<HTMLInputElement>) {
const params = new URLSearchParams(searchParams);

if (event.target.value) {
params.set('query', event.target.value);
} else {
params.delete('query');
}

setSearchParams(params);
}

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

<p className="panel-tabs" data-cy="SexFilter">
<SearchLink
params={{ sex: null }}
className={gender === '' ? 'is-active' : ''}
>
All
</SearchLink>
<SearchLink
params={{ sex: 'm' }}
className={gender === 'm' ? 'is-active' : ''}
>
Male
</SearchLink>
<SearchLink
params={{ sex: 'f' }}
className={gender === 'f' ? 'is-active' : ''}
>
Female
</SearchLink>
</p>

<div className="panel-block">
<p className="control has-icons-left">
<input
data-cy="NameFilter"
type="search"
className="input"
placeholder="Search"
value={query}
onChange={handleQueryChange}
/>
<span className="icon is-left">
<i className="fas fa-search" aria-hidden="true" />
</span>
</p>
</div>

<div className="panel-block">
<div className="level is-flex-grow-1 is-mobile" data-cy="CenturyFilter">
<div className="level-left">
{['16', '17', '18', '19', '20'].map(currentCentury => (
<SearchLink
data-cy="century"
className={classNames('button mr-1', {
'is-info': century.includes(currentCentury),
})}
params={{
century: century.includes(currentCentury)
? century.filter(curr => curr !== currentCentury)
: [...century, currentCentury],
}}
key={currentCentury}
>
{currentCentury}
</SearchLink>
))}
</div>
<div className="level-right ml-4">
<SearchLink
data-cy="centuryALL"
className="button is-success is-outlined"
params={{ century: null }}
>
All
</SearchLink>
</div>
</div>
</div>

<div className="panel-block">
<SearchLink
className="button is-link is-outlined is-fullwidth"
params={{ query: null, sex: null, century: null }}
>
Reset all filters
</SearchLink>
</div>
</nav>
);
};
148 changes: 148 additions & 0 deletions src/PeoplePage/PeoplePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { getPeople } from '../api';
import { Loader } from '../components/Loader';
import { PeopleTable } from '../PeopleTable/PeopleTable';
import { Person } from '../types/Person';
import { PeopleFilters } from '../PeopleFilter/PeopleFilter';

export const PeoplePage: React.FC = () => {
const [people, setPeople] = useState<Person[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const { slug } = useParams<{ slug: string }>();
const [searchParams, setSearchParams] = useSearchParams();

// Получаем параметры сортировки из URL
const initialSortField = searchParams.get('sortField') as keyof Person | null;
const initialSortOrder = searchParams.get('sortOrder') as
| 'asc'
| 'desc'
| null;

const [sortField, setSortField] = useState<keyof Person | null>(
initialSortField,
);
const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(
initialSortOrder,
);

useEffect(() => {
getPeople()
.then(data => {
setPeople(data);
setLoading(false);
})
.catch(() => {
setLoading(false);
setError(true);
});
}, []);

if (error) {
return (
<p data-cy="peopleLoadingError" className="has-text-danger">
Something went wrong
</p>
);
}

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

let filteredPeople = people.filter(person => {
const matchesName = (name: string) =>
name.toLowerCase().includes(query.toLowerCase());
const century = Math.floor((person.born + 99) / 100);

return (
(query === '' ||
matchesName(person.name) ||
matchesName(person.motherName || '') ||
matchesName(person.fatherName || '')) &&
(sex === '' || person.sex === sex) &&
(centuries.length === 0 || centuries.includes(century.toString()))
);
});

if (sortField) {
filteredPeople = [...filteredPeople].sort((a, b) => {
const aValue = a[sortField as keyof Person];
const bValue = b[sortField as keyof Person];

if (typeof aValue === 'string' && typeof bValue === 'string') {
return sortOrder === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
}

if (typeof aValue === 'number' && typeof bValue === 'number') {
return sortOrder === 'asc' ? aValue - bValue : bValue - aValue;
}

return 0;
});
}

const handleSort = (field: keyof Person) => {
const newSortOrder =
sortField === field && sortOrder === 'asc' ? 'desc' : 'asc';

setSortField(field);
setSortOrder(newSortOrder);

setSearchParams({
...Object.fromEntries(searchParams.entries()),
sortField: field,
sortOrder: newSortOrder,
});
};

return (
<>
<h1 className="title">People Page</h1>

<div className="block">
<div className="columns is-desktop is-flex-direction-row-reverse">
<div className="column is-7-tablet is-narrow-desktop">
<PeopleFilters />
</div>

<div className="column">
<div className="box table-container">
{loading ? (
<Loader />
) : (
<>
{people.length === 0 ? (
<p data-cy="noPeopleMessage">
There are no people on the server
</p>
) : (
<>
{filteredPeople.length === 0 ? (
<p>
There are no people matching the current search
criteria
</p>
) : (
<PeopleTable
people={filteredPeople}
selectedSlug={slug}
onSort={handleSort}
sortField={sortField}
sortOrder={sortOrder}
/>
)}
</>
)}
</>
)}
</div>
</div>
</div>
</div>
</>
);
};
Loading
Loading