From a50dacbb14e84e25febc687547db7c48218eaaa1 Mon Sep 17 00:00:00 2001 From: Sergey_Sofronov Date: Fri, 24 Jan 2025 15:55:41 +0500 Subject: [PATCH 1/2] module8-task2 --- src/const.js | 1 - src/event-api-service.js | 19 ++++--------------- src/presenter/board-presenter.js | 28 +++++++++++++--------------- src/presenter/event-presenter.js | 4 ++-- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/const.js b/src/const.js index 652bbd1..650483f 100644 --- a/src/const.js +++ b/src/const.js @@ -75,7 +75,6 @@ export const TripTitleQuantity = { export const UserAction = { UPDATE_EVENT: 'UPDATE_EVENT', - CANCEL_EVENT: 'CANCEL_EVENT', CREATE_EVENT: 'CREATE_EVENT', DELETE_EVENT: 'DELETE_EVENT', }; diff --git a/src/event-api-service.js b/src/event-api-service.js index 8707999..0a22e80 100644 --- a/src/event-api-service.js +++ b/src/event-api-service.js @@ -4,18 +4,11 @@ import ApiService from './framework/api-service'; export default class EventApiService extends ApiService { async getTripData() { - const data = await Promise.all([ + return Promise.all([ this._load({ url: 'points' }), this._load({ url: 'offers' }), this._load({ url: 'destinations' }) - ]).then((response) => response.map(ApiService.parseResponse)); - - const result = []; - for await (const item of data) { - result.push(item); - } - - return result; + ]).then((response) => Promise.all(response.map(ApiService.parseResponse))); } async createEvent(event) { @@ -26,9 +19,7 @@ export default class EventApiService extends ApiService { headers: new Headers({ 'Content-Type': 'application/json' }), }); - const parsedResponse = await ApiService.parseResponse(response); - - return parsedResponse; + return ApiService.parseResponse(response); } async updateEvent(event) { @@ -39,9 +30,7 @@ export default class EventApiService extends ApiService { headers: new Headers({ 'Content-Type': 'application/json' }), }); - const parsedResponse = await ApiService.parseResponse(response); - - return parsedResponse; + return ApiService.parseResponse(response); } async deleteEvent(event) { diff --git a/src/presenter/board-presenter.js b/src/presenter/board-presenter.js index 5c2e543..a4ac080 100644 --- a/src/presenter/board-presenter.js +++ b/src/presenter/board-presenter.js @@ -59,7 +59,7 @@ export default class BoardPresenter { this.#renderNoEvents(); return; } - + remove(this.#noEventsComponent); this.#renderSort(); this.#renderEventList(); this.#renderEvents(); @@ -67,6 +67,7 @@ export default class BoardPresenter { #renderNoEvents = () => { const message = this.#isLoading ? EventListMessage.LOADING : EventListMessage[this.#tripModel.filterType]; + this.#isLoading = ''; this.#noEventsComponent = new NoEventsView(message); render(this.#boardContainer, this.#noEventsComponent); }; @@ -106,10 +107,6 @@ export default class BoardPresenter { this.#activeSortType = SortType.DAY; this.#tripModel.updateFilterType(UpdateType.MAJOR, FilterType.EVERYTHING); this.#newEventButtonComponent.disable(); - if (this.#events.length === 0) { - remove(this.#noEventsComponent); - this.#renderEventList(); //!!! не удалять EventList? - } this.#newEventPresenter = this.#createEventPresenter(this.#tripModel.getDefaultEvent(), FormMode.CREATE); }; @@ -145,6 +142,15 @@ export default class BoardPresenter { } }; + #deleteEvent = (updateType, updatedEvent) => { + this.#destroyNewEventPresenter(); + if (this.#events.length === 0) { + this.#renderNoEvents(); + } else { + this.#tripModel.deleteEvent(updateType, updatedEvent); + } + }; + #viewActionHandler = (actionType, updateType, updatedEvent) => { switch (actionType) { @@ -156,15 +162,8 @@ export default class BoardPresenter { this.#tripModel.createEvent(updateType, updatedEvent); break; - case UserAction.CANCEL_EVENT: - this.#destroyNewEventPresenter(); - if (this.#events.length === 0) { - this.#renderNoEvents(); - } - break; - case UserAction.DELETE_EVENT: - this.#tripModel.deleteEvent(updateType, updatedEvent); + this.#deleteEvent(updateType, updatedEvent); break; } }; @@ -177,6 +176,7 @@ export default class BoardPresenter { case UpdateType.PATCH: this.#eventPresenters.get(payload.id).init(payload); break; + case UpdateType.MINOR: this.#updateEventList(); break; @@ -191,8 +191,6 @@ export default class BoardPresenter { break; case UpdateType.INIT: - this.#isLoading = false; - remove(this.#noEventsComponent); this.#newEventButtonComponent.enable(); this.#renderBoard(); break; diff --git a/src/presenter/event-presenter.js b/src/presenter/event-presenter.js index 78f7ff4..56a58cd 100644 --- a/src/presenter/event-presenter.js +++ b/src/presenter/event-presenter.js @@ -35,6 +35,7 @@ export default class EventPresenter { const prevEventComponent = this.#eventComponent; const prevFormComponent = this.#formComponent; + //!!! update this.#eventComponent = new EventView(this.#event, this.#offers, this.#destinations).setOnFavoriteClickHandler(this.#favoriteClickHandler); this.#formComponent = new FormView(this.#event, this.#offers, this.#destinations, formMode) .setOnFormSubmitHandler(this.#formSubmitHandler) @@ -90,8 +91,7 @@ export default class EventPresenter { #formDeleteHandler = (updatedEvent) => { const eventsQuantity = this.#getEventsQuantity() - 1; const updateType = eventsQuantity ? UpdateType.MINOR : UpdateType.MAJOR; - const action = (this.#formMode === FormMode.CREATE) ? UserAction.CANCEL_EVENT : UserAction.DELETE_EVENT; - this.#viewActionCallback?.(action, updateType, updatedEvent); + this.#viewActionCallback?.(UserAction.DELETE_EVENT, updateType, updatedEvent); }; #favoriteClickHandler = () => { From d559f8fff580116461620a40de54bd3f14bcec58 Mon Sep 17 00:00:00 2001 From: Sergey Sofronov Date: Sat, 25 Jan 2025 19:23:53 +0500 Subject: [PATCH 2/2] module8-task2 --- mocks/destination.js | 110 ---------- mocks/generate-data.js | 9 - mocks/offer.js | 208 ------------------- mocks/point.js | 322 ------------------------------ src/const.js | 10 +- src/framework/render.js | 1 + src/main.js | 2 +- src/model/trip-model.js | 35 ++-- src/presenter/board-presenter.js | 96 ++++++--- src/presenter/event-presenter.js | 53 +++-- src/presenter/header-presenter.js | 22 +- src/view/event-view.js | 21 +- src/view/filter-view.js | 19 -- 13 files changed, 151 insertions(+), 757 deletions(-) delete mode 100644 mocks/destination.js delete mode 100644 mocks/generate-data.js delete mode 100644 mocks/offer.js delete mode 100644 mocks/point.js diff --git a/mocks/destination.js b/mocks/destination.js deleted file mode 100644 index 41d1968..0000000 --- a/mocks/destination.js +++ /dev/null @@ -1,110 +0,0 @@ -const destinations = [ - { - id: 'c7fcf894-e8ef-4347-94cf-8c76aa6c6833', - description: 'Chamonix - a perfect place to stay with a family', - name: 'Chamonix', - pictures: [] - }, - { - id: '82a3bad6-6498-4989-ae4f-815082508c30', - description: '', - name: 'Venice', - pictures: [] - }, - { - id: 'ec9110f7-4767-4179-b856-5d8bb23324ec', - description: '', - name: 'Moscow', - pictures: [] - }, - { - id: '070ea513-2210-42df-bad4-ede76ba22a3e', - description: 'Sochi - is a beautiful city', - name: 'Sochi', - pictures: [] - }, - { - id: 'e4bc89ed-4df2-40a4-b8d1-21b953502799', - description: 'Berlin - for those who value comfort and coziness', - name: 'Berlin', - pictures: [ - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/18.jpg', - description: 'Berlin a perfect place to stay with a family' - }, - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/13.jpg', - description: 'Berlin with a beautiful old town' - } - ] - }, - { - id: '13a37338-b4b1-466e-aafe-092f3c247708', - description: 'Geneva - full of of cozy canteens where you can try the best coffee in the Middle East', - name: 'Geneva', - pictures: [ - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/18.jpg', - description: 'Geneva middle-eastern paradise' - }, - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/4.jpg', - description: 'Geneva a true asian pearl' - }, - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/8.jpg', - description: 'Geneva with crowded streets' - } - ] - }, - { - id: '64a8701c-632e-48ae-9052-353c1a3465df', - description: 'Frankfurt - for those who value comfort and coziness', - name: 'Frankfurt', - pictures: [ - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/10.jpg', - description: 'Frankfurt a perfect place to stay with a family' - } - ] - }, - { - id: 'fdd837e1-fd2d-463c-9a2d-555cf8efa89e', - description: 'Valencia - a true asian pearl', - name: 'Valencia', - pictures: [ - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/20.jpg', - description: 'Valencia with an embankment of a mighty river as a centre of attraction' - }, - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/15.jpg', - description: 'Valencia for those who value comfort and coziness' - }, - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/16.jpg', - description: 'Valencia famous for its crowded street markets with the best street food in Asia' - } - ] - }, - { - id: 'fed714ef-b342-45dc-8788-633def721433', - description: '', - name: 'Monaco', - pictures: [] - }, - { - id: '466c2985-2db8-47fa-85e4-0d07cb9e48fe', - description: 'Vien - is a beautiful city', - name: 'Vien', - pictures: [ - { - src: 'https://22.objects.htmlacademy.pro/static/destinations/1.jpg', - description: 'Vien a true asian pearl' - } - ] - } -]; - -export const getDestinationMocks = () => destinations; - diff --git a/mocks/generate-data.js b/mocks/generate-data.js deleted file mode 100644 index 44484c0..0000000 --- a/mocks/generate-data.js +++ /dev/null @@ -1,9 +0,0 @@ -import { mix } from '../src/utils/common'; -import { getDestinationMocks } from './destination'; -import { getOfferMocks } from './offer'; -import { getPointMocks } from './point'; - -const EVENT_QUANTITY = 5; - -export const generateTripData = (quantity = EVENT_QUANTITY) => [mix(getPointMocks(), quantity), getOfferMocks(), getDestinationMocks()]; - diff --git a/mocks/offer.js b/mocks/offer.js deleted file mode 100644 index ddc5563..0000000 --- a/mocks/offer.js +++ /dev/null @@ -1,208 +0,0 @@ -const offers = [ - { - type: 'taxi', - offers: [ - { - id: 'df01f087-8af8-4024-ade2-434e572a1939', - title: 'Upgrade to a business class', - price: 187 - }, - { - id: '0690c5cc-879c-4aa5-80c1-244935547967', - title: 'Choose the radio station', - price: 193 - }, - { - id: 'bba6b2a4-115c-4470-9e70-bab52e7b6da9', - title: 'Choose temperature', - price: 36 - }, - { - id: '61a27c8e-492d-4194-8d09-c85e8fbc0ed2', - title: 'Drive quickly, I\'m in a hurry', - price: 133 - }, - { - id: '428136ce-b310-4e4e-bb3e-e09946178b81', - title: 'Drive slowly', - price: 50 - } - ] - }, - { - type: 'bus', - offers: [ - { - id: '3529d1ca-96d4-4530-8cf5-3f3b0f570ed4', - title: 'Infotainment system', - price: 68 - }, - { - id: '32f9d674-3739-427f-903f-daa3c2cf722b', - title: 'Order meal', - price: 116 - }, - { - id: '199e8e51-dc14-4795-9638-5f707452da51', - title: 'Choose seats', - price: 134 - } - ] - }, - { - type: 'train', - offers: [ - { - id: 'b64cbb73-09c3-4d68-be36-95d4478d344c', - title: 'Book a taxi at the arrival point', - price: 42 - }, - { - id: 'f7429a41-90e3-448e-af6a-91f77c1d1f49', - title: 'Order a breakfast', - price: 139 - }, - { - id: '5093df18-a4d0-42f7-b9cd-b6ef378563fa', - title: 'Wake up at a certain time', - price: 69 - } - ] - }, - { - type: 'flight', - offers: [ - { - id: '9b77d7bc-4650-465d-98da-c12fe7f3495c', - title: 'Choose meal', - price: 63 - }, - { - id: '1021812a-82b3-4fb5-a657-51d6b005d366', - title: 'Choose seats', - price: 122 - }, - { - id: '4dc8e801-b123-489e-816a-1b900e12d270', - title: 'Upgrade to comfort class', - price: 157 - }, - { - id: '151f488d-e03a-4347-8837-2e7171061983', - title: 'Upgrade to business class', - price: 140 - }, - { - id: '7511ff9e-227d-4732-9e27-d4cd1b78c220', - title: 'Add luggage', - price: 158 - }, - { - id: '52cfccc0-c186-4dea-9fe4-b04528650af9', - title: 'Business lounge', - price: 41 - } - ] - }, - { - type: 'check-in', - offers: [ - { - id: 'e3dbdc12-23b8-453f-afd1-e0dcb281bf7a', - title: 'Choose the time of check-in', - price: 195 - }, - { - id: '56b72afa-d7d0-4dab-9d80-e64a2442783e', - title: 'Choose the time of check-out', - price: 155 - }, - { - id: '73522a17-f05d-49b9-a141-5ebccae6fb24', - title: 'Add breakfast', - price: 107 - }, - { - id: 'd658acef-46f7-4c88-b7ed-df131ffabe0c', - title: 'Laundry', - price: 183 - }, - { - id: '4a956f99-a055-4143-969f-b29f2be5da50', - title: 'Order a meal from the restaurant', - price: 101 - } - ] - }, - { - type: 'sightseeing', - offers: [] - }, - { - type: 'ship', - offers: [ - { - id: 'ed0ddafb-1f70-4d26-b212-c40f42b92c4f', - title: 'Choose meal', - price: 146 - }, - { - id: '35aa9a64-dbfd-4059-bb77-117e839bfcc2', - title: 'Choose seats', - price: 60 - }, - { - id: '0a72ec19-d026-4a7b-9158-f00431ff883f', - title: 'Upgrade to comfort class', - price: 160 - }, - { - id: '1ca8aa30-0131-4b9f-956a-b7f90d2bf6a8', - title: 'Upgrade to business class', - price: 57 - }, - { - id: '00fdf487-91f5-4175-97cc-4b8cf30240a0', - title: 'Add luggage', - price: 81 - }, - { - id: '6e21cc5e-e5ef-453d-a401-86846f294d02', - title: 'Business lounge', - price: 146 - } - ] - }, - { - type: 'drive', - offers: [ - { - id: '50cd2308-6764-4111-9800-4027c4516b90', - title: 'With automatic transmission', - price: 34 - }, - { - id: 'd82abaa6-4c4e-4bb4-a49d-83e80c37cdd5', - title: 'With air conditioning', - price: 101 - } - ] - }, - { - type: 'restaurant', - offers: [ - { - id: '89b4eb84-3d09-475f-814a-879bcf96a900', - title: 'Choose live music', - price: 110 - }, - { - id: 'd1d46527-003a-43e2-a3c1-714db831a54e', - title: 'Choose VIP area', - price: 33 - } - ] - } -]; - -export const getOfferMocks = () => offers; diff --git a/mocks/point.js b/mocks/point.js deleted file mode 100644 index 6e45805..0000000 --- a/mocks/point.js +++ /dev/null @@ -1,322 +0,0 @@ -const points = [ - { - id: '8220b3c4-7c8b-43b9-a36c-87860c29a201', - basePrice: 7863, - dateFrom: '2024-12-03T11:14:08.471Z', - dateTo: '2024-12-05T04:59:08.471Z', - destinationId: '82a3bad6-6498-4989-ae4f-815082508c30', - isFavorite: false, - offerIds: [], - type: 'sightseeing' - }, - { - id: '7bd944c8-0d45-426b-aaf7-a87f096e8c53', - basePrice: 67, - dateFrom: '2024-12-06T08:46:08.471Z', - dateTo: '2024-12-06T16:52:08.471Z', - destinationId: '070ea513-2210-42df-bad4-ede76ba22a3e', - isFavorite: false, - offerIds: [ - '89b4eb84-3d09-475f-814a-879bcf96a900', - 'd1d46527-003a-43e2-a3c1-714db831a54e' - ], - type: 'restaurant' - }, - { - id: '33280e36-1490-4a7a-b497-d2c0e38f5442', - basePrice: 3087, - dateFrom: '2024-12-07T04:06:08.471Z', - dateTo: '2024-12-09T00:49:08.471Z', - destinationId: '466c2985-2db8-47fa-85e4-0d07cb9e48fe', - isFavorite: true, - offerIds: [], - type: 'sightseeing' - }, - { - id: '939e4b88-b4d0-49e7-97bb-3544cc12e1a1', - basePrice: 2921, - dateFrom: '2024-12-09T14:04:08.471Z', - dateTo: '2024-12-10T12:35:08.471Z', - destinationId: 'ec9110f7-4767-4179-b856-5d8bb23324ec', - isFavorite: false, - offerIds: [], - type: 'sightseeing' - }, - { - id: '8319ffe9-6f6b-4fc7-bc7e-6266c4ac0fab', - basePrice: 1499, - dateFrom: '2024-12-10T18:44:08.471Z', - dateTo: '2024-12-12T16:09:08.471Z', - destinationId: 'e4bc89ed-4df2-40a4-b8d1-21b953502799', - isFavorite: true, - offerIds: [ - '35aa9a64-dbfd-4059-bb77-117e839bfcc2', - '0a72ec19-d026-4a7b-9158-f00431ff883f', - '1ca8aa30-0131-4b9f-956a-b7f90d2bf6a8', - '00fdf487-91f5-4175-97cc-4b8cf30240a0', - '6e21cc5e-e5ef-453d-a401-86846f294d02' - ], - type: 'ship' - }, - { - id: 'b4fae42a-bfa8-48eb-8b53-c01c591185b6', - basePrice: 1350, - dateFrom: '2024-12-13T19:10:08.471Z', - dateTo: '2024-12-15T17:23:08.471Z', - destinationId: '64a8701c-632e-48ae-9052-353c1a3465df', - isFavorite: false, - offerIds: [ - '199e8e51-dc14-4795-9638-5f707452da51' - ], - type: 'bus' - }, - { - id: '58bae41c-eb1d-4ef5-ac46-b22681b052b1', - basePrice: 3905, - dateFrom: '2024-12-17T11:38:08.471Z', - dateTo: '2024-12-17T21:26:08.471Z', - destinationId: '82a3bad6-6498-4989-ae4f-815082508c30', - isFavorite: false, - offerIds: [ - '73522a17-f05d-49b9-a141-5ebccae6fb24', - 'd658acef-46f7-4c88-b7ed-df131ffabe0c', - '4a956f99-a055-4143-969f-b29f2be5da50' - ], - type: 'check-in' - }, - { - id: 'ab96a7c9-cd35-4ae0-8ca6-33e50c3376a5', - basePrice: 8353, - dateFrom: '2024-12-19T14:12:08.471Z', - dateTo: '2024-12-21T11:32:08.471Z', - destinationId: 'c7fcf894-e8ef-4347-94cf-8c76aa6c6833', - isFavorite: false, - offerIds: [ - 'd1d46527-003a-43e2-a3c1-714db831a54e' - ], - type: 'restaurant' - }, - { - id: 'bcc01238-cfd9-4432-b53e-fcea1b958e08', - basePrice: 330, - dateFrom: '2024-12-21T23:11:08.471Z', - dateTo: '2024-12-23T20:10:08.471Z', - destinationId: 'ec9110f7-4767-4179-b856-5d8bb23324ec', - isFavorite: false, - offerIds: [ - 'd82abaa6-4c4e-4bb4-a49d-83e80c37cdd5' - ], - type: 'drive' - }, - { - id: '91214a0f-dec3-4402-873c-a66f63a024f4', - basePrice: 1387, - dateFrom: '2024-12-25T07:41:08.471Z', - dateTo: '2024-12-27T08:06:08.471Z', - destinationId: '82a3bad6-6498-4989-ae4f-815082508c30', - isFavorite: false, - offerIds: [], - type: 'bus' - }, - { - id: 'fc4b109b-7a99-444d-a583-caa344411cb8', - basePrice: 12, - dateFrom: '2024-12-27T21:44:08.471Z', - dateTo: '2024-12-29T10:43:08.471Z', - destinationId: 'ec9110f7-4767-4179-b856-5d8bb23324ec', - isFavorite: false, - offerIds: [ - 'e3dbdc12-23b8-453f-afd1-e0dcb281bf7a', - '56b72afa-d7d0-4dab-9d80-e64a2442783e', - '73522a17-f05d-49b9-a141-5ebccae6fb24', - 'd658acef-46f7-4c88-b7ed-df131ffabe0c', - '4a956f99-a055-4143-969f-b29f2be5da50' - ], - type: 'check-in' - }, - { - id: '0de5090a-72e9-418b-89b0-5926aea97d4a', - basePrice: 4884, - dateFrom: '2024-12-31T10:15:08.471Z', - dateTo: '2025-01-01T23:58:08.471Z', - destinationId: '82a3bad6-6498-4989-ae4f-815082508c30', - isFavorite: false, - offerIds: [], - type: 'drive' - }, - { - id: '4df8e4e0-bbca-4b72-91c3-ef2654b4d60f', - basePrice: 6597, - dateFrom: '2025-01-02T10:15:08.471Z', - dateTo: '2025-01-02T19:36:08.471Z', - destinationId: '070ea513-2210-42df-bad4-ede76ba22a3e', - isFavorite: false, - offerIds: [ - '6e21cc5e-e5ef-453d-a401-86846f294d02' - ], - type: 'ship' - }, - { - id: 'da0271c3-fc37-4eaa-a2be-290c8903b02f', - basePrice: 7863, - dateFrom: '2025-01-03T22:15:08.471Z', - dateTo: '2025-01-04T19:50:08.471Z', - destinationId: 'fdd837e1-fd2d-463c-9a2d-555cf8efa89e', - isFavorite: true, - offerIds: [ - '5093df18-a4d0-42f7-b9cd-b6ef378563fa' - ], - type: 'train' - }, - { - id: '5a73c621-2544-4995-990e-3ffaf72464f9', - basePrice: 9488, - dateFrom: '2025-01-05T19:34:08.471Z', - dateTo: '2025-01-06T16:19:08.471Z', - destinationId: '466c2985-2db8-47fa-85e4-0d07cb9e48fe', - isFavorite: false, - offerIds: [ - 'df01f087-8af8-4024-ade2-434e572a1939', - '0690c5cc-879c-4aa5-80c1-244935547967', - 'bba6b2a4-115c-4470-9e70-bab52e7b6da9', - '61a27c8e-492d-4194-8d09-c85e8fbc0ed2', - '428136ce-b310-4e4e-bb3e-e09946178b81' - ], - type: 'taxi' - }, - { - id: 'cf1362d0-2e46-4f39-aa39-85e2c602b7e8', - basePrice: 6644, - dateFrom: '2025-01-08T06:43:08.471Z', - dateTo: '2025-01-09T13:18:08.471Z', - destinationId: '82a3bad6-6498-4989-ae4f-815082508c30', - isFavorite: false, - offerIds: [], - type: 'sightseeing' - }, - { - id: 'c6b22ee7-7613-4853-8baf-291c479d879a', - basePrice: 5731, - dateFrom: '2025-01-10T14:06:08.471Z', - dateTo: '2025-01-12T12:22:08.471Z', - destinationId: '070ea513-2210-42df-bad4-ede76ba22a3e', - isFavorite: true, - offerIds: [ - '1021812a-82b3-4fb5-a657-51d6b005d366', - '4dc8e801-b123-489e-816a-1b900e12d270', - '151f488d-e03a-4347-8837-2e7171061983', - '7511ff9e-227d-4732-9e27-d4cd1b78c220', - '52cfccc0-c186-4dea-9fe4-b04528650af9' - ], - type: 'flight' - }, - { - id: '0e475bfd-d99a-412c-8270-efab3cfe7399', - basePrice: 3306, - dateFrom: '2025-01-14T04:58:08.471Z', - dateTo: '2025-01-14T18:23:08.471Z', - destinationId: 'ec9110f7-4767-4179-b856-5d8bb23324ec', - isFavorite: true, - offerIds: [ - '00fdf487-91f5-4175-97cc-4b8cf30240a0', - '6e21cc5e-e5ef-453d-a401-86846f294d02' - ], - type: 'ship' - }, - { - id: '4402d983-2d5e-46da-8c96-3c06f2b24e51', - basePrice: 1890, - dateFrom: '2025-01-15T13:06:08.471Z', - dateTo: '2025-01-16T01:26:08.471Z', - destinationId: '070ea513-2210-42df-bad4-ede76ba22a3e', - isFavorite: true, - offerIds: [ - '61a27c8e-492d-4194-8d09-c85e8fbc0ed2', - '428136ce-b310-4e4e-bb3e-e09946178b81' - ], - type: 'taxi' - }, - { - id: 'fdae4f8a-f63b-418c-8a92-2d4001eecad4', - basePrice: 5102, - dateFrom: '2025-01-17T17:21:08.471Z', - dateTo: '2025-01-19T05:47:08.471Z', - destinationId: 'ec9110f7-4767-4179-b856-5d8bb23324ec', - isFavorite: true, - offerIds: [ - '56b72afa-d7d0-4dab-9d80-e64a2442783e', - '73522a17-f05d-49b9-a141-5ebccae6fb24', - 'd658acef-46f7-4c88-b7ed-df131ffabe0c', - '4a956f99-a055-4143-969f-b29f2be5da50' - ], - type: 'check-in' - }, - { - id: '59a69467-c2cc-45d2-bde2-10cb698e7260', - basePrice: 2276, - dateFrom: '2025-01-21T04:56:08.471Z', - dateTo: '2025-01-22T00:39:08.471Z', - destinationId: 'c7fcf894-e8ef-4347-94cf-8c76aa6c6833', - isFavorite: false, - offerIds: [ - '7511ff9e-227d-4732-9e27-d4cd1b78c220', - '52cfccc0-c186-4dea-9fe4-b04528650af9' - ], - type: 'flight' - }, - { - id: 'f7737a0d-3124-49eb-8487-d16d1c690d42', - basePrice: 1237, - dateFrom: '2025-01-22T22:40:08.471Z', - dateTo: '2025-01-24T16:43:08.471Z', - destinationId: '64a8701c-632e-48ae-9052-353c1a3465df', - isFavorite: false, - offerIds: [ - '6e21cc5e-e5ef-453d-a401-86846f294d02' - ], - type: 'ship' - }, - { - id: '73edeee1-d1fe-4371-a2f3-1012eaef876f', - basePrice: 2489, - dateFrom: '2025-01-26T06:26:08.471Z', - dateTo: '2025-01-27T18:41:08.471Z', - destinationId: 'fdd837e1-fd2d-463c-9a2d-555cf8efa89e', - isFavorite: true, - offerIds: [ - '151f488d-e03a-4347-8837-2e7171061983', - '7511ff9e-227d-4732-9e27-d4cd1b78c220', - '52cfccc0-c186-4dea-9fe4-b04528650af9' - ], - type: 'flight' - }, - { - id: '3a88f1e1-5926-43aa-aa65-be250ff0947f', - basePrice: 5807, - dateFrom: '2025-01-28T23:33:08.471Z', - dateTo: '2025-01-30T12:51:08.471Z', - destinationId: 'e4bc89ed-4df2-40a4-b8d1-21b953502799', - isFavorite: true, - offerIds: [ - '4dc8e801-b123-489e-816a-1b900e12d270', - '151f488d-e03a-4347-8837-2e7171061983', - '7511ff9e-227d-4732-9e27-d4cd1b78c220', - '52cfccc0-c186-4dea-9fe4-b04528650af9' - ], - type: 'flight' - }, - { - id: '413b04c2-c606-48f9-b0b7-d2a062bbbf6a', - basePrice: 7758, - dateFrom: '2025-01-31T09:26:08.471Z', - dateTo: '2025-02-02T04:15:08.471Z', - destinationId: '64a8701c-632e-48ae-9052-353c1a3465df', - isFavorite: true, - offerIds: [ - '4a956f99-a055-4143-969f-b29f2be5da50' - ], - type: 'check-in' - } -]; - -export const getPointMocks = () => points; diff --git a/src/const.js b/src/const.js index 650483f..a870197 100644 --- a/src/const.js +++ b/src/const.js @@ -31,7 +31,8 @@ export const EventListMessage = { [FilterType.PAST]: 'There are no past events now', [FilterType.PRESENT]: 'There are no present events now', [FilterType.FUTURE]: 'There are no future events now', - LOADING: 'Loading...' + LOADING: 'Loading...', + ERROR: 'The app is not available' }; export const SortType = { @@ -83,8 +84,6 @@ export const UpdateType = { PATCH: 'PATCH', MINOR: 'MINOR', MAJOR: 'MAJOR', - FILTER: 'FILTER', - INIT: 'INIT', }; export const Method = { @@ -93,3 +92,8 @@ export const Method = { POST: 'POST', DELETE: 'DELETE', }; + +export const UiBlockerTimeLimit = { + LOWER_LIMIT: 350, + UPPER_LIMIT: 1000, +}; diff --git a/src/framework/render.js b/src/framework/render.js index 5ef9fa5..3314582 100644 --- a/src/framework/render.js +++ b/src/framework/render.js @@ -38,6 +38,7 @@ function render(container, component, place = RenderPosition.BEFOREEND) { if (!((container instanceof Element) && (component instanceof Element))) { throw new Error('Container or component aren\'t instance of Element'); } + container.insertAdjacentElement(place, component); } diff --git a/src/main.js b/src/main.js index 018d7be..ac8171a 100644 --- a/src/main.js +++ b/src/main.js @@ -12,6 +12,7 @@ const filtersContainer = tripInfoContainer.querySelector('.trip-controls__filter const contentContainer = document.querySelector('.trip-events'); const model = new TripModel(new EventApiService(END_POINT, AUTHORIZATION)); +model.init(); const header = new HeaderPresenter(tripInfoContainer, filtersContainer, model); header.init(); @@ -19,4 +20,3 @@ header.init(); const board = new BoardPresenter(contentContainer, tripInfoContainer, model); board.init(); -model.init(); diff --git a/src/model/trip-model.js b/src/model/trip-model.js index 96055f3..0a13c4e 100644 --- a/src/model/trip-model.js +++ b/src/model/trip-model.js @@ -1,15 +1,19 @@ import Observable from '../framework/observable'; import { createFilters } from '../utils/filter'; import { createSortItems } from '../utils/sort'; -import { generateTripData } from '../../mocks/generate-data'; -import { FilterType, tripDefault, UpdateType } from '../const'; +import { EventListMessage, FilterType, tripDefault, UpdateType } from '../const'; export default class TripModel extends Observable { #eventApiService = null; + /** @type {Map} */ #events = new Map(); - #destinations = new Map(); + /** @type {Map} */ #offers = new Map(); + /** @type {Map} */ + #destinations = new Map(); + #filterType = FilterType.EVERYTHING; + #error = ''; constructor(eventApiService) { super(); @@ -45,6 +49,10 @@ export default class TripModel extends Observable { return this.#events.size; } + get errorMessage() { + return this.#error; + } + getDefaultEvent = () => { const date = new Date().toISOString(); const type = this.#offers.has(tripDefault.type) ? tripDefault.type : this.#offers.keys()[0]; @@ -65,26 +73,11 @@ export default class TripModel extends Observable { this.#events = new Map(rawEvents.map(this.#adaptEventToClient)); this.#offers = new Map(rawOffers.map(this.#adaptOffersToClient)); this.#destinations = new Map(rawDestinations.map(this.#adaptDestinationsToClient)); - } catch (err) { - - const [rawEvents, rawOffers, rawDestinations] = generateTripData(); - - rawEvents.forEach((event) => { - this.#events.set(event.id, event); - }); - - rawOffers.forEach(({ type, offers }) => { - this.#offers.set(type, offers); - }); - - rawDestinations.forEach((destination) => { - this.#destinations.set(destination.id, destination); - }); - + } catch { + this.#error = EventListMessage.ERROR; } finally { - this._notify(UpdateType.INIT); + this._notify(UpdateType.MAJOR); } - } #checkEventExistence = (id) => { diff --git a/src/presenter/board-presenter.js b/src/presenter/board-presenter.js index a4ac080..e491ee8 100644 --- a/src/presenter/board-presenter.js +++ b/src/presenter/board-presenter.js @@ -1,5 +1,5 @@ import { sortEvents } from '../utils/sort'; -import { ElementSelectors as E, EventListMessage, EventMode, FilterType, FormMode, KeyCode, SortType, UpdateType, UserAction } from '../const'; +import { ElementSelectors as E, EventListMessage, EventMode, FilterType, FormMode, KeyCode, SortType, UiBlockerTimeLimit, UpdateType, UserAction } from '../const'; import SortView from '../view/sort-view'; import EventListView from '../view/event-list-view'; import NoEventsView from '../view/no-events-view'; @@ -7,6 +7,7 @@ import NewEventButtonView from '../view/new-event-button-view'; import { filterEvents } from '../utils/filter'; import EventPresenter from './event-presenter'; import { remove, render } from '../framework/render'; +import UiBlocker from '../framework/ui-blocker/ui-blocker'; export default class BoardPresenter { #boardContainer = null; @@ -30,6 +31,11 @@ export default class BoardPresenter { #isLoading = true; + #uiBlocker = new UiBlocker({ + lowerLimit: UiBlockerTimeLimit.LOWER_LIMIT, + upperLimit: UiBlockerTimeLimit.UPPER_LIMIT + }); + constructor(boardContainer, newEventButtonContainer, tripModel) { this.#boardContainer = boardContainer; this.#newEventButtonContainer = newEventButtonContainer; @@ -46,7 +52,6 @@ export default class BoardPresenter { } init() { - this.#newEventButtonComponent.setOnClickHandler(this.#newEventHandler); render(this.#newEventButtonContainer, this.#newEventButtonComponent); @@ -54,25 +59,53 @@ export default class BoardPresenter { } #renderBoard = () => { - - if ((this.#events.length === 0) || (this.#isLoading)) { - this.#renderNoEvents(); + // if ((this.#events.length === 0) || (this.#isLoading)) { + // this.#renderNoEvents(); + // return; + // } + + const message = this.#getNoEventMessage(); + if (message) { + this.#renderNoEvents(message); return; } - remove(this.#noEventsComponent); + + this.#newEventButtonComponent.enable(); this.#renderSort(); this.#renderEventList(); this.#renderEvents(); }; - #renderNoEvents = () => { - const message = this.#isLoading ? EventListMessage.LOADING : EventListMessage[this.#tripModel.filterType]; - this.#isLoading = ''; + #renderNoEvents = (message) => { + // let mesa + // if (this.#isLoading) { + // const message = EventListMessage.LOADING; + // } else { + // const message = this.#tripModel.loadingError ? EventListMessage.ERROR : EventListMessage[this.#tripModel.filterType]; + // } + + // const message = this.#isLoading ? EventListMessage.LOADING : EventListMessage[this.#tripModel.filterType]; + // this.#isLoading = ''; + this.#noEventsComponent = new NoEventsView(message); render(this.#boardContainer, this.#noEventsComponent); }; + #getNoEventMessage() { + if (this.#isLoading) { + this.#isLoading = ''; + return EventListMessage.LOADING; + } + + if (this.#tripModel.error) { + return this.#tripModel.error; + } + + return (this.#events.length === 0) ? EventListMessage[this.#tripModel.filterType] : ''; + } + #renderEventList = () => { + remove(this.#noEventsComponent); this.#eventListComponent = new EventListView() .setEventToggleHandler(this.#eventToggleHandler) .setEscKeyDownHandler(this.#escKeyHandler); @@ -107,6 +140,9 @@ export default class BoardPresenter { this.#activeSortType = SortType.DAY; this.#tripModel.updateFilterType(UpdateType.MAJOR, FilterType.EVERYTHING); this.#newEventButtonComponent.disable(); + if (this.#events.length === 0) { + this.#renderEventList(); + } this.#newEventPresenter = this.#createEventPresenter(this.#tripModel.getDefaultEvent(), FormMode.CREATE); }; @@ -142,34 +178,51 @@ export default class BoardPresenter { } }; - #deleteEvent = (updateType, updatedEvent) => { + #deleteEvent = async (updateType, updatedEvent) => { + if (updatedEvent.id) { + await this.#tripModel.deleteEvent(updateType, updatedEvent); + return; + } + this.#destroyNewEventPresenter(); if (this.#events.length === 0) { this.#renderNoEvents(); - } else { - this.#tripModel.deleteEvent(updateType, updatedEvent); } }; - #viewActionHandler = (actionType, updateType, updatedEvent) => { + #viewActionHandler = async (actionType, updateType, updatedEvent) => { + this.#uiBlocker.block(); switch (actionType) { case UserAction.UPDATE_EVENT: - this.#tripModel.updateEvent(updateType, updatedEvent); + try { + await this.#tripModel.updateEvent(updateType, updatedEvent); + } catch { + this.#eventPresenters.get(updatedEvent.id).abort(); + } break; case UserAction.CREATE_EVENT: - this.#tripModel.createEvent(updateType, updatedEvent); + try { + await this.#tripModel.createEvent(updateType, updatedEvent); + } catch { + this.#newEventPresenter.abort(); + } break; case UserAction.DELETE_EVENT: - this.#deleteEvent(updateType, updatedEvent); + try { + await this.#deleteEvent(updateType, updatedEvent); + } catch { + this.#eventPresenters.get(updatedEvent.id)?.abort(); + } break; } + + this.#uiBlocker.unblock(); }; #onModelChangeHandler = (updateType, payload) => { - this.#events = filterEvents(this.#tripModel.events, this.#tripModel.filterType); switch (updateType) { @@ -182,18 +235,9 @@ export default class BoardPresenter { break; case UpdateType.MAJOR: - this.#updateBoard(); - break; - - case UpdateType.FILTER: this.#activeSortType = SortType.DAY; this.#updateBoard(); break; - - case UpdateType.INIT: - this.#newEventButtonComponent.enable(); - this.#renderBoard(); - break; } }; diff --git a/src/presenter/event-presenter.js b/src/presenter/event-presenter.js index 56a58cd..711f494 100644 --- a/src/presenter/event-presenter.js +++ b/src/presenter/event-presenter.js @@ -15,7 +15,8 @@ export default class EventPresenter { /** @type {Map} */ #destinations = null; - #formMode = ''; + #formMode = FormMode.EDIT; + #eventMode = EventMode.DEFAULT; #viewActionCallback = null; #getEventsQuantity = null; @@ -31,40 +32,46 @@ export default class EventPresenter { this.#offers = offers; this.#destinations = destinations; this.#formMode = formMode; + this.#eventMode = (this.#formMode === FormMode.CREATE) ? EventMode.FORM : EventMode.DEFAULT; - const prevEventComponent = this.#eventComponent; - const prevFormComponent = this.#formComponent; - //!!! update - this.#eventComponent = new EventView(this.#event, this.#offers, this.#destinations).setOnFavoriteClickHandler(this.#favoriteClickHandler); - this.#formComponent = new FormView(this.#event, this.#offers, this.#destinations, formMode) - .setOnFormSubmitHandler(this.#formSubmitHandler) - .setOnFormDeleteHandler(this.#formDeleteHandler); + if ((this.#eventComponent === null) || (this.#formComponent === null)) { - if (prevEventComponent === null || prevFormComponent === null) { - const renderingComponent = (this.#formMode === FormMode.CREATE) ? this.#formComponent : this.#eventComponent; + this.#eventComponent = new EventView(this.#event, this.#offers, this.#destinations) + .setOnFavoriteClickHandler(this.#favoriteClickHandler); + + this.#formComponent = new FormView(this.#event, this.#offers, this.#destinations, formMode) + .setOnFormSubmitHandler(this.#formSubmitHandler) + .setOnFormDeleteHandler(this.#formDeleteHandler); + + const component = (this.#formMode === FormMode.CREATE) ? this.#formComponent : this.#eventComponent; const position = (this.#formMode === FormMode.CREATE) ? RenderPosition.AFTERBEGIN : RenderPosition.BEFOREEND; - render(this.#eventContainer, renderingComponent, position); - return this; - } - if (this.#eventContainer.contains(prevEventComponent.element)) { - replace(prevEventComponent, this.#eventComponent); + render(this.#eventContainer, component, position); + return this; } - if (this.#eventContainer.contains(prevFormComponent.element)) { - replace(prevFormComponent, this.#formComponent); + if (this.#eventMode === EventMode.DEFAULT) { + this.#eventComponent.updateElement(this.#event); + } else { + this.#formComponent.updateElement(this.#event); } - remove(prevEventComponent); - remove(prevFormComponent); - return this; } - destroy() { + destroy = () => { remove(this.#eventComponent); remove(this.#formComponent); - } + }; + + abort = () => { + if (this.#eventMode === EventMode.DEFAULT) { + this.#eventComponent?.shake(); + return; + } + + this.#formComponent?.shake(() => this.#formComponent.reset()); + }; setViewActionHandler = (callback) => { this.#viewActionCallback = callback; @@ -74,8 +81,10 @@ export default class EventPresenter { toggleEventView = (direction = EventMode.DEFAULT) => { if (direction === EventMode.DEFAULT) { this.#formComponent.reset(this.#event); + this.#eventMode = EventMode.DEFAULT; replace(this.#formComponent, this.#eventComponent); } else { + this.#eventMode = EventMode.FORM; replace(this.#eventComponent, this.#formComponent); } diff --git a/src/presenter/header-presenter.js b/src/presenter/header-presenter.js index eb76cca..19c2840 100644 --- a/src/presenter/header-presenter.js +++ b/src/presenter/header-presenter.js @@ -13,6 +13,8 @@ export default class HeaderPresenter { /** @type {TripModel} */ #tripModel = null; + #activeFilter = ''; + constructor(headerContainer, filtersContainer, tripModel) { this.#headerContainer = headerContainer; this.#filterContainer = filtersContainer; @@ -31,13 +33,7 @@ export default class HeaderPresenter { #renderTripInfo = () => { - if (this.#tripInfoComponent && (this.#tripModel.eventsSize === 0)) { - remove(this.#tripInfoComponent); - this.#tripInfoComponent = null; - return; - } - - if (this.#tripModel.eventsSize !== 0) { + if (this.#tripModel.eventsSize > 0) { const prevComponent = this.#tripInfoComponent; this.#tripInfoComponent = new TripInfoView(this.#events, this.#tripModel.offers, this.#tripModel.destinations); @@ -47,6 +43,13 @@ export default class HeaderPresenter { replace(prevComponent, this.#tripInfoComponent); remove(prevComponent); } + + return; + } + + if (this.#tripInfoComponent) { + remove(this.#tripInfoComponent); + this.#tripInfoComponent = null; } }; @@ -64,12 +67,13 @@ export default class HeaderPresenter { }; #onModelChangeHandler = (updateType) => { - if ((updateType !== UpdateType.PATCH) && (updateType !== UpdateType.FILTER)) { + if ((updateType !== UpdateType.PATCH) && (this.#activeFilter !== this.#tripModel.filterType)) { + this.#activeFilter = this.#tripModel.filterType; this.init(); } }; #onFilterChangeHandler = (filterType) => { - this.#tripModel.updateFilterType(UpdateType.FILTER, filterType); + this.#tripModel.updateFilterType(UpdateType.MAJOR, filterType); }; } diff --git a/src/view/event-view.js b/src/view/event-view.js index 53588d5..e9cc51e 100644 --- a/src/view/event-view.js +++ b/src/view/event-view.js @@ -1,7 +1,7 @@ import dayjs from 'dayjs'; -import AbstractView from '../framework/view/abstract-view'; import { DateFormat } from '../const'; import { getDuration } from '../utils/event'; +import AbstractStatefulView from '../framework/view/abstract-stateful-view'; const createOfferListTemplate = (offers) => ( `${offers.map(({ title, price }) => (` @@ -75,27 +75,34 @@ const createEventTemplate = (event, offers, destinations) => { ); }; -export default class EventView extends AbstractView { - /** @type {TripEvent} */ - #event = null; +export default class EventView extends AbstractStatefulView { /** @type {Map} */ #offers = null; /** @type {Map} */ #destinations = null; + #onFavoriteClickCallback = null; + constructor(event, offers, destinations) { super(); - this.#event = event; + this._setState(event); this.#offers = offers; this.#destinations = destinations; + this._restoreHandlers(); } get template() { - return createEventTemplate(this.#event, this.#offers, this.#destinations); + return createEventTemplate(this._state, this.#offers, this.#destinations); } + _restoreHandlers = () => { + this.createEventListener('.event__favorite-btn', 'click', this.#onFavoriteClickHandler); + }; + setOnFavoriteClickHandler = (callback) => { - this.createEventListener('.event__favorite-btn', 'click', callback); + this.#onFavoriteClickCallback = callback; return this; }; + + #onFavoriteClickHandler = () => this.#onFavoriteClickCallback?.(); } diff --git a/src/view/filter-view.js b/src/view/filter-view.js index 4d4dd98..40b0f4c 100644 --- a/src/view/filter-view.js +++ b/src/view/filter-view.js @@ -31,7 +31,6 @@ export default class FilterView extends AbstractView { this.#filters = filters; this.#activeFilter = activeFilter; this.createEventListener(this.element, 'click', this.#onFilterChangeHandler); - this.createEventListener(this.element, 'submit', this.#onFormSubmitHandler, { isPreventDefault: true }); } get template() { @@ -59,22 +58,4 @@ export default class FilterView extends AbstractView { this.#activeFilter = target.dataset.filterType; this.#onFilterChangeCallback?.(this.#activeFilter); }; - - #onFormSubmitHandler = () => { - let newFilter = this.#activeFilter; - - for (const element of this.element['trip-filter']) { - if (element.checked) { - newFilter = element.value; - break; - } - } - - if (newFilter === this.#activeFilter) { - return; - } - - this.#activeFilter = newFilter; - this.#onFilterChangeCallback?.(this.#activeFilter); - }; }