diff --git a/package-lock.json b/package-lock.json index 7c38823..a3b9f94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3423,18 +3423,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/d": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", @@ -11138,12 +11126,6 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, "d": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", diff --git a/src/const.js b/src/const.js index e045e84..570623b 100644 --- a/src/const.js +++ b/src/const.js @@ -61,6 +61,60 @@ const DurationFormat = { MINS: 'mm[M]', }; +const FilterType = { + ANY: 'any', + FUTURE: 'future', + PRESENT: 'present', + PAST: 'past' +}; + +const SortType = { + DAY: 'day', + EVENT: 'event', + TIME: 'time', + PRICE: 'price', + OFFER: 'offer', +}; + +const FilterSettings = { + [FilterType.ANY]: { + label: 'Everything', + defaultSelected: true, + }, + [FilterType.FUTURE]: { label: 'Future' }, + [FilterType.PRESENT]: { label: 'Present' }, + [FilterType.PAST]: { label: 'Past' }, +}; + +const SORTING_COLUMNS = [ + { + type: SortType.DAY, + label: 'Day', + active: true, + defaultSelected: true, + }, + { + type: SortType.EVENT, + label: 'Event', + active: false, + }, + { + type: SortType.TIME, + label: 'Time', + active: true, + }, + { + type: SortType.PRICE, + label: 'Price', + active: true, + }, + { + type: SortType.OFFER, + label: 'Offer', + active: false, + }, +]; + export { POINTS_COUNT, MSEC_IN_SEC, @@ -78,4 +132,8 @@ export { MocksMaxCount, DateFormat, DurationFormat, + FilterType, + FilterSettings, + SortType, + SORTING_COLUMNS, }; diff --git a/src/framework/api-service.js b/src/framework/api-service.js index ef9f928..ff97aee 100644 --- a/src/framework/api-service.js +++ b/src/framework/api-service.js @@ -44,7 +44,7 @@ export default class ApiService { /** * Метод для обработки ответа * @param {Response} response Объект ответа - * @returns {Promise} + * @returns {Promise} */ static parseResponse(response) { return response.json(); diff --git a/src/framework/ui-blocker/ui-blocker.css b/src/framework/ui-blocker/ui-blocker.css index 489756d..6bcc6d5 100644 --- a/src/framework/ui-blocker/ui-blocker.css +++ b/src/framework/ui-blocker/ui-blocker.css @@ -1,11 +1,11 @@ .ui-blocker { display: none; place-content: center; - position: fixed; + position: absolute; top: 0; + right: 0; + bottom: 0; left: 0; - min-width: 100%; - min-height: 100%; z-index: 1000; cursor: wait; background-color: rgba(255, 255, 255, 0.5); diff --git a/src/framework/view/abstract-view.js b/src/framework/view/abstract-view.js index fa2a552..131f2d8 100644 --- a/src/framework/view/abstract-view.js +++ b/src/framework/view/abstract-view.js @@ -14,6 +14,9 @@ export default class AbstractView { /** @type {HTMLElement|null} Элемент представления */ #element = null; + /** @type {Object} Объект с колбэками. Может использоваться для хранения обработчиков событий */ + _callback = {}; + constructor() { if (new.target === AbstractView) { throw new Error('Can\'t instantiate AbstractView, only concrete one.'); diff --git a/src/presenter/filters-presenter.js b/src/presenter/filters-presenter.js index fa959de..bc52099 100644 --- a/src/presenter/filters-presenter.js +++ b/src/presenter/filters-presenter.js @@ -1,14 +1,30 @@ import { render } from '../framework/render.js'; - import FiltersView from '../view/filters-view.js'; +import { FilterSettings } from '../const.js'; +import { filterByType } from '../utils'; const filtersContainer = document.querySelector('.trip-controls__filters'); export default class FiltersPresenter { + #pointsModel = null; + #filters = []; + + constructor({pointsModel}) { + this.#pointsModel = pointsModel; + + this.#filters = Object.entries(filterByType) + .map(([type, filter]) => ({ + ...FilterSettings[type], + type, + disabled: filter(this.#pointsModel.get()).length === 0 + })); + } init() { - render(new FiltersView(), filtersContainer); + render(new FiltersView({ + items: this.#filters + }), filtersContainer); } } diff --git a/src/service/mock-service.js b/src/service/mock-service.js index 85a70b3..1e1785a 100644 --- a/src/service/mock-service.js +++ b/src/service/mock-service.js @@ -58,7 +58,7 @@ export default class MockService { #generatePoints() { return Array.from({ - length: MocksMaxCount.POINTS + length: getRandomPositiveNumber(0, MocksMaxCount.POINTS) }, () => { const type = getRandomArrayElement(EVENT_TYPES); const destination = getRandomArrayElement(this.#destinations); diff --git a/src/utils.js b/src/utils.js index 945dcf0..5a4c397 100644 --- a/src/utils.js +++ b/src/utils.js @@ -5,7 +5,8 @@ import { MSEC_IN_HOUR, MSEC_IN_DAY, DateFormat, - DurationFormat + DurationFormat, + FilterType } from './const.js'; const getRandomArrayElement = (items) => items[Math.floor(Math.random() * items.length)]; @@ -14,7 +15,7 @@ const getRandomPositiveNumber = (min = 0, max = 1) => { const upper = Math.floor(Math.max(min, max)); return Math.floor(lower + Math.random() * (upper - lower + 1)); }; -const getRandomDate = (start = new Date(2022, 0, 1), end = new Date()) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); +const getRandomDate = (start = new Date(2022, 0, 1), end = new Date(2025, 0, 1)) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); const formatDate = (currentDate, format = DateFormat.FULL) => dayjs(currentDate).format(format); const calculateDuration = (dateFrom, dateTo) => { @@ -46,6 +47,17 @@ const incrementCounter = (START_FROM) => { const toCapitalize = (str) => `${str[0].toUpperCase()}${str.slice(1)}`; +const isPointFuture = (point) => dayjs().isBefore(point.dateFrom); +const isPointPresent = (point) => dayjs().isAfter(point.dateFrom) && dayjs().isBefore(point.dateTo); +const isPointPast = (point) => dayjs().isAfter(point.dateTo); + +const filterByType = { + [FilterType.ANY]: (points) => [...points], + [FilterType.FUTURE]: (points) => points.filter((point) => isPointFuture(point)), + [FilterType.PRESENT]: (points) => points.filter((point) => isPointPresent(point)), + [FilterType.PAST]: (points) => points.filter((point) => isPointPast(point)) +}; + export { getRandomArrayElement, getRandomPositiveNumber, @@ -54,4 +66,5 @@ export { calculateDuration, incrementCounter, toCapitalize, + filterByType, }; diff --git a/src/view/create-sort-view.js b/src/view/create-sort-view.js index 2bbeefb..e013206 100644 --- a/src/view/create-sort-view.js +++ b/src/view/create-sort-view.js @@ -1,37 +1,26 @@ -import AbstractView from '../framework/view/abstract-view.js'; +import { SORTING_COLUMNS } from '../const.js'; +import ItemListView from './item-list-view.js'; -function createSortingTemplate() { - return ( - `
-
- - -
- -
- - -
- -
- - -
- -
- - -
+function createSortingItemTemplate(column) { + const { type, label, active, defaultSelected } = column; + return ` +
+ + +
`; +} -
- - -
-
` - ); +function createSortingTemplate() { + return `
+ ${SORTING_COLUMNS.map(createSortingItemTemplate).join('')} +
`; } -export default class SortingView extends AbstractView { +export default class SortingView extends ItemListView { + constructor() { + super({ items: SORTING_COLUMNS }); + } + get template() { return createSortingTemplate(); } diff --git a/src/view/filters-view.js b/src/view/filters-view.js index ee56713..b8611d0 100644 --- a/src/view/filters-view.js +++ b/src/view/filters-view.js @@ -1,35 +1,26 @@ -import AbstractView from '../framework/view/abstract-view.js'; +import ItemListView from './item-list-view.js'; -function createFiltersTemplate() { +function createFilterItemTemplate(filter) { + const { type, label, defaultSelected, disabled } = filter; return ( - `
-
- - -
- -
- - -
- -
- - -
- -
- - -
+ `
+ + +
` + ); +} +function createFiltersTemplate(filters) { + return ( + ` + ${filters.map(createFilterItemTemplate).join('')}
` ); } -export default class FiltersView extends AbstractView { +export default class FiltersView extends ItemListView { get template() { - return createFiltersTemplate(); + return createFiltersTemplate(this._items); } } diff --git a/src/view/item-list-view.js b/src/view/item-list-view.js new file mode 100644 index 0000000..4ed69ae --- /dev/null +++ b/src/view/item-list-view.js @@ -0,0 +1,21 @@ +import AbstractView from '../framework/view/abstract-view.js'; + +export default class ItemListView extends AbstractView { + _items = []; + _handleItemChange = null; + + constructor ({ + items, + onItemChange + }) { + super(); + this._items = items; + this._handleItemChange = onItemChange; + this.element.addEventListener('change', this.#itemChangeHandler); + } + + #itemChangeHandler = (evt) => { + evt.preventDefault(); + this._handleItemChange?.(evt.target.dataset.item); + }; +}