fix: harden admin and CMPP delivery workflows

This commit is contained in:
hectorzhao
2026-07-15 11:21:02 +08:00
parent 00b6d95752
commit e47432bc9d
34 changed files with 878 additions and 226 deletions
+27 -49
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { Download, MessageSquare, Search, Smartphone } from 'lucide-react';
import { AlertTriangle, Download, 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, Table, type DateRangeValue, type TableColumn } from '@/components/ui';
@@ -251,8 +251,13 @@ function SendDetailModal({
<div><span></span><strong>{getStatusLabel(record.status)}</strong></div>
<div><span></span><strong>{record.submitStatus ?? '-'}</strong></div>
<div><span></span><strong>{record.receiptStatus ?? '-'}</strong></div>
<div><span></span><strong>{record.errorMessage ?? record.errorCode ?? '-'}</strong></div>
</div>
{['failed', 'rejected'].includes(record.status) || record.errorMessage || record.errorCode ? (
<div className="admin-sms-detail-failure" role="alert">
<AlertTriangle size={20} />
<div><span></span><strong>{record.errorMessage ?? record.errorCode ?? '未返回明确失败原因'}</strong></div>
</div>
) : null}
</section>
<section>
@@ -415,53 +420,26 @@ export function AdminSmsRecordsPage() {
<div className="admin-sms-record-toolbar">
<Button icon={<Download size={16} />} onClick={() => downloadCsv(filteredRows)} variant="ghost">CSV</Button>
</div>
<div className="ui-table-wrap">
<table className="ui-table admin-sms-record-table">
<thead>
<tr>
<th style={{ width: '170px' }}></th>
<th></th>
<th style={{ width: '170px' }}></th>
<th style={{ width: '300px' }}></th>
<th style={{ width: '120px' }}></th>
</tr>
</thead>
<tbody>
{filteredRows.length === 0 ? (
<tr>
<td className="ui-table__empty" colSpan={5}></td>
</tr>
) : visibleRows.map((record) => (
<tr key={record.id}>
<td>
<div className="admin-sms-record-sender">
<strong>{record.tenant?.name ?? record.tenantId}</strong>
<span>{record.application?.name ?? record.applicationId ?? '-'}</span>
<small>{getDate(record.queuedAt)}<br />{getClock(record.queuedAt)}</small>
</div>
</td>
<td><p className="admin-sms-record-content">{record.content}</p></td>
<td>
<div className="admin-sms-record-phone">
<strong>{record.phoneNumber}</strong>
<span>{record.province ?? '-'} {getCarrierLabel(record.carrier)}</span>
<small>{record.content.length}/{record.billingUnits}</small>
</div>
</td>
<td>
<div className="admin-sms-record-channel">
<strong>{record.channel?.name ?? record.channelId ?? '-'}</strong>
<StatusLine status={record.status} />
<span>{getTime(record.deliveredAt)}</span>
</div>
</td>
<td style={{ textAlign: 'right' }}>
<button className="admin-sms-record-detail-link" onClick={() => setSelectedRecord(record)} type="button"></button>
</td>
</tr>
))}
</tbody>
</table>
<div className="admin-sms-record-list">
{filteredRows.length === 0 ? <div className="ui-table__empty"></div> : visibleRows.map((record) => (
<article className="admin-sms-record-card" key={record.id}>
<header>
<div className="admin-sms-record-sender">
<strong>{record.tenant?.name ?? record.tenantId}</strong>
<span>{record.application?.name ?? record.applicationId ?? '-'}</span>
</div>
<StatusLine status={record.status} />
<time>{getDate(record.queuedAt)} {getClock(record.queuedAt)}</time>
</header>
<p className="admin-sms-record-content">{record.content}</p>
<div className="admin-sms-record-card__meta">
<div><span></span><strong>{record.phoneNumber}</strong><small>{record.province ?? '-'} · {getCarrierLabel(record.carrier)}</small></div>
<div><span></span><strong>{record.billingUnits} / ¥{(record.amountCents / 100).toFixed(3)}</strong><small>{record.content.length} </small></div>
<div><span></span><strong>{record.channel?.name ?? record.channelId ?? '-'}</strong><small> {getTime(record.deliveredAt)}</small></div>
</div>
<footer><button className="admin-sms-record-detail-link" onClick={() => setSelectedRecord(record)} type="button"></button></footer>
</article>
))}
</div>
<Pagination
nextDisabled={currentPage >= totalPages}