Inhalt

Aktueller Ordner: duesseldorfer-schuelerinventar-python-client
⬅ Übergeordnet

duesk_client_text.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Düsseldorfer Schülerinventar (DÜSK) - Python Client
GUI mit reiner Textdarstellung (keine Diagramme)
"""

import sys
import subprocess
import requests
import math
from datetime import datetime

# GUI imports
try:
    from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                                  QHBoxLayout, QLabel, QLineEdit, QPushButton, 
                                  QTableWidget, QTableWidgetItem, QMessageBox,
                                  QHeaderView, QDialog, QDialogButtonBox, 
                                  QFormLayout, QProgressDialog, QGroupBox,
                                  QTabWidget, QComboBox, QScrollArea, QTextEdit,
                                  QSplitter, QRadioButton, QButtonGroup,
                                  QFileDialog, QToolBar)
    from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
    from PyQt6.QtGui import QFont, QAction
except ImportError:
    print("Installiere PyQt6...")
    subprocess.check_call([sys.executable, "-m", "pip", "install", "PyQt6", "requests"])
    from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                                  QHBoxLayout, QLabel, QLineEdit, QPushButton, 
                                  QTableWidget, QTableWidgetItem, QMessageBox,
                                  QHeaderView, QDialog, QDialogButtonBox, 
                                  QFormLayout, QProgressDialog, QGroupBox,
                                  QTabWidget, QComboBox, QScrollArea, QTextEdit,
                                  QSplitter, QRadioButton, QButtonGroup,
                                  QFileDialog, QToolBar)
    from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
    from PyQt6.QtGui import QFont, QAction

API_BASE_URL = "https://paul-koop.org/api/"

# ============ NORMWERTE AUS IHRER DATENBANK ============
NORM_SE_HS = {1: [21.33, 25.33, 29.33, 33.32, 37.32], 2: [20.87, 24.95, 29.03, 33.13, 37.18],
              3: [17.93, 21.37, 24.80, 28.23, 31.67], 4: [13.98, 17.71, 21.44, 25.17, 28.90],
              5: [24.60, 28.55, 33.04, 37.53, 42.01], 6: [15.53, 18.97, 22.40, 25.83, 29.27]}

NORM_FE_HS = {1: [12.66, 18.16, 23.66, 29.16, 34.66], 2: [13.33, 18.42, 23.51, 28.60, 33.69],
              3: [10.75, 15.41, 20.07, 24.73, 29.39], 4: [14.22, 15.30, 16.38, 17.46, 18.54],
              5: [14.12, 20.21, 26.30, 32.39, 38.48], 6: [10.53, 14.51, 18.49, 22.47, 26.45]}

NORM_SE_FS = {1: [17.54, 24.03, 30.53, 37.02, 43.51], 2: [17.80, 24.26, 30.73, 37.19, 43.65],
              3: [18.03, 22.41, 26.79, 31.17, 35.55], 4: [14.28, 15.55, 16.83, 18.10, 19.37],
              5: [20.69, 27.49, 34.29, 41.09, 47.89], 6: [12.44, 18.06, 23.68, 29.29, 34.91]}

NORM_FE_FS = {1: [15.30, 19.79, 24.28, 28.77, 33.26], 2: [14.63, 18.94, 23.25, 27.56, 31.87],
              3: [14.62, 17.81, 21.00, 24.19, 27.38], 4: [15.00, 15.55, 16.10, 16.65, 17.20],
              5: [18.44, 22.61, 26.78, 30.95, 35.12], 6: [9.79, 13.97, 18.15, 22.33, 26.51]}


class APIWorker(QThread):
    finished = pyqtSignal(object)
    error = pyqtSignal(str)
    
    def __init__(self, endpoint, method="GET", data=None, params=None, user_id=None, session=None):
        super().__init__()
        self.endpoint = endpoint
        self.method = method
        self.data = data
        self.params = params
        self.user_id = user_id
        self.session = session
        
    def run(self):
        try:
            url = API_BASE_URL + self.endpoint
            headers = {'Content-Type': 'application/json'}
            if self.user_id and self.session:
                headers['X-User-ID'] = str(self.user_id)
                headers['X-Session'] = self.session
                
            if self.method == "GET":
                response = requests.get(url, headers=headers, params=self.params, timeout=30)
            elif self.method == "POST":
                response = requests.post(url, json=self.data, headers=headers, timeout=30)
            elif self.method == "PUT":
                response = requests.put(url, json=self.data, headers=headers, timeout=30)
            elif self.method == "DELETE":
                response = requests.delete(url, headers=headers, params=self.params, timeout=30)
            else:
                self.error.emit(f"Unbekannte Methode: {self.method}")
                return
                
            if response.status_code == 200:
                self.finished.emit(response.json())
            else:
                self.error.emit(f"HTTP {response.status_code}")
        except Exception as e:
            self.error.emit(str(e))


class ProfileViewDialog(QDialog):
    """Profilansicht mit reinen Text-Tabellen (keine Diagramme)"""
    
    KOMPETENZEN = ["Arbeitsverhalten", "Lernverhalten", "Sozialverhalten", 
                   "Fachkompetenz", "Personale Kompetenz", "Methodenkompetenz"]
    
    def __init__(self, profile_data, parent=None):
        super().__init__(parent)
        self.profile = profile_data
        self.current_norm = "HS"
        self.init_ui()
        self.calculate()
        
    def init_ui(self):
        self.setWindowTitle(f"Profil: {self.profile.get('name', 'Unbekannt')}")
        self.setMinimumSize(900, 700)
        
        layout = QVBoxLayout()
        
        # Kopfzeile
        header = QHBoxLayout()
        header.addWidget(QLabel(f"<h2>{self.profile.get('name', 'Unbekannt')}</h2>"))
        header.addStretch()
        header.addWidget(QLabel(f"<b>Profil-ID:</b> {self.profile.get('profilID', '?')}"))
        layout.addLayout(header)
        
        # Normauswahl
        norm_row = QHBoxLayout()
        norm_row.addWidget(QLabel("Normtabelle:"))
        self.norm_combo = QComboBox()
        self.norm_combo.addItem("Hauptschule (HS)", "HS")
        self.norm_combo.addItem("Förderschule (FS)", "FS")
        self.norm_combo.currentIndexChanged.connect(self.on_norm_change)
        norm_row.addWidget(self.norm_combo)
        norm_row.addStretch()
        layout.addLayout(norm_row)
        
        # Tabellen nebeneinander
        tables_widget = QWidget()
        tables_layout = QHBoxLayout(tables_widget)
        
        # Selbsteinschätzung
        self.se_table = self.create_competence_table("Selbsteinschätzung (SE)")
        tables_layout.addWidget(self.se_table)
        
        # Fremdeinschätzung
        self.fe_table = self.create_competence_table("Fremdeinschätzung (FE)")
        tables_layout.addWidget(self.fe_table)
        
        layout.addWidget(tables_widget)
        
        # Statistik
        stats_group = QGroupBox("Statistische Auswertung")
        stats_layout = QVBoxLayout()
        
        self.corr_label = QLabel()
        self.agree_label = QLabel()
        self.analysis = QTextEdit()
        self.analysis.setReadOnly(True)
        self.analysis.setMaximumHeight(120)
        
        stats_layout.addWidget(self.corr_label)
        stats_layout.addWidget(self.agree_label)
        stats_layout.addWidget(self.analysis)
        stats_group.setLayout(stats_layout)
        layout.addWidget(stats_group)
        
        # Alle Items (36) in scrollbarer Tabelle
        items_group = QGroupBox("Item-Werte (alle 36 Items)")
        items_layout = QVBoxLayout()
        
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        self.items_table = QTableWidget(36, 3)
        self.items_table.setHorizontalHeaderLabels(["Item", "Selbst", "Fremd"])
        self.items_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
        scroll.setWidget(self.items_table)
        items_layout.addWidget(scroll)
        
        items_group.setLayout(items_layout)
        layout.addWidget(items_group)
        
        # Schließen
        close_btn = QPushButton("Schließen")
        close_btn.setStyleSheet("background-color: #6699CC; color: white; padding: 8px;")
        close_btn.clicked.connect(self.accept)
        layout.addWidget(close_btn)
        
        self.setLayout(layout)
        
    def create_competence_table(self, title):
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.addWidget(QLabel(f"<b>{title}</b>", alignment=Qt.AlignmentFlag.AlignCenter))
        
        table = QTableWidget(6, 5)
        table.setHorizontalHeaderLabels(["1", "2", "3", "4", "5"])
        table.setVerticalHeaderLabels(self.KOMPETENZEN)
        table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        layout.addWidget(table)
        
        if title == "Selbsteinschätzung (SE)":
            self.se_table = table
        else:
            self.fe_table = table
            
        return widget
        
    def on_norm_change(self):
        self.current_norm = self.norm_combo.currentData()
        self.calculate()
        
    def calculate_sums(self, items):
        sums = [0] * 7
        sums[1] = sum(items[0:10])
        sums[2] = sum(items[10:20])
        sums[3] = sum(items[20:28]) + items[8] + items[9]
        sums[4] = sum(items[28:36])
        sums[5] = items[0] + items[1] + items[5] + items[6] + items[7] + items[8] + items[9] + items[11] + items[12] + items[13] + items[14]
        sums[6] = items[2] + items[3] + items[4] + items[8] + items[9] + items[10] + items[16] + items[17]
        return sums
        
    def get_profile_values(self, sums, norm):
        values = [0] * 6
        for k in range(1, 7):
            placed = False
            for p in range(5):
                if sums[k] < norm[k][p]:
                    values[k-1] = p
                    placed = True
                    break
            if not placed:
                values[k-1] = 4
        return values
        
    def fill_table(self, table, values):
        for i, v in enumerate(values):
            for j in range(5):
                table.setItem(i, j, QTableWidgetItem("X" if j == v else ""))
                table.item(i, j).setTextAlignment(Qt.AlignmentFlag.AlignCenter)
        table.resizeColumnsToContents()
        
    def calculate(self):
        # Items auslesen
        se_items = [int(self.profile.get(f'item{i}', 2)) for i in range(1, 37)]
        fe_items = [int(self.profile.get(f'feitem{i}', 2)) for i in range(1, 37)]
        
        # Summen
        se_sums = self.calculate_sums(se_items)
        fe_sums = self.calculate_sums(fe_items)
        
        # Normen
        if self.current_norm == "HS":
            norm_se, norm_fe = NORM_SE_HS, NORM_FE_HS
        else:
            norm_se, norm_fe = NORM_SE_FS, NORM_FE_FS
        
        # Profile (0-4)
        se_prof = self.get_profile_values(se_sums, norm_se)
        fe_prof = self.get_profile_values(fe_sums, norm_fe)
        
        # Tabellen füllen
        self.fill_table(self.se_table, se_prof)
        self.fill_table(self.fe_table, fe_prof)
        
        # Korrelation (Werte 1-5 für Korrelation)
        se_vals = [v + 1 for v in se_prof]
        fe_vals = [v + 1 for v in fe_prof]
        corr = self.calc_correlation(se_vals, fe_vals)
        agree = sum(1 for s, f in zip(se_items, fe_items) if s == f) * 100 / 36
        
        self.corr_label.setText(f"<b>Korrelation:</b> {corr:.2f}")
        self.agree_label.setText(f"<b>Übereinstimmung:</b> {agree:.1f}%")
        
        # Auswertung
        ratings = ["weit unterdurchschnittlich", "unterdurchschnittlich", "durchschnittlich", 
                   "überdurchschnittlich", "weit überdurchschnittlich"]
        
        text = "Selbsteinschätzung:\n"
        for i, v in enumerate(se_vals):
            text += f"  • {self.KOMPETENZEN[i]}: {ratings[v-1]}\n"
        text += "\nFremdeinschätzung:\n"
        for i, v in enumerate(fe_vals):
            text += f"  • {self.KOMPETENZEN[i]}: {ratings[v-1]}\n"
        text += f"\nInterpretation (Korrelation {corr:.2f}): "
        if corr >= 0.8:
            text += "Sehr gute Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
        elif corr >= 0.5:
            text += "Mäßige Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
        elif corr >= 0.3:
            text += "Schwache Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
        else:
            text += "Keine signifikante Übereinstimmung zwischen Selbst- und Fremdeinschätzung."
        self.analysis.setText(text)
        
        # Item-Tabelle (alle 36)
        names = [
            "1. Zuverlässigkeit", "2. Arbeitstempo", "3. Arbeitsplanung", "4. Organisationsfähigkeit",
            "5. Geschicklichkeit", "6. Ordnung", "7. Sorgfalt", "8. Kreativität", 
            "9. Problemlösungsfähigkeit", "10. Abstraktionsvermögen", "11. Selbstständigkeit", 
            "12. Belastbarkeit", "13. Konzentrationsfähigkeit", "14. Verantwortungsbewusstsein", 
            "15. Eigeninitiative", "16. Leistungsbereitschaft", "17. Auffassungsgabe", 
            "18. Merkfähigkeit", "19. Motivationsfähigkeit", "20. Reflektionsfähigkeit", 
            "21. Teamfähigkeit", "22. Hilfsbereitschaft", "23. Kontaktfähigkeit", 
            "24. Respektvoller Umgang", "25. Kommunikationsfähigkeit", "26. Einfühlungsvermögen",
            "27. Konfliktfähigkeit", "28. Kritikfähigkeit", "29. Schreiben", "30. Lesen",
            "31. Mathematik", "32. Naturwissenschaft", "33. Fremdsprachen", 
            "34. Präsentationsfähigkeit", "35. PC Kenntnisse", "36. Fächerübergreifendes Denken"
        ]
        for i, name in enumerate(names):
            self.items_table.setItem(i, 0, QTableWidgetItem(name))
            self.items_table.setItem(i, 1, QTableWidgetItem(str(se_items[i])))
            self.items_table.setItem(i, 2, QTableWidgetItem(str(fe_items[i])))
            
    def calc_correlation(self, a, b):
        ma, mb = sum(a)/6, sum(b)/6
        num = sum((a[i]-ma)*(b[i]-mb) for i in range(6))
        den = math.sqrt(sum((a[i]-ma)**2 for i in range(6)) * sum((b[i]-mb)**2 for i in range(6)))
        return num/den if den != 0 else 0


class ProfileEditDialog(QDialog):
    """Dialog zum Erstellen/Bearbeiten eines Profils"""
    
    ITEMS = [
        "Zuverlässigkeit", "Arbeitstempo", "Arbeitsplanung", "Organisationsfähigkeit",
        "Geschicklichkeit", "Ordnung", "Sorgfalt", "Kreativität", "Problemlösungsfähigkeit",
        "Abstraktionsvermögen", "Selbstständigkeit", "Belastbarkeit", "Konzentrationsfähigkeit",
        "Verantwortungsbewusstsein", "Eigeninitiative", "Leistungsbereitschaft", "Auffassungsgabe",
        "Merkfähigkeit", "Motivationsfähigkeit", "Reflektionsfähigkeit", "Teamfähigkeit",
        "Hilfsbereitschaft", "Kontaktfähigkeit", "Respektvoller Umgang", "Kommunikationsfähigkeit",
        "Einfühlungsvermögen", "Konfliktfähigkeit", "Kritikfähigkeit", "Schreiben", "Lesen",
        "Mathematik", "Naturwissenschaft", "Fremdsprachen", "Präsentationsfähigkeit",
        "PC Kenntnisse", "Fächerübergreifendes Denken"
    ]
    
    def __init__(self, user_id, session, profile=None, groups=None, parent=None):
        super().__init__(parent)
        self.user_id, self.session = user_id, session
        self.profile = profile or {}
        self.is_edit = profile is not None
        self.groups = groups or []
        self.se_btns, self.fe_btns = {}, {}
        self.init_ui()
        
    def init_ui(self):
        self.setWindowTitle("Profil bearbeiten" if self.is_edit else "Neues Profil")
        self.setMinimumSize(800, 600)
        
        layout = QVBoxLayout()
        
        # Basisinformationen
        info_group = QGroupBox("Profil-Informationen")
        info_layout = QFormLayout()
        
        self.name_edit = QLineEdit()
        self.name_edit.setText(self.profile.get('name', ''))
        info_layout.addRow("Name:", self.name_edit)
        
        self.group_combo = QComboBox()
        if self.groups:
            for g in self.groups:
                self.group_combo.addItem(g.get('name', ''), g.get('gruppeID'))
        else:
            self.group_combo.addItem("Keine Gruppen", None)
        if self.profile.get('gruppeID'):
            idx = self.group_combo.findData(self.profile.get('gruppeID'))
            if idx >= 0:
                self.group_combo.setCurrentIndex(idx)
        info_layout.addRow("Gruppe:", self.group_combo)
        
        self.new_group = QLineEdit()
        self.new_group.setPlaceholderText("Neue Gruppe (optional)")
        info_layout.addRow("Neue Gruppe:", self.new_group)
        
        info_group.setLayout(info_layout)
        layout.addWidget(info_group)
        
        # Tabs für SE und FE
        tabs = QTabWidget()
        
        se_widget = self.create_item_tab("Selbsteinschätzung", "se")
        tabs.addTab(se_widget, "Selbsteinschätzung")
        
        fe_widget = self.create_item_tab("Fremdeinschätzung", "fe")
        tabs.addTab(fe_widget, "Fremdeinschätzung")
        
        layout.addWidget(tabs)
        
        # Buttons
        btns = QHBoxLayout()
        save = QPushButton("Speichern")
        save.setStyleSheet("background-color: #4CAF50; color: white; padding: 8px;")
        save.clicked.connect(self.save)
        cancel = QPushButton("Abbrechen")
        cancel.clicked.connect(self.reject)
        btns.addStretch()
        btns.addWidget(save)
        btns.addWidget(cancel)
        layout.addLayout(btns)
        
        self.setLayout(layout)
        self.load_values()
        
    def create_item_tab(self, title, prefix):
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        container = QWidget()
        layout = QVBoxLayout(container)
        
        btns = self.se_btns if prefix == "se" else self.fe_btns
        for i, name in enumerate(self.ITEMS, 1):
            group = QGroupBox(f"{i:2d}. {name}")
            hbox = QHBoxLayout()
            bg = QButtonGroup()
            for val, label in [(4, "trifft voll zu (4)"), (3, "trifft zu (3)"), 
                              (2, "trifft teilweise zu (2)"), (1, "trifft nicht zu (1)")]:
                rb = QRadioButton(label)
                hbox.addWidget(rb)
                bg.addButton(rb, val)
            group.setLayout(hbox)
            layout.addWidget(group)
            btns[i] = bg
        
        scroll.setWidget(container)
        return scroll
        
    def load_values(self):
        for i in range(1, 37):
            if i in self.se_btns:
                val = self.profile.get(f'item{i}', 2)
                if btn := self.se_btns[i].button(val):
                    btn.setChecked(True)
            if i in self.fe_btns:
                val = self.profile.get(f'feitem{i}', 2)
                if btn := self.fe_btns[i].button(val):
                    btn.setChecked(True)
                    
    def save(self):
        name = self.name_edit.text().strip()
        if not name:
            QMessageBox.warning(self, "Fehler", "Bitte Name eingeben")
            return
            
        data = {'name': name}
        if self.group_combo.currentData():
            data['gruppeID'] = self.group_combo.currentData()
        if ng := self.new_group.text().strip():
            data['namegruppe'] = ng
            
        for i in range(1, 37):
            data[f'item{i}'] = self.se_btns[i].checkedId() if i in self.se_btns and self.se_btns[i].checkedId() != -1 else 2
            data[f'feitem{i}'] = self.fe_btns[i].checkedId() if i in self.fe_btns and self.fe_btns[i].checkedId() != -1 else 2
            
        if self.is_edit:
            data['profilID'] = self.profile.get('profilID')
            worker = APIWorker("api_profiles.php", "PUT", data, user_id=self.user_id, session=self.session)
        else:
            worker = APIWorker("api_profiles.php", "POST", data, user_id=self.user_id, session=self.session)
            
        worker.finished.connect(self.save_done)
        worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", e))
        worker.start()
        
        self.progress = QProgressDialog("Speichern...", None, 0, 0, self)
        self.progress.show()
        
    def save_done(self, resp):
        self.progress.close()
        if resp.get('success'):
            QMessageBox.information(self, "Erfolg", "Profil gespeichert")
            self.accept()
        else:
            QMessageBox.critical(self, "Fehler", resp.get('error', 'Unbekannter Fehler'))


class LoginDialog(QDialog):
    def __init__(self):
        super().__init__()
        self.user_id = self.session = None
        self.setWindowTitle("DÜSK - Anmeldung")
        self.setModal(True)
        self.setMinimumSize(350, 200)
        
        layout = QVBoxLayout()
        layout.addWidget(QLabel("DÜSK - Düsseldorfer Schülerinventar", 
                                alignment=Qt.AlignmentFlag.AlignCenter,
                                font=QFont("Arial", 16, QFont.Weight.Bold)))
        
        form = QFormLayout()
        self.user_edit = QLineEdit()
        self.user_edit.setText("gast")
        self.pw_edit = QLineEdit()
        self.pw_edit.setText("gast")
        self.pw_edit.setEchoMode(QLineEdit.EchoMode.Password)
        form.addRow("Benutzername:", self.user_edit)
        form.addRow("Passwort:", self.pw_edit)
        layout.addLayout(form)
        
        btns = QDialogButtonBox()
        login = btns.addButton("Anmelden", QDialogButtonBox.ButtonRole.AcceptRole)
        cancel = btns.addButton("Abbrechen", QDialogButtonBox.ButtonRole.RejectRole)
        login.clicked.connect(self.do_login)
        cancel.clicked.connect(self.reject)
        layout.addWidget(btns)
        
        layout.addWidget(QLabel("Server: paul-koop.org\nBenutzung mit gast/gast möglich",
                                alignment=Qt.AlignmentFlag.AlignCenter,
                                styleSheet="color: gray; font-size: 10px;"))
        self.setLayout(layout)
        
    def do_login(self):
        self.login_btn = self.findChild(QDialogButtonBox).buttons()[0]
        self.login_btn.setEnabled(False)
        
        worker = APIWorker("api_login.php", "POST", 
                          {"username": self.user_edit.text().strip(), 
                           "password": self.pw_edit.text().strip()})
        worker.finished.connect(self.on_success)
        worker.error.connect(self.on_error)
        worker.start()
        
        self.progress = QProgressDialog("Anmeldung...", None, 0, 0, self)
        self.progress.show()
        
    def on_success(self, resp):
        self.progress.close()
        if resp.get('success') or resp.get('userID'):
            self.user_id = resp['userID']
            self.session = resp['session']
            self.accept()
        else:
            self.login_btn.setEnabled(True)
            QMessageBox.warning(self, "Fehler", "Anmeldung fehlgeschlagen")
            
    def on_error(self, error):
        self.progress.close()
        self.login_btn.setEnabled(True)
        QMessageBox.critical(self, "Fehler", f"Verbindungsfehler: {error}")


class MainWindow(QMainWindow):
    def __init__(self, user_id, session):
        super().__init__()
        self.user_id, self.session = user_id, session
        self.profiles = []
        self.groups = []
        self.setWindowTitle("DÜSK - Düsseldorfer Schülerinventar")
        self.setMinimumSize(1000, 500)
        
        central = QWidget()
        self.setCentralWidget(central)
        layout = QVBoxLayout(central)
        
        # Header
        header = QLabel("DÜSK - Düsseldorfer Schülerinventar")
        header.setAlignment(Qt.AlignmentFlag.AlignCenter)
        header.setStyleSheet("font-size: 24px; font-weight: bold; background-color: #6699CC; color: white; padding: 15px;")
        layout.addWidget(header)
        
        # Toolbar
        toolbar = QToolBar()
        new_btn = QPushButton("Neues Profil")
        new_btn.clicked.connect(self.new_profile)
        toolbar.addWidget(new_btn)
        refresh_btn = QPushButton("Aktualisieren")
        refresh_btn.clicked.connect(self.load_profiles)
        toolbar.addWidget(refresh_btn)
        groups_btn = QPushButton("Gruppen verwalten")
        groups_btn.clicked.connect(self.manage_groups)
        toolbar.addWidget(groups_btn)
        toolbar.addSeparator()
        logout_btn = QPushButton("Abmelden")
        logout_btn.setStyleSheet("background-color: #f44336; color: white;")
        logout_btn.clicked.connect(self.logout)
        toolbar.addWidget(logout_btn)
        self.addToolBar(toolbar)
        
        # Tabelle
        self.table = QTableWidget()
        self.table.setColumnCount(4)
        self.table.setHorizontalHeaderLabels(["Name", "Gruppe", "ProfilID", "Aktion"])
        self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
        layout.addWidget(self.table)
        
        # Footer
        footer = QLabel("Paul Koop M.A. - Thomashofstrasse 19, 52070 Aachen")
        footer.setAlignment(Qt.AlignmentFlag.AlignCenter)
        footer.setStyleSheet("background-color: #6699CC; color: white; padding: 10px; margin-top: 10px;")
        layout.addWidget(footer)
        
        # Gruppen laden (asynchron, aber ohne das Fenster zu blockieren)
        self.load_groups()
        self.load_profiles()
        
    def load_groups(self):
        worker = APIWorker("api_groups.php", "GET", user_id=self.user_id, session=self.session)
        worker.finished.connect(self.groups_loaded)
        worker.error.connect(lambda e: print(f"Fehler beim Laden der Gruppen: {e}"))
        worker.start()
        
    def groups_loaded(self, resp):
        if isinstance(resp, list):
            self.groups = resp
            print(f"Gruppen geladen: {len(self.groups)}")
        
    def load_profiles(self):
        worker = APIWorker("api_profiles.php", "GET", user_id=self.user_id, session=self.session)
        worker.finished.connect(self.on_profiles)
        worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", e))
        worker.start()
        
    def on_profiles(self, resp):
        if isinstance(resp, list):
            self.profiles = resp
            self.table.setRowCount(len(resp))
            for i, p in enumerate(resp):
                self.table.setItem(i, 0, QTableWidgetItem(p.get('name', '')))
                self.table.setItem(i, 1, QTableWidgetItem(p.get('gruppename', '')))
                self.table.setItem(i, 2, QTableWidgetItem(str(p.get('profilID', ''))))
                
                actions = QWidget()
                hbox = QHBoxLayout(actions)
                hbox.setContentsMargins(0, 0, 0, 0)
                
                view = QPushButton("Anzeigen")
                view.setStyleSheet("background-color: #4CAF50; color: white;")
                view.clicked.connect(lambda _, pid=p.get('profilID'): self.view_profile(pid))
                hbox.addWidget(view)
                
                edit = QPushButton("Bearbeiten")
                edit.clicked.connect(lambda _, pid=p.get('profilID'): self.edit_profile(pid))
                hbox.addWidget(edit)
                
                delete = QPushButton("Löschen")
                delete.setStyleSheet("background-color: #f44336; color: white;")
                delete.clicked.connect(lambda _, pid=p.get('profilID'): self.delete_profile(pid))
                hbox.addWidget(delete)
                
                self.table.setCellWidget(i, 3, actions)
            self.table.resizeRowsToContents()
            
    def view_profile(self, pid):
        worker = APIWorker("api_profiles.php", "GET", params={"id": pid},
                          user_id=self.user_id, session=self.session)
        worker.finished.connect(lambda r: ProfileViewDialog(r).exec() if isinstance(r, dict) else None)
        worker.error.connect(lambda e: QMessageBox.critical(self, "Fehler", e))
        worker.start()
        
    def edit_profile(self, pid):
        worker = APIWorker("api_profiles.php", "GET", params={"id": pid},
                          user_id=self.user_id, session=self.session)
        worker.finished.connect(lambda r: self.open_editor(r))
        worker.start()
        
    def open_editor(self, profile):
        if isinstance(profile, dict):
            dlg = ProfileEditDialog(self.user_id, self.session, profile, self.groups)
            if dlg.exec():
                self.load_profiles()
                
    def new_profile(self):
        dlg = ProfileEditDialog(self.user_id, self.session, groups=self.groups)
        if dlg.exec():
            self.load_profiles()
            
    def delete_profile(self, pid):
        if QMessageBox.question(self, "Löschen", "Profil wirklich löschen?") == QMessageBox.StandardButton.Yes:
            worker = APIWorker("api_profiles.php", "DELETE", params={"id": pid},
                              user_id=self.user_id, session=self.session)
            worker.finished.connect(lambda r: self.load_profiles())
            worker.start()
            
    def manage_groups(self):
        dlg = GroupManagerDialog(self.user_id, self.session, self.groups)
        if dlg.exec():
            self.load_groups()
            self.load_profiles()
            
    def logout(self):
        APIWorker("api_logout.php", "POST", user_id=self.user_id, session=self.session).start()
        self.close()


class GroupManagerDialog(QDialog):
    def __init__(self, user_id, session, groups, parent=None):
        super().__init__(parent)
        self.user_id, self.session = user_id, session
        self.groups = groups
        self.init_ui()
        
    def init_ui(self):
        self.setWindowTitle("Gruppenverwaltung")
        self.setMinimumSize(400, 300)
        
        layout = QVBoxLayout()
        
        self.group_list = QTableWidget()
        self.group_list.setColumnCount(2)
        self.group_list.setHorizontalHeaderLabels(["Gruppenname", "Aktion"])
        self.group_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
        
        self.refresh_list()
        layout.addWidget(self.group_list)
        
        add_layout = QHBoxLayout()
        self.new_name = QLineEdit()
        self.new_name.setPlaceholderText("Neuer Gruppenname")
        add_btn = QPushButton("Hinzufügen")
        add_btn.clicked.connect(self.add_group)
        add_layout.addWidget(self.new_name)
        add_layout.addWidget(add_btn)
        layout.addLayout(add_layout)
        
        close_btn = QPushButton("Schließen")
        close_btn.clicked.connect(self.accept)
        layout.addWidget(close_btn)
        
        self.setLayout(layout)
        
    def refresh_list(self):
        self.group_list.setRowCount(len(self.groups))
        for i, g in enumerate(self.groups):
            self.group_list.setItem(i, 0, QTableWidgetItem(g.get('name', '')))
            del_btn = QPushButton("Löschen")
            del_btn.clicked.connect(lambda _, gid=g.get('gruppeID'): self.delete_group(gid))
            self.group_list.setCellWidget(i, 1, del_btn)
            
    def add_group(self):
        name = self.new_name.text().strip()
        if not name:
            QMessageBox.warning(self, "Fehler", "Bitte Namen eingeben")
            return
        worker = APIWorker("api_groups.php", "POST", data={"name": name},
                          user_id=self.user_id, session=self.session)
        worker.finished.connect(self.on_added)
        worker.start()
        
    def on_added(self, resp):
        if resp.get('success'):
            self.new_name.clear()
            self.load_groups()
            
    def delete_group(self, group_id):
        if QMessageBox.question(self, "Löschen", "Gruppe wirklich löschen?") == QMessageBox.StandardButton.Yes:
            worker = APIWorker("api_groups.php", "DELETE", params={"id": group_id},
                              user_id=self.user_id, session=self.session)
            worker.finished.connect(lambda r: self.load_groups())
            worker.start()
            
    def load_groups(self):
        worker = APIWorker("api_groups.php", "GET", user_id=self.user_id, session=self.session)
        worker.finished.connect(self.on_loaded)
        worker.start()
        
    def on_loaded(self, resp):
        if isinstance(resp, list):
            self.groups = resp
            self.refresh_list()


def main():
    print("Starte DÜSK Client...")
    app = QApplication(sys.argv)
    login = LoginDialog()
    if login.exec() == QDialog.DialogCode.Accepted:
        print(f"Login erfolgreich - UserID: {login.user_id}")
        win = MainWindow(login.user_id, login.session)
        win.show()
        sys.exit(app.exec())
    else:
        print("Login abgebrochen")
        sys.exit(0)

if __name__ == "__main__":
    main()