import React, { useState, useEffect } from 'react'; import { CompanyNews, Department } from '../../types'; import { backendApi } from '../../services/apiClient'; import { readCache, saveCache } from '../../hooks/useCachedFetch'; import { REFRESH_EVENTS } from '../../constants/refreshEvents'; import { Newspaper, Plus, Edit, Trash2, Send, X, Clock, CheckCircle2, FileEdit } from 'lucide-react'; type StatusFilter = 'all' | 'draft' | 'pending' | 'published'; const DEPT_LABELS: Record = { production: 'Производство', pr: 'PR', finance: 'Финансы', development: 'Развитие', legal: 'Юр. отдел', hr: 'Кадры', }; const CACHE_KEY = 'mkd_office_news_cache'; export const CompanyNewsRegistry: React.FC = () => { const cached = readCache(CACHE_KEY, []); const [items, setItems] = useState(cached); const [loading, setLoading] = useState(cached.length === 0); const [statusFilter, setStatusFilter] = useState('all'); const [modalOpen, setModalOpen] = useState(false); const [editingItem, setEditingItem] = useState(null); const [submitLoading, setSubmitLoading] = useState(false); const [error, setError] = useState(null); const fetchList = async (showSpinner = true) => { if (showSpinner && !(statusFilter === 'all' && cached.length > 0)) setLoading(true); try { const params = statusFilter !== 'all' ? { status: statusFilter, limit: 100 } : { limit: 100 }; const list = await backendApi.getCompanyNews(params); const arr = Array.isArray(list) ? list : []; setItems(arr); if (statusFilter === 'all') saveCache(CACHE_KEY, arr); } catch (err) { console.error('[CompanyNewsRegistry] fetch:', err); setItems([]); } finally { setLoading(false); } }; useEffect(() => { fetchList(); }, [statusFilter]); useEffect(() => { const onRefresh = () => fetchList(false); window.addEventListener(REFRESH_EVENTS.news, onRefresh); return () => window.removeEventListener(REFRESH_EVENTS.news, onRefresh); }, []); useEffect(() => { const interval = setInterval(() => fetchList(false), 10 * 1000); return () => clearInterval(interval); }, []); const handleCreate = () => { setEditingItem(null); setModalOpen(true); setError(null); }; const handleEdit = (item: CompanyNews) => { setEditingItem(item); setModalOpen(true); setError(null); }; const handlePublish = async (item: CompanyNews) => { if (item.status === 'published') return; setSubmitLoading(true); setError(null); try { await backendApi.updateCompanyNews(item.id, { ...item, status: 'published' }); window.dispatchEvent(new CustomEvent('mkd-news-changed')); await fetchList(); } catch (err: any) { setError(err?.message || 'Не удалось опубликовать'); } finally { setSubmitLoading(false); } }; const handleDelete = async (item: CompanyNews) => { if (!confirm(`Удалить новость «${item.title}»?`)) return; setSubmitLoading(true); setError(null); try { await backendApi.deleteCompanyNews(item.id); window.dispatchEvent(new CustomEvent('mkd-news-changed')); await fetchList(); setModalOpen(false); setEditingItem(null); } catch (err: any) { setError(err?.message || 'Не удалось удалить'); } finally { setSubmitLoading(false); } }; const handleModalSuccess = () => { setModalOpen(false); setEditingItem(null); fetchList(); }; const filtered = items; return (

Реестр новостей компании

{(['all', 'draft', 'pending', 'published'] as StatusFilter[]).map((s) => ( ))}
{error && (
{error}
)} {loading ? (

Загрузка...

) : filtered.length === 0 ? (

Нет новостей

) : (
    {filtered.map((n) => (
  • {n.title}

    {n.publishedAt ? new Date(n.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric' }) : new Date(n.createdAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric' })} {n.createdByName && {n.createdByName}} {n.status === 'published' && } {n.status === 'pending' && } {n.status === 'draft' && } {n.status === 'published' ? 'Опубликовано' : n.status === 'pending' ? 'На согласовании' : 'Черновик'}
    {n.body &&

    {n.body}

    }
    {n.status !== 'published' && ( )}
  • ))}
)} {modalOpen && ( { setModalOpen(false); setEditingItem(null); setError(null); }} onSuccess={handleModalSuccess} onError={(msg) => setError(msg)} /> )}
); }; // ========== Form Modal (create / edit) ========== interface CompanyNewsFormModalProps { item: CompanyNews | null; onClose: () => void; onSuccess: () => void; onError: (msg: string) => void; } const CompanyNewsFormModal: React.FC = ({ item, onClose, onSuccess, onError }) => { const [title, setTitle] = useState(item?.title ?? ''); const [body, setBody] = useState(item?.body ?? ''); const [status, setStatus] = useState(item?.status ?? 'draft'); const [notifyDepartments, setNotifyDepartments] = useState(item?.notifyDepartments ?? []); const [notifyEmployeeIds, setNotifyEmployeeIds] = useState(item?.notifyEmployeeIds ?? []); const [employeesList, setEmployeesList] = useState>([]); const [loading, setLoading] = useState(false); useEffect(() => { if (item) { setTitle(item.title); setBody(item.body ?? ''); setStatus(item.status); setNotifyDepartments(Array.isArray(item.notifyDepartments) ? item.notifyDepartments : []); setNotifyEmployeeIds(Array.isArray(item.notifyEmployeeIds) ? item.notifyEmployeeIds : []); } else { setTitle(''); setBody(''); setStatus('draft'); setNotifyDepartments([]); setNotifyEmployeeIds([]); } }, [item]); useEffect(() => { backendApi.getEmployeesList().then((list) => setEmployeesList(Array.isArray(list) ? list : [])).catch(() => setEmployeesList([])); }, []); const toggleDepartment = (d: Department) => { setNotifyDepartments((prev) => (prev.includes(d) ? prev.filter((x) => x !== d) : [...prev, d])); }; const toggleEmployee = (id: string) => { setNotifyEmployeeIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!title.trim()) { onError('Укажите заголовок'); return; } setLoading(true); onError(''); try { if (item) { await backendApi.updateCompanyNews(item.id, { title: title.trim(), body: body.trim() || null, status, notifyDepartments: notifyDepartments.length ? notifyDepartments : undefined, notifyEmployeeIds: notifyEmployeeIds.length ? notifyEmployeeIds : undefined, }); } else { await backendApi.createCompanyNews({ title: title.trim(), body: body.trim() || null, status, notifyDepartments: notifyDepartments.length ? notifyDepartments : undefined, notifyEmployeeIds: notifyEmployeeIds.length ? notifyEmployeeIds : undefined, }); } window.dispatchEvent(new CustomEvent('mkd-news-changed')); onSuccess(); } catch (err: any) { onError(err?.message || 'Не удалось сохранить'); } finally { setLoading(false); } }; return (
e.stopPropagation()}>

{item ? 'Редактировать новость' : 'Создать новость'}

setTitle(e.target.value)} className="w-full px-4 py-2.5 border border-slate-200 rounded-xl text-sm font-medium text-slate-800 placeholder-slate-400 focus:ring-2 focus:ring-primary-500 focus:border-transparent" placeholder="Заголовок новости" />