feat: улучшить учебные периоды и просмотр расписаний

This commit is contained in:
Zuev
2026-08-15 00:20:00 +03:00
parent 855d191517
commit 52d516dd8f
22 changed files with 1107 additions and 250 deletions

View File

@@ -29,6 +29,33 @@ const SEMESTER_LABELS = {
const WHOLE_GROUP_SUBGROUP_VALUE = '__whole_group__';
const ACADEMIC_HOURS_PER_SLOT = 2;
const VISUAL_MENU_GAP = 8;
const VISUAL_MENU_VIEWPORT_PADDING = 8;
export function calculateVisualContextMenuPosition({
anchorRect,
menuWidth,
menuHeight,
viewportWidth,
viewportHeight
}) {
const maxLeft = Math.max(VISUAL_MENU_VIEWPORT_PADDING, viewportWidth - menuWidth - VISUAL_MENU_VIEWPORT_PADDING);
const maxTop = Math.max(VISUAL_MENU_VIEWPORT_PADDING, viewportHeight - menuHeight - VISUAL_MENU_VIEWPORT_PADDING);
let left = anchorRect.right + VISUAL_MENU_GAP;
let top = anchorRect.top;
if (left > maxLeft) {
left = anchorRect.left - menuWidth - VISUAL_MENU_GAP;
}
if (top > maxTop) {
top = anchorRect.bottom - menuHeight;
}
return {
left: Math.round(Math.max(VISUAL_MENU_VIEWPORT_PADDING, Math.min(left, maxLeft))),
top: Math.round(Math.max(VISUAL_MENU_VIEWPORT_PADDING, Math.min(top, maxTop)))
};
}
export function scheduleSlotActionLabel(slotCount) {
return slotCount === 1 ? 'Очистить' : 'Удалить';
@@ -160,6 +187,7 @@ export async function initSchedule() {
let syncingVisualPeriod = false;
let pendingConflictResolution = null;
let visualContextTarget = null;
let visualContextMenuPositionAnimation = null;
let pendingSlotMove = null;
let creatingDraft = false;
@@ -207,6 +235,7 @@ export async function initSchedule() {
visualSemesterSelect?.addEventListener('change', handleVisualSemesterChange);
visualGroupFilter?.addEventListener('change', handleVisualGroupFilterChange);
visualStage?.addEventListener('click', handleVisualStageClick);
visualDrawer?.addEventListener('scroll', closeVisualContextMenu, true);
conflictCloseButton?.addEventListener('click', closeConflictModal);
conflictCancelButton?.addEventListener('click', closeConflictModal);
conflictModal?.addEventListener('click', (event) => {
@@ -555,6 +584,7 @@ export async function initSchedule() {
function closeVisualDrawer() {
if (!visualDrawer) return;
closeVisualContextMenu();
visualDrawer.classList.remove('open');
visualDrawer.setAttribute('aria-hidden', 'true');
visualToggleButton?.classList.remove('open');
@@ -882,6 +912,23 @@ export async function initSchedule() {
};
if (!visualContextMenu) return;
visualContextMenu.hidden = false;
const anchorRect = button.getBoundingClientRect();
const position = calculateVisualContextMenuPosition({
anchorRect,
menuWidth: visualContextMenu.offsetWidth,
menuHeight: visualContextMenu.offsetHeight,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight
});
visualContextMenuPositionAnimation?.cancel();
const positionedTransform = `translate3d(${position.left}px, ${position.top}px, 0)`;
visualContextMenuPositionAnimation = visualContextMenu.animate(
[
{ transform: positionedTransform },
{ transform: positionedTransform }
],
{ duration: 1, fill: 'forwards' }
);
visualContextMenu.querySelector('button')?.focus();
}
@@ -889,6 +936,8 @@ export async function initSchedule() {
visualContextTarget = null;
if (!visualContextMenu) return;
visualContextMenu.hidden = true;
visualContextMenuPositionAnimation?.cancel();
visualContextMenuPositionAnimation = null;
}
async function handleVisualContextMenuClick(event) {