feat: 리눅스 환경 파일 동기화, Dockerfile 및 docker-compose 추가, 캐시 무시 설정

This commit is contained in:
2026-06-22 10:06:19 +09:00
parent b864d615ea
commit 705246923b
59 changed files with 7653 additions and 5794 deletions

View File

@@ -1,139 +1,139 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>데이터 분석 - Project Master Sabermetrics</title>
<link rel="stylesheet" href="style/common.css">
<link rel="stylesheet" href="style/analysis.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
</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" onclick="location.href='/dashboard'">대시보드</li>
<li class="nav-item" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item active" onclick="location.href='/analysis'">분석</li>
</ul>
</nav>
<main class="analysis-content">
<header class="analysis-header">
<div class="title-group">
<div class="ai-badge">AI Sabermetrics</div>
<h2>시스템 운영 자산 가치 분석</h2>
<p>수집된 활동 로그 및 자산 데이터를 기반으로 한 통계적 활력 지표 (Beta)</p>
</div>
<div class="analysis-actions">
<button class="btn btn-primary" onclick="location.reload()">데이터 갱신</button>
</div>
</header>
<!-- 상단 정보 영역 -->
<div class="top-info-grid">
<section class="dl-model-info">
<div class="card-header">
<h4><i class="ai-icon">AI</i> Hybrid Prediction Engine</h4>
</div>
<div class="card-body">
<div class="model-desc-vertical">
<div class="model-item-vertical">
<span class="model-tag">분석 모델</span>
<p>최근 9회차 시계열의 Velocity 및 변화율 분석</p>
</div>
<div class="model-item-vertical">
<span class="model-tag">가중치 로직</span>
<p>활동 시 '선형 유지', 정체 시 '지수 감쇄' 동적 적용</p>
</div>
</div>
</div>
</section>
<section class="soi-deep-dive">
<div class="card-header">
<h4><i class="info-icon">i</i> AI 위험 적응형 모델 (AAS) 기반 지표 정의</h4>
</div>
<div class="card-body">
<div class="soi-info-columns">
<div class="soi-info-column">
<h6>1. 자산 가치 변동 추적</h6>
<p>규모를 감지하여, 대형 프로젝트 정체 시 데이터 가치 하락 속도를 <strong>가속(Acceleration)</strong>시킵니다.</p>
</div>
<div class="soi-info-column">
<h6>2. 활동 시계열 관성 분석</h6>
<p>최근 활동의 연속성을 분석하여, 단기 정체 시에도 과거의 <strong>운영 모멘텀</strong>을 반영하여 지수를 보정합니다.</p>
</div>
<div class="soi-info-column">
<h6>3. 동적 가치 계수</h6>
<p>프로젝트마다 <strong>개별화된 감쇄 곡선</strong>을 생성하여 현장에 가장 밀착된 보존율을 제공합니다.</p>
</div>
</div>
</div>
</section>
</div>
<!-- 메인 분석 차트 영역 -->
<div class="analysis-charts-grid">
<div class="chart-container-box">
<h5>운영 활력 분포 (Activity Distribution)</h5>
<canvas id="statusChart"></canvas>
</div>
<div class="chart-container-box">
<h5>자산 가치 전략 매트릭스 (Strategic Analysis)</h5>
<canvas id="forecastChart"></canvas>
</div>
</div>
<!-- 리더보드 영역 -->
<div class="analysis-card timeline-analysis">
<div class="card-header">
<div style="display: flex; flex-direction: column; gap: 4px;">
<h4>Project Activity Vitality Leaderboard (AVI Status)</h4>
<p style="font-size: 11px; color: #888; margin: 0;">운영 표준(AVI 70%) 대비 운영 활력 및 VCI 기여 리더보드</p>
</div>
<div class="card-tools">
<span id="avg-system-info" style="font-size: 11px; color: #888;">* AVI (Activity Vitality Index)</span>
</div>
</div>
<div class="card-body">
<div class="d-war-guide">
<div class="guide-item active-low"><span>70%↑</span> 정상 운영</div>
<div class="guide-item warning-mid"><span>30~70%</span> 관리 주의</div>
<div class="guide-item danger-high"><span>10~30%</span> 위험 노출</div>
<div class="guide-item hazard-critical"><span>10%↓</span> 중단/방치</div>
</div>
<div id="p-war-table-container">
<!-- JS에 의해 동적으로 테이블 삽입 -->
</div>
</div>
</div>
</main>
<!-- 설명 모달 -->
<div id="analysisModal" class="modal-overlay" onclick="if(event.target===this) closeAnalysisModal()">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">분석 상세</h3>
<span class="modal-close" onclick="closeAnalysisModal()">&times;</span>
</div>
<div class="modal-body" id="modalBody">
<!-- 내용 동적 삽입 -->
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/analysis.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>데이터 분석 - Project Master Sabermetrics</title>
<link rel="stylesheet" href="style/common.css">
<link rel="stylesheet" href="style/analysis.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
</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" onclick="location.href='/dashboard'">대시보드</li>
<li class="nav-item" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item active" onclick="location.href='/analysis'">분석</li>
</ul>
</nav>
<main class="analysis-content">
<header class="analysis-header">
<div class="title-group">
<div class="ai-badge">AI Sabermetrics</div>
<h2>시스템 운영 자산 가치 분석</h2>
<p>수집된 활동 로그 및 자산 데이터를 기반으로 한 통계적 활력 지표 (Beta)</p>
</div>
<div class="analysis-actions">
<button class="btn btn-primary" onclick="location.reload()">데이터 갱신</button>
</div>
</header>
<!-- 상단 정보 영역 -->
<div class="top-info-grid">
<section class="dl-model-info">
<div class="card-header">
<h4><i class="ai-icon">AI</i> Hybrid Prediction Engine</h4>
</div>
<div class="card-body">
<div class="model-desc-vertical">
<div class="model-item-vertical">
<span class="model-tag">분석 모델</span>
<p>최근 9회차 시계열의 Velocity 및 변화율 분석</p>
</div>
<div class="model-item-vertical">
<span class="model-tag">가중치 로직</span>
<p>활동 시 '선형 유지', 정체 시 '지수 감쇄' 동적 적용</p>
</div>
</div>
</div>
</section>
<section class="soi-deep-dive">
<div class="card-header">
<h4><i class="info-icon">i</i> AI 위험 적응형 모델 (AAS) 기반 지표 정의</h4>
</div>
<div class="card-body">
<div class="soi-info-columns">
<div class="soi-info-column">
<h6>1. 자산 가치 변동 추적</h6>
<p>규모를 감지하여, 대형 프로젝트 정체 시 데이터 가치 하락 속도를 <strong>가속(Acceleration)</strong>시킵니다.</p>
</div>
<div class="soi-info-column">
<h6>2. 활동 시계열 관성 분석</h6>
<p>최근 활동의 연속성을 분석하여, 단기 정체 시에도 과거의 <strong>운영 모멘텀</strong>을 반영하여 지수를 보정합니다.</p>
</div>
<div class="soi-info-column">
<h6>3. 동적 가치 계수</h6>
<p>프로젝트마다 <strong>개별화된 감쇄 곡선</strong>을 생성하여 현장에 가장 밀착된 보존율을 제공합니다.</p>
</div>
</div>
</div>
</section>
</div>
<!-- 메인 분석 차트 영역 -->
<div class="analysis-charts-grid">
<div class="chart-container-box">
<h5>운영 활력 분포 (Activity Distribution)</h5>
<canvas id="statusChart"></canvas>
</div>
<div class="chart-container-box">
<h5>자산 가치 전략 매트릭스 (Strategic Analysis)</h5>
<canvas id="forecastChart"></canvas>
</div>
</div>
<!-- 리더보드 영역 -->
<div class="analysis-card timeline-analysis">
<div class="card-header">
<div style="display: flex; flex-direction: column; gap: 4px;">
<h4>Project Activity Vitality Leaderboard (AVI Status)</h4>
<p style="font-size: 11px; color: #888; margin: 0;">전체 포트폴리오 평균(0.0) 대비 운영 가치 기여(VCI) 리더보드</p>
</div>
<div class="card-tools">
<span id="avg-system-info" style="font-size: 11px; color: #888;">* AVI (Activity Vitality Index)</span>
</div>
</div>
<div class="card-body">
<div class="d-war-guide">
<div class="guide-item active-low"><span>70%↑</span> 정상 운영</div>
<div class="guide-item warning-mid"><span>30~70%</span> 관리 주의</div>
<div class="guide-item danger-high"><span>10~30%</span> 위험 노출</div>
<div class="guide-item hazard-critical"><span>10%↓</span> 중단/방치</div>
</div>
<div id="p-war-table-container">
<!-- JS에 의해 동적으로 테이블 삽입 -->
</div>
</div>
</div>
</main>
<!-- 설명 모달 -->
<div id="analysisModal" class="modal-overlay" onclick="if(event.target===this) closeAnalysisModal()">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">분석 상세</h3>
<span class="modal-close" onclick="closeAnalysisModal()">&times;</span>
</div>
<div class="modal-body" id="modalBody">
<!-- 내용 동적 삽입 -->
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/analysis.js"></script>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>데이터 분석 (테스트) - Project Master Sabermetrics</title>
<link rel="stylesheet" href="style/common.css">
<link rel="stylesheet" href="style/analysis.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
</head>
<body>
<nav class="topbar">
<div class="topbar-header">
<a href="/">
<h2>Project Master Test (TEST)</h2>
</a>
</div>
<ul class="nav-list">
<li class="nav-item" onclick="location.href='/dashboard_test'">대시보드(테스트)</li>
<li class="nav-item" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item active" onclick="location.href='/analysis_test'">분석(테스트)</li>
</ul>
</nav>
<main class="analysis-content">
<header class="analysis-header">
<div class="title-group">
<div class="ai-badge" style="background: var(--ai-color);">AI Sabermetrics [TEST]</div>
<h2>시스템 운영 자산 가치 분석 (테스트 환경)</h2>
<p>테스트용 데이터베이스(PM_proto_test)를 기반으로 한 활력 지표 분석입니다.</p>
</div>
<div class="analysis-actions">
<button class="btn btn-primary" onclick="location.reload()">데이터 갱신</button>
</div>
</header>
<!-- 상단 정보 영역 -->
<div class="top-info-grid">
<section class="dl-model-info">
<div class="card-header">
<h4><i class="ai-icon">AI</i> Hybrid Prediction Engine (TEST)</h4>
</div>
<div class="card-body">
<div class="model-desc-vertical">
<div class="model-item-vertical">
<span class="model-tag">분석 모델</span>
<p>최근 9회차 시계열의 Velocity 및 변화율 분석</p>
</div>
<div class="model-item-vertical">
<span class="model-tag">가중치 로직</span>
<p>활동 시 '선형 유지', 정체 시 '지수 감쇄' 동적 적용</p>
</div>
</div>
</div>
</section>
<section class="soi-deep-dive">
<div class="card-header">
<h4><i class="info-icon">i</i> AI 위험 적응형 모델 (AAS) 기반 지표 정의</h4>
</div>
<div class="card-body">
<div class="soi-info-columns">
<div class="soi-info-column">
<h6>1. 자산 가치 변동 추적</h6>
<p>규모를 감지하여, 대형 프로젝트 정체 시 데이터 가치 하락 속도를 <strong>가속(Acceleration)</strong>시킵니다.</p>
</div>
<div class="soi-info-column">
<h6>2. 활동 시계열 관성 분석</h6>
<p>최근 활동의 연속성을 분석하여, 단기 정체 시에도 과거의 <strong>운영 모멘텀</strong>을 반영하여 지수를 보정합니다.</p>
</div>
<div class="soi-info-column">
<h6>3. 동적 가치 계수</h6>
<p>프로젝트마다 <strong>개별화된 감쇄 곡선</strong>을 생성하여 현장에 가장 밀착된 보존율을 제공합니다.</p>
</div>
</div>
</div>
</section>
</div>
<!-- 메인 분석 차트 영역 -->
<div class="analysis-charts-grid">
<div class="chart-container-box">
<h5>운영 활력 분포 (Activity Distribution)</h5>
<canvas id="statusChart"></canvas>
</div>
<div class="chart-container-box">
<h5>자산 가치 전략 매트릭스 (Strategic Analysis)</h5>
<canvas id="forecastChart"></canvas>
</div>
</div>
<!-- 리더보드 영역 -->
<div class="analysis-card timeline-analysis">
<div class="card-header">
<div style="display: flex; flex-direction: column; gap: 4px;">
<h4>Project Activity Vitality Leaderboard (AVI Status) [TEST]</h4>
<p style="font-size: 11px; color: #888; margin: 0;">전체 포트폴리오 평균(0.0) 대비 운영 가치 기여(VCI) 리더보드</p>
</div>
<div class="card-tools">
<span id="avg-system-info" style="font-size: 11px; color: #888;">* AVI (Activity Vitality Index)</span>
</div>
</div>
<div class="card-body">
<div class="d-war-guide">
<div class="guide-item active-low"><span>70%↑</span> 정상 운영</div>
<div class="guide-item warning-mid"><span>30~70%</span> 관리 주의</div>
<div class="guide-item danger-high"><span>10~30%</span> 위험 노출</div>
<div class="guide-item hazard-critical"><span>10%↓</span> 중단/방치</div>
</div>
<div id="p-war-table-container">
<!-- JS에 의해 동적으로 테이블 삽입 -->
</div>
</div>
</div>
</main>
<!-- 설명 모달 -->
<div id="analysisModal" class="modal-overlay" onclick="if(event.target===this) closeAnalysisModal()">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">분석 상세 (TEST)</h3>
<span class="modal-close" onclick="closeAnalysisModal()">&times;</span>
</div>
<div class="modal-body" id="modalBody">
<!-- 내용 동적 삽입 -->
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/analysis_test.js"></script>
</body>
</html>

