# -*- coding: utf-8 -*- """ 인수/인계 다이얼로그 모듈 팀 간 업무 인수인계를 처리합니다. """ from datetime import datetime, date from typing import Optional, List, Dict, Any from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView, QScrollArea, QSplitter, QPushButton, QCheckBox ) from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QFont from ui.base.base_dialog import BaseDialog from ui.components.custom_button import CustomButton from ui.components.custom_input import CustomComboBox from core.config import ConfigManager from core.constants import TEAMS from core.logger import get_logger from database.crud import CRUDManager from services.weather_service import WeatherService logger = get_logger(__name__) # 팀 순환 순서 (4->3->2->1->4...) TEAM_CYCLE = ["4팀", "3팀", "2팀", "1팀"] def get_next_team(current_team: str) -> str: """다음 순번 팀 반환 (4->3->2->1->4)""" try: idx = TEAM_CYCLE.index(current_team) return TEAM_CYCLE[(idx + 1) % len(TEAM_CYCLE)] except ValueError: return "1팀" def get_handover_info(current_team: str, current_shift: str) -> Dict[str, str]: """ 인계팀/인수팀 정보 반환 주간->야간, 야간->주간 인계 같은 근무유형 내에서 4->3->2->1 순환 Returns: { 'handing_team': 인계팀, 'handing_shift': 인계 근무, 'receiving_team': 인수팀, 'receiving_shift': 인수 근무, } """ if current_shift == "주간": # 주간 -> 야간 인계 # 야간팀은 같은 순환에서 다음 팀 receiving_shift = "야간" receiving_team = get_next_team(current_team) else: # 야간 -> 주간 인계 # 주간팀은 같은 순환에서 다음 팀 receiving_shift = "주간" receiving_team = get_next_team(current_team) return { 'handing_team': current_team, 'handing_shift': current_shift, 'receiving_team': receiving_team, 'receiving_shift': receiving_shift, } class HandoverDialog(BaseDialog): """ 인수/인계 다이얼로그 팀 간 업무 인수인계를 처리합니다. - 왼쪽: 인계팀 정보 - 오른쪽: 인수팀 정보 - 가운데: 섹션별 인계 내용 """ handover_completed = Signal() def __init__( self, parent=None, current_team: str = "1팀", current_shift: str = "주간" ): self.config = ConfigManager() self.crud = CRUDManager() self.weather_service = WeatherService() # 인계/인수 팀 정보 계산 self.handover_info = get_handover_info(current_team, current_shift) self.handing_team = self.handover_info['handing_team'] self.handing_shift = self.handover_info['handing_shift'] self.receiving_team = self.handover_info['receiving_team'] self.receiving_shift = self.handover_info['receiving_shift'] # 인계할 항목 추적 self._handover_items = { 'instructions': [], 'faults': [], 'works': [], 'miscs': [], } # 할일 목록에 추가할 항목 self._todo_items = [] super().__init__( parent, title="인수/인계", width=1400, height=900, min_width=1200, min_height=700, resizable=True ) self._setup_content() self._load_data() def change_UI_Info(self, handing_team: str, handing_shift: str, receiving_team: str, receiving_shift: str): """UI 정보 변경""" self.handing_team = handing_team self.handing_shift = handing_shift self.receiving_team = receiving_team self.receiving_shift = receiving_shift self.title = f"인수/인계 - {handing_team} {handing_shift} → {receiving_team} {receiving_shift}" self.setWindowTitle(self.title) self._setup_content() self._load_data() def _setup_content(self): """컨텐츠 설정""" theme = self.config.theme is_dark = theme == 'dark' # 메인 레이아웃 main_widget = QWidget() main_layout = QVBoxLayout(main_widget) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(16) # 상단 정보 영역 self._create_header_section(main_layout) # 중앙 콘텐츠 영역 (좌/우/중앙) self._create_main_content(main_layout) # 하단 버튼 영역 self._create_footer_section(main_layout) self.content_layout.addWidget(main_widget) def _create_header_section(self, layout: QVBoxLayout): """상단 정보 섹션 생성""" theme = self.config.theme is_dark = theme == 'dark' header = QWidget() header_layout = QHBoxLayout(header) header_layout.setContentsMargins(16, 8, 16, 8) header_layout.setSpacing(24) # 날짜 today = date.today() date_label = QLabel(f"📅 {today.year}년 {today.month}월 {today.day}일") date_label.setFont(QFont("GmarketSans", 16, QFont.Bold)) date_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") header_layout.addWidget(date_label) # 날씨 weather_data = self.weather_service.get_last_weather() weather_icon = weather_data.get('icon', '🌤') weather_temp = weather_data.get('temp', '--') weather_cond = weather_data.get('condition', '정보 없음') weather_label = QLabel(f"{weather_icon} {weather_temp}°C {weather_cond}") weather_label.setFont(QFont("GmarketSans", 14)) weather_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};") header_layout.addWidget(weather_label) header_layout.addStretch() # 인계 방향 표시 arrow_label = QLabel( f"📤 {self.handing_team} {self.handing_shift} → " f"📥 {self.receiving_team} {self.receiving_shift}" ) arrow_label.setFont(QFont("GmarketSans", 16, QFont.Bold)) arrow_label.setStyleSheet(f"color: {'#3b82f6' if is_dark else '#2563eb'};") header_layout.addWidget(arrow_label) # 헤더 스타일 header.setStyleSheet(f""" QWidget {{ background-color: {'#1e293b' if is_dark else '#f1f5f9'}; border-radius: 8px; }} """) layout.addWidget(header) def _create_main_content(self, layout: QVBoxLayout): """메인 콘텐츠 영역 생성""" theme = self.config.theme is_dark = theme == 'dark' # 스플리터로 좌/중앙/우 분할 splitter = QSplitter(Qt.Horizontal) # 왼쪽: 인계팀 정보 left_panel = self._create_team_panel( "인계팀", self.handing_team, self.handing_shift, is_handing=True ) splitter.addWidget(left_panel) # 가운데: 섹션별 내용 center_panel = self._create_center_panel() splitter.addWidget(center_panel) # 오른쪽: 인수팀 정보 right_panel = self._create_team_panel( "인수팀", self.receiving_team, self.receiving_shift, is_handing=False ) splitter.addWidget(right_panel) # 비율 설정 (2:6:2) splitter.setSizes([200, 600, 200]) layout.addWidget(splitter, 1) def _create_team_panel( self, title: str, team: str, shift: str, is_handing: bool ) -> QWidget: """팀 패널 생성""" theme = self.config.theme is_dark = theme == 'dark' panel = QWidget() panel_layout = QVBoxLayout(panel) panel_layout.setContentsMargins(12, 12, 12, 12) panel_layout.setSpacing(12) # 제목 title_label = QLabel(f"{title}") title_label.setFont(QFont("GmarketSans", 14, QFont.Bold)) title_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") title_label.setAlignment(Qt.AlignCenter) panel_layout.addWidget(title_label) # 팀/근무 정보 shift_icon = "☀️" if shift == "주간" else "🌙" team_label = QLabel(f"{team} {shift} {shift_icon}") team_label.setFont(QFont("GmarketSans", 18, QFont.Bold)) team_label.setStyleSheet(f"color: {'#3b82f6' if is_dark else '#2563eb'};") team_label.setAlignment(Qt.AlignCenter) panel_layout.addWidget(team_label) # 구분선 separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setStyleSheet(f"color: {'#334155' if is_dark else '#e2e8f0'};") panel_layout.addWidget(separator) # 당무 정보 self._create_duty_info(panel_layout, team, shift, is_handing) # 구분선 separator2 = QFrame() separator2.setFrameShape(QFrame.HLine) separator2.setStyleSheet(f"color: {'#334155' if is_dark else '#e2e8f0'};") panel_layout.addWidget(separator2) # 일상검수/청소 정보 self._create_daily_info(panel_layout, team, shift, is_handing) panel_layout.addStretch() # 패널 스타일 panel.setStyleSheet(f""" QWidget {{ background-color: {'#1e293b' if is_dark else '#ffffff'}; border: 1px solid {'#334155' if is_dark else '#e2e8f0'}; border-radius: 8px; }} """) return panel def _create_duty_info( self, layout: QVBoxLayout, team: str, shift: str, is_handing: bool ): """당무 정보 생성""" theme = self.config.theme is_dark = theme == 'dark' # 당무 정보 조회 today = date.today() duty = self.crud.get_duty_schedule(today, team, shift) vice_leader = duty.vice_leader_name if duty else "미지정" operator = duty.operator_name if duty else "미지정" # 부팀장 당무 vice_widget = QWidget() vice_layout = QHBoxLayout(vice_widget) vice_layout.setContentsMargins(0, 0, 0, 0) vice_label = QLabel("부팀장 당무:") vice_label.setFont(QFont("GmarketSans", 11)) vice_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};") vice_layout.addWidget(vice_label) if is_handing: # 인계팀은 표시만 vice_value = QLabel(vice_leader) vice_value.setFont(QFont("GmarketSans", 12, QFont.Bold)) vice_value.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") vice_layout.addWidget(vice_value) else: # 인수팀은 변경 가능 self.receiving_vice_combo = CustomComboBox(placeholder="선택") members = self.crud.get_team_members_by_team(team, "부팀장", active_only=True) for m in members: self.receiving_vice_combo.addItem(m.name, m.id) if vice_leader and vice_leader != "미지정": idx = self.receiving_vice_combo.findText(vice_leader) if idx >= 0: self.receiving_vice_combo.setCurrentIndex(idx) vice_layout.addWidget(self.receiving_vice_combo) layout.addWidget(vice_widget) # 운용 당무 op_widget = QWidget() op_layout = QHBoxLayout(op_widget) op_layout.setContentsMargins(0, 0, 0, 0) op_label = QLabel("운용 당무:") op_label.setFont(QFont("GmarketSans", 11)) op_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};") op_layout.addWidget(op_label) if is_handing: op_value = QLabel(operator) op_value.setFont(QFont("GmarketSans", 12, QFont.Bold)) op_value.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") op_layout.addWidget(op_value) else: self.receiving_op_combo = CustomComboBox(placeholder="선택") members = self.crud.get_team_members_by_team(team, "운용", active_only=True) for m in members: self.receiving_op_combo.addItem(m.name, m.id) if operator and operator != "미지정": idx = self.receiving_op_combo.findText(operator) if idx >= 0: self.receiving_op_combo.setCurrentIndex(idx) op_layout.addWidget(self.receiving_op_combo) layout.addWidget(op_widget) def _create_daily_info( self, layout: QVBoxLayout, team: str, shift: str, is_handing: bool ): """일상검수/청소 정보 생성""" theme = self.config.theme is_dark = theme == 'dark' # 일상차량 레이블 daily_label = QLabel("일상검수 차량") daily_label.setFont(QFont("GmarketSans", 11, QFont.Bold)) daily_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};") layout.addWidget(daily_label) # TODO: 일상검수 차량 목록 표시 daily_info = QLabel("(일상검수 정보)") daily_info.setFont(QFont("GmarketSans", 10)) daily_info.setStyleSheet(f"color: {'#64748b' if is_dark else '#94a3b8'};") layout.addWidget(daily_info) # 청소 레이블 cleaning_label = QLabel("청소") cleaning_label.setFont(QFont("GmarketSans", 11, QFont.Bold)) cleaning_label.setStyleSheet(f"color: {'#94a3b8' if is_dark else '#64748b'};") layout.addWidget(cleaning_label) # TODO: 청소 정보 표시 cleaning_info = QLabel("(청소 정보)") cleaning_info.setFont(QFont("GmarketSans", 10)) cleaning_info.setStyleSheet(f"color: {'#64748b' if is_dark else '#94a3b8'};") layout.addWidget(cleaning_info) def _create_center_panel(self) -> QWidget: """중앙 패널 생성 (섹션별 탭)""" theme = self.config.theme is_dark = theme == 'dark' panel = QWidget() panel_layout = QVBoxLayout(panel) panel_layout.setContentsMargins(0, 0, 0, 0) panel_layout.setSpacing(0) # 탭 위젯 self.section_tabs = QTabWidget() self.section_tabs.setFont(QFont("GmarketSans", 12)) # 탭 스타일 if is_dark: self.section_tabs.setStyleSheet(""" QTabWidget::pane { border: 1px solid #334155; border-radius: 8px; background-color: #1e293b; } QTabBar::tab { background-color: #1e293b; color: #94a3b8; padding: 10px 20px; border-top-left-radius: 6px; border-top-right-radius: 6px; margin-right: 2px; } QTabBar::tab:selected { background-color: #334155; color: #f8fafc; } """) else: self.section_tabs.setStyleSheet(""" QTabWidget::pane { border: 1px solid #e2e8f0; border-radius: 8px; background-color: #ffffff; } QTabBar::tab { background-color: #f1f5f9; color: #64748b; padding: 10px 20px; border-top-left-radius: 6px; border-top-right-radius: 6px; margin-right: 2px; } QTabBar::tab:selected { background-color: #ffffff; color: #1e293b; } """) # 섹션별 탭 생성 sections = [ ("지시사항", "instructions"), ("고장", "faults"), ("작업", "works"), ("기타", "miscs"), ] self.section_widgets = {} for title, key in sections: tab = self._create_section_tab(key) self.section_tabs.addTab(tab, title) self.section_widgets[key] = tab panel_layout.addWidget(self.section_tabs) return panel def _create_section_tab(self, section_key: str) -> QWidget: """섹션 탭 생성""" theme = self.config.theme is_dark = theme == 'dark' tab = QWidget() tab_layout = QHBoxLayout(tab) tab_layout.setContentsMargins(8, 8, 8, 8) tab_layout.setSpacing(8) # 왼쪽: 인계팀 항목 (인계 버튼 포함) left_widget = self._create_handing_section(section_key) tab_layout.addWidget(left_widget, 1) # 오른쪽: 인수팀 항목 (할일 추가 버튼 포함) right_widget = self._create_receiving_section(section_key) tab_layout.addWidget(right_widget, 1) return tab def _create_handing_section(self, section_key: str) -> QWidget: """인계팀 섹션 생성""" theme = self.config.theme is_dark = theme == 'dark' widget = QWidget() layout = QVBoxLayout(widget) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(8) # 제목 title = QLabel(f"📤 {self.handing_team} 인계 항목") title.setFont(QFont("GmarketSans", 12, QFont.Bold)) title.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") layout.addWidget(title) # 스크롤 영역 scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) content = QWidget() content_layout = QVBoxLayout(content) content_layout.setContentsMargins(4, 4, 4, 4) content_layout.setSpacing(8) # 항목들은 나중에 로드 setattr(self, f"handing_{section_key}_layout", content_layout) content_layout.addStretch() scroll.setWidget(content) # 스크롤 스타일 scroll.setStyleSheet(f""" QScrollArea {{ border: 1px solid {'#334155' if is_dark else '#e2e8f0'}; border-radius: 6px; background-color: {'#0f172a' if is_dark else '#f8fafc'}; }} """) layout.addWidget(scroll, 1) return widget def _create_receiving_section(self, section_key: str) -> QWidget: """인수팀 섹션 생성""" theme = self.config.theme is_dark = theme == 'dark' widget = QWidget() layout = QVBoxLayout(widget) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(8) # 제목 + 직접추가 버튼 header = QWidget() header_layout = QHBoxLayout(header) header_layout.setContentsMargins(0, 0, 0, 0) title = QLabel(f"📥 {self.receiving_team} 인수 항목") title.setFont(QFont("GmarketSans", 12, QFont.Bold)) title.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") header_layout.addWidget(title) header_layout.addStretch() # 직접추가 버튼 add_btn = CustomButton("+ 직접추가", style_type="outline", fixed_height=28) add_btn.clicked.connect(lambda: self._on_add_item_clicked(section_key)) header_layout.addWidget(add_btn) layout.addWidget(header) # 스크롤 영역 scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) content = QWidget() content_layout = QVBoxLayout(content) content_layout.setContentsMargins(4, 4, 4, 4) content_layout.setSpacing(8) # 항목들은 나중에 로드 setattr(self, f"receiving_{section_key}_layout", content_layout) content_layout.addStretch() scroll.setWidget(content) scroll.setStyleSheet(f""" QScrollArea {{ border: 1px solid {'#334155' if is_dark else '#e2e8f0'}; border-radius: 6px; background-color: {'#0f172a' if is_dark else '#f8fafc'}; }} """) layout.addWidget(scroll, 1) return widget def _create_footer_section(self, layout: QVBoxLayout): """하단 버튼 영역 생성""" theme = self.config.theme is_dark = theme == 'dark' footer = QWidget() footer_layout = QHBoxLayout(footer) footer_layout.setContentsMargins(16, 8, 16, 8) footer_layout.setSpacing(16) footer_layout.addStretch() # 취소 버튼 cancel_btn = CustomButton("취소", style_type="secondary", fixed_height=40) cancel_btn.setFixedWidth(120) cancel_btn.clicked.connect(self.reject) footer_layout.addWidget(cancel_btn) # 인수인계 완료 버튼 complete_btn = CustomButton( "✅ 인수/인계 완료", style_type="primary", fixed_height=40 ) complete_btn.setFixedWidth(180) complete_btn.clicked.connect(self._on_complete_clicked) footer_layout.addWidget(complete_btn) layout.addWidget(footer) def _load_data(self): """데이터 로드""" self._load_section_data("instructions") self._load_section_data("faults") self._load_section_data("works") self._load_section_data("miscs") def _load_section_data(self, section_key: str): """섹션 데이터 로드""" theme = self.config.theme is_dark = theme == 'dark' # 인계팀 데이터 로드 handing_layout = getattr(self, f"handing_{section_key}_layout", None) if not handing_layout: return # 기존 항목 제거 (stretch 제외) while handing_layout.count() > 1: item = handing_layout.takeAt(0) if item.widget(): item.widget().deleteLater() # 데이터 조회 today = date.today() items = [] if section_key == "instructions": # get_instructions_by_date 사용 (팀 필터링은 나중에) all_items = self.crud.get_instructions_by_date(today, include_continuous=True) # 팀 필터링 items = [item for item in all_items if item.created_team == self.handing_team] elif section_key == "faults": items = self.crud.get_faults_by_date_range( today, today, team=self.handing_team ) elif section_key == "works": items = self.crud.get_works_by_date_range( today, today, team=self.handing_team ) elif section_key == "miscs": items = self.crud.get_miscs_by_date_range( today, today, team=self.handing_team ) # 항목 추가 for item in items: item_widget = self._create_handing_item_widget(section_key, item) handing_layout.insertWidget(handing_layout.count() - 1, item_widget) def _create_handing_item_widget(self, section_key: str, item) -> QWidget: """인계 항목 위젯 생성""" theme = self.config.theme is_dark = theme == 'dark' widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(8) # 내용 content = self._get_item_display_text(section_key, item) content_label = QLabel(content) content_label.setFont(QFont("GmarketSans", 11)) content_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") content_label.setWordWrap(True) layout.addWidget(content_label, 1) # 인계 버튼 handover_btn = CustomButton("인계 →", style_type="primary", fixed_height=28) handover_btn.setFixedWidth(70) handover_btn.clicked.connect( lambda: self._on_handover_item_clicked(section_key, item) ) layout.addWidget(handover_btn) # 위젯 스타일 widget.setStyleSheet(f""" QWidget {{ background-color: {'#1e293b' if is_dark else '#ffffff'}; border: 1px solid {'#334155' if is_dark else '#e2e8f0'}; border-radius: 6px; }} """) return widget def _create_receiving_item_widget( self, section_key: str, item, content: str ) -> QWidget: """인수 항목 위젯 생성""" theme = self.config.theme is_dark = theme == 'dark' widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(8) # 할일 추가 버튼 todo_btn = CustomButton("📝", style_type="text", fixed_height=28) todo_btn.setFixedWidth(36) todo_btn.setToolTip("할일 목록에 추가") todo_btn.clicked.connect( lambda: self._on_add_to_todo_clicked(section_key, item, content) ) layout.addWidget(todo_btn) # 내용 content_label = QLabel(content) content_label.setFont(QFont("GmarketSans", 11)) content_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") content_label.setWordWrap(True) layout.addWidget(content_label, 1) # 위젯 스타일 widget.setStyleSheet(f""" QWidget {{ background-color: {'#22c55e20' if is_dark else '#dcfce7'}; border: 1px solid {'#22c55e' if is_dark else '#86efac'}; border-radius: 6px; }} """) return widget def _get_item_display_text(self, section_key: str, item) -> str: """항목 표시 텍스트 생성""" if section_key == "instructions": return f"[{item.instructor}] {item.instruction_content[:50]}..." elif section_key == "faults": return f"[{item.train_number}] {item.fault_content[:50]}..." elif section_key == "works": return f"[{item.work_type}] {item.work_content[:50]}..." elif section_key == "miscs": return f"{item.content[:60]}..." return str(item) def _on_handover_item_clicked(self, section_key: str, item): """인계 버튼 클릭""" # 인수팀 영역에 추가 receiving_layout = getattr(self, f"receiving_{section_key}_layout", None) if not receiving_layout: return content = self._get_item_display_text(section_key, item) item_widget = self._create_receiving_item_widget(section_key, item, content) receiving_layout.insertWidget(receiving_layout.count() - 1, item_widget) # 인계 항목 추적 self._handover_items[section_key].append(item) logger.info("항목 인계: %s - ID %s", section_key, item.id) def _on_add_to_todo_clicked(self, section_key: str, item, content: str): """할일 추가 버튼 클릭""" self._todo_items.append({ 'section': section_key, 'item': item, 'content': content, }) logger.info("할일 추가: %s", content[:30]) def _on_add_item_clicked(self, section_key: str): """직접추가 버튼 클릭""" from ui.dialogs.input_dialog import InputDialog dialog = InputDialog( self, title="항목 직접 추가", label="내용을 입력하세요:", multiline=True ) if dialog.exec(): content = dialog.get_value() if content: # 인수팀 영역에 추가 receiving_layout = getattr(self, f"receiving_{section_key}_layout", None) if receiving_layout: # 직접 추가 항목 위젯 item_widget = self._create_manual_item_widget(section_key, content) receiving_layout.insertWidget(receiving_layout.count() - 1, item_widget) # 할일 목록에도 추가 self._todo_items.append({ 'section': section_key, 'item': None, 'content': content, }) def _create_manual_item_widget(self, section_key: str, content: str) -> QWidget: """직접 추가 항목 위젯 생성""" theme = self.config.theme is_dark = theme == 'dark' widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(8) # 직접추가 표시 tag_label = QLabel("📌") layout.addWidget(tag_label) # 내용 content_label = QLabel(content) content_label.setFont(QFont("GmarketSans", 11)) content_label.setStyleSheet(f"color: {'#f8fafc' if is_dark else '#1e293b'};") content_label.setWordWrap(True) layout.addWidget(content_label, 1) # 위젯 스타일 widget.setStyleSheet(f""" QWidget {{ background-color: {'#f59e0b20' if is_dark else '#fef3c7'}; border: 1px solid {'#f59e0b' if is_dark else '#fcd34d'}; border-radius: 6px; }} """) return widget def _on_complete_clicked(self): """인수인계 완료 버튼 클릭""" today = date.today() # 인수팀 당무 저장 if hasattr(self, 'receiving_vice_combo') and hasattr(self, 'receiving_op_combo'): vice_name = self.receiving_vice_combo.currentText() vice_id = self.receiving_vice_combo.currentData() op_name = self.receiving_op_combo.currentText() op_id = self.receiving_op_combo.currentData() if vice_name or op_name: self.crud.upsert_duty_schedule( duty_date=today, team=self.receiving_team, shift_type=self.receiving_shift, vice_leader_id=vice_id, vice_leader_name=vice_name, operator_id=op_id, operator_name=op_name ) # 할일 목록에 추가 for todo_item in self._todo_items: self.crud.create_todo( created_date=today, created_team=self.receiving_team, content=todo_item['content'], is_completed=False ) # 인계 항목들의 팀 확인 상태 업데이트 for section_key, items in self._handover_items.items(): for item in items: item.set_team_confirmation(self.receiving_team, True) # 업데이트 if section_key == "instructions": self.crud.update_instruction( item.id, team_confirmations=item.team_confirmations ) elif section_key == "faults": self.crud.update_fault( item.id, team_confirmations=item.team_confirmations ) elif section_key == "works": self.crud.update_work( item.id, team_confirmations=item.team_confirmations ) elif section_key == "miscs": self.crud.update_misc( item.id, team_confirmations=item.team_confirmations ) logger.info( "인수인계 완료: %s %s -> %s %s", self.handing_team, self.handing_shift, self.receiving_team, self.receiving_shift ) self.handover_completed.emit() self.accept()