기능 추가 및 버그 수정 — 처리시간 지표, 대시보드 차트, UI 개선
## 처리시간 지표 - 업무시간 기준(09-18 평일) / 공휴일 제외 24h / 달력 기준 3가지 모드 선택 - 공휴일 DB 관리 (holidays 테이블, 수동 등록·삭제·일괄 추가) - 2026년 공휴일 등록 지원 - 설정 페이지에서 라디오 버튼으로 모드 선택 ## 대시보드 차트 - 월별 평균 처리시간 막대 차트 추가 - 월별 신고 접수 건수 누적 막대 차트 추가 - 월별 → 일별 드릴다운 (막대 클릭 시 해당 월의 일별 차트로 전환) - 일별 막대 클릭 시 처리 완료/신고 접수 상세 내역 모달 - 충전기별 누적 고장 건수 Top 10 수평 막대 차트 추가 ## 신고 목록 - # 컬럼을 DB PK 대신 현재 목록 순서(1, 2, 3…)로 표시 - 엑셀 export 접수번호도 순차번호로 변경 ## 모바일 네비게이션 버그 수정 - 모바일에서 가로 오버플로우 시 nav가 body 넓이로 늘어나 햄버거 버튼이 화면 밖으로 밀리는 문제 수정 - nav를 position:fixed + body padding-top:54px 로 변경 (전체 페이지 적용) - 충전기 관리·신고 목록 페이지 지도 컨테이너에 isolation:isolate 적용 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import os
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Body
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
from database import get_db
|
||||
import models
|
||||
from auth import require_admin, get_current_user
|
||||
@@ -41,7 +41,80 @@ def delete_type(type_id: int, db: Session = Depends(get_db), _=Depends(require_a
|
||||
db.delete(t); db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
# ── 충전기 종류별 에러 코드 ──────────────────────────
|
||||
@router.get("/types/{type_id}/errors")
|
||||
def list_type_errors(type_id: int, db: Session = Depends(get_db)):
|
||||
errors = (db.query(models.ChargerTypeError)
|
||||
.filter_by(charger_type_id=type_id)
|
||||
.order_by(models.ChargerTypeError.display_order)
|
||||
.all())
|
||||
return [{"id": e.id, "error_code": e.error_code, "error_name": e.error_name,
|
||||
"range_condition": e.range_condition, "description": e.description,
|
||||
"auto_recovery": e.auto_recovery, "display_order": e.display_order}
|
||||
for e in errors]
|
||||
|
||||
@router.post("/types/{type_id}/errors")
|
||||
def create_type_error(
|
||||
type_id: int,
|
||||
error_code: str = Form(...), error_name: str = Form(...),
|
||||
range_condition: str = Form(""), description: str = Form(""),
|
||||
auto_recovery: bool = Form(True), display_order: int = Form(0),
|
||||
db: Session = Depends(get_db), _=Depends(require_admin)
|
||||
):
|
||||
if not db.query(models.ChargerType).filter_by(id=type_id).first():
|
||||
raise HTTPException(404)
|
||||
e = models.ChargerTypeError(
|
||||
charger_type_id=type_id, error_code=error_code, error_name=error_name,
|
||||
range_condition=range_condition or None, description=description or None,
|
||||
auto_recovery=auto_recovery, display_order=display_order
|
||||
)
|
||||
db.add(e); db.commit(); db.refresh(e)
|
||||
return {"id": e.id}
|
||||
|
||||
@router.put("/types/{type_id}/errors/{error_id}")
|
||||
def update_type_error(
|
||||
type_id: int, error_id: int,
|
||||
error_code: str = Form(...), error_name: str = Form(...),
|
||||
range_condition: str = Form(""), description: str = Form(""),
|
||||
auto_recovery: bool = Form(True), display_order: int = Form(0),
|
||||
db: Session = Depends(get_db), _=Depends(require_admin)
|
||||
):
|
||||
e = db.query(models.ChargerTypeError).filter_by(id=error_id, charger_type_id=type_id).first()
|
||||
if not e: raise HTTPException(404)
|
||||
e.error_code = error_code; e.error_name = error_name
|
||||
e.range_condition = range_condition or None; e.description = description or None
|
||||
e.auto_recovery = auto_recovery; e.display_order = display_order
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@router.delete("/types/{type_id}/errors/{error_id}")
|
||||
def delete_type_error(
|
||||
type_id: int, error_id: int,
|
||||
db: Session = Depends(get_db), _=Depends(require_admin)
|
||||
):
|
||||
e = db.query(models.ChargerTypeError).filter_by(id=error_id, charger_type_id=type_id).first()
|
||||
if not e: raise HTTPException(404)
|
||||
db.delete(e); db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
# ── 충전기 ──────────────────────────────────────────
|
||||
@router.delete("/bulk")
|
||||
def bulk_delete_chargers(
|
||||
ids: List[str] = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
_=Depends(require_admin)
|
||||
):
|
||||
if not ids:
|
||||
raise HTTPException(400, "삭제할 항목을 선택하세요.")
|
||||
has_reports = db.query(models.Report.charger_id).filter(
|
||||
models.Report.charger_id.in_(ids)).distinct().all()
|
||||
if has_reports:
|
||||
blocked = [r[0] for r in has_reports]
|
||||
raise HTTPException(400, f"신고 내역이 있는 충전기는 삭제할 수 없습니다: {', '.join(blocked)}")
|
||||
result = db.query(models.Charger).filter(models.Charger.id.in_(ids)).delete(synchronize_session=False)
|
||||
db.commit()
|
||||
return {"deleted": result}
|
||||
|
||||
@router.get("")
|
||||
def list_chargers(db: Session = Depends(get_db)):
|
||||
chargers = db.query(models.Charger).order_by(models.Charger.id).all()
|
||||
@@ -62,6 +135,19 @@ def list_chargers(db: Session = Depends(get_db)):
|
||||
})
|
||||
return result
|
||||
|
||||
@router.get("/{charger_id}/errors")
|
||||
def get_charger_errors(charger_id: str, db: Session = Depends(get_db)):
|
||||
c = db.query(models.Charger).filter_by(id=charger_id).first()
|
||||
if not c: raise HTTPException(404)
|
||||
if not c.charger_type_id: return []
|
||||
errors = (db.query(models.ChargerTypeError)
|
||||
.filter_by(charger_type_id=c.charger_type_id)
|
||||
.order_by(models.ChargerTypeError.display_order)
|
||||
.all())
|
||||
return [{"id": e.id, "error_code": e.error_code, "error_name": e.error_name,
|
||||
"range_condition": e.range_condition, "auto_recovery": e.auto_recovery}
|
||||
for e in errors]
|
||||
|
||||
@router.get("/{charger_id}")
|
||||
def get_charger(charger_id: str, db: Session = Depends(get_db)):
|
||||
c = db.query(models.Charger).filter_by(id=charger_id).first()
|
||||
|
||||
Reference in New Issue
Block a user