From 5be51d88f7b83f6aa55f7231ef48734d6b2c8e8a Mon Sep 17 00:00:00 2001 From: Alexey Pavlov Date: Mon, 15 Jun 2026 10:28:07 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20channel=20history=20page=20=E2=80=94=20?= =?UTF-8?q?published=20posts=20with=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/channels/[id]/history/page.js | 28 ++++++ components/ChannelHistory.js | 144 ++++++++++++++++++++++++++++++ components/ChannelView.js | 6 +- 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 app/channels/[id]/history/page.js create mode 100644 components/ChannelHistory.js diff --git a/app/channels/[id]/history/page.js b/app/channels/[id]/history/page.js new file mode 100644 index 0000000..eb79e80 --- /dev/null +++ b/app/channels/[id]/history/page.js @@ -0,0 +1,28 @@ +import { notFound, redirect } from 'next/navigation'; +import { requireUser } from '@/lib/session'; +import { engine } from '@/lib/engine'; +import Header from '@/components/Header'; +import ChannelHistory from '@/components/ChannelHistory'; + +export default async function ChannelHistoryPage({ params }) { + const user = await requireUser(); + if (!user) redirect('/login'); + + const { id } = await params; + + let channel, posts; + try { + channel = await engine.getChannel(user.id, id); + posts = await engine.listUserPosts(user.id, { channel_id: id, status: 'published', limit: 100 }); + } catch { + notFound(); + } + if (!channel) notFound(); + + return ( + <> +
+ + + ); +} diff --git a/components/ChannelHistory.js b/components/ChannelHistory.js new file mode 100644 index 0000000..40a2bce --- /dev/null +++ b/components/ChannelHistory.js @@ -0,0 +1,144 @@ +'use client'; +import { useState } from 'react'; +import Link from 'next/link'; +import { ArrowLeft, Clock, Image as ImageIcon, Copy, Check, Search } from 'lucide-react'; + +function timeAgo(dateStr) { + if (!dateStr) return ''; + const diff = Date.now() - new Date(dateStr).getTime(); + const m = Math.floor(diff / 60000); + if (m < 60) return `${m} мин назад`; + const h = Math.floor(m / 60); + if (h < 24) return `${h} ч назад`; + const d = Math.floor(h / 24); + if (d < 30) return `${d} дн назад`; + return new Date(dateStr).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric' }); +} + +function PostCard({ p }) { + const [copied, setCopied] = useState(false); + const [expanded, setExpanded] = useState(false); + + const preview = p.content?.slice(0, 200) || ''; + const isLong = (p.content?.length || 0) > 200; + + function copy() { + navigator.clipboard.writeText(p.content || ''); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( +
+ {/* Шапка */} +
+
+ + {timeAgo(p.published_at || p.updated_at || p.created_at)} +
+ +
+ + {/* Картинка */} + {p.image_url && ( + + )} + {!p.image_url && ( +
+ + Без изображения +
+ )} + + {/* Текст */} +
+ {expanded ? p.content : preview} + {isLong && !expanded && } +
+ {isLong && ( + + )} +
+ ); +} + +export default function ChannelHistory({ channel, posts }) { + const [query, setQuery] = useState(''); + + const filtered = query.trim() + ? posts.filter(p => p.content?.toLowerCase().includes(query.toLowerCase())) + : posts; + + return ( +
+ {/* Навигация */} +
+ + + {channel.name} + + / + История публикаций +
+ + {/* Статистика + поиск */} +
+

+ {posts.length === 0 + ? 'Публикаций пока нет' + : `${posts.length} ${posts.length === 1 ? 'публикация' : posts.length < 5 ? 'публикации' : 'публикаций'}`} +

+ {posts.length > 0 && ( +
+ + setQuery(e.target.value)} + className="pl-8 pr-3 py-1.5 text-sm border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-300 w-48" + /> +
+ )} +
+ + {/* Список постов */} + {filtered.length === 0 && query ? ( +

Ничего не найдено

+ ) : filtered.length === 0 ? ( +
+ +

Опубликованных постов пока нет

+ + Создать первый пост → + +
+ ) : ( +
+ {filtered.map(p => )} +
+ )} +
+ ); +} diff --git a/components/ChannelView.js b/components/ChannelView.js index e4a0586..9abce45 100644 --- a/components/ChannelView.js +++ b/components/ChannelView.js @@ -4,7 +4,7 @@ import Link from 'next/link'; import { ArrowLeft, Sparkles, Wand2, Copy, Check, Loader2, Settings, Image as ImageIcon, RefreshCw, Scissors, Maximize2, Zap, Heart, - MessageSquare, Pencil, X, ChevronDown, Send, Clock, Trash2 + MessageSquare, Pencil, X, ChevronDown, Send, Clock, Trash2, History } from 'lucide-react'; const GOAL_LABELS = { @@ -276,6 +276,10 @@ export default function ChannelView({ channel }) { {channel.niche &&

{channel.niche}

} + + + История + Настройки