diff --git a/app/admin/(protected)/drafts/page.js b/app/admin/(protected)/drafts/page.js index cba82c8..bc1da60 100644 --- a/app/admin/(protected)/drafts/page.js +++ b/app/admin/(protected)/drafts/page.js @@ -1,6 +1,7 @@ 'use client'; import { useState, useEffect, useCallback } from 'react'; -import { CheckCircle, RefreshCw, Pencil, Clock, ImageIcon, ChevronDown, ChevronUp, X, Save } from 'lucide-react'; +import { CheckCircle, RefreshCw, Pencil, Clock, ImageIcon, ChevronDown, ChevronUp, X, Save, BookOpen, Maximize2 } from 'lucide-react'; +import { marked } from 'marked'; const COVER_STYLES = [ { id: 'robot-workspace', name: '🤖 Робот за работой' }, @@ -12,10 +13,14 @@ const COVER_STYLES = [ ]; const CATEGORY_LABELS = { - 'ai-tools': '🤖 AI Tools', - 'cybersec': '🔒 Cybersec', - 'automation': '⚡ Automation', - 'ai-dev': '💻 AI Dev', + 'ai-tools': '🤖 AI Tools', + 'cybersec': '🔒 Cybersec', + 'automation': '⚡ Automation', + 'ai-dev': '💻 AI Dev', + 'digest': '🗞 Digest', + 'opinions': '💬 Opinions', + 'tutorials': '📘 Tutorials', + 'comparisons': '⚖️ Comparisons', }; function DraftCard({ draft, onApproved, onCoverRegenerated }) { @@ -29,6 +34,22 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) { const [selectedStyle, setSelectedStyle] = useState(''); const [coverUrl, setCoverUrl] = useState(draft.cover_url); const [msg, setMsg] = useState(''); + const [reading, setReading] = useState(false); + const [full, setFull] = useState(null); + const [readLoading, setReadLoading] = useState(false); + + async function openReader() { + setReading(true); + if (full) return; + setReadLoading(true); + try { + const r = await fetch(`/api/admin/drafts/${draft.id}`); + const d = await r.json(); + if (d && d.content) setFull(d); + else setMsg('❌ ' + (d.error || 'Не удалось загрузить текст')); + } catch { setMsg('❌ Ошибка сети'); } + setReadLoading(false); + } async function approve() { setLoading(true); setMsg(''); @@ -86,12 +107,16 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) { return (
-
+
+ + + +
@@ -161,12 +186,57 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) { Редактировать )} + {!editing && ( + + )}
+ + {reading && ( +
setReading(false)} + className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-start sm:items-center justify-center p-4 overflow-y-auto"> +
e.stopPropagation()} + className="bg-white dark:bg-neutral-900 rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto shadow-2xl my-auto"> +
+ + {CATEGORY_LABELS[draft.category] || draft.category} + + +
+ {coverSrc && ( + + )} +
+
+ {draft.reading_time} мин чтения +
+

+ {full?.title || editTitle} +

+ {readLoading ? ( +
Загружаю текст…
+ ) : full ? ( +
+ ) : ( +
Нет текста
+ )} +
+
+
+ )}
); } diff --git a/app/api/admin/drafts/[id]/route.js b/app/api/admin/drafts/[id]/route.js index 5b71bb1..b0aaef2 100644 --- a/app/api/admin/drafts/[id]/route.js +++ b/app/api/admin/drafts/[id]/route.js @@ -2,6 +2,16 @@ import { NextResponse } from 'next/server'; import { requireAdminAuth } from '@/lib/adminAuth'; import { engineFetch } from '@/lib/engine'; +export async function GET(req, context) { + await requireAdminAuth(); + try { + const data = await engineFetch(`/api/drafts/${(await context.params).id}`); + return NextResponse.json(data); + } catch (err) { + return NextResponse.json({ error: err.message }, { status: 500 }); + } +} + export async function PATCH(req, context) { await requireAdminAuth(); try {