From fc6537439f0b4a63683f6d11afa2173830b65b69 Mon Sep 17 00:00:00 2001 From: elpatron Date: Mon, 17 Mar 2025 20:13:23 +0100 Subject: [PATCH] Erste Version mit spezifischen Suchfeldern und Teilen-Funktion --- README.md | 50 ++++++++------ app.py | 55 ++++++++++++---- docker-compose.yml | 14 ++++ static/favicon.ico | Bin 0 -> 3262 bytes templates/index.html | 152 +++++++++++++++++++++++++++++++++++++------ 5 files changed, 221 insertions(+), 50 deletions(-) create mode 100644 docker-compose.yml create mode 100644 static/favicon.ico diff --git a/README.md b/README.md index 0597bdf..37654c3 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,18 @@ Eine Flask-basierte Webanwendung zur Suche in Kundendaten aus einer CSV-Datei. ## Features - Live-Suche während der Eingabe -- Suche nach: +- Spezifische Suchfelder für: - Kundennummer - Name (Vor- und Nachname) - Fachrichtung - Ort +- Allgemeine Suche über alle Felder - Klickbare Links für: - Telefonnummern (tel:) - E-Mail-Adressen (mailto:) - Adressen (Google Maps) - Kundennummern (KKBefe-System) +- Teilen-Funktion für einzelne Suchergebnisse - Responsive Design mit Bootstrap - Docker-Container-Unterstützung @@ -37,6 +39,7 @@ medi-customers/ ├── spezexpo.csv # Kundendaten ├── requirements.txt # Python-Abhängigkeiten ├── Dockerfile # Docker-Konfiguration +├── docker-compose.yml # Docker Compose Konfiguration └── .dockerignore # Docker-Ignore-Datei ``` @@ -76,14 +79,11 @@ Die Anwendung erwartet eine CSV-Datei (`spezexpo.csv`) mit folgenden Spalten: ### Docker-Container 1. Docker installieren -2. Container bauen: +2. Container mit Docker Compose starten: ```bash - docker build -t medi-customers . - ``` -3. Container starten: - ```bash - docker run -d -p 5000:5000 --name medi-customers medi-customers + docker-compose up --build ``` + Die Anwendung ist dann unter `http://localhost:5001` erreichbar. ## API-Endpunkte @@ -91,16 +91,22 @@ Die Anwendung erwartet eine CSV-Datei (`spezexpo.csv`) mit folgenden Spalten: - Rendert die Hauptseite ### GET /search -- Sucht nach Kunden basierend auf dem Query-Parameter -- Parameter: `q` (Suchbegriff) +- Sucht nach Kunden basierend auf verschiedenen Parametern +- Parameter: + - `name`: Suche nach Vor- oder Nachname + - `ort`: Suche nach Ort + - `kundennummer`: Suche nach Kundennummer + - `fachrichtung`: Suche nach Fachrichtung + - `q`: Allgemeine Suche über alle Felder - Returns: JSON-Array mit gefundenen Kunden ## Frontend-Funktionen ### Suchfunktion - Live-Suche mit 300ms Debounce -- Minimale Suchlänge: 2 Zeichen -- Suche wird bei Enter-Taste sofort ausgeführt +- Spezifische Suchfelder für präzise Suche +- Allgemeine Suche für breite Suche +- Kombinierbare Suchkriterien ### Link-Generierung - `createPhoneLink()`: Erstellt tel:-Links mit führender 0 @@ -108,6 +114,11 @@ Die Anwendung erwartet eine CSV-Datei (`spezexpo.csv`) mit folgenden Spalten: - `createAddressLink()`: Erstellt Google Maps-Links - `createCustomerLink()`: Erstellt KKBefe-System-Links +### Teilen-Funktion +- Individueller Teilen-Button für jedes Suchergebnis +- Kopiert einen direkten Link zum spezifischen Kunden +- Visuelles Feedback beim Kopieren + ## Fehlerbehandlung - Logging für Backend-Fehler @@ -132,25 +143,24 @@ python app.py ### Container-Verwaltung ```bash # Container stoppen -docker stop medi-customers +docker-compose down # Container starten -docker start medi-customers +docker-compose up + +# Container im Hintergrund starten +docker-compose up -d # Container-Logs anzeigen -docker logs medi-customers - -# Container entfernen -docker rm medi-customers +docker-compose logs -f ``` ### Datenaktualisierung 1. CSV-Datei aktualisieren 2. Container neu bauen und starten: ```bash - docker stop medi-customers - docker build -t medi-customers . - docker run -d -p 5000:5000 --name medi-customers medi-customers + docker-compose down + docker-compose up --build ``` ## Sicherheit diff --git a/app.py b/app.py index c46c58a..5f39b47 100644 --- a/app.py +++ b/app.py @@ -1,10 +1,10 @@ -from flask import Flask, render_template, request, jsonify +from flask import Flask, render_template, request, jsonify, url_for import pandas as pd import os import logging import numpy as np -app = Flask(__name__) +app = Flask(__name__, static_folder='static') logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -35,21 +35,54 @@ def index(): @app.route('/search') def search(): try: + # Spezifische Suchparameter + name = request.args.get('name', '').lower() + ort = request.args.get('ort', '').lower() + kundennummer = request.args.get('kundennummer', '').lower() + fachrichtung = request.args.get('fachrichtung', '').lower() + + # Allgemeine Suche (für die alte Funktionalität) query = request.args.get('q', '').lower() - logger.info(f"Suche nach: {query}") + + logger.info(f"Suche nach - Name: {name}, Ort: {ort}, Kundennummer: {kundennummer}, Fachrichtung: {fachrichtung}, Query: {query}") df = load_data() if df is None: return jsonify({"error": "Datenbank konnte nicht geladen werden"}), 500 - # Suche über verschiedene Felder - mask = ( - df['Vorname'].str.lower().str.contains(query, na=False) | - df['Nachname'].str.lower().str.contains(query, na=False) | - df['Fachrichtung'].str.lower().str.contains(query, na=False) | - df['Ort'].str.lower().str.contains(query, na=False) | - df['Nummer'].astype(str).str.contains(query, na=False) # Suche nach Kundennummer - ) + # Basis-Mask (immer True) + mask = pd.Series(True, index=df.index) + + # Spezifische Suchkriterien anwenden + if name: + name_mask = ( + df['Vorname'].str.lower().str.contains(name, na=False) | + df['Nachname'].str.lower().str.contains(name, na=False) + ) + mask &= name_mask + + if ort: + ort_mask = df['Ort'].str.lower().str.contains(ort, na=False) + mask &= ort_mask + + if kundennummer: + kunden_mask = df['Nummer'].astype(str).str.contains(kundennummer, na=False) + mask &= kunden_mask + + if fachrichtung: + fach_mask = df['Fachrichtung'].str.lower().str.contains(fachrichtung, na=False) + mask &= fach_mask + + # Wenn eine allgemeine Suche vorhanden ist, diese zusätzlich anwenden + if query: + query_mask = ( + df['Vorname'].str.lower().str.contains(query, na=False) | + df['Nachname'].str.lower().str.contains(query, na=False) | + df['Fachrichtung'].str.lower().str.contains(query, na=False) | + df['Ort'].str.lower().str.contains(query, na=False) | + df['Nummer'].astype(str).str.contains(query, na=False) + ) + mask &= query_mask results = df[mask].to_dict('records') logger.info(f"{len(results)} Ergebnisse gefunden") diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e0a2a8a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.8' + +services: + web: + build: . + ports: + - "5001:5000" + volumes: + - .:/app + environment: + - FLASK_APP=app.py + - FLASK_ENV=development + - FLASK_DEBUG=1 + command: flask run --host=0.0.0.0 \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6e9601c1c2536837c3b1b868c36e584489d76d83 GIT binary patch literal 3262 zcmeHKYesML@A_Iw}jp7)&Rob!I?ye}Ui z7Wg?k6Fkq87iS4MM+kAl4T=Qe-sbnawob2q8iCJ)gCk1iyORUd=7xsoni}HbE)jC| z=wC*9f`7LhcR;9$@pZ^M1K7E4B-N(Kwrs=Ja%`-;|Ved8doS z!9l^}Dg6Al!Q+*Cdfu?GKzos;OJ))ss|A*HUa>@Ig59WgX!+mey7 zOCN)FOC;uv4T|}(v72YkU`ls59R0!qMt{1qlkzZhWo3WR?~zK`jlWJzQ2n*VMe5pEUzdA$F!~?b+bIvD zx3u(B|1>_#miRJ0juV9afYY>V|CGA82noRfpK5Dk?;mYR3C_`;=NIIz{rP=nM&{-w z;d1YLd95!kQ5}=PP#qRl#O2=Oa%cPdDG#GhF3+{L23}@nQl7~V(C;lNp(Kn=v)QQE z8}xeP>gv|!Cfj9PTifRKdb7#I=8-mgWHPN>&R$nzLIUy+;?OD*u^S)3Mv|`=2r%IR zg@Sfhrlum8$V&wr&U=l9@<%X6>gt|`gw*i)I+=`i$9t4GIpJj?mEE$lL%Ct6tOIR? z5p7IHBQRK5S>faJFd(2^EM^!yG0@!b@NIj0=%WOP?d_3)4~a5g-@fwl4zXCBovjuM z5t--F(G3Ct>dULFETB0pjakT~Mw4%21N~i9hFv~>w=WQ)5(;07L@nv*Eoo_u@$smJ zO^J!h3gXM8^9j1^6HbO0zoZ2e=}ZyOq(1P51# zhN93MQ)W?)!Zg{4M?mB_Xl1 literal 0 HcmV?d00001 diff --git a/templates/index.html b/templates/index.html index 453cedd..0c88ade 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,6 +4,7 @@ medisoftware Kundensuche + @@ -67,9 +104,28 @@

