fix(小宝预警): 剩余工作量展示天数换算
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import { CalendarClock, ChevronRight, Sparkles, TrendingDown, TrendingUp, TriangleAlert } from 'lucide-react';
|
import { CalendarClock, ChevronRight, Sparkles, TrendingDown, TrendingUp, TriangleAlert } from 'lucide-react';
|
||||||
import type { XiaobaoRiskLevel, XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
|
import type { XiaobaoRiskLevel, XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
|
||||||
import { formatDateTime } from '@/lib/format';
|
import { formatDateTime } from '@/lib/format';
|
||||||
|
import { formatRemainingWork } from '@/lib/xiaobao-warning-view';
|
||||||
|
|
||||||
const RISK_LEVEL_LABEL: Record<XiaobaoRiskLevel, string> = {
|
const RISK_LEVEL_LABEL: Record<XiaobaoRiskLevel, string> = {
|
||||||
on_track: '按期',
|
on_track: '按期',
|
||||||
@@ -50,7 +51,7 @@ export function XiaobaoWarningCard({ risk, onClick }: { risk: XiaobaoVersionRisk
|
|||||||
<div className="mt-3 grid grid-cols-4 gap-2">
|
<div className="mt-3 grid grid-cols-4 gap-2">
|
||||||
<Metric label="风险分" value={String(risk.riskScore)} tone={risk.riskScore >= 70 ? 'danger' : risk.riskScore >= 45 ? 'warn' : 'ok'} />
|
<Metric label="风险分" value={String(risk.riskScore)} tone={risk.riskScore >= 70 ? 'danger' : risk.riskScore >= 45 ? 'warn' : 'ok'} />
|
||||||
<Metric label="延期" value={risk.delayDays > 0 ? `${risk.delayDays}天` : '0天'} tone={risk.delayDays > 0 ? 'danger' : 'ok'} />
|
<Metric label="延期" value={risk.delayDays > 0 ? `${risk.delayDays}天` : '0天'} tone={risk.delayDays > 0 ? 'danger' : 'ok'} />
|
||||||
<Metric label="剩余" value={`${risk.remainingWorkHours}h`} tone={risk.remainingWorkHours > 0 ? 'warn' : 'ok'} />
|
<Metric label="剩余" value={formatRemainingWork(risk.remainingWorkHours)} tone={risk.remainingWorkHours > 0 ? 'warn' : 'ok'} />
|
||||||
<Metric label="置信" value={`${risk.confidence}%`} tone={risk.confidence < 50 ? 'danger' : risk.confidence < 75 ? 'warn' : 'ok'} />
|
<Metric label="置信" value={`${risk.confidence}%`} tone={risk.confidence < 50 ? 'danger' : risk.confidence < 75 ? 'warn' : 'ok'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { CalendarClock, Sparkles, X } from 'lucide-react';
|
import { CalendarClock, Sparkles, X } from 'lucide-react';
|
||||||
import type { XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
|
import type { XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
|
||||||
import { formatDateTime } from '@/lib/format';
|
import { formatDateTime } from '@/lib/format';
|
||||||
|
import { formatRemainingWork } from '@/lib/xiaobao-warning-view';
|
||||||
|
|
||||||
export function XiaobaoWarningDrawer({
|
export function XiaobaoWarningDrawer({
|
||||||
risk,
|
risk,
|
||||||
@@ -46,7 +47,7 @@ export function XiaobaoWarningDrawer({
|
|||||||
<div className="mb-5 grid grid-cols-2 gap-3">
|
<div className="mb-5 grid grid-cols-2 gap-3">
|
||||||
<Summary label="风险分" value={`${risk.riskScore}`} />
|
<Summary label="风险分" value={`${risk.riskScore}`} />
|
||||||
<Summary label="置信度" value={`${risk.confidence}%`} />
|
<Summary label="置信度" value={`${risk.confidence}%`} />
|
||||||
<Summary label="剩余工作量" value={`${risk.remainingWorkHours}h`} />
|
<Summary label="剩余工作量" value={formatRemainingWork(risk.remainingWorkHours)} />
|
||||||
<Summary label="预计延期" value={risk.delayDays > 0 ? `${risk.delayDays}天` : '0天'} />
|
<Summary label="预计延期" value={risk.delayDays > 0 ? `${risk.delayDays}天` : '0天'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import type { VersionWithContext } from './derive';
|
import type { VersionWithContext } from './derive';
|
||||||
import { filterXiaobaoWarningVersions } from './xiaobao-warning-view';
|
import { filterXiaobaoWarningVersions, formatRemainingWork } from './xiaobao-warning-view';
|
||||||
|
|
||||||
function version(patch: Partial<VersionWithContext> = {}): VersionWithContext {
|
function version(patch: Partial<VersionWithContext> = {}): VersionWithContext {
|
||||||
return {
|
return {
|
||||||
@@ -45,3 +45,10 @@ test('filterXiaobaoWarningVersions limits non-managers to versions where they ar
|
|||||||
|
|
||||||
assert.deepEqual(result.map((item) => item.id), ['ver-1']);
|
assert.deepEqual(result.map((item) => item.id), ['ver-1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('formatRemainingWork keeps hours and adds work-day conversion', () => {
|
||||||
|
assert.equal(formatRemainingWork(0), '0h / 0天');
|
||||||
|
assert.equal(formatRemainingWork(8), '8h / 1天');
|
||||||
|
assert.equal(formatRemainingWork(12), '12h / 1.5天');
|
||||||
|
assert.equal(formatRemainingWork(0.5), '0.5h / 0.1天');
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { VersionWithContext } from './derive';
|
import type { VersionWithContext } from './derive';
|
||||||
|
import { WORK_HOURS } from './work-hours';
|
||||||
|
|
||||||
const UNFINISHED_VERSION_STATUSES = new Set(['planned', 'developing', 'paused']);
|
const UNFINISHED_VERSION_STATUSES = new Set(['planned', 'developing', 'paused']);
|
||||||
|
|
||||||
@@ -18,3 +19,14 @@ export function filterXiaobaoWarningVersions(
|
|||||||
return (version.members ?? []).some((member) => member.name === filter.userName);
|
return (version.members ?? []).some((member) => member.name === filter.userName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatRemainingWork(hours: number): string {
|
||||||
|
const safeHours = Number.isFinite(hours) && hours > 0 ? hours : 0;
|
||||||
|
const days = safeHours / WORK_HOURS.hoursPerDay;
|
||||||
|
return `${formatNumber(safeHours)}h / ${formatNumber(days)}天`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(value: number): string {
|
||||||
|
if (Number.isInteger(value)) return String(value);
|
||||||
|
return value.toFixed(1).replace(/\.0$/, '');
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user