fix: restore carrier reporting status summaries

This commit is contained in:
hectorzhao
2026-09-02 10:27:29 +08:00
parent 9e34757d6f
commit cd824999f3
9 changed files with 58 additions and 28 deletions
+2
View File
@@ -4,10 +4,12 @@ describe('summarizeReportStatuses', () => {
it.each([ it.each([
[[], { status: 'not_applicable', approved: 0, total: 0 }], [[], { status: 'not_applicable', approved: 0, total: 0 }],
[['approved', 'approved'], { status: 'approved', approved: 2, total: 2 }], [['approved', 'approved'], { status: 'approved', approved: 2, total: 2 }],
[['abandoned', 'abandoned'], { status: 'abandoned', approved: 0, total: 2 }],
[['failed', 'rejected'], { status: 'failed', approved: 0, total: 2 }], [['failed', 'rejected'], { status: 'failed', approved: 0, total: 2 }],
[['approved', 'failed'], { status: 'partial_success', approved: 1, total: 2 }], [['approved', 'failed'], { status: 'partial_success', approved: 1, total: 2 }],
[['failed', 'pending'], { status: 'reporting', approved: 0, total: 2 }], [['failed', 'pending'], { status: 'reporting', approved: 0, total: 2 }],
[['waiting_material', 'pending'], { status: 'waiting_material', approved: 0, total: 2 }], [['waiting_material', 'pending'], { status: 'waiting_material', approved: 0, total: 2 }],
[['abandoned', 'reporting'], { status: 'reporting', approved: 0, total: 2 }],
])('summarizes %j without allowing one failure to override other targets', (statuses, expected) => { ])('summarizes %j without allowing one failure to override other targets', (statuses, expected) => {
expect(summarizeReportStatuses(statuses)).toEqual(expected); expect(summarizeReportStatuses(statuses)).toEqual(expected);
}); });
+2
View File
@@ -11,8 +11,10 @@ export function summarizeReportStatuses(statuses: string[]): ReportStatusSummary
const approved = statuses.filter((status) => status === 'approved').length; const approved = statuses.filter((status) => status === 'approved').length;
const failed = statuses.filter((status) => FAILED_REPORT_STATUSES.has(status)).length; const failed = statuses.filter((status) => FAILED_REPORT_STATUSES.has(status)).length;
const abandoned = statuses.filter((status) => status === 'abandoned').length;
if (approved === statuses.length) return { status: 'approved', approved, total: statuses.length }; if (approved === statuses.length) return { status: 'approved', approved, total: statuses.length };
if (abandoned === statuses.length) return { status: 'abandoned', approved, total: statuses.length };
// Overall failure means every current target failed. A single failed channel must not // Overall failure means every current target failed. A single failed channel must not
// erase successful channels or targets that can still finish reporting. // erase successful channels or targets that can still finish reporting.
@@ -9,7 +9,7 @@
}, },
{ {
"name": "CarrierReportTag", "name": "CarrierReportTag",
"canonicalSha256": "2ee4038f46fdb4c8e9af1fdbc5f7959f38d2360e081c7dd3b44d465be3b6cfef" "canonicalSha256": "6296b4332ef0669b17a3aa6703e7e407aa44008a9820d6fccff3313b238f1914"
}, },
{ {
"name": "signatureCardVisual", "name": "signatureCardVisual",
@@ -76,7 +76,7 @@
"canonicalSha256": "2f71bdc2f8044ca9b403affbb0cb3f31587e4c004a90e9e0836aa6143033b6da" "canonicalSha256": "2f71bdc2f8044ca9b403affbb0cb3f31587e4c004a90e9e0836aa6143033b6da"
} }
], ],
"tableJsxSha256": "9d8b0f90e6a375fb3d019985d2b6d3409d7b41956e1893f0654c80499c1b64b4", "tableJsxSha256": "3f321d3490479e62db729ebc49d4cc7eef46c24c9eaf5add7a0d046965de3109",
"apiCalls": [ "apiCalls": [
"listEnterpriseSignaturesPage", "listEnterpriseSignaturesPage",
"listTenants", "listTenants",
+3 -2
View File
@@ -4997,8 +4997,9 @@ npm run verify:phase8
| 用例ID | 场景 | 预期 | | 用例ID | 场景 | 预期 |
| --- | --- | --- | | --- | --- | --- |
| TC-ENTERPRISE-SIGNATURE-DENSITY-001 | 首次打开运营端企业签名管理 | 不请求或展示顶部全部/待审核/报备中/异常统计;短信签名Tab不显示总数;列表使用紧凑表头与行布局 | | TC-ENTERPRISE-SIGNATURE-DENSITY-001 | 首次打开运营端企业签名管理 | 不请求或展示顶部全部/待审核/报备中/异常统计;短信签名Tab不显示总数;列表使用紧凑表头与行布局 |
| TC-ENTERPRISE-SIGNATURE-DENSITY-002 | 查看签名及展开的引流信息 | 两级表头统一使用“审核状态”;移动/联通/电信仅显示已通过通道数/总通道数,不显示日期;引流信息列只显示条数,不显示异常数 | | TC-ENTERPRISE-SIGNATURE-DENSITY-002 | 查看签名及展开的引流信息 | 两级表头统一使用“审核状态”;签名及引流信息的移动/联通/电信列同时显示汇总状态及已通过通道数/总通道数,不显示日期;引流信息列只显示条数,不显示异常数 |
| TC-ENTERPRISE-SIGNATURE-DENSITY-003 | 查看签名查询和签名列 | 查询标签及表头统一为“签名”,不显示签名用途;签名统一显示完整中文黑括号,例如“【聆界科技】”,已有括号不得重复包裹 | | TC-ENTERPRISE-SIGNATURE-DENSITY-003 | 查看签名查询和签名列 | 查询标签及表头统一为“签名”,不显示签名用途;签名统一显示完整中文黑括号,例如“【聆界科技】”,已有括号不得重复包裹 |
| TC-ENTERPRISE-SIGNATURE-DENSITY-004 | 点击签名升序或降序按钮并翻页 | 请求携带signatureSort=asc/desc,后端先按签名及ID稳定排序再分页;切换排序回第一页,不只重排当前页,不产生重复请求 | | TC-ENTERPRISE-SIGNATURE-DENSITY-004 | 点击签名表头旁的向上或向下三角按钮并翻页 | 请求携带signatureSort=asc/desc,后端先按签名及ID稳定排序再分页;切换排序回第一页,不只重排当前页,不产生重复请求 |
| TC-ENTERPRISE-SIGNATURE-DENSITY-005 | 查看签名及引流信息操作列 | 两级操作列都只展示“报备状态、编辑、删除”;不显示“报备详情”;三个入口继续调用原真实后端流程 | | TC-ENTERPRISE-SIGNATURE-DENSITY-005 | 查看签名及引流信息操作列 | 两级操作列都只展示“报备状态、编辑、删除”;不显示“报备详情”;三个入口继续调用原真实后端流程 |
| TC-ENTERPRISE-SIGNATURE-DENSITY-006 | 1600×1000桌面视口查看并展开首条签名 | 表头与数据列对齐,操作按钮不换行,展开表格无裁切;页面无框架错误层,控制台无相关错误;窄视口由列表容器横向滚动,不挤压错列 | | TC-ENTERPRISE-SIGNATURE-DENSITY-006 | 1600×1000桌面视口查看并展开首条签名 | 表头与数据列对齐,操作按钮不换行,展开表格无裁切;页面无框架错误层,控制台无相关错误;窄视口由列表容器横向滚动,不挤压错列 |
| TC-ENTERPRISE-SIGNATURE-DENSITY-007 | 某一运营商下所有当前目标通道的报备任务均为abandoned | 该签名及其引流信息在对应运营商列汇总为“放弃报备”,同时显示0/总通道数;仅部分通道放弃时不得误判为全部放弃 |
+9
View File
@@ -4249,3 +4249,12 @@ git diff --check
- Browser插件本轮不可用,常规Chrome/Edge自动化进程因本机策略启动即退出;改用工作区自带Playwright Chromium复核本地构建。1600×1000页面身份、非空、无框架错误层、控制台、截图和交互通过:展开首条后可见3条引流信息;两级各3个指定操作、0个报备详情;点击降序实际请求`/api/admin/enterprise-signatures?signatureSort=desc&page=1&pageSize=10`。视觉夹具仅用于渲染验证,不冒充真实API验收。 - Browser插件本轮不可用,常规Chrome/Edge自动化进程因本机策略启动即退出;改用工作区自带Playwright Chromium复核本地构建。1600×1000页面身份、非空、无框架错误层、控制台、截图和交互通过:展开首条后可见3条引流信息;两级各3个指定操作、0个报备详情;点击降序实际请求`/api/admin/enterprise-signatures?signatureSort=desc&page=1&pageSize=10`。视觉夹具仅用于渲染验证,不冒充真实API验收。
- 本轮不推送、不部署,不访问或修改测试/预生产业务数据;未发送、补发、重投短信,未修改余额、通道或客户配置。真实登录环境API、浏览器控制台及页面效果仍须在未来获授权发布后复核。 - 本轮不推送、不部署,不访问或修改测试/预生产业务数据;未发送、补发、重投短信,未修改余额、通道或客户配置。真实登录环境API、浏览器控制台及页面效果仍须在未来获授权发布后复核。
- 提交只暂存本轮源码、测试、R4契约、测试用例及本节进度追加;原有4份修改文档、3份未跟踪文档、未跟踪`=`文件和`docs/testing-progress.md`中另一会话26行追加继续保留,不覆盖、不夹带。 - 提交只暂存本轮源码、测试、R4契约、测试用例及本节进度追加;原有4份修改文档、3份未跟踪文档、未跟踪`=`文件和`docs/testing-progress.md`中另一会话26行追加继续保留,不覆盖、不夹带。
## 2026-09-02 企业签名三网状态恢复与放弃报备汇总
- 重新核验 `main / 9e34757`、最近提交及交接进度后定位回归:高密度列表将三网单元格收敛为纯 `approved/total` 数字,导致签名和引流信息的状态语义不可见;公共报备汇总函数此前也没有“全部目标均为 abandoned”的独立结果。
- 签名及展开的引流信息三网列现同时展示汇总状态和通过数/总通道数,覆盖全部通过、部分通过、报备中、资料待补充、报备失败、放弃报备和未报备;状态及数量继续来自真实列表API的汇总字段。只有同一运营商下全部当前目标通道均为 `abandoned` 才汇总为“放弃报备”,混合状态保持其可执行状态,不误判为全部放弃。
- 签名表头的升序/降序图标由箭头改为上下实心三角,保留按钮无障碍名称、当前排序态及服务端稳定分页排序语义。同步更新R4契约和 `TC-ENTERPRISE-SIGNATURE-DENSITY-002/004/007`
- 定向公共汇总8项、企业签名组件2项通过;全量API 51套586项、前端10文件50项通过,前后端TypeScript、定向ESLint(仅保留既有Fast Refresh警告)、Vite构建、依赖安全、结构质量、R4契约、包体积及 `git diff --check` 通过。Vite仍仅有既有Chart分块超过500kB提示,入口gzip 107.39KiB符合250KiB预算。
- 测试环境 `100.93.204.60` 当前经Tailscale显示离线、最后在线约12小时前,ping及SSH均不可达;因此尚不能重新建立和验证本次独立恢复资产,也未部署、重启服务或变更测试数据。待主机恢复连通后,必须先完成PostgreSQL custom dump、运行目录、环境/systemd/Nginx/原标记/SHA清单及三项可恢复性校验,才能发布。
- 本轮未访问或修改预生产,未发送、补发或重投短信,未修改余额、通道或客户配置。原有4份修改文档、3份未跟踪文档、未跟踪`=`文件及本文件中其他会话追加保持不动。
@@ -17,7 +17,7 @@ const signature = {
carrierReportSummary: { carrierReportSummary: {
mobile: { status: 'approved', approved: 3, total: 3 }, mobile: { status: 'approved', approved: 3, total: 3 },
unicom: { status: 'reporting', approved: 2, total: 3 }, unicom: { status: 'reporting', approved: 2, total: 3 },
telecom: { status: 'waiting_material', approved: 1, total: 3 }, telecom: { status: 'abandoned', approved: 0, total: 3 },
}, },
drainageInfo: { links: [{ id: 'drainage-1', siteName: 'www.lisglo.com', url: 'www.lisglo.com', auditStatus: 'approved' }] }, drainageInfo: { links: [{ id: 'drainage-1', siteName: 'www.lisglo.com', url: 'www.lisglo.com', auditStatus: 'approved' }] },
drainageCarrierReportSummary: { drainageCarrierReportSummary: {
@@ -42,15 +42,17 @@ function renderTable(overrides: Partial<Parameters<typeof EnterpriseSignaturesTa
} }
describe('EnterpriseSignaturesTable dense presentation', () => { describe('EnterpriseSignaturesTable dense presentation', () => {
it('uses compact headers, bracketed signatures, channel counts and only the requested row actions', () => { it('uses compact headers, bracketed signatures, carrier statuses with counts and only the requested row actions', () => {
renderTable(); renderTable();
expect(screen.getByText('【聆界科技】')).toBeVisible(); expect(screen.getByText('【聆界科技】')).toBeVisible();
expect(screen.getAllByText('审核状态')).toHaveLength(2); expect(screen.getAllByText('审核状态')).toHaveLength(2);
expect(screen.queryByText('签名名称')).not.toBeInTheDocument(); expect(screen.queryByText('签名名称')).not.toBeInTheDocument();
expect(screen.queryByText('签名审核')).not.toBeInTheDocument(); expect(screen.queryByText('签名审核')).not.toBeInTheDocument();
expect(screen.queryByText('报备详情')).not.toBeInTheDocument(); expect(screen.queryByText('报备详情')).not.toBeInTheDocument();
expect(screen.getAllByLabelText('已报备通过3个,总通道数3个')).toHaveLength(2); expect(screen.getAllByLabelText('全部通过,已报备通过3个,总通道数3个')).toHaveLength(2);
expect(screen.getByLabelText('已报备通过2个,总通道数3个')).toHaveTextContent('2/3'); expect(screen.getByLabelText('部分通过,已报备通过2个,总通道数3个')).toHaveTextContent('部分通过2/3');
expect(screen.getByLabelText('放弃报备,已报备通过0个,总通道数3个')).toHaveTextContent('放弃报备0/3');
expect(screen.getByLabelText('资料待补充,已报备通过0个,总通道数3个')).toHaveTextContent('资料待补充0/3');
expect(screen.getAllByRole('button', { name: '报备状态' })).toHaveLength(2); expect(screen.getAllByRole('button', { name: '报备状态' })).toHaveLength(2);
expect(screen.getAllByRole('button', { name: '编辑' })).toHaveLength(2); expect(screen.getAllByRole('button', { name: '编辑' })).toHaveLength(2);
expect(screen.getAllByRole('button', { name: '删除' })).toHaveLength(2); expect(screen.getAllByRole('button', { name: '删除' })).toHaveLength(2);
@@ -61,5 +63,8 @@ describe('EnterpriseSignaturesTable dense presentation', () => {
renderTable({ expandedSignatureId: '', setSignatureSort }); renderTable({ expandedSignatureId: '', setSignatureSort });
await userEvent.click(screen.getByRole('button', { name: '签名降序' })); await userEvent.click(screen.getByRole('button', { name: '签名降序' }));
expect(setSignatureSort).toHaveBeenCalledWith('desc'); expect(setSignatureSort).toHaveBeenCalledWith('desc');
expect(screen.getByRole('button', { name: '签名升序' }).querySelector('.lucide-triangle')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '签名降序' }).querySelector('.enterprise-signature-table__sort-triangle--down')).toBeInTheDocument();
expect(document.querySelector('.lucide-arrow-up, .lucide-arrow-down')).not.toBeInTheDocument();
}); });
}); });
@@ -1,5 +1,5 @@
import type { Dispatch, SetStateAction } from 'react'; import type { Dispatch, SetStateAction } from 'react';
import { ArrowDown, ArrowUp, ChevronDown, ChevronRight, Edit3, Plus } from 'lucide-react'; import { ChevronDown, ChevronRight, Edit3, Plus, Triangle } from 'lucide-react';
import type { ClientSmsSignature } from '@/api/adminApi'; import type { ClientSmsSignature } from '@/api/adminApi';
import { Button, DeleteRiskAction, Pagination } from '@/components/ui'; import { Button, DeleteRiskAction, Pagination } from '@/components/ui';
import { AuditStatusTag, CarrierReportCount, formatSignatureName, readDrainagePayload, signatureCardVisual } from './signature.helpers'; import { AuditStatusTag, CarrierReportCount, formatSignatureName, readDrainagePayload, signatureCardVisual } from './signature.helpers';
@@ -51,8 +51,8 @@ export function EnterpriseSignaturesTable({
<span className="enterprise-signature-table__sortable"> <span className="enterprise-signature-table__sortable">
<span className="enterprise-signature-table__sort-actions"> <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 === 'asc'} icon={<Triangle aria-hidden="true" fill="currentColor" size={11} />} 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> <Button aria-pressed={signatureSort === 'desc'} icon={<Triangle aria-hidden="true" className="enterprise-signature-table__sort-triangle--down" fill="currentColor" size={11} />} iconOnly onClick={() => setSignatureSort('desc')} size="sm" variant={signatureSort === 'desc' ? 'secondary' : 'ghost'}></Button>
</span> </span>
</span> </span>
<span></span> <span></span>
@@ -16,19 +16,13 @@ export function carrierLabel(carrier?: string | null) {
} }
export function formatSignatureName(name: string) { export function formatSignatureName(name: string) {
const value = name.trim().replace(/^[【\[]+|[】\]]+$/g, '').trim(); const value = name.trim().replace(/^(?:【|\[)+|(?:】|\])+$/g, '').trim();
return value ? `${value}` : '—'; return value ? `${value}` : '—';
} }
export function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }) { export function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }) {
if (!summary || summary.status === 'not_applicable' || summary.total === 0) return <Tag tone="neutral"></Tag>; if (!summary || summary.status === 'not_applicable' || summary.total === 0) return <Tag tone="neutral"></Tag>;
let label = '未报备'; const { label, tone } = carrierReportMeta(summary);
let tone: 'success' | 'danger' | 'warning' | 'info' | 'neutral' = 'neutral';
if (summary.status === 'approved') { label = '全部通过'; tone = 'success'; }
else if (summary.status === 'failed' || summary.status === 'rejected') { label = '报备失败'; tone = 'danger'; }
else if (summary.status === 'waiting_material') { label = '资料待补充'; tone = 'warning'; }
else if (summary.approved > 0) { label = '部分通过'; tone = 'info'; }
else if (summary.status === 'reporting' || summary.status === 'exporting') { label = '报备中'; tone = 'warning'; }
return <span className="carrier-report-summary"><Tag tone={tone}>{label}</Tag><small>{summary.approved}/{summary.total}</small></span>; return <span className="carrier-report-summary"><Tag tone={tone}>{label}</Tag><small>{summary.approved}/{summary.total}</small></span>;
} }
@@ -36,20 +30,31 @@ export function CarrierReportCount({ summary }: { summary?: CarrierReportSummary
if (!summary || summary.status === 'not_applicable' || summary.total === 0) { if (!summary || summary.status === 'not_applicable' || summary.total === 0) {
return <span className="carrier-report-count carrier-report-count--neutral" title="没有适用的目标通道"></span>; return <span className="carrier-report-count carrier-report-count--neutral" title="没有适用的目标通道"></span>;
} }
const tone = summary.approved === summary.total const { label, tone } = carrierReportMeta(summary);
? 'success'
: ['failed', 'rejected'].includes(summary.status) ? 'danger' : 'warning';
return ( return (
<span <span
aria-label={`已报备通过${summary.approved}个,总通道数${summary.total}`} aria-label={`${label}已报备通过${summary.approved}个,总通道数${summary.total}`}
className={`carrier-report-count carrier-report-count--${tone}`} className={`carrier-report-count carrier-report-count--${tone}`}
title={`已报备通过 ${summary.approved} 个 / 总通道数 ${summary.total}`} title={`${label}已报备通过 ${summary.approved} 个 / 总通道数 ${summary.total}`}
> >
{summary.approved}/{summary.total} <span>{label}</span>
<small>{summary.approved}/{summary.total}</small>
</span> </span>
); );
} }
function carrierReportMeta(summary: CarrierReportSummary) {
let label = '未报备';
let tone: 'success' | 'danger' | 'warning' | 'info' | 'neutral' = 'neutral';
if (summary.status === 'approved') { label = '全部通过'; tone = 'success'; }
else if (summary.status === 'abandoned') { label = '放弃报备'; tone = 'neutral'; }
else if (summary.status === 'failed' || summary.status === 'rejected') { label = '报备失败'; tone = 'danger'; }
else if (summary.status === 'waiting_material') { label = '资料待补充'; tone = 'warning'; }
else if (summary.approved > 0) { label = '部分通过'; tone = 'info'; }
else if (summary.status === 'reporting' || summary.status === 'exporting') { label = '报备中'; tone = 'warning'; }
return { label, tone };
}
export function signatureCardVisual(auditStatus: string, summaries?: Record<string, CarrierReportSummary>) { export function signatureCardVisual(auditStatus: string, summaries?: Record<string, CarrierReportSummary>) {
if (auditStatus === 'rejected') return { label: '签名审核已驳回', tone: 'red' as SignatureCardTone }; if (auditStatus === 'rejected') return { label: '签名审核已驳回', tone: 'red' as SignatureCardTone };
if (auditStatus === 'pending') return { label: '签名待审核', tone: 'amber' as SignatureCardTone }; if (auditStatus === 'pending') return { label: '签名待审核', tone: 'amber' as SignatureCardTone };
+9 -3
View File
@@ -2072,24 +2072,30 @@
.enterprise-signature-table__sortable { gap: 4px; } .enterprise-signature-table__sortable { gap: 4px; }
.enterprise-signature-table__sort-actions { gap: 2px; } .enterprise-signature-table__sort-actions { gap: 2px; }
.enterprise-signature-table__sort-actions .ui-button { min-height: 26px; min-width: 26px; padding: 4px; } .enterprise-signature-table__sort-actions .ui-button { min-height: 26px; min-width: 26px; padding: 4px; }
.enterprise-signature-table__sort-triangle--down { transform: rotate(180deg); }
.enterprise-signature-table__signature { min-width: 0; } .enterprise-signature-table__signature { min-width: 0; }
.enterprise-signature-table__signature strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .enterprise-signature-table__signature strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.carrier-report-count { .carrier-report-count {
align-items: center; align-items: center;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
display: inline-flex; display: inline-grid;
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
gap: 1px;
justify-content: center; justify-content: center;
min-width: 40px; justify-items: center;
padding: 4px 7px; line-height: 1.2;
min-width: 64px;
padding: 4px 6px;
} }
.carrier-report-count small { font-size: 11px; font-weight: var(--font-weight-medium); opacity: .82; }
.carrier-report-count--success { background: var(--color-success-soft); color: var(--color-success); } .carrier-report-count--success { background: var(--color-success-soft); color: var(--color-success); }
.carrier-report-count--warning { background: var(--color-warning-soft); color: var(--color-warning); } .carrier-report-count--warning { background: var(--color-warning-soft); color: var(--color-warning); }
.carrier-report-count--danger { background: var(--color-danger-soft); color: var(--color-danger); } .carrier-report-count--danger { background: var(--color-danger-soft); color: var(--color-danger); }
.carrier-report-count--info { background: var(--color-info-soft); color: var(--color-info); }
.carrier-report-count--neutral { background: var(--color-surface-subtle); color: var(--color-text-subtle); } .carrier-report-count--neutral { background: var(--color-surface-subtle); color: var(--color-text-subtle); }
.signature-report-status { .signature-report-status {