medisoftware Kundensuche

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ placeholder="Allgemeine Suche..."> 🔍
@@ -85,6 +141,10 @@
+ + @@ -94,11 +154,8 @@ function createPhoneLink(phone) { if (!phone) return 'N/A'; - // Entferne alle nicht-numerischen Zeichen für den tel: Link const cleaned = phone.replace(/\D/g, ''); - // Füge immer eine führende 0 hinzu const telLink = '0' + cleaned; - // Zeige die ursprüngliche Nummer an return `${phone}`; } @@ -126,9 +183,37 @@ `; } + function showCopyFeedback() { + const feedback = document.getElementById('shareFeedback'); + feedback.style.display = 'block'; + feedback.style.opacity = '1'; + + feedback.addEventListener('animationend', () => { + feedback.style.display = 'none'; + }, { once: true }); + } + + async function copyCustomerLink(customerNumber) { + const url = new URL(window.location.href); + url.searchParams.set('kundennummer', customerNumber); + + try { + await navigator.clipboard.writeText(url.toString()); + showCopyFeedback(); + } catch (err) { + console.error('Fehler beim Kopieren:', err); + } + } + function searchCustomers() { + const name = document.getElementById('nameInput').value; + const ort = document.getElementById('ortInput').value; + const kundennummer = document.getElementById('kundennummerInput').value; + const fachrichtung = document.getElementById('fachrichtungInput').value; const query = document.getElementById('searchInput').value; - if (query.length < 2) { + + // Prüfe, ob mindestens ein Suchfeld ausgefüllt ist + if (!name && !ort && !kundennummer && !fachrichtung && !query) { document.getElementById('results').innerHTML = ''; return; } @@ -137,7 +222,15 @@ document.getElementById('loading').style.display = 'block'; document.getElementById('results').innerHTML = ''; - fetch(`/search?q=${encodeURIComponent(query)}`) + // URL-Parameter erstellen + const params = new URLSearchParams(); + if (name) params.append('name', name); + if (ort) params.append('ort', ort); + if (kundennummer) params.append('kundennummer', kundennummer); + if (fachrichtung) params.append('fachrichtung', fachrichtung); + if (query) params.append('q', query); + + fetch(`/search?${params.toString()}`) .then(response => { if (!response.ok) { return response.json().then(data => { @@ -170,6 +263,11 @@ Telefon: ${createPhoneLink(customer.Tel)}
E-Mail: ${createEmailLink(customer.mail)}

+
+ +
`; resultsDiv.appendChild(card); @@ -181,26 +279,42 @@ `
${error.message}
`; }) .finally(() => { - // Lade-Animation ausblenden document.getElementById('loading').style.display = 'none'; }); } // Event-Listener für die Live-Suche - document.getElementById('searchInput').addEventListener('input', function(e) { - // Clear previous timeout - clearTimeout(searchTimeout); - - // Set new timeout - searchTimeout = setTimeout(() => { - searchCustomers(); - }, 300); // 300ms Verzögerung + const searchInputs = [ + document.getElementById('nameInput'), + document.getElementById('ortInput'), + document.getElementById('kundennummerInput'), + document.getElementById('fachrichtungInput'), + document.getElementById('searchInput') + ]; + + searchInputs.forEach(input => { + input.addEventListener('input', function() { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(searchCustomers, 300); + }); }); - // Suche bei Enter-Taste - document.getElementById('searchInput').addEventListener('keypress', function(e) { - if (e.key === 'Enter') { - clearTimeout(searchTimeout); + // URL-Parameter beim Laden der Seite prüfen + window.addEventListener('load', function() { + const urlParams = new URLSearchParams(window.location.search); + const name = urlParams.get('name'); + const ort = urlParams.get('ort'); + const kundennummer = urlParams.get('kundennummer'); + const fachrichtung = urlParams.get('fachrichtung'); + const query = urlParams.get('q'); + + if (name) document.getElementById('nameInput').value = name; + if (ort) document.getElementById('ortInput').value = ort; + if (kundennummer) document.getElementById('kundennummerInput').value = kundennummer; + if (fachrichtung) document.getElementById('fachrichtungInput').value = fachrichtung; + if (query) document.getElementById('searchInput').value = query; + + if (name || ort || kundennummer || fachrichtung || query) { searchCustomers(); } });