feat: 문의사항(Q&A) 게시판 구현 및 대시보드 활성도 분석 로직 개선

- [server.py] 문의사항 API(목록, 상세, 답변 저장/삭제) 및 라우트 추가
- [templates/inquiries.html] 문의사항 게시판 HTML 구조 구현 (Sticky 헤더, 아코디언 상세)
- [js/inquiries.js] 비동기 데이터 로드, 아코디언 토글, 답변 편집 로직 구현
- [style/inquiries.css] 와이드 레이아웃, Sticky 헤더, 아코디언 및 상태 배지 스타일 적용
- [server.py, js/dashboard.js] '폴더 자동 삭제' 로그 발생 시 14일 경과 여부와 관계없이 '방치'로 분류하도록 로직 수정
- [templates/dashboard.html] 대시보드 내 문의사항 탭 활성화 및 링크 연동
This commit is contained in:
2026-03-12 18:01:51 +09:00
parent 600c54c1f0
commit 5ddaad6a2e
6 changed files with 766 additions and 12 deletions

View File

@@ -71,6 +71,86 @@ async def get_dashboard(request: Request):
async def get_mail_test(request: Request):
return templates.TemplateResponse("mailTest.html", {"request": request})
@app.get("/inquiries")
async def get_inquiries_page(request: Request):
return templates.TemplateResponse("inquiries.html", {"request": request})
class InquiryReplyRequest(BaseModel):
reply: str
status: str
handler: str
# --- 문의사항 API ---
@app.get("/api/inquiries")
async def get_inquiries(pm_type: str = None, category: str = None, status: str = None, keyword: str = None):
# ... (existing code)
try:
with get_db_connection() as conn:
with conn.cursor() as cursor:
sql = "SELECT * FROM inquiries WHERE 1=1"
params = []
if pm_type:
sql += " AND pm_type = %s"
params.append(pm_type)
if category:
sql += " AND category = %s"
params.append(category)
if status:
sql += " AND status = %s"
params.append(status)
if keyword:
sql += " AND (content LIKE %s OR author LIKE %s OR project_nm LIKE %s)"
params.extend([f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"])
sql += " ORDER BY no DESC"
cursor.execute(sql, params)
return cursor.fetchall()
except Exception as e:
return {"error": str(e)}
@app.get("/api/inquiries/{id}")
async def get_inquiry_detail(id: int):
try:
with get_db_connection() as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM inquiries WHERE id = %s", (id,))
return cursor.fetchone()
except Exception as e:
return {"error": str(e)}
@app.post("/api/inquiries/{id}/reply")
async def update_inquiry_reply(id: int, req: InquiryReplyRequest):
try:
with get_db_connection() as conn:
with conn.cursor() as cursor:
handled_date = datetime.now().strftime("%Y. %m. %d")
sql = """
UPDATE inquiries
SET reply = %s, status = %s, handler = %s, handled_date = %s
WHERE id = %s
"""
cursor.execute(sql, (req.reply, req.status, req.handler, handled_date, id))
conn.commit()
return {"success": True}
except Exception as e:
return {"error": str(e)}
@app.delete("/api/inquiries/{id}/reply")
async def delete_inquiry_reply(id: int):
try:
with get_db_connection() as conn:
with conn.cursor() as cursor:
sql = """
UPDATE inquiries
SET reply = '', status = '미확인', handled_date = ''
WHERE id = %s
"""
cursor.execute(sql, (id,))
conn.commit()
return {"success": True}
except Exception as e:
return {"error": str(e)}
# --- 분석 및 수집 API ---
@app.get("/available-dates")
async def get_available_dates():
@@ -153,14 +233,19 @@ async def get_project_activity(date: str = None):
# [핵심] 파일이 0개면 무조건 "데이터 없음"
status = "unknown"
elif has_log:
# 로그 날짜가 있는 경우 정밀 분석
match = re.search(r'(\d{4})\.(\d{2})\.(\d{2})', log)
if match:
diff = (target_date_dt - datetime.strptime(match.group(0), "%Y.%m.%d")).days
status = "active" if diff <= 7 else "warning" if diff <= 14 else "stale"
days = diff
else:
if "폴더자동삭제" in log.replace(" ", ""):
# [추가] 폴더 자동 삭제인 경우 날짜 상관없이 무조건 "방치"
status = "stale"
days = 999
else:
# 로그 날짜가 있는 경우 정밀 분석
match = re.search(r'(\d{4})\.(\d{2})\.(\d{2})', log)
if match:
diff = (target_date_dt - datetime.strptime(match.group(0), "%Y.%m.%d")).days
status = "active" if diff <= 7 else "warning" if diff <= 14 else "stale"
days = diff
else:
status = "stale"
else:
# 파일은 있지만 로그가 없는 경우
status = "stale"