From 898ed6fec1cade90f88155d536b74a0897e71b85 Mon Sep 17 00:00:00 2001 From: igor_nadein Date: Wed, 25 Feb 2026 00:47:45 +0300 Subject: [PATCH] =?UTF-8?q?feat(calendar):=20=D0=B4=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=83=D0=BF=20=D0=BA=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8?= =?UTF-8?q?=D1=8F=D0=BC=20=D0=B4=D0=BB=D1=8F=20=D1=83=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2=20+=20UX=20=D1=83=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - Расширен get_queryset в ScheduleEventViewSet: теперь пользователь видит события где он участник (EventRelation), даже если не имеет доступа к календарю - Добавлен action my-events для получения только событий участника - Исправлена архитектурная проблема: участники событий теперь могут видеть свои события Frontend: - Добавлен метод apiClient.getMyEvents() для получения событий пользователя - Добавлена подсказка в EventModal о логике начала/конца для повторяющихся событий - Улучшен UX: пользователи понимают что начало/конец применяются ко всем повторениям --- backend/api/v1/schedule/views.py | 78 ++- frontend/src/components/EventModal.tsx | 6 + frontend/src/lib/api.ts | 756 +++++++++++++++++++++++++ 3 files changed, 839 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/api.ts diff --git a/backend/api/v1/schedule/views.py b/backend/api/v1/schedule/views.py index 001352d7..c6b7f6db 100644 --- a/backend/api/v1/schedule/views.py +++ b/backend/api/v1/schedule/views.py @@ -321,8 +321,39 @@ class ScheduleEventViewSet(viewsets.ModelViewSet): return EventSerializer def get_queryset(self): - """Фильтрация событий.""" + """Фильтрация событий. + + Пользователь видит события из: + 1. Календарей, где он участник (CalendarRelation) + 2. Событий, где он участник (EventRelation) + """ + from django.contrib.auth import get_user_model + User = get_user_model() + qs = super().get_queryset() + user = self.request.user + + # Админы видят все события + if not (user.is_staff or user.is_superuser): + ct = ContentType.objects.get_for_model(User) + + # ID календарей где пользователь участник + accessible_calendar_ids = CalendarRelation.objects.filter( + content_type=ct, + object_id=user.id + ).values_list('calendar_id', flat=True) + + # ID событий где пользователь участник + participant_event_ids = EventRelation.objects.filter( + content_type=ct, + object_id=user.id + ).values_list('event_id', flat=True) + + # Объединяем: события из доступных календарей ИЛИ где пользователь участник + qs = qs.filter( + Q(calendar_id__in=accessible_calendar_ids) | + Q(id__in=participant_event_ids) + ) # Фильтр по календарю calendar_id = self.request.query_params.get('calendar') @@ -409,6 +440,51 @@ class ScheduleEventViewSet(viewsets.ModelViewSet): }) return Response(occurrences) + + @action(detail=False, methods=['get'], url_path='my-events') + def my_events(self, request): + """Получить события текущего пользователя (где он участник). + + Query params: + - start: начало диапазона (ISO 8601, необязательно) + - end: конец диапазона (ISO 8601, необязательно) + + Возвращает только события где пользователь является участником (EventRelation). + """ + from django.contrib.auth import get_user_model + User = get_user_model() + + user = request.user + ct = ContentType.objects.get_for_model(User) + + # ID событий где пользователь участник + participant_event_ids = EventRelation.objects.filter( + content_type=ct, + object_id=user.id + ).values_list('event_id', flat=True) + + # Фильтруем события + qs = self.queryset.filter(id__in=participant_event_ids) + + # Фильтр по диапазону дат (если указан) + start_str = request.query_params.get('start') + end_str = request.query_params.get('end') + + if start_str and end_str: + start_dt = parse_datetime(start_str) + end_dt = parse_datetime(end_str) + + if start_dt and end_dt: + qs = qs.filter( + start__lte=end_dt, + end__gte=start_dt + ) + + qs = qs.order_by('start') + + # Используем EventListSerializer для детальной информации + serializer = EventListSerializer(qs, many=True) + return Response(serializer.data) class ScheduleRuleViewSet(viewsets.ModelViewSet): diff --git a/frontend/src/components/EventModal.tsx b/frontend/src/components/EventModal.tsx index 9b08c74e..60b1fe54 100644 --- a/frontend/src/components/EventModal.tsx +++ b/frontend/src/components/EventModal.tsx @@ -334,6 +334,12 @@ export function EventModal({ + {editingEvent.isRecurring && ( +

+ 💡 Начало и конец определяют время первого события и его продолжительность. Эти параметры будут применены ко всем повторениям. +

+ )} +