import { api } from '../../../js/api.js'; import { escapeHtml, hideAlert, showAlert } from '../../../js/utils.js'; const APPLY_MODE_LABELS = { DEFAULT: 'применяется по умолчанию', WEEKDAY: 'применяется автоматически', MANUAL: 'применяется вручную' }; const DAY_SHORT_LABELS = { 1: 'Пн', 2: 'Вт', 3: 'Ср', 4: 'Чт', 5: 'Пт', 6: 'Сб', 7: 'Вс' }; export async function initTimeSlotsSettings() { const scopeSelect = document.getElementById('time-slot-scope-select'); const scopeSummary = document.getElementById('time-slot-scope-summary'); const scopeOpenCreateButton = document.getElementById('time-slot-scope-open-create'); const scopeModal = document.getElementById('time-slot-scope-modal'); const scopeModalCloseButton = document.getElementById('time-slot-scope-modal-close'); const scopeModalCancelButton = document.getElementById('time-slot-scope-modal-cancel'); const scopeForm = document.getElementById('time-slot-scope-form'); const scopeNameInput = document.getElementById('time-slot-scope-name'); const scopeDeleteButton = document.getElementById('time-slot-scope-delete'); const form = document.getElementById('time-slot-form'); const title = document.getElementById('time-slot-form-title'); const idInput = document.getElementById('time-slot-id'); const orderInput = document.getElementById('time-slot-order'); const startInput = document.getElementById('time-slot-start'); const endInput = document.getElementById('time-slot-end'); const durationInput = document.getElementById('time-slot-duration'); const resetButton = document.getElementById('time-slot-reset'); const refreshButton = document.getElementById('time-slots-refresh'); const tbody = document.getElementById('time-slots-tbody'); let scopes = []; let slots = []; let activeScopeId = null; scopeSelect?.addEventListener('change', () => { activeScopeId = Number(scopeSelect.value); resetForm(); renderActiveScope(); }); scopeOpenCreateButton?.addEventListener('click', openScopeModal); scopeModalCloseButton?.addEventListener('click', closeScopeModal); scopeModalCancelButton?.addEventListener('click', closeScopeModal); scopeModal?.addEventListener('click', (event) => { if (event.target === scopeModal) closeScopeModal(); }); scopeModal?.addEventListener('keydown', (event) => { if (event.key === 'Escape') closeScopeModal(); }); scopeForm?.addEventListener('submit', createScope); scopeDeleteButton?.addEventListener('click', deleteActiveScope); form?.addEventListener('submit', saveSlot); resetButton?.addEventListener('click', resetForm); refreshButton?.addEventListener('click', loadAll); startInput?.addEventListener('change', calculateDuration); endInput?.addEventListener('change', calculateDuration); tbody?.addEventListener('click', handleSlotTableClick); await loadAll(); async function loadAll() { tbody.innerHTML = 'Загрузка...'; hideAlert('time-slot-alert'); try { [scopes, slots] = await Promise.all([ api.get('/api/admin/time-slots/scopes'), api.get('/api/admin/time-slots') ]); if (!activeScopeId || !scopes.some(scope => scope.id === activeScopeId)) { activeScopeId = defaultScope()?.id || scopes[0]?.id || null; } render(); } catch (error) { tbody.innerHTML = `Ошибка загрузки: ${escapeHtml(error.message)}`; } } function render() { renderScopeSelects(); renderActiveScope(); } function renderActiveScope() { updateScopeActions(); renderScopeSummary(); renderSlots(); } function renderScopeSelects() { scopeSelect.innerHTML = scopes.map(scope => ` `).join(''); scopeSelect.value = activeScopeId ? String(activeScopeId) : ''; updateScopeActions(); } function updateScopeActions() { const activeScope = selectedScope(); scopeDeleteButton.disabled = !activeScope || activeScope.systemScope; } function renderScopeSummary() { const scope = selectedScope(); if (!scope) { scopeSummary.innerHTML = 'Сетки времени не настроены'; return; } const mode = APPLY_MODE_LABELS[scope.applyMode] || scope.applyMode; const day = scope.applyMode === 'WEEKDAY' ? `, ${dayShort(scope.dayOfWeek)}` : ''; const slotCount = slots.filter(slot => slot.scopeId === scope.id).length; scopeSummary.innerHTML = ` ${escapeHtml(scopeBadgeLabel(scope))} ${escapeHtml(mode + day)}. Слотов: ${slotCount}. `; } function renderSlots() { const scopeSlots = slots .filter(slot => slot.scopeId === activeScopeId) .sort((a, b) => (a.orderNumber || 0) - (b.orderNumber || 0)); if (!scopeSlots.length) { tbody.innerHTML = 'В выбранной сетке нет слотов'; return; } tbody.innerHTML = scopeSlots.map(slot => ` ${escapeHtml(String(slot.orderNumber ?? '-'))} ${escapeHtml(trimTime(slot.startTime))} ${escapeHtml(trimTime(slot.endTime))} ${escapeHtml(String(slot.durationMinutes ?? '-'))} `).join(''); } function openScopeModal() { scopeForm.reset(); hideAlert('time-slot-scope-alert'); scopeModal.classList.add('open'); scopeModal.setAttribute('aria-hidden', 'false'); window.setTimeout(() => scopeNameInput?.focus(), 0); } function closeScopeModal() { scopeModal.classList.remove('open'); scopeModal.setAttribute('aria-hidden', 'true'); hideAlert('time-slot-scope-alert'); } async function createScope(event) { event.preventDefault(); const name = scopeNameInput.value.trim(); if (!name) { showAlert('time-slot-scope-alert', 'Введите название ручной сетки', 'error'); return; } try { const saved = await api.post('/api/admin/time-slots/scopes', { name }); activeScopeId = saved.id; closeScopeModal(); showAlert('time-slot-alert', 'Сетка времени создана', 'success'); await loadAll(); } catch (error) { showAlert('time-slot-scope-alert', error.message || 'Ошибка создания сетки времени', 'error'); } } async function deleteActiveScope() { const scope = selectedScope(); if (!scope || scope.systemScope) return; if (!confirm(`Удалить сетку "${scope.name}" вместе с её слотами и ручными назначениями?`)) return; try { await api.delete('/api/admin/time-slots/scopes/' + scope.id); activeScopeId = defaultScope()?.id || null; resetForm(); showAlert('time-slot-alert', 'Сетка времени удалена', 'success'); await loadAll(); } catch (error) { showAlert('time-slot-alert', error.message || 'Ошибка удаления сетки времени', 'error'); } } async function saveSlot(event) { event.preventDefault(); hideAlert('time-slot-alert'); const payload = { id: idInput.value ? Number(idInput.value) : null, orderNumber: Number(orderInput.value), scopeId: activeScopeId, startTime: startInput.value, endTime: endInput.value }; try { validateSlotPayload(payload); const id = idInput.value; await (id ? api.put('/api/admin/time-slots/' + id, payload) : api.post('/api/admin/time-slots', payload)); showAlert('time-slot-alert', 'Временной слот сохранён', 'success'); resetForm(); await loadAll(); } catch (error) { showAlert('time-slot-alert', error.message || 'Ошибка сохранения временного слота', 'error'); } } async function handleSlotTableClick(event) { const editButton = event.target.closest('.btn-edit-time-slot'); const deleteButton = event.target.closest('.btn-delete-time-slot'); if (editButton) { const slot = slots.find(item => String(item.id) === String(editButton.dataset.id)); if (!slot) return; activeScopeId = slot.scopeId; title.textContent = 'Редактирование слота'; idInput.value = slot.id; orderInput.value = slot.orderNumber || ''; startInput.value = trimTime(slot.startTime); endInput.value = trimTime(slot.endTime); durationInput.value = slot.durationMinutes || ''; hideAlert('time-slot-alert'); render(); form.scrollIntoView({ behavior: 'smooth', block: 'start' }); return; } if (deleteButton) { if (!confirm('Удалить временной слот?')) return; try { await api.delete('/api/admin/time-slots/' + deleteButton.dataset.id); showAlert('time-slot-alert', 'Временной слот удалён', 'success'); resetForm(); await loadAll(); } catch (error) { showAlert('time-slot-alert', error.message || 'Ошибка удаления временного слота', 'error'); } } } function validateSlotPayload(payload) { if (!payload.scopeId) { throw new Error('Выберите сетку времени'); } if (!payload.orderNumber || payload.orderNumber <= 0) { throw new Error('Укажите номер пары'); } if (!payload.startTime || !payload.endTime) { throw new Error('Укажите время начала и окончания'); } if (payload.startTime >= payload.endTime) { throw new Error('Время начала должно быть раньше времени окончания'); } } function calculateDuration() { durationInput.value = ''; if (!startInput.value || !endInput.value || startInput.value >= endInput.value) return; const [startHour, startMinute] = startInput.value.split(':').map(Number); const [endHour, endMinute] = endInput.value.split(':').map(Number); durationInput.value = String((endHour * 60 + endMinute) - (startHour * 60 + startMinute)); } function resetForm() { form.reset(); idInput.value = ''; title.textContent = 'Новый слот'; hideAlert('time-slot-alert'); } function selectedScope() { return scopes.find(scope => scope.id === activeScopeId) || null; } function defaultScope() { return scopes.find(scope => scope.applyMode === 'DEFAULT') || null; } } function scopeOptionLabel(scope) { const suffix = scope.applyMode === 'WEEKDAY' ? `, ${dayShort(scope.dayOfWeek)}` : ''; return `${scope.name} - ${APPLY_MODE_LABELS[scope.applyMode] || scope.applyMode}${suffix}`; } function scopeBadgeLabel(scope) { if (scope.applyMode === 'DEFAULT') return 'Базовая'; if (scope.applyMode === 'WEEKDAY') return dayShort(scope.dayOfWeek); return 'Ручная'; } function dayShort(dayOfWeek) { return DAY_SHORT_LABELS[Number(dayOfWeek)] || '-'; } function trimTime(value) { return value ? String(value).slice(0, 5) : ''; }