forked from admin/zeropost-tool
feat: drafts UI — /drafts review page + batch generate button
/drafts page: список черновиков по статусам (pending/approved/rejected)
Одобрить + выбрать время → scheduled_post в календарь
Редактировать текст inline, отклонить, удалить
Header: ссылка 'Черновики' (FileText иконка)
ChannelView: кнопка 'Авто ×N' для batch-генерации (async)
ChannelEdit AI-стиль: секция авто-черновиков (toggle + count + time)
API routes: /api/drafts, /api/drafts/[id]/{approve,reject}
/api/channels/[channelId]/drafts/generate
This commit is contained in:
@@ -111,6 +111,10 @@ export default function ChannelEdit({ channel }) {
|
||||
// AI-стиль
|
||||
const [aiStylePrompt, setAiStylePrompt] = useState(channel.ai_style_prompt || '');
|
||||
const [imageQuality, setImageQuality] = useState(channel.image_quality || 'standard');
|
||||
// Авто-черновики
|
||||
const [autoDraftEnabled, setAutoDraftEnabled] = useState(channel.auto_draft_enabled || false);
|
||||
const [autoDraftCount, setAutoDraftCount] = useState(channel.auto_draft_count || 3);
|
||||
const [autoDraftTime, setAutoDraftTime] = useState(channel.auto_draft_time || '08:00');
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
@@ -129,6 +133,9 @@ export default function ChannelEdit({ channel }) {
|
||||
vk_access_token: vkToken.trim() || null,
|
||||
ai_style_prompt: aiStylePrompt.trim() || null,
|
||||
image_quality: imageQuality,
|
||||
auto_draft_enabled: autoDraftEnabled,
|
||||
auto_draft_count: autoDraftCount,
|
||||
auto_draft_time: autoDraftTime,
|
||||
style: {
|
||||
tone, formality, humor,
|
||||
post_length: postLength,
|
||||
@@ -496,6 +503,54 @@ export default function ChannelEdit({ channel }) {
|
||||
|
||||
{/* Банк тем */}
|
||||
<TopicBank channelId={channel.id} />
|
||||
|
||||
{/* Авто-черновики */}
|
||||
<div className="card p-5 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm flex items-center gap-2">
|
||||
<span>⚡</span> Авто-генерация черновиков
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 mt-0.5">
|
||||
Система генерирует посты каждый день — ты одобряешь вечером
|
||||
</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" className="sr-only peer"
|
||||
checked={autoDraftEnabled}
|
||||
onChange={e => setAutoDraftEnabled(e.target.checked)} />
|
||||
<div className="w-10 h-5 bg-gray-600 peer-focus:outline-none rounded-full peer
|
||||
peer-checked:after:translate-x-full peer-checked:after:border-white
|
||||
after:content-[''] after:absolute after:top-0.5 after:left-[2px]
|
||||
after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all
|
||||
peer-checked:bg-accent" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{autoDraftEnabled && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="label text-xs mb-1">Постов в день</label>
|
||||
<select value={autoDraftCount} onChange={e => setAutoDraftCount(+e.target.value)}
|
||||
className="input w-full text-sm py-1.5">
|
||||
{[1,2,3,5,7,10].map(n => <option key={n} value={n}>{n} {n===1?'пост':'постов'}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label text-xs mb-1">Время генерации</label>
|
||||
<input type="time" value={autoDraftTime}
|
||||
onChange={e => setAutoDraftTime(e.target.value)}
|
||||
className="input w-full text-sm py-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-gray-500">
|
||||
Черновики появляются на странице{' '}
|
||||
<a href="/drafts" target="_blank" className="text-accent hover:underline">Черновики</a>.
|
||||
Там можно редактировать, одобрять и планировать публикацию.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ export default function ChannelView({ channel }) {
|
||||
const [showPhotoSearch, setShowPhotoSearch] = useState(false);
|
||||
const [showFromUrl, setShowFromUrl] = useState(false);
|
||||
const [showPoll, setShowPoll] = useState(false);
|
||||
const [batchCount, setBatchCount] = useState(3);
|
||||
const [batchLoading, setBatchLoading] = useState(false);
|
||||
|
||||
// Трансформации
|
||||
const [transforming, setTransforming] = useState(false);
|
||||
@@ -400,6 +402,31 @@ export default function ChannelView({ channel }) {
|
||||
Опрос
|
||||
</button>
|
||||
)}
|
||||
{/* Batch-генерация черновиков */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={async () => {
|
||||
setBatchLoading(true);
|
||||
try {
|
||||
const res = await fetch(`/api/channels/${channel.id}/drafts/generate?count=${batchCount}`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
}).then(r => r.json());
|
||||
if (res.ok) alert(`✅ Генерирую ${batchCount} черновиков — через несколько минут появятся в /drafts`);
|
||||
else alert(res.error || 'Ошибка');
|
||||
} catch { alert('Ошибка'); }
|
||||
setBatchLoading(false);
|
||||
}}
|
||||
disabled={batchLoading}
|
||||
className="text-xs inline-flex items-center gap-1 text-purple-400 hover:text-purple-300 transition-colors"
|
||||
>
|
||||
<span>{batchLoading ? '⏳' : '⚡'}</span>
|
||||
Авто ×
|
||||
</button>
|
||||
<select value={batchCount} onChange={e => setBatchCount(+e.target.value)}
|
||||
className="text-xs bg-surface2 border border-border rounded px-1 py-0.5 text-gray-400">
|
||||
{[1,2,3,5,7,10].map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={fetchIdeas}
|
||||
disabled={loadingIdeas}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Sparkles, LogOut, Settings2, CalendarDays, TrendingUp, Coins } from 'lucide-react';
|
||||
import { Sparkles, LogOut, Settings2, CalendarDays, TrendingUp, Coins, FileText } from 'lucide-react';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
|
||||
export default function Header({ user }) {
|
||||
@@ -34,6 +34,10 @@ export default function Header({ user }) {
|
||||
<CalendarDays className="w-4 h-4" />
|
||||
<span>Календарь</span>
|
||||
</Link>
|
||||
<Link href="/drafts" className="btn-ghost px-3 py-1.5 text-sm flex items-center gap-1.5">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span>Черновики</span>
|
||||
</Link>
|
||||
{user?.isAdmin && (
|
||||
<Link href="/spending" className="btn-ghost px-3 py-1.5 text-sm flex items-center gap-1.5">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
|
||||
Reference in New Issue
Block a user