fix(wbs-ux): structure FactorParamDetailLayout as a distinct 6:4 Split Pane with physical Split Divider Bar
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 10s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 10s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 19s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 10s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 10s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 19s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
This commit is contained in:
@@ -73,136 +73,214 @@ onMounted(loadFactors);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="split-layout-container">
|
||||
<!-- 좌측 마스터 테이블 영역 -->
|
||||
<div class="master-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">📊 팩터 수식 원장 (factor_version_history)</h3>
|
||||
<span class="panel-subtitle">수식을 클릭하면 우측 상세 매개변수가 바인딩됩니다.</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="douzone-grid-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>팩터 ID</th>
|
||||
<th>수식 명칭</th>
|
||||
<th>카테고리</th>
|
||||
<th>보정 상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in factors" :key="item.factor_id"
|
||||
@click="selectFactor(item)"
|
||||
:class="{ 'selected-row': selectedFactor?.factor_id === item.factor_id }">
|
||||
<td class="font-mono font-bold">{{ item.factor_id }}</td>
|
||||
<td>{{ item.formula_name }}</td>
|
||||
<td><span class="badge badge-category">{{ item.category }}</span></td>
|
||||
<td>
|
||||
<span class="badge badge-status" :class="item.calibration_state.toLowerCase()">
|
||||
{{ item.calibration_state }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="split-screen-wrapper">
|
||||
<div class="page-top-bar">
|
||||
<h3 class="page-title">타입 A: 마스터-디테일 스플릿 화면 (Master-Detail Split View)</h3>
|
||||
<span class="page-desc">좌측 수식 목록과 우측 파라미터 제어 폼이 6:4 비율 Split Pane으로 세분화됩니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- 우측 디테일 폼 영역 -->
|
||||
<div class="detail-panel" v-if="selectedFactor">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">⚙️ 임계 한도 매개변수 설정 [{{ selectedFactor.factor_id }}]</h3>
|
||||
<!-- 2단 Split 구도 컨테이너 -->
|
||||
<div class="split-pane-container">
|
||||
<!-- 1. 좌측 마스터 패널 (60%) -->
|
||||
<div class="master-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-badge">Master List</span>
|
||||
<h4 class="panel-heading">📊 팩터 수식 원장 (factor_version_history)</h4>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<table class="douzone-grid-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>팩터 ID</th>
|
||||
<th>수식 명칭</th>
|
||||
<th>카테고리</th>
|
||||
<th>보정 상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in factors" :key="item.factor_id"
|
||||
@click="selectFactor(item)"
|
||||
:class="{ 'selected-row': selectedFactor?.factor_id === item.factor_id }">
|
||||
<td class="font-mono font-bold">{{ item.factor_id }}</td>
|
||||
<td>{{ item.formula_name }}</td>
|
||||
<td><span class="badge badge-category">{{ item.category }}</span></td>
|
||||
<td>
|
||||
<span class="badge badge-status" :class="item.calibration_state.toLowerCase()">
|
||||
{{ item.calibration_state }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body form-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">팩터 명칭</label>
|
||||
<input type="text" class="form-input readonly" :value="selectedFactor.formula_name" readonly />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">상한 Threshold (Upper Limit)</label>
|
||||
<input type="number" class="form-input" v-model.number="selectedFactor.threshold_params.upper" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">하한 Threshold (Lower Limit)</label>
|
||||
<input type="number" class="form-input" v-model.number="selectedFactor.threshold_params.lower" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">보정 상태 (Calibration State)</label>
|
||||
<select class="form-input" v-model="selectedFactor.calibration_state">
|
||||
<option value="CALIBRATED">CALIBRATED (검증 완료)</option>
|
||||
<option value="OPTIMIZING">OPTIMIZING (최적화 중)</option>
|
||||
<option value="STALE">STALE (만료됨)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-save-action" :disabled="isSaving" @click="saveThreshold">
|
||||
{{ isSaving ? '저장 처리 중...' : '💾 PostgreSQL 원장 업데이트 저장' }}
|
||||
</button>
|
||||
|
||||
<!-- 2. 중앙 물리 스플릿 구분선 (Split Divider Bar) -->
|
||||
<div class="split-divider">
|
||||
<div class="divider-line"></div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 우측 디테일 패널 (40%) -->
|
||||
<div class="detail-panel" v-if="selectedFactor">
|
||||
<div class="panel-header">
|
||||
<span class="panel-badge badge-active">Detail Form</span>
|
||||
<h4 class="panel-heading">⚙️ 임계 한도 매개변수 설정 [{{ selectedFactor.factor_id }}]</h4>
|
||||
</div>
|
||||
<div class="panel-content form-container">
|
||||
<div class="form-group">
|
||||
<label class="form-label">팩터 식별자</label>
|
||||
<input type="text" class="form-input readonly" :value="selectedFactor.factor_id" readonly />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">수식 명칭</label>
|
||||
<input type="text" class="form-input readonly" :value="selectedFactor.formula_name" readonly />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">상한 Threshold (Upper Limit)</label>
|
||||
<input type="number" class="form-input" v-model.number="selectedFactor.threshold_params.upper" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">하한 Threshold (Lower Limit)</label>
|
||||
<input type="number" class="form-input" v-model.number="selectedFactor.threshold_params.lower" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">보정 상태 (Calibration State)</label>
|
||||
<select class="form-input" v-model="selectedFactor.calibration_state">
|
||||
<option value="CALIBRATED">CALIBRATED (검증 완료)</option>
|
||||
<option value="OPTIMIZING">OPTIMIZING (최적화 중)</option>
|
||||
<option value="STALE">STALE (만료됨)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-save-action" :disabled="isSaving" @click="saveThreshold">
|
||||
{{ isSaving ? '저장 처리 중...' : '💾 PostgreSQL 원장 업데이트 저장' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 선택 없을 때 스플릿 디테일 엠티 패널 -->
|
||||
<div class="detail-panel empty-panel" v-else>
|
||||
<div class="panel-header">
|
||||
<span class="panel-badge">Detail Form</span>
|
||||
<h4 class="panel-heading">⚙️ 임계 한도 매개변수 설정</h4>
|
||||
</div>
|
||||
<div class="empty-message-box">
|
||||
<span class="empty-icon">👈</span>
|
||||
<p class="empty-title">팩터 항목이 선택되지 않았습니다.</p>
|
||||
<p class="empty-desc">좌측 마스터 테이블에서 수식 항목을 클릭하면 40% 영역에 상세 설정 폼이 바인딩됩니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-panel empty-panel" v-else>
|
||||
<p>좌측 팩터 수식을 선택하면 상세 파라미터 편집 폼이 노출됩니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.split-layout-container {
|
||||
.split-screen-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 110px);
|
||||
background-color: #F1F5F9;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background-color: #F1F5F9;
|
||||
box-sizing: border-box;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.master-panel {
|
||||
flex: 2;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.page-top-bar {
|
||||
border-bottom: 1px solid #CBD5E1;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 1;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.empty-panel {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #94A3B8;
|
||||
font-size: 0.85rem;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 12px 16px;
|
||||
background-color: #F8FAFC;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 0.9rem;
|
||||
.page-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #1E293B;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel-subtitle {
|
||||
.page-desc {
|
||||
font-size: 0.75rem;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
.split-pane-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.master-panel {
|
||||
flex: 6;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-radius: 6px 0 0 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.split-divider {
|
||||
width: 8px;
|
||||
background-color: #CBD5E1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: col-resize;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.split-divider:hover {
|
||||
background-color: #2563EB;
|
||||
}
|
||||
|
||||
.divider-line {
|
||||
width: 2px;
|
||||
height: 30px;
|
||||
background-color: #94A3B8;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 4;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-left: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 10px 16px;
|
||||
background-color: #F8FAFC;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.panel-badge {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
background-color: #E2E8F0;
|
||||
color: #475569;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.panel-badge.badge-active {
|
||||
background-color: #2563EB;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: #1E293B;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
@@ -242,13 +320,8 @@ onMounted(loadFactors);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-mono { font-family: monospace; }
|
||||
.font-bold { font-weight: 700; }
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
@@ -258,31 +331,21 @@ onMounted(loadFactors);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.badge-category {
|
||||
background-color: #E2E8F0;
|
||||
color: #475569;
|
||||
}
|
||||
.badge-category { background-color: #E2E8F0; color: #475569; }
|
||||
.badge-status.calibrated { background-color: #DCFCE7; color: #166534; }
|
||||
.badge-status.optimizing { background-color: #FEF3C7; color: #92400E; }
|
||||
|
||||
.badge-status.calibrated {
|
||||
background-color: #DCFCE7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.badge-status.optimizing {
|
||||
background-color: #FEF3C7;
|
||||
color: #92400E;
|
||||
}
|
||||
|
||||
.form-body {
|
||||
.form-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@@ -297,11 +360,6 @@ onMounted(loadFactors);
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
outline: none;
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #2563EB;
|
||||
}
|
||||
|
||||
.form-input.readonly {
|
||||
@@ -319,15 +377,22 @@ onMounted(loadFactors);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-save-action:hover {
|
||||
background-color: #1D4ED8;
|
||||
.btn-save-action:hover { background-color: #1D4ED8; }
|
||||
|
||||
.empty-message-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
.btn-save-action:disabled {
|
||||
background-color: #94A3B8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.empty-icon { font-size: 2rem; margin-bottom: 8px; }
|
||||
.empty-title { font-size: 0.9rem; font-weight: 700; color: #334155; margin: 0 0 4px 0; }
|
||||
.empty-desc { font-size: 0.75rem; color: #94A3B8; margin: 0; max-width: 240px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user