import React, { useState, useEffect } from 'react'; import { Plus, X, CheckCircle, XCircle, Calendar, Pencil, Trash2, Eye, FileText, Image as ImageIcon, Upload, Edit } from 'lucide-react'; import { backendApi } from '../../services/apiClient'; import type { ScheduledPost, SMMChannel } from '../../types'; export const PublicationSchedule: React.FC = () => { const [selectedMonth, setSelectedMonth] = useState(() => { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; }); // Посты (отложенные) const [posts, setPosts] = useState([]); const [postsLoading, setPostsLoading] = useState(false); // Каналы для постов const [channels, setChannels] = useState([]); // Модалки const [postModal, setPostModal] = useState(false); const [viewPostModal, setViewPostModal] = useState(null); // Формы const [postForm, setPostForm] = useState({ title: '', content: '', channelIds: [] as number[], scheduledAt: '', image: null as File | null, imagePreview: '' as string | '' }); const [editingPost, setEditingPost] = useState(null); const [saving, setSaving] = useState(false); const [actionLoading, setActionLoading] = useState(false); const [editContent, setEditContent] = useState(''); useEffect(() => { loadPosts(); loadChannels(); }, [selectedMonth]); const loadPosts = () => { setPostsLoading(true); const from = `${selectedMonth}-01T00:00:00`; const to = new Date(new Date(selectedMonth + '-01').setMonth(new Date(selectedMonth + '-01').getMonth() + 1)).toISOString(); backendApi.getScheduledPosts({ from, to, limit: 100 }) .then((data) => setPosts(Array.isArray(data) ? data : [])) .catch(() => setPosts([])) .finally(() => setPostsLoading(false)); }; const loadChannels = () => { backendApi.getSMMChannels() .then((data) => setChannels(Array.isArray(data) ? data : [])) .catch(() => setChannels([])); }; const openPostCreate = () => { setEditingPost(null); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(10, 0, 0, 0); setPostForm({ title: '', content: '', channelIds: [], scheduledAt: tomorrow.toISOString().slice(0, 16), image: null, imagePreview: '' }); setPostModal(true); }; const openPostEdit = (post: ScheduledPost) => { setEditingPost(post); setPostForm({ title: post.title, content: post.content, channelIds: post.channelIds || [], scheduledAt: new Date(post.scheduledAt).toISOString().slice(0, 16), image: null, imagePreview: post.imageUrl || '' }); setPostModal(true); }; const savePost = () => { const title = postForm.title.trim(); const content = postForm.content.trim(); if (!title || !content || !postForm.scheduledAt) return; setSaving(true); if (editingPost) { backendApi.updateScheduledPost(editingPost.id, { title, content, channelIds: postForm.channelIds, scheduledAt: postForm.scheduledAt, image: postForm.image || undefined, removeImage: !postForm.image && !postForm.imagePreview ? true : undefined }) .then(() => { setPostModal(false); loadPosts(); }) .finally(() => setSaving(false)); } else { backendApi.createScheduledPost({ title, content, channelIds: postForm.channelIds, scheduledAt: postForm.scheduledAt, status: 'pending_approval', // Сразу отправляем на утверждение image: postForm.image || undefined }) .then(() => { setPostModal(false); loadPosts(); }) .finally(() => setSaving(false)); } }; const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { setPostForm(f => ({ ...f, image: file, imagePreview: URL.createObjectURL(file) })); } }; const handleApprovePost = (id: number) => { setActionLoading(true); backendApi.approveScheduledPost(id) .then(() => { setViewPostModal(null); loadPosts(); }) .finally(() => setActionLoading(false)); }; const handleRejectPost = (id: number) => { const reason = prompt('Укажите причину отклонения:'); if (!reason) return; setActionLoading(true); backendApi.rejectScheduledPost(id, { rejectionReason: reason }) .then(() => { setViewPostModal(null); loadPosts(); }) .finally(() => setActionLoading(false)); }; const handleSendToEdit = (id: number) => { if (!editContent.trim()) return; setActionLoading(true); backendApi.sendScheduledPostToEdit(id, { editedContent: editContent }) .then(() => { setViewPostModal(null); setEditContent(''); loadPosts(); }) .finally(() => setActionLoading(false)); }; const handleDeletePost = (id: number) => { if (!confirm('Удалить пост?')) return; backendApi.deleteScheduledPost(id).then(() => loadPosts()); }; const getStatusColor = (status: string) => { switch (status) { case 'approved': return 'text-emerald-600 bg-emerald-50'; case 'pending_approval': return 'text-amber-600 bg-amber-50'; case 'rejected': return 'text-red-600 bg-red-50'; case 'published': return 'text-blue-600 bg-blue-50'; case 'edited': return 'text-purple-600 bg-purple-50'; default: return 'text-slate-500 bg-slate-50'; } }; const getStatusLabel = (status: string) => { switch (status) { case 'draft': return 'Черновик'; case 'pending_approval': return 'На согласовании'; case 'approved': return 'Одобрено'; case 'rejected': return 'Отклонено'; case 'edited': return 'На редактировании'; case 'published': return 'Опубликовано'; default: return status; } }; return (
setSelectedMonth(e.target.value)} className="px-4 py-2 border border-slate-200 rounded-xl text-sm" />
{/* Список отложенных постов */} {postsLoading ? (
Загрузка...
) : posts.length === 0 ? (

Нет отложенных постов для выбранного месяца

) : (
{posts.map((post) => (
setViewPostModal(post)}>
{new Date(post.scheduledAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })} {getStatusLabel(post.status)}

{post.title}

{post.channelIds && post.channelIds.length > 0 && (

{post.channelIds.length} канал{post.channelIds.length > 1 ? 'ов' : ''}

)} {post.imageUrl && (
Есть изображение
)}
))}
)} {/* Модалка создания/редактирования поста */} {postModal && (
!saving && setPostModal(false)}>
e.stopPropagation()}>

{editingPost ? 'Редактировать пост' : 'Создать отложенный пост'}

setPostForm((f) => ({ ...f, title: e.target.value }))} placeholder="Например: Отчёт о работе за месяц" className="w-full px-4 py-2.5 border border-slate-200 rounded-xl text-sm" />
setPostForm((f) => ({ ...f, scheduledAt: e.target.value }))} className="w-full px-4 py-2.5 border border-slate-200 rounded-xl text-sm" />