Перейти к содержанию

P2-017 Frontend: Создание и редактирование события

Метаданные

Поле Значение
Фаза Phase 2: Core
Статус done
Приоритет critical
Связь с roadmap Roadmap - Frontend

Контекст

Бизнес-контекст

Организатор создаёт событие через веб-интерфейс: заполняет информацию, настраивает типы билетов, публикует. Форма должна быть интуитивной и поддерживать все возможности Event Service.

Технический контекст

  • Многошаговая форма или табы
  • React Hook Form + Zod
  • TanStack Query для мутаций
  • Markdown editor для описания

Существующий код: - events/page.tsx — список событий - events/new/page.tsx — создание

Связанные документы: - Event Service API - User Journeys - Journey 1

Цель

Реализовать полнофункциональные страницы создания, редактирования и управления событиями.

Definition of Ready (DoR)

  • [x] Контекст понятен и описан
  • [x] Цель сформулирована
  • [x] Acceptance Criteria определены
  • [x] Технические детали проработаны
  • [x] Зависимости определены
  • [x] Нет блокеров

Acceptance Criteria

Список событий (/dashboard/events)

  • [x] Таблица с событиями организации
  • [x] Колонки: название, дата, статус, регистрации
  • [x] Фильтры: статус, дата
  • [x] Поиск по названию
  • [x] Кнопка «Создать событие»
  • [x] Actions: редактировать, опубликовать, отменить, удалить
  • [x] Pagination

Создание события (/dashboard/events/new)

  • [x] Форма с основной информацией:
  • Название (обязательно)
  • Описание (Markdown)
  • Дата и время начала (обязательно)
  • Дата и время окончания (опционально)
  • Timezone
  • Тип локации (онлайн/офлайн/гибрид)
  • Адрес или URL
  • Обложка (загрузка изображения)
  • Видимость участников (CLOSED/OPEN)
  • Привязка к группе (опционально)
  • [x] Типы билетов (название, количество, период продаж)
  • [x] Предпросмотр
  • [x] Кнопки: Сохранить черновик, Опубликовать
  • [x] Настройка повторения события (recurring events)

Редактирование события (/dashboard/events/[id]/edit)

  • [x] Загрузка существующих данных
  • [x] Те же поля что и при создании
  • [x] Ограничения для опубликованных событий
  • [x] История изменений

Управление событием (/dashboard/events/[id])

  • [x] Обзор события (статистика)
  • [x] Список регистраций
  • [x] Actions: редактировать, опубликовать/снять, отменить
  • [x] При отмене — подтверждение и причина (cancelReason)

Типы билетов (inline)

  • [x] Добавление типа билета на форме события
  • [x] Редактирование существующих
  • [x] Удаление (если нет регистраций)
  • [x] Деактивация (если есть регистрации)
  • [x] Drag-and-drop для сортировки

Markdown Editor

  • [x] Простой редактор для описания
  • [x] Предпросмотр
  • [x] Базовое форматирование (bold, italic, списки, ссылки)

Definition of Done (DoD)

  • [x] Все Acceptance Criteria выполнены
  • [x] Используются только shadcn/ui компоненты
  • [x] React Hook Form + Zod
  • [x] TanStack Query mutations
  • [x] Optimistic updates
  • [x] Loading и error states
  • [x] Code review пройден
  • [x] CI/CD pipeline проходит

Технические детали

Структура файлов

frontend/app/(dashboard)/dashboard/events/
├── page.tsx                  — список событий
├── new/page.tsx              — создание
├── [id]/
│   ├── page.tsx              — детали события
│   └── edit/page.tsx         — редактирование

frontend/components/features/events/
├── event-list.tsx
├── event-form.tsx
├── event-card.tsx
├── event-activity-log.tsx       — история изменений
├── recurrence-config.tsx        — настройка повторения
├── ticket-type-form.tsx
├── ticket-type-list.tsx
└── event-status-badge.tsx

Event Form Schema

const eventFormSchema = z.object({
  title: z.string().min(1, 'Название обязательно').max(255),
  description: z.string().optional(),
  startsAt: z.string().datetime(),
  endsAt: z.string().datetime().optional(),
  timezone: z.string().default('Europe/Moscow'),
  locationType: z.enum(['ONLINE', 'OFFLINE', 'HYBRID']),
  locationAddress: z.string().optional(),
  locationUrl: z.string().url().optional(),
  groupId: z.string().uuid().optional(),
  participantsVisibility: z.enum(['CLOSED', 'OPEN']).default('CLOSED'),
  ticketTypes: z.array(z.object({
    name: z.string().min(1).max(100),
    description: z.string().optional(),
    quantity: z.number().min(1).optional(),
    salesStart: z.string().datetime().optional(),
    salesEnd: z.string().datetime().optional(),
  })).min(1, 'Добавьте хотя бы один тип билета'),
});

useCreateEvent Hook

export function useCreateEvent() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (data: CreateEventRequest) => {
      // 1. Создать событие
      const event = await eventApi.create(data);
      // 2. Создать типы билетов
      await Promise.all(
        data.ticketTypes.map(tt =>
          eventApi.createTicketType(event.id, tt)
        )
      );
      return event;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['events'] });
      toast.success('Событие создано');
    },
  });
}

Зависимости

Блокирует

  • P2-018 Публичная страница события

Зависит от

Out of Scope

  • Drag-and-drop builder для формы регистрации
  • Templates
  • Bulk operations

Реализовано дополнительно

Ранее находилось в Out of Scope, но было реализовано:

  • cancelReason — причина отмены события (backend API + frontend UI)
  • История изменений — audit log событий (создание, обновление, статус)
  • Recurring Events — повторяющиеся события (UI конфигурации, backend миграции)

Заметки

  • Рассмотреть использование react-markdown для превью описания
  • Date picker должен учитывать timezone
  • При публикации — валидация что есть хотя бы один тип билета
  • Изображение обложки загружается через Media Service