diff --git a/src/frontend/src/composables/useSystemSettings.ts b/src/frontend/src/composables/useSystemSettings.ts new file mode 100644 index 00000000..d21dce46 --- /dev/null +++ b/src/frontend/src/composables/useSystemSettings.ts @@ -0,0 +1,182 @@ +// useSystemSettings.ts +import { ref, computed, onMounted } from 'vue' + +export interface AuditLog { + timestamp: string; + user: string; + change: string; +} + +export 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[]; +} + +export function useSystemSettings() { + const activeDomain = ref<'ALL' | 'OMS' | 'WMS' | 'ERP'>('ALL'); + const searchKeyword = ref(''); + const activeCategoryTab = ref('ALL'); + const lastDraftSavedTime = ref(null); + + const isOmsModalOpen = ref(false); + const isWmsModalOpen = ref(false); + const isErpModalOpen = ref(false); + const isModalOpen = ref(false); + const isGuideModalOpen = ref(false); + const isEditMode = ref(false); + + const masterPanelWidth = ref(60); + const isDraggingSplitter = ref(false); + + 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] OMS Order Router: Waterfall Priority Table active', + '[14:02:15] [WARN] WMS Vault Safety Check: D+2 Cash level 500,000,000 KRW OK' + ]); + + const items = ref([]); + const selectedItem = ref(null); + + const toastMessage = ref<{ text: string; type: 'success' | 'warning' | 'error' } | null>(null); + const showToast = (text: string, type: 'success' | 'warning' | 'error' = 'success') => { + toastMessage.value = { text, type }; + setTimeout(() => { + toastMessage.value = null; + }, 3000); + }; + + const filteredItems = computed(() => { + return items.value.filter(item => { + 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 matchDomain && matchCategory && matchSearch; + }); + }); + + const loadData = async () => { + items.value = [ + { + 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', 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', 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', 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: [] + } + ]; + if (items.value.length > 0) { + selectedItem.value = JSON.parse(JSON.stringify(items.value[0])); + } + }; + + const cloneSelectedItem = () => { + if (!selectedItem.value) return; + const cloned: SettingItem = { + ...JSON.parse(JSON.stringify(selectedItem.value)), + id: items.value.length + 1, + setting_key: `${selectedItem.value.setting_key}_COPY`, + lock_version: 1, + updated_at: new Date().toISOString().slice(0, 16).replace('T', ' '), + audit_history: [{ timestamp: new Date().toISOString().slice(0, 16).replace('T', ' '), user: 'admin_kjh', change: '๊ธฐ์กด ํ•ญ๋ชฉ ๊ธฐ๋ฐ˜ ๋ณต์ œ ์ƒ์„ฑ' }] + }; + items.value.unshift(cloned); + selectedItem.value = cloned; + showToast(`๐Ÿ“‹ [${cloned.setting_key}] ํ•ญ๋ชฉ์ด ๋ณต์ œ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`, 'success'); + }; + + const runFactorBatch = () => { + if (isBatchRunning.value) return; + isBatchRunning.value = true; + batchProgress.value = 0; + 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 Engine finished in 1.2s`); + showToast('โœ… ํŒฉํ„ฐ ์žฌ๊ณ„์‚ฐ ๋ฐฐ์น˜๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.', 'success'); + } + }, 300); + }; + + const saveDetailForm = () => { + if (!selectedItem.value) return; + const idx = items.value.findIndex(i => i.id === selectedItem.value?.id); + if (idx !== -1) { + selectedItem.value.lock_version += 1; + if (!selectedItem.value.audit_history) selectedItem.value.audit_history = []; + selectedItem.value.audit_history.unshift({ + timestamp: new Date().toISOString().slice(0, 16).replace('T', ' '), + user: 'admin_kjh', + change: `์ˆ˜์ • ์™„๋ฃŒ (Ver -> v${selectedItem.value.lock_version})` + }); + items.value[idx] = JSON.parse(JSON.stringify(selectedItem.value)); + isEditMode.value = false; + lastDraftSavedTime.value = new Date().toLocaleTimeString(); + 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'); + } + }; + + const deleteSelectedItem = () => { + if (!selectedItem.value) return; + if (confirm(`[${selectedItem.value.setting_key}] ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?`)) { + 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'); + } + }; + + const setSplitRatio = (percent: number) => { + masterPanelWidth.value = percent; + showToast(`์Šคํ”Œ๋ฆฟ ๋ถ„ํ•  ๋น„์œจ์ด ${percent}% : ${100 - percent}% ๋กœ ์กฐ์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`, 'success'); + }; + + onMounted(loadData); + + return { + activeDomain, searchKeyword, activeCategoryTab, lastDraftSavedTime, + isOmsModalOpen, isWmsModalOpen, isErpModalOpen, isModalOpen, isGuideModalOpen, isEditMode, + masterPanelWidth, isDraggingSplitter, pingMs, isBatchRunning, batchProgress, dbConnections, aiRecommendation, + liveLogs, items, selectedItem, toastMessage, filteredItems, + showToast, loadData, cloneSelectedItem, runFactorBatch, saveDetailForm, deleteSelectedItem, setSplitRatio + }; +} diff --git a/src/frontend/src/views/SystemSettingsView.vue b/src/frontend/src/views/SystemSettingsView.vue index 8da9ff68..9a98f4b8 100644 --- a/src/frontend/src/views/SystemSettingsView.vue +++ b/src/frontend/src/views/SystemSettingsView.vue @@ -1,150 +1,35 @@