View File

@@ -1,118 +1,118 @@
<!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="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis'">분석</li>
</ul>
</nav>
<main class="main-content">
<header>
<h2>프로젝트 현황</h2>
<div class="header-actions" style="display: flex; align-items: center; gap: 15px;">
<div class="base-date-info">기준날짜: <strong id="baseDate">-</strong></div>
<button id="syncBtn" class="sync-btn" onclick="syncData()">
<span class="spinner"></span>
데이터 동기화
</button>
<div class="admin-info">접속자: <strong>이태훈[전체관리자]</strong></div>
</div>
</header>
<!-- 프로젝트 활성도 대시보드 (전체 너비 래퍼) -->
<div class="activity-dashboard-wrapper">
<div id="activityDashboard" class="activity-dashboard">
<!-- JS에서 동적 삽입 -->
</div>
</div>
<!-- 실시간 로그 콘솔 (본문 내부로 복구) -->
<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>
<!-- 모달 레이어 (공통 규격 적용) -->
<div id="authModal" class="modal-overlay">
<div class="modal-content" style="max-width: 440px; padding: 40px; text-align: center;" onclick="event.stopPropagation()">
<div class="auth-header" style="margin-bottom: 32px;">
<i class="fas fa-lock" style="font-size: 32px; color: var(--primary-color); margin-bottom: 16px; display: block;"></i>
<h3 style="font-size: 20px; font-weight: 800; color: #111; margin-bottom: 8px;">크롤링 권한 인증</h3>
<p style="font-size: 13px; color: var(--text-sub);">시스템 동기화를 위해 관리자 계정으로 로그인하세요.</p>
</div>
<div class="auth-body" style="display: flex; flex-direction: column; gap: 20px; text-align: left; margin-bottom: 32px;">
<div class="input-group">
<label>관리자 아이디</label>
<input type="text" id="authId" placeholder="아이디를 입력하세요">
</div>
<div class="input-group">
<label>비밀번호</label>
<input type="password" id="authPw" placeholder="비밀번호를 입력하세요"
onkeyup="if(event.key==='Enter') submitAuth()">
</div>
<div id="authErrorMessage" class="error-text" style="display:none; color: var(--error-color); font-size: 12px; font-weight: 600; text-align: center; margin-top: -10px;">크롤링을 할 수 없습니다.</div>
</div>
<div class="auth-footer" style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 12px;">
<button class="btn btn-secondary" style="height: 48px;" onclick="ModalManager.close('authModal')">취소</button>
<button class="btn btn-primary" style="height: 48px;" onclick="submitAuth()">인증 및 실행</button>
</div>
</div>
</div>
<div id="activityDetailModal" class="modal-overlay" onclick="ModalManager.close('activityDetailModal')">
<div class="modal-content" style="max-width: 600px; padding: 0; overflow: hidden;" onclick="event.stopPropagation()">
<div class="modal-header" style="padding: 20px; margin-bottom: 0;">
<h3 id="modalTitle">상세 목록</h3>
<span class="modal-close" onclick="ModalManager.close('activityDetailModal')">&times;</span>
</div>
<div class="modal-body" style="padding: 20px; max-height: 70vh; overflow-y: auto;">
<table class="data-table">
<thead>
<tr>
<th>프로젝트명</th>
<th>담당부서</th>
<th>담당자</th>
</tr>
</thead>
<tbody id="modalTableBody">
<!-- JS에서 동적 삽입 -->
</tbody>
</table>
</div>
<div style="padding: 16px 20px; border-top: 1px solid var(--border-color); text-align: right; background: #fdfdfd;">
<button class="btn btn-secondary" onclick="ModalManager.close('activityDetailModal')">닫기</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/dashboard.js"></script>
</body>
<!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="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis'">분석</li>
</ul>
</nav>
<main class="main-content">
<header>
<h2>프로젝트 현황</h2>
<div class="header-actions" style="display: flex; align-items: center; gap: 15px;">
<div class="base-date-info">기준날짜: <strong id="baseDate">-</strong></div>
<button id="syncBtn" class="sync-btn" onclick="syncData()">
<span class="spinner"></span>
데이터 동기화
</button>
<div class="admin-info">접속자: <strong>이태훈[전체관리자]</strong></div>
</div>
</header>
<!-- 프로젝트 활성도 대시보드 (전체 너비 래퍼) -->
<div class="activity-dashboard-wrapper">
<div id="activityDashboard" class="activity-dashboard">
<!-- JS에서 동적 삽입 -->
</div>
</div>
<!-- 실시간 로그 콘솔 (본문 내부로 복구) -->
<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>
<!-- 모달 레이어 (공통 규격 적용) -->
<div id="authModal" class="modal-overlay">
<div class="modal-content" style="max-width: 440px; padding: 40px; text-align: center;" onclick="event.stopPropagation()">
<div class="auth-header" style="margin-bottom: 32px;">
<i class="fas fa-lock" style="font-size: 32px; color: var(--primary-color); margin-bottom: 16px; display: block;"></i>
<h3 style="font-size: 20px; font-weight: 800; color: #111; margin-bottom: 8px;">크롤링 권한 인증</h3>
<p style="font-size: 13px; color: var(--text-sub);">시스템 동기화를 위해 관리자 계정으로 로그인하세요.</p>
</div>
<div class="auth-body" style="display: flex; flex-direction: column; gap: 20px; text-align: left; margin-bottom: 32px;">
<div class="input-group">
<label>관리자 아이디</label>
<input type="text" id="authId" placeholder="아이디를 입력하세요">
</div>
<div class="input-group">
<label>비밀번호</label>
<input type="password" id="authPw" placeholder="비밀번호를 입력하세요"
onkeyup="if(event.key==='Enter') submitAuth()">
</div>
<div id="authErrorMessage" class="error-text" style="display:none; color: var(--error-color); font-size: 12px; font-weight: 600; text-align: center; margin-top: -10px;">크롤링을 할 수 없습니다.</div>
</div>
<div class="auth-footer" style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 12px;">
<button class="btn btn-secondary" style="height: 48px;" onclick="ModalManager.close('authModal')">취소</button>
<button class="btn btn-primary" style="height: 48px;" onclick="submitAuth()">인증 및 실행</button>
</div>
</div>
</div>
<div id="activityDetailModal" class="modal-overlay" onclick="ModalManager.close('activityDetailModal')">
<div class="modal-content" style="max-width: 600px; padding: 0; overflow: hidden;" onclick="event.stopPropagation()">
<div class="modal-header" style="padding: 20px; margin-bottom: 0;">
<h3 id="modalTitle">상세 목록</h3>
<span class="modal-close" onclick="ModalManager.close('activityDetailModal')">&times;</span>
</div>
<div class="modal-body" style="padding: 20px; max-height: 70vh; overflow-y: auto;">
<table class="data-table">
<thead>
<tr>
<th>프로젝트명</th>
<th>담당부서</th>
<th>담당자</th>
</tr>
</thead>
<tbody id="modalTableBody">
<!-- JS에서 동적 삽입 -->
</tbody>
</table>
</div>
<div style="padding: 16px 20px; border-top: 1px solid var(--border-color); text-align: right; background: #fdfdfd;">
<button class="btn btn-secondary" onclick="ModalManager.close('activityDetailModal')">닫기</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/dashboard.js"></script>
</body>
</html>

