feat(categories): admin section + dynamic categories everywhere
New /admin/categories page:
- list with icon, name, slug, color preview, article/topic counters
- new/edit form (slug locked after creation since it's FK in articles/topics)
- 12 color palette, sort_order, archive toggle (soft delete)
- hard delete only when archived AND no articles/topics attached
AdminNav: new pin 'Категории' (FolderPlus) in 'Контент' group, before Статьи.
Dynamic categories — hardcoded CAT_LABELS removed from:
- components/admin/AutogenPanel.js — now accepts categories prop, builds
lookup map from DB, supports all 12 palette colors
- app/page.js — CATEGORY_ORDER hardcode removed; renders categories in
sort_order from DB, skips empty ones
Plumbing: app/admin/api/categories/[...path]/route.js — catch-all proxy
This commit is contained in:
@@ -4,7 +4,7 @@ import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
LayoutDashboard, FileText, Radio, Zap, Settings, LogOut, ExternalLink,
|
||||
MessageCircle, Clock, Coffee, Menu, X,
|
||||
MessageCircle, Clock, Coffee, Menu, X, FolderPlus,
|
||||
} from 'lucide-react';
|
||||
|
||||
// Группы пунктов меню — масштабируется, без давки сверху
|
||||
@@ -18,7 +18,8 @@ const GROUPS = [
|
||||
{
|
||||
label: 'Контент',
|
||||
items: [
|
||||
{ href: '/admin/articles', label: 'Статьи', icon: FileText },
|
||||
{ href: '/admin/categories', label: 'Категории', icon: FolderPlus },
|
||||
{ href: '/admin/articles', label: 'Статьи', icon: FileText },
|
||||
{ href: '/admin/drafts', label: 'Черновики', icon: Clock },
|
||||
{ href: '/admin/notes', label: 'Заметки', icon: MessageCircle },
|
||||
{ href: '/admin/zero', label: 'Зеро', icon: Coffee },
|
||||
|
||||
@@ -3,18 +3,21 @@ import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Play, Plus, Trash2, RefreshCw, Clock, CheckCircle, XCircle, Zap } from 'lucide-react';
|
||||
|
||||
const CAT_LABELS = {
|
||||
'ai-tools': { name: 'ИИ-инструменты', icon: '🤖', color: 'emerald' },
|
||||
'cybersec': { name: 'Кибербезопасность', icon: '🔒', color: 'red' },
|
||||
'automation': { name: 'Автоматизация', icon: '⚡', color: 'amber' },
|
||||
'ai-dev': { name: 'Разработка с ИИ', icon: '💻', color: 'blue' },
|
||||
};
|
||||
|
||||
// Все цвета палитры — synced с /admin/categories.
|
||||
// Хардкода категорий больше нет: они приходят из БД через prop categories.
|
||||
const COLOR = {
|
||||
emerald: 'bg-emerald-50 dark:bg-emerald-950 border-emerald-200 dark:border-emerald-800 text-emerald-700 dark:text-emerald-300',
|
||||
red: 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800 text-red-700 dark:text-red-300',
|
||||
amber: 'bg-amber-50 dark:bg-amber-950 border-amber-200 dark:border-amber-800 text-amber-700 dark:text-amber-300',
|
||||
blue: 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-300',
|
||||
purple: 'bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800 text-purple-700 dark:text-purple-300',
|
||||
pink: 'bg-pink-50 dark:bg-pink-950 border-pink-200 dark:border-pink-800 text-pink-700 dark:text-pink-300',
|
||||
cyan: 'bg-cyan-50 dark:bg-cyan-950 border-cyan-200 dark:border-cyan-800 text-cyan-700 dark:text-cyan-300',
|
||||
orange: 'bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800 text-orange-700 dark:text-orange-300',
|
||||
lime: 'bg-lime-50 dark:bg-lime-950 border-lime-200 dark:border-lime-800 text-lime-700 dark:text-lime-300',
|
||||
rose: 'bg-rose-50 dark:bg-rose-950 border-rose-200 dark:border-rose-800 text-rose-700 dark:text-rose-300',
|
||||
slate: 'bg-slate-50 dark:bg-slate-900 border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300',
|
||||
neutral: 'bg-neutral-50 dark:bg-neutral-900 border-neutral-200 dark:border-neutral-700 text-neutral-700 dark:text-neutral-300',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -63,7 +66,17 @@ function fmtNextRun(date) {
|
||||
});
|
||||
}
|
||||
|
||||
export default function AutogenPanel({ status, queue, topics }) {
|
||||
// catMap превращает массив категорий в lookup-объект по slug → { name, icon, color }
|
||||
function buildCatMap(categories) {
|
||||
const map = {};
|
||||
for (const c of (categories || [])) {
|
||||
map[c.slug] = { name: c.name, icon: c.icon || '📝', color: c.color || 'emerald' };
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export default function AutogenPanel({ status, queue, topics, categories = [] }) {
|
||||
const CAT_LABELS = buildCatMap(categories);
|
||||
const router = useRouter();
|
||||
const [running, setRunning] = useState({});
|
||||
const [toast, setToast] = useState(null);
|
||||
|
||||
Reference in New Issue
Block a user