diff --git a/src/style.css b/src/style.css
index 0bcbce1..7f0e5c4 100644
--- a/src/style.css
+++ b/src/style.css
@@ -356,3 +356,64 @@ tbody tr:hover { background-color: var(--bg-color); }
display: flex; justify-content: space-between; align-items: center;
}
.footer-actions { display: flex; gap: 0.5rem; }
+
+/* Search Filter Bar */
+.search-bar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ background-color: var(--white);
+ padding: 1.25rem;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ margin-bottom: 2rem;
+ align-items: flex-end;
+}
+
+.search-item {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ min-width: 180px;
+}
+
+.search-item.flex-1 {
+ flex: 1;
+ min-width: 250px;
+}
+
+.search-item label {
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.search-item input,
+.search-item select {
+ padding: 0.5rem 0.75rem;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ font-size: 0.875rem;
+ outline: none;
+ transition: all 0.2s;
+ background-color: var(--white);
+}
+
+.search-item input:focus,
+.search-item select:focus {
+ border-color: var(--primary-color);
+ box-shadow: 0 0 0 2px rgba(30, 81, 73, 0.1);
+}
+
+.btn-reset {
+ height: 36px;
+ padding: 0 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.875rem;
+ color: var(--text-muted);
+}
+
diff --git a/src/views/AssetTableView.ts b/src/views/AssetTableView.ts
index 99184c0..1a0d4d4 100644
--- a/src/views/AssetTableView.ts
+++ b/src/views/AssetTableView.ts
@@ -11,7 +11,7 @@ import { openSwUserModal } from '../components/Modal/SWUserModal';
*/
export function renderTable(mainContent: HTMLElement) {
const container = document.createElement('div');
- container.className = 'table-container';
+ container.className = 'view-container'; // 배경과 테두리가 없는 투명한 컨테이너
const table = document.createElement('table');
if (state.activeCategory === 'hw') {
@@ -30,8 +30,11 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
const list = state.masterData.hw.filter(a => a.type === state.activeSubTab);
if (state.activeSubTab === '개인PC') {
+ const tableWrapper = document.createElement('div');
+ tableWrapper.className = 'table-container';
table.innerHTML = `| No | 법인 | 자산코드 | 사용자 | 위치 | CPU | GPU | RAM | SSD1 | SSD2 | HDD1 | HDD2 | 구매일 | 금액 | 납품업체 | 품의서 | 관리 |
|---|
`;
- container.appendChild(table);
+ tableWrapper.appendChild(table);
+ container.appendChild(tableWrapper);
mainContent.appendChild(container);
const tbody = document.getElementById('dynamic-tbody')!;
if (list.length === 0) { tbody.innerHTML = `| 등록된 자산이 없습니다. |
`; return; }
@@ -44,8 +47,11 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
tbody.appendChild(tr);
});
} else if (state.activeSubTab === '스토리지') {
+ const tableWrapper = document.createElement('div');
+ tableWrapper.className = 'table-container';
table.innerHTML = `| No | 법인 | 유형 | 자산코드 | 명칭 | 위치 | 모델명 | 용량 | 담당자(정) | IP주소 | 구매일 | 금액 | 관리 |
`;
- container.appendChild(table);
+ tableWrapper.appendChild(table);
+ container.appendChild(tableWrapper);
mainContent.appendChild(container);
const tbody = document.getElementById('dynamic-tbody')!;
if (list.length === 0) { tbody.innerHTML = `| 등록된 자산이 없습니다. |
`; return; }
@@ -58,8 +64,11 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
tbody.appendChild(tr);
});
} else {
+ const tableWrapper = document.createElement('div');
+ tableWrapper.className = 'table-container';
table.innerHTML = `| No | 법인 | ${state.activeSubTab === '전산비품' ? '유형 | ' : ''}자산코드 | 명칭 | 위치 | 관리자 | 구매일 | 금액 | 관리 |
`;
- container.appendChild(table);
+ tableWrapper.appendChild(table);
+ container.appendChild(tableWrapper);
mainContent.appendChild(container);
const tbody = document.getElementById('dynamic-tbody')!;
if (list.length === 0) { tbody.innerHTML = `| 등록된 자산이 없습니다. |
`; return; }
@@ -75,25 +84,118 @@ function renderHwTable(table: HTMLTableElement, container: HTMLElement, mainCont
}
function renderSwTable(table: HTMLTableElement, container: HTMLElement, mainContent: HTMLElement) {
- const list = state.masterData.sw.filter(a => a.type === state.activeSubTab);
+ const fullList = state.masterData.sw.filter(a => a.type === state.activeSubTab);
const isSub = state.activeSubTab === '구독SW';
+
+ // 0. Container 준비 (조회 바 + 테이블)
+ container.innerHTML = '';
+ // 1. 조회 바 (Filter Bar) 생성
+ const filterBar = document.createElement('div');
+ filterBar.className = 'search-bar';
+ filterBar.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ container.appendChild(filterBar);
+
+ // 2. 테이블 기본 구조 생성
+ const tableWrapper = document.createElement('div');
+ tableWrapper.className = 'table-container';
table.classList.add('sw-table');
table.innerHTML = `| No. | 분야 | 법인 | 부서 | 제품명 | 구매일 | ${isSub ? '구독일 | ' : ''}금액 | 수량 | 사용가능 | 관리 |
`;
- container.appendChild(table);
- mainContent.appendChild(container);
- const tbody = document.getElementById('dynamic-tbody')!;
- if (list.length === 0) { tbody.innerHTML = `| 정보가 없습니다. |
`; return; }
- list.forEach((asset, idx) => {
- const assigned = state.masterData.swUsers.filter(u => u.swId === asset.id).length;
- const avail = (typeof asset.수량 === 'number' ? asset.수량 : parseInt(asset.수량||'0', 10)) - assigned;
- const tr = document.createElement('tr');
- tr.style.cursor = 'pointer';
- tr.innerHTML = `${idx+1} | ${asset.분야||''} | ${asset.법인} | ${asset.부서||''} | ${asset.제품명} | ${asset.구매일||''} | ${isSub ? `${asset.구독일||''} | ` : ''}${asset.금액||'0'} | ${asset.수량} | ${avail} | | `;
- tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openSwModal(asset); });
- tr.querySelector('.btn-edit')?.addEventListener('click', () => openSwModal(asset));
- tr.querySelector('.btn-users')?.addEventListener('click', () => openSwUserModal(asset));
- tbody.appendChild(tr);
+ tableWrapper.appendChild(table);
+ container.appendChild(tableWrapper);
+ mainContent.appendChild(container);
+
+ const tbody = document.getElementById('dynamic-tbody')!;
+
+ // 3. 필터링 및 테이블 업데이트 로직
+ const updateTable = () => {
+ const keyword = (document.getElementById('filter-keyword') as HTMLInputElement).value.toLowerCase().trim();
+ const field = (document.getElementById('filter-field') as HTMLSelectElement).value;
+ const corp = (document.getElementById('filter-corp') as HTMLSelectElement).value;
+
+ const filtered = fullList.filter(asset => {
+ const matchKeyword = !keyword ||
+ (asset.제품명 || '').toLowerCase().includes(keyword) ||
+ (asset.부서 || '').toLowerCase().includes(keyword);
+ const matchField = !field || asset.분야 === field;
+ const matchCorp = !corp || asset.법인 === corp;
+ return matchKeyword && matchField && matchCorp;
+ });
+
+ tbody.innerHTML = '';
+ if (filtered.length === 0) {
+ tbody.innerHTML = `| 검색 결과가 없습니다. |
`;
+ return;
+ }
+
+ filtered.forEach((asset, idx) => {
+ const assigned = state.masterData.swUsers.filter(u => u.swId === asset.id).length;
+ const avail = (typeof asset.수량 === 'number' ? asset.수량 : parseInt(asset.수량||'0', 10)) - assigned;
+ const tr = document.createElement('tr');
+ tr.style.cursor = 'pointer';
+ tr.innerHTML = `${idx+1} | ${asset.분야||''} | ${asset.법인} | ${asset.부서||''} | ${asset.제품명} | ${asset.구매일||''} | ${isSub ? `${asset.구독일||''} | ` : ''}${asset.금액||'0'} | ${asset.수량} | ${avail} | | `;
+ tr.addEventListener('click', (e) => { if (!(e.target as HTMLElement).closest('button')) openSwModal(asset); });
+ tr.querySelector('.btn-edit')?.addEventListener('click', () => openSwModal(asset));
+ tr.querySelector('.btn-users')?.addEventListener('click', () => openSwUserModal(asset));
+ tbody.appendChild(tr);
+ });
+
+ // 버튼 내 아이콘 다시 그리기
+ createIcons({
+ icons: { Edit2, Users, RefreshCcw: CalendarClock } // RefreshCcw는 아래 버튼용
+ });
+ // 초기화 버튼 아이콘은 별도로
+ createIcons({
+ scope: filterBar
+ });
+ };
+
+ // 4. 이벤트 바인딩
+ 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 resetBtn = document.getElementById('btn-reset-filters') as HTMLButtonElement;
+
+ keywordInput.addEventListener('input', updateTable);
+ fieldSelect.addEventListener('change', updateTable);
+ corpSelect.addEventListener('change', updateTable);
+
+ resetBtn.addEventListener('click', () => {
+ keywordInput.value = '';
+ fieldSelect.value = '';
+ corpSelect.value = '';
+ updateTable();
});
+
+ // 초기 실행
+ updateTable();
}
+