Как создать планировщик по управлению недвижимостью с помощью Bryntum и ванильного JavaScript

Компонент Bryntum Scheduler  —  это виджет управления ресурсами на JavaScript, который используется для того, чтобы вести сложные графики. Он обладает обширным функционалом и высокой производительностью. Вы можете настроить этот компонент в соответствии со своими потребностями.

В этом туториале с помощью Brynthum Scheduler мы создадим приложение для управления недвижимостью, которое будет отслеживать заказы на аренду различных квартир. Планировщик, написанный на ванильном JavaScript, будет включать следующие возможности.

  • Для объектов недвижимости будут установлены разные цены за ночь, а если объект недоступен для бронирования, то даты можно отложить.
  • Пользователь может добавлять новые события бронирования, нажимая на них и перетаскивая курсором в пустые места строк планировщика. Стоимость пребывания будет автоматически рассчитана на основе количества дней и цены за ночь.
  • Даты бронирования не должны накладываться друг на друга.
  • Пользователь может указывать детали при бронировании: имя клиента, продолжительность пребывания, цену и количество гостей.
  • Предусматривается сводная строка для отображения количества бронирований и количества гостей в день. Пользователь может переключаться между опциями сводки с помощью кнопок в верхней части планировщика. При выборе нескольких строк с объектами недвижимости рассчитываются суммарные итоговые значения для выбранных объектов.

После завершения работы у вас будет планировщик по управлению недвижимостью, похожий на тот, что показан на изображении ниже, а также в этом коротком демо-видео.

Попробуйте также поэкспериментировать с этим демо в режиме реального времени.

Начало работы

Итак, начнем с шагов, указанных в руководстве из документации Bryntum. Мы будем брать файлы из пробной дистрибутивной папки. Если вместо этого вы хотите использовать npm, следуйте этому руководству из документации.

Загрузите бесплатную пробную версию дистрибутивной папки планировщика Bryntum 5.2.0 (сделать это можно здесь). Если вы уже купили лицензионную версию, войдите в систему, чтобы получить ее.

Распакуйте скачанную папку-дистрибутив на компьютере. Мы будем использовать следующие папки для создания приложения по управлению недвижимостью.

  • /build: папка-дистрибутив, которая содержит пакеты JS, темы CSS, локали и шрифты.
  • /lib: исходный код. Его можно включить в проект ES6+ с помощью import.
  • /resources: SCSS файлы для создания темы (возможно, также генерирование пользовательской темы).
  • /examples/_shared: общие библиотечные ресурсы, используемые в демо-образцах.
  • /examples/booking: код, используемый в демо-версии планировщика.

Скопируем файлы и папки в новую папку для приложения по управлению недвижимостью. В корневой папке проекта нужно создать папку schedulerDist и добавить в нее следующие файлы и папки из дистрибутивной папки Bryntum Scheduler:

В папке examples нам нужна только папка browser и файлы shared.css, shared.css.map и shared.scss.

Теперь нужно добавить следующие файлы и папки из дистрибутивной папки Bryntum Scheduler /examples/booking/ в корневую папку проекта. В этой папке находится код для демо-версии, которую мы создадим в данном туториале:

В папке lib замените импорт файлов в DailyRate.js, DaySelector.js, PropertyModel.js и ReservationModel.js так, как показано ниже.

// Импорт для DailyRate.js

import {
    ResourceTimeRangeStore,
    DateHelper,
    RecurringTimeSpansMixin,
    RecurringTimeSpan,
    ResourceTimeRangeModel,
}  from '../schedulerDist/build/scheduler.module.js';

// Импорт для DaySelector.js

import {
    GridFeatureManager,
    Tooltip,
    DateHelper,
    DomHelper,
    EventHelper,
    InstancePlugin,
} from '../schedulerDist/build/scheduler.module.js';

// Импорт для PropertyModel.js

import { ResourceModel } from '../schedulerDist/build/scheduler.module.js';

// Импорт для ReservationModel.js

import { EventModel } from '../schedulerDist/build/scheduler.module.js';

В папке resources выполните следующие действия по поиску и замене импорта файлов:

Создайте файл index.html в корневой папке приложения и добавьте в него следующие строки:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta name="description" content="This example shows a demo booking application, using ResourceTimeRanges and the summary feature."/>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
	<title>Bryntum Scheduler - Property booking demo</title>
	<script src="./schedulerDist/examples/_shared/browser/helper.js"></script>
	<link rel="stylesheet" href="./schedulerDist/build/scheduler.stockholm.css" data-bryntum-theme>
	<link rel="stylesheet" href="./schedulerDist/examples/_shared/shared.css">
	<link rel="stylesheet" href="./resources/app.css">
</head>

<body>
	<div id="container">
	</div>
	<script type="module" src="./app.js"></script>
</body>

</html>

Создайте файл app.js в корневой папке приложения. При дальнейшем оформлении планировщика нам нужно будет работать только в этом файле.

Для разделения кода на отдельные модули, которые можно импортировать и экспортировать, нам понадобятся модули JavaScript. Поэтому нужен локальный HTTP-сервер для запуска кода. Получить локальный сервер с перезагрузкой в режиме реального времени можно, установив npm-пакет Live Server. Если вы используете VS Code, установите расширение Live Server.

Создание экземпляра планировщика

Чтобы создать планировщик, импортируем для начала все классы, которые нам понадобятся, из папки schedulerDist/build/ и папки lib в корне приложения:

import {
Scheduler,
DisplayField,
Summary,
ResourceInfoColumn,
TimeRanges,
ResourceTimeRanges,
DateHelper,
StringHelper,
} from './schedulerDist/build/scheduler.module.js';
import './lib/DaySelector.js';
import DailyRateStore from './lib/DailyRateStore.js';
import ReservationModel from './lib/ReservationModel.js';
import PropertyModel from './lib/PropertyModel.js';

Теперь создадим экземпляр планировщика и настроим его с помощью встроенных свойств:

const scheduler = new Scheduler({
appendTo : 'container',
});

Свойство appendTo указывает id DOM-элемента, к которому будет добавлен планировщик в качестве дочернего элемента. В файле index.html есть элемент <div> с id контейнера.

Если вы сейчас откроете сервер разработки в браузере, то увидите планировщик Bryntum Scheduler:

Теперь переходим к настройке планировщика с помощью дополнительных свойств, чтобы сделать его по-настоящему полезным инструментом.

Настройка диапазона дат и столбцов

Установим диапазон дат, которые отображаются в верхней части планировщика, и создадим столбец для ресурсов, которые будут объектами недвижимости.

Добавьте в планировщик следующие свойства:

    viewPreset        : 'dayAndMonth',
resourceImagePath : 'resources/',
startDate : new Date(2022, 10, 28),
endDate : new Date(2022, 11, 20),

ViewPreset указывает на тип представления временной шкалы. В этом случае мы будем использовать представление по дням и месяцам для календаря. Ресурс ImagePath содержит изображения объектов недвижимости, которые будут отображаться в специальной колонке Property, которую мы создадим. Установим начальную и конечную дату представления календаря с помощью объектов JavaScript Date.

Добавьте свойство columns, чтобы определить объект размещения:

    columns : [
{
type : 'resourceInfo',
text : 'Property',
width : 260,
}
],

Мы установили тип resourceInfo, чтобы отобразить базовую информацию для ресурсов планировщика, которые будут представлять собой объекты недвижимости.

Теперь в планировщике появится колонка PROPERTY, а заголовок календаря красиво отформатируется:

Получение данных с помощью менеджера Crud Manager

Планировщик включает в себя crudManager для загрузки и сохранения данных, получаемых дистанционно. У него есть свойство loadUrl, которое мы будем использовать для заполнения хранилищ данных планировщика. Добавьте свойство crudManager в планировщик:

    crudManager : {
        autoLoad      : true,
        resourceStore : {
            modelClass : PropertyModel
        },
            
       eventStore : {
            modelClass : ReservationModel
        },

        resourceTimeRangeStore : new DailyRateStore(),

        loadUrl : 'data/data.json'
    },

Свойство loadUrl определяет источник данных (url), которым является файл data.json в папке data. Можно также настроить crudManager на синхронизацию изменений данных на определенный url бэкенда. Дополнительную информацию можно найти в руководстве Saving data.

