feat(wbs-ux): upgrade SystemSettingsView into a comprehensive CRUD masterpiece with Search Toolbar, Split AG Grid, Detail Form, and Modal Dialog
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 11s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 20s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 9s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (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) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 11s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 20s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 9s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (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) / Notify PR Results (push) Has been skipped
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<!-- SystemSettingsView.vue -->
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import QuantLabel from '../components/QuantLabel.vue'
|
||||
import QuantInput from '../components/QuantInput.vue'
|
||||
import QuantDatePicker from '../components/QuantDatePicker.vue'
|
||||
@@ -9,124 +10,490 @@ import QuantRadio from '../components/QuantRadio.vue'
|
||||
import QuantTextArea from '../components/QuantTextArea.vue'
|
||||
import QuantAutoComplete from '../components/QuantAutoComplete.vue'
|
||||
import QuantStatusChip from '../components/QuantStatusChip.vue'
|
||||
import QuantDataGrid from '../components/QuantDataGrid.vue'
|
||||
import type { ColDef } from 'ag-grid-community'
|
||||
|
||||
const codeVal = ref('005930')
|
||||
const dateVal = ref('20260722')
|
||||
const currencyVal = ref('340500')
|
||||
const comboVal = ref('ACTIVE')
|
||||
const checkVal = ref(true)
|
||||
const radioVal = ref('A')
|
||||
const textVal = ref('더존 회계시스템 기준 6대 표준 입력 컴포넌트 템플릿 설정')
|
||||
const autoVal = ref('')
|
||||
interface SettingItem {
|
||||
id: number;
|
||||
setting_key: string;
|
||||
category: string;
|
||||
setting_value: string;
|
||||
status: 'ACTIVE' | 'WARNING' | 'BLOCKED';
|
||||
updated_at: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
const comboOptions = [
|
||||
{ label: 'ACTIVE (운영)', value: 'ACTIVE' },
|
||||
{ label: 'LIMIT (제한)', value: 'LIMIT' },
|
||||
{ label: 'ARCHIVED (보관)', value: 'ARCHIVED' }
|
||||
]
|
||||
// 1. 검색 및 필터 바 상태
|
||||
const searchKeyword = ref('');
|
||||
const statusFilter = ref('ALL');
|
||||
|
||||
const radioOptions = [
|
||||
{ label: '유형 A (표준)', value: 'A' },
|
||||
{ label: '유형 B (확장)', value: 'B' }
|
||||
]
|
||||
// 2. Master-Detail 바인딩 상태
|
||||
const items = ref<SettingItem[]>([]);
|
||||
const selectedItem = ref<SettingItem | null>(null);
|
||||
|
||||
const autoSuggestions = [
|
||||
{ label: '삼성전자', value: '005930' },
|
||||
{ label: 'SK하이닉스', value: '000660' },
|
||||
{ label: 'NAVER', value: '035420' }
|
||||
]
|
||||
// 3. 모달 대화상자 상태
|
||||
const isModalOpen = ref(false);
|
||||
const modalItem = ref<SettingItem>({
|
||||
id: 0,
|
||||
setting_key: '',
|
||||
category: 'SYSTEM',
|
||||
setting_value: '',
|
||||
status: 'ACTIVE',
|
||||
updated_at: '',
|
||||
note: ''
|
||||
});
|
||||
|
||||
// 4. AG Grid 컬럼 정의
|
||||
const columnDefs = ref<ColDef[]>([
|
||||
{ field: 'id', headerName: 'ID', width: 70, sortable: true },
|
||||
{ field: 'setting_key', headerName: '설정 키 (Key)', width: 160, sortable: true, filter: true },
|
||||
{ field: 'category', headerName: '카테고리', width: 120 },
|
||||
{ field: 'setting_value', headerName: '설정 값 (Value)', width: 160 },
|
||||
{
|
||||
field: 'status',
|
||||
headerName: '상태 칩',
|
||||
width: 120,
|
||||
cellRenderer: (params: any) => {
|
||||
const type = params.value === 'ACTIVE' ? 'PASS' : params.value === 'WARNING' ? 'WARNING' : 'BLOCKED';
|
||||
return `<span class="status-chip ${type.toLowerCase()}">${params.value}</span>`;
|
||||
}
|
||||
},
|
||||
{ field: 'updated_at', headerName: '수정일시', width: 140 }
|
||||
]);
|
||||
|
||||
const loadData = async () => {
|
||||
// 모의 CRUD 데이터 세트
|
||||
items.value = [
|
||||
{ id: 1, setting_key: 'D2_CASH_TARGET', category: 'BUDGET', setting_value: '500,000,000', status: 'ACTIVE', updated_at: '2026-07-25 14:00', note: 'D+2 즉시방어 현금 목표' },
|
||||
{ id: 2, setting_key: 'RSI_UPPER_LIMIT', category: 'FACTOR', setting_value: '70.0', status: 'ACTIVE', updated_at: '2026-07-24 18:30', note: '과매수 상한 임계값' },
|
||||
{ id: 3, setting_key: 'ANTI_LATE_ENTRY', category: 'RISK', setting_value: 'ENABLED', status: 'WARNING', updated_at: '2026-07-22 10:15', note: '추격매수 방지 가드 레벨 2' },
|
||||
{ id: 4, setting_key: 'WATERFALL_SELL_PRIORITY', category: 'EXECUTION', setting_value: 'STRICT', status: 'BLOCKED', updated_at: '2026-07-20 09:00', note: '단일 sell priority waterfall 룰' }
|
||||
];
|
||||
if (items.value.length > 0) {
|
||||
selectedItem.value = JSON.parse(JSON.stringify(items.value[0]));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRowSelect = (rows: any[]) => {
|
||||
if (rows.length > 0) {
|
||||
selectedItem.value = JSON.parse(JSON.stringify(rows[0]));
|
||||
}
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
modalItem.value = {
|
||||
id: items.value.length + 1,
|
||||
setting_key: '',
|
||||
category: 'FACTOR',
|
||||
setting_value: '',
|
||||
status: 'ACTIVE',
|
||||
updated_at: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
||||
note: ''
|
||||
};
|
||||
isModalOpen.value = true;
|
||||
};
|
||||
|
||||
const saveModalItem = () => {
|
||||
if (!modalItem.value.setting_key) {
|
||||
alert('설정 키는 필수 입력 항목입니다.');
|
||||
return;
|
||||
}
|
||||
items.value.unshift({ ...modalItem.value });
|
||||
selectedItem.value = { ...modalItem.value };
|
||||
isModalOpen.value = false;
|
||||
alert('신규 설정 항목이 성공적으로 등록되었습니다.');
|
||||
};
|
||||
|
||||
const saveDetailForm = () => {
|
||||
if (!selectedItem.value) return;
|
||||
const idx = items.value.findIndex(i => i.id === selectedItem.value?.id);
|
||||
if (idx !== -1) {
|
||||
items.value[idx] = JSON.parse(JSON.stringify(selectedItem.value));
|
||||
alert('상세 변경 사항이 DB에 저장되었습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSelectedItem = () => {
|
||||
if (!selectedItem.value) return;
|
||||
if (confirm(`[${selectedItem.value.setting_key}] 항목을 삭제하시겠습니까?`)) {
|
||||
items.value = items.value.filter(i => i.id !== selectedItem.value?.id);
|
||||
selectedItem.value = items.value.length > 0 ? items.value[0] : null;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(loadData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Type 4: Standardized High-Density Input Components Template View -->
|
||||
<div style="display: flex; flex-direction: column; height: 100%; width: 100%; background: #F4F6F9; overflow-y: auto; padding: 16px;">
|
||||
<div style="background: #34495E; color: white; padding: 10px 16px; font-weight: bold; border-radius: 4px 4px 0 0; display: flex; justify-content: space-between;">
|
||||
<span><i class="ti ti-forms me-1"></i> SCR-07: 더존 ERP 표준 컴포넌트 & 입력 UX 마스크 통합 템플릿 (Type 4)</span>
|
||||
<div>
|
||||
<button style="background: #27AE60; color: white; border: none; padding: 4px 12px; font-weight: bold; border-radius: 3px; cursor: pointer;">
|
||||
<span class="hotkey-badge">F7</span>엑셀 다운로드
|
||||
</button>
|
||||
<div class="crud-masterpiece-container">
|
||||
<!-- Top Action & Search Bar -->
|
||||
<div class="crud-toolbar">
|
||||
<div class="toolbar-title">
|
||||
<h3 class="title-text">⚙️ SCR-07: 시스템 캘리브레이션 & 올인원 CRUD 통합 매니저</h3>
|
||||
<span class="subtitle-text">Master-Detail, AG Grid, Modal Dialog, 더존 6대 표준 컴포넌트 풀 세트</span>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
<div class="search-box">
|
||||
<input type="text" class="search-input" placeholder="설정 키/설명 검색..." v-model="searchKeyword" />
|
||||
</div>
|
||||
<button class="btn-action btn-search" @click="loadData"><span class="badge-key">F3</span>조회</button>
|
||||
<button class="btn-action btn-create" @click="openCreateModal"><span class="badge-key">F4</span>신규 등록</button>
|
||||
<button class="btn-action btn-delete" @click="deleteSelectedItem"><span class="badge-key">F5</span>삭제</button>
|
||||
<button class="btn-action btn-excel"><span class="badge-key">F7</span>엑셀 다운로드</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- High-Density Form Grid -->
|
||||
<div style="background: white; border: 1px solid #CBD5E1; border-top: none; padding: 16px; border-radius: 0 0 4px 4px;">
|
||||
<h4 style="margin-top: 0; color: #2C3E50; border-bottom: 2px solid #2C3E50; padding-bottom: 6px;">
|
||||
표준 입력 컴포넌트 마스크 및 기본 CRUD 입력 UX 규격
|
||||
</h4>
|
||||
<!-- Main Split Section (60% Master Grid : 40% Detail Form) -->
|
||||
<div class="crud-split-section">
|
||||
<!-- Master Grid Panel (Left 60%) -->
|
||||
<div class="master-grid-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-tag">Master Table</span>
|
||||
<h4 class="panel-title">📋 시스템 원장 항목 리스트 (총 {{ items.length }}건)</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<QuantDataGrid
|
||||
:columnDefs="columnDefs"
|
||||
:rowData="items"
|
||||
rowSelection="single"
|
||||
@row-selected="handleRowSelect"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span>선택된 ID: {{ selectedItem?.id || '없음' }}</span>
|
||||
<QuantStatusChip :type="selectedItem?.status === 'ACTIVE' ? 'PASS' : 'WARNING'" :label="selectedItem?.status || 'N/A'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse; margin-top: 12px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="종목 코드" required />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantInput v-model="codeVal" placeholder="코드 입력" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="수집 영업일자" required />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantDatePicker v-model="dateVal" />
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Split Divider Line -->
|
||||
<div class="split-divider">
|
||||
<div class="divider-handle"></div>
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="통화 금액(원)" required />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantInput v-model="currencyVal" type="currency" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="상태 선택" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantComboBox v-model="comboVal" :options="comboOptions" />
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Detail Form Panel (Right 40%) -->
|
||||
<div class="detail-form-panel" v-if="selectedItem">
|
||||
<div class="panel-header">
|
||||
<span class="panel-tag tag-active">Detail Form</span>
|
||||
<h4 class="panel-title">✏️ 상세 편집 폼 [{{ selectedItem.setting_key }}]</h4>
|
||||
</div>
|
||||
<div class="panel-body form-scroll-body">
|
||||
<div class="form-row">
|
||||
<QuantLabel text="설정 키 (Key)" required />
|
||||
<QuantInput v-model="selectedItem.setting_key" placeholder="키 명칭" />
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="체크박스 옵션" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantCheckBox v-model="checkVal" label="Anti-Late Entry Gate 활성화" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="라디오 선택" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantRadio v-model="radioVal" name="typeGroup" :options="radioOptions" />
|
||||
</td>
|
||||
</tr>
|
||||
<div class="form-row">
|
||||
<QuantLabel text="카테고리" required />
|
||||
<QuantComboBox
|
||||
v-model="selectedItem.category"
|
||||
:options="[
|
||||
{ label: 'BUDGET (예산)', value: 'BUDGET' },
|
||||
{ label: 'FACTOR (팩터)', value: 'FACTOR' },
|
||||
{ label: 'RISK (리스크)', value: 'RISK' },
|
||||
{ label: 'EXECUTION (체결)', value: 'EXECUTION' }
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="Auto Complete" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantAutoComplete v-model="autoVal" :suggestions="autoSuggestions" placeholder="종목명 자동완성..." />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="상태 칩 가드" />
|
||||
</td>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantStatusChip type="PASS" label="PASS (정상)" />
|
||||
</td>
|
||||
</tr>
|
||||
<div class="form-row">
|
||||
<QuantLabel text="설정 값 (Value)" required />
|
||||
<QuantInput v-model="selectedItem.setting_value" placeholder="설정값" />
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #CBD5E1; background: #F8FAFC;">
|
||||
<QuantLabel text="상세 비고 내용" />
|
||||
</td>
|
||||
<td colspan="3" style="padding: 8px; border: 1px solid #CBD5E1;">
|
||||
<QuantTextArea v-model="textVal" :rows="3" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="form-row">
|
||||
<QuantLabel text="상태 제어" />
|
||||
<QuantRadio
|
||||
v-model="selectedItem.status"
|
||||
name="statusRadio"
|
||||
:options="[
|
||||
{ label: 'ACTIVE (정상)', value: 'ACTIVE' },
|
||||
{ label: 'WARNING (경고)', value: 'WARNING' },
|
||||
{ label: 'BLOCKED (차단)', value: 'BLOCKED' }
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<QuantLabel text="하네스 동기화" />
|
||||
<QuantCheckBox v-model="selectedItem.id" label="PostgreSQL History-First 원장 동기화" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<QuantLabel text="상세 비고 설명" />
|
||||
<QuantTextArea v-model="selectedItem.note" :rows="3" />
|
||||
</div>
|
||||
|
||||
<button class="btn-save-form" @click="saveDetailForm">
|
||||
💾 PostgreSQL 설정 변경사항 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Dialog (신규 등록 대화상자) -->
|
||||
<div class="modal-backdrop" v-if="isModalOpen">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">✨ 신규 시스템 설정 항목 등록 Modal</h4>
|
||||
<button class="btn-close-modal" @click="isModalOpen = false">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-row mb-3">
|
||||
<QuantLabel text="설정 키 (Key)" required />
|
||||
<QuantInput v-model="modalItem.setting_key" placeholder="예: MAX_POSITION_SIZE" />
|
||||
</div>
|
||||
<div class="form-row mb-3">
|
||||
<QuantLabel text="설정 값 (Value)" required />
|
||||
<QuantInput v-model="modalItem.setting_value" placeholder="예: 50,000,000" />
|
||||
</div>
|
||||
<div class="form-row mb-3">
|
||||
<QuantLabel text="설명 비고" />
|
||||
<QuantTextArea v-model="modalItem.note" :rows="2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-modal-cancel" @click="isModalOpen = false">취소</button>
|
||||
<button class="btn-modal-save" @click="saveModalItem">저장 실행</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.crud-masterpiece-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 110px);
|
||||
background-color: #F1F5F9;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.crud-toolbar {
|
||||
background-color: #34495E;
|
||||
color: #FFFFFF;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-text { font-size: 0.95rem; font-weight: 700; margin: 0; }
|
||||
.subtitle-text { font-size: 0.75rem; color: #CBD5E1; }
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #64748B;
|
||||
font-size: 0.8rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-search { background-color: #2563EB; }
|
||||
.btn-create { background-color: #166534; }
|
||||
.btn-delete { background-color: #DC2626; }
|
||||
.btn-excel { background-color: #D97706; }
|
||||
|
||||
.badge-key {
|
||||
background: rgba(255,255,255,0.2);
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.crud-split-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.master-grid-panel {
|
||||
flex: 6;
|
||||
background: white;
|
||||
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;
|
||||
}
|
||||
|
||||
.divider-handle {
|
||||
width: 2px;
|
||||
height: 30px;
|
||||
background-color: #64748B;
|
||||
}
|
||||
|
||||
.detail-form-panel {
|
||||
flex: 4;
|
||||
background: white;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-left: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
background-color: #F8FAFC;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #E2E8F0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.panel-tag {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
background: #E2E8F0;
|
||||
color: #475569;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.panel-tag.tag-active { background: #2563EB; color: white; }
|
||||
.panel-title { font-size: 0.85rem; font-weight: 700; color: #1E293B; margin: 0; }
|
||||
|
||||
.panel-body { flex: 1; overflow-y: auto; padding: 16px; }
|
||||
|
||||
.panel-footer {
|
||||
padding: 8px 16px;
|
||||
background: #F8FAFC;
|
||||
border-top: 1px solid #E2E8F0;
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-scroll-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-save-form {
|
||||
margin-top: 16px;
|
||||
padding: 10px 0;
|
||||
background-color: #2563EB;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Modal Dialog Styles */
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
background: white;
|
||||
width: 480px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: #34495E;
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-title { font-size: 0.9rem; font-weight: 700; margin: 0; }
|
||||
.btn-close-modal { background: none; border: none; color: white; font-size: 1rem; cursor: pointer; }
|
||||
|
||||
.modal-body { padding: 16px; }
|
||||
.modal-footer {
|
||||
background: #F8FAFC;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
border-top: 1px solid #E2E8F0;
|
||||
}
|
||||
|
||||
.btn-modal-cancel {
|
||||
padding: 6px 14px;
|
||||
background: #E2E8F0;
|
||||
color: #475569;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-modal-save {
|
||||
padding: 6px 14px;
|
||||
background: #166534;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.status-chip) {
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:deep(.status-chip.pass) { background-color: #DCFCE7; color: #166534; }
|
||||
:deep(.status-chip.warning) { background-color: #FEF3C7; color: #92400E; }
|
||||
:deep(.status-chip.blocked) { background-color: #FEE2E2; color: #991B1B; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user