diff --git a/src/frontend/src/views/SystemSettingsView.vue b/src/frontend/src/views/SystemSettingsView.vue index 50ef3867..fcbea956 100644 --- a/src/frontend/src/views/SystemSettingsView.vue +++ b/src/frontend/src/views/SystemSettingsView.vue @@ -23,42 +23,75 @@ interface SettingItem { id: number; setting_key: string; category: string; + domain: 'OMS' | 'WMS' | 'ERP'; setting_value: string; status: 'ACTIVE' | 'WARNING' | 'BLOCKED'; updated_at: string; note: string; lock_version: number; + maker_checker: 'APPROVED' | 'PENDING' | 'REJECTED'; attachments?: string[]; audit_history?: AuditLog[]; } -// 1. 검색, 필터, 탭 상태 +// 1. 도메인 컨텍스트 (OMS / WMS / ERP) & 카테고리 탭 +const activeDomain = ref<'ALL' | 'OMS' | 'WMS' | 'ERP'>('ALL'); const searchKeyword = ref(''); -const activeTab = ref('ALL'); +const activeCategoryTab = ref('ALL'); -// 2. Master-Detail 바인딩 및 편집 모드 토글 상태 +// 2. 동적 드래그 반응형 Split Pane Resizer 상태 +const masterPanelWidth = ref(60); // 기본 60% +const isDraggingSplitter = ref(false); + +const startSplitterDrag = (e: MouseEvent) => { + isDraggingSplitter.value = true; + document.addEventListener('mousemove', onSplitterMouseMove); + document.addEventListener('mouseup', stopSplitterDrag); +}; + +const onSplitterMouseMove = (e: MouseEvent) => { + if (!isDraggingSplitter.value) return; + const container = document.querySelector('.crud-split-section'); + if (container) { + const rect = container.getBoundingClientRect(); + const newPercent = ((e.clientX - rect.left) / rect.width) * 100; + if (newPercent >= 25 && newPercent <= 75) { + masterPanelWidth.value = Math.round(newPercent); + } + } +}; + +const stopSplitterDrag = () => { + isDraggingSplitter.value = false; + document.removeEventListener('mousemove', onSplitterMouseMove); + document.removeEventListener('mouseup', stopSplitterDrag); +}; + +// 3. Master-Detail 바인딩 및 편집 모드 토글 상태 const items = ref([]); const selectedItem = ref(null); const isEditMode = ref(false); -// 3. 현장감 텔레메트리, 소켓, 로그 스트리밍 상태 +// 4. 현장감 텔레메트리, 소켓, AI AX 코파일럿 상태 const pingMs = ref(4); const isBatchRunning = ref(false); const batchProgress = ref(0); const dbConnections = ref(4); +const aiRecommendation = ref('🤖 AI AX Recommendation: RSI 상한값을 70.0에서 68.5로 보정 시 슬리피지 0.14% 감소가 예측됩니다.'); + const liveLogs = ref([ '[14:00:01] [INFO] SignalR WebSocket Hub connected to wss://localhost:5173/hub', '[14:00:05] [INFO] DbUp Migration schema verified (v2026.07.25_001)', - '[14:02:10] [INFO] Factor RSI calculated successfully (0.012s)', - '[14:02:15] [WARN] D2 Cash threshold check passed (500,000,000 KRW target)' + '[14:02:10] [INFO] OMS Order Router: Waterfall Priority Table active', + '[14:02:15] [WARN] WMS Vault Safety Check: D+2 Cash level 500,000,000 KRW OK' ]); -// 4. 페이지네이션 및 엑셀 드롭다운 상태 +// 5. 페이지네이션 및 드롭다운 상태 const currentPage = ref(1); const showExportMenu = ref(false); const isGuideModalOpen = ref(false); -// 5. 토스트 알림 시스템 상태 +// 6. 토스트 알림 메시지 const toastMessage = ref<{ text: string; type: 'success' | 'warning' | 'error' } | null>(null); const showToast = (text: string, type: 'success' | 'warning' | 'error' = 'success') => { toastMessage.value = { text, type }; @@ -67,68 +100,76 @@ const showToast = (text: string, type: 'success' | 'warning' | 'error' = 'succes }, 3000); }; -// 6. 모달 대화상자 상태 +// 7. 모달 대화상자 상태 const isModalOpen = ref(false); const modalItem = ref({ id: 0, setting_key: '', category: 'SYSTEM', + domain: 'OMS', setting_value: '', status: 'ACTIVE', updated_at: '', note: '', - lock_version: 1 + lock_version: 1, + maker_checker: 'APPROVED' }); -// 7. 필터링된 항목 계산 +// 8. 필터링된 항목 계산 const filteredItems = computed(() => { return items.value.filter(item => { - const matchTab = activeTab.value === 'ALL' || item.category === activeTab.value; + const matchDomain = activeDomain.value === 'ALL' || item.domain === activeDomain.value; + const matchCategory = activeCategoryTab.value === 'ALL' || item.category === activeCategoryTab.value; const matchSearch = !searchKeyword.value || item.setting_key.toLowerCase().includes(searchKeyword.value.toLowerCase()) || item.note.toLowerCase().includes(searchKeyword.value.toLowerCase()); - return matchTab && matchSearch; + return matchDomain && matchCategory && matchSearch; }); }); -// 8. AG Grid 컬럼 정의 +// 9. AG Grid 컬럼 정의 const columnDefs = ref([ - { field: 'id', headerName: 'ID', width: 60, sortable: true }, - { field: 'setting_key', headerName: '설정 키 (Key)', width: 170, sortable: true, filter: true }, - { field: 'category', headerName: '카테고리', width: 110 }, - { field: 'setting_value', headerName: '설정 값 (Value)', width: 140 }, + { field: 'id', headerName: 'ID', width: 55, sortable: true }, + { field: 'domain', headerName: '도메인', width: 85, cellRenderer: (p: any) => `${p.value}` }, + { field: 'setting_key', headerName: '설정 키 (Key)', width: 160, sortable: true, filter: true }, + { field: 'category', headerName: '카테고리', width: 100 }, + { field: 'setting_value', headerName: '설정 값 (Value)', width: 130 }, { field: 'status', headerName: '상태 칩', - width: 110, + width: 100, cellRenderer: (params: any) => { const type = params.value === 'ACTIVE' ? 'PASS' : params.value === 'WARNING' ? 'WARNING' : 'BLOCKED'; return `${params.value}`; } }, - { field: 'lock_version', headerName: 'Ver', width: 60 }, - { field: 'updated_at', headerName: '수정일시', width: 130 } + { field: 'maker_checker', headerName: '승인상태', width: 95 }, + { field: 'lock_version', headerName: 'Ver', width: 55 } ]); const loadData = async () => { 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 즉시방어 현금 목표', lock_version: 3, attachments: ['budget_spec_v1.pdf'], + id: 1, setting_key: 'D2_CASH_TARGET', category: 'BUDGET', domain: 'WMS', setting_value: '500,000,000', status: 'ACTIVE', updated_at: '2026-07-25 14:00', note: 'D+2 즉시방어 현금 목표', lock_version: 3, maker_checker: 'APPROVED', attachments: ['budget_spec_v1.pdf'], audit_history: [ { timestamp: '2026-07-25 14:00', user: 'admin_kjh', change: '설정값 4.8억 -> 5.0억 변경' }, { timestamp: '2026-07-20 09:00', user: 'system', change: '신규 등록' } ] }, { - id: 2, setting_key: 'RSI_UPPER_LIMIT', category: 'FACTOR', setting_value: '70.0', status: 'ACTIVE', updated_at: '2026-07-24 18:30', note: '과매수 상한 임계값', lock_version: 1, attachments: [], + id: 2, setting_key: 'RSI_UPPER_LIMIT', category: 'FACTOR', domain: 'OMS', setting_value: '70.0', status: 'ACTIVE', updated_at: '2026-07-24 18:30', note: '과매수 상한 임계값', lock_version: 1, maker_checker: 'APPROVED', attachments: [], audit_history: [{ timestamp: '2026-07-24 18:30', user: 'quant_dev', change: '상한 75 -> 70 보정' }] }, { - id: 3, setting_key: 'ANTI_LATE_ENTRY', category: 'RISK', setting_value: 'ENABLED', status: 'WARNING', updated_at: '2026-07-22 10:15', note: '추격매수 방지 가드 레벨 2', lock_version: 5, attachments: ['gate_audit.log'], + id: 3, setting_key: 'ANTI_LATE_ENTRY', category: 'RISK', domain: 'WMS', setting_value: 'ENABLED', status: 'WARNING', updated_at: '2026-07-22 10:15', note: '추격매수 방지 가드 레벨 2', lock_version: 5, maker_checker: 'PENDING', attachments: ['gate_audit.log'], audit_history: [{ timestamp: '2026-07-22 10:15', user: 'risk_officer', change: '경고 플래그 활성화' }] }, { - 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 룰', lock_version: 2, attachments: [], + id: 4, setting_key: 'WATERFALL_SELL_PRIORITY', category: 'EXECUTION', domain: 'OMS', setting_value: 'STRICT', status: 'BLOCKED', updated_at: '2026-07-20 09:00', note: '단일 sell priority waterfall 룰', lock_version: 2, maker_checker: 'APPROVED', attachments: [], + audit_history: [] + }, + { + id: 5, setting_key: 'ERP_TAX_ACCOUNT_CODE', category: 'ACCOUNTING', domain: 'ERP', setting_value: '1110-CASH', status: 'ACTIVE', updated_at: '2026-07-25 10:00', note: 'ERP 세무 현금 계정 맵핑', lock_version: 1, maker_checker: 'APPROVED', attachments: ['tax_map.xlsx'], audit_history: [] } ]; @@ -141,19 +182,26 @@ const runFactorBatch = () => { if (isBatchRunning.value) return; isBatchRunning.value = true; batchProgress.value = 0; - liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [EXEC] Triggered factor recalculation batch pipeline`); - showToast('⚡ 팩터 재계산 비동기 배치 작업이 트리거되었습니다.', 'success'); + liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [OMS/WMS] Executing Batch Engine Pipeline...`); + showToast('⚡ OMS/WMS/ERP 팩터 재계산 비동기 배치 작업이 시작되었습니다.', 'success'); const timer = setInterval(() => { batchProgress.value += 25; if (batchProgress.value >= 100) { clearInterval(timer); isBatchRunning.value = false; - liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [SUCCESS] Batch pipeline finished in 1.2s`); - showToast('✅ 팩터 재계산 배치가 성공적으로 완료되었습니다 (소요시간: 1.2s)', 'success'); + liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [SUCCESS] Batch Engine finished in 1.2s`); + showToast('✅ 팩터 재계산 배치가 성공적으로 완료되었습니다.', 'success'); } }, 300); }; +const applyAiRecommendation = () => { + if (selectedItem.value) { + selectedItem.value.setting_value = '68.5'; + showToast('🤖 AI AX 추천 가이드 (RSI 68.5)가 양식에 적용되었습니다.', 'success'); + } +}; + const handleRowSelect = (rows: any[]) => { if (rows.length > 0) { selectedItem.value = JSON.parse(JSON.stringify(rows[0])); @@ -166,11 +214,13 @@ const openCreateModal = () => { id: items.value.length + 1, setting_key: '', category: 'FACTOR', + domain: 'OMS', setting_value: '', status: 'ACTIVE', updated_at: new Date().toISOString().slice(0, 16).replace('T', ' '), note: '', - lock_version: 1 + lock_version: 1, + maker_checker: 'APPROVED' }; isModalOpen.value = true; }; @@ -183,7 +233,7 @@ const saveModalItem = () => { items.value.unshift({ ...modalItem.value }); selectedItem.value = { ...modalItem.value }; isModalOpen.value = false; - liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [CREATE] Added new setting key: ${modalItem.value.setting_key}`); + liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [CREATE] Added ${modalItem.value.domain} key: ${modalItem.value.setting_key}`); showToast('신규 설정 항목이 성공적으로 등록되었습니다.', 'success'); }; @@ -196,12 +246,12 @@ const saveDetailForm = () => { selectedItem.value.audit_history.unshift({ timestamp: new Date().toISOString().slice(0, 16).replace('T', ' '), user: 'admin_kjh', - change: `설정값 변경 (Ver -> v${selectedItem.value.lock_version})` + change: `수정 완료 (Ver -> v${selectedItem.value.lock_version})` }); items.value[idx] = JSON.parse(JSON.stringify(selectedItem.value)); isEditMode.value = false; - liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [UPDATE] Key '${selectedItem.value.setting_key}' saved (Ver v${selectedItem.value.lock_version})`); - showToast(`PostgreSQL 원장 설정 변경사항이 저장되었습니다 (Lock Ver: v${selectedItem.value.lock_version}).`, 'success'); + liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [UPDATE] Saved key '${selectedItem.value.setting_key}' (Lock Ver v${selectedItem.value.lock_version})`); + showToast(`PostgreSQL 원장 설정이 저장되었습니다 (Lock Ver: v${selectedItem.value.lock_version}).`, 'success'); } }; @@ -211,14 +261,14 @@ const deleteSelectedItem = () => { liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [DELETE] Deleted key: ${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; - showToast('항목이 성공적으로 삭제되었습니다.', 'warning'); + showToast('항목이 삭제되었습니다.', 'warning'); } }; const triggerExport = (fmt: string) => { showExportMenu.value = false; - liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [EXPORT] Exporting data stream as ${fmt}`); - showToast(`${fmt} 형식으로 대용량 스트리밍 내보내기를 시작합니다.`, 'success'); + liveLogs.value.unshift(`[${new Date().toLocaleTimeString()}] [EXPORT] Streaming ${fmt}`); + showToast(`${fmt} 포맷 내보내기를 시작합니다.`, 'success'); }; onMounted(loadData); @@ -236,23 +286,29 @@ onMounted(loadData); - +
-

