- 모던 다크 미니멀 테마(NanumSquare 번들, 단일 accent #4DABF7, 8px radius, flat 버튼, 다크 기본값) - 라인 아이콘 시스템(ui/icons.py, QtSvg) — 앱 전반 이모지 교체 - 다크 깨짐 수정: 테이블 헤더/코너 흰색, 도움말 탭 흰 라인, 트레이/미니위젯 메뉴 - fix: 자동 적립 OFF가 자동 퇴근 경로에서 무시되던 버그(게이팅) - feat: 연장근무 적립 기록 삭제(우클릭) - 테스트 3건 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
"""
|
|
목표 진행률 위젯.
|
|
|
|
월 연장근무 상한 + 일평균 목표를 stats_view 또는 메인에 표시.
|
|
설정값이 0이면 비활성 (위젯 자체 hide).
|
|
"""
|
|
from __future__ import annotations
|
|
from datetime import datetime, date
|
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar
|
|
from PyQt5.QtCore import Qt
|
|
|
|
|
|
class GoalWidget(QWidget):
|
|
"""월간 목표 진행률 표시."""
|
|
|
|
def __init__(self, db, parent=None):
|
|
super().__init__(parent)
|
|
self.db = db
|
|
layout = QVBoxLayout()
|
|
layout.setContentsMargins(8, 6, 8, 6)
|
|
layout.setSpacing(4)
|
|
|
|
title = QLabel("이번 달 목표")
|
|
title.setStyleSheet("font-weight: bold;")
|
|
layout.addWidget(title)
|
|
|
|
# 연장근무 상한
|
|
ot_row = QHBoxLayout()
|
|
self.ot_label = QLabel("연장근무:")
|
|
self.ot_label.setFixedWidth(100)
|
|
self.ot_bar = QProgressBar()
|
|
self.ot_bar.setTextVisible(True)
|
|
self.ot_bar.setFixedHeight(18)
|
|
ot_row.addWidget(self.ot_label)
|
|
ot_row.addWidget(self.ot_bar, 1)
|
|
layout.addLayout(ot_row)
|
|
|
|
# 일평균
|
|
avg_row = QHBoxLayout()
|
|
self.avg_label = QLabel("일평균:")
|
|
self.avg_label.setFixedWidth(100)
|
|
self.avg_bar = QProgressBar()
|
|
self.avg_bar.setTextVisible(True)
|
|
self.avg_bar.setFixedHeight(18)
|
|
avg_row.addWidget(self.avg_label)
|
|
avg_row.addWidget(self.avg_bar, 1)
|
|
layout.addLayout(avg_row)
|
|
|
|
self.setLayout(layout)
|
|
|
|
def refresh(self):
|
|
"""현재 설정값과 이번 달 통계로 진행률 갱신. 0=비활성 시 row 숨김."""
|
|
try:
|
|
ot_target = int(self.db.get_setting('goal_overtime_max_monthly', '0') or 0)
|
|
avg_target = float(self.db.get_setting('goal_avg_hours_daily', '0') or 0)
|
|
except (ValueError, TypeError):
|
|
ot_target, avg_target = 0, 0.0
|
|
|
|
# 둘 다 비활성이면 위젯 자체 숨김
|
|
if ot_target <= 0 and avg_target <= 0:
|
|
self.setVisible(False)
|
|
return
|
|
self.setVisible(True)
|
|
|
|
now = datetime.now()
|
|
stats = self.db.get_monthly_stats(now.year, now.month)
|
|
ot_total = (stats.get('total_overtime_minutes') or 0)
|
|
total_h = stats.get('total_hours') or 0
|
|
work_days = stats.get('work_days') or 1
|
|
|
|
# 연장근무 상한 (낮을수록 좋음)
|
|
if ot_target > 0:
|
|
self.ot_label.setVisible(True)
|
|
self.ot_bar.setVisible(True)
|
|
self.ot_bar.setMaximum(ot_target)
|
|
self.ot_bar.setValue(min(ot_total, ot_target))
|
|
ratio = ot_total / ot_target if ot_target else 0
|
|
ot_h, ot_m = ot_total // 60, ot_total % 60
|
|
tg_h, tg_m = ot_target // 60, ot_target % 60
|
|
self.ot_bar.setFormat(f"{ot_h}h {ot_m}m / {tg_h}h {tg_m}m")
|
|
color = '#51CF66' if ratio < 0.6 else ('#FAB005' if ratio < 1.0 else '#FA5252')
|
|
self.ot_bar.setStyleSheet(f"QProgressBar::chunk {{ background-color: {color}; }}")
|
|
else:
|
|
self.ot_label.setVisible(False)
|
|
self.ot_bar.setVisible(False)
|
|
|
|
# 일평균 (목표 시간보다 적으면 좋음)
|
|
if avg_target > 0:
|
|
self.avg_label.setVisible(True)
|
|
self.avg_bar.setVisible(True)
|
|
avg = total_h / work_days if work_days else 0
|
|
self.avg_bar.setMaximum(int(avg_target * 100))
|
|
self.avg_bar.setValue(int(min(avg, avg_target) * 100))
|
|
self.avg_bar.setFormat(f"{avg:.1f}h / {avg_target:.1f}h")
|
|
ratio = avg / avg_target if avg_target else 0
|
|
color = '#51CF66' if ratio < 0.9 else ('#FAB005' if ratio < 1.1 else '#FA5252')
|
|
self.avg_bar.setStyleSheet(f"QProgressBar::chunk {{ background-color: {color}; }}")
|
|
else:
|
|
self.avg_label.setVisible(False)
|
|
self.avg_bar.setVisible(False)
|