fix: align daily operations statistics
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { AlertTriangle, Download, MessageSquare, Search, Smartphone } from 'lucide-react';
|
||||
import { AlertTriangle, Download, Info, MessageSquare, Search, Smartphone } from 'lucide-react';
|
||||
import { adminApi, type SmsMessageRecord, type SmsMessageSegmentAudit, type SmsReceiptRecord, type SmsSubmitRecord } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Pagination, Select, Tag, type DateRangeValue } from '@/components/ui';
|
||||
import { formatCents } from '@/utils/currency';
|
||||
@@ -8,8 +8,9 @@ const statusLabelMap: Record<string, string> = {
|
||||
delivered: '发送成功',
|
||||
queued: '排队中',
|
||||
submitted: '已提交',
|
||||
submit_failed: '提交失败',
|
||||
unknown: '未知',
|
||||
failed: '失败',
|
||||
failed: '送达失败',
|
||||
rejected: '已拒绝',
|
||||
};
|
||||
|
||||
@@ -17,6 +18,7 @@ const statusToneMap: Record<string, 'success' | 'neutral' | 'danger' | 'info'> =
|
||||
delivered: 'success',
|
||||
queued: 'info',
|
||||
submitted: 'info',
|
||||
submit_failed: 'danger',
|
||||
unknown: 'neutral',
|
||||
failed: 'danger',
|
||||
rejected: 'danger',
|
||||
@@ -26,6 +28,7 @@ const statusDotClassMap: Record<string, string> = {
|
||||
delivered: 'is-success',
|
||||
queued: 'is-unknown',
|
||||
submitted: 'is-unknown',
|
||||
submit_failed: 'is-failed',
|
||||
unknown: 'is-unknown',
|
||||
failed: 'is-failed',
|
||||
rejected: 'is-failed',
|
||||
@@ -85,6 +88,36 @@ function getStatusLabel(status?: string | null) {
|
||||
return status ? (statusLabelMap[status] ?? status) : '-';
|
||||
}
|
||||
|
||||
function isSubmitFailure(record: SmsMessageRecord) {
|
||||
return record.status === 'submit_failed' || ['rejected', 'timeout'].includes(record.submitStatus ?? '');
|
||||
}
|
||||
|
||||
function getRecordStatus(record: SmsMessageRecord) {
|
||||
return isSubmitFailure(record) ? 'submit_failed' : record.status;
|
||||
}
|
||||
|
||||
function getRecordStatusLabel(record: SmsMessageRecord) {
|
||||
return getStatusLabel(getRecordStatus(record));
|
||||
}
|
||||
|
||||
function getReceiptNotice(record: SmsMessageRecord) {
|
||||
const hasPlatformFailureReceipt = (record.receiptRecords ?? []).some((receipt) =>
|
||||
receipt.gatewayMessageId.startsWith('PLATFORM:') && receipt.rawStatus === 'REJECTD',
|
||||
);
|
||||
if (hasPlatformFailureReceipt) {
|
||||
const deliveries = (record.downstreamDeliveries ?? []).filter((item) => item.deliveryType === 'receipt');
|
||||
if (deliveries.some((item) => item.status === 'delivered')) {
|
||||
return '平台已生成失败回执并通知企业';
|
||||
}
|
||||
const deliveryStatuses = Array.from(new Set(deliveries.map((item) => item.status)));
|
||||
return `平台已生成失败回执,企业通知状态:${deliveryStatuses.join('、') || '待投递'}`;
|
||||
}
|
||||
if (!record.tenantId && !record.applicationId && record.messageId.startsWith('MSG-TEST-')) {
|
||||
return '运营端通道测试,无需生成客户回执';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCarrierLabel(carrier?: string | null) {
|
||||
return carrier ? (carrierLabelMap[carrier] ?? carrier) : '-';
|
||||
}
|
||||
@@ -121,7 +154,8 @@ function buildRouteRows(record: SmsMessageRecord): RouteRow[] {
|
||||
}];
|
||||
}
|
||||
|
||||
function StatusLine({ status }: { status: string }) {
|
||||
function StatusLine({ record }: { record: SmsMessageRecord }) {
|
||||
const status = getRecordStatus(record);
|
||||
return (
|
||||
<span className="admin-sms-record-status">
|
||||
<i className={statusDotClassMap[status] ?? 'is-unknown'} />
|
||||
@@ -149,7 +183,7 @@ function downloadCsv(records: SmsMessageRecord[]) {
|
||||
record.billingUnits,
|
||||
formatCents(record.amountCents),
|
||||
record.channel?.name ?? record.channelId ?? '',
|
||||
getStatusLabel(record.status),
|
||||
getRecordStatusLabel(record),
|
||||
getTime(record.deliveredAt),
|
||||
record.content,
|
||||
]),
|
||||
@@ -176,6 +210,8 @@ function SendDetailModal({
|
||||
}) {
|
||||
const routeRows = buildRouteRows(record);
|
||||
const sentAccessNumber = `${record.channel?.srcId ?? ''}${record.applicationExtension ?? ''}`;
|
||||
const displayStatus = getRecordStatus(record);
|
||||
const receiptNotice = getReceiptNotice(record);
|
||||
return (
|
||||
<Modal
|
||||
footer={<Button onClick={onClose} variant="ghost">关闭</Button>}
|
||||
@@ -188,7 +224,7 @@ function SendDetailModal({
|
||||
<div className="admin-sms-detail-overview">
|
||||
<div>
|
||||
<span>最终状态</span>
|
||||
<Tag tone={record.status === 'delivered' ? 'success' : ['failed', 'rejected'].includes(record.status) ? 'danger' : 'info'}>{getStatusLabel(record.status)}</Tag>
|
||||
<Tag tone={statusToneMap[displayStatus] ?? 'info'}>{getRecordStatusLabel(record)}</Tag>
|
||||
</div>
|
||||
<div>
|
||||
<span>提交状态</span>
|
||||
@@ -219,6 +255,12 @@ function SendDetailModal({
|
||||
<strong>{sentAccessNumber || '-'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{receiptNotice ? (
|
||||
<div className="admin-sms-detail-notice" role="status">
|
||||
<Info size={20} />
|
||||
<strong>{receiptNotice}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
<section>
|
||||
<h3><MessageSquare size={18} /> 短信内容</h3>
|
||||
<p className="admin-sms-detail-content">{record.content}</p>
|
||||
@@ -260,7 +302,7 @@ function SendDetailModal({
|
||||
<h3>状态信息</h3>
|
||||
<div className="admin-sms-detail-status-grid">
|
||||
<div><span>消息编号</span><strong>{record.messageId}</strong></div>
|
||||
<div><span>发送状态</span><strong>{getStatusLabel(record.status)}</strong></div>
|
||||
<div><span>发送状态</span><strong>{getRecordStatusLabel(record)}</strong></div>
|
||||
<div><span>提交状态</span><strong>{record.submitStatus ?? '-'}</strong></div>
|
||||
<div><span>回执状态</span><strong>{record.receiptStatus ?? '-'}</strong></div>
|
||||
</div>
|
||||
@@ -377,7 +419,11 @@ export function AdminSmsRecordsPage() {
|
||||
|
||||
const enterpriseOptions = useMemo(() => {
|
||||
const tenants = new Map<string, string>();
|
||||
records.forEach((record) => tenants.set(record.tenantId, record.tenant?.name ?? record.tenantId));
|
||||
records.forEach((record) => {
|
||||
if (record.tenantId) {
|
||||
tenants.set(record.tenantId, record.tenant?.name ?? record.tenantId);
|
||||
}
|
||||
});
|
||||
return [{ label: '全部企业', value: 'all' }, ...Array.from(tenants, ([value, label]) => ({ label, value }))];
|
||||
}, [records]);
|
||||
|
||||
@@ -441,7 +487,8 @@ export function AdminSmsRecordsPage() {
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '发送成功', value: 'delivered' },
|
||||
{ label: '未知', value: 'unknown' },
|
||||
{ label: '失败', value: 'failed' },
|
||||
{ label: '提交失败', value: 'submit_failed' },
|
||||
{ label: '送达失败', value: 'failed' },
|
||||
]}
|
||||
value={status}
|
||||
/>
|
||||
@@ -460,10 +507,10 @@ export function AdminSmsRecordsPage() {
|
||||
<article className="admin-sms-record-card" key={record.id}>
|
||||
<header>
|
||||
<div className="admin-sms-record-sender">
|
||||
<strong>{record.tenant?.name ?? record.tenantId}</strong>
|
||||
<strong>{record.tenant?.name ?? record.tenantId ?? '运营端通道测试'}</strong>
|
||||
<span>{record.application?.name ?? record.applicationId ?? '-'}</span>
|
||||
</div>
|
||||
<StatusLine status={record.status} />
|
||||
<StatusLine record={record} />
|
||||
<time>{getDate(record.queuedAt)} {getClock(record.queuedAt)}</time>
|
||||
</header>
|
||||
<p className="admin-sms-record-content">{record.content}</p>
|
||||
|
||||
Reference in New Issue
Block a user