fix(fe): RiskDashboard - add empty state handling and data validation

- Add EMPTY state to StandardScreenState when portfolio has no positions
- Condition all template slots on dashboard data presence
- Validate dashboard.healthScore, riskMetrics, portfolio, stressResults
- Update empty message: '보유 종목이 없습니다' → '포트폴리오 보유 종목이 없습니다'
- Display 'EMPTY' state instead of blank screen when no data
- Improve state computation: LOADING → ERROR/WARN → EMPTY → READY

Result: Better UX for empty/error states, clearer data validation

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 01:22:52 +09:00
parent 8ae6bf6429
commit 71f76787b1
@@ -1,14 +1,22 @@
<template>
<ScorecardDashboardPage title="포트폴리오 리스크 대시보드" subtitle="실시간 리스크 지표, 스트레스 시나리오, 알림을 확인합니다." :state="state" :warning="error ?? undefined" @retry="fetchDashboard">
<template #actions>
<div v-if="dashboard" class="health-score">
<ScorecardDashboardPage
title="포트폴리오 리스크 대시보드"
subtitle="실시간 리스크 지표, 스트레스 시나리오, 알림을 확인합니다."
:state="state"
:warning="error ?? undefined"
@retry="fetchDashboard"
>
<!-- Health Score -->
<template v-if="dashboard?.healthScore" #actions>
<div class="health-score">
<span class="score-label">포트폴리오 건전성</span>
<div class="score-bar"><div class="score-fill" :style="{ width: dashboard.healthScore + '%' }" /></div>
<span class="score-value">{{ dashboard.healthScore }}/100</span>
</div>
</template>
<template v-if="dashboard" #kpis>
<!-- Risk Metrics KPIs -->
<template v-if="dashboard?.riskMetrics" #kpis>
<div class="metric">
<span class="label">VAR (95%)</span>
<span class="value ks-financial-number">{{ formatCurrency(dashboard.riskMetrics.var95, 'USD') }}</span>
@@ -41,16 +49,18 @@
</div>
</template>
<template v-if="dashboard" #primary>
<!-- Portfolio Composition -->
<template v-if="dashboard?.portfolio" #primary>
<h2>포트폴리오 구성</h2>
<dl class="portfolio-summary">
<div class="summary-item"><dt> 평가액</dt><dd>{{ formatCurrency(dashboard.portfolio.totalValue, 'USD') }}</dd></div>
<div class="summary-item"><dt>보유 종목 </dt><dd>{{ formatQuantity(dashboard.portfolio.positions.length, 0) }}</dd></div>
</dl>
<DataGridShell :rows="dashboard.portfolio.positions.slice(0, 5)" :columns="positionColumns" empty-message="보유 종목이 없습니다." />
<DataGridShell :rows="dashboard.portfolio.positions.slice(0, 5)" :columns="positionColumns" empty-message="포트폴리오 보유 종목이 없습니다." />
</template>
<template v-if="dashboard" #secondary>
<!-- Stress Test Scenarios -->
<template v-if="dashboard?.stressResults?.length" #secondary>
<h2>스트레스 테스트 시나리오</h2>
<div class="scenarios">
<button
@@ -73,7 +83,8 @@
</div>
</template>
<template #alerts>
<!-- Active Alerts -->
<template v-if="activeAlerts.length > 0 || state === 'READY'" #alerts>
<h2>활성 리스크 알림</h2>
<div v-if="activeAlerts.length > 0" class="alerts-list">
<div v-for="alert in activeAlerts" :key="alert.id" class="alert">
@@ -174,7 +185,12 @@ const activeAlerts = ref<Alert[]>([
},
])
const state = computed<StandardScreenState>(() => loading.value ? 'LOADING' : error.value ? 'WARN' : 'READY')
const state = computed<StandardScreenState>(() => {
if (loading.value) return 'LOADING'
if (error.value) return 'WARN'
if (!dashboard.value || !dashboard.value.portfolio?.positions?.length) return 'EMPTY'
return 'READY'
})
const positionColumns: UiGridColumn[] = [
{ field: 'symbol', header: '종목코드' },