15 Commits

Author SHA1 Message Date
997786be54 Feature: Suchfeld für Fachrichtung hinzugefügt 2025-03-18 15:11:15 +01:00
c4974787d4 Dokumentation: Korrigiere Versionsangabe am Ende der README.md 2025-03-18 14:08:04 +01:00
a42bdaa721 Dokumentation: Aktualisiere Versionsnummer in README.md auf v1.2.4 2025-03-18 14:06:31 +01:00
49938a1085 Version 1.2.4: Performance-Optimierung durch Datenbankindizes 2025-03-18 14:02:07 +01:00
d0a27fe095 Performance: Indizes für alle Suchfelder hinzugefügt 2025-03-18 14:01:08 +01:00
d388bce528 Footer Text geändert 2025-03-18 13:59:57 +01:00
aabb4540c9 Version 1.2.3: Performance-Optimierung durch Entfernung von Debug-Ausgaben 2025-03-18 13:57:32 +01:00
ffde078238 Git: Füge data/customers.db zur .gitignore hinzu 2025-03-18 13:55:17 +01:00
9e320c4eb2 Performance: Entferne alle console.log Anweisungen 2025-03-18 13:53:14 +01:00
58ed5fe867 Performance: Entferne Debug-Logging aus createPhoneLink Funktion 2025-03-18 13:50:32 +01:00
72676edc10 Git: Füge data/customers.db zur .gitignore hinzu 2025-03-18 13:48:55 +01:00
d5954eac89 Version 1.2.2: Verbesserte Telefonnummern-Formatierung und Dokumentation 2025-03-18 13:47:41 +01:00
68a2db28a1 Dokumentation: Aktualisierte README.md und CHANGELOG.md für Version 1.2.2 2025-03-18 13:46:33 +01:00
13709de515 Version 1.2.2: Verbesserte Telefonnummern-Formatierung 2025-03-18 13:43:07 +01:00
2c65d5f651 Dokumentation: Aktualisierung für Version 1.2.1 2025-03-18 12:56:15 +01:00
9 changed files with 351 additions and 283 deletions

6
.gitignore vendored
View File

@@ -45,4 +45,8 @@ coverage.xml
.docker/
# Daten
spezexpo.csv
spezexpo.csv
# Database
*.db
data/customers.db

View File

