180 lines
6.7 KiB
TypeScript
180 lines
6.7 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { adminApi } from '@/api/adminApi';
|
|
import { Breadcrumb, type DateRangeValue } from '@/components/ui';
|
|
import { SmsTaskFilter } from './sms-task-progress/SmsTaskFilter';
|
|
import { SmsTaskTable } from './sms-task-progress/SmsTaskTable';
|
|
import { TaskDetailModal } from './sms-task-progress/TaskDetailModal';
|
|
import { TaskPhoneListModal } from './sms-task-progress/TaskPhoneListModal';
|
|
import { TerminateTaskModal } from './sms-task-progress/TerminateTaskModal';
|
|
import { mapTask } from './sms-task-progress/taskModel';
|
|
import type { SmsTask } from './sms-task-progress/taskTypes';
|
|
import './sms-task-progress/AdminSmsTaskProgressPage.css';
|
|
|
|
const pageSize = 10;
|
|
|
|
export function AdminSmsTaskProgressPage() {
|
|
const [tasks, setTasks] = useState<SmsTask[]>([]);
|
|
const [keyword, setKeyword] = useState('');
|
|
const [enterprise, setEnterprise] = useState('all');
|
|
const [application, setApplication] = useState('all');
|
|
const [status, setStatus] = useState('all');
|
|
const [submittedDateRange, setSubmittedDateRange] = useState<DateRangeValue>({});
|
|
const [hoveredTaskId, setHoveredTaskId] = useState<string | null>(null);
|
|
const [page, setPage] = useState(1);
|
|
const [selectedTask, setSelectedTask] = useState<SmsTask | null>(null);
|
|
const [phoneTarget, setPhoneTarget] = useState<SmsTask | null>(null);
|
|
const [terminateTarget, setTerminateTarget] = useState<SmsTask | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
const [total, setTotal] = useState(0);
|
|
const [filterTenants, setFilterTenants] = useState<string[]>([]);
|
|
const [filterApplications, setFilterApplications] = useState<Array<{ tenantName: string; name: string }>>([]);
|
|
|
|
function loadTasks(targetPage = page) {
|
|
setLoading(true);
|
|
adminApi.listAdminBatchTasksPage({
|
|
keyword: keyword || undefined,
|
|
enterpriseKeyword: enterprise === 'all' ? undefined : enterprise,
|
|
applicationKeyword: application === 'all' ? undefined : application,
|
|
status: status === 'all' ? undefined : status,
|
|
createdAtFrom: submittedDateRange.start || undefined,
|
|
createdAtTo: submittedDateRange.end || undefined,
|
|
page: targetPage,
|
|
pageSize,
|
|
})
|
|
.then((result) => {
|
|
setTasks(result.items.map(mapTask));
|
|
setTotal(result.total);
|
|
setError('');
|
|
})
|
|
.catch((reason: Error) => setError(reason.message || '短信任务进度加载失败'))
|
|
.finally(() => setLoading(false));
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadTasks(page);
|
|
}, [page]);
|
|
|
|
useEffect(() => {
|
|
Promise.all([adminApi.listTenantOptions(), adminApi.listEnterpriseApplicationOptions()])
|
|
.then(([tenants, applications]) => {
|
|
const tenantNameById = new Map(tenants.map((item) => [item.id, item.name]));
|
|
setFilterTenants(tenants.filter((item) => item.status !== 'deleted').map((item) => item.name));
|
|
setFilterApplications(applications
|
|
.filter((item) => item.status !== 'deleted')
|
|
.map((item) => ({ tenantName: tenantNameById.get(item.tenantId) ?? '', name: item.name })));
|
|
})
|
|
.catch((failure: Error) => setError(failure.message || '任务筛选项加载失败'));
|
|
}, []);
|
|
|
|
const enterpriseOptions = useMemo(
|
|
() => [{ label: '全部企业', value: 'all' }, ...filterTenants.map((name) => ({ label: name, value: name }))],
|
|
[filterTenants],
|
|
);
|
|
|
|
const applicationOptions = useMemo(() => {
|
|
const names = Array.from(new Set(filterApplications
|
|
.filter((item) => enterprise === 'all' || item.tenantName === enterprise)
|
|
.map((item) => item.name)));
|
|
return [{ label: '全部应用', value: 'all' }, ...names.map((name) => ({ label: name, value: name }))];
|
|
}, [enterprise, filterApplications]);
|
|
|
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
const currentPage = Math.min(page, totalPages);
|
|
|
|
function resetFilters() {
|
|
setKeyword('');
|
|
setEnterprise('all');
|
|
setApplication('all');
|
|
setStatus('all');
|
|
setSubmittedDateRange({});
|
|
}
|
|
|
|
function terminateTask(task: SmsTask) {
|
|
adminApi.terminateAdminBatchTask(task.backendId)
|
|
.then(() => {
|
|
setTerminateTarget(null);
|
|
setSelectedTask(null);
|
|
loadTasks();
|
|
})
|
|
.catch((reason: Error) => setError(reason.message || '发送批次终止失败'));
|
|
}
|
|
|
|
return (
|
|
<section className="page-stack admin-sms-task-page">
|
|
<div className="page-heading">
|
|
<div>
|
|
<Breadcrumb items={['发送任务', '短信任务进度']} />
|
|
<h1>短信任务进度</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<SmsTaskFilter
|
|
application={application}
|
|
applicationOptions={applicationOptions}
|
|
enterprise={enterprise}
|
|
enterpriseOptions={enterpriseOptions}
|
|
keyword={keyword}
|
|
status={status}
|
|
statusOptions={[
|
|
{ label: '全部状态', value: 'all' },
|
|
{ label: '待审核', value: 'pending_review' },
|
|
{ label: '等待定时发送', value: 'scheduled' },
|
|
{ label: '排队中', value: 'queued' },
|
|
{ label: '发送中', value: 'sending' },
|
|
{ label: '已完成', value: 'finished' },
|
|
{ label: '失败', value: 'failed' },
|
|
{ label: '已拒绝', value: 'rejected' },
|
|
{ label: '已终止', value: 'canceled' },
|
|
]}
|
|
submittedDateRange={submittedDateRange}
|
|
onApplicationChange={setApplication}
|
|
onEnterpriseChange={(value) => {
|
|
setEnterprise(value);
|
|
setApplication('all');
|
|
}}
|
|
onKeywordChange={setKeyword}
|
|
onStatusChange={setStatus}
|
|
onQuery={() => {
|
|
if (page !== 1) setPage(1);
|
|
else loadTasks(1);
|
|
}}
|
|
onReset={resetFilters}
|
|
onSubmittedDateRangeChange={setSubmittedDateRange}
|
|
/>
|
|
|
|
{error ? <p className="form-error">{error}</p> : null}
|
|
|
|
<SmsTaskTable
|
|
currentPage={currentPage}
|
|
hoveredTaskId={hoveredTaskId}
|
|
loading={loading}
|
|
tasks={tasks}
|
|
total={total}
|
|
totalPages={totalPages}
|
|
onHoverTask={setHoveredTaskId}
|
|
onOpenDetail={setSelectedTask}
|
|
onOpenPhones={setPhoneTarget}
|
|
onPageChange={setPage}
|
|
onTerminate={setTerminateTarget}
|
|
/>
|
|
|
|
{selectedTask ? <TaskDetailModal onClose={() => setSelectedTask(null)} task={selectedTask} /> : null}
|
|
{phoneTarget ? (
|
|
<TaskPhoneListModal
|
|
onClose={() => setPhoneTarget(null)}
|
|
onError={setError}
|
|
task={phoneTarget}
|
|
/>
|
|
) : null}
|
|
{terminateTarget ? (
|
|
<TerminateTaskModal
|
|
onCancel={() => setTerminateTarget(null)}
|
|
onConfirm={() => terminateTask(terminateTarget)}
|
|
task={terminateTarget}
|
|
/>
|
|
) : null}
|
|
</section>
|
|
);
|
|
}
|