diff --git a/package-lock.json b/package-lock.json index cc20247..b70ff83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "name": "big-trip", "version": "22.0.0", "dependencies": { - "dayjs": "1.11.7" + "dayjs": "1.11.7", + "flatpickr": "4.6.13" }, "devDependencies": { "@babel/core": "7.21.4", @@ -4258,6 +4259,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatpickr": { + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", + "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" + }, "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", diff --git a/package.json b/package.json index d6de6a1..1017e18 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "node": "20" }, "dependencies": { - "dayjs": "1.11.7" + "dayjs": "1.11.7", + "flatpickr": "4.6.13" } } diff --git a/src/const.js b/src/const.js index c33ea98..eba4e8c 100644 --- a/src/const.js +++ b/src/const.js @@ -1,15 +1,5 @@ export const EVENT_TYPES = ['taxi', 'bus', 'train', 'ship', 'drive', 'flight', 'check-in', 'sightseeing', 'restaurant']; -export const getDefaultPoint = () => ({ - 'basePrice': 0, - 'dateFrom': new Date().toISOString(), - 'dateTo': new Date().toISOString(), - 'destination': '', - 'isFavorite': false, - 'offers': [], - 'type': EVENT_TYPES[0], -}); - export const FilterTypes = { EVERYTHING: 'everything', FUTURE: 'future', @@ -31,3 +21,13 @@ export const DateFormats = { DATE_TIME: 'YYYY-MM-DDTHH:mm', MONTH_DAY: 'MMM D', }; + +export function idCounter() { + let id = 1; + + return { + getId: function() { + return id++; + } + }; +} diff --git a/src/main.js b/src/main.js index 92b7990..c0e0c61 100644 --- a/src/main.js +++ b/src/main.js @@ -3,8 +3,9 @@ import TripPlanPresenter from './presenter/trip-plan-presenter'; const headerContainer = document.querySelector('.trip-controls__filters'); const mainContainer = document.querySelector('.trip-events'); +const newPointButton = document.querySelector('.trip-main__event-add-btn'); const pointModel = new PointModel(); pointModel.init(); -const tripPlanPresenter = new TripPlanPresenter(headerContainer, mainContainer, pointModel); +const tripPlanPresenter = new TripPlanPresenter(headerContainer, mainContainer, pointModel, newPointButton); tripPlanPresenter.init(); diff --git a/src/mock/points.js b/src/mock/points.js index 447eafe..9374165 100644 --- a/src/mock/points.js +++ b/src/mock/points.js @@ -1,42 +1,55 @@ +import { idCounter } from '../const.js'; +export const id = idCounter(); + +export const getDefaultPoint = () => ({ + basePrice: 0, + dateFrom: new Date().toISOString(), + dateTo: new Date().toISOString(), + destination: '2', + isFavorite: false, + offers: [], + type: 'flight', +}); + export const points = [ { - 'id': '1', - 'basePrice': 1200, - 'dateFrom': '2019-07-09T18:15:56.845Z', - 'dateTo': '2019-07-10T19:55:56.845Z', - 'destination': '1', - 'isFavorite': true, - 'offers': [ '2' ], - 'type': 'taxi' + id: id.getId(), + basePrice: 1200, + dateFrom: '2019-07-09T18:15:56.845Z', + dateTo: '2019-07-10T19:55:56.845Z', + destination: '1', + isFavorite: true, + offers: ['2'], + type: 'taxi', }, { - 'id': '2', - 'basePrice': 1100, - 'dateFrom': '2019-06-10T21:55:56.845Z', - 'dateTo': '2019-06-11T12:22:13.375Z', - 'destination': '2', - 'isFavorite': false, - 'offers': [ '5', '6' ], - 'type': 'bus' + id: id.getId(), + basePrice: 1100, + dateFrom: '2019-06-10T21:55:56.845Z', + dateTo: '2019-06-11T12:22:13.375Z', + destination: '2', + isFavorite: false, + offers: ['5', '6'], + type: 'bus', }, { - 'id': '3', - 'basePrice': 1500, - 'dateFrom': '2019-07-10T07:55:56.845Z', - 'dateTo': '2019-07-10T17:19:13.375Z', - 'destination': '3', - 'isFavorite': true, - 'offers': [ '7' ], - 'type': 'flight' + id: id.getId(), + basePrice: 1500, + dateFrom: '2019-07-10T07:55:56.845Z', + dateTo: '2019-07-10T17:19:13.375Z', + destination: '3', + isFavorite: true, + offers: ['7'], + type: 'flight', }, { - 'id': '4', - 'basePrice': 1900, - 'dateFrom': '2019-08-12T07:55:56.845Z', - 'dateTo': '2019-08-12T11:19:13.375Z', - 'destination': '2', - 'isFavorite': false, - 'offers': [], - 'type': 'train' + id: id.getId(), + basePrice: 1900, + dateFrom: '2019-08-12T07:55:56.845Z', + dateTo: '2019-08-12T11:19:13.375Z', + destination: '2', + isFavorite: false, + offers: [], + type: 'train', }, ]; diff --git a/src/model/point-model.js b/src/model/point-model.js index c04d96a..896e5bd 100644 --- a/src/model/point-model.js +++ b/src/model/point-model.js @@ -1,4 +1,4 @@ -import { points } from '../mock/points'; +import { points, id } from '../mock/points'; import { destinations } from '../mock/destinations'; import { offers } from '../mock/offers'; import { FilterTypes } from '../const'; @@ -37,4 +37,15 @@ export default class PointModel { item.id === point.id ? point : item ); } + + addPoint(newPoint) { + const point = { ...newPoint, id: id.getId() }; + this.#points.push(point); + return point; + } + + deletePoint(point) { + const newPoints = this.#points.filter((item) => item.id !== point.id); + this.#points = newPoints; + } } diff --git a/src/presenter/point-presenter.js b/src/presenter/point-presenter.js index 73562d9..a667f0c 100644 --- a/src/presenter/point-presenter.js +++ b/src/presenter/point-presenter.js @@ -8,19 +8,21 @@ export default class PointPresenter { #offers = null; #view = null; #changeEditingPoint = null; + #deletePoint = null; - constructor({ destinations, offers, container, updatePoint, changeEditingPoint }) { + constructor({ destinations, offers, container, updatePoint, changeEditingPoint, deletePoint }) { this.#pointsListComponent = container; this.updatePoint = updatePoint; this.#destinations = destinations; this.#offers = offers; this.#changeEditingPoint = changeEditingPoint; + this.#deletePoint = deletePoint; } - init(point) { + init(point, renderPosition) { this.point = point; this.#view = this.createPointView(); - render(this.#view, this.#pointsListComponent); + render(this.#view, this.#pointsListComponent, renderPosition); } createPointView() { @@ -43,7 +45,8 @@ export default class PointPresenter { destinations: this.#destinations, offers: this.#offers, onFormClose: this.#onFormClose, - onFormSubmit: this.#onFormSubmit + onFormSubmit: this.#onFormSubmit, + onDeletePoint: this.#onDeletePoint, }); } @@ -87,4 +90,9 @@ export default class PointPresenter { #onFormSubmit = (updatedPoint) => { this.updatePoint(updatedPoint); }; + + #onDeletePoint = (deletedPoint) => { + this.#deletePoint(deletedPoint); + }; + } diff --git a/src/presenter/trip-plan-presenter.js b/src/presenter/trip-plan-presenter.js index 4c9cbfc..f18fcc0 100644 --- a/src/presenter/trip-plan-presenter.js +++ b/src/presenter/trip-plan-presenter.js @@ -2,9 +2,10 @@ import FilterForm from '../view/filter'; import SortForm from '../view/sort'; import PointsList from '../view/points-list'; import EmptyPointsListView from '../view/empty-points-list.js'; -import { render } from '../framework/render.js'; +import { render, RenderPosition } from '../framework/render.js'; import PointPresenter from './point-presenter.js'; import { sortByDay, sortByTime, sortByPrice } from '../helpers.js'; +import { getDefaultPoint } from '../mock/points.js'; export default class TripPlanPresenter { #pointsListComponent = new PointsList(); @@ -14,16 +15,20 @@ export default class TripPlanPresenter { #pointPresenters = new Map(); editingPointId = null; #currentSortType = 'sort-day'; + #newPointButton = null; + #newPointPresenter = null; - constructor(headerContainer, mainContainer, pointModel) { + constructor(headerContainer, mainContainer, pointModel, newPointButton) { this.#filterContainer = headerContainer; this.#eventsContainer = mainContainer; this.#pointModel = pointModel; + this.#newPointButton = newPointButton; } init() { render(new FilterForm(), this.#filterContainer); this.#renderPointsList(); + this.#newPointButton.addEventListener('click', this.#onNewEventClick); } #renderPointsList() { @@ -70,11 +75,30 @@ export default class TripPlanPresenter { container: this.#pointsListComponent.element, updatePoint: this.updatePoint, changeEditingPoint: this.changeEditingPoint, + deletePoint: this.#deletePoint, }); pointPresenter.init(point); this.#pointPresenters.set(point.id, pointPresenter); }; + #onNewEventClick = () => { + this.#newPointPresenter = new PointPresenter({ + destinations: this.#pointModel.destinations, + offers: this.#pointModel.offers, + container: this.#pointsListComponent.element, + updatePoint: this.updatePoint, + changeEditingPoint: this.#onCancelButton, + + }); + this.#newPointPresenter.init(getDefaultPoint(), RenderPosition.AFTERBEGIN); + this.#newPointPresenter.enableEditMode(); + }; + + #onCancelButton = () => { + this.#newPointPresenter.removeComponent(); + this.#newPointPresenter = null; + }; + changeEditingPoint = (pointId) => { if (pointId === null) { this.#pointPresenters.get(this.editingPointId).disableEditMode(); @@ -114,4 +138,9 @@ export default class TripPlanPresenter { this.#clearPointsList(); this.#renderPoints(); }; + + #deletePoint = (deletedPoint) => { + this.#pointPresenters.get(deletedPoint.id).removeComponent(); + this.#pointModel.deletePoint(deletedPoint); + }; } diff --git a/src/view/point-form.js b/src/view/point-form.js index a17b86a..3c95845 100644 --- a/src/view/point-form.js +++ b/src/view/point-form.js @@ -4,6 +4,8 @@ import { EVENT_TYPES } from '../const'; import { getDate } from '../helpers'; import { DateFormats } from '../const.js'; import { remove } from '../framework/render.js'; +import flatpickr from 'flatpickr'; +import 'flatpickr/dist/flatpickr.min.css'; const createTypesList = (pointId, type) => `
- - ${point.id - ? ` + ${pointId === 'new-point' + ? '' + : `` - : ''} + `}
@@ -125,14 +127,17 @@ export default class PointForm extends AbstractStatefulView { #offers = null; #handleFormClose = null; #handleFormSubmit = null; + #datepicker = null; + #onDeletePoint = null; - constructor({point, destinations, offers, onFormClose, onFormSubmit}) { + constructor({point, destinations, offers, onFormClose, onFormSubmit, onDeletePoint}) { super(); this.#point = point; this.#destinations = destinations; this.#offers = offers; this.#handleFormClose = onFormClose; this.#handleFormSubmit = onFormSubmit; + this.#onDeletePoint = onDeletePoint; this._setState(PointForm.parsePointToState(point)); this._restoreHandlers(); @@ -153,10 +158,20 @@ export default class PointForm extends AbstractStatefulView { _restoreHandlers() { document.addEventListener('keydown', this.#escapeHandler); - this.element.querySelector('.event__rollup-btn').addEventListener('click', this.#formCloseHandler); + this.element.querySelector('.event__rollup-btn')?.addEventListener('click', this.#formCloseHandler); this.element.querySelector('.event__save-btn').addEventListener('click', this.#formSubmitHandler); this.element.querySelector('.event__type-list').addEventListener('change', this.#onEventTypeChange); this.element.querySelector('.event__input--destination').addEventListener('change', this.#onDestinationChange); + this.element.querySelector('.event__input--price').addEventListener('change', this.#onPriceChange); + this.element.querySelector('.event__reset-btn').addEventListener('click', this.#formCloseHandler); + if (this.#point.id) { + this.element.querySelector('.event__reset-btn').addEventListener('click', this.#deletePointHandler); + } else { + this.element.querySelector('.event__reset-btn').addEventListener('click', this.#formCloseHandler); + } + + this.#setDatepicker('input[name=event-start-time]', this._state.dateFrom, this.#dateFromChangeHandler); + this.#setDatepicker('input[name=event-end-time]', this._state.dateTo, this.#dateToChangeHandler); } #formCloseHandler = (evt) => { @@ -175,6 +190,10 @@ export default class PointForm extends AbstractStatefulView { } }; + #deletePointHandler = () => { + this.#onDeletePoint(this.#point); + }; + remove = () => { remove(this); document.removeEventListener('keydown', this.#escapeHandler); @@ -188,4 +207,27 @@ export default class PointForm extends AbstractStatefulView { const newDestination = this.#destinations.find((destination) => destination.name === evt.target.value); this.updateElement({destination: newDestination.id}); }; + + #onPriceChange = (evt) => { + const newPrice = evt.target.value; + this.updateElement({basePrice: newPrice}); + }; + + #setDatepicker = (input, defaultTime, handler) => { + this.#datepicker = flatpickr(this.element.querySelector(input), { + enableTime: true, + time_24hr: true, //eslint-disable-line + dateFormat: 'y/m/d H:i', + defaultDate: defaultTime, + onChange: handler, + }); + }; + + #dateFromChangeHandler = ([newDate]) => { + this.updateElement({dateFrom: newDate}); + }; + + #dateToChangeHandler = ([newDate]) => { + this.updateElement({dateTo: newDate}); + }; } diff --git a/src/view/point.js b/src/view/point.js index f9dae4f..14654be 100644 --- a/src/view/point.js +++ b/src/view/point.js @@ -9,7 +9,7 @@ const createPointTemplate = (point, destinations, offers) => { const pointDestination = destinations.find((item) => item.id === point.destination).name; const typeOffers = offers.find((item) => item.type === point.type).offers; - const pointOffers = typeOffers.filter((item) => point.offers.includes(item.id)); + const pointOffers = typeOffers?.filter((item) => point.offers.includes(item.id)); return `