Files
sam-manage/resources/views/stats/dashboard/index.blade.php
권혁성 5dd580623e feat:통계 대시보드 페이지 신규 구현 (/stats/dashboard)
- 모델 7개: StatSalesDaily, StatFinanceDaily, StatProductionDaily,
  StatInventoryDaily, StatSystemDaily, StatSalesMonthly, StatFinanceMonthly
- DashboardStatService: 요약카드, 7일 추이차트, 알림, 월간요약 데이터
- StatDashboardController: HX-Redirect 패턴 적용
- 뷰: 요약카드 6개 + Chart.js 4개 차트 + 알림/월간요약 하단섹션
- 기존 대시보드 "통계 및 리포트" 바로가기 링크 연결
- 헤더 테넌트 선택 기준 전체/개별 테넌트 필터링 지원

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:03:58 +09:00

243 lines
8.2 KiB
PHP

@extends('layouts.app')
@section('title', '통계 대시보드')
@section('page-title', '통계 대시보드')
@section('content')
{{-- 요약 카드 --}}
@include('stats.dashboard.partials._summary-cards')
{{-- 추이 차트 --}}
@include('stats.dashboard.partials._charts')
{{-- 하단 섹션 --}}
@include('stats.dashboard.partials._bottom-section')
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const salesTrend = @json($salesTrend);
const financeTrend = @json($financeTrend);
const productionTrend = @json($productionTrend);
const systemTrend = @json($systemTrend);
const defaultOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, padding: 15, font: { size: 11 } }
}
},
scales: {
x: {
grid: { display: false },
ticks: { font: { size: 11 } }
},
y: {
beginAtZero: true,
grid: { color: '#f3f4f6' },
ticks: {
font: { size: 11 },
callback: function(value) {
if (value >= 1000000) return (value / 1000000).toFixed(0) + 'M';
if (value >= 1000) return (value / 1000).toFixed(0) + 'K';
return value;
}
}
}
}
};
// 매출 추이 (Bar + Line)
new Chart(document.getElementById('salesChart'), {
type: 'bar',
data: {
labels: salesTrend.labels,
datasets: [
{
label: '주문금액',
data: salesTrend.datasets.order_amount,
backgroundColor: 'rgba(59, 130, 246, 0.5)',
borderColor: 'rgb(59, 130, 246)',
borderWidth: 1,
borderRadius: 4,
order: 2,
},
{
label: '매출액',
data: salesTrend.datasets.sales_amount,
type: 'line',
borderColor: 'rgb(16, 185, 129)',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: 'rgb(16, 185, 129)',
fill: true,
tension: 0.3,
order: 1,
}
]
},
options: defaultOptions,
});
// 자금 흐름 (Stacked Bar)
new Chart(document.getElementById('financeChart'), {
type: 'bar',
data: {
labels: financeTrend.labels,
datasets: [
{
label: '입금',
data: financeTrend.datasets.deposit_amount,
backgroundColor: 'rgba(16, 185, 129, 0.7)',
borderRadius: 4,
},
{
label: '출금',
data: financeTrend.datasets.withdrawal_amount.map(v => -v),
backgroundColor: 'rgba(239, 68, 68, 0.7)',
borderRadius: 4,
}
]
},
options: {
...defaultOptions,
scales: {
...defaultOptions.scales,
x: { ...defaultOptions.scales.x, stacked: true },
y: {
...defaultOptions.scales.y,
stacked: true,
beginAtZero: false,
ticks: {
...defaultOptions.scales.y.ticks,
callback: function(value) {
const abs = Math.abs(value);
if (abs >= 1000000) return (value / 1000000).toFixed(0) + 'M';
if (abs >= 1000) return (value / 1000).toFixed(0) + 'K';
return value;
}
}
}
}
},
});
// 생산 현황 (Dual axis Line)
new Chart(document.getElementById('productionChart'), {
type: 'line',
data: {
labels: productionTrend.labels,
datasets: [
{
label: '생산수량',
data: productionTrend.datasets.production_qty,
borderColor: 'rgb(139, 92, 246)',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: 'rgb(139, 92, 246)',
fill: true,
tension: 0.3,
yAxisID: 'y',
},
{
label: '불량률(%)',
data: productionTrend.datasets.defect_rate,
borderColor: 'rgb(239, 68, 68)',
borderWidth: 2,
borderDash: [5, 5],
pointRadius: 3,
pointBackgroundColor: 'rgb(239, 68, 68)',
tension: 0.3,
yAxisID: 'y1',
}
]
},
options: {
...defaultOptions,
scales: {
...defaultOptions.scales,
y: {
...defaultOptions.scales.y,
position: 'left',
title: { display: true, text: '생산수량', font: { size: 11 } },
},
y1: {
position: 'right',
beginAtZero: true,
grid: { drawOnChartArea: false },
ticks: {
font: { size: 11 },
callback: function(value) { return value + '%'; }
},
title: { display: true, text: '불량률(%)', font: { size: 11 } },
}
}
},
});
// 시스템 활동 (Line)
new Chart(document.getElementById('systemChart'), {
type: 'line',
data: {
labels: systemTrend.labels,
datasets: [
{
label: '활성사용자',
data: systemTrend.datasets.active_user_count,
borderColor: 'rgb(99, 102, 241)',
backgroundColor: 'rgba(99, 102, 241, 0.1)',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: 'rgb(99, 102, 241)',
fill: true,
tension: 0.3,
yAxisID: 'y',
},
{
label: 'API 요청',
data: systemTrend.datasets.api_request_count,
borderColor: 'rgb(245, 158, 11)',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: 'rgb(245, 158, 11)',
tension: 0.3,
yAxisID: 'y1',
}
]
},
options: {
...defaultOptions,
scales: {
...defaultOptions.scales,
y: {
...defaultOptions.scales.y,
position: 'left',
title: { display: true, text: '사용자', font: { size: 11 } },
},
y1: {
position: 'right',
beginAtZero: true,
grid: { drawOnChartArea: false },
ticks: {
font: { size: 11 },
callback: function(value) {
if (value >= 1000) return (value / 1000).toFixed(0) + 'K';
return value;
}
},
title: { display: true, text: 'API 요청', font: { size: 11 } },
}
}
},
});
});
</script>
@endpush