feat: 모든 카테고리(HW, SW, SW 사용자) DB 일괄 덮어쓰기 저장 기능 구현
This commit is contained in:
104
src/views/Dashboard/HwDashboard.ts
Normal file
104
src/views/Dashboard/HwDashboard.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { state } from '../../core/state';
|
||||
import { HardwareAsset } from '../../core/excelHandler';
|
||||
import { openDashboardDetail } from '../../components/Modal/DashboardDetailModal';
|
||||
import { normalizeDate } from '../../core/utils';
|
||||
|
||||
declare var Chart: any;
|
||||
|
||||
export function renderHwDashboard(container: HTMLElement) {
|
||||
const types = ['개인PC', '서버', '스토리지', '전산비품'];
|
||||
const units = ['대', '대', '대', '개'];
|
||||
const groups: any = {};
|
||||
|
||||
types.forEach(t => { groups[t] = { idle: [], active: [] }; });
|
||||
|
||||
state.masterData.hw.forEach(a => {
|
||||
if (!groups[a.type]) return;
|
||||
if (isHwIdle(a)) groups[a.type].idle.push(a);
|
||||
else groups[a.type].active.push(a);
|
||||
});
|
||||
|
||||
let usageCards = '';
|
||||
types.forEach((t, i) => {
|
||||
const total = groups[t].idle.length + groups[t].active.length;
|
||||
const used = groups[t].active.length;
|
||||
const per = total > 0 ? Math.round((used / total) * 100) : 0;
|
||||
const barColor = per >= 50 ? 'var(--dash-primary)' : 'var(--dash-danger)';
|
||||
|
||||
usageCards += `
|
||||
<div class="dashboard-card" data-action="idle" data-type="${t}" style="padding: 1.25rem 1.5rem; cursor:pointer; min-height:auto;">
|
||||
<span style="font-size:1rem; font-weight:700; color:var(--text-main);">${t} 사용현황</span>
|
||||
<div style="font-size: 0.8125rem; color:var(--text-muted); margin-bottom: 1rem;">
|
||||
${total}${units[i]} 중 ${used}${units[i]} 사용 중
|
||||
</div>
|
||||
<div style="font-size: 2rem; font-weight:700; color:${barColor}; line-height:1;">${per}%</div>
|
||||
<div style="width:100%; height:4px; background-color:var(--border-color); border-radius:2px; overflow:hidden; margin-top:0.75rem;">
|
||||
<div style="width:${per}%; height:100%; background-color:${barColor};"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="view-container">
|
||||
<h3 class="dashboard-section-title">자산 사용현황 요약</h3>
|
||||
<div class="dashboard-grid">${usageCards}</div>
|
||||
|
||||
<h3 class="dashboard-section-title">하드웨어 보유 통계</h3>
|
||||
<div class="dashboard-layout-2col">
|
||||
<div class="dashboard-card">
|
||||
<h4 style="margin-bottom:1rem; font-size:0.9rem; color:var(--text-muted);">자산 유형별 보유 현황</h4>
|
||||
<canvas id="chart-hw-types"></canvas>
|
||||
</div>
|
||||
<div class="dashboard-card">
|
||||
<h4 style="margin-bottom:1rem; font-size:0.9rem; color:var(--text-muted);">법인별 자산 분포</h4>
|
||||
<canvas id="chart-hw-corps"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
if (typeof Chart === 'undefined') return;
|
||||
const ctxType = (document.getElementById('chart-hw-types') as HTMLCanvasElement)?.getContext('2d');
|
||||
const ctxCorp = (document.getElementById('chart-hw-corps') as HTMLCanvasElement)?.getContext('2d');
|
||||
if (ctxType) {
|
||||
const chart = new Chart(ctxType, {
|
||||
type: 'doughnut',
|
||||
data: { labels: types, datasets: [{ data: types.map(t => state.masterData.hw.filter(a => a.type === t).length), backgroundColor: ['#1E5149', '#3b82f6', '#10b981', '#f59e0b'] }] },
|
||||
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right' } } }
|
||||
});
|
||||
state.activeCharts.push(chart);
|
||||
}
|
||||
if (ctxCorp) {
|
||||
const corps = ['한맥', '삼안', '바론'];
|
||||
const chart = new Chart(ctxCorp, {
|
||||
type: 'bar',
|
||||
data: { labels: corps, datasets: [{ label: '보유 수량', data: corps.map(c => state.masterData.hw.filter(a => a.법인 === c).length), backgroundColor: 'rgba(30, 81, 73, 0.7)', borderRadius: 4 }] },
|
||||
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } }
|
||||
});
|
||||
state.activeCharts.push(chart);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
container.querySelectorAll('[data-action="idle"]').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const t = card.getAttribute('data-type')!;
|
||||
openDashboardDetail(`[${t}] 유휴 자산 목록`, groups[t].idle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isHwIdle(a: HardwareAsset) {
|
||||
if (a.type === '개인PC') return !a.사용자 || a.사용자.trim() === '' || a.사용자.trim() === '-';
|
||||
if (a.type === '스토리지') return !a.담당자_정 || a.담당자_정.trim() === '' || a.담당자_정.trim() === '-';
|
||||
return !a.관리자 || a.관리자.trim() === '' || a.관리자.trim() === '-';
|
||||
}
|
||||
|
||||
function getHwAgeYears(a: HardwareAsset) {
|
||||
if (!a.구매일) return 0;
|
||||
try {
|
||||
const buyDate = new Date(normalizeDate(a.구매일));
|
||||
if (isNaN(buyDate.getTime())) return 0;
|
||||
return (Date.now() - buyDate.getTime()) / (1000 * 60 * 60 * 24 * 365.25);
|
||||
} catch { return 0; }
|
||||
}
|
||||
Reference in New Issue
Block a user