feat: PC 맞춤형 대시보드 구현 및 자산 현황 레이아웃 최적화
- PC 자산 관리 화면에 유형별(공용/서버/개인) 통계 및 차트 적용 - 자산 현황 대시보드의 위치 분류 체계 통일 (센터/IDC/한맥빌딩) - 하단 요약 표의 자산번호 컬럼을 비고(Memo)로 교체 및 말줄임표 적용 - 차트 크기 확대 및 네비게이션 메뉴 레이아웃 안정화 - ListFactory.ts 내 formatInline 미정의 오류 수정
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
:root {
|
||||
/* --- System Colors (Added) --- */
|
||||
/* --- System Colors --- */
|
||||
--color-red: #F21D0D;
|
||||
--color-pink: #E8175E;
|
||||
--color-magenta: #B92ED1;
|
||||
@@ -15,37 +15,6 @@
|
||||
--color-iron: #7F7F7F;
|
||||
--color-steel: #688897;
|
||||
|
||||
--color-red-light: #FEE9E7;
|
||||
--color-pink-light: #FDE8EF;
|
||||
--color-magenta-light: #F8EBFB;
|
||||
--color-purple-light: #F1ECF9;
|
||||
--color-navy-light: #EDEEF9;
|
||||
--color-blue-light: #E7F4FE;
|
||||
--color-cyan-light: #E6F7FF;
|
||||
--color-green-light: #EEF8EE;
|
||||
--color-yellow-light: #FFF9E6;
|
||||
--color-orange-light: #FFF5E6;
|
||||
--color-dahong-light: #FFECE6;
|
||||
--color-brown-light: #F6F1EF;
|
||||
--color-iron-light: #F3F3F3;
|
||||
--color-steel-light: #F0F4F5;
|
||||
|
||||
--color-red-medium: #FAA59E;
|
||||
--color-pink-medium: #F6A2BF;
|
||||
--color-magenta-medium: #E3ABEC;
|
||||
--color-purple-medium: #C5B1E7;
|
||||
--color-navy-medium: #B3BBE5;
|
||||
--color-blue-medium: #9ED1FA;
|
||||
--color-cyan-medium: #9ADFFE;
|
||||
--color-green-medium: #B8E0B9;
|
||||
--color-yellow-medium: #FFE599;
|
||||
--color-orange-medium: #FFD699;
|
||||
--color-dahong-medium: #FFB199;
|
||||
--color-dahong: #FF3D00;
|
||||
--color-dahong-light: #FFECE6;
|
||||
--color-dahong-medium: #FFB199;
|
||||
--color-dahong-dark: #cc3100;
|
||||
|
||||
/* --- Primary Brand Levels --- */
|
||||
--primary-lv-0: #E9EEED;
|
||||
--primary-lv-1: #D2DCDB;
|
||||
@@ -63,40 +32,26 @@
|
||||
--primary-hover: var(--primary-lv-5);
|
||||
--primary-light: var(--primary-lv-0);
|
||||
|
||||
--edit-mode-color: var(--color-dahong);
|
||||
--edit-mode-light: var(--color-dahong-light);
|
||||
--edit-mode-focus: var(--color-dahong-medium);
|
||||
--edit-mode-dark: var(--color-dahong-dark);
|
||||
|
||||
--text-main: #111827;
|
||||
--text-muted: #6B7280;
|
||||
--border-color: #E5E7EB;
|
||||
--bg-color: #F9FAFB;
|
||||
--bg-light: #FAFAFA;
|
||||
--sidebar-bg: #ffffff;
|
||||
--white: #FFFFFF;
|
||||
--danger: var(--color-red);
|
||||
--info: var(--color-blue);
|
||||
--success: var(--color-green);
|
||||
--warning: var(--color-orange);
|
||||
|
||||
--dash-primary: #6cc020;
|
||||
--dash-light: #f2f9ec;
|
||||
--dash-danger: #cf222e;
|
||||
|
||||
--header-height: 52px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
letter-spacing: -0.02em;
|
||||
/* 모든 요소에 자간 규칙 일괄 적용 */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: 'Pretendard Variable', Pretendard, sans-serif;
|
||||
color: var(--text-main);
|
||||
background-color: var(--bg-color);
|
||||
line-height: 1.5;
|
||||
@@ -111,12 +66,13 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* --- Main Header & GNB/LNB --- */
|
||||
/* --- Header --- */
|
||||
.main-header {
|
||||
background-color: var(--white);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
z-index: 100;
|
||||
height: var(--header-height);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
@@ -127,239 +83,45 @@ body {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 0.75rem; }
|
||||
.main-logo { height: 34px; width: auto; }
|
||||
.brand h1 { font-size: 1.1rem; font-weight: 800; color: var(--text-main); white-space: nowrap; }
|
||||
.brand h1 .sub-title { font-size: 0.85rem; color: var(--primary-color); font-weight: 600; margin-left: 0.25rem; }
|
||||
|
||||
.main-logo {
|
||||
height: 34px;
|
||||
width: auto;
|
||||
}
|
||||
.integrated-nav { flex: 1; height: 100%; display: flex; align-items: center; gap: 0.25rem; overflow: hidden; }
|
||||
.nav-group { display: flex; align-items: center; height: 100%; position: relative; flex-shrink: 0; }
|
||||
.gnb-trigger { font-size: 14px; font-weight: 700; color: var(--text-muted); padding: 0 0.75rem; cursor: pointer; height: 100%; display: flex; align-items: center; white-space: nowrap; transition: color 0.2s; }
|
||||
.nav-group.active .gnb-trigger, .nav-group:hover .gnb-trigger { color: var(--text-main); }
|
||||
.lnb-shelf { display: none; align-items: center; gap: 0.2rem; padding: 0 0.5rem; height: 60%; border-left: 1px solid var(--border-color); margin-left: 0.2rem; }
|
||||
|
||||
.brand h1 {
|
||||
font-size: 1.1rem;
|
||||
/* 전체적으로 살짝 축소 */
|
||||
font-weight: 800;
|
||||
color: var(--text-main);
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* 기본적으로 활성 탭의 서브메뉴 표시 */
|
||||
.nav-group.active.is-showing-shelf .lnb-shelf { display: flex; }
|
||||
|
||||
.brand h1 .sub-title {
|
||||
font-size: 0.85rem;
|
||||
/* 영문 제목은 더 작게 */
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
/* GNB 전체 영역에 마우스가 올라가면 활성 탭의 서브메뉴를 일단 숨김 (다른 메뉴 탐색 우선) */
|
||||
.integrated-nav:hover .nav-group.active.is-showing-shelf .lnb-shelf { display: none; }
|
||||
|
||||
.integrated-nav {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
/* 마우스가 올라간 메뉴의 서브메뉴만 표시 */
|
||||
.nav-group:hover .lnb-shelf { display: flex !important; }
|
||||
|
||||
.nav-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.lnb-item { font-size: 13px; font-weight: 500; color: var(--text-muted); cursor: pointer; padding: 0.2rem 0.6rem; border-radius: 4px; white-space: nowrap; transition: all 0.2s; }
|
||||
.lnb-item:hover { color: var(--primary-color); background-color: var(--primary-light); }
|
||||
.lnb-item.active { color: var(--primary-color); background-color: var(--primary-light); font-weight: 700; }
|
||||
|
||||
.gnb-trigger {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
padding: 0 1rem;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.role-switcher { display: flex; align-items: center; gap: 0.75rem; padding: 0 0.75rem; border-right: 1px solid var(--border-color); height: 24px; }
|
||||
.role-label { font-size: 11px; font-weight: 700; color: var(--text-muted); }
|
||||
.role-label.active { color: var(--primary-color); }
|
||||
.switch { position: relative; display: inline-block; width: 34px; height: 18px; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
|
||||
.slider:before { position: absolute; content: ""; height: 12px; width: 12px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
|
||||
input:checked + .slider { background-color: var(--color-orange); }
|
||||
input:checked + .slider:before { transform: translateX(16px); }
|
||||
|
||||
.lnb-shelf {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0 0.75rem;
|
||||
height: 60%;
|
||||
border-left: 1px solid var(--border-color);
|
||||
margin-left: 0.25rem;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.nav-group:hover .lnb-shelf,
|
||||
.nav-group.is-showing-shelf .lnb-shelf {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.lnb-item {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lnb-item:hover {
|
||||
color: var(--primary-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.lnb-item.active {
|
||||
color: var(--primary-color);
|
||||
background-color: var(--primary-light);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Role Switcher Toggle --- */
|
||||
.role-switcher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-right: 0.5rem;
|
||||
padding: 0 0.75rem;
|
||||
border-right: 1px solid var(--border-color);
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.role-label {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.role-label.active {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.role-label.admin.active {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
/* Toggle Switch Base */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 34px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--color-orange);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px var(--color-orange);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* --- Global Actions & Buttons --- */
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0 0.8rem;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
line-height: 1;
|
||||
white-space: nowrap; /* 텍스트 줄바꿈 방지 */
|
||||
flex-shrink: 0; /* 크기 찌그러짐 방지 */
|
||||
}
|
||||
|
||||
.btn i,
|
||||
.btn svg {
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--white);
|
||||
border: 1px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: var(--text-muted);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
color: var(--danger) !important;
|
||||
border-color: var(--danger) !important;
|
||||
}
|
||||
|
||||
/* --- Layout Frame --- */
|
||||
/* --- Layout Content --- */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 1.25rem 2rem 0; /* 상단 여백 1.25rem 추가 */
|
||||
padding: 1.25rem 2rem 0;
|
||||
overflow: hidden;
|
||||
/* 전체 스크롤 차단 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -370,93 +132,58 @@ input:checked + .slider:before {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
/* 내부 스크롤을 유도하기 위해 설정 */
|
||||
}
|
||||
|
||||
.view-content-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* --- View Toggle --- */
|
||||
.view-toggle-container { margin-bottom: 1rem; display: flex; justify-content: flex-start; }
|
||||
.view-toggle { display: inline-flex; background-color: var(--primary-lv-0); padding: 4px; border-radius: 8px; border: 1px solid var(--border-color); }
|
||||
.toggle-btn { padding: 6px 16px; font-size: 13px; font-weight: 600; color: var(--text-muted); background: none; border: none; border-radius: 6px; cursor: pointer; }
|
||||
.toggle-btn.active { background-color: var(--white); color: var(--primary-color); box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
||||
|
||||
/* --- System Status List (Docker Style) --- */
|
||||
.system-status-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.system-list-header { display: flex; align-items: center; padding: 0.75rem 1.25rem; background-color: var(--bg-light); border-bottom: 1px solid var(--border-color); font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; }
|
||||
.system-row { display: flex; align-items: center; padding: 1rem 1.25rem; background-color: var(--white); border: 1px solid var(--border-color); border-radius: 6px; transition: all 0.2s; }
|
||||
.system-row:hover { border-color: var(--primary-lv-3); box-shadow: 0 4px 12px rgba(0,0,0,0.03); }
|
||||
.col-status { width: 100px; display: flex; align-items: center; gap: 0.5rem; }
|
||||
.col-info { flex: 1.5; }
|
||||
.col-network { flex: 1; }
|
||||
.col-remote { flex: 1; display: flex; align-items: center; gap: 0.5rem; }
|
||||
.col-traffic { flex: 1.2; }
|
||||
.col-actions { width: 120px; display: flex; justify-content: flex-end; }
|
||||
.status-dot { width: 10px; height: 10px; border-radius: 50%; }
|
||||
.status-dot.online { background-color: var(--success); box-shadow: 0 0 6px var(--success); }
|
||||
.status-text { font-size: 11px; font-weight: 600; color: var(--success); }
|
||||
.asset-primary { font-weight: 700; font-size: 14px; }
|
||||
.asset-secondary { font-size: 12px; color: var(--text-muted); }
|
||||
.ip-address { font-weight: 600; font-family: monospace; color: var(--primary-color); }
|
||||
.traffic-mini-chart { display: flex; flex-direction: column; gap: 4px; }
|
||||
.traffic-info { display: flex; justify-content: space-between; font-size: 11px; }
|
||||
.progress-bg { height: 4px; background: var(--primary-lv-0); border-radius: 2px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: var(--primary-color); }
|
||||
.icon-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 4px; border: 1px solid var(--border-color); background: var(--white); color: var(--text-muted); cursor: pointer; }
|
||||
.icon-btn:hover { background-color: var(--primary-light); border-color: var(--primary-color); color: var(--primary-color); }
|
||||
|
||||
/* --- Footer --- */
|
||||
.main-footer {
|
||||
height: 40px;
|
||||
background-color: var(--white);
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.main-footer { height: 40px; background-color: var(--white); border-top: 1px solid var(--border-color); display: flex; align-items: center; justify-content: flex-end; padding: 0 1.5rem; flex-shrink: 0; }
|
||||
.main-footer p { font-size: 0.75rem; color: var(--text-muted); }
|
||||
|
||||
.main-footer p {
|
||||
font-family: 'Pretendard Variable', Pretendard, sans-serif;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: -0.0175rem;
|
||||
color: var(--text-muted);
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
-webkit-user-drag: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.text-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* --- Utility Styles --- */
|
||||
.badge {
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-muted {
|
||||
background-color: #9CA3AF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.text-tag {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
padding: 1px 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* --- Responsive Design (Tablet & Mobile) --- */
|
||||
@media (max-width: 1200px) {
|
||||
.header-container { gap: 0.75rem; padding: 0 1rem; }
|
||||
.brand h1 { font-size: 1rem; }
|
||||
.brand h1 .sub-title { font-size: 0.75rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.main-header { height: auto; padding: 0.5rem 0; }
|
||||
.header-container { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
|
||||
.integrated-nav { width: 100%; justify-content: flex-start; border-top: 1px solid var(--border-color); padding-top: 0.5rem; }
|
||||
.header-actions { width: 100%; justify-content: flex-end; padding-top: 0.5rem; }
|
||||
.content-area { padding: 0 1rem; }
|
||||
}
|
||||
/* --- Utility --- */
|
||||
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem; padding: 0 0.8rem; font-size: 12px; font-weight: 600; border-radius: 4px; cursor: pointer; height: 28px; }
|
||||
.btn-primary { background-color: var(--primary-color); color: var(--white); border: none; }
|
||||
.btn-outline { background-color: transparent; color: var(--text-muted); border: 1px solid var(--border-color); }
|
||||
.badge { padding: 2px 6px; border-radius: 4px; font-size: 11px; font-weight: 700; }
|
||||
.badge-primary { background-color: var(--primary-color); color: white; }
|
||||
.badge-light { background: var(--bg-color); color: var(--text-muted); border: 1px solid var(--border-color); }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.brand h1 .sub-title { display: none; } /* 아주 좁은 화면에선 영문명 숨김 */
|
||||
.header-actions .btn span { display: none; } /* 버튼 텍스트 숨기고 아이콘만 표시 */
|
||||
.header-actions .btn { padding: 0 0.5rem; }
|
||||
}
|
||||
.brand h1 .sub-title { display: none; }
|
||||
.header-actions .btn span { display: none; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema';
|
||||
import { dynamicSort, renderPageHeader } from '../../core/utils';
|
||||
import { dynamicSort, renderPageHeader, calculateAssetAge, formatInline } from '../../core/utils';
|
||||
import { setupTableSorting, SortState } from '../../core/tableHandler';
|
||||
import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler';
|
||||
import { createIcons, RefreshCcw, Plus, Edit2, Trash2, Users, Cloud, CreditCard, DollarSign, Paperclip } from 'lucide';
|
||||
import { state } from '../../core/state';
|
||||
import {
|
||||
createIcons, RefreshCcw, Plus, Edit2, Trash2, Users, Cloud,
|
||||
CreditCard, DollarSign, Paperclip
|
||||
} from 'lucide';
|
||||
|
||||
export interface ColumnDef {
|
||||
header: string;
|
||||
@@ -28,97 +32,340 @@ export interface ListViewConfig {
|
||||
columns: ColumnDef[];
|
||||
onRowClick?: (asset: any) => void;
|
||||
emptyMessage?: string;
|
||||
persistentSortState?: SortState; // Allow passing external sort state (like DomainListView)
|
||||
persistentSortState?: SortState;
|
||||
}
|
||||
|
||||
export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
// 1. 컨테이너 초기화 및 헤더 렌더링
|
||||
container.innerHTML = '';
|
||||
renderPageHeader(container, config.title);
|
||||
|
||||
const fullList = config.dataSource();
|
||||
let sortState: SortState = config.persistentSortState || { key: '', direction: 'asc' };
|
||||
|
||||
// Initialize currentFilters with all possible keys to avoid undefined issues
|
||||
let currentFilters: any = { keyword: '', corp: '', dept: '', loc: '', field: '', type: '' };
|
||||
|
||||
// 강제로 기본 뷰 모드를 'system' (자산 현황)으로 설정
|
||||
state.currentViewMode = 'system';
|
||||
|
||||
// 2. 뷰 전환 토글 버튼 생성 (명칭 변경)
|
||||
const toggleWrapper = document.createElement('div');
|
||||
toggleWrapper.className = 'view-toggle-container';
|
||||
toggleWrapper.innerHTML = `
|
||||
<div class="view-toggle">
|
||||
<button class="toggle-btn ${state.currentViewMode === 'system' ? 'active' : ''}" data-mode="system">자산 현황</button>
|
||||
<button class="toggle-btn ${state.currentViewMode === 'asset' ? 'active' : ''}" data-mode="asset">자산 목록</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(toggleWrapper);
|
||||
|
||||
// 3. 필터 바 생성 (자산 목록에서만 사용)
|
||||
const filterBar = document.createElement('div');
|
||||
filterBar.className = 'search-bar';
|
||||
container.appendChild(filterBar);
|
||||
|
||||
// 4. 컨텐츠 영역 생성
|
||||
const contentWrapper = document.createElement('div');
|
||||
contentWrapper.className = 'view-content-wrapper';
|
||||
container.appendChild(contentWrapper);
|
||||
|
||||
// --- 내부 상태 ---
|
||||
let selectedLocation: string | null = '기술개발센터';
|
||||
let selectedDetailLocation: string | null = null;
|
||||
|
||||
|
||||
// [자산 현황] 대시보드 렌더러
|
||||
const renderSystemStatus = () => {
|
||||
const isPcView = config.title === 'PC';
|
||||
const locationCounts: Record<string, number> = {};
|
||||
const pcTypeCounts = { public: 0, server: 0, personal: 0 };
|
||||
const extSubCounts = { tech: 0, idc: 0, hm: 0 };
|
||||
const intSubCounts = { tech: 0, idc: 0, hm: 0 };
|
||||
let internalCount = 0;
|
||||
let externalCount = 0;
|
||||
|
||||
fullList.forEach(asset => {
|
||||
const loc = asset[ASSET_SCHEMA.LOCATION.key] || '미지정';
|
||||
const serviceType = asset[ASSET_SCHEMA.SERVICE_TYPE.key] || '외부';
|
||||
const type = asset[ASSET_SCHEMA.ASSET_TYPE.key] || '';
|
||||
|
||||
locationCounts[loc] = (locationCounts[loc] || 0) + 1;
|
||||
|
||||
if (isPcView) {
|
||||
if (type.includes('공용')) pcTypeCounts.public++;
|
||||
else if (type.includes('서버')) pcTypeCounts.server++;
|
||||
else pcTypeCounts.personal++;
|
||||
}
|
||||
|
||||
if (serviceType === '내부') {
|
||||
internalCount++;
|
||||
if (loc === '기술개발센터') intSubCounts.tech++;
|
||||
else if (loc === 'IDC') intSubCounts.idc++;
|
||||
else if (loc === '한맥빌딩') intSubCounts.hm++;
|
||||
} else {
|
||||
externalCount++;
|
||||
if (loc === '기술개발센터') extSubCounts.tech++;
|
||||
else if (loc === 'IDC') extSubCounts.idc++;
|
||||
else if (loc === '한맥빌딩') extSubCounts.hm++;
|
||||
}
|
||||
});
|
||||
|
||||
const locLabels = Object.keys(locationCounts).sort((a, b) => locationCounts[b] - locationCounts[a]);
|
||||
const pcLabels = ['공용PC', '서버PC', '개인PC'];
|
||||
const pcData = [pcTypeCounts.public, pcTypeCounts.server, pcTypeCounts.personal];
|
||||
|
||||
const chartLabels = isPcView ? pcLabels : locLabels;
|
||||
const chartData = isPcView ? pcData : locLabels.map(l => locationCounts[l]);
|
||||
const chartColors = ['#1E5149', '#4255bd', '#92400E', '#B91C1C', '#6D28D9', '#BE185D', '#0369A1', '#15803D', '#4B5563'];
|
||||
|
||||
const updateTableOnly = () => {
|
||||
let filtered = selectedLocation
|
||||
? fullList.filter(a => (a[ASSET_SCHEMA.LOCATION.key] || '미지정') === selectedLocation)
|
||||
: fullList;
|
||||
const currentDetailLocs = Array.from(new Set(filtered.map(a => a[ASSET_SCHEMA.LOC_DETAIL.key] || '미지정'))).sort();
|
||||
if (selectedDetailLocation) filtered = filtered.filter(a => (a[ASSET_SCHEMA.LOC_DETAIL.key] || '미지정') === selectedDetailLocation);
|
||||
const finalDisplayList = (!selectedLocation && !selectedDetailLocation) ? filtered.slice(0, 10) : filtered;
|
||||
|
||||
const titleEl = document.getElementById('list-section-title');
|
||||
if (titleEl) titleEl.textContent = selectedLocation ? `${selectedLocation} 자산 현황 (${finalDisplayList.length}대)` : '위치별 자산등록현황 (최근 등록)';
|
||||
const selectEl = document.getElementById('select-detail-loc') as HTMLSelectElement;
|
||||
if (selectEl && !selectedDetailLocation) {
|
||||
selectEl.innerHTML = `<option value="">전체보기</option>` + currentDetailLocs.map(dl => `<option value="${dl}">${dl}</option>`).join('');
|
||||
}
|
||||
|
||||
const tbody = document.getElementById('system-status-tbody');
|
||||
if (tbody) {
|
||||
tbody.innerHTML = finalDisplayList.length === 0
|
||||
? `<tr><td colspan="4" style="padding: 3rem; text-align: center; color: var(--text-muted);">조회된 자산이 없습니다.</td></tr>`
|
||||
: finalDisplayList.map(asset => {
|
||||
const purpose = asset[ASSET_SCHEMA.ASSET_PURPOSE.key] || '';
|
||||
const serviceType = asset[ASSET_SCHEMA.SERVICE_TYPE.key] || '외부';
|
||||
const labelColor = serviceType === '내부' ? '#94A3B8' : '#35635C';
|
||||
const memo = asset[ASSET_SCHEMA.MEMO.key] || '';
|
||||
return `
|
||||
<tr style="border-bottom: 1px solid var(--border-color); cursor: pointer;" class="mini-row" data-id="${asset.id}">
|
||||
<td style="padding: 10px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"><span style="color: ${labelColor}; font-weight: 700; font-size: 12px;">${serviceType}</span></td>
|
||||
<td style="padding: 10px 0; font-weight: 600; color: var(--text-main); font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${purpose}">${purpose || '-'}</td>
|
||||
<td style="padding: 10px 0; color: var(--text-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${formatInline(memo)}">${formatInline(memo) || '-'}</td>
|
||||
<td style="padding: 10px 0; text-align: center; color: var(--text-main); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${asset[ASSET_SCHEMA.LOC_DETAIL.key] || '-'}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
tbody.querySelectorAll('.mini-row').forEach(row => {
|
||||
row.addEventListener('click', () => {
|
||||
const id = (row as HTMLElement).getAttribute('data-id');
|
||||
const asset = fullList.find(a => a.id === id);
|
||||
if (asset && config.onRowClick) config.onRowClick(asset);
|
||||
});
|
||||
row.addEventListener('mouseenter', () => { (row as HTMLElement).style.backgroundColor = '#F8FAFA'; });
|
||||
row.addEventListener('mouseleave', () => { (row as HTMLElement).style.backgroundColor = 'transparent'; });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
contentWrapper.innerHTML = `
|
||||
<div class="system-dashboard" style="height: calc(100vh - 240px); overflow: hidden; padding: 0.5rem 0; font-family: 'Pretendard', sans-serif; letter-spacing: -0.02em; display: flex; flex-direction: column;">
|
||||
|
||||
<!-- [자산 통계 그룹] -->
|
||||
<div style="border-bottom: 1px solid var(--border-color); padding-bottom: 1.25rem; margin-bottom: 1.5rem; flex-shrink: 0; display: grid; grid-template-columns: 1fr 1.5fr 1.5fr; gap: 2rem;">
|
||||
<div class="stat-group-item" style="min-width: 0;">
|
||||
<div style="font-size: 11px; font-weight: 600; color: var(--text-muted); margin-bottom: 0.25rem;">총 보유 자산</div>
|
||||
<div style="font-size: 28px; font-weight: 800; color: var(--text-main); line-height: 1.1;">${fullList.length}<span style="font-size: 13px; font-weight: 600; margin-left: 4px; color: var(--text-muted);">대</span></div>
|
||||
${isPcView ? `
|
||||
<div style="display: flex; gap: 0.75rem; font-size: 11px; color: var(--text-muted); margin-top: 0.5rem;">
|
||||
<span>공용: <strong style="color:var(--text-main)">${pcTypeCounts.public}</strong></span>
|
||||
<span>서버: <strong style="color:var(--text-main)">${pcTypeCounts.server}</strong></span>
|
||||
<span>개인: <strong style="color:var(--text-main)">${pcTypeCounts.personal}</strong></span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="stat-group-item" style="border-left: 1px solid var(--border-color); padding-left: 1.5rem; min-width: 0;">
|
||||
${isPcView ? '' : `
|
||||
<div style="display: flex; align-items: flex-end; gap: 0.75rem; margin-bottom: 0.5rem;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: var(--text-muted);">외부 (운영)</span>
|
||||
<span style="font-size: 22px; font-weight: 800; color: #35635C; line-height: 1;">${externalCount}</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.75rem; font-size: 11px; color: var(--text-muted); flex-wrap: wrap;">
|
||||
<span>기술개발센터: <strong style="color:var(--text-main)">${extSubCounts.tech}</strong></span>
|
||||
<span>IDC: <strong style="color:var(--text-main)">${extSubCounts.idc}</strong></span>
|
||||
<span>한맥빌딩: <strong style="color:var(--text-main)">${extSubCounts.hm}</strong></span>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<div class="stat-group-item" style="border-left: 1px solid var(--border-color); padding-left: 1.5rem; min-width: 0;">
|
||||
${isPcView ? '' : `
|
||||
<div style="display: flex; align-items: flex-end; gap: 0.75rem; margin-bottom: 0.5rem;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: var(--text-muted);">내부 (테스트)</span>
|
||||
<span style="font-size: 22px; font-weight: 800; color: #94A3B8; line-height: 1;">${internalCount}</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.75rem; font-size: 11px; color: var(--text-muted); flex-wrap: wrap;">
|
||||
<span>기술개발센터: <strong style="color:var(--text-main)">${intSubCounts.tech}</strong></span>
|
||||
<span>IDC: <strong style="color:var(--text-main)">${intSubCounts.idc}</strong></span>
|
||||
<span>한맥빌딩: <strong style="color:var(--text-main)">${intSubCounts.hm}</strong></span>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1.2fr 1.6fr; gap: 2.5rem; flex: 1; min-height: 0;">
|
||||
<!-- 차트 구역 -->
|
||||
<div class="chart-section" style="display: flex; flex-direction: column; min-height: 0; width: 100%;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; flex-shrink: 0;">
|
||||
<h4 style="font-size: 14px; font-weight: 700; color: var(--text-main);">${isPcView ? '유형별 분포' : '위치별 분포'}</h4>
|
||||
${!isPcView && selectedLocation ? `<button id="btn-reset-loc" style="font-size:11px; color:var(--primary-color); background:none; border:none; cursor:pointer; font-weight:700;">초기화 ↺</button>` : ''}
|
||||
</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 1rem; flex-shrink: 0;">
|
||||
${chartLabels.map((l, i) => `
|
||||
<div style="font-size: 11px; display: flex; align-items: center; gap: 5px; cursor:pointer;
|
||||
color: ${!isPcView && selectedLocation === l ? 'var(--primary-color)' : 'var(--text-main)'};
|
||||
font-weight: ${!isPcView && selectedLocation === l ? '800' : '500'};"
|
||||
${!isPcView ? `onclick="window.dispatchLocFilter('${l}')"` : ''}>
|
||||
<span style="width: 6px; height: 6px; border-radius: 50%; background: ${chartColors[i % chartColors.length]}; opacity: ${!isPcView && selectedLocation && selectedLocation !== l ? 0.2 : 0.8}"></span>
|
||||
<span>${l}</span>
|
||||
<span style="color: var(--text-muted); opacity: 0.6;">${chartData[i]}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div style="flex: 1; position: relative; min-height: 250px; max-height: 320px; display: flex; justify-content: center;">
|
||||
<canvas id="system-location-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-section" style="display: flex; flex-direction: column; min-height: 0;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; height: 32px; flex-shrink: 0;">
|
||||
<h4 id="list-section-title" style="font-size: 14px; font-weight: 700; color: var(--text-main);">자산등록현황</h4>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: var(--text-muted);">상세위치 필터:</span>
|
||||
<select id="select-detail-loc" style="padding: 2px 8px; font-size: 11px; border-radius: 4px; border: 1px solid var(--border-color); outline: none; background: white; cursor:pointer; font-family: 'Pretendard';"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1; overflow-y: auto; border-top: 2px solid var(--text-main);">
|
||||
<table style="width: 100%; border-collapse: collapse; table-layout: fixed;">
|
||||
<thead style="position: sticky; top: 0; background: white; z-index: 10;">
|
||||
<tr style="text-align: left; font-size: 11px;">
|
||||
<th style="padding: 8px 0; font-weight: 700; border-bottom: 1px solid var(--border-color); width: 50px;">분류</th>
|
||||
<th style="padding: 8px 0; font-weight: 700; border-bottom: 1px solid var(--border-color); width: 130px;">용도</th>
|
||||
<th style="padding: 8px 0; font-weight: 700; border-bottom: 1px solid var(--border-color);">비고</th>
|
||||
<th style="padding: 8px 0; text-align: center; font-weight: 700; border-bottom: 1px solid var(--border-color); width: 90px;">상세위치</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="system-status-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
(window as any).dispatchLocFilter = (loc: string) => {
|
||||
if (isPcView) return; // PC 뷰에서는 위치 필터링 비활성화 (유형별로 보기 때문)
|
||||
selectedLocation = loc;
|
||||
selectedDetailLocation = null;
|
||||
renderSystemStatus();
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
const ctx = document.getElementById('system-location-chart') as HTMLCanvasElement;
|
||||
if (ctx && typeof (window as any).Chart !== 'undefined') {
|
||||
new (window as any).Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: { labels: chartLabels, datasets: [{ data: chartData, backgroundColor: chartColors, borderWidth: 0 }] },
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false, cutout: '70%',
|
||||
onClick: (evt: any, elements: any[]) => {
|
||||
if (!isPcView && elements.length > 0) {
|
||||
selectedLocation = locLabels[elements[0].index];
|
||||
selectedDetailLocation = null;
|
||||
renderSystemStatus();
|
||||
}
|
||||
},
|
||||
plugins: { legend: { display: false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById('btn-reset-loc')?.addEventListener('click', () => {
|
||||
selectedLocation = null;
|
||||
selectedDetailLocation = null;
|
||||
renderSystemStatus();
|
||||
});
|
||||
document.getElementById('select-detail-loc')?.addEventListener('change', (e) => {
|
||||
selectedDetailLocation = (e.target as HTMLSelectElement).value || null;
|
||||
updateTableOnly();
|
||||
});
|
||||
updateTableOnly();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// [자산 목록] 테이블 렌더러
|
||||
const tableWrapper = document.createElement('div');
|
||||
tableWrapper.className = 'table-container';
|
||||
const table = document.createElement('table');
|
||||
|
||||
// 1. 헤더 생성
|
||||
const thead = document.createElement('thead');
|
||||
const trHead = document.createElement('tr');
|
||||
config.columns.forEach(col => {
|
||||
const th = document.createElement('th');
|
||||
th.innerHTML = col.header;
|
||||
if (col.sortKey) th.setAttribute('data-sort', col.sortKey);
|
||||
if (col.width) th.style.width = col.width;
|
||||
if (col.align) th.style.textAlign = col.align;
|
||||
if (col.className) th.className = col.className;
|
||||
trHead.appendChild(th);
|
||||
});
|
||||
thead.appendChild(trHead);
|
||||
table.appendChild(thead);
|
||||
|
||||
// 2. 본문 생성
|
||||
const tbody = document.createElement('tbody');
|
||||
tbody.id = 'dynamic-tbody';
|
||||
table.appendChild(thead);
|
||||
table.appendChild(tbody);
|
||||
|
||||
tableWrapper.appendChild(table);
|
||||
container.appendChild(tableWrapper);
|
||||
|
||||
// 3. 테이블 업데이트 로직
|
||||
const updateTable = () => {
|
||||
if (state.currentViewMode !== 'asset') return;
|
||||
let filtered = applyCommonFilters(fullList, currentFilters, config.searchKeys as any[]);
|
||||
if (sortState.key) filtered = dynamicSort(filtered, sortState.key, sortState.direction);
|
||||
|
||||
if (sortState.key) {
|
||||
filtered = dynamicSort(filtered, sortState.key, sortState.direction);
|
||||
}
|
||||
thead.innerHTML = `<tr>${config.columns.map(col => `
|
||||
<th ${col.sortKey ? `data-sort="${col.sortKey}"` : ''}
|
||||
style="${col.width ? `width:${col.width};` : ''}${col.align ? `text-align:${col.align};` : ''}"
|
||||
class="${col.className || ''}">${col.header}</th>`).join('')}</tr>`;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
if (filtered.length === 0) {
|
||||
const emptyMsg = config.emptyMessage || UI_TEXT.MESSAGES.NO_DATA;
|
||||
tbody.innerHTML = `<tr><td colspan="${config.columns.length}" class="text-center" style="padding: 3rem; color: var(--text-muted);">${emptyMsg}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = filtered.length === 0
|
||||
? `<tr><td colspan="${config.columns.length}" class="text-center" style="padding: 3rem; color: var(--text-muted);">${config.emptyMessage || UI_TEXT.MESSAGES.NO_DATA}</td></tr>`
|
||||
: filtered.map(asset => `
|
||||
<tr style="cursor:pointer;" class="asset-row" data-id="${asset.id}">
|
||||
${config.columns.map(col => `<td style="${col.align ? `text-align:${col.align};` : ''}" class="${col.className || ''}">${col.render(asset)}</td>`).join('')}
|
||||
</tr>`).join('');
|
||||
|
||||
filtered.forEach((asset) => {
|
||||
const tr = document.createElement('tr');
|
||||
if (config.onRowClick) {
|
||||
tr.style.cursor = 'pointer';
|
||||
tr.addEventListener('click', () => config.onRowClick!(asset));
|
||||
}
|
||||
|
||||
config.columns.forEach(col => {
|
||||
const td = document.createElement('td');
|
||||
if (col.align) td.style.textAlign = col.align;
|
||||
if (col.className) td.className = col.className;
|
||||
td.innerHTML = col.render(asset);
|
||||
tr.appendChild(td);
|
||||
});
|
||||
|
||||
tbody.appendChild(tr);
|
||||
tbody.querySelectorAll('.asset-row').forEach((tr, idx) => {
|
||||
tr.addEventListener('click', () => config.onRowClick && config.onRowClick(filtered[idx]));
|
||||
});
|
||||
|
||||
setupTableSorting(table, sortState, (key, dir) => {
|
||||
sortState = { key, direction: dir };
|
||||
// If external state was provided, sync it back
|
||||
if (config.persistentSortState) {
|
||||
config.persistentSortState.key = key;
|
||||
config.persistentSortState.direction = dir;
|
||||
config.persistentSortState.key = key;
|
||||
config.persistentSortState.direction = dir;
|
||||
}
|
||||
updateTable();
|
||||
});
|
||||
|
||||
// 모든 가능한 아이콘 로드 (안전하게)
|
||||
createIcons({ icons: { RefreshCcw, Plus, Edit2, Trash2, Users, Cloud, CreditCard, DollarSign, Paperclip } });
|
||||
};
|
||||
|
||||
// 4. 필터 바 렌더링
|
||||
// --- 뷰 전환 로직 ---
|
||||
const switchView = () => {
|
||||
contentWrapper.innerHTML = '';
|
||||
if (state.currentViewMode === 'asset') {
|
||||
filterBar.style.display = 'flex';
|
||||
contentWrapper.style.overflowY = 'auto';
|
||||
contentWrapper.appendChild(tableWrapper);
|
||||
updateTable();
|
||||
} else {
|
||||
filterBar.style.display = 'none';
|
||||
contentWrapper.style.overflowY = 'hidden';
|
||||
renderSystemStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// 토글 버튼 이벤트
|
||||
toggleWrapper.addEventListener('click', (e) => {
|
||||
const btn = (e.target as HTMLElement).closest('.toggle-btn') as HTMLButtonElement;
|
||||
if (!btn) return;
|
||||
toggleWrapper.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
state.currentViewMode = btn.getAttribute('data-mode') as 'asset' | 'system';
|
||||
switchView();
|
||||
});
|
||||
|
||||
// 필터 바 초기화
|
||||
renderFilterBar(filterBar, {
|
||||
...config.filterOptions,
|
||||
onFilterChange: (filters) => {
|
||||
@@ -127,18 +374,11 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 동적 Select 박스 데이터 채우기
|
||||
// 셀렉트 박스 채우기
|
||||
const populateSelect = (selector: string, dataKey: string) => {
|
||||
const select = container.querySelector(selector) as HTMLSelectElement;
|
||||
if (select) {
|
||||
// Handle multiple possible keys for department names due to legacy data
|
||||
const getVal = (a: any) => {
|
||||
if (dataKey === ASSET_SCHEMA.CURRENT_DEPT.key) {
|
||||
return a[dataKey] || a['현사용부서'] || a['현사용조직'];
|
||||
}
|
||||
return a[dataKey];
|
||||
}
|
||||
|
||||
const getVal = (a: any) => dataKey === ASSET_SCHEMA.CURRENT_DEPT.key ? (a[dataKey] || a['현사용부서'] || a['현사용조직']) : a[dataKey];
|
||||
const uniqueValues = Array.from(new Set(fullList.map(getVal))).filter(Boolean).sort();
|
||||
uniqueValues.forEach(val => {
|
||||
const opt = document.createElement('option');
|
||||
@@ -154,6 +394,6 @@ export function createListView(container: HTMLElement, config: ListViewConfig) {
|
||||
if (config.filterOptions.showCorp) populateSelect('#filter-corp', ASSET_SCHEMA.PURCHASE_CORP.key);
|
||||
if (config.filterOptions.showType) populateSelect('#filter-type', ASSET_SCHEMA.ASSET_TYPE.key);
|
||||
|
||||
// 6. 초기 렌더링
|
||||
updateTable();
|
||||
// 초기 실행
|
||||
switchView();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user