diff --git a/apps/web/components/xiaobao-warning/XiaobaoWarningCard.tsx b/apps/web/components/xiaobao-warning/XiaobaoWarningCard.tsx index 5e1608b..0434581 100644 --- a/apps/web/components/xiaobao-warning/XiaobaoWarningCard.tsx +++ b/apps/web/components/xiaobao-warning/XiaobaoWarningCard.tsx @@ -3,6 +3,7 @@ import { CalendarClock, ChevronRight, Sparkles, TrendingDown, TrendingUp, TriangleAlert } from 'lucide-react'; import type { XiaobaoRiskLevel, XiaobaoVersionRisk } from '@/lib/xiaobao-risk'; import { formatDateTime } from '@/lib/format'; +import { formatRemainingWork } from '@/lib/xiaobao-warning-view'; const RISK_LEVEL_LABEL: Record = { on_track: '按期', @@ -50,7 +51,7 @@ export function XiaobaoWarningCard({ risk, onClick }: { risk: XiaobaoVersionRisk
= 70 ? 'danger' : risk.riskScore >= 45 ? 'warn' : 'ok'} /> 0 ? `${risk.delayDays}天` : '0天'} tone={risk.delayDays > 0 ? 'danger' : 'ok'} /> - 0 ? 'warn' : 'ok'} /> + 0 ? 'warn' : 'ok'} />
diff --git a/apps/web/components/xiaobao-warning/XiaobaoWarningDrawer.tsx b/apps/web/components/xiaobao-warning/XiaobaoWarningDrawer.tsx index 8258110..4fdebd9 100644 --- a/apps/web/components/xiaobao-warning/XiaobaoWarningDrawer.tsx +++ b/apps/web/components/xiaobao-warning/XiaobaoWarningDrawer.tsx @@ -3,6 +3,7 @@ import { CalendarClock, Sparkles, X } from 'lucide-react'; import type { XiaobaoVersionRisk } from '@/lib/xiaobao-risk'; import { formatDateTime } from '@/lib/format'; +import { formatRemainingWork } from '@/lib/xiaobao-warning-view'; export function XiaobaoWarningDrawer({ risk, @@ -46,7 +47,7 @@ export function XiaobaoWarningDrawer({
- + 0 ? `${risk.delayDays}天` : '0天'} />
diff --git a/apps/web/lib/xiaobao-warning-view.test.ts b/apps/web/lib/xiaobao-warning-view.test.ts index c8c7939..fa0ed37 100644 --- a/apps/web/lib/xiaobao-warning-view.test.ts +++ b/apps/web/lib/xiaobao-warning-view.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import type { VersionWithContext } from './derive'; -import { filterXiaobaoWarningVersions } from './xiaobao-warning-view'; +import { filterXiaobaoWarningVersions, formatRemainingWork } from './xiaobao-warning-view'; function version(patch: Partial = {}): VersionWithContext { return { @@ -45,3 +45,10 @@ test('filterXiaobaoWarningVersions limits non-managers to versions where they ar 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天'); +}); diff --git a/apps/web/lib/xiaobao-warning-view.ts b/apps/web/lib/xiaobao-warning-view.ts index d88ac94..230e928 100644 --- a/apps/web/lib/xiaobao-warning-view.ts +++ b/apps/web/lib/xiaobao-warning-view.ts @@ -1,4 +1,5 @@ import type { VersionWithContext } from './derive'; +import { WORK_HOURS } from './work-hours'; 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); }); } + +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$/, ''); +}