import { useEffect, useRef, useState } from 'react'; import { AlertDialog, ConfirmDialog } from '../../shared/ui/ConfirmDialog.jsx'; import { archiveClassroom, classroomStatusLabel, createClassroom, createEquipment, deleteEquipment, equipmentIdSelection, equipmentNamesText, equipmentSelectLabel, fetchClassrooms, fetchEquipments, parseCapacity, restoreClassroom, toggleClassroomAvailability, updateClassroom } from '../classrooms-service.js'; export function ClassroomsTab() { const [equipments, setEquipments] = useState([]); const [classrooms, setClassrooms] = useState(null); const [loadError, setLoadError] = useState(false); const [createName, setCreateName] = useState(''); const [createCapacity, setCreateCapacity] = useState(''); const [createEquipmentIds, setCreateEquipmentIds] = useState(() => new Set()); const [createAlert, setCreateAlert] = useState({ text: '', isError: false }); const [editModal, setEditModal] = useState(null); const [equipmentModal, setEquipmentModal] = useState(null); const [confirmState, setConfirmState] = useState(null); const [alertState, setAlertState] = useState(null); useEffect(() => { let active = true; (async () => { try { const [loadedEquipments, loadedClassrooms] = await Promise.all([ fetchEquipments(), fetchClassrooms() ]); if (!active) return; setEquipments(loadedEquipments || []); setClassrooms(loadedClassrooms || []); setLoadError(false); } catch { if (active) setLoadError(true); } })(); return () => { active = false; }; }, []); async function reloadClassrooms() { try { setClassrooms(await fetchClassrooms()); setLoadError(false); } catch { setLoadError(true); } } async function reloadEquipments() { const loaded = await fetchEquipments(); setEquipments(loaded || []); return loaded || []; } function toggleCreateEquipment(equipmentId) { setCreateEquipmentIds(current => { const next = new Set(current); if (next.has(equipmentId)) { next.delete(equipmentId); } else { next.add(equipmentId); } return next; }); } async function submitCreate(event) { event.preventDefault(); const name = createName.trim(); const capacity = parseCapacity(createCapacity); if (!name || Number.isNaN(capacity)) { setCreateAlert({ text: 'Заполните обязательные поля', isError: true }); return; } try { const created = await createClassroom({ name, capacity, equipmentIds: [...createEquipmentIds].map(Number) }); setCreateAlert({ text: `Аудитория «${created.name}» добавлена`, isError: false }); setCreateName(''); setCreateCapacity(''); setCreateEquipmentIds(new Set()); await reloadClassrooms(); } catch (error) { setCreateAlert({ text: error.message || 'Ошибка создания', isError: true }); } } function confirmArchive(classroom) { setConfirmState({ title: 'Вывод аудитории из эксплуатации', message: 'Историческое расписание сохранится, но аудитория перестанет участвовать в планировании.', confirmLabel: 'Вывести из эксплуатации', danger: true, action: async () => { try { await archiveClassroom(classroom.id); await reloadClassrooms(); } catch (error) { setAlertState({ title: 'Ошибка архивирования', message: error.message || 'Не удалось архивировать аудиторию' }); } } }); } async function restore(classroom) { try { await restoreClassroom(classroom.id); await reloadClassrooms(); } catch (error) { setAlertState({ title: 'Ошибка восстановления', message: error.message || 'Не удалось восстановить аудиторию' }); } } async function toggleAvailability(classroom) { try { await toggleClassroomAvailability(classroom.id, !classroom.isAvailable); await reloadClassrooms(); } catch (error) { setAlertState({ title: 'Ошибка изменения статуса', message: error.message || 'Не удалось изменить доступность аудитории' }); } } async function openEditModal(classroomId) { try { const fresh = await fetchClassrooms(); const classroom = (fresh || []).find(item => String(item.id) === String(classroomId)); if (!classroom) return; setEditModal({ classroom, name: classroom.name, capacity: String(classroom.capacity), equipmentIds: new Set((classroom.equipments || []).map(equipment => String(equipment.id))), alert: { text: '', isError: false } }); } catch (error) { setAlertState({ title: 'Ошибка загрузки', message: error.message || 'Не удалось загрузить данные аудитории' }); } } function toggleEditEquipment(equipmentId) { setEditModal(current => { if (!current) return current; const next = new Set(current.equipmentIds); if (next.has(equipmentId)) { next.delete(equipmentId); } else { next.add(equipmentId); } return { ...current, equipmentIds: next }; }); } async function submitEdit(event) { event.preventDefault(); const modal = editModal; const name = modal.name.trim(); const capacity = parseCapacity(modal.capacity); if (!name || Number.isNaN(capacity)) { setEditModal({ ...modal, alert: { text: 'Заполните обязательные поля', isError: true } }); return; } try { const updated = await updateClassroom(modal.classroom.id, { name, capacity, equipmentIds: [...modal.equipmentIds].map(Number), isAvailable: modal.classroom.isAvailable }); setEditModal(null); setCreateAlert({ text: `Аудитория «${updated.name}» обновлена`, isError: false }); await reloadClassrooms(); } catch (error) { setEditModal({ ...modal, alert: { text: error.message || 'Ошибка обновления', isError: true } }); } } async function openEquipmentModal() { setEquipmentModal({ name: '', list: null, alert: { text: '', isError: false } }); await reloadEquipmentModalList(); } async function reloadEquipmentModalList() { try { const loaded = await reloadEquipments(); setEquipmentModal(current => (current ? { ...current, list: loaded } : current)); } catch { setEquipmentModal(current => (current ? { ...current, list: [] } : current)); } } async function submitEquipment(event) { event.preventDefault(); const modal = equipmentModal; const name = modal.name.trim(); if (!name) { setEquipmentModal({ ...modal, alert: { text: 'Введите название', isError: true } }); return; } try { const created = await createEquipment(name); setEquipmentModal(current => ( current ? { ...current, name: '', alert: { text: `Оборудование «${created.name}» добавлено`, isError: false } } : current )); await reloadEquipmentModalList(); } catch (error) { setEquipmentModal({ ...modal, alert: { text: error.message || 'Ошибка добавления', isError: true } }); } } function confirmDeleteEquipment(equipment) { setConfirmState({ title: 'Удаление оборудования', message: 'Удалить оборудование из каталога?', confirmLabel: 'Удалить', danger: true, action: async () => { try { await deleteEquipment(equipment.id); await reloadEquipmentModalList(); } catch (error) { setAlertState({ title: 'Ошибка удаления', message: error.message || 'Не удалось удалить оборудование' }); } } }); } async function runConfirm() { const confirm = confirmState; setConfirmState(null); await confirm.action(); } return ( <>

Новая аудитория

setCreateName(event.target.value)} />
setCreateCapacity(event.target.value)} />
{createAlert.text}