⚙️ SCR-07: 현장감 퀀트 CRUD 매니저

+

⚙️ SCR-07: OMS · WMS · ERP · AX 통합 어드민

🟢 Live ({{ pingMs }}ms) - 📡 SignalR Connected + 📡 SignalR Active +
+ +
+ + + +
- Master-Detail, AG Grid, Batch Progress, Lock Guard, Telemetry, Log Terminal, 더존 컴포넌트
🛡️ DbUp: v2026.07.25 - + @@ -277,22 +333,28 @@ onMounted(loadData);
+ +
+ {{ aiRecommendation }} + +
+
-
- -
- -
+ +
+ +
Master Table -

📋 원장 항목 리스트 (검색 결과 {{ filteredItems.length }}건 / 총 {{ items.length }}건)

+

📋 OMS/WMS/ERP 원장 목록 ({{ filteredItems.length }}건 / 총 {{ items.length }}건)

- - -
+ +
- -
+ +
- Detail Form + {{ selectedItem.domain }} Form

✏️ 상세 데이터 [{{ selectedItem.setting_key }}]

-
🔒 Ver: v{{ selectedItem.lock_version }} + {{ selectedItem.maker_checker }}
@@ -336,23 +397,49 @@ onMounted(loadData);
+ +
+ 📌 {{ selectedItem.domain }} 도메인 전용 컴포넌트 +
+ 📉 호가 스프레드 가드: Ask 0.05% / Bid 0.04% | Waterfall 매도 순위: Strict Sell Priority #1 +
+
+ 🏦 Vault 현금 안전율: D+2 Target 5억 KRW (보호율 100%) | 사이징 가드: Level 2 Active +
+
+ 📑 ERP 계정코드: 1110-CASH (일반현금) | Maker-Checker 결재 릴레이: 최종 승인완료 (Approved) +
+
+
- - + +
+ + +
@@ -388,9 +475,9 @@ onMounted(loadData);
- +
-
💻 실시간 퀀트 엔진 로그 터미널
+
💻 실시간 퀀트 파이프라인 로그 터미널
{{ log }} @@ -398,7 +485,7 @@ onMounted(loadData);
- +
🕒 변경 감사 이력 타임라인 (Audit Trail)
@@ -434,13 +521,21 @@ onMounted(loadData);
- +