Files
lislgosms/src/apps/admin/channels/ChannelTable.tsx
T

102 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Copy, Eye, FileText, Pencil, Power, Send } from 'lucide-react';
import { DeleteRiskAction, Pagination, Tag } from '@/components/ui';
import { formatCents } from '@/utils/currency';
import { carrierLabelMap, carrierToneMap, statusLabelMap, statusToneMap } from './channelModel';
import type { ChannelConfirmAction, ChannelModalState, SmsChannel } from './channelTypes';
function RateBlock({ label, rate, count, tone = 'neutral' }: { label: string; rate: number; count: number; tone?: 'success' | 'warning' | 'danger' | 'neutral' }) {
return (
<div className={`sms-channel-rate sms-channel-rate--${tone}`}>
<small>{label}</small>
<strong>{rate}%</strong>
<span>{count.toLocaleString('zh-CN')}</span>
</div>
);
}
export function ChannelTable({
channels,
currentPage,
total,
totalPages,
onPageChange,
onOpenLogs,
onOpenReports,
onEdit,
onConfirm,
onTest,
onDeleted,
}: {
channels: SmsChannel[];
currentPage: number;
total: number;
totalPages: number;
onPageChange: (page: number | ((value: number) => number)) => void;
onOpenLogs: (channel: SmsChannel) => void;
onOpenReports: (channel: SmsChannel) => void;
onEdit: (modal: ChannelModalState) => void;
onConfirm: (action: ChannelConfirmAction) => void;
onTest: (channel: SmsChannel) => void;
onDeleted: () => void;
}) {
return (
<div className="surface sms-channel-table">
<div className="sms-channel-table__head">
<span>通道信息</span>
<span>运营商 / 成本</span>
<span>状态</span>
<span>今日提交</span>
<span>今日提交 / 送达质量</span>
<span>操作</span>
</div>
{channels.map((channel) => (
<article className="sms-channel-table__row" key={channel.id}>
<div className="sms-channel-identity">
<strong>{channel.name}</strong>
<span>通道 ID{channel.id}</span>
</div>
<div className="sms-channel-carrier-price">
<Tag tone={carrierToneMap[channel.carrier]}>{carrierLabelMap[channel.carrier]}</Tag>
<strong>{formatCents(channel.unitPrice)} </strong>
</div>
<div className="sms-channel-status-cell">
<Tag tone={statusToneMap[channel.status]}>{statusLabelMap[channel.status]}</Tag>
<button onClick={() => onOpenLogs(channel)} type="button">
<FileText size={14} />连接日志
</button>
</div>
<strong className="sms-channel-total">{channel.total.toLocaleString('zh-CN')}</strong>
<div className="sms-channel-quality">
<RateBlock count={channel.submitFailureCount} label="提交失败" rate={channel.submitFailureRate} tone={channel.submitFailureCount > 0 ? 'danger' : 'neutral'} />
<RateBlock count={channel.successCount} label="送达成功" rate={channel.successRate} tone={channel.successRate >= 80 ? 'success' : 'warning'} />
<RateBlock count={channel.unknownCount} label="回执未知" rate={channel.unknownRate} />
<RateBlock count={channel.failureCount} label="送达失败" rate={channel.failureRate} tone={channel.failureRate >= 50 ? 'danger' : 'neutral'} />
</div>
<div className="sms-channel-actions">
<button className="sms-channel-report-entry" onClick={() => onOpenReports(channel)} type="button">
<Eye size={15} />报备详情
</button>
<button onClick={() => onEdit({ mode: 'edit', channel })} type="button"><Pencil size={15} />编辑</button>
<button onClick={() => onConfirm({ type: 'copy', channel })} type="button"><Copy size={15} />复制通道</button>
<button onClick={() => onTest(channel)} type="button"><Send size={15} />发送测试</button>
<button className={channel.status === 'stopped' ? 'is-success' : 'is-warning'} onClick={() => onConfirm({ type: 'toggle', channel })} type="button">
<Power size={15} />{channel.status === 'stopped' ? '启用' : '停用'}
</button>
<DeleteRiskAction onCompleted={onDeleted} portal="admin" targetId={channel.id} targetType="channel" />
</div>
</article>
))}
<Pagination
nextDisabled={currentPage >= totalPages}
onNext={() => onPageChange((value) => Math.min(totalPages, value + 1))}
onPrevious={() => onPageChange((value) => Math.max(1, value - 1))}
page={currentPage}
totalPages={totalPages}
onPageChange={onPageChange}
previousDisabled={currentPage <= 1}
total={total}
/>
</div>
);
}