939 lines
33 KiB
Python
939 lines
33 KiB
Python
# -*- 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()
|
|
|