Files
dbx-main/app/templates/main.html
T

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>