From bad25e7bbf81db96b663d0740e2f95fe50c63a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B0=D1=81=D1=82=D0=B0=D1=81=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=A2=D0=BE=D0=BF=D0=BE=D1=80=D0=BA=D0=BE=D0=B2=D0=B0?= Date: Tue, 28 May 2024 06:05:25 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B5=D0=BD=D1=8F=D0=B9-=D1=83=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D1=8F=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 7 +- public/index.html | 68 +++++----- src/const.js | 22 +++- src/main.js | 35 ++++- src/model/destinations-model.js | 17 ++- src/model/filter-model.js | 15 +++ src/model/offers-model.js | 17 ++- src/model/points-model.js | 38 +++++- src/presenter/filter-presenter.js | 58 ++++++++- src/presenter/new-point-button-presenter.js | 31 +++++ src/presenter/new-point-presenter.js | 74 +++++++++++ src/presenter/point-presenter.js | 39 ++++-- src/presenter/trip-presenter.js | 137 ++++++++++++++++---- src/render.js | 19 --- src/service/mock-service.js | 12 ++ src/utils.js | 9 +- src/view/edit-point-view.js | 78 ++++++++--- src/view/filter-view.js | 33 +++-- src/view/message-view.js | 33 +++++ src/view/new-point-button-view.js | 31 +++++ src/view/point-view.js | 2 - src/view/sort-view.js | 21 +-- src/view/trip-info-view.js | 4 +- 23 files changed, 633 insertions(+), 167 deletions(-) create mode 100644 src/model/filter-model.js create mode 100644 src/presenter/new-point-button-presenter.js create mode 100644 src/presenter/new-point-presenter.js delete mode 100644 src/render.js create mode 100644 src/view/message-view.js create mode 100644 src/view/new-point-button-view.js diff --git a/package-lock.json b/package-lock.json index ff003bc..13e625c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "19.0.0", "dependencies": { "dayjs": "1.11.6", - "flatpickr": "4.6.13" + "flatpickr": "4.6.13", + "he": "1.2.0" }, "devDependencies": { "@babel/core": "7.20.5", @@ -4591,7 +4592,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, "bin": { "he": "bin/he" } @@ -11305,8 +11305,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "hpack.js": { "version": "2.1.6", diff --git a/public/index.html b/public/index.html index 44c26b4..f0db507 100644 --- a/public/index.html +++ b/public/index.html @@ -1,41 +1,43 @@ - - - - - Big Trip - - - - - - +
+
+
+

Trip events

- + + + +
+
+
+ - - - - - diff --git a/src/const.js b/src/const.js index e675ccd..c9be995 100644 --- a/src/const.js +++ b/src/const.js @@ -89,6 +89,23 @@ const EnabledSortType = { [SortType.OFFERS]: false }; +const UserAction = { + UPDATE_POINT: 'UPDATE_POINT', + ADD_POINT: 'ADD_POINT', + DELETE_POINT: 'DELETE_POINT', +}; + +const UpdateType = { + PATCH: 'PATCH', + MINOR: 'MINOR', + MAJOR: 'MAJOR' +}; + +const EditType = { + EDITING: 'EDITING', + CREATING: 'CREATING' +} + export { OFFER_COUNT, DESTINATION_COUNT, @@ -103,5 +120,8 @@ export { FilterType, Mode, SortType, - EnabledSortType + EnabledSortType, + UserAction, + UpdateType, + EditType } diff --git a/src/main.js b/src/main.js index 04d7fa9..b5219ab 100644 --- a/src/main.js +++ b/src/main.js @@ -1,14 +1,18 @@ import TripInfoView from './view/trip-info-view.js'; import TripPresenter from './presenter/trip-presenter.js'; import FilterPresenter from './presenter/filter-presenter.js'; +import NewPointButtonPresenter from './presenter/new-point-button-presenter.js'; import MockService from './service/mock-service.js'; import DestinationsModel from './model/destinations-model.js'; import OffersModel from './model/offers-model.js'; import PointsModel from './model/points-model.js'; +import FilterModel from './model/filter-model.js'; import { render, RenderPosition } from './framework/render.js'; -const tripInfoElement = document.querySelector('.trip-main'); -const siteMainElement = document.querySelector('.page-main'); +const bodyElement = document.querySelector('body'); +const headerElement = bodyElement.querySelector('.page-header'); +const tripInfoElement = headerElement.querySelector('.trip-main'); +const siteMainElement = bodyElement.querySelector('.page-main'); const eventListElement = siteMainElement.querySelector('.trip-events'); const filterElement = tripInfoElement.querySelector('.trip-controls__filters'); @@ -16,11 +20,32 @@ const mockService = new MockService(); const destinationsModel = new DestinationsModel(mockService); const offersModel = new OffersModel(mockService); const pointsModel = new PointsModel(mockService); +const filterModel = new FilterModel(); -const tripPresenter = new TripPresenter({ tripContainer: eventListElement, destinationsModel, offersModel, pointsModel }); -const filterPresenter = new FilterPresenter({ container: filterElement, pointsModel }); +const newPointButtonPresenter = new NewPointButtonPresenter({ + container: tripInfoElement +}); + +const filterPresenter = new FilterPresenter({ + container: filterElement, + pointsModel, + filterModel +}); + +const tripPresenter = new TripPresenter({ + tripContainer: eventListElement, + destinationsModel, + offersModel, + pointsModel, + filterModel, + newPointButtonPresenter: newPointButtonPresenter +}); render(new TripInfoView(), tripInfoElement, RenderPosition.AFTERBEGIN); -tripPresenter.init(); +newPointButtonPresenter.init({ + onButtonClick: tripPresenter.newPointButtonClickHandler +}); + filterPresenter.init(); +tripPresenter.init(); diff --git a/src/model/destinations-model.js b/src/model/destinations-model.js index e2225a8..46ddff7 100644 --- a/src/model/destinations-model.js +++ b/src/model/destinations-model.js @@ -1,14 +1,21 @@ -export default class DestinationsModel { +import Observable from '../framework/observable.js'; + +export default class DestinationsModel extends Observable { + #service = null; + #destinations = []; + constructor(service) { - this.service = service; - this.destinations = this.service.getDestinations(); + super(); + this.#service = service; + this.#destinations = this.#service.getDestinations(); } get() { - return this.destinations; + return this.#destinations; } getById(id) { - return this.destinations.find((destination) => destination.id === id); + return this.#destinations + .find((destination) => destination.id === id); } } diff --git a/src/model/filter-model.js b/src/model/filter-model.js new file mode 100644 index 0000000..3152aca --- /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() { + return this.#filter; + } + + set(updateType, update) { + this.#filter = update; + this._notify(updateType, update) + } +} diff --git a/src/model/offers-model.js b/src/model/offers-model.js index 03added..091ba04 100644 --- a/src/model/offers-model.js +++ b/src/model/offers-model.js @@ -1,14 +1,21 @@ -export default class OffersModel { +import Observable from '../framework/observable.js'; + +export default class OffersModel extends Observable { + #service = null; + #offers = []; + constructor(service) { - this.service = service; - this.offers = this.service.getOffers(); + super(); + this.#service = service; + this.#offers = this.#service.getOffers(); } get() { - return this.offers; + return this.#offers; } getByType(type) { - return this.offers.find((offer) => offer.type === type).offers; + return this.#offers + .find((offer) => offer.type === type).offers; } } diff --git a/src/model/points-model.js b/src/model/points-model.js index ec813b9..9ab0c1f 100644 --- a/src/model/points-model.js +++ b/src/model/points-model.js @@ -1,10 +1,40 @@ -export default class PointsModel { +import Observable from '../framework/observable.js'; + +import { updateItem } from '../utils.js'; + +export default class PointsModel extends Observable { + #points = []; + #service = null; + constructor(service) { - this.service = service; - this.points = this.service.getPoints(); + super(); + this.#service = service; + this.#points = this.#service.getPoints(); } get() { - return this.points; + return this.#points; + } + + getById(id) { + return this.#points.find((point) => point.id === id); + } + + update(updateType, point) { + const updatedPoint = this.#service.updatePoint(point); + this.#points = updateItem(this.#points, updatedPoint); + this._notify(updateType, updatedPoint); + } + + add(updateType, point) { + const addedPoint = this.#service.addPoint(point); + this.#points.push(addedPoint); + this._notify(updateType, addedPoint); + } + + delete(updateType, point) { + this.#service.deletePoint(point); + this.#points = this.#points.filter((pointItem) => pointItem.id !== point.id); + this._notify(updateType); } } diff --git a/src/presenter/filter-presenter.js b/src/presenter/filter-presenter.js index 9c17249..2358338 100644 --- a/src/presenter/filter-presenter.js +++ b/src/presenter/filter-presenter.js @@ -1,19 +1,63 @@ -import FilterView from '../view/filter-view.js'; -import { render } from '../framework/render.js'; -import { generateFilters } from '../mock/filter.js'; +import FilterView from "../view/filter-view.js"; +import { render, replace, remove } from "../framework/render.js"; +import { UpdateType } from "../const.js"; +import { filter } from "../utils/filter.js"; export default class FilterPresenter { #container = null; + #filterComponent = null; + #pointsModel = null; - #filters = []; + #filterModel = null; + + #currentFilter = null; - constructor({ container, pointsModel }) { + constructor({ container, pointsModel, filterModel }) { this.#container = container; this.#pointsModel = pointsModel; - this.#filters = generateFilters(this.#pointsModel.get()); + this.#filterModel = filterModel; + + this.#pointsModel.addObserver(this.#modelEventHandler); + this.#filterModel.addObserver(this.#modelEventHandler); + } + + get filters() { + const points = this.#pointsModel.get(); + + return Object.entries(filter) + .map(([filterType, filterPoints]) => ({ + type: filterType, + isChecked: filterType === this.#currentFilter, + isDisabled: filterPoints(points).length === 0 + })); } init() { - render(new FilterView(this.#filters), this.#container); + this.#currentFilter = this.#filterModel.get(); + + const filters = this.filters; + + const prevFilterComponent = this.#filterComponent; + + this.#filterComponent = new FilterView({ + items: filters, + onItemChange: this.#filterTypeChangeHandler + }); + + if (prevFilterComponent === null) { + render(this.#filterComponent, this.#container); + return; + } + + replace(this.#filterComponent, prevFilterComponent); + remove(prevFilterComponent); } + + #filterTypeChangeHandler = (filterType) => { + this.#filterModel.set(UpdateType.MAJOR, filterType); + }; + + #modelEventHandler = () => { + this.init(); + }; } diff --git a/src/presenter/new-point-button-presenter.js b/src/presenter/new-point-button-presenter.js new file mode 100644 index 0000000..de1a90d --- /dev/null +++ b/src/presenter/new-point-button-presenter.js @@ -0,0 +1,31 @@ +import NewPointButtonView from '../view/new-point-button-view.js'; + +import { render } from '../framework/render.js'; + +export default class NewPointButtonPresenter { + #container = null; + #button = null; + #handleButtonClick = null; + + constructor({ container }) { + this.#container = container; + } + + init({ onButtonClick }) { + this.#handleButtonClick = onButtonClick; + this.#button = new NewPointButtonView({ onClick: this.#buttonClickHandler }); + render(this.#button, this.#container); + } + + disableButton() { + this.#button.setDisabled(true); + } + + enableButton() { + this.#button.setDisabled(false); + } + + #buttonClickHandler = () => { + this.#handleButtonClick(); + }; +} diff --git a/src/presenter/new-point-presenter.js b/src/presenter/new-point-presenter.js new file mode 100644 index 0000000..2de63a8 --- /dev/null +++ b/src/presenter/new-point-presenter.js @@ -0,0 +1,74 @@ +import EditPointView from "../view/edit-point-view.js"; + +import { remove, render, RenderPosition } from '../framework/render.js'; +import { UserAction, UpdateType, EditType } from "../const.js"; + +export default class NewPointPresenter { + #container = null; + + #destinationsModel = null; + #offersModel = null; + + #pointNewComponent = null; + + #handleDataChange = null; + #handleDestroy = null; + + constructor({ container, destinationsModel, offersModel, onDataChange, onDestroy }) { + this.#container = container; + this.#destinationsModel = destinationsModel; + this.#offersModel = offersModel; + this.#handleDataChange = onDataChange; + this.#handleDestroy = onDestroy; + }; + + init() { + if (this.#pointNewComponent !== null) { + return; + } + + this.#pointNewComponent = new EditPointView({ + pointDestination: this.#destinationsModel.get(), + pointOffers: this.#offersModel.get(), + onSubmitClick: this.#formSubmitHandler, + onResetClick: this.#resetButtonClickHandler, + pointType: EditType.CREATING + }); + + render(this.#pointNewComponent, this.#container, RenderPosition.AFTERBEGIN); + document.addEventListener('keydown', this.#escKeyDownHandler); + } + + destroy = ({ isCanceled = true } = {}) => { + if (this.#pointNewComponent === null) { + return; + } + + remove(this.#pointNewComponent); + this.#pointNewComponent = null; + document.removeEventListener('keydown', this.#escKeyDownHandler); + + this.#handleDestroy({ isCanceled }); + }; + + #formSubmitHandler = (point) => { + this.#handleDataChange( + UserAction.ADD_POINT, + UpdateType.MINOR, + point + ); + + this.destroy({ isCanceled: false }); + }; + + #resetButtonClickHandler = () => { + this.destroy(); + }; + + #escKeyDownHandler = (evt) => { + if (evt.key === 'Escape') { + evt.preventDefault(); + this.destroy(); + } + }; +} diff --git a/src/presenter/point-presenter.js b/src/presenter/point-presenter.js index 300f7ba..2d05712 100644 --- a/src/presenter/point-presenter.js +++ b/src/presenter/point-presenter.js @@ -1,7 +1,8 @@ import PointView from '../view/point-view.js'; import EditPointView from '../view/edit-point-view.js'; import { remove, render, replace } from '../framework/render.js'; -import { Mode } from '../const.js'; +import { Mode, UserAction, UpdateType } from '../const.js'; +import { isBigDifference } from '../utils.js'; export default class PointPresenter { #container = null; @@ -44,7 +45,8 @@ export default class PointPresenter { pointDestination: this.#destinationsModel.get(), pointOffers: this.#offersModel.get(), onSubmitClick: this.#formSubmitHandler, - onResetClick: this.#resetButtonClickHandler + onResetClick: this.#resetButtonClickHandler, + onDeleteClick: this.#deleteButtonClickHandler }) if (!prevPointComponent || !prevEditPointComponent) { @@ -89,6 +91,14 @@ export default class PointPresenter { this.#mode = Mode.DEFAULT; }; + #deleteButtonClickHandler = (point) => { + this.#handleDataChange( + UserAction.DELETE_POINT, + UpdateType.MINOR, + point + ); + }; + #escKeyDownHandler = (evt) => { if (evt.key === 'Escape') { evt.preventDefault(); @@ -102,16 +112,27 @@ export default class PointPresenter { }; #favoriteClickHandler = () => { - this.#handleDataChange({ - ...this.#point, - isFavorite: !this.#point.isFavorite - }); + this.#handleDataChange( + UserAction.UPDATE_POINT, + UpdateType.PATCH, + { + ...this.#point, + isFavorite: !this.#point.isFavorite + } + ); }; - #formSubmitHandler = (point) => { - this.#handleDataChange(point); + #formSubmitHandler = (updatedPoint) => { + const isMinor = isBigDifference(updatedPoint, this.#point); + + this.#handleDataChange( + UserAction.UPDATE_POINT, + isMinor ? UpdateType.MINOR : UpdateType.PATCH, + updatedPoint + ); + this.#replaceFormToPoint(); - } + }; #resetButtonClickHandler = () => { this.#editPointComponent.reset(this.#point); diff --git a/src/presenter/trip-presenter.js b/src/presenter/trip-presenter.js index 5b4f7bb..910e998 100644 --- a/src/presenter/trip-presenter.js +++ b/src/presenter/trip-presenter.js @@ -1,40 +1,71 @@ import SortView from '../view/sort-view.js'; import TripView from '../view/point-list-view.js'; -import EmptyListView from '../view/empty-list-view.js'; +import MessageView from '../view/message-view.js'; import PointPresenter from './point-presenter.js'; +import NewPointPresenter from './new-point-presenter.js'; import { render, replace, remove } from '../framework/render.js'; -import { updateItem } from '../utils.js'; -import { SortType } from '../const.js'; +import { SortType, EnabledSortType, UserAction, UpdateType, FilterType } from '../const.js'; import { sort } from '../utils/sort.js'; -import PointListView from '../view/point-list-view.js'; +import { filter } from '../utils/filter.js'; export default class TripPresenter { #tripContainer = null; + + #pointListComponent = new TripView(); + #sortComponent = null; + #messageComponent = null; + #destinationsModel = null; #offersModel = null; #pointsModel = null; - #pointListComponent = new TripView(); - #sortComponent = null; + #filterModel = null; - #points = []; + #pointPresenters = new Map(); + #newPointPresenter = null; + #newPointButtonPresenter = null; #currentSortType = SortType.DAY; + #isCreating = false; - #pointPresenters = new Map(); - - constructor({ tripContainer, destinationsModel, offersModel, pointsModel }) { + constructor({ tripContainer, destinationsModel, offersModel, pointsModel, filterModel, newPointButtonPresenter }) { this.#tripContainer = tripContainer; this.#destinationsModel = destinationsModel; this.#offersModel = offersModel; this.#pointsModel = pointsModel; + this.#filterModel = filterModel; + this.#newPointButtonPresenter = newPointButtonPresenter; - this.#points = sort[SortType.DAY]([...this.#pointsModel.get()]); + this.#newPointPresenter = new NewPointPresenter({ + container: this.#pointListComponent.element, + destinationsModel: this.#destinationsModel, + offersModel: this.#offersModel, + onDataChange: this.#pointChangeHandler, + onDestroy: this.#newPointDestroyHandler + }); + + this.#pointsModel.addObserver(this.#modelEventHandler); + this.#filterModel.addObserver(this.#modelEventHandler); + } + + get points() { + const filterType = this.#filterModel.get(); + const filteredPoints = filter[filterType](this.#pointsModel.get()); + + return sort[this.#currentSortType](filteredPoints); } init() { this.#renderBoard(); } + newPointButtonClickHandler = () => { + this.#isCreating = true; + this.#currentSortType = SortType.DAY; + this.#filterModel.set(UpdateType.MAJOR, FilterType.EVERYTHING); + this.#newPointButtonPresenter.disableButton(); + this.#newPointPresenter.init(); + }; + #renderPoint = (point) => { const pointPresenter = new PointPresenter({ container: this.#pointListComponent.element, @@ -49,13 +80,8 @@ export default class TripPresenter { this.#pointPresenters.set(point.id, pointPresenter); } - #sortPoints = (sortType) => { - this.#currentSortType = sortType; - this.#points = sort[this.#currentSortType](this.#points); - } - #renderPoints = () => { - this.#points.forEach((point) => { + this.points.forEach((point) => { this.#renderPoint(point); }); }; @@ -63,13 +89,21 @@ export default class TripPresenter { #clearPoints = () => { this.#pointPresenters.forEach((presenter) => presenter.destroy()); this.#pointPresenters.clear(); + this.#newPointPresenter.destroy(); } #renderSort = () => { const prevSortComponent = this.#sortComponent; + const sortTypes = Object.values(SortType) + .map((type) => ({ + type, + isChecked: (type === this.#currentSortType), + isDisabled: !EnabledSortType[type] + })); + this.#sortComponent = new SortView({ - sortType: this.#currentSortType, + items: sortTypes, onItemChange: this.#sortTypeChangeHandler }); @@ -79,16 +113,23 @@ export default class TripPresenter { } else { render(this.#sortComponent, this.#tripContainer); } + }; + + #renderMessage() { + this.#messageComponent = new MessageView({ + filterType: this.#filterModel.get() + }); + render(this.#messageComponent, this.#tripContainer); } + #renderPointContainer = () => { - this.#pointListComponent = new PointListView(); render(this.#pointListComponent, this.#tripContainer); } #renderBoard = () => { - if (this.#points.length === 0) { - render(new EmptyListView(), this.#tripContainer); + if (this.points.length === 0 && !this.#isCreating) { + this.#renderMessage(); return; } @@ -97,17 +138,63 @@ export default class TripPresenter { this.#renderPoints(); }; - #pointChangeHandler = (updatedPoint) => { - this.#points = updateItem(this.#points, updatedPoint); - this.#pointPresenters.get(updatedPoint.id).init(updatedPoint); + #clearBoard = ({ resetSortType = false } = {}) => { + this.#clearPoints(); + remove(this.#messageComponent); + remove(this.#sortComponent); + this.#sortComponent = null; + + if (resetSortType) { + this.#currentSortType = SortType.DAY; + } } + #pointChangeHandler = (actionType, updateType, update) => { + switch (actionType) { + case UserAction.UPDATE_POINT: + this.#pointsModel.update(updateType, update); + break; + case UserAction.DELETE_POINT: + this.#pointsModel.delete(updateType, update); + break; + case UserAction.ADD_POINT: + this.#pointsModel.add(updateType, update); + break; + } + }; + + #modelEventHandler = (updateType, data) => { + switch (updateType) { + case UpdateType.PATCH: + this.#pointPresenters?.get(data.id)?.init(data); + break; + case UpdateType.MINOR: + this.#clearBoard(); + this.#renderBoard(); + break; + case UpdateType.MAJOR: + this.#clearBoard({ resetSortType: true }); + this.#renderBoard(); + break; + } + }; + #modeChangeHandler = () => { this.#pointPresenters.forEach((presenter) => presenter.resetView()); + this.#newPointPresenter.destroy(); }; + #newPointDestroyHandler = () => { + this.#isCreating = false; + this.#newPointButtonPresenter.enableButton(); + if (this.points.length === 0 && isCanceled) { + this.#clearBoard(); + this.#renderBoard(); + } + } + #sortTypeChangeHandler = (sortType) => { - this.#sortPoints(sortType); + this.#currentSortType = sortType; this.#clearPoints(); this.#renderSort(); this.#renderPoints(); diff --git a/src/render.js b/src/render.js deleted file mode 100644 index c01e5a6..0000000 --- a/src/render.js +++ /dev/null @@ -1,19 +0,0 @@ -const RenderPosition = { - BEFOREBEGIN: 'beforebegin', - AFTERBEGIN: 'afterbegin', - BEFOREEND: 'beforeend', - AFTEREND: 'afterend', -}; - -function createElement(template) { - const newElement = document.createElement('div'); - newElement.innerHTML = template; - - return newElement.firstElementChild; -} - -function render(component, container, place = RenderPosition.BEFOREEND) { - container.insertAdjacentElement(place, component.getElement()); -} - -export { RenderPosition, createElement, render }; diff --git a/src/service/mock-service.js b/src/service/mock-service.js index 8b2d2bf..724b2af 100644 --- a/src/service/mock-service.js +++ b/src/service/mock-service.js @@ -59,4 +59,16 @@ export default class MockService { return generatePoint(type, destination.id, offerIds); }); } + + updatePoint(updatedPoint) { + return updatedPoint; + } + + addPoint(data) { + return { ...data, id: crypto.randomUUID() }; + } + + deletePoint() { + //... + } } diff --git a/src/utils.js b/src/utils.js index a185b19..e674817 100644 --- a/src/utils.js +++ b/src/utils.js @@ -117,6 +117,12 @@ function getPointsDurationDifference(pointA, pointB) { return durationB - durationA; } +function isBigDifference(pointA, pointB) { + return pointA.dateFrom !== pointB.dateFrom + || pointA.basePrice !== pointB.basePrice + || getPointDuration(pointA.dateFrom, pointA.dateTo) !== getPointDuration(pointB.dateFrom, pointB.dateTo); +} + export { getDate, getRandomInteger, @@ -132,5 +138,6 @@ export { updateItem, getPointsDateDifference, getPointsPriceDifference, - getPointsDurationDifference + getPointsDurationDifference, + isBigDifference } diff --git a/src/view/edit-point-view.js b/src/view/edit-point-view.js index 5a3911d..e4b078c 100644 --- a/src/view/edit-point-view.js +++ b/src/view/edit-point-view.js @@ -1,8 +1,39 @@ import AbstractStatefulView from '../framework/view/abstract-stateful-view.js'; -import { POINT_EMPTY, TYPES, CITIES } from "../const.js"; +import { POINT_EMPTY, TYPES, CITIES, EditType } from "../const.js"; import { formatStringToDateTime } from '../utils.js'; import 'flatpickr/dist/flatpickr.min.css'; import flatpickr from 'flatpickr'; +import he from 'he'; + +const ButtonLabel = { + CANCEL_DEFAULT: 'Cancel', + DELETE_DEFAULT: 'Delete', + SAVE_DEFAULT: 'Save' +} + +const createSaveButtonTemplate = () => { + const label = ButtonLabel.SAVE_DEFAULT; + return ``; +} + +const createResetButtonTemplate = ({ pointType }) => { + const label = pointType === EditType.CREATING + ? ButtonLabel.CANCEL_DEFAULT + : ButtonLabel.DELETE_DEFAULT; + return ``; +} + +const createRollupButtonTemplate = () => { + return `` +} + +const createPointEditControlsTemplate = ({ pointType }) => { + return ` + ${createSaveButtonTemplate()} + ${createResetButtonTemplate({ pointType })} + ${(pointType === EditType.EDITING) ? createRollupButtonTemplate() : ''} + `; +} const createPointCitiesOptionsTemplate = () => { return ( @@ -30,8 +61,7 @@ const createPointTypesTemplate = (currentType) => { } const createPointOffersTemplate = ({ offersId, currentOffers }) => { - console.log(offersId) - const offerItems = currentOffers.map((offer) => { + const offerItems = currentOffers.map(offer => { const isChecked = offersId.includes(offer.id) ? 'checked' : ''; return ( `
@@ -48,7 +78,7 @@ const createPointOffersTemplate = ({ offersId, currentOffers }) => { return `
${offerItems}
`; } -const createEditPointTemplate = ({ state, pointDestination, pointOffers }) => { +const createEditPointTemplate = ({ state, pointDestination, pointOffers, pointType }) => { const { point } = state; const { basePrice, dateFrom, dateTo, type } = point; @@ -76,28 +106,24 @@ const createEditPointTemplate = ({ state, pointDestination, pointOffers }) => { - + ${createPointCitiesOptionsTemplate()}
- + - +
- +
- - - + ${createPointEditControlsTemplate({ pointType })}
${(currentOffers.length !== 0) ? `
@@ -122,15 +148,19 @@ export default class EditPointView extends AbstractStatefulView { #pointOffers = null; #handleSubmitClick = null; #handleResetClick = null; + #handleDeleteClick = null; #datepickerFrom = null; #datepickerTo = null; + #pointType; - constructor({ point = POINT_EMPTY, pointDestination, pointOffers, onSubmitClick, onResetClick }) { + constructor({ point = POINT_EMPTY, pointDestination, pointOffers, pointType = EditType.EDITING, onSubmitClick, onResetClick, onDeleteClick }) { super(); this.#pointDestination = pointDestination; this.#pointOffers = pointOffers; this.#handleSubmitClick = onSubmitClick; this.#handleResetClick = onResetClick; + this.#handleDeleteClick = onDeleteClick; + this.#pointType = pointType; this._setState(EditPointView.parsePointToState({ point })) @@ -141,7 +171,8 @@ export default class EditPointView extends AbstractStatefulView { return createEditPointTemplate({ state: this._state, pointDestination: this.#pointDestination, - pointOffers: this.#pointOffers + pointOffers: this.#pointOffers, + pointType: this.#pointType }); } @@ -162,11 +193,17 @@ export default class EditPointView extends AbstractStatefulView { }; _restoreHandlers = () => { - this.element.querySelector('form').addEventListener('submit', this.#formSubmitHandler); + if (this.#pointType === EditType.EDITING) { + this.element.querySelector('.event__rollup-btn').addEventListener('click', this.#resetButtonClickHandler); - this.element.querySelector('.event__rollup-btn').addEventListener('click', this.#resetButtonClickHandler); + this.element.querySelector('.event__reset-btn').addEventListener('click', this.#deleteButtonClickHandler) + } - this.element.querySelector('.event__reset-btn').addEventListener('click', this.#resetButtonClickHandler) + if (this.#pointType === EditType.CREATING) { + this.element.querySelector('.event__reset-btn').addEventListener('click', this.#resetButtonClickHandler) + } + + this.element.querySelector('form').addEventListener('submit', this.#formSubmitHandler); this.element.querySelector('.event__type-group').addEventListener('change', this.#typeChangeHandler); @@ -189,6 +226,11 @@ export default class EditPointView extends AbstractStatefulView { this.#handleResetClick(); } + #deleteButtonClickHandler = (evt) => { + evt.preventDefault(); + this.#handleDeleteClick(EditPointView.parseStateToPoint(this._state)); + } + #typeChangeHandler = (evt) => { this.updateElement({ point: { diff --git a/src/view/filter-view.js b/src/view/filter-view.js index a969897..3c37819 100644 --- a/src/view/filter-view.js +++ b/src/view/filter-view.js @@ -5,31 +5,36 @@ const createFilterItemsTemplate = ({ filters }) => { const filterItems = filters.map(filter => { return ( `
- - -
` - ); + + + ` + ) }).join(''); return filterItems; -}; +} const createFilterTemplate = ({ filters }) => { return ( `
- ${createFilterItemsTemplate({ filters })} - + ${createFilterItemsTemplate({ filters })} +
` ); -}; +} export default class FilterView extends AbstractView { - #filters = []; + #filters = null; + #handleFilterTypeChange = null; - constructor(filters) { + constructor({ items, onItemChange }) { super(); - this.#filters = filters; + this.#filters = items; + this.#handleFilterTypeChange = onItemChange; + + this.element.addEventListener('change', this.#filterTypeChangeHandler); } get template() { @@ -38,4 +43,8 @@ export default class FilterView extends AbstractView { }); } + #filterTypeChangeHandler = (evt) => { + evt.preventDefault(); + this.#handleFilterTypeChange(evt.target.value); + }; } diff --git a/src/view/message-view.js b/src/view/message-view.js new file mode 100644 index 0000000..a94abf8 --- /dev/null +++ b/src/view/message-view.js @@ -0,0 +1,33 @@ +import AbstractView from "../framework/view/abstract-view.js"; +import { FilterType } from "../const.js"; + +const FilterMessage = { + [FilterType.EVERYTHING]: 'Click New Event to create your first point', + [FilterType.FUTURE]: 'There are no future events now', + [FilterType.PRESENT]: 'There are no present events now', + [FilterType.PAST]: 'There are no past events now' +} + +const createMessageTemplate = ({ message }) => { + return ( + `
+

Trip events

+

${message}

+
` + ); +} + +export default class MessageView extends AbstractView { + #filterType; + + constructor({ filterType }) { + super(); + this.#filterType = filterType; + } + + get template() { + const message = FilterMessage[this.#filterType]; + + return createMessageTemplate({ message }); + } +} diff --git a/src/view/new-point-button-view.js b/src/view/new-point-button-view.js new file mode 100644 index 0000000..4a35ce8 --- /dev/null +++ b/src/view/new-point-button-view.js @@ -0,0 +1,31 @@ +import AbstractView from "../framework/view/abstract-view.js"; + +const createNewPointButtonTemplate = () => { + return ( + '' + ); +} + +export default class NewPointButtonView extends AbstractView { + #handleClick = null; + + constructor({ onClick }) { + super(); + this.#handleClick = onClick; + + this.element.addEventListener('click', this.#clickHandler); + } + + get template() { + return createNewPointButtonTemplate(); + } + + setDisabled = (isDisabled) => { + this.element.disabled = isDisabled; + } + + #clickHandler = (evt) => { + evt.preventDefault(); + this.#handleClick(); + } +} diff --git a/src/view/point-view.js b/src/view/point-view.js index 6d13c8a..4c1937c 100644 --- a/src/view/point-view.js +++ b/src/view/point-view.js @@ -2,9 +2,7 @@ import AbstractView from '../framework/view/abstract-view.js'; import { formatStringToDateTime, formatStringToShortDate, formatStringToTime, getPointDuration } from '../utils.js'; const createPointOffersTemplate = ({ offersId, pointOffers }) => { - const selectedOffers = pointOffers.filter((offer) => offersId.includes(offer.id)); - console.log(selectedOffers) if (selectedOffers.length === 0) { return ''; diff --git a/src/view/sort-view.js b/src/view/sort-view.js index 73e0d81..2b41ce6 100644 --- a/src/view/sort-view.js +++ b/src/view/sort-view.js @@ -1,5 +1,4 @@ import AbstractView from '../framework/view/abstract-view.js'; -import { EnabledSortType, SortType } from '../const.js'; const createSortItemsTemplate = ({ items }) => { const sortItems = items.map(sortItem => { @@ -35,20 +34,14 @@ const createSortTemplate = ({ items }) => { export default class SortView extends AbstractView { #items = null; - #onItemChange = null; + #handleSortTypeChange = null; - constructor({ sortType, onItemChange }) { + constructor({ items, onItemChange }) { super(); + this.#items = items; + this.#handleSortTypeChange = onItemChange; - this.#items = Object.values(SortType).map((type) => ({ - type, - isChecked: type === sortType, - isDisabled: !EnabledSortType[type] - })); - - this.#onItemChange = onItemChange; - - this.element.addEventListener('change', this.#itemChangeHandler); + this.element.addEventListener('change', this.#sortTypeChangeHandler); } get template() { @@ -57,8 +50,8 @@ export default class SortView extends AbstractView { }); } - #itemChangeHandler = (evt) => { + #sortTypeChangeHandler = (evt) => { evt.preventDefault(); - this.#onItemChange(evt.target.dataset.sortType); + this.#handleSortTypeChange(evt.target.dataset.sortType); }; } diff --git a/src/view/trip-info-view.js b/src/view/trip-info-view.js index 49fbf6c..a70cb81 100644 --- a/src/view/trip-info-view.js +++ b/src/view/trip-info-view.js @@ -5,16 +5,14 @@ const createTripInfoTemplate = () => { `

Amsterdam — Chamonix — Geneva

-

Mar 18 — 20

-

Total: € 1230

` ); -}; +} export default class TripInfoView extends AbstractView { get template() {