feat: журнальная главная, страница Зеро, TG-баннер, stats, auto-publish UI

- Журнальная главная: hero, CategoryRow, PopularBlock, RecentBlock (Сегодня/Вчера/Неделя)
- ArticleCard: 3 размера (hero/regular/compact), цветной badge без дублей тегов
- ArticleCoverSVG: 6 брендовых палитр, аватар Зеро в углу вместо #ZEROPOST
- /about/zero: страница персонажа с галереей 8 поз
- Footer: TG-баннер с аватаром Зеро на каждой странице
- Конец статьи: блок «Понравилась? → Подписаться на канал»
- ChannelEditor: 4 вкладки (Настройки/Расписание/Авто-публикация/Ручная)
- AutoPublishTab: toggle, категории, delay, template, live preview
- ArticlePicker: typeahead с was_sent_to_channel / next_scheduled_at флагами
- /admin/channels/[id]/stats: график роста подписчиков (recharts)
- Dashboard: блок TG-статистики (подписчики, delta 24h/7d, постов)
- Header: упрощён до 2 пунктов desktop + расширенное мобильное меню
- AutogenPanel: корректные time-picker'ы, calcNextRun с учётом last_run_at
This commit is contained in:
Nik (Claude)
2026-06-07 14:04:09 +03:00
parent 6f7c47a258
commit 334b2f51df
32 changed files with 2492 additions and 353 deletions
+68
View File
@@ -154,3 +154,71 @@ export async function adminGetChannelPosts(channelId) {
}
// ── Admin Settings API ────────────────────────────────────────────────────────
export async function adminListSettings() {
return call('/api/settings/admin');
}
export async function adminUpdateSetting(key, value) {
return call(`/api/settings/admin/${encodeURIComponent(key)}`, {
method: 'PUT',
body: JSON.stringify({ value }),
});
}
// ── Admin Articles search (typeahead) ─────────────────────────────────────────
export async function adminSearchArticles({ q = '', status = 'published', category = '', channelId = null, limit = 20 } = {}) {
const params = new URLSearchParams();
if (q) params.set('q', q);
if (status) params.set('status', status);
if (category) params.set('category', category);
if (channelId)params.set('channel_id', String(channelId));
params.set('limit', String(limit));
return call(`/api/articles/admin/search?${params.toString()}`);
}
// ── Admin Scheduled posts ─────────────────────────────────────────────────────
export async function adminGetScheduledQueue(channelId = null) {
if (channelId) return call(`/api/channels/admin/${channelId}/scheduled`);
return call('/api/scheduled-posts/queue');
}
export async function adminScheduleArticle(channelId, { article_id, custom_text, scheduled_at } = {}) {
return call(`/api/channels/admin/${channelId}/schedule`, {
method: 'POST',
body: JSON.stringify({ article_id, custom_text, scheduled_at }),
});
}
export async function adminCancelScheduled(scheduledPostId) {
return call(`/api/scheduled-posts/${scheduledPostId}`, { method: 'DELETE' });
}
export async function adminPreviewTemplate({ article_id, template }) {
return call('/api/scheduled-posts/preview', {
method: 'POST',
body: JSON.stringify({ article_id, template }),
});
}
export async function adminRequeueArticle(articleId) {
return call(`/api/scheduled-posts/schedule-article/${articleId}`, { method: 'POST' });
}
// Главная страница — собранный набор секций
export async function getHomeData() {
return call('/api/articles/home');
}
// ── Channel stats ─────────────────────────────────────────────────────────────
export async function getChannelSummary(channelId) {
return call(`/api/channel-stats/${channelId}/summary`);
}
export async function getChannelHistory(channelId, days = 30) {
return call(`/api/channel-stats/${channelId}/history?days=${days}`);
}