// team.jsx — экран «Команда» (?view=team) для founder/moderator. Минимально и функционально: // Кейсы — очередь на проверке: опубликовать (+баллы автору) / скрыть (баллы снимаются) / // вернуть с комментарием; флажок «на сайт» — только если автор дал согласие // Эфиры — отметить пришедших (+50) и ведущего (+100) по событию из data/events.json // + флажок «Показывать на сайте» (неделя 3: уходит в GET /api/public/events) // Подписки — у кого заканчивается подписка (3/7/14 дней) и истекла за 14 дней, ответ «Почему уходишь?», CSV // Бонусы — кто что получил (manual — связаться самим) // Ответы — «Почему уходишь?» и «Что получилось?» из сообщений Николая // Тексты — сообщения Николая, бонусы, подписи тарифа, экран ухода (texts.jsx) // Вошли без доступа — кто вошёл через Hakku ID без подписки: «Выдать доступ» дописывает строку в таблицу // участников (источник lms-grant) и сразу синкает; Getplat: активные и «требует проверки» (сумма не из маппинга) // Права проверяет сервер (/api/admin/*), здесь только кнопки. const CONSENT_LABEL = { name: 'с именем и фото', anon: 'без имени', no: 'нет, только в сообществе' }; const fmtDT = (iso) => iso ? new Date(iso).toLocaleString('ru-RU', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Moscow' }) : ''; function TeamCase({ c, onChanged, nav }) { const [open, setOpen] = React.useState(false); const [comment, setComment] = React.useState(''); const [site, setSite] = React.useState(!!c.on_site); const [busy, setBusy] = React.useState(false); const [msg, setMsg] = React.useState(''); const canSite = c.consent === 'name' || c.consent === 'anon'; const act = async (action) => { if (action === 'return' && !comment.trim()) return setMsg('Напиши автору, что поправить.'); setBusy(true); setMsg(''); const r = await postJSON('/api/admin/cases/' + encodeURIComponent(c.id), { action, comment, on_site: canSite ? site : false }); setBusy(false); if (!r.ok) return setMsg('Не получилось: ' + ((r.j && r.j.error) || 'ошибка сервера')); const p = r.j.points; toast(action === 'publish' ? `Опубликован${p ? `, автору +${p}` : p === 0 ? ', сверх лимита: 0 баллов' : ''}` : action === 'hide' ? `Скрыт${p ? `, снято ${Math.abs(p)}` : ''}` : action === 'return' ? 'Вернули автору' : 'Сохранено'); onChanged(); }; const s = CASE_STATUS[c.status] || ['', '']; return (
{s[0]}{fmtDT(c.created_at)}
{c.title}
{c.author.name} · {c.author.email} · сайт: {CONSENT_LABEL[c.consent] || '—'}{c.on_site ? ' · на сайте' : ''}
{open &&
} {c.team_comment &&
Комментарий: {c.team_comment}
}
{c.status !== 'published' && ( )} {c.status === 'published' && canSite && ( )} {c.status !== 'published' && } {c.status !== 'hidden' && } {c.status === 'pending' && }
{c.status !== 'published' && (