diff --git a/src/components/Navigation.ts b/src/components/Navigation.ts index 564a646..180c95c 100644 --- a/src/components/Navigation.ts +++ b/src/components/Navigation.ts @@ -43,7 +43,7 @@ export function renderNavigation(onTabChange: (tab: string) => void) { trigger.addEventListener('click', () => { if (state.activeCategory !== catKey) { - state.activeCategory = catKey; + state.activeCategory = catKey as any; const firstTab = config.tabs[0]; state.activeSubTab = firstTab; render(); @@ -55,14 +55,14 @@ export function renderNavigation(onTabChange: (tab: string) => void) { const shelf = document.createElement('div'); shelf.className = 'lnb-shelf'; - config.tabs.forEach(tab => { + config.tabs.forEach((tab: string) => { const item = document.createElement('div'); item.className = `lnb-item ${isActive && state.activeSubTab === tab ? 'active' : ''}`; item.textContent = tab; item.addEventListener('click', (e) => { e.stopPropagation(); - state.activeCategory = catKey; + state.activeCategory = catKey as any; state.activeSubTab = tab; render(); onTabChange(tab); diff --git a/src/core/state.ts b/src/core/state.ts index 864737b..a2a4daf 100644 --- a/src/core/state.ts +++ b/src/core/state.ts @@ -18,6 +18,8 @@ export interface MasterAssetData { domain: any[]; cost: any[]; vip: any[]; + mobile?: any[]; // Legacy mobile support + equip?: any[]; // Backward compat // Backward compatibility subSw: any[]; diff --git a/src/views/Dashboard/SwDashboard.ts b/src/views/Dashboard/SwDashboard.ts index 67f4db2..aba42e5 100644 --- a/src/views/Dashboard/SwDashboard.ts +++ b/src/views/Dashboard/SwDashboard.ts @@ -75,7 +75,7 @@ export function renderSwDashboard(container: HTMLElement) { } function isSWExpiring(sw: any) { - const expiry = sw[ASSET_SCHEMA.EXPIRY_DATE.key]; + const expiry = sw[ASSET_SCHEMA.EXPIRED_DATE.key]; if (!expiry) return false; const endMs = new Date(normalizeDate(expiry)).getTime(); const diffDays = (endMs - Date.now()) / (1000 * 60 * 60 * 24); diff --git a/src/views/List/CloudListView.ts b/src/views/List/CloudListView.ts index a91b6d4..8f9b87f 100644 --- a/src/views/List/CloudListView.ts +++ b/src/views/List/CloudListView.ts @@ -1,104 +1,31 @@ import { state } from '../../core/state'; import { openSwModal } from '../../components/Modal/SWModal'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { dynamicSort, formatInline, getActionButtonsHTML, renderPageHeader } from '../../core/utils'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, Cloud, CreditCard, DollarSign, RefreshCcw, Plus } from 'lucide'; +import { formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 클라우드(운영 서비스) 자산 목록 뷰 - */ export function renderCloudList(container: HTMLElement) { - renderPageHeader(container, '클라우드'); - - const fullList = state.masterData.cloud || []; - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.PRODUCT_NAME.ui} - ${ASSET_SCHEMA.ASSET_PURPOSE.ui} - ${ASSET_SCHEMA.PURCHASE_VENDOR.ui} - ${ASSET_SCHEMA.PURCHASE_AMOUNT.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['PRODUCT_NAME', 'ASSET_PURPOSE', 'PURCHASE_VENDOR']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.PRODUCT_NAME.key]||''} - ${asset[ASSET_SCHEMA.ASSET_PURPOSE.key]||''} - ${asset[ASSET_SCHEMA.PURCHASE_VENDOR.key]||''} - ₩ ${asset[ASSET_SCHEMA.PURCHASE_AMOUNT.key] ? Number(String(asset[ASSET_SCHEMA.PURCHASE_AMOUNT.key]).replace(/,/g, '')).toLocaleString() : '0'} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'')} - `; - - tr.addEventListener('click', () => openSwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { Cloud, CreditCard, DollarSign, RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.PURCHASE_VENDOR.ui})`, - showCorp: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '클라우드', + dataSource: () => state.masterData.cloud || [], + searchKeys: ['PRODUCT_NAME', 'ASSET_PURPOSE', 'PURCHASE_VENDOR'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.PURCHASE_VENDOR.ui})`, + showCorp: true, + showDept: true + }, + onRowClick: (asset) => openSwModal(asset, 'view'), + columns: [ + { header: ASSET_SCHEMA.PRODUCT_NAME.ui, sortKey: ASSET_SCHEMA.PRODUCT_NAME.key, render: a => a[ASSET_SCHEMA.PRODUCT_NAME.key] || '' }, + { header: ASSET_SCHEMA.ASSET_PURPOSE.ui, sortKey: ASSET_SCHEMA.ASSET_PURPOSE.key, render: a => a[ASSET_SCHEMA.ASSET_PURPOSE.key] || '' }, + { header: ASSET_SCHEMA.PURCHASE_VENDOR.ui, sortKey: ASSET_SCHEMA.PURCHASE_VENDOR.key, render: a => a[ASSET_SCHEMA.PURCHASE_VENDOR.key] || '' }, + { + header: ASSET_SCHEMA.PURCHASE_AMOUNT.ui, + sortKey: ASSET_SCHEMA.PURCHASE_AMOUNT.key, + align: 'right', + render: a => `₩ ${a[ASSET_SCHEMA.PURCHASE_AMOUNT.key] ? Number(String(a[ASSET_SCHEMA.PURCHASE_AMOUNT.key]).replace(/,/g, '')).toLocaleString() : '0'}` + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '') } + ] }); - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/CostListView.ts b/src/views/List/CostListView.ts index 590bbd2..8d93c19 100644 --- a/src/views/List/CostListView.ts +++ b/src/views/List/CostListView.ts @@ -1,108 +1,35 @@ import { state } from '../../core/state'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 비용관리 자산 목록 뷰 - */ export function renderCostList(container: HTMLElement) { - renderPageHeader(container, '비용관리'); - - const fullList = sortAssets(state.masterData.cloud?.filter((a: any) => a.category === '비용관리') || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.ASSET_PURPOSE.ui} - 현 사용자 - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.EMAIL_ACCOUNT.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['PRODUCT_NAME', 'MANAGER_MAIN', 'EMAIL_ACCOUNT']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''} - ${formatInline(asset[ASSET_SCHEMA.ASSET_PURPOSE.key] || '-')} - ${asset[ASSET_SCHEMA.MANAGER_MAIN.key] || '-'} - ${displayLoc} - ${asset[ASSET_SCHEMA.EMAIL_ACCOUNT.key] || '-'} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => alert('상세 정보 준비 중입니다.')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.MANAGER_MAIN.ui})`, - showCorp: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '비용관리', + dataSource: () => sortAssets(state.masterData.cloud?.filter((a: any) => a.category === '비용관리') || []), + searchKeys: ['PRODUCT_NAME', 'MANAGER_MAIN', 'EMAIL_ACCOUNT'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.MANAGER_MAIN.ui})`, + showCorp: true, + showDept: true + }, + onRowClick: () => alert('상세 정보 준비 중입니다.'), + columns: [ + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '' }, + { header: ASSET_SCHEMA.ASSET_PURPOSE.ui, sortKey: ASSET_SCHEMA.ASSET_PURPOSE.key, render: a => formatInline(a[ASSET_SCHEMA.ASSET_PURPOSE.key] || '-') }, + { header: '현 사용자', sortKey: ASSET_SCHEMA.MANAGER_MAIN.key, align: 'center', render: a => a[ASSET_SCHEMA.MANAGER_MAIN.key] || '-' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.EMAIL_ACCOUNT.ui, sortKey: ASSET_SCHEMA.EMAIL_ACCOUNT.key, render: a => a[ASSET_SCHEMA.EMAIL_ACCOUNT.key] || '-' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/DomainListView.ts b/src/views/List/DomainListView.ts index 75eff70..16305b6 100644 --- a/src/views/List/DomainListView.ts +++ b/src/views/List/DomainListView.ts @@ -1,108 +1,38 @@ import { state } from '../../core/state'; -import { dynamicSort, formatInline, getActionButtonsHTML, renderPageHeader } from '../../core/utils'; -import { createIcons, Plus, Edit2, Trash2, RefreshCcw } from 'lucide'; import { openDomainModal } from '../../components/Modal/DomainModal'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; +import { formatInline } from '../../core/utils'; import { ASSET_SCHEMA } from '../../core/schema'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; +import { SortState } from '../../core/tableHandler'; +import { createListView } from './ListFactory'; // 정렬 상태를 모듈 수준에서 관리하여 화면 갱신 시에도 유지되도록 함 let persistentSortState: SortState = { key: '', direction: 'asc' }; export function renderDomainList(container: HTMLElement) { - renderPageHeader(container, '도메인'); - - const fullList = state.masterData.domain; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - // 검색바 추가 - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.DOMAIN_ADDR.ui} - ${ASSET_SCHEMA.ASSET_PURPOSE.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.PURCHASE_CORP.ui} - ${ASSET_SCHEMA.EXPIRED_DATE.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['DOMAIN_ADDR', 'ASSET_PURPOSE', 'PRODUCT_NAME']); - - if (persistentSortState.key) { - filtered = dynamicSort(filtered, persistentSortState.key, persistentSortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `등록된 도메인 정보가 없습니다.`; - return; - } - - filtered.forEach((item, idx) => { - const tr = document.createElement('tr'); - tr.className = 'domain-row'; - tr.style.cursor = 'pointer'; - - tr.innerHTML = ` - ${item[ASSET_SCHEMA.DOMAIN_ADDR.key] || ''} - ${item[ASSET_SCHEMA.ASSET_PURPOSE.key] || ''} - ${item[ASSET_SCHEMA.ASSET_TYPE.key] || '-'} - ${item[ASSET_SCHEMA.PURCHASE_CORP.key] || ''} - ${item[ASSET_SCHEMA.EXPIRED_DATE.key] || ''} - ${formatInline(item[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', (e) => { - openDomainModal(item); - }); - tbody.appendChild(tr); - }); - - setupTableSorting(table, persistentSortState, (key, dir) => { - persistentSortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { Plus, Edit2, Trash2, RefreshCcw } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.DOMAIN_ADDR.ui}/${ASSET_SCHEMA.PRODUCT_NAME.ui})`, - showCorp: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '도메인', + dataSource: () => state.masterData.domain || [], + searchKeys: ['DOMAIN_ADDR', 'ASSET_PURPOSE', 'PRODUCT_NAME'], + persistentSortState, + emptyMessage: '등록된 도메인 정보가 없습니다.', + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.DOMAIN_ADDR.ui}/${ASSET_SCHEMA.PRODUCT_NAME.ui})`, + showCorp: true, + showDept: true + }, + onRowClick: (item) => openDomainModal(item), + columns: [ + { header: ASSET_SCHEMA.DOMAIN_ADDR.ui, sortKey: ASSET_SCHEMA.DOMAIN_ADDR.key, align: 'left', render: a => a[ASSET_SCHEMA.DOMAIN_ADDR.key] || '' }, + { header: ASSET_SCHEMA.ASSET_PURPOSE.ui, sortKey: ASSET_SCHEMA.ASSET_PURPOSE.key, align: 'left', render: a => a[ASSET_SCHEMA.ASSET_PURPOSE.key] || '' }, + { + header: ASSET_SCHEMA.ASSET_TYPE.ui, + sortKey: ASSET_SCHEMA.ASSET_TYPE.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.ASSET_TYPE.key] || '-'}` + }, + { header: ASSET_SCHEMA.PURCHASE_CORP.ui, sortKey: ASSET_SCHEMA.PURCHASE_CORP.key, align: 'center', render: a => a[ASSET_SCHEMA.PURCHASE_CORP.key] || '' }, + { header: ASSET_SCHEMA.EXPIRED_DATE.ui, sortKey: ASSET_SCHEMA.EXPIRED_DATE.key, align: 'center', render: a => a[ASSET_SCHEMA.EXPIRED_DATE.key] || '' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/EquipmentListView.ts b/src/views/List/EquipmentListView.ts index bafc3b6..4a0f212 100644 --- a/src/views/List/EquipmentListView.ts +++ b/src/views/List/EquipmentListView.ts @@ -1,125 +1,43 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 전산비품 자산 목록 뷰 - */ export function renderEquipmentList(container: HTMLElement) { - renderPageHeader(container, '업무지원장비'); - - const fullList = sortAssets(state.masterData.equipment); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', loc: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.CURRENT_USER.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.ASSET_MFR.ui} - ${ASSET_SCHEMA.MODEL_NAME.ui} - ${ASSET_SCHEMA.ASSET_COUNT.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME', 'CURRENT_USER', 'ASSET_MFR']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key] || '보관중'} - ${asset[ASSET_SCHEMA.CURRENT_USER.key] || '-'} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''} - ${asset[ASSET_SCHEMA.ASSET_MFR.key] || ''} - ${formatInline(asset[ASSET_SCHEMA.MODEL_NAME.key] || asset.명칭)} - ${asset[ASSET_SCHEMA.ASSET_COUNT.key] || '1'} - ${displayLoc} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.ASSET_MFR.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '업무지원장비', + dataSource: () => sortAssets(state.masterData.equipment || []), + searchKeys: ['MODEL_NAME', 'CURRENT_USER', 'ASSET_MFR'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.ASSET_MFR.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { + header: ASSET_SCHEMA.HW_STATUS.ui, + sortKey: ASSET_SCHEMA.HW_STATUS.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.HW_STATUS.key] || '보관중'}` + }, + { header: ASSET_SCHEMA.CURRENT_USER.ui, sortKey: ASSET_SCHEMA.CURRENT_USER.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '' }, + { header: ASSET_SCHEMA.ASSET_MFR.ui, sortKey: ASSET_SCHEMA.ASSET_MFR.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_MFR.key] || '' }, + { header: ASSET_SCHEMA.MODEL_NAME.ui, sortKey: ASSET_SCHEMA.MODEL_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.MODEL_NAME.key] || a.명칭 || '-') }, + { header: ASSET_SCHEMA.ASSET_COUNT.ui, sortKey: ASSET_SCHEMA.ASSET_COUNT.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_COUNT.key] || '1' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Location Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/FacilityListView.ts b/src/views/List/FacilityListView.ts index 335d917..85346ce 100644 --- a/src/views/List/FacilityListView.ts +++ b/src/views/List/FacilityListView.ts @@ -1,123 +1,42 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 시설자산 자산 목록 뷰 - */ export function renderFacilityList(container: HTMLElement) { - renderPageHeader(container, '사무가구'); - - const fullList = sortAssets(state.masterData.equipment?.filter((a: any) => a.category === '시설자산') || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.ASSET_MFR.ui} - ${ASSET_SCHEMA.MODEL_NAME.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.ASSET_COUNT.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME', 'ASSET_MFR']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key] || '보관중'} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''} - ${asset[ASSET_SCHEMA.ASSET_MFR.key] || ''} - ${formatInline(asset[ASSET_SCHEMA.MODEL_NAME.key] || '-')} - ${displayLoc} - ${asset[ASSET_SCHEMA.ASSET_COUNT.key] || '1'} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '사무가구', + dataSource: () => sortAssets(state.masterData.equipment?.filter((a: any) => a.category === '시설자산') || []), + searchKeys: ['MODEL_NAME', 'ASSET_MFR'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { + header: ASSET_SCHEMA.HW_STATUS.ui, + sortKey: ASSET_SCHEMA.HW_STATUS.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.HW_STATUS.key] || '보관중'}` + }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '' }, + { header: ASSET_SCHEMA.ASSET_MFR.ui, sortKey: ASSET_SCHEMA.ASSET_MFR.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_MFR.key] || '' }, + { header: ASSET_SCHEMA.MODEL_NAME.ui, sortKey: ASSET_SCHEMA.MODEL_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.MODEL_NAME.key] || '-') }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.ASSET_COUNT.ui, sortKey: ASSET_SCHEMA.ASSET_COUNT.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_COUNT.key] || '1' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Loc Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/GiftListView.ts b/src/views/List/GiftListView.ts index 3c63cbd..2f7d381 100644 --- a/src/views/List/GiftListView.ts +++ b/src/views/List/GiftListView.ts @@ -1,101 +1,25 @@ import { state } from '../../core/state'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 선물(내빈/외빈) 자산 목록 뷰 - */ export function renderGiftList(container: HTMLElement) { - renderPageHeader(container, '선물'); - - const fullList = sortAssets(state.masterData.equipment?.filter((a: any) => a.category === '선물') || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - 자산명 - 구매연월 - ${ASSET_SCHEMA.EXPIRED_DATE.ui} - ${ASSET_SCHEMA.ASSET_COUNT.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['PRODUCT_NAME', 'MODEL_NAME']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - tr.innerHTML = ` - ${formatInline(asset[ASSET_SCHEMA.PRODUCT_NAME.key] || asset[ASSET_SCHEMA.MODEL_NAME.key] || '-')} - ${asset[ASSET_SCHEMA.PURCHASE_DATE.key] || ''} - ${asset[ASSET_SCHEMA.EXPIRED_DATE.key] || ''} - ${asset[ASSET_SCHEMA.ASSET_COUNT.key] || '1'} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => alert('상세 정보 준비 중입니다.')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui})`, - showCorp: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '선물', + dataSource: () => sortAssets(state.masterData.equipment?.filter((a: any) => a.category === '선물') || []), + searchKeys: ['PRODUCT_NAME', 'MODEL_NAME'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui})`, + showCorp: true, + showDept: true + }, + onRowClick: () => alert('상세 정보 준비 중입니다.'), + columns: [ + { header: '자산명', sortKey: ASSET_SCHEMA.PRODUCT_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.PRODUCT_NAME.key] || a[ASSET_SCHEMA.MODEL_NAME.key] || '-') }, + { header: '구매연월', sortKey: ASSET_SCHEMA.PURCHASE_DATE.key, align: 'center', render: a => a[ASSET_SCHEMA.PURCHASE_DATE.key] || '' }, + { header: ASSET_SCHEMA.EXPIRED_DATE.ui, sortKey: ASSET_SCHEMA.EXPIRED_DATE.key, align: 'center', render: a => a[ASSET_SCHEMA.EXPIRED_DATE.key] || '' }, + { header: ASSET_SCHEMA.ASSET_COUNT.ui, sortKey: ASSET_SCHEMA.ASSET_COUNT.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_COUNT.key] || '1' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/ListFactory.ts b/src/views/List/ListFactory.ts new file mode 100644 index 0000000..6a08671 --- /dev/null +++ b/src/views/List/ListFactory.ts @@ -0,0 +1,157 @@ +import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; +import { dynamicSort, renderPageHeader } 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'; + +export interface ColumnDef { + header: string; + sortKey?: string; + width?: string; + align?: 'left' | 'center' | 'right'; + className?: string; + render: (asset: any) => string; +} + +export interface ListViewConfig { + title: string; + dataSource: () => any[]; + searchKeys: string[]; + filterOptions: { + keywordLabel: string; + showCorp?: boolean; + showDept?: boolean; + showLoc?: boolean; + showField?: boolean; + }; + columns: ColumnDef[]; + onRowClick?: (asset: any) => void; + emptyMessage?: string; + persistentSortState?: SortState; // Allow passing external sort state (like DomainListView) +} + +export function createListView(container: HTMLElement, config: ListViewConfig) { + 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: '' }; + + const filterBar = document.createElement('div'); + filterBar.className = 'search-bar'; + container.appendChild(filterBar); + + 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(tbody); + + tableWrapper.appendChild(table); + container.appendChild(tableWrapper); + + // 3. 테이블 업데이트 로직 + const updateTable = () => { + let filtered = applyCommonFilters(fullList, currentFilters, config.searchKeys as any[]); + + if (sortState.key) { + filtered = dynamicSort(filtered, sortState.key, sortState.direction); + } + + tbody.innerHTML = ''; + if (filtered.length === 0) { + const emptyMsg = config.emptyMessage || UI_TEXT.MESSAGES.NO_DATA; + tbody.innerHTML = `${emptyMsg}`; + return; + } + + 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); + }); + + 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; + } + updateTable(); + }); + + // 모든 가능한 아이콘 로드 (안전하게) + createIcons({ icons: { RefreshCcw, Plus, Edit2, Trash2, Users, Cloud, CreditCard, DollarSign, Paperclip } }); + }; + + // 4. 필터 바 렌더링 + renderFilterBar(filterBar, { + ...config.filterOptions, + onFilterChange: (filters) => { + currentFilters = { ...currentFilters, ...filters }; + updateTable(); + } + }); + + // 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 uniqueValues = Array.from(new Set(fullList.map(getVal))).filter(Boolean).sort(); + uniqueValues.forEach(val => { + const opt = document.createElement('option'); + opt.value = String(val); + opt.textContent = String(val); + select.appendChild(opt); + }); + } + }; + + if (config.filterOptions.showLoc) populateSelect('#filter-loc', ASSET_SCHEMA.LOCATION.key); + if (config.filterOptions.showDept) populateSelect('#filter-dept', ASSET_SCHEMA.CURRENT_DEPT.key); + if (config.filterOptions.showCorp) populateSelect('#filter-corp', ASSET_SCHEMA.PURCHASE_CORP.key); + + // 6. 초기 렌더링 + updateTable(); +} diff --git a/src/views/List/MobileListView.ts b/src/views/List/MobileListView.ts index 7f04760..d0a76b9 100644 --- a/src/views/List/MobileListView.ts +++ b/src/views/List/MobileListView.ts @@ -1,111 +1,38 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, Paperclip, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 모바일 자산 목록 뷰 (레거시 지원용) - */ export function renderMobileList(container: HTMLElement) { - renderPageHeader(container, 'PC'); - - const fullList = sortAssets(state.masterData.mobile || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.PURCHASE_CORP.ui} - ${ASSET_SCHEMA.MODEL_NAME.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.PURCHASE_DATE.ui} - ${ASSET_SCHEMA.PURCHASE_AMOUNT.ui} - 담당자 - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset: any, idx: number) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const mainManager = asset[ASSET_SCHEMA.MANAGER_MAIN.key] || ''; - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key] || '운영중'} - ${asset[ASSET_SCHEMA.PURCHASE_CORP.key] || ''} - ${asset[ASSET_SCHEMA.MODEL_NAME.key] || ''} - ${(asset[ASSET_SCHEMA.LOCATION.key] || '') + (asset[ASSET_SCHEMA.LOC_DETAIL.key] ? `(${asset[ASSET_SCHEMA.LOC_DETAIL.key]})` : (asset[ASSET_SCHEMA.LOCATION.key] ? '' : '-'))} - ${asset[ASSET_SCHEMA.PURCHASE_DATE.key] || ''} - ${Number(asset[ASSET_SCHEMA.PURCHASE_AMOUNT.key]||0).toLocaleString()} - ${mainManager} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { Paperclip, RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui})`, - showCorp: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: 'PC', // Legacy support + dataSource: () => sortAssets(state.masterData.mobile || []), + searchKeys: ['MODEL_NAME'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui})`, + showCorp: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { header: ASSET_SCHEMA.HW_STATUS.ui, sortKey: ASSET_SCHEMA.HW_STATUS.key, align: 'center', render: a => a[ASSET_SCHEMA.HW_STATUS.key] || '운영중' }, + { header: ASSET_SCHEMA.PURCHASE_CORP.ui, sortKey: ASSET_SCHEMA.PURCHASE_CORP.key, align: 'center', render: a => a[ASSET_SCHEMA.PURCHASE_CORP.key] || '' }, + { header: ASSET_SCHEMA.MODEL_NAME.ui, sortKey: ASSET_SCHEMA.MODEL_NAME.key, render: a => a[ASSET_SCHEMA.MODEL_NAME.key] || '' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.PURCHASE_DATE.ui, sortKey: ASSET_SCHEMA.PURCHASE_DATE.key, align: 'center', render: a => a[ASSET_SCHEMA.PURCHASE_DATE.key] || '' }, + { header: ASSET_SCHEMA.PURCHASE_AMOUNT.ui, sortKey: ASSET_SCHEMA.PURCHASE_AMOUNT.key, align: 'right', render: a => Number(a[ASSET_SCHEMA.PURCHASE_AMOUNT.key]||0).toLocaleString() }, + { header: '담당자', align: 'center', render: a => a[ASSET_SCHEMA.MANAGER_MAIN.key] || '' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/NetworkListView.ts b/src/views/List/NetworkListView.ts index dcab486..21d7417 100644 --- a/src/views/List/NetworkListView.ts +++ b/src/views/List/NetworkListView.ts @@ -1,125 +1,43 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 네트워크 자산 목록 뷰 - */ export function renderNetworkList(container: HTMLElement) { - renderPageHeader(container, '네트워크'); - - const fullList = sortAssets(state.masterData.network || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', loc: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.CURRENT_USER.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.ASSET_MFR.ui} - ${ASSET_SCHEMA.MODEL_NAME.ui} - ${ASSET_SCHEMA.ASSET_COUNT.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME', 'CURRENT_USER', 'ASSET_MFR']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key] || '운영중'} - ${asset[ASSET_SCHEMA.CURRENT_USER.key] || '-'} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''} - ${asset[ASSET_SCHEMA.ASSET_MFR.key] || ''} - ${formatInline(asset[ASSET_SCHEMA.MODEL_NAME.key] || '-')} - ${asset[ASSET_SCHEMA.ASSET_COUNT.key] || '1'} - ${displayLoc} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.ASSET_MFR.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '네트워크', + dataSource: () => sortAssets(state.masterData.network || []), + searchKeys: ['MODEL_NAME', 'CURRENT_USER', 'ASSET_MFR'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.ASSET_MFR.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { + header: ASSET_SCHEMA.HW_STATUS.ui, + sortKey: ASSET_SCHEMA.HW_STATUS.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.HW_STATUS.key] || '운영중'}` + }, + { header: ASSET_SCHEMA.CURRENT_USER.ui, sortKey: ASSET_SCHEMA.CURRENT_USER.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '' }, + { header: ASSET_SCHEMA.ASSET_MFR.ui, sortKey: ASSET_SCHEMA.ASSET_MFR.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_MFR.key] || '' }, + { header: ASSET_SCHEMA.MODEL_NAME.ui, sortKey: ASSET_SCHEMA.MODEL_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.MODEL_NAME.key] || '-') }, + { header: ASSET_SCHEMA.ASSET_COUNT.ui, sortKey: ASSET_SCHEMA.ASSET_COUNT.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_COUNT.key] || '1' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Location Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/PcListView.ts b/src/views/List/PcListView.ts index 552973f..a7b0990 100644 --- a/src/views/List/PcListView.ts +++ b/src/views/List/PcListView.ts @@ -1,127 +1,39 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, Paperclip, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; export function renderPcList(container: HTMLElement) { - renderPageHeader(container, 'PC'); - - // asset_pc 데이터 중 '서버PC' 유형은 제외하고 렌더링 (서버 리스트에서 보여줌) - const fullList = sortAssets((state.masterData.pc || []).filter((a: any) => a.asset_type !== '서버PC')); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.CURRENT_USER.ui} - ${ASSET_SCHEMA.CPU.ui} - ${ASSET_SCHEMA.MAINBOARD.ui} - ${ASSET_SCHEMA.RAM.ui} - ${ASSET_SCHEMA.GPU.ui} - SSD1 - SSD2 - HDD1 - HDD2 - HDD3 - HDD4 - ${ASSET_SCHEMA.MAC_ADDR.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['CURRENT_DEPT', 'CURRENT_USER', 'MODEL_NAME', 'MAC_ADDR', 'MANAGER_MAIN']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.CURRENT_USER.key]||'-'} - ${asset[ASSET_SCHEMA.CPU.key]||''} - ${asset[ASSET_SCHEMA.MAINBOARD.key]||'-'} - ${asset[ASSET_SCHEMA.RAM.key]||''} - ${asset[ASSET_SCHEMA.GPU.key]||'-'} - ${asset[ASSET_SCHEMA.SSD1.key]||'-'} - ${asset[ASSET_SCHEMA.SSD2.key]||'-'} - ${asset[ASSET_SCHEMA.HDD1.key]||'-'} - ${asset[ASSET_SCHEMA.HDD2.key]||'-'} - ${asset[ASSET_SCHEMA.HDD3.key]||'-'} - ${asset[ASSET_SCHEMA.HDD4.key]||'-'} - ${asset[ASSET_SCHEMA.MAC_ADDR.key]||'-'} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - createIcons({ icons: { Paperclip, RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.MANAGER_MAIN.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: 'PC', + dataSource: () => sortAssets((state.masterData.pc || []).filter((a: any) => a.asset_type !== '서버PC')), + searchKeys: ['CURRENT_DEPT', 'CURRENT_USER', 'MODEL_NAME', 'MAC_ADDR', 'MANAGER_MAIN'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.MANAGER_MAIN.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { header: ASSET_SCHEMA.CURRENT_USER.ui, sortKey: ASSET_SCHEMA.CURRENT_USER.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' }, + { header: ASSET_SCHEMA.CPU.ui, sortKey: ASSET_SCHEMA.CPU.key, align: 'center', render: a => a[ASSET_SCHEMA.CPU.key] || '' }, + { header: ASSET_SCHEMA.MAINBOARD.ui, sortKey: ASSET_SCHEMA.MAINBOARD.key, align: 'center', render: a => a[ASSET_SCHEMA.MAINBOARD.key] || '-' }, + { header: ASSET_SCHEMA.RAM.ui, sortKey: ASSET_SCHEMA.RAM.key, align: 'center', render: a => a[ASSET_SCHEMA.RAM.key] || '' }, + { header: ASSET_SCHEMA.GPU.ui, sortKey: ASSET_SCHEMA.GPU.key, align: 'center', render: a => a[ASSET_SCHEMA.GPU.key] || '-' }, + { header: 'SSD1', sortKey: ASSET_SCHEMA.SSD1.key, align: 'center', render: a => a[ASSET_SCHEMA.SSD1.key] || '-' }, + { header: 'SSD2', sortKey: ASSET_SCHEMA.SSD2.key, align: 'center', render: a => a[ASSET_SCHEMA.SSD2.key] || '-' }, + { header: 'HDD1', sortKey: ASSET_SCHEMA.HDD1.key, align: 'center', render: a => a[ASSET_SCHEMA.HDD1.key] || '-' }, + { header: 'HDD2', sortKey: ASSET_SCHEMA.HDD2.key, align: 'center', render: a => a[ASSET_SCHEMA.HDD2.key] || '-' }, + { header: 'HDD3', sortKey: ASSET_SCHEMA.HDD3.key, align: 'center', render: a => a[ASSET_SCHEMA.HDD3.key] || '-' }, + { header: 'HDD4', sortKey: ASSET_SCHEMA.HDD4.key, align: 'center', render: a => a[ASSET_SCHEMA.HDD4.key] || '-' }, + { + header: ASSET_SCHEMA.MAC_ADDR.ui, + sortKey: ASSET_SCHEMA.MAC_ADDR.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.MAC_ADDR.key] || '-'}` + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Loc Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key] || a['현사용부서'] || a['현사용조직']))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } diff --git a/src/views/List/PcPartListView.ts b/src/views/List/PcPartListView.ts index 718accd..49ed92d 100644 --- a/src/views/List/PcPartListView.ts +++ b/src/views/List/PcPartListView.ts @@ -1,127 +1,44 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * PC부품 자산 목록 뷰 - */ export function renderPcPartList(container: HTMLElement) { - renderPageHeader(container, 'PC부품'); - - const fullList = sortAssets(state.masterData.equipment?.filter((a: any) => a.category === 'PC부품') || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', loc: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.ASSET_MFR.ui} - ${ASSET_SCHEMA.MODEL_NAME.ui} - ${ASSET_SCHEMA.VOLUME.ui} - ${ASSET_SCHEMA.MONITOR_INCH.ui} - ${ASSET_SCHEMA.ASSET_COUNT.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME', 'ASSET_TYPE']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key] || '보관중'} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''} - ${asset[ASSET_SCHEMA.ASSET_MFR.key] || ''} - ${formatInline(asset[ASSET_SCHEMA.MODEL_NAME.key] || '-')} - ${asset[ASSET_SCHEMA.VOLUME.key] || '-'} - ${asset[ASSET_SCHEMA.MONITOR_INCH.key] || '-'} - ${asset[ASSET_SCHEMA.ASSET_COUNT.key] || '1'} - ${displayLoc} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: 'PC부품', + dataSource: () => sortAssets(state.masterData.equipment?.filter((a: any) => a.category === 'PC부품') || []), + searchKeys: ['MODEL_NAME', 'ASSET_TYPE'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { + header: ASSET_SCHEMA.HW_STATUS.ui, + sortKey: ASSET_SCHEMA.HW_STATUS.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.HW_STATUS.key] || '보관중'}` + }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '' }, + { header: ASSET_SCHEMA.ASSET_MFR.ui, sortKey: ASSET_SCHEMA.ASSET_MFR.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_MFR.key] || '' }, + { header: ASSET_SCHEMA.MODEL_NAME.ui, sortKey: ASSET_SCHEMA.MODEL_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.MODEL_NAME.key] || '-') }, + { header: ASSET_SCHEMA.VOLUME.ui, sortKey: ASSET_SCHEMA.VOLUME.key, align: 'center', render: a => a[ASSET_SCHEMA.VOLUME.key] || '-' }, + { header: ASSET_SCHEMA.MONITOR_INCH.ui, sortKey: ASSET_SCHEMA.MONITOR_INCH.key, align: 'center', render: a => a[ASSET_SCHEMA.MONITOR_INCH.key] || '-' }, + { header: ASSET_SCHEMA.ASSET_COUNT.ui, sortKey: ASSET_SCHEMA.ASSET_COUNT.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_COUNT.key] || '1' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Location Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/ServerListView.ts b/src/views/List/ServerListView.ts index f90de9b..9ece689 100644 --- a/src/views/List/ServerListView.ts +++ b/src/views/List/ServerListView.ts @@ -1,125 +1,44 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; export function renderServerList(container: HTMLElement) { - renderPageHeader(container, '서버'); - - // asset_server 데이터와 asset_pc 데이터 중 '서버PC' 유형만 추출하여 병합 - const serverList = state.masterData.server || []; - const serverPcList = (state.masterData.pc || []).filter((a: any) => a.asset_type === '서버PC'); - const fullList = sortAssets([...serverList, ...serverPcList]); - - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', loc: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.CURRENT_DEPT.ui} - ${ASSET_SCHEMA.ASSET_PURPOSE.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - 모델/메인보드 - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['CURRENT_DEPT', 'MODEL_NAME', 'ASSET_PURPOSE']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - const modelOrMainboard = asset[ASSET_SCHEMA.MODEL_NAME.key] - || asset[ASSET_SCHEMA.ASSET_NAME.key] - || asset[ASSET_SCHEMA.MAINBOARD.key] - || '-'; - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.CURRENT_DEPT.key]||'-'} - ${formatInline(asset[ASSET_SCHEMA.ASSET_PURPOSE.key]||'-')} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key]||'-'} - ${formatInline(modelOrMainboard)} - ${displayLoc} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.CURRENT_DEPT.ui}/${ASSET_SCHEMA.MODEL_NAME.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '서버', + dataSource: () => { + const serverList = state.masterData.server || []; + const serverPcList = (state.masterData.pc || []).filter((a: any) => a.asset_type === '서버PC'); + return sortAssets([...serverList, ...serverPcList]); + }, + searchKeys: ['CURRENT_DEPT', 'MODEL_NAME', 'ASSET_PURPOSE'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.CURRENT_DEPT.ui}/${ASSET_SCHEMA.MODEL_NAME.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { header: ASSET_SCHEMA.CURRENT_DEPT.ui, sortKey: ASSET_SCHEMA.CURRENT_DEPT.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_DEPT.key] || '-' }, + { header: ASSET_SCHEMA.ASSET_PURPOSE.ui, sortKey: ASSET_SCHEMA.ASSET_PURPOSE.key, width: '15%', render: a => formatInline(a[ASSET_SCHEMA.ASSET_PURPOSE.key] || '-') }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', width: '10%', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '-' }, + { + header: '모델/메인보드', + width: '15%', + render: a => formatInline(a[ASSET_SCHEMA.MODEL_NAME.key] || a[ASSET_SCHEMA.ASSET_NAME.key] || a[ASSET_SCHEMA.MAINBOARD.key] || '-') + }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, width: '35%', className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Location Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = dept; - opt.textContent = dept; - deptSelect.appendChild(opt); - }); - } - - updateTable(); } diff --git a/src/views/List/SpaceInfoListView.ts b/src/views/List/SpaceInfoListView.ts index 58225b0..39759c8 100644 --- a/src/views/List/SpaceInfoListView.ts +++ b/src/views/List/SpaceInfoListView.ts @@ -1,121 +1,41 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 공간정보장비 자산 목록 뷰 - */ export function renderSpaceInfoList(container: HTMLElement) { - renderPageHeader(container, '공간정보장비'); - - const fullList = sortAssets(state.masterData.equipment?.filter((a: any) => a.category === '공간정보장비') || []); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', loc: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.CURRENT_USER.ui} - ${ASSET_SCHEMA.ASSET_NAME.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME', 'PRODUCT_NAME', 'CURRENT_USER']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key] || '운영중'} - ${asset[ASSET_SCHEMA.CURRENT_USER.key] || '-'} - ${formatInline(asset[ASSET_SCHEMA.PRODUCT_NAME.key] || asset[ASSET_SCHEMA.MODEL_NAME.key] || asset[ASSET_SCHEMA.ASSET_NAME.key] || '-')} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key] || ''} - ${displayLoc} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '공간정보장비', + dataSource: () => sortAssets(state.masterData.equipment?.filter((a: any) => a.category === '공간정보장비') || []), + searchKeys: ['MODEL_NAME', 'PRODUCT_NAME', 'CURRENT_USER'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { + header: ASSET_SCHEMA.HW_STATUS.ui, + sortKey: ASSET_SCHEMA.HW_STATUS.key, + align: 'center', + render: a => `${a[ASSET_SCHEMA.HW_STATUS.key] || '운영중'}` + }, + { header: ASSET_SCHEMA.CURRENT_USER.ui, sortKey: ASSET_SCHEMA.CURRENT_USER.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' }, + { header: ASSET_SCHEMA.ASSET_NAME.ui, sortKey: ASSET_SCHEMA.ASSET_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.PRODUCT_NAME.key] || a[ASSET_SCHEMA.MODEL_NAME.key] || a[ASSET_SCHEMA.ASSET_NAME.key] || '-') }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Location Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/List/StorageListView.ts b/src/views/List/StorageListView.ts index b3e1647..d804f2f 100644 --- a/src/views/List/StorageListView.ts +++ b/src/views/List/StorageListView.ts @@ -1,123 +1,38 @@ import { state } from '../../core/state'; import { openHwModal } from '../../components/Modal/HWModal'; -import { formatInline, sortAssets, dynamicSort, renderPageHeader } from '../../core/utils'; -import { ASSET_SCHEMA, UI_TEXT } from '../../core/schema'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, RefreshCcw, Plus } from 'lucide'; +import { sortAssets, formatInline } from '../../core/utils'; +import { ASSET_SCHEMA } from '../../core/schema'; +import { createListView } from './ListFactory'; -/** - * 스토리지 자산 목록 뷰 - */ export function renderStorageList(container: HTMLElement) { - renderPageHeader(container, '스토리지'); - - const fullList = sortAssets(state.masterData.storage); - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', loc: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - table.innerHTML = ` - - - ${ASSET_SCHEMA.HW_STATUS.ui} - ${ASSET_SCHEMA.CURRENT_USER.ui} - ${ASSET_SCHEMA.ASSET_TYPE.ui} - ${ASSET_SCHEMA.VOLUME.ui} - ${ASSET_SCHEMA.MODEL_NAME.ui} - ${ASSET_SCHEMA.SERIAL_NUM.ui} - ${ASSET_SCHEMA.LOCATION.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['MODEL_NAME', 'CURRENT_USER', 'SERIAL_NUM']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `${UI_TEXT.MESSAGES.NO_DATA}`; - return; - } - - filtered.forEach((asset, idx) => { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - const loc = asset[ASSET_SCHEMA.LOCATION.key] || ''; - const detail = asset[ASSET_SCHEMA.LOC_DETAIL.key] || ''; - const displayLoc = detail ? `${loc}(${detail})` : (loc || '-'); - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.HW_STATUS.key]||'-'} - ${asset[ASSET_SCHEMA.CURRENT_USER.key]||'-'} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key]||'-'} - ${asset[ASSET_SCHEMA.VOLUME.key]||'-'} - ${formatInline(asset[ASSET_SCHEMA.MODEL_NAME.key]||asset[ASSET_SCHEMA.ASSET_NAME.key]||'-')} - ${asset[ASSET_SCHEMA.SERIAL_NUM.key]||'-'} - ${displayLoc} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openHwModal(asset, 'view')); - tbody.appendChild(tr); - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - createIcons({ icons: { RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`, - showLoc: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: '스토리지', + dataSource: () => sortAssets(state.masterData.storage || []), + searchKeys: ['MODEL_NAME', 'CURRENT_USER', 'SERIAL_NUM'], + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.MODEL_NAME.ui}/${ASSET_SCHEMA.CURRENT_USER.ui})`, + showLoc: true, + showDept: true + }, + onRowClick: (asset) => openHwModal(asset, 'view'), + columns: [ + { header: ASSET_SCHEMA.HW_STATUS.ui, sortKey: ASSET_SCHEMA.HW_STATUS.key, align: 'center', render: a => a[ASSET_SCHEMA.HW_STATUS.key] || '-' }, + { header: ASSET_SCHEMA.CURRENT_USER.ui, sortKey: ASSET_SCHEMA.CURRENT_USER.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' }, + { header: ASSET_SCHEMA.ASSET_TYPE.ui, sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '-' }, + { header: ASSET_SCHEMA.VOLUME.ui, sortKey: ASSET_SCHEMA.VOLUME.key, align: 'center', render: a => a[ASSET_SCHEMA.VOLUME.key] || '-' }, + { header: ASSET_SCHEMA.MODEL_NAME.ui, sortKey: ASSET_SCHEMA.MODEL_NAME.key, render: a => formatInline(a[ASSET_SCHEMA.MODEL_NAME.key] || a[ASSET_SCHEMA.ASSET_NAME.key] || '-') }, + { header: ASSET_SCHEMA.SERIAL_NUM.ui, sortKey: ASSET_SCHEMA.SERIAL_NUM.key, align: 'center', render: a => a[ASSET_SCHEMA.SERIAL_NUM.key] || '-' }, + { + header: ASSET_SCHEMA.LOCATION.ui, + sortKey: ASSET_SCHEMA.LOCATION.key, + align: 'center', + render: a => { + const loc = a[ASSET_SCHEMA.LOCATION.key] || ''; + const detail = a[ASSET_SCHEMA.LOC_DETAIL.key] || ''; + return detail ? `${loc}(${detail})` : (loc || '-'); + } + }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Location Options - const locSelect = container.querySelector('#filter-loc') as HTMLSelectElement; - if (locSelect) { - const locations = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.LOCATION.key]))).filter(Boolean).sort(); - locations.forEach(loc => { - const opt = document.createElement('option'); - opt.value = String(loc); - opt.textContent = String(loc); - locSelect.appendChild(opt); - }); - } - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } diff --git a/src/views/List/SwListView.ts b/src/views/List/SwListView.ts index 2bdfe01..13906a1 100644 --- a/src/views/List/SwListView.ts +++ b/src/views/List/SwListView.ts @@ -1,145 +1,42 @@ import { state } from '../../core/state'; import { openSwModal } from '../../components/Modal/SWModal'; -import { sortAssets, dynamicSort, formatInline, getActionButtonsHTML, renderPageHeader } from '../../core/utils'; -import { setupTableSorting, SortState } from '../../core/tableHandler'; +import { sortAssets, formatInline } from '../../core/utils'; import { ASSET_SCHEMA } from '../../core/schema'; -import { renderFilterBar, applyCommonFilters } from '../../core/filterHandler'; -import { createIcons, Edit2, Users, RefreshCcw, Plus } from 'lucide'; +import { createListView } from './ListFactory'; export function renderSwList(container: HTMLElement) { const isInternal = state.activeSubTab === '내부'; - renderPageHeader(container, isInternal ? '내부' : '외부'); - const fullList = sortAssets(isInternal ? state.masterData.swInternal : state.masterData.swExternal); - - let sortState: SortState = { key: '', direction: 'asc' }; - let currentFilters = { keyword: '', corp: '', dept: '', field: '' }; - - const filterBar = document.createElement('div'); - filterBar.className = 'search-bar'; - container.appendChild(filterBar); - - const tableWrapper = document.createElement('div'); - tableWrapper.className = 'table-container'; - const table = document.createElement('table'); - - if (isInternal) { - table.innerHTML = ` - - - ${ASSET_SCHEMA.SW_FIELD.ui} - ${ASSET_SCHEMA.DEV_OBJ.ui} - ${ASSET_SCHEMA.SW_STATUS.ui} - ${ASSET_SCHEMA.SW_TYPE.ui} - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - } else { - table.innerHTML = ` - - - 자산명 - 유형 - ${ASSET_SCHEMA.SW_STATUS.ui} - ${ASSET_SCHEMA.SW_FIELD.ui} - ${ASSET_SCHEMA.CURRENT_DEPT.ui} - ${ASSET_SCHEMA.CURRENT_USER.ui} - ${ASSET_SCHEMA.PREV_USER.ui} - 구매연월 - 시작일 - 만료일 - ${ASSET_SCHEMA.MEMO.ui} - - - - `; - } - - tableWrapper.appendChild(table); - container.appendChild(tableWrapper); - const tbody = table.querySelector('tbody')!; - - const updateTable = () => { - let filtered = applyCommonFilters(fullList, currentFilters, ['PRODUCT_NAME', 'CURRENT_USER', 'CURRENT_DEPT']); - - if (sortState.key) { - filtered = dynamicSort(filtered, sortState.key, sortState.direction); - } - - tbody.innerHTML = ''; - if (filtered.length === 0) { - tbody.innerHTML = `검색 결과가 없습니다.`; - return; - } - - filtered.forEach((asset, idx) => { - if (isInternal) { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.SW_FIELD.key]||''} - ${asset[ASSET_SCHEMA.DEV_OBJ.key]||''} - ${asset[ASSET_SCHEMA.SW_STATUS.key]||'보유중'} - ${asset[ASSET_SCHEMA.SW_TYPE.key]||'내부'} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openSwModal(asset, 'view')); - tbody.appendChild(tr); - } else { - const tr = document.createElement('tr'); - tr.style.cursor = 'pointer'; - - tr.innerHTML = ` - ${asset[ASSET_SCHEMA.PRODUCT_NAME.key]||''} - ${asset[ASSET_SCHEMA.ASSET_TYPE.key]||'외부'} - ${asset[ASSET_SCHEMA.SW_STATUS.key]||'사용중'} - ${asset[ASSET_SCHEMA.SW_FIELD.key]||''} - ${asset[ASSET_SCHEMA.CURRENT_DEPT.key]||''} - ${asset[ASSET_SCHEMA.CURRENT_USER.key]||'-'} - ${asset[ASSET_SCHEMA.PREV_USER.key]||'-'} - ${asset[ASSET_SCHEMA.PURCHASE_DATE.key]||''} - ${asset[ASSET_SCHEMA.PURCHASE_DATE.key]||''} - ${asset[ASSET_SCHEMA.EXPIRED_DATE.key]||''} - ${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')} - `; - tr.addEventListener('click', () => openSwModal(asset, 'view')); - tbody.appendChild(tr); - } - }); - - setupTableSorting(table, sortState, (key, dir) => { - sortState = { key, direction: dir }; - updateTable(); - }); - - createIcons({ icons: { Edit2, Users, RefreshCcw, Plus } }); - }; - - renderFilterBar(filterBar, { - keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.CURRENT_DEPT.ui})`, - showField: true, - showCorp: true, - showDept: true, - onFilterChange: (filters) => { - currentFilters = filters; - updateTable(); - } + createListView(container, { + title: isInternal ? '내부' : '외부', + dataSource: () => sortAssets(isInternal ? state.masterData.swInternal : state.masterData.swExternal), + searchKeys: ['PRODUCT_NAME', 'CURRENT_USER', 'CURRENT_DEPT'], + emptyMessage: '검색 결과가 없습니다.', + filterOptions: { + keywordLabel: `통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.CURRENT_DEPT.ui})`, + showField: true, + showCorp: true, + showDept: true + }, + onRowClick: (asset) => openSwModal(asset, 'view'), + columns: isInternal ? [ + { header: ASSET_SCHEMA.SW_FIELD.ui, sortKey: ASSET_SCHEMA.SW_FIELD.key, align: 'center', render: a => a[ASSET_SCHEMA.SW_FIELD.key] || '' }, + { header: ASSET_SCHEMA.DEV_OBJ.ui, sortKey: ASSET_SCHEMA.DEV_OBJ.key, align: 'center', render: a => a[ASSET_SCHEMA.DEV_OBJ.key] || '' }, + { header: ASSET_SCHEMA.SW_STATUS.ui, sortKey: ASSET_SCHEMA.SW_STATUS.key, align: 'center', render: a => a[ASSET_SCHEMA.SW_STATUS.key] || '보유중' }, + { header: ASSET_SCHEMA.SW_TYPE.ui, sortKey: ASSET_SCHEMA.SW_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.SW_TYPE.key] || '내부' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] : [ + { header: '자산명', sortKey: ASSET_SCHEMA.PRODUCT_NAME.key, render: a => a[ASSET_SCHEMA.PRODUCT_NAME.key] || '' }, + { header: '유형', sortKey: ASSET_SCHEMA.ASSET_TYPE.key, align: 'center', render: a => a[ASSET_SCHEMA.ASSET_TYPE.key] || '외부' }, + { header: ASSET_SCHEMA.SW_STATUS.ui, sortKey: ASSET_SCHEMA.SW_STATUS.key, align: 'center', render: a => a[ASSET_SCHEMA.SW_STATUS.key] || '사용중' }, + { header: ASSET_SCHEMA.SW_FIELD.ui, sortKey: ASSET_SCHEMA.SW_FIELD.key, align: 'center', render: a => a[ASSET_SCHEMA.SW_FIELD.key] || '' }, + { header: ASSET_SCHEMA.CURRENT_DEPT.ui, sortKey: ASSET_SCHEMA.CURRENT_DEPT.key, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_DEPT.key] || '' }, + { header: ASSET_SCHEMA.CURRENT_USER.ui, align: 'center', render: a => a[ASSET_SCHEMA.CURRENT_USER.key] || '-' }, + { header: ASSET_SCHEMA.PREV_USER.ui, align: 'center', render: a => a[ASSET_SCHEMA.PREV_USER.key] || '-' }, + { header: '구매연월', sortKey: ASSET_SCHEMA.PURCHASE_DATE.key, align: 'center', render: a => a[ASSET_SCHEMA.PURCHASE_DATE.key] || '' }, + { header: '시작일', align: 'center', render: a => a[ASSET_SCHEMA.PURCHASE_DATE.key] || '' }, + { header: '만료일', sortKey: ASSET_SCHEMA.EXPIRED_DATE.key, align: 'center', render: a => a[ASSET_SCHEMA.EXPIRED_DATE.key] || '' }, + { header: ASSET_SCHEMA.MEMO.ui, sortKey: ASSET_SCHEMA.MEMO.key, className: 'col-memo', render: a => formatInline(a[ASSET_SCHEMA.MEMO.key] || '-') } + ] }); - - // Populate Dept Options - const deptSelect = container.querySelector('#filter-dept') as HTMLSelectElement; - if (deptSelect) { - const orgUnits = Array.from(new Set(fullList.map(a => a[ASSET_SCHEMA.CURRENT_DEPT.key]))).filter(Boolean).sort(); - orgUnits.forEach(dept => { - const opt = document.createElement('option'); - opt.value = String(dept); - opt.textContent = String(dept); - deptSelect.appendChild(opt); - }); - } - - updateTable(); } - diff --git a/src/views/SW_Table.ts b/src/views/SW_Table.ts index cc15dfc..a02684a 100644 --- a/src/views/SW_Table.ts +++ b/src/views/SW_Table.ts @@ -13,7 +13,6 @@ import { renderSpaceInfoList } from './List/SpaceInfoListView'; import { renderGiftList } from './List/GiftListView'; import { renderFacilityList } from './List/FacilityListView'; import { renderCostList } from './List/CostListView'; -import { renderUserList } from './List/UserListView'; import { createIcons, Plus, X, LayoutDashboard, Monitor, Server, Database, Laptop, CalendarClock, Key, Cpu, Layers, Users, Paperclip, Edit2, RefreshCcw } from 'lucide'; /**