Настройка отображения расписания врачей на 1С-Битрикс
Страница врача с текстом «Записаться по телефону» — это потерянный онлайн-трафик. Пользователь хочет видеть конкретные доступные дни и часы, а не звонить в регистратуру. Отображение расписания — отдельная задача от онлайн-записи: расписание должно быть наглядным, быстрым и актуальным, даже если кнопка «Записаться» ведёт к звонку.
Модели отображения расписания
Модель 1: Недельный вид. Сетка дней недели × временные слоты. Зелёные ячейки — свободно, серые — занято или нерабочее время. Кликабельные — при наличии онлайн-записи.
Модель 2: Список ближайших доступных дат. «Ближайшая запись: завтра, 14:30». Компактно, но менее информативно.
Модель 3: Горизонтальный слайдер дат. На 7–14 дней вперёд. Популярен в мобильных UI.
Выбор зависит от специализации клиники: для узких специалистов с редкими свободными слотами — список ближайших дат, для терапевта с плотным расписанием — недельная сетка.
Компонент расписания
/local/components/local/doctor.schedule/class.php:
class DoctorScheduleComponent extends CBitrixComponent
{
public function executeComponent(): void
{
$doctorId = (int)($this->arParams['DOCTOR_ID'] ?? 0);
$weeksAhead = (int)($this->arParams['WEEKS_AHEAD'] ?? 2);
if (!$doctorId) {
$this->arResult = ['ERROR' => 'Doctor not specified'];
$this->includeComponentTemplate();
return;
}
$dateFrom = new \DateTime();
$dateTo = (clone $dateFrom)->modify("+{$weeksAhead} weeks");
// Загружаем слоты из таблицы (или из МИС через кеш)
$slots = $this->loadSlots($doctorId, $dateFrom, $dateTo);
// Группируем по дате
$scheduleByDate = [];
foreach ($slots as $slot) {
$date = $slot['SLOT_DATE'];
if (!isset($scheduleByDate[$date])) {
$scheduleByDate[$date] = [
'date' => $date,
'day_name' => $this->getDayName(new \DateTime($date)),
'free_count' => 0,
'slots' => [],
];
}
$scheduleByDate[$date]['slots'][] = $slot;
if ($slot['STATUS'] === 'free') {
$scheduleByDate[$date]['free_count']++;
}
}
// Ближайший доступный слот
$nextFreeSlot = $this->getNextFreeSlot($slots);
$this->arResult = [
'DOCTOR_ID' => $doctorId,
'SCHEDULE' => $scheduleByDate,
'NEXT_FREE_SLOT' => $nextFreeSlot,
'DATE_FROM' => $dateFrom->format('Y-m-d'),
'DATE_TO' => $dateTo->format('Y-m-d'),
];
$this->setResultCacheKeys(['SCHEDULE', 'NEXT_FREE_SLOT']);
$this->includeComponentTemplate();
}
private function loadSlots(int $doctorId, \DateTime $from, \DateTime $to): array
{
return LocalDoctorSlotsTable::getList([
'filter' => [
'DOCTOR_ID' => $doctorId,
'>=SLOT_DATE' => $from->format('Y-m-d'),
'<=SLOT_DATE' => $to->format('Y-m-d'),
],
'order' => ['SLOT_DATE' => 'ASC', 'SLOT_TIME' => 'ASC'],
'select' => ['ID', 'SLOT_DATE', 'SLOT_TIME', 'STATUS'],
])->fetchAll();
}
}
Кеширование
Расписание — данные, которые меняются при новой записи. Кешируем с автоинвалидацией:
// В class.php — включаем кеш компонента
$this->arParams['CACHE_TYPE'] = 'A'; // авто
$this->arParams['CACHE_TIME'] = 120; // 2 минуты
// При создании записи — сбрасываем кеш компонента для врача
// В AppointmentService после bookSlot():
\CBitrixComponent::clearComponentCache('local:doctor.schedule', '', [
'DOCTOR_ID' => $doctorId
]);
Для AJAX-запросов расписания (динамическое обновление при переключении недели) — отдельный кеш через \Bitrix\Main\Data\Cache.
Шаблон: недельная сетка
templates/.default/template.php:
$today = new \DateTime();
$daysOfWeek = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
?>
<div class="doctor-schedule" data-doctor-id="<?= $arResult['DOCTOR_ID'] ?>">
<!-- Навигация по неделям -->
<div class="schedule-nav">
<button class="schedule-prev" data-offset="-7">← Предыдущая неделя</button>
<button class="schedule-next" data-offset="7">Следующая неделя →</button>
</div>
<div class="schedule-grid">
<?php foreach ($arResult['SCHEDULE'] as $dateStr => $dayData): ?>
<?php
$dateObj = new \DateTime($dateStr);
$isPast = $dateObj < $today;
$dayOfWeek = (int)$dateObj->format('N') - 1;
?>
<div class="schedule-day <?= $isPast ? 'past' : '' ?> <?= $dayData['free_count'] > 0 ? 'has-slots' : 'no-slots' ?>">
<div class="day-header">
<span class="day-name"><?= $daysOfWeek[$dayOfWeek] ?></span>
<span class="day-date"><?= $dateObj->format('d.m') ?></span>
</div>
<?php if ($dayData['free_count'] > 0): ?>
<div class="slots-container">
<?php foreach ($dayData['slots'] as $slot): ?>
<?php if ($slot['STATUS'] === 'free'): ?>
<button class="slot-btn free"
data-slot-id="<?= $slot['ID'] ?>"
data-time="<?= substr($slot['SLOT_TIME'], 0, 5) ?>">
<?= substr($slot['SLOT_TIME'], 0, 5) ?>
</button>
<?php endif; ?>
<?php endforeach; ?>
</div>
<div class="day-free-count"><?= $dayData['free_count'] ?> места</div>
<?php else: ?>
<div class="no-slots-label">Нет записи</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php if ($arResult['NEXT_FREE_SLOT']): ?>
<div class="next-available">
Ближайшая свободная запись:
<strong><?= date('d.m.Y', strtotime($arResult['NEXT_FREE_SLOT']['SLOT_DATE'])) ?></strong>
в <strong><?= substr($arResult['NEXT_FREE_SLOT']['SLOT_TIME'], 0, 5) ?></strong>
</div>
<?php endif; ?>
</div>
AJAX-подгрузка при переключении недели
document.querySelectorAll('.schedule-prev, .schedule-next').forEach(btn => {
btn.addEventListener('click', async function() {
const doctorId = document.querySelector('.doctor-schedule').dataset.doctorId;
const offset = parseInt(this.dataset.offset);
const dateFrom = new Date(currentDateFrom);
dateFrom.setDate(dateFrom.getDate() + offset);
const res = await fetch('/local/ajax/doctor-schedule.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
doctor_id: doctorId,
date_from: dateFrom.toISOString().split('T')[0],
sessid: BX.bitrix_sessid()
})
});
const data = await res.json();
renderScheduleGrid(data.schedule);
currentDateFrom = dateFrom;
});
});
Отображение расписания на странице списка врачей
На странице каталога врачей полное расписание не нужно — достаточно индикатора «Ближайшая запись: завтра». Это один SQL-запрос по всем врачам:
SELECT
DOCTOR_ID,
MIN(CONCAT(SLOT_DATE, ' ', SLOT_TIME)) as NEXT_FREE_SLOT
FROM local_doctor_slots
WHERE STATUS = 'free'
AND SLOT_DATE >= CURDATE()
GROUP BY DOCTOR_ID
Состав работ
- Компонент
doctor.scheduleс кешированием - Шаблон: недельная сетка или список дат (на выбор)
- AJAX-навигация по неделям
- Индикатор «Ближайшая запись» для списка врачей
- Инвалидация кеша при изменении слотов
Сроки: 1–2 недели для базового компонента с одним видом отображения. 3–4 недели с несколькими видами, AJAX-навигацией и интеграцией с МИС.







