Налаштування відображення розкладу лікарів на 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Показано 1 з 1 послугУсі 1626 послуг
Налаштування відображення розкладу лікарів на 1С-Бітрікс
Проста
~1 робочий день
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Налаштування відображення розкладу лікарів на 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-навігацією та інтеграцією з МІС.