diff --git a/app/admin/(protected)/articles/page.js b/app/admin/(protected)/articles/page.js
index 5959729..e923273 100644
--- a/app/admin/(protected)/articles/page.js
+++ b/app/admin/(protected)/articles/page.js
@@ -1,6 +1,7 @@
import Link from 'next/link';
import { adminListArticles } from '@/lib/engine';
-import { Plus, Pencil, Eye } from 'lucide-react';
+import { Plus } from 'lucide-react';
+import ArticleRowActions, { StatusBadge } from '@/components/admin/ArticleRowActions';
export const dynamic = 'force-dynamic';
export const metadata = { title: 'Статьи' };
@@ -55,35 +56,13 @@ export default async function AdminArticlesPage() {
-
- {a.status === 'published' ? 'Опубликована' : 'Черновик'}
-
+
|
{a.published_at ? new Date(a.published_at).toLocaleDateString('ru-RU') : '—'}
|
-
+
|
))}
diff --git a/app/admin/api/articles/[id]/route.js b/app/admin/api/articles/[id]/route.js
index 17119a0..ad7ae43 100644
--- a/app/admin/api/articles/[id]/route.js
+++ b/app/admin/api/articles/[id]/route.js
@@ -1,18 +1,37 @@
+/**
+ * Proxy для /admin/api/articles/:id → engine /api/articles/:id
+ * Используется для DELETE из admin UI.
+ */
import { NextResponse } from 'next/server';
import { checkAdminAuth } from '@/lib/adminAuth';
-import { adminUpdateArticle, adminDeleteArticle } from '@/lib/engine';
-export async function PATCH(req, { params }) {
- if (!(await checkAdminAuth())) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+const E = process.env.ENGINE_URL || 'http://127.0.0.1:3030';
+const S = process.env.ENGINE_SECRET || 'zeropost_internal_2026';
+
+async function proxy(req, { params }) {
+ if (!(await checkAdminAuth())) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
const { id } = await params;
- const body = await req.json();
- const result = await adminUpdateArticle(id, body);
- return NextResponse.json(result);
+ const url = `${E}/api/articles/${id}`;
+
+ const headers = { 'x-internal-secret': S, 'x-user-id': '1' };
+ let body;
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
+ headers['Content-Type'] = 'application/json';
+ body = (await req.text()) || undefined;
+ }
+
+ try {
+ const res = await fetch(url, { method: req.method, headers, body, cache: 'no-store' });
+ const data = await res.json().catch(() => ({}));
+ return NextResponse.json(data, { status: res.status });
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 502 });
+ }
}
-export async function DELETE(req, { params }) {
- if (!(await checkAdminAuth())) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
- const { id } = await params;
- const result = await adminDeleteArticle(id);
- return NextResponse.json(result);
-}
+export const GET = proxy;
+export const PATCH = proxy;
+export const PUT = proxy;
+export const DELETE = proxy;
diff --git a/components/admin/ArticleRowActions.js b/components/admin/ArticleRowActions.js
new file mode 100644
index 0000000..8fb384f
--- /dev/null
+++ b/components/admin/ArticleRowActions.js
@@ -0,0 +1,95 @@
+'use client';
+import Link from 'next/link';
+import { useState } from 'react';
+import { Pencil, Eye, Trash2, Loader2, Clock } from 'lucide-react';
+
+/**
+ * Действия в строке статьи: редактировать, открыть, удалить.
+ * Удаление через confirm() + POST proxy → engine DELETE /api/articles/:id.
+ */
+export default function ArticleRowActions({ article }) {
+ const [busy, setBusy] = useState(false);
+ const [gone, setGone] = useState(false);
+
+ async function handleDelete(e) {
+ e.preventDefault();
+ if (!confirm(`Удалить «${article.title.slice(0, 60)}»? Это действие нельзя отменить.`)) return;
+ setBusy(true);
+ try {
+ const r = await fetch(`/admin/api/articles/${article.id}`, { method: 'DELETE' });
+ if (!r.ok) {
+ const d = await r.json().catch(() => ({}));
+ alert('Ошибка: ' + (d.error || r.status));
+ setBusy(false);
+ return;
+ }
+ setGone(true);
+ // Refresh страницы, чтобы серверный render обновил список
+ setTimeout(() => window.location.reload(), 400);
+ } catch (err) {
+ alert('Ошибка: ' + err.message);
+ setBusy(false);
+ }
+ }
+
+ if (gone) {
+ return (
+
+ удалена
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+/**
+ * Для статуса показываем дополнительно «выйдет в HH:MM» если published_at в будущем.
+ */
+export function StatusBadge({ status, published_at }) {
+ const future = status === 'published' && published_at && new Date(published_at) > new Date();
+ if (future) {
+ const d = new Date(published_at);
+ const label = d.toLocaleString('ru-RU', { timeZone: 'Europe/Moscow', day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
+ return (
+
+ {label}
+
+ );
+ }
+ return (
+
+ {status === 'published' ? 'Опубликована' : 'Черновик'}
+
+ );
+}