View File

@@ -0,0 +1,118 @@
<!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 (TEST)</h2>
</a>
</div>
<ul class="nav-list">
<li class="nav-item active" onclick="location.href='/dashboard_test'">대시보드(테스트)</li>
<li class="nav-item" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis_test'">분석(테스트)</li>
</ul>
</nav>
<main class="main-content">
<header>
<h2>프로젝트 현황 (테스트 환경)</h2>
<div class="header-actions" style="display: flex; align-items: center; gap: 15px;">
<div class="base-date-info">기준날짜: <strong id="baseDate">-</strong></div>
<button id="syncBtn" class="sync-btn" onclick="syncData()">
<span class="spinner"></span>
데이터 동기화 (TEST)
</button>
<div class="admin-info">접속자: <strong>이태훈[테스트관리자]</strong></div>
</div>
</header>
<!-- 프로젝트 활성도 대시보드 (전체 너비 래퍼) -->
<div class="activity-dashboard-wrapper">
<div id="activityDashboard" class="activity-dashboard">
<!-- JS에서 동적 삽입 -->
</div>
</div>
<!-- 실시간 로그 콘솔 (본문 내부로 복구) -->
<div id="logConsole" class="log-console" style="display:none;">
<div class="log-console-header">실시간 수집 로그 [PM Overseas - TEST]</div>
<div id="logBody"></div>
</div>
<div id="projectAccordion">
<!-- Multi-level Accordion items will be generated here -->
</div>
</main>
<!-- 모달 레이어 (공통 규격 적용) -->
<div id="authModal" class="modal-overlay">
<div class="modal-content" style="max-width: 440px; padding: 40px; text-align: center;" onclick="event.stopPropagation()">
<div class="auth-header" style="margin-bottom: 32px;">
<i class="fas fa-lock" style="font-size: 32px; color: var(--primary-color); margin-bottom: 16px; display: block;"></i>
<h3 style="font-size: 20px; font-weight: 800; color: #111; margin-bottom: 8px;">크롤링 권한 인증 (TEST)</h3>
<p style="font-size: 13px; color: var(--text-sub);">테스트 DB 동기화를 위해 계정으로 로그인하세요.</p>
</div>
<div class="auth-body" style="display: flex; flex-direction: column; gap: 20px; text-align: left; margin-bottom: 32px;">
<div class="input-group">
<label>관리자 아이디</label>
<input type="text" id="authId" placeholder="아이디를 입력하세요">
</div>
<div class="input-group">
<label>비밀번호</label>
<input type="password" id="authPw" placeholder="비밀번호를 입력하세요"
onkeyup="if(event.key==='Enter') submitAuth()">
</div>
<div id="authErrorMessage" class="error-text" style="display:none; color: var(--error-color); font-size: 12px; font-weight: 600; text-align: center; margin-top: -10px;">크롤링을 할 수 없습니다.</div>
</div>
<div class="auth-footer" style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 12px;">
<button class="btn btn-secondary" style="height: 48px;" onclick="ModalManager.close('authModal')">취소</button>
<button class="btn btn-primary" style="height: 48px;" onclick="submitAuth()">인증 및 실행</button>
</div>
</div>
</div>
<div id="activityDetailModal" class="modal-overlay" onclick="ModalManager.close('activityDetailModal')">
<div class="modal-content" style="max-width: 600px; padding: 0; overflow: hidden;" onclick="event.stopPropagation()">
<div class="modal-header" style="padding: 20px; margin-bottom: 0;">
<h3 id="modalTitle">상세 목록 (TEST)</h3>
<span class="modal-close" onclick="ModalManager.close('activityDetailModal')">&times;</span>
</div>
<div class="modal-body" style="padding: 20px; max-height: 70vh; overflow-y: auto;">
<table class="data-table">
<thead>
<tr>
<th>프로젝트명</th>
<th>담당부서</th>
<th>담당자</th>
</tr>
</thead>
<tbody id="modalTableBody">
<!-- JS에서 동적 삽입 -->
</tbody>
</table>
</div>
<div style="padding: 16px 20px; border-top: 1px solid var(--border-color); text-align: right; background: #fdfdfd;">
<button class="btn btn-secondary" onclick="ModalManager.close('activityDetailModal')">닫기</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/dashboard_test.js"></script>
</body>
</html>

