# announcement_dialog.py from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QLabel, QPushButton, QScrollArea, QWidget, QTextEdit, QHBoxLayout, QCheckBox, QMessageBox ) from PySide6.QtCore import Qt, QTimer, QSettings from PySide6.QtGui import QFont from datetime import datetime, timezone class AnnouncementDialog(QDialog): def __init__(self, supabase_manager, user_info, parent=None): super().__init__(parent) self.supabase_manager = supabase_manager self.user_info = user_info # dict, 예: {"email": "...", "name": "...", ...} self.settings = QSettings("WhenRideMycar", "AutoPercenty3") self.setWindowTitle("로그인 성공 - 공지사항 및 사용자 정보") self.setMinimumSize(600, 500) self.auto_close_timer = None # 자동 닫힘 타이머 self.remaining_seconds = 3 # 3초 카운트다운 self.init_ui() self.load_announcements() self.check_auto_close_condition() self.handle_membership_validity() def init_ui(self): main_layout = QVBoxLayout(self) main_layout.setContentsMargins(20, 20, 20, 20) main_layout.setSpacing(15) # 사용자 정보 패널 self.info_label = QLabel(self.format_user_info()) self.info_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #333;") main_layout.addWidget(self.info_label) # 이벤트 안내 레이블 (나중에 get_membership_message의 결과를 표시) self.event_label = QLabel("") self.event_label.setStyleSheet("font-size: 14px; color: #d32f2f;") main_layout.addWidget(self.event_label) # 공지사항 헤더 header_label = QLabel("새 공지사항") header_label.setStyleSheet("font-size: 16px; font-weight: bold;") main_layout.addWidget(header_label) # 스크롤 영역에 공지사항 목록 self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) container = QWidget() self.ann_layout = QVBoxLayout(container) self.ann_layout.setSpacing(10) container.setLayout(self.ann_layout) self.scroll_area.setWidget(container) main_layout.addWidget(self.scroll_area) # "다음부터 공지사항 보지 않기" 체크박스 self.hide_checkbox = QCheckBox("다음부터 공지사항 보지 않기") self.hide_checkbox.setToolTip("체크 시, 새로운 공지사항이 없으면 3초 후 자동으로 닫힙니다.") main_layout.addWidget(self.hide_checkbox) # 카운트다운 레이블 (초 단위) self.countdown_label = QLabel("") self.countdown_label.setAlignment(Qt.AlignRight) self.countdown_label.setStyleSheet("font-size: 12px; color: #666;") main_layout.addWidget(self.countdown_label) # 닫기 버튼 self.close_btn = QPushButton("닫기") self.close_btn.setStyleSheet(""" QPushButton { background-color: #1877f2; color: white; border-radius: 4px; padding: 8px; font-size: 14px; } QPushButton:hover { background-color: #166fe5; } """) self.close_btn.clicked.connect(self.accept) main_layout.addWidget(self.close_btn) self.setLayout(main_layout) def format_user_info(self): # 환영 메시지: nickname이 있으면 사용, 없으면 이메일 사용 welcome = self.user_info.get("nickname") or self.user_info.get("email", "사용자") email = self.user_info.get("email") membership = self.user_info.get("membership_level", "free") # 남은 사용 기간 계산 (payment_period_end와 현재 시간 비교) payment_period_end_str = self.user_info.get("payment_period_end") remaining_period_str = "N/A" if payment_period_end_str: try: # payment_period_end_str에 타임존 정보가 없다면 UTC로 지정 payment_period_end = datetime.fromisoformat(payment_period_end_str) if payment_period_end.tzinfo is None: payment_period_end = payment_period_end.replace(tzinfo=timezone.utc) now = datetime.now(timezone.utc) if payment_period_end > now: delta = payment_period_end - now days = delta.days hours, remainder = divmod(delta.seconds, 3600) minutes, seconds = divmod(remainder, 60) remaining_period_str = f"{days}일 {hours:02d}:{minutes:02d}:{seconds:02d}" else: remaining_period_str = "만료됨" except Exception as e: remaining_period_str = "N/A" print(f"에러 : {e}") # 마지막 로그인 시간 포맷 (YYYY-MM-DD HH:MM:SS) last_login_str = self.user_info.get("last_login", "N/A") formatted_last_login = last_login_str try: dt = datetime.fromisoformat(last_login_str) formatted_last_login = dt.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass # banned_keyword_count: SupabaseManager에서 해당 사용자의 금지어 개수를 조회 user_id = self.user_info.get("id") banned_keyword_count = self.supabase_manager.get_banned_keyword_count(user_id) info_text = ( f"환영합니다, {welcome}님!\n" f"회원이메일: {email} | 회원 등급: {membership} | 남은 사용 기간: {remaining_period_str}\n" f"관리 중 금지키워드: {banned_keyword_count}개 | 마지막 로그인: {formatted_last_login}" ) return info_text def load_announcements(self): """공지사항 목록을 Supabase에서 로드하고, 읽은 항목은 QSettings에 기록된 대로 처리합니다.""" # 먼저, 기존 위젯 제거 for i in reversed(range(self.ann_layout.count())): widget = self.ann_layout.itemAt(i).widget() if widget: widget.setParent(None) try: announcements = self.supabase_manager.get_announcements() if not announcements: self.ann_layout.addWidget(QLabel("새 공지사항이 없습니다.")) return # QSettings에서 이미 읽은 공지사항 ID 목록을 불러옴 read_ids = self.settings.value("read_announcements", []) if not isinstance(read_ids, list): read_ids = [] # 정렬: position 기준 오름차순 announcements = sorted(announcements, key=lambda x: x["position"]) for ann in announcements: # 만약 공지사항이 중요하지 않고, 이미 읽은 상태라면 표시하지 않음. if (ann.get("id") in read_ids) and (not ann.get("important", False)): continue # 버튼 텍스트: 중요 공지라면 "[중요공지]" 접두어 추가 prefix = "[중요공지] " if ann.get("important", False) else "" btn = QPushButton(f"{prefix}{ann['position']}. {ann['title']}") btn.setStyleSheet(""" QPushButton { background-color: #1877f2; color: white; border-radius: 5px; padding: 8px; text-align: left; font-size: 14px; } QPushButton:hover { background-color: #166fe5; } """) # 버튼 클릭 시 상세 공지사항 팝업 표시 btn.clicked.connect(lambda checked, a=ann: self.show_announcement_detail(a)) self.ann_layout.addWidget(btn) except Exception as e: QMessageBox.warning(self, "오류", f"공지사항 로드 중 오류 발생: {e}") def show_announcement_detail(self, announcement): """공지사항 버튼 클릭 시 상세 내용을 팝업으로 보여주고, 읽은 것으로 처리합니다.""" try: dialog = QDialog(self) dialog.setWindowTitle(f"{announcement['position']}. {announcement['title']}") dialog.setMinimumSize(400, 300) layout = QVBoxLayout(dialog) text_edit = QTextEdit() text_edit.setReadOnly(True) text_edit.setHtml(announcement["content"]) layout.addWidget(text_edit) close_btn = QPushButton("닫기") close_btn.clicked.connect(dialog.accept) layout.addWidget(close_btn) dialog.setLayout(layout) dialog.exec() # 읽음 처리 (중요 공지가 아니면) if not announcement.get("important", False): read_ids = self.settings.value("read_announcements", []) if not isinstance(read_ids, list): read_ids = [] if announcement.get("id") not in read_ids: read_ids.append(announcement.get("id")) self.settings.setValue("read_announcements", read_ids) self.settings.sync() self.load_announcements() except Exception as e: QMessageBox.warning(self, "오류", f"공지사항 표시 중 오류 발생: {e}") def handle_membership_validity(self): """ SupabaseManager의 check_membership_validity()를 호출하여 사용 기간이 유효한지 확인합니다. - 유효하다면 get_membership_message()로 이벤트 메시지를 표시합니다. - 만약 기간이 만료되었다면, 재결제 안내 메시지와 함께 다이얼로그를 종료(프로그램 사용 제한)합니다. """ valid = self.supabase_manager.check_membership_validity(self.user_info) print(f"membership_validity : {valid}") if valid: membership_msg = self.supabase_manager.get_membership_message(self.user_info) self.event_label.setText(membership_msg) # 만약 "다음부터 공지사항 보지 않기" 체크되어 있고, 공지사항 목록이 비어있다면 자동 종료 조건을 시작 self.check_auto_close_condition() else: QMessageBox.warning(self, "사용 기간 만료", "사용 기간이 만료되었습니다. 재결제 후 이용해 주세요.") # 재결제 페이지 호출(예: RePaymentDialog) 또는 프로그램 종료 # self.open_repayment_page() self.reject() def open_repayment_page(self): """ 재결제 안내 및 재결제 페이지를 표시합니다. 실제 구현에서는 재결제 다이얼로그나 웹 페이지를 호출할 수 있습니다. """ QMessageBox.information(self, "재결제 안내", "사용 기간이 만료되었습니다. 재결제 페이지로 이동합니다.") # 예: self.parent().open_repayment_dialog() 또는 웹 브라우저를 통해 특정 URL 오픈 def check_auto_close_condition(self): """ "다음부터 공지사항 보지 않기" 체크박스가 선택되어 있고, 현재 표시할 공지사항(읽지 않은, 중요 공지를 제외한)이 없다면 3초 후 자동으로 닫히도록 카운트다운 시작. """ # 조건: 체크박스가 체크되어 있고, announcements가 빈 경우 announcements = self.supabase_manager.get_announcements() read_ids = self.settings.value("read_announcements", []) if not isinstance(read_ids, list): read_ids = [] # 필터: 중요 공지를 제외하고, 읽지 않은 항목만 계산 new_announcements = [ann for ann in announcements if (ann.get("id") not in read_ids) and (not ann.get("important", False))] if self.hide_checkbox.isChecked() and not new_announcements: # 3초 카운트다운 시작 self.remaining_seconds = 3 self.countdown_label.setText(f"자동 종료까지: {self.remaining_seconds}초") self.auto_close_timer = QTimer(self) self.auto_close_timer.timeout.connect(self.update_countdown) self.auto_close_timer.start(1000) def update_countdown(self): self.remaining_seconds -= 1 if self.remaining_seconds > 0: self.countdown_label.setText(f"자동 종료까지: {self.remaining_seconds}초") else: self.auto_close_timer.stop() self.accept() # 다이얼로그 자동 종료