diff --git a/package-lock.json b/package-lock.json index 442816d..c598e7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "big-trip", "version": "22.0.0", + "dependencies": { + "dayjs": "1.11.7" + }, "devDependencies": { "@babel/core": "7.21.4", "@babel/preset-env": "7.21.4", @@ -3257,6 +3260,11 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 3e38fa0..3604b67 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,8 @@ }, "engines": { "node": "20" + }, + "dependencies": { + "dayjs": "1.11.7" } } diff --git a/src/const.js b/src/const.js new file mode 100644 index 0000000..c008a71 --- /dev/null +++ b/src/const.js @@ -0,0 +1,11 @@ +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], +}); diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..f48a342 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,22 @@ +import dayjs from 'dayjs'; +import durationPlugin from 'dayjs/plugin/duration'; + +dayjs.extend(durationPlugin); + +/** Возвращает слово с заглавной буквы */ +export const capitalize = (word) => word[0].toUpperCase() + word.slice(1); + +/** Возвращает слечайный элемент массива */ +export const getRandomArrayElement = (items) => items[Math.floot(Math.randon() * items.length)]; + +export const getDate = (date) => dayjs(date).format('YYYY-MM-DD'); +export const getDateSeparatedBySlash = (date) => dayjs(date).format('YY/MM/DD'); +export const getTime = (date) => dayjs(date).format('HH:mm'); +export const getDateTime = (date) => dayjs(date).format('YYYY-MM-DDTHH:mm'); +export const getHumanDate = (date) => dayjs(date).format('MMM D'); +export const getDateDuration = (date1, date2) => { + const diff = dayjs(date2).diff(date1); + const diffDuration = dayjs.duration(diff); + return diffDuration.format('HH[H] mm[M]'); +}; + diff --git a/src/helpers/string.js b/src/helpers/string.js deleted file mode 100644 index fca6db3..0000000 --- a/src/helpers/string.js +++ /dev/null @@ -1,2 +0,0 @@ -/** Возвращает слово с заглавной буквы */ -export const capitalize = (word) => word[0].toUpperCase() + word.slice(1); diff --git a/src/main.js b/src/main.js index 98e2b87..92b7990 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,10 @@ +import PointModel from './model/point-model'; import TripPlanPresenter from './presenter/trip-plan-presenter'; -const tripPlanetPresenter = new TripPlanPresenter(); +const headerContainer = document.querySelector('.trip-controls__filters'); +const mainContainer = document.querySelector('.trip-events'); +const pointModel = new PointModel(); +pointModel.init(); -tripPlanetPresenter.init(); +const tripPlanPresenter = new TripPlanPresenter(headerContainer, mainContainer, pointModel); +tripPlanPresenter.init(); diff --git a/src/mock/destinations.js b/src/mock/destinations.js new file mode 100644 index 0000000..51e5b70 --- /dev/null +++ b/src/mock/destinations.js @@ -0,0 +1,59 @@ +export const destinations = [ + { + 'id': '1', + 'description': 'Chamonix, is a beautiful city, a true asian pearl, with crowded streets.', + 'name': 'Chamonix', + 'pictures': [ + { + 'src': 'https://loremflickr.com/248/152?random=3', + 'description': 'Chamonix parliament building' + }, + { + 'src': 'https://loremflickr.com/248/152?random=2', + 'description': 'Chamonix parliament building' + }, + { + 'src': 'https://loremflickr.com/248/152?random=1', + 'description': 'Chamonix parliament building' + } + ] + }, + { + 'id': '2', + 'description': 'London is the capital of Great Britain', + 'name': 'London', + 'pictures': [ + { + 'src': 'http://picsum.photos/300/200?r=0.0762563005163317', + 'description': 'Chamonix parliament building' + }, + { + 'src': 'https://loremflickr.com/248/152?random=6', + 'description': 'Chamonix parliament building' + }, + { + 'src': 'https://loremflickr.com/248/152?random=8', + 'description': 'Chamonix parliament building' + } + ] + }, + { + 'id': '3', + 'description': 'Saint-Petersburg is the most beautiful city in the world', + 'name': 'Saint-Petersburg', + 'pictures': [ + { + 'src': 'http://picsum.photos/300/200?r=0.0762563005163317', + 'description': 'Chamonix parliament building' + }, + { + 'src': 'https://loremflickr.com/248/152?random=4', + 'description': 'Chamonix parliament building' + }, + { + 'src': 'https://loremflickr.com/248/152?random=9', + 'description': 'Chamonix parliament building' + } + ] + } +]; diff --git a/src/mock/offers.js b/src/mock/offers.js new file mode 100644 index 0000000..f74dfea --- /dev/null +++ b/src/mock/offers.js @@ -0,0 +1,66 @@ +export const offers = [ + { + 'type': 'taxi', + 'offers': [ + { + 'id': '1', + 'title': 'Upgrade to a business class', + 'price': 120 + }, + { + 'id': '2', + 'title': 'Add luggage', + 'price': 130 + }, + { + 'id': '3', + 'title': 'Add meal', + 'price': 170 + }, + ] + }, + { + 'type': 'bus', + 'offers': [ + { + 'id': '4', + 'title': 'Switch to comfort class', + 'price': 220 + }, + { + 'id': '5', + 'title': 'Add meal', + 'price': 420 + }, + { + 'id': '6', + 'title': 'Upgrade to a business class', + 'price': 820 + }, + ] + }, + { + 'type': 'flight', + 'offers': [ + { + 'id': '7', + 'title': 'Choose seats', + 'price': 4420 + }, + { + 'id': '8', + 'title': 'Upgrade to a business class', + 'price': 8620 + }, + { + 'id': '9', + 'title': 'Travel by train', + 'price': 20 + }, + ] + }, + { + 'type': 'train', + 'offers': [], + } +]; diff --git a/src/mock/points.js b/src/mock/points.js new file mode 100644 index 0000000..24dcfa0 --- /dev/null +++ b/src/mock/points.js @@ -0,0 +1,42 @@ +export const points = [ + { + 'id': '1', + 'basePrice': 1200, + 'dateFrom': '2019-07-10T18:15:56.845Z', + 'dateTo': '2019-07-10T18:35:56.845Z', + 'destination': '1', + 'isFavorite': true, + 'offers': [ '2' ], + 'type': 'taxi' + }, + { + 'id': '2', + 'basePrice': 1100, + 'dateFrom': '2019-06-10T21:55:56.845Z', + 'dateTo': '2019-07-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-08-11T11:19:13.375Z', + 'destination': '3', + 'isFavorite': true, + 'offers': [ '7' ], + 'type': 'flight' + }, + { + 'id': '4', + 'basePrice': 1900, + 'dateFrom': '2019-07-10T07:55:56.845Z', + 'dateTo': '2019-08-11T11:19:13.375Z', + 'destination': '2', + 'isFavorite': false, + 'offers': [], + 'type': 'train' + }, +]; diff --git a/src/model/point-model.js b/src/model/point-model.js new file mode 100644 index 0000000..8499b5b --- /dev/null +++ b/src/model/point-model.js @@ -0,0 +1,23 @@ +import { points } from '../mock/points'; +import { destinations } from '../mock/destinations'; +import { offers } from '../mock/offers'; + +export default class PointModel { + init() { + this.points = points; + this.destinations = destinations; + this.offers = offers; + } + + getPoints() { + return this.points; + } + + getDestinations() { + return this.destinations; + } + + getOffers() { + return this.offers; + } +} diff --git a/src/presenter/trip-plan-presenter.js b/src/presenter/trip-plan-presenter.js index 62bcb83..9036c14 100644 --- a/src/presenter/trip-plan-presenter.js +++ b/src/presenter/trip-plan-presenter.js @@ -1,26 +1,33 @@ -import FilterForm from '../view/filter-form'; -import SortForm from '../view/sort-form'; -import WaypointForm from '../view/waypoint-form'; -import Waypoint from '../view/waypoint'; +import FilterForm from '../view/filter'; +import SortForm from '../view/sort'; +import PointForm from '../view/point-form'; +import PointsList from '../view/points-list'; +import Point from '../view/point'; import { render } from '../render'; +import { getDefaultPoint } from '../const'; export default class TripPlanPresenter { - filterFormComponent = new FilterForm(); - sortFormComponent = new SortForm(); - waypointFormComponent = new WaypointForm(); + pointsListComponent = new PointsList(); - constructor() { - this.filterContainer = document.querySelector('.trip-controls__filters'); - this.eventsContainer = document.querySelector('.trip-events'); + constructor(headerContainer, mainContainer, pointModel) { + this.filterContainer = headerContainer; + this.eventsContainer = mainContainer; + this.pointModel = pointModel; } init() { - render(this.filterFormComponent, this.filterContainer); - render(this.sortFormComponent, this.eventsContainer); - render(this.waypointFormComponent, this.eventsContainer); + const points = this.pointModel.getPoints(); + const destinations = this.pointModel.getDestinations(); + const offers = this.pointModel.getOffers(); - for (let i = 0; i < 3; i++) { - render(new Waypoint(), this.eventsContainer); + render(new FilterForm(), this.filterContainer); + render(new SortForm(), this.eventsContainer); + render(this.pointsListComponent, this.eventsContainer); + render(new PointForm(getDefaultPoint(), destinations, offers), this.pointsListComponent.element); + render(new PointForm(points[1], destinations, offers), this.pointsListComponent.element); + + for (const point of points) { + render(new Point({ point, destinations, offers}), this.pointsListComponent.element); } } } diff --git a/src/view/filter-form.js b/src/view/filter.js similarity index 93% rename from src/view/filter-form.js rename to src/view/filter.js index 86bdf90..a501be1 100644 --- a/src/view/filter-form.js +++ b/src/view/filter.js @@ -30,7 +30,7 @@ const createFilterItemTemplate = ({label, id, initialValue}) => ` `; -const createFilterFormTemplate = () => ` +const createFilterTemplate = () => `
${FILTER_ITEMS.map((item) => createFilterItemTemplate(item)).join('\n')} @@ -39,7 +39,7 @@ const createFilterFormTemplate = () => ` export default class FilterForm { getTemplate() { - return createFilterFormTemplate(); + return createFilterTemplate(); } getElement() { diff --git a/src/view/point-form.js b/src/view/point-form.js new file mode 100644 index 0000000..c2d415f --- /dev/null +++ b/src/view/point-form.js @@ -0,0 +1,142 @@ +import { capitalize } from '../helpers'; +import { createElement} from '../render'; +import { EVENT_TYPES } from '../const'; +import { getTime, getDateSeparatedBySlash } from '../helpers'; + +const createTypesList = (pointId) => `
+ + + +
+
+ Event type + ${EVENT_TYPES.map((eventType) => ` +
+ + +
`).join('\n')} +
+
+
`; + +const createPointDestinationTemplate = (pointDestination) => { + const {description, pictures} = pointDestination || {}; + return ( + `
+

Destination

+

${description}

+ ${pictures.length + ? `
+
+ ${pictures.map((picture) => `${picture.description}`)} +
+
` + : ''} +
` + ); +}; +const createPointOffersTemplate = (typeOffers, point) => { + const pointOffers = typeOffers.filter((item) => point.offers.includes(item.id)); + const activeOffers = pointOffers.map((offer) => offer.id); + const offers = typeOffers.map((typeOffer) => ( + `
+ + +
`)).join('\n'); + + + return ( + `
+

Offers

+
+ ${offers} +
` + ); +}; + +const createPointFormTemplate = (point, destinations, offers) => { + const {basePrice, dateFrom, dateTo, type} = point; + const pointDestination = destinations.find((item) => item.id === point.destination); + const {name} = pointDestination || {}; + const typeOffers = offers.find((item) => item.type === point.type).offers; + const pointId = point.id ?? 0; + + return `
  • + +
    + ${createTypesList(pointId)} + +
    + + + + ${destinations.map((dest) => ``).join('\n')} + +
    + +
    + + + — + + +
    + +
    + + +
    + + + + ${point.id + ? `` + : ''} +
    +
    + ${typeOffers.length ? createPointOffersTemplate(typeOffers, point) : ''} +
    + ${pointDestination ? createPointDestinationTemplate(pointDestination) : ''} +
  • +
    +`; +}; + +export default class PointForm { + constructor(point, destinations, offers) { + this.point = point; + this.destinations = destinations; + this.offers = offers; + } + + getTemplate() { + return createPointFormTemplate(this.point, this.destinations, this.offers); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } + +} diff --git a/src/view/point.js b/src/view/point.js new file mode 100644 index 0000000..22f5cab --- /dev/null +++ b/src/view/point.js @@ -0,0 +1,76 @@ +import { getDate, getDateTime, getHumanDate, getTime, getDateDuration } from '../helpers'; +import { capitalize } from '../helpers'; +import { createElement } from '../render'; + +const createPointTemplate = (point, destinations, offers) => { + const {basePrice, dateFrom, dateTo, isFavorite} = point; + 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)); + + return `
  • +
    + +
    + Event type icon +
    +

    ${capitalize(point.type)} ${pointDestination}

    +
    +

    + + — + +

    +

    ${getDateDuration(dateFrom, dateTo)}

    +
    +

    + € ${basePrice} +

    +

    Offers:

    + + + +
    +
  • `; +}; + +export default class Point { + constructor({point, destinations, offers}) { + this.point = point; + this.destinations = destinations; + this.offers = offers; + } + + getTemplate(point) { + return createPointTemplate(point, this.destinations, this.offers); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate(this.point)); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/points-list.js b/src/view/points-list.js new file mode 100644 index 0000000..9c02a46 --- /dev/null +++ b/src/view/points-list.js @@ -0,0 +1,22 @@ +import { createElement } from '../render'; + +const createPointFormTemplate = () => ''; + +export default class PointsList { + + getTemplate() { + return createPointFormTemplate(); + } + + getElement() { + if (!this.element) { + this.element = createElement(this.getTemplate()); + } + + return this.element; + } + + removeElement() { + this.element = null; + } +} diff --git a/src/view/sort-form.js b/src/view/sort.js similarity index 94% rename from src/view/sort-form.js rename to src/view/sort.js index 84c4148..c94799c 100644 --- a/src/view/sort-form.js +++ b/src/view/sort.js @@ -39,14 +39,14 @@ const createSortItemTemplate = ({label, id, initialValue, className}) => ` `; -const createSortFormTemplate = () => ` +const createSortTemplate = () => `
    ${SORT_ITEMS.map((item) => createSortItemTemplate(item)).join('\n')}
    `; export default class SortForm { getTemplate() { - return createSortFormTemplate(); + return createSortTemplate(); } getElement() { diff --git a/src/view/waypoint-form.js b/src/view/waypoint-form.js deleted file mode 100644 index 152cb68..0000000 --- a/src/view/waypoint-form.js +++ /dev/null @@ -1,150 +0,0 @@ -import { capitalize } from '../helpers/string'; -import { createElement } from '../render'; - -const EVENT_ITEMS = ['taxi', 'bus', 'train', 'ship', 'drive', 'flight', 'check-in', 'sightseeing', 'restaurant'].map((type) => ({ - label: capitalize(type), - id: `event-type-${type}-1`, - initialValue: type, - className: `event__type-label--${type}`, -})); - -const createEventItemTemplate = ({label, id, initialValue, className}) => ` -
    - - -
    `; - -const createWaypointFormTemplate = () => `
    -
    -
    - - - -
    -
    - Event type - ${EVENT_ITEMS.map((item) => createEventItemTemplate(item)).join('\n')} -
    -
    -
    - -
    - - - - - - - -
    - -
    - - - — - - -
    - -
    - - -
    - - - -
    -
    -
    -

    Offers

    - -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    -
    - -
    -

    Destination

    -

    Geneva is a city in Switzerland that lies at the southern tip of expansive Lac Léman (Lake Geneva). Surrounded by the Alps and Jura mountains, the city has views of dramatic Mont Blanc.

    - -
    -
    - Event photo - Event photo - Event photo - Event photo - Event photo -
    -
    -
    -
    -
    `; - -export default class WaypointForm { - getTemplate() { - return createWaypointFormTemplate(); - } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } -} diff --git a/src/view/waypoint.js b/src/view/waypoint.js deleted file mode 100644 index 9edbe1a..0000000 --- a/src/view/waypoint.js +++ /dev/null @@ -1,57 +0,0 @@ -import { createElement } from '../render'; - -const createWaypointTemplate = () => ` -
    - -
    - Event type icon -
    -

    Taxi Amsterdam

    -
    -

    - - — - -

    -

    30M

    -
    -

    - € 20 -

    -

    Offers:

    - - - -
    `; - -export default class Waypoint { - getTemplate() { - return createWaypointTemplate(); - } - - getElement() { - if (!this.element) { - this.element = createElement(this.getTemplate()); - } - - return this.element; - } - - removeElement() { - this.element = null; - } -} -