## 처리시간 지표 - 업무시간 기준(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>
102 lines
3.9 KiB
Python
102 lines
3.9 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Form, Body
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional
|
|
from database import get_db
|
|
import models
|
|
from auth import require_admin, hash_password, get_current_user
|
|
|
|
router = APIRouter(prefix="/api/accounts", tags=["accounts"])
|
|
|
|
@router.get("")
|
|
def list_users(role: Optional[str] = None, db: Session = Depends(get_db), _=Depends(require_admin)):
|
|
q = db.query(models.User)
|
|
if role: q = q.filter(models.User.role == role)
|
|
return [{
|
|
"id": u.id, "username": u.username, "role": u.role,
|
|
"company": u.company, "name": u.name, "phone": u.phone,
|
|
"email": u.email, "is_active": u.is_active,
|
|
"is_pending": getattr(u, 'is_pending', False),
|
|
"created_at": u.created_at.isoformat(),
|
|
} for u in q.order_by(models.User.id).all()]
|
|
|
|
@router.post("")
|
|
def create_user(
|
|
username: str = Form(...), password: str = Form(...),
|
|
role: str = Form(...), name: str = Form(...),
|
|
company: str = Form(""), phone: str = Form(""), email: str = Form(""),
|
|
db: Session = Depends(get_db), _=Depends(require_admin)
|
|
):
|
|
if db.query(models.User).filter_by(username=username).first():
|
|
raise HTTPException(400, "이미 존재하는 아이디입니다.")
|
|
u = models.User(
|
|
username=username, password_hash=hash_password(password),
|
|
role=role, name=name, company=company or None,
|
|
phone=phone or None, email=email or None
|
|
)
|
|
db.add(u); db.commit(); db.refresh(u)
|
|
return {"id": u.id, "username": u.username}
|
|
|
|
@router.put("/{user_id}")
|
|
def update_user(
|
|
user_id: int,
|
|
name: str = Form(...), company: str = Form(""),
|
|
phone: str = Form(""), email: str = Form(""),
|
|
is_active: bool = Form(True),
|
|
password: Optional[str] = Form(None),
|
|
db: Session = Depends(get_db), _=Depends(require_admin)
|
|
):
|
|
u = db.query(models.User).filter_by(id=user_id).first()
|
|
if not u: raise HTTPException(404)
|
|
u.name = name; u.company = company or None
|
|
u.phone = phone or None; u.email = email or None
|
|
u.is_active = is_active
|
|
if password: u.password_hash = hash_password(password)
|
|
db.commit()
|
|
return {"ok": True}
|
|
|
|
@router.patch("/{user_id}/approve")
|
|
def approve_user(user_id: int, db: Session = Depends(get_db), _=Depends(require_admin)):
|
|
u = db.query(models.User).filter_by(id=user_id).first()
|
|
if not u: raise HTTPException(404)
|
|
u.is_active = True
|
|
u.is_pending = False
|
|
db.commit()
|
|
return {"ok": True}
|
|
|
|
@router.delete("/bulk")
|
|
def bulk_delete_accounts(
|
|
ids: List[int] = Body(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(require_admin)
|
|
):
|
|
if not ids:
|
|
raise HTTPException(400, "삭제할 항목을 선택하세요.")
|
|
safe_ids = [i for i in ids if i != current_user.id]
|
|
count = db.query(models.User).filter(models.User.id.in_(safe_ids)).update(
|
|
{"is_active": False}, synchronize_session=False)
|
|
db.commit()
|
|
return {"deactivated": count}
|
|
|
|
@router.delete("/{user_id}")
|
|
def delete_user(user_id: int, db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(require_admin)):
|
|
if user_id == current_user.id:
|
|
raise HTTPException(400, "자신의 계정은 삭제할 수 없습니다.")
|
|
u = db.query(models.User).filter_by(id=user_id).first()
|
|
if not u: raise HTTPException(404)
|
|
u.is_active = False; db.commit()
|
|
return {"ok": True}
|
|
|
|
@router.patch("/me/password")
|
|
def change_my_password(
|
|
current_password: str = Form(...), new_password: str = Form(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user)
|
|
):
|
|
from auth import verify_password
|
|
if not verify_password(current_password, current_user.password_hash):
|
|
raise HTTPException(400, "현재 비밀번호가 올바르지 않습니다.")
|
|
current_user.password_hash = hash_password(new_password)
|
|
db.commit()
|
|
return {"ok": True}
|