You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
# backend/app/routers/dashboard.py
|
|
from fastapi import APIRouter, Depends
|
|
from sqlmodel import Session, select
|
|
|
|
from ..db import get_session
|
|
from ..models import Approval, Briefing, Event, Goal, InboxItem, Person, Project, Task
|
|
from ..routers.inbox import latest_cls
|
|
from ..schemas import (
|
|
ApprovalSummaryOut,
|
|
BadgesOut,
|
|
BriefingOut,
|
|
DashboardOut,
|
|
EventOut,
|
|
GoalOut,
|
|
InboxRecentOut,
|
|
TaskSummaryItem,
|
|
TaskSummaryOut,
|
|
UserOut,
|
|
WeatherOut,
|
|
)
|
|
|
|
router = APIRouter() # prefix 없음. main.py 에서 prefix="/api" 등록.
|
|
|
|
# 날씨 아이콘 저장값 → 표시값 매핑(cloudSun 저장 → sun 표시)
|
|
WEATHER_ICON_MAP = {"cloudSun": "sun"}
|
|
|
|
|
|
@router.get("/dashboard", response_model=DashboardOut)
|
|
def dashboard(s: Session = Depends(get_session)):
|
|
me = s.exec(select(Person).where(Person.is_me == True)).first() # noqa: E712
|
|
user = UserOut(name=me.name, initial=me.initial) if me else UserOut(name="", initial="")
|
|
|
|
br = s.exec(select(Briefing)).first()
|
|
if br:
|
|
weather = WeatherOut(
|
|
temp=br.weather_temp, cond=br.weather_cond,
|
|
icon=WEATHER_ICON_MAP.get(br.weather_icon, br.weather_icon),
|
|
)
|
|
briefing = BriefingOut(
|
|
today=br.today, weather=weather, commute=br.commute, sleep=br.sleep, note=br.note
|
|
)
|
|
saved_today, today_routed = br.saved_today, br.today_routed
|
|
else:
|
|
briefing = BriefingOut(
|
|
today="", weather=WeatherOut(temp=0, cond="", icon="sun"),
|
|
commute="", sleep="", note="",
|
|
)
|
|
saved_today, today_routed = "", 0
|
|
|
|
schedule = [
|
|
EventOut(
|
|
id=e.id, time=e.time, title=e.title, tag=e.tag, dur=e.dur, tone=e.tone, soon=e.soon
|
|
)
|
|
for e in s.exec(select(Event).order_by(Event.sort_order)).all()
|
|
]
|
|
|
|
tasks = s.exec(select(Task)).all()
|
|
proj_name = {p.id: p.name for p in s.exec(select(Project)).all()}
|
|
# 요약은 최상위(parent_id=None) 미완료 작업만, 상위 5건. open_count=최상위 미완료 수.
|
|
open_tasks = sorted(
|
|
[t for t in tasks if t.parent_id is None and t.status.value != "done"],
|
|
key=lambda t: t.sort_order,
|
|
)
|
|
items = [
|
|
TaskSummaryItem(
|
|
id=t.id, title=t.title, prio=t.prio.value, project=proj_name.get(t.project_id, "")
|
|
)
|
|
for t in open_tasks[:5]
|
|
]
|
|
task_summary = TaskSummaryOut(open_count=len(open_tasks), items=items)
|
|
|
|
goals = [
|
|
GoalOut(id=g.id, title=g.title, pct=g.pct, sub=g.sub, tone=g.tone)
|
|
for g in s.exec(select(Goal).order_by(Goal.sort_order)).all()
|
|
]
|
|
|
|
apprs = s.exec(select(Approval).order_by(Approval.sort_order)).all()
|
|
high = [a for a in apprs if a.risk == "high"]
|
|
approvals_summary = [
|
|
ApprovalSummaryOut(id=a.id, icon=a.icon, tone=a.tone, title=a.title, time=a.time)
|
|
for a in high[:3]
|
|
] # high-risk 만, 최대 3건
|
|
|
|
recent = s.exec(select(InboxItem).order_by(InboxItem.created_at.desc()).limit(3)).all()
|
|
# 평탄화: 각 항목의 최신 classification 에서 type/proj_label/tone 을 끌어와 채운다.
|
|
inbox_recent = []
|
|
for it in recent:
|
|
c = latest_cls(s, it.id)
|
|
inbox_recent.append(
|
|
InboxRecentOut(
|
|
id=it.id,
|
|
kind=it.kind.value if hasattr(it.kind, "value") else it.kind,
|
|
raw=it.raw,
|
|
type=(c.type.value if c and hasattr(c.type, "value") else (c.type if c else "")),
|
|
proj_label=c.proj_label if c else "",
|
|
tone=c.tone if c else "ink",
|
|
)
|
|
)
|
|
|
|
badges = BadgesOut(appr=len(high), task=len(open_tasks), noti=6)
|
|
return DashboardOut(
|
|
user=user, briefing=briefing, saved_today=saved_today, today_routed=today_routed,
|
|
schedule=schedule, task_summary=task_summary, goals=goals,
|
|
approvals_summary=approvals_summary, inbox_recent=inbox_recent, badges=badges,
|
|
)
|