<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enzycept Plus Solution - Sales Dashboard</title>
<script>
(function() {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
})();
</script>
<script src="https://cdn.plot.ly/plotly-3.3.0.min.js"></script>
<style>
/* ===== CSS 변수 (저명도 다크모드 적용) ===== */
:root {
--bg-color: #ffffff;
--text-color: #212529;
--header-bg: #f8f9fa;
--border-color: #dee2e6;
--card-bg: #ffffff;
--info-header: #4FC3F7;
}
[data-theme="dark"] {
--bg-color: #0d1117;
--text-color: #b0b8c1; /* 명도를 낮춘 부드러운 텍스트 */
--header-bg: #161b22;
--border-color: #30363d;
--card-bg: #1c2128;
--info-header: #4FC3F7;
}
/* ===== 레이아웃 최적화 (넘침 방지) ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box; /* 패딩이 너비에 포함되도록 설정 */
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.2s ease, color 0.2s ease;
overflow-x: hidden; /* 가로 스크롤 방지 */
}
.header {
background-color: var(--header-bg);
padding: 15px 25px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.theme-toggle {
background: var(--card-bg);
border: 1px solid var(--border-color);
color: var(--text-color);
cursor: pointer;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.85rem;
transition: all 0.2s;
}
.container {
width: 100%;
max-width: 100%; /* Toledo 사이드바 환경에 맞게 유연하게 설정 */
margin: 0 auto;
padding: 20px;
}
.info-box {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 18px;
margin-bottom: 20px;
}
.info-box h3 {
margin-bottom: 6px;
color: var(--info-header);
}
/* 차트 컨테이너 넘침 방지 설정 */
#dashboard-plot {
width: 100%;
max-width: 100%;
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 10px;
overflow: hidden; /* 내부 요소가 튀어나오지 않게 함 */
}
kbd {
background: var(--header-bg);
border: 1px solid var(--border-color);
padding: 2px 5px;
border-radius: 3px;
font-size: 0.75rem;
}
</style>
</head>
<body>
<div class="header">
<h1>📊 Sales Analysis Dashboard</h1>
<button class="theme-toggle" onclick="toggleTheme()" id="themeBtn">🌙 Dark Mode</button>
</div>
<div class="container">
<div class="info-box">
<h3>Dashboard Overview</h3>
<p>Enzycept Plus Solution (2023-2025). <kbd>Ctrl+Shift+D</kbd>로 테마 전환 가능.</p>
</div>
<div id="dashboard-plot"></div>
</div>
<script type="text/javascript">
// 데이터 정의
const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const sales2023 = [3.90, 3.37, 4.37, 2.14, 2.62, 3.29, 6.65, 1.56, 5.23, 3.85, 3.29, 5.12];
const sales2024 = [3.56, 6.10, 1.97, 5.03, 5.48, 3.43, 6.66, 4.08, 4.21, 2.96, 6.96, 9.89];
const sales2025 = [13.37, 6.42, 9.16, 12.96, 5.08, 10.75, 6.66, 21.22, 8.83, 16.98, 7.93, 3.69];
const qty2023 = [294, 234, 307, 140, 188, 225, 485, 99, 362, 315, 240, 357];
const qty2024 = [264, 440, 134, 366, 384, 260, 419, 287, 257, 144, 464, 1019];
const qty2025 = [1169, 456, 831, 1120, 398, 970, 499, 2143, 617, 1670, 567, 216];
function getTraces(isDark) {
const textColor = isDark ? '#b0b8c1' : '#212529';
const tableBg = isDark ? '#1c2128' : '#ffffff';
const tableLine = isDark ? '#30363d' : '#dee2e6';
return [
{ x: months, y: sales2023, name: '23 Sales', type: 'bar', marker: {color: '#4FC3F7'}, xaxis: 'x', yaxis: 'y' },
{ x: months, y: sales2024, name: '24 Sales', type: 'bar', marker: {color: '#66BB6A'}, xaxis: 'x', yaxis: 'y' },
{ x: months, y: sales2025, name: '25 Sales', type: 'bar', marker: {color: '#FF7043'}, xaxis: 'x', yaxis: 'y' },
{ x: months, y: qty2023, name: '23 Qty', type: 'bar', marker: {color: '#4FC3F7', opacity: 0.6}, xaxis: 'x2', yaxis: 'y2', showlegend: false },
{ x: months, y: qty2024, name: '24 Qty', type: 'bar', marker: {color: '#66BB6A', opacity: 0.6}, xaxis: 'x2', yaxis: 'y2', showlegend: false },
{ x: months, y: qty2025, name: '25 Qty', type: 'bar', marker: {color: '#FF7043', opacity: 0.6}, xaxis: 'x2', yaxis: 'y2', showlegend: false },
{ x: ['2023', '2024', '2025'], y: [45.4, 60.3, 123.0], type: 'bar', marker: {color: ['#4FC3F7', '#66BB6A', '#FF7043']}, xaxis: 'x3', yaxis: 'y3', showlegend: false },
{
type: 'table',
domain: { x: [0.55, 1.0], y: [0, 0.44] },
header: {
values: [["<b>Metric</b>"], ["<b>2023</b>"], ["<b>2024</b>"], ["<b>2025</b>"]],
align: "center", fill: {color: isDark ? '#30363d' : '#4FC3F7'},
font: {color: "white", size: 12}, line: {color: tableLine}
},
cells: {
values: [["Sales", "Qty", "Growth"], ["45.4M", "3,246", "-"], ["60.3M", "4,438", "+33%"], ["123.0M", "10,656", "+104%"]],
align: ["left", "right"], fill: {color: tableBg},
font: {color: textColor, size: 11}, line: {color: tableLine}, height: 28
}
}
];
}
function applyTheme(theme) {
const isDark = (theme === 'dark');
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
document.getElementById('themeBtn').textContent = isDark ? '☀️ Light Mode' : '🌙 Dark Mode';
const layout = {
height: 900,
autosize: true, // 컨테이너 크기에 맞춤
paper_bgcolor: isDark ? '#1c2128' : '#ffffff',
plot_bgcolor: isDark ? '#1c2128' : '#f8f9fa',
font: { color: isDark ? '#b0b8c1' : '#212529', family: 'Segoe UI' },
grid: { rows: 2, columns: 2, pattern: 'independent' },
xaxis: { title: 'Sales Trend', gridcolor: isDark ? '#30363d' : '#dee2e6' },
yaxis: { title: 'M KRW', gridcolor: isDark ? '#30363d' : '#dee2e6' },
xaxis2: { title: 'Quantity Trend', gridcolor: isDark ? '#30363d' : '#dee2e6' },
yaxis2: { title: 'ea', gridcolor: isDark ? '#30363d' : '#dee2e6' },
xaxis3: { title: 'Annual Comparison', gridcolor: isDark ? '#30363d' : '#dee2e6' },
yaxis3: { title: 'M KRW', gridcolor: isDark ? '#30363d' : '#dee2e6' },
margin: { t: 50, b: 80, l: 50, r: 20 },
legend: { orientation: 'h', x: 0.5, xanchor: 'center', y: -0.1 }
};
Plotly.react('dashboard-plot', getTraces(isDark), layout, {responsive: true, displayModeBar: false});
}
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
applyTheme(current === 'light' ? 'dark' : 'light');
}
document.addEventListener('DOMContentLoaded', () => {
applyTheme(localStorage.getItem('theme') || 'dark');
});
// 창 크기가 변경될 때 차트 크기 재조정
window.addEventListener('resize', () => {
Plotly.Plots.resize('dashboard-plot');
});
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'D') {
e.preventDefault();
toggleTheme();
}
});
</script>
</body>
</html>