Определим три хранилища данных, которые будем использовать. Они нужны для моделей и хранилищ, определенных в папке ./lib. В resourceStore находятся данные для объектов недвижимости. Это хранилище использует модель PropertyModel, которая дополняет модель планировщика ResourceModel, добавляя к ней дополнительное поле sleeps. Это свойство используется в файле data/data.json в строках ресурсов. Оно определяет количество людей, которое может вместить каждый объект.

EventStore хранит данные для событий бронирования. Он использует пользовательскую модель ReservationModel, которая расширяет модель планировщика EventModel (добавляются дополнительные поля guests и pricePerNight). Эти дополнительные свойства используются в файле data/data.json в строках событий.

Хранилище resourceTimeRangeStore содержит данные для ресурса временных диапазонов, которые используются для отображения цен на конкретные даты и объекты в части календаря планировщика. Класс DailyRateStore расширяет класс планировщика RecurringTimeSpansMixin: в планировщик добавляется функционал повторяющихся временных интервалов. Этот класс использует модель DailyRateModel, которая имеет поле pricePerNight. В нем также есть метод getPricePerNightFor, который мы будем использовать для получения цены за ночь для объекта недвижимости при создании нового события бронирования на определенную дату.

В файле data/data.json строки resourceTimeRanges снабжены полем pricePerNight и правилом recurrenceRule, указывающим дни, в которые применяются цены. Есть также поле exceptionDates, которое показывает дни, в которые цены не применимы. Это те дни, когда объекты недоступны для бронирования.

Теперь в приложении можно видеть объекты недвижимости и имена тех, кто их забронировал:

Однако здесь не отражены цены по датам и объектам размещения. Исправим это.

Отображение цен по датам и объектам недвижимости

Сначала увеличим свойства событий бронирования rowHeight и barMargin. Добавьте следующие свойства в планировщик:

    rowHeight         : 70,
barMargin : 15,

Теперь добавим в планировщик свойство features:

features: {
// Временные диапазоны используются для затенения времени в прошлом (имитация)
timeRanges : true,
// Временные диапазоны используются для отображения цен по датам и объектам недвижимости
resourceTimeRanges : {
enableMouseEvents : true
},
},

Свойство timeRanges используется для визуализации временного диапазона. В нашем случае оно имитирует затенение времени в прошлом. Отображаемый временной диапазон определяется в свойстве timeRanges в файле data/data.json. У него есть свойство cls, которое является пользовательским классом CSS. Затененный класс определен в resources/app.css.

Теперь добавьте следующие три свойства в планировщик, ниже свойства endDate:

    zoomOnTimeAxisDoubleClick : false,
zoomOnMouseWheel : false,
allowOverlap : false,

Свойства zoomOnTimeAxisDoubleClick и zoomOnMouseWheel предотвращают увеличение и уменьшение масштаба текущего представления календаря. Свойство allowOverlap не допускает наложение одного события бронирования на другое. Попробуйте переместить событие бронирования на дату, на которой уже размещено другое событие бронирования. Ничего не получится. Также нельзя двигать события бронирования в затененной области. Затененный слой предназначен для того, чтобы нельзя было перемещать события в прошлом.

Можно увеличить продолжительность бронирования, потянув за край соответствующей плашки. Чтобы улучшить пользовательское восприятие этой функции, сделаем так, чтобы ширина плашки бронирования простиралась до следующего дня при изменении ее размера. Добавьте следующие свойства в планировщик ниже свойства barMargin:

snap : true,
tickSize : 100,
timeResolution : {
unit : 'day',
increment : 1
},

Теперь при изменении размера плашки бронирования ее ширина будет увеличиваться до следующего дня. Пользователь не сможет, например, забронировать объект на полдня.

Вы, вероятно, заметили, что добавлять новые события бронирования пока нельзя. Начнем работать над этим. 

Пользовательская функция добавления бронирования

