276 lines
11 KiB
PHP
276 lines
11 KiB
PHP
<section class="card individual">
|
|
<div class="chart-wrap">
|
|
<div class="card-header">
|
|
<h3>연간 개인별 판매 실적<span class="tail"></span></h3>
|
|
<div class="legend">
|
|
<span>
|
|
<span class="legend-dot line-area01"></span>
|
|
권혁진 수석
|
|
<span class="badge-individual01"></span>
|
|
</span>
|
|
<span>
|
|
<span class="legend-dot line-area02"></span>
|
|
염승호 수석
|
|
<span class="badge-individual02"></span>
|
|
</span>
|
|
<span class="unit">(단위:copy) </span>
|
|
</div>
|
|
</div>
|
|
<div class="chart-body">
|
|
<!-- <div class="badge-box">
|
|
|
|
|
|
</div> -->
|
|
<canvas id="individual_chart"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="person-box">
|
|
<div class="person-name"><span>권혁진</span> <small>수석</small></div>
|
|
<div class="stat-row individual01">
|
|
<div class="label-box">
|
|
<span class="label">누적 판매수</span>
|
|
<!-- <div class="change-down">전월대비 <em>27</em></div> -->
|
|
</div>
|
|
<div>
|
|
<span class="big-num">162</span><span class="unit copy">copy</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-row">
|
|
<div class="label-box">
|
|
<span class="label">목표 달성률</span>
|
|
<!-- <div class="change-up">전월대비 <em>15p</em></div> -->
|
|
</div>
|
|
<div><span class="big-num">81</span><span class="unit">%</span></div>
|
|
</div>
|
|
</div>
|
|
<div class="person-box">
|
|
<div class="person-name">
|
|
<span>염승호</span>
|
|
<small>수석</small>
|
|
</div>
|
|
<div class="stat-row individual02">
|
|
<div class="label-box">
|
|
<span class="label">누적 판매수</span>
|
|
<!-- <div class="change-up">전월대비 <em>105</em></div> -->
|
|
</div>
|
|
<div>
|
|
<span class="big-num">508</span><span class="unit copy">copy</span>
|
|
</div>
|
|
</div>
|
|
<div class="stat-row">
|
|
<div class="label-box">
|
|
<span class="label">목표 달성률</span>
|
|
<!-- <div class="change-up">전월대비 <em>63p</em></div> -->
|
|
</div>
|
|
<div><span class="big-num">169.3</span><span class="unit">%</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<script>
|
|
(() => {
|
|
|
|
let INDIVIDUAL_DATA = null;
|
|
|
|
// -----------------------------
|
|
// 1) UI 숫자 반영
|
|
// -----------------------------
|
|
function updateIndividualUI() {
|
|
if (!INDIVIDUAL_DATA || INDIVIDUAL_DATA.length < 2) return;
|
|
|
|
const p1 = INDIVIDUAL_DATA[0];
|
|
const p2 = INDIVIDUAL_DATA[1];
|
|
|
|
// 이름
|
|
document.querySelector('.person-box:nth-child(1) .person-name span').textContent = p1.name;
|
|
document.querySelector('.person-box:nth-child(2) .person-name span').textContent = p2.name;
|
|
|
|
// 누적 판매수
|
|
document.querySelector('.individual01 .big-num').textContent = p1.total;
|
|
document.querySelector('.individual02 .big-num').textContent = p2.total;
|
|
|
|
// 달성률
|
|
document.querySelector('.person-box:nth-child(1) .stat-row:nth-child(3) .big-num').textContent = p1.rate;
|
|
document.querySelector('.person-box:nth-child(2) .stat-row:nth-child(3) .big-num').textContent = p2.rate;
|
|
|
|
// 개인별 목표(배지)
|
|
document.querySelector('.badge-individual01').textContent = `목표 ${p1.target_qty}`;
|
|
document.querySelector('.badge-individual02').textContent = `목표 ${p2.target_qty}`;
|
|
}
|
|
|
|
// -----------------------------
|
|
// 2) 차트 생성
|
|
// -----------------------------
|
|
const labels12 = Array.from({ length: 12 }, (_, i) => String(i + 1));
|
|
|
|
const currentMonth = new Date().getMonth() + 1;
|
|
const currentMonthStr = String(currentMonth);
|
|
|
|
let individualInstance = null;
|
|
|
|
function createIndividualChart() {
|
|
const ctx = document.getElementById("individual_chart");
|
|
if (!ctx || !INDIVIDUAL_DATA || INDIVIDUAL_DATA.length < 2) return;
|
|
|
|
if (individualInstance) individualInstance.destroy();
|
|
|
|
individualInstance = new Chart(ctx, {
|
|
type: "line",
|
|
data: {
|
|
labels: labels12,
|
|
datasets: [
|
|
{
|
|
label: "권혁진 수석",
|
|
data: [...INDIVIDUAL_DATA[0].monthly, null],
|
|
fill: true,
|
|
backgroundColor: (ctx) => {
|
|
const a = ctx.chart.chartArea;
|
|
return !a
|
|
? cssVar("--bg-chart-area01")
|
|
: safeGrad(
|
|
ctx.chart.ctx,
|
|
a,
|
|
[
|
|
{ stop: 0, color: cssVar("--bg-chart-area01") },
|
|
{ stop: 1, color: "rgba(255, 255, 255, 0.3)" },
|
|
],
|
|
true
|
|
);
|
|
},
|
|
borderColor: cssVar("--bg-chart-linearea01"),
|
|
borderWidth: 1,
|
|
pointRadius: 4,
|
|
tension: 0,
|
|
},
|
|
{
|
|
label: "염승호 수석",
|
|
data: [...INDIVIDUAL_DATA[1].monthly, null],
|
|
fill: true,
|
|
backgroundColor: (ctx) => {
|
|
const a = ctx.chart.chartArea;
|
|
return !a
|
|
? cssVar("--bg-chart-area02")
|
|
: safeGrad(
|
|
ctx.chart.ctx,
|
|
a,
|
|
[
|
|
{ stop: 0, color: cssVar("--bg-chart-area02") },
|
|
{ stop: 1, color: "rgba(255, 255, 255, 0.3)" },
|
|
],
|
|
true
|
|
);
|
|
},
|
|
borderColor: cssVar("--bg-chart-linearea02"),
|
|
borderWidth: 1,
|
|
pointRadius: 4,
|
|
tension: 0,
|
|
},
|
|
],
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
layout: { padding: { top: 16 } },
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: { enabled: false },
|
|
},
|
|
scales: {
|
|
y: {
|
|
afterBuildTicks: (scale) => {
|
|
const maxValue = scale.chart.data.datasets
|
|
.flatMap((d) => d.data)
|
|
.filter((n) => typeof n === "number");
|
|
|
|
const realMax = Math.max(...maxValue);
|
|
const rawMax = realMax + 50;
|
|
const targetMax = Math.ceil(rawMax / 100) * 100;
|
|
const mid = Math.round(targetMax / 2);
|
|
|
|
scale.max = targetMax;
|
|
scale.min = 0;
|
|
scale.ticks = [
|
|
{ value: 0 },
|
|
{ value: mid },
|
|
{ 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: {
|
|
offset: true,
|
|
grid: {
|
|
drawTicks: false,
|
|
display: true,
|
|
color: cssVar("--grid-border") || "#e0e0e0",
|
|
drawOnChartArea: true,
|
|
z: -1,
|
|
},
|
|
ticks: {
|
|
color: (ctx) =>
|
|
ctx.tick.label === currentMonthStr
|
|
? cssVar("--chart-point")
|
|
: cssVar("--chart-base"),
|
|
font: (ctx) =>
|
|
ctx.tick.label === currentMonthStr
|
|
? { weight: "bold", size: 18 }
|
|
: { weight: "normal", size: 18 },
|
|
padding: 10,
|
|
callback: (_, index) => labels12[index],
|
|
},
|
|
border: {
|
|
display: true,
|
|
dash: [3, 3],
|
|
color: cssVar("--grid-border-dashed"),
|
|
width: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
plugins: [
|
|
highlightXFactory(currentMonthStr),
|
|
hideNullPointsPlugin,
|
|
createPointGradientPluginWithBorder(),
|
|
],
|
|
});
|
|
}
|
|
|
|
// -----------------------------
|
|
// 3) 개인별 데이터 로딩 함수 (🔥 핵심)
|
|
// -----------------------------
|
|
function loadIndividualDashboardData() {
|
|
return fetch(`/egbim/bbs/sales_dashboard.php?year=${window.APP_YEAR}&_=${Date.now()}`)
|
|
.then(r => r.json())
|
|
.then(json => {
|
|
INDIVIDUAL_DATA = json.individual || [];
|
|
updateIndividualUI();
|
|
createIndividualChart();
|
|
});
|
|
}
|
|
|
|
// -----------------------------
|
|
// 4) 전역 등록 + 최초 실행
|
|
// -----------------------------
|
|
window.loadIndividualDashboardData = loadIndividualDashboardData;
|
|
loadIndividualDashboardData();
|
|
|
|
})();
|
|
</script>
|
|
|
|
|