feat: add phone frequency controls and modularize codebase

This commit is contained in:
hectorzhao
2026-07-31 22:25:23 +08:00
parent 0af671b4ed
commit ca4f591a13
216 changed files with 41579 additions and 23694 deletions
@@ -0,0 +1,140 @@
import { AlertTriangle, Info, MessageSquare } from 'lucide-react';
import type { SmsMessageRecord, SmsMessageSegmentAudit } from '@/api/adminApi';
import { Button, Modal, Tag } from '@/components/ui';
import {
buildRouteRows,
getCarrierLabel,
getReceiptNotice,
getRecordStatus,
getRecordStatusLabel,
getTime,
statusToneMap,
} from './smsRecordModel';
type SendDetailModalProps = {
record: SmsMessageRecord;
segmentAudits: SmsMessageSegmentAudit[];
segmentLoading: boolean;
onClose: () => void;
};
export function SendDetailModal({
record,
segmentAudits,
segmentLoading,
onClose,
}: SendDetailModalProps) {
const routeRows = buildRouteRows(record, segmentAudits);
const channelGroupNames = Array.from(new Set(routeRows.map((route) => route.channelGroup).filter(Boolean)));
const orderedSegmentAudits = [...segmentAudits].sort((left, right) => {
const timeDiff = new Date(left.createdAt).getTime() - new Date(right.createdAt).getTime();
return timeDiff || left.segmentIndex - right.segmentIndex || left.id.localeCompare(right.id);
});
const sentAccessNumber = `${record.channel?.srcId ?? ''}${record.applicationExtension ?? ''}`;
const displayStatus = getRecordStatus(record);
const receiptNotice = getReceiptNotice(record);
return (
<Modal
footer={<Button onClick={onClose} variant="ghost"></Button>}
onClose={onClose}
open
size="xl"
title={<div className="template-modal-title"><h2></h2><p>{record.messageId}</p></div>}
>
<div className="admin-sms-send-detail">
<div className="admin-sms-detail-overview">
<div>
<span></span>
<Tag tone={statusToneMap[displayStatus] ?? 'info'}>{getRecordStatusLabel(record)}</Tag>
</div>
<div><span></span><strong>{record.submitStatus ?? '-'}</strong></div>
<div><span></span><strong>{record.receiptStatus ?? '-'}</strong></div>
<div><span></span><strong>{getTime(record.queuedAt)}</strong></div>
<div><span></span><strong>{record.phoneNumber || '-'}</strong></div>
<div><span></span><strong>{record.province ?? '-'} / {getCarrierLabel(record.carrier)}</strong></div>
<div><span></span><strong>{channelGroupNames.join(' / ') || '-'}</strong></div>
<div><span></span><strong>{record.clientSrcId || '-'}</strong></div>
<div><span></span><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>
</section>
<section>
<h3></h3>
<div className="admin-sms-route-list">
{routeRows.map((route, index) => (
<article key={route.id}>
<span>{index + 1}</span>
<div>
<strong>{route.channel}</strong>
<p className="muted">{route.channelGroup ?? '-'}</p>
<dl>
<div><dt></dt><dd>{getTime(route.sentAt)}</dd></div>
<div><dt></dt><dd>{getTime(route.receiptAt)}</dd></div>
<div><dt></dt><dd>{route.receiptCode ?? '-'}</dd></div>
<div><dt></dt><dd>{route.submitStatus ?? '-'}</dd></div>
</dl>
</div>
</article>
))}
</div>
</section>
<section>
<h3></h3>
<div className="admin-sms-detail-status-grid">
<div><span></span><strong>{record.messageId}</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>
{['submit_failed', 'failed', 'rejected'].includes(displayStatus) ? (
<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>
<h3></h3>
{segmentLoading ? <div className="ui-table__empty">...</div> : segmentAudits.length === 0 ? (
<div className="ui-table__empty"></div>
) : (
<div className="admin-sms-segment-list">
{orderedSegmentAudits.map((segment) => (
<article className="admin-sms-segment-card" key={segment.id}>
<header>
<strong> {segment.segmentIndex}/{segment.segmentTotal}</strong>
<div>
<Tag tone={segment.submitStatus === 'accepted' ? 'success' : segment.submitStatus === 'queued' ? 'info' : 'danger'}>{segment.submitStatus}</Tag>
{segment.receiptStatus ? <Tag tone={segment.receiptStatus === 'delivered' ? 'success' : segment.receiptStatus === 'unknown' ? 'neutral' : 'danger'}>{segment.receiptStatus}</Tag> : null}
</div>
</header>
<dl>
<div><dt></dt><dd>{segment.channel?.name ?? segment.channelId ?? '-'}</dd></div>
<div><dt>Sequence</dt><dd>{segment.sequenceId ?? '-'}</dd></div>
<div><dt> ID</dt><dd>{segment.submitId}</dd></div>
<div><dt> MsgId</dt><dd>{segment.gatewayMessageId ?? '-'}</dd></div>
<div><dt></dt><dd>{segment.compensationType ?? '-'}</dd></div>
<div><dt></dt><dd>{getTime(segment.createdAt)}</dd></div>
<div><dt></dt><dd>{segment.errorMessage ?? segment.errorCode ?? '-'}</dd></div>
</dl>
</article>
))}
</div>
)}
</section>
</div>
</Modal>
);
}