Оснастим приложение пользовательской функцией, которая позволит добавлять новые события бронирования, щелкая на них и перетаскивая курсором в пустые части строк планировщика. Стоимость пребывания будет автоматически рассчитана на основе количества дней и цены за ночь. При создании события бронирования во всплывающей подсказке будет показана рассчитанная стоимость и продолжительность пребывания.

Добавьте следующее свойство к планировщику внутри свойства features:

        // Пользовательская функция, позволяющая выбирать дни перетаскиванием (см. lib/DaySelector.js)
daySelector : true,

Класс, определяющий эту пользовательскую функцию, находится в lib/DaySelector.js.

Попробуйте добавить событие. Вы заметите, что появится редактор событий. К нему можно получить доступ и другим путем  —  щелкнуть правой кнопкой мыши на уже размещенном бронировании. Редактор событий пока не содержит всех необходимых нам полей, таких как количество гостей. Перейдем к соответствующей настройке в следующем разделе.

Настройка всплывающего меню редактора события

Чтобы настроить всплывающее меню редактора события, воспользуемся функцией eventEdit. Добавьте эту функцию в планировщик внутри свойства features:

        // Настройте редактор событий, чтобы он оптимально соответствовал условиям использования
eventEdit : {
editorConfig : {
autoUpdateRecord : true,
defaults : {
labelPosition : 'above'
}
},
items : {
startTimeField : false,
endTimeField : false,
endDateField : false,

nameField : {
label : 'Guest name'
},
resourceField : {
label : 'Property'
},
startDateField : {
label : 'Check-in',
flex : '1 0 50%'
},
durationField : {
type : 'number',
label : 'Nights',
name : 'duration',
flex : '1 0 50%',
cls : 'b-inline',
min : 0,
weight : 500
},
priceField : {
type : 'number',
name : 'pricePerNight',
label : 'Price per night (USD)',
weight : 210
},
// Custom field for number of guests
guestsField : {
type : 'number',
name : 'guests',
label : 'Number of guests',
weight : 210,
value : 2,
required : true,
min : 1
}
}
},

Мы задаем полям, которые нам не нужны, значение false и определяем поля, которые нам нужны. Для полей NIGHTS и NUMBER OF GUESTS устанавливаем минимальное значение, равное одному. Поле NUMBER OF GUESTS по умолчанию имеет значение 2 и является обязательным для заполнения.

Отключим некоторые функции, которые нам не нужны в рамках этого туториала. Добавим следующие свойства внутри свойства features:

        // Отключаем функции, которые не будут использоваться в этом туториале (в основном это делается для предотвращения масштабирования, в результате чего используется фиксированное представление)
cellMenu : false,
scheduleMenu : false,
timeAxisHeaderMenu : false,
eventDragCreate : false,
scheduleTooltip : false,

Если вы создадите сейчас событие бронирования, то заметите, что поле PRICE PER NIGHT (USD) (цена за ночь в долларах США) пустое. Мы можем сделать так, чтобы оно автоматически заполнялось ценой за ночь для первого забронированного дня. Добавьте следующий метод в планировщик, ниже свойства crudManager:

    onBeforeEventAdd({ eventRecord, resourceRecords }) {
console.log("beforeEventAdd");
// Скопируйте цену в запись о бронировании после ее создания
eventRecord.pricePerNight = this.resourceTimeRangeStore.getPricePerNightFor(resourceRecords[0], eventRecord.startDate);
},

Событие beforeEventAdd выполняется перед добавлением события. Мы добавляем цену за ночь в eventRecord с помощью метода getPricePerNightFor, определенного в lib/DailyRateStore.js.

Теперь при добавлении задачи поле PRICE PER NIGHT (USD) заполняется автоматически.

Изменение текста в плашке бронирования

В настоящее время в плашке бронирования отображается только имя гостя. Изменим ее таким образом, чтобы там также отображалось количество гостей.

Добавьте метод eventRenderer в планировщик под методом onBeforeEventAdd:

    eventRenderer({ eventRecord }) {
return StringHelper.xss`${eventRecord.name} <i class="b-fa b-fa-user"><sup>${eventRecord.guests}</sup>`;
},

