diff --git a/public/index.html b/public/index.html index 44c26b4..ebb0970 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,7 @@

Filter events

- + diff --git a/src/const.js b/src/const.js index a54e4c7..dbbdff2 100644 --- a/src/const.js +++ b/src/const.js @@ -50,10 +50,24 @@ const SortType = { TIME: 'time', }; +const UserAction = { + UPDATE_EVENT: 'UPDATE_EVENT', + ADD_EVENT: 'ADD_EVENT', + DELETE_EVENT: 'DELETE_EVENT', +}; + +const UpdateType = { + PATCH: 'PATCH', + MINOR: 'MINOR', + MAJOR: 'MAJOR', +}; + export { EVENTS_TYPES, CITIES, SENTENCES, FilterType, - SortType + SortType, + UserAction, + UpdateType }; diff --git a/src/main.js b/src/main.js index a3efec8..2af8dc7 100644 --- a/src/main.js +++ b/src/main.js @@ -1,9 +1,10 @@ import { render } from './framework/render.js'; import EventsModel from './model/events-model.js'; +import FilterModel from './model/filter-model.js'; import NewTripInfoView from './view/new-trip-info-view.js'; -import NewTripFiltersView from './view/new-filters-view.js'; import BoardPresenter from './presenter/board-presenter.js'; -import { generateFilter } from './mock/filter.js'; +import FilterPresenter from './presenter/filter-presenter.js'; +import NewAddEventButtonView from './view/new-add-event-button-view.js'; const tripMainContainer = document.querySelector('.trip-main'); @@ -11,16 +12,36 @@ const tripFiltersContainer = tripMainContainer.querySelector('.trip-controls__fi const tripEventsContainer = document.querySelector('.trip-events'); const eventsModel = new EventsModel(); +const filterModel = new FilterModel(); const boardPresenter = new BoardPresenter({ container: tripEventsContainer, + eventsModel, + filterModel, + onNewEventDestroy: handleNewEventFormClose, +}); +const filterPresenter = new FilterPresenter({ + filterContainer: tripFiltersContainer, + filterModel, eventsModel }); -const filters = generateFilter(eventsModel.userEvents); +const newEventButtonComponent = new NewAddEventButtonView({ + onClick: handleNewEventButtonClick +}); -render (new NewTripInfoView(), tripMainContainer, 'AFTERBEGIN'); -render (new NewTripFiltersView({filters}), tripFiltersContainer); +function handleNewEventFormClose() { + newEventButtonComponent.element.disabled = false; +} +function handleNewEventButtonClick() { + boardPresenter.createEvent(); + newEventButtonComponent.element.disabled = true; +} + + +render (new NewTripInfoView(), tripMainContainer, 'AFTERBEGIN'); +render(newEventButtonComponent, tripMainContainer); +filterPresenter.init(); boardPresenter.init(); diff --git a/src/mock/filter.js b/src/mock/filter.js deleted file mode 100644 index af7d8a6..0000000 --- a/src/mock/filter.js +++ /dev/null @@ -1,12 +0,0 @@ -import { filter } from '../utils/filter.js'; - -function generateFilter(userEvents) { - return Object.entries(filter).map( - ([filterType, filterEvents]) => ({ - type: filterType, - count: filterEvents(userEvents).length, - }), - ); -} - -export { generateFilter }; diff --git a/src/model/events-model.js b/src/model/events-model.js index 0f41d62..98f61b1 100644 --- a/src/model/events-model.js +++ b/src/model/events-model.js @@ -1,6 +1,7 @@ import EventsConnector from './events-connector'; +import Observable from '../framework/observable.js'; -export default class EventsModel { +export default class EventsModel extends Observable { #EventsConnector = new EventsConnector; #eventsList = this.#EventsConnector.userEvents; #destinationsData = this.#EventsConnector.destinationsData; @@ -10,12 +11,71 @@ export default class EventsModel { return this.#eventsList; } - get destinationsData() { + get destinationsData () { return this.#destinationsData; } + #generateDefaultEvent = () => { + const defaultType = 'flight'; + const defaultOffers = this.getOffersMapByType(defaultType); + return { + id: '', + basePrice: 0, + dateFrom: '', + dateTo: '', + destination: {}, + isFavorite: false, + offers: defaultOffers, + type: defaultType, + }; + }; + + get defaultEvent () { + return this.#generateDefaultEvent(); + } + findDestinationData = (destinationId) => this.#destinationsData.find((destination) => destination.id === destinationId); getOffersMapByType = (type) => this.#offersMap.get(type) || null; + updateEvent(updateType, update) { + const index = this.#eventsList.findIndex((event) => event.id === update.id); + + if (index === -1) { + throw new Error('Can\'t update unexisting event'); + } + + this.#eventsList = [ + ...this.#eventsList.slice(0, index), + update, + ...this.#eventsList.slice(index + 1), + ]; + + this._notify(updateType, update); + } + + addEvent(updateType, update) { + this.#eventsList = [ + update, + ...this.#eventsList, + ]; + + this._notify(updateType, update); + } + + deleteEvent(updateType, update) { + const index = this.#eventsList.findIndex((event) => event.id === update.id); + + if (index === -1) { + throw new Error('Can\'t delete unexisting event'); + } + + this.#eventsList = [ + ...this.#eventsList.slice(0, index), + ...this.#eventsList.slice(index + 1), + ]; + + this._notify(updateType); + } + } diff --git a/src/model/filter-model.js b/src/model/filter-model.js new file mode 100644 index 0000000..cd06887 --- /dev/null +++ b/src/model/filter-model.js @@ -0,0 +1,15 @@ +import Observable from '../framework/observable.js'; +import { FilterType } from '../const.js'; + +export default class FilterModel extends Observable { + #filter = FilterType.EVERYTHING; + + get filter() { + return this.#filter; + } + + setFilter(updateType, filter) { + this.#filter = filter; + this._notify(updateType, filter); + } +} diff --git a/src/presenter/board-presenter.js b/src/presenter/board-presenter.js index 9719fbb..7cd7229 100644 --- a/src/presenter/board-presenter.js +++ b/src/presenter/board-presenter.js @@ -1,47 +1,83 @@ -import { render } from '../framework/render.js'; -import { updateItem } from '../utils/common.js'; +import { render, remove } from '../framework/render.js'; import NewTripSortView from '../view/new-sort-container-view.js'; import NewEventsListView from '../view/new-events-list-view.js'; import EventPresenter from './event-presenter.js'; import NoEventsView from '../view/no-events-view.js'; -import { SortType } from '../const.js'; +import NewEventPresenter from './new-event-presenter.js'; +import { SortType, UpdateType, UserAction, FilterType } from '../const.js'; import { sortEventsPrice, sortEventsTime } from '../utils/event.js'; +import { filter } from '../utils/filter.js'; export default class BoardPresenter { #container = null; #eventsModel = null; + #filterModel = null; #sortComponent = null; + #noEventComponent = null; #eventsListComponent = new NewEventsListView(); - #eventsList = []; #eventPresenters = new Map(); + #newEventPresenter = null; #currentSortType = SortType.DEFAULT; - #sourcedBoardEvents = []; + #filterType = FilterType.EVERYTHING; #findDestinationData = null; #destinationsData = null; #getOffersMapByType = null; + #defaultEvent = null; - constructor ({container, eventsModel}) { + constructor ({container, filterModel, eventsModel, onNewEventDestroy}) { this.#container = container; this.#eventsModel = eventsModel; + this.#filterModel = filterModel; this.#findDestinationData = this.#eventsModel.findDestinationData; this.#destinationsData = this.#eventsModel.destinationsData; this.#getOffersMapByType = this.#eventsModel.getOffersMapByType; + this.#defaultEvent = this.#eventsModel.defaultEvent; + + this.#newEventPresenter = new NewEventPresenter({ + eventListContainer: this.#eventsListComponent.element, + onDataChange: this.#handleViewAction, + onDestroy: onNewEventDestroy, + findDestinationData: this.#findDestinationData, + destinationsData: this.#destinationsData, + getOffersMapByType: this.#getOffersMapByType, + userEvent: this.#defaultEvent, + }); + + this.#eventsModel.addObserver(this.#handleModelEvent); + this.#filterModel.addObserver(this.#handleModelEvent); + } + + get eventsList () { + this.#filterType = this.#filterModel.filter; + const userEvents = this.#eventsModel.userEvents; + const filteredEvents = filter[this.#filterType](userEvents); + + switch (this.#currentSortType) { + case SortType.PRICE: + return filteredEvents.sort(sortEventsPrice); + case SortType.TIME: + return filteredEvents.sort(sortEventsTime); + } + return filteredEvents; } init () { - this.#eventsList = [...this.#eventsModel.userEvents]; - this.#sourcedBoardEvents = [...this.#eventsModel.userEvents]; this.#renderBoard(); - this.#renderSort(); + } + + createEvent() { + this.#currentSortType = SortType.DEFAULT; + this.#filterModel.setFilter(UpdateType.MAJOR, FilterType.EVERYTHING); + this.#newEventPresenter.init(); } #renderEvent(inputUserEvent) { const eventPresenter = new EventPresenter({ container: this.#eventsListComponent.element, - onDataChange: this.#handleEventChange, + onDataChange: this.#handleViewAction, onModeChange: this.#handleModeChange, findDestinationData: this.#findDestinationData, destinationsData: this.#destinationsData, @@ -52,22 +88,10 @@ export default class BoardPresenter { } #renderNoEvents() { - render (new NoEventsView(), this.#container); - } - - #sortEvents(sortType) { - switch (sortType) { - case SortType.PRICE: - this.#eventsList.sort(sortEventsPrice); - break; - case SortType.TIME: - this.#eventsList.sort(sortEventsTime); - break; - default: - this.#eventsList = [...this.#sourcedBoardEvents]; - } - - this.#currentSortType = sortType; + this.#noEventComponent = new NoEventsView({ + filterType: this.#filterType + }); + render (this.#noEventComponent, this.#container); } #handleSortTypeChange = (sortType) => { @@ -75,14 +99,15 @@ export default class BoardPresenter { return; } - this.#sortEvents(sortType); + this.#currentSortType = sortType; - this.#clearEventList(); + this.#clearBoard(); this.#renderBoard(); }; #renderSort() { this.#sortComponent = new NewTripSortView({ + currentSortType: this.#currentSortType, onSortTypeChange: this.#handleSortTypeChange }); @@ -90,31 +115,67 @@ export default class BoardPresenter { } #renderBoard () { - if (this.#eventsList.length === 0) { + if (this.eventsList.length === 0) { this.#renderNoEvents(); return; } render(this.#eventsListComponent, this.#container); - for (let i = 0; i < this.#eventsList.length; i++) { - this.#renderEvent(this.#eventsList[i]); + for (let i = 0; i < this.eventsList.length; i++) { + this.#renderEvent(this.eventsList[i]); } + this.#renderSort(); } - #handleEventChange = (updatedEvent) => { - this.#eventsList = updateItem(this.#eventsList, updatedEvent); - this.#sourcedBoardEvents = updateItem(this.#sourcedBoardEvents, updatedEvent); - this.#eventPresenters.get(updatedEvent.id).init(updatedEvent); - }; - #handleModeChange = () => { + this.#newEventPresenter.destroy(); this.#eventPresenters.forEach((presenter) => presenter.resetView()); }; - #clearEventList() { + #clearBoard({resetSortType = false} = {}) { + this.#newEventPresenter.destroy(); this.#eventPresenters.forEach((presenter) => presenter.destroy()); this.#eventPresenters.clear(); + + remove(this.#sortComponent); + if (this.#noEventComponent) { + remove(this.#noEventComponent); + } + + if (resetSortType) { + this.#currentSortType = SortType.DEFAULT; + } } + #handleViewAction = (actionType, updateType, update) => { + switch (actionType) { + case UserAction.UPDATE_EVENT: + this.#eventsModel.updateEvent(updateType, update); + break; + case UserAction.ADD_EVENT: + this.#eventsModel.addEvent(updateType, update); + break; + case UserAction.DELETE_EVENT: + this.#eventsModel.deleteEvent(updateType, update); + break; + } + }; + + #handleModelEvent = (updateType, data) => { + switch (updateType) { + case UpdateType.PATCH: + this.#eventPresenters.get(data.id).init(data); + break; + case UpdateType.MINOR: + this.#clearBoard(); + this.#renderBoard(); + break; + case UpdateType.MAJOR: + this.#clearBoard({resetSortType: true}); + this.#renderBoard(); + break; + } + }; + } diff --git a/src/presenter/event-presenter.js b/src/presenter/event-presenter.js index 99aeaf6..2b76a7a 100644 --- a/src/presenter/event-presenter.js +++ b/src/presenter/event-presenter.js @@ -2,6 +2,8 @@ import { render, replace, remove } from '../framework/render.js'; import { isEscapeKey } from '../utils/common.js'; import NewEventsItemView from '../view/new-events-item-view.js'; import NewEventEditElementView from '../view/new-event-edit-element-view.js'; +import { UserAction, UpdateType } from '../const.js'; +import {isEventFuture, isEventPresent, isEventPast, isDatesEqual} from '../utils/event.js'; const Mode = { DEFAULT: 'DEFAULT', @@ -44,10 +46,13 @@ export default class EventPresenter { }); this.#eventEditComponent = new NewEventEditElementView({ userEvent: this.#eventItem, - onClick: this.#handleSaveClick, + onClick: this.#handleOpenEventClick, + onSubmit: this.#handleFormSubmit, + onDeleteClick: this.#handleDeleteClick, findDestinationData: this.#findDestinationData, destinationsData: this.#destinationsData, getOffersMapByType: this.#getOffersMapByType, + }); if (prevEventComponent === null || prevEventEditComponent === null) { @@ -101,12 +106,45 @@ export default class EventPresenter { this.#replaceEventCardToEditForm(); }; + #handleDeleteClick = (event) => { + this.#handleDataChange( + UserAction.DELETE_EVENT, + UpdateType.MINOR, + event, + ); + }; + #handleFavoriteClick = () => { - this.#handleDataChange({...this.#eventItem, isFavorite: !this.#eventItem.isFavorite}); + this.#handleDataChange( + UserAction.UPDATE_EVENT, + UpdateType.MINOR, + {...this.#eventItem, isFavorite: !this.#eventItem.isFavorite} + ); + }; + + #handleOpenEventClick = (eventItem) => { + this.#handleDataChange( + UserAction.UPDATE_EVENT, + UpdateType.MINOR, + eventItem, + ); + this.#replaceEditFormToEventCard(); }; - #handleSaveClick = (eventItem) => { - this.#handleDataChange(eventItem); + #handleFormSubmit = (update) => { + + const isMinorUpdate = + !isDatesEqual(this.#eventItem.dateFrom, update.dateFrom) || + !isDatesEqual(this.#eventItem.dateTo, update.dateTo) || + isEventFuture(update.dateFrom) !== isEventFuture(this.#eventItem.dateFrom) || + isEventPresent(update.dateFrom, update.dateTo) !== isEventPresent(this.#eventItem.dateFrom, this.#eventItem.dateTo) || + isEventPast(update.dateTo) !== isEventPast(this.#eventItem.dateTo); + + this.#handleDataChange( + UserAction.UPDATE_EVENT, + isMinorUpdate ? UpdateType.MINOR : UpdateType.PATCH, + update, + ); this.#replaceEditFormToEventCard(); }; diff --git a/src/presenter/filter-presenter.js b/src/presenter/filter-presenter.js new file mode 100644 index 0000000..52a5f9a --- /dev/null +++ b/src/presenter/filter-presenter.js @@ -0,0 +1,61 @@ +import { render, replace, remove } from '../framework/render.js'; +import NewTripFiltersView from '../view/new-filters-view.js'; +import { filter } from '../utils/filter.js'; +import { FilterType, UpdateType } from '../const.js'; + +export default class FilterPresenter { + #filterContainer = null; + #filterModel = null; + #eventsModel = null; + + #filterComponent = null; + + constructor({filterContainer, filterModel, eventsModel}) { + this.#filterContainer = filterContainer; + this.#filterModel = filterModel; + this.#eventsModel = eventsModel; + + this.#eventsModel.addObserver(this.#handleModelEvent); + this.#filterModel.addObserver(this.#handleModelEvent); + } + + get filters() { + const userEvents = this.#eventsModel.userEvents; + + return Object.values(FilterType).map((type) => ({ + type, + count: filter[type](userEvents).length + })); + } + + init() { + const filters = this.filters; + const prevFilterComponent = this.#filterComponent; + + this.#filterComponent = new NewTripFiltersView({ + filters, + currentFilterType: this.#filterModel.filter, + onFilterTypeChange: this.#handleFilterTypeChange + }); + + if (prevFilterComponent === null) { + render(this.#filterComponent, this.#filterContainer); + return; + } + + replace(this.#filterComponent, prevFilterComponent); + remove(prevFilterComponent); + } + + #handleModelEvent = () => { + this.init(); + }; + + #handleFilterTypeChange = (filterType) => { + if (this.#filterModel.filter === filterType) { + return; + } + + this.#filterModel.setFilter(UpdateType.MAJOR, filterType); + }; +} diff --git a/src/presenter/new-event-presenter.js b/src/presenter/new-event-presenter.js new file mode 100644 index 0000000..014f173 --- /dev/null +++ b/src/presenter/new-event-presenter.js @@ -0,0 +1,81 @@ +import { remove, render, RenderPosition } from '../framework/render.js'; +import NewEventEditElementView from '../view/new-event-edit-element-view.js'; +import { nanoid } from 'nanoid'; +import { UserAction, UpdateType } from '../const.js'; + +export default class NewEventPresenter { + #eventListContainer = null; + #handleDataChange = null; + #handleDestroy = null; + + #findDestinationData = null; + #destinationsData = null; + #getOffersMapByType = null; + #userEvent = null; + + #eventEditComponent = null; + + constructor({eventListContainer, onDataChange, onDestroy, findDestinationData, destinationsData, getOffersMapByType, userEvent}) { + this.#eventListContainer = eventListContainer; + this.#handleDataChange = onDataChange; + this.#handleDestroy = onDestroy; + this.#findDestinationData = findDestinationData; + this.#destinationsData = destinationsData; + this.#getOffersMapByType = getOffersMapByType; + this.#userEvent = userEvent; + } + + init() { + if (this.#eventEditComponent !== null) { + return; + } + + this.#eventEditComponent = new NewEventEditElementView({ + userEvent: this.#userEvent, + onClick: this.#handleDeleteClick, + onSubmit: this.#handleFormSubmit, + onDeleteClick: this.#handleDeleteClick, + findDestinationData: this.#findDestinationData, + destinationsData: this.#destinationsData, + getOffersMapByType: this.#getOffersMapByType, + isDefaultEvent: true, + }); + + render(this.#eventEditComponent, this.#eventListContainer, RenderPosition.AFTERBEGIN); + + document.addEventListener('keydown', this.#escKeyDownHandler); + } + + destroy() { + if (this.#eventEditComponent === null) { + return; + } + + this.#handleDestroy(); + + remove(this.#eventEditComponent); + this.#eventEditComponent = null; + + document.removeEventListener('keydown', this.#escKeyDownHandler); + } + + #handleFormSubmit = (event) => { + this.#handleDataChange( + UserAction.ADD_EVENT, + UpdateType.MINOR, + {id: nanoid(), ...event}, + ); + this.destroy(); + }; + + #handleDeleteClick = () => { + this.destroy(); + }; + + #escKeyDownHandler = (evt) => { + if (evt.key === 'Escape' || evt.key === 'Esc') { + evt.preventDefault(); + this.destroy(); + } + }; +} diff --git a/src/utils/common.js b/src/utils/common.js index ac2612d..fb2a07f 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -47,10 +47,8 @@ const convertKeysToCamelCase = (items) => { return items; }; -const updateItem = (items, update) => items.map((item) => item.id === update.id ? update : item); export { - updateItem, getRandomNumber, getRandomString, getRandomBoolean, diff --git a/src/utils/event.js b/src/utils/event.js index 50ed9f5..af33c9b 100644 --- a/src/utils/event.js +++ b/src/utils/event.js @@ -2,18 +2,19 @@ import dayjs from 'dayjs'; const humanizeDueDate = (dueDate, dateFormat) => dueDate ? dayjs(dueDate).format(dateFormat) : ''; -const isEventFuture = (dateFrom) => dateFrom && dayjs().isAfter(dateFrom, 'D'); +const isEventFuture = (dateFrom) => dateFrom && dayjs().isBefore(dateFrom, 'D'); const isEventPresent = (dateFrom, dateTo) => { const now = dayjs(); return dateFrom && dateTo && - now.isAfter(dateFrom, 'D') && - now.isBefore(dateTo, 'D') || - now.isSame(dateTo, 'D'); + (now.isAfter(dateFrom, 'D') || now.isSame(dateFrom, 'D')) && + (now.isBefore(dateTo, 'D') || now.isSame(dateTo, 'D')); }; const isEventPast = (dateTo) => dateTo && dayjs().isAfter(dateTo, 'D'); +const isDatesEqual = (dateA, dateB) => (dateA === null && dateB === null) || dayjs(dateA).isSame(dateB, 'D'); + const sortEventsPrice = (eventA, eventB) => eventB.basePrice - eventA.basePrice; const sortEventsTime = (eventA, eventB) => { @@ -33,6 +34,7 @@ export { isEventFuture, isEventPresent, isEventPast, + isDatesEqual, sortEventsPrice, sortEventsTime }; diff --git a/src/utils/filter.js b/src/utils/filter.js index 549190a..9492cff 100644 --- a/src/utils/filter.js +++ b/src/utils/filter.js @@ -4,8 +4,8 @@ import { isEventFuture, isEventPresent, isEventPast } from './event'; const filter = { [FilterType.EVERYTHING]: (userEvents) => userEvents, [FilterType.FUTURE]: (userEvents) => userEvents.filter((userEvent) => isEventFuture(userEvent.dateFrom)), - [FilterType.PRESENT]: (userEvents) => userEvents.filter((userEvent) => isEventPresent(userEvent.dateFrom)), - [FilterType.PAST]: (userEvents) => userEvents.filter((userEvent) => isEventPast(userEvent.dateFrom)), + [FilterType.PRESENT]: (userEvents) => userEvents.filter((userEvent) => isEventPresent(userEvent.dateFrom, userEvent.dateTo)), + [FilterType.PAST]: (userEvents) => userEvents.filter((userEvent) => isEventPast(userEvent.dateTo)), }; export { filter }; diff --git a/src/view/new-add-event-button-view.js b/src/view/new-add-event-button-view.js new file mode 100644 index 0000000..a3143bc --- /dev/null +++ b/src/view/new-add-event-button-view.js @@ -0,0 +1,24 @@ +import AbstractView from '../framework/view/abstract-view'; + + +const createAddEventButtonTemplate = () => ''; + +export default class NewAddEventButtonView extends AbstractView { + #handleClick = null; + + constructor({onClick}) { + super(); + this.#handleClick = onClick; + this.element.addEventListener('click', this.#clickHandler); + } + + get template() { + return createAddEventButtonTemplate(); + } + + #clickHandler = (evt) => { + evt.preventDefault(); + this.#handleClick(); + }; + +} diff --git a/src/view/new-event-add-element-view.js b/src/view/new-event-add-element-view.js deleted file mode 100644 index 70af7d5..0000000 --- a/src/view/new-event-add-element-view.js +++ /dev/null @@ -1,172 +0,0 @@ -import AbstractView from '../framework/view/abstract-view'; - -function createNewEventAddElementTemplate() { - return `
  • -
    -
    -
    - - - -
    -
    - Event type - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    -
    -
    - -
    - - - - - - - -
    - -
    - - - — - - -
    - -
    - - -
    - - - -
    -
    -
    -

    Offers

    - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    -
    - -
    -

    Destination

    -

    Geneva is a city in Switzerland that lies at the southern tip of expansive Lac Léman (Lake Geneva). Surrounded by the Alps and Jura mountains, the city has views of dramatic Mont Blanc.

    - -
    -
    - Event photo - Event photo - Event photo - Event photo - Event photo -
    -
    -
    -
    -
    -
  • `; -} - -export default class NewEventAddElementView extends AbstractView { - get template () { - return createNewEventAddElementTemplate(); - } -} diff --git a/src/view/new-event-edit-element-view.js b/src/view/new-event-edit-element-view.js index 928264e..072f002 100644 --- a/src/view/new-event-edit-element-view.js +++ b/src/view/new-event-edit-element-view.js @@ -24,6 +24,19 @@ const createOffers = (offersMap) => { return offersHTML; }; +const createOffersContainer = (offersMap) => { + if (!offersMap.size > 0) { + return ''; + } + return `
    +

    Offers

    + +
    + ${createOffers(offersMap)} +
    +
    `; +}; + const createType = (type) => { const typeToLowerCase = type.toLowerCase(); return `
    @@ -54,7 +67,26 @@ const createDestinations = (destinationsList) => { return destinationsHTML; }; -const createNewEventEditElementTemplate = (eventData, destinationsList) => { +const createDestinationDescription = (description) => { + if (!description) { + return ''; + } + return `
    +

    Destination

    +

    ${description}

    +
    `; +}; + +const createOpenEventButton = (isDefaultEvent) => { + if (isDefaultEvent) { + return ''; + } + return ``; +}; + +const createNewEventEditElementTemplate = (eventData, destinationsList, isDefaultEvent) => { const {basePrice, type, offers, destination, dateFrom, dateTo} = eventData; return `
  • @@ -79,7 +111,7 @@ const createNewEventEditElementTemplate = (eventData, destinationsList) => { - + ${createDestinations(destinationsList)} @@ -103,23 +135,11 @@ const createNewEventEditElementTemplate = (eventData, destinationsList) => { - + ${createOpenEventButton(isDefaultEvent)}
    -
    -

    Offers

    - -
    - ${createOffers(offers)} -
    -
    - -
    -

    Destination

    -

    ${destination.description}

    -
    + ${createOffersContainer(offers)} + ${createDestinationDescription(destination.description)}
  • `; @@ -128,24 +148,28 @@ const createNewEventEditElementTemplate = (eventData, destinationsList) => { export default class NewEventEditElementView extends AbstractStatefulView { #eventData = null; #handleClick = null; + #handleDeleteClick = null; #rollupButton = null; #destinationsData = null; #findDestinationData = null; #getOffersMapByType = null; #datepicker = null; + #isDefaultEvent = null; #handleSubmit = null; #formElement = null; - constructor ({userEvent, onClick, findDestinationData, destinationsData, getOffersMapByType, onSubmit}) { + constructor ({userEvent, onClick, onSubmit, onDeleteClick, findDestinationData, destinationsData, getOffersMapByType, isDefaultEvent}) { super(); this.#eventData = userEvent; this.#findDestinationData = findDestinationData; this.#destinationsData = destinationsData; this.#getOffersMapByType = getOffersMapByType; + this.#isDefaultEvent = isDefaultEvent; this._setState(NewEventEditElementView.parseEventDataToState(userEvent)); this.#handleClick = onClick; + this.#handleDeleteClick = onDeleteClick; this.#handleSubmit = onSubmit; this._restoreHandlers(); @@ -153,7 +177,7 @@ export default class NewEventEditElementView extends AbstractStatefulView { } get template () { - return createNewEventEditElementTemplate(this._state, this.#destinationsData); + return createNewEventEditElementTemplate(this._state, this.#destinationsData, this.#isDefaultEvent); } removeElement() { @@ -175,7 +199,12 @@ export default class NewEventEditElementView extends AbstractStatefulView { this.#rollupButton = this.element.querySelector('.event__rollup-btn'); this.#formElement = this.element.querySelector('.event--edit'); this.#formElement.addEventListener('submit', this.#submitHandler); - this.#rollupButton.addEventListener('click', this.#clickHandler); + if (!this.#isDefaultEvent) { + this.#rollupButton.addEventListener('click', this.#clickHandler); + } + + this.element.querySelector('.event__reset-btn') + .addEventListener('click', this.#deleteHandler); this.element.querySelector('#event-price-1') .addEventListener('input', this.#eventPriceToggleHandler); @@ -201,7 +230,12 @@ export default class NewEventEditElementView extends AbstractStatefulView { #submitHandler = (evt) => { evt.preventDefault(); - this.#handleClick(NewEventEditElementView.parseStateToEventData(this._state)); + this.#handleSubmit(NewEventEditElementView.parseStateToEventData(this._state)); + }; + + #deleteHandler = (evt) => { + evt.preventDefault(); + this.#handleDeleteClick(NewEventEditElementView.parseStateToEventData(this._state)); }; removeEventListeners() { @@ -294,7 +328,7 @@ export default class NewEventEditElementView extends AbstractStatefulView { evt.preventDefault(); const offerId = evt.currentTarget.dataset.offerId; - const offers = new Map (this._state.offers); + const offers = this._state.offers; if (offers.has(offerId)) { const offer = offers.get(offerId); diff --git a/src/view/new-events-item-view.js b/src/view/new-events-item-view.js index a4ac1cc..7449a5d 100644 --- a/src/view/new-events-item-view.js +++ b/src/view/new-events-item-view.js @@ -35,7 +35,9 @@ const createNewOffer = (offer) => { const createOffers = (offers) => { let offersHTML = ''; offers.forEach((offer) => { - offersHTML += createNewOffer(offer); + if (offer.isActive) { + offersHTML += createNewOffer(offer); + } }); return offersHTML; }; diff --git a/src/view/new-filters-view.js b/src/view/new-filters-view.js index 4d7ed05..1fc01cb 100644 --- a/src/view/new-filters-view.js +++ b/src/view/new-filters-view.js @@ -1,6 +1,6 @@ import AbstractView from '../framework/view/abstract-view'; -const createFilterItemTemplate = (filter, isChecked) => { +const createFilterItemTemplate = (filter, currentFilterType) => { const {type, count} = filter; return `
    @@ -10,15 +10,15 @@ const createFilterItemTemplate = (filter, isChecked) => { type="radio" name="trip-filter" value="${type}" - ${isChecked ? 'checked' : ''} + ${type === currentFilterType ? 'checked' : ''} ${count === 0 ? 'disabled' : ''}>
    `; }; -const createNewTripFiltersTemplate = (filterItems) => { +const createNewTripFiltersTemplate = (filterItems, currentFilterType) => { const filterItemsTemplate = filterItems - .map((filter, index) => createFilterItemTemplate(filter, index === 0)) + .map((filter) => createFilterItemTemplate(filter, currentFilterType)) .join(''); return `
    @@ -29,13 +29,24 @@ const createNewTripFiltersTemplate = (filterItems) => { export default class NewTripFiltersView extends AbstractView { #filters = null; + #currentFilter = null; + #handleFilterTypeChange = null; - constructor (filters) { + constructor ({filters, currentFilterType, onFilterTypeChange}) { super(); this.#filters = filters; + this.#currentFilter = currentFilterType; + this.#handleFilterTypeChange = onFilterTypeChange; + + this.element.addEventListener('change', this.#filterTypeChangeHandler); } get template () { - return createNewTripFiltersTemplate(this.#filters.filters); + return createNewTripFiltersTemplate(this.#filters, this.#currentFilter); } + + #filterTypeChangeHandler = (evt) => { + evt.preventDefault(); + this.#handleFilterTypeChange(evt.target.value); + }; } diff --git a/src/view/new-sort-container-view.js b/src/view/new-sort-container-view.js index d5fdcc8..8cc9c73 100644 --- a/src/view/new-sort-container-view.js +++ b/src/view/new-sort-container-view.js @@ -1,10 +1,10 @@ import AbstractView from '../framework/view/abstract-view'; import { SortType } from '../const'; -function createNewTripSortTemplate() { +function createNewTripSortTemplate(currentSortType) { return `
    - +
    @@ -14,12 +14,12 @@ function createNewTripSortTemplate() {
    - +
    - +
    @@ -32,16 +32,18 @@ function createNewTripSortTemplate() { export default class NewTripSortView extends AbstractView { #handleSortTypeChange = null; + #currentSortType = null; - constructor({onSortTypeChange}) { + constructor({currentSortType, onSortTypeChange}) { super(); + this.#currentSortType = currentSortType; this.#handleSortTypeChange = onSortTypeChange; this.element.addEventListener('click', this.#sortTypeChangeHandler); } get template () { - return createNewTripSortTemplate(); + return createNewTripSortTemplate(this.#currentSortType); } #sortTypeChangeHandler = (evt) => { diff --git a/src/view/no-events-view.js b/src/view/no-events-view.js index 382d50d..cacd286 100644 --- a/src/view/no-events-view.js +++ b/src/view/no-events-view.js @@ -1,11 +1,27 @@ import AbstractView from '../framework/view/abstract-view'; +import { FilterType } from '../const.js'; -function createNoEventsViewTemplate() { - return '

    Loading...

    '; -} +const NoTasksTextType = { + [FilterType.EVERYTHING]: 'Click New Event to create your first point', + [FilterType.FUTURE]: 'There are no future events', + [FilterType.PRESENT]: 'There are no present events', + [FilterType.PAST]: 'There are no past events', +}; + +const createNoEventsViewTemplate = (filterType) => { + const noEventTextValue = NoTasksTextType[filterType]; + return `

    ${noEventTextValue}

    `; +}; export default class NoEventsView extends AbstractView { + #filterType = null; + + constructor({filterType}) { + super(); + this.#filterType = filterType; + } + get template () { - return createNoEventsViewTemplate(); + return createNoEventsViewTemplate(this.#filterType); } }