feat: 메일 관리 UI 개편 및 시스템 구조 최적화

- UI/UX: 메일 관리 레이아웃 고도화 및 미리보기 토글 핸들 도입
- 기능: 주소록 CRUD 기능 추가 및 모달 인터페이스 개선
- 구조: CSS 파일 기능별 분리 및 Jinja2 템플릿 엔진 도입
- 백엔드: OCR 비동기 처리 및 CSV 파싱(BOM) 안정화
- 데이터: 2026.03.04 기준 최신 프로젝트 현황 업데이트
This commit is contained in:
2026-03-04 17:58:54 +09:00
parent ff9146cfee
commit d246b08799
20 changed files with 2074 additions and 1138 deletions

61
templates/dashboard.html Normal file
View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Master Overseas 관리자</title>
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
<link rel="stylesheet" href="style/common.css">
<link rel="stylesheet" href="style/dashboard.css">
</head>
<body>
<nav class="topbar">
<div class="topbar-header">
<a href="/">
<h2>Project Master Test</h2>
</a>
</div>
<ul class="nav-list">
<li class="nav-item active" onclick="location.href='/dashboard'">대시보드</li>
<li class="nav-item" onclick="alert('준비 중입니다.')">문의사항</li>
<li class="nav-item" onclick="alert('준비 중입니다.')">로그관리</li>
<li class="nav-item" onclick="alert('준비 중입니다.')">파일관리</li>
<li class="nav-item" onclick="alert('준비 중입니다.')">인원관리</li>
<li class="nav-item" onclick="alert('준비 중입니다.')">공지사항</li>
</ul>
</nav>
<main class="main-content">
<header>
<div class="flex-center">
<h1>프로젝트 현황</h1>
</div>
<div class="flex-center">
<button id="syncBtn" class="sync-btn" onclick="syncData()">
<span class="spinner"></span>
데이터 동기화 (크롤링)
</button>
<div class="admin-info">접속자: <strong>이태훈[전체관리자]</strong></div>
</div>
</header>
<!-- 실시간 로그 콘솔 추가 -->
<div id="logConsole" class="log-console" style="display:none;">
<div class="log-console-header">실시간 수집 로그 [PM Overseas]</div>
<div id="logBody"></div>
</div>
<div id="projectAccordion">
<!-- Multi-level Accordion items will be generated here -->
</div>
</main>
<script src="js/common.js"></script>
<script src="js/dashboard.js"></script>
</body>
</html>

46
templates/index.html Normal file
View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Master Portal</title>
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
<link rel="stylesheet" href="style/common.css">
<link rel="stylesheet" href="style/dashboard.css">
</head>
<body>
<nav class="topbar">
<div class="topbar-header">
<a href="/"><h2>Project Master Test</h2></a>
</div>
</nav>
<div class="portal-container">
<div class="portal-header">
<h1>Project Master 테스트</h1>
<p>원하시는 서비스에 접속하려면 아래 버튼을 클릭하세요.</p>
</div>
<div class="button-grid">
<a href="/dashboard" class="portal-card">
<div class="icon">📊</div>
<h2>관리자 페이지 테스트</h2>
<p>관리자 페이지 테스트 입니다.</p>
</a>
<a href="/mailTest" class="portal-card">
<div class="icon">✉️</div>
<h2>메일 테스트</h2>
<p>메일 기능 테스트 페이지입니다.</p>
</a>
</div>
</div>
<script src="js/common.js"></script>
</body>
</html>

140
templates/mailTest.html Normal file
View File