Функция eventRenderer вызывается, когда событие отображается в планировщике. Возвращается строка, которая создает HTML, отображаемый на плашке бронирования. Добавляем имя забронировавшего и количество гостей. Для обозначения количества гостей используем иконку Font Awesome. Версия Font Awesome, используемая в Bryntum, имеет префикс b-fa вместо fa, чтобы не произошло конфликтов с другими версиями Font Awesome на странице. Источник этих иконок находится в schedulerDist/build/fonts, и они импортируются как font-face в /schedulerDist/build/scheduler.stockholm.css.

Теперь на плашке бронирования вы видите иконку с цифрой, обозначающей количество гостей.

Настройка всплывающей подсказки события

При наведении курсора на событие бронирования появляется всплывающая подсказка по умолчанию, которая показывает начальную и конечную дату бронирования. Настроим ее так, чтобы она отображала дату регистрации и продолжительность пребывания. Добавьте функцию eventTooltip в планировщик внутри свойства features:

       // Настройка всплывающей подсказки события для отображения даты регистрации и продолжительности пребывания
eventTooltip : {
template : ({ eventRecord }) => `
<h4>Check-in:</h4>
${DateHelper.format(eventRecord.startDate, 'MMM Do')}
<h4>Length of stay:</h4>
${eventRecord.duration} nights
`
},

Функция eventTooltip позволяет настраивать шаблон, используемый для отображения всплывающей подсказки. Метод template получает данные о событии в качестве аргументов. Аргумент eventRecord используется для вывода строки, отображающей нужные нам данные о бронировании.

Добавление сводной строки

Последняя функция, которую мы добавим,  —  сводная строка, показывающая количество бронирований и количество гостей в день.

Добавьте функцию summary в планировщик внутри свойства features:

        // Различные сводки для каждой отметки временной оси в зависимости от того, какая кнопка на панели инструментов нажата
summary : {
renderer : ({ events : reservations }) => {
let result;

if (scheduler.widgetMap.summaryButton.value === 'count') {
result = reservations.length;
}
else {
result = reservations.reduce((total, reservation) => total + reservation.guests, 0);
}
return StringHelper.xss`${result || ''}`;
}
},

Функция summary отображает сводную строку в нижней части сетки. Она рендерит количество бронирований и общее количество гостей в зависимости от значения группы summaryButton, которую мы скоро создадим.

Сделаем панель инструментов в верхней части планировщика, которая будет снабжена группой summaryButton с переключающимся значением. Добавьте следующее свойство в планировщик:

     tbar : [
{
type : 'widget',
html : 'Sum:'
},
{
type : 'buttongroup',
ref : 'summaryButton',
toggleGroup : true,
items : [{
value : 'count',
pressed : true,
text : 'Booked properties / day'
}, {
value : 'guests',
text : 'Booked guests / day'
}],
onClick() {
scheduler.features.summary.refresh();
}
},
{
text : 'Sum selected rows',
toggleable : true,
onToggle : 'up.onSumToggle'
}
],

summaryButton имеет переключаемое значение, которое является либо счетчиком, либо количеством гостей. При нажатии на одно из них значение обновляется, и вместе с этим обновляется значение функции summary.

Есть также кнопка “Sum selected rows”, которая позволяет подсчитать количество забронированных объектов и гостей только для выбранных строк. Чтобы запустить эту функцию, нужно определить метод onSumToggle. Добавьте метод onSumToggle в планировщик ниже метода eventRenderer:

    onSumToggle() {
this.features.summary.selectedOnly = !this.features.summary.selectedOnly;
},

Теперь можно определить количество забронированных объектов недвижимости и гостей в день в рамках всего планировщика или только в выбранных строках.

Дальнейшие шаги

Это руководство должно послужить вам отправной точкой для создания собственного планировщика по управлению недвижимостью с помощью Bryntum и ванильного JavaScript. Есть множество способов дополнить и улучшить его. Например, ограничить количество гостей для одного бронирования на основе максимального количества человек, которое может вместить объект размещения.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Mats Bryntse: Creating a property management scheduler with Bryntum using vanilla JavaScript

Предыдущая статьяКак сократить время начальной загрузки веб-приложения
Следующая статьяКак ускорить сайт с помощью Varnish HTTP Cache и Docker