feat: complete signature and drainage reporting workflows
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ArrowLeft, ChevronDown, ChevronRight, Eye, FileSliders, Plus, Search } from 'lucide-react';
|
||||
import { ArrowLeft, Eye, FileSliders, Search } from 'lucide-react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { adminApi, type AdminChannel, type ChannelReportField, type ClientSmsSignature, type DictionaryItem, type ReportRecord, type ReportTask } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Input, Modal, Select, Tag, Textarea } from '@/components/ui';
|
||||
@@ -16,6 +16,8 @@ const statusMeta: Record<string, { label: string; tone: 'success' | 'danger' | '
|
||||
pending: { label: '未报备', tone: 'neutral' },
|
||||
waiting_material: { label: '资料待补充', tone: 'warning' },
|
||||
exporting: { label: '报备中', tone: 'warning' },
|
||||
reporting: { label: '报备中', tone: 'warning' },
|
||||
abandoned: { label: '已放弃', tone: 'neutral' },
|
||||
partial_success: { label: '部分成功', tone: 'warning' },
|
||||
filing: { label: '报备中', tone: 'warning' },
|
||||
};
|
||||
@@ -72,7 +74,6 @@ export function AdminChannelReportPage() {
|
||||
const [signatures, setSignatures] = useState<ClientSmsSignature[]>([]);
|
||||
const [fields, setFields] = useState<ChannelReportField[]>([]);
|
||||
const [libraryFields, setLibraryFields] = useState<DictionaryItem[]>([]);
|
||||
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [status, setStatus] = useState('all');
|
||||
const [detail, setDetail] = useState<{ task: ReportTask; signature?: ClientSmsSignature; drainage?: DrainageItem }>();
|
||||
@@ -109,7 +110,8 @@ export function AdminChannelReportPage() {
|
||||
const signatureMap = useMemo(() => new Map(signatures.map((item) => [item.id, item])), [signatures]);
|
||||
const visibleTasks = useMemo(() => tasks.filter((task) => {
|
||||
const signature = signatureMap.get(task.signatureId);
|
||||
const matchesKeyword = !keyword.trim() || [signature?.name, signature?.tenant?.name, signature?.application?.name].some((value) => String(value ?? '').includes(keyword.trim()));
|
||||
const drainage = drainageItems(signature).find((item) => String(item.id) === task.drainageItemId);
|
||||
const matchesKeyword = !keyword.trim() || [signature?.name, signature?.tenant?.name, signature?.application?.name, drainage?.siteName, drainage?.url].some((value) => String(value ?? '').includes(keyword.trim()));
|
||||
return matchesKeyword && (status === 'all' || task.status === status);
|
||||
}), [keyword, signatureMap, status, tasks]);
|
||||
|
||||
@@ -124,17 +126,9 @@ export function AdminChannelReportPage() {
|
||||
.catch((failure: Error) => setError(failure.message || '报备字段保存失败'));
|
||||
}
|
||||
|
||||
function toggle(signatureId: string) {
|
||||
setExpanded((current) => {
|
||||
const next = new Set(current);
|
||||
next.has(signatureId) ? next.delete(signatureId) : next.add(signatureId);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function saveTaskStatus() {
|
||||
if (!statusTask) return;
|
||||
adminApi.changeReportTaskStatuses({ items: [{ signatureId: statusTask.signatureId, channelId: statusTask.channelId, status: nextStatus }], reason: statusReason })
|
||||
adminApi.changeReportTaskStatuses({ items: [{ signatureId: statusTask.signatureId, channelId: statusTask.channelId, reportType: statusTask.reportType, drainageItemId: statusTask.drainageItemId ?? undefined, status: nextStatus }], reason: statusReason })
|
||||
.then(() => { setStatusTask(undefined); setStatusReason(''); loadData(); })
|
||||
.catch((failure: Error) => setError(failure.message || '报备状态保存失败'));
|
||||
}
|
||||
@@ -159,37 +153,27 @@ export function AdminChannelReportPage() {
|
||||
<div className="channel-report-filter-grid">
|
||||
<Input label="签名/企业/应用" onChange={(event) => setKeyword(event.target.value)} placeholder="请输入关键词" prefix={<Search size={16} />} value={keyword} />
|
||||
<Select label="报备状态" onChange={(event) => setStatus(event.target.value)} options={[{ label: '全部状态', value: 'all' }, ...Object.entries(statusMeta).filter(([value]) => ['approved', 'failed', 'pending', 'waiting_material', 'exporting', 'partial_success'].includes(value)).map(([value, meta]) => ({ label: meta.label, value }))]} value={status} />
|
||||
<div><strong>真实数据口径</strong><p className="muted">仅展示该通道已生成的签名报备任务;引流信息从签名资料中展开。</p></div>
|
||||
<div><strong>真实数据口径</strong><p className="muted">签名和引流信息分别展示在该通道上的真实报备任务。</p></div>
|
||||
</div>
|
||||
<div className="channel-report-filter-footer"><span>共 {visibleTasks.length} 条签名报备任务</span><div><Button onClick={() => { setKeyword(''); setStatus('all'); }} variant="ghost">重置</Button><Button icon={<Search size={16} />} onClick={loadData}>查询</Button></div></div>
|
||||
<div className="channel-report-filter-footer"><span>共 {visibleTasks.length} 条报备任务</span><div><Button onClick={() => { setKeyword(''); setStatus('all'); }} variant="ghost">重置</Button><Button icon={<Search size={16} />} onClick={loadData}>查询</Button></div></div>
|
||||
</div>
|
||||
|
||||
<div className="surface channel-report-table">
|
||||
<div className="channel-report-table__head"><span /><span>短信签名 / 引流信息</span><span>报备状态</span><span>提交时间</span><span>报备时间</span><span>最后更新</span><span>发送统计</span><span>操作</span></div>
|
||||
{visibleTasks.length === 0 ? <div className="channel-report-empty">当前通道暂无真实报备任务</div> : visibleTasks.map((task) => {
|
||||
const signature = signatureMap.get(task.signatureId);
|
||||
const links = drainageItems(signature);
|
||||
const isExpanded = expanded.has(task.signatureId);
|
||||
const drainage = task.reportType === 'drainage' ? drainageItems(signature).find((item) => String(item.id) === task.drainageItemId) : undefined;
|
||||
const exported = lastRecord(task.id, 'export');
|
||||
const imported = lastRecord(task.id, 'receipt_import');
|
||||
return <div key={task.id}>
|
||||
<div className="channel-report-row channel-report-row--signature">
|
||||
return <div className={`channel-report-row ${drainage ? 'channel-report-row--drainage' : 'channel-report-row--signature'}`} key={task.id}>
|
||||
<span />
|
||||
<div className="channel-report-name"><button disabled={links.length === 0} onClick={() => toggle(task.signatureId)} type="button">{isExpanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}</button><span><strong>{formatSignatureName(signature?.name ?? task.signature?.name)}</strong><small>{signature?.tenant?.name ?? task.tenantId}{links.length ? <b>{links.length}</b> : null}</small></span></div>
|
||||
<div className={`channel-report-name ${drainage ? 'channel-report-name--flow' : ''}`}>{drainage ? <i /> : null}<span><strong>{drainage ? String(drainage.siteName || drainage.url || '引流信息') : formatSignatureName(signature?.name ?? task.signature?.name)}</strong><small>{drainage ? `${String(drainage.url ?? '')} · ${formatSignatureName(signature?.name ?? task.signature?.name)}` : signature?.tenant?.name ?? task.tenantId}</small></span></div>
|
||||
<ReportStatus value={task.status} />
|
||||
<DateTime value={task.createdAt} />
|
||||
<DateTime value={drainage?.submittedAt ?? task.createdAt} />
|
||||
<DateTime value={imported?.createdAt ?? exported?.createdAt} />
|
||||
<DateTime value={task.updatedAt} />
|
||||
<div className="channel-report-stats"><span>成功<strong className="is-success">-</strong><b>暂无统计</b></span><span>未知<strong className="is-warning">-</strong><b>暂无统计</b></span><span>失败<strong className="is-danger">-</strong><b>暂无统计</b></span></div>
|
||||
<div className="channel-report-actions"><button onClick={() => setDetail({ task, signature })} type="button"><Eye size={16} />查看详情</button><button className="is-warning" onClick={() => { setStatusTask(task); setNextStatus(task.status); }} type="button">修改状态</button></div>
|
||||
</div>
|
||||
{isExpanded ? links.map((link, index) => <div className="channel-report-row channel-report-row--drainage" key={String(link.id ?? index)}>
|
||||
<span />
|
||||
<div className="channel-report-name channel-report-name--flow"><i /><span><strong>{String(link.siteName || link.url || `引流信息 ${index + 1}`)}</strong><small>{String(link.url ?? '')}</small></span></div>
|
||||
<ReportStatus value={task.status} /><DateTime value={link.submittedAt ?? signature?.updatedAt} /><DateTime value={imported?.createdAt} /><DateTime value={signature?.updatedAt} />
|
||||
<div className="channel-report-stats"><span>成功<strong className="is-success">-</strong><b>暂无统计</b></span><span>未知<strong className="is-warning">-</strong><b>暂无统计</b></span><span>失败<strong className="is-danger">-</strong><b>暂无统计</b></span></div>
|
||||
<div className="channel-report-actions"><button onClick={() => setDetail({ task, signature, drainage: link })} type="button"><Eye size={16} />查看详情</button></div>
|
||||
</div>) : null}
|
||||
<div className="channel-report-actions"><button onClick={() => setDetail({ task, signature, drainage })} type="button"><Eye size={16} />查看详情</button><button className="is-warning" onClick={() => { setStatusTask(task); setNextStatus(task.status); }} type="button">修改状态</button></div>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user