333 lines
14 KiB
PHP
333 lines
14 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* 라이트(축약) 대시보드 본문 — `bag/layout/main`에 삽입.
|
|||
|
|
*
|
|||
|
|
* `dashboard_blend_inner` 기반으로 다음을 제거:
|
|||
|
|
* - KPI 카드: 「회원승인 대기」, 「지정판매소 등록」
|
|||
|
|
* - 상단 표 그리드 중: 「최근 이벤트 로그」(주간 스파크 포함)
|
|||
|
|
* - 차트 중: 도넛/주간 막대/레이더/분기 스택/요일 폴라/누적 영역
|
|||
|
|
* - 하단 보조 영역: 지정판매소 요약·승인 대기 표·운영 브리핑
|
|||
|
|
* 남기는 그래프 3종:
|
|||
|
|
* 1) 월별 출고 vs 구매신청 건수 (최근 12개월)
|
|||
|
|
* 2) 품목별 재고 (천 장)
|
|||
|
|
* 3) 판매소별 월 출고 TOP
|
|||
|
|
*
|
|||
|
|
* @var string $lgLabel
|
|||
|
|
*/
|
|||
|
|
$lgLabel = $lgLabel ?? '북구';
|
|||
|
|
$mbName = session()->get('mb_name') ?? '담당자';
|
|||
|
|
$dashHome = base_url('dashboard');
|
|||
|
|
$dashBlend = base_url('dashboard/blend');
|
|||
|
|
$dashLite = base_url('dashboard/lite');
|
|||
|
|
|
|||
|
|
// KPI: 회원승인/지정판매소 등록을 제외한 6칸.
|
|||
|
|
$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-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'],
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$notices = [
|
|||
|
|
'2월 말 정기 재고 실사 안내 — 2/28 17:00 마감',
|
|||
|
|
'봉투 단가 조정 예고 — 3/1 적용 예정 (안내문 배포 완료)',
|
|||
|
|
];
|
|||
|
|
?>
|
|||
|
|
<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>
|
|||
|
|
.blend-dash-lite {
|
|||
|
|
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
.blend-dash-lite .dense-table th, .blend-dash-lite .dense-table td { padding: 0.25rem 0.4rem; line-height: 1.25; }
|
|||
|
|
.blend-dash-lite .dense-table thead th { font-size: 11px; font-weight: 600; color: #555; background: #f3f4f6; border-bottom: 1px solid #d1d5db; }
|
|||
|
|
.blend-dash-lite .dense-table tbody td { border-bottom: 1px solid #eee; font-size: 11px; }
|
|||
|
|
.blend-dash-lite .chart-card {
|
|||
|
|
background: #fff;
|
|||
|
|
border: 1px solid #e5e7eb;
|
|||
|
|
border-radius: 0.25rem;
|
|||
|
|
box-shadow: 0 1px 2px rgba(0,0,0,.04);
|
|||
|
|
}
|
|||
|
|
.blend-dash-lite .chart-card h2 {
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
color: #1f2937;
|
|||
|
|
padding: 0.4rem 0.5rem;
|
|||
|
|
border-bottom: 1px solid #f3f4f6;
|
|||
|
|
background: #fafafa;
|
|||
|
|
}
|
|||
|
|
.blend-dash-lite .chart-wrap { position: relative; height: 220px; padding: 0.4rem 0.5rem 0.5rem; }
|
|||
|
|
.blend-dash-lite .chart-wrap.tall { height: 280px; }
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<div class="blend-dash-lite bg-[#f0f2f5] -mx-4 -my-4 p-2 sm:p-3 min-h-full">
|
|||
|
|
<div class="bg-gradient-to-r from-[#eff5fb] to-[#e8eef8] border border-gray-300 rounded-sm px-3 py-1 flex flex-wrap items-center justify-between gap-2 text-[11px] mb-2">
|
|||
|
|
<span class="font-semibold text-gray-800">
|
|||
|
|
<i class="fa-solid fa-gauge-simple-high text-[#2b4c8c] mr-1"></i>업무 현황 · 라이트
|
|||
|
|
<span class="font-normal text-gray-500 ml-1">· 핵심 KPI·표 + 그래프 3종</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><?= esc($lgLabel) ?> · <strong class="text-gray-800"><?= esc($mbName) ?></strong></span>
|
|||
|
|
<button type="button" class="bg-[#2b4c8c] text-white px-2 py-0.5 rounded text-[11px]" onclick="location.reload()"><i class="fa-solid fa-rotate mr-0.5"></i>새로고침</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="space-y-2">
|
|||
|
|
<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-3 lg:grid-cols-6 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>
|
|||
|
|
|
|||
|
|
<div class="grid grid-cols-1 xl: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-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-[220px] 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="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-[220px] 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-[6rem]" title="<?= esc($r[1]) ?>"><?= esc($r[1]) ?></td>
|
|||
|
|
<td class="truncate max-w-[8rem]" 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>
|
|||
|
|
</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="liteChLineYear"></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"><canvas id="liteChBarSku"></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"><canvas id="liteChBarHStore"></canvas></div>
|
|||
|
|
</section>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p class="text-center text-[10px] text-gray-400 pb-1">
|
|||
|
|
<a href="<?= esc($dashHome) ?>" class="text-[#2b4c8c] hover:underline">메인 /dashboard</a>
|
|||
|
|
· <a href="<?= esc($dashBlend) ?>" class="text-[#2b4c8c] hover:underline">/dashboard/blend</a>
|
|||
|
|
· <span class="text-gray-700 font-semibold">/dashboard/lite (현재)</span>
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
(function () {
|
|||
|
|
const C = {
|
|||
|
|
primary: '#2b4c8c',
|
|||
|
|
blue: '#3b82f6',
|
|||
|
|
teal: '#0d9488',
|
|||
|
|
emerald: '#059669',
|
|||
|
|
amber: '#d97706',
|
|||
|
|
rose: '#e11d48',
|
|||
|
|
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('liteChLineYear'), {
|
|||
|
|
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,
|
|||
|
|
scales: {
|
|||
|
|
x: axisOpts.scales.x,
|
|||
|
|
y: { type: 'linear', position: 'left', grid: { color: C.grid }, title: { display: true, text: '출고', font: { size: 10 } }, beginAtZero: true },
|
|||
|
|
y1: {
|
|||
|
|
type: 'linear',
|
|||
|
|
position: 'right',
|
|||
|
|
grid: { drawOnChartArea: false },
|
|||
|
|
title: { display: true, text: '건수', font: { size: 10 } },
|
|||
|
|
beginAtZero: true,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
new Chart(document.getElementById('liteChBarSku'), {
|
|||
|
|
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('liteChBarHStore'), {
|
|||
|
|
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 } } },
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
</div>
|