- Standardized sub-tab rendering in PartsMasterListView to prevent duplication - Updated badge classes to match system design guide (badge-success/warning) - Refactored JobSpecModal to use unified .grid-form and header identity - Preserved all functional logic and tab-switching behavior from collaborator
175 lines
6.3 KiB
TypeScript
175 lines
6.3 KiB
TypeScript
import { state } from '../../core/state';
|
|
import { openPartsMasterModal } from '../../components/Modal/PartsMasterModal';
|
|
import { openJobSpecModal } from '../../components/Modal/JobSpecModal';
|
|
import { formatInline } from '../../core/utils';
|
|
import { createListView } from './ListFactory';
|
|
|
|
export let activePartsMasterSubTab: 'parts-master' | 'job-spec' = 'parts-master';
|
|
|
|
export function renderPartsMasterList(container: HTMLElement) {
|
|
if (activePartsMasterSubTab === 'parts-master') {
|
|
createListView(container, {
|
|
title: '부품 마스터',
|
|
dataSource: () => state.masterData.partsMaster || [],
|
|
searchKeys: ['component_name', 'category', 'score_tier'],
|
|
filterOptions: {
|
|
keywordLabel: '부품명 / 등급 검색',
|
|
showLoc: false,
|
|
showDept: false,
|
|
showType: false
|
|
},
|
|
onRowClick: (component) => openPartsMasterModal(component, 'view'),
|
|
columns: [
|
|
{
|
|
header: 'ID',
|
|
sortKey: 'id',
|
|
align: 'center',
|
|
width: '5%',
|
|
render: c => c.id.toString()
|
|
},
|
|
{
|
|
header: '분류',
|
|
sortKey: 'category',
|
|
align: 'center',
|
|
width: '15%',
|
|
render: c => {
|
|
let badgeClass = 'badge-primary';
|
|
if (c.category === 'CPU') badgeClass = 'badge-primary';
|
|
else if (c.category === 'GPU') badgeClass = 'badge-success';
|
|
else if (c.category === 'RAM') badgeClass = 'badge-warning';
|
|
return `<span class="badge ${badgeClass}">${c.category}</span>`;
|
|
}
|
|
},
|
|
{
|
|
header: '부품 표준 명칭',
|
|
sortKey: 'component_name',
|
|
render: c => formatInline(c.component_name || '-')
|
|
},
|
|
{
|
|
header: '성능 등급',
|
|
sortKey: 'score_tier',
|
|
align: 'center',
|
|
width: '15%',
|
|
render: c => c.score_tier || '-'
|
|
},
|
|
{
|
|
header: '감점 점수',
|
|
sortKey: 'deduction',
|
|
align: 'center',
|
|
width: '15%',
|
|
render: c => {
|
|
const score = c.deduction || 0;
|
|
let color = '#3b82f6'; // blue
|
|
if (score >= 20) color = '#ef4444'; // red
|
|
else if (score >= 10) color = '#f59e0b'; // orange
|
|
return `<strong style="color: ${color}; font-size: 14px;">-${score}점</strong>`;
|
|
}
|
|
}
|
|
]
|
|
});
|
|
} else {
|
|
createListView(container, {
|
|
title: '직무별 기준 사양',
|
|
dataSource: () => state.masterData.jobSpecs || [],
|
|
searchKeys: ['job_name', 'cpu_standard', 'ram_standard', 'gpu_standard', 'remarks'],
|
|
filterOptions: {
|
|
keywordLabel: '직무명 / 사양 검색',
|
|
showLoc: false,
|
|
showDept: false,
|
|
showType: false
|
|
},
|
|
onRowClick: (jobSpec) => openJobSpecModal(jobSpec, 'view'),
|
|
columns: [
|
|
{
|
|
header: 'ID',
|
|
sortKey: 'id',
|
|
align: 'center',
|
|
width: '5%',
|
|
render: j => j.id.toString()
|
|
},
|
|
{
|
|
header: '직무명',
|
|
sortKey: 'job_name',
|
|
width: '15%',
|
|
render: j => `<strong style="color: var(--primary-color); font-size: 14px;">${formatInline(j.job_name || '-')}</strong>`
|
|
},
|
|
{
|
|
header: '권장 CPU 사양',
|
|
sortKey: 'cpu_standard',
|
|
render: j => formatInline(j.cpu_standard || '-')
|
|
},
|
|
{
|
|
header: '권장 RAM 사양',
|
|
sortKey: 'ram_standard',
|
|
width: '12%',
|
|
render: j => formatInline(j.ram_standard || '-')
|
|
},
|
|
{
|
|
header: '권장 GPU 사양',
|
|
sortKey: 'gpu_standard',
|
|
render: j => formatInline(j.gpu_standard || '-')
|
|
},
|
|
{
|
|
header: '기준 점수',
|
|
sortKey: 'min_score',
|
|
align: 'center',
|
|
width: '10%',
|
|
render: j => `<span style="font-weight: 700;">${j.min_score || 0}점 이상</span>`
|
|
},
|
|
{
|
|
header: '비고',
|
|
sortKey: 'remarks',
|
|
width: '20%',
|
|
render: j => formatInline(j.remarks || '-')
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
renderSubTabs(container);
|
|
}
|
|
|
|
function renderSubTabs(container: HTMLElement) {
|
|
const header = container.querySelector('.page-header');
|
|
if (!header) return;
|
|
|
|
// 기존에 생성된 탭 바가 있다면 제거하여 중복 방지 (스타일만 수정하는 최소 침습 방식)
|
|
const existingTabs = container.querySelector('.sub-tab-container');
|
|
if (existingTabs) existingTabs.remove();
|
|
|
|
const tabContainer = document.createElement('div');
|
|
tabContainer.className = 'sub-tab-container';
|
|
tabContainer.style.cssText = 'display: flex; gap: 1rem; padding: 0 2rem; border-bottom: 1px solid var(--hairline); background: var(--canvas);';
|
|
|
|
const tab1Active = activePartsMasterSubTab === 'parts-master';
|
|
const tab2Active = activePartsMasterSubTab === 'job-spec';
|
|
|
|
tabContainer.innerHTML = `
|
|
<button id="tab-parts-master" class="sub-tab-btn ${tab1Active ? 'active' : ''}" style="padding: 1rem 0.5rem; border: none; background: none; font-size: var(--fs-sm); font-weight: 600; cursor: pointer; color: ${tab1Active ? 'var(--primary)' : 'var(--mute)'}; position: relative; border-bottom: 2px solid ${tab1Active ? 'var(--primary)' : 'transparent'}; margin-bottom: -1px;">
|
|
부품 표준 등급
|
|
</button>
|
|
<button id="tab-job-spec" class="sub-tab-btn ${tab2Active ? 'active' : ''}" style="padding: 1rem 0.5rem; border: none; background: none; font-size: var(--fs-sm); font-weight: 600; cursor: pointer; color: ${tab2Active ? 'var(--primary)' : 'var(--mute)'}; position: relative; border-bottom: 2px solid ${tab2Active ? 'var(--primary)' : 'transparent'}; margin-bottom: -1px;">
|
|
직무별 기준 사양
|
|
</button>
|
|
`;
|
|
|
|
header.parentNode!.insertBefore(tabContainer, header.nextSibling);
|
|
|
|
const tabPartsMaster = tabContainer.querySelector('#tab-parts-master')!;
|
|
const tabJobSpec = tabContainer.querySelector('#tab-job-spec')!;
|
|
|
|
tabPartsMaster.addEventListener('click', () => {
|
|
if (activePartsMasterSubTab !== 'parts-master') {
|
|
activePartsMasterSubTab = 'parts-master';
|
|
renderPartsMasterList(container);
|
|
}
|
|
});
|
|
|
|
tabJobSpec.addEventListener('click', () => {
|
|
if (activePartsMasterSubTab !== 'job-spec') {
|
|
activePartsMasterSubTab = 'job-spec';
|
|
renderPartsMasterList(container);
|
|
}
|
|
});
|
|
}
|