HM-56. Добавлена страница для просмотра логов с фильтрацией, пагинацией (#20)
This commit is contained in:
15
src/api/StorageLogsAPI.js
Normal file
15
src/api/StorageLogsAPI.js
Normal file
@ -0,0 +1,15 @@
|
||||
import axios from 'axios';
|
||||
import {API_URL, ENDPOINTS} from './consts';
|
||||
|
||||
class StorageLogsApi {
|
||||
URL = `${API_URL}${ENDPOINTS.SERVER_LOGS}`;
|
||||
|
||||
request = async () => {
|
||||
const {data} = await axios.get(this.URL);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
const storageLogsApi = new StorageLogsApi();
|
||||
|
||||
export default storageLogsApi;
|
||||
@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import {API_URL, ENDPOINT, TESTING_HEADERS} from './consts';
|
||||
import {API_URL, ENDPOINTS, TESTING_HEADERS} from './consts';
|
||||
|
||||
/**
|
||||
* @interface Store
|
||||
@ -17,7 +17,7 @@ import {API_URL, ENDPOINT, TESTING_HEADERS} from './consts';
|
||||
* @class
|
||||
*/
|
||||
class StorageServiceApi {
|
||||
URL = `${API_URL}${ENDPOINT}`;
|
||||
URL = `${API_URL}${ENDPOINTS.STORE}`;
|
||||
|
||||
get defaultConfig () {
|
||||
if (location.hostname.includes('localhost')) {
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
|
||||
export const API_URL = 'http://api.storage.vigdorov.ru';
|
||||
|
||||
export const ENDPOINT = '/store';
|
||||
export const ENDPOINTS = {
|
||||
STORE: '/store',
|
||||
CLIENT_LOGS: '/logs/client',
|
||||
SERVER_LOGS: '/logs/server',
|
||||
};
|
||||
|
||||
export const TESTING_HEADERS = {
|
||||
'Api-Name': 'store-service-test',
|
||||
|
||||
45
src/app.html
45
src/app.html
@ -66,6 +66,51 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон универсальной таблицы -->
|
||||
<template id="uni-table">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<!-- Шаблон колонки заголовка универсильной таблицы -->
|
||||
<template id="uni-table-th">
|
||||
<th></th>
|
||||
</template>
|
||||
<!-- Шаблон строки универсальной таблицы -->
|
||||
<template id="uni-table-row">
|
||||
<tr></tr>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон пагинации -->
|
||||
<template id="pagination">
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination justify-content-center"></ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон фильтра страницы logs -->
|
||||
<template id="logs-filters">
|
||||
<form class="row p-3 m-0">
|
||||
<div class="col-md-auto">
|
||||
<div class="row">
|
||||
<div class="mb-3">
|
||||
<label for="logs-filter-message" class="form-label">Найти в сообщениях</label>
|
||||
<input type="text" class="form-control" id="logs-filter-message" placeholder="Введите текст">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary mb-3">Поиск</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон кнопки -->
|
||||
<template id="test-button">
|
||||
<button type="button" class="btn btn-primary">Проверка сборки</button>
|
||||
|
||||
@ -8,6 +8,7 @@ import navMenuButtons from './components/navigation-buttons-component/NavButtonC
|
||||
import {NAV_MENU} from './components/navigation-buttons-component/constants';
|
||||
import routeService from './services/RouteService';
|
||||
import RouterPagesContainer from './components/router-pages-container/index';
|
||||
import LogsPage from './components/logs-page/index';
|
||||
|
||||
navMenuButtons.render(NAV_MENU);
|
||||
|
||||
@ -24,6 +25,7 @@ const routerPagesContainer = new RouterPagesContainer();
|
||||
routerPagesContainer.addRoutes([
|
||||
{url: '/', pageComponent: MainPage},
|
||||
{url: '/api', pageComponent: ApiPage},
|
||||
{url: '/logs', pageComponent: LogsPage},
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@ -43,12 +43,19 @@ class Component extends EmitService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод уничтожения компонента. Удаляет элемент из верстки, снимает обработчики и очищает подписки
|
||||
* Метод удаляет всех слушателей текущего компонента
|
||||
*/
|
||||
destroy = () => {
|
||||
clearListeners = () => {
|
||||
this._listeners.forEach(({element, eventName, listener}) => {
|
||||
element.removeEventListener(eventName, listener);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод уничтожения компонента. Удаляет элемент из верстки, снимает обработчики и очищает подписки
|
||||
*/
|
||||
destroy = () => {
|
||||
this.clearListeners();
|
||||
this.mainNode.remove();
|
||||
this._listeners = [];
|
||||
this.clearSubscribes();
|
||||
|
||||
25
src/components/logs-filters/LogsFilters.js
Normal file
25
src/components/logs-filters/LogsFilters.js
Normal file
@ -0,0 +1,25 @@
|
||||
import Component from '../component/index';
|
||||
import routeService from '../../services/RouteService';
|
||||
|
||||
class LogsFilters extends Component {
|
||||
constructor (parentNode) {
|
||||
super('#logs-filters', parentNode);
|
||||
|
||||
this.messageInput = this.mainNode.querySelector('#logs-filter-message');
|
||||
|
||||
const {message = ''} = routeService.getUrlData().query;
|
||||
|
||||
this.messageInput.value = message;
|
||||
|
||||
this.addEventListener(this.mainNode, 'submit', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
routeService.pushQuery({
|
||||
message: this.messageInput.value,
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default LogsFilters;
|
||||
3
src/components/logs-filters/index.js
Normal file
3
src/components/logs-filters/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import LogsFilters from './LogsFilters';
|
||||
|
||||
export default LogsFilters;
|
||||
83
src/components/logs-page/LogsPage.js
Normal file
83
src/components/logs-page/LogsPage.js
Normal file
@ -0,0 +1,83 @@
|
||||
import Component from '../component/index';
|
||||
import Table from '../table';
|
||||
import storageLogsApi from '../../api/StorageLogsAPI';
|
||||
import Pagination from '../pagination';
|
||||
import LogsFilters from '../logs-filters';
|
||||
import routeService from '../../services/RouteService';
|
||||
import {prepareToNumber} from '../../utils/urlUtils';
|
||||
|
||||
const COLS = [
|
||||
{id: '_id', label: 'id', width: '240px'},
|
||||
{id: 'date', label: 'Дата', width: '150px'},
|
||||
{id: 'type', label: 'Тип', width: '70px'},
|
||||
{id: 'message', label: 'Сообщение', width: '200px'},
|
||||
{id: 'trace', label: 'Стек', width: '200px'},
|
||||
];
|
||||
|
||||
const ELEMENTS_ON_PAGE = 15;
|
||||
|
||||
class LogsPage extends Component {
|
||||
constructor (mainNodeSelector, parentNode) {
|
||||
super(mainNodeSelector, parentNode);
|
||||
|
||||
this.filters = new LogsFilters(this.mainNode);
|
||||
|
||||
this.table = new Table(this.mainNode, COLS);
|
||||
|
||||
this.pagination = new Pagination(this.mainNode);
|
||||
|
||||
routeService.onChange(this.renderTable);
|
||||
|
||||
this.pagination.onPageChange((pageNumber) => {
|
||||
const start = (pageNumber - 1) * ELEMENTS_ON_PAGE;
|
||||
const end = start + ELEMENTS_ON_PAGE;
|
||||
const rows = this.logList.slice(start, end + 1);
|
||||
|
||||
this.table.render(rows);
|
||||
});
|
||||
|
||||
this.loadLogList();
|
||||
}
|
||||
|
||||
loadLogList = async () => {
|
||||
this.logList = await storageLogsApi.request();
|
||||
this.renderTable();
|
||||
}
|
||||
|
||||
filterRows = (queryMessage) => {
|
||||
return this.logList.reduce((memo, row) => {
|
||||
const message = row.message.toLowerCase();
|
||||
const searchMessage = (queryMessage || '').toLowerCase();
|
||||
if (searchMessage === '') {
|
||||
memo.push(row);
|
||||
return memo;
|
||||
}
|
||||
if (message.includes(searchMessage)) {
|
||||
const replaceMessage = new RegExp(searchMessage, 'g');
|
||||
const newText = message.replace(replaceMessage, `<span class="text-warning bg-dark">${searchMessage}</span>`);
|
||||
memo.push({
|
||||
...row,
|
||||
message: newText,
|
||||
});
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
}
|
||||
|
||||
renderTable = () => {
|
||||
const {query} = routeService.getUrlData();
|
||||
const filterRows = this.filterRows(query.message);
|
||||
const countPages = Math.ceil(filterRows.length / ELEMENTS_ON_PAGE);
|
||||
const pageNumber = prepareToNumber(query.pageNumber, countPages);
|
||||
this.pagination.changeCountPages(countPages);
|
||||
|
||||
const start = (pageNumber - 1) * ELEMENTS_ON_PAGE;
|
||||
const end = start + ELEMENTS_ON_PAGE;
|
||||
const rows = filterRows.slice(start, end + 1);
|
||||
|
||||
this.table.render(rows);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default LogsPage;
|
||||
3
src/components/logs-page/index.js
Normal file
3
src/components/logs-page/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import LogsPage from './LogsPage';
|
||||
|
||||
export default LogsPage;
|
||||
@ -8,7 +8,7 @@ export const NAV_MENU = [
|
||||
url: '/api'
|
||||
},
|
||||
{
|
||||
title: 'Еще что-то',
|
||||
url: '/something'
|
||||
title: 'Журнал',
|
||||
url: '/logs'
|
||||
},
|
||||
];
|
||||
|
||||
111
src/components/pagination/Pagination.js
Normal file
111
src/components/pagination/Pagination.js
Normal file
@ -0,0 +1,111 @@
|
||||
import Component from '../component/index';
|
||||
import routeService from '../../services/RouteService';
|
||||
import {createElement} from '../../utils/elementUtils';
|
||||
import {prepareToNumber} from '../../utils/urlUtils';
|
||||
|
||||
const LEFT_ICON = '«';
|
||||
const RIGHT_ICON = '»';
|
||||
|
||||
const CHAGE_PAGE = 'changePage';
|
||||
|
||||
class Pagination extends Component {
|
||||
buttons = [];
|
||||
|
||||
constructor (parentNode) {
|
||||
super('#pagination', parentNode);
|
||||
|
||||
this.currentPage = 0;
|
||||
this.countPages = 0;
|
||||
|
||||
this.container = this.mainNode.querySelector('.pagination');
|
||||
|
||||
routeService.onChange(() => {
|
||||
this.renderButtons();
|
||||
});
|
||||
}
|
||||
|
||||
renderOneButton = (text, isDisabled = false) => {
|
||||
const li = createElement({
|
||||
tagName: 'li',
|
||||
parentNode: this.container,
|
||||
options: {
|
||||
className: 'page-item',
|
||||
disabled: isDisabled,
|
||||
},
|
||||
});
|
||||
const button = createElement({
|
||||
tagName: 'button',
|
||||
parentNode: li,
|
||||
options: {
|
||||
className: 'page-link pe-auto',
|
||||
innerHTML: text,
|
||||
},
|
||||
});
|
||||
if (isDisabled) {
|
||||
li.classList.add('disabled');
|
||||
button.setAttribute('disabled', 'true');
|
||||
}
|
||||
if (text === this.currentPage) {
|
||||
li.classList.add('active');
|
||||
}
|
||||
this.addEventListener(button, 'click', () => {
|
||||
const nextPage = (() => {
|
||||
if (text === LEFT_ICON) {
|
||||
return this.currentPage - 1;
|
||||
}
|
||||
if (text === RIGHT_ICON) {
|
||||
return this.currentPage + 1;
|
||||
}
|
||||
return text;
|
||||
})();
|
||||
|
||||
routeService.pushQuery({
|
||||
pageNumber: nextPage,
|
||||
});
|
||||
});
|
||||
this.buttons.push(li);
|
||||
}
|
||||
|
||||
clearButtons = () => {
|
||||
this.buttons.forEach((b) => {
|
||||
b.remove();
|
||||
});
|
||||
|
||||
this.clearListeners();
|
||||
|
||||
this.buttons = [];
|
||||
}
|
||||
|
||||
changeCountPages = (countPages) => {
|
||||
if (countPages !== this.countPages) {
|
||||
this.countPages = countPages;
|
||||
|
||||
this.renderButtons();
|
||||
}
|
||||
}
|
||||
|
||||
renderButtons = () => {
|
||||
this.clearButtons();
|
||||
const {pageNumber} = routeService.getUrlData().query;
|
||||
this.currentPage = prepareToNumber(pageNumber, this.countPages);
|
||||
|
||||
const start = this.currentPage < 3 ? 1 : this.currentPage - 2;
|
||||
const end = start + 4 > this.countPages ? this.countPages : start + 4;
|
||||
|
||||
this.renderOneButton(LEFT_ICON, this.currentPage <= 1);
|
||||
|
||||
for (let i = start; i <= end; i += 1) {
|
||||
this.renderOneButton(i);
|
||||
}
|
||||
|
||||
this.renderOneButton(RIGHT_ICON, this.currentPage >= this.countPages);
|
||||
|
||||
this.next(CHAGE_PAGE, this.currentPage);
|
||||
}
|
||||
|
||||
onPageChange = (listener) => {
|
||||
this.subscribe(CHAGE_PAGE, listener);
|
||||
}
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
3
src/components/pagination/index.js
Normal file
3
src/components/pagination/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Pagination from './Pagination';
|
||||
|
||||
export default Pagination;
|
||||
13
src/components/table/HeaderCol.js
Normal file
13
src/components/table/HeaderCol.js
Normal file
@ -0,0 +1,13 @@
|
||||
import Component from '../component/index';
|
||||
|
||||
class HeaderCol extends Component {
|
||||
constructor (parentNode, col) {
|
||||
super('#uni-table-th', parentNode);
|
||||
|
||||
this.id = col.id;
|
||||
this.mainNode.textContent = col.label;
|
||||
this.mainNode.style.minWidth = col.width ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
export default HeaderCol;
|
||||
89
src/components/table/Table.js
Normal file
89
src/components/table/Table.js
Normal file
@ -0,0 +1,89 @@
|
||||
import Component from '../component/index';
|
||||
import HeaderCol from './HeaderCol';
|
||||
import TableRow from './TableRow';
|
||||
|
||||
const ROW_CLICK = 'ROW_CLICK';
|
||||
|
||||
/**
|
||||
* @interface Col
|
||||
* @type {Object}
|
||||
* @property {string} id - id колонки, которая соответствует ключу объекта
|
||||
* @property {string} label - читаемое название колонки
|
||||
*/
|
||||
|
||||
/**
|
||||
* Класс создания Таблицы. В нее при создании передаем массив с колонками, а сами строчки рендерим в тот момент,
|
||||
* когда у нас есть на руках сформированные данные
|
||||
* @param {Node} parentNode
|
||||
* @param {Col[]} cols
|
||||
*
|
||||
* @example
|
||||
* const table = new Table(document.body, [
|
||||
* {id: 'key', label: 'Ключ'},
|
||||
* {id: 'value', label: 'Значение'},
|
||||
* {id: 'author', label: 'Автор'},
|
||||
* ]);
|
||||
*
|
||||
* table.render([
|
||||
* {key: 'testApi', value: '', author: 'Tester'},
|
||||
* {key: 'testApi2', value: '', author: 'Tester'},
|
||||
* ]);
|
||||
*/
|
||||
class Table extends Component {
|
||||
constructor (parentNode, cols) {
|
||||
super('#uni-table', parentNode);
|
||||
|
||||
this.theadTr = this.mainNode.querySelector('thead tr');
|
||||
this.tbody = this.mainNode.querySelector('tbody');
|
||||
|
||||
this.cols = cols;
|
||||
this.renderHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод нужен для отрисовки шапки таблицы, вызывается конструктором самостоятельно
|
||||
*/
|
||||
renderHeader = () => {
|
||||
if (this.headerCols) {
|
||||
this.headerCols.forEach((col) => {
|
||||
col.destroy();
|
||||
});
|
||||
}
|
||||
this.headerCols = this.cols.map((col) => new HeaderCol(this.theadTr, col));
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод рендерит строчки таблицы
|
||||
* @param {{[key: string]: string}[]} rows - список строк для рендера
|
||||
*/
|
||||
render = (rows) => {
|
||||
if (this.rows) {
|
||||
this.rows.forEach((row) => {
|
||||
row.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
this.rows = rows.map((row) => {
|
||||
const rowComponent = new TableRow(this.tbody, this.cols, row);
|
||||
rowComponent.onClick((event) => {
|
||||
this.next(ROW_CLICK, event, row);
|
||||
});
|
||||
return rowComponent;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для подписки на клики по строчкам, передает event и данные строчки
|
||||
* @param {Function} listener - слушатель
|
||||
*
|
||||
* @example
|
||||
* table.onRowClick((event, row) => {
|
||||
* // Ваш код
|
||||
* });
|
||||
*/
|
||||
onRowClick = (listener) => {
|
||||
this.subscribe(ROW_CLICK, listener);
|
||||
}
|
||||
}
|
||||
|
||||
export default Table;
|
||||
31
src/components/table/TableRow.js
Normal file
31
src/components/table/TableRow.js
Normal file
@ -0,0 +1,31 @@
|
||||
import Component from '../component/index';
|
||||
import {createElement} from '../../utils/elementUtils';
|
||||
|
||||
const CLICK = 'click';
|
||||
|
||||
class TableRow extends Component {
|
||||
constructor (parentNode, cols, row) {
|
||||
super('#uni-table-row', parentNode);
|
||||
|
||||
cols.forEach((col) => {
|
||||
const cell = createElement({
|
||||
tagName: 'td',
|
||||
parentNode: this.mainNode,
|
||||
options: {
|
||||
className: 'text-break'
|
||||
},
|
||||
});
|
||||
cell.innerHTML = row[col.id];
|
||||
});
|
||||
|
||||
this.mainNode.addEventListener(CLICK, (event) => {
|
||||
this.next(CLICK, event);
|
||||
});
|
||||
}
|
||||
|
||||
onClick = (listener) => {
|
||||
this.subscribe(CLICK, listener);
|
||||
}
|
||||
}
|
||||
|
||||
export default TableRow;
|
||||
3
src/components/table/index.js
Normal file
3
src/components/table/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Table from './Table';
|
||||
|
||||
export default Table;
|
||||
@ -64,7 +64,7 @@ class RouteService extends EmitService {
|
||||
/**
|
||||
* Метод перехода по маршрутам
|
||||
* @param {string} url - принимает маршрут для перехода
|
||||
* @param {query} query - объект с парами ключ-значение для url
|
||||
* @param {Object<string, string>} query - объект с парами ключ-значение для url
|
||||
* @example
|
||||
* // Переход по заданному url
|
||||
* _.goTo('/users', {
|
||||
@ -73,12 +73,33 @@ class RouteService extends EmitService {
|
||||
* });
|
||||
* // Это создаст строку в url - site.ru/users?key=testApi&author=Petrov
|
||||
*/
|
||||
goTo = (url, query) => {
|
||||
const urlWithQuery = makeUrlWithQuery(url, query);
|
||||
goTo = (url, query = {}) => {
|
||||
const prepareQuery = Object.entries(query)
|
||||
.reduce((memo, [key, value]) => {
|
||||
if (value) {
|
||||
memo[key] = value;
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
const urlWithQuery = makeUrlWithQuery(url, prepareQuery);
|
||||
this.history.pushState({}, '', urlWithQuery);
|
||||
this.generateNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет query в url сохраняя текущий url
|
||||
* @param {Object<string, string>} newQuery - объект с новыми query ключами-значениями
|
||||
* @param {boolean} isClear - указать true, чтобы очистить предыдущий query, иначе параметры
|
||||
* смерджатся с приоритетом у нового query
|
||||
*/
|
||||
pushQuery = (newQuery, isClear = false) => {
|
||||
const {url, query} = this.getUrlData();
|
||||
this.goTo(url, {
|
||||
...(isClear ? {} : query),
|
||||
...newQuery,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* С помощью этого метода подписываемся на событие изменения роута.
|
||||
* @param {RouterListener} listener - слушатель для события изменения роута
|
||||
|
||||
34
src/utils/elementUtils.js
Normal file
34
src/utils/elementUtils.js
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @interface CreateElementProps
|
||||
* @type {Object}
|
||||
* @property {string} tagName - имя создаваемого тега
|
||||
* @property {Node} parentNode - родительский Node в который поместить элемент
|
||||
* @property {Object<string, string>} options - опции, которые можно присвоить объекту
|
||||
* @property {Object<string, string>} args - аргументы, которые нужно прикрепить к Node
|
||||
*/
|
||||
|
||||
/**
|
||||
* Функция создания элементов
|
||||
* @function createElement
|
||||
* @param {CreateElementProps} createElementProps - параметры для функции
|
||||
* @returns {Node}
|
||||
*/
|
||||
export const createElement = (createElementProps) => {
|
||||
const {tagName, parentNode, options, args} = createElementProps;
|
||||
const element = document.createElement(tagName);
|
||||
if (options) {
|
||||
Object.entries(options)
|
||||
.forEach(([key, value]) => {
|
||||
element[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
if (args) {
|
||||
Object.entries(args)
|
||||
.forEach(([attr, value]) => {
|
||||
element.setAttribute(attr, value);
|
||||
});
|
||||
}
|
||||
parentNode.appendChild(element);
|
||||
return element;
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
import {stringify} from 'querystring';
|
||||
import toNumber from 'lodash/toNumber';
|
||||
|
||||
/**
|
||||
* Из маршрута и объекта query создает строку для url
|
||||
@ -9,3 +10,20 @@ export const makeUrlWithQuery = (url = '', query = {}) => {
|
||||
const stringQuery = stringify(query);
|
||||
return url + (stringQuery ? `?${stringQuery}` : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Преобразует текстовое значение из url'a в номер страницы. Если не получается, то возвращает 1 страницу.
|
||||
* @param {unknown} number - значение из url'a, которое мы хотим превратить в номер страницы
|
||||
* @param {number} countPages - общее количество страниц
|
||||
*/
|
||||
export const prepareToNumber = (number, countPages) => {
|
||||
const prepare = toNumber(number);
|
||||
const prepareNaN = Number.isNaN(prepare) ? 1 : prepare;
|
||||
if (prepareNaN < 1) {
|
||||
return 1;
|
||||
}
|
||||
if (prepareNaN > countPages) {
|
||||
return countPages;
|
||||
}
|
||||
return prepareNaN;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user