From b7f45066f052b6bd1e53aacb165aeb3eeb6940b7 Mon Sep 17 00:00:00 2001 From: foefl Date: Thu, 25 Jun 2026 10:41:13 +0200 Subject: [PATCH] draft for company profile --- src/wce_crm/backend/backend.py | 21 +- src/wce_crm/form_defs.py | 3 +- src/wce_crm/gui.py | 405 ++++++++++++++++++++++++++++++++- 3 files changed, 417 insertions(+), 12 deletions(-) diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index fff4103..3ccadf3 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -221,6 +221,8 @@ def front_get_company_list() -> list[FrontpageCompany]: stmt = sql.select( db.grunderfassung.c.erfassung_id, db.grunderfassung.c.Partnersuche__un_suche, + db.grunderfassung.c.Stammdaten__vorname, + db.grunderfassung.c.Stammdaten__name, db.grunderfassung.c.Metadaten_aktualisierung, ).order_by(db.grunderfassung.c.Metadaten_aktualisierung.desc()) @@ -229,20 +231,25 @@ def front_get_company_list() -> list[FrontpageCompany]: front_page_companies: list[FrontpageCompany] = [] - for entry in res: - erfassung_id = entry[0] - ma_id = entry[1] - datetime_akt = cast(datetime.datetime, entry[2]) + for entry in res.mappings(): + erfassung_id = entry["erfassung_id"] + ma_id = entry["Partnersuche__un_suche"] + datetime_akt = cast(datetime.datetime, entry["Metadaten_aktualisierung"]) datetime_akt = datetime_akt.astimezone() - name: str = "PLATZHALTER INDIVIDUAL" - is_company: bool = False + name: str + is_company: bool if ma_id is not None: comp_info = initrec_comp_search_get_info(ma_id) name = comp_info["ma_unternehmensname"] is_company = True + else: + surname: str = entry["Stammdaten__name"] + first_name: str = entry["Stammdaten__vorname"] + names_to_join = [n for n in (first_name, surname) if n] + name = " ".join(names_to_join) + is_company = False - # front_page_companies.append(FrontpageCompany(erfassung_id, ma_id, name, datetime_akt)) front_page_companies.append( FrontpageCompany( erfassung_id=erfassung_id, diff --git a/src/wce_crm/form_defs.py b/src/wce_crm/form_defs.py index 365827c..3986bbc 100644 --- a/src/wce_crm/form_defs.py +++ b/src/wce_crm/form_defs.py @@ -323,12 +323,13 @@ INITREC_COMP_SEARCH_HEAD = [ ] INITREC_CONTACT_PERSON = [ + # TODO check removal FormField( "Name Unternehmen/Netzwerkpartner (vorausgefüllt von Suche)", FormFieldType.TEXT, key="KP_name_partner", required=False, - placeholder="Text wird nach gewähltem Unternehmen angezeigt", + placeholder="[TODO: Logik in Verbindung mit Unternehmen unklar]", readonly=True, ), FormField( diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index b5ebef9..2453b64 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -29,6 +29,7 @@ from PySide6.QtCore import ( ) from PySide6.QtGui import QAction from PySide6.QtWidgets import ( + QAbstractItemView, QApplication, QComboBox, QCompleter, @@ -38,6 +39,7 @@ from PySide6.QtWidgets import ( QGridLayout, QGroupBox, QHBoxLayout, + QHeaderView, QLabel, QLayout, QLineEdit, @@ -48,6 +50,8 @@ from PySide6.QtWidgets import ( QScrollArea, QSizePolicy, QStackedWidget, + QTableWidget, + QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -2045,17 +2049,22 @@ class ClickableCell(QFrame): class HeaderCell(QLabel): - def __init__(self, text): + def __init__( + self, + text: str, + font_size: int = 12, + ): super().__init__(text) self.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.setStyleSheet(""" - HeaderCell { + self.setStyleSheet(f""" + HeaderCell {{ background-color: #e2e8f0; color: #475569; font-weight: bold; padding: 10px; border-radius: 6px; - } + font-size: {font_size}px; + }} """) @@ -2139,6 +2148,7 @@ class Page_InitRecCompany(QWidget): back_main_requested = Signal() # back to main page back_requested = Signal() # back button update_triggered = Signal() # form saved (data changed for front page) + company_profile_requested = Signal() def __init__(self): super().__init__() @@ -2161,6 +2171,12 @@ class Page_InitRecCompany(QWidget): header_layout = QVBoxLayout(header_container) header_layout.setContentsMargins(0, 0, 0, 10) + comp_profile_btn = QPushButton("Unternehmensprofil") + comp_profile_btn.clicked.connect(lambda: self.company_profile_requested.emit()) + comp_profile_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + comp_profile_btn.setMinimumWidth(250) + # comp_profile_btn.setMaximumWidth(200) + back_btn_main = QPushButton("← Zurück zur Übersicht") back_btn_main.clicked.connect(lambda: self.back_main_requested.emit()) back_btn_main.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) @@ -2176,6 +2192,8 @@ class Page_InitRecCompany(QWidget): title.setStyleSheet("font-size: 20px; font-weight: bold;") header_layout.setSpacing(5) + header_layout.addWidget(comp_profile_btn) + header_layout.addSpacing(15) header_layout.addWidget(back_btn_step) header_layout.addWidget(back_btn_main) header_layout.addWidget(title) @@ -2393,6 +2411,376 @@ class Page_InitRecPerson(QWidget): self.auto_form.reset_form() +class Page_CompanyProfile(QWidget): + back_main_requested = Signal() # back to main page + back_requested = Signal() # back button + # TODO update should trigger something else + update_triggered = Signal() # form saved (data changed for front page) + + def __init__(self): + super().__init__() + # Hauptlayout der Seite + outer_layout = QHBoxLayout(self) + vert_layout = QVBoxLayout() + # main_layout.setContentsMargins(0, 0, 0, 0) + outer_layout.addStretch(1) + outer_layout.addLayout(vert_layout, stretch=100) + # outer_layout.addWidget(scroll_area, stretch=100) + outer_layout.addStretch(1) + # Optional: Damit der Container oben am Rand klebt + outer_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + + # --- HEADER --- + header_container = QWidget() + header_container.setMinimumWidth(700) + header_container.setMaximumWidth(1200) + header_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + header_layout = QVBoxLayout(header_container) + header_layout.setContentsMargins(0, 0, 0, 10) + + back_btn_main = QPushButton("← Zurück zur Übersicht") + back_btn_main.clicked.connect(lambda: self.back_main_requested.emit()) + back_btn_main.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + back_btn_main.setMinimumWidth(200) + back_btn_main.setMaximumWidth(200) + back_btn_step = QPushButton("← Zurück") + back_btn_step.clicked.connect(lambda: self.back_requested.emit()) + back_btn_step.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + back_btn_step.setMinimumWidth(200) + back_btn_step.setMaximumWidth(200) + + title = QLabel("Unternehmensprofil") + title.setStyleSheet("font-size: 20px; font-weight: bold;") + + header_layout.setSpacing(5) + + header_layout.addSpacing(15) + header_layout.addWidget(back_btn_step) + header_layout.addWidget(back_btn_main) + header_layout.addWidget(title) + vert_layout.addWidget(header_container) + + # --- MAIN CONTENT --- + container = QWidget() + # SCROLL AREA + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setMinimumWidth(700) + scroll_area.setMaximumWidth(1200) + scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + # optional: remove frame to look more modern + scroll_area.setFrameShape(QFrame.Shape.NoFrame) + scroll_area.setWidget(container) + + vert_layout.addWidget(scroll_area) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + + # --- MAIN LAYOUT --- + if DEBUG: + separator1 = QFrame() + separator1.setFrameShape(QFrame.Shape.HLine) + separator1.setFrameShadow(QFrame.Shadow.Sunken) + container_layout.addWidget(separator1) + + layout_change_title = QHBoxLayout() + self.change_title_text = QLineEdit(placeholderText="Unternehmensname eingeben...") + layout_change_title.addWidget(self.change_title_text) + change_title_btn = QPushButton("ändern") + layout_change_title.addWidget(change_title_btn) + change_title_btn.clicked.connect(self.set_company_name) + container_layout.addLayout(layout_change_title) + + separator2 = QFrame() + separator2.setFrameShape(QFrame.Shape.HLine) + separator2.setFrameShadow(QFrame.Shadow.Sunken) + container_layout.addWidget(separator2) + + container_layout.addSpacing(30) + + self.title_company_name = HeaderCell("Unternehmen: n.v.", font_size=16) + container_layout.addWidget(self.title_company_name) + + container_layout.addSpacing(20) + + # separation in left and right + main_hor_layout = QHBoxLayout() + main_hor_layout.setSpacing(20) + # main_hor_layout.setContentsMargins(20, 20, 20, 20) + + # left column + left_frame = QFrame() + left_frame.setObjectName("Segment") + left_frame.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum) + left_layout = QVBoxLayout(left_frame) + main_hor_layout.addWidget(left_frame, stretch=1, alignment=Qt.AlignmentFlag.AlignTop) + master_data_label = HeaderCell("Stammdaten & Vermerke") + master_data_label.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + left_layout.addWidget(master_data_label) + # self.table_master_data = MasterDataTable() + self.table_master_data = MasterDataFormWidget() + left_layout.addWidget(self.table_master_data) + left_layout.addWidget(QLabel("PLATZHALTER Vermerke: Welche Notizen?")) + + # right column + right_column_layout = QVBoxLayout() + right_column_layout.setSpacing(20) + main_hor_layout.addLayout(right_column_layout, stretch=1) + + # top right area + top_right_frame = QFrame() + top_right_frame.setObjectName("Segment") + top_right_frame.setSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum + ) + top_right_layout = QVBoxLayout(top_right_frame) + common_consultancy_label = HeaderCell("Pauschalberatung") + common_consultancy_label.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + top_right_layout.addWidget(common_consultancy_label) + top_right_new_btn = QPushButton("+ Hinzufügen") + top_right_new_btn.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + top_right_new_btn.setMinimumHeight(30) + top_right_layout.addWidget(top_right_new_btn) + + # bottom right area + bottom_right_frame = QFrame() + bottom_right_frame.setObjectName("Segment") + bottom_right_frame.setSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum + ) + bottom_right_layout = QVBoxLayout(bottom_right_frame) + individual_consultancy_label = HeaderCell("Individualberatung") + individual_consultancy_label.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + bottom_right_layout.addWidget(individual_consultancy_label) + bottom_right_new_btn = QPushButton("+ Hinzufügen") + bottom_right_new_btn.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + bottom_right_new_btn.setMinimumHeight(30) + bottom_right_layout.addWidget(bottom_right_new_btn) + + right_column_layout.addWidget(top_right_frame) + right_column_layout.addWidget(bottom_right_frame) + right_column_layout.addStretch(1) + + container_layout.addLayout(main_hor_layout) + container_layout.addStretch() + + self.setStyleSheet(""" + QFrame#Segment { + background-color: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 12px; + padding: 3px; + } + """) + + # self.setStyleSheet(""" + # /* Hintergrund des Hauptfensters */ + # QWidget { + # background-color: #f1f5f9; + # } + + # /* Styling für unsere 3 Segmente */ + # QFrame#Segment { + # background-color: #ffffff; + # border: 1px solid #e2e8f0; + # border-radius: 12px; + # padding: 15px; + # } + + # /* Text-Styling */ + # QLabel { + # color: #334155; + # font-size: 16px; + # font-weight: bold; + # } + # """) + + def set_company_name(self, name: str = "") -> None: + if not name: + name = self.change_title_text.text() + if not name: + name = "n.v." + + txt = f"Unternehmen: {name}" + self.title_company_name.setText(txt) + + +class MasterDataTable(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Stammdaten Anzeige") + self.resize(450, 400) + + layout = QVBoxLayout(self) + layout.setContentsMargins(15, 15, 15, 15) + + # 1. Tabelle erstellen (0 Zeilen initial, 2 Spalten für Merkmal & Wert) + self.table = QTableWidget(0, 2) + + # --- DER TRICK FÜR DEN CLEANEN LOOK --- + self.table.horizontalHeader().hide() # Spalten-Header ausblenden + self.table.verticalHeader().hide() # Zeilen-Header (Nummerierung) ausblenden + self.table.setShowGrid(False) # Gitternetzlinien deaktivieren + + # Verhalten der Tabelle anpassen + self.table.setSelectionMode( + QAbstractItemView.SelectionMode.NoSelection + ) # Nicht auswählbar + self.table.setEditTriggers( + QAbstractItemView.EditTrigger.NoEditTriggers + ) # Schreibgeschützt + + # Spaltenbreite steuern: + # Linke Spalte passt sich dem Text an, rechte Spalte füllt den Rest des Fensters + self.table.horizontalHeader().setSectionResizeMode( + 0, QHeaderView.ResizeMode.ResizeToContents + ) + self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) + + layout.addWidget(self.table) + + # Daten laden + self.load_data() + self.style_table() + + def load_data(self): + + daten = [ + ("Anwendungsname", "MyQtApp Pro"), + ("Version", "2.4.1-beta"), + ("Datenbank-Pfad", "/var/lib/sqlite/data.db"), + ("Letzter Login", "2026-06-25 09:30"), + ("Benutzer-ID", "USR-88291"), + ("Verschlüsselung", "AES-256-GCM"), + ] + + # Tabelle befüllen + self.table.setRowCount(len(daten)) + for zeile_index, (merkmal, wert) in enumerate(daten): + # Merkmal-Zelle (Links) + item_merkmal = QTableWidgetItem(merkmal) + item_merkmal.setTextAlignment( + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter + ) + self.table.setItem(zeile_index, 0, item_merkmal) + + # Wert-Zelle (Rechts) + item_wert = QTableWidgetItem(wert) + item_wert.setTextAlignment( + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter + ) + self.table.setItem(zeile_index, 1, item_wert) + + def style_table(self): + self.setStyleSheet(""" + QWidget { + background-color: transparent; + } + QTableWidget { + border: none; + background-color: transparent; + } + QTableWidget::item { + padding: 8px; + color: #334155; + font-size: 14px; + } + QTableWidget::item:first-column { + font-weight: bold; + color: #64748b; + padding-right: 20px; /* Mehr Abstand zum Wert */ + } + """) + + +class MasterDataFormWidget(QWidget): + def __init__(self): + super().__init__() + + # main layout + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + + # form in frame + segment_frame = QFrame() + segment_frame.setObjectName("Segment") + # form layout + form_layout = QFormLayout(segment_frame) + form_layout.setSpacing(15) # Vertikaler Abstand zwischen den Zeilen + form_layout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) # Merkmale linksbündig + + # load data + self.load_data(form_layout) + + main_layout.addWidget(segment_frame)n + self.style_ui() + + def load_data(self, form_layout): + daten = [ + ("Anwendungsname", "MyQtApp Pro"), + ("Version", "2.4.1-beta"), + ("Datenbank-Pfad", "/var/lib/sqlite/data.db"), + ("Letzter Login", "2026-06-25 09:30"), + ("Benutzer-ID", "USR-88291"), + ("Verschlüsselung", "AES-256-GCM"), + ] + simulated_retrieved_data = {x[0]: x[1] for x in daten} + + for merkmal, wert in simulated_retrieved_data.items(): + lbl_merkmal = QLabel(f"{merkmal}") + lbl_merkmal.setObjectName("Merkmal") + + lbl_wert = QLabel(str(wert)) + lbl_wert.setObjectName("Wert") + + lbl_wert.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + + form_layout.addRow(lbl_merkmal, lbl_wert) + + def style_ui(self): + self.setStyleSheet(""" + /* Hintergrund des Fensters */ + QWidget { + background-color: transparent; + } + + /* Unser optisch abgetrenntes Segment */ + QFrame#Segment { + background-color: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 12px; + padding: 5px; + } + + /* Textgröße für alle Elemente im Formular hochsetzen */ + QLabel { + font-size: 12px; + } + + /* Linke Spalte (Merkmale): Grau und Fett */ + QLabel#Merkmal { + font-weight: bold; + color: #64748b; + } + + /* Rechte Spalte (Werte): Dunkel und gut lesbar */ + QLabel#Wert { + color: #0f172a; + } + """) + + def clear_layout( layout: QLayout | None, ) -> None: @@ -2441,6 +2829,7 @@ class MainWindow(QMainWindow): self.initrec_company.back_main_requested.connect(self.show_main_page) self.initrec_company.back_requested.connect(self.show_new_entry_select) self.initrec_company.update_triggered.connect(self.update_grid) + self.initrec_company.company_profile_requested.connect(self.show_page_company_profile) self.stack.addWidget(self.initrec_company) # SITE: 'Grunderfassung Person' self.initrec_person = Page_InitRecPerson() @@ -2448,6 +2837,11 @@ class MainWindow(QMainWindow): self.initrec_person.back_requested.connect(self.show_new_entry_select) self.initrec_person.update_triggered.connect(self.update_grid) self.stack.addWidget(self.initrec_person) + # SITE: 'Unternehmensprofil' + self.company_profile = Page_CompanyProfile() + self.company_profile.back_main_requested.connect(self.show_main_page) + self.company_profile.back_requested.connect(self.show_page_initrec_company) + self.stack.addWidget(self.company_profile) def setup_main_page(self): # QMainWindow defines frame --> container widget needed for the middle @@ -2613,6 +3007,9 @@ class MainWindow(QMainWindow): self.initrec_person.reset_form() self.stack.setCurrentWidget(self.initrec_person) + def show_page_company_profile(self) -> None: + self.stack.setCurrentWidget(self.company_profile) + # --- MENÜ LOGIK --- def create_menu(self): menu_bar = self.menuBar()