diff --git a/app.py b/app.py index 5b1267b..7523d71 100644 --- a/app.py +++ b/app.py @@ -330,6 +330,33 @@ def sw(): def offline(): return render_template('offline.html') +@app.route('/api/search', methods=['POST']) +def api_search(): + """API-Endpunkt für die Kundensuche""" + try: + search_params = request.get_json() + results = search_customers(search_params) + return jsonify(results) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/customers') +def api_customers(): + """API-Endpunkt für alle Kunden""" + try: + conn = sqlite3.connect(DB_FILE) + c = conn.cursor() + c.execute('SELECT * FROM customers') + columns = [description[0] for description in c.description] + results = [] + for row in c.fetchall(): + customer = dict(zip(columns, row)) + results.append(customer) + conn.close() + return jsonify(results) + except Exception as e: + return jsonify({"error": str(e)}), 500 + def init_app(app): """Initialisiert die Anwendung mit allen notwendigen Einstellungen.""" with app.app_context(): diff --git a/static/css/styles.css b/static/css/styles.css index 1f54322..48cbe4b 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -253,4 +253,82 @@ body { .search-options .form-check-label { cursor: pointer; user-select: none; +} + +/* Offline-Indikator */ +.offline-indicator { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #ff9800; + color: white; + padding: 10px 20px; + border-radius: 5px; + display: flex; + align-items: center; + gap: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + z-index: 1000; +} + +.offline-icon { + font-size: 1.2em; +} + +/* Synchronisations-Status */ +.sync-status { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #4CAF50; + color: white; + padding: 10px 20px; + border-radius: 5px; + display: flex; + align-items: center; + gap: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + z-index: 1000; +} + +.sync-icon { + font-size: 1.2em; + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* Offline-Modus Styling */ +body.offline { + filter: grayscale(20%); +} + +body.offline .search-container { + opacity: 0.9; +} + +body.offline .footer::after { + content: "Offline-Modus"; + display: block; + text-align: center; + color: #ff9800; + font-size: 0.8em; + margin-top: 5px; +} + +/* Offline-Fallback für Bilder */ +.offline img { + opacity: 0.8; +} + +/* Verbesserte Sichtbarkeit im Offline-Modus */ +.offline .search-field input { + background-color: rgba(255, 255, 255, 0.9); +} + +.offline .result-count { + color: #666; } \ No newline at end of file diff --git a/static/js/db.js b/static/js/db.js new file mode 100644 index 0000000..8756981 --- /dev/null +++ b/static/js/db.js @@ -0,0 +1,144 @@ +// IndexedDB Konfiguration +const DB_NAME = 'mediCustomersDB'; +const DB_VERSION = 1; +const STORE_NAME = 'customers'; + +// Datenbank initialisieren +const initDB = () => { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION); + + request.onerror = () => { + console.error('Fehler beim Öffnen der Datenbank:', request.error); + reject(request.error); + }; + + request.onsuccess = () => { + console.log('Datenbank erfolgreich geöffnet'); + resolve(request.result); + }; + + request.onupgradeneeded = (event) => { + const db = event.target.result; + if (!db.objectStoreNames.contains(STORE_NAME)) { + const store = db.createObjectStore(STORE_NAME, { keyPath: 'nummer' }); + // Indizes für die Suche erstellen + store.createIndex('name', 'name', { unique: false }); + store.createIndex('ort', 'ort', { unique: false }); + store.createIndex('plz', 'plz', { unique: false }); + store.createIndex('fachrichtung', 'fachrichtung', { unique: false }); + } + }; + }); +}; + +// Kunden in IndexedDB speichern +const saveCustomers = async (customers) => { + const db = await initDB(); + const tx = db.transaction(STORE_NAME, 'readwrite'); + const store = tx.objectStore(STORE_NAME); + + return Promise.all(customers.map(customer => { + return new Promise((resolve, reject) => { + const request = store.put(customer); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); + })); +}; + +// Kunden aus IndexedDB suchen +const searchCustomersOffline = async (searchParams) => { + const db = await initDB(); + const tx = db.transaction(STORE_NAME, 'readonly'); + const store = tx.objectStore(STORE_NAME); + + return new Promise((resolve, reject) => { + const request = store.getAll(); + + request.onsuccess = () => { + let results = request.result; + + // Filtern basierend auf den Suchparametern + if (searchParams.q) { + const searchTerms = searchParams.q.toLowerCase().split(' '); + const operator = searchParams.operator || 'or'; + + results = results.filter(customer => { + const searchableText = `${customer.name} ${customer.ort} ${customer.nummer} ${customer.plz} ${customer.fachrichtung}`.toLowerCase(); + + if (operator === 'and') { + return searchTerms.every(term => searchableText.includes(term)); + } else { + return searchTerms.some(term => searchableText.includes(term)); + } + }); + } + + // Spezifische Feldsuche + if (searchParams.name) { + results = results.filter(c => c.name.toLowerCase().includes(searchParams.name.toLowerCase())); + } + if (searchParams.ort) { + results = results.filter(c => c.ort.toLowerCase().includes(searchParams.ort.toLowerCase())); + } + if (searchParams.nummer) { + results = results.filter(c => c.nummer.toString().includes(searchParams.nummer)); + } + if (searchParams.plz) { + results = results.filter(c => c.plz.includes(searchParams.plz)); + } + if (searchParams.fachrichtung) { + results = results.filter(c => c.fachrichtung.toLowerCase().includes(searchParams.fachrichtung.toLowerCase())); + } + + resolve(results); + }; + + request.onerror = () => { + reject(request.error); + }; + }); +}; + +// Synchronisationsstatus speichern +const syncStatus = { + lastSync: null, + isOnline: navigator.onLine +}; + +// Event Listener für Online/Offline Status +window.addEventListener('online', () => { + syncStatus.isOnline = true; + document.body.classList.remove('offline'); + synchronizeData(); +}); + +window.addEventListener('offline', () => { + syncStatus.isOnline = false; + document.body.classList.add('offline'); +}); + +// Daten mit dem Server synchronisieren +const synchronizeData = async () => { + if (!syncStatus.isOnline) return; + + try { + const response = await fetch('/api/customers'); + const customers = await response.json(); + await saveCustomers(customers); + syncStatus.lastSync = new Date(); + console.log('Daten erfolgreich synchronisiert'); + } catch (error) { + console.error('Fehler bei der Synchronisation:', error); + } +}; + +// Export der Funktionen +window.dbHelper = { + initDB, + saveCustomers, + searchCustomersOffline, + synchronizeData, + syncStatus +}; \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 61c6496..d38c4c4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -14,6 +14,7 @@ +