From 45e236ff1bfba23b82449560cd9a4dc86473667f Mon Sep 17 00:00:00 2001 From: UserIsMH Date: Sun, 22 Jun 2025 20:49:31 +0200 Subject: [PATCH] Lead Management - Zwischenstand --- CLAUDE.md | 11 ++ OPERATIONS_GUIDE.md | 7 +- SYSTEM_DOCUMENTATION.md | 8 + v2_adminpanel/leads/routes.py | 158 ++++++++++++++++-- .../templates/leads/lead_management.html | 141 ++++++++++++++++ v2_adminpanel/templates/base.html | 6 + .../templates/customers_licenses.html | 3 - 7 files changed, 311 insertions(+), 23 deletions(-) create mode 100644 v2_adminpanel/leads/templates/leads/lead_management.html diff --git a/CLAUDE.md b/CLAUDE.md index 725bab5..b1a1c0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -140,4 +140,15 @@ Public access: Port 80 via Nginx - Don't abstract code that's only used once - Implement exactly what's requested, nothing more +## Recent Updates + +### June 22, 2025 - 20:26 +- Added Lead Management to main navigation (above Ressourcen Pool) +- Created Lead Management dashboard with: + - Overview statistics (institutions, contacts, user attribution) + - Recent activity feed showing who added/edited what + - Quick actions (add institution, view all, export) + - Shared information view between users rac00n and w@rh@mm3r +- Route: `/leads/management` accessible via navbar "Lead Management" + ## Last Updated: June 22, 2025 \ No newline at end of file diff --git a/OPERATIONS_GUIDE.md b/OPERATIONS_GUIDE.md index af2baf6..5c11f05 100644 --- a/OPERATIONS_GUIDE.md +++ b/OPERATIONS_GUIDE.md @@ -133,13 +133,16 @@ Das Admin Panel bietet alle benötigten Überwachungsfunktionen: ## Features Overview ### Lead Management System -- Accessible via "Leads" button on Customers & Licenses page -- Manage potential customers and contacts +- **UPDATE 22.06.2025**: Jetzt direkt über Navbar "Lead Management" erreichbar +- Lead Management Dashboard unter `/leads/management` +- Gemeinsame Kontaktdatenbank zwischen rac00n und w@rh@mm3r - Features: + - Dashboard mit Statistiken und Aktivitätsfeed - Institution management - Contact persons with multiple phones/emails - Versioned notes system - Full audit trail + - Benutzer-Attribution (wer hat was hinzugefügt) ### Resource Pool Management - Domain allocation system diff --git a/SYSTEM_DOCUMENTATION.md b/SYSTEM_DOCUMENTATION.md index bfacb62..c5e74d0 100644 --- a/SYSTEM_DOCUMENTATION.md +++ b/SYSTEM_DOCUMENTATION.md @@ -137,6 +137,14 @@ Key feature: Monthly partitioned `license_heartbeats` table. ### Status **Vollständig implementiert** als Teil des Admin Panels unter `/leads/` +### Update June 22, 2025 - 20:26 +- **Neuer Navbar-Eintrag**: "Lead Management" über "Ressourcen Pool" +- **Lead Management Dashboard** unter `/leads/management` mit: + - Übersicht Statistiken (Institutionen, Kontakte, Benutzer-Attribution) + - Aktivitätsfeed zeigt wer was hinzugefügt/bearbeitet hat + - Schnellaktionen (Institution hinzufügen, alle anzeigen, exportieren) + - Geteilte Informationsansicht zwischen rac00n und w@rh@mm3r + ### Architecture - **Modular Architecture**: Clean separation of concerns - **Service Layer Pattern**: Business logic in `leads/services.py` diff --git a/v2_adminpanel/leads/routes.py b/v2_adminpanel/leads/routes.py index bed98c3..7a8062f 100644 --- a/v2_adminpanel/leads/routes.py +++ b/v2_adminpanel/leads/routes.py @@ -9,22 +9,144 @@ from db import get_db_connection from uuid import UUID import traceback -# Initialize service -lead_repository = LeadRepository(get_db_connection) -lead_service = LeadService(lead_repository) +# Service will be initialized per request +lead_repository = None +lead_service = None + +def get_lead_service(): + """Get or create lead service instance""" + global lead_repository, lead_service + if lead_service is None: + lead_repository = LeadRepository(get_db_connection) # Pass the function, not call it + lead_service = LeadService(lead_repository) + return lead_service # HTML Routes +@leads_bp.route('/management') +@login_required +def lead_management(): + """Lead Management Dashboard""" + try: + # Get current user + current_user = flask_session.get('username', 'System') + other_user = 'w@rh@mm3r' if current_user == 'rac00n' else 'rac00n' + + # Initialize defaults + total_institutions = 0 + total_contacts = 0 + my_entries = 0 + other_entries = 0 + activities = [] + + with get_db_connection() as conn: + cur = conn.cursor() + + # Get statistics + cur.execute("SELECT COUNT(*) FROM lead_institutions") + result = cur.fetchone() + if result: + total_institutions = result[0] + + cur.execute("SELECT COUNT(*) FROM lead_contacts") + result = cur.fetchone() + if result: + total_contacts = result[0] + + # Count entries by current user + cur.execute(""" + SELECT COUNT(*) FROM audit_log + WHERE username = %s + AND entity_type IN ('lead_institution', 'lead_contact') + AND action = 'CREATE' + """, (current_user,)) + result = cur.fetchone() + if result: + my_entries = result[0] + + # Count entries by other user + cur.execute(""" + SELECT COUNT(*) FROM audit_log + WHERE username = %s + AND entity_type IN ('lead_institution', 'lead_contact') + AND action = 'CREATE' + """, (other_user,)) + result = cur.fetchone() + if result: + other_entries = result[0] + + # Get recent activities + cur.execute(""" + SELECT timestamp, username, action, entity_type, additional_info + FROM audit_log + WHERE entity_type IN ('lead_institution', 'lead_contact') + ORDER BY timestamp DESC + LIMIT 10 + """) + rows = cur.fetchall() + if rows: + for row in rows: + activities.append({ + 'timestamp': row[0], + 'username': row[1], + 'action': row[2], + 'entity_type': row[3].replace('lead_', ''), + 'additional_info': row[4] + }) + + cur.close() + + return render_template('leads/lead_management.html', + total_institutions=total_institutions, + total_contacts=total_contacts, + my_entries=my_entries, + other_entries=other_entries, + other_user=other_user, + current_user=current_user, + recent_activities=activities) + except Exception as e: + import traceback + print(f"Error in lead_management: {str(e)}") + print(traceback.format_exc()) + flash(f'Fehler beim Laden des Dashboards: {str(e)}', 'error') + current_user = flask_session.get('username', 'System') + return render_template('leads/lead_management.html', + total_institutions=0, + total_contacts=0, + my_entries=0, + other_entries=0, + other_user='', + current_user=current_user, + recent_activities=[]) + @leads_bp.route('/') @login_required def institutions(): """List all institutions""" try: - institutions = lead_service.list_institutions() + institutions = get_lead_service().list_institutions() return render_template('leads/institutions.html', institutions=institutions) except Exception as e: flash(f'Fehler beim Laden der Institutionen: {str(e)}', 'error') return render_template('leads/institutions.html', institutions=[]) +@leads_bp.route('/institution/add', methods=['POST']) +@login_required +def add_institution(): + """Add new institution from form""" + try: + name = request.form.get('name') + if not name: + flash('Name ist erforderlich', 'error') + return redirect(url_for('leads.lead_management')) + + # Add institution + get_lead_service().create_institution(name, flask_session.get('username', 'System')) + flash(f'Institution "{name}" wurde erfolgreich hinzugefügt', 'success') + except Exception as e: + flash(f'Fehler beim Hinzufügen der Institution: {str(e)}', 'error') + + return redirect(url_for('leads.lead_management')) + @leads_bp.route('/institution/') @login_required def institution_detail(institution_id): @@ -35,7 +157,7 @@ def institution_detail(institution_id): flash('Institution nicht gefunden', 'error') return redirect(url_for('leads.institutions')) - contacts = lead_service.list_contacts_by_institution(institution_id) + contacts = get_lead_service().list_contacts_by_institution(institution_id) return render_template('leads/institution_detail.html', institution=institution, contacts=contacts) @@ -48,7 +170,7 @@ def institution_detail(institution_id): def contact_detail(contact_id): """Show contact details with notes""" try: - contact = lead_service.get_contact_details(contact_id) + contact = get_lead_service().get_contact_details(contact_id) return render_template('leads/contact_detail.html', contact=contact) except Exception as e: flash(f'Fehler beim Laden des Kontakts: {str(e)}', 'error') @@ -59,7 +181,7 @@ def contact_detail(contact_id): def all_contacts(): """Show all contacts across all institutions""" try: - contacts = lead_service.list_all_contacts() + contacts = get_lead_service().list_all_contacts() return render_template('leads/all_contacts.html', contacts=contacts) except Exception as e: flash(f'Fehler beim Laden der Kontakte: {str(e)}', 'error') @@ -72,7 +194,7 @@ def create_institution(): """Create new institution""" try: data = request.get_json() - institution = lead_service.create_institution( + institution = get_lead_service().create_institution( data['name'], flask_session.get('username') ) @@ -88,7 +210,7 @@ def update_institution(institution_id): """Update institution""" try: data = request.get_json() - institution = lead_service.update_institution( + institution = get_lead_service().update_institution( institution_id, data['name'], flask_session.get('username') @@ -105,7 +227,7 @@ def create_contact(): """Create new contact""" try: data = request.get_json() - contact = lead_service.create_contact(data, flask_session.get('username')) + contact = get_lead_service().create_contact(data, flask_session.get('username')) return jsonify({'success': True, 'contact': contact}) except ValueError as e: return jsonify({'success': False, 'error': str(e)}), 400 @@ -118,7 +240,7 @@ def update_contact(contact_id): """Update contact""" try: data = request.get_json() - contact = lead_service.update_contact( + contact = get_lead_service().update_contact( contact_id, data, flask_session.get('username') @@ -135,7 +257,7 @@ def add_phone(contact_id): """Add phone to contact""" try: data = request.get_json() - detail = lead_service.add_phone( + detail = get_lead_service().add_phone( contact_id, data['phone_number'], data.get('phone_type'), @@ -153,7 +275,7 @@ def add_email(contact_id): """Add email to contact""" try: data = request.get_json() - detail = lead_service.add_email( + detail = get_lead_service().add_email( contact_id, data['email'], data.get('email_type'), @@ -171,7 +293,7 @@ def update_detail(detail_id): """Update contact detail (phone/email)""" try: data = request.get_json() - detail = lead_service.update_contact_detail( + detail = get_lead_service().update_contact_detail( detail_id, data['detail_value'], data.get('detail_label'), @@ -188,7 +310,7 @@ def update_detail(detail_id): def delete_detail(detail_id): """Delete contact detail""" try: - success = lead_service.delete_contact_detail( + success = get_lead_service().delete_contact_detail( detail_id, flask_session.get('username') ) @@ -202,7 +324,7 @@ def add_note(contact_id): """Add note to contact""" try: data = request.get_json() - note = lead_service.add_note( + note = get_lead_service().add_note( contact_id, data['note_text'], flask_session.get('username') @@ -219,7 +341,7 @@ def update_note(note_id): """Update note""" try: data = request.get_json() - note = lead_service.update_note( + note = get_lead_service().update_note( note_id, data['note_text'], flask_session.get('username') @@ -235,7 +357,7 @@ def update_note(note_id): def delete_note(note_id): """Delete note""" try: - success = lead_service.delete_note( + success = get_lead_service().delete_note( note_id, flask_session.get('username') ) diff --git a/v2_adminpanel/leads/templates/leads/lead_management.html b/v2_adminpanel/leads/templates/leads/lead_management.html new file mode 100644 index 0000000..f5c1086 --- /dev/null +++ b/v2_adminpanel/leads/templates/leads/lead_management.html @@ -0,0 +1,141 @@ +{% extends "base.html" %} + +{% block title %}Lead Management{% endblock %} + +{% block content %} +
+
+

