handOver2/ui/dialogs/train_input_dialog.py

457 lines
16 KiB
Python

# -*- coding: utf-8 -*-
"""
편성 입력 다이얼로그 모듈
일상검수 편성 입력을 위한 다이얼로그입니다.
"""
from datetime import date
from typing import Optional, List, Dict, Any
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QScrollArea, QButtonGroup,
QTextEdit, QCheckBox, QLineEdit
)
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QFont
from ui.base.base_dialog import BaseDialog
from ui.components.chips.choice_chip_button import ChoiceChipButton
from ui.components.flow_layout import FlowLayout
from ui.components.train_selection import TrainSelectionWidget
from core.config import ConfigManager
from core.logger import get_logger
from database.common_db_manager import CommonDatabaseManager
logger = get_logger(__name__)
class CleaningChip(QPushButton):
"""
청소유형 선택 칩
청소유형을 표시하며, 유형에 따라 다른 아이콘을 표시합니다.
- 없음: 기본
- 중청소: 파란 네모 □
- 대청소: 빨간 동그라미 ○
"""
chip_selected = Signal(str) # cleaning_type
def __init__(self, cleaning_type: str, parent=None):
super().__init__(parent)
self.config = ConfigManager()
self.cleaning_type = cleaning_type
self._is_selected = False
# 아이콘과 텍스트 설정
if cleaning_type == "중청소":
self.setText("□ 중청소")
self.icon_color = "#3b82f6" # 파란색
elif cleaning_type == "대청소":
self.setText("○ 대청소")
self.icon_color = "#ef4444" # 빨간색
else:
self.setText("없음")
self.icon_color = "#64748b"
self.setMinimumWidth(80)
self.setFixedHeight(36)
self.setCursor(Qt.PointingHandCursor)
self.setFont(QFont("GmarketSans", 11, QFont.Bold))
self.clicked.connect(self._on_clicked)
self._apply_style()
def _on_clicked(self):
"""클릭 이벤트"""
self.chip_selected.emit(self.cleaning_type)
def set_selected(self, selected: bool):
"""선택 상태 설정"""
self._is_selected = selected
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
if self._is_selected:
self.setStyleSheet(f"""
QPushButton {{
background-color: {self.icon_color};
color: white;
border: 3px solid {'#1d4ed8' if self.cleaning_type == '중청소' else '#b91c1c' if self.cleaning_type == '대청소' else '#475569'};
border-radius: 8px;
padding: 0 12px;
font-weight: bold;
}}
""")
else:
if theme == 'dark':
bg = "#334155"
text = "#94a3b8"
else:
bg = "#e2e8f0"
text = "#64748b"
self.setStyleSheet(f"""
QPushButton {{
background-color: {bg};
color: {text};
border: 2px solid transparent;
border-radius: 8px;
padding: 0 12px;
}}
QPushButton:hover {{
background-color: {self.icon_color};
color: white;
}}
""")
class WorkChip(QPushButton):
"""
작업여부 선택 칩
작업여부를 표시합니다.
- 없음: 기본
- 있음: 노란색 느낌표
"""
chip_selected = Signal(bool) # has_work
def __init__(self, has_work: bool, label: str, parent=None):
super().__init__(parent)
self.config = ConfigManager()
self.has_work = has_work
self.label = label
self._is_selected = False
# 텍스트 설정
if has_work:
self.setText("! 있음")
self.icon_color = "#f59e0b" # 노란색
else:
self.setText("없음")
self.icon_color = "#64748b"
self.setMinimumWidth(70)
self.setFixedHeight(36)
self.setCursor(Qt.PointingHandCursor)
self.setFont(QFont("GmarketSans", 11, QFont.Bold))
self.clicked.connect(self._on_clicked)
self._apply_style()
def _on_clicked(self):
"""클릭 이벤트"""
self.chip_selected.emit(self.has_work)
def set_selected(self, selected: bool):
"""선택 상태 설정"""
self._is_selected = selected
self._apply_style()
def _apply_style(self):
"""스타일 적용"""
theme = self.config.theme
if self._is_selected:
self.setStyleSheet(f"""
QPushButton {{
background-color: {self.icon_color};
color: white;
border: 3px solid {'#d97706' if self.has_work else '#475569'};
border-radius: 8px;
padding: 0 12px;
font-weight: bold;
}}
""")
else:
if theme == 'dark':
bg = "#334155"
text = "#94a3b8"
else:
bg = "#e2e8f0"
text = "#64748b"
self.setStyleSheet(f"""
QPushButton {{
background-color: {bg};
color: {text};
border: 2px solid transparent;
border-radius: 8px;
padding: 0 12px;
}}
QPushButton:hover {{
background-color: {self.icon_color};
color: white;
}}
""")
class TrainInputDialog(BaseDialog):
"""
편성 입력 다이얼로그
일상검수 편성 및 청소 유형을 입력합니다.
"""
def __init__(
self,
parent=None,
shift_type: str = "",
slot_number: int = 0,
inspection_date: date = None,
existing_trains: List[str] = None
):
super().__init__(
parent,
title=f"{shift_type} {slot_number}번 슬롯",
width=420,
height=700
)
self.db = CommonDatabaseManager()
self.shift_type = shift_type
self.slot_number = slot_number
self.inspection_date = inspection_date or date.today()
self.existing_trains = existing_trains or []
self._selected_train_number: str = ""
self._selected_cleaning: str = "없음"
self._selected_has_work: bool = False
self._work_content: str = ""
self._is_work_completed: bool = False
# self._train_chips: List[TrainChip] = [] # Removed
self._cleaning_chips: List[CleaningChip] = []
self._work_chips: List[WorkChip] = []
self._setup_fields()
self.add_confirm_cancel_buttons()
def _setup_fields(self):
"""필드 설정"""
theme = self.config.theme
text_color = "#f8fafc" if theme == 'dark' else "#1e293b"
sub_text_color = "#94a3b8" if theme == 'dark' else "#64748b"
border_color = "#334155" if theme == 'dark' else "#e2e8f0"
# === 편성번호 섹션 ===
train_section = QWidget()
train_layout = QVBoxLayout(train_section)
train_layout.setContentsMargins(0, 0, 0, 0)
train_layout.setSpacing(8)
# 헤더 (라벨 + 토글)
header = QWidget()
header_layout = QHBoxLayout(header)
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.setSpacing(8)
train_label = QLabel("편성번호")
train_label.setFont(QFont("GmarketSans", 12, QFont.Bold))
train_label.setStyleSheet(f"color: {text_color};")
header_layout.addWidget(train_label)
required_mark = QLabel("*")
required_mark.setFont(QFont("GmarketSans", 12, QFont.Bold))
required_mark.setStyleSheet("color: #ef4444;")
header_layout.addWidget(required_mark)
header_layout.addStretch()
# 편성 선택 위젯
self.train_selection = TrainSelectionWidget(existing_trains=self.existing_trains)
self.train_selection.train_selected.connect(self._on_train_selected)
train_layout.addWidget(self.train_selection)
self.content_layout.addWidget(train_section)
# 구분선
separator1 = QFrame()
separator1.setFrameShape(QFrame.HLine)
separator1.setStyleSheet(f"color: {border_color};")
self.content_layout.addWidget(separator1)
# === 청소유형 섹션 ===
cleaning_section = QWidget()
cleaning_layout = QVBoxLayout(cleaning_section)
cleaning_layout.setContentsMargins(0, 0, 0, 0)
cleaning_layout.setSpacing(8)
cleaning_label = QLabel("청소유형")
cleaning_label.setFont(QFont("GmarketSans", 12, QFont.Bold))
cleaning_label.setStyleSheet(f"color: {text_color};")
cleaning_layout.addWidget(cleaning_label)
# 청소유형 칩 컨테이너
cleaning_container = QWidget()
cleaning_chip_layout = QHBoxLayout(cleaning_container)
cleaning_chip_layout.setContentsMargins(0, 0, 0, 0)
cleaning_chip_layout.setSpacing(8)
cleaning_options = ["없음", "중청소", "대청소"]
for option in cleaning_options:
chip = CleaningChip(option)
chip.chip_selected.connect(self._on_cleaning_selected)
self._cleaning_chips.append(chip)
cleaning_chip_layout.addWidget(chip)
# 기본 선택: 없음
self._cleaning_chips[0].set_selected(True)
cleaning_chip_layout.addStretch()
cleaning_layout.addWidget(cleaning_container)
self.content_layout.addWidget(cleaning_section)
# 구분선
separator2 = QFrame()
separator2.setFrameShape(QFrame.HLine)
separator2.setStyleSheet(f"color: {border_color};")
self.content_layout.addWidget(separator2)
# === 작업여부 섹션 ===
work_section = QWidget()
work_layout = QVBoxLayout(work_section)
work_layout.setContentsMargins(0, 0, 0, 0)
work_layout.setSpacing(8)
work_label = QLabel("작업여부")
work_label.setFont(QFont("GmarketSans", 12, QFont.Bold))
work_label.setStyleSheet(f"color: {text_color};")
work_layout.addWidget(work_label)
# 작업여부 칩 컨테이너
work_container = QWidget()
work_chip_layout = QHBoxLayout(work_container)
work_chip_layout.setContentsMargins(0, 0, 0, 0)
work_chip_layout.setSpacing(8)
# 없음, 있음 칩
work_none = WorkChip(False, "없음")
work_none.chip_selected.connect(self._on_work_selected)
self._work_chips.append(work_none)
work_chip_layout.addWidget(work_none)
work_has = WorkChip(True, "있음")
work_has.chip_selected.connect(self._on_work_selected)
self._work_chips.append(work_has)
work_chip_layout.addWidget(work_has)
# 기본 선택: 없음
self._work_chips[0].set_selected(True)
work_chip_layout.addStretch()
work_layout.addWidget(work_container)
# 작업 상세 입력 (작업 있음 선택 시 표시)
self.work_detail_container = QWidget()
work_detail_layout = QVBoxLayout(self.work_detail_container)
work_detail_layout.setContentsMargins(0, 0, 0, 0)
work_detail_layout.setSpacing(8)
# 작업 내용 입력
work_content_label = QLabel("작업 내용")
work_content_label.setFont(QFont("GmarketSans", 11))
work_content_label.setStyleSheet(f"color: {text_color};")
work_detail_layout.addWidget(work_content_label)
self.work_content_edit = QTextEdit()
self.work_content_edit.setPlaceholderText("작업 내용을 입력하세요...")
self.work_content_edit.setFixedHeight(80)
self.work_content_edit.setStyleSheet(f"""
QTextEdit {{
background-color: {'#334155' if theme == 'dark' else '#f1f5f9'};
border: 1px solid {border_color};
border-radius: 8px;
padding: 8px;
color: {text_color};
}}
""")
work_detail_layout.addWidget(self.work_content_edit)
# 작업 완료 여부
self.work_completed_check = QCheckBox("작업 완료")
self.work_completed_check.setFont(QFont("GmarketSans", 11))
self.work_completed_check.setStyleSheet(f"""
QCheckBox {{
color: {text_color};
spacing: 8px;
}}
QCheckBox::indicator {{
width: 20px;
height: 20px;
border: 2px solid {border_color};
border-radius: 4px;
}}
QCheckBox::indicator:checked {{
background-color: #3b82f6;
border-color: #3b82f6;
image: url(resources/icons/check.png); /* 아이콘이 없다면 체크 표시만 */
}}
""")
work_detail_layout.addWidget(self.work_completed_check)
self.work_detail_container.setVisible(False)
work_layout.addWidget(self.work_detail_container)
self.content_layout.addWidget(work_section)
self.content_layout.addStretch()
# 초기 편성 칩 생성 (신평 모드로 시작)
# self._load_trains(depot_filter="신평") # Handled by widget
# def _load_trains(self, depot_filter: str = None): ... (Removed)
# def _create_train_chips(self, trains: List[Dict[str, Any]]): ... (Removed)
# def _on_depot_filter_changed(self, depot: str): ... (Removed)
def _on_train_selected(self, train_number: str):
"""편성 선택"""
self._selected_train_number = train_number
# 라벨 업데이트 (Optional, widget handles selection visual)
# theme = self.config.theme
# text_color = "#f8fafc" if theme == 'dark' else "#1e293b"
# self.selected_train_label.setText(f"선택: {train_number}")
# self.selected_train_label.setStyleSheet(f"color: {text_color}; font-weight: bold;")
def _on_cleaning_selected(self, cleaning_type: str):
"""청소유형 선택"""
for chip in self._cleaning_chips:
chip.set_selected(chip.cleaning_type == cleaning_type)
self._selected_cleaning = cleaning_type
def _on_work_selected(self, has_work: bool):
"""작업여부 선택"""
for chip in self._work_chips:
chip.set_selected(chip.has_work == has_work)
self._selected_has_work = has_work
self.work_detail_container.setVisible(has_work)
# 다이얼로그 크기 조정 (내용에 맞게)
self.adjustSize()
def get_data(self) -> dict:
"""입력 데이터 반환"""
return {
"train_number": self._selected_train_number,
"cleaning_type": self._selected_cleaning,
"has_work": self._selected_has_work,
"work_content": self.work_content_edit.toPlainText(),
"is_work_completed": self.work_completed_check.isChecked(),
}