feat(calendar): информационное окно вхождения — статус, кнопка «Редактировать» для владельца
ConferenceOccurrenceDialog расширен под общий информационный диалог (клик по чипу — и для чужих, и для своих конференций): статус-бейдж (общая логика occurrenceBadge вынесена в lib/occurrenceStatus.ts, переиспользуется с ConferenceCalendar), кнопка «Редактировать», открывающая модалку формы — только когда передан ownedConference (текущий пользователь — организатор). Плюс Escape и возврат фокуса (useModalDismiss).
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { Calendar, Lock, Play, Repeat, X } from 'lucide-react'
|
||||
import type { OccurrenceOut } from '@/api/conferences'
|
||||
import { Calendar, Hash, Lock, Pencil, Play, Repeat, X } from 'lucide-react'
|
||||
import type { ConferenceOut, OccurrenceOut } from '@/api/conferences'
|
||||
import { occurrenceBadge, OCCURRENCE_BADGE_ICON } from '@/lib/occurrenceStatus'
|
||||
import { useModalDismiss } from '@/hooks/useModalDismiss'
|
||||
|
||||
interface ConferenceOccurrenceDialogProps {
|
||||
occurrence: OccurrenceOut
|
||||
/** Присутствует только когда текущий пользователь — организатор этого вхождения (`is_owner`) — показывает кнопку «Редактировать». */
|
||||
ownedConference?: ConferenceOut
|
||||
onClose: () => void
|
||||
onEdit?: (conference: ConferenceOut) => void
|
||||
}
|
||||
|
||||
function formatRange(startsAt: string, endsAt: string): string {
|
||||
@@ -18,14 +23,19 @@ function formatRange(startsAt: string, endsAt: string): string {
|
||||
|
||||
/**
|
||||
* Быстрый просмотр вхождения конференции по клику на чип в календаре — для
|
||||
* конференций, которые пользователь не организует (не владелец —
|
||||
* `is_owner`): свои конференции CalendarPage открывает сразу в форме
|
||||
* редактирования (`ConferenceFormCard`). Здесь — минимум: когда, статус, вход
|
||||
* и ссылка на управление; подробности состава участников/организатора — в
|
||||
* ховер-карточке при наведении/фокусе на чип (`ConferenceHoverCard`).
|
||||
* всех конференций, включая свои: редактирование теперь отдельное действие
|
||||
* (карандашик на чипе или кнопка «Редактировать» здесь, у владельца), сам
|
||||
* клик по чипу всегда открывает это окно. Показывает статус, время, номер и,
|
||||
* для владельца, вход в редактирование; подробности состава
|
||||
* участников/организатора — в ховер-карточке при наведении/фокусе на чип
|
||||
* (`ConferenceHoverCard`).
|
||||
*/
|
||||
export function ConferenceOccurrenceDialog({ occurrence, onClose }: ConferenceOccurrenceDialogProps) {
|
||||
export function ConferenceOccurrenceDialog({ occurrence, ownedConference, onClose, onEdit }: ConferenceOccurrenceDialogProps) {
|
||||
const navigate = useNavigate()
|
||||
useModalDismiss(onClose)
|
||||
|
||||
const badge = occurrenceBadge(occurrence, new Date(occurrence.starts_at), new Date(occurrence.ends_at))
|
||||
const BadgeIcon = OCCURRENCE_BADGE_ICON[badge.kind]
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="occurrence-dialog-title" onClick={onClose}>
|
||||
@@ -40,13 +50,18 @@ export function ConferenceOccurrenceDialog({ occurrence, onClose }: ConferenceOc
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span className={`conf-badge ${badge.kind}`} style={{ marginBottom: 'var(--space-4)' }}>
|
||||
<BadgeIcon style={{ width: 11, height: 11 }} aria-hidden="true" />
|
||||
{badge.label}
|
||||
</span>
|
||||
|
||||
<div className="detail-row">
|
||||
<Calendar style={{ width: 18, height: 18 }} aria-hidden="true" />
|
||||
<span className="label">Когда</span>
|
||||
<span>{formatRange(occurrence.starts_at, occurrence.ends_at)}</span>
|
||||
</div>
|
||||
<div className="detail-row">
|
||||
<Repeat style={{ width: 18, height: 18 }} aria-hidden="true" />
|
||||
<Hash style={{ width: 18, height: 18 }} aria-hidden="true" />
|
||||
<span className="label">Номер</span>
|
||||
<span>{occurrence.number}</span>
|
||||
</div>
|
||||
@@ -63,10 +78,18 @@ export function ConferenceOccurrenceDialog({ occurrence, onClose }: ConferenceOc
|
||||
<Play style={{ width: 16, height: 16 }} aria-hidden="true" />
|
||||
Войти
|
||||
</button>
|
||||
{ownedConference && onEdit && (
|
||||
<button type="button" className="btn btn-secondary" onClick={() => onEdit(ownedConference)}>
|
||||
<Pencil style={{ width: 16, height: 16 }} aria-hidden="true" />
|
||||
Редактировать
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="field-hint" style={{ textAlign: 'center', marginTop: 'var(--space-3)' }}>
|
||||
Управление и редактирование — в <Link to="/my-conferences">«Моих конференциях»</Link>
|
||||
</p>
|
||||
{!ownedConference && (
|
||||
<p className="field-hint" style={{ textAlign: 'center', marginTop: 'var(--space-3)' }}>
|
||||
Управление и редактирование — в <Link to="/my-conferences">«Моих конференциях»</Link>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
24
frontend/src/lib/occurrenceStatus.ts
Normal file
24
frontend/src/lib/occurrenceStatus.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { CheckCircle2, Clock, Play, Repeat } from 'lucide-react'
|
||||
import type { OccurrenceOut } from '@/api/conferences'
|
||||
|
||||
export type OccurrenceBadgeKind = 'scheduled' | 'pinned' | 'live' | 'ended'
|
||||
|
||||
/** Общая логика статус-бейджа вхождения — используется чипом календаря и информационным окном (`ConferenceOccurrenceDialog`). */
|
||||
export function occurrenceBadge(
|
||||
occ: OccurrenceOut,
|
||||
start: Date | null,
|
||||
end: Date | null,
|
||||
): { kind: OccurrenceBadgeKind; label: string } {
|
||||
const now = new Date()
|
||||
if (start && end && now >= start && now <= end) return { kind: 'live', label: 'Идёт сейчас' }
|
||||
if (end && now > end) return { kind: 'ended', label: 'Завершена' }
|
||||
if (occ.is_pinned) return { kind: 'pinned', label: 'Закреплена' }
|
||||
return { kind: 'scheduled', label: 'Запланирована' }
|
||||
}
|
||||
|
||||
export const OCCURRENCE_BADGE_ICON: Record<OccurrenceBadgeKind, typeof Clock> = {
|
||||
scheduled: Clock,
|
||||
pinned: Repeat,
|
||||
live: Play,
|
||||
ended: CheckCircle2,
|
||||
}
|
||||
Reference in New Issue
Block a user