843 lines
33 KiB
Python
843 lines
33 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
기타 섹션 모듈
|
|
전동차 관련 작업 외 나머지 사항을 관리하는 섹션입니다.
|
|
"""
|
|
|
|
from datetime import date
|
|
from typing import List, Optional, Any
|
|
|
|
from PySide6.QtWidgets import QDialog
|
|
from PySide6.QtCore import Qt, QPoint
|
|
|
|
from ui.base.base_section import BaseSection, FieldConfig
|
|
from ui.dialogs.input_dialog import SectionInputDialog
|
|
from ui.widgets.clickableLabel import ClickableLabel
|
|
from ui.components.popup_widget import PopupWidget
|
|
from database.models import Misc
|
|
from core.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class MiscSection(BaseSection):
|
|
"""
|
|
기타 섹션
|
|
|
|
전동차 관련 작업 외 나머지 사항을 표시하고 관리합니다.
|
|
|
|
필드:
|
|
- 전달자, 전달내용, 특이사항, 관련문서, 팀확인, 완료
|
|
"""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent, "miscs", Misc)
|
|
|
|
# 필드 설정
|
|
self._setup_fields()
|
|
|
|
# 초기화 후 처리 (설정 로드)
|
|
self._post_init()
|
|
|
|
# 팝업 추적용
|
|
self._current_popup: Optional[PopupWidget] = None
|
|
self._current_team_popup: Optional[PopupWidget] = None
|
|
|
|
# 초기 데이터 로드
|
|
self.load_data()
|
|
|
|
logger.info("기타 섹션 초기화 완료")
|
|
|
|
def _setup_fields(self):
|
|
"""필드 설정"""
|
|
self.fields = [
|
|
FieldConfig("created_date", "생성일", width=80, required=True, editable=False, field_type="date"),
|
|
FieldConfig("created_team", "생성팀", width=60, required=True, editable=False),
|
|
FieldConfig("reporter", "전달자", width=80),
|
|
FieldConfig("report_content", "전달내용", width=350),
|
|
FieldConfig("remarks", "특이사항", width=180),
|
|
FieldConfig("related_document", "관련문서", width=100),
|
|
FieldConfig("team_confirmations", "확인팀", width=110),
|
|
FieldConfig("is_completed", "완료", width=40, field_type="checkbox"),
|
|
]
|
|
|
|
def _fetch_data(self, **filters) -> List[Misc]:
|
|
"""데이터 조회"""
|
|
return self.crud.get_all_miscs()
|
|
|
|
def on_add_clicked(self):
|
|
"""추가 버튼 클릭"""
|
|
dialog = MiscInputDialog(self)
|
|
if dialog.exec() == QDialog.Accepted:
|
|
data = dialog.get_data()
|
|
self.crud.create_misc(**data)
|
|
self.refresh_data()
|
|
|
|
def on_edit_clicked(self, record_id: int):
|
|
"""편집 버튼 클릭"""
|
|
record = self.crud.get_misc(record_id)
|
|
if record:
|
|
dialog = MiscInputDialog(self, record)
|
|
if dialog.exec() == QDialog.Accepted:
|
|
data = dialog.get_data()
|
|
self.crud.update_misc(record_id, **data)
|
|
self.refresh_data()
|
|
|
|
def _delete_record(self, record_id: int):
|
|
"""레코드 삭제"""
|
|
self.crud.delete_misc(record_id)
|
|
self.refresh_data()
|
|
|
|
def on_search_changed(self, text: str):
|
|
"""검색어 변경"""
|
|
if text:
|
|
results = self.crud._search(
|
|
"miscs",
|
|
Misc,
|
|
["reporter", "report_content", "remarks"],
|
|
text
|
|
)
|
|
self.current_records = results
|
|
self._update_table()
|
|
else:
|
|
self.load_data()
|
|
|
|
def _update_team_confirmations(self, record_id: int, confirmations: dict):
|
|
"""팀확인 상태 업데이트"""
|
|
import json
|
|
self.crud.update_misc(record_id, team_confirmations=json.dumps(confirmations, ensure_ascii=False))
|
|
self.signals.data_changed.emit(self.table_name)
|
|
|
|
def _mark_as_completed(self, record_id: int):
|
|
"""레코드를 완료로 표시"""
|
|
from datetime import datetime
|
|
self.crud.update_misc(record_id, is_completed=True, completed_at=datetime.now())
|
|
self.signals.data_changed.emit(self.table_name)
|
|
|
|
def _on_double_clicked(self, item):
|
|
"""더블클릭 이벤트 오버라이드"""
|
|
row = item.row()
|
|
col = item.column()
|
|
|
|
# 위젯인 경우 (ClickableLabel 등)
|
|
widget = self.table.cellWidget(row, col)
|
|
if widget:
|
|
record_id = widget.property("record_id")
|
|
if not record_id:
|
|
# 첫 번째 셀에서 레코드 ID 가져오기
|
|
first_item = self.table.item(row, 0)
|
|
if first_item:
|
|
record_id = first_item.data(Qt.UserRole)
|
|
|
|
if record_id:
|
|
visible_fields = [f for f in self.fields if f.visible]
|
|
if col < len(visible_fields):
|
|
field = visible_fields[col]
|
|
record = self.crud.get_misc(record_id)
|
|
if record:
|
|
self._edit_field_inline(row, col, field, record)
|
|
return
|
|
|
|
# 일반 아이템인 경우 부모 클래스 처리
|
|
super()._on_double_clicked(item)
|
|
|
|
def _edit_field_inline(self, row: int, col: int, field: FieldConfig, record: Misc):
|
|
"""인라인 필드 편집"""
|
|
if field.name == "reporter":
|
|
self._edit_reporter(row, col, record)
|
|
elif field.name == "report_content":
|
|
self._edit_report_content(row, col, record)
|
|
elif field.name == "remarks":
|
|
self._edit_remarks(row, col, record)
|
|
elif field.name == "related_document":
|
|
self._edit_related_document(row, col, record)
|
|
elif field.name == "team_confirmations":
|
|
self._edit_team_confirmations(row, col, record)
|
|
elif field.name == "created_team":
|
|
self._edit_created_team(row, col, record)
|
|
else:
|
|
# 기본 편집 모드
|
|
self._enable_cell_editing(row, col, field)
|
|
|
|
def _edit_reporter(self, row: int, col: int, record: Misc):
|
|
"""전달자 편집"""
|
|
from ui.components.custom_input import CustomLineEdit
|
|
from PySide6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox
|
|
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("전달자 입력")
|
|
dialog.setModal(True)
|
|
layout = QVBoxLayout(dialog)
|
|
|
|
input_field = CustomLineEdit(placeholder="전달자 이름")
|
|
if record.reporter:
|
|
input_field.setText(record.reporter)
|
|
layout.addWidget(input_field)
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
buttons.accepted.connect(dialog.accept)
|
|
buttons.rejected.connect(dialog.reject)
|
|
layout.addWidget(buttons)
|
|
|
|
if dialog.exec() == QDialog.Accepted:
|
|
reporter = input_field.text()
|
|
self.crud.update_misc(record.id, reporter=reporter)
|
|
self.refresh_data()
|
|
|
|
def _edit_report_content(self, row: int, col: int, record: Misc):
|
|
"""전달내용 편집"""
|
|
from ui.components.custom_input import CustomTextEdit, LabeledInput
|
|
from PySide6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox
|
|
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("전달내용 편집")
|
|
dialog.setModal(True)
|
|
layout = QVBoxLayout(dialog)
|
|
|
|
text_input = CustomTextEdit(placeholder="전달 내용을 입력하세요", min_height=150)
|
|
text_input.set_text(record.report_content or "")
|
|
layout.addWidget(LabeledInput("전달내용", text_input, required=True))
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
buttons.accepted.connect(dialog.accept)
|
|
buttons.rejected.connect(dialog.reject)
|
|
layout.addWidget(buttons)
|
|
|
|
if dialog.exec() == QDialog.Accepted:
|
|
new_content = text_input.get_text()
|
|
self.crud.update_misc(record.id, report_content=new_content)
|
|
self.refresh_data()
|
|
|
|
def _edit_remarks(self, row: int, col: int, record: Misc):
|
|
"""특이사항 편집"""
|
|
from ui.components.custom_input import CustomTextEdit, LabeledInput
|
|
from PySide6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox
|
|
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("특이사항 편집")
|
|
dialog.setModal(True)
|
|
layout = QVBoxLayout(dialog)
|
|
|
|
text_input = CustomTextEdit(placeholder="특이사항을 입력하세요", min_height=100)
|
|
text_input.set_text(record.remarks or "")
|
|
layout.addWidget(LabeledInput("특이사항", text_input))
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
buttons.accepted.connect(dialog.accept)
|
|
buttons.rejected.connect(dialog.reject)
|
|
layout.addWidget(buttons)
|
|
|
|
if dialog.exec() == QDialog.Accepted:
|
|
new_remarks = text_input.get_text()
|
|
self.crud.update_misc(record.id, remarks=new_remarks)
|
|
self.refresh_data()
|
|
|
|
def _edit_related_document(self, row: int, col: int, record: Misc):
|
|
"""관련문서 편집"""
|
|
from ui.components.custom_input import CustomLineEdit
|
|
from PySide6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox
|
|
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("관련문서 입력")
|
|
dialog.setModal(True)
|
|
layout = QVBoxLayout(dialog)
|
|
|
|
input_field = CustomLineEdit(placeholder="관련 문서 (파일명 또는 링크)")
|
|
if record.related_document:
|
|
input_field.setText(record.related_document)
|
|
layout.addWidget(input_field)
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
buttons.accepted.connect(dialog.accept)
|
|
buttons.rejected.connect(dialog.reject)
|
|
layout.addWidget(buttons)
|
|
|
|
if dialog.exec() == QDialog.Accepted:
|
|
related_document = input_field.text()
|
|
self.crud.update_misc(record.id, related_document=related_document)
|
|
self.refresh_data()
|
|
|
|
def _edit_team_confirmations(self, row: int, col: int, record: Misc):
|
|
"""확인팀 편집 (다중선택)"""
|
|
widget = self.table.cellWidget(row, col)
|
|
if widget:
|
|
label = widget.findChild(ClickableLabel)
|
|
if label:
|
|
self._show_team_confirmations_popup(record, label)
|
|
|
|
def _edit_created_team(self, row: int, col: int, record: Misc):
|
|
"""생성팀 편집"""
|
|
widget = self.table.cellWidget(row, col)
|
|
if widget:
|
|
label = widget.findChild(ClickableLabel)
|
|
if label:
|
|
self._show_created_team_popup(record, label)
|
|
|
|
def _update_table(self):
|
|
"""테이블 업데이트 (오버라이드)"""
|
|
self.table.setRowCount(0)
|
|
|
|
# 표시할 필드만 필터링
|
|
visible_fields = [f for f in self.fields if f.visible]
|
|
|
|
# 컬럼 설정
|
|
self.table.setColumnCount(len(visible_fields))
|
|
self.table.setHorizontalHeaderLabels([f.label for f in visible_fields])
|
|
|
|
# 컬럼 너비 설정
|
|
for i, field in enumerate(visible_fields):
|
|
self.table.setColumnWidth(i, field.width)
|
|
|
|
# 데이터 채우기 (완료된 레코드는 제외)
|
|
for record in self.current_records:
|
|
# 완료된 레코드는 섹션에서 숨김 (DB에는 유지)
|
|
if hasattr(record, 'is_completed') and record.is_completed:
|
|
continue
|
|
|
|
row = self.table.rowCount()
|
|
self.table.insertRow(row)
|
|
|
|
max_height = int(40 * 1.15) # 기본 높이 15% 증가 (46px)
|
|
for col, field in enumerate(visible_fields):
|
|
value = getattr(record, field.name, "")
|
|
|
|
# 완료 필드는 버튼 위젯으로 표시
|
|
if field.name == "is_completed":
|
|
self._set_completion_button(row, col, record)
|
|
# 확인팀은 ClickableLabel로 표시
|
|
elif field.name == "team_confirmations":
|
|
self._set_team_label(row, col, field, value, record)
|
|
# 생성팀, 전달자, 관련문서는 ClickableLabel로 표시
|
|
elif field.name in ["created_team", "reporter", "related_document"]:
|
|
self._set_clickable_label(row, col, field, value, record)
|
|
# 날짜 필드는 ClickableLabel로 표시
|
|
elif field.name == "created_date":
|
|
self._set_clickable_label(row, col, field, value, record)
|
|
# 전달내용, 특이사항은 줄바꿈이 가능한 위젯으로 표시
|
|
elif field.name in ["report_content", "remarks"]:
|
|
content_height = self._set_content_label(row, col, field, value, record)
|
|
if content_height:
|
|
max_height = max(max_height, content_height)
|
|
else:
|
|
item = self._create_table_item(field, value, record)
|
|
self.table.setItem(row, col, item)
|
|
|
|
# 행 높이 설정
|
|
self.table.setRowHeight(row, max_height)
|
|
|
|
# 레코드 ID 저장 (첫 번째 셀에)
|
|
first_item = self.table.item(row, 0)
|
|
if first_item:
|
|
first_item.setData(Qt.UserRole, record.id)
|
|
else:
|
|
# 첫 번째 셀이 위젯인 경우
|
|
widget = self.table.cellWidget(row, 0)
|
|
if widget:
|
|
widget.setProperty("record_id", record.id)
|
|
|
|
def _set_clickable_label(self, row: int, col: int, field: FieldConfig, value: Any, record: Misc):
|
|
"""ClickableLabel 설정"""
|
|
from PySide6.QtWidgets import QWidget, QHBoxLayout
|
|
|
|
display_value = self._format_value(field, value) if value else ""
|
|
|
|
# ClickableLabel 생성
|
|
label = ClickableLabel(display_value or "미지정", enable_hover=True)
|
|
label.setAlignment(Qt.AlignCenter)
|
|
label.setWordWrap(True)
|
|
|
|
# 통일된 색상 적용
|
|
theme = self.config.theme
|
|
unified_color = "#64748b"
|
|
|
|
if theme == 'dark':
|
|
label.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: {unified_color};
|
|
color: #ffffff;
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
border-radius: 0px;
|
|
padding: 0px;
|
|
margin: 0px;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
}}
|
|
QLabel:hover {{
|
|
background-color: {unified_color};
|
|
border-color: rgba(255,255,255,0.4);
|
|
}}
|
|
""")
|
|
else:
|
|
label.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: {unified_color};
|
|
color: #ffffff;
|
|
border: 1px solid rgba(0,0,0,0.1);
|
|
border-radius: 0px;
|
|
padding: 0px;
|
|
margin: 0px;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
}}
|
|
QLabel:hover {{
|
|
background-color: {unified_color};
|
|
border-color: rgba(0,0,0,0.2);
|
|
}}
|
|
""")
|
|
|
|
# 더블클릭 시 편집
|
|
def on_double_clicked():
|
|
self._edit_field_inline(row, col, field, record)
|
|
|
|
label.double_clicked.connect(on_double_clicked)
|
|
|
|
# 레코드 정보 저장
|
|
label.setProperty("record_id", record.id)
|
|
label.setProperty("field_name", field.name)
|
|
label.setProperty("field_value", value or "")
|
|
|
|
# 컨테이너 위젯으로 감싸서 구분선 적용
|
|
container = QWidget()
|
|
container_layout = QHBoxLayout(container)
|
|
container_layout.setContentsMargins(0, 0, 0, 0)
|
|
container_layout.setSpacing(0)
|
|
container_layout.addWidget(label)
|
|
|
|
# 구분선 스타일 적용
|
|
gridline_color = "#475569" if theme == 'dark' else "#cbd5e1"
|
|
container.setStyleSheet(f"border-right: 1px solid {gridline_color};")
|
|
|
|
# 셀에 위젯 설정
|
|
self.table.setCellWidget(row, col, container)
|
|
|
|
def _set_team_label(self, row: int, col: int, field: FieldConfig, value: Any, record: Misc):
|
|
"""확인팀 라벨 설정 (공간 효율 최대화)"""
|
|
from PySide6.QtWidgets import QWidget, QHBoxLayout
|
|
import json
|
|
|
|
theme = self.config.theme
|
|
unified_color = "#64748b"
|
|
|
|
# 확인팀: 확인된 팀 숫자만 표시 (예: "1 2 4")
|
|
try:
|
|
confirmations = json.loads(value) if isinstance(value, str) else value
|
|
if isinstance(confirmations, dict):
|
|
# 팀 이름에서 숫자만 추출하여 정렬
|
|
confirmed_nums = sorted([
|
|
team.replace("팀", "")
|
|
for team, confirmed in confirmations.items()
|
|
if confirmed
|
|
])
|
|
display_value = " ".join(confirmed_nums) if confirmed_nums else "-"
|
|
else:
|
|
display_value = "-"
|
|
except Exception:
|
|
display_value = "-"
|
|
|
|
# ClickableLabel 생성
|
|
label = ClickableLabel(display_value, enable_hover=False)
|
|
label.setAlignment(Qt.AlignCenter)
|
|
|
|
# 통일된 스타일 적용
|
|
if theme == 'dark':
|
|
label.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: {unified_color};
|
|
color: #ffffff;
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
border-radius: 0px;
|
|
padding: 0px;
|
|
margin: 0px;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
}}
|
|
QLabel:hover {{
|
|
background-color: {unified_color};
|
|
border-color: rgba(255,255,255,0.4);
|
|
}}
|
|
""")
|
|
else:
|
|
label.setStyleSheet(f"""
|
|
QLabel {{
|
|
background-color: {unified_color};
|
|
color: #ffffff;
|
|
border: 1px solid rgba(0,0,0,0.1);
|
|
border-radius: 0px;
|
|
padding: 0px;
|
|
margin: 0px;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
}}
|
|
QLabel:hover {{
|
|
background-color: {unified_color};
|
|
border-color: rgba(0,0,0,0.2);
|
|
}}
|
|
""")
|
|
|
|
# 클릭/더블클릭 이벤트 연결
|
|
def on_clicked():
|
|
self._show_team_confirmations_popup(record, label)
|
|
|
|
def on_double_clicked():
|
|
self._edit_field_inline(row, col, field, record)
|
|
|
|
label.clicked.connect(on_clicked)
|
|
label.double_clicked.connect(on_double_clicked)
|
|
|
|
# 레코드 정보 저장
|
|
label.setProperty("record_id", record.id)
|
|
label.setProperty("field_name", field.name)
|
|
|
|
# 컨테이너 위젯으로 감싸기
|
|
container = QWidget()
|
|
container_layout = QHBoxLayout(container)
|
|
container_layout.setContentsMargins(0, 0, 0, 0)
|
|
container_layout.setSpacing(0)
|
|
container_layout.addWidget(label)
|
|
|
|
# 구분선 스타일 적용
|
|
gridline_color = "#475569" if theme == 'dark' else "#cbd5e1"
|
|
container.setStyleSheet(f"border-right: 1px solid {gridline_color};")
|
|
|
|
# 셀에 위젯 설정
|
|
self.table.setCellWidget(row, col, container)
|
|
|
|
def _set_content_label(self, row: int, col: int, field: FieldConfig, value: Any, record: Misc) -> int:
|
|
"""전달내용/특이사항 라벨 설정 (줄바꿈 지원)
|
|
|
|
Returns:
|
|
계산된 셀 높이 (픽셀)
|
|
"""
|
|
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
|
|
|
display_value = self._format_value(field, value) if value else ""
|
|
|
|
# ClickableLabel 생성 (줄바꿈 지원)
|
|
label = ClickableLabel(display_value or "", enable_hover=False, enable_double_click=True, enable_right_click=True)
|
|
label.setWordWrap(True)
|
|
label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
|
|
|
# 레코드 정보 저장
|
|
label.setProperty("record_id", record.id)
|
|
label.setProperty("field_name", field.name)
|
|
label.setProperty("field_value", value or "")
|
|
|
|
# 더블클릭 시 편집
|
|
def on_double_clicked():
|
|
self._edit_field_inline(row, col, field, record)
|
|
|
|
label.double_clicked.connect(on_double_clicked)
|
|
|
|
# 우클릭 컨텍스트 메뉴
|
|
def on_right_clicked(pos):
|
|
from PySide6.QtWidgets import QMenu
|
|
menu = QMenu(self)
|
|
|
|
edit_action = menu.addAction("편집")
|
|
edit_action.triggered.connect(lambda: self._edit_field_inline(row, col, field, record))
|
|
|
|
menu.exec_(label.mapToGlobal(pos))
|
|
|
|
label.right_clicked.connect(on_right_clicked)
|
|
|
|
# 컨테이너 위젯으로 감싸기
|
|
container = QWidget()
|
|
container_layout = QVBoxLayout(container)
|
|
container_layout.setContentsMargins(8, 4, 8, 4)
|
|
container_layout.setSpacing(0)
|
|
container_layout.addWidget(label)
|
|
|
|
# 구분선 및 텍스트 색상 스타일 적용
|
|
theme = self.config.theme
|
|
gridline_color = "#475569" if theme == 'dark' else "#cbd5e1"
|
|
text_color = "#f8fafc" if theme == 'dark' else "#1e293b"
|
|
|
|
# 높이 계산
|
|
font_metrics = self.table.fontMetrics()
|
|
base_font_size = 13
|
|
col_width = self.table.columnWidth(col)
|
|
needs_wrap = False
|
|
calculated_height = int(40 * 1.15)
|
|
|
|
if display_value and col_width > 0:
|
|
available_width = col_width - 16
|
|
text_width = font_metrics.horizontalAdvance(display_value)
|
|
|
|
if text_width > available_width:
|
|
needs_wrap = True
|
|
wrap_font_size = base_font_size - 2
|
|
|
|
from PySide6.QtGui import QFont, QFontMetrics
|
|
small_font = QFont(self.table.font())
|
|
small_font.setPointSize(wrap_font_size)
|
|
small_font_metrics = QFontMetrics(small_font)
|
|
|
|
chars_per_line = max(1, available_width // small_font_metrics.averageCharWidth())
|
|
lines = max(1, (len(display_value) // chars_per_line) + 1)
|
|
line_height = small_font_metrics.height() + 6
|
|
calculated_height = max(int(40 * 1.15), lines * line_height + 8)
|
|
else:
|
|
calculated_height = int(40 * 1.15)
|
|
|
|
# 라벨에 스타일 적용
|
|
if needs_wrap:
|
|
wrap_font_size = base_font_size - 2
|
|
label.setStyleSheet(f"""
|
|
QLabel {{
|
|
color: {text_color};
|
|
background-color: transparent;
|
|
font-size: {wrap_font_size}px;
|
|
}}
|
|
""")
|
|
else:
|
|
label.setStyleSheet(f"""
|
|
QLabel {{
|
|
color: {text_color};
|
|
background-color: transparent;
|
|
}}
|
|
""")
|
|
|
|
container.setStyleSheet(f"border-right: 1px solid {gridline_color};")
|
|
|
|
# 셀에 위젯 설정
|
|
self.table.setCellWidget(row, col, container)
|
|
|
|
return calculated_height
|
|
|
|
def _show_team_confirmations_popup(self, record: Misc, label: ClickableLabel):
|
|
"""확인팀 선택 팝업 표시 (다중선택)"""
|
|
from ui.components.chips.choice_chip_button import ChoiceChipButton
|
|
from ui.components.popup_widget import PopupWidget
|
|
from core.constants import TEAMS
|
|
from PySide6.QtWidgets import QHBoxLayout, QWidget
|
|
import json
|
|
|
|
# 기존 팝업이 있으면 닫기
|
|
if self._current_team_popup:
|
|
self._current_team_popup.hide_popup()
|
|
self._current_team_popup = None
|
|
|
|
# 현재 확인된 팀 찾기
|
|
current_confirmations = {}
|
|
try:
|
|
confirmations = json.loads(record.team_confirmations) if isinstance(record.team_confirmations, str) else record.team_confirmations
|
|
if isinstance(confirmations, dict):
|
|
current_confirmations = confirmations
|
|
except Exception:
|
|
pass
|
|
|
|
# 팝업 생성
|
|
popup = PopupWidget(self, title="확인팀 선택", width=320, auto_hide=False)
|
|
self._current_team_popup = popup
|
|
|
|
# 칩 그룹 생성 (다중선택용)
|
|
chip_group = {}
|
|
chip_container = QWidget()
|
|
chip_layout = QHBoxLayout(chip_container)
|
|
chip_layout.setContentsMargins(0, 0, 0, 0)
|
|
chip_layout.setSpacing(8)
|
|
|
|
def update_confirmations():
|
|
"""확인 상태 업데이트"""
|
|
confirmations = {team: chip.isChecked() for team, chip in chip_group.items()}
|
|
self._update_team_confirmations(record.id, confirmations)
|
|
self.refresh_data()
|
|
|
|
for team in TEAMS:
|
|
is_checked = current_confirmations.get(team, False)
|
|
chip_bg = "#64748b" if is_checked else "#00bfa5"
|
|
|
|
chip = ChoiceChipButton(
|
|
text=team,
|
|
key=team,
|
|
bg=chip_bg
|
|
)
|
|
chip_group[team] = chip
|
|
chip_layout.addWidget(chip)
|
|
|
|
if is_checked:
|
|
chip.setChecked(True)
|
|
|
|
def make_toggle_handler(team_key: str):
|
|
def handler(_key: str, checked: bool):
|
|
chip = chip_group[team_key]
|
|
if checked:
|
|
chip.set_bg("#64748b")
|
|
else:
|
|
chip.set_bg("#00bfa5")
|
|
update_confirmations()
|
|
return handler
|
|
|
|
chip.toggled_key.connect(make_toggle_handler(team))
|
|
|
|
chip_layout.addStretch()
|
|
popup.content_layout.addWidget(chip_container)
|
|
|
|
# 팝업 크기 자동 조정
|
|
chip_container.adjustSize()
|
|
chip_width = chip_container.sizeHint().width()
|
|
popup_width = max(200, min(400, chip_width + 40))
|
|
popup.container.setFixedWidth(popup_width)
|
|
|
|
# 팝업이 마우스 밖으로 나가면 닫기
|
|
original_leave_event = popup.leaveEvent
|
|
def on_popup_leave(event):
|
|
if self._current_team_popup == popup:
|
|
popup.hide_popup()
|
|
self._current_team_popup = None
|
|
original_leave_event(event)
|
|
popup.leaveEvent = on_popup_leave
|
|
|
|
# 마우스 추적 활성화
|
|
popup.setMouseTracking(True)
|
|
popup.container.setMouseTracking(True)
|
|
|
|
# 라벨 위치 기준으로 팝업 위치 계산
|
|
label_pos = label.mapToGlobal(QPoint(0, 0))
|
|
popup_pos = QPoint(label_pos.x(), label_pos.y() + label.height() + 5)
|
|
popup.show_at(popup_pos)
|
|
|
|
def _show_created_team_popup(self, record: Misc, label: ClickableLabel):
|
|
"""생성팀 선택 팝업 표시 (단일선택)"""
|
|
from ui.components.chips.choice_chip_button import ChoiceChipButton
|
|
from ui.components.popup_widget import PopupWidget
|
|
from core.constants import TEAMS
|
|
from PySide6.QtWidgets import QHBoxLayout, QWidget, QButtonGroup
|
|
|
|
# 기존 팝업이 있으면 닫기
|
|
if self._current_team_popup:
|
|
self._current_team_popup.hide_popup()
|
|
self._current_team_popup = None
|
|
|
|
current_team = record.created_team or ""
|
|
|
|
# 팝업 생성
|
|
popup = PopupWidget(self, title="생성팀 선택", width=400, auto_hide=False)
|
|
self._current_team_popup = popup
|
|
|
|
# 칩 그룹 생성 (단일선택용)
|
|
button_group = QButtonGroup()
|
|
button_group.setExclusive(True)
|
|
|
|
chip_container = QWidget()
|
|
chip_layout = QHBoxLayout(chip_container)
|
|
chip_layout.setContentsMargins(0, 0, 0, 0)
|
|
chip_layout.setSpacing(8)
|
|
|
|
def on_team_selected(team_key: str):
|
|
self.crud.update_misc(record.id, created_team=team_key)
|
|
self.refresh_data()
|
|
popup.hide_popup()
|
|
self._current_team_popup = None
|
|
|
|
for team in TEAMS:
|
|
chip_bg = "#64748b" if team == current_team else "#2979ff"
|
|
|
|
chip = ChoiceChipButton(
|
|
text=team,
|
|
key=team,
|
|
bg=chip_bg
|
|
)
|
|
button_group.addButton(chip)
|
|
chip_layout.addWidget(chip)
|
|
|
|
if team == current_team:
|
|
chip.setChecked(True)
|
|
|
|
def make_chip_handler(team_key: str):
|
|
def handler():
|
|
for btn in button_group.buttons():
|
|
if isinstance(btn, ChoiceChipButton):
|
|
if btn.key == team_key:
|
|
btn.set_bg("#64748b")
|
|
else:
|
|
btn.set_bg("#2979ff")
|
|
on_team_selected(team_key)
|
|
return handler
|
|
|
|
chip.clicked_key.connect(make_chip_handler(team))
|
|
|
|
chip_layout.addStretch()
|
|
popup.content_layout.addWidget(chip_container)
|
|
|
|
# 팝업 크기 자동 조정
|
|
chip_container.adjustSize()
|
|
chip_width = chip_container.sizeHint().width()
|
|
popup_width = max(200, min(400, chip_width + 40))
|
|
popup.container.setFixedWidth(popup_width)
|
|
|
|
# 팝업이 마우스 밖으로 나가면 닫기
|
|
original_leave_event = popup.leaveEvent
|
|
def on_popup_leave(event):
|
|
if self._current_team_popup == popup:
|
|
popup.hide_popup()
|
|
self._current_team_popup = None
|
|
original_leave_event(event)
|
|
popup.leaveEvent = on_popup_leave
|
|
|
|
popup.setMouseTracking(True)
|
|
popup.container.setMouseTracking(True)
|
|
|
|
label_pos = label.mapToGlobal(QPoint(0, 0))
|
|
popup_pos = QPoint(label_pos.x(), label_pos.y() + label.height() + 5)
|
|
popup.show_at(popup_pos)
|
|
|
|
|
|
class MiscInputDialog(SectionInputDialog):
|
|
"""기타 입력 다이얼로그"""
|
|
|
|
def __init__(self, parent=None, record: Misc = None):
|
|
super().__init__(
|
|
parent,
|
|
title="기타 추가" if record is None else "기타 편집",
|
|
record=record,
|
|
width=450,
|
|
height=650
|
|
)
|
|
|
|
self._setup_misc_fields()
|
|
|
|
if record:
|
|
self._load_record(record)
|
|
|
|
def _setup_misc_fields(self):
|
|
"""기타 필드 설정"""
|
|
from ui.components.custom_input import CustomLineEdit, CustomTextEdit, LabeledInput
|
|
|
|
# 전달자
|
|
self.reporter_input = CustomLineEdit(placeholder="전달자 이름")
|
|
self.content_layout.addWidget(LabeledInput("전달자", self.reporter_input))
|
|
|
|
# 전달내용
|
|
self.content_input = CustomTextEdit(placeholder="전달 내용을 입력하세요", min_height=80)
|
|
self.content_layout.addWidget(LabeledInput("전달내용", self.content_input, required=True))
|
|
|
|
# 특이사항
|
|
self.remarks_input = CustomTextEdit(placeholder="특이사항을 입력하세요", min_height=60)
|
|
self.content_layout.addWidget(LabeledInput("특이사항", self.remarks_input))
|
|
|
|
# 관련문서
|
|
self.document_input = CustomLineEdit(placeholder="관련 문서 (파일명 또는 링크)")
|
|
self.content_layout.addWidget(LabeledInput("관련문서", self.document_input))
|
|
|
|
def _load_record(self, record: Misc):
|
|
"""레코드 데이터 로드"""
|
|
self.reporter_input.setText(record.reporter or "")
|
|
self.content_input.set_text(record.report_content or "")
|
|
self.remarks_input.set_text(record.remarks or "")
|
|
self.document_input.setText(record.related_document or "")
|
|
|
|
def get_data(self) -> dict:
|
|
"""입력 데이터 반환"""
|
|
return {
|
|
"created_date": date.today().isoformat(),
|
|
"created_team": self.config.current_team,
|
|
"reporter": self.reporter_input.text(),
|
|
"report_content": self.content_input.get_text(),
|
|
"remarks": self.remarks_input.get_text(),
|
|
"related_document": self.document_input.text(),
|
|
}
|
|
|
|
|