mirror of
https://github.com/hansjone/netx.git
synced 2026-10-11 05:43:20 +08:00
Tighten biz compare fullscreen board for cutover scanning.
Collapse equal pair cells, hide display columns by default, fold field filters, and keep failed sheets first so diffs stand out. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ee3ed26c88
commit
8a79e962a8
4 changed files with 259 additions and 88 deletions
|
|
@ -511,6 +511,9 @@ const en = {
|
||||||
phaseQueued: "Queued",
|
phaseQueued: "Queued",
|
||||||
phaseLoading: "Loading",
|
phaseLoading: "Loading",
|
||||||
phaseSqlCount: "Counting",
|
phaseSqlCount: "Counting",
|
||||||
|
phaseSqlProject: "Preparing tables",
|
||||||
|
phaseSqlJoin: "Joining",
|
||||||
|
phaseSqlFailFetch: "Fetching fails",
|
||||||
phaseComparing: "Comparing",
|
phaseComparing: "Comparing",
|
||||||
phasePersisting: "Writing",
|
phasePersisting: "Writing",
|
||||||
phasePersistingFail: "Writing fails",
|
phasePersistingFail: "Writing fails",
|
||||||
|
|
@ -520,6 +523,11 @@ const en = {
|
||||||
phaseCancelled: "Cancelled",
|
phaseCancelled: "Cancelled",
|
||||||
sheetPending: "Pending",
|
sheetPending: "Pending",
|
||||||
colProgress: "Progress / result",
|
colProgress: "Progress / result",
|
||||||
|
passRateScope: "all rows",
|
||||||
|
keyFiltersToggle: "Field filters",
|
||||||
|
hideDisplayCols: "Hide display cols",
|
||||||
|
showDisplayCols: "Show display cols",
|
||||||
|
hideDisplayColsHint: "Compact: hide display-only columns so keys and diffs stand out",
|
||||||
ranWithDuration: "Compare finished ({{s}}s)",
|
ranWithDuration: "Compare finished ({{s}}s)",
|
||||||
unchangedNotStored: "Success rows were counted but not stored. Set “Store success rows” to sample and re-run for spot-check.",
|
unchangedNotStored: "Success rows were counted but not stored. Set “Store success rows” to sample and re-run for spot-check.",
|
||||||
unchangedSampleHint: "{{total}} success rows total; browsing a stratified sample of {{listed}}. Search any route for live source lookup. Pass rate uses all {{total}}.",
|
unchangedSampleHint: "{{total}} success rows total; browsing a stratified sample of {{listed}}. Search any route for live source lookup. Pass rate uses all {{total}}.",
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,9 @@ const zh = {
|
||||||
phaseQueued: "排队中",
|
phaseQueued: "排队中",
|
||||||
phaseLoading: "加载数据",
|
phaseLoading: "加载数据",
|
||||||
phaseSqlCount: "库内统计",
|
phaseSqlCount: "库内统计",
|
||||||
|
phaseSqlProject: "准备比对表",
|
||||||
|
phaseSqlJoin: "库内比对",
|
||||||
|
phaseSqlFailFetch: "拉取失败行",
|
||||||
phaseComparing: "比对中",
|
phaseComparing: "比对中",
|
||||||
phasePersisting: "写入结果",
|
phasePersisting: "写入结果",
|
||||||
phasePersistingFail: "写入失败行",
|
phasePersistingFail: "写入失败行",
|
||||||
|
|
@ -519,6 +522,11 @@ const zh = {
|
||||||
phaseCancelled: "已取消",
|
phaseCancelled: "已取消",
|
||||||
sheetPending: "等待中",
|
sheetPending: "等待中",
|
||||||
colProgress: "进度 / 结果",
|
colProgress: "进度 / 结果",
|
||||||
|
passRateScope: "全表",
|
||||||
|
keyFiltersToggle: "字段筛选",
|
||||||
|
hideDisplayCols: "隐藏展示列",
|
||||||
|
showDisplayCols: "显示展示列",
|
||||||
|
hideDisplayColsHint: "紧凑模式:默认隐藏不参与比对的展示列,突出 Key 与差异",
|
||||||
ranWithDuration: "比对完成(耗时 {{s}} 秒)",
|
ranWithDuration: "比对完成(耗时 {{s}} 秒)",
|
||||||
unchangedNotStored: "成功行仅统计数量未落库。可在任务配置将「成功行保存」改为抽样后重新比对(抽查用)。",
|
unchangedNotStored: "成功行仅统计数量未落库。可在任务配置将「成功行保存」改为抽样后重新比对(抽查用)。",
|
||||||
unchangedSampleHint: "成功共 {{total}} 条,明细抽样 {{listed}} 条(分层抽查)。要查任意路由请输入筛选条件——将按原表即时判定。通过率按全部 {{total}} 计。",
|
unchangedSampleHint: "成功共 {{total}} 条,明细抽样 {{listed}} 条(分层抽查)。要查任意路由请输入筛选条件——将按原表即时判定。通过率按全部 {{total}} 计。",
|
||||||
|
|
|
||||||
|
|
@ -12970,10 +12970,6 @@ body:has(.bs-cmp-board:fullscreen) {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-val-cell--diff {
|
|
||||||
background: rgba(245, 158, 11, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-cmp-pair {
|
.bs-cmp-pair {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -12981,6 +12977,10 @@ body:has(.bs-cmp-board:fullscreen) {
|
||||||
min-width: 4.5rem;
|
min-width: 4.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bs-cmp-pair--same {
|
||||||
|
min-width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.bs-cmp-pair__row {
|
.bs-cmp-pair__row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.6em minmax(0, 1fr);
|
grid-template-columns: 1.6em minmax(0, 1fr);
|
||||||
|
|
@ -13005,6 +13005,20 @@ body:has(.bs-cmp-board:fullscreen) {
|
||||||
color: #86efac;
|
color: #86efac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bs-cmp-val-cell--pair.is-same {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-val--same {
|
||||||
|
color: #cbd5e1;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-val-cell--diff {
|
||||||
|
background: rgba(245, 158, 11, 0.1);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(245, 158, 11, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
.bs-cmp-val {
|
.bs-cmp-val {
|
||||||
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
|
|
@ -13012,6 +13026,63 @@ body:has(.bs-cmp-board:fullscreen) {
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bs-cmp-progress.is-compact {
|
||||||
|
padding: 6px 10px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-progress.is-compact .bs-cmp-progress__track {
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-progress.is-compact .bs-cmp-progress__meta {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-strip__pass-scope {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #64748b;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-strip__toggle {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-strip__toggle:hover {
|
||||||
|
border-color: rgba(96, 165, 250, 0.45);
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-strip__toggle.is-active {
|
||||||
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
|
background: rgba(37, 99, 235, 0.18);
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-strip__hint {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-cmp-strip .bs-cmp-key-filters {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.bs-cmp-val.is-empty {
|
.bs-cmp-val.is-empty {
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,8 @@ function PairCell(props: {
|
||||||
const post = afterText || "—";
|
const post = afterText || "—";
|
||||||
const isAdded = kind === "added";
|
const isAdded = kind === "added";
|
||||||
const isRemoved = kind === "removed";
|
const isRemoved = kind === "removed";
|
||||||
|
const same =
|
||||||
|
!mismatch && !isAdded && !isRemoved && String(beforeText || "") === String(afterText || "");
|
||||||
// Whole-row missing/extra: emphasize the present side; do not strike it out.
|
// Whole-row missing/extra: emphasize the present side; do not strike it out.
|
||||||
const preClass = [
|
const preClass = [
|
||||||
"bs-cmp-val",
|
"bs-cmp-val",
|
||||||
|
|
@ -217,20 +219,31 @@ function PairCell(props: {
|
||||||
<td
|
<td
|
||||||
className={`bs-cmp-val-cell bs-cmp-val-cell--pair${
|
className={`bs-cmp-val-cell bs-cmp-val-cell--pair${
|
||||||
mismatch || isAdded || isRemoved ? " bs-cmp-val-cell--diff" : ""
|
mismatch || isAdded || isRemoved ? " bs-cmp-val-cell--diff" : ""
|
||||||
}${isAdded ? " is-added" : ""}${isRemoved ? " is-removed" : ""}${
|
}${same ? " is-same" : ""}${isAdded ? " is-added" : ""}${isRemoved ? " is-removed" : ""}${
|
||||||
zoneStart ? " bs-cmp-zone-start" : ""
|
zoneStart ? " bs-cmp-zone-start" : ""
|
||||||
}`}
|
}`}
|
||||||
|
title={
|
||||||
|
same
|
||||||
|
? pre
|
||||||
|
: `${beforeLabel} ${isAdded ? "—" : pre} → ${afterLabel} ${isRemoved ? "—" : post}`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="bs-cmp-pair">
|
{same ? (
|
||||||
<div className="bs-cmp-pair__row">
|
<div className="bs-cmp-pair bs-cmp-pair--same">
|
||||||
<span className="bs-cmp-pair__tag">{beforeLabel}</span>
|
<span className="bs-cmp-val bs-cmp-val--same">{pre || "—"}</span>
|
||||||
<span className={preClass}>{isAdded ? "—" : pre}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="bs-cmp-pair__row">
|
) : (
|
||||||
<span className="bs-cmp-pair__tag">{afterLabel}</span>
|
<div className="bs-cmp-pair">
|
||||||
<span className={postClass}>{isRemoved ? "—" : post}</span>
|
<div className="bs-cmp-pair__row">
|
||||||
|
<span className="bs-cmp-pair__tag">{beforeLabel}</span>
|
||||||
|
<span className={preClass}>{isAdded ? "—" : pre}</span>
|
||||||
|
</div>
|
||||||
|
<div className="bs-cmp-pair__row">
|
||||||
|
<span className="bs-cmp-pair__tag">{afterLabel}</span>
|
||||||
|
<span className={postClass}>{isRemoved ? "—" : post}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
{reason ? <div className="bs-cmp-val-reason muted">{reason}</div> : null}
|
{reason ? <div className="bs-cmp-val-reason muted">{reason}</div> : null}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|
@ -363,6 +376,9 @@ function localizeRunPhase(phase: string | undefined, t: (key: string) => string)
|
||||||
queued: "bizCompare.phaseQueued",
|
queued: "bizCompare.phaseQueued",
|
||||||
loading: "bizCompare.phaseLoading",
|
loading: "bizCompare.phaseLoading",
|
||||||
sql_count: "bizCompare.phaseSqlCount",
|
sql_count: "bizCompare.phaseSqlCount",
|
||||||
|
sql_project: "bizCompare.phaseSqlProject",
|
||||||
|
sql_join: "bizCompare.phaseSqlJoin",
|
||||||
|
sql_fail_fetch: "bizCompare.phaseSqlFailFetch",
|
||||||
comparing: "bizCompare.phaseComparing",
|
comparing: "bizCompare.phaseComparing",
|
||||||
done: "bizCompare.phaseDone",
|
done: "bizCompare.phaseDone",
|
||||||
failed: "bizCompare.phaseFailed",
|
failed: "bizCompare.phaseFailed",
|
||||||
|
|
@ -944,6 +960,8 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
const [resultKw, setResultKw] = useState("");
|
const [resultKw, setResultKw] = useState("");
|
||||||
const debouncedResultKw = useDebouncedValue(resultKw, 300);
|
const debouncedResultKw = useDebouncedValue(resultKw, 300);
|
||||||
const [resultKeyFilters, setResultKeyFilters] = useState<Record<string, string>>({});
|
const [resultKeyFilters, setResultKeyFilters] = useState<Record<string, string>>({});
|
||||||
|
const [keyFiltersOpen, setKeyFiltersOpen] = useState(false);
|
||||||
|
const [hideDisplayCols, setHideDisplayCols] = useState(true);
|
||||||
const debouncedKeyFiltersJson = useDebouncedValue(JSON.stringify(resultKeyFilters), 300);
|
const debouncedKeyFiltersJson = useDebouncedValue(JSON.stringify(resultKeyFilters), 300);
|
||||||
const debouncedKeyFilters = useMemo(() => {
|
const debouncedKeyFilters = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -959,6 +977,10 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
}, [debouncedKeyFiltersJson]);
|
}, [debouncedKeyFiltersJson]);
|
||||||
const hasResultSearch =
|
const hasResultSearch =
|
||||||
Boolean(debouncedResultKw.trim()) || Object.keys(debouncedKeyFilters).length > 0;
|
Boolean(debouncedResultKw.trim()) || Object.keys(debouncedKeyFilters).length > 0;
|
||||||
|
const activeKeyFilterCount = Object.keys(resultKeyFilters).filter((k) =>
|
||||||
|
String(resultKeyFilters[k] || "").trim(),
|
||||||
|
).length;
|
||||||
|
const keyFiltersVisible = keyFiltersOpen || activeKeyFilterCount > 0;
|
||||||
const [resultPage, setResultPage] = useState(1);
|
const [resultPage, setResultPage] = useState(1);
|
||||||
const [resultPageSize, setResultPageSize] = useState(100);
|
const [resultPageSize, setResultPageSize] = useState(100);
|
||||||
const [resultTotal, setResultTotal] = useState(0);
|
const [resultTotal, setResultTotal] = useState(0);
|
||||||
|
|
@ -1419,7 +1441,12 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
1 +
|
1 +
|
||||||
(showFailCol ? 1 : 0) +
|
(showFailCol ? 1 : 0) +
|
||||||
resultColumns.keys.length +
|
resultColumns.keys.length +
|
||||||
Math.max(resultColumns.extras.length, 0);
|
Math.max(
|
||||||
|
resultColumns.extras.filter(
|
||||||
|
(f) => resultColumns.compareSet.has(f) || !hideDisplayCols,
|
||||||
|
).length,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
const rememberTableScroll = useCallback(() => {
|
const rememberTableScroll = useCallback(() => {
|
||||||
const wrap = tableScrollRef.current;
|
const wrap = tableScrollRef.current;
|
||||||
|
|
@ -3404,7 +3431,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{runDetail && runIsActive ? (
|
{runDetail && runIsActive ? (
|
||||||
<div className="bs-cmp-progress" role="status" aria-live="polite">
|
<div className="bs-cmp-progress is-compact" role="status" aria-live="polite">
|
||||||
<div className="bs-cmp-progress__head">
|
<div className="bs-cmp-progress__head">
|
||||||
<NmStatusChip color="accent">{t("bizCompare.runStatusRunning")}</NmStatusChip>
|
<NmStatusChip color="accent">{t("bizCompare.runStatusRunning")}</NmStatusChip>
|
||||||
{runEngine === "sql" || runEngine === "python" ? (
|
{runEngine === "sql" || runEngine === "python" ? (
|
||||||
|
|
@ -3537,36 +3564,54 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
<div className="bs-cmp-nav__list" role="tablist">
|
<div className="bs-cmp-nav__list" role="tablist">
|
||||||
{(sheetCards.length
|
{(sheetCards.length
|
||||||
? sheetCards
|
? sheetCards
|
||||||
: runSheets.map((s) => {
|
: [...runSheets]
|
||||||
const removed = Number(s.summary?.removed || 0);
|
.map((s) => {
|
||||||
const changed = Number(s.summary?.changed || 0);
|
const removed = Number(s.summary?.removed || 0);
|
||||||
const unchanged = Number(s.summary?.unchanged || 0);
|
const changed = Number(s.summary?.changed || 0);
|
||||||
const fail = removed + changed;
|
const unchanged = Number(s.summary?.unchanged || 0);
|
||||||
const judged = fail + unchanged;
|
const fail = removed + changed;
|
||||||
const st = String((s as any).status || "done");
|
const judged = fail + unchanged;
|
||||||
const pending = ["pending", "running", "queued"].includes(st);
|
const st = String((s as any).status || "done");
|
||||||
return {
|
const pending = ["pending", "running", "queued"].includes(st);
|
||||||
sheet_id: sheetIdentity(s),
|
return {
|
||||||
title: sheetLabel(s),
|
sheet_id: sheetIdentity(s),
|
||||||
metric_id: s.metric_id,
|
title: sheetLabel(s),
|
||||||
mode: s.mode,
|
metric_id: s.metric_id,
|
||||||
status: st,
|
mode: s.mode,
|
||||||
added: s.summary?.added,
|
status: st,
|
||||||
removed,
|
added: s.summary?.added,
|
||||||
changed,
|
removed,
|
||||||
unchanged,
|
changed,
|
||||||
before_count: s.summary?.before_count,
|
unchanged,
|
||||||
after_count: s.summary?.after_count,
|
before_count: s.summary?.before_count,
|
||||||
fail_count: fail,
|
after_count: s.summary?.after_count,
|
||||||
success_count: unchanged,
|
fail_count: fail,
|
||||||
diff_count: fail,
|
success_count: unchanged,
|
||||||
pass_rate: pending
|
diff_count: fail,
|
||||||
? null
|
pass_rate: pending
|
||||||
: judged
|
? null
|
||||||
? Math.round((unchanged / judged) * 1000) / 10
|
: judged
|
||||||
: 100,
|
? Math.round((unchanged / judged) * 1000) / 10
|
||||||
};
|
: 100,
|
||||||
})
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const pa = ["pending", "running", "queued"].includes(
|
||||||
|
String(a.status || ""),
|
||||||
|
)
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
const pb = ["pending", "running", "queued"].includes(
|
||||||
|
String(b.status || ""),
|
||||||
|
)
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
if (pa !== pb) return pa - pb;
|
||||||
|
const da = Number(a.fail_count || 0);
|
||||||
|
const db = Number(b.fail_count || 0);
|
||||||
|
if (da !== db) return db - da;
|
||||||
|
return String(a.metric_id).localeCompare(String(b.metric_id));
|
||||||
|
})
|
||||||
).map((c) => {
|
).map((c) => {
|
||||||
const fail = sheetFailOf(c);
|
const fail = sheetFailOf(c);
|
||||||
const ok = sheetSuccessOf(c);
|
const ok = sheetSuccessOf(c);
|
||||||
|
|
@ -3586,11 +3631,17 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
className={`bs-cmp-nav__item${active ? " is-active" : ""}${
|
className={`bs-cmp-nav__item${active ? " is-active" : ""}${
|
||||||
pending ? " is-pending" : fail > 0 ? " has-diff" : " is-clean"
|
pending ? " is-pending" : fail > 0 ? " has-diff" : " is-clean"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setResultSheetId(id)}
|
onClick={() => {
|
||||||
|
setResultSheetId(id);
|
||||||
|
// Cutover default: land on fails when switching sheets
|
||||||
|
if (fail > 0 && kindFilter === "unchanged") {
|
||||||
|
setKindFilter("diff");
|
||||||
|
}
|
||||||
|
}}
|
||||||
title={
|
title={
|
||||||
pending
|
pending
|
||||||
? `${label} · ${t("bizCompare.sheetPending")}`
|
? `${label} · ${t("bizCompare.sheetPending")}`
|
||||||
: `${label} · ${t("bizCompare.kindFail")} ${fail} · ${t("bizCompare.kindSuccess")} ${ok} · ${t("bizCompare.passRateShort")} ${rate}%`
|
: `${label} · ${t("bizCompare.kindFail")} ${fail} · ${t("bizCompare.kindSuccess")} ${ok} · ${t("bizCompare.passRateShort")} ${rate ?? "…"}%`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="bs-cmp-nav__dot" aria-hidden />
|
<span className="bs-cmp-nav__dot" aria-hidden />
|
||||||
|
|
@ -3650,9 +3701,15 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
: ` · ${activeSheetCard?.before_count ?? 0}→${activeSheetCard?.after_count ?? 0}`}
|
: ` · ${activeSheetCard?.before_count ?? 0}→${activeSheetCard?.after_count ?? 0}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bs-cmp-strip__pass" title={t("bizCompare.passRate")}>
|
<div
|
||||||
|
className="bs-cmp-strip__pass"
|
||||||
|
title={`${t("bizCompare.passRate")} · ${t("bizCompare.passRateScope")}`}
|
||||||
|
>
|
||||||
<span className="bs-cmp-strip__pass-label">
|
<span className="bs-cmp-strip__pass-label">
|
||||||
{t("bizCompare.passRate")}
|
{t("bizCompare.passRate")}
|
||||||
|
<span className="bs-cmp-strip__pass-scope">
|
||||||
|
{t("bizCompare.passRateScope")}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="bs-cmp-strip__pass-value">
|
<span className="bs-cmp-strip__pass-value">
|
||||||
{activeSheetPending || activePassRate === null
|
{activeSheetPending || activePassRate === null
|
||||||
|
|
@ -3708,9 +3765,31 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
<span className="muted bs-sheet-count">
|
<span className="muted bs-sheet-count">
|
||||||
{diffsLoading ? "…" : `${pagedDiffs.length}/${resultTotal}`}
|
{diffsLoading ? "…" : `${pagedDiffs.length}/${resultTotal}`}
|
||||||
</span>
|
</span>
|
||||||
|
{resultSearchKeyFields.length ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`bs-cmp-strip__toggle${keyFiltersVisible ? " is-active" : ""}`}
|
||||||
|
onClick={() => setKeyFiltersOpen((v) => !v)}
|
||||||
|
>
|
||||||
|
{t("bizCompare.keyFiltersToggle")}
|
||||||
|
{activeKeyFilterCount
|
||||||
|
? ` (${activeKeyFilterCount})`
|
||||||
|
: ""}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`bs-cmp-strip__toggle${hideDisplayCols ? " is-active" : ""}`}
|
||||||
|
title={t("bizCompare.hideDisplayColsHint")}
|
||||||
|
onClick={() => setHideDisplayCols((v) => !v)}
|
||||||
|
>
|
||||||
|
{hideDisplayCols
|
||||||
|
? t("bizCompare.showDisplayCols")
|
||||||
|
: t("bizCompare.hideDisplayCols")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{resultSearchKeyFields.length ? (
|
{resultSearchKeyFields.length && keyFiltersVisible ? (
|
||||||
<div className="bs-cmp-key-filters" style={{ marginTop: 6 }}>
|
<div className="bs-cmp-key-filters">
|
||||||
{resultSearchKeyFields.map((f) => (
|
{resultSearchKeyFields.map((f) => (
|
||||||
<label key={f} className="bs-cmp-key-filter">
|
<label key={f} className="bs-cmp-key-filter">
|
||||||
<span className="muted">{f}</span>
|
<span className="muted">{f}</span>
|
||||||
|
|
@ -3735,9 +3814,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
{Object.keys(resultKeyFilters).some((k) =>
|
{activeKeyFilterCount ? (
|
||||||
String(resultKeyFilters[k] || "").trim(),
|
|
||||||
) ? (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="linkish"
|
className="linkish"
|
||||||
|
|
@ -3749,7 +3826,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{isLiveSearch ? (
|
{isLiveSearch ? (
|
||||||
<p className="muted bm-hint" style={{ margin: "4px 0 0" }}>
|
<p className="muted bm-hint bs-cmp-strip__hint">
|
||||||
{diffsTruncated
|
{diffsTruncated
|
||||||
? t("bizCompare.liveSearchTruncatedHint")
|
? t("bizCompare.liveSearchTruncatedHint")
|
||||||
: t("bizCompare.liveSearchHint")}
|
: t("bizCompare.liveSearchHint")}
|
||||||
|
|
@ -3773,7 +3850,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
runDetail?.summary?.unchanged_listed ??
|
runDetail?.summary?.unchanged_listed ??
|
||||||
0,
|
0,
|
||||||
) > 0)) ? (
|
) > 0)) ? (
|
||||||
<p className="muted bm-hint" style={{ margin: "4px 0 0" }}>
|
<p className="muted bm-hint bs-cmp-strip__hint">
|
||||||
{t("bizCompare.unchangedSampleHint", {
|
{t("bizCompare.unchangedSampleHint", {
|
||||||
listed: String(
|
listed: String(
|
||||||
Number(
|
Number(
|
||||||
|
|
@ -3808,9 +3885,11 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
const compareCols = resultColumns.extras.filter((f) =>
|
const compareCols = resultColumns.extras.filter((f) =>
|
||||||
resultColumns.compareSet.has(f),
|
resultColumns.compareSet.has(f),
|
||||||
);
|
);
|
||||||
const displayCols = resultColumns.extras.filter(
|
const displayCols = hideDisplayCols
|
||||||
(f) => !resultColumns.compareSet.has(f),
|
? []
|
||||||
);
|
: resultColumns.extras.filter(
|
||||||
|
(f) => !resultColumns.compareSet.has(f),
|
||||||
|
);
|
||||||
const verdictColSpan = showFailCol ? 2 : 1;
|
const verdictColSpan = showFailCol ? 2 : 1;
|
||||||
const hasGroups =
|
const hasGroups =
|
||||||
keyCols.length + compareCols.length + displayCols.length > 0;
|
keyCols.length + compareCols.length + displayCols.length > 0;
|
||||||
|
|
@ -3942,36 +4021,41 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
||||||
{cellText(d.key?.[k] ?? pre[k] ?? post[k]) || "—"}
|
{cellText(d.key?.[k] ?? pre[k] ?? post[k]) || "—"}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
{resultColumns.extras.map((f, fi) => {
|
{resultColumns.extras
|
||||||
const pv = cellText(pre[f]);
|
.filter(
|
||||||
const av = cellText(post[f]);
|
(f) =>
|
||||||
const ch = d.changes?.[f];
|
resultColumns.compareSet.has(f) || !hideDisplayCols,
|
||||||
const isCmp = resultColumns.compareSet.has(f);
|
)
|
||||||
const prev = resultColumns.extras[fi - 1];
|
.map((f, fi, visibleExtras) => {
|
||||||
const prevCmp = prev
|
const pv = cellText(pre[f]);
|
||||||
? resultColumns.compareSet.has(prev)
|
const av = cellText(post[f]);
|
||||||
: null;
|
const ch = d.changes?.[f];
|
||||||
const zoneStart = fi === 0 || prevCmp !== isCmp;
|
const isCmp = resultColumns.compareSet.has(f);
|
||||||
const mismatch =
|
const prev = visibleExtras[fi - 1];
|
||||||
d.kind === "added" || d.kind === "removed"
|
const prevCmp = prev
|
||||||
? Boolean(pv || av)
|
? resultColumns.compareSet.has(prev)
|
||||||
: isCmp
|
: null;
|
||||||
? Boolean(ch)
|
const zoneStart = fi === 0 || prevCmp !== isCmp;
|
||||||
: pv !== av;
|
const mismatch =
|
||||||
return (
|
d.kind === "added" || d.kind === "removed"
|
||||||
<PairCell
|
? Boolean(pv || av)
|
||||||
key={f}
|
: isCmp
|
||||||
beforeText={pv}
|
? Boolean(ch)
|
||||||
afterText={av}
|
: pv !== av;
|
||||||
kind={d.kind}
|
return (
|
||||||
mismatch={mismatch}
|
<PairCell
|
||||||
reason={ch?.reason}
|
key={f}
|
||||||
beforeLabel={t("bizCompare.pairBefore")}
|
beforeText={pv}
|
||||||
afterLabel={t("bizCompare.pairAfter")}
|
afterText={av}
|
||||||
zoneStart={zoneStart}
|
kind={d.kind}
|
||||||
/>
|
mismatch={mismatch}
|
||||||
);
|
reason={ch?.reason}
|
||||||
})}
|
beforeLabel={t("bizCompare.pairBefore")}
|
||||||
|
afterLabel={t("bizCompare.pairAfter")}
|
||||||
|
zoneStart={zoneStart}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue