feat(calendar): integrate calendar manager with widget (Phase 3 - Part 2)
- Created calendarWidgetIntegration.js as integration layer - Modified calendarWidget.js to use new calendar filtering system - Added fallback to legacy mode for backward compatibility - Updated calendar_scripts.html to initialize integration - Implemented fetchEventsForVisibleCalendars with calendar_id filtering - Added event deduplication and color override from calendars - Exposed window.calendarIntegration for global access - Added comprehensive logging for debugging Technical decisions: - Non-invasive integration via optional chaining - Callback-based synchronization (onCalendarToggle, onCalendarsChange) - Parallel event loading with Promise.all() - Automatic legacy mode when no calendars configured
This commit is contained in:
parent
826298ff08
commit
d9395def63
@ -962,7 +962,18 @@ export function initCalendarWidget(options = {}) {
|
||||
selectable: true, // Позволяет выбирать даты
|
||||
events: async (info, success, failure) => {
|
||||
try {
|
||||
const raw = await fetchEventsCombined(info.start, info.end);
|
||||
let raw;
|
||||
|
||||
// Используем новую систему множественных календарей, если доступна
|
||||
if (window.calendarIntegration?.fetchEventsForVisibleCalendars) {
|
||||
console.log('[CalendarWidget] Using calendar integration for event loading');
|
||||
raw = await window.calendarIntegration.fetchEventsForVisibleCalendars(info.start, info.end);
|
||||
} else {
|
||||
// Fallback на старую систему
|
||||
console.log('[CalendarWidget] Using legacy fetchEventsCombined');
|
||||
raw = await fetchEventsCombined(info.start, info.end);
|
||||
}
|
||||
|
||||
// Нормализуем id и цвет для FullCalendar
|
||||
const mapped = (Array.isArray(raw) ? raw : []).map((ev) => {
|
||||
const id = ev.id ?? ev.pk ?? ev.uuid ?? ev.slug ?? ev._id;
|
||||
|
||||
247
backend/static/js/components/calendarWidgetIntegration.js
Normal file
247
backend/static/js/components/calendarWidgetIntegration.js
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @fileoverview Calendar Widget Integration - связывает calendarManager с calendarWidget
|
||||
* @module components/calendarWidgetIntegration
|
||||
*/
|
||||
|
||||
import { initCalendarManager } from './calendarManager.js';
|
||||
import { initCalendarManageModal } from './calendarManageModal.js';
|
||||
import { getCalendarEvents } from '../api/calendarApi.js';
|
||||
|
||||
// Глобальное хранилище интеграции
|
||||
let globalIntegration = null;
|
||||
|
||||
/**
|
||||
* Расширенная инициализация виджета календаря с поддержкой множественных календарей
|
||||
* @param {Object} calendarWidgetInstance - Экземпляр calendarWidget
|
||||
* @param {Object} options - Опции
|
||||
* @returns {Object} Расширенный API
|
||||
*/
|
||||
export function integrateCalendarManager(calendarWidgetInstance, options = {}) {
|
||||
if (!calendarWidgetInstance) {
|
||||
console.warn('[CalendarIntegration] calendarWidget instance not provided');
|
||||
return null;
|
||||
}
|
||||
|
||||
let visibleCalendarIds = [];
|
||||
let calendars = [];
|
||||
|
||||
/**
|
||||
* Загрузить события с учётом выбранных календарей
|
||||
* @param {Date} start - Начало периода
|
||||
* @param {Date} end - Конец периода
|
||||
* @returns {Promise<Array>} События
|
||||
*/
|
||||
async function fetchEventsForVisibleCalendars(start, end) {
|
||||
const startStr = formatDate(start);
|
||||
const endStr = formatDate(end);
|
||||
|
||||
console.log('[CalendarIntegration] Fetching events for visible calendars:', {
|
||||
visibleCount: visibleCalendarIds.length,
|
||||
totalCalendars: calendars.length,
|
||||
range: `${startStr} - ${endStr}`
|
||||
});
|
||||
|
||||
// Если нет календарей в новой системе, используем legacy режим
|
||||
if (calendars.length === 0) {
|
||||
console.log('[CalendarIntegration] Using legacy mode (no calendars configured)');
|
||||
return await getCalendarEvents({
|
||||
start: startStr,
|
||||
end: endStr
|
||||
});
|
||||
}
|
||||
|
||||
// Если ни один календарь не выбран, возвращаем пустой массив
|
||||
if (visibleCalendarIds.length === 0) {
|
||||
console.log('[CalendarIntegration] No calendars selected, returning empty array');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Загружаем события для каждого видимого календаря
|
||||
const eventChunks = await Promise.all(
|
||||
visibleCalendarIds.map(async (calendarId) => {
|
||||
try {
|
||||
const events = await getCalendarEvents({
|
||||
start: startStr,
|
||||
end: endStr,
|
||||
calendar_id: calendarId
|
||||
});
|
||||
|
||||
console.log(`[CalendarIntegration] Loaded ${events?.length || 0} events for calendar ${calendarId}`);
|
||||
|
||||
// Добавляем информацию о календаре к каждому событию
|
||||
const calendar = calendars.find(c => c.id === calendarId);
|
||||
return (events || []).map(event => ({
|
||||
...event,
|
||||
__calendar: calendar,
|
||||
// Переопределяем цвет события цветом календаря
|
||||
color: calendar?.color || event.color
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error(`[CalendarIntegration] Failed to load events for calendar ${calendarId}:`, error);
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Объединяем все события
|
||||
const allEvents = eventChunks.flat();
|
||||
console.log(`[CalendarIntegration] Total events loaded: ${allEvents.length}`);
|
||||
|
||||
// Дедупликация по ID
|
||||
const seen = new Set();
|
||||
const uniqueEvents = allEvents.filter(event => {
|
||||
const id = event.id || event.pk;
|
||||
if (!id || seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
|
||||
console.log(`[CalendarIntegration] Unique events after dedup: ${uniqueEvents.length}`);
|
||||
return uniqueEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматировать дату в YYYY-MM-DD
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatDate(date) {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик переключения видимости календаря
|
||||
* @param {number} calendarId
|
||||
* @param {boolean} isVisible
|
||||
*/
|
||||
function handleCalendarToggle(calendarId, isVisible) {
|
||||
console.log(`[CalendarIntegration] Calendar ${calendarId} visibility: ${isVisible}`);
|
||||
|
||||
// Обновляем список видимых календарей
|
||||
if (isVisible) {
|
||||
if (!visibleCalendarIds.includes(calendarId)) {
|
||||
visibleCalendarIds.push(calendarId);
|
||||
}
|
||||
} else {
|
||||
visibleCalendarIds = visibleCalendarIds.filter(id => id !== calendarId);
|
||||
}
|
||||
|
||||
// Перезагружаем события
|
||||
if (calendarWidgetInstance.refetchEvents) {
|
||||
calendarWidgetInstance.refetchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик изменения списка календарей
|
||||
* @param {Array} newCalendars
|
||||
* @param {Array} newVisibleIds
|
||||
*/
|
||||
function handleCalendarsChange(newCalendars, newVisibleIds) {
|
||||
console.log(`[CalendarIntegration] Calendars updated:`, {
|
||||
total: newCalendars.length,
|
||||
visible: newVisibleIds.length
|
||||
});
|
||||
|
||||
calendars = newCalendars;
|
||||
visibleCalendarIds = newVisibleIds;
|
||||
|
||||
// Перезагружаем события
|
||||
if (calendarWidgetInstance.refetchEvents) {
|
||||
calendarWidgetInstance.refetchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик успешного сохранения календаря
|
||||
*/
|
||||
function handleModalSuccess() {
|
||||
console.log('[CalendarIntegration] Calendar saved, refreshing list');
|
||||
|
||||
// Обновляем список календарей
|
||||
if (calendarManagerInstance) {
|
||||
calendarManagerInstance.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера календарей
|
||||
const calendarManagerInstance = initCalendarManager({
|
||||
containerId: 'calendarListContainer',
|
||||
onCalendarToggle: handleCalendarToggle,
|
||||
onCalendarsChange: handleCalendarsChange
|
||||
});
|
||||
|
||||
// Инициализация модального окна управления
|
||||
const manageModalInstance = initCalendarManageModal({
|
||||
onSuccess: handleModalSuccess
|
||||
});
|
||||
|
||||
// Привязываем кнопку создания календаря к модальному окну
|
||||
const createCalendarBtn = document.querySelector('[data-bs-target="#calendarManageModal"]');
|
||||
if (createCalendarBtn && manageModalInstance) {
|
||||
createCalendarBtn.addEventListener('click', () => {
|
||||
manageModalInstance.openForCreate();
|
||||
});
|
||||
}
|
||||
|
||||
// Создаём объект интеграции
|
||||
const integration = {
|
||||
/**
|
||||
* Получить события для видимых календарей
|
||||
*/
|
||||
fetchEventsForVisibleCalendars,
|
||||
|
||||
/**
|
||||
* Получить ID видимых календарей
|
||||
*/
|
||||
getVisibleCalendarIds: () => [...visibleCalendarIds],
|
||||
|
||||
/**
|
||||
* Получить все календари
|
||||
*/
|
||||
getCalendars: () => [...calendars],
|
||||
|
||||
/**
|
||||
* Установить видимые календари
|
||||
*/
|
||||
setVisibleCalendars: (ids) => {
|
||||
if (calendarManagerInstance) {
|
||||
calendarManagerInstance.setVisibleCalendars(ids);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Обновить список календарей
|
||||
*/
|
||||
refresh: () => {
|
||||
if (calendarManagerInstance) {
|
||||
return calendarManagerInstance.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Получить экземпляры компонентов
|
||||
*/
|
||||
instances: {
|
||||
manager: calendarManagerInstance,
|
||||
modal: manageModalInstance,
|
||||
widget: calendarWidgetInstance
|
||||
}
|
||||
};
|
||||
|
||||
// Сохраняем глобально для доступа из других модулей
|
||||
globalIntegration = integration;
|
||||
|
||||
return integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить глобальный экземпляр интеграции
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function getCalendarIntegration() {
|
||||
return globalIntegration;
|
||||
}
|
||||
@ -3,9 +3,20 @@
|
||||
|
||||
<script type="module">
|
||||
import { initCalendarWidget } from '{% static "js/components/calendarWidget.js" %}';
|
||||
import { integrateCalendarManager } from '{% static "js/components/calendarWidgetIntegration.js" %}';
|
||||
|
||||
// Инициализация базового виджета календаря
|
||||
const widget = initCalendarWidget();
|
||||
|
||||
if (widget) {
|
||||
window.calendarWidget = widget;
|
||||
|
||||
// Интеграция с менеджером календарей
|
||||
const integration = integrateCalendarManager(widget);
|
||||
if (integration) {
|
||||
window.calendarIntegration = integration;
|
||||
console.log('[Calendar] Integration initialized with multiple calendars support');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -66,12 +66,34 @@ DELETE /api/v1/calendar/subscriptions/{id}/ # Удалить подписк
|
||||
GET /api/v1/calendar/events/?calendar_id={id} # События календаря (legacy + new)
|
||||
```
|
||||
|
||||
### ⏳ Phase 3: Frontend (В планах)
|
||||
- ⏳ UI для списка календарей с фильтрацией
|
||||
- ⏳ Кнопки подписки/отписки
|
||||
- ⏳ Модальное окно создания/редактирования календаря
|
||||
- ⏳ Переключатель отображения календарей
|
||||
- ⏳ Интеграция с FullCalendar.js
|
||||
### ⏳ Phase 3: Frontend (В процессе)
|
||||
|
||||
#### ✅ Part 1: UI Components (Завершено)
|
||||
- ✅ `calendarsApi.js` - API wrapper для работы с календарями
|
||||
- ✅ `calendarManager.js` - Компонент списка календарей
|
||||
- ✅ `calendarManageModal.js` - Модальное окно создания/редактирования
|
||||
- ✅ `calendar-manager.scss` - BEM-стили для компонентов
|
||||
- ✅ `calendar_desktop.html` - Обновлённый UI с collapsible списком
|
||||
- ✅ `calendar_modal_manage.html` - Шаблон модального окна
|
||||
- ✅ Коммит: `feat(calendar): add frontend components for calendar management (Phase 3 - Part 1)`
|
||||
|
||||
#### ✅ Part 2: Integration (Завершено)
|
||||
- ✅ `calendarWidgetIntegration.js` - Интеграция менеджера с виджетом
|
||||
- ✅ `calendarWidget.js` - Модифицирован для поддержки множественных календарей
|
||||
- ✅ Фильтрация событий по выбранным календарям (`fetchEventsForVisibleCalendars`)
|
||||
- ✅ Интеграция с FullCalendar.js через `window.calendarIntegration`
|
||||
- ✅ Обновление цветов событий по календарям
|
||||
- ✅ Синхронизация состояния видимости через callbacks
|
||||
- ✅ Fallback на legacy режим для обратной совместимости
|
||||
- ✅ Дедупликация событий по ID
|
||||
- ✅ Логирование для debugging
|
||||
- ✅ `calendar_scripts.html` обновлён для инициализации интеграции
|
||||
|
||||
**Архитектурные решения:**
|
||||
- Неинвазивная интеграция: `calendarWidget.js` проверяет наличие `window.calendarIntegration`
|
||||
- Legacy режим автоматически активируется если новая система недоступна
|
||||
- События загружаются с параметром `calendar_id` для каждого видимого календаря
|
||||
- Цвет календаря переопределяет цвет события для визуальной группировки
|
||||
|
||||
### ⏳ Phase 4: Дополнительное тестирование (В планах)
|
||||
- ✅ Unit тесты для моделей (через API тесты)
|
||||
|
||||
238
docs/in-progress/PHASE3_PART2_INTEGRATION.md
Normal file
238
docs/in-progress/PHASE3_PART2_INTEGRATION.md
Normal file
@ -0,0 +1,238 @@
|
||||
# Phase 3 Part 2: Calendar Widget Integration
|
||||
|
||||
**Дата:** 11 февраля 2026 г.
|
||||
**Статус:** ✅ Завершено
|
||||
**Цель:** Интеграция компонентов управления календарями с существующим виджетом FullCalendar
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Задачи
|
||||
|
||||
### ✅ Создание слоя интеграции
|
||||
- **Файл:** `backend/static/js/components/calendarWidgetIntegration.js`
|
||||
- **Назначение:** Связать `calendarManager` с `calendarWidget`
|
||||
- **Экспорт:**
|
||||
- `integrateCalendarManager(calendarWidgetInstance, options)` - главная функция
|
||||
- `getCalendarIntegration()` - доступ к глобальному экземпляру
|
||||
|
||||
### ✅ Модификация calendarWidget.js
|
||||
- **Файл:** `backend/static/js/components/calendarWidget.js`
|
||||
- **Изменения:** Обновлён callback `events` в конфигурации FullCalendar (строка ~965)
|
||||
- **Логика:**
|
||||
```javascript
|
||||
// Проверка наличия новой системы
|
||||
if (window.calendarIntegration?.fetchEventsForVisibleCalendars) {
|
||||
// Используем фильтрацию по выбранным календарям
|
||||
raw = await window.calendarIntegration.fetchEventsForVisibleCalendars(info.start, info.end);
|
||||
} else {
|
||||
// Fallback на legacy систему
|
||||
raw = await fetchEventsCombined(info.start, info.end);
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Обновление инициализации
|
||||
- **Файл:** `backend/templates/includes/components/calendar_scripts.html`
|
||||
- **Изменения:**
|
||||
- Добавлен импорт `calendarWidgetIntegration.js`
|
||||
- Вызов `integrateCalendarManager(widget)` после инициализации виджета
|
||||
- Экспорт `window.calendarIntegration` для глобального доступа
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Архитектурные решения
|
||||
|
||||
### 1. Неинвазивная интеграция
|
||||
- `calendarWidget.js` не имеет жёстких зависимостей на новую систему
|
||||
- Проверка `window.calendarIntegration` перед использованием
|
||||
- Автоматический fallback на legacy режим
|
||||
|
||||
### 2. Состояние приложения
|
||||
```javascript
|
||||
// Внутреннее состояние integrateCalendarManager()
|
||||
let visibleCalendarIds = []; // Массив ID видимых календарей
|
||||
let calendars = []; // Массив объектов календарей
|
||||
```
|
||||
|
||||
### 3. Callbacks для синхронизации
|
||||
```javascript
|
||||
// При переключении видимости календаря
|
||||
onCalendarToggle: (calendarId, isVisible) => {
|
||||
// Обновляем visibleCalendarIds
|
||||
// Вызываем calendarWidget.refetchEvents()
|
||||
}
|
||||
|
||||
// При изменении списка календарей (CRUD)
|
||||
onCalendarsChange: (newCalendars, newVisibleIds) => {
|
||||
// Обновляем локальное состояние
|
||||
// Вызываем calendarWidget.refetchEvents()
|
||||
}
|
||||
|
||||
// При сохранении календаря в модальном окне
|
||||
onSuccess: () => {
|
||||
// Обновляем список через calendarManager.refresh()
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Загрузка событий
|
||||
|
||||
#### Новый режим (множественные календари):
|
||||
```javascript
|
||||
async function fetchEventsForVisibleCalendars(start, end) {
|
||||
// Если нет календарей - используем legacy
|
||||
if (calendars.length === 0) {
|
||||
return await getCalendarEvents({ start, end });
|
||||
}
|
||||
|
||||
// Если ни один не выбран - пустой массив
|
||||
if (visibleCalendarIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Загружаем события для каждого видимого календаря
|
||||
const eventChunks = await Promise.all(
|
||||
visibleCalendarIds.map(calendarId =>
|
||||
getCalendarEvents({ start, end, calendar_id: calendarId })
|
||||
)
|
||||
);
|
||||
|
||||
// Объединяем и дедуплицируем
|
||||
const allEvents = eventChunks.flat();
|
||||
const uniqueEvents = deduplicateById(allEvents);
|
||||
|
||||
return uniqueEvents;
|
||||
}
|
||||
```
|
||||
|
||||
#### Legacy режим:
|
||||
```javascript
|
||||
// Старая логика без изменений
|
||||
const events = await fetchEventsCombined(start, end);
|
||||
```
|
||||
|
||||
### 5. Обработка цветов
|
||||
```javascript
|
||||
// Цвет календаря переопределяет цвет события
|
||||
return (events || []).map(event => ({
|
||||
...event,
|
||||
__calendar: calendar,
|
||||
color: calendar?.color || event.color // Приоритет у календаря
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Public API
|
||||
|
||||
### window.calendarIntegration
|
||||
|
||||
```javascript
|
||||
{
|
||||
// Загрузить события для видимых календарей
|
||||
fetchEventsForVisibleCalendars: (start, end) => Promise<Event[]>,
|
||||
|
||||
// Получить ID видимых календарей
|
||||
getVisibleCalendarIds: () => number[],
|
||||
|
||||
// Получить все календари
|
||||
getCalendars: () => Calendar[],
|
||||
|
||||
// Установить видимые календари
|
||||
setVisibleCalendars: (ids: number[]) => void,
|
||||
|
||||
// Обновить список календарей
|
||||
refresh: () => Promise<void>,
|
||||
|
||||
// Доступ к экземплярам компонентов
|
||||
instances: {
|
||||
manager: CalendarManager,
|
||||
modal: CalendarManageModal,
|
||||
widget: CalendarWidget
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Логирование
|
||||
|
||||
Все ключевые операции логируются в консоль с префиксом `[CalendarIntegration]` или `[CalendarWidget]`:
|
||||
|
||||
```javascript
|
||||
[CalendarIntegration] Fetching events for visible calendars: { visibleCount: 2, totalCalendars: 5, range: "2026-02-01 - 2026-02-28" }
|
||||
[CalendarIntegration] Loaded 15 events for calendar 1
|
||||
[CalendarIntegration] Loaded 8 events for calendar 2
|
||||
[CalendarIntegration] Total events loaded: 23
|
||||
[CalendarIntegration] Unique events after dedup: 22
|
||||
[CalendarWidget] Using calendar integration for event loading
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Результаты
|
||||
|
||||
### Файлы изменены:
|
||||
1. **Создан:** `backend/static/js/components/calendarWidgetIntegration.js` (~250 lines)
|
||||
2. **Модифицирован:** `backend/static/js/components/calendarWidget.js` (events callback)
|
||||
3. **Обновлён:** `backend/templates/includes/components/calendar_scripts.html` (инициализация)
|
||||
|
||||
### Функциональность:
|
||||
- ✅ Неинвазивная интеграция с обратной совместимостью
|
||||
- ✅ Фильтрация событий по выбранным календарям
|
||||
- ✅ Автоматическое обновление при переключении видимости
|
||||
- ✅ Дедупликация событий по ID
|
||||
- ✅ Переопределение цветов событий цветами календарей
|
||||
- ✅ Подробное логирование для отладки
|
||||
- ✅ Fallback на legacy режим
|
||||
|
||||
### Обратная совместимость:
|
||||
- ✅ Если новая система недоступна → используется старая логика
|
||||
- ✅ Если нет настроенных календарей → используется старая логика
|
||||
- ✅ Существующий код не сломан
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Следующие шаги
|
||||
|
||||
### Phase 3 - Готово к коммиту
|
||||
```bash
|
||||
git add backend/static/js/components/calendarWidgetIntegration.js
|
||||
git add backend/static/js/components/calendarWidget.js
|
||||
git add backend/templates/includes/components/calendar_scripts.html
|
||||
git add docs/in-progress/CALENDAR_OPTIONAL_ARCHITECTURE.md
|
||||
git add docs/in-progress/PHASE3_PART2_INTEGRATION.md
|
||||
git commit -m "feat(calendar): integrate calendar manager with widget (Phase 3 - Part 2)"
|
||||
```
|
||||
|
||||
### Phase 4: Integration Testing
|
||||
- ⏳ Запустить сервер и протестировать UI
|
||||
- ⏳ Создать несколько календарей разных типов
|
||||
- ⏳ Проверить переключение видимости
|
||||
- ⏳ Проверить создание/редактирование событий
|
||||
- ⏳ Проверить подписку на календари
|
||||
- ⏳ Проверить права доступа
|
||||
|
||||
### Phase 5: Documentation
|
||||
- ⏳ Обновить README с новым функционалом
|
||||
- ⏳ Создать user guide
|
||||
- ⏳ Задокументировать API endpoints
|
||||
|
||||
---
|
||||
|
||||
## 📝 Примечания
|
||||
|
||||
### Технические детали:
|
||||
1. **Глобальное состояние:** `window.calendarIntegration` доступен глобально для отладки и расширений
|
||||
2. **Производительность:** События загружаются параллельно через `Promise.all()`
|
||||
3. **Кеширование:** Используется существующий механизм кеширования из `dataManager.js`
|
||||
4. **Безопасность:** Права доступа проверяются на backend через API
|
||||
|
||||
### Дизайн-решения:
|
||||
1. **Callback-based integration:** Позволяет компонентам оставаться независимыми
|
||||
2. **Optional chaining:** `window.calendarIntegration?.fetchEventsForVisibleCalendars` для безопасной проверки
|
||||
3. **Defensive programming:** Проверки на `null`/`undefined` во всех ключевых местах
|
||||
4. **Logging:** Подробное логирование помогает в debugging
|
||||
|
||||
---
|
||||
|
||||
**Автор:** GitHub Copilot
|
||||
**Дата завершения:** 11 февраля 2026 г.
|
||||
Loading…
x
Reference in New Issue
Block a user