View File

@@ -1,46 +1,58 @@
<!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>
<!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>
<h3>대시보드</h3>
<p>시스템 운영 현황 및 핵심 지표 요약</p>
</a>
<a href="/inquiries" class="portal-card">
<div class="icon">📝</div>
<h3>문의사항</h3>
<p>프로젝트 관련 문의 및 기술 지원 관리</p>
</a>
<a href="/mailTest" class="portal-card">
<div class="icon">📧</div>
<h3>메일관리</h3>
<p>수집 메일 분석 및 첨부파일 분류 현황</p>
</a>
<a href="/analysis" class="portal-card">
<div class="icon">📈</div>
<h3>분석 (Sabermetrics)</h3>
<p>AI 기반 운영 활력 및 자산 가치 정밀 분석</p>
</a>
</div>
</div>
<script src="js/common.js"></script>
</body>
</html>

View File

@@ -1,194 +1,194 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>문의사항 관리 - Project Master</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">
<link rel="stylesheet" href="style/inquiries.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" onclick="location.href='/dashboard'">대시보드</li>
<li class="nav-item active" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis'">분석</li>
</ul>
</nav>
<main class="inquiry-board">
<div id="stickyHeader" class="board-sticky-header">
<div class="board-header">
<div>
<h2>문의사항</h2>
<p style="font-size: 13px; color: #666; margin-top: 4px;">시스템 운영 관련 불편사항 및 개선 요청 관리</p>
</div>
<div class="header-stats" id="headerStats">
<div class="stat-item total">
<span class="stat-label">전체</span>
<span class="stat-value" id="countTotal">0</span>
</div>
<div class="stat-item complete">
<span class="stat-label">완료</span>
<span class="stat-value" id="countComplete">0</span>
</div>
<div class="stat-item working">
<span class="stat-label">작업 중</span>
<span class="stat-value" id="countWorking">0</span>
</div>
<div class="stat-item checking">
<span class="stat-label">확인 중</span>
<span class="stat-value" id="countChecking">0</span>
</div>
<div class="stat-item pending">
<span class="stat-label">개발예정</span>
<span class="stat-value" id="countPending">0</span>
</div>
<div class="stat-item unconfirmed">
<span class="stat-label">미확인</span>
<span class="stat-value" id="countUnconfirmed">0</span>
</div>
</div>
</div>
<!-- 시트 상단 공지 영역 재현 (통합 박스) -->
<div class="notice-container">
<div style="margin-bottom: 12px; display: flex; gap: 20px;">
<div style="flex: 1;">
<h4
style="color: #d32f2f; margin-bottom: 5px; font-size: 14px; display: flex; align-items: center; gap: 6px;">
<span style="font-size: 16px;">📢</span> &lt;공지&gt;
</h4>
<ul style="font-size: 12px; line-height: 1.6; color: #444; padding-left: 20px; margin: 0;">
<li>파워포인트 파일에 읽기전용포함(제한)글꼴이 있는 경우 컨버팅이 되지 않습니다. 제한된 글꼴 제거 후 업로드 권장.</li>
<li>이미지는 '문의사항 이미지' 시트에 문의사항 번호와 함께 업로드 부탁드립니다.</li>
</ul>
</div>
<div style="flex: 2; border-left: 1px dashed #ddd; padding-left: 20px;">
<h4
style="color: #1976d2; margin-bottom: 5px; font-size: 14px; display: flex; align-items: center; gap: 6px;">
<span style="font-size: 16px;">📂</span> &lt;이전 문의사항 요약&gt;
</h4>
<div
style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 11px; color: #666; line-height: 1.4;">
<div><strong>1. 폴더트리:</strong> 하위폴더 권한, 숨김 기능 등 (완료)</div>
<div><strong>2. 뷰어:</strong> 변환 오류, 특정 문서 깨짐 등 (완료)</div>
<div><strong>3. 업로드/다운로드:</strong> 대용량 전송 오류 등 (완료)</div>
<div><strong>4. 기타:</strong> 정렬, 화면 개선 등 (완료)</div>
</div>
</div>
</div>
</div>
<div class="filter-section">
<div class="filter-group">
<label>시스템(PM 종류)</label>
<select id="filterPmType" onchange="loadInquiries()">
<option value="">전체</option>
<option value="Overseas">Overseas</option>
<option value="기술개발센터">기술개발센터</option>
<option value="장헌">장헌</option>
</select>
</div>
<div class="filter-group">
<label>구분(카테고리)</label>
<select id="filterCategory" onchange="loadInquiries()">
<option value="">전체</option>
<option value="업로드">업로드</option>
<option value="다운로드">다운로드</option>
<option value="폴더트리">폴더트리</option>
<option value="뷰어">뷰어</option>
<option value="기타">기타</option>
</select>
</div>
<div class="filter-group">
<label>처리여부</label>
<select id="filterStatus" onchange="loadInquiries()">
<option value="">전체</option>
<option value="완료">완료</option>
<option value="작업 중">작업 중</option>
<option value="확인 중">확인 중</option>
<option value="개발예정">개발예정</option>
<option value="미확인">미확인</option>
</select>
</div>
<div class="filter-group">
<label>검색(내용/작성자/프로젝트)</label>
<input type="text" id="searchKeyword" placeholder="검색어 입력 후 Enter..."
onkeyup="if(event.key==='Enter') loadInquiries()" style="width: 250px;">
</div>
</div>
</div>
<table class="inquiry-table">
<thead>
<tr>
<th width="50" class="sortable" onclick="handleSort('no')">
<div class="header-content">No <span id="sort-no" class="sort-icon"></span></div>
</th>
<th width="80">이미지</th>
<th width="120" class="sortable" onclick="handleSort('pm_type')">
<div class="header-content">PM 종류 <span id="sort-pm_type" class="sort-icon"></span></div>
</th>
<th width="100" class="sortable" onclick="handleSort('browser')">
<div class="header-content">환경 <span id="sort-browser" class="sort-icon"></span></div>
</th>
<th width="150" class="sortable" onclick="handleSort('category')">
<div class="header-content">구분 <span id="sort-category" class="sort-icon"></span></div>
</th>
<th class="sortable" onclick="handleSort('project_nm')">
<div class="header-content">프로젝트 <span id="sort-project_nm" class="sort-icon"></span></div>
</th>
<th width="400">문의내용</th>
<th width="100" class="sortable" onclick="handleSort('author')">
<div class="header-content">작성자 <span id="sort-author" class="sort-icon"></span></div>
</th>
<th width="120" class="sortable" onclick="handleSort('reg_date')">
<div class="header-content">날짜 <span id="sort-reg_date" class="sort-icon"></span></div>
</th>
<th width="400">답변내용</th>
<th width="100" class="sortable" onclick="handleSort('status')">
<div class="header-content">상태 <span id="sort-status" class="sort-icon"></span></div>
</th>
</tr>
</thead>
<tbody id="inquiryList">
<!-- Data will be loaded here -->
</tbody>
</table>
</main>
<!-- 이미지 크게 보기 모달 (디자인 가이드 - 화이트 계열 및 우측 하단 닫기 적용) -->
<div id="imageModal" class="modal-overlay" onclick="ModalManager.close('imageModal')">
<div class="modal-content" style="max-width: 960px; width: 92%; padding: 0; overflow: hidden; border-radius: 12px; border: 1px solid #e2e8f0; background: #fff;" onclick="event.stopPropagation()">
<div class="modal-header" style="padding: 16px 24px; margin-bottom: 0; border-bottom: 1px solid #f1f5f9; background: #fff;">
<h3 style="color: #1e5149; font-weight: 700; font-size: 16px;">첨부 이미지 확대 보기</h3>
<span class="modal-close" onclick="ModalManager.close('imageModal')" style="font-size: 24px; color: #94a3b8;">&times;</span>
</div>
<div style="padding: 32px; background: #fff; display: flex; justify-content: center; align-items: center; min-height: 300px; max-height: 75vh; overflow: auto;">
<img id="modalImage" style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1);">
</div>
<div style="padding: 16px 24px; border-top: 1px solid #f1f5f9; text-align: right; background: #fff;">
<button class="_button-medium" style="background: #1e5149; color: #fff; border: none; padding: 10px 28px; border-radius: 8px; cursor: pointer; transition: background 0.2s;"
onmouseover="this.style.background='#163b36'" onmouseout="this.style.background='#1e5149'"
onclick="ModalManager.close('imageModal')">닫기</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/inquiries.js"></script>
</body>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>문의사항 관리 - Project Master</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">
<link rel="stylesheet" href="style/inquiries.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" onclick="location.href='/dashboard'">대시보드</li>
<li class="nav-item active" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis'">분석</li>
</ul>
</nav>
<main class="inquiry-board">
<div id="stickyHeader" class="board-sticky-header">
<div class="board-header">
<div>
<h2>문의사항</h2>
<p style="font-size: 13px; color: #666; margin-top: 4px;">시스템 운영 관련 불편사항 및 개선 요청 관리</p>
</div>
<div class="header-stats" id="headerStats">
<div class="stat-item total">
<span class="stat-label">전체</span>
<span class="stat-value" id="countTotal">0</span>
</div>
<div class="stat-item complete">
<span class="stat-label">완료</span>
<span class="stat-value" id="countComplete">0</span>
</div>
<div class="stat-item working">
<span class="stat-label">작업 중</span>
<span class="stat-value" id="countWorking">0</span>
</div>
<div class="stat-item checking">
<span class="stat-label">확인 중</span>
<span class="stat-value" id="countChecking">0</span>
</div>
<div class="stat-item pending">
<span class="stat-label">개발예정</span>
<span class="stat-value" id="countPending">0</span>
</div>
<div class="stat-item unconfirmed">
<span class="stat-label">미확인</span>
<span class="stat-value" id="countUnconfirmed">0</span>
</div>
</div>
</div>
<!-- 시트 상단 공지 영역 재현 (통합 박스) -->
<div class="notice-container">
<div style="margin-bottom: 12px; display: flex; gap: 20px;">
<div style="flex: 1;">
<h4
style="color: #d32f2f; margin-bottom: 5px; font-size: 14px; display: flex; align-items: center; gap: 6px;">
<span style="font-size: 16px;">📢</span> &lt;공지&gt;
</h4>
<ul style="font-size: 12px; line-height: 1.6; color: #444; padding-left: 20px; margin: 0;">
<li>파워포인트 파일에 읽기전용포함(제한)글꼴이 있는 경우 컨버팅이 되지 않습니다. 제한된 글꼴 제거 후 업로드 권장.</li>
<li>이미지는 '문의사항 이미지' 시트에 문의사항 번호와 함께 업로드 부탁드립니다.</li>
</ul>
</div>
<div style="flex: 2; border-left: 1px dashed #ddd; padding-left: 20px;">
<h4
style="color: #1976d2; margin-bottom: 5px; font-size: 14px; display: flex; align-items: center; gap: 6px;">
<span style="font-size: 16px;">📂</span> &lt;이전 문의사항 요약&gt;
</h4>
<div
style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 11px; color: #666; line-height: 1.4;">
<div><strong>1. 폴더트리:</strong> 하위폴더 권한, 숨김 기능 등 (완료)</div>
<div><strong>2. 뷰어:</strong> 변환 오류, 특정 문서 깨짐 등 (완료)</div>
<div><strong>3. 업로드/다운로드:</strong> 대용량 전송 오류 등 (완료)</div>
<div><strong>4. 기타:</strong> 정렬, 화면 개선 등 (완료)</div>
</div>
</div>
</div>
</div>
<div class="filter-section">
<div class="filter-group">
<label>시스템(PM 종류)</label>
<select id="filterPmType" onchange="loadInquiries()">
<option value="">전체</option>
<option value="Overseas">Overseas</option>
<option value="기술개발센터">기술개발센터</option>
<option value="장헌">장헌</option>
</select>
</div>
<div class="filter-group">
<label>구분(카테고리)</label>
<select id="filterCategory" onchange="loadInquiries()">
<option value="">전체</option>
<option value="업로드">업로드</option>
<option value="다운로드">다운로드</option>
<option value="폴더트리">폴더트리</option>
<option value="뷰어">뷰어</option>
<option value="기타">기타</option>
</select>
</div>
<div class="filter-group">
<label>처리여부</label>
<select id="filterStatus" onchange="loadInquiries()">
<option value="">전체</option>
<option value="완료">완료</option>
<option value="작업 중">작업 중</option>
<option value="확인 중">확인 중</option>
<option value="개발예정">개발예정</option>
<option value="미확인">미확인</option>
</select>
</div>
<div class="filter-group">
<label>검색(내용/작성자/프로젝트)</label>
<input type="text" id="searchKeyword" placeholder="검색어 입력 후 Enter..."
onkeyup="if(event.key==='Enter') loadInquiries()" style="width: 250px;">
</div>
</div>
</div>
<table class="inquiry-table">
<thead>
<tr>
<th width="50" class="sortable" onclick="handleSort('no')">
<div class="header-content">No <span id="sort-no" class="sort-icon"></span></div>
</th>
<th width="80">이미지</th>
<th width="120" class="sortable" onclick="handleSort('pm_type')">
<div class="header-content">PM 종류 <span id="sort-pm_type" class="sort-icon"></span></div>
</th>
<th width="100" class="sortable" onclick="handleSort('browser')">
<div class="header-content">환경 <span id="sort-browser" class="sort-icon"></span></div>
</th>
<th width="150" class="sortable" onclick="handleSort('category')">
<div class="header-content">구분 <span id="sort-category" class="sort-icon"></span></div>
</th>
<th class="sortable" onclick="handleSort('project_nm')">
<div class="header-content">프로젝트 <span id="sort-project_nm" class="sort-icon"></span></div>
</th>
<th width="400">문의내용</th>
<th width="100" class="sortable" onclick="handleSort('author')">
<div class="header-content">작성자 <span id="sort-author" class="sort-icon"></span></div>
</th>
<th width="120" class="sortable" onclick="handleSort('reg_date')">
<div class="header-content">날짜 <span id="sort-reg_date" class="sort-icon"></span></div>
</th>
<th width="400">답변내용</th>
<th width="100" class="sortable" onclick="handleSort('status')">
<div class="header-content">상태 <span id="sort-status" class="sort-icon"></span></div>
</th>
</tr>
</thead>
<tbody id="inquiryList">
<!-- Data will be loaded here -->
</tbody>
</table>
</main>
<!-- 이미지 크게 보기 모달 (디자인 가이드 - 화이트 계열 및 우측 하단 닫기 적용) -->
<div id="imageModal" class="modal-overlay" onclick="ModalManager.close('imageModal')">
<div class="modal-content" style="max-width: 960px; width: 92%; padding: 0; overflow: hidden; border-radius: 12px; border: 1px solid #e2e8f0; background: #fff;" onclick="event.stopPropagation()">
<div class="modal-header" style="padding: 16px 24px; margin-bottom: 0; border-bottom: 1px solid #f1f5f9; background: #fff;">
<h3 style="color: #1e5149; font-weight: 700; font-size: 16px;">첨부 이미지 확대 보기</h3>
<span class="modal-close" onclick="ModalManager.close('imageModal')" style="font-size: 24px; color: #94a3b8;">&times;</span>
</div>
<div style="padding: 32px; background: #fff; display: flex; justify-content: center; align-items: center; min-height: 300px; max-height: 75vh; overflow: auto;">
<img id="modalImage" style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1);">
</div>
<div style="padding: 16px 24px; border-top: 1px solid #f1f5f9; text-align: right; background: #fff;">
<button class="_button-medium" style="background: #1e5149; color: #fff; border: none; padding: 10px 28px; border-radius: 8px; cursor: pointer; transition: background 0.2s;"
onmouseover="this.style.background='#163b36'" onmouseout="this.style.background='#1e5149'"
onclick="ModalManager.close('imageModal')">닫기</button>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/inquiries.js"></script>
</body>
</html>

