diff --git a/src/const.js b/src/const.js index e0ea723..3f8c252 100644 --- a/src/const.js +++ b/src/const.js @@ -110,6 +110,13 @@ const EditType = { const Method = { GET: 'GET', PUT: 'PUT', + POST: 'POST', + DELETE: 'DELETE' +}; + +const TimeLimit = { + LOWER_LIMIT: 350, + UPPER_LIMIT: 1000, }; export { @@ -130,5 +137,6 @@ export { UserAction, UpdateType, EditType, - Method + Method, + TimeLimit } diff --git a/src/model/points-model.js b/src/model/points-model.js index 8e4a6c8..3df5efc 100644 --- a/src/model/points-model.js +++ b/src/model/points-model.js @@ -42,7 +42,6 @@ export default class PointsModel extends Observable { async update(updateType, point) { try { - console.log('ф', this.#service.updatePoint(adaptToServer(point))) const updatedPoint = await this.#service.updatePoint(adaptToServer(point)); const adaptedPoint = adaptToClient(updatedPoint); this.#points = updateItem(this.#points, adaptedPoint); @@ -52,15 +51,24 @@ export default class PointsModel extends Observable { } } - add(updateType, point) { - const addedPoint = this.#service.addPoint(point); - this.#points.push(addedPoint); - this._notify(updateType, addedPoint); + async add(updateType, point) { + try { + const addedPoint = await this.#service.addPoint(adaptToServer(point)); + const adaptedPoint = adaptToClient(addedPoint); + this.#points.push(adaptedPoint); + this._notify(updateType, adaptedPoint); + } catch { + throw new Error('Can\'t add point'); + } } - delete(updateType, point) { - this.#service.deletePoint(point); - this.#points = this.#points.filter((pointItem) => pointItem.id !== point.id); - this._notify(updateType); + async delete(updateType, point) { + try { + await this.#service.deletePoint(point); + this.#points = this.#points.filter((pointItem) => pointItem.id !== point.id); + this._notify(updateType); + } catch { + throw new Error('Can\'t delete point'); + } } } diff --git a/src/presenter/new-point-presenter.js b/src/presenter/new-point-presenter.js index 2de63a8..96ff1ef 100644 --- a/src/presenter/new-point-presenter.js +++ b/src/presenter/new-point-presenter.js @@ -51,14 +51,31 @@ export default class NewPointPresenter { this.#handleDestroy({ isCanceled }); }; + setSaving = () => { + this.#pointNewComponent.updateElement({ + isDisabled: true, + isSaving: true, + }); + } + + setAborting = () => { + const resetFormState = () => { + this.#pointNewComponent.updateElement({ + isDisabled: false, + isSaving: false, + isDeleting: false, + }); + }; + + this.#pointNewComponent.shake(resetFormState); + } + #formSubmitHandler = (point) => { this.#handleDataChange( UserAction.ADD_POINT, UpdateType.MINOR, point ); - - this.destroy({ isCanceled: false }); }; #resetButtonClickHandler = () => { diff --git a/src/presenter/point-presenter.js b/src/presenter/point-presenter.js index 2d05712..061ea50 100644 --- a/src/presenter/point-presenter.js +++ b/src/presenter/point-presenter.js @@ -76,7 +76,42 @@ export default class PointPresenter { destroy = () => { remove(this.#pointComponent); remove(this.#editPointComponent); - } + }; + + setSaving = () => { + if (this.#mode === Mode.EDITING) { + this.#editPointComponent.updateElement({ + isDisabled: true, + isSaving: true + }); + } + }; + + setDeleting = () => { + this.#editPointComponent.updateElement({ + isDisabled: true, + isDeleting: true + }); + }; + + setAborting = () => { + if (this.#mode === Mode.DEFAULT) { + this.#pointComponent.shake(); + return; + } + + if (this.#mode === Mode.EDITING) { + const resetFormState = () => { + this.#editPointComponent.updateElement({ + isDisabled: false, + isSaving: false, + isDeleting: false + }); + }; + + this.#editPointComponent.shake(resetFormState); + } + }; #replacePointToForm = () => { replace(this.#editPointComponent, this.#pointComponent); diff --git a/src/presenter/trip-presenter.js b/src/presenter/trip-presenter.js index d0c426f..ec60eb8 100644 --- a/src/presenter/trip-presenter.js +++ b/src/presenter/trip-presenter.js @@ -5,9 +5,10 @@ import LoadingView from '../view/loading-view.js'; import PointPresenter from './point-presenter.js'; import NewPointPresenter from './new-point-presenter.js'; import { render, replace, remove, RenderPosition } from '../framework/render.js'; -import { SortType, EnabledSortType, UserAction, UpdateType, FilterType } from '../const.js'; +import { SortType, EnabledSortType, UserAction, UpdateType, FilterType, TimeLimit } from '../const.js'; import { sort } from '../utils/sort.js'; import { filter } from '../utils/filter.js'; +import UiBlocker from '../framework/ui-blocker/ui-blocker.js'; export default class TripPresenter { #tripContainer = null; @@ -16,6 +17,10 @@ export default class TripPresenter { #sortComponent = null; #messageComponent = null; #loadingComponent = new LoadingView(); + #uiBlocker = new UiBlocker({ + lowerLimit: TimeLimit.LOWER_LIMIT, + upperLimit: TimeLimit.UPPER_LIMIT + }); #destinationsModel = null; #offersModel = null; @@ -43,7 +48,7 @@ export default class TripPresenter { container: this.#pointListComponent.element, destinationsModel: this.#destinationsModel, offersModel: this.#offersModel, - onDataChange: this.#pointChangeHandler, + onDataChange: this.#viewActionHandler, onDestroy: this.#newPointDestroyHandler }); @@ -75,7 +80,7 @@ export default class TripPresenter { container: this.#pointListComponent.element, destinationsModel: this.#destinationsModel, offersModel: this.#offersModel, - onDataChange: this.#pointChangeHandler, + onDataChange: this.#viewActionHandler, onModeChange: this.#modeChangeHandler }); @@ -138,9 +143,12 @@ export default class TripPresenter { #renderBoard = () => { if (this.#isLoading) { this.#renderLoading(); + this.#newPointButtonPresenter.disableButton(); return; } + this.#newPointButtonPresenter.enableButton(); + if (this.#isLoadingError) { this.#clearBoard({ resetSortType: true }); remove(this.#sortComponent); @@ -170,18 +178,35 @@ export default class TripPresenter { } } - #pointChangeHandler = (actionType, updateType, update) => { + #viewActionHandler = async (actionType, updateType, update) => { + this.#uiBlocker.block(); switch (actionType) { case UserAction.UPDATE_POINT: - this.#pointsModel.update(updateType, update); + this.#pointPresenters.get(update.id).setSaving(); + try { + await this.#pointsModel.update(updateType, update); + } catch { + this.#pointPresenters.get(update.id).setAborting(); + } break; case UserAction.DELETE_POINT: - this.#pointsModel.delete(updateType, update); + this.#pointPresenters.get(update.id).setDeleting(); + try { + await this.#pointsModel.delete(updateType, update); + } catch { + this.#pointPresenters.get(update.id).setAborting(); + } break; case UserAction.ADD_POINT: - this.#pointsModel.add(updateType, update); + this.#newPointPresenter.setSaving(); + try { + await this.#pointsModel.add(updateType, update); + } catch { + this.#newPointPresenter.setAborting(); + } break; } + this.#uiBlocker.unblock(); }; #modelEventHandler = (updateType, data) => { diff --git a/src/service/points-api-service.js b/src/service/points-api-service.js index 9d4b1ed..3d0e8c2 100644 --- a/src/service/points-api-service.js +++ b/src/service/points-api-service.js @@ -31,11 +31,23 @@ export default class PointsApiService extends ApiService { return parsedResponse; } - addPoint(data) { - return { ...data, id: crypto.randomUUID() }; + async addPoint(point) { + const response = await this._load({ + url: `points`, + method: Method.POST, + body: JSON.stringify(point), + headers: new Headers({ 'Content-Type': 'application/json' }) + }); + + const parsedResponse = await ApiService.parseResponse(response); + + return parsedResponse; } - deletePoint() { - //... + async deletePoint(point) { + await this._load({ + url: `points/${point.id}`, + method: Method.DELETE + }); } } diff --git a/src/utils/point.js b/src/utils/point.js index 6c35a53..790d928 100644 --- a/src/utils/point.js +++ b/src/utils/point.js @@ -18,7 +18,7 @@ function adaptToClient(point) { function adaptToServer(point) { const adaptedPoint = { ...point, - ['base_price']: point.basePrice, + ['base_price']: Number(point.basePrice), ['date_from']: new Date(point.dateFrom).toISOString(), ['date_to']: new Date(point.dateTo).toISOString(), ['is_favorite']: point.isFavorite diff --git a/src/view/edit-point-view.js b/src/view/edit-point-view.js index f378b5d..2ac095a 100644 --- a/src/view/edit-point-view.js +++ b/src/view/edit-point-view.js @@ -8,36 +8,42 @@ import he from 'he'; const ButtonLabel = { CANCEL_DEFAULT: 'Cancel', DELETE_DEFAULT: 'Delete', - SAVE_DEFAULT: 'Save' + DELETE_IN_PROGRESS: 'Deleting...', + SAVE_DEFAULT: 'Save', + SAVE_IN_PROGRESS: 'Saving...' } -const createSaveButtonTemplate = () => { - const label = ButtonLabel.SAVE_DEFAULT; - return ``; +const createSaveButtonTemplate = ({ isSaving, isDisabled }) => { + const label = isSaving ? ButtonLabel.SAVE_IN_PROGRESS : ButtonLabel.SAVE_DEFAULT; + return ``; } -const createResetButtonTemplate = ({ pointType }) => { - const label = pointType === EditType.CREATING - ? ButtonLabel.CANCEL_DEFAULT - : ButtonLabel.DELETE_DEFAULT; - return ``; +const createResetButtonTemplate = ({ pointType, isDeleting, isDisabled }) => { + let label; + + if (pointType === EditType.CREATING) { + label = ButtonLabel.CANCEL_DEFAULT; + } else { + label = isDeleting ? ButtonLabel.DELETE_IN_PROGRESS : ButtonLabel.DELETE_DEFAULT; + } + return ``; } -const createRollupButtonTemplate = () => { - return `` +const createRollupButtonTemplate = (isDisabled) => { + return `` } -const createPointEditControlsTemplate = ({ pointType }) => { +const createPointEditControlsTemplate = ({ pointType, isSaving, isDeleting, isDisabled }) => { return ` - ${createSaveButtonTemplate()} - ${createResetButtonTemplate({ pointType })} - ${(pointType === EditType.EDITING) ? createRollupButtonTemplate() : ''} + ${createSaveButtonTemplate({ isSaving, isDisabled })} + ${createResetButtonTemplate({ pointType, isDeleting, isDisabled })} + ${(pointType === EditType.EDITING) ? createRollupButtonTemplate(isDisabled) : ''} `; } -const createPointCitiesOptionsTemplate = () => { +const createPointCitiesOptionsTemplate = (isDisabled) => { return ( - ` + ` ${CITIES.map((city) => ``).join('')} ` ); @@ -52,10 +58,10 @@ const createPointPhotosTemplate = (pointDestination) => { ); } -const createPointTypesTemplate = (currentType) => { +const createPointTypesTemplate = ({ currentType, isDisabled }) => { return TYPES.map((type) => `
- +
`).join(''); } @@ -79,7 +85,7 @@ const createPointOffersTemplate = ({ offersId, currentOffers }) => { } const createEditPointTemplate = ({ state, pointDestination, pointOffers, pointType }) => { - const { point } = state; + const { point, isDisabled, isSaving, isDeleting } = state; const { basePrice, dateFrom, dateTo, type } = point; const currentDestination = pointDestination.find((destination) => destination.id === point.destination); @@ -94,11 +100,11 @@ const createEditPointTemplate = ({ state, pointDestination, pointOffers, pointTy Choose event type Event type icon - +
Event type - ${createPointTypesTemplate(type)} + ${createPointTypesTemplate({ type, isDisabled })}
@@ -106,24 +112,24 @@ const createEditPointTemplate = ({ state, pointDestination, pointOffers, pointTy - - ${createPointCitiesOptionsTemplate()} + + ${createPointCitiesOptionsTemplate(isDisabled)}
- - - — - - + + + — + +
- +
- ${createPointEditControlsTemplate({ pointType })} + ${createPointEditControlsTemplate({ pointType, isDisabled, isSaving, isDeleting })}
${(currentOffers.length !== 0) ? `
@@ -332,7 +338,17 @@ export default class EditPointView extends AbstractStatefulView { ) }; - static parsePointToState = ({ point }) => ({ point }); + static parsePointToState = ({ + point, + isDisabled = false, + isSaving = false, + isDeleting = false + }) => ({ + point, + isDisabled, + isSaving, + isDeleting + }); static parseStateToPoint = (state) => state.point; }