Files
ITAM-test/src/views/List/SwListView.ts
Taehoon d34ebb8500 feat: restructure navigation, customize list columns, and move action buttons to search bar
1. Restructured navigation hierarchy (Hardware, Software, Ops Support, etc.).
2. Customized table columns for all asset categories according to new specs.
3. Moved Template/Upload/Export/Add buttons to search bar with layout optimization.
4. Hidden Asset Code and Previous User from list views (Modal only).
5. Added Current/Previous User and detailed PC spec fields (GPU, HDD3/4).
2026-05-20 14:34:07 +09:00

174 lines
8.4 KiB
TypeScript

import { state } from '../../core/state';
import { openSwModal } from '../../components/Modal/SWModal';
import { openSwUserModal } from '../../components/Modal/SWUserModal';
import { sortAssets, dynamicSort, formatPrice, getActionButtonsHTML } from '../../core/utils';
import { setupTableSorting, SortState } from '../../core/tableHandler';
import { ASSET_SCHEMA } from '../../core/schema';
import { CORP_LIST } from '../../components/Modal/SharedData';
import { generateOptionsHTML } from '../../components/Modal/ModalUtils';
import { createIcons, Edit2, Users, RefreshCcw, Download, Upload, FileSpreadsheet, Plus } from 'lucide';
export function renderSwList(container: HTMLElement) {
const isInternal = state.activeSubTab === '내부';
const fullList = sortAssets(isInternal ? state.masterData.swInternal : state.masterData.swExternal);
let sortState: SortState = { key: '', direction: 'asc' };
const filterBar = document.createElement('div');
filterBar.className = 'search-bar';
filterBar.innerHTML = `
<div class="search-item flex-1">
<label>통합 검색 (${ASSET_SCHEMA.PRODUCT_NAME.ui}/${ASSET_SCHEMA.CURRENT_DEPT.ui})</label>
<input type="text" id="filter-keyword" placeholder="검색어를 입력하세요..." autocomplete="off">
</div>
<div class="search-item">
<label>${ASSET_SCHEMA.SW_FIELD.ui}</label>
<select id="filter-field">
<option value="">전체 분야</option>
<option value="업무공통">업무공통</option>
<option value="개발S/W">개발S/W</option>
<option value="디자인">디자인</option>
<option value="설계S/W">설계S/W</option>
</select>
</div>
<div class="search-item">
<label>${ASSET_SCHEMA.PURCHASE_CORP.ui}</label>
<select id="filter-corp">${generateOptionsHTML(CORP_LIST, '', true)}</select>
</div>
<button id="btn-reset-filters" class="btn btn-outline btn-reset">
<i data-lucide="refresh-ccw"></i> 필터 초기화
</button>
${getActionButtonsHTML()}
`;
container.appendChild(filterBar);
const tableWrapper = document.createElement('div');
tableWrapper.className = 'table-container';
const table = document.createElement('table');
if (isInternal) {
table.innerHTML = `
<thead>
<tr>
<th style="text-align:center; width: 50px;">No.</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.SW_FIELD.key}">${ASSET_SCHEMA.SW_FIELD.ui}</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.DEV_OBJ.key}">${ASSET_SCHEMA.DEV_OBJ.ui}</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.SW_STATUS.key}">${ASSET_SCHEMA.SW_STATUS.ui}</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.SW_TYPE.key}">${ASSET_SCHEMA.SW_TYPE.ui}</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.MEMO.key}">${ASSET_SCHEMA.MEMO.ui}</th>
</tr>
</thead>
<tbody id="dynamic-tbody"></tbody>
`;
} else {
table.innerHTML = `
<thead>
<tr>
<th style="text-align:center; width: 50px;">No.</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.PRODUCT_NAME.key}">자산명</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.ASSET_TYPE.key}">유형</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.SW_STATUS.key}">${ASSET_SCHEMA.SW_STATUS.ui}</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.SW_FIELD.key}">${ASSET_SCHEMA.SW_FIELD.ui}</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.CURRENT_DEPT.key}">${ASSET_SCHEMA.CURRENT_DEPT.ui}</th>
<th style="text-align:center;">현 사용자</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.PURCHASE_DATE.key}">구매연월</th>
<th style="text-align:center;">시작일</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.EXPIRY_DATE.key}">만료일</th>
<th style="text-align:center;" data-sort="${ASSET_SCHEMA.MEMO.key}">${ASSET_SCHEMA.MEMO.ui}</th>
</tr>
</thead>
<tbody id="dynamic-tbody"></tbody>
`;
}
tableWrapper.appendChild(table);
container.appendChild(tableWrapper);
const tbody = table.querySelector('tbody')!;
const updateTable = () => {
const keywordInput = document.getElementById('filter-keyword') as HTMLInputElement;
const fieldSelect = document.getElementById('filter-field') as HTMLSelectElement;
const corpSelect = document.getElementById('filter-corp') as HTMLSelectElement;
const keyword = keywordInput ? keywordInput.value.toLowerCase().trim() : '';
const field = fieldSelect ? fieldSelect.value : '';
const corp = corpSelect ? corpSelect.value : '';
let filtered = fullList.filter(asset => {
const matchKeyword = !keyword ||
(asset[ASSET_SCHEMA.PRODUCT_NAME.key] || '').toLowerCase().includes(keyword) ||
(asset[ASSET_SCHEMA.CURRENT_DEPT.key] || '').toLowerCase().includes(keyword);
const matchField = !field || asset[ASSET_SCHEMA.SW_FIELD.key] === field;
const matchCorp = !corp || asset[ASSET_SCHEMA.PURCHASE_CORP.key] === corp;
return matchKeyword && matchField && matchCorp;
});
if (sortState.key) {
filtered = dynamicSort(filtered, sortState.key, sortState.direction);
}
tbody.innerHTML = '';
if (filtered.length === 0) {
tbody.innerHTML = `<tr><td colspan="${isInternal ? 6 : 11}" style="text-align:center; padding: 3rem; color: var(--text-muted);">검색 결과가 없습니다.</td></tr>`;
return;
}
filtered.forEach((asset, idx) => {
if (isInternal) {
const tr = document.createElement('tr');
tr.style.cursor = 'pointer';
tr.innerHTML = `
<td style="text-align:center;">${idx+1}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.SW_FIELD.key]||''}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.DEV_OBJ.key]||''}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.SW_STATUS.key]||'보유중'}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.SW_TYPE.key]||'내부'}</td>
<td>${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')}</td>
`;
tr.addEventListener('click', () => openSwModal(asset, 'view'));
tbody.appendChild(tr);
} else {
const tr = document.createElement('tr');
tr.style.cursor = 'pointer';
const users = state.masterData.swUsers.filter(u => u.sw_id === asset.id);
const userText = users.length > 0 ? `${users[0].user_name}${users.length > 1 ? ' 외 ' + (users.length - 1) : ''}` : '-';
tr.innerHTML = `
<td style="text-align:center;">${idx+1}</td>
<td>${asset[ASSET_SCHEMA.PRODUCT_NAME.key]||''}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.ASSET_TYPE.key]||'외부'}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.SW_STATUS.key]||'사용중'}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.SW_FIELD.key]||''}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.CURRENT_DEPT.key]||''}</td>
<td style="text-align:center;">${userText}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.PURCHASE_DATE.key]||''}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.PURCHASE_DATE.key]||''}</td>
<td style="text-align:center;">${asset[ASSET_SCHEMA.EXPIRY_DATE.key]||''}</td>
<td>${formatInline(asset[ASSET_SCHEMA.MEMO.key]||'-')}</td>
`;
tr.addEventListener('click', () => openSwModal(asset, 'view'));
tbody.appendChild(tr);
}
});
setupTableSorting(table, sortState, (key, dir) => {
sortState = { key, direction: dir };
updateTable();
});
createIcons({ icons: { Edit2, Users, RefreshCcw, Download, Upload, FileSpreadsheet, Plus } });
};
document.getElementById('filter-keyword')?.addEventListener('input', updateTable);
document.getElementById('filter-field')?.addEventListener('change', updateTable);
document.getElementById('filter-corp')?.addEventListener('change', updateTable);
document.getElementById('btn-reset-filters')?.addEventListener('click', () => {
(document.getElementById('filter-keyword') as HTMLInputElement).value = '';
(document.getElementById('filter-field') as HTMLSelectElement).value = '';
(document.getElementById('filter-corp') as HTMLSelectElement).value = '';
updateTable();
});
updateTable();
}