@@ -5,7 +5,39 @@ Alle wichtigen Änderungen an diesem Projekt werden in dieser Datei dokumentiert
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/),
und dieses Projekt adhäriert zu [Semantic Versioning](https://semver.org/lang/de/).
## [v1.2.0] - 2024-03-18
## [1.2.4] - 2024-03-19
### Geändert
- Performance-Optimierung: Indizes für alle Suchfelder hinzugefügt
- Verbesserte Suchgeschwindigkeit durch optimierte Datenbankindizes
- Zusammengesetzter Index für die häufigste Suchkombination (Name + Ort) hinzugefügt
## [1.2.3] - 2024-03-19
### Geändert
- Performance-Optimierung: Entfernung aller console.log Anweisungen
- Verbesserte Code-Qualität durch Entfernung von Debug-Ausgaben
## [1.2.2] - 2024-03-19
### Geändert
- Verbesserte Telefonnummern-Formatierung: Führende "0" wird immer hinzugefügt, wenn der Benutzer von einer erlaubten IP-Adresse zugreift
- Debug-Logging für Telefonnummern-Formatierung hinzugefügt
- Verbesserte Benutzerfreundlichkeit bei der Anzeige von Telefonnummern
### Behoben
- Problem mit fehlender führender "0" bei Telefonnummern für autorisierte Benutzer
## [1.2.1] - 2024-03-18
### Geändert
- Verbesserte CSV-Import-Funktionalität mit pandas
- Korrektur des Login-Prozesses
- Verbesserte Fehlerbehandlung und Logging
- Anpassung der Spaltennamen für den CSV-Import
### Behoben
- Login-Prozess funktioniert jetzt korrekt mit dem Passwort aus der .env Datei
- CSV-Import verarbeitet Anführungszeichen korrekt
- Verbesserte Fehlerbehandlung beim Datenbankimport
## [1.2.0] - 2024-03-18
### Geändert
- IP-Subnetz-Prüfung korrigiert (CIDR-Notation wird jetzt korrekt ausgewertet)

View File

@@ -10,8 +10,12 @@ COPY requirements.txt .
# Installiere Abhängigkeiten
RUN pip install --no-cache-dir -r requirements.txt
# Kopiere den Anwendungscode
COPY . .
# Erstelle das data Verzeichnis und setze Berechtigungen
RUN mkdir -p /app/data && \
chmod 755 /app/data
# Kopiere die Anwendungsdateien
COPY . /app/
# Exponiere Port 5000
EXPOSE 5000

View File

@@ -1,6 +1,6 @@
# medisoftware Kundensuche
# Medi-Customers
Eine einfache Webanwendung zur Suche nach medisoftware Kunden mit IP-basierter Zugriffssteuerung.
Eine Flask-basierte Webanwendung zur Verwaltung von Kundenkontakten für medizinische Einrichtungen.
## Features
@@ -51,7 +51,7 @@ Die Anwendung unterstützt CIDR-Notation für IP-Bereiche. Beispiele:
## Version
Aktuelle Version: v1.2.0
Aktuelle Version: 1.2.4
## Lizenz
@@ -103,4 +103,4 @@ curl "http://localhost:5001/search?fachrichtung=Zahnarzt&ort=Berlin&name=Schmidt
## Version
Aktuelle Version: [v1.2.0](CHANGELOG.md#v120---2024-03-17)
Aktuelle Version: [v1.2.4](CHANGELOG.md#v124---2024-03-19)

144
app.py
View File

@@ -21,7 +21,10 @@ logger = logging.getLogger(__name__)
VERSION = "1.2.1"
# Pfad zur CSV-Datei
CSV_FILE = "data/customers.csv"
CSV_FILE = 'data/customers.csv'
# Pfad zur Datenbank
DB_FILE = 'data/customers.db'
# Lade Umgebungsvariablen
load_dotenv()
@@ -31,11 +34,11 @@ STATIC_PASSWORD = os.getenv('LOGIN_PASSWORD', 'default-password')
ALLOWED_IP_RANGES = os.getenv('ALLOWED_IP_RANGES', '').split(',')
def init_db():
"""Initialisiert die SQLite-Datenbank und erstellt die notwendigen Tabellen."""
conn = sqlite3.connect('customers.db')
"""Initialisiert die SQLite-Datenbank mit der notwendigen Tabelle."""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Erstelle die Kunden-Tabelle
# Erstelle die Tabelle mit Indizes
c.execute('''
CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -47,16 +50,32 @@ def init_db():
telefon TEXT,
mobil TEXT,
email TEXT,
bemerkung TEXT
bemerkung TEXT,
fachrichtung TEXT
)
''')
# Erstelle Indizes für alle Suchfelder
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_nummer ON customers(nummer)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_name ON customers(name)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_strasse ON customers(strasse)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_plz ON customers(plz)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_ort ON customers(ort)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_telefon ON customers(telefon)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_mobil ON customers(mobil)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_email ON customers(email)')
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_fachrichtung ON customers(fachrichtung)')
# Erstelle einen zusammengesetzten Index für die häufigste Suchkombination
c.execute('CREATE INDEX IF NOT EXISTS idx_customers_name_ort ON customers(name, ort)')
conn.commit()
conn.close()
logger.info('Datenbank initialisiert')
def import_csv():
"""Importiert die Daten aus der CSV-Datei in die SQLite-Datenbank."""
conn = sqlite3.connect('customers.db')
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Lösche bestehende Daten
@@ -64,7 +83,7 @@ def import_csv():
try:
# Lese die CSV-Datei mit pandas
df = pd.read_csv('data/customers.csv', sep=',', encoding='utf-8', quotechar='"')
df = pd.read_csv(CSV_FILE, sep=',', encoding='utf-8', quotechar='"')
# Entferne Anführungszeichen aus den Spaltennamen
df.columns = df.columns.str.strip('"')
@@ -80,8 +99,8 @@ def import_csv():
# Importiere die Daten
for _, row in df.iterrows():
c.execute('''
INSERT INTO customers (nummer, name, strasse, plz, ort, telefon, mobil, email, bemerkung)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO customers (nummer, name, strasse, plz, ort, telefon, mobil, email, bemerkung, fachrichtung)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
row['Nummer'],
row['name'],
@@ -91,7 +110,8 @@ def import_csv():
row['Tel'],
row['Handy'],
row['mail'],
f"Fachrichtung: {row['Fachrichtung']}"
f"Fachrichtung: {row['Fachrichtung']}",
row['Fachrichtung']
))
conn.commit()
@@ -103,48 +123,66 @@ def import_csv():
conn.close()
def search_customers(search_params):
"""Sucht Kunden in der Datenbank basierend auf den Suchparametern."""
conn = sqlite3.connect('customers.db')
"""Sucht nach Kunden basierend auf den Suchparametern."""
conn = sqlite3.connect(DB_FILE)
c = conn.cursor()
# Erstelle die WHERE-Bedingungen basierend auf den Suchparametern
conditions = []
params = []
if search_params.get('name'):
conditions.append('name LIKE ?')
params.append(f'%{search_params["name"]}%')
if search_params.get('ort'):
conditions.append('ort LIKE ?')
params.append(f'%{search_params["ort"]}%')
if search_params.get('nummer'):
conditions.append('nummer LIKE ?')
params.append(f'%{search_params["nummer"]}%')
if search_params.get('plz'):
conditions.append('plz LIKE ?')
params.append(f'%{search_params["plz"]}%')
# Erstelle die SQL-Abfrage
sql = 'SELECT * FROM customers'
if conditions:
sql += ' WHERE ' + ' AND '.join(conditions)
# Führe die Abfrage aus
c.execute(sql, params)
results = c.fetchall()
# Konvertiere die Ergebnisse in ein Dictionary
columns = ['id', 'nummer', 'name', 'strasse', 'plz', 'ort', 'telefon', 'mobil', 'email', 'bemerkung']
customers = []
for row in results:
customer = dict(zip(columns, row))
customers.append(customer)
conn.close()
return customers
try:
# Baue die SQL-Abfrage dynamisch auf
query = "SELECT * FROM customers WHERE 1=1"
params = []
# Allgemeine Suche über alle Felder
if search_params.get('q'):
search_term = f"%{search_params['q']}%"
query += " AND (name LIKE ? OR ort LIKE ? OR nummer LIKE ? OR telefon LIKE ? OR mobil LIKE ? OR email LIKE ? OR bemerkung LIKE ? OR fachrichtung LIKE ?)"
params.extend([search_term] * 8)
# Spezifische Suche für einzelne Felder
if search_params.get('name'):
query += " AND name LIKE ?"
params.append(f"%{search_params['name']}%")
if search_params.get('ort'):
query += " AND ort LIKE ?"
params.append(f"%{search_params['ort']}%")
if search_params.get('nummer'):
query += " AND nummer LIKE ?"
params.append(f"%{search_params['nummer']}%")
if search_params.get('plz'):
query += " AND plz LIKE ?"
params.append(f"%{search_params['plz']}%")
# Führe die Abfrage aus
c.execute(query, params)
results = c.fetchall()
# Formatiere die Ergebnisse
customers = []
for row in results:
customer = {
'id': row[0],
'nummer': row[1],
'name': row[2],
'strasse': row[3],
'plz': row[4],
'ort': row[5],
'telefon': row[6],
'mobil': row[7],
'email': row[8],
'bemerkung': row[9],
'fachrichtung': row[10]
}
customers.append(customer)
return customers
except Exception as e:
logger.error(f"Fehler bei der Kundensuche: {str(e)}")
raise
finally:
conn.close()
def clean_dataframe(df):
"""Konvertiert NaN-Werte in None für JSON-Kompatibilität"""
@@ -233,7 +271,11 @@ def search():
'name': request.args.get('name', ''),
'ort': request.args.get('ort', ''),
'nummer': request.args.get('nummer', ''),
'plz': request.args.get('plz', '')
'plz': request.args.get('plz', ''),
'telefon': request.args.get('telefon', ''),
'email': request.args.get('email', ''),
'q': request.args.get('q', ''),
'fachrichtung': request.args.get('fachrichtung', '')
}
# Führe die Suche in der Datenbank durch

View File

@@ -4,9 +4,11 @@ services:
ports:
- "5001:5000"
volumes:
- .:/app
- ./data:/app/data
environment:
- FLASK_APP=app.py
- FLASK_ENV=development
- FLASK_DEBUG=1
- FLASK_ENV=production
- SECRET_KEY=your-super-secret-key-here
- LOGIN_PASSWORD=changeme
- ALLOWED_IP_RANGES=213.178.68.218/29,192.168.0.0/24,192.168.177.0/24
command: flask run --host=0.0.0.0

View File

@@ -109,21 +109,31 @@ body {
display: flex;
justify-content: flex-end;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e9ecef;
}
.share-button {
padding: 5px 10px;
border-radius: 15px;
font-size: 0.9em;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
background-color: #0d6efd;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.2s;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.share-button:hover {
background-color: #0b5ed7;
transform: translateY(-1px);
}
.share-button i {
font-size: 1rem;
}
.search-fields {
@@ -204,4 +214,25 @@ body {
.footer-link:hover {
text-decoration: underline;
}
.general-search {
max-width: 800px;
margin: 0 auto;
}
.general-search .input-group {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.general-search .form-control {
height: 3.5rem;
font-size: 1.2rem;
padding: 0.75rem 1rem;
}
.general-search .search-icon,
.general-search .reset-icon {
font-size: 1.2rem;
padding: 0 1rem;
}

View File

@@ -16,45 +16,57 @@
<img src="{{ url_for('static', filename='medisoftware_logo_rb_200.png') }}" alt="medisoftware Logo" class="img-fluid" style="max-width: 200px;">
</div>
<div class="search-container">
<h1 class="text-center mb-4">medisoftware Kundensuche</h1>
<h1 class="text-center mb-4">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 class="general-search mb-4">
<div class="input-group">
<input type="text" id="q" class="form-control form-control-lg" placeholder="Allgemeine Suche" oninput="searchCustomers()">
<i class="fas fa-times reset-icon" onclick="clearInput('q')"></i>
<i class="fas fa-search search-icon"></i>
</div>
</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 class="input-group">
<input type="text" id="nameInput" class="form-control" placeholder="Name" oninput="searchCustomers()">
<i class="fas fa-times reset-icon" onclick="clearInput('nameInput')"></i>
<i class="fas fa-search search-icon"></i>
</div>
</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 class="input-group">
<input type="text" id="ortInput" class="form-control" placeholder="Ort" oninput="searchCustomers()">
<i class="fas fa-times reset-icon" onclick="clearInput('ortInput')"></i>
<i class="fas fa-search search-icon"></i>
</div>
</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 class="input-group">
<input type="text" id="nummerInput" class="form-control" placeholder="Kundennummer" oninput="searchCustomers()">
<i class="fas fa-times reset-icon" onclick="clearInput('nummerInput')"></i>
<i class="fas fa-search search-icon"></i>
</div>
</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 class="input-group">
<input type="text" id="plzInput" class="form-control" placeholder="PLZ" oninput="searchCustomers()">
<i class="fas fa-times reset-icon" onclick="clearInput('plzInput')"></i>
<i class="fas fa-search search-icon"></i>
</div>
</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 class="form-group">
<label for="fachrichtungInput">Fachrichtung</label>
<input type="text" class="form-control" id="fachrichtungInput" placeholder="Fachrichtung eingeben">
</div>
</div>
<div class="result-counts">
<span id="generalCount" class="result-count"></span>
<span id="resultCount" class="result-count"></span>
</div>
<div id="loading" class="loading">
@@ -63,21 +75,19 @@
</div>
</div>
<div id="results" class="mt-4">
<!-- Hier werden die Suchergebnisse angezeigt -->
</div>
<div id="results"></div>
</div>
</div>
</div>
<div id="shareFeedback" class="share-feedback">
Link kopiert!
Link in die Zwischenablage kopiert!
</div>
<footer class="footer">
<div class="footer-content">
Made with ❤️ and 🍺 by <a href="https://www.medisoftware.de" target="_blank" class="footer-link">medisoftware</a>
<div style="font-size: 0.8em;">Version: v1.2.0</div>
Proudly made with ❤️ and 🍺 by <a href="https://www.medisoftware.de" target="_blank" class="footer-link">medisoftware</a>
<div style="font-size: 0.8em;">Version: v1.2.2</div>
</div>
</footer>
@@ -87,15 +97,31 @@
function createPhoneLink(phone) {
if (!phone) return 'N/A';
const cleaned = phone.replace(/[^\d+\s]/g, '');
const clientIP = '{{ request.headers.get("X-Forwarded-For", request.remote_addr) }}';
const allowedIPRanges = '{{ allowed_ip_ranges }}'.split(',');
// Überprüfen, ob die Client-IP in einem der erlaubten Bereiche liegt
const isAllowed = allowedIPRanges.some(range => clientIP.startsWith(range.trim()));
const telLink = cleaned.startsWith('+') ? cleaned : (isAllowed ? '0' + cleaned.replace(/\s/g, '') : cleaned.replace(/\s/g, ''));
return `<a href="tel:${telLink}" class="phone-link">${phone}</a>`;
const isAllowed = allowedIPRanges.some(range => isIPInSubnet(clientIP, range.trim()));
// Entferne alle nicht-numerischen Zeichen
let cleanNumber = phone.replace(/\D/g, '');
// Füge eine führende 0 hinzu, wenn isAllowed true ist
if (isAllowed) {
cleanNumber = '0' + cleanNumber;
}
// Formatiere die Nummer
let formattedNumber = cleanNumber;
if (cleanNumber.length === 11) {
formattedNumber = cleanNumber.replace(/(\d{4})(\d{7})/, '$1-$2');
} else if (cleanNumber.length === 10) {
formattedNumber = cleanNumber.replace(/(\d{3})(\d{7})/, '$1-$2');
}
// Erstelle den Link
return `<a href="tel:${cleanNumber}" class="phone-link">${formattedNumber}</a>`;
}
function createEmailLink(email) {
@@ -103,48 +129,10 @@
return `<a href="mailto:${email}" class="email-link">${email}</a>`;
}
function highlightText(text, searchTerms) {
// Konvertiere text zu String und prüfe auf null/undefined
const textStr = String(text || '');
if (!textStr || !searchTerms || searchTerms.length === 0) return textStr;
// Escapen der Suchbegriffe für reguläre Ausdrücke
const escapedTerms = searchTerms.map(term =>
String(term || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
).filter(term => term.length > 0);
if (escapedTerms.length === 0) return textStr;
// Erstelle einen temporären div-Element
const tempDiv = document.createElement('div');
tempDiv.innerHTML = textStr;
// Funktion zum Hervorheben von Text
function highlightNode(node) {
if (node.nodeType === 3) { // Text node
const text = node.textContent;
let newText = text;
escapedTerms.forEach(term => {
const regex = new RegExp(`(${term})`, 'gi');
newText = newText.replace(regex, '<mark>$1</mark>');
});
if (newText !== text) {
const span = document.createElement('span');
span.innerHTML = newText;
node.parentNode.replaceChild(span, node);
}
} else if (node.nodeType === 1) { // Element node
// Überspringe mark-Tags und Links
if (node.tagName !== 'MARK' && node.tagName !== 'A') {
Array.from(node.childNodes).forEach(highlightNode);
}
}
}
highlightNode(tempDiv);
return tempDiv.innerHTML;
function highlightText(text, searchTerm) {
if (!searchTerm) return text;
const regex = new RegExp(`(${searchTerm})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
function createAddressLink(street, plz, city) {
@@ -153,7 +141,6 @@
const searchQuery = encodeURIComponent(address);
const routeQuery = encodeURIComponent(address);
const clientIP = '{{ request.headers.get("X-Forwarded-For", request.remote_addr) }}';
console.log('Client IP in createAddressLink:', clientIP);
return `<span class="address-text">${address}</span>
<a href="https://www.google.com/maps/search/?api=1&query=${searchQuery}"
class="address-link" target="_blank" rel="noopener noreferrer">
@@ -190,19 +177,12 @@
const clientIP = '{{ request.headers.get("X-Forwarded-For", request.remote_addr) }}';
const allowedIPRanges = '{{ allowed_ip_ranges }}'.split(',');
// Debug-Ausgabe für die IP-Bereiche
console.log('Client IP in createCustomerLink:', clientIP);
console.log('Allowed IP Ranges:', allowedIPRanges);
// Überprüfe, ob die Client-IP in einem der erlaubten Bereiche liegt
const isAllowed = allowedIPRanges.some(range => {
const trimmedRange = range.trim();
console.log('Checking range:', trimmedRange);
return isIPInSubnet(clientIP, trimmedRange);
});
console.log('isAllowed in createCustomerLink:', isAllowed);
const adjustedNumber = adjustCustomerNumber(nummer);
if (isAllowed) {
return `<a href="medisw:openkkbefe/P${adjustedNumber}?NetGrp=4" class="customer-link">${nummer}</a>`;
@@ -229,161 +209,138 @@
await navigator.clipboard.writeText(url.toString());
showCopyFeedback();
} catch (err) {
console.error('Fehler beim Kopieren:', err);
// Fehlerbehandlung ohne console.log
}
}
function updateResultCounts() {
// Nur Gesamtzahl anzeigen
const generalCount = lastResults.length;
document.getElementById('generalCount').textContent =
document.getElementById('resultCount').textContent =
generalCount > 0 ? `${generalCount} Treffer gefunden` : '';
document.getElementById('generalCount').classList.toggle('visible', generalCount > 0);
document.getElementById('resultCount').classList.toggle('visible', generalCount > 0);
}
function searchCustomers() {
const query = document.getElementById('searchInput').value.trim();
const fachrichtung = document.getElementById('fachrichtungInput').value.trim();
const ort = document.getElementById('ortInput').value.trim();
const name = document.getElementById('nameInput').value.trim();
const telefon = document.getElementById('telefonInput').value.trim();
const kundennummer = document.getElementById('kundennummerInput')?.value.trim() || '';
function displayResults(results) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '';
// Sammle alle nicht-leeren Suchbegriffe
const searchTerms = [query, fachrichtung, ort, name, telefon, kundennummer]
.filter(term => term && term.length > 0);
// Prüfe, ob alle Suchfelder leer sind
if (searchTerms.length === 0) {
const resultsDiv = document.getElementById('results');
const generalCount = document.getElementById('generalCount');
resultsDiv.innerHTML = '';
generalCount.textContent = '';
generalCount.classList.remove('visible');
if (results.length === 0) {
resultsDiv.innerHTML = '<p>Keine Ergebnisse gefunden.</p>';
return;
}
const resultsDiv = document.getElementById('results');
const loadingDiv = document.getElementById('loading');
loadingDiv.style.display = 'block';
resultsDiv.innerHTML = '';
const searchTerm = document.getElementById('q').value;
const searchParams = new URLSearchParams();
if (query) searchParams.append('q', query);
if (fachrichtung) searchParams.append('fachrichtung', fachrichtung);
if (ort) searchParams.append('ort', ort);
if (name) searchParams.append('name', name);
if (telefon) searchParams.append('telefon', telefon);
if (kundennummer) searchParams.append('kundennummer', kundennummer);
fetch(`/search?${searchParams.toString()}`)
results.forEach(customer => {
const card = document.createElement('div');
card.className = 'customer-card';
card.innerHTML = `
<div class="customer-info">
<h5 class="mb-1">${highlightText(customer.name, searchTerm)}</h5>
<p class="mb-1 customer-number">${createCustomerLink(customer.nummer)}</p>
<p class="mb-1">${createAddressLink(customer.strasse, customer.plz, customer.ort)}</p>
<p class="mb-1">Tel: ${createPhoneLink(customer.telefon)}</p>
${customer.mobil ? `<p class="mb-1">Mobil: ${createPhoneLink(customer.mobil)}</p>` : ''}
${customer.email ? `<p class="mb-1">E-Mail: ${createEmailLink(customer.email)}</p>` : ''}
${customer.bemerkung ? `<p class="mb-1">Bemerkung: ${customer.bemerkung}</p>` : ''}
</div>
<div class="card-actions">
<button class="share-button" onclick="copyCustomerLink('${customer.nummer}')">
<i class="fas fa-share-alt"></i> Teilen
</button>
</div>
`;
resultsDiv.appendChild(card);
});
}
function searchCustomers() {
const q = document.getElementById('q').value;
const name = document.getElementById('nameInput').value;
const ort = document.getElementById('ortInput').value;
const nummer = document.getElementById('nummerInput').value;
const plz = document.getElementById('plzInput').value;
const fachrichtung = document.getElementById('fachrichtungInput').value;
// Zeige das Lade-Icon
document.getElementById('loading').style.display = 'block';
// Baue die Suchanfrage
const params = new URLSearchParams();
if (q) params.append('q', q);
if (name) params.append('name', name);
if (ort) params.append('ort', ort);
if (nummer) params.append('nummer', nummer);
if (plz) params.append('plz', plz);
if (fachrichtung) params.append('fachrichtung', fachrichtung);
// Führe die Suche durch
fetch('/search?' + params.toString())
.then(response => response.json())
.then(data => {
resultsDiv.innerHTML = '';
// Verstecke das Lade-Icon
document.getElementById('loading').style.display = 'none';
// Prüfe, ob data ein Objekt mit results-Array ist
if (!data || !data.results || !Array.isArray(data.results)) {
console.error('Unerwartetes Datenformat:', data);
resultsDiv.innerHTML = '<div class="error">Unerwartetes Datenformat vom Server</div>';
if (data.error) {
return;
}
const results = data.results;
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="no-results">Keine Ergebnisse gefunden</div>';
} else {
results.forEach(customer => {
const card = document.createElement('div');
card.className = 'customer-card';
// Debug-Ausgabe für die Kundendaten
console.log('Kundendaten:', customer);
console.log('Alle verfügbaren Felder:', Object.keys(customer));
console.log('Telefon-bezogene Felder:', {
Telefon: customer.Telefon,
Telefonnummer: customer.Telefonnummer,
telefon: customer.telefon,
telefonnummer: customer.telefonnummer,
phone: customer.phone,
'phone.number': customer.phone?.number
});
// Erstelle die Adresse mit Hervorhebung
const address = `${customer.Strasse || ''}, ${customer.PLZ || ''} ${customer.Ort || ''}`;
const addressLink = createAddressLink(customer.Strasse, customer.PLZ, customer.Ort);
const highlightedAddress = highlightText(addressLink, searchTerms);
// Erstelle die Kundennummer mit Hervorhebung
const highlightedNumber = highlightText(customer.Nummer, searchTerms);
const customerLink = createCustomerLink(customer.Nummer);
// Erstelle die Telefonnummern mit Hervorhebung
let phoneNumber = '';
let companyPhone = '';
let mobilePhone = '';
if (typeof customer === 'object') {
phoneNumber = customer.Tel || '';
companyPhone = customer['Tele Firma'] || '';
mobilePhone = customer.Handy || '';
}
const phoneLink = createPhoneLink(phoneNumber);
const companyPhoneLink = createPhoneLink(companyPhone);
const mobilePhoneLink = createPhoneLink(mobilePhone);
const highlightedPhone = highlightText(phoneLink, searchTerms);
const highlightedCompanyPhone = highlightText(companyPhoneLink, searchTerms);
const highlightedMobilePhone = highlightText(mobilePhoneLink, searchTerms);
card.innerHTML = `
<div class="customer-info">
<strong>Kundennummer:</strong> ${customerLink}<br>
<strong>Name:</strong> ${highlightText(`${customer.Vorname || ''} ${customer.Nachname || ''}`, searchTerms)}<br>
<strong>Fachrichtung:</strong> ${highlightText(customer.Fachrichtung || '', searchTerms)}<br>
<strong>Adresse:</strong> ${highlightedAddress}<br>
<strong>Telefon:</strong> ${highlightedPhone}<br>
<strong>Firma:</strong> ${highlightedCompanyPhone}<br>
<strong>Mobil:</strong> ${highlightedMobilePhone}
</div>
<button class="share-button" onclick="copyCustomerLink('${adjustCustomerNumber(customer.Nummer)}')">
lastResults = data;
updateResultCounts();
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '';
if (data.length === 0) {
resultsDiv.innerHTML = '<p class="text-center text-muted">Keine Ergebnisse gefunden</p>';
return;
}
data.forEach(customer => {
const card = document.createElement('div');
card.className = 'customer-card';
card.innerHTML = `
<div class="customer-info">
<h5 class="mb-1">${highlightText(customer.name, q || name)}</h5>
<p class="mb-1 customer-number">${createCustomerLink(customer.nummer)}</p>
<p class="mb-1">${createAddressLink(customer.strasse, customer.plz, customer.ort)}</p>
<p class="mb-1">Tel: ${createPhoneLink(customer.telefon)}</p>
${customer.mobil ? `<p class="mb-1">Mobil: ${createPhoneLink(customer.mobil)}</p>` : ''}
${customer.email ? `<p class="mb-1">E-Mail: ${createEmailLink(customer.email)}</p>` : ''}
${customer.bemerkung ? `<p class="mb-1">Bemerkung: ${customer.bemerkung}</p>` : ''}
</div>
<div class="card-actions">
<button class="share-button" onclick="copyCustomerLink('${customer.nummer}')">
<i class="fas fa-share-alt"></i> Teilen
</button>
`;
resultsDiv.appendChild(card);
});
}
// Aktualisiere die Anzahl der Treffer
const generalCount = document.getElementById('generalCount');
generalCount.textContent = results.length > 0 ? `${results.length} Treffer gefunden` : '';
generalCount.classList.toggle('visible', results.length > 0);
</div>
`;
resultsDiv.appendChild(card);
});
})
.catch(error => {
console.error('Fehler bei der Suche:', error);
resultsDiv.innerHTML = '<div class="error">Ein Fehler ist aufgetreten</div>';
})
.finally(() => {
loadingDiv.style.display = 'none';
document.getElementById('loading').style.display = 'none';
});
}
// Event-Listener für die Live-Suche
const searchInputs = [
document.getElementById('q'),
document.getElementById('nameInput'),
document.getElementById('ortInput'),
document.getElementById('kundennummerInput'),
document.getElementById('fachrichtungInput'),
document.getElementById('telefonInput'),
document.getElementById('searchInput')
document.getElementById('nummerInput'),
document.getElementById('plzInput'),
document.getElementById('fachrichtungInput')
];
const resetIcons = [
document.getElementById('nameReset'),
document.getElementById('ortReset'),
document.getElementById('kundennummerReset'),
document.getElementById('fachrichtungReset'),
document.getElementById('telefonReset'),
document.getElementById('searchReset')
document.querySelector('.reset-icon[onclick="clearInput(\'q\')"]'),
document.querySelector('.reset-icon[onclick="clearInput(\'nameInput\')"]'),
document.querySelector('.reset-icon[onclick="clearInput(\'ortInput\')"]'),
document.querySelector('.reset-icon[onclick="clearInput(\'nummerInput\')"]'),
document.querySelector('.reset-icon[onclick="clearInput(\'plzInput\')"]'),
document.querySelector('.reset-icon[onclick="clearInput(\'fachrichtungInput\')"]')
];
searchInputs.forEach((input, index) => {
@@ -408,18 +365,14 @@
const name = urlParams.get('name');
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');
const plz = urlParams.get('plz');
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 (kundennummer) document.getElementById('nummerInput').value = kundennummer;
if (plz) document.getElementById('plzInput').value = plz;
if (name || ort || kundennummer || fachrichtung || telefon || query) {
if (name || ort || kundennummer || plz) {
searchCustomers();
}
});

View File

@@ -52,8 +52,8 @@
</div>
<footer class="footer">
<div class="footer-content">
Made with ❤️ and 🍺 by <a href="https://www.medisoftware.de" target="_blank" class="footer-link">medisoftware</a>
<div style="font-size: 0.8em;">Version: v1.2.0</div>
Proudly made with ❤️ and 🍺 by <a href="https://www.medisoftware.de" target="_blank" class="footer-link">medisoftware</a>
<div style="font-size: 0.8em;">Version: v1.2.3</div>
</div>
</footer>
</body>