-
Notifications
You must be signed in to change notification settings - Fork 1.7k
solution #1474
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
base: master
Are you sure you want to change the base?
solution #1474
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,67 @@ | ||
| import React from 'react'; | ||
| import './App.css'; | ||
| import { getNumbers } from './utils'; | ||
| import { Pagination } from './components/Pagination'; | ||
| import { useSearchParams } from 'react-router-dom'; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| const items = getNumbers(1, 42).map(n => `Item ${n}`); | ||
| const total = items.length; | ||
|
|
||
| export const App: React.FC = () => { | ||
| const [searchParams, setSearchParams] = useSearchParams(); | ||
|
|
||
| const currentPage = Number(searchParams.get('page')) || 1; | ||
| const perPage = Number(searchParams.get('perPage')) || 5; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better to have validation - for now, can enter |
||
|
|
||
| const startIndex = (currentPage - 1) * perPage; | ||
| const visibleItems = items.slice(startIndex, startIndex + perPage); | ||
|
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. potential bug here, if currentPage > max pages → the array will be empty, need clamp: |
||
|
|
||
| const updateParams = (params: { | ||
| page?: string | number; | ||
| perPage?: string | number; | ||
| }) => { | ||
| const newParams = new URLSearchParams(searchParams); | ||
|
|
||
| Object.entries(params).forEach(([key, value]) => { | ||
| if (value === null) { | ||
| newParams.delete(key); | ||
| } else { | ||
| newParams.set(key, value.toString()); | ||
| } | ||
| }); | ||
|
|
||
| setSearchParams(newParams); | ||
| }; | ||
|
|
||
| const handlePerPageChange = (event: React.ChangeEvent<HTMLSelectElement>) => { | ||
| // При зміні perPage завжди скидаємо на 1 сторінку за умовою | ||
| updateParams({ | ||
| perPage: event.target.value, | ||
| page: 1, | ||
| }); | ||
| }; | ||
|
|
||
| const handlePageChange = (page: number) => { | ||
| updateParams({ page }); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="container"> | ||
| <h1>Items with Pagination</h1> | ||
|
|
||
| <p className="lead" data-cy="info"> | ||
| Page 1 (items 1 - 5 of 42) | ||
| {`Page ${currentPage} (items ${startIndex + 1} - ${Math.min(currentPage * perPage, total)} of ${total})`} | ||
| </p> | ||
|
|
||
| <div className="form-group row"> | ||
| <div className="col-3 col-sm-2 col-xl-1"> | ||
| <select | ||
| data-cy="perPageSelector" | ||
| id="perPageSelector" | ||
| className="form-control"> | ||
| className="form-control" | ||
| value={perPage} | ||
| onChange={handlePerPageChange} | ||
| > | ||
| <option value="3">3</option> | ||
| <option value="5">5</option> | ||
| <option value="10">10</option> | ||
|
|
@@ -32,78 +74,19 @@ export const App: React.FC = () => { | |
| </label> | ||
| </div> | ||
|
|
||
| {/* Move this markup to Pagination */} | ||
| <ul className="pagination"> | ||
| <li className="page-item disabled"> | ||
| <a | ||
| data-cy="prevLink" | ||
| className="page-link" | ||
| href="#prev" | ||
| aria-disabled="true"> | ||
| « | ||
| </a> | ||
| </li> | ||
| <li className="page-item active"> | ||
| <a data-cy="pageLink" className="page-link" href="#1"> | ||
| 1 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#2"> | ||
| 2 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#3"> | ||
| 3 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#4"> | ||
| 4 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#5"> | ||
| 5 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#6"> | ||
| 6 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#7"> | ||
| 7 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#8"> | ||
| 8 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a data-cy="pageLink" className="page-link" href="#9"> | ||
| 9 | ||
| </a> | ||
| </li> | ||
| <li className="page-item"> | ||
| <a | ||
| data-cy="nextLink" | ||
| className="page-link" | ||
| href="#next" | ||
| aria-disabled="false"> | ||
| » | ||
| </a> | ||
| </li> | ||
| </ul> | ||
| <Pagination | ||
| total={total} | ||
| perPage={perPage} | ||
| currentPage={currentPage} | ||
| onPageChange={handlePageChange} | ||
| /> | ||
|
|
||
| <ul> | ||
| <li data-cy="item">Item 1</li> | ||
| <li data-cy="item">Item 2</li> | ||
| <li data-cy="item">Item 3</li> | ||
| <li data-cy="item">Item 4</li> | ||
| <li data-cy="item">Item 5</li> | ||
| {visibleItems.map(item => ( | ||
| <li key={item} data-cy="item"> | ||
| {item} | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,74 @@ | ||
| export const Pagination = () => {}; | ||
| type Props = { | ||
| total: number; | ||
| perPage: number; | ||
| currentPage?: number; | ||
| onPageChange: (page: number) => void; | ||
| }; | ||
|
|
||
| export const Pagination: React.FC<Props> = ({ | ||
| total, | ||
| perPage, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no validation — user can pass any value via URL. |
||
| currentPage = 1, | ||
| onPageChange, | ||
| }) => { | ||
| const totalPages = Math.ceil(total / perPage); | ||
|
|
||
| // Використовуй цю функцію всюди, де є зміна сторінки | ||
| const handlePageClick = (e: React.MouseEvent, newPage: number) => { | ||
| e.preventDefault(); | ||
| if (newPage !== currentPage && newPage >= 1 && newPage <= totalPages) { | ||
| onPageChange(newPage); | ||
| } | ||
| }; | ||
|
|
||
| const pages = Array.from({ length: totalPages }, (_, i) => i + 1); | ||
|
|
||
| return ( | ||
| <ul className="pagination"> | ||
| {/* Кнопка « */} | ||
| <li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}> | ||
| <a | ||
| data-cy="prevLink" | ||
| className="page-link" | ||
| href="#prev" | ||
| aria-disabled={currentPage === 1} | ||
| onClick={e => handlePageClick(e, currentPage - 1)} | ||
| > | ||
| « | ||
| </a> | ||
| </li> | ||
|
|
||
| {/* Список сторінок */} | ||
| {pages.map(page => ( | ||
| <li | ||
| key={page} | ||
| className={`page-item ${currentPage === page ? 'active' : ''}`} | ||
| > | ||
| <a | ||
| data-cy="pageLink" | ||
| className="page-link" | ||
| href={`#${page}`} | ||
| onClick={e => handlePageClick(e, page)} | ||
| > | ||
| {page} | ||
| </a> | ||
| </li> | ||
| ))} | ||
|
|
||
| {/* Кнопка » */} | ||
| <li | ||
| className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`} | ||
| > | ||
| <a | ||
| data-cy="nextLink" | ||
| className="page-link" | ||
| href="#next" | ||
| aria-disabled={currentPage === totalPages} | ||
| onClick={e => handlePageClick(e, currentPage + 1)} | ||
| > | ||
| » | ||
| </a> | ||
| </li> | ||
| </ul> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,11 @@ | ||
| import { createRoot } from 'react-dom/client'; | ||
|
|
||
| import { HashRouter as Router } from 'react-router-dom'; | ||
| import { App } from './App'; | ||
|
|
||
| createRoot(document.getElementById('root') as HTMLElement).render(<App />); | ||
| const container = document.getElementById('root') as HTMLElement; | ||
|
|
||
| createRoot(container).render( | ||
| <Router> | ||
| <App /> | ||
| </Router>, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This component is missing the requirement to integrate with React Router. The task specifies that
pageandperPageshould be stored in the URL as query parameters (e.g.,?page=2&perPage=7) and used to initialize the state when the page loads.