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 @@
+
+ Link kopiert!
+
+
@@ -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();
}
});