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';
/**