8f3761aeae
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
172 lines
6.8 KiB
HTML
172 lines
6.8 KiB
HTML
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>ERP 시스템 — DBX Corporation</title>
|
|
<link rel="stylesheet" href="/static/erp.css" />
|
|
</head>
|
|
<body class="erp-body">
|
|
|
|
<div class="erp-shell">
|
|
|
|
<!-- ── 상단 네비게이션 ── -->
|
|
<nav class="erp-nav">
|
|
<div class="erp-nav-left">
|
|
<a href="/" class="erp-brand">
|
|
<img src="/static/dbx-logo.png" alt="DBX" class="erp-brand-logo" />
|
|
<span class="erp-brand-divider"></span>
|
|
<span class="erp-brand-system">DBX ERP System</span>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="erp-nav-right">
|
|
{% if is_admin %}
|
|
<a class="erp-btn erp-btn-ghost" href="/admin">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 2 4 6v6c0 5 3.4 9.4 8 10 4.6-.6 8-5 8-10V6l-8-4Z"/>
|
|
</svg>
|
|
관리자
|
|
</a>
|
|
{% endif %}
|
|
|
|
<div class="erp-user-chip">
|
|
{% if user.picture %}
|
|
<img class="erp-user-avatar" src="{{ user.picture }}" alt="{{ user.name }}" />
|
|
{% else %}
|
|
<span class="erp-user-avatar-fallback">{{ user.name[0] | upper }}</span>
|
|
{% endif %}
|
|
<div class="erp-user-info">
|
|
<span class="erp-user-name">{{ user.name }}</span>
|
|
<span class="erp-user-email">{{ user.email }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<a class="erp-btn erp-btn-outline" href="/logout">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
|
<polyline points="16 17 21 12 16 7"/>
|
|
<line x1="21" y1="12" x2="9" y2="12"/>
|
|
</svg>
|
|
로그아웃
|
|
</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- ── 본문 ── -->
|
|
<main class="erp-main">
|
|
|
|
<h1 class="erp-greeting">안녕하세요, {{ user.name }}님</h1>
|
|
<p class="erp-greeting-sub" id="erp-greeting-sub"></p>
|
|
|
|
<div class="erp-section-head">
|
|
<h2 class="erp-section-title">업무 모듈</h2>
|
|
<span class="erp-section-meta">{{ menu_items | length }}개 모듈</span>
|
|
</div>
|
|
|
|
<div class="erp-grid">
|
|
{% for item in menu_items %}
|
|
{% set clickable = item.allowed and item.status == 'ready' %}
|
|
{% if clickable %}
|
|
<a class="erp-card" href="{{ item.url }}"
|
|
target="_blank" rel="noopener noreferrer"
|
|
data-clickable="true"
|
|
{% if item.health_url %}data-health-url="{{ item.health_url }}"{% endif %}>
|
|
{% else %}
|
|
<div class="erp-card" data-clickable="false">
|
|
{% endif %}
|
|
|
|
<div class="erp-card-head">
|
|
<div>
|
|
<div class="erp-card-title">{{ item.title }}</div>
|
|
<div class="erp-card-subtitle">{{ item.subtitle }}</div>
|
|
</div>
|
|
{% if item.status == 'preparing' %}
|
|
<span class="erp-badge erp-badge-neutral">준비중</span>
|
|
{% elif not item.allowed %}
|
|
<span class="erp-badge erp-badge-outline">권한 없음</span>
|
|
{% elif item.health_url %}
|
|
<span class="erp-badge erp-badge-neutral erp-badge-dot"
|
|
data-health-url="{{ item.health_url }}" data-status="checking">확인 중</span>
|
|
{% else %}
|
|
<span class="erp-badge erp-badge-inverse">활성</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<p class="erp-card-desc">{{ item.description }}</p>
|
|
|
|
<div class="erp-card-foot">
|
|
<span class="erp-badge erp-badge-outline">{{ item.category }}</span>
|
|
{% if clickable %}
|
|
<span class="erp-card-link">
|
|
바로가기
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
|
stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
<polyline points="12 5 19 12 12 19"/>
|
|
</svg>
|
|
</span>
|
|
{% elif item.status == 'preparing' %}
|
|
<span class="erp-card-link" style="color: var(--color-midtone-gray);">출시 예정</span>
|
|
{% else %}
|
|
<span class="erp-card-link" style="color: var(--color-midtone-gray);">접근 불가</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if clickable %}
|
|
</a>
|
|
{% else %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
// 인사말 — 시간대별
|
|
(function() {
|
|
const d = new Date();
|
|
const h = d.getHours();
|
|
let greet = "오늘도 좋은 하루 되세요.";
|
|
if (h < 5) greet = "늦은 시간까지 수고 많으세요.";
|
|
else if (h < 12) greet = "활기찬 아침입니다.";
|
|
else if (h < 18) greet = "오후 업무 화이팅입니다.";
|
|
else greet = "오늘도 고생 많으셨습니다.";
|
|
const days = ["일","월","화","수","목","금","토"];
|
|
const dateStr = `${d.getFullYear()}.${String(d.getMonth()+1).padStart(2,"0")}.${String(d.getDate()).padStart(2,"0")} (${days[d.getDay()]})`;
|
|
document.getElementById("erp-greeting-sub").textContent = `${dateStr} · ${greet}`;
|
|
})();
|
|
|
|
// 헬스 배지 — data-health-url 이 있는 배지만 갱신
|
|
async function refreshHealth() {
|
|
const badges = document.querySelectorAll(".erp-badge[data-health-url]");
|
|
await Promise.all(Array.from(badges).map(async (badge) => {
|
|
const url = badge.getAttribute("data-health-url");
|
|
try {
|
|
const res = await fetch(url, { cache: "no-store", credentials: "omit" });
|
|
if (res.ok) {
|
|
badge.dataset.status = "online";
|
|
badge.className = "erp-badge erp-badge-success erp-badge-dot";
|
|
badge.textContent = "온라인";
|
|
} else {
|
|
badge.dataset.status = "offline";
|
|
badge.className = "erp-badge erp-badge-danger erp-badge-dot";
|
|
badge.textContent = "오프라인";
|
|
}
|
|
} catch {
|
|
badge.dataset.status = "offline";
|
|
badge.className = "erp-badge erp-badge-danger erp-badge-dot";
|
|
badge.textContent = "오프라인";
|
|
}
|
|
}));
|
|
}
|
|
refreshHealth();
|
|
setInterval(refreshHealth, 30000);
|
|
</script>
|
|
</body>
|
|
</html>
|