@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Mail Manager</title>
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
<link rel="stylesheet" href="style/common.css">
<link rel="stylesheet" href="style/mail.css">
</head>
<body>
{% include 'modals/path_selector.html' %}
<nav class="topbar">
<div class="topbar-header">
<a href="/">
<h2>Project Master Test</h2>
</a>
</div>
<ul class="nav-list">
<li class="nav-item" onclick="location.href='/dashboard'">대시보드</li>
<li class="nav-item active" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item">로그관리</li>
<li class="nav-item">파일관리</li>
</ul>
</nav>
<div class="mail-wrapper">
<!-- 메일 리스트 영역 (사이드바 삭제됨) -->
<section class="mail-list-area">
<div class="mail-tabs">
<div class="mail-tab active" onclick="switchMailTab(this, 'inbound')">📥 수신</div>
<div class="mail-tab" onclick="switchMailTab(this, 'outbound')">📤 발신</div>
<div class="mail-tab" onclick="switchMailTab(this, 'drafts')">📝 임시</div>
<div class="mail-tab" onclick="switchMailTab(this, 'deleted')">🗑️ 휴지통</div>
</div>
<div class="search-bar" style="display:flex; flex-direction:column; gap:8px;">
<input type="text" style="height: 32px; width:100%;" placeholder="제목, 내용, 기관 검색...">
<select style="height: 32px; width:100%; padding:4px; font-size:12px;">
<option>모든 상대기관</option>
<option>라오스 농림부</option>
<option>베트남 전력청</option>
</select>
<div class="flex-center" style="gap:4px; width:100%;">
<input type="date"
style="flex:1; height:32px; padding:4px; font-size:11px; border:1px solid var(--border-color); border-radius:4px;">
<span style="font-size:12px; color:var(--text-sub);">~</span>
<input type="date"
style="flex:1; height:32px; padding:4px; font-size:11px; border:1px solid var(--border-color); border-radius:4px;">
</div>
</div>
<div class="mail-items-container">
<div class="mail-item active">
<div class="flex-between" style="margin-bottom:4px;">
<span style="font-weight:700; color:var(--primary-color);">라오스 농림부</span>
<span style="font-size:11px; color:var(--text-sub);">오후 2:30</span>
</div>
<div style="font-weight:600; font-size:12px; margin-bottom:4px;">ITTC 교육센터 착공식 일정 협의</div>
<div
style="font-size:11px; color:var(--text-sub); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">
안녕하세요. 착공식 관련하여 첨부 드리는 공문을...</div>
</div>
</div>
<!-- 메일쓰기 및 주소록 버튼 하단 고정 -->
<div class="address-book-footer flex-center" style="gap:8px;">
<button class="btn-confirm"
style="background:var(--primary-color); color:#fff; font-size:12px; padding:8px; flex:1;"
onclick="alert('메일 쓰기 창을 엽니다.')">✍️ 메일쓰기</button>
<button class="btn-confirm"
style="background:#fff; color:var(--primary-color); border:1px solid var(--primary-color); font-size:12px; padding:8px; flex:1;"
onclick="openAddressBook()">📘 주소록</button>
</div>
</section>
<!-- 메일 본문 영역 -->
<section class="mail-content-area">
<div class="mail-content-header">
<h2 style="font-size:18px; color:var(--primary-color); margin-bottom:8px;">ITTC 교육센터 착공식 일정 협의 요청</h2>
<div style="font-size:12px; color:var(--text-sub);"><strong>보낸사람</strong> pany.s@lao.gov.la (라오스 농림부)
</div>
<div style="font-size:12px; color:var(--text-sub);"><strong>날짜</strong> 2026년 2월 26일 14:30</div>
</div>
<div class="mail-body">
안녕하세요. 이태훈 선임연구원님.<br><br>
라오스 ITTC 관개 교육센터 착공식과 관련하여 정부 측 인사의 일정을 반영한 최종 공문을 송부합니다.<br>
</div>
<div class="attachment-area">
<div class="flex-between" style="margin-bottom:12px;">
<div style="font-weight:700; font-size:13px;">첨부파일 리스트</div>
<div class="ai-toggle-wrap">
<span class="ai-label">AI 판단</span>
<label class="switch">
<input type="checkbox" id="aiToggle" onchange="renderFiles()">
<span class="slider"></span>
</label>
</div>
</div>
<div id="attachmentList"></div>
</div>
</section>
<!-- 우측 미리보기 영역 -->
<aside class="mail-preview-area" id="mailPreviewArea">
<!-- 토글 핸들 버튼 -->
<div class="preview-toggle-handle" onclick="togglePreviewAuto()">
<span id="previewToggleIcon"></span>
</div>
<div class="preview-header">
<div class="flex-center" style="gap:8px;">
<h3>미리보기</h3>
<span style="font-size:10px; color:var(--text-sub); font-weight:400;">최대 10페이지까지만 표시됩니다.</span>
</div>
<div class="flex-center" style="gap:12px;">
<button id="fullViewBtn" class="_button-xsmall"
style="background:var(--primary-lv-0); color:#111111; border:none; padding:4px 12px; height:24px; cursor:pointer; display:none;">전체보기</button>
</div>
</div>
<div class="a4-container" id="previewContainer">
<div class="preview-placeholder">파일을 클릭하면<br>미리보기가 표시됩니다.</div>
</div>
</div>
</aside>
</div>
{% include 'modals/address_book.html' %}
<script src="js/common.js"></script>
<script src="js/mail.js"></script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
<!-- 주소록 모달 -->
<div id="addressBookModal" class="modal-overlay">
<div class="modal-content" style="max-width: 850px;">
<div class="modal-header">
<h3>공사 관계자 주소록</h3>
<div class="flex-center" style="gap:10px;">
<button class="_button-small" style="border:none;" onclick="toggleAddContactForm()">+ 추가하기</button>
<span class="modal-close" onclick="closeAddressBook()">&times;</span>
</div>
</div>
<!-- 주소록 추가 폼 (기본 숨김) -->
<div id="addContactForm"
style="display:none; background:var(--bg-muted); padding:15px; border-radius:8px; margin-bottom:15px; border:1px solid var(--border-color);">
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:10px; margin-bottom:10px;">
<input type="text" id="newContactName" placeholder="성명"
style="padding:8px; border:1px solid #ccc; border-radius:4px; font-size:12px;">
<input type="text" id="newContactDept" placeholder="소속/직위"
style="padding:8px; border:1px solid #ccc; border-radius:4px; font-size:12px;">
<input type="text" id="newContactEmail" placeholder="이메일"
style="padding:8px; border:1px solid #ccc; border-radius:4px; font-size:12px;">
<input type="text" id="newContactPhone" placeholder="연락처"
style="padding:8px; border:1px solid #ccc; border-radius:4px; font-size:12px;">
</div>
<div class="flex-center" style="gap:8px;">
<button class="_button-medium" style="flex:1; background:var(--primary-color); color:#fff;"
onclick="addContact()">저장</button>
<button class="_button-medium" style="flex:1; background:#718096; color:#fff;"
onclick="toggleAddContactForm()">취소</button>
</div>
</div>
<div class="search-bar" style="background:#fff; padding:0 0 15px 0;">
<input type="text" placeholder="이름, 부서, 연락처 검색..." style="margin-bottom:8px; height: 32px;">
</div>
<div style="max-height: 400px; overflow-y: auto;">
<table class="data-table">
<thead>
<tr>
<th>성명</th>
<th>소속/직위</th>
<th>이메일</th>
<th>연락처</th>
<th style="text-align:right;">관리</th>
</tr>
</thead>
<tbody id="addressBookBody">
<!-- 동적으로 렌더링됨 -->
</tbody>
</table>
</div>
<button class="btn-confirm" style="margin-top:20px;" onclick="closeAddressBook()">닫기</button>
</div>
</div>

View File

@@ -0,0 +1,22 @@
<!-- 경로 선택 모달 -->
<div id="pathModal" class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h3>파일 보관 경로 선택</h3>
<span class="modal-close" onclick="closeModal()">&times;</span>
</div>
<div class="select-group">
<label>탭 (Tab)</label>
<select id="tabSelect" class="modal-select" onchange="updateCategories()"></select>
</div>
<div class="select-group">
<label>카테고리 (Category)</label>
<select id="categorySelect" class="modal-select" onchange="updateSubs()"></select>
</div>
<div class="select-group">
<label>서브카테고리 (Sub-Category)</label>
<select id="subSelect" class="modal-select"></select>
</div>
<button class="btn-confirm" onclick="applyPathSelection()">경로 확정하기</button>
</div>
</div>