Skip to content
Closed
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
203 changes: 111 additions & 92 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
"bulma": "^1.0.1",
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-router-dom": "^7.13.0"
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
"@mate-academy/scripts": "^1.8.5",
"@mate-academy/scripts": "^2.1.3",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/classnames": "^2.3.0",
"@types/node": "^20.14.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
53 changes: 21 additions & 32 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';

import '@fortawesome/fontawesome-free/css/all.css';
import 'bulma/css/bulma.css';
import './App.scss';

// import peopleFromServer from './people.json';
import { Header } from './components/Header';
import { HomePage } from './pages/HomePage';
import { PeoplePage } from './pages/PeoplePage';
import { NotFoundPage } from './pages/NotFoundPage';

export class App extends React.Component {
state = {};
export const App: React.FC = () => {
return (
<div className="box">
<Header />

render() {
return (
<div className="box">
<h1 className="title">People table</h1>
<Routes>
<Route path="/" element={<HomePage />} />

<table className="table is-striped is-narrow">
<thead>
<tr>
<th>name</th>
<th>sex</th>
<th>born</th>
</tr>
</thead>
<Route path="/home" element={<Navigate to="/" replace />} />

<tbody>
<tr>
<td>Carolus Haverbeke</td>
<td>m</td>
<td>1832</td>
</tr>
<Route path="/people">
<Route index element={<PeoplePage />} />
<Route path=":slug" element={<PeoplePage />} />
</Route>

<tr>
<td>Emma de Milliano</td>
<td>f</td>
<td>1842</td>
</tr>
</tbody>
</table>
</div>
);
}
}
<Route path="*" element={<NotFoundPage />} />
</Routes>
</div>
);
};
24 changes: 24 additions & 0 deletions src/api/people.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Person, PersonWithParents } from '../types/Person';

const API_URL =
'https://mate-academy.github.io/react_people-table/api/people.json';

export async function getPeople(): Promise<PersonWithParents[]> {
const response = await fetch(API_URL);

if (!response.ok) {
throw new Error('Failed to load people');
}

const people: Person[] = await response.json();

const byName = new Map<string, Person>();

people.forEach(p => byName.set(p.name, p));

return people.map(person => ({
...person,
mother: person.motherName ? (byName.get(person.motherName) ?? null) : null,
father: person.fatherName ? (byName.get(person.fatherName) ?? null) : null,
}));
}
18 changes: 18 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NavLink } from 'react-router-dom';

export const Header = () => (
<header className="mb-4">
<nav className="tabs is-boxed">
<ul>
<li>
<NavLink to="/" end>
Home
</NavLink>
</li>
<li>
<NavLink to="/people">People</NavLink>
</li>
</ul>
</nav>
</header>
);
25 changes: 25 additions & 0 deletions src/components/PeopleTable/PeopleTable.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.PeopleTable {
border-collapse: collapse;
}

.PeopleTable th.is-active {
font-weight: 700;
text-decoration: underline;
}

.Person.is-selected {
background-color: #fff3c4; // any visible highlight
}

.PersonName--m {
color: blue;
}

.PersonName--f {
color: red;
}

.PersonName--plain {
color: black;
font-weight: 700;
}
91 changes: 91 additions & 0 deletions src/components/PeopleTable/PeopleTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { PersonWithParents } from '../../types/Person';
import { PersonRow } from './PersonRow';
import './PeopleTable.scss';

type SortBy = 'name' | 'sex' | 'born' | 'died';
type SortOrder = 'asc' | 'desc';

Check failure on line 6 in src/components/PeopleTable/PeopleTable.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

'SortOrder' is defined but never used

type Props = {
people: PersonWithParents[];
selectedSlug: string | undefined;
sortBy: string | null;
sortOrder: string | null;
onSort: (field: SortBy) => void;
};

const SORTABLE: SortBy[] = ['name', 'sex', 'born', 'died'];

Check failure on line 16 in src/components/PeopleTable/PeopleTable.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

'SORTABLE' is assigned a value but never used

export const PeopleTable: React.FC<Props> = ({
people,
selectedSlug,
sortBy,
sortOrder,
onSort,
}) => {
const renderSortIcon = (field: SortBy) => {
if (sortBy !== field) {
return <img src="/react_people-table/images/sort_both.png" alt="sort" />;
}

return sortOrder === 'desc' ? (
<img src="/react_people-table/images/sort_desc.png" alt="desc" />
) : (
<img src="/react_people-table/images/sort_asc.png" alt="asc" />
);
};

const isActive = (field: SortBy) => sortBy === field;

return (
<table className="PeopleTable table is-striped is-narrow is-fullwidth">
<thead>
<tr>
<th
className={isActive('name') ? 'is-active' : ''}
onClick={() => onSort('name')}
style={{ cursor: 'pointer' }}
>
name {renderSortIcon('name')}
</th>

<th
className={isActive('sex') ? 'is-active' : ''}
onClick={() => onSort('sex')}
style={{ cursor: 'pointer' }}
>
sex {renderSortIcon('sex')}
</th>

<th
className={isActive('born') ? 'is-active' : ''}
onClick={() => onSort('born')}
style={{ cursor: 'pointer' }}
>
born {renderSortIcon('born')}
</th>

<th
className={isActive('died') ? 'is-active' : ''}
onClick={() => onSort('died')}
style={{ cursor: 'pointer' }}
>
died {renderSortIcon('died')}
</th>

<th>mother</th>
<th>father</th>
</tr>
</thead>

<tbody>
{people.map(person => (
<PersonRow
key={person.slug}
person={person}
isSelected={selectedSlug === person.slug}
/>
))}
</tbody>
</table>
);
};
28 changes: 28 additions & 0 deletions src/components/PeopleTable/PersonName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import classNames from 'classnames';
import { Link, useLocation } from 'react-router-dom';
import { Person } from '../../types/Person';

type Props = {
person?: Person | null;
nameFallback?: string; // used when parent not found
};

export const PersonName: React.FC<Props> = ({ person, nameFallback }) => {
const location = useLocation();

if (!person) {
return <span className="PersonName--plain">{nameFallback ?? ''}</span>;
}

return (
<Link
to={`/people/${person.slug}${location.search}`}
className={classNames({
'PersonName--m': person.sex === 'm',
'PersonName--f': person.sex === 'f',
})}
>
{person.name}
</Link>
);
};
35 changes: 35 additions & 0 deletions src/components/PeopleTable/PersonRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import classNames from 'classnames';
import { PersonWithParents } from '../../types/Person';
import { PersonName } from './PersonName';

type Props = {
person: PersonWithParents;
isSelected: boolean;
};

export const PersonRow: React.FC<Props> = ({ person, isSelected }) => (
<tr className={classNames('Person', { 'is-selected': isSelected })}>
<td>
<PersonName person={person} />
</td>
<td>{person.sex}</td>
<td>{person.born}</td>
<td>{person.died}</td>

<td>
{person.mother ? (
<PersonName person={person.mother} />
) : (
<PersonName person={null} nameFallback={person.motherName ?? ''} />
)}
</td>

<td>
{person.father ? (
<PersonName person={person.father} />
) : (
<PersonName person={null} nameFallback={person.fatherName ?? ''} />
)}
</td>
</tr>
);
1 change: 1 addition & 0 deletions src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const HomePage = () => <h1 className="title">Home page</h1>;
1 change: 1 addition & 0 deletions src/pages/NotFoundPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NotFoundPage = () => <h1 className="title">Page not found</h1>;
Loading
Loading