refactor(fe): standardize ShadowRunList page with BatchOperationsPageV2 and DataGridShell
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 54 KiB |
@@ -1,11 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, reactive } from 'vue'
|
||||||
import { SkeletonLoader } from '../../../shared/ui/components'
|
import BatchOperationsPageV2 from '../../../shared/ui/screen-types/v2/BatchOperationsPageV2.vue'
|
||||||
|
import DataGridShell from '../../../shared/ui/DataGridShell.vue'
|
||||||
|
import { KsButton } from '../../../shared/ui/components'
|
||||||
|
import type { StandardScreenState } from '../../../shared/ui/contracts/screenContract'
|
||||||
|
import type { UiGridColumn } from '../../../shared/ui/adapter/contracts'
|
||||||
import { useShadowRunsList } from '../composables/useShadowRuns'
|
import { useShadowRunsList } from '../composables/useShadowRuns'
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(20)
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
const evidence = reactive({
|
||||||
|
asOf: '2026-08-15T19:40:00Z',
|
||||||
|
version: 'v60-T08-Contract',
|
||||||
|
})
|
||||||
|
|
||||||
const queryParams = computed(() => ({
|
const queryParams = computed(() => ({
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
@@ -13,122 +22,57 @@ const queryParams = computed(() => ({
|
|||||||
|
|
||||||
const shadowRunsQuery = useShadowRunsList(queryParams.value)
|
const shadowRunsQuery = useShadowRunsList(queryParams.value)
|
||||||
|
|
||||||
|
const screenState = computed<StandardScreenState>(() => {
|
||||||
|
if (shadowRunsQuery.isPending.value) return 'LOADING'
|
||||||
|
if (shadowRunsQuery.isError.value) return 'ERROR'
|
||||||
|
const data = shadowRunsQuery.data.value as any
|
||||||
|
if (!data?.items?.length) return 'EMPTY'
|
||||||
|
return 'READY'
|
||||||
|
})
|
||||||
|
|
||||||
const items = computed(() => {
|
const items = computed(() => {
|
||||||
const data = shadowRunsQuery.data as any
|
const data = shadowRunsQuery.data.value as any
|
||||||
return data?.items || []
|
return data?.items || []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const columns: UiGridColumn[] = [
|
||||||
|
{ field: 'runId', header: 'Run ID', minWidth: 120, flex: 2, formatter: val => String(val).startsWith('#') ? String(val) : `#${String(val).slice(0, 8)}...` },
|
||||||
|
{ field: 'modelName', header: 'Model Name', minWidth: 200, flex: 3 },
|
||||||
|
{ field: 'status', header: 'Status', minWidth: 100, flex: 1, formatter: val => String(val).toUpperCase() },
|
||||||
|
{ field: 'pbo', header: 'PBO', minWidth: 80, flex: 1, formatter: val => `${val}%` },
|
||||||
|
{ field: 'dsr', header: 'DSR', minWidth: 80, flex: 1, formatter: val => `${val}%` },
|
||||||
|
{ field: 'oos', header: 'OOS', minWidth: 80, flex: 1, formatter: val => `${val}%` },
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleRetry = () => {
|
||||||
|
shadowRunsQuery.refetch()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="shadow-run-list-page">
|
<BatchOperationsPageV2
|
||||||
<header class="page-header">
|
title="Shadow Run 실행 검증 (Shadow Run Validation)"
|
||||||
<h1>Shadow Run Validation</h1>
|
subtitle="252+ 거래일 백테스트 검증 결과를 관찰하고 통계적 우위(PBO/DSR/OOS)를 평가합니다."
|
||||||
<p>View and manage shadow run validations (252+ trading day backtests)</p>
|
:state="screenState"
|
||||||
</header>
|
:evidence="evidence"
|
||||||
|
:error="shadowRunsQuery.error.value ?? undefined"
|
||||||
<!-- Loading State -->
|
@retry="handleRetry"
|
||||||
<div v-if="shadowRunsQuery.isPending" class="loading-state">
|
>
|
||||||
<SkeletonLoader type="table" :rows="5" />
|
<template #actions>
|
||||||
</div>
|
<KsButton
|
||||||
|
label="▶ 실행 검증 재요청"
|
||||||
<!-- Error State -->
|
variant="primary"
|
||||||
<div v-else-if="shadowRunsQuery.isError" class="error-state">
|
aria-label="Shadow Run 검증 실행"
|
||||||
<p>Failed to load shadow runs</p>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<div v-else-if="!items.length" class="empty-state">
|
|
||||||
<p>No shadow runs found. Create a new shadow run to get started.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data State -->
|
|
||||||
<div v-else class="shadow-runs-grid">
|
|
||||||
<table class="shadow-runs-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Run ID</th>
|
|
||||||
<th>Model</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>PBO</th>
|
|
||||||
<th>DSR</th>
|
|
||||||
<th>OOS</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="run in items" :key="(run as any).id" data-testid="shadow-run-row">
|
|
||||||
<td>{{ (run as any).id }}</td>
|
|
||||||
<td>{{ (run as any).modelName }}</td>
|
|
||||||
<td>{{ (run as any).status }}</td>
|
|
||||||
<td>{{ (run as any).pbo }}%</td>
|
|
||||||
<td>{{ (run as any).dsr }}%</td>
|
|
||||||
<td>{{ (run as any).oos }}%</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<DataGridShell
|
||||||
.shadow-run-list-page {
|
:rows="items"
|
||||||
padding: 2rem;
|
:columns="columns"
|
||||||
max-width: 1400px;
|
:loading="shadowRunsQuery.isPending.value"
|
||||||
margin: 0 auto;
|
empty-message="조회된 Shadow Run 검증 내역이 없습니다."
|
||||||
}
|
height="calc(100vh - 210px)"
|
||||||
|
/>
|
||||||
|
</BatchOperationsPageV2>
|
||||||
|
</template>
|
||||||
|
|
||||||
.page-header {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
margin: 0.5rem 0 0 0;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-state,
|
|
||||||
.error-state,
|
|
||||||
.empty-state {
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid var(--color-border-primary);
|
|
||||||
border-radius: var(--border-radius-md);
|
|
||||||
background-color: var(--color-background-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-runs-grid {
|
|
||||||
border: 1px solid var(--color-border-primary);
|
|
||||||
border-radius: var(--border-radius-md);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-runs-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-runs-table thead {
|
|
||||||
background-color: var(--color-background-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-runs-table th {
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: 600;
|
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-runs-table td {
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-runs-table tbody tr:hover {
|
|
||||||
background-color: var(--color-background-hover);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user