Files
AccountForger-neuerUpload/views/components/accounts_overview_view.py
Claude Project Manager 2d276f167c Batch-Export geht jetzt
2025-11-16 23:24:04 +01:00

610 Zeilen
21 KiB
Python

"""
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))