feat: densify enterprise signature management
This commit is contained in:
@@ -6,7 +6,7 @@ import { ReportMaterialImportModal } from './ReportMaterialImportModal';
|
||||
import { DrainageFormModal } from './enterprise-signatures/DrainageFormModal';
|
||||
import { EnterpriseSignaturesTable } from './enterprise-signatures/EnterpriseSignaturesTable';
|
||||
import { SignatureFormModal } from './enterprise-signatures/SignatureFormModal';
|
||||
import { ChannelReportStatusModal, ConfirmModal, DrainageReportModal, DrainageReportStatusModal, SignatureReportModal } from './enterprise-signatures/SignatureReportModals';
|
||||
import { ChannelReportStatusModal, ConfirmModal, DrainageReportStatusModal } from './enterprise-signatures/SignatureReportModals';
|
||||
import { buildDrainagePayload, readDrainagePayload } from './enterprise-signatures/signature.helpers';
|
||||
import type { DrainageInfo, SignatureFormState } from './enterprise-signatures/signature.types';
|
||||
|
||||
@@ -16,7 +16,6 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
const [applications, setApplications] = useState<ClientSmsApplication[]>([]);
|
||||
const [deleteTarget, setDeleteTarget] = useState<{ kind: 'drainage'; signatureId: string; id: string; name: string } | null>(null);
|
||||
const [drainageModal, setDrainageModal] = useState<{ signatureId: string; item?: DrainageInfo } | null>(null);
|
||||
const [drainageReport, setDrainageReport] = useState<{ signature: ClientSmsSignature; item: DrainageInfo } | null>(null);
|
||||
const [drainageStatusTarget, setDrainageStatusTarget] = useState<{ signature: ClientSmsSignature; item: DrainageInfo } | null>(null);
|
||||
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
|
||||
const [appliedEnterpriseKeyword, setAppliedEnterpriseKeyword] = useState('');
|
||||
@@ -27,9 +26,9 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
const [error, setError] = useState('');
|
||||
const [expandedSignatureId, setExpandedSignatureId] = useState('');
|
||||
const [signatureKeyword, setSignatureKeyword] = useState('');
|
||||
const [signatureSort, setSignatureSort] = useState<'asc' | 'desc'>('asc');
|
||||
const [appliedSignatureKeyword, setAppliedSignatureKeyword] = useState('');
|
||||
const [signatureModal, setSignatureModal] = useState<ClientSmsSignature | 'new' | null>(null);
|
||||
const [signatureReport, setSignatureReport] = useState<ClientSmsSignature | null>(null);
|
||||
const [reportStatusTarget, setReportStatusTarget] = useState<ClientSmsSignature | null>(null);
|
||||
const [signatures, setSignatures] = useState<ClientSmsSignature[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
@@ -40,10 +39,10 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
async function loadData(filters = { enterpriseKeyword: appliedEnterpriseKeyword, applicationKeyword: appliedApplicationKeyword, signatureKeyword: appliedSignatureKeyword, drainageKeyword: appliedDrainageKeyword }, targetPage = page) {
|
||||
async function loadData(filters = { enterpriseKeyword: appliedEnterpriseKeyword, applicationKeyword: appliedApplicationKeyword, signatureKeyword: appliedSignatureKeyword, drainageKeyword: appliedDrainageKeyword }, targetPage = page, targetSort = signatureSort) {
|
||||
try {
|
||||
const [signatureResult, tenantItems, applicationItems] = await Promise.all([
|
||||
adminApi.listEnterpriseSignaturesPage({ ...filters, page: targetPage, pageSize }),
|
||||
adminApi.listEnterpriseSignaturesPage({ ...filters, signatureSort: targetSort, page: targetPage, pageSize }),
|
||||
adminApi.listTenants(),
|
||||
adminApi.listEnterpriseApplicationOptions(),
|
||||
]);
|
||||
@@ -130,13 +129,17 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
loadData={loadData}
|
||||
setDeleteTarget={setDeleteTarget}
|
||||
setDrainageModal={setDrainageModal}
|
||||
setDrainageReport={setDrainageReport}
|
||||
setDrainageStatusTarget={setDrainageStatusTarget}
|
||||
setExpandedSignatureId={setExpandedSignatureId}
|
||||
setPage={setPage}
|
||||
setSignatureSort={(nextSort) => {
|
||||
setSignatureSort(nextSort);
|
||||
if (page === 1) void loadData(undefined, 1, nextSort);
|
||||
else setPage(1);
|
||||
}}
|
||||
setReportStatusTarget={setReportStatusTarget}
|
||||
setSignatureModal={setSignatureModal}
|
||||
setSignatureReport={setSignatureReport}
|
||||
signatureSort={signatureSort}
|
||||
total={total}
|
||||
totalPages={totalPages}
|
||||
visibleSignatures={visibleSignatures}
|
||||
@@ -159,7 +162,7 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
<div className="surface admin-split-filter">
|
||||
<Input label="企业名称" onChange={(event) => setEnterpriseKeyword(event.target.value)} placeholder="请输入企业名称" prefix={<Search size={16} />} value={enterpriseKeyword} />
|
||||
<Input label="企业应用" onChange={(event) => setApplicationKeyword(event.target.value)} placeholder="请输入企业应用名称" prefix={<Search size={16} />} value={applicationKeyword} />
|
||||
<Input label="签名名称" onChange={(event) => setSignatureKeyword(event.target.value)} placeholder="请输入签名名称或用途" prefix={<Search size={16} />} value={signatureKeyword} />
|
||||
<Input label="签名" onChange={(event) => setSignatureKeyword(event.target.value)} placeholder="请输入签名" prefix={<Search size={16} />} value={signatureKeyword} />
|
||||
<Input label="引流信息" onChange={(event) => setDrainageKeyword(event.target.value)} placeholder="请输入引流信息、URL 或备注" prefix={<Search size={16} />} value={drainageKeyword} />
|
||||
<div className="admin-split-filter__actions">
|
||||
<Button icon={<Search size={16} />} onClick={() => {
|
||||
@@ -214,7 +217,6 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
setMessage('导入解析完成,合格资料已进入审核中心的导入批次');
|
||||
void loadData();
|
||||
}} /> : null}
|
||||
{signatureReport ? <SignatureReportModal item={signatureReport} onClose={() => setSignatureReport(null)} /> : null}
|
||||
{reportStatusTarget ? <ChannelReportStatusModal item={reportStatusTarget} onClose={() => setReportStatusTarget(null)} onSaved={() => { setReportStatusTarget(null); void loadData(); }} /> : null}
|
||||
{drainageModal ? (
|
||||
<DrainageFormModal
|
||||
@@ -224,7 +226,6 @@ export function AdminEnterpriseSignaturesPage() {
|
||||
onSubmit={(item) => { void saveDrainage(drainageModal.signatureId, item); }}
|
||||
/>
|
||||
) : null}
|
||||
{drainageReport ? <DrainageReportModal item={drainageReport.item} onClose={() => setDrainageReport(null)} signature={drainageReport.signature} /> : null}
|
||||
{drainageStatusTarget ? <DrainageReportStatusModal item={drainageStatusTarget.item} onClose={() => setDrainageStatusTarget(null)} onSaved={() => { setDrainageStatusTarget(null); void loadData(); }} signature={drainageStatusTarget.signature} /> : null}
|
||||
{deleteTarget ? (
|
||||
<ConfirmModal
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { ClientSmsSignature } from '@/api/adminApi';
|
||||
import { EnterpriseSignaturesTable } from './EnterpriseSignaturesTable';
|
||||
|
||||
const signature = {
|
||||
id: 'signature-1',
|
||||
tenantId: 'tenant-1',
|
||||
applicationId: 'app-1',
|
||||
name: '【聆界科技】',
|
||||
auditStatus: 'approved',
|
||||
createdAt: '2026-09-01T00:00:00.000Z',
|
||||
updatedAt: '2026-09-01T01:00:00.000Z',
|
||||
tenant: { id: 'tenant-1', name: '深圳市聆界科技有限公司', status: 'active' },
|
||||
application: { id: 'app-1', tenantId: 'tenant-1', name: '营销通知应用', status: 'active' },
|
||||
carrierReportSummary: {
|
||||
mobile: { status: 'approved', approved: 3, total: 3 },
|
||||
unicom: { status: 'reporting', approved: 2, total: 3 },
|
||||
telecom: { status: 'waiting_material', approved: 1, total: 3 },
|
||||
},
|
||||
drainageInfo: { links: [{ id: 'drainage-1', siteName: 'www.lisglo.com', url: 'www.lisglo.com', auditStatus: 'approved' }] },
|
||||
drainageCarrierReportSummary: {
|
||||
'drainage-1': {
|
||||
mobile: { status: 'approved', approved: 3, total: 3 },
|
||||
unicom: { status: 'reporting', approved: 1, total: 3 },
|
||||
telecom: { status: 'waiting_material', approved: 0, total: 3 },
|
||||
},
|
||||
},
|
||||
} as unknown as ClientSmsSignature;
|
||||
|
||||
function renderTable(overrides: Partial<Parameters<typeof EnterpriseSignaturesTable>[0]> = {}) {
|
||||
const props: Parameters<typeof EnterpriseSignaturesTable>[0] = {
|
||||
appliedDrainageKeyword: '', currentPage: 1, expandedSignatureId: signature.id,
|
||||
filteredSignatures: [signature], visibleSignatures: [signature], total: 1, totalPages: 1,
|
||||
loadData: vi.fn().mockResolvedValue(undefined), setDeleteTarget: vi.fn(), setDrainageModal: vi.fn(),
|
||||
setDrainageStatusTarget: vi.fn(), setExpandedSignatureId: vi.fn(), setPage: vi.fn(),
|
||||
setReportStatusTarget: vi.fn(), setSignatureModal: vi.fn(), setSignatureSort: vi.fn(), signatureSort: 'asc',
|
||||
...overrides,
|
||||
};
|
||||
return { ...render(<EnterpriseSignaturesTable {...props} />), props };
|
||||
}
|
||||
|
||||
describe('EnterpriseSignaturesTable dense presentation', () => {
|
||||
it('uses compact headers, bracketed signatures, channel counts and only the requested row actions', () => {
|
||||
renderTable();
|
||||
expect(screen.getByText('【聆界科技】')).toBeVisible();
|
||||
expect(screen.getAllByText('审核状态')).toHaveLength(2);
|
||||
expect(screen.queryByText('签名名称')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('签名审核')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('报备详情')).not.toBeInTheDocument();
|
||||
expect(screen.getAllByLabelText('已报备通过3个,总通道数3个')).toHaveLength(2);
|
||||
expect(screen.getByLabelText('已报备通过2个,总通道数3个')).toHaveTextContent('2/3');
|
||||
expect(screen.getAllByRole('button', { name: '报备状态' })).toHaveLength(2);
|
||||
expect(screen.getAllByRole('button', { name: '编辑' })).toHaveLength(2);
|
||||
expect(screen.getAllByRole('button', { name: '删除' })).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('requests a real descending sort from the signature column control', async () => {
|
||||
const setSignatureSort = vi.fn();
|
||||
renderTable({ expandedSignatureId: '', setSignatureSort });
|
||||
await userEvent.click(screen.getByRole('button', { name: '签名降序' }));
|
||||
expect(setSignatureSort).toHaveBeenCalledWith('desc');
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { ChevronDown, ChevronRight, Edit3, FileText, Plus } from 'lucide-react';
|
||||
import { ArrowDown, ArrowUp, ChevronDown, ChevronRight, Edit3, Plus } from 'lucide-react';
|
||||
import type { ClientSmsSignature } from '@/api/adminApi';
|
||||
import { Button, CarrierTag, DeleteRiskAction, Pagination } from '@/components/ui';
|
||||
import { AuditStatusTag, CarrierReportTag, readDrainagePayload, signatureCardVisual } from './signature.helpers';
|
||||
import { Button, DeleteRiskAction, Pagination } from '@/components/ui';
|
||||
import { AuditStatusTag, CarrierReportCount, formatSignatureName, readDrainagePayload, signatureCardVisual } from './signature.helpers';
|
||||
import type { DrainageInfo } from './signature.types';
|
||||
|
||||
type EnterpriseSignaturesTableProps = {
|
||||
@@ -13,13 +13,13 @@ type EnterpriseSignaturesTableProps = {
|
||||
loadData: () => Promise<void>;
|
||||
setDeleteTarget: Dispatch<SetStateAction<{ kind: 'drainage'; signatureId: string; id: string; name: string } | null>>;
|
||||
setDrainageModal: Dispatch<SetStateAction<{ signatureId: string; item?: DrainageInfo } | null>>;
|
||||
setDrainageReport: Dispatch<SetStateAction<{ signature: ClientSmsSignature; item: DrainageInfo } | null>>;
|
||||
setDrainageStatusTarget: Dispatch<SetStateAction<{ signature: ClientSmsSignature; item: DrainageInfo } | null>>;
|
||||
setExpandedSignatureId: Dispatch<SetStateAction<string>>;
|
||||
setPage: Dispatch<SetStateAction<number>>;
|
||||
setSignatureSort: (sort: 'asc' | 'desc') => void;
|
||||
setReportStatusTarget: Dispatch<SetStateAction<ClientSmsSignature | null>>;
|
||||
setSignatureModal: Dispatch<SetStateAction<ClientSmsSignature | 'new' | null>>;
|
||||
setSignatureReport: Dispatch<SetStateAction<ClientSmsSignature | null>>;
|
||||
signatureSort: 'asc' | 'desc';
|
||||
total: number;
|
||||
totalPages: number;
|
||||
visibleSignatures: ClientSmsSignature[];
|
||||
@@ -33,19 +33,37 @@ export function EnterpriseSignaturesTable({
|
||||
loadData,
|
||||
setDeleteTarget,
|
||||
setDrainageModal,
|
||||
setDrainageReport,
|
||||
setDrainageStatusTarget,
|
||||
setExpandedSignatureId,
|
||||
setPage,
|
||||
setSignatureSort,
|
||||
setReportStatusTarget,
|
||||
setSignatureModal,
|
||||
setSignatureReport,
|
||||
signatureSort,
|
||||
total,
|
||||
totalPages,
|
||||
visibleSignatures,
|
||||
}: EnterpriseSignaturesTableProps) {
|
||||
return (
|
||||
<div className="signature-list admin-enterprise-signature-list">
|
||||
<div className="enterprise-signature-table__head" role="row">
|
||||
<span aria-hidden="true" />
|
||||
<span className="enterprise-signature-table__sortable">
|
||||
签名
|
||||
<span className="enterprise-signature-table__sort-actions">
|
||||
<Button aria-pressed={signatureSort === 'asc'} icon={<ArrowUp size={13} />} iconOnly onClick={() => setSignatureSort('asc')} size="sm" variant={signatureSort === 'asc' ? 'secondary' : 'ghost'}>签名升序</Button>
|
||||
<Button aria-pressed={signatureSort === 'desc'} icon={<ArrowDown size={13} />} iconOnly onClick={() => setSignatureSort('desc')} size="sm" variant={signatureSort === 'desc' ? 'secondary' : 'ghost'}>签名降序</Button>
|
||||
</span>
|
||||
</span>
|
||||
<span>企业</span>
|
||||
<span>应用</span>
|
||||
<span>审核状态</span>
|
||||
<span>移动</span>
|
||||
<span>联通</span>
|
||||
<span>电信</span>
|
||||
<span>引流信息</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
{visibleSignatures.map((signature) => {
|
||||
const payload = readDrainagePayload(signature);
|
||||
const visibleDrainageLinks = appliedDrainageKeyword
|
||||
@@ -59,16 +77,15 @@ export function EnterpriseSignaturesTable({
|
||||
<button aria-label="展开签名" onClick={() => setExpandedSignatureId(expanded ? '' : signature.id)} type="button">
|
||||
{expanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
|
||||
</button>
|
||||
<div><span>签名名称</span><strong>{signature.name}</strong></div>
|
||||
<div><span>企业</span><span className="signature-summary__regular-value">{signature.tenant?.name ?? signature.tenantId}</span></div>
|
||||
<div><span>应用</span><span className="signature-summary__regular-value">{signature.application?.name ?? '-'}</span></div>
|
||||
<div><span>签名审核</span><AuditStatusTag status={signature.auditStatus} /></div>
|
||||
<div><CarrierTag carrier="mobile" /><CarrierReportTag summary={signature.carrierReportSummary?.mobile} /></div>
|
||||
<div><CarrierTag carrier="unicom" /><CarrierReportTag summary={signature.carrierReportSummary?.unicom} /></div>
|
||||
<div><CarrierTag carrier="telecom" /><CarrierReportTag summary={signature.carrierReportSummary?.telecom} /></div>
|
||||
<div><span>引流信息</span><strong>{payload.links.length} 条</strong></div>
|
||||
<div className="enterprise-signature-table__signature" data-label="签名"><strong>{formatSignatureName(signature.name)}</strong></div>
|
||||
<div data-label="企业"><span className="signature-summary__regular-value">{signature.tenant?.name ?? signature.tenantId}</span></div>
|
||||
<div data-label="应用"><span className="signature-summary__regular-value">{signature.application?.name ?? '-'}</span></div>
|
||||
<div data-label="审核状态"><AuditStatusTag status={signature.auditStatus} /></div>
|
||||
<div data-label="移动"><CarrierReportCount summary={signature.carrierReportSummary?.mobile} /></div>
|
||||
<div data-label="联通"><CarrierReportCount summary={signature.carrierReportSummary?.unicom} /></div>
|
||||
<div data-label="电信"><CarrierReportCount summary={signature.carrierReportSummary?.telecom} /></div>
|
||||
<div data-label="引流信息"><strong>{payload.links.length} 条</strong></div>
|
||||
<div className="signature-actions">
|
||||
<Button icon={<FileText size={16} />} onClick={() => setSignatureReport(signature)} size="sm" variant="ghost">报备详情</Button>
|
||||
<Button icon={<Edit3 size={16} />} onClick={() => setReportStatusTarget(signature)} size="sm" variant="ghost">报备状态</Button>
|
||||
<Button icon={<Edit3 size={16} />} onClick={() => setSignatureModal(signature)} size="sm" variant="ghost">编辑</Button>
|
||||
<DeleteRiskAction onCompleted={() => void loadData()} portal="admin" targetId={signature.id} targetType="signature" />
|
||||
@@ -82,9 +99,9 @@ export function EnterpriseSignaturesTable({
|
||||
<div className="drainage-table__head">
|
||||
<span>引流url或号码</span>
|
||||
<span>审核状态</span>
|
||||
<CarrierTag carrier="mobile" />
|
||||
<CarrierTag carrier="unicom" />
|
||||
<CarrierTag carrier="telecom" />
|
||||
<span>移动</span>
|
||||
<span>联通</span>
|
||||
<span>电信</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
{visibleDrainageLinks.map((item) => {
|
||||
@@ -93,11 +110,10 @@ export function EnterpriseSignaturesTable({
|
||||
<div className="drainage-table__row" key={item.id}>
|
||||
<span className="drainage-table__url" title={item.url}>{item.url}</span>
|
||||
<AuditStatusTag status={item.auditStatus ?? 'pending'} />
|
||||
<CarrierReportTag summary={summary?.mobile} />
|
||||
<CarrierReportTag summary={summary?.unicom} />
|
||||
<CarrierReportTag summary={summary?.telecom} />
|
||||
<CarrierReportCount summary={summary?.mobile} />
|
||||
<CarrierReportCount summary={summary?.unicom} />
|
||||
<CarrierReportCount summary={summary?.telecom} />
|
||||
<span className="drainage-row-actions">
|
||||
<Button onClick={() => setDrainageReport({ signature, item })} size="sm" variant="ghost">报备详情</Button>
|
||||
<Button disabled={item.auditStatus !== 'approved'} onClick={() => setDrainageStatusTarget({ signature, item })} size="sm" variant="ghost">报备状态</Button>
|
||||
<Button onClick={() => setDrainageModal({ signatureId: signature.id, item })} size="sm" variant="ghost">编辑</Button>
|
||||
<Button onClick={() => setDeleteTarget({ kind: 'drainage', signatureId: signature.id, id: item.id, name: item.url })} size="sm" variant="danger">删除</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { Info } from 'lucide-react';
|
||||
import { adminApi, type ClientSmsSignature } from '@/api/adminApi';
|
||||
import { Button, CarrierTag, Modal, Select, Textarea } from '@/components/ui';
|
||||
import { AuditStatusTag, carrierLabel, CarrierReportTag, formatDate } from './signature.helpers';
|
||||
import { carrierLabel, CarrierReportTag } from './signature.helpers';
|
||||
import type { DrainageInfo } from './signature.types';
|
||||
|
||||
const reportStatusOptions = [
|
||||
@@ -11,28 +11,6 @@ const reportStatusOptions = [
|
||||
{ label: '报备失败', value: 'failed' }, { label: '放弃报备', value: 'abandoned' },
|
||||
];
|
||||
|
||||
export function SignatureReportModal({ item, onClose }: { item: ClientSmsSignature; onClose: () => void }) {
|
||||
return (
|
||||
<Modal footer={<Button onClick={onClose}>关闭</Button>} onClose={onClose} open title="签名报备详情">
|
||||
<div className="admin-report-detail">
|
||||
<div className="detail-grid">
|
||||
<div><span>企业名称</span><strong>{item.tenant?.name ?? item.tenantId}</strong></div>
|
||||
<div><span>应用名称</span><strong>{item.application?.name ?? '-'}</strong></div>
|
||||
<div><span>签名名称</span><strong>{item.name}</strong></div>
|
||||
<div><span>签名审核</span><AuditStatusTag status={item.auditStatus} /></div>
|
||||
<div><span>更新时间</span><strong>{formatDate(item.updatedAt)}</strong></div>
|
||||
</div>
|
||||
<div className="admin-report-tabs">
|
||||
<button className="admin-report-carrier--mobile active" type="button"><CarrierTag carrier="mobile" /><span><CarrierReportTag summary={item.carrierReportSummary?.mobile} /></span></button>
|
||||
<button className="admin-report-carrier--unicom active" type="button"><CarrierTag carrier="unicom" /><span><CarrierReportTag summary={item.carrierReportSummary?.unicom} /></span></button>
|
||||
<button className="admin-report-carrier--telecom active" type="button"><CarrierTag carrier="telecom" /><span><CarrierReportTag summary={item.carrierReportSummary?.telecom} /></span></button>
|
||||
</div>
|
||||
<div className="page-stack">{(item.reportTargets ?? []).map((target) => <div className="surface" key={`${target.channelId}:${target.carrier}`} style={{ display: 'flex', justifyContent: 'space-between', padding: 12 }}><span>{target.channel.name} <CarrierTag carrier={target.carrier} /></span><CarrierReportTag summary={{ status: target.status, approved: target.status === 'approved' ? 1 : 0, total: 1 }} /></div>)}</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChannelReportStatusModal({ item, onClose, onSaved }: { item: ClientSmsSignature; onClose: () => void; onSaved: () => void }) {
|
||||
const targets = item.reportTargets ?? [];
|
||||
const carriers = ['mobile', 'unicom', 'telecom'] as const;
|
||||
@@ -56,24 +34,6 @@ export function ChannelReportStatusModal({ item, onClose, onSaved }: { item: Cli
|
||||
</Modal>;
|
||||
}
|
||||
|
||||
export function DrainageReportModal({ item, onClose, signature }: { item: DrainageInfo; onClose: () => void; signature: ClientSmsSignature }) {
|
||||
const summary = signature.drainageCarrierReportSummary?.[item.id];
|
||||
const targets = signature.drainageReportTargets?.[item.id] ?? [];
|
||||
return (
|
||||
<Modal footer={<Button onClick={onClose}>关闭</Button>} onClose={onClose} open size="xl" title="引流信息报备详情">
|
||||
<div className="detail-grid">
|
||||
<div><span>引流url或号码</span><strong>{item.url}</strong></div>
|
||||
<div><span>引流url或号码</span><strong>{item.url}</strong></div>
|
||||
<div><CarrierTag carrier="mobile" /><CarrierReportTag summary={summary?.mobile} /></div>
|
||||
<div><CarrierTag carrier="unicom" /><CarrierReportTag summary={summary?.unicom} /></div>
|
||||
<div><CarrierTag carrier="telecom" /><CarrierReportTag summary={summary?.telecom} /></div>
|
||||
<div className="detail-grid__wide"><span>备注</span><strong>{item.remark || '-'}</strong></div>
|
||||
</div>
|
||||
<div className="page-stack" style={{ marginTop: 16 }}>{targets.map((target) => <div className="surface" key={target.channelId} style={{ display: 'flex', justifyContent: 'space-between', padding: 12 }}><span>{target.channel.name}({carrierLabel(target.channel.carrier)})</span><CarrierReportTag summary={{ status: target.status, approved: target.status === 'approved' ? 1 : 0, total: 1 }} /></div>)}</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function DrainageReportStatusModal({ item, onClose, onSaved, signature }: { item: DrainageInfo; onClose: () => void; onSaved: () => void; signature: ClientSmsSignature }) {
|
||||
const targets = signature.drainageReportTargets?.[item.id] ?? [];
|
||||
const [statuses, setStatuses] = useState<Record<string, string>>(() => Object.fromEntries(targets.map((target) => [target.channelId, target.status])));
|
||||
|
||||
@@ -15,6 +15,11 @@ export function carrierLabel(carrier?: string | null) {
|
||||
return carrierLabels[carrier] ?? carrier;
|
||||
}
|
||||
|
||||
export function formatSignatureName(name: string) {
|
||||
const value = name.trim().replace(/^[【\[]+|[】\]]+$/g, '').trim();
|
||||
return value ? `【${value}】` : '—';
|
||||
}
|
||||
|
||||
export function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }) {
|
||||
if (!summary || summary.status === 'not_applicable' || summary.total === 0) return <Tag tone="neutral">不适用</Tag>;
|
||||
let label = '未报备';
|
||||
@@ -27,6 +32,24 @@ export function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }
|
||||
return <span className="carrier-report-summary"><Tag tone={tone}>{label}</Tag><small>({summary.approved}/{summary.total})</small></span>;
|
||||
}
|
||||
|
||||
export function CarrierReportCount({ summary }: { summary?: CarrierReportSummary }) {
|
||||
if (!summary || summary.status === 'not_applicable' || summary.total === 0) {
|
||||
return <span className="carrier-report-count carrier-report-count--neutral" title="没有适用的目标通道">—</span>;
|
||||
}
|
||||
const tone = summary.approved === summary.total
|
||||
? 'success'
|
||||
: ['failed', 'rejected'].includes(summary.status) ? 'danger' : 'warning';
|
||||
return (
|
||||
<span
|
||||
aria-label={`已报备通过${summary.approved}个,总通道数${summary.total}个`}
|
||||
className={`carrier-report-count carrier-report-count--${tone}`}
|
||||
title={`已报备通过 ${summary.approved} 个 / 总通道数 ${summary.total} 个`}
|
||||
>
|
||||
{summary.approved}/{summary.total}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function signatureCardVisual(auditStatus: string, summaries?: Record<string, CarrierReportSummary>) {
|
||||
if (auditStatus === 'rejected') return { label: '签名审核已驳回', tone: 'red' as SignatureCardTone };
|
||||
if (auditStatus === 'pending') return { label: '签名待审核', tone: 'amber' as SignatureCardTone };
|
||||
|
||||
Reference in New Issue
Block a user