Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
import { Link, useNavigate } from 'react-router-dom'
import { Calendar, Lock, Play, Repeat, X } from 'lucide-react'
import type { OccurrenceOut } from '@/api/conferences'
interface ConferenceOccurrenceDialogProps {
occurrence: OccurrenceOut
onClose: () => void
}
function formatRange(startsAt: string, endsAt: string): string {
const start = new Date(startsAt)
const end = new Date(endsAt)
const dateLabel = start.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' })
const startTime = start.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
const endTime = end.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
return `${dateLabel}, ${startTime}${endTime}`
}
/**
* Быстрый просмотр вхождения конференции по клику на чип в календаре — для
* конференций, которые пользователь не организует (не владелец —
* `is_owner`): свои конференции CalendarPage открывает сразу в форме
* редактирования (`ConferenceFormCard`). Здесь — минимум: когда, статус, вход
* и ссылка на управление; подробности состава участников/организатора — в
* ховер-карточке при наведении/фокусе на чип (`ConferenceHoverCard`).
*/
export function ConferenceOccurrenceDialog({ occurrence, onClose }: ConferenceOccurrenceDialogProps) {
const navigate = useNavigate()
return (
<div className="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="occurrence-dialog-title" onClick={onClose}>
<div className="modal-panel" onClick={(e) => e.stopPropagation()}>
<div className="modal-head">
<h2 id="occurrence-dialog-title">
{occurrence.is_closed && <Lock className="title-lock" aria-hidden="true" />}
{occurrence.title ?? 'Конференция без названия'}
</h2>
<button type="button" className="modal-close" aria-label="Закрыть" onClick={onClose}>
<X className="lucide" style={{ width: 16, height: 16 }} aria-hidden="true" />
</button>
</div>
<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" />
<span className="label">Номер</span>
<span>{occurrence.number}</span>
</div>
{occurrence.is_pinned && (
<div className="detail-row">
<Repeat style={{ width: 18, height: 18 }} aria-hidden="true" />
<span className="label">Статус</span>
<span>Закреплённая конференция повторяется по расписанию</span>
</div>
)}
<div className="modal-actions">
<button type="button" className="btn btn-primary" onClick={() => navigate(`/j/${occurrence.slug}`)}>
<Play 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>
</div>
</div>
)
}