324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
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';
|
|
import styles from '../styles/Common.module.css';
|
|
|
|
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([]);
|
|
const [smtpServers, setSmtpServers] = useState([]);
|
|
|
|
useEffect(() => {
|
|
fetchCampaigns(page);
|
|
// eslint-disable-next-line
|
|
}, [page]);
|
|
|
|
useEffect(() => {
|
|
fetchGroups();
|
|
fetchVersions();
|
|
fetchSmtpServers();
|
|
// 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 fetchSmtpServers = async () => {
|
|
try {
|
|
const res = await fetch('/api/mail/smtp-servers', {
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok && Array.isArray(data.rows)) {
|
|
setSmtpServers(data.rows);
|
|
} else if (res.ok && Array.isArray(data)) {
|
|
setSmtpServers(data);
|
|
} else {
|
|
setSmtpServers([]);
|
|
}
|
|
} catch {
|
|
setSmtpServers([]);
|
|
}
|
|
};
|
|
|
|
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)
|
|
});
|
|
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: null,
|
|
template_version_id: null,
|
|
subject_override: '',
|
|
status: 'draft',
|
|
scheduled_at: '',
|
|
smtp_server_id: null,
|
|
user_id: user?.id // Добавляем user_id
|
|
});
|
|
};
|
|
|
|
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)
|
|
});
|
|
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 (
|
|
<div className={styles.container}>
|
|
<div className={styles.pageHeader}>
|
|
<h2 className={styles.pageTitle}>Кампания</h2>
|
|
<div className={styles.pageActions}>
|
|
<button
|
|
onClick={handleCreate}
|
|
className={`${styles.button} ${styles.buttonGradient}`}
|
|
>
|
|
+ Добавить кампанию
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{loading && <div className={styles.loading}>Загрузка...</div>}
|
|
{error && <div className={styles.error}>{error}</div>}
|
|
|
|
{!loading && !error && (
|
|
<>
|
|
<table className={styles.table}>
|
|
<thead className={styles.tableHeader}>
|
|
<tr>
|
|
<th className={styles.tableHeaderCell}>ID</th>
|
|
<th className={styles.tableHeaderCell}>Группа</th>
|
|
<th className={styles.tableHeaderCell}>Версия шаблона</th>
|
|
<th className={styles.tableHeaderCell}>Тема</th>
|
|
<th className={styles.tableHeaderCell}>Статус</th>
|
|
<th className={styles.tableHeaderCell}>Запланировано</th>
|
|
<th className={styles.tableHeaderCell}>Действия</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{campaigns.map(c => (
|
|
<tr key={c.id} className={styles.tableRow}>
|
|
<td className={styles.tableCell}>{c.id}</td>
|
|
<td className={styles.tableCell}>{getGroupName(c.group_id)}</td>
|
|
<td className={styles.tableCell}>{getVersionName(c.template_version_id)}</td>
|
|
<td className={styles.tableCell}>{c.subject_override || ''}</td>
|
|
<td className={styles.tableCell}>{c.status}</td>
|
|
<td className={styles.tableCell}>{c.scheduled_at ? new Date(c.scheduled_at).toLocaleString() : ''}</td>
|
|
<td className={styles.tableCellActions}>
|
|
<button
|
|
onClick={() => handleEdit(c)}
|
|
className={`${styles.button} ${styles.buttonPrimary}`}
|
|
style={{ marginRight: '8px' }}
|
|
>
|
|
Редактировать
|
|
</button>
|
|
<button
|
|
onClick={() => handleDelete(c.id)}
|
|
className={`${styles.button} ${styles.buttonDanger}`}
|
|
disabled={deleteLoading === c.id}
|
|
>
|
|
{deleteLoading === c.id ? 'Удаление...' : 'Удалить'}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
{campaigns.length === 0 && (
|
|
<tr>
|
|
<td colSpan={7} className={styles.emptyState}>
|
|
<div className={styles.emptyStateIcon}>📧</div>
|
|
<div className={styles.emptyStateTitle}>Нет кампаний</div>
|
|
<div className={styles.emptyStateText}>Создайте первую кампанию для начала работы</div>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
<Paginator
|
|
page={page}
|
|
total={total}
|
|
pageSize={PAGE_SIZE}
|
|
onPageChange={setPage}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{editCampaign && (
|
|
<EditCampaignModal
|
|
isOpen={!!editCampaign}
|
|
onClose={() => setEditCampaign(null)}
|
|
campaign={editCampaign}
|
|
groups={groups}
|
|
versions={versions}
|
|
smtpServers={smtpServers}
|
|
loading={editLoading}
|
|
onChange={setEditCampaign}
|
|
onSave={handleEditSave}
|
|
getVersionName={getVersionName}
|
|
/>
|
|
)}
|
|
|
|
{createCampaign && (
|
|
<CreateCampaignModal
|
|
isOpen={!!createCampaign}
|
|
onClose={() => setCreateCampaign(null)}
|
|
campaign={createCampaign}
|
|
groups={groups}
|
|
versions={versions}
|
|
smtpServers={smtpServers}
|
|
loading={createLoading}
|
|
onChange={setCreateCampaign}
|
|
onSave={handleCreateSave}
|
|
getVersionName={getVersionName}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default CampaignPage;
|