diff --git a/src/const.js b/src/const.js index 8d29191..988b7fe 100644 --- a/src/const.js +++ b/src/const.js @@ -1,4 +1,35 @@ -const EVENT_TYPES = ['taxi', 'bus', 'train', 'ship', 'drive', 'flight', 'check-in', 'sightseeing', 'restaurant']; +const EVENT_TYPES = [ + 'taxi' + , 'bus' + , 'train' + , 'ship' + , 'drive' + , 'flight' + , 'check-in' + , 'sightseeing' + , 'restaurant']; +const MESSAGE = { + EMPTY: 'Click New Event to create your first point', + LOADING: 'Loading...', + FAILED_LOAD: 'Failed to load latest route information', +}; -export {EVENT_TYPES}; +const FilterType = { + EVERYTHING: 'everything', + FUTURE: 'future', + PRESENT: 'present', + PAST: 'past' +}; + +const SortType = { + DAY: 'day', + EVENT: 'event', + TIME: 'time', + PRICE: 'price', + OFFERS: 'offers', +}; + +const DisabledSortType = [SortType.EVENT, SortType.OFFERS]; + +export {EVENT_TYPES, MESSAGE, FilterType, SortType, DisabledSortType}; diff --git a/src/framework/view/abstract-view.js b/src/framework/view/abstract-view.js index fa2a552..b527e82 100644 --- a/src/framework/view/abstract-view.js +++ b/src/framework/view/abstract-view.js @@ -14,6 +14,7 @@ export default class AbstractView { /** @type {HTMLElement|null} Элемент представления */ #element = null; + constructor() { if (new.target === AbstractView) { throw new Error('Can\'t instantiate AbstractView, only concrete one.'); diff --git a/src/main.js b/src/main.js index 0e90837..297796f 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,4 @@ import ListPresenter from './presenter/list-presenter.js'; - import PointsTripModel from './model/points-trip-model.js'; import OffersTripsModel from './model/offers-trip-model.js'; import DestinationsTripModel from './model/destinations-trip-model.js'; diff --git a/src/mock/filter.js b/src/mock/filter.js new file mode 100644 index 0000000..08d3ec2 --- /dev/null +++ b/src/mock/filter.js @@ -0,0 +1,7 @@ +import { filter } from '../utils/filter.js'; + +function generateFilter() { + return Object.entries(filter).map(([filterType]) => ({type: filterType})); +} + +export { generateFilter }; diff --git a/src/model/points-trip-model.js b/src/model/points-trip-model.js index 130f96a..5213b39 100644 --- a/src/model/points-trip-model.js +++ b/src/model/points-trip-model.js @@ -6,7 +6,7 @@ export default class PointsTripModel { this.#points = points; } - get() { + get points() { return this.#points; } } diff --git a/src/presenter/header-presenter.js b/src/presenter/header-presenter.js new file mode 100644 index 0000000..8143efe --- /dev/null +++ b/src/presenter/header-presenter.js @@ -0,0 +1,34 @@ +import { render, RenderPosition } from '../framework/render.js'; + +import SectionTripInfoView from '../view/section-trip-info-view.js'; +import NewEventButtonView from '../view/new-event-button-view.js'; + +const tripMain = document.querySelector('.trip-main'); + + +export default class HeaderPresenter { + + #pointsTrip = null; + #destinations = null; + #listPoints = []; + + constructor({destinations, pointsTrip}) { + this.#pointsTrip = pointsTrip; + this.#destinations = destinations; + } + + init() { + this.#listPoints = [...this.#pointsTrip]; + + this.#renderTripHeader(); + + } + + + /** Создание шапки сайта */ + #renderTripHeader() { + render(new SectionTripInfoView({allDestinations: this.#destinations , allPoints: this.#listPoints}), tripMain, RenderPosition.AFTERBEGIN); // Заголовок, даты, общая цена + + render(new NewEventButtonView(), tripMain); // Заголовок, кнопка добавить событие + } +} diff --git a/src/presenter/list-presenter.js b/src/presenter/list-presenter.js index c55a814..c06086e 100644 --- a/src/presenter/list-presenter.js +++ b/src/presenter/list-presenter.js @@ -1,34 +1,39 @@ -import { render, RenderPosition, replace } from '../framework/render.js'; +import { render, replace } from '../framework/render.js'; +import { MESSAGE } from '../const.js'; +import { SortType } from '../const.js'; +import { sortEventsByDay, sortEventsByTime, sortEventsByPrice } from '../utils/filter.js'; +import { generateFilter } from '../mock/filter.js'; -import SectionTripInfoView from '../view/section-trip-info-view.js'; -import NewEventButtonView from '../view/new-event-button-view.js'; +import SortButtonView from '../view/sort-button-view.js'; import TripFiltersFormView from '../view/trip-filters-form-view.js'; - -import SortButtonView from '../view/sort-view.js'; import TripEventListView from '../view/trip-events-list-view.js'; import EventItemView from '../view/event-item-view.js'; // import AddNewPointView from '../view/add-new-point-view.js'; import EditPointView from '../view/edit-poit-view.js'; import TripEventsMessage from '../view/trip-events-message-view.js'; -const tripMain = document.querySelector('.trip-main'); -const tripControlsFilters = document.querySelector('.trip-controls__filters'); +import HeaderPresenter from './header-presenter.js'; + +const tripFiltersElement = document.querySelector('.trip-controls__filters'); + export default class ListPresenter { - #listContainer; - #pointsTrip; - #destinations; - #offers; + #listContainer = null; + #pointsTrip = null; + #destinations = null; + #offers = null; #listComponent = new TripEventListView(); #listPoints = []; - + #sourcedTripPoints = []; + #sortComponent = null; + #currentSortType = SortType.DAY; constructor({ listContainer, pointsTripModel, destinationsTripModel, offersTripModel }) { this.#listContainer = listContainer; - this.#pointsTrip = pointsTripModel.get(); + this.#pointsTrip = pointsTripModel.points; this.#destinations = destinationsTripModel; this.#offers = offersTripModel; } @@ -36,11 +41,62 @@ export default class ListPresenter { init() { this.#listPoints = [...this.#pointsTrip]; - /** Отрисовка всех компонентов */ + this.#sourcedTripPoints = [...this.#pointsTrip]; + + + this.#headerPresenter({destinations:this.#destinations, pointsTrip: this.#pointsTrip}); + + /** Рендерим кнопки сортировки */ + this.#renderSort(); + + /** Рендерим форму фильтрации */ + this.#renderFilters(); + + /** Отрисовка всех компонентов путешествия */ this.#renderList(); + + } + + #renderList() { + + /** Рендерим список для новых событий */ + render(this.#listComponent, this.#listContainer); + + if (this.#listPoints.length === 0) { + /** Если список событий пуст, то отрисовываем сообщение */ + render(new TripEventsMessage(MESSAGE.EMPTY), this.#listContainer); + + } else { + /** Если список событий не пуст, то отрисовываем события */ + + /** Рендерим редактируемое событие */ + this.#rederTripEvent(this.#listPoints[0]); + + /** Рендерим список событий */ + this.#renderAllTripEvents(); + } + + + // Переделать логику отрисовки новой точки! + // render(new AddNewPointView({pointsTrip: this.#listPoints, offers: this.#offers}), this.#listContainer); + } + + #headerPresenter({destinations, pointsTrip}) { + const headerPresenter = new HeaderPresenter({ + destinations, + pointsTrip, + }); + + return headerPresenter.init(); } + #renderFilters() { + const filters = generateFilter(); + + render(new TripFiltersFormView({filters}), tripFiltersElement); + } + /** Елемент события путешествия */ #tripEventData(item) { const destination = this.#destinations.getDestinationById(item); @@ -112,33 +168,42 @@ export default class ListPresenter { }); } - #renderList() { - /** Отрисовка шапки сайта */ - render(new SectionTripInfoView({allDestinations: this.#destinations, allPoints: this.#listPoints}), tripMain, RenderPosition.AFTERBEGIN); // Заголовок, даты, общая цена - render(new NewEventButtonView(), tripMain); // Заголовок, кнопка добавить событие - render (new TripFiltersFormView(), tripControlsFilters); // Кнопки сортировки - - - /** Рендерим список для новых событий */ - render(this.#listComponent, this.#listContainer); + /** Отрисовка cортировки событий путешествия */ + #renderSort() { + this.#sortComponent = new SortButtonView({ + onSortTypeChange: this.#handleSortTypeChange, + currentSortType: this.#currentSortType, + }); - /** Если список событий пуст, то отрисовываем сообщение */ - if (this.#listPoints.length === 0) { - render(new TripEventsMessage, this.#listContainer); + render(this.#sortComponent, this.#listContainer); + } + /** Сортировка событий путешествия */ + #sortTripPoints(sortType) { + + switch (sortType) { + case SortType.DAY: + this.#pointsTrip.sort(sortEventsByDay); + break; + case SortType.TIME: + this.#pointsTrip.sort(sortEventsByTime); + break; + case SortType.PRICE: + this.#pointsTrip.sort(sortEventsByPrice); + break; + default: + this.#pointsTrip = [...this.#sourcedTripPoints]; } - /** Рендерим кнопки сортировки */ - render(new SortButtonView(), this.#listContainer); - /** Рендерим редактируемое событие */ - this.#rederTripEvent(this.#listPoints[0]); - - /** Рендерим список событий */ - this.#renderAllTripEvents(); + this.#currentSortType = sortType; + } + #handleSortTypeChange = (sortType) => { + if (this.#currentSortType === sortType) { + return; + } - // Переделать логику отрисовки новой точки! - // render(new AddNewPointView({pointsTrip: this.#listPoints, offers: this.#offers}), this.#listContainer); - } + this.#sortTripPoints(sortType); + }; } diff --git a/src/utils/filter.js b/src/utils/filter.js new file mode 100644 index 0000000..dd3f6a0 --- /dev/null +++ b/src/utils/filter.js @@ -0,0 +1,53 @@ +import { FilterType } from '../const.js'; +import dayjs from 'dayjs'; + +const filter = { + [FilterType.EVERYTHING]: (pointsTrip) => pointsTrip, + [FilterType.FUTURE]: (pointsTrip) => pointsTrip.filter((pointTrip) => new Date(pointTrip.date_from) > Date.now()), + [FilterType.PRESENT]: (pointsTrip) => pointsTrip.filter((pointTrip) => new Date(pointTrip.date_from) <= Date.now() && new Date(pointTrip.date_to) >= Date.now()), + [FilterType.PAST]: (pointsTrip) => pointsTrip.filter((pointTrip) => new Date(pointTrip.date_to) < Date.now()), +}; + + +function sortEventsByDay (eventA, eventB) { + + if (dayjs(eventA.date_from).diff(dayjs(eventB.date_from)) < 0) { + return -1; + } + + if (dayjs(eventA.date_from).diff(dayjs(eventB.date_from)) > 0) { + return 1; + } + + return 0; +} + +function sortEventsByTime (eventA, eventB) { + + if (dayjs(eventA.date_from).diff(dayjs(eventA.date_to)) < + dayjs(eventB.date_from).diff(dayjs(eventB.date_to))) { + return -1; + } + + if (dayjs(eventA.date_from).diff(dayjs(eventA.date_to)) > + dayjs(eventB.date_from).diff(dayjs(eventB.date_to))) { + return 1; + } + + return 0; +} + +function sortEventsByPrice (eventA, eventB) { + + if (eventA.base_price < eventB.base_price) { + return -1; + } + + if (eventA.base_price > eventB.base_price) { + return 1; + } + + return 0; +} + +export { filter, sortEventsByDay, sortEventsByTime, sortEventsByPrice }; diff --git a/src/utils.js b/src/utils/utils.js similarity index 82% rename from src/utils.js rename to src/utils/utils.js index 095ac86..98b4747 100644 --- a/src/utils.js +++ b/src/utils/utils.js @@ -1,14 +1,5 @@ import dayjs from 'dayjs'; -/** - * Returns a random element from the given array. - * @param {Array} items - Array of any type of elements. - * @returns {any} - Random element from the array. - */ -function getRandomArrayElement(items) { - return items[Math.floor(Math.random() * items.length)]; -} - const FORMATS = { 'headerDate': 'DD MMM', 'date': 'MMM D', @@ -62,4 +53,4 @@ function capitalizeFirstLetter(word) { return word[0].toUpperCase() + word.slice(1); } -export { getRandomArrayElement, humanizeEventDate, getDuration, capitalizeFirstLetter }; +export { humanizeEventDate, getDuration, capitalizeFirstLetter }; diff --git a/src/view/add-new-point-view.js b/src/view/add-new-point-view.js index 31097b1..34fc5b5 100644 --- a/src/view/add-new-point-view.js +++ b/src/view/add-new-point-view.js @@ -165,8 +165,8 @@ function createAddNewPointTemplate() { export default class AddNewPointView extends AbstractView { - #pointsTrip; - #offers; + #pointsTrip = null; + #offers = null; constructor(pointsTrip, offers) { super(); diff --git a/src/view/edit-poit-view.js b/src/view/edit-poit-view.js index 6f72e08..27b0a18 100644 --- a/src/view/edit-poit-view.js +++ b/src/view/edit-poit-view.js @@ -1,5 +1,5 @@ import {EVENT_TYPES} from '../const.js'; -import { humanizeEventDate, capitalizeFirstLetter } from '../utils.js'; +import { humanizeEventDate, capitalizeFirstLetter } from '../utils/utils.js'; import AbstractView from '../framework/view/abstract-view.js'; function createOffersTemplate(tripEventData) { @@ -154,12 +154,12 @@ function createEditPointTemplate(tripEventData, destinations, allDestinations) { export default class EditPointView extends AbstractView { - #tripEventData; - #destinations; - #allDestinations; + #tripEventData = null; + #destinations = null; + #allDestinations = null; - #handleFormSubmit; - #handleCloseFormClick; + #handleFormSubmit = null; + #handleCloseFormClick = null; constructor({tripEventData, destinations, allDestinations, onFormSubmit, onCloseFormClick}) { super(); diff --git a/src/view/event-item-view.js b/src/view/event-item-view.js index 465028d..12deb90 100644 --- a/src/view/event-item-view.js +++ b/src/view/event-item-view.js @@ -1,4 +1,4 @@ -import {humanizeEventDate, getDuration} from '../utils.js'; +import {humanizeEventDate, getDuration} from '../utils/utils.js'; import AbstractView from '../framework/view/abstract-view.js'; function createOffersTemplate(offers) { @@ -67,8 +67,8 @@ function createEventItemTemplate(tripEventData) { export default class EventItemView extends AbstractView { - #tripEventData; - #handleEditClick; + #tripEventData = null; + #handleEditClick = null; constructor(tripEventData, {onEditClick}) { super(); diff --git a/src/view/section-trip-info-view.js b/src/view/section-trip-info-view.js index 88d8523..9344c85 100644 --- a/src/view/section-trip-info-view.js +++ b/src/view/section-trip-info-view.js @@ -1,22 +1,44 @@ import AbstractView from '../framework/view/abstract-view.js'; -import { humanizeEventDate } from '../utils.js'; +import { humanizeEventDate } from '../utils/utils.js'; function createSectionTripInfoTemplate(allDestinations, allPoints) { - const eventDateStart = allPoints[0].date_from; - const eventDateEnd = allPoints[allPoints.length - 1].date_to; - const allDestinationsStr = allDestinations.map((destination) => (destination.name)).join(' — '); + function getDateAllPoints() { + + let eventDateStart = ''; + let eventDateEnd = ''; + let allDestinationsStr = ''; + if (allPoints.length !== 0) { + eventDateStart = allPoints[0].date_from; + eventDateEnd = allPoints[allPoints.length - 1].date_to; + allDestinationsStr = allDestinations.map((destination) => (destination.name)).join(' — '); + return {eventDateStart, eventDateEnd, allDestinationsStr}; + } + + return {eventDateStart, eventDateEnd, allDestinationsStr}; + } + const date = getDateAllPoints(); /** Без учета выбранных предложений */ - const totalBasePrice = allPoints.reduce((acc, point) => acc + point.base_price, 0); + function getTotalBasePrice() { + + let allBasePrice = 0; + if (allPoints.length !== 0) { + allBasePrice = allPoints.reduce((acc, point) => acc + point.base_price, 0); + return allBasePrice; + } + return allBasePrice; + } + + const totalBasePrice = getTotalBasePrice(); return ( `
-

${allDestinationsStr}

+

${date.allDestinationsStr}

-

${humanizeEventDate(eventDateStart, 'headerDate')} — ${humanizeEventDate(eventDateEnd, 'headerDate')}

+

${humanizeEventDate(date.eventDateStart, 'headerDate')} — ${humanizeEventDate(date.eventDateEnd, 'headerDate')}

@@ -28,8 +50,8 @@ function createSectionTripInfoTemplate(allDestinations, allPoints) { export default class SectionTripInfoView extends AbstractView { - #allDestinations; - #allPoints; + #allDestinations = null; + #allPoints = null; constructor({allDestinations, allPoints}) { diff --git a/src/view/sort-button-view.js b/src/view/sort-button-view.js new file mode 100644 index 0000000..0797394 --- /dev/null +++ b/src/view/sort-button-view.js @@ -0,0 +1,51 @@ +import AbstractView from '../framework/view/abstract-view.js'; +import { SortType, DisabledSortType } from '../const.js'; + +function createSortButtonTemplate(currentSortType) { + + return (` +

+ + ${Object.values(SortType).map((type) => (` +
+ + +
+ `)).join('')} + +
+ `); +} + +export default class SortButtonView extends AbstractView { + + #handleSortTypeChange = null; + #currentSortType = null; + + constructor({ onSortTypeChange, currentSortType }) { + super(); + this.#handleSortTypeChange = onSortTypeChange; + this.#currentSortType = currentSortType; + + this.element.addEventListener('change', this.#sortTypeChangeHandler); + } + + #sortTypeChangeHandler = (evt) => { + evt.preventDefault(); + this.#handleSortTypeChange(evt.target.dataset.sortType); + }; + + get template() { + return createSortButtonTemplate(this.#currentSortType); + } +} diff --git a/src/view/sort-view.js b/src/view/sort-view.js deleted file mode 100644 index dad059c..0000000 --- a/src/view/sort-view.js +++ /dev/null @@ -1,38 +0,0 @@ -import AbstractView from '../framework/view/abstract-view.js'; - -function createSortButtonTemplate() { - return ( - `
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
-
` - ); -} - -export default class SortButtonView extends AbstractView { - get template() { - return createSortButtonTemplate(); - } -} diff --git a/src/view/trip-events-message-view.js b/src/view/trip-events-message-view.js index 061795c..720e3b3 100644 --- a/src/view/trip-events-message-view.js +++ b/src/view/trip-events-message-view.js @@ -1,12 +1,21 @@ import AbstractView from '../framework/view/abstract-view.js'; -function createTripEventsMessageTemplate() { - return '

Click New Event to create your first point

'; +function createTripEventsMessageTemplate(message) { + return `

${message}

`; } export default class TripEventsMessage extends AbstractView { + + #message = null; + + constructor(message) { + + super(); + this.#message = message; + } + get template() { - return createTripEventsMessageTemplate(); + return createTripEventsMessageTemplate(this.#message); } } diff --git a/src/view/trip-filters-form-view.js b/src/view/trip-filters-form-view.js index 5e023d3..d06361a 100644 --- a/src/view/trip-filters-form-view.js +++ b/src/view/trip-filters-form-view.js @@ -1,34 +1,44 @@ import AbstractView from '../framework/view/abstract-view.js'; -function createTripFiltersFormTemplate() { - return `
-
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
`; +function createTripFiltersFormTemplate(filters) { + + return (` +
+
+

Filter events

+
+ + ${filters.map((filter) => (` +
+ + ${filter.count === 0 ? 'disabled' : ''} + +
+ `)).join('')} + + +
+
+
+ `); } export default class TripFiltersFormView extends AbstractView { + + #filters = null; + + constructor({ filters }) { + super(); + this.#filters = filters; + } + get template() { - return createTripFiltersFormTemplate(); + return createTripFiltersFormTemplate(this.#filters); } }