diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc
index 5f24040..b9302e9 100644
Binary files a/__pycache__/server.cpython-312.pyc and b/__pycache__/server.cpython-312.pyc differ
diff --git a/js/analysis.js b/js/analysis.js
new file mode 100644
index 0000000..16940aa
--- /dev/null
+++ b/js/analysis.js
@@ -0,0 +1,122 @@
+/**
+ * Project Master Analysis JS
+ * P-WAR (Project Performance Above Replacement) 분석 엔진
+ */
+
+document.addEventListener('DOMContentLoaded', () => {
+ console.log("Analysis engine initialized...");
+ loadPWarData();
+});
+
+async function loadPWarData() {
+ try {
+ const response = await fetch('/api/analysis/p-war');
+ const data = await response.json();
+
+ if (data.error) throw new Error(data.error);
+
+ updateSummaryMetrics(data);
+ renderPWarLeaderboard(data);
+ renderRiskSignals(data);
+
+ // 시스템 평균 정보 표시
+ if (data.length > 0 && data[0].avg_info) {
+ const avg = data[0].avg_info;
+ document.getElementById('avg-system-info').textContent =
+ `* 0.0 = 시스템 평균 (파일 ${avg.avg_files.toLocaleString()}개 / 방치 ${avg.avg_stagnant}일 / 리스크 ${avg.avg_risk}건)`;
+ }
+
+ } catch (e) {
+ console.error("분석 데이터 로딩 실패:", e);
+ }
+}
+
+function updateSummaryMetrics(data) {
+ // 1. 평균 P-WAR 산출
+ const avgPWar = data.reduce((acc, cur) => acc + cur.p_war, 0) / data.length;
+ document.querySelector('.metric-card.sra .value').textContent = avgPWar.toFixed(2);
+
+ // 2. 고위험 좀비 프로젝트 비율 (P-WAR < -1.0 기준)
+ const zombieCount = data.filter(p => p.p_war < -1.0).length;
+ const zombieRate = (zombieCount / data.length) * 100;
+ document.querySelector('.metric-card.stability .value').textContent = `${zombieRate.toFixed(1)}%`;
+
+ // 3. 총 활성 리소스 규모
+ const totalActiveFiles = data.filter(p => p.p_war > 0).reduce((acc, cur) => acc + cur.file_count, 0);
+ document.querySelector('.metric-card.piso .value').textContent = (totalActiveFiles / 1000).toFixed(1) + "k";
+
+ // 4. 방치 리스크 총합
+ const totalRisks = data.reduce((acc, cur) => acc + cur.risk_count, 0);
+ document.querySelector('.metric-card.iwar .value').textContent = totalRisks;
+}
+
+function renderPWarLeaderboard(data) {
+ const container = document.querySelector('.timeline-analysis .card-body');
+
+ const sortedData = [...data].sort((a, b) => b.p_war - a.p_war);
+
+ container.innerHTML = `
+
+ `;
+}
+
+function renderRiskSignals(data) {
+ const container = document.querySelector('.risk-signal-list');
+
+ // 1. 시스템 삭제(잠김예정) 프로젝트 우선 추출
+ const autoDeleted = data.filter(p => p.is_auto_delete).slice(0, 3);
+ // 2. 그 외 P-WAR가 낮은 순(음수)으로 추출
+ const highRiskProjects = data.filter(p => p.p_war < -1.0 && !p.is_auto_delete).slice(0, 5 - autoDeleted.length);
+
+ const combined = [...autoDeleted, ...highRiskProjects];
+
+ container.innerHTML = combined.map(p => `
+
+
${p.project_nm} (${p.master})
+
+ ${p.is_auto_delete ? '[잠김예정] 활동 부재로 인한 시스템 자동 삭제 발생' : `P-WAR ${p.p_war} (대체 수준 이하 정체)`}
+
+
위험
+
+ `).join('');
+}
diff --git a/js/inquiries.js b/js/inquiries.js
index e615c37..74197d0 100644
--- a/js/inquiries.js
+++ b/js/inquiries.js
@@ -4,12 +4,14 @@
*/
// --- 초기화 ---
+let allInquiries = [];
+let currentSort = { field: 'no', direction: 'desc' };
+
async function loadInquiries() {
initStickyHeader();
const pmType = document.getElementById('filterPmType').value;
const category = document.getElementById('filterCategory').value;
- const status = document.getElementById('filterStatus').value;
const keyword = document.getElementById('searchKeyword').value;
const params = new URLSearchParams({
@@ -20,17 +22,87 @@ async function loadInquiries() {
try {
const response = await fetch(`${API.INQUIRIES}?${params}`);
- const data = await response.json();
-
- updateStats(data);
-
- const filteredData = status ? data.filter(item => item.status === status) : data;
- renderInquiryList(filteredData);
+ allInquiries = await response.json();
+
+ refreshInquiryBoard();
} catch (e) {
console.error("데이터 로딩 중 오류 발생:", e);
}
}
+function refreshInquiryBoard() {
+ const status = document.getElementById('filterStatus').value;
+
+ // 1. 상태 필터링
+ let filteredData = status ? allInquiries.filter(item => item.status === status) : [...allInquiries];
+
+ // 2. 정렬 적용
+ filteredData = sortData(filteredData);
+
+ // 3. 통계 및 리스트 렌더링
+ updateStats(allInquiries);
+ updateSortUI();
+ renderInquiryList(filteredData);
+}
+
+function handleSort(field) {
+ if (currentSort.field === field) {
+ currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
+ } else {
+ currentSort.field = field;
+ currentSort.direction = 'asc';
+ }
+ refreshInquiryBoard();
+}
+
+function sortData(data) {
+ const { field, direction } = currentSort;
+ const modifier = direction === 'asc' ? 1 : -1;
+
+ return data.sort((a, b) => {
+ let valA = a[field];
+ let valB = b[field];
+
+ // 숫자형 변환 시도 (No 필드 등)
+ if (field === 'no' || !isNaN(valA)) {
+ valA = Number(valA);
+ valB = Number(valB);
+ }
+
+ // null/undefined 처리
+ if (valA === null || valA === undefined) valA = "";
+ if (valB === null || valB === undefined) valB = "";
+
+ if (valA < valB) return -1 * modifier;
+ if (valA > valB) return 1 * modifier;
+ return 0;
+ });
+}
+
+function updateSortUI() {
+ // 모든 헤더 클래스 및 아이콘 초기화
+ document.querySelectorAll('.inquiry-table thead th.sortable').forEach(th => {
+ th.classList.remove('active-sort');
+ const icon = th.querySelector('.sort-icon');
+ if (icon) {
+ // 레이아웃 시프트 방지를 위해 투명한 기본 아이콘(또는 공백) 유지
+ icon.textContent = "▲";
+ icon.style.opacity = "0";
+ }
+ });
+
+ // 현재 정렬된 헤더 강조 및 아이콘 표시
+ const activeTh = document.querySelector(`.inquiry-table thead th[onclick*="'${currentSort.field}'"]`);
+ if (activeTh) {
+ activeTh.classList.add('active-sort');
+ const icon = activeTh.querySelector('.sort-icon');
+ if (icon) {
+ icon.textContent = currentSort.direction === 'asc' ? "▲" : "▼";
+ icon.style.opacity = "1";
+ }
+ }
+}
+
function initStickyHeader() {
const header = document.getElementById('stickyHeader');
const thead = document.querySelector('.inquiry-table thead');
diff --git a/server.py b/server.py
index aa8244c..4b45c10 100644
--- a/server.py
+++ b/server.py
@@ -76,6 +76,10 @@ async def get_mail_test(request: Request):
async def get_inquiries_page(request: Request):
return templates.TemplateResponse("inquiries.html", {"request": request})
+@app.get("/analysis")
+async def get_analysis_page(request: Request):
+ return templates.TemplateResponse("analysis.html", {"request": request})
+
class InquiryReplyRequest(BaseModel):
reply: str
status: str
@@ -251,6 +255,95 @@ async def stop_sync():
crawl_stop_event.set()
return {"success": True}
+@app.get("/api/analysis/p-war")
+async def get_p_war_analysis():
+ """P-WAR(Project Performance Above Replacement) 분석 API - 실제 평균 기반"""
+ try:
+ with get_db_connection() as conn:
+ with conn.cursor() as cursor:
+ cursor.execute(DashboardQueries.GET_LAST_CRAWL_DATE)
+ last_date = cursor.fetchone()['last_date']
+
+ cursor.execute(DashboardQueries.GET_PROJECT_LIST, (last_date,))
+ projects = cursor.fetchall()
+
+ cursor.execute("SELECT project_nm, COUNT(*) as cnt FROM inquiries WHERE status != '완료' GROUP BY project_nm")
+ inquiry_risks = {row['project_nm']: row['cnt'] for row in cursor.fetchall()}
+
+ import math
+ temp_data = []
+ total_files = 0
+ total_stagnant = 0
+ total_risk = 0
+ count = len(projects)
+
+ if count == 0: return []
+
+ # 1. 1차 순회: 전체 합계 계산 (평균 산출용)
+ for p in projects:
+ file_count = int(p['file_count']) if p['file_count'] else 0
+ log = p['recent_log']
+
+ days_stagnant = 10
+ if log and log != "데이터 없음":
+ match = re.search(r'(\d{4})\.(\d{2})\.(\d{2})', log)
+ if match:
+ log_date = datetime.strptime(match.group(0), "%Y.%m.%d").date()
+ days_stagnant = (last_date - log_date).days
+
+ risk_count = inquiry_risks.get(p['project_nm'], 0)
+
+ total_files += file_count
+ total_stagnant += days_stagnant
+ total_risk += risk_count
+ temp_data.append((p, file_count, days_stagnant, risk_count))
+
+ # 2. 시스템 실제 평균(Mean) 산출
+ avg_files = total_files / count
+ avg_stagnant = 5 # 사용자 요청에 따라 방치 기준을 5일로 강제 고정 (엄격한 판정)
+ avg_risk = total_risk / count
+
+ # 3. 평균 수준의 프로젝트 가치(V_avg) 정의
+ v_rep = ( (1 / (1 + avg_stagnant)) * math.log10(avg_files + 1) ) - (avg_risk * 0.5)
+
+ results = []
+ # 4. 2차 순회: P-WAR 산출 (개별 가치 - 평균 가치)
+ for p, f_cnt, d_stg, r_cnt in temp_data:
+ name = p['short_nm'] or p['project_nm']
+ log = p['recent_log'] or ""
+ is_auto_delete = "폴더자동삭제" in log.replace(" ", "")
+
+ activity_factor = 1 / (1 + d_stg)
+ scale_factor = math.log10(f_cnt + 1)
+ v_project = (activity_factor * scale_factor) - (r_cnt * 0.5)
+
+ # [추가] 폴더 자동 삭제 페널티 부여 (실질적 관리 부재)
+ if is_auto_delete:
+ v_project -= 1.5
+
+ p_war = v_project - v_rep
+
+ results.append({
+ "project_nm": name,
+ "file_count": f_cnt,
+ "days_stagnant": d_stg,
+ "risk_count": r_cnt,
+ "p_war": round(p_war, 3),
+ "is_auto_delete": is_auto_delete,
+ "master": p['master'],
+ "dept": p['department'],
+ "avg_info": {
+ "avg_files": round(avg_files, 1),
+ "avg_stagnant": round(avg_stagnant, 1),
+ "avg_risk": round(avg_risk, 2)
+ }
+ })
+
+ results.sort(key=lambda x: x['p_war'])
+ return results
+ except Exception as e:
+ return {"error": str(e)}
+
@app.get("/attachments")
async def get_attachments():
path = "sample"
diff --git a/style/analysis.css b/style/analysis.css
new file mode 100644
index 0000000..6e87950
--- /dev/null
+++ b/style/analysis.css
@@ -0,0 +1,301 @@
+/* Analysis Page Styles */
+
+.analysis-content {
+ padding: 24px;
+ max-width: 1400px;
+ margin: var(--topbar-h, 36px) auto 0;
+}
+
+.analysis-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ padding: 10px 0 30px 0;
+ margin-bottom: 10px;
+}
+
+.ai-badge {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 20px;
+ background: var(--ai-color, linear-gradient(135deg, #6366f1 0%, #a855f7 100%));
+ color: #fff;
+ font-size: 11px;
+ font-weight: 700;
+ margin-bottom: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.analysis-header h2 { font-size: 24px; font-weight: 800; color: #111; margin: 0; }
+.analysis-header p { font-size: 13px; color: #666; margin-top: 6px; }
+
+.btn-refresh {
+ padding: 10px 20px;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+.btn-refresh:hover { background: #f8f9fa; border-color: #bbb; }
+
+/* 1. Metrics Grid */
+.metrics-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.metric-card {
+ background: #fff;
+ padding: 24px;
+ border-radius: 16px;
+ border: 1px solid #eef0f2;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.04);
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.metric-card .label {
+ font-size: 12px;
+ font-weight: 600;
+ color: #888;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ position: relative; /* 툴팁 배치를 위해 추가 */
+}
+
+/* 툴팁 스타일 추가 */
+.metric-card .label:hover::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ bottom: 100%;
+ left: 0;
+ width: 220px;
+ padding: 12px;
+ background: #1e293b;
+ color: #fff;
+ font-size: 11px;
+ font-weight: 400;
+ line-height: 1.5;
+ border-radius: 8px;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
+ z-index: 10;
+ margin-bottom: 10px;
+ pointer-events: none;
+ white-space: normal;
+}
+
+.metric-card .label:hover::before {
+ content: '';
+ position: absolute;
+ bottom: 100%;
+ left: 20px;
+ border: 6px solid transparent;
+ border-top-color: #1e293b;
+ margin-bottom: -2px;
+ z-index: 10;
+}
+.info-icon { width: 14px; height: 14px; border-radius: 50%; background: #eee; display: inline-flex; align-items: center; justify-content: center; font-size: 9px; cursor: help; font-style: normal; }
+
+.metric-card .value { font-size: 32px; font-weight: 800; color: #1e5149; margin: 0; }
+
+.trend { font-size: 11px; font-weight: 700; }
+.trend.up { color: #d32f2f; }
+.trend.down { color: #1976d2; }
+.trend.steady { color: #666; }
+
+/* 2. Main Grid Layout */
+.analysis-main-grid {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 24px;
+ margin-bottom: 24px;
+}
+
+.analysis-card {
+ background: #fff;
+ border-radius: 16px;
+ border: 1px solid #eef2f6;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.04);
+ overflow: hidden;
+}
+
+.card-header {
+ padding: 20px 24px;
+ border-bottom: 1px solid #f1f5f9;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.card-header h4 { margin: 0; font-size: 15px; font-weight: 700; color: #334155; }
+
+.card-body { padding: 24px; }
+
+/* 테이블 스크롤 래퍼 */
+.table-scroll-wrapper {
+ max-height: 600px;
+ overflow-y: auto;
+ border-radius: 8px;
+ border: 1px solid #eef2f6;
+}
+
+/* 스크롤바 커스텀 */
+.table-scroll-wrapper::-webkit-scrollbar { width: 6px; }
+.table-scroll-wrapper::-webkit-scrollbar-track { background: #f8fafc; }
+.table-scroll-wrapper::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
+.table-scroll-wrapper::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
+
+.chart-placeholder {
+ height: 300px;
+ background: #f8fafc;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #94a3b8;
+ border: 1px dashed #e2e8f0;
+}
+
+/* D-WAR 테이블 스타일 추가 */
+.d-war-table { width: 100%; border-radius: 12px; overflow: hidden; }
+.d-war-table th { background: #f1f5f9; color: #475569; font-size: 11px; padding: 12px; }
+.d-war-table td { padding: 14px 12px; border-bottom: 1px solid #f1f5f9; }
+.d-war-value { font-weight: 800; color: #1e5149; text-align: center; font-size: 15px; }
+.p-war-value { font-weight: 800; text-align: center; font-size: 15px; }
+.text-plus { color: #1d4ed8; }
+.text-minus { color: #dc2626; }
+
+/* 관리 상태 배지 스타일 */
+.badge-system {
+ display: inline-block;
+ padding: 4px 10px;
+ background: #450a0a;
+ color: #fecaca;
+ border: 1px solid #7f1d1d;
+ font-size: 11px;
+ font-weight: 800;
+ border-radius: 6px;
+ white-space: nowrap;
+}
+
+.badge-active {
+ display: inline-block;
+ padding: 4px 10px;
+ background: #f0fdf4;
+ color: #166534;
+ border: 1px solid #dcfce7;
+ font-size: 11px;
+ font-weight: 700;
+ border-radius: 6px;
+ white-space: nowrap;
+}
+
+.badge-warning {
+ display: inline-block;
+ padding: 4px 10px;
+ background: #fffbeb;
+ color: #92400e;
+ border: 1px solid #fef3c7;
+ font-size: 11px;
+ font-weight: 700;
+ border-radius: 6px;
+ white-space: nowrap;
+}
+
+.badge-danger {
+ display: inline-block;
+ padding: 4px 10px;
+ background: #fef2f2;
+ color: #991b1b;
+ border: 1px solid #fee2e2;
+ font-size: 11px;
+ font-weight: 700;
+ border-radius: 6px;
+ white-space: nowrap;
+}
+
+/* 행 강조 스타일 수정 */
+.row-danger { background: #fff1f2 !important; }
+.row-warning { background: #fffaf0 !important; }
+.row-success { background: #f0fdf4 !important; }
+
+.font-bold { font-weight: 700; }
+
+/* P-WAR 가이드 스타일 */
+.d-war-guide {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 20px;
+ padding: 12px 20px;
+ background: #f8fafc;
+ border-radius: 8px;
+ border: 1px solid #e2e8f0;
+}
+
+.guide-item {
+ font-size: 12px;
+ font-weight: 600;
+ color: #64748b;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.guide-item span {
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ color: #fff;
+}
+
+.active-low span { background: #2563eb; }
+.warning-mid span { background: #22c55e; }
+.danger-high span { background: #f59e0b; }
+.hazard-critical span { background: #ef4444; }
+
+/* 3. Risk Signal List */
+.risk-signal-list { display: flex; flex-direction: column; gap: 12px; }
+
+.risk-item {
+ padding: 16px;
+ border-radius: 12px;
+ display: grid;
+ grid-template-columns: 1fr 40px;
+ gap: 4px;
+ position: relative;
+}
+
+.risk-project { font-size: 13px; font-weight: 700; color: #1e293b; }
+.risk-reason { font-size: 11px; color: #64748b; margin-top: 4px; }
+.risk-status {
+ grid-row: span 2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 800;
+ border-radius: 8px;
+}
+
+.risk-item.high { background: #fff1f2; border-left: 4px solid #f43f5e; }
+.risk-item.high .risk-status { color: #f43f5e; }
+.risk-item.warning { background: #fffbeb; border-left: 4px solid #f59e0b; }
+.risk-item.warning .risk-status { color: #f59e0b; }
+.risk-item.safe { background: #f0fdf4; border-left: 4px solid #22c55e; }
+.risk-item.safe .risk-status { color: #22c55e; }
+
+/* 4. Factor Section */
+.factor-grid { display: flex; flex-direction: column; gap: 16px; }
+.factor-item { display: grid; grid-template-columns: 200px 1fr 60px; align-items: center; gap: 20px; }
+.factor-name { font-size: 13px; font-weight: 600; color: #475569; }
+.factor-bar-wrapper { height: 8px; background: #f1f5f9; border-radius: 4px; overflow: hidden; }
+.factor-bar { height: 100%; background: var(--ai-color, #6366f1); border-radius: 4px; }
+.factor-value { font-size: 12px; font-weight: 700; color: #1e5149; text-align: right; }
diff --git a/style/inquiries.css b/style/inquiries.css
index 88f6e9e..b6711bf 100644
--- a/style/inquiries.css
+++ b/style/inquiries.css
@@ -108,6 +108,42 @@
z-index: 900;
}
+/* 정렬 가능한 헤더 스타일 추가 */
+.inquiry-table thead th.sortable {
+ cursor: pointer;
+ user-select: none;
+ transition: background 0.2s;
+ white-space: nowrap;
+}
+
+.inquiry-table thead th.sortable .header-content {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.sort-icon {
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ font-size: 8px;
+ color: #ccc;
+ line-height: 1;
+ margin-left: 2px;
+}
+
+.inquiry-table thead th.active-sort {
+ color: #1e5149;
+ background: #f0f7f6;
+}
+
+.inquiry-table thead th.active-sort .sort-icon {
+ color: #1e5149;
+ font-size: 10px;
+}
+
.inquiry-table td {
padding: 14px 16px;
font-size: 13px;
diff --git a/templates/analysis.html b/templates/analysis.html
new file mode 100644
index 0000000..37c963d
--- /dev/null
+++ b/templates/analysis.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+ 데이터 분석 - Project Master Sabermetrics
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 평균 P-WAR (기여도) ?
+
0.00
+ 대체 수준(0.0) 대비
+
+
+
+
+
+ 미결 리스크 총합 ?
+
0
+ 실시간 집계
+
+
+
+
+
+ 활성 자원 규모 ?
+
0
+ 시스템 기여 자원
+
+
+
+
+
+ 좀비 프로젝트 비율 ?
+
0%
+ 집중 관리 대상
+
+
+
+
+
+
+
+
+
+
+
+
+
+
양수(+) 운영 중
+
음수(-) 위험군
+
-0.3 이하 방치-삭제대상
+
시스템삭제 잠김예정 프로젝트
+
+
+
+
+
+
+
+
+
+
+
+
프로젝트 A (해외/중동)
+
파일 급증 대비 활동 정체 (P-ISO 급락)
+
위험
+
+
+
프로젝트 B (기술개발)
+
특정 환경(IE/Edge) 문의 집중 발생
+
주의
+
+
+
프로젝트 C (국내/장헌)
+
로그 활동성 및 해결률 안정적 유지
+
안전
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/dashboard.html b/templates/dashboard.html
index 34300be..5eb189b 100644
--- a/templates/dashboard.html
+++ b/templates/dashboard.html
@@ -22,10 +22,8 @@
- 대시보드
- 문의사항
- - 로그관리
- - 파일관리
- - 인원관리
- - 공지사항
+ - 메일관리
+ - 분석
diff --git a/templates/inquiries.html b/templates/inquiries.html
index 13b6b4c..3b39ca1 100644
--- a/templates/inquiries.html
+++ b/templates/inquiries.html
@@ -22,10 +22,8 @@
- 대시보드
- 문의사항
- - 로그관리
- - 파일관리
- - 인원관리
- - 공지사항
+ - 메일관리
+ - 분석
@@ -136,17 +134,33 @@
- | No |
+
+
+ |
이미지 |
- PM 종류 |
- 환경 |
- 구분 |
- 프로젝트 |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
문의내용 |
- 작성자 |
- 날짜 |
+
+
+ |
+
+
+ |
답변내용 |
- 상태 |
+
+
+ |
diff --git a/templates/mailTest.html b/templates/mailTest.html
index a71474d..4f98cdd 100644
--- a/templates/mailTest.html
+++ b/templates/mailTest.html
@@ -22,9 +22,9 @@
- 대시보드
+ - 문의사항
- 메일관리
- - 로그관리
- - 파일관리
+ - 분석