조직도 중심 메인 화면 샘플 구성

This commit is contained in:
hyunho
2026-03-25 10:50:28 +09:00
parent 3b4169d301
commit 00ca43e4cd
3 changed files with 394 additions and 231 deletions

View File

@@ -7,7 +7,6 @@ const loginMessage = document.getElementById("login-message");
const logoutBtn = document.getElementById("logout-btn"); const logoutBtn = document.getElementById("logout-btn");
const userBadge = document.getElementById("user-badge"); const userBadge = document.getElementById("user-badge");
const healthStatus = document.getElementById("health-status"); const healthStatus = document.getElementById("health-status");
const refreshHealthBtn = document.getElementById("refresh-health-btn");
function getSession() { function getSession() {
try { try {
@@ -38,14 +37,15 @@ function renderAuth() {
async function refreshHealth() { async function refreshHealth() {
if (!healthStatus) return; if (!healthStatus) return;
healthStatus.textContent = "서버 상태 확인하는 중입니다."; healthStatus.textContent = "서버 상태 확인";
try { try {
const response = await fetch("/api/health"); const response = await fetch("/api/health");
if (!response.ok) throw new Error("unhealthy"); if (!response.ok) throw new Error("unhealthy");
const payload = await response.json(); const payload = await response.json();
healthStatus.textContent = `API 상태: ${payload.status}`; const count = typeof payload.member_count === "number" ? ` / ${payload.member_count}` : "";
healthStatus.textContent = `API ${payload.status}${count}`;
} catch (error) { } catch (error) {
healthStatus.textContent = "API 연결할 수 없습니다. backend 컨테이너를 확인해주세요."; healthStatus.textContent = "API 연결 실패";
} }
} }
@@ -77,9 +77,4 @@ if (logoutBtn) {
}); });
} }
if (refreshHealthBtn) {
refreshHealthBtn.addEventListener("click", refreshHealth);
}
renderAuth(); renderAuth();

95
frontend/public/index.html Executable file → Normal file
View File

@@ -3,24 +3,33 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MH Dashboard Hub</title> <title>MH 조직현황 대시보드</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=SUIT:wght@400;500;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;600;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css"> <link rel="stylesheet" href="/styles.css">
</head> </head>
<body> <body>
<div class="app-shell"> <section id="login-panel" class="login-screen">
<section class="panel hero" id="login-panel"> <div class="login-hero">
<div class="hero-copy"> <p class="eyebrow">MH Dashboard</p>
<p class="eyebrow">Intranet Preview</p> <h1>조직도를 기준으로<br>메인 화면을 구성합니다.</h1>
<h1>MH Dashboard Hub</h1>
<p class="hero-text"> <p class="hero-text">
현재 단계에서는 화면상 로그인만 우선 적용합니다. 로그인 후 조직도 레거시 화면과 현재는 조직도 모듈이 메인 화면입니다. 이후 다른 기능이 합쳐지더라도
서버 준비 상태를 한 곳에서 확인할 수 있니다. 같은 헤더와 같은 화면 체계 안에서 확장될 수 있도록 구조를 먼저 맞춥니다.
</p> </p>
<div class="hero-points">
<span>API 기반 조직 데이터</span>
<span>고정 헤더 구조</span>
<span>확장 가능한 메인 레이아웃</span>
</div> </div>
</div>
<form id="login-form" class="login-card"> <form id="login-form" class="login-card">
<div class="login-card-head">
<p class="eyebrow">Preview Login</p>
<h2>조직도 메인 진입</h2>
</div>
<label> <label>
<span>아이디</span> <span>아이디</span>
<input name="username" type="text" placeholder="예: admin" required> <input name="username" type="text" placeholder="예: admin" required>
@@ -29,51 +38,49 @@
<span>비밀번호</span> <span>비밀번호</span>
<input name="password" type="password" placeholder="아무 값이나 입력" required> <input name="password" type="password" placeholder="아무 값이나 입력" required>
</label> </label>
<button type="submit">대시보드 입장</button> <button type="submit">메인 화면 열기</button>
<p id="login-message" class="helper-text">사내망용 임시 로그인 화면입니다.</p> <p id="login-message" class="helper-text">사내망 시연용 임시 로그인입니다.</p>
</form> </form>
</section> </section>
<section id="dashboard-panel" class="hidden"> <section id="dashboard-panel" class="dashboard-shell hidden">
<header class="topbar"> <header class="dashboard-header">
<div> <div class="brand-block">
<p class="eyebrow">Internal Dashboard</p> <p class="eyebrow">MH Dashboard</p>
<h2>조직 관리 허브</h2> <h2>조직현황 메인</h2>
</div> </div>
<div class="topbar-actions">
<span id="user-badge" class="badge"></span> <div class="header-center">
<button id="logout-btn" class="secondary">로그아웃</button> <button class="nav-pill active" type="button">조직도</button>
<span class="nav-pill muted">추가기능 준비중</span>
<span class="nav-pill muted">추가기능 준비중</span>
<span class="nav-pill muted">추가기능 준비중</span>
</div>
<div class="header-actions">
<span id="health-status" class="status-badge">서버 상태 확인 중</span>
<span id="user-badge" class="user-badge"></span>
<button id="logout-btn" class="ghost-button" type="button">로그아웃</button>
</div> </div>
</header> </header>
<main class="grid"> <main class="dashboard-main">
<article class="panel card"> <section class="main-stage">
<p class="eyebrow">Legacy Module</p> <div class="stage-topline">
<h3>조직도 관리</h3> <div>
<p>기존 단일 HTML 조직도 도구를 보존한 상태로 연결했습니다.</p> <p class="stage-label">Main View</p>
<a class="primary-link" href="/organization.html">레거시 조직도 열기</a> <h3>조직도 메인 화면 샘플</h3>
</article> </div>
<a class="stage-link" href="/legacy/organization" target="_blank" rel="noreferrer">새 창으로 열기</a>
</div>
<article class="panel card"> <div class="stage-frame">
<p class="eyebrow">API Readiness</p> <iframe src="/legacy/organization" title="조직도 메인 화면"></iframe>
<h3>서버 상태</h3> </div>
<p id="health-status">서버 상태를 확인하는 중입니다.</p> </section>
<button id="refresh-health-btn" class="secondary">상태 새로고침</button>
</article>
<article class="panel card">
<p class="eyebrow">Roadmap</p>
<h3>다음 단계</h3>
<ul class="roadmap">
<li>프로필 사진 업로드 API 연결</li>
<li>좌석 배치도 좌표 저장 기능 연결</li>
<li>월말 스냅샷 자동화</li>
</ul>
</article>
</main> </main>
</section> </section>
</div>
<script src="/app.js"></script> <script src="/app.js"></script>
</body> </body>
</html> </html>

483
frontend/public/styles.css Executable file → Normal file
View File

@@ -1,210 +1,371 @@
:root { :root {
--bg: #eef4f1; --bg: #f1f5f9;
--panel: rgba(255, 255, 255, 0.86); --bg-strong: #e2e8f0;
--line: rgba(15, 23, 42, 0.08); --panel: rgba(255, 255, 255, 0.92);
--text: #173028; --line: rgba(148, 163, 184, 0.35);
--muted: #5f746d; --text: #0f172a;
--accent: #0f766e; --muted: #64748b;
--accent-soft: #d7f3ee; --header: #1e293b;
--shadow: 0 24px 60px rgba(14, 48, 41, 0.12); --accent: #4f46e5;
--accent-soft: #e0e7ff;
--shadow: 0 24px 60px rgba(15, 23, 42, 0.16);
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
html,
body { body {
margin: 0; margin: 0;
min-height: 100vh; min-height: 100%;
font-family: "SUIT", sans-serif; font-family: "Pretendard", sans-serif;
color: var(--text); color: var(--text);
background: background:
radial-gradient(circle at top left, rgba(15, 118, 110, 0.22), transparent 28%), radial-gradient(circle at top left, rgba(79, 70, 229, 0.12), transparent 22%),
radial-gradient(circle at bottom right, rgba(20, 184, 166, 0.18), transparent 24%), radial-gradient(circle at bottom right, rgba(148, 163, 184, 0.18), transparent 28%),
linear-gradient(135deg, #f5fbf7 0%, #e9f0fb 100%); var(--bg);
} }
.app-shell { body {
max-width: 1240px; min-height: 100vh;
margin: 0 auto;
padding: 48px 20px 56px;
}
.panel {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 28px;
box-shadow: var(--shadow);
backdrop-filter: blur(12px);
}
.hero {
display: grid;
gap: 28px;
grid-template-columns: 1.2fr 0.8fr;
padding: 36px;
}
.eyebrow {
margin: 0 0 10px;
color: var(--accent);
font-size: 12px;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.hero h1,
.topbar h2,
.card h3 {
margin: 0;
}
.hero h1 {
font-size: clamp(2.3rem, 5vw, 4.5rem);
line-height: 0.95;
}
.hero-text,
.card p,
.helper-text,
.roadmap {
color: var(--muted);
line-height: 1.6;
}
.login-card {
display: grid;
gap: 16px;
padding: 24px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(15, 23, 42, 0.06);
}
.login-card label,
.topbar {
display: flex;
flex-direction: column;
gap: 8px;
} }
button,
input, input,
button, a {
.anchor-button,
.primary-link {
font: inherit; font: inherit;
} }
input {
border: 1px solid rgba(15, 23, 42, 0.12);
border-radius: 14px;
padding: 14px 16px;
background: #fff;
}
button,
.primary-link,
.anchor-button {
display: inline-flex;
justify-content: center;
align-items: center;
gap: 8px;
min-height: 48px;
border-radius: 14px;
border: none;
cursor: pointer;
font-weight: 800;
text-decoration: none;
}
button,
.primary-link {
background: var(--accent);
color: #fff;
}
.secondary,
.anchor-button {
background: var(--accent-soft);
color: var(--accent);
}
.hidden { .hidden {
display: none !important; display: none !important;
} }
.topbar { .eyebrow {
flex-direction: row; margin: 0 0 10px;
justify-content: space-between; color: var(--accent);
font-size: 11px;
font-weight: 900;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.login-screen {
min-height: 100vh;
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 28px;
padding: 32px;
}
.login-hero,
.login-card {
border: 1px solid var(--line);
border-radius: 30px;
background: var(--panel);
box-shadow: var(--shadow);
backdrop-filter: blur(14px);
}
.login-hero {
padding: 44px;
display: flex;
flex-direction: column;
justify-content: center;
min-height: calc(100vh - 64px);
}
.login-hero h1,
.login-card h2,
.dashboard-header h2,
.stage-topline h3 {
margin: 0;
}
.login-hero h1 {
font-size: clamp(2.4rem, 5vw, 4.8rem);
line-height: 0.96;
letter-spacing: -0.04em;
}
.hero-text,
.helper-text {
color: var(--muted);
line-height: 1.7;
}
.hero-text {
max-width: 680px;
margin: 20px 0 0;
font-size: 16px;
}
.hero-points {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 28px;
}
.hero-points span {
padding: 12px 16px;
border-radius: 999px;
background: #fff;
border: 1px solid rgba(79, 70, 229, 0.12);
color: #334155;
font-size: 13px;
font-weight: 800;
}
.login-card {
align-self: center;
padding: 28px;
display: grid;
gap: 16px;
}
.login-card-head {
margin-bottom: 8px;
}
.login-card label {
display: flex;
flex-direction: column;
gap: 8px;
font-size: 13px;
font-weight: 800;
color: #475569;
}
.login-card input {
min-height: 52px;
border: 1px solid rgba(148, 163, 184, 0.45);
border-radius: 16px;
padding: 14px 16px;
background: #fff;
outline: none;
}
.login-card input:focus {
border-color: rgba(79, 70, 229, 0.55);
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
}
.login-card button,
.ghost-button,
.stage-link {
display: inline-flex;
align-items: center; align-items: center;
margin-bottom: 24px; justify-content: center;
min-height: 48px;
border-radius: 14px;
font-weight: 800;
text-decoration: none;
cursor: pointer;
} }
.topbar.compact { .login-card button {
max-width: 1240px; margin-top: 8px;
margin: 0 auto; border: none;
padding: 28px 20px 0; color: #fff;
background: var(--accent);
} }
.topbar-actions { .helper-text {
margin: 0;
font-size: 13px;
}
.dashboard-shell {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.dashboard-header {
min-height: 88px;
background: rgba(30, 41, 59, 0.96);
color: #fff;
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 20px;
padding: 18px 26px;
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: blur(16px);
}
.dashboard-header .eyebrow {
color: #a5b4fc;
margin-bottom: 6px;
}
.header-center {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
}
.nav-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 40px;
padding: 0 16px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.08);
color: #e2e8f0;
font-size: 13px;
font-weight: 800;
}
.nav-pill.active {
background: var(--accent);
border-color: transparent;
color: #fff;
box-shadow: 0 10px 24px rgba(79, 70, 229, 0.3);
}
.nav-pill.muted {
color: #94a3b8;
}
.header-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
} }
.badge { .status-badge,
padding: 10px 14px; .user-badge {
display: inline-flex;
align-items: center;
min-height: 40px;
padding: 0 14px;
border-radius: 999px; border-radius: 999px;
background: rgba(15, 118, 110, 0.12); font-size: 12px;
font-weight: 800;
}
.status-badge {
background: rgba(79, 70, 229, 0.18);
color: #c7d2fe;
}
.user-badge {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
.ghost-button {
border: 1px solid rgba(255, 255, 255, 0.16);
background: transparent;
color: #fff;
padding: 0 16px;
}
.dashboard-main {
flex: 1;
min-height: calc(100vh - 88px);
padding: 18px;
}
.main-stage {
height: calc(100vh - 124px);
display: flex;
flex-direction: column;
gap: 14px;
}
.stage-topline {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 18px;
padding: 0 8px;
}
.stage-label {
margin: 0 0 6px;
color: var(--muted);
font-size: 11px;
font-weight: 900;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.stage-topline h3 {
font-size: 28px;
line-height: 1;
letter-spacing: -0.03em;
}
.stage-link {
padding: 0 16px;
color: var(--accent); color: var(--accent);
font-weight: 700; border: 1px solid rgba(79, 70, 229, 0.18);
background: rgba(255, 255, 255, 0.78);
} }
.grid { .stage-frame {
display: grid; flex: 1;
gap: 20px; min-height: 0;
grid-template-columns: repeat(3, minmax(0, 1fr)); border-radius: 28px;
} border: 1px solid rgba(148, 163, 184, 0.26);
background: rgba(255, 255, 255, 0.85);
.card {
padding: 24px;
}
.roadmap {
padding-left: 18px;
margin: 0;
}
.iframe-wrap {
max-width: 1240px;
margin: 0 auto;
padding: 20px;
}
.iframe-wrap iframe {
width: 100%;
min-height: calc(100vh - 130px);
border: 1px solid var(--line);
border-radius: 24px;
background: #fff;
box-shadow: var(--shadow); box-shadow: var(--shadow);
overflow: hidden;
} }
.subpage-body { .stage-frame iframe {
padding-bottom: 20px; width: 100%;
height: 100%;
border: 0;
background: #fff;
} }
@media (max-width: 920px) { @media (max-width: 1180px) {
.hero, .dashboard-header {
.grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
align-items: flex-start;
} }
.topbar, .header-center {
.topbar-actions { justify-content: flex-start;
}
.header-actions {
flex-wrap: wrap;
}
}
@media (max-width: 980px) {
.login-screen {
grid-template-columns: 1fr;
padding: 20px;
}
.login-hero {
min-height: auto;
}
}
@media (max-width: 720px) {
.dashboard-main {
padding: 12px;
}
.main-stage {
height: calc(100vh - 170px);
}
.stage-topline {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }
} }