📊 Lead Management

+
+ + +
+
+
+
+

{{ total_institutions }}

+ Institutionen +
+
+
+
+
+
+

{{ total_contacts }}

+ Kontakte +
+
+
+
+ + +
+
+
Schnellaktionen
+
+ + Institutionen anzeigen + + + + Exportieren + +
+
+
+ + +
+
+
Letzte Aktivitäten
+
+
+
+ + + + + + + + + + + {% for activity in recent_activities %} + + + + + + + {% endfor %} + +
ZeitpunktBenutzerAktionDetails
{{ activity.timestamp.strftime('%d.%m.%Y %H:%M') }} + + {{ activity.username }} + + + {% if activity.action == 'CREATE' %} + ➕ Erstellt + {% elif activity.action == 'UPDATE' %} + ✏️ Bearbeitet + {% elif activity.action == 'DELETE' %} + 🗑️ Gelöscht + {% endif %} + + {{ activity.entity_type|title }} + {% if activity.additional_info %} + - {{ activity.additional_info }} + {% endif %} +
+ {% if not recent_activities %} +

Noch keine Aktivitäten vorhanden.

+ {% endif %} +
+
+
+ + +
+
+
Suche
+
+
+
+
+ + +
+
+
+
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/v2_adminpanel/templates/base.html b/v2_adminpanel/templates/base.html index be63a1b..3a77ef3 100644 --- a/v2_adminpanel/templates/base.html +++ b/v2_adminpanel/templates/base.html @@ -401,6 +401,12 @@ +