781 lines
28 KiB
Python
781 lines
28 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
팀 인원 설정 다이얼로그
|
|
각 팀의 부팀장과 운용 인원을 설정합니다.
|
|
"""
|
|
|
|
from typing import List, Optional, Dict
|
|
from PySide6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
|
|
QLabel, QPushButton, QLineEdit, QListWidget, QListWidgetItem,
|
|
QMessageBox
|
|
)
|
|
from PySide6.QtCore import Qt, Signal, QSize, QTimer
|
|
from PySide6.QtGui import QColor, QPainter, QPen, QBrush
|
|
|
|
from ui.base.base_dialog import BaseDialog
|
|
from ui.components.custom_input import CustomLineEdit
|
|
from ui.components.custom_button import CustomButton
|
|
from ui.components.custom_checkbox import CustomCheckBox
|
|
from ui.styles.style_manager import StyleManager
|
|
from database.crud import CRUDManager
|
|
from database.models import TeamMember
|
|
from core.constants import TEAMS
|
|
from core.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class PositionBox(QWidget):
|
|
"""직책 표시 박스 (부팀장/운용)"""
|
|
|
|
position_toggled = Signal(int, str) # member_id, new_position
|
|
|
|
def __init__(self, member_id: int, position: str, parent=None):
|
|
super().__init__(parent)
|
|
self.member_id = member_id
|
|
self.position = position
|
|
self.style_manager = StyleManager()
|
|
self.setFixedSize(60, 30)
|
|
self.setCursor(Qt.PointingHandCursor)
|
|
|
|
def paintEvent(self, _event):
|
|
"""그리기"""
|
|
painter = QPainter(self)
|
|
painter.setRenderHint(QPainter.Antialiasing)
|
|
|
|
colors = self.style_manager.get_colors()
|
|
font = self.style_manager.get_font("dialog", "content")
|
|
|
|
# 직책에 따른 색상 구분
|
|
if self.position == "부팀장":
|
|
bg_color = QColor("#3b82f6") # 파란색
|
|
text_color = QColor("#ffffff")
|
|
else: # 운용
|
|
bg_color = QColor("#22c55e") # 초록색
|
|
text_color = QColor("#ffffff")
|
|
|
|
# 배경
|
|
rect = self.rect().adjusted(2, 2, -2, -2)
|
|
painter.setBrush(QBrush(bg_color))
|
|
painter.setPen(QPen(QColor(colors['border']), 1))
|
|
painter.drawRoundedRect(rect, 4, 4)
|
|
|
|
# 텍스트
|
|
painter.setPen(text_color)
|
|
painter.setFont(font)
|
|
painter.drawText(rect, Qt.AlignCenter, self.position)
|
|
|
|
def mousePressEvent(self, _event):
|
|
"""클릭으로 직책 토글"""
|
|
if _event.button() == Qt.LeftButton:
|
|
new_position = "운용" if self.position == "부팀장" else "부팀장"
|
|
self.position = new_position
|
|
self.position_toggled.emit(self.member_id, new_position)
|
|
self.update()
|
|
|
|
|
|
class NameLabel(QLabel):
|
|
"""이름 레이블 (더블클릭으로 수정)"""
|
|
|
|
name_changed = Signal(int, str) # member_id, new_name
|
|
|
|
def __init__(self, member_id: int, name: str, parent=None):
|
|
super().__init__(name, parent)
|
|
self.member_id = member_id
|
|
self._original_name = name
|
|
self._is_editing = False
|
|
self.edit_widget = None
|
|
self.style_manager = StyleManager()
|
|
self.setCursor(Qt.PointingHandCursor)
|
|
|
|
def mouseDoubleClickEvent(self, _event):
|
|
"""더블클릭으로 수정 모드"""
|
|
if self._is_editing:
|
|
return
|
|
|
|
self._is_editing = True
|
|
self._original_name = self.text()
|
|
|
|
# 다이얼로그 레벨로 편집 위젯 생성
|
|
dialog = self._find_dialog()
|
|
if not dialog:
|
|
return
|
|
|
|
# 편집 모드로 전환 (다이얼로그 위에 표시)
|
|
self.edit_widget = QLineEdit(self.text(), dialog)
|
|
|
|
# 리스트 항목의 전역 좌표로 위치 설정
|
|
# NameLabel의 부모(MemberListItem)를 통해 위치 계산
|
|
parent_widget = self.parent()
|
|
if parent_widget:
|
|
# MemberListItem의 전역 위치
|
|
item_global_pos = parent_widget.mapToGlobal(parent_widget.rect().topLeft())
|
|
# 다이얼로그의 전역 위치
|
|
dialog_global_pos = dialog.mapToGlobal(dialog.rect().topLeft())
|
|
# 상대 위치 계산
|
|
relative_x = item_global_pos.x() - dialog_global_pos.x()
|
|
relative_y = item_global_pos.y() - dialog_global_pos.y()
|
|
|
|
# NameLabel의 상대 위치 추가 (부모 위젯 내에서의 위치)
|
|
name_pos_in_parent = self.pos()
|
|
relative_x += name_pos_in_parent.x()
|
|
relative_y += name_pos_in_parent.y()
|
|
else:
|
|
# 폴백: 다이얼로그 중앙
|
|
relative_x = dialog.width() // 2 - 150
|
|
relative_y = dialog.height() // 2
|
|
|
|
# 다이얼로그 내 위치로 조정
|
|
self.edit_widget.setGeometry(
|
|
relative_x,
|
|
relative_y,
|
|
max(self.width(), 200), # 최소 너비
|
|
max(self.height(), 40) # 최소 높이
|
|
)
|
|
|
|
# 스타일 적용
|
|
colors = self.style_manager.get_colors()
|
|
input_font = self.style_manager.get_font("dialog", "input")
|
|
input_height = self.style_manager.calculate_input_height(
|
|
font=input_font, area="dialog", style="input"
|
|
)
|
|
self.edit_widget.setStyleSheet(f"""
|
|
QLineEdit {{
|
|
background-color: {colors['input_bg']};
|
|
color: {colors['input_text']};
|
|
border: 2px solid {colors['accent']};
|
|
border-radius: 6px;
|
|
padding: {input_height // 4}px 12px;
|
|
font-family: '{input_font.family()}';
|
|
font-size: {input_font.pointSize()}pt;
|
|
min-height: {input_height}px;
|
|
}}
|
|
QLineEdit:focus {{
|
|
border-color: {colors['accent']};
|
|
outline: none;
|
|
}}
|
|
""")
|
|
self.edit_widget.setFont(input_font)
|
|
|
|
# z-order 최상위로
|
|
self.edit_widget.raise_()
|
|
self.edit_widget.selectAll()
|
|
self.edit_widget.setFocus()
|
|
self.edit_widget.show()
|
|
|
|
# 엔터 키 처리 (다이얼로그 닫힘 방지)
|
|
def handle_return_pressed():
|
|
self._finish_edit()
|
|
|
|
self.edit_widget.returnPressed.connect(handle_return_pressed)
|
|
self.edit_widget.editingFinished.connect(self._finish_edit)
|
|
|
|
# keyPressEvent 오버라이드하여 엔터 키가 다이얼로그로 전파되지 않도록
|
|
original_key_press = self.edit_widget.keyPressEvent
|
|
def key_press_handler(event):
|
|
if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
|
|
self._finish_edit()
|
|
event.accept() # 이벤트 소비하여 전파 방지
|
|
else:
|
|
original_key_press(event)
|
|
self.edit_widget.keyPressEvent = key_press_handler
|
|
|
|
# focusOutEvent 오버라이드
|
|
original_focus_out = self.edit_widget.focusOutEvent
|
|
def focus_out_handler(event):
|
|
original_focus_out(event)
|
|
self._finish_edit()
|
|
self.edit_widget.focusOutEvent = focus_out_handler
|
|
|
|
def _find_dialog(self):
|
|
"""다이얼로그 찾기"""
|
|
parent = self.parent()
|
|
while parent:
|
|
from ui.base.base_dialog import BaseDialog
|
|
if isinstance(parent, BaseDialog):
|
|
return parent
|
|
parent = parent.parent()
|
|
return None
|
|
|
|
def _finish_edit(self):
|
|
"""편집 완료"""
|
|
if not self._is_editing or not self.edit_widget:
|
|
return
|
|
|
|
new_name = self.edit_widget.text().strip()
|
|
if new_name and new_name != self._original_name:
|
|
# 이름 변경 시그널 발생
|
|
self.name_changed.emit(self.member_id, new_name)
|
|
# 리스트 아이템 텍스트 업데이트
|
|
self.setText(new_name)
|
|
|
|
# 편집 위젯 제거
|
|
self.edit_widget.deleteLater()
|
|
self.edit_widget = None
|
|
self._is_editing = False
|
|
|
|
|
|
class MemberListItem(QWidget):
|
|
"""인원 리스트 항목 위젯"""
|
|
|
|
# 시그널
|
|
position_toggled = Signal(int, str) # member_id, new_position
|
|
name_changed = Signal(int, str) # member_id, new_name
|
|
delete_requested = Signal(int) # member_id
|
|
|
|
def __init__(self, member: TeamMember, group_number: int, parent=None):
|
|
super().__init__(parent)
|
|
self.member = member
|
|
self.group_number = group_number
|
|
self.style_manager = StyleManager()
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""UI 설정"""
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(8, 4, 8, 4)
|
|
layout.setSpacing(8)
|
|
|
|
colors = self.style_manager.get_colors()
|
|
|
|
# 체크박스 (커스텀)
|
|
self.checkbox = CustomCheckBox()
|
|
self.checkbox.setFixedSize(24, 24)
|
|
self.checkbox.stateChanged.connect(self._on_checkbox_changed)
|
|
layout.addWidget(self.checkbox)
|
|
|
|
# 직책 박스
|
|
self.position_box = PositionBox(self.member.id, self.member.position, self)
|
|
self.position_box.position_toggled.connect(self._on_position_toggled)
|
|
layout.addWidget(self.position_box)
|
|
|
|
# 이름
|
|
self.name_label = NameLabel(self.member.id, self.member.name, self)
|
|
name_font = self.style_manager.get_font("dialog", "content")
|
|
self.name_label.setFont(name_font)
|
|
name_height = self.style_manager.calculate_label_height(
|
|
font=name_font, area="dialog", style="content"
|
|
)
|
|
self.name_label.setMinimumHeight(name_height)
|
|
self.name_label.name_changed.connect(self._on_name_changed)
|
|
layout.addWidget(self.name_label, 1)
|
|
|
|
# 그룹번호
|
|
if self.group_number > 0:
|
|
group_label = QLabel(f"그룹{self.group_number}")
|
|
group_font = self.style_manager.get_font("dialog", "content")
|
|
group_label.setFont(group_font)
|
|
group_label.setStyleSheet(f"color: {colors['text_tertiary']};")
|
|
layout.addWidget(group_label)
|
|
else:
|
|
layout.addWidget(QLabel("")) # 공간 확보
|
|
|
|
# 삭제 버튼
|
|
self.delete_btn = QPushButton("삭제")
|
|
self.delete_btn.setFixedSize(50, 28)
|
|
self.delete_btn.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: {colors['error']};
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-size: 11pt;
|
|
}}
|
|
QPushButton:hover {{
|
|
background-color: #dc2626;
|
|
}}
|
|
""")
|
|
self.delete_btn.clicked.connect(self._on_delete_clicked)
|
|
layout.addWidget(self.delete_btn)
|
|
|
|
def _on_position_toggled(self, member_id: int, new_position: str):
|
|
"""직책 토글"""
|
|
self.member.position = new_position
|
|
# 시그널 emit
|
|
self.position_toggled.emit(member_id, new_position)
|
|
|
|
def _on_name_changed(self, member_id: int, new_name: str):
|
|
"""이름 변경"""
|
|
self.member.name = new_name
|
|
self.name_label.setText(new_name)
|
|
# 시그널 emit
|
|
self.name_changed.emit(member_id, new_name)
|
|
|
|
def _on_delete_clicked(self):
|
|
"""삭제 버튼 클릭"""
|
|
# 시그널 emit
|
|
self.delete_requested.emit(self.member.id)
|
|
|
|
def set_group_color(self, color: str):
|
|
"""그룹 배경색 설정"""
|
|
self.setStyleSheet(f"background-color: {color}; border-radius: 4px;")
|
|
|
|
def _on_checkbox_changed(self, state):
|
|
"""체크박스 변경 시"""
|
|
self._notify_selection_change()
|
|
|
|
def _notify_selection_change(self):
|
|
"""체크박스 변경 알림"""
|
|
# MemberListWidget 찾기
|
|
parent = self.parent()
|
|
while parent:
|
|
if isinstance(parent, MemberListWidget):
|
|
parent.check_selection()
|
|
break
|
|
parent = parent.parent()
|
|
|
|
|
|
class MemberListWidget(QWidget):
|
|
"""인원 목록 위젯"""
|
|
|
|
member_changed = Signal()
|
|
|
|
def __init__(
|
|
self,
|
|
parent=None,
|
|
team: str = "",
|
|
position: Optional[str] = None,
|
|
max_members: int = 3
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self.team = team
|
|
self.position = position
|
|
self.max_members = max_members
|
|
self.crud = CRUDManager()
|
|
self.style_manager = StyleManager()
|
|
self.members: List[TeamMember] = []
|
|
self.group_colors: Dict[int, str] = {}
|
|
|
|
self._setup_ui()
|
|
self._load_members()
|
|
|
|
def _setup_ui(self):
|
|
"""UI 설정"""
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(8)
|
|
|
|
colors = self.style_manager.get_colors()
|
|
|
|
# 제목
|
|
if self.position:
|
|
title_text = f"{self.position} ({self.max_members}명)"
|
|
else:
|
|
title_text = "인원 목록"
|
|
title = QLabel(title_text)
|
|
title_font = self.style_manager.get_font("dialog", "label")
|
|
title.setFont(title_font)
|
|
title_height = self.style_manager.calculate_label_height(
|
|
font=title_font, area="dialog", style="label"
|
|
)
|
|
title.setStyleSheet(f"""
|
|
color: {colors['text_primary']};
|
|
font-weight: bold;
|
|
min-height: {title_height}px;
|
|
""")
|
|
layout.addWidget(title)
|
|
|
|
# 리스트
|
|
self.list_widget = QListWidget()
|
|
self.list_widget.setFont(self.style_manager.get_font("dialog", "content"))
|
|
self.list_widget.setMaximumHeight(300)
|
|
self.list_widget.setStyleSheet(f"""
|
|
QListWidget {{
|
|
background-color: {colors['bg_secondary']};
|
|
border: 1px solid {colors['border']};
|
|
border-radius: 6px;
|
|
}}
|
|
QListWidget::item {{
|
|
border-bottom: 1px solid {colors['border']};
|
|
}}
|
|
QListWidget::item:last {{
|
|
border-bottom: none;
|
|
}}
|
|
""")
|
|
layout.addWidget(self.list_widget)
|
|
|
|
# 입력 영역
|
|
input_layout = QHBoxLayout()
|
|
input_layout.setSpacing(8)
|
|
|
|
self.name_input = CustomLineEdit(placeholder="이름 입력")
|
|
# 엔터 키 이벤트 처리 (다이얼로그 닫힘 방지)
|
|
def handle_return_pressed():
|
|
self._add_member()
|
|
self.name_input.returnPressed.connect(handle_return_pressed)
|
|
|
|
# 엔터 키가 다이얼로그로 전파되지 않도록 처리
|
|
original_key_press = self.name_input.keyPressEvent
|
|
def key_press_handler(event):
|
|
if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
|
|
self._add_member()
|
|
event.accept()
|
|
else:
|
|
original_key_press(event)
|
|
self.name_input.keyPressEvent = key_press_handler
|
|
|
|
input_layout.addWidget(self.name_input, 1)
|
|
|
|
self.add_btn = CustomButton("추가", style_type="primary", fixed_height=36)
|
|
self.add_btn.clicked.connect(self._add_member)
|
|
input_layout.addWidget(self.add_btn)
|
|
|
|
self.group_btn = CustomButton("그룹묶기", style_type="outline", fixed_height=36)
|
|
self.group_btn.setEnabled(False)
|
|
self.group_btn.clicked.connect(self._toggle_group)
|
|
input_layout.addWidget(self.group_btn)
|
|
|
|
layout.addLayout(input_layout)
|
|
|
|
# 선택 변경 타이머 (너무 빈번한 호출 방지)
|
|
self._selection_timer = QTimer()
|
|
self._selection_timer.setSingleShot(True)
|
|
self._selection_timer.timeout.connect(self._update_group_button_state)
|
|
|
|
|
|
def _load_members(self):
|
|
"""인원 목록 로드"""
|
|
self.list_widget.clear()
|
|
self.members = []
|
|
|
|
# 디버깅: 조회 파라미터 로그
|
|
logger.debug("인원 목록 로드: 팀=%s, 직책=%s, 활성화만=%s",
|
|
self.team, self.position, True)
|
|
|
|
all_members = self.crud.get_team_members_by_team(
|
|
self.team,
|
|
position=None, # 모든 직책
|
|
active_only=True
|
|
)
|
|
|
|
# 디버깅: 조회 결과 로그
|
|
logger.debug("조회된 멤버 수: %d (팀=%s)", len(all_members), self.team)
|
|
if all_members:
|
|
logger.debug("멤버 목록: %s", [f"{m.name}({m.position})" for m in all_members])
|
|
|
|
# 그룹 번호 계산
|
|
group_map = self._calculate_groups(all_members)
|
|
|
|
# 모든 인원 표시 (직책 필터링 없음)
|
|
for member in all_members:
|
|
self.members.append(member)
|
|
group_number = group_map.get(member.id, 0)
|
|
|
|
# 리스트 항목 생성
|
|
item = QListWidgetItem()
|
|
item.setSizeHint(QSize(0, 40))
|
|
|
|
member_widget = MemberListItem(member, group_number, self)
|
|
member_widget.position_toggled.connect(self._on_position_toggled)
|
|
member_widget.name_changed.connect(self._on_name_changed)
|
|
member_widget.delete_requested.connect(self._on_delete_requested)
|
|
|
|
# 그룹 배경색 설정
|
|
if group_number > 0:
|
|
color = self._get_group_color(group_number)
|
|
member_widget.set_group_color(color)
|
|
|
|
self.list_widget.addItem(item)
|
|
self.list_widget.setItemWidget(item, member_widget)
|
|
|
|
self._update_group_button_state()
|
|
|
|
def _calculate_groups(self, members: List[TeamMember]) -> Dict[int, int]:
|
|
"""그룹 번호 계산"""
|
|
group_map = {}
|
|
group_counter = 1
|
|
|
|
for member in members:
|
|
if member.partner_id and member.id not in group_map:
|
|
# 파트너 찾기
|
|
partner = next((m for m in members if m.id == member.partner_id), None)
|
|
if partner and partner.id not in group_map:
|
|
group_num = group_counter
|
|
group_counter += 1
|
|
group_map[member.id] = group_num
|
|
group_map[partner.id] = group_num
|
|
|
|
return group_map
|
|
|
|
def _get_group_color(self, group_number: int) -> str:
|
|
"""그룹별 색상 반환"""
|
|
colors = [
|
|
"#e0f2fe", # 그룹1 - 연한 파랑
|
|
"#fef3c7", # 그룹2 - 연한 노랑
|
|
"#fce7f3", # 그룹3 - 연한 분홍
|
|
"#d1fae5", # 그룹4 - 연한 초록
|
|
"#e9d5ff", # 그룹5 - 연한 보라
|
|
]
|
|
theme = self.style_manager.config.theme
|
|
if theme == 'dark':
|
|
colors = [
|
|
"#1e3a5f", # 그룹1 - 어두운 파랑
|
|
"#5a4a1f", # 그룹2 - 어두운 노랑
|
|
"#5a2a4a", # 그룹3 - 어두운 분홍
|
|
"#1a4a2f", # 그룹4 - 어두운 초록
|
|
"#4a2a5a", # 그룹5 - 어두운 보라
|
|
]
|
|
|
|
return colors[(group_number - 1) % len(colors)]
|
|
|
|
def check_selection(self):
|
|
"""체크박스 변경 감지 (public 메서드)"""
|
|
self._selection_timer.start(100) # 100ms 후 호출
|
|
|
|
|
|
def _update_group_button_state(self):
|
|
"""체크된 아이템을 분석하여 그룹 버튼 활성화 여부 결정"""
|
|
checked_widgets = []
|
|
for i in range(self.list_widget.count()):
|
|
item = self.list_widget.item(i)
|
|
widget = self.list_widget.itemWidget(item)
|
|
if isinstance(widget, MemberListItem) and widget.checkbox.isChecked():
|
|
checked_widgets.append(widget)
|
|
|
|
# 초기화
|
|
self.group_btn.setEnabled(False)
|
|
self.group_btn.setText("그룹묶기")
|
|
|
|
# 조건: 정확히 2명 선택
|
|
if len(checked_widgets) != 2:
|
|
return
|
|
|
|
w1, w2 = checked_widgets[0], checked_widgets[1]
|
|
|
|
# DB에서 최신 데이터 가져오기
|
|
m1 = self.crud.get_team_member(w1.member.id)
|
|
m2 = self.crud.get_team_member(w2.member.id)
|
|
|
|
if not m1 or not m2:
|
|
return
|
|
|
|
# 위젯의 member 객체도 업데이트
|
|
w1.member = m1
|
|
w2.member = m2
|
|
|
|
# 조건: 부팀장 1명 + 운용 1명
|
|
positions = {m1.position, m2.position}
|
|
if "부팀장" not in positions or "운용" not in positions:
|
|
return
|
|
|
|
# 로직: 이미 짝궁인가? (양방향 체크)
|
|
is_partners = (m1.partner_id == m2.id) or (m2.partner_id == m1.id)
|
|
|
|
if is_partners:
|
|
# 이미 그룹임 -> 그룹 해제 모드
|
|
self.group_btn.setText("그룹해제")
|
|
self.group_btn.setEnabled(True)
|
|
else:
|
|
# 그룹이 아님 -> 그룹 묶기 모드
|
|
self.group_btn.setText("그룹묶기")
|
|
self.group_btn.setEnabled(True)
|
|
|
|
def _add_member(self):
|
|
"""인원 추가"""
|
|
name = self.name_input.text().strip()
|
|
if not name:
|
|
return
|
|
|
|
# 최대 인원 확인 (제한이 있는 경우만)
|
|
if self.max_members > 0 and len(self.members) >= self.max_members:
|
|
QMessageBox.warning(
|
|
self,
|
|
"인원 초과",
|
|
f"최대 {self.max_members}명까지 등록 가능합니다."
|
|
)
|
|
return
|
|
|
|
# 순서 결정
|
|
order = len(self.members)
|
|
|
|
# DB에 추가 (기본 부팀장)
|
|
try:
|
|
new_member = self.crud.create_team_member(
|
|
team=self.team,
|
|
position="부팀장", # 기본값
|
|
name=name,
|
|
order=order
|
|
)
|
|
logger.info("팀 인원 추가 성공: 팀=%s, 이름=%s, 직책=%s, ID=%s",
|
|
self.team, name, "부팀장", new_member.id if new_member else "None")
|
|
except Exception as e:
|
|
logger.error("팀 인원 추가 실패: 팀=%s, 이름=%s, 오류=%s", self.team, name, e)
|
|
QMessageBox.warning(
|
|
self,
|
|
"추가 실패",
|
|
f"인원 추가에 실패했습니다.\n오류: {str(e)}"
|
|
)
|
|
return
|
|
|
|
self.name_input.clear()
|
|
self.name_input.setFocus() # 포커스 유지
|
|
self._load_members()
|
|
self.member_changed.emit()
|
|
|
|
def _on_position_toggled(self, member_id: int, new_position: str):
|
|
"""직책 토글"""
|
|
self.crud.update_team_member(member_id, position=new_position)
|
|
self._load_members()
|
|
self.member_changed.emit()
|
|
|
|
def _on_name_changed(self, member_id: int, new_name: str):
|
|
"""이름 변경"""
|
|
self.crud.update_team_member(member_id, name=new_name)
|
|
self.member_changed.emit()
|
|
|
|
def _on_delete_requested(self, member_id: int):
|
|
"""삭제 요청"""
|
|
# 확인 메시지
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"삭제 확인",
|
|
"정말 삭제하시겠습니까?",
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
self.crud.delete_team_member(member_id)
|
|
self._load_members()
|
|
self.member_changed.emit()
|
|
logger.info("팀 인원 삭제: ID %s", member_id)
|
|
|
|
def _toggle_group(self):
|
|
"""그룹 묶기/해제"""
|
|
checked_items = []
|
|
|
|
for i in range(self.list_widget.count()):
|
|
item = self.list_widget.item(i)
|
|
widget = self.list_widget.itemWidget(item)
|
|
if isinstance(widget, MemberListItem) and widget.checkbox.isChecked():
|
|
checked_items.append(widget.member)
|
|
|
|
if len(checked_items) != 2:
|
|
return
|
|
|
|
member1 = checked_items[0]
|
|
member2 = checked_items[1]
|
|
|
|
if self.group_btn.text() == "그룹해제":
|
|
# 그룹 해제
|
|
self.crud.set_partner(member1.id, None)
|
|
self.crud.set_partner(member2.id, None)
|
|
else:
|
|
# 그룹 묶기
|
|
self.crud.set_partner(member1.id, member2.id)
|
|
|
|
# 체크박스 해제
|
|
for i in range(self.list_widget.count()):
|
|
item = self.list_widget.item(i)
|
|
widget = self.list_widget.itemWidget(item)
|
|
if isinstance(widget, MemberListItem):
|
|
widget.checkbox.setChecked(False)
|
|
|
|
self._load_members()
|
|
self.member_changed.emit()
|
|
|
|
|
|
class TeamSettingsDialog(BaseDialog):
|
|
"""
|
|
팀 인원 설정 다이얼로그
|
|
|
|
각 팀의 부팀장과 운용 인원을 설정합니다.
|
|
"""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(
|
|
parent,
|
|
title="팀 인원 설정",
|
|
width=400,
|
|
height=600,
|
|
min_width=300,
|
|
min_height=500
|
|
)
|
|
|
|
self.style_manager = StyleManager()
|
|
self._setup_content()
|
|
self.add_button("닫기", self.close, primary=True)
|
|
|
|
def keyPressEvent(self, event):
|
|
"""키 이벤트 처리 - 편집 중일 때 엔터 키가 다이얼로그를 닫지 않도록"""
|
|
# 편집 중인 위젯이 있는지 확인
|
|
for tab_idx in range(self.tabs.count()):
|
|
tab = self.tabs.widget(tab_idx)
|
|
member_list = tab.findChild(MemberListWidget)
|
|
if member_list:
|
|
for i in range(member_list.list_widget.count()):
|
|
item = member_list.list_widget.item(i)
|
|
widget = member_list.list_widget.itemWidget(item)
|
|
if isinstance(widget, MemberListItem):
|
|
name_label = widget.name_label
|
|
if name_label._is_editing and name_label.edit_widget:
|
|
# 편집 중이면 엔터 키를 무시
|
|
if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
|
|
event.accept()
|
|
return
|
|
|
|
# 편집 중이 아니면 기본 동작 수행
|
|
super().keyPressEvent(event)
|
|
|
|
def _setup_content(self):
|
|
"""컨텐츠 설정"""
|
|
colors = self.style_manager.get_colors()
|
|
|
|
# 탭 위젯
|
|
self.tabs = QTabWidget()
|
|
tab_font = self.style_manager.get_font("dialog", "label")
|
|
self.tabs.setFont(tab_font)
|
|
|
|
# 탭 스타일 적용
|
|
self.tabs.setStyleSheet(f"""
|
|
QTabWidget::pane {{
|
|
border: 1px solid {colors['border']};
|
|
border-radius: 8px;
|
|
background-color: {colors['bg_secondary']};
|
|
}}
|
|
QTabBar::tab {{
|
|
background-color: {colors['bg_tertiary']};
|
|
color: {colors['text_secondary']};
|
|
padding: 8px 16px;
|
|
border-top-left-radius: 6px;
|
|
border-top-right-radius: 6px;
|
|
font-family: '{tab_font.family()}';
|
|
font-size: {tab_font.pointSize()}pt;
|
|
min-height: {self.style_manager.calculate_label_height(font=tab_font, area="dialog", style="label")}px;
|
|
}}
|
|
QTabBar::tab:selected {{
|
|
background-color: {colors['bg_secondary']};
|
|
color: {colors['text_primary']};
|
|
font-weight: bold;
|
|
}}
|
|
QTabBar::tab:hover {{
|
|
background-color: {colors['bg_hover']};
|
|
}}
|
|
""")
|
|
|
|
# 각 팀별 탭 생성
|
|
for team in TEAMS:
|
|
tab = self._create_team_tab(team)
|
|
self.tabs.addTab(tab, team)
|
|
|
|
self.content_layout.addWidget(self.tabs)
|
|
|
|
def _create_team_tab(self, team: str) -> QWidget:
|
|
"""팀별 탭 생성"""
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setContentsMargins(16, 16, 16, 16)
|
|
layout.setSpacing(16)
|
|
|
|
# 통합 인원 목록 (부팀장과 운용 모두)
|
|
member_list = MemberListWidget(
|
|
parent=tab,
|
|
team=team,
|
|
position=None, # 모든 직책
|
|
max_members=10 # 제한 없음
|
|
)
|
|
layout.addWidget(member_list, 1)
|
|
|
|
return tab
|