""" 반복 연차 등록/관리 다이얼로그. 지원: 매주/격주 요일, 매월 N일. """ from __future__ import annotations from datetime import date from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QDateEdit, QSpinBox, QDoubleSpinBox, QLineEdit, QGroupBox, QListWidget, QListWidgetItem, QMessageBox, QCheckBox, QButtonGroup, QRadioButton) from PyQt5.QtCore import QDate, Qt from core.recurring_leaves import describe_pattern from core.i18n import tr from ui.styles import apply_dark_titlebar _KO_WEEKDAYS = [('월', 'mon'), ('화', 'tue'), ('수', 'wed'), ('목', 'thu'), ('금', 'fri'), ('토', 'sat'), ('일', 'sun')] class RecurringLeaveDialog(QDialog): """반복 연차 패턴 추가/삭제.""" def __init__(self, parent=None, db=None): super().__init__(parent) self.db = db self.setWindowTitle(tr('recurring.title')) self.setMinimumSize(540, 480) self._build_ui() self._reload_list() apply_dark_titlebar(self) def _build_ui(self): layout = QVBoxLayout() # 기존 패턴 목록 list_group = QGroupBox(tr('recurring.list_group')) lg = QVBoxLayout() self.list_widget = QListWidget() self.list_widget.setMinimumHeight(160) lg.addWidget(self.list_widget) del_btn = QPushButton(tr('recurring.btn_delete_selected')) del_btn.clicked.connect(self._delete_selected) lg.addWidget(del_btn) list_group.setLayout(lg) layout.addWidget(list_group) # 신규 등록 add_group = QGroupBox(tr('recurring.add_group')) ag = QVBoxLayout() # 패턴 종류 kind_row = QHBoxLayout() kind_row.addWidget(QLabel(tr('recurring.label_cycle'))) self.kind_group = QButtonGroup(self) self.rb_weekly = QRadioButton(tr('recurring.weekly')) self.rb_weekly.setChecked(True) self.rb_biweekly = QRadioButton(tr('recurring.biweekly')) self.rb_monthly = QRadioButton(tr('recurring.monthly')) for rb in (self.rb_weekly, self.rb_biweekly, self.rb_monthly): self.kind_group.addButton(rb) kind_row.addWidget(rb) kind_row.addStretch() ag.addLayout(kind_row) # 요일 체크박스 (weekly/biweekly) wd_row = QHBoxLayout() wd_row.addWidget(QLabel(tr('recurring.label_weekday'))) self.weekday_checks = [] for ko, en in _KO_WEEKDAYS: cb = QCheckBox(tr(f'label.weekday_{en}')) self.weekday_checks.append((cb, en)) wd_row.addWidget(cb) wd_row.addStretch() ag.addLayout(wd_row) # 매월 N일 month_row = QHBoxLayout() month_row.addWidget(QLabel(tr('recurring.label_monthly_day'))) self.day_of_month = QSpinBox() self.day_of_month.setRange(1, 31) self.day_of_month.setValue(15) self.day_of_month.setSuffix(tr('recurring.day_suffix')) month_row.addWidget(self.day_of_month) month_row.addStretch() ag.addLayout(month_row) # 차감 일수 days_row = QHBoxLayout() days_row.addWidget(QLabel(tr('recurring.label_deduction'))) self.days_combo = QComboBox() self.days_combo.addItem(tr('recurring.deduction_full'), 1.0) self.days_combo.addItem(tr('recurring.deduction_half'), 0.5) self.days_combo.addItem(tr('recurring.deduction_quarter'), 0.25) days_row.addWidget(self.days_combo) days_row.addStretch() ag.addLayout(days_row) # 시작/종료 날짜 date_row = QHBoxLayout() date_row.addWidget(QLabel(tr('recurring.label_start'))) self.start_edit = QDateEdit() self.start_edit.setDate(QDate.currentDate()) self.start_edit.setCalendarPopup(True) date_row.addWidget(self.start_edit) date_row.addWidget(QLabel(tr('recurring.label_end'))) self.end_edit = QDateEdit() self.end_edit.setDate(QDate.currentDate().addMonths(6)) self.end_edit.setCalendarPopup(True) date_row.addWidget(self.end_edit) self.no_end_check = QCheckBox(tr('recurring.no_end')) self.no_end_check.toggled.connect( lambda v: self.end_edit.setEnabled(not v) ) date_row.addWidget(self.no_end_check) date_row.addStretch() ag.addLayout(date_row) # 메모 memo_row = QHBoxLayout() memo_row.addWidget(QLabel(tr('recurring.label_memo'))) self.memo_edit = QLineEdit() self.memo_edit.setPlaceholderText(tr('recurring.memo_placeholder')) memo_row.addWidget(self.memo_edit) ag.addLayout(memo_row) # 추가 버튼 add_btn = QPushButton(tr('recurring.btn_add')) add_btn.setObjectName("btn_primary") add_btn.clicked.connect(self._save) ag.addWidget(add_btn) add_group.setLayout(ag) layout.addWidget(add_group) # 닫기 close_btn = QPushButton(tr('btn.close')) close_btn.clicked.connect(self.close) layout.addWidget(close_btn) self.setLayout(layout) def _reload_list(self): self.list_widget.clear() for r in self.db.get_recurring_leaves(): desc = describe_pattern(r['pattern']) end = r.get('end_date') or tr('recurring.no_end') text = (f"[{r['id']}] {desc} · {r['days']}일 ({r['leave_type']}) " f"· {r['start_date']} ~ {end}") if r.get('memo'): text += f" — {r['memo']}" item = QListWidgetItem(text) item.setData(Qt.UserRole, r['id']) self.list_widget.addItem(item) def _delete_selected(self): item = self.list_widget.currentItem() if not item: return rec_id = item.data(Qt.UserRole) reply = QMessageBox.question( self, tr('recurring.delete_confirm_title'), tr('recurring.delete_confirm_body', item=item.text()), QMessageBox.Yes | QMessageBox.No, ) if reply == QMessageBox.Yes: self.db.delete_recurring_leave(rec_id) self._reload_list() def _build_pattern(self) -> str | None: if self.rb_monthly.isChecked(): return f"monthly:{self.day_of_month.value()}" # weekly/biweekly chosen = [en for cb, en in self.weekday_checks if cb.isChecked()] if not chosen: return None prefix = 'weekly' if self.rb_weekly.isChecked() else 'biweekly' return f"{prefix}:" + ",".join(chosen) def _save(self): pattern = self._build_pattern() if not pattern: QMessageBox.warning(self, tr('recurring.input_error_title'), tr('recurring.input_error_weekday')) return days = self.days_combo.currentData() leave_type = self.days_combo.currentText().split(' ')[1].strip('()') start = self.start_edit.date().toString('yyyy-MM-dd') end = None if self.no_end_check.isChecked() else self.end_edit.date().toString('yyyy-MM-dd') memo = self.memo_edit.text().strip() self.db.add_recurring_leave(pattern, leave_type, days, start, end, memo) QMessageBox.information(self, tr('recurring.add_done_title'), tr('recurring.add_done_body', pattern=describe_pattern(pattern))) self.memo_edit.clear() self._reload_list()