fix(web): add knowledge base quality rail
Some checks failed
CD Pipeline / tests (push) Successful in 1m28s
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / build-and-deploy (push) Successful in 4m1s
CD Pipeline / post-deploy-checks (push) Failing after 1m6s

This commit is contained in:
Your Name
2026-06-03 07:54:52 +08:00
parent 1f3b871e28
commit 02d13e0b6e
3 changed files with 68 additions and 0 deletions

View File

@@ -1418,6 +1418,16 @@
"categoryDistribution": "分類分佈",
"categoryOther": "其他分類"
},
"quality": {
"title": "資料品質軌道",
"scope": "目前列表",
"reviewBacklog": "待審核",
"freshWithin7d": "7 天內更新",
"incidentLinked": "事故關聯",
"signalRich": "訊號完整",
"playbookLinked": "Playbook 關聯",
"countOfLoaded": "{count} / {total}"
},
"noResults": "找不到相關知識條目",
"createEntry": "新增條目",
"viewCount": "瀏覽",

View File

@@ -1418,6 +1418,16 @@
"categoryDistribution": "分類分佈",
"categoryOther": "其他分類"
},
"quality": {
"title": "資料品質軌道",
"scope": "目前列表",
"reviewBacklog": "待審核",
"freshWithin7d": "7 天內更新",
"incidentLinked": "事故關聯",
"signalRich": "訊號完整",
"playbookLinked": "Playbook 關聯",
"countOfLoaded": "{count} / {total}"
},
"noResults": "找不到相關知識條目",
"createEntry": "新增條目",
"viewCount": "瀏覽",

View File

@@ -390,6 +390,29 @@ export default function KnowledgeBasePage({
]
}, [categories, totalCount])
const qualityRows = useMemo(() => {
const loaded = displayedEntries.length
const now = Date.now()
const freshWindowMs = 7 * 24 * 60 * 60 * 1000
const pct = (count: number) => loaded > 0 ? Math.round((count / loaded) * 100) : 0
const reviewBacklog = displayedEntries.filter(entry => entry.status === 'draft' || entry.status === 'review').length
const freshWithin7d = displayedEntries.filter(entry => {
const updatedAt = Date.parse(entry.updated_at)
return Number.isFinite(updatedAt) && now - updatedAt <= freshWindowMs
}).length
const incidentLinked = displayedEntries.filter(entry => Boolean(entry.related_incident_id)).length
const playbookLinked = displayedEntries.filter(entry => Boolean(entry.related_playbook_id)).length
const signalRich = displayedEntries.filter(entry => getSignalTags(entry).length > 0).length
return [
{ key: 'reviewBacklog', count: reviewBacklog, pct: pct(reviewBacklog), tone: 'bg-status-warning' },
{ key: 'freshWithin7d', count: freshWithin7d, pct: pct(freshWithin7d), tone: 'bg-status-healthy' },
{ key: 'incidentLinked', count: incidentLinked, pct: pct(incidentLinked), tone: 'bg-claw-blue' },
{ key: 'signalRich', count: signalRich, pct: pct(signalRich), tone: 'bg-purple-500' },
{ key: 'playbookLinked', count: playbookLinked, pct: pct(playbookLinked), tone: 'bg-nothing-gray-400' },
] as const
}, [displayedEntries])
const content = (
<div className="flex h-[calc(100vh-64px)] flex-col lg:flex-row">
@@ -542,6 +565,31 @@ export default function KnowledgeBasePage({
</div>
</div>
</div>
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
<div className="mb-2 flex items-center justify-between gap-2">
<p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('quality.title')}</p>
<span className="shrink-0 text-[10px] font-body text-muted">{t('quality.scope')}</span>
</div>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-5">
{qualityRows.map(row => (
<div key={row.key} className="min-w-0">
<div className="mb-1 flex items-center justify-between gap-2">
<span className="truncate text-[10px] font-body text-secondary">{t(`quality.${row.key}`)}</span>
<span className="text-[10px] font-label tabular-nums text-muted">
{t('quality.countOfLoaded', { count: formatCount(row.count), total: formatCount(visibleSummary.loaded) })}
</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-nothing-gray-100">
<div
className={cn('h-full rounded-full', row.tone)}
style={{ width: `${row.pct}%` }}
/>
</div>
</div>
))}
</div>
</div>
</section>
{/* 結果列表 */}