diff --git a/src/main.js b/src/main.js index 9f9c926..a3a0ec5 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,7 @@ import FilterModel from './model/filter-model.js'; import PointsModel from './model/points-model.js'; import PointApiService from './point-api-service.js'; -const AUTHORIZATION = 'Basic eo2w590ikq9889h'; +const AUTHORIZATION = 'Basic eo2w590ikq97389h'; const END_POINT = 'https://22.objects.htmlacademy.pro/big-trip'; const tripSection = document.querySelector('.trip-events'); diff --git a/src/model/points-model.js b/src/model/points-model.js index bc72662..07d6c8b 100644 --- a/src/model/points-model.js +++ b/src/model/points-model.js @@ -6,7 +6,15 @@ export default class PointsModel extends Observable { #points = []; #destinations = []; #offers = []; - #defaultPoint = []; + #defaultPoint = { + basePrice: 0, + dateFrom: '', + dateTo: '', + destination: '', + isFavorite: false, + offers: [], + type: 'flight' + }; constructor({pointApiService}) { super(); @@ -67,28 +75,37 @@ export default class PointsModel extends Observable { } } - addPoint(updateType, update) { - this.#points = [ - update, - ...this.#points - ]; - - this._notify(updateType, update); + async addPoint(updateType, update) { + try { + const response = await this.#pointApiService.addPoint(update); + const newPoint = this.#adaptToCLient(response); + this.#points = [ + newPoint, + ...this.#points + ]; + this._notify(updateType, newPoint); + } catch (err) { + throw new Error(err); + } } - deleteTask(updateType, update) { + async deletePoint(updateType, update) { const index = this.#points.findIndex((point) => point.id === update.id); if (index === -1) { throw new Error ('Can\'t delete unexisting point'); } - this.#points = [ - ...this.#points.slice(0, index), - ...this.#points.slice(index + 1) - ]; - - this._notify(updateType); + try { + await this.#pointApiService.deletePoint(update); + this.#points = [ + ...this.#points.slice(0, index), + ...this.#points.slice(index + 1) + ]; + this._notify(updateType); + } catch (err) { + throw new Error(err); + } } #adaptToCLient(point) { diff --git a/src/point-api-service.js b/src/point-api-service.js index b7f690b..f54c3c5 100644 --- a/src/point-api-service.js +++ b/src/point-api-service.js @@ -1,8 +1,10 @@ import ApiService from './framework/api-service'; const Method = { - GET: 'Get', - PUT: 'Put' + GET: 'GET', + PUT: 'PUT', + POST: 'POST', + DELETE: 'DELETE' }; export default class PointApiService extends ApiService { @@ -33,9 +35,31 @@ export default class PointApiService extends ApiService { return parsedResponse; } + async addPoint(point) { + const response = await this._load({ + url: 'points', + method: Method.POST, + body: JSON.stringify(this.#adaptToServer(point)), + headers: new Headers({'Content-Type': 'application/json'}) + }); + + const parsedResponse = await ApiService.parseResponse(response); + + return parsedResponse; + } + + async deletePoint(point) { + const response = await this._load({ + url: `points/${point.id}`, + method: Method.DELETE + }); + + return response; + } + #adaptToServer(point) { const adaptedPoint = {...point, - 'base_price': point.basePrice, + 'base_price': parseInt(point.basePrice, 10), 'date_from': point.dateFrom instanceof Date ? point.dateFrom.toISOString() : null, 'date_to': point.dateTo instanceof Date ? point.dateTo.toISOString() : null, 'is_favorite': point.isFavorite diff --git a/src/presenter/new-point-presenter.js b/src/presenter/new-point-presenter.js index 36b04fc..741c41c 100644 --- a/src/presenter/new-point-presenter.js +++ b/src/presenter/new-point-presenter.js @@ -45,14 +45,31 @@ export default class NewPointPresenter { document.removeEventListener('keydown', this.#escKeyDownHandler); } + setSaving() { + this.#createTripForm.updateElement({ + isDisabled: true, + isSaving: true + }); + } + + setAborting() { + const resetFormState = () => { + this.#createTripForm.updateElement({ + isDisabled: false, + isSaving: false, + }); + }; + + + this.#createTripForm.shake(resetFormState); + } + #handleFormSubmit = (point) => { this.#handleDataChange( UserAction.ADD_POINT, UpdateType.MINOR, - {id: `test${Math.random().toString()}`, ...point} + point ); - - this.destroy(); }; #handleDeleteClick = () => { diff --git a/src/presenter/point-presenter.js b/src/presenter/point-presenter.js index 6b54573..a56a3a1 100644 --- a/src/presenter/point-presenter.js +++ b/src/presenter/point-presenter.js @@ -105,6 +105,41 @@ export default class PointPresenter { } } + setAborting() { + if (this.#mode === Mode.DEFAULT) { + this.#tripPoint.shake(); + return; + } + + const resetFormState = () => { + this.#editTripForm.updateElement({ + isDisabled: false, + isSaving: false, + isDeleting: false + }); + }; + + this.#editTripForm.shake(resetFormState); + } + + setSaving() { + if (this.#mode === Mode.EDITING) { + this.#editTripForm.updateElement({ + isDisabled: true, + isSaving: true + }); + } + } + + setDeleting() { + if (this.#mode === Mode.EDITING) { + this.#editTripForm.updateElement({ + isDisabled: true, + isDeleting: true + }); + } + } + #handleFavoriteClick = () => { this.#handleDataChange( UserAction.UPDATE_POINT, diff --git a/src/presenter/trip-presenter.js b/src/presenter/trip-presenter.js index 54c7d15..8012ff5 100644 --- a/src/presenter/trip-presenter.js +++ b/src/presenter/trip-presenter.js @@ -5,9 +5,14 @@ import ListEmptyView from '../view/list-empty-view.js'; import PointPresenter from './point-presenter.js'; import NewPointPresenter from './new-point-presenter.js'; import LoadingListView from '../view/loading-list-view.js'; +import UiBlocker from '../framework/ui-blocker/ui-blocker.js'; import { sortPointsByDay, findSortingDuration, filter } from '../util.js'; import { FilterType, SortingType, UpdateType, UserAction } from '../const.js'; +const TimeLimit = { + LOWER_LIMIT: 350, + UPPER_LIMIT: 100 +}; export default class TripPresenter { #tripList = new TripListView(); @@ -25,9 +30,12 @@ export default class TripPresenter { #filterType; #noPointsComponent; #newPointPresenter = null; - #defaultPoint = null; #isLoading = true; + #uiBlocker = new UiBlocker({ + lowerLimit: TimeLimit.LOWER_LIMIT, + upperLimit: TimeLimit.UPPER_LIMIT + }); constructor ({ container, pointsModel, filterModel }) { this.#container = container; @@ -37,14 +45,13 @@ export default class TripPresenter { tripList: this.#tripList, onDataChange: this.#handleViewAction, }); - this.#pointsModel.addObserver(this.#handleModelEvent); this.#filterModel.addObserver(this.#handleModelEvent); + this.#pointsModel.addObserver(this.#handleModelEvent); } get points () { this.#destinations = this.#pointsModel.destinations; this.#offers = this.#pointsModel.offers; - this.#defaultPoint = this.#pointsModel.defaultPoint[0]; this.#filterType = this.#filterModel.filter; const points = this.#pointsModel.points; const filteredPoints = filter[this.#filterType](points); @@ -63,13 +70,12 @@ export default class TripPresenter { init () { this.#renderList(); - // this.#createPoint(); } createPoint() { this.#currentSortingType = SortingType.DAY; this.#filterModel.setFilter(UpdateType.MAJOR, FilterType.EVERYTHING); - this.#newPointPresenter.init(this.#defaultPoint, this.#destinations, this.#offers); + this.#newPointPresenter.init(this.#pointsModel.defaultPoint, this.#destinations, this.#offers); } #handleModeChange = () => { @@ -98,18 +104,35 @@ export default class TripPresenter { this.#currentSortingType = sortingType; } - #handleViewAction = (actionType, updateType, update) => { + #handleViewAction = async (actionType, updateType, update) => { + this.#uiBlocker.block(); switch (actionType) { case UserAction.UPDATE_POINT: - this.#pointsModel.updatePoint(updateType, update); + this.#pointPresenters.get(update.id).setSaving(); + try { + await this.#pointsModel.updatePoint(updateType, update); + } catch (err) { + this.#pointPresenters.get(update.id).setAborting(); + } break; case UserAction.ADD_POINT: - this.#pointsModel.addPoint(updateType, update); + this.#newPointPresenter.setSaving(); + try { + await this.#pointsModel.addPoint(updateType, update); + } catch(err) { + this.#newPointPresenter.setAborting(); + } break; case UserAction.DELETE_POINT: - this.#pointsModel.deleteTask(updateType, update); + this.#pointPresenters.get(update.id).setDeleting(); + try { + await this.#pointsModel.deletePoint(updateType, update); + } catch(err) { + this.#pointPresenters.get(update.id).setAborting(); + } break; } + this.#uiBlocker.unblock(); }; #handleModelEvent = (updateType, data) => { @@ -160,16 +183,20 @@ export default class TripPresenter { pointPresenter.init(point, destination, offer); this.#pointPresenters.set(point.id, pointPresenter); + remove(this.#noPointsComponent); } #renderLoading() { render(this.#loadingComponent, this.#container, RenderPosition.AFTERBEGIN); } - #renderNoPointsComponent () { + #renderNoPointsComponent (points) { this.#noPointsComponent = new ListEmptyView({ filterType: this.#filterType }); + if (!points.length) { + render(this.#noPointsComponent, this.#container); + } } #renderList() { @@ -182,12 +209,7 @@ export default class TripPresenter { this.#renderSorting(); render(this.#tripList, this.#container); - if (this.points.length === 0) { - return render(new ListEmptyView({ - filterType: this.#filterType - }), this.#container); - } - + this.#renderNoPointsComponent(points); points.forEach((point) => this.#renderPoint(point, this.#destinations, this.#offers)); } @@ -204,9 +226,9 @@ export default class TripPresenter { remove(this.#sortingComponent); remove(this.#loadingComponent); - if (this.#noPointsComponent) { - remove(this.#noPointsComponent); - } + // if (this.#noPointsComponent) { + // remove(this.#noPointsComponent); + // } if (resetSortingType) { this.#currentSortingType = SortingType.DAY; diff --git a/src/view/create-form-view.js b/src/view/create-form-view.js index 2b65510..082e080 100644 --- a/src/view/create-form-view.js +++ b/src/view/create-form-view.js @@ -39,7 +39,7 @@ const findDestination = (point, destinations) => { }; const createFormTemplate = (point, destinations, offers) => { - const { basePrice, dateFrom, dateTo, type } = point; + const { basePrice, dateFrom, dateTo, type, isDisabled, isSaving } = point; const offersByType = offers.find((offer) => offer.type === point.type).offers; const pointDestination = findDestination(point, destinations); @@ -77,10 +77,10 @@ const createFormTemplate = (point, destinations, offers) => {
- + - +
@@ -91,7 +91,7 @@ const createFormTemplate = (point, destinations, offers) => {
- +
@@ -199,7 +199,8 @@ export default class CreateFormView extends AbstractStatefulView { /* eslint-enable */ defaultDate: this._state.dateFrom, maxDate: this._state.dateTo, - onChange: this.#dateFromChangeHandler + onChange: this.#dateFromChangeHandler, + } ); @@ -219,12 +220,18 @@ export default class CreateFormView extends AbstractStatefulView { } static parsePointToState(point) { - return {...point}; + return {...point, + isDisabled: false, + isSaving: false, + }; } static parseStateToPoint(state) { const point = {...state}; + delete point.isDisabled; + delete point.isSaving; + return point; } @@ -260,6 +267,11 @@ export default class CreateFormView extends AbstractStatefulView { this.#datepickerFrom.destroy(); this.#datepickerFrom = null; } + + if(this.#datepickerTo) { + this.#datepickerTo.destroy(); + this.#datepickerTo = null; + } } reset(point) { @@ -271,5 +283,6 @@ export default class CreateFormView extends AbstractStatefulView { #deletePointHandler = (evt) => { evt.preventDefault(); this.#handleDeleteClick(CreateFormView.parseStateToPoint(this._state)); + this.#newEventBtn.disabled = false; }; } diff --git a/src/view/edit-form-view.js b/src/view/edit-form-view.js index 54f15bc..06b9e4a 100644 --- a/src/view/edit-form-view.js +++ b/src/view/edit-form-view.js @@ -22,7 +22,7 @@ const createPictures = (destination) => { }; const createEditFormTemplate = (point, destinations, offers) => { - const { basePrice, dateFrom, dateTo, type } = point; + const { basePrice, dateFrom, dateTo, type, isDisabled, isDeleting, isSaving } = point; const offersByType = offers.find((offer) => offer.type === type).offers; const pointDestination = destinations.find((destination) => destination.id === point.destination); const destinationDescription = destinations.find((destination) => destination.id === point.destination).description; @@ -76,8 +76,8 @@ const createEditFormTemplate = (point, destinations, offers) => { - - + + @@ -184,6 +184,7 @@ export default class EditFormView extends AbstractStatefulView { time_24hr: true, /* eslint-enable */ defaultDate: this._state.dateFrom, + minDate: new Date(), maxDate: this._state.dateTo, onChange: this.#dateFromChangeHandler } @@ -205,12 +206,20 @@ export default class EditFormView extends AbstractStatefulView { } static parsePointToState(point) { - return {...point}; + return {...point, + isDisabled: false, + isSaving: false, + isDeleting: false + }; } static parseStateToPoint(state) { const point = {...state}; + delete point.isDisabled; + delete point.isSaving; + delete point.isDeleting; + return point; } @@ -246,6 +255,11 @@ export default class EditFormView extends AbstractStatefulView { this.#datepickerFrom.destroy(); this.#datepickerFrom = null; } + + if(this.#datepickerTo) { + this.#datepickerTo.destroy(); + this.#datepickerTo = null; + } } reset(point) { diff --git a/src/view/list-empty-view.js b/src/view/list-empty-view.js index 41484ca..f15bb97 100644 --- a/src/view/list-empty-view.js +++ b/src/view/list-empty-view.js @@ -1,7 +1,6 @@ import AbstractView from '../framework/view/abstract-view.js'; const Messages = { - ERROR: 'Something went wrong. We\'re on it right now', EVERYTHING: 'Click New Event to create your first point', FUTURE: 'There are no past events now', PRESENT: 'There are no present events now', @@ -9,7 +8,7 @@ const Messages = { }; const createEmptyListMessageTemplate = (filterType) => { - const message = Messages[filterType]; + const message = Messages[filterType.toUpperCase()]; return `

${message}

`; };