Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
163fb792a0 | |||
733161dd46 | |||
4e2703ba0a | |||
02e72b8347 | |||
0285f0a520 | |||
8fb10a35e8 | |||
6b017e641d | |||
4d110b6019 | |||
71d811ea98 | |||
2de5c21442 | |||
c6f170e1fc | |||
ff4f322ca4 | |||
5eaa79ed62 | |||
6657101cd6 | |||
9f4c532d66 | |||
83cedcc555 | |||
2eddb3ad20 |
50
README.md
50
README.md
@@ -100,6 +100,55 @@ Die Anwendung erwartet eine CSV-Datei (`spezexpo.csv`) mit folgenden Spalten:
|
||||
- `q`: Allgemeine Suche über alle Felder
|
||||
- Returns: JSON-Array mit gefundenen Kunden
|
||||
|
||||
#### API-Beispiele
|
||||
|
||||
1. Suche nach Namen:
|
||||
```bash
|
||||
curl "http://localhost:5001/search?name=Schmidt"
|
||||
```
|
||||
|
||||
2. Suche nach Ort:
|
||||
```bash
|
||||
curl "http://localhost:5001/search?ort=Berlin"
|
||||
```
|
||||
|
||||
3. Suche nach Kundennummer:
|
||||
```bash
|
||||
curl "http://localhost:5001/search?kundennummer=12345"
|
||||
```
|
||||
|
||||
4. Suche nach Fachrichtung:
|
||||
```bash
|
||||
curl "http://localhost:5001/search?fachrichtung=Allgemeinmedizin"
|
||||
```
|
||||
|
||||
5. Kombinierte Suche:
|
||||
```bash
|
||||
curl "http://localhost:5001/search?name=Schmidt&ort=Berlin&fachrichtung=Allgemeinmedizin"
|
||||
```
|
||||
|
||||
6. Allgemeine Suche:
|
||||
```bash
|
||||
curl "http://localhost:5001/search?q=Schmidt"
|
||||
```
|
||||
|
||||
#### Beispiel-Response
|
||||
```json
|
||||
[
|
||||
{
|
||||
"Nummer": "12345",
|
||||
"Vorname": "Max",
|
||||
"Nachname": "Schmidt",
|
||||
"Fachrichtung": "Allgemeinmedizin",
|
||||
"Strasse": "Hauptstraße 1",
|
||||
"PLZ": "10115",
|
||||
"Ort": "Berlin",
|
||||
"Tel": "030-123456",
|
||||
"mail": "max.schmidt@example.com"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Frontend-Funktionen
|
||||
|
||||
### Suchfunktion
|
||||
@@ -107,6 +156,7 @@ Die Anwendung erwartet eine CSV-Datei (`spezexpo.csv`) mit folgenden Spalten:
|
||||
- Spezifische Suchfelder für präzise Suche
|
||||
- Allgemeine Suche für breite Suche
|
||||
- Kombinierbare Suchkriterien
|
||||
- Trefferzähler für jedes Suchfeld
|
||||
|
||||
### Link-Generierung
|
||||
- `createPhoneLink()`: Erstellt tel:-Links mit führender 0
|
||||
|
95
app.py
95
app.py
@@ -3,11 +3,18 @@ import pandas as pd
|
||||
import os
|
||||
import logging
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__, static_folder='static')
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Version der Anwendung
|
||||
VERSION = "1.0.1"
|
||||
|
||||
# Pfad zur CSV-Datei
|
||||
CSV_FILE = "data/customers.csv"
|
||||
|
||||
def clean_dataframe(df):
|
||||
"""Konvertiert NaN-Werte in None für JSON-Kompatibilität"""
|
||||
return df.replace({np.nan: None})
|
||||
@@ -16,11 +23,11 @@ def clean_dataframe(df):
|
||||
def load_data():
|
||||
try:
|
||||
logger.info("Versuche CSV-Datei zu laden...")
|
||||
if not os.path.exists('spezexpo.csv'):
|
||||
logger.error("CSV-Datei 'spezexpo.csv' nicht gefunden!")
|
||||
if not os.path.exists(CSV_FILE):
|
||||
logger.error(f"CSV-Datei '{CSV_FILE}' nicht gefunden!")
|
||||
return None
|
||||
|
||||
df = pd.read_csv('spezexpo.csv', encoding='utf-8')
|
||||
df = pd.read_csv(CSV_FILE, encoding='utf-8')
|
||||
df = clean_dataframe(df)
|
||||
logger.info(f"CSV-Datei erfolgreich geladen. {len(df)} Einträge gefunden.")
|
||||
return df
|
||||
@@ -35,54 +42,56 @@ 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 - Name: {name}, Ort: {ort}, Kundennummer: {kundennummer}, Fachrichtung: {fachrichtung}, Query: {query}")
|
||||
|
||||
# CSV-Datei laden
|
||||
df = load_data()
|
||||
if df is None:
|
||||
return jsonify({"error": "Datenbank konnte nicht geladen werden"}), 500
|
||||
|
||||
# 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)
|
||||
|
||||
# Suchparameter aus der URL holen
|
||||
name = request.args.get('name', '').strip()
|
||||
ort = request.args.get('ort', '').strip()
|
||||
kundennummer = request.args.get('kundennummer', '').strip()
|
||||
fachrichtung = request.args.get('fachrichtung', '').strip()
|
||||
telefon = request.args.get('telefon', '').strip()
|
||||
query = request.args.get('q', '').strip()
|
||||
|
||||
# Wenn keine spezifischen Suchkriterien angegeben sind, aber eine allgemeine Suche
|
||||
if not any([name, ort, kundennummer, fachrichtung, telefon]) and query:
|
||||
# Suche in allen relevanten Feldern
|
||||
mask = (
|
||||
df['Vorname'].str.contains(query, case=False, na=False) |
|
||||
df['Nachname'].str.contains(query, case=False, na=False) |
|
||||
df['Ort'].str.contains(query, case=False, na=False) |
|
||||
df['Nummer'].astype(str).str.contains(query, case=False, na=False) |
|
||||
df['Fachrichtung'].str.contains(query, case=False, na=False) |
|
||||
df['Tel'].astype(str).str.contains(query, case=False, na=False)
|
||||
)
|
||||
mask &= name_mask
|
||||
else:
|
||||
# Spezifische Suche
|
||||
mask = pd.Series(True, index=df.index)
|
||||
|
||||
if ort:
|
||||
ort_mask = df['Ort'].str.lower().str.contains(ort, na=False)
|
||||
mask &= ort_mask
|
||||
if name:
|
||||
name_mask = (
|
||||
df['Vorname'].str.contains(name, case=False, na=False) |
|
||||
df['Nachname'].str.contains(name, case=False, na=False)
|
||||
)
|
||||
mask &= name_mask
|
||||
|
||||
if kundennummer:
|
||||
kunden_mask = df['Nummer'].astype(str).str.contains(kundennummer, na=False)
|
||||
mask &= kunden_mask
|
||||
if ort:
|
||||
ort_mask = df['Ort'].str.contains(ort, case=False, na=False)
|
||||
mask &= ort_mask
|
||||
|
||||
if fachrichtung:
|
||||
fach_mask = df['Fachrichtung'].str.lower().str.contains(fachrichtung, na=False)
|
||||
mask &= fach_mask
|
||||
if kundennummer:
|
||||
kundennummer_mask = df['Nummer'].astype(str).str.contains(kundennummer, case=False, na=False)
|
||||
mask &= kundennummer_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
|
||||
if fachrichtung:
|
||||
fachrichtung_mask = df['Fachrichtung'].str.contains(fachrichtung, case=False, na=False)
|
||||
mask &= fachrichtung_mask
|
||||
|
||||
if telefon:
|
||||
telefon_mask = df['Tel'].astype(str).str.contains(telefon, case=False, na=False)
|
||||
mask &= telefon_mask
|
||||
|
||||
results = df[mask].to_dict('records')
|
||||
logger.info(f"{len(results)} Ergebnisse gefunden")
|
||||
|
1172
data/customers.csv
Normal file
1172
data/customers.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
<title>medisoftware Kundensuche</title>
|
||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
@@ -42,12 +43,26 @@
|
||||
.phone-link:hover, .email-link:hover, .address-link:hover, .customer-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.search-icon {
|
||||
.search-icon, .reset-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #6c757d;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
z-index: 10;
|
||||
}
|
||||
.reset-icon {
|
||||
right: 10px;
|
||||
}
|
||||
.search-icon {
|
||||
right: 35px;
|
||||
}
|
||||
.reset-icon.visible {
|
||||
display: block;
|
||||
}
|
||||
.search-icon.visible {
|
||||
display: block;
|
||||
}
|
||||
.customer-number {
|
||||
color: #6c757d;
|
||||
@@ -97,6 +112,33 @@
|
||||
.search-field {
|
||||
position: relative;
|
||||
}
|
||||
.input-group {
|
||||
position: relative;
|
||||
}
|
||||
.result-counts {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.result-count {
|
||||
background-color: #e9ecef;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.9em;
|
||||
color: #6c757d;
|
||||
display: none;
|
||||
}
|
||||
.result-count.visible {
|
||||
display: inline-block;
|
||||
}
|
||||
.location-pin {
|
||||
color: #dc3545;
|
||||
margin-left: 4px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -104,29 +146,43 @@
|
||||
<div class="container search-container">
|
||||
<h1 class="text-center mb-4">medisoftware Kundensuche</h1>
|
||||
|
||||
<div class="input-group mb-4 position-relative">
|
||||
<input type="text" id="searchInput" class="form-control form-control-lg"
|
||||
placeholder="Allgemeine Suche...">
|
||||
<i class="fa-solid fa-xmark reset-icon" id="searchReset"></i>
|
||||
<span class="search-icon">🔍</span>
|
||||
</div>
|
||||
|
||||
<div class="search-fields">
|
||||
<div class="search-field">
|
||||
<input type="text" id="nameInput" class="form-control"
|
||||
placeholder="Name...">
|
||||
<i class="fa-solid fa-xmark reset-icon" id="nameReset"></i>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<input type="text" id="ortInput" class="form-control"
|
||||
placeholder="Ort...">
|
||||
<i class="fa-solid fa-xmark reset-icon" id="ortReset"></i>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<input type="text" id="kundennummerInput" class="form-control"
|
||||
placeholder="Kundennummer...">
|
||||
<i class="fa-solid fa-xmark reset-icon" id="kundennummerReset"></i>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<input type="text" id="fachrichtungInput" class="form-control"
|
||||
placeholder="Fachrichtung...">
|
||||
<i class="fa-solid fa-xmark reset-icon" id="fachrichtungReset"></i>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<input type="text" id="telefonInput" class="form-control"
|
||||
placeholder="Telefon...">
|
||||
<i class="fa-solid fa-xmark reset-icon" id="telefonReset"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-4 position-relative">
|
||||
<input type="text" id="searchInput" class="form-control form-control-lg"
|
||||
placeholder="Allgemeine Suche...">
|
||||
<span class="search-icon">🔍</span>
|
||||
<div class="result-counts">
|
||||
<span id="generalCount" class="result-count"></span>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="loading">
|
||||
@@ -151,6 +207,7 @@
|
||||
|
||||
<script>
|
||||
let searchTimeout;
|
||||
let lastResults = [];
|
||||
|
||||
function createPhoneLink(phone) {
|
||||
if (!phone) return 'N/A';
|
||||
@@ -168,10 +225,10 @@
|
||||
if (!street || !plz || !city) return 'N/A';
|
||||
const address = `${street}, ${plz} ${city}`;
|
||||
const searchQuery = encodeURIComponent(address);
|
||||
return `<a href="https://www.google.com/maps/search/?api=1&query=${searchQuery}"
|
||||
return `${address}
|
||||
<a href="https://www.google.com/maps/search/?api=1&query=${searchQuery}"
|
||||
class="address-link" target="_blank" rel="noopener noreferrer">
|
||||
${address}
|
||||
<span class="ms-1">🗺️</span>
|
||||
<i class="fa-solid fa-location-pin location-pin"></i>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
@@ -205,16 +262,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateResultCounts() {
|
||||
// Nur Gesamtzahl anzeigen
|
||||
const generalCount = lastResults.length;
|
||||
document.getElementById('generalCount').textContent =
|
||||
generalCount > 0 ? `${generalCount} Treffer gefunden` : '';
|
||||
document.getElementById('generalCount').classList.toggle('visible', generalCount > 0);
|
||||
}
|
||||
|
||||
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 telefon = document.getElementById('telefonInput').value;
|
||||
const query = document.getElementById('searchInput').value;
|
||||
|
||||
// Prüfe, ob mindestens ein Suchfeld ausgefüllt ist
|
||||
if (!name && !ort && !kundennummer && !fachrichtung && !query) {
|
||||
if (!name && !ort && !kundennummer && !fachrichtung && !telefon && !query) {
|
||||
document.getElementById('results').innerHTML = '';
|
||||
lastResults = [];
|
||||
updateResultCounts();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,6 +296,7 @@
|
||||
if (ort) params.append('ort', ort);
|
||||
if (kundennummer) params.append('kundennummer', kundennummer);
|
||||
if (fachrichtung) params.append('fachrichtung', fachrichtung);
|
||||
if (telefon) params.append('telefon', telefon);
|
||||
if (query) params.append('q', query);
|
||||
|
||||
fetch(`/search?${params.toString()}`)
|
||||
@@ -245,6 +314,8 @@
|
||||
|
||||
if (data.length === 0) {
|
||||
resultsDiv.innerHTML = '<div class="alert alert-info">Keine Ergebnisse gefunden.</div>';
|
||||
lastResults = [];
|
||||
updateResultCounts();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,11 +343,16 @@
|
||||
`;
|
||||
resultsDiv.appendChild(card);
|
||||
});
|
||||
|
||||
lastResults = data;
|
||||
updateResultCounts();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler:', error);
|
||||
document.getElementById('results').innerHTML =
|
||||
`<div class="alert alert-danger">${error.message}</div>`;
|
||||
lastResults = [];
|
||||
updateResultCounts();
|
||||
})
|
||||
.finally(() => {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
@@ -289,13 +365,32 @@
|
||||
document.getElementById('ortInput'),
|
||||
document.getElementById('kundennummerInput'),
|
||||
document.getElementById('fachrichtungInput'),
|
||||
document.getElementById('telefonInput'),
|
||||
document.getElementById('searchInput')
|
||||
];
|
||||
|
||||
searchInputs.forEach(input => {
|
||||
const resetIcons = [
|
||||
document.getElementById('nameReset'),
|
||||
document.getElementById('ortReset'),
|
||||
document.getElementById('kundennummerReset'),
|
||||
document.getElementById('fachrichtungReset'),
|
||||
document.getElementById('telefonReset'),
|
||||
document.getElementById('searchReset')
|
||||
];
|
||||
|
||||
searchInputs.forEach((input, index) => {
|
||||
input.addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(searchCustomers, 300);
|
||||
|
||||
// Reset-Icon anzeigen/verstecken
|
||||
resetIcons[index].classList.toggle('visible', this.value.length > 0);
|
||||
});
|
||||
|
||||
// Reset-Funktionalität
|
||||
resetIcons[index].addEventListener('click', function() {
|
||||
searchInputs[index].value = '';
|
||||
searchCustomers();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -306,15 +401,17 @@
|
||||
const ort = urlParams.get('ort');
|
||||
const kundennummer = urlParams.get('kundennummer');
|
||||
const fachrichtung = urlParams.get('fachrichtung');
|
||||
const telefon = urlParams.get('telefon');
|
||||
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 (telefon) document.getElementById('telefonInput').value = telefon;
|
||||
if (query) document.getElementById('searchInput').value = query;
|
||||
|
||||
if (name || ort || kundennummer || fachrichtung || query) {
|
||||
if (name || ort || kundennummer || fachrichtung || telefon || query) {
|
||||
searchCustomers();
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user