Commit 78aa85f9 by Ooh-Ao

admin dashboard

parent 5e6ea45f
# myproject/controllers/admin_dashboard_controller.py
from datetime import date
from sqlalchemy import select, func, extract, case
from sqlalchemy.ext.asyncio import AsyncSession
from ..models.project import Project
from ..models.member import Member
from ..models.equipment import Equipment
from ..models.project_equipment import ProjectEquipment
from ..models.borrow_transaction import BorrowTransaction
from ..schemas.admin_dashboard_schema import (
SummaryCard, TrendItem, PieSlice
)
async def get_admin_dashboard(
db: AsyncSession,
start_date: date,
end_date: date
):
# 1) Summary Cards
total_members = await db.scalar(select(func.count()).select_from(Member))
prev_members = await db.scalar(
select(func.count()).select_from(Member)
.where(Member.createdAt.between(start_date.replace(year=start_date.year-1), start_date))
)
total_projects = await db.scalar(select(func.count()).select_from(Project))
prev_projects = await db.scalar(
select(func.count()).select_from(Project)
.where(Project.createdAt.between(start_date.replace(year=start_date.year-1), start_date))
)
total_equips = await db.scalar(select(func.count()).select_from(Equipment))
prev_equips = await db.scalar(
select(func.count()).select_from(Equipment)
.where(Equipment.createdAt.between(start_date.replace(year=start_date.year-1), start_date))
)
summary = [
SummaryCard(label="จำนวนผู้ใช้งานทั้งหมด",
value=total_members,
change=(total_members - prev_members) / prev_members * 100 if prev_members else 0),
SummaryCard(label="จำนวนโครงการทั้งหมด",
value=total_projects,
change=(total_projects - prev_projects) / prev_projects * 100 if prev_projects else 0),
SummaryCard(label="จำนวนอุปกรณ์ทั้งหมด",
value=total_equips,
change=(total_equips - prev_equips) / prev_equips * 100 if prev_equips else 0),
]
# 2) Monthly Trends (bar chart) ยืม vs คืน
stmt = (
select(
extract('month', BorrowTransaction.created_at).label('m'),
func.sum(case((BorrowTransaction.status != 'returned', 1), else_=0)).label('borrowed'),
func.sum(case((BorrowTransaction.status == 'returned', 1), else_=0)).label('returned'),
)
.where(BorrowTransaction.created_at.between(start_date, end_date))
.group_by('m')
.order_by('m')
)
rows = (await db.execute(stmt)).all()
monthly_trends = [
TrendItem(period=date(1900, int(m), 1).strftime('%b'),
borrowed=int(b), returned=int(r))
for m, b, r in rows
]
# 3) Equipment Distribution (pie chart)
stmt2 = (
select(
Equipment.equipmentName,
func.coalesce(func.sum(ProjectEquipment.quantity_in_project), 0).label('count')
)
.join(ProjectEquipment, Equipment.equipmentId == ProjectEquipment.equipmentId)
.group_by(Equipment.equipmentName)
)
rows2 = (await db.execute(stmt2)).all()
equipment_distribution = [
PieSlice(category=name, count=int(cnt))
for name, cnt in rows2
]
# 4) Borrow/Return Line (line chart)
stmt3 = (
select(
extract('month', BorrowTransaction.created_at).label('m'),
func.sum(case((BorrowTransaction.status != 'returned', 1), else_=0)).label('borrowed'),
func.sum(case((BorrowTransaction.status == 'returned', 1), else_=0)).label('returned'),
)
.where(BorrowTransaction.created_at.between(start_date, end_date))
.group_by('m')
.order_by('m')
)
rows3 = (await db.execute(stmt3)).all()
borrow_return_line = [
TrendItem(period=date(1900, int(m), 1).strftime('%b'),
borrowed=int(b), returned=int(r))
for m, b, r in rows3
]
return {
"period": {"start": start_date, "end": end_date},
"summary": summary,
"monthly_trends": monthly_trends,
"equipment_distribution": equipment_distribution,
"borrow_return_line": borrow_return_line,
}
...@@ -10,6 +10,7 @@ from .routes.project_routes import router as project_router ...@@ -10,6 +10,7 @@ from .routes.project_routes import router as project_router
from .routes.project_member_routes import router as pm_router from .routes.project_member_routes import router as pm_router
from .routes.project_equipment_routes import router as pe_router from .routes.project_equipment_routes import router as pe_router
from .routes.borrow_routes import router as borrow_router from .routes.borrow_routes import router as borrow_router
from .routes.admin_dashboard_routes import router as admin_dashboard_router
from .routes.inventory_lot_router import router as inventory_lot_router from .routes.inventory_lot_router import router as inventory_lot_router
from .routes.equipment_category_router import router as equipment_category_router from .routes.equipment_category_router import router as equipment_category_router
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
...@@ -47,6 +48,7 @@ app.include_router(project_router, prefix="/projects", tags=["Projects"]) ...@@ -47,6 +48,7 @@ app.include_router(project_router, prefix="/projects", tags=["Projects"])
app.include_router(pm_router, prefix="/project-members", tags=["ProjectMembers"]) app.include_router(pm_router, prefix="/project-members", tags=["ProjectMembers"])
app.include_router(pe_router, prefix="/project-equipments", tags=["ProjectEquipments"]) app.include_router(pe_router, prefix="/project-equipments", tags=["ProjectEquipments"])
app.include_router(borrow_router, prefix="/borrow-transactions", tags=["BorrowTransactions"]) app.include_router(borrow_router, prefix="/borrow-transactions", tags=["BorrowTransactions"])
app.include_router(admin_dashboard_router, prefix="/admin", tags=["AdminDashboard"])
app.include_router(file_upload_router.router, prefix="/api", tags=["File Upload"]) app.include_router(file_upload_router.router, prefix="/api", tags=["File Upload"])
@app.get("/") @app.get("/")
async def root(): async def root():
......
# myproject/routes/admin_dashboard_routes.py
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from datetime import date, timedelta
from typing import Optional
from ..config.database import get_db
from ..controllers.admin_dashboard_controller import get_admin_dashboard
from ..schemas.admin_dashboard_schema import AdminDashboardResponse
router = APIRouter(prefix="/admin/dashboard", tags=["AdminDashboard"])
@router.get("", response_model=AdminDashboardResponse)
async def admin_dashboard(
start_date: Optional[date] = Query(None, description="YYYY-MM-DD"),
end_date: Optional[date] = Query(None, description="YYYY-MM-DD"),
db: AsyncSession = Depends(get_db)
):
# กำหนด default ช่วง 1 ปีย้อนหลังถ้าไม่ส่งมา
if not end_date:
end_date = date.today()
if not start_date:
start_date = end_date - timedelta(days=365)
return await get_admin_dashboard(db, start_date, end_date)
# myproject/schemas/admin_dashboard_schema.py
from pydantic import BaseModel
from datetime import date
from typing import List
class SummaryCard(BaseModel):
label: str
value: int
change: float # เปอร์เซนต์เปลี่ยนแปลงเทียบช่วงก่อนหน้า (–1.05 = -1.05%)
class TrendItem(BaseModel):
period: str # เช่น "Jan", "Feb", ...
borrowed: int
returned: int
class PieSlice(BaseModel):
category: str # เช่น ชื่ออุปกรณ์ หรือ "Returned"/"Borrowed"
count: int
class AdminDashboardResponse(BaseModel):
period: dict # {"start": date, "end": date}
summary: List[SummaryCard]
monthly_trends: List[TrendItem] # bar chart เดือนละยอดยืม/คืน
equipment_distribution: List[PieSlice] # pie chart สัดส่วนอุปกรณ์
borrow_return_line: List[TrendItem] # line chart ยืมกับคืนรายเดือน
class Config:
orm_mode = True
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment