""" Accounts Overview View - Account-Übersicht im Mockup-Style """ import logging from PyQt5.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QLabel, QPushButton, QScrollArea, QGridLayout, QFrame, QMessageBox, QCheckBox ) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont from views.widgets.account_card import AccountCard from application.services.platform_service import PlatformService logger = logging.getLogger("accounts_overview") class SidebarFilter(QWidget): """Sidebar mit Plattform-Filter nach Styleguide""" filter_changed = pyqtSignal(str) def __init__(self, language_manager=None, db_manager=None): super().__init__() self.language_manager = language_manager self.db_manager = db_manager self.platform_service = PlatformService(db_manager) self.filter_buttons = [] self.account_counts = {} self.init_ui() if self.language_manager: self.language_manager.language_changed.connect(self.update_texts) self.update_texts() def init_ui(self): """Initialisiert die UI nach Styleguide""" self.setMaximumWidth(260) # Styleguide: Sidebar-Breite self.setObjectName("filter_sidebar") # For QSS targeting layout = QVBoxLayout(self) layout.setContentsMargins(20, 20, 20, 20) layout.setSpacing(8) # Title removed - no longer needed # Build filter list dynamically self.filters = [("Alle", "all")] # Get platforms that should be shown in filters filter_platforms = self.platform_service.get_filter_platforms() for platform in filter_platforms: self.filters.append((platform.display_name, platform.id)) # Create filter buttons for name, key in self.filters: btn = self._create_filter_button(name, key) self.filter_buttons.append((btn, key)) layout.addWidget(btn) layout.addStretch() def _create_filter_button(self, name, key): """Erstellt einen Filter-Button""" btn = QPushButton(f"{name} (0)") btn.setObjectName(key) btn.setCursor(Qt.PointingHandCursor) btn.setObjectName("filter_button") # For QSS targeting btn.clicked.connect(lambda: self._on_filter_clicked(key)) # Erste Option als aktiv setzen if key == "all": btn.setProperty("selected", "true") btn.setStyle(btn.style()) return btn def _on_filter_clicked(self, key): """Behandelt Filter-Klicks""" # Update button states for btn, btn_key in self.filter_buttons: btn.setProperty("selected", "true" if btn_key == key else "false") btn.setStyle(btn.style()) self.filter_changed.emit(key) def update_counts(self, counts): """Aktualisiert die Account-Anzahlen""" self.account_counts = counts for btn, key in self.filter_buttons: count = counts.get(key, 0) name = next(name for name, k in self.filters if k == key) btn.setText(f"{name} ({count})") def update_texts(self): """Aktualisiert die Texte gemäß der aktuellen Sprache""" if not self.language_manager: return # Title removed - no text update needed class AccountsOverviewView(QWidget): """ Account-Übersicht im Mockup-Style mit: - Sidebar-Filter - Grid-Layout mit Account-Karten - Batch-Export mit Checkbox-Auswahl """ # Signals account_login_requested = pyqtSignal(dict) account_export_requested = pyqtSignal(dict) account_delete_requested = pyqtSignal(dict) export_requested = pyqtSignal() # Für Kompatibilität bulk_export_requested = pyqtSignal(list) # List[int] - account_ids def __init__(self, db_manager=None, language_manager=None): super().__init__() self.db_manager = db_manager self.language_manager = language_manager self.current_filter = "all" self.accounts = [] self.selected_account_ids = set() # Set of selected account IDs self.account_cards = [] # List of AccountCard widgets self._updating_selection = False # Flag to prevent selection loops self.init_ui() if self.language_manager: self.language_manager.language_changed.connect(self.update_texts) self.update_texts() def init_ui(self): """Initialisiert die UI nach Styleguide""" self.setObjectName("accounts_overview") # For QSS targeting # Hauptlayout main_layout = QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) # Sidebar self.sidebar = SidebarFilter(self.language_manager, self.db_manager) self.sidebar.filter_changed.connect(self._on_filter_changed) main_layout.addWidget(self.sidebar) # Content Area content_widget = QWidget() content_widget.setObjectName("accounts_content") # For QSS targeting content_layout = QVBoxLayout(content_widget) content_layout.setContentsMargins(40, 30, 40, 30) content_layout.setSpacing(20) # Header header_layout = QHBoxLayout() self.title = QLabel("Alle Accounts") title_font = QFont("Poppins", 24) title_font.setBold(True) self.title.setFont(title_font) self.title.setObjectName("section_title") # For QSS targeting header_layout.addWidget(self.title) header_layout.addStretch() # Selection Mode Button self.selection_mode_btn = QPushButton("Exportieren") self.selection_mode_btn.setCursor(Qt.PointingHandCursor) self.selection_mode_btn.setStyleSheet(""" QPushButton { background-color: #10B981; color: white; border: none; border-radius: 6px; font-size: 13px; font-weight: 600; font-family: 'Poppins', sans-serif; padding: 8px 16px; min-width: 100px; } QPushButton:hover { background-color: #059669; } QPushButton:pressed { background-color: #047857; } """) self.selection_mode_btn.clicked.connect(self._enable_selection_mode) header_layout.addWidget(self.selection_mode_btn) content_layout.addLayout(header_layout) # Selection Toolbar (initially hidden) self.toolbar = QFrame() self.toolbar.setObjectName("selection_toolbar") self.toolbar.setVisible(False) self.toolbar.setStyleSheet(""" #selection_toolbar { background-color: #F3F4F6; border-radius: 8px; padding: 12px 16px; } """) toolbar_layout = QHBoxLayout(self.toolbar) toolbar_layout.setContentsMargins(0, 0, 0, 0) toolbar_layout.setSpacing(16) # "Alle auswählen" Checkbox self.select_all_checkbox = QCheckBox("Alle auswählen") self.select_all_checkbox.setTristate(True) self.select_all_checkbox.setStyleSheet(""" QCheckBox { font-size: 13px; font-family: 'Poppins', sans-serif; font-weight: 500; color: #374151; } QCheckBox::indicator { width: 18px; height: 18px; border-radius: 4px; border: 2px solid #D1D5DB; } QCheckBox::indicator:checked { background-color: #0099CC; border-color: #0099CC; } QCheckBox::indicator:indeterminate { background-color: #0099CC; border-color: #0099CC; opacity: 0.6; } """) self.select_all_checkbox.stateChanged.connect(self._on_select_all_changed) toolbar_layout.addWidget(self.select_all_checkbox) # Selection count label self.selection_count_label = QLabel("0 ausgewählt") self.selection_count_label.setStyleSheet(""" color: #6B7280; font-size: 13px; font-family: 'Poppins', sans-serif; """) toolbar_layout.addWidget(self.selection_count_label) toolbar_layout.addStretch() # Export button self.bulk_export_btn = QPushButton("Exportieren") self.bulk_export_btn.setEnabled(False) self.bulk_export_btn.setCursor(Qt.PointingHandCursor) self.bulk_export_btn.setStyleSheet(""" QPushButton { background-color: #0099CC; color: white; border: none; border-radius: 6px; font-size: 13px; font-weight: 600; font-family: 'Poppins', sans-serif; padding: 8px 16px; min-width: 100px; } QPushButton:hover { background-color: #0078A3; } QPushButton:pressed { background-color: #005C7A; } QPushButton:disabled { background-color: #D1D5DB; color: #9CA3AF; } """) self.bulk_export_btn.clicked.connect(self._on_bulk_export_clicked) toolbar_layout.addWidget(self.bulk_export_btn) # Close selection mode button close_btn = QPushButton("✕") close_btn.setToolTip("Auswahl beenden") close_btn.setCursor(Qt.PointingHandCursor) close_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #6B7280; border: none; font-size: 18px; padding: 4px; min-width: 24px; max-width: 24px; } QPushButton:hover { color: #374151; } """) close_btn.clicked.connect(self._disable_selection_mode) toolbar_layout.addWidget(close_btn) content_layout.addWidget(self.toolbar) # Scroll Area für Accounts self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) # No inline styles - handled by QSS # Grid Container self.container = QWidget() self.container.setObjectName("grid_container") # For QSS targeting self.grid_layout = QGridLayout(self.container) self.grid_layout.setSpacing(24) # Styleguide Grid-Gap self.grid_layout.setContentsMargins(0, 0, 0, 0) self.scroll.setWidget(self.container) content_layout.addWidget(self.scroll) main_layout.addWidget(content_widget) # Initial load self.load_accounts() def load_accounts(self): """Lädt Accounts aus der Datenbank""" if not self.db_manager: return try: # Hole alle Accounts aus der Datenbank self.accounts = self.db_manager.get_all_accounts() self._update_display() self._update_sidebar_counts() except Exception as e: logger.error(f"Fehler beim Laden der Accounts: {e}") self.accounts = [] QMessageBox.warning( self, "Fehler", f"Fehler beim Laden der Accounts:\n{str(e)}" ) def _update_display(self): """Aktualisiert die Anzeige basierend auf dem aktuellen Filter""" # Clear existing widgets and account_cards list self.account_cards = [] while self.grid_layout.count(): child = self.grid_layout.takeAt(0) if child.widget(): child.widget().deleteLater() # Filter accounts if self.current_filter == "all": filtered_accounts = self.accounts else: # Use PlatformName for consistent filtering from domain.value_objects.platform_name import PlatformName filter_platform = PlatformName(self.current_filter) filtered_accounts = [ acc for acc in self.accounts if PlatformName.is_valid(acc.get("platform", "")) and PlatformName(acc.get("platform", "")) == filter_platform ] # Gruppiere nach Plattform wenn "Alle" ausgewählt if self.current_filter == "all": self._display_grouped_accounts(filtered_accounts) else: self._display_accounts_grid(filtered_accounts) def _display_grouped_accounts(self, accounts): """Zeigt Accounts gruppiert nach Plattform""" # Gruppiere Accounts nach Plattform platforms = {} for acc in accounts: platform = acc.get("platform", "unknown").lower() if platform not in platforms: platforms[platform] = [] platforms[platform].append(acc) row = 0 for platform, platform_accounts in sorted(platforms.items()): if not platform_accounts: continue # Platform Header header = QLabel(platform.capitalize()) header_font = QFont("Poppins", 18) header_font.setWeight(QFont.DemiBold) header.setFont(header_font) header.setObjectName("platform_header") # For QSS targeting self.grid_layout.addWidget(header, row, 0, 1, 3) row += 1 # Accounts col = 0 for acc in platform_accounts: card = self._create_account_card(acc) self.grid_layout.addWidget(card, row, col) col += 1 if col > 2: # 3 columns col = 0 row += 1 if col > 0: row += 1 row += 1 # Extra space between platforms # Add stretch self.grid_layout.setRowStretch(row, 1) def _display_accounts_grid(self, accounts): """Zeigt Accounts in einem einfachen Grid""" row = 0 col = 0 for acc in accounts: card = self._create_account_card(acc) self.grid_layout.addWidget(card, row, col) col += 1 if col > 2: # 3 columns col = 0 row += 1 # Add stretch self.grid_layout.setRowStretch(row + 1, 1) def _create_account_card(self, account_data): """Erstellt eine Account-Karte""" card = AccountCard(account_data, self.language_manager) # Verbinde Signals card.login_requested.connect(self.account_login_requested.emit) card.export_requested.connect(self.account_export_requested.emit) card.delete_requested.connect(self._on_delete_requested) card.selection_changed.connect(self._on_card_selection_changed) # Zu Liste hinzufügen für Batch-Operations self.account_cards.append(card) return card def _on_delete_requested(self, account_data): """Behandelt Delete-Anfragen mit Bestätigung""" msg_box = QMessageBox(self) msg_box.setWindowTitle("Account löschen") msg_box.setText(f"Möchten Sie den Account '{account_data.get('username', '')}' wirklich löschen?") msg_box.setIcon(QMessageBox.Question) # Custom Buttons hinzufügen delete_button = msg_box.addButton("Löschen", QMessageBox.YesRole) cancel_button = msg_box.addButton("Abbrechen", QMessageBox.NoRole) msg_box.setDefaultButton(cancel_button) # Abbrechen als Standard # Set object name for QSS targeting delete_button.setObjectName("delete_confirm_button") msg_box.exec_() if msg_box.clickedButton() == delete_button: self.account_delete_requested.emit(account_data) # Reload accounts nach Löschung self.load_accounts() def _on_filter_changed(self, filter_key): """Behandelt Filter-Änderungen""" self.current_filter = filter_key # Update title using PlatformName for consistency if filter_key == "all": self.title.setText("Alle Accounts") else: try: from domain.value_objects.platform_name import PlatformName platform_obj = PlatformName(filter_key) platform_name = platform_obj.display except: # Fallback platform_name = filter_key.capitalize() if filter_key == "x": platform_name = "X" self.title.setText(f"{platform_name} Accounts") self._update_display() def _update_sidebar_counts(self): """Aktualisiert die Account-Anzahlen in der Sidebar""" counts = {"all": len(self.accounts)} # Zähle Accounts pro Plattform for acc in self.accounts: platform = acc.get("platform", "").lower() if platform: counts[platform] = counts.get(platform, 0) + 1 self.sidebar.update_counts(counts) def update_texts(self): """Aktualisiert die Texte gemäß der aktuellen Sprache""" if not self.language_manager: return # Update title based on current filter if self.current_filter == "all": self.title.setText( self.language_manager.get_text("accounts_overview.all_accounts", "Alle Accounts") ) else: platform_name = self.current_filter.capitalize() if self.current_filter == "x": platform_name = "X (Twitter)" self.title.setText( self.language_manager.get_text( f"accounts_overview.{self.current_filter}_accounts", f"{platform_name} Accounts" ) ) def update_session_status(self, account_id, status): """ Session-Status-Update deaktiviert (Session-Funktionalität entfernt). """ # Session-Funktionalität wurde entfernt - diese Methode macht nichts mehr pass # ========== Selection Mode Methods ========== def _enable_selection_mode(self): """Aktiviert den Selection-Modus""" # Show toolbar, hide selection button self.toolbar.setVisible(True) self.selection_mode_btn.setVisible(False) # Enable selection mode on all cards for card in self.account_cards: card.set_selection_mode(True) # Reset selection state self.selected_account_ids.clear() self._update_selection_ui() def _disable_selection_mode(self): """Deaktiviert den Selection-Modus""" # Hide toolbar, show selection button self.toolbar.setVisible(False) self.selection_mode_btn.setVisible(True) # Disable selection mode on all cards for card in self.account_cards: card.set_selection_mode(False) # Clear selection self.selected_account_ids.clear() self._update_selection_ui() def _on_card_selection_changed(self, account_id: int, selected: bool): """Handler wenn eine Card ausgewählt/abgewählt wird""" if selected: self.selected_account_ids.add(account_id) else: self.selected_account_ids.discard(account_id) self._update_selection_ui() def _on_select_all_changed(self, state): """Handler für "Alle auswählen" Checkbox""" if self._updating_selection: return select_all = (state == Qt.Checked) # Set all cards to the same selection state self._updating_selection = True for card in self.account_cards: card.set_selected(select_all) self._updating_selection = False def _update_selection_ui(self): """Aktualisiert die Selection-UI (Count-Label, Button-Status)""" count = len(self.selected_account_ids) # Update count label if count == 0: self.selection_count_label.setText("0 ausgewählt") elif count == 1: self.selection_count_label.setText("1 ausgewählt") else: self.selection_count_label.setText(f"{count} ausgewählt") # Enable/Disable export button self.bulk_export_btn.setEnabled(count > 0) # Update "Alle auswählen" checkbox state total_cards = len(self.account_cards) if total_cards > 0: self._updating_selection = True if count == 0: self.select_all_checkbox.setCheckState(Qt.Unchecked) elif count == total_cards: self.select_all_checkbox.setCheckState(Qt.Checked) else: self.select_all_checkbox.setCheckState(Qt.PartiallyChecked) self._updating_selection = False def _on_bulk_export_clicked(self): """Handler für Bulk-Export-Button""" if len(self.selected_account_ids) == 0: return # Emit signal with list of selected account IDs self.bulk_export_requested.emit(list(self.selected_account_ids))