@@ -0,0 +1,700 @@
< ? php
/**
* dense(정보 집약 표·KPI) + charts(Chart.js) 혼합 대시보드
*
* @var string $lgLabel
*/
$lgLabel = $lgLabel ? ? '북구' ;
$mbName = session () -> get ( 'mb_name' ) ? ? '담당자' ;
$dashClassic = base_url ( 'dashboard/classic-mock' );
$dashModern = base_url ( 'dashboard/modern' );
$dashDense = base_url ( 'dashboard/dense' );
$dashCharts = base_url ( 'dashboard/charts' );
$dashBlend = base_url ( 'dashboard/blend' );
$kpiTop = [
[ 'icon' => 'fa-triangle-exclamation' , 'c' => 'text-amber-700' , 'bg' => 'bg-amber-50' , 'v' => '3' , 'l' => '재고부족' , 'sub' => '품목' ],
[ 'icon' => 'fa-cart-shopping' , 'c' => 'text-sky-700' , 'bg' => 'bg-sky-50' , 'v' => '12' , 'l' => '구매신청' , 'sub' => '미처리' ],
[ 'icon' => 'fa-truck' , 'c' => 'text-emerald-700' , 'bg' => 'bg-emerald-50' , 'v' => '8' , 'l' => '발주·입고' , 'sub' => '금주' ],
[ 'icon' => 'fa-user-clock' , 'c' => 'text-violet-700' , 'bg' => 'bg-violet-50' , 'v' => '4' , 'l' => '회원승인' , 'sub' => '대기' ],
[ 'icon' => 'fa-store' , 'c' => 'text-rose-700' , 'bg' => 'bg-rose-50' , 'v' => '127' , 'l' => '지정판매소' , 'sub' => '등록' ],
[ 'icon' => 'fa-boxes-stacked' , 'c' => 'text-slate-700' , 'bg' => 'bg-slate-100' , 'v' => '48.2k' , 'l' => '봉투재고' , 'sub' => '장 합계' ],
[ 'icon' => 'fa-file-invoice' , 'c' => 'text-orange-700' , 'bg' => 'bg-orange-50' , 'v' => '6' , 'l' => '세금계산서' , 'sub' => '발행대기' ],
[ 'icon' => 'fa-headset' , 'c' => 'text-cyan-700' , 'bg' => 'bg-cyan-50' , 'v' => '2' , 'l' => '민원·문의' , 'sub' => '오늘' ],
];
$stockRows = [
[ '일반 5L' , '12,400' , '안전' , '3.2주' ],
[ '일반 10L' , '8,200' , '주의' , '1.8주' ],
[ '일반 20L' , '2,100' , '부족' , '0.6주' ],
[ '음식물 스티커' , '15,000' , '안전' , '5.1주' ],
[ '재사용봉투' , '4,300' , '안전' , '2.4주' ],
[ '특수규격 A' , '890' , '부족' , '0.3주' ],
];
$orderRows = [
[ 'PO-2025-0218' , '○○상사' , '일반 5L× 2박스' , '발주확인' , '02-26 10:20' ],
[ 'PO-2025-0217' , '△△유통' , '스티커 500매' , '납품중' , '02-26 09:05' ],
[ 'PO-2025-0216' , '□□종량제' , '20L 혼합' , '입고완료' , '02-25 16:40' ],
[ 'REQ-8841' , '행복마트 북구점' , '5L 2,000장' , '접수' , '02-26 09:12' ],
[ 'REQ-8839' , '○○슈퍼' , '스티커 500' , '처리중' , '02-26 08:45' ],
];
$logRows = [
[ '10:42' , 'system' , '일일 재고 스냅샷 생성 완료' ],
[ '10:18' , 'user' , esc ( $mbName ) . ' 로그인 (IP 마스킹)' ],
[ '09:55' , 'batch' , '구매신청 자동 분배 3건' ],
[ '09:30' , 'admin' , '판매소 코드 2건 갱신' ],
[ '08:12' , 'api' , '세금계산서 연동 응답 정상' ],
];
$storeSummary = [
[ '행복마트 북구점' , '42' , '정상' , '02-26' ],
[ '○○슈퍼' , '38' , '정상' , '02-25' ],
[ '△△상회' , '15' , '연체1건' , '02-20' ],
[ '□□마트' , '29' , '정상' , '02-26' ],
[ '◇◇할인점' , '51' , '정상' , '02-26' ],
];
$approvals = [
[ '김○○' , '판매소' , '02-26' , '서류검토' ],
[ '이○○' , '일반' , '02-25' , '본인확인' ],
[ '박○○' , '판매소' , '02-25' , '주소불일치' ],
];
$notices = [
'2월 말 정기 재고 실사 안내 — 2/28 17:00 마감' ,
'봉투 단가 조정 예고 — 3/1 적용 예정 (안내문 배포 완료)' ,
];
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>종량제 시스템 — 종합·그래프 혼합</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
body {
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
font-size: 12px;
color: #333;
}
.nav-top a.nav-active {
color: #2b4c8c;
font-weight: 600;
border-bottom: 2px solid #2b4c8c;
padding-bottom: 2px;
margin-bottom: -2px;
}
.dense-table th, .dense-table td { padding: 0.25rem 0.4rem; line-height: 1.25; }
.dense-table thead th { font-size: 11px; font-weight: 600; color: #555; background: #f3f4f6; border-bottom: 1px solid #d1d5db; }
.dense-table tbody td { border-bottom: 1px solid #eee; font-size: 11px; }
.spark { display: flex; align-items: flex-end; gap: 2px; height: 36px; }
.spark span { flex: 1; background: linear-gradient(180deg, #3b82f6, #93c5fd); border-radius: 1px; min-width: 4px; }
.chart-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
box-shadow: 0 1px 2px rgba(0,0,0,.04);
}
.chart-card h2 {
font-size: 11px;
font-weight: 700;
color: #1f2937;
padding: 0.4rem 0.5rem;
border-bottom: 1px solid #f3f4f6;
background: #fafafa;
}
.chart-wrap { position: relative; height: 180px; padding: 0.4rem 0.5rem 0.5rem; }
.chart-wrap.tall { height: 260px; }
.chart-wrap.wide { height: 220px; }
</style>
</head>
<body class="bg-[#f0f2f5] flex flex-col min-h-screen">
<header class="border-b border-gray-300 bg-white shadow-sm shrink-0" data-purpose="top-navigation">
<div class="flex items-center justify-between px-3 py-1.5 gap-3 flex-wrap">
<div class="flex items-center gap-2 shrink-0">
<div class="flex items-center gap-2 text-green-700 font-bold text-base">
<i class="fa-solid fa-recycle text-lg"></i>
<span>종량제 시스템</span>
</div>
<span class="hidden sm:inline text-[11px] text-gray-500 border-l border-gray-300 pl-2">
<?= esc($lgLabel) ?> · <strong class="text-gray-700"><?= esc($mbName) ?></strong>님
</span>
</div>
<nav class="nav-top hidden lg:flex flex-wrap items-center gap-3 xl:gap-4 text-[13px] font-medium text-gray-700">
<a class="nav-active flex items-center gap-1 whitespace-nowrap" href="<?= esc($dashBlend) ?>">
<i class="fa-solid fa-gauge-high"></i> 업무 현황
</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-regular fa-file-lines"></i> 문서 관리</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-box-open"></i> 규격</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-bag-shopping"></i> 봉투 양식</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-table"></i> 데이터 양식</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-clock-rotate-left"></i> 사용 내역</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="<?= base_url('bag/inventory-inquiry') ?>"><i class="fa-solid fa-boxes-stacked"></i> 재고 현황</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="<?= base_url('bag/waste-suibal-enterprise') ?>"><i class="fa-solid fa-table-list"></i> 수불 현황</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-chart-line"></i> 통계 분석</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-gear"></i> 설정</a>
</nav>
<div class="flex items-center gap-1.5 shrink-0 text-[11px]">
<a href="<?= esc($dashClassic) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline">클래식</a>
<span class="text-gray-300 hidden md:inline">|</span>
<a href="<?= esc($dashModern) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline">모던</a>
<span class="text-gray-300 hidden md:inline">|</span>
<a href="<?= esc($dashDense) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline" title="정보 집약">종합</a>
<span class="text-gray-300 hidden md:inline">|</span>
<a href="<?= esc($dashCharts) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline" title="그래프만">차트</a>
<a href="<?= base_url('logout') ?>" class="text-gray-500 hover:text-gray-800 p-1 ml-1" title="로그아웃">
<i class="fa-solid fa-arrow-right-from-bracket"></i>
</a>
</div>
</div>
</header>
<div class="bg-gradient-to-r from-[#eff5fb] to-[#e8eef8] border-b border-gray-300 px-3 py-1 flex flex-wrap items-center justify-between gap-2 text-[11px] shrink-0">
<span class="font-semibold text-gray-800">
<i class="fa-solid fa-layer-group text-[#2b4c8c] mr-1"></i>종합·그래프 혼합 현황
<span class="font-normal text-gray-500 ml-1">· dense 표/KPI + Chart.js</span>
</span>
<div class="flex flex-wrap items-center gap-2 text-gray-600">
<span><i class="fa-regular fa-calendar mr-0.5"></i><?= date('Y-m-d (D) H:i') ?></span>
<span class="text-gray-300">|</span>
<span>기준지자체 <strong class="text-gray-800"><?= esc($lgLabel) ?></strong></span>
<button type="button" class="bg-[#2b4c8c] text-white px-2 py-0.5 rounded text-[11px]"><i class="fa-solid fa-rotate mr-0.5"></i>새로고침</button>
</div>
</div>
<main class="flex-1 overflow-y-auto p-2 sm:p-3">
<?php if (session()->getFlashdata('success')): ?>
<div class="mb-2 p-2 rounded border border-green-200 bg-green-50 text-green-800 text-[11px]"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<div class="mb-2 flex flex-wrap gap-2">
<?php foreach ($notices as $n): ?>
<div class="flex-1 min-w-[200px] flex items-center gap-2 bg-amber-50 border border-amber-200 text-amber-900 px-2 py-1 rounded text-[11px]">
<i class="fa-solid fa-bullhorn shrink-0"></i>
<span class="truncate" title="<?= esc($n) ?>"><?= esc($n) ?></span>
</div>
<?php endforeach; ?>
</div>
<div class="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-1.5 mb-2">
<?php foreach ($kpiTop as $k): ?>
<div class="bg-white border border-gray-200 rounded px-2 py-1.5 flex items-center gap-2 shadow-sm">
<div class="w-8 h-8 rounded <?= $k['bg'] ?> <?= $k['c'] ?> flex items-center justify-center shrink-0 text-sm">
<i class="fa-solid <?= esc($k['icon'], 'attr') ?>"></i>
</div>
<div class="min-w-0">
<div class="text-base font-bold text-gray-900 leading-tight"><?= esc($k['v']) ?></div>
<div class="text-[10px] text-gray-500 leading-tight"><?= esc($k['l']) ?></div>
<div class="text-[9px] text-gray-400"><?= esc($k['sub']) ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- dense: 재고 / 발주·신청 / 로그+스파크 -->
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2 mb-2">
<section class="xl:col-span-4 bg-white border border-gray-200 rounded shadow-sm overflow-hidden">
<div class="px-2 py-1 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
<h2 class="text-[11px] font-bold text-gray-800"><i class="fa-solid fa-warehouse text-[#2b4c8c] mr-1"></i>품목별 재고·소진예상</h2>
<a href="<?= base_url('bag/inventory-inquiry') ?>" class="text-[10px] text-blue-600 hover:underline">상세</a>
</div>
<div class="overflow-x-auto max-h-[200px] overflow-y-auto">
<table class="w-full dense-table text-left">
<thead>
<tr>
<th>품목</th>
<th class="text-right">재고(장)</th>
<th>상태</th>
<th class="text-right">소진</th>
</tr>
</thead>
<tbody>
<?php foreach ($stockRows as $r): ?>
<tr>
<td class="font-medium text-gray-800"><?= esc($r[0]) ?></td>
<td class="text-right tabular-nums"><?= esc($r[1]) ?></td>
<td>
<?php
$badge = match ($r[2]) {
'안전' => 'bg-emerald-100 text-emerald-800',
'주의' => 'bg-amber-100 text-amber-800',
'부족' => 'bg-red-100 text-red-800',
default => 'bg-gray-100 text-gray-700',
};
?>
<span class="text-[10px] px-1 py-0 rounded <?= $badge ?>"><?= esc($r[2]) ?></span>
</td>
<td class="text-right text-gray-600"><?= esc($r[3]) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<section class="xl:col-span-4 bg-white border border-gray-200 rounded shadow-sm overflow-hidden">
<div class="px-2 py-1 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
<h2 class="text-[11px] font-bold text-gray-800"><i class="fa-solid fa-list-check text-[#2b4c8c] mr-1"></i>발주 / 구매신청 진행</h2>
<span class="text-[10px] text-gray-500">최근 5건</span>
</div>
<div class="overflow-x-auto max-h-[200px] overflow-y-auto">
<table class="w-full dense-table text-left">
<thead>
<tr>
<th>문서</th>
<th>상대</th>
<th>내용</th>
<th>단계</th>
<th class="text-right">시각</th>
</tr>
</thead>
<tbody>
<?php foreach ($orderRows as $r): ?>
<tr>
<td class="text-blue-700 font-mono text-[10px]"><?= esc($r[0]) ?></td>
<td class="truncate max-w-[4.5rem]" title="<?= esc($r[1]) ?>"><?= esc($r[1]) ?></td>
<td class="truncate max-w-[5rem]" title="<?= esc($r[2]) ?>"><?= esc($r[2]) ?></td>
<td><span class="text-[10px] bg-slate-100 px-1 rounded"><?= esc($r[3]) ?></span></td>
<td class="text-right text-gray-500 text-[10px] whitespace-nowrap"><?= esc($r[4]) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<section class="xl:col-span-4 bg-white border border-gray-200 rounded shadow-sm overflow-hidden flex flex-col">
<div class="px-2 py-1 border-b border-gray-200 bg-gray-50">
<h2 class="text-[11px] font-bold text-gray-800"><i class="fa-solid fa-clock-rotate-left text-[#2b4c8c] mr-1"></i>최근 이벤트 로그</h2>
</div>
<ul class="flex-1 max-h-[120px] overflow-y-auto divide-y divide-gray-100 px-2 py-0.5">
<?php foreach ($logRows as $L): ?>
<li class="py-1 flex gap-2 text-[10px]">
<span class="text-gray-400 font-mono shrink-0 w-8"><?= esc($L[0]) ?></span>
<span class="shrink-0 w-12 text-center rounded bg-gray-100 text-gray-600"><?= esc($L[1]) ?></span>
<span class="text-gray-700"><?= $L[2] ?></span>
</li>
<?php endforeach; ?>
</ul>
<div class="border-t border-gray-100 px-2 py-1.5 bg-gray-50/80">
<div class="text-[10px] font-semibold text-gray-600 mb-1">주간 봉투 출고(천 장, 목업)</div>
<div class="spark" title="주간 추이">
<?php foreach ([40, 55, 48, 62, 58, 71, 65] as $h): ?>
<span style="height: <?= (int) $h ?>%"></span>
<?php endforeach; ?>
</div>
<div class="flex justify-between text-[9px] text-gray-400 mt-0.5">
<span>월</span><span>화</span><span>수</span><span>목</span><span>금</span><span>토</span><span>일</span>
</div>
</div>
</section>
</div>
<!-- charts: 요약 차트 4종 (dense 행 아래) -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-2 mb-2">
<section class="chart-card">
<h2><i class="fa-solid fa-chart-pie text-[#2b4c8c] mr-1"></i>규격 출고 비중</h2>
<div class="chart-wrap"><canvas id="chDoughnutSpec"></canvas></div>
</section>
<section class="chart-card">
<h2><i class="fa-solid fa-circle-notch text-[#2b4c8c] mr-1"></i>구매신청 처리 단계</h2>
<div class="chart-wrap"><canvas id="chDoughnutFlow"></canvas></div>
</section>
<section class="chart-card">
<h2><i class="fa-solid fa-calendar-week text-[#2b4c8c] mr-1"></i>금주 일별 출고(천장)</h2>
<div class="chart-wrap"><canvas id="chBarWeek"></canvas></div>
</section>
<section class="chart-card">
<h2><i class="fa-solid fa-bullseye text-[#2b4c8c] mr-1"></i>운영 지표 (목업)</h2>
<div class="chart-wrap"><canvas id="chRadar"></canvas></div>
</section>
</div>
<section class="chart-card mb-2">
<h2><i class="fa-solid fa-chart-line text-[#2b4c8c] mr-1"></i>월별 출고 vs 구매신청 건수 (최근 12개월)</h2>
<div class="chart-wrap tall"><canvas id="chLineYear"></canvas></div>
</section>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2 mb-2">
<section class="chart-card">
<h2><i class="fa-solid fa-boxes-stacked text-[#2b4c8c] mr-1"></i>품목별 재고 (천 장)</h2>
<div class="chart-wrap wide"><canvas id="chBarSku"></canvas></div>
</section>
<section class="chart-card">
<h2><i class="fa-solid fa-store text-[#2b4c8c] mr-1"></i>판매소별 월 출고 TOP</h2>
<div class="chart-wrap wide"><canvas id="chBarHStore"></canvas></div>
</section>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2 mb-2">
<section class="bg-white border border-gray-200 rounded shadow-sm overflow-hidden">
<div class="px-2 py-1 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
<h2 class="text-[11px] font-bold text-gray-800"><i class="fa-solid fa-store text-[#2b4c8c] mr-1"></i>지정판매소 요약</h2>
<span class="text-[10px] text-gray-500">상위 5곳</span>
</div>
<table class="w-full dense-table">
<thead>
<tr>
<th>판매소명</th>
<th class="text-right">월 봉투(백장)</th>
<th>상태</th>
<th class="text-right">최종거래</th>
</tr>
</thead>
<tbody>
<?php foreach ($storeSummary as $s): ?>
<tr>
<td class="font-medium"><?= esc($s[0]) ?></td>
<td class="text-right tabular-nums"><?= esc($s[1]) ?></td>
<td>
<?php if ($s[2] === '정상'): ?>
<span class="text-[10px] text-emerald-700"><?= esc($s[2]) ?></span>
<?php else: ?>
<span class="text-[10px] text-red-600"><?= esc($s[2]) ?></span>
<?php endif; ?>
</td>
<td class="text-right text-gray-500"><?= esc($s[3]) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
<section class="bg-white border border-gray-200 rounded shadow-sm overflow-hidden">
<div class="px-2 py-1 border-b border-gray-200 bg-gray-50">
<h2 class="text-[11px] font-bold text-gray-800"><i class="fa-solid fa-user-check text-[#2b4c8c] mr-1"></i>회원·판매소 승인 대기</h2>
</div>
<div class="grid grid-cols-3 gap-1 p-1.5 border-b border-gray-100 bg-gray-50/50 text-center">
<div>
<div class="text-lg font-bold text-[#2b4c8c]">4</div>
<div class="text-[9px] text-gray-500">전체 대기</div>
</div>
<div>
<div class="text-lg font-bold text-amber-600">2</div>
<div class="text-[9px] text-gray-500">오늘 접수</div>
</div>
<div>
<div class="text-lg font-bold text-gray-600">1.2일</div>
<div class="text-[9px] text-gray-500">평균 처리</div>
</div>
</div>
<table class="w-full dense-table">
<thead>
<tr>
<th>신청자</th>
<th>유형</th>
<th>접수일</th>
<th>메모</th>
</tr>
</thead>
<tbody>
<?php foreach ($approvals as $a): ?>
<tr>
<td><?= esc($a[0]) ?></td>
<td><?= esc($a[1]) ?></td>
<td class="text-gray-600"><?= esc($a[2]) ?></td>
<td class="text-gray-500 truncate max-w-[6rem]"><?= esc($a[3]) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2 mb-2">
<section class="chart-card">
<h2><i class="fa-solid fa-layer-group text-[#2b4c8c] mr-1"></i>분기별 입고 / 출고 / 조정</h2>
<div class="chart-wrap wide"><canvas id="chStackedQ"></canvas></div>
</section>
<section class="chart-card">
<h2><i class="fa-solid fa-compass text-[#2b4c8c] mr-1"></i>요일·시간대 신청 분포</h2>
<div class="chart-wrap wide"><canvas id="chPolarTime"></canvas></div>
</section>
</div>
<div class="bg-white border border-gray-200 rounded shadow-sm p-2 mb-2">
<h3 class="text-[11px] font-bold text-gray-800 mb-1"><i class="fa-solid fa-clipboard-list text-[#2b4c8c] mr-1"></i>운영 브리핑 · 추가 그래프</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
<ul class="text-[11px] text-gray-600 space-y-0.5 list-disc list-inside">
<li>다음 주 예상 구매신청 <strong class="text-gray-900">약 28건</strong> (전주 대비 +12%)</li>
<li><strong class="text-red-700">일반 20L</strong>·<strong class="text-red-700">특수규격 A</strong> 발주 권고</li>
<li>세금계산서 <strong>6건</strong> 미발행 — 회계 알림 발송됨</li>
</ul>
<div class="chart-card border-0 shadow-none">
<h2 class="rounded-t"><i class="fa-solid fa-chart-area text-[#2b4c8c] mr-1"></i>누적 출고 추이 (올해)</h2>
<div class="chart-wrap"><canvas id="chAreaCum"></canvas></div>
</div>
</div>
</div>
<p class="text-center text-[10px] text-gray-400">
<strong class="text-gray-600">/dashboard/blend</strong> (표 + 차트 혼합)
· <a href="<?= esc($dashDense) ?>" class="text-[#2b4c8c] hover:underline">/dashboard/dense</a>
· <a href="<?= esc($dashCharts) ?>" class="text-[#2b4c8c] hover:underline">/dashboard/charts</a>
· <a href="<?= esc($dashClassic) ?>" class="text-[#2b4c8c] hover:underline">클래식</a>
</p>
</main>
<script>
(function () {
const C = {
primary: '#2b4c8c',
blue: '#3b82f6',
teal: '#0d9488',
emerald: '#059669',
amber: '#d97706',
rose: '#e11d48',
violet: '#7c3aed',
slate: '#64748b',
grid: 'rgba(0,0,0,.06)',
};
Chart.defaults.font.family = "'Malgun Gothic','Apple SD Gothic Neo','Noto Sans KR',sans-serif";
Chart.defaults.font.size = 11;
Chart.defaults.color = '#4b5563';
const commonOpts = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 10, padding: 8, font: { size: 10 } } },
},
};
const axisOpts = {
scales: {
x: { grid: { color: C.grid }, ticks: { maxRotation: 45, minRotation: 0, font: { size: 10 } } },
y: { grid: { color: C.grid }, ticks: { font: { size: 10 } }, beginAtZero: true },
},
};
new Chart(document.getElementById('chDoughnutSpec'), {
type: 'doughnut',
data: {
labels: ['일반 5L', '일반 10·20L', '음식물 스티커', '재사용', '기타'],
datasets: [{
data: [38, 22, 28, 9, 3],
backgroundColor: [C.primary, C.blue, C.teal, C.emerald, C.slate],
borderWidth: 0,
}],
},
options: { ...commonOpts, cutout: '58%' },
});
new Chart(document.getElementById('chDoughnutFlow'), {
type: 'doughnut',
data: {
labels: ['접수', '검토', '발주', '납품', '완료'],
datasets: [{
data: [12, 8, 6, 5, 42],
backgroundColor: [C.amber, C.blue, C.violet, C.teal, C.emerald],
borderWidth: 0,
}],
},
options: { ...commonOpts, cutout: '55%' },
});
new Chart(document.getElementById('chBarWeek'), {
type: 'bar',
data: {
labels: ['월', '화', '수', '목', '금', '토', '일'],
datasets: [{
label: '출고',
data: [42, 55, 48, 61, 58, 22, 8],
backgroundColor: C.primary,
borderRadius: 4,
}],
},
options: { ...commonOpts, ...axisOpts, plugins: { ...commonOpts.plugins, legend: { display: false } } },
});
new Chart(document.getElementById('chRadar'), {
type: 'radar',
data: {
labels: ['재고안정', '신청처리', '납기준수', '민원응대', '데이터품질'],
datasets: [
{
label: '이번 달',
data: [82, 76, 88, 71, 85],
borderColor: C.primary,
backgroundColor: 'rgba(43, 76, 140, 0.2)',
pointBackgroundColor: C.primary,
},
{
label: '전월',
data: [78, 80, 84, 75, 80],
borderColor: C.blue,
backgroundColor: 'rgba(59, 130, 246, 0.12)',
pointBackgroundColor: C.blue,
},
],
},
options: {
...commonOpts,
scales: {
r: {
beginAtZero: true,
max: 100,
ticks: { stepSize: 20, font: { size: 9 } },
grid: { color: C.grid },
pointLabels: { font: { size: 10 } },
},
},
},
});
new Chart(document.getElementById('chLineYear'), {
type: 'line',
data: {
labels: ['3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월', '1월', '2월'],
datasets: [
{
label: '출고(천 장)',
data: [320, 340, 310, 355, 380, 360, 370, 390, 400, 385, 410, 395],
borderColor: C.primary,
backgroundColor: 'rgba(43, 76, 140, 0.08)',
fill: true,
tension: 0.35,
pointRadius: 3,
},
{
label: '구매신청(건)',
data: [118, 125, 112, 130, 142, 128, 135, 140, 155, 148, 160, 152],
borderColor: C.teal,
backgroundColor: 'transparent',
tension: 0.35,
yAxisID: 'y1',
pointRadius: 3,
},
],
},
options: {
...commonOpts,
...axisOpts,
scales: {
x: axisOpts.scales.x,
y: { type: 'linear', position: 'left', grid: { color: C.grid }, title: { display: true, text: '출고', font: { size: 10 } } },
y1: {
type: 'linear',
position: 'right',
grid: { drawOnChartArea: false },
title: { display: true, text: '건수', font: { size: 10 } },
beginAtZero: true,
},
},
},
});
new Chart(document.getElementById('chBarSku'), {
type: 'bar',
data: {
labels: ['5L', '10L', '20L', '스티커', '재사용', '특수'],
datasets: [{
label: '재고',
data: [12.4, 8.2, 2.1, 15.0, 4.3, 0.9],
backgroundColor: [C.primary, C.blue, C.amber, C.teal, C.emerald, C.rose],
borderRadius: 4,
}],
},
options: {
...commonOpts,
...axisOpts,
indexAxis: 'x',
plugins: { ...commonOpts.plugins, legend: { display: false } },
},
});
new Chart(document.getElementById('chBarHStore'), {
type: 'bar',
data: {
labels: ['행복마트 북구', '◇◇할인점', '□□마트', '○○슈퍼', '△△상회'],
datasets: [{
label: '천 장',
data: [5.2, 4.8, 3.9, 3.5, 2.1],
backgroundColor: C.primary,
borderRadius: 4,
}],
},
options: {
...commonOpts,
indexAxis: 'y',
plugins: { ...commonOpts.plugins, legend: { display: false } },
scales: {
x: { grid: { color: C.grid }, beginAtZero: true, ticks: { font: { size: 10 } } },
y: { grid: { display: false }, ticks: { font: { size: 10 } } },
},
},
});
new Chart(document.getElementById('chStackedQ'), {
type: 'bar',
data: {
labels: ['1분기', '2분기', '3분기', '4분기(예)'],
datasets: [
{ label: '입고', data: [420, 450, 480, 460], backgroundColor: C.emerald, stack: 's' },
{ label: '출고', data: [380, 410, 440, 430], backgroundColor: C.primary, stack: 's' },
{ label: '조정', data: [12, 8, 15, 10], backgroundColor: C.amber, stack: 's' },
],
},
options: {
...commonOpts,
scales: {
x: { stacked: true, grid: { display: false }, ticks: { font: { size: 10 } } },
y: { stacked: true, grid: { color: C.grid }, ticks: { font: { size: 10 } }, beginAtZero: true },
},
},
});
new Chart(document.getElementById('chAreaCum'), {
type: 'line',
data: {
labels: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
datasets: [{
label: '누적 출고(만 장)',
data: [3.2, 6.8, 10.5, 14.2, 18.0, 21.5, 25.1, 28.9, 32.4, 36.0, 39.8, 43.5],
borderColor: C.blue,
backgroundColor: 'rgba(59, 130, 246, 0.25)',
fill: true,
tension: 0.4,
pointRadius: 0,
}],
},
options: { ...commonOpts, ...axisOpts, plugins: { ...commonOpts.plugins, legend: { display: true } } },
});
new Chart(document.getElementById('chPolarTime'), {
type: 'polarArea',
data: {
labels: ['평일 오전', '평일 오후', '평일 야간', '주말'],
datasets: [{
data: [28, 45, 8, 19],
backgroundColor: [
'rgba(43, 76, 140, 0.75)',
'rgba(13, 148, 136, 0.7)',
'rgba(217, 119, 6, 0.65)',
'rgba(124, 58, 237, 0.65)',
],
borderWidth: 1,
borderColor: '#fff',
}],
},
options: {
...commonOpts,
scales: {
r: {
ticks: { backdropColor: 'transparent', font: { size: 9 } },
grid: { color: C.grid },
pointLabels: { font: { size: 10 } },
},
},
},
});
})();
</script>
</body>
</html>