diff --git a/README.md b/README.md index b07609f..d7a57c8 100644 --- a/README.md +++ b/README.md @@ -413,7 +413,10 @@ tests/ - Длинные ссылки в карточке переносятся по служебным символам и каждые 24 символа (``), поэтому карточка не вылезает за экран даже в старых браузерах, где нет `overflow-wrap`. -- На вкладке «Аудитории» есть фильтры: **тип** (фразой, как в таблице), +- На вкладке «Аудитории» есть фильтры: **тип** (в карточке — формулировка из + таблицы, в фильтре близкие варианты объединены: «комп.класс» включает + «комп.класс Linux» и «компьютерный класс», «кабинет английского» — + «английский»; есть пункт «без типа»), **вместимость** («не меньше N мест») и **оснащение** чекбоксами с выбором условия «все выбранные (И)» или «любое из выбранных (ИЛИ)». Выбранное запоминается в браузере, под списком видно «Показано N из M» и пояснения: @@ -448,7 +451,10 @@ tests/ В ответах `rooms-day` и `rooms-free` по каждой аудитории, кроме `room` и `info` (описание как в таблице), идут разобранные атрибуты: `type` (тип фразой из -таблицы: «комп.класс», «кабинет английского», «лекционная»…), `capacity` +таблицы: «комп.класс», «кабинет английского», «лекционная»…) и `type_group` +(тот же тип, но близкие формулировки сведены к одной — «комп.класс», +«комп.класс Linux» и «компьютерный класс» дают группу «комп.класс»), +`capacity` (мест; для компьютерных классов — рабочие места; `null`, если в таблице нет данных), `equipment` (список: «ПК», «Проектор», «Интерактивная панель», «Телевизор», «Маркер», «Меловая доска») и `available_days` (дни, если в diff --git a/src/backend/schedule/parsing.py b/src/backend/schedule/parsing.py index c7e3946..1198344 100644 --- a/src/backend/schedule/parsing.py +++ b/src/backend/schedule/parsing.py @@ -404,6 +404,31 @@ def room_type(text: str) -> str: return "" +def room_type_group(type_text: str) -> str: + """Общий тип для фильтра: близкие формулировки сводим к одной. + + «комп.класс», «комп.класс Linux», «компьютерный класс» -> «комп.класс»; + «английский», «кабинет английского» -> «кабинет английского». + В карточке при этом остаётся формулировка из таблицы. + """ + low = (type_text or "").lower() + if not low: + return "" + if "комп" in low and "класс" in low: + return "комп.класс" + if "англ" in low: + return "кабинет английского" + if "каф" in low: + return "аудитория кафедры" + if low.startswith("лекционно"): + return "лекционно-практическая" + if low.startswith("лекционн"): + return "лекционная" + if "практическ" in low: + return "аудитория для практических" + return type_text + + def room_equipment(text: str) -> list[str]: """Оснащение тегами; «без проектора» означает, что проектора нет.""" negated: set[str] = set() @@ -447,10 +472,12 @@ def parse_rooms_info(sheets: dict[str, Worksheet]) -> dict: " ".join(ln.strip() for ln in str(desc[col]).split("\n") if ln.strip()) ) label = f"ауд. {number}" + room_kind = room_type(text) info[label] = { "room": label, "info": text, - "type": room_type(text), + "type": room_kind, + "type_group": room_type_group(room_kind), "capacity": room_capacity(text), "equipment": room_equipment(text), "available_days": room_weekdays(text), @@ -465,6 +492,7 @@ def room_payload(label: str, info: dict | None = None) -> dict: "room": label, "info": info.get("info", ""), "type": info.get("type", ""), + "type_group": info.get("type_group", ""), "capacity": info.get("capacity"), "equipment": list(info.get("equipment") or []), "available_days": info.get("available_days"), diff --git a/src/frontend/static/app.js b/src/frontend/static/app.js index 6bac157..47a66cf 100644 --- a/src/frontend/static/app.js +++ b/src/frontend/static/app.js @@ -469,7 +469,12 @@ function roomAttrsText(room) { function roomMatchesFilter(room) { const filter = roomsState.filter; - if (filter.type && (room.type || "") !== filter.type) return false; + const group = room.type_group || ""; + if (filter.type === "__none__") { + if (group) return false; // только аудитории без указанного типа + } else if (filter.type && group !== filter.type) { + return false; + } const min = parseInt(filter.capacity, 10); if (!isNaN(min) && min > 0 && (room.capacity == null || room.capacity < min)) return false; const wanted = filter.equipment; @@ -533,7 +538,8 @@ function drawRoomsScreen(status) { function roomsToolbar() { const all = roomsAll(); - const types = [...new Set(all.map((r) => r.type).filter(Boolean))].sort((a, b) => a.localeCompare(b, "ru")); + // близкие формулировки типов объединены в общий тип (room_type_group) + const types = [...new Set(all.map((r) => r.type_group).filter(Boolean))].sort((a, b) => a.localeCompare(b, "ru")); const filter = roomsState.filter; const box = document.createElement("div"); box.className = "rooms-filters"; @@ -543,7 +549,7 @@ function roomsToolbar() { `` ).join("")} ${roomsState.mode === "free" ? ` -