View File

@@ -1,144 +1,144 @@
<!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" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item active" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis'">분석</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" id="startDate"
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" id="endDate"
style="flex:1; height:32px; padding:4px; font-size:11px; border:1px solid var(--border-color); border-radius:4px;">
</div>
<div class="flex-center" style="gap:8px; margin-top:4px;">
<button class="btn-confirm" style="flex:1; height:32px; font-size:12px; background:var(--primary-color); color:#fff; display:flex; align-items:center; justify-content:center;" onclick="searchMails()">검색</button>
<button class="btn-confirm" style="flex:1; height:32px; font-size:12px; background:#fff; color:var(--text-sub); border:1px solid var(--border-color); display:flex; align-items:center; justify-content:center;" onclick="resetSearch()">초기화</button>
</div>
</div>
<!-- 선택 삭제 액션바 -->
<div id="mailBulkActions" class="mail-bulk-actions">
<div class="flex-center" style="gap:8px;">
<input type="checkbox" id="selectAllMails" onclick="toggleSelectAll(this)">
<span id="selectedCount">0개 선택됨</span>
</div>
<button class="_button-xsmall" style="background:#fff5f5; color:#e53e3e; border:1px solid #feb2b2;" onclick="deleteSelectedMails()">선택 삭제</button>
</div>
<div class="mail-items-container">
<!-- JavaScript (renderMailList)에서 렌더링됨 -->
</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>
</aside>
</div>
{% include 'modals/address_book.html' %}
<script src="js/common.js"></script>
<script src="js/mail.js"></script>
</body>
<!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" onclick="location.href='/inquiries'">문의사항</li>
<li class="nav-item active" onclick="location.href='/mailTest'">메일관리</li>
<li class="nav-item" onclick="location.href='/analysis'">분석</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" id="startDate"
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" id="endDate"
style="flex:1; height:32px; padding:4px; font-size:11px; border:1px solid var(--border-color); border-radius:4px;">
</div>
<div class="flex-center" style="gap:8px; margin-top:4px;">
<button class="btn-confirm" style="flex:1; height:32px; font-size:12px; background:var(--primary-color); color:#fff; display:flex; align-items:center; justify-content:center;" onclick="searchMails()">검색</button>
<button class="btn-confirm" style="flex:1; height:32px; font-size:12px; background:#fff; color:var(--text-sub); border:1px solid var(--border-color); display:flex; align-items:center; justify-content:center;" onclick="resetSearch()">초기화</button>
</div>
</div>
<!-- 선택 삭제 액션바 -->
<div id="mailBulkActions" class="mail-bulk-actions">
<div class="flex-center" style="gap:8px;">
<input type="checkbox" id="selectAllMails" onclick="toggleSelectAll(this)">
<span id="selectedCount">0개 선택됨</span>
</div>
<button class="_button-xsmall" style="background:#fff5f5; color:#e53e3e; border:1px solid #feb2b2;" onclick="deleteSelectedMails()">선택 삭제</button>
</div>
<div class="mail-items-container">
<!-- JavaScript (renderMailList)에서 렌더링됨 -->
</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>
</aside>
</div>
{% include 'modals/address_book.html' %}
<script src="js/common.js"></script>
<script src="js/mail.js"></script>
</body>
</html>

