457 lines
16 KiB
Python
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(),
|
|
}
|