Список аудиторий

{loadError ? ( ) : classrooms === null ? ( ) : classrooms.length === 0 ? ( ) : classrooms.map(classroom => ( ))}
ID Название Вместимость Оборудование Статус Действия
Ошибка загрузки данных
Нет аудиторий
{classroom.id} {classroom.name} {classroom.capacity} чел. {equipmentNamesText(classroom) || '—'}
{classroomStatusLabel(classroom)}
{classroom.status === 'ARCHIVED' ? ( ) : ( )}
{editModal && (
{ if (event.target === event.currentTarget) setEditModal(null); }} >

Редактировать аудиторию

setEditModal({ ...editModal, name: event.target.value })} />
setEditModal({ ...editModal, capacity: event.target.value })} />
{editModal.alert.text}
)} {equipmentModal && (
{ if (event.target === event.currentTarget) setEquipmentModal(null); }} >

Справочник оборудования

Добавить оборудование

setEquipmentModal({ ...equipmentModal, name: event.target.value })} />
{equipmentModal.alert.text}
{equipmentModal.list === null ? ( ) : equipmentModal.list.length === 0 ? ( ) : equipmentModal.list.map(equipment => ( ))}
ID Название Действия
Загрузка...
Нет оборудования
{equipment.id} {equipment.name}
)} {confirmState && ( void runConfirm()} onClose={() => setConfirmState(null)} /> )} {alertState && ( setAlertState(null)} /> )} ); } function EquipmentMultiSelect({ equipments, selectedIds, onToggle }) { const rootRef = useRef(null); const [open, setOpen] = useState(false); useEffect(() => { if (!open) return undefined; function handlePointerDown(event) { if (rootRef.current && !rootRef.current.contains(event.target)) { setOpen(false); } } document.addEventListener('mousedown', handlePointerDown); return () => document.removeEventListener('mousedown', handlePointerDown); }, [open]); const selection = equipmentIdSelection(equipments, [...selectedIds]); const selectedNames = selection.filter(item => item.checked).map(item => item.name); return (
{equipments.length === 0 ? (

Оборудование отсутствует

) : selection.map(item => ( ))}
); }