View File

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

View File

@@ -1,24 +1,24 @@
<!-- 경로 선택 모달 (공통 규격 적용) -->
<div id="pathModal" class="modal-overlay" onclick="ModalManager.close('pathModal')">
<div class="modal-content" style="max-width: 500px;" onclick="event.stopPropagation()">
<div class="modal-header">
<h3>파일 보관 경로 선택</h3>
<span class="modal-close" onclick="ModalManager.close('pathModal')">&times;</span>
</div>
<div style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px;">
<div class="select-group" style="margin-bottom: 0;">
<label>탭 (Tab)</label>
<select id="tabSelect" class="modal-select" onchange="updateCategories()"></select>
</div>
<div class="select-group" style="margin-bottom: 0;">
<label>카테고리 (Category)</label>
<select id="categorySelect" class="modal-select" onchange="updateSubs()"></select>
</div>
<div class="select-group" style="margin-bottom: 0;">
<label>서브카테고리 (Sub-Category)</label>
<select id="subSelect" class="modal-select"></select>
</div>
</div>
<button class="btn btn-primary" style="width: 100%; height: 44px;" onclick="applyPathSelection()">경로 확정하기</button>
</div>
</div>
<!-- 경로 선택 모달 (공통 규격 적용) -->
<div id="pathModal" class="modal-overlay" onclick="ModalManager.close('pathModal')">
<div class="modal-content" style="max-width: 500px;" onclick="event.stopPropagation()">
<div class="modal-header">
<h3>파일 보관 경로 선택</h3>
<span class="modal-close" onclick="ModalManager.close('pathModal')">&times;</span>
</div>
<div style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px;">
<div class="select-group" style="margin-bottom: 0;">
<label>탭 (Tab)</label>
<select id="tabSelect" class="modal-select" onchange="updateCategories()"></select>
</div>
<div class="select-group" style="margin-bottom: 0;">
<label>카테고리 (Category)</label>
<select id="categorySelect" class="modal-select" onchange="updateSubs()"></select>
</div>
<div class="select-group" style="margin-bottom: 0;">
<label>서브카테고리 (Sub-Category)</label>
<select id="subSelect" class="modal-select"></select>
</div>
</div>
<button class="btn btn-primary" style="width: 100%; height: 44px;" onclick="applyPathSelection()">경로 확정하기</button>
</div>
</div>