mirror of
https://github.com/hansjone/netx.git
synced 2026-10-19 03:00:46 +08:00
Fix collect time on compare sides and clean left-nav stat digits.
Fill started_at from batch lists, show compact times, and right-align color-only -/+~/ = counts per sheet. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d96cbf330a
commit
357b831f2d
3 changed files with 169 additions and 134 deletions
|
|
@ -406,6 +406,8 @@ def list_batches(db: Session, task_id: str, *, limit: int = 50) -> list[dict[str
|
||||||
"command_count": b.command_count,
|
"command_count": b.command_count,
|
||||||
"row_count": b.row_count,
|
"row_count": b.row_count,
|
||||||
"message": b.message,
|
"message": b.message,
|
||||||
|
"ne_name": b.ne_name or "",
|
||||||
|
"ne_id": b.ne_id or "",
|
||||||
"started_at": b.started_at.isoformat() + "Z" if b.started_at else None,
|
"started_at": b.started_at.isoformat() + "Z" if b.started_at else None,
|
||||||
"ended_at": b.ended_at.isoformat() + "Z" if b.ended_at else None,
|
"ended_at": b.ended_at.isoformat() + "Z" if b.ended_at else None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10130,7 +10130,7 @@ html.login-page--paused .login-page__flare {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--bs-cmp-border, rgba(148, 163, 184, 0.22));
|
border: 1px solid var(--bs-cmp-border, rgba(148, 163, 184, 0.22));
|
||||||
background: rgba(15, 23, 42, 0.45);
|
background: rgba(15, 23, 42, 0.45);
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-sides__side {
|
.bs-cmp-sides__side {
|
||||||
|
|
@ -10139,9 +10139,11 @@ html.login-page--paused .login-page__flare {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
flex: 0 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-sides__side.is-before {
|
.bs-cmp-sides__side.is-before {
|
||||||
|
|
@ -10177,7 +10179,9 @@ html.login-page--paused .login-page__flare {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 10rem;
|
max-width: 8rem;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 0 1 auto;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -10186,6 +10190,7 @@ html.login-page--paused .login-page__flare {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-sides__arrow {
|
.bs-cmp-sides__arrow {
|
||||||
|
|
@ -10256,6 +10261,18 @@ html.login-page--paused .login-page__flare {
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bs-cmp-nav__legend {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1.6em);
|
||||||
|
gap: 2px;
|
||||||
|
justify-items: end;
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-left: auto;
|
||||||
|
width: fit-content;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__list {
|
.bs-cmp-nav__list {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
@ -10269,10 +10286,9 @@ html.login-page--paused .login-page__flare {
|
||||||
.bs-cmp-nav__item {
|
.bs-cmp-nav__item {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 8px minmax(0, 1fr);
|
grid-template-columns: 8px minmax(0, 1fr) auto;
|
||||||
grid-template-rows: auto auto;
|
gap: 0 8px;
|
||||||
gap: 4px 8px;
|
align-items: center;
|
||||||
align-items: start;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|
@ -10300,7 +10316,6 @@ html.login-page--paused .login-page__flare {
|
||||||
background: #64748b;
|
background: #64748b;
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
grid-row: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__item.is-clean .bs-cmp-nav__dot {
|
.bs-cmp-nav__item.is-clean .bs-cmp-nav__dot {
|
||||||
|
|
@ -10318,81 +10333,45 @@ html.login-page--paused .login-page__flare {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 1;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-cmp-nav__badges {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 4px;
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 2;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__badge {
|
.bs-cmp-nav__stats {
|
||||||
|
display: inline-grid;
|
||||||
|
grid-template-columns: repeat(4, 1.6em);
|
||||||
|
gap: 2px;
|
||||||
|
justify-items: end;
|
||||||
|
align-items: center;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
white-space: nowrap;
|
|
||||||
padding: 1px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
line-height: 1.45;
|
|
||||||
color: #94a3b8;
|
|
||||||
background: rgba(148, 163, 184, 0.1);
|
|
||||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__badge b {
|
.bs-cmp-nav__num {
|
||||||
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-left: 2px;
|
line-height: 1;
|
||||||
color: #cbd5e1;
|
color: #475569;
|
||||||
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__badge--removed.is-hot {
|
.bs-cmp-nav__num--removed.is-hot {
|
||||||
color: #fecaca;
|
color: #f87171;
|
||||||
background: rgba(239, 68, 68, 0.14);
|
opacity: 1;
|
||||||
border-color: rgba(248, 113, 113, 0.35);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__badge--removed.is-hot b {
|
.bs-cmp-nav__num--added.is-hot {
|
||||||
color: #fca5a5;
|
color: #4ade80;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__badge--added.is-hot {
|
.bs-cmp-nav__num--changed.is-hot {
|
||||||
color: #bbf7d0;
|
|
||||||
background: rgba(34, 197, 94, 0.14);
|
|
||||||
border-color: rgba(74, 222, 128, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-cmp-nav__badge--added.is-hot b {
|
|
||||||
color: #86efac;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-cmp-nav__badge--changed.is-hot {
|
|
||||||
color: #fde68a;
|
|
||||||
background: rgba(245, 158, 11, 0.14);
|
|
||||||
border-color: rgba(251, 191, 36, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-cmp-nav__badge--changed.is-hot b {
|
|
||||||
color: #fbbf24;
|
color: #fbbf24;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-nav__badge--unchanged b {
|
.bs-cmp-nav__num--unchanged {
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
opacity: 0.85;
|
||||||
|
|
||||||
.bs-cmp-nav__badge--pass.is-hot {
|
|
||||||
color: #bbf7d0;
|
|
||||||
background: rgba(34, 197, 94, 0.14);
|
|
||||||
border-color: rgba(74, 222, 128, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-cmp-nav__badge--pass.is-hot b {
|
|
||||||
color: #86efac;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-cmp-main {
|
.bs-cmp-main {
|
||||||
|
|
|
||||||
|
|
@ -224,30 +224,61 @@ function sideDeviceName(side?: {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sideCollectTime(side?: { started_at?: string | null } | null): string {
|
function sideCollectTime(side?: { started_at?: string | null } | null): string {
|
||||||
return fmtTime(side?.started_at);
|
const raw = side?.started_at;
|
||||||
|
if (!raw) return "—";
|
||||||
|
const full = fmtTime(raw);
|
||||||
|
if (!full || full === raw) {
|
||||||
|
// Prefer compact local time when possible
|
||||||
|
try {
|
||||||
|
const d = new Date(/[zZ]$|[+-]\d{2}:\d{2}$/.test(String(raw)) ? raw : `${String(raw).replace(" ", "T")}Z`);
|
||||||
|
if (!Number.isNaN(d.getTime())) {
|
||||||
|
const m = d.getMonth() + 1;
|
||||||
|
const day = d.getDate();
|
||||||
|
const hh = String(d.getHours()).padStart(2, "0");
|
||||||
|
const mm = String(d.getMinutes()).padStart(2, "0");
|
||||||
|
return `${m}/${day} ${hh}:${mm}`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* fall through */
|
||||||
|
}
|
||||||
|
return full || "—";
|
||||||
|
}
|
||||||
|
// Compact: drop seconds / year noise from locale string when long
|
||||||
|
// e.g. "2026/9/18 09:35:53" -> "9/18 09:35"
|
||||||
|
const m = String(full).match(/(?:\d{4}[/-])?(\d{1,2})[/-](\d{1,2})\s+(\d{2}):(\d{2})/);
|
||||||
|
if (m) return `${m[1]}/${m[2]} ${m[3]}:${m[4]}`;
|
||||||
|
return full;
|
||||||
}
|
}
|
||||||
|
|
||||||
function enrichSideFromTask(
|
type SideInfo = {
|
||||||
side: Record<string, unknown> | null | undefined,
|
|
||||||
taskId: string,
|
|
||||||
tasks: TaskOpt[],
|
|
||||||
): { ne_name?: string; ne_ip?: string; label?: string; batch_id?: string; started_at?: string | null } {
|
|
||||||
const base = { ...(side || {}) } as {
|
|
||||||
ne_name?: string;
|
ne_name?: string;
|
||||||
ne_ip?: string;
|
ne_ip?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
batch_id?: string;
|
batch_id?: string;
|
||||||
started_at?: string | null;
|
started_at?: string | null;
|
||||||
};
|
};
|
||||||
if (sideDeviceName(base) !== "—") return base;
|
|
||||||
|
function enrichSide(
|
||||||
|
side: Record<string, unknown> | null | undefined,
|
||||||
|
taskId: string,
|
||||||
|
tasks: TaskOpt[],
|
||||||
|
batches: BatchOpt[],
|
||||||
|
): SideInfo {
|
||||||
|
const base: SideInfo = { ...(side || {}) } as SideInfo;
|
||||||
|
const bid = String(base.batch_id || "").trim();
|
||||||
|
if (!base.started_at && bid) {
|
||||||
|
const batch = batches.find((b) => b.id === bid);
|
||||||
|
if (batch?.started_at) base.started_at = batch.started_at;
|
||||||
|
}
|
||||||
|
if (sideDeviceName(base) === "—") {
|
||||||
const task = tasks.find((t) => t.id === taskId);
|
const task = tasks.find((t) => t.id === taskId);
|
||||||
if (!task) return base;
|
if (task) {
|
||||||
return {
|
base.ne_name = task.ne_name || base.ne_name;
|
||||||
...base,
|
base.ne_ip = task.ne_ip || base.ne_ip;
|
||||||
ne_name: task.ne_name || base.ne_name,
|
base.label = task.ne_name || task.ne_ip || base.label;
|
||||||
ne_ip: task.ne_ip || base.ne_ip,
|
}
|
||||||
label: task.ne_name || task.ne_ip || base.label,
|
}
|
||||||
};
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
function taskLabel(row: TaskOpt) {
|
function taskLabel(row: TaskOpt) {
|
||||||
|
|
@ -2113,15 +2144,17 @@ export function BizComparePage() {
|
||||||
>
|
>
|
||||||
<option value="">{t("bizCompare.pickRun")}</option>
|
<option value="">{t("bizCompare.pickRun")}</option>
|
||||||
{runs.map((r) => {
|
{runs.map((r) => {
|
||||||
const before = enrichSideFromTask(
|
const before = enrichSide(
|
||||||
(r as any).before,
|
(r as any).before,
|
||||||
beforeTaskId,
|
beforeTaskId,
|
||||||
tasks,
|
tasks,
|
||||||
|
beforeBatches,
|
||||||
);
|
);
|
||||||
const after = enrichSideFromTask(
|
const after = enrichSide(
|
||||||
(r as any).after,
|
(r as any).after,
|
||||||
afterTaskId || beforeTaskId,
|
afterTaskId || beforeTaskId,
|
||||||
tasks,
|
tasks,
|
||||||
|
afterBatches.length ? afterBatches : beforeBatches,
|
||||||
);
|
);
|
||||||
const bl = sideDeviceName(before);
|
const bl = sideDeviceName(before);
|
||||||
const al = sideDeviceName(after);
|
const al = sideDeviceName(after);
|
||||||
|
|
@ -2135,28 +2168,31 @@ export function BizComparePage() {
|
||||||
{runDetail ? (
|
{runDetail ? (
|
||||||
<div className="bs-cmp-sides" aria-label={t("bizCompare.sidesTitle")}>
|
<div className="bs-cmp-sides" aria-label={t("bizCompare.sidesTitle")}>
|
||||||
{(() => {
|
{(() => {
|
||||||
const before = enrichSideFromTask(
|
const before = enrichSide(
|
||||||
runDetail.before,
|
runDetail.before,
|
||||||
beforeTaskId,
|
beforeTaskId,
|
||||||
tasks,
|
tasks,
|
||||||
|
beforeBatches,
|
||||||
);
|
);
|
||||||
const after = enrichSideFromTask(
|
const after = enrichSide(
|
||||||
runDetail.after,
|
runDetail.after,
|
||||||
afterTaskId || beforeTaskId,
|
afterTaskId || beforeTaskId,
|
||||||
tasks,
|
tasks,
|
||||||
|
afterBatches.length ? afterBatches : beforeBatches,
|
||||||
);
|
);
|
||||||
|
const beforeName = sideDeviceName(before);
|
||||||
|
const afterName = sideDeviceName(after);
|
||||||
|
const beforeTime = sideCollectTime(before);
|
||||||
|
const afterTime = sideCollectTime(after);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bs-cmp-sides__side is-before">
|
<div className="bs-cmp-sides__side is-before">
|
||||||
<span className="bs-cmp-sides__tag">{t("bizCompare.sideBefore")}</span>
|
<span className="bs-cmp-sides__tag">{t("bizCompare.sideBefore")}</span>
|
||||||
<strong
|
<strong className="bs-cmp-sides__name" title={`${beforeName} ${beforeTime}`}>
|
||||||
className="bs-cmp-sides__name"
|
{beforeName}
|
||||||
title={sideDeviceName(before)}
|
|
||||||
>
|
|
||||||
{sideDeviceName(before)}
|
|
||||||
</strong>
|
</strong>
|
||||||
<span className="bs-cmp-sides__meta muted">
|
<span className="bs-cmp-sides__meta" title={beforeTime}>
|
||||||
{sideCollectTime(before)}
|
{beforeTime}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="bs-cmp-sides__arrow" aria-hidden>
|
<span className="bs-cmp-sides__arrow" aria-hidden>
|
||||||
|
|
@ -2164,14 +2200,11 @@ export function BizComparePage() {
|
||||||
</span>
|
</span>
|
||||||
<div className="bs-cmp-sides__side is-after">
|
<div className="bs-cmp-sides__side is-after">
|
||||||
<span className="bs-cmp-sides__tag">{t("bizCompare.sideAfter")}</span>
|
<span className="bs-cmp-sides__tag">{t("bizCompare.sideAfter")}</span>
|
||||||
<strong
|
<strong className="bs-cmp-sides__name" title={`${afterName} ${afterTime}`}>
|
||||||
className="bs-cmp-sides__name"
|
{afterName}
|
||||||
title={sideDeviceName(after)}
|
|
||||||
>
|
|
||||||
{sideDeviceName(after)}
|
|
||||||
</strong>
|
</strong>
|
||||||
<span className="bs-cmp-sides__meta muted">
|
<span className="bs-cmp-sides__meta" title={afterTime}>
|
||||||
{sideCollectTime(after)}
|
{afterTime}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
@ -2212,6 +2245,32 @@ export function BizComparePage() {
|
||||||
? ` · ${t("bizCompare.sheetFailCount", { n: String(sheetFailCount) })}`
|
? ` · ${t("bizCompare.sheetFailCount", { n: String(sheetFailCount) })}`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="bs-cmp-nav__legend" aria-hidden>
|
||||||
|
<span
|
||||||
|
className="bs-cmp-nav__num bs-cmp-nav__num--removed is-hot"
|
||||||
|
title={t("bizCompare.missCount")}
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="bs-cmp-nav__num bs-cmp-nav__num--added is-hot"
|
||||||
|
title={t("bizCompare.extraCount")}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="bs-cmp-nav__num bs-cmp-nav__num--changed is-hot"
|
||||||
|
title={t("bizCompare.mismatchCount")}
|
||||||
|
>
|
||||||
|
~
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="bs-cmp-nav__num bs-cmp-nav__num--unchanged"
|
||||||
|
title={t("bizCompare.matchCount")}
|
||||||
|
>
|
||||||
|
=
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bs-cmp-nav__list" role="tablist">
|
<div className="bs-cmp-nav__list" role="tablist">
|
||||||
{(sheetCards.length
|
{(sheetCards.length
|
||||||
|
|
@ -2248,39 +2307,34 @@ export function BizComparePage() {
|
||||||
>
|
>
|
||||||
<span className="bs-cmp-nav__dot" aria-hidden />
|
<span className="bs-cmp-nav__dot" aria-hidden />
|
||||||
<span className="bs-cmp-nav__name">{c.metric_id}</span>
|
<span className="bs-cmp-nav__name">{c.metric_id}</span>
|
||||||
<span className="bs-cmp-nav__badges">
|
<span
|
||||||
{dirty > 0 ? (
|
className="bs-cmp-nav__stats"
|
||||||
<>
|
title={`${t("bizCompare.missCount")} ${Number(c.removed || 0)} · ${t("bizCompare.extraCount")} ${Number(c.added || 0)} · ${t("bizCompare.mismatchCount")} ${Number(c.changed || 0)} · ${t("bizCompare.matchCount")} ${Number(c.unchanged || 0)}`}
|
||||||
{Number(c.removed || 0) > 0 ? (
|
>
|
||||||
<span className="bs-cmp-nav__badge bs-cmp-nav__badge--removed is-hot">
|
<span
|
||||||
{t("bizCompare.missCount")}{" "}
|
className={`bs-cmp-nav__num bs-cmp-nav__num--removed${
|
||||||
<b>{Number(c.removed || 0)}</b>
|
Number(c.removed || 0) > 0 ? " is-hot" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{Number(c.removed || 0)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
<span
|
||||||
{Number(c.added || 0) > 0 ? (
|
className={`bs-cmp-nav__num bs-cmp-nav__num--added${
|
||||||
<span className="bs-cmp-nav__badge bs-cmp-nav__badge--added is-hot">
|
Number(c.added || 0) > 0 ? " is-hot" : ""
|
||||||
{t("bizCompare.extraCount")}{" "}
|
}`}
|
||||||
<b>{Number(c.added || 0)}</b>
|
>
|
||||||
|
{Number(c.added || 0)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
<span
|
||||||
{Number(c.changed || 0) > 0 ? (
|
className={`bs-cmp-nav__num bs-cmp-nav__num--changed${
|
||||||
<span className="bs-cmp-nav__badge bs-cmp-nav__badge--changed is-hot">
|
Number(c.changed || 0) > 0 ? " is-hot" : ""
|
||||||
{t("bizCompare.mismatchCount")}{" "}
|
}`}
|
||||||
<b>{Number(c.changed || 0)}</b>
|
>
|
||||||
|
{Number(c.changed || 0)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
<span className="bs-cmp-nav__num bs-cmp-nav__num--unchanged">
|
||||||
</>
|
{Number(c.unchanged || 0)}
|
||||||
) : (
|
|
||||||
<span className="bs-cmp-nav__badge bs-cmp-nav__badge--pass is-hot">
|
|
||||||
{t("bizCompare.kindPass")}
|
|
||||||
{Number(c.unchanged || 0) > 0 ? (
|
|
||||||
<>
|
|
||||||
{" "}
|
|
||||||
<b>{Number(c.unchanged || 0)}</b>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue