Files
dbx-main/app/templates/erp_base.html
T
king 8cfe963526 feat(erp): 공용 확인/경고 창으로 브라우저 기본 confirm 대체
브라우저 기본 confirm()/alert() 은 제목에 도메인이 찍히고("dbx.no1king.
freeddns.org 내용:") 위치·정렬·아이콘을 바꿀 수 없다. 같은 역할의 창을 직접
그려 화면 정중앙에 띄운다.

- app/static/erp-dialog.{js,css} 신규. 제목은 "No.1 King ERP 프로그램",
  내용은 가운데 정렬, 왼쪽에 경고 삼각형 아이콘(인라인 SVG - 외부 아이콘
  라이브러리를 쓰지 않는다).
- erpConfirm()/erpAlert() 은 Promise 를 돌려준다. 기본 confirm() 과 달리
  동기 반환이 불가능하므로 호출부를 then() 으로 바꿨다. 폼 제출은 일단
  막고 확인 후 form.submit() 으로 다시 보낸다(required 검사는 그 전에 이미
  통과한다).
- HTML 만으로 쓰려면 form 에 data-erp-confirm 을 달면 된다. 카페24 시스템·
  예약 화면의 인라인 onsubmit=confirm(...) 을 이 방식으로 옮겼다.
- 메시지는 textContent 로 넣어 서버·사용자 문자열이 HTML 로 해석되지 않는다.
- Esc 취소 / Enter 확인 / Tab 은 창 안에서만 돌고, 닫을 때 원래 포커스로
  돌아간다. 바깥을 누르면 취소(안전한 쪽).
- erp_base.html 에서 전역 로드. defer 를 쓰지 않아 각 화면의 인라인
  스크립트가 곧바로 쓸 수 있다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:33:42 +09:00

172 lines
7.4 KiB
HTML

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ page_title or "ERP" }} — DBX Corporation</title>
<link rel="stylesheet" href="/static/erp.css?v=20260615b" />
<link rel="stylesheet" href="/static/erp-shell.css?v=20260615b" />
<link rel="stylesheet" href="/static/erp-attach-viewer.css" />
<link rel="stylesheet" href="/static/erp-dialog.css?v=20260819a" />
{% block head_extra %}{% endblock %}
</head>
<body class="erp-body erp-app-body">
<div class="erp-app">
<!-- ── 좌측 사이드바 ── -->
<aside class="erp-sidebar" data-collapsed="false">
<div class="erp-sidebar-head">
<a href="/" class="erp-sidebar-brand">
<img src="/static/dbx-logo.png" alt="DBX" class="erp-sidebar-logo" />
<span class="erp-sidebar-system">DBX ERP System</span>
</a>
<button type="button" class="erp-sidebar-toggle" id="erp-sidebar-toggle" aria-label="사이드바 접기">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"/>
</svg>
</button>
</div>
<nav class="erp-sidebar-nav">
{% set last_group = namespace(value=None) %}
{% for item in nav_items %}
{% if item.group != last_group.value %}
<div class="erp-sidebar-group">{{ item.group }}</div>
{% set last_group.value = item.group %}
{% endif %}
{% set classes = "erp-sidebar-item" %}
{% if item.active %}{% set classes = classes + " is-active" %}{% endif %}
{% if item.disabled %}{% set classes = classes + " is-disabled" %}{% endif %}
{% if item.disabled %}
<div class="{{ classes }}" title="{{ item.disabled_reason or '준비중' }}">
{% else %}
<a class="{{ classes }}" href="{{ item.url }}"
{% if item.target == '_blank' %}target="_blank" rel="noopener noreferrer"{% endif %}>
{% endif %}
<span class="erp-sidebar-icon">{{ item.icon | safe }}</span>
<span class="erp-sidebar-label">{{ item.label }}</span>
{% if item.badge %}<span class="erp-sidebar-badge">{{ item.badge }}</span>{% endif %}
{% if item.target == '_blank' %}
<span class="erp-sidebar-ext" aria-hidden="true">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15 3 21 3 21 9"/>
<line x1="10" y1="14" x2="21" y2="3"/>
</svg>
</span>
{% endif %}
{% if item.disabled %}
</div>
{% else %}
</a>
{% endif %}
{% endfor %}
</nav>
<div class="erp-sidebar-foot">
{% if user.is_super_admin %}
<a class="erp-sidebar-item" href="/modules" title="업무 모듈 선택 화면">
<span class="erp-sidebar-icon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>
</svg>
</span>
<span class="erp-sidebar-label">업무 모듈</span>
</a>
{% endif %}
{% if is_admin %}
<a class="erp-sidebar-item" href="/admin">
<span class="erp-sidebar-icon">
<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>
</span>
<span class="erp-sidebar-label">관리자</span>
</a>
{% endif %}
</div>
</aside>
<!-- ── 우측 콘텐츠 ── -->
<div class="erp-content">
<header class="erp-topbar">
<div class="erp-topbar-left">
<button type="button" class="erp-sidebar-mobile-toggle" id="erp-sidebar-mobile-toggle" aria-label="메뉴 열기">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>
<div class="erp-topbar-title">
<h1>{{ page_title or "" }}</h1>
{% if page_subtitle %}<p>{{ page_subtitle }}</p>{% endif %}
</div>
</div>
<div class="erp-topbar-right">
{% block topbar_actions %}{% endblock %}
<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">로그아웃</a>
</div>
</header>
<main class="erp-page">
{% block content %}{% endblock %}
</main>
</div>
</div>
<script>
(function () {
const sidebar = document.querySelector(".erp-sidebar");
const collapseBtn = document.getElementById("erp-sidebar-toggle");
const mobileBtn = document.getElementById("erp-sidebar-mobile-toggle");
if (collapseBtn) {
collapseBtn.addEventListener("click", () => {
const next = sidebar.dataset.collapsed === "true" ? "false" : "true";
sidebar.dataset.collapsed = next;
try { localStorage.setItem("erp_sidebar_collapsed", next); } catch (_) {}
});
try {
const saved = localStorage.getItem("erp_sidebar_collapsed");
if (saved === "true") sidebar.dataset.collapsed = "true";
} catch (_) {}
}
if (mobileBtn) {
mobileBtn.addEventListener("click", () => {
sidebar.classList.toggle("is-open");
});
}
})();
</script>
{# 공용 확인/경고 창. defer 를 쓰지 않는다 — 아래 {% block scripts %} 의
인라인 스크립트가 곧바로 erpConfirm/erpAlert 을 쓸 수 있어야 한다. #}
<script src="/static/erp-dialog.js?v=20260819a"></script>
<script src="/static/erp-attach-viewer.js" defer></script>
{% block scripts %}{% endblock %}
</body>
</html>