import React, { useState, useEffect } from 'react'; import { useUser } from '../context/UserContext'; import EditCampaignModal from '../modals/EditCampaignModal'; import CreateCampaignModal from '../modals/CreateCampaignModal'; import Paginator from '../components/Paginator'; const PAGE_SIZE = 10; function CampaignPage() { const { token, user } = useUser(); const [campaigns, setCampaigns] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [editCampaign, setEditCampaign] = useState(null); const [editLoading, setEditLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(null); const [createCampaign, setCreateCampaign] = useState(null); const [createLoading, setCreateLoading] = useState(false); const [groups, setGroups] = useState([]); const [versions, setVersions] = useState([]); useEffect(() => { fetchCampaigns(page); // eslint-disable-next-line }, [page]); useEffect(() => { fetchGroups(); fetchVersions(); // eslint-disable-next-line }, []); const fetchCampaigns = async (page) => { setLoading(true); setError(''); try { const offset = (page - 1) * PAGE_SIZE; const res = await fetch(`/api/mail/campaigns?limit=${PAGE_SIZE}&offset=${offset}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} }); const data = await res.json(); if (!res.ok) { setError(data.error || 'Ошибка загрузки'); setCampaigns([]); setTotal(0); } else { setCampaigns(Array.isArray(data.rows) ? data.rows : []); setTotal(data.count || 0); } } catch (e) { setError('Ошибка сети'); setCampaigns([]); setTotal(0); } finally { setLoading(false); } }; const fetchGroups = async () => { try { const res = await fetch('/api/mail/mailing-groups', { headers: token ? { Authorization: `Bearer ${token}` } : {} }); const data = await res.json(); if (res.ok && Array.isArray(data.rows)) { setGroups(data.rows); } else if (res.ok && Array.isArray(data)) { setGroups(data); } else { setGroups([]); } } catch { setGroups([]); } }; const fetchVersions = async () => { try { const res = await fetch('/api/mail/email-template-versions', { headers: token ? { Authorization: `Bearer ${token}` } : {} }); const data = await res.json(); if (res.ok && Array.isArray(data.rows)) { setVersions(data.rows); } else if (res.ok && Array.isArray(data)) { setVersions(data); } else { setVersions([]); } } catch { setVersions([]); } }; const handleDelete = async (id) => { if (!window.confirm('Удалить кампанию?')) return; setDeleteLoading(id); try { const res = await fetch(`/api/mail/campaigns/${id}`, { method: 'DELETE', headers: token ? { Authorization: `Bearer ${token}` } : {} }); if (!res.ok) { const data = await res.json(); alert(data.error || 'Ошибка удаления'); } else { fetchCampaigns(page); } } catch (e) { alert('Ошибка сети'); } finally { setDeleteLoading(null); } }; const handleEdit = (c) => { setEditCampaign(c); }; const handleEditSave = async (e) => { e.preventDefault(); setEditLoading(true); try { const res = await fetch(`/api/mail/campaigns/${editCampaign.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify({ ...editCampaign, user_id: user?.id }) }); if (!res.ok) { const data = await res.json(); alert(data.error || 'Ошибка обновления'); } else { setEditCampaign(null); fetchCampaigns(page); } } catch (e) { alert('Ошибка сети'); } finally { setEditLoading(false); } }; const handleCreate = () => { setCreateCampaign({ group_id: groups[0]?.id || '', template_version_id: versions[0]?.id || '', subject_override: '', scheduled_at: '', status: 'draft', }); }; const handleCreateSave = async (e) => { e.preventDefault(); setCreateLoading(true); try { const res = await fetch('/api/mail/campaigns', { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify({ ...createCampaign, user_id: user?.id }) }); if (!res.ok) { const data = await res.json(); alert(data.error || 'Ошибка создания'); } else { setCreateCampaign(null); fetchCampaigns(page); } } catch (e) { alert('Ошибка сети'); } finally { setCreateLoading(false); } }; const getGroupName = (id) => groups.find(g => g.id === id)?.name || id; const getVersionName = (id) => { const v = versions.find(v => v.id === id); return v ? `#${v.id} ${v.subject}` : id; }; return (

Кампания

{loading &&
Загрузка...
} {error &&
{error}
} {!loading && !error && ( <> {campaigns.map(c => ( ))} {campaigns.length === 0 && ( )}
ID Группа Версия шаблона Тема Статус Запланировано
{c.id} {getGroupName(c.group_id)} {getVersionName(c.template_version_id)} {c.subject_override || ''} {c.status} {c.scheduled_at ? new Date(c.scheduled_at).toLocaleString() : ''}
Нет данных
)} {editCampaign && ( setEditCampaign(null)} campaign={editCampaign} groups={groups} versions={versions} loading={editLoading} onChange={setEditCampaign} onSave={handleEditSave} getVersionName={getVersionName} /> )} {createCampaign && ( setCreateCampaign(null)} campaign={createCampaign} groups={groups} versions={versions} loading={createLoading} onChange={setCreateCampaign} onSave={handleCreateSave} getVersionName={getVersionName} /> )}
); } const thStyle = { padding: '10px 16px', textAlign: 'left', fontWeight: 600, borderBottom: '2px solid #e5e7eb', background: '#f3f4f6' }; const tdStyle = { padding: '10px 16px', background: '#fff' }; const btnStyle = { marginRight: 8, padding: '6px 12px', borderRadius: 6, border: 'none', background: '#6366f1', color: '#fff', cursor: 'pointer', fontWeight: 500 }; const modalOverlayStyle = { position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.18)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }; const modalStyle = { background: '#fff', borderRadius: 14, padding: '32px 28px 24px 28px', minWidth: 340, boxShadow: '0 12px 48px 0 rgba(31,38,135,0.22)', position: 'relative', animation: 'modalIn 0.18s cubic-bezier(.4,1.3,.6,1)'}; const closeBtnStyle = { position: 'absolute', top: 12, right: 16, background: 'none', border: 'none', fontSize: 26, color: '#6366f1', cursor: 'pointer', fontWeight: 700, lineHeight: 1, padding: 0, zIndex: 2, opacity: 0.8, transition: 'opacity 0.2s' }; const labelStyle = { fontWeight: 500, color: '#374151', fontSize: 15, display: 'flex', flexDirection: 'column', gap: 4 }; const inputStyle = { marginTop: 4, padding: '10px 12px', borderRadius: 8, border: '1.5px solid #c7d2fe', fontSize: 16, outline: 'none', background: '#f8fafc', transition: 'border 0.2s' }; const saveBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; const cancelBtnStyle = { background: '#f3f4f6', color: '#6366f1', border: 'none', borderRadius: 8, padding: '10px 18px', fontSize: 16, fontWeight: 600, cursor: 'pointer', transition: 'background 0.2s' }; const addBtnStyle = { background: 'linear-gradient(90deg, #6366f1 0%, #06b6d4 100%)', color: '#fff', border: 'none', borderRadius: 8, padding: '10px 22px', fontSize: 16, fontWeight: 600, cursor: 'pointer', boxShadow: '0 2px 8px 0 rgba(99,102,241,0.10)', transition: 'background 0.2s' }; export default CampaignPage;