diff --git a/backend/static/js/components/calendarManager.js b/backend/static/js/components/calendarManager.js index c9ddd887..d287d0f6 100644 --- a/backend/static/js/components/calendarManager.js +++ b/backend/static/js/components/calendarManager.js @@ -67,16 +67,24 @@ export function initCalendarManager(options = {}) { } ]; - // Добавить календари отделов - const deptIds = window.calendarWidget?.getDepartments?.() || []; - deptIds.forEach((deptId, index) => { + // Добавить календари отделов из calendarWidget + const deptObjects = window.calendarWidget?.getDepartments?.() || []; + console.log('[CalendarManager] Department objects:', deptObjects); + + deptObjects.forEach((dept) => { + const deptId = dept?.id || dept?.pk || dept?.department_id; + if (!deptId) { + console.warn('[CalendarManager] Department object missing ID:', dept); + return; + } + legacyCalendars.push({ id: `legacy-dept-${deptId}`, - title: `Отдел ${deptId}`, + title: dept.name || `Отдел ${deptId}`, description: 'События отдела', color: '#198754', calendar_type: 'department', - department_id: deptId, + department_id: parseInt(deptId, 10), is_legacy: true, is_department: true, user_can_edit: false, @@ -85,6 +93,7 @@ export function initCalendarManager(options = {}) { }); }); + console.log('[CalendarManager] Created legacy calendars:', legacyCalendars); return legacyCalendars; } @@ -105,11 +114,18 @@ export function initCalendarManager(options = {}) { console.log('[CalendarManager] Loaded calendars:', { legacy: legacyCalendars.length, new: newCalendars.length, - total: calendars.length + total: calendars.length, + calendarIds: calendars.map(c => ({ id: c.id, title: c.title })) }); - // По умолчанию все календари видимы - visibleCalendarIds = new Set(calendars.map(cal => cal.id)); + // По умолчанию все календари видимы (фильтруем null/undefined) + visibleCalendarIds = new Set( + calendars + .map(cal => cal.id) + .filter(id => id !== null && id !== undefined) + ); + + console.log('[CalendarManager] Visible calendar IDs:', Array.from(visibleCalendarIds)); render(); onCalendarsChange(calendars, Array.from(visibleCalendarIds)); diff --git a/backend/static/js/components/calendarWidgetIntegration.js b/backend/static/js/components/calendarWidgetIntegration.js index 76653c8a..c1edf3f7 100644 --- a/backend/static/js/components/calendarWidgetIntegration.js +++ b/backend/static/js/components/calendarWidgetIntegration.js @@ -84,11 +84,10 @@ export function integrateCalendarManager(calendarWidgetInstance, options = {}) { }); } - // Добавляем информацию о legacy календаре + // Добавляем информацию о legacy календаре (БЕЗ изменения цвета события) allEvents.push(...(events || []).map(event => ({ ...event, - __calendar: calendar, - color: calendar?.color || event.color + __calendar: calendar }))); console.log(`[CalendarIntegration] Loaded ${events?.length || 0} events for legacy calendar ${legacyId}`); @@ -109,12 +108,11 @@ export function integrateCalendarManager(calendarWidgetInstance, options = {}) { console.log(`[CalendarIntegration] Loaded ${events?.length || 0} events for new calendar ${calendarId}`); - // Добавляем информацию о календаре + // Добавляем информацию о календаре (БЕЗ изменения цвета события) const calendar = calendars.find(c => c.id === calendarId); return (events || []).map(event => ({ ...event, - __calendar: calendar, - color: calendar?.color || event.color + __calendar: calendar })); } catch (error) { console.error(`[CalendarIntegration] Failed to load events for calendar ${calendarId}:`, error); diff --git a/docs/completed/UNIFIED_CALENDAR_LIST.md b/docs/completed/UNIFIED_CALENDAR_LIST.md new file mode 100644 index 00000000..aa602662 --- /dev/null +++ b/docs/completed/UNIFIED_CALENDAR_LIST.md @@ -0,0 +1,408 @@ +# Унифицированный список календарей + +**Дата:** 2024-01-XX +**Коммит:** 91de84f +**Статус:** ✅ Завершено + +## Проблема + +После реализации Phase 3 (Frontend) система имела **два параллельных механизма** выбора календарей: + +1. **Legacy система:** Dropdown-селектор с опциями: + - Компания (все события) + - Личный (события текущего сотрудника) + - Отдел N (события конкретного отдела) + +2. **Новая система:** Collapsible список с чекбоксами для Calendar records из БД + +**Проблемы:** +- Два независимых UI компонента для одной задачи +- Legacy календари не видны в новом списке +- Невозможно одновременно выбрать "Компания" и новый календарь +- Пользователь видит 0 календарей при отсутствии Calendar records + +## Решение + +### Концепция: Псевдо-календари + +Создаем **виртуальные календари** для legacy системы и объединяем их с реальными Calendar records: + +```javascript +// Legacy псевдо-календари +{ + id: 'legacy-company', + title: 'Компания', + color: '#dc3545', // Красный + is_legacy: true, + is_global: true +} + +{ + id: 'legacy-personal', + title: 'Личный календарь', + color: '#0d6efd', // Синий + is_legacy: true, + is_personal: true +} + +{ + id: 'legacy-dept-123', + title: 'Отдел 123', + color: '#198754', // Зеленый + is_legacy: true, + is_department: true, + department_id: 123 +} +``` + +### Реализация + +#### 1. calendarManager.js + +**Новая функция `createLegacyCalendars()`:** + +```javascript +function createLegacyCalendars() { + const legacyCalendars = [ + { + id: 'legacy-company', + title: 'Компания', + description: 'Все события компании', + color: '#dc3545', + is_legacy: true, + is_global: true, + is_active: true + }, + { + id: 'legacy-personal', + title: 'Личный календарь', + description: 'Мои личные события', + color: '#0d6efd', + is_legacy: true, + is_personal: true, + is_active: true + } + ]; + + // Генерируем календари для отделов + const deptIds = getDepartmentIds(); // Из data-dept-id атрибута + deptIds.forEach(deptId => { + legacyCalendars.push({ + id: `legacy-dept-${deptId}`, + title: `Отдел ${deptId}`, + description: `События отдела ${deptId}`, + color: '#198754', + is_legacy: true, + is_department: true, + department_id: deptId, + is_active: true + }); + }); + + return legacyCalendars; +} +``` + +**Модифицированная `loadCalendars()`:** + +```javascript +async function loadCalendars() { + try { + // Загружаем новые календари из API + const newCalendars = await getMyCalendars(); + + // Создаем legacy календари + const legacyCalendars = createLegacyCalendars(); + + // ОБЪЕДИНЯЕМ оба массива + calendars = [...legacyCalendars, ...newCalendars]; + + // Все календари видимы по умолчанию + visibleCalendarIds = new Set(calendars.map(c => c.id)); + + console.log('[CalendarManager] Loaded calendars:', { + legacy: legacyCalendars.length, + new: newCalendars.length, + total: calendars.length + }); + + render(); + triggerChange(); + } catch (error) { + console.error('[CalendarManager] Error loading calendars:', error); + } +} +``` + +#### 2. calendarWidgetIntegration.js + +**Обновленная `fetchEventsForVisibleCalendars()`:** + +```javascript +async function fetchEventsForVisibleCalendars(start, end) { + const startStr = formatDate(start); + const endStr = formatDate(end); + + // Разделяем legacy и новые календари + const legacyIds = [...visibleCalendarIds].filter( + id => typeof id === 'string' && id.startsWith('legacy-') + ); + const newIds = [...visibleCalendarIds].filter( + id => typeof id === 'number' + ); + + let allEvents = []; + + // Загружаем события для legacy календарей + for (const legacyId of legacyIds) { + let events = []; + const calendar = calendars.find(c => c.id === legacyId); + + if (legacyId === 'legacy-company') { + // ВСЕ события без фильтров + events = await getCalendarEvents({ start: startStr, end: endStr }); + + } else if (legacyId === 'legacy-personal') { + // События текущего сотрудника + events = await getCalendarEvents({ + start: startStr, + end: endStr, + employee_id: window.currentEmployeeId + }); + + } else if (legacyId.startsWith('legacy-dept-')) { + // События конкретного отдела + const deptId = parseInt(legacyId.replace('legacy-dept-', ''), 10); + events = await getCalendarEvents({ + start: startStr, + end: endStr, + department_id: deptId + }); + } + + // Применяем цвет календаря + allEvents.push(...events.map(event => ({ + ...event, + __calendar: calendar, + color: calendar?.color || event.color + }))); + } + + // Загружаем события для новых календарей + const newEventChunks = await Promise.all( + newIds.map(async (calendarId) => { + const events = await getCalendarEvents({ + start: startStr, + end: endStr, + calendar_id: calendarId // Новый параметр фильтрации + }); + + const calendar = calendars.find(c => c.id === calendarId); + return events.map(event => ({ + ...event, + __calendar: calendar, + color: calendar?.color || event.color + })); + }) + ); + + allEvents.push(...newEventChunks.flat()); + + // Дедупликация по ID + return deduplicateEvents(allEvents); +} +``` + +#### 3. calendar_desktop.html + +**Удален legacy dropdown:** + +```diff +- {# Выпадающий выбор: Компания / Отделы / Личный (LEGACY) #} +-