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) : '';
}