Files
2026-07-23 16:15:57 +09:00

246 lines
8.0 KiB
PHP

<section class="card yearly">
<div class="card-header">
<h3>연간 판매 실적<span class="tail"></span></h3>
</div>
<div class="card-body">
<!-- 누적 판매수 -->
<div class="stat-row accrue">
<div class="label-box">
<span class="label">누적 판매수</span>
<span class="change-up">전월대비 <em id="year_compare_copy">0</em></span>
</div>
<div>
<span class="big-num" id="year_total_copy">0</span>
<span class="unit copy">copy</span>
</div>
</div>
<!-- 누적 판매액 -->
<div class="stat-row">
<div class="label-box">
<span class="label">누적 판매액</span>
<span class="change">전월대비 <em id="year_compare_amount">0</em></span>
</div>
<div>
<span class="big-num" id="year_total_amount">0.0</span>
<span class="unit">만원</span>
</div>
</div>
<!-- 목표 달성률 -->
<div class="stat-row">
<div class="label-box">
<span class="label">목표 달성률</span>
<span class="change-up">전월대비 <em id="year_compare_rate">0</em></span>
</div>
<div>
<span class="big-num" id="year_target_rate">0</span>
<span class="unit">%</span>
</div>
</div>
</div>
<div class="chart-wrap">
<div class="chart-header">
<span class="badge-year">목표 <span id="year_target_qty">0</span></span>
<div class="legend">
<span><span class="legend-dot mix-bar"></span>월별 판매수</span>
<span><span class="legend-dot mix-line"></span>누적 판매수</span>
<span class="unit">(단위:copy) </span>
</div>
</div>
<div class="chart-body">
<canvas id="yearly_chart"></canvas>
</div>
</div>
</section>
<script>
(() => {
// -------------------------------
// 1) 기본값 초기화 (시작 시 반드시 0으로 렌더)
// -------------------------------
const currentMonth = String(new Date().getMonth() + 1);
window.yearlyMonthly = Array(12).fill(0);
window.yearlyCumulative = Array(12).fill(0);
const labels12 = Array.from({ length: 12 }, (_, i) => String(i + 1));
let yearlyInstance = null;
// -------------------------------
// 4) API 로딩 → 데이터 반영
// -------------------------------
function loadYearlyDashboardData() {
return fetch(`/egbim/bbs/sales_dashboard.php?year=${window.APP_YEAR}&_=${Date.now()}`)
.then(res => res.json())
.then(data => {
const monthlyArr = [];
for (let i = 1; i <= 12; i++) {
monthlyArr.push(data.monthly[i] ?? 0);
}
const cumulativeArr = [];
monthlyArr.reduce((acc, cur) => {
cumulativeArr.push(acc + cur);
return acc + cur;
}, 0);
const lastIdx = monthlyArr.reduce((l, v, i) => v > 0 ? i : l, -1);
window.yearlyMonthly = monthlyArr;
window.yearlyCumulative = cumulativeArr.map((v, i) => i <= lastIdx ? v : null);
// 수치 업데이트
year_target_rate.textContent = data.yearly_rate;
year_target_qty.textContent = data.yearly_target_qty;
year_total_copy.textContent = data.total_copy;
year_total_amount.textContent = Math.round(data.total_amount / 10000);
updateDiff("year_compare_copy", data.diff_copy);
updateDiff("year_compare_amount", Math.floor(data.diff_amount / 10000));
updateDiff("year_compare_rate", Math.round(data.diff_rate * 10) / 10);
// 🔥 차트 갱신
createYearlyChart();
});
}
window.loadYearlyDashboardData = loadYearlyDashboardData;
loadYearlyDashboardData();
// -------------------------------
// 2) 차트 생성 / 갱신 함수
// -------------------------------
function createYearlyChart() {
const canvas = document.getElementById("yearly_chart");
if (!canvas) return;
// ✅ 최초 1회만 생성
if (!yearlyInstance) {
yearlyInstance = new Chart(canvas, {
type: "bar",
data: {
labels: labels12,
datasets: [
{
type: "bar",
order: 2,
data: window.yearlyMonthly,
backgroundColor: cssVar("--chart-mixbar"),
borderRadius: { topLeft: 2, topRight: 2 },
},
{
type: "line",
order: 1,
data: window.yearlyCumulative,
borderColor: cssVar("--chart-mixline"),
borderWidth: 1,
pointRadius: 4,
fill: false,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: null },
plugins: {
legend: { display: false },
tooltip: { enabled: false },
},
layout: {
padding: { top: 16 },
},
scales: {
y: {
afterBuildTicks: (scale) => {
const allValues = scale.chart.data.datasets
.flatMap((d) => d.data)
.filter((v) => typeof v === "number");
const maxVal = Math.max(...allValues, 0);
const rawMax = maxVal + 50;
const targetMax = Math.ceil(rawMax / 100) * 100;
const midVal = Math.round(targetMax / 2);
scale.max = targetMax;
scale.min = 0;
scale.ticks = [
{ value: 0 },
{ value: midVal },
{ value: targetMax },
];
scale._midIndex = 1;
},
grid: {
drawTicks: false,
color: (ctx) =>
ctx.index === ctx.scale._midIndex
? cssVar("--grid-border-dashed")
: "rgba(0,0,0,0)",
lineWidth: (ctx) =>
ctx.index === ctx.scale._midIndex ? 1 : 0,
},
border: { display: false },
ticks: {
padding: 4,
color: cssVar("--chart-base"),
},
},
x: {
grid: {
drawTicks: false,
display: true,
drawOnChartArea: true,
color: (ctx) =>
ctx.index === 0 || ctx.index == null
? "rgba(0,0,0,0)"
: cssVar("--grid-border-dashed"),
lineWidth: (ctx) =>
ctx.index === 0 || ctx.index == null ? 0 : 1,
},
ticks: {
padding: 10,
color: (ctx) =>
ctx.tick.label === currentMonth
? cssVar("--chart-point")
: cssVar("--chart-base"),
font: (ctx) =>
ctx.tick.label === currentMonth
? { weight: "bold", size: 18 }
: { weight: "normal", size: 18 },
},
border: {
display: true,
dash: [3, 3],
color: cssVar("--grid-border-dashed"),
width: 1,
},
},
},
},
plugins: [
createMixedValuePlugin(12),
createMixedPointGradientPlugin(),
highlightXFactory(currentMonth),
hideNullPointsPlugin,
],
});
}
// 🔥 여기만 추가됨 (기존 destroy 제거)
else {
yearlyInstance.data.datasets[0].data = window.yearlyMonthly;
yearlyInstance.data.datasets[1].data = window.yearlyCumulative;
yearlyInstance.update();
}
}
// -------------------------------
// 3) 초기 렌더
// -------------------------------
createYearlyChart();
loadYearlyDashboardData(); // 최초 데이터
})();
</script>