// ════════════════════════════════════════════════════════════════ // /admin/invites — отдельная страница для управления приглашениями // ════════════════════════════════════════════════════════════════ function InvitesPanel({ currentUser, onRoute }) { const A = window.SDH_ADMIN; const [openCreate, setOpenCreate] = React.useState(false); const [createdInvite, setCreatedInvite] = React.useState(null); const [shareInvite, setShareInvite] = React.useState(null); const pending = A.invites.filter(i => i.status === 'pending'); const accepted = A.invites.filter(i => i.status === 'accepted'); const archived = A.invites.filter(i => i.status === 'expired' || i.status === 'revoked'); return (
} />
{/* Pending */} {pending.length === 0 ? : pending.map(inv => ( setShareInvite(inv)} /> ))}
{/* Accepted */} {accepted.length === 0 ? : accepted.map(inv => )}
{/* Archived */} {archived.length === 0 ? : archived.map(inv => )}
{openCreate && ( setOpenCreate(false)} onCreated={(inv) => { setOpenCreate(false); setCreatedInvite(inv); }} /> )} {createdInvite && setCreatedInvite(null)} />} {shareInvite && setShareInvite(null)} />} ); } function Bucket({ title, count, hue, subtitle, help, children }) { return (
{title.toUpperCase()} {count} {help && что это }
{subtitle && (

{subtitle}

)}
{children}
); } function InviteRow({ invite, archived, onShare }) { const sentDate = invite.sent_at ? new Date(invite.sent_at) : null; const daysAgo = sentDate ? Math.round((new Date('2026-05-23T10:00:00Z') - sentDate) / 86400000) : null; const [busy, setBusy] = React.useState(null); // 'resend' | 'revoke' | null async function handleResend() { if (!invite.id || invite.id === 'inv_new') return; setBusy('resend'); if (window.SDH_USE_API && window.SDH_API) { try { await window.SDH_API.post(`/api/admin/invites/${invite.id}/resend`); alert('Приглашение поставлено в очередь (email отключён, ссылка остаётся в админке).'); } catch (e) { alert('Ошибка: ' + ((e && e.message) || e)); } } else { alert('Mock: resend OK'); } setBusy(null); } async function handleRevoke() { if (!invite.id || invite.id === 'inv_new') return; if (!confirm(`Отозвать приглашение для ${invite.email}?`)) return; setBusy('revoke'); if (window.SDH_USE_API && window.SDH_API) { try { await window.SDH_API.del(`/api/admin/invites/${invite.id}`); if (window.SDH_HYDRATE && window.SDH_HYDRATE.refreshInvites) { await window.SDH_HYDRATE.refreshInvites(); } } catch (e) { alert('Ошибка: ' + ((e && e.message) || e)); setBusy(null); return; } } else { // Mock: update in-memory if (window.SDH_ADMIN && Array.isArray(window.SDH_ADMIN.invites)) { const inv = window.SDH_ADMIN.invites.find(i => i.id === invite.id); if (inv) inv.status = 'revoked'; } } setBusy(null); } return (
{invite.email}
{invite.note &&
{invite.note}
}
{invite.status === 'accepted' ? `принято ${daysAgo} д назад` : invite.status === 'expired' ? `истекло ${daysAgo} д назад` : invite.status === 'revoked' ? `отозвано ${daysAgo} д назад` : `отправлено ${daysAgo} д назад`}
{invite.status === 'pending' && ( )} {invite.status === 'accepted' && ( )} {archived && invite.status !== 'accepted' && ( )}
); } window.InvitesPanel = InvitesPanel;