feat(flow-logs): fix text overlapping, show full asset code, filter by current month and support JSON logs
This commit is contained in:
@@ -19,6 +19,7 @@ let corpChartInstance4p: any = null;
|
||||
let totalServerMismatchByPurposeChartInstance4p: any = null;
|
||||
let serverServiceChartInstance4p: any = null;
|
||||
let serverStatusChartInstance4p: any = null;
|
||||
let pcFlowChartInstance: any = null;
|
||||
|
||||
// ─── 서버 용도별 카테고리 분류 헬퍼 ───
|
||||
function categorizePurpose(purpose: string): string {
|
||||
@@ -563,12 +564,82 @@ function buildServerStatusTableRows(list: any[]): string {
|
||||
export function renderHwDashboard(container: HTMLElement) {
|
||||
const allHw = state.masterData.hw || [];
|
||||
|
||||
// --- PC FLOW LOGS DATA PREP ---
|
||||
const logs = state.masterData.logs || [];
|
||||
const now = new Date();
|
||||
const currentYearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
let currentMonthCheckout = 0;
|
||||
let currentMonthReturn = 0;
|
||||
let currentMonthMove = 0;
|
||||
|
||||
let totalCheckout = 0;
|
||||
let totalReturn = 0;
|
||||
let totalMove = 0;
|
||||
|
||||
const flowLogs = logs.filter((log: any) => {
|
||||
const details = log.details || '';
|
||||
const isFlow = details.includes('[불출]') || details.includes('[반납]') || details.includes('[입고]') || details.includes('[이동]') || details.includes('[이관]');
|
||||
|
||||
if (isFlow) {
|
||||
const logDate = log.log_date || '';
|
||||
const isCurrentMonth = logDate.startsWith(currentYearMonth);
|
||||
|
||||
if (details.includes('[불출]')) {
|
||||
totalCheckout++;
|
||||
if (isCurrentMonth) currentMonthCheckout++;
|
||||
} else if (details.includes('[반납]') || details.includes('[입고]')) {
|
||||
totalReturn++;
|
||||
if (isCurrentMonth) currentMonthReturn++;
|
||||
} else if (details.includes('[이동]') || details.includes('[이관]')) {
|
||||
totalMove++;
|
||||
if (isCurrentMonth) currentMonthMove++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const recentFlowLogs = flowLogs.slice(0, 5);
|
||||
let recentFlowLogsHtml = '';
|
||||
if (recentFlowLogs.length === 0) {
|
||||
recentFlowLogsHtml = '<tr><td colspan="4" style="text-align:center; padding:1.5rem; color:#94A3B8; font-size:12px;">최근 유동 이력이 없습니다.</td></tr>';
|
||||
} else {
|
||||
recentFlowLogs.forEach((log: any) => {
|
||||
const details = log.details || '';
|
||||
let badgeHtml = '';
|
||||
if (details.includes('[불출]')) {
|
||||
badgeHtml = '<span style="background:#E0F2FE;color:#0369A1;padding:2px 6px;border-radius:4px;font-size:11px;font-weight:700;">불출</span>';
|
||||
} else if (details.includes('[반납]') || details.includes('[입고]')) {
|
||||
badgeHtml = '<span style="background:#DCFCE7;color:#15803D;padding:2px 6px;border-radius:4px;font-size:11px;font-weight:700;">입고</span>';
|
||||
} else if (details.includes('[이동]') || details.includes('[이관]')) {
|
||||
badgeHtml = '<span style="background:#FEF3C7;color:#B45309;padding:2px 6px;border-radius:4px;font-size:11px;font-weight:700;">이동</span>';
|
||||
}
|
||||
|
||||
const cleanDetails = details.replace(/^\[(불출|반납|입고|이동|이관)\]\s*/, '');
|
||||
|
||||
recentFlowLogsHtml += `
|
||||
<tr style="border-bottom: 1px solid #F1F5F9; font-size: 13px;">
|
||||
<td style="padding: 8px; color: #64748B;">${log.log_date || '-'}</td>
|
||||
<td style="padding: 8px;">${badgeHtml}</td>
|
||||
<td style="padding: 8px; font-weight: 600; color: #334155;">${log.log_user || '시스템'}</td>
|
||||
<td style="padding: 8px; color: #475569;" title="${details}">${cleanDetails}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
// --- PC DATA PREP ---
|
||||
const pcs = allHw.filter(a => {
|
||||
const cat = a[ASSET_SCHEMA.CATEGORY.key] || '';
|
||||
const type = a[ASSET_SCHEMA.ASSET_TYPE.key] || '';
|
||||
const job = a[ASSET_SCHEMA.USER_POSITION.key] || '';
|
||||
return (cat === 'PC' || type === '개인PC' || type === '노트북' || type === '공용PC') && job !== '재고PC';
|
||||
const status = a[ASSET_SCHEMA.HW_STATUS.key] || '';
|
||||
const user = a[ASSET_SCHEMA.CURRENT_USER.key] || '';
|
||||
return (cat === 'PC' || type === '개인PC' || type === '노트북' || type === '공용PC') &&
|
||||
job !== '재고PC' &&
|
||||
status === '사용중' &&
|
||||
user.trim() !== '';
|
||||
});
|
||||
|
||||
const jobScores: Record<string, { totalScore: number; count: number; avg: number }> = {};
|
||||
@@ -824,7 +895,7 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
'</div>' +
|
||||
'<div class="slider-controls">' +
|
||||
'<button id="slider-prev" class="slider-nav-btn" disabled><i data-lucide="chevron-left"></i></button>' +
|
||||
'<span id="slider-indicator" class="slider-indicator">1 / 4</span>' +
|
||||
'<span id="slider-indicator" class="slider-indicator">1 / 5</span>' +
|
||||
'<button id="slider-next" class="slider-nav-btn"><i data-lucide="chevron-right"></i></button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
@@ -1082,8 +1153,6 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
'<div style="flex: 1; min-height: 0; position: relative;"><canvas id="chart-server-status-4p" style="position: absolute; top:0; left:0; width:100%; height:100%;"></canvas></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
|
||||
'</div>' +
|
||||
@@ -1108,7 +1177,10 @@ export function renderHwDashboard(container: HTMLElement) {
|
||||
serverServiceGroups,
|
||||
serverStatusGroups,
|
||||
purposeServerUnders,
|
||||
purposeServerOvers
|
||||
purposeServerOvers,
|
||||
totalCheckout,
|
||||
totalReturn,
|
||||
totalMove
|
||||
);
|
||||
|
||||
// 기획서 보기 버튼 클릭 이벤트 바인딩
|
||||
@@ -1191,7 +1263,10 @@ function initCharts(
|
||||
serviceGroups: any,
|
||||
statusGroups: any,
|
||||
purposeServerUnders?: any,
|
||||
purposeServerOvers?: any
|
||||
purposeServerOvers?: any,
|
||||
totalCheckout?: number,
|
||||
totalReturn?: number,
|
||||
totalMove?: number
|
||||
) {
|
||||
// 직무별 점수
|
||||
const jobCtx = document.getElementById('chart-job-scores') as HTMLCanvasElement;
|
||||
@@ -1654,4 +1729,39 @@ function initCharts(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// PC 유동 비율 도넛 차트
|
||||
const flowCtx = document.getElementById('chart-pc-flow-stats') as HTMLCanvasElement;
|
||||
if (flowCtx && typeof Chart !== 'undefined') {
|
||||
const tCheckout = totalCheckout || 0;
|
||||
const tReturn = totalReturn || 0;
|
||||
const tMove = totalMove || 0;
|
||||
|
||||
if (pcFlowChartInstance) {
|
||||
pcFlowChartInstance.destroy();
|
||||
pcFlowChartInstance = null;
|
||||
}
|
||||
|
||||
pcFlowChartInstance = new Chart(flowCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['불출', '입고(반납)', '이동(이관)'],
|
||||
datasets: [{
|
||||
data: [tCheckout, tReturn, tMove],
|
||||
backgroundColor: ['#3B82F6', '#10B981', '#F59E0B'],
|
||||
borderWidth: 0,
|
||||
hoverOffset: 8
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'bottom', labels: { padding: 10, usePointStyle: true, boxWidth: 10 } }
|
||||
},
|
||||
cutout: '75%',
|
||||
animation: { animateScale: true, animateRotate: true }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user