PageSourceSearch

https://app.medi-one.ai/modules/pages.js?v=20260908-projabc2-prema…raus1-thraus1-elprof3-kie2-tfneu1-kie3-bl1h

js medi-one.ai collected 2026-10-05 14:14:21 UTC 264,893 bytes, 3,999 lines download raw bytes

1// Import benötigter Module
2import { Auth } from './auth.js';
3import UI from './ui.js?v=20260820-dd1-imperr1-bann1-impauth1';
4// LAZY: import { Tasks } from "./tasks.js";
5// LAZY: import { Calendar } from './calendar.js?v=20260826-tdup2-badge1-vserie1-it1';
6// LAZY: import { CallSummary } from './callSummary.js';
7import { WhiteLabel } from './whitelabel.js';
8import { Users } from './users.js?v=20260626-merk1-imperr1-bann1-impauth1-kiteam1-bl1h';
9import { Employees } from './employees.js?v=20260826-empb1-zsui1-kteam1';
10import { Permissions } from './permissions.js?v=20260727-audiowording-merk1-impauth1-kteam1'; // Rechte & Zugriffe v2 + deep-link
11import { Contacts } from './contacts.js?v=20260807-noshow2-soap1-p5f1-rrev1-bill1-intake1-alarm1-tokfix1-dat1-mod1b-nam1-merk1--kal1-kont1--kd1-prax1-nsl1-nsl2-impauth1-tdup2-dnd1-kf1-kf3-kt3-it1';
12import ContactDetail from './contactDetail.js?v=20260807-noshow2-soap1-p5f1-rrev1-bill1-intake1-alarm1-tokfix1-dat1-mod1b-nam1-merk1--kal1-kont1--kd1-prax1-nsl1-nsl2-impauth1-tdup2-kf1-kf3-kteam1-kt3';
13import { MultiCall } from './multiCall.js';
14import { loadDashboardStats } from '../dashboard.js?v=20260826-resfix2';
15import { loadAnalyticsPage } from './analytics.js?v=20260710-volabel-merk1-ausw1-ausw2--sku1';
16import { loadRezeptionPage } from './rezeption.js?v=20260805-wlfix1-merk1-rez1-rez2-rezauto1';
17// AgentsDetail wird lazy geladen (siehe Verwendung in 'agent-details' case)
18// Global Softphone Manager — auto-bootstraps on first import,
19// initializes a headless AgentSoftphone for every agent the user owns
20// that has softphone enabled. This makes inbound calls work on EVERY page,
21// not just the agent detail page.
22import './globalSoftphone.js?v=20260801-wssfb-merk1--sp1--sp2---sp5---obh1-tst1-vj1-spb1-cw1-uebers1';
23import PhoneNumbers from './phoneNumbers.js?v=20260728-pndelete-merk1-btntext1-numtext1-impauth1-zp1-zp2-zp3-kt1-zalu1-nsauf1';
24// LAZY: import { SystemInterfaces } from './systemInterfaces.js';
25import { CalendarIntegration } from './calendarIntegration.js?v=20260826-caltrenn1-lf1-ok1-badge1';
26import { CRMIntegration } from './crmIntegration.js';
27import { SocialMediaIntegration } from './socialMediaIntegration.js';
28import GooglePeopleIntegration from './googlePeople.js';
29import { AUFGABE_OPTIONS, DEFAULT_INDUSTRIES } from './constants.js';
30import { emailReader } from './emailReader.js';
31import { Subscription } from './subscription.js?v=20260805-trialcard-crv2-merk1-impauth1-kl2-dblabo1-planw1-keinabo1-planw2-planw2b-rurl1-kiabo1-abomob1';
32import { Profile } from './profile.js?v=20260803-itemnotify-crv2-merk1-prax1-prax2--prax2--prax3-pk1b-avv1-nw1-mk2-impauth1-dblabo1-mail1-rurl1-abomob1-avvfirma1-dm1-dm2-it1';
33import { Language } from './language.js';
34import { openLeadStatusModal, createCustomLeadStatus } from '../app.js';
35import { doc, getDoc } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js';
36import { db } from '../firebase-init.js';
37// LAZY: import { CallAnalysis } from './callAnalysis.js?v=20260521-perfopt2-merk1-impauth1';
38// LAZY: import { ApiIntegration } from './apiIntegration.js';
39// LAZY: import { TicketSystem } from "./ticketSystem.js";
40import emailAiAgent from './emailAiAgent.js'; // Import the new module
41
42// praxis-modus-v1 (13.08.2026): true, wenn die Praxis-Ansicht gilt - entweder
43// ueber die mediOne-Domain (physio-edition) oder ueber die Betriebsart des
44// Mandanten (physio-menu, gesetzt in app.js aus /api/users/me).
45function _voPraxisAnsicht() {
46    const h = document.documentElement.classList, b = document.body.classList;
47    return h.contains('physio-edition') || b.contains('physio-edition')
48        || h.contains('physio-menu') || b.contains('physio-menu');
49}
50
51import { WorkflowUI } from './workflows.js'; // Import the new WorkflowUI module
52// LAZY: import { ScriptAnalysis } from './scriptAnalysis.js'; // Import the ScriptAnalysis module
53// LAZY: import { UserCallStats } from './userCallStats.js'; // Import User Call Stats module
54
55// +++ NEU: Singleton-Instanz für AgentsDetail +++
56let agentsDetailSingleton = null;
57
58// Kurze Übersetzungsfunktion für einfacheren Zugriff
59function t(text) {
60    return Language.translate(text);
61}
62
63export class Pages {
64    static currentPageModule = null;
65    
66    static isAdmin() {
67        return Auth.currentUser && Auth.currentUser.uid === 'FgYIPgcxuSRLyH1gw9WI0AZ07LO2';
68    }
69
70    static async isAdminOrReseller() {
71        try {
72            const isAdmin = await Auth.checkAdminAccess();
73            const isReseller = await Auth.checkResellerAccess();
74            return isAdmin || isReseller;
75        } catch (error) {
76            console.error('Error checking admin/reseller access:', error);
77            return false;
78        }
79    }
80    
81    static cleanupCurrentPage() {
82        // Clean up ads-reports charts specifically
83        if (window.adsReports && window.adsReports.cleanup) {
84            window.adsReports.cleanup();
85        }
86        
87        // Clean up any other page-specific resources
88        if (this.currentPageModule && this.currentPageModule.cleanup) {
89            this.currentPageModule.cleanup();
90        }
91    }
92
93    static async loadFromHash(hash) {
94        console.log('loadFromHash called with:', hash);
95        const mainContent = document.getElementById('mainContent');
96        if (!mainContent) {
97            console.error('Main content container not found');
98            return;
99        }
100
101        try {
102            // Remove the leading '#'
103            let route = hash.substring(1);
104            // Strip optional leading '/' (so '#/permissions' == '#permissions')
105            if (route.startsWith('/')) route = route.substring(1);
106            // Strip query-string — Deep-Link-Params (z.B. ?user=uid) werden
107            // auf Seitenebene via Permissions.parseHashParams() gelesen.
108            const qIdx = route.indexOf('?');
109            if (qIdx >= 0) route = route.substring(0, qIdx);
110
111            // Example route: contact-detail/<phoneNumber>
112            if (route.startsWith('contact-detail/')) {
113                const phoneNumberEncoded = route.split('/')[1];
114                if (phoneNumberEncoded) {
115                    const phoneNumber = decodeURIComponent(phoneNumberEncoded);
116                    console.log(`Hash route matched: contact-detail, Phone: ${phoneNumber}`);
117                    
118                    // --- MODIFIED: Get container reference and pass it to init ---
119                    mainContent.innerHTML = `<div id="contactDetailContainer" class="container-fluid"></div>`; // Leeren Container erstellen
120                    const containerElement = mainContent.querySelector('#contactDetailContainer'); // Get reference to the new container
121                    if (!containerElement) {
122                        console.error('Could not find #contactDetailContainer immediately after creating it.');
123                        UI.showError('Fehler beim Initialisieren der Kontaktdetailseite.');
124                        return; 
125                    }
126                    const contactDetail = new ContactDetail(null, phoneNumber); // Instanz mit Telefonnummer erstellen
127                    await contactDetail.init(containerElement); // Pass the container element to init
128                    // --- END MODIFIED ---
129                    
130                    this.updateActiveNavigation('contacts'); // Highlight contacts in nav
131                } else {
132                    console.warn('Phone number missing in hash route:', hash);
133                    // Optionally redirect to a default page or show an error
134                    await this.loadPage('dashboard');
135                }
136            } 
137            // Handle SIP Settings route
138            else if (route === 'sip-settings') {
139                console.log('Loading SIP Settings page');
140                // Import and render SIP settings with cache buster
141                const timestamp = new Date().getTime();
142                const { sipSettings } = await import(`./sipSettings.js?v=${timestamp}`);
143                sipSettings.renderSettingsPage();
144                this.updateActiveNavigation('sip-settings');
145            }
146            // Handle Ring Groups route (Hunt-Group / Ring-All)
147            else if (route === 'ring-groups') {
148                console.log('Loading Ring Groups page');
149                const timestamp = new Date().getTime();
150                const { ringGroups } = await import(`./ringGroups.js?v=${timestamp}`);
151                await ringGroups.renderPage();
152                this.updateActiveNavigation('ring-groups');
153            }
154            // Handle Phone Setup route (Telefon einrichten — eigene Seite, kein Modal)
155            else if (route === 'phone-setup') {
156                console.log('Loading Phone Setup page');
157                const timestamp = new Date().getTime();
158                const { PhoneSetupWizard } = await import(`./phoneSetupWizard.js?v=${timestamp}`);
159                await PhoneSetupWizard.renderPage();
160                this.updateActiveNavigation('phone-numbers');
161            }
162            // Handle Zeitplan route (Erreichbarkeit je Rufnummer -- eigene Seite, kein Modal)
163            // zeitplan-v4-seite: Muster wie contact-detail/<phone>. Die pnId steht im
164            // Hash, damit die Seite direkt verlinkbar ist und ein Neuladen sie wieder
165            // aufbaut -- beides ging mit dem Modal nicht.
166            else if (route.startsWith('zeitplan/')) {
167                const pnEncoded = route.split('/')[1];
168                if (pnEncoded) {
169                    const pnId = decodeURIComponent(pnEncoded);
170                    console.log('Loading Zeitplan page for', pnId);
171                    const timestamp = new Date().getTime();
172                    const { Zeitplan } = await import(`./zeitplanDialog.js?v=${timestamp}`);
173                    await Zeitplan.renderPage(pnId);
174                    this.updateActiveNavigation('phone-numbers');
175                } else {
176                    console.warn('Zeitplan-Route ohne Rufnummern-ID:', hash);
177                    await this.loadPage('phone-numbers');
178                }
179            }
180            // nummernauftritt-v1: Telefonnummer als eigene Seite (Begruessung und
181            // Rolle je Nummer brauchen zwei mehrzeilige Felder - dafuer war im
182            // Modal kein Platz). Muster wie zeitplan/<id>.
183            else if (route.startsWith('nummer/')) {
184                const pnEncoded = route.split('/')[1];
185                if (pnEncoded) {
186                    const pnId = decodeURIComponent(pnEncoded);
187                    console.log('Loading Nummer page for', pnId);
188                    const timestamp = new Date().getTime();
189                    const { NummerSeite } = await import(`./nummerSeite.js?v=${timestamp}`);
190                    await NummerSeite.renderPage(pnId);
191                    this.updateActiveNavigation('phone-numbers');
192                } else {
193                    console.warn('Nummer-Route ohne ID:', hash);
194                    await this.loadPage('phone-numbers');
195                }
196            }
197            // Add more hash routes here if needed
198            // else if (route.startsWith('some-other-page/')) { ... }
199            else {
200                console.log('Hash does not match known route, potentially loading page by name:', route);
201                // Fallback: Try loading page by name if hash matches a known page key
202                // This allows linking directly to pages like #contacts
203                await this.loadPage(route); 
204            }
205        } catch (error) {
206            console.error('Error loading page from hash:', hash, error);
207            UI.showError('Fehler beim Navigieren zur Seite: ' + error.message);
208            mainContent.innerHTML = '<div class="alert alert-danger">Fehler beim Laden der Seite.</div>';
209        }
210    }
211
212    /* kiteam-inhaber-v3b (24.09.2026): Darf dieser Nutzer "Mein KI-Team"
213       oeffnen? WORTGLEICH zur Menue-Regel in app.js (_kiTeamSichtbar):
214       Server-Urteil kiteam_inhaber (services/kiteam_inhaber), sonst
215       Inhaber-Rolle + Direktkunde, sonst Mitarbeiter mit Modulrecht.
216       Fail-closed: ohne Profil kein Zugang. */
217    static async _kiTeamErlaubt() {
218        try {
219            if (await Auth.checkAdminAccess()) return true;
220            const me = await Auth._fetchMe();
221            if (!me) return false;
222            if (me.kiteam_inhaber === true) return true;
223            const rolle = String(me.role || '').toLowerCase();
224            const direkt = !me.managed_by && rolle !== 'reseller' && me.whitelabel !== true;
225            if (['admin', 'user'].includes(rolle) && direkt) return true;
226            return rolle === 'employee' && (me.menu_permissions || {}).ai_team === true;
227        } catch (e) {
228            return false;
229        }
230    }
231
232    static async loadPage(page, params = {}) {
233        try {
234            console.log('Loading page:', page, params && Object.keys(params).length ? params : ''); // Debug log
235
236            // Alias: 'agents-detail' (z.B. aus dem KI-Chat) verhaelt sich wie 'agents'
237            // und uebergibt agentId via URL-Query, damit AgentsDetail.init() sie liest.
238            if (page === 'agents-detail') {
239                if (params && params.agentId) {
240                    const newSearch = '?page=agents&id=' + encodeURIComponent(params.agentId);
241                    window.history.pushState({}, '', window.location.pathname + newSearch + window.location.hash);
242                }
243                page = 'agents';
244            } else if (page === 'agents') {
245                /* editagent-v2: direkte Listen-Navigation (Sidebar) raeumt stale id aus der URL,
246                   damit AgentsDetail.init() die Liste zeigt statt des zuletzt geoeffneten Agenten */
247                if (new URLSearchParams(window.location.search).get('id')) {
248                    window.history.pushState({}, '', window.location.pathname + '?page=agents' + window.location.hash);
249                }
250            }
251            
252            // Clean up previous page resources before loading new page
253            this.cleanupCurrentPage();
254            
255            // Clear any previous error messages
256            UI.clearError();
257            
258            // Get main content container first
259            const mainContent = document.getElementById('mainContent');
260            if (!mainContent) {
261                throw new Error('Main content container not found');
262            }
263
264            // Überprüfe Admin-Berechtigung für geschützte Seiten
265            // ai-team-v1: vorerst nur Admin sichtbar (Michael 10.08.)
266            // kiteam-inhaber-v3b (24.09.2026): Diese Sperre fragte bis heute
267            // role === 'admin' (Auth.checkAdminAccess) - sie wurde bei der
268            // Freigabe fuer Kontoinhaber (kiteam-inhaber-v2, 14.09.) nicht
269            // mitgezogen. Folge: JEDER Nicht-Admin sah am Rechner "Zugriff
270            // verweigert", obwohl Menue und Server ihn durchliessen. Jetzt
271            // dieselbe Regel wie der Menuepunkt in app.js; der Server
272            // (routes/ai_team.py) bleibt die eigentliche Grenze.
273            if (['ai-team', 'ai-postfach', 'ai-dateien'].includes(page)
274                    && !(await Pages._kiTeamErlaubt())) {   // ai-team-menue-v1
275                mainContent.innerHTML = `
276                    <div class="container py-4">
277                        <div class="alert alert-danger" role="alert">
278                            <h4 class="alert-heading">Zugriff verweigert</h4>
279                            <p>„Mein KI-Team“ steht Ihnen nicht zur Verfügung. Der Kontoinhaber kann den Zugriff unter „Rechte &amp; Zugriffe“ freigeben.</p>
280                        </div>
281                    </div>
282                `;
283                return;
284            }
285
286            // kizugang-v1: Der KI-Zugang gilt fuer die ganze Anwendung.
287            // Serverseitig ist er ebenfalls admin-gegatet; das hier ist nur
288            // die freundliche Antwort statt eines rohen 403.
289            if (page === 'ki-zugang' && !(await Auth.checkAdminAccess())) {
290                mainContent.innerHTML = `
291                    <div class="container py-4">
292                        <div class="alert alert-danger" role="alert">
293                            <h4 class="alert-heading">Zugriff verweigert</h4>
294                            <p>Nur Administratoren k&ouml;nnen den KI-Zugang verwalten.</p>
295                        </div>
296                    </div>
297                `;
298                return;
299            }
300
301            if (page === 'admin-funnel' && !(await Auth.checkAdminAccess())) {
302                mainContent.innerHTML = `
303                    <div class="container py-4">
304                        <div class="alert alert-danger" role="alert">
305                            <h4 class="alert-heading">Zugriff verweigert</h4>
306                            <p>Nur Administratoren können das Funnel-Dashboard sehen.</p>
307                        </div>
308                    </div>
309                `;
310                return;
311            }
312
313            if (page === 'whitelabel' && !this.isAdmin()) {
314                mainContent.innerHTML = `
315                    <div class="container py-4">
316                        <div class="alert alert-danger" role="alert">
317                            <h4 class="alert-heading">Zugriff verweigert</h4>
318                            <p>Sie haben keine Berechtigung, diese Seite aufzurufen. Nur Administratoren können auf diese Seite zugreifen.</p>
319                        </div>
320                    </div>
321                `;
322                return;
323            }
324            
325            // Überprüfe Admin- oder Reseller-Berechtigung für Benutzerverwaltung
326            if (page === 'users' && !(await this.isAdminOrReseller())) {
327                mainContent.innerHTML = `
328                    <div class="container py-4">
329                        <div class="alert alert-danger" role="alert">
330                            <h4 class="alert-heading">Zugriff verweigert</h4>
331                            <p>Sie haben keine Berechtigung, diese Seite aufzurufen. Nur Administratoren und Reseller können auf die Benutzerverwaltung zugreifen.</p>
332                        </div>
333                    </div>
334                `;
335                return;
336            }
337            
338            // Überprüfe Berechtigung für Mitarbeiterverwaltung (Admin, Reseller und Tenant-Owner)
339            if (page === 'employees' || page === 'permissions') {
340                const userRole = await Auth.getUserRole();
341                if (userRole !== 'admin' && userRole !== 'user' && userRole !== 'reseller') {
342                    mainContent.innerHTML = `
343                        <div class="container py-4">
344                            <div class="alert alert-danger" role="alert">
345                                <h4 class="alert-heading">Zugriff verweigert</h4>
346                                <p>Sie haben keine Berechtigung, diese Seite aufzurufen. Nur Administratoren, Reseller und Tenant-Owner können auf die Rechteverwaltung zugreifen.</p>
347                            </div>
348                        </div>
349                    `;
350                    return;
351                }
352            }
353            
354            // Überprüfe Menüberechtigungen für andere Seiten
355            // WICHTIG: Bei Impersonation (Reseller/Admin Login bei Kunde) die Berechtigungsprüfung überspringen
356            // Quelle: /api/users/me liest die effektive 3-Tier-Kaskade aus Postgres.
357            // Direkter Firestore-Read wurde 2026-05-18 entfernt, weil Firestore-Projekt
358            // crm-v2-q3gwc8 abgeschaltet ist und FS-Cache veraltete Werte liefert.
359            const isImpersonating = localStorage.getItem('impersonation_token');
360            if (Auth.currentUser && !isImpersonating) {
361                let menuPermissions = null;
362                try {
363                    const token = await Auth.getIdToken();
364                    const meResp = await fetch('/api/users/me', { headers: { 'Authorization': `Bearer ${token}` }});
365                    if (meResp.ok) {
366                        const me = await meResp.json();
367                        menuPermissions = me.menu_permissions || {};
368                    }
369                } catch (e) {
370                    console.warn('Page-Guard: /api/users/me Fehler — Permission-Check übersprungen:', e);
371                }
372
373                if (menuPermissions) {
374                    // Prüfe Berechtigung basierend auf der Seite
375                    if (
376                        (page === 'agents' && menuPermissions.inbound === false) ||
377                        (page === 'inbox' && menuPermissions.inbox === false) ||
378                        (page === 'system-interfaces' && menuPermissions.system_interfaces === false) ||
379                        (page === 'multicall' && menuPermissions.outbound === false) ||
380                        (page === 'deals' && menuPermissions.deals === false) ||
381                        (page === 'rezeption' && menuPermissions.rezeption !== true) ||
382                        (page === 'verordnungen' && menuPermissions.verordnungen !== true) ||
383                        /* perm-page-rights-v1: eigene Rechte statt Kalender-Recht.
384                           Ohne dieses Gate bliebe die Seite ueber die Adresszeile
385                           erreichbar, auch wenn der Menuepunkt versteckt ist. */
386                        (page === 'no-show' && menuPermissions.no_show !== true) ||
387                        (page === 'reviews' && menuPermissions.reviews !== true) ||
388                        (page === 'anamnese-templates' && menuPermissions.anamnese !== true) ||
389                        (page === 'tickets' && menuPermissions.tickets !== true) ||
390                        /* adminperm-v1 */
391                        (page === 'users' && menuPermissions.users !== true) ||
392                        (page === 'employees' && menuPermissions.employees !== true) ||
393                        (page === 'permissions' && menuPermissions.permissions !== true) ||
394                        (page === 'heygen-avatars' && menuPermissions.avatars !== true) ||
395                        (page === 'video-rooms' && menuPermissions.video_rooms !== true) ||
396                        (page.startsWith('erp-') && menuPermissions.erp === false) ||
397                        /* perm-accounting-menu-v1: fail-closed wie die uebrigen Opt-in-Seiten.
398                           '=== false' liess die Seite durch, sobald der Schluessel fehlte. */
399                        (page.startsWith('accounting-') && menuPermissions.accounting !== true)
400                    ) {
401                        mainContent.innerHTML = `
402                            <div class="container py-4">
403                                <div class="alert alert-danger" role="alert">
404                                    <h4 class="alert-heading">Zugriff verweigert</h4>
405                                    <p>Sie haben keine Berechtigung, diese Seite aufzurufen.</p>
406                                </div>
407                            </div>
408                        `;
409                        return;
410                    }
411                }
412            }
413            
414            // Parse page name and query parameters (e.g., 'user-call-stats?userId=xxx')
415            const [pageName, queryString] = page.split('?');
416            const pageParams = new URLSearchParams(queryString || '');
417
418            // Update active navigation link
419            this.updateActiveNavigation(pageName);
420
421            // Clear previous content
422            mainContent.innerHTML = '';
423
424            switch (pageName) {
425                case 'dashboard':
426                    return await this.loadDashboard();
427                case 'analytics':
428                    return await this.loadAnalytics();
429                case 'rezeption':
430                    return await this.loadRezeption();
431                case 'documentation':
432                    return await this.loadDocumentation();
433                case 'calendar-integration':
434                    return await CalendarIntegration.configure();
435                case 'social-media-integration':
436                    return await SocialMediaIntegration.configure();
437                case 'deals':
438                    return await this.loadDeals();
439                case 'email':
440                case 'inbox':
441                    console.log('Loading email page');
442                    return await emailReader.init();
443                case 'booking':
444                    console.log('Loading booking page');
445                    const userId = window.location.pathname.split('/').pop();
446                    mainContent.innerHTML = `
447                        <div id="bookingContainer" class="container-fluid">
448                            <div id="calendar"></div>
449                            <div id="timeSlots" class="mt-4" style="display: none;">
450                                <h3>Verfügbare Zeiten</h3>
451                                <div id="timeSlotsList" class="list-group"></div>
452                            </div>
453                            <div id="bookingForm" class="mt-4" style="display: none;">
454                                <div class="card">
455                                    <div class="card-body">
456                                        <h3>Termindetails</h3>
457                                        <form id="appointmentForm">
458                                            <div class="mb-3">
459                                                <label for="name" class="form-label">
459Name</label>
460                                                <input type="text" class="form-control" id="name" required>
461                                            </div>
462                                            <div class="mb-3">
463                                                <label for="email" class="form-label">E-Mail</label>
464                                                <input type="email" class="form-control" id="email" required>
465                                            </div>
466                                            <div class="mb-3">
467                                                <label for="phone" class="form-label">Telefon</label>
468                                                <input type="tel" class="form-control" id="phone" required>
469                                            </div>
470                                            <div class="mb-3">
471                                                <label for="notes" class="form-label">Anmerkungen</label>
472                                                <textarea class="form-control" id="notes" rows="3"></textarea>
473                                            </div>
474                                            <button type="submit" class="btn btn-primary">${t('Termin buchen')}</button>
475                                        </form>
476                                    </div>
477                                </div>
478                            </div>
479                        </div>
480                    `;
481                    
482                    // Initialize the booking functionality
483                    const bookingScript = document.createElement('script');
484                    bookingScript.type = 'module';
485                    bookingScript.src = '/static/modules/booking.js';
486                    document.body.appendChild(bookingScript);
487                    break;
488                case 'contact-detail':
489                    console.log('Loading contact detail page'); // Debug log
490                    const contactId = new URLSearchParams(window.location.search).get('id');
491                    mainContent.innerHTML = `
492                        <div id="contactDetailContainer" class="container-fluid">
493                            <div class="text-center mt-5">
494                                <div class="spinner-border" role="status">
495                                    <span class="visually-hidden">Loading...</span>
496                                </div>
497                            </div>
498                        </div>
499                    `;
500                    
501                    // Ensure container exists before initializing
502                    const container = document.getElementById('contactDetailContainer');
503                    if (!container) {
504                        console.error('Contact detail container not found after creation');
505                        return;
506                    }
507                    
508                    const contactDetail = new ContactDetail(contactId);
509                    await contactDetail.init(container); // Pass the container element
510                    break;
511                case 'contacts':
512                    return await this.loadContacts();
513                    break;
514                case 'calendar':
515                    return await this.loadCalendar();
516                case 'calendar-settings':
517                    return await this.loadCalendarSettings();
518                case 'calendar-new-event':
519                    return await this.loadCalendarNewEvent();
520                case 'calendar-team':
521                    return await this.loadCalendarTeam();
522                case 'patient-notifications':      // pn-settings-ui-v1
523                    return await this.loadPatientNotifySettings();
524                case 'ai-team':                   // ai-team-ui-v1 (nur Admin)
525                    return await this.loadAiTeam();
526                case 'ai-postfach':               // ai-team-menue-v1
527                    return await this.loadAiTeam({ reiter: 'postfach' });
528                case 'ai-dateien':                // ai-team-menue-v1
529                    return await this.loadAiTeam({ reiter: 'dateien' });
530                case 'no-show':                   // no-show-ui-v1
531                    return await this.loadNoShow();
532                case 'reviews':                   // reviews-ui-v1
533                    return await this.loadReviews();
534                case 'anamnese-templates':        // anamnese-route-v1
535                    return await this.loadAnamneseTemplates();
536                case 'mytask':
537                case 'tasks':
538                    return await this.loadTasks();
539                case 'tickets':
540                    return await this.loadTickets();
541                case 'verordnungen':
542                    return await this.loadVerordnungen();
543                // Accounting Pages (Buchhaltung - nur Admin)
544                case 'accounting-dashboard':
545                    return await this.loadAccountingPage('dashboard');
546                case 'accounting-bookings':
547                    return await this.loadAccountingPage('bookings');
548                case 'accounting-receipts':
549                    try {
550                        const accRecMod = await import('./accountingReceipts.js?v=20260807-fix-merk1-belegid1-fbtn1');
551                        accRecMod.loadAccountingReceiptsPage();
552                    } catch (err) {
553                        console.error('Error loading Receipts:', err);
554                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
555                    }
556                    break;
557                case 'accounting-reports':
558                    try {
559                        const accRepMod = await import('./accountingReports.js?v=20260807-tokens-merk1');
560                        accRepMod.loadAccountingReportsPage();
561                    } catch (err) {
562                        console.error('Error loading Reports:', err);
563                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
564                    }
565                    break;
566                case 'accounting-tax':
567                    return await this.loadAccountingPage('tax');
568                case 'accounting-datev':
569                    return await this.loadAccountingPage('datev');
570                case 'accounting-settings':
571                    return await this.loadAccountingPage('settings');
572                // Google Ads Pages
573                case "ads-dashboard":
574                case "ads-setup":
575                case "ads-settings":
576                case "ads-campaigns":
577                case "ads-campaigns-new":  // New campaign creator with GPT-4
578                case "ads-keywords":
579                case "ads-alerts":
580                case "ads-automation":
581                case "ads-reports":
582                case "ads-bulk":
583                case "tracking-manager":
584                case "tracking-dashboard":
585                    return await this.loadAdsPage(page);
586                case 'call-detail':  // calldetail-v1
587                    return await this.loadCallDetail();
588                case 'call-summary':
589                    return await this.loadCallSummary();
590                case 'call-analysis':
591                    // Add the basic HTML structure for the call analysis page with Tabs
592                    mainContent.innerHTML = `
593<style>
594    /* Header-Styling für Konsistenz */
595    .callanalysis-header {
596        display: flex;
597        justify-content: space-between;
598        align-items: center;
599        padding: 1.5rem;
600        background: linear-gradient(135deg, #ffffff 0%, #f5f5f5 100%);
601        border-radius: 8px;
602        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
603        margin-bottom: 2rem;
604    }
605    
606    .callanalysis-header h1 {
607        margin: 0;
608        font-size: 1.5rem;
609        color: #2e2e2e;
610    }
611    
612    .callanalysis-header-actions {
613        display: flex;
614        gap: 0.5rem;
615        align-items: center;
616    }
617</style>
618<div class="container-fluid mt-4">
619    <div class="callanalysis-header">
620        <h1>${t('Gesprächsanalyse')}</h1>
621        <div class="callanalysis-header-actions">
622            <!-- Platzhalter für zukünftige Action-Buttons -->
623        </div>
624    </div>
625
626    <!-- Nav tabs -->
627    <ul class="nav nav-tabs mb-3" id="callAnalysisTabs" role="tablist">
628        <li class="nav-item" role="presentation">
629            <button class="nav-link active" id="inbound-tab" data-bs-toggle="tab" data-bs-target="#inbound-tab-pane" type="button" role="tab" aria-controls="inbound-tab-pane" aria-selected="true">${t('Eingehend')}</button>
630        </li>
631        <li class="nav-item" role="presentation">
632            <button class="nav-link" id="outbound-tab" data-bs-toggle="tab" data-bs-target="#outbound-tab-pane" type="button" role="tab" aria-controls="outbound-tab-pane" aria-selected="false">${t('Ausgehend')}</button>
633        </li>
634    </ul>
635
636    <!-- Tab panes -->
637    <div class="tab-content" id="callAnalysisTabContent">
638        <!-- Inbound Tab Pane -->
639        <div class="tab-pane fade show active" id="inbound-tab-pane" role="tabpanel" aria-labelledby="inbound-tab" tabindex="0">
640            <div class="card shadow mb-4">
641                <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
642                    <h6 class="m-0 font-weight-bold text-primary">${t('Analyse Eingehender Anrufe')}</h6>
643                </div>
644                <div class="card-body">
645                    <div id="inboundLoadingIndicator" class="text-center" style="display: none;">
646                        <div class="spinner-border text-primary" role="status">
647                            <span class="visually-hidden">Loading...</span>
648                        </div>
649                        <p>Lade eingehende Anrufdaten...</p>
650                    </div>
651                    <div id="inboundCallAnalysis">
652                        <!-- Inbound charts and data will be rendered here -->
653                         <p id="noInboundDataMessage" class="text-muted text-center" style="display: none;">Keine Daten für eingehende Anrufe gefunden.</p>
654                         <canvas id="inboundSentimentChart" style="display: none;"></canvas>
655                         <canvas id="inboundKeywordsChart" style="display: none;"></canvas>
656                         <div id="inboundSummaries" class="mt-3" style="max-height: 300px; overflow-y: auto;"></div>
657                         <!-- Placeholder for AI Analysis -->
658                         <div id="inboundChatGPTAnalysis" class="mt-4"></div>
659                         <div id="inboundMetricsCards" class="row mt-4"></div>
660                    </div>
661                </div>
662            </div>
663        </div>
664
665        <!-- Outbound Tab Pane -->
666        <div class="tab-pane fade" id="outbound-tab-pane" role="tabpanel" aria-labelledby="outbound-tab" tabindex="0">
667            <div class="card shadow mb-4">
668                <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
669                    <h6 class="m-0 font-weight-bold text-success">${t('Analyse Ausgehender Anrufe')}</h6>
670                </div>
671                <div class="card-body">
672                     <div id="outboundLoadingIndicator" class="text-center" style="display: none;">
673                        <div class="spinner-border text-success" role="status">
674                            <span class="visually-hidden">Loading...</span>
675                        </div>
676                        <p>Lade ausgehende Anrufdaten...</p>
677                    </div>
678                    <div id="outboundCallAnalysis">
679                        <!-- Outbound charts and data will be rendered here -->
680                         <p id="noOutboundDataMessage" class="text-muted text-center" style="display: none;">Keine Daten für ausgehende Anrufe gefunden.</p>
681                        <canvas id="outboundSentimentChart" style="display: none;"></canvas>
682                        <canvas id="outboundKeywordsChart" style="display: none;"></canvas>
683                        <div id="outboundSummaries" class="mt-3" style="max-height: 300px; overflow-y: auto;"></div>
684                        <!-- Placeholder for AI Analysis -->
685                        <div id="outboundChatGPTAnalysis" class="mt-4"></div>
686                        <div id="outboundMetricsCards" class="row mt-4"></div>
687                    </div>
688                </div>
689            </div>
690        </div>
691    </div>
692</div>
693                    `;
694                    return await this.loadCallAnalysis();
695                case 'whitelabel':
696                    return await this.loadWhitelabel();
697                case 'mitarbeiterverzeichnis': {
698                    const { PersonenVerzeichnis } = await import('./personenVerzeichnis.js?v=20260909-personen4-pvbreit5-pvux2');
699                    await PersonenVerzeichnis.load();
700                    break;
701                }
702                case 'ki-zugang': {
703                    const { KiZugang } = await import('./kiZugang.js?v=20260817-kiz3---kiz5---tok1---tok2---tok3---kizan1---kk1---kk2---kizenter1---kizkonto1-kizdirekt1');
704                    await KiZugang.load();
705                    break;
706                }
707                case 'admin-funnel': {
708                    const { AdminFunnel } = await import(`./adminFunnel.js?v=${Date.now()}`);
709                    await AdminFunnel.load();
710                    break;
711                }
712                case 'users':
713                    return await Users.load();
714                case 'user-call-stats': {
715                    const { UserCallStats } = await import("./userCallStats.js");
716                    // loadFromHash strippt den Query-String bevor loadPage aufgerufen wird,
717                    // daher fallen pageParams hier oft leer aus. Wir lesen die Params zusaetzlich
718                    // aus window.location.hash, wo der Browser den Original-Hash inkl. Query haelt.
719                    const _rawHash = window.location.hash || '';
720                    const _qStart = _rawHash.indexOf('?');
721                    const _hashParams = new URLSearchParams(_qStart >= 0 ? _rawHash.substring(_qStart + 1) : '');
722                    const statsUserId = pageParams.get('userId') || _hashParams.get('userId');
723                    const statsUserName = pageParams.get('userName') || _hashParams.get('userName');
724                    await UserCallStats.load(statsUserId, statsUserName);
725                    break;
726                }
727
728
729                case 'accounting-bank':
730                    try {
731                        const accBankMod = await import('./accountingBank.js?v=20260807-acctnames-merk1');
732                        accBankMod.loadAccountingBankPage();
733                    } catch (error) {
734                        console.error('Error loading Accounting Bank:', error);
735                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler beim Laden der Bank-Integration: ' + error.message + '</div>';
736                    }
737                    break;
738                case 'employees':
739                    return await Employees.load();
740                case 'permissions':
741                    return await Permissions.load();
742                case 'create-user':
743                    return await this.loadCreateUser();
744                case 'multicall':
745                    await MultiCall.load();
746                    break;
747                case 'script-analysis': {
748                    const { ScriptAnalysis } = await import('./scriptAnalysis.js');
749                    await ScriptAnalysis.load();
750                    break;
751                }
752                // sperrliste-v1: Gesperrte Anrufer (Wunsch T-2026-0061)
753                case 'blocked-callers': {
754                    const { BlockedCallers } = await import(`./blockedCallers.js?v=${Date.now()}`);
755                    mainContent.innerHTML = BlockedCallers.getPage();
756                    await BlockedCallers.initialize();
757                    break;
758                }
759                case 'phone-numbers':
760                    mainContent.innerHTML = PhoneNumbers.getPage();
761                    await PhoneNumbers.initialize();
762                    break;
763                // DEPRECATED: Tavus Avatar - ersetzt durch LiveAvatar
764                // case 'tavus-replicas':
765                //     const timestamp = new Date().getTime();
766                //     const { TavusReplicas } = await import(`./tavusReplicas.js?v=${timestamp}`);
767                //     if (!window.tavusReplicas) {
768                //         window.tavusReplicas = new TavusReplicas();
769                //     } else {
770                //         window.tavusReplicas.render();
771                //     }
772                //     break;
773
774                // liveavatar-removed-20260509
775                case 'heygen-avatars':
776                    // Load HeyGen Avatar Management page
777                    try {
778                        const response = await fetch('/static/heygen-avatars.html');
779                        if (!response.ok) {
780                            throw new Error('Failed to load HeyGen Avatars page');
781                        }
782                        const html = await response.text();
783                        mainContent.innerHTML = html;
784
785                        // Execute scripts in the loaded HTML (handles both src= and inline, module + classic)
786                        const scripts = mainContent.querySelectorAll('script');
787                        const loadScript = (script) => new Promise((resolve) => {
788                            const newScript = document.createElement('script');
789                            if (script.type) newScript.type = script.type;
790                            if (script.src) {
791                                newScript.src = script.src;
792                                newScript.onload = () => resolve();
793                                newScript.onerror = () => resolve();
794                            } else {
795                                newScript.textContent = script.textContent;
796                                resolve();
797                            }
798                            document.body.appendChild(newScript);
799                            if (!script.src) resolve();
800                        });
801                        // Load sequentially so libs (src) finish before inline init scripts run
802                        (async () => {
803                            for (const script of scripts) {
804                                await loadScript(script);
805                            }
806                        })();
807
808                        // Make the page visible after DOM update - use nextTick
809                        setTimeout(() => {
810                            const heygemPage = document.getElementById('heygen-avatars-page');
811                            if (heygemPage) {
812                                heygemPage.style.display = 'block';
813                                heygemPage.style.visibility = 'visible';
814                                heygemPage.style.opacity = '1';
815                                console.log('✅ HeyGen page made visible');
816                            } else {
817                                console.error('❌ heygen-avatars-page element not found');
818                            }
819                        }, 100);
820                    } catch (error) {
821                        console.error('Error loading HeyGen Avatars:', error);
822                        mainContent.innerHTML = `
823                            <div class="alert alert-danger">
824                                Fehler beim Laden der Avatar-Verwaltung: ${error.message}
825                            </div>
826                        `;
827                    }
828                    break;
829
830                case 'avatar-lab':
831                    // Avatar Lab - Admin only 2D avatar development
832                    await import('./avatarLab.js');
833                    if (window.avatarLab) {
834                        await window.avatarLab.init();
835                    }
836                    break;
837                case 'video-rooms': {
838                    const ts = Date.now();
839                    const mod = await import(`./videoRooms.js?v=${ts}`);
840                    await mod.initVideoRoomsPage();
841                    this.updateActiveNavigation('video-rooms');
842                    break;
843                }
844                case 'ring-groups': {
845                    const timestamp = new Date().getTime();
846                    const { ringGroups } = await import(`./ringGroups.js?v=${timestamp}`);
847                    await ringGroups.renderPage();
848                    this.updateActiveNavigation('ring-groups');
849                    break;
850                }
851                case 'softphone-page':
852                    // Full-page softphone view
853                    mainContent.innerHTML = `
854                        <div class="page-header d-flex justify-content-between align-items-center mb-4">
855                            <div>
856                                <h2><i class="fas fa-headset me-2" style="color:#7a9db5"></i>Softphone</h2>
857                                <p class="text-muted mb-0">Anrufe tätigen und empfangen direkt im Browser</p>
858                            </div>
859                        </div>
860                        <div class="row justify-content-center">
861                            <div class="col-md-5 col-lg-4">
862                                <div class="card shadow-sm" style="border-radius:16px;overflow:hidden">
863                                    <div class="card-body p-0" id="softphonePageContainer">
864                                        <p class="text-muted text-center py-5">Softphone wird geladen...</p>
865                                    </div>
866                                </div>
867                            </div>
868                        </div>
869                    `;
870                    // The FAB's panel & agent-select work globally,
871                    // this page just provides a dedicated entry point.
872                    // Auto-open the FAB panel if not already open
873                    try {
874                        const { softphoneFab: spFab }
874 = await import('./softphoneFab.js?v=20260714-regsync-merk1-wm1---obh1');
875                        setTimeout(() => { if (spFab && !spFab._open) spFab._openPanel(); }, 300);
876                    } catch (e) { console.warn('Softphone page init:', e); }
877                    break;
878                case 'pbx-dashboard':
879                    // Dynamically import the PBX Dashboard module
880                    try {
881                        const PBXDashboardModule = await import('./pbxDashboard.js');
882                        const PBXDashboard = PBXDashboardModule.default || PBXDashboardModule.PBXDashboard || PBXDashboardModule;
883
884                        if (PBXDashboard && PBXDashboard.render) {
885                            mainContent.innerHTML = PBXDashboard.render();
886                            PBXDashboard.init();
887                        } else {
888                            console.error('PBXDashboard module not properly loaded');
889                            mainContent.innerHTML = '<div class="alert alert-danger">Fehler beim Laden des PBX Dashboards</div>';
890                        }
891                    } catch (error) {
892                        console.error('Error loading PBX Dashboard:', error);
893                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler beim Laden des PBX Dashboards: ' + error.message + '</div>';
894                    }
895                    break;
896
897                // ============================================================================
898                // ERP MODULE PAGES
899                // ============================================================================
900                case 'erp-dashboard':
901                case 'erp-invoices':
902                case 'erp-products':
903                case 'erp-suppliers':
904                case 'erp-orders':
905                case 'erp-projects':
906                case 'erp-timesheets':
907                case 'erp-quotes':
908                case 'erp-delivery-notes':
909                case 'erp-recurring-invoices':
910                // erp-ausw-menue-v1: page.replace('erp-','') ergibt 'reports',
911                // navigateTo('reports') ruft loadReportsPage(). Kein Sonderfall noetig.
912                case 'erp-reports':
913                    // Load ERP module
914                    try {
915                        const erpVersion = "20260827_rechspalten2-verl1-kf3";
916                        mainContent.innerHTML = `
917                            <link rel="stylesheet" href="/static/css/erp.css?v=${erpVersion}">
918                            <div id="erp-content" class="erp-container">
919                                <div class="text-center mt-5">
920                                    <div class="spinner-border" role="status">
921                                        <span class="visually-hidden">Loading...</span>
922                                    </div>
923                                </div>
924                            </div>
925                        `;
926
927                        const erpModule = await import(`./erp.js?v=20260908-projbug1-${erpVersion}`);
928                        await erpModule.initERP();
929
930                        // Navigate to specific sub-module based on page
931                        const subModule = page.replace('erp-', '');
932                        if (subModule !== 'dashboard') {
933                            erpModule.navigateTo(subModule === 'orders' ? 'purchase-orders' : subModule);
934                        }
935                    } catch (error) {
936                        console.error('Error loading ERP module:', error);
937                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler beim Laden des ERP-Moduls: ' + error.message + '</div>';
938                    }
939                    break;
940
941                case 'agents':
942                    mainContent.innerHTML = `
943                        <div id="agentDetailContainer" class="container-fluid">
944                            <div class="text-center mt-5">
945                                <div class="spinner-border" role="status">
946                                    <span class="visually-hidden">Loading...</span>
947                                </div>
948                            </div>
949                        </div>
950                    `;
951                    
952                    // +++ NEU: Singleton-Logik +++
953                    if (!agentsDetailSingleton) {
954                        console.log("Creating new AgentsDetail singleton instance.");
955                        // Create an instance WITHOUT passing auth/db
956                        const { AgentsDetail } = await import('./agentsDetail.js?v=20260806-migrateoutline1-gpuoff2-gpuoff3-merk1--sp1--sp2--sp3---sp4---tc1---gem1---vpq1---vpq2---gflach1---obh1-anvor1-anvor2-g35t1-iwrt3-iwbt1-iwall1-iwdd1-sonic36-feablauf1-feablauf2-feablauf2b-feablauf2c-feablauf2d-feablauf2e-feablauf2f-feablauf3-piiredakt1-piiumzug1-piikat1-piip3-piihinw1-piiname1-gstt1-smallweg1-gsttaus1-sttboost1-boostph1-boostui1-umlaut1-fish6-tur
956n1-atwien1-atlock1-wienraus1-premat1-atprem1-promptan1b-uml1-agentvoll1-paloesch1-gptlive1-vpoa1-piiwirkt1-uebmob1-uebfix1-ansage1-prov1-gptvoice2-tfueber3-tfname1-hkarte2-gemlive30-bonsai1-bonsairaus1-dm7-klon1-import1-loesch1-elprof1-elprof2-ragz1-btraus1-thraus1-elprof3-kie2-tfneu1-kie3'); agentsDetailSingleton = new AgentsDetail(); window.agentsDetail = agentsDetailSingleton; 
957                    } else {
958                        console.log("Reusing existing AgentsDetail singleton instance.");
959                    }
960                    // Rufe init auf der Singleton-Instanz auf
961                    await agentsDetailSingleton.init(); 
962                    // +++ ENDE NEU +++
963                    break;
964                case 'crm':
965                    await CRMIntegration.configure();
966                    break;
967                case 'system-interfaces': {
968                    const { SystemInterfaces } = await import('./systemInterfaces.js?v=20260625-shopware-merk1-dd1-numtext1-impauth1-zp1-zp3-zalu1-bowl1-wamandant1-tokhinw1-umlfix1-waueb1-waemp1');
969                    return await SystemInterfaces.load();
970                }
971                case 'subscription':
972                    console.log('Loading subscription page');
973                    return await Subscription.loadSubscriptionPage();
974                case 'affiliate': {
975                    console.log('Loading affiliate page');
976                    const { Affiliate } = await import('./affiliate.js?v=20260611-assign-merk1');
977                    return await Affiliate.loadAffiliatePage();
978                }
979                case 'profile':
980                    console.log('Loading profile page');
981                    return await Profile.loadUserProfile();
982                case 'api-integration': {
983                    const { ApiIntegration } = await import('./apiIntegration.js');
984                    console.log('Loading API integration page');
985                    return await ApiIntegration.load();
986                }
987                case 'google-contacts-integration':
988                    console.log('Loading Google Contacts integration page');
989                    return await this.loadGoogleContactsIntegration();
990                case 'email-ai-agent': {
991                    // ai-team-emailagent-abloesung-v1: Der alte "E-Mail KI
992                    // Agent" hatte 0 Nutzer und kein Backend. Wer den alten
993                    // Link noch als Lesezeichen hat, landet mit einer
994                    // Erklaerung beim KI-Team, statt vor einer toten Seite.
995                    mainContent.innerHTML =
996                        '<div class="container-fluid py-4">'
997                        + '<div class="card"><div class="card-body">'
998                        + '<h5><i class="fas fa-users-gear me-2"></i>Der E-Mail-Assistent ist umgezogen</h5>'
999                        + '<p class="text-muted mb-3">Er ist jetzt Teil von <strong>Mein KI-Team</strong>. '
1000                        + 'Dort richten Sie das Postfach einmal unter „Zugänge“ ein und weisen es '
1001                        + 'den Mitarbeitern zu, die damit arbeiten sollen.</p>'
1002                        + '<a href="#ai-team" class="btn btn-primary">Zu „Mein KI-Team“</a>'
1003                        + '</div></div></div>';
1004                    break;
1005                }
1006                case 'automation-studio': // n8n Automation Studio
1007                    console.log('Loading Automation Studio (n8n)');
1008                    if (window.WorkflowAutomation) {
1009                        // Module is already loaded
1010                        window.WorkflowAutomation.init();
1011                        const automationContainer = window.WorkflowAutomation.render();
1012                        mainContent.innerHTML = '';
1013                        mainContent.appendChild(automationContainer);
1014                    } else {
1015                        // Module not loaded yet, show error or placeholder
1016                        mainContent.innerHTML = `
1017                            <style>
1018                                /* Header-Styling für Konsistenz */
1019                                .automation-header {
1020                                    display: flex;
1021                                    justify-content: space-between;
1022                                    align-items: center;
1023                                    padding: 1.5rem;
1024                                    background: linear-gradient(135deg, #ffffff 0%, #f5f5f5 100%);
1025                                    border-radius: 8px;
1026                                    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
1027                                    margin-bottom: 2rem;
1028                                }
1029                                
1030                                .automation-header h1 {
1031                                    margin: 0;
1032                                    font-size: 1.5rem;
1033                                    color: #2e2e2e;
1034                                }
1035                                
1036                                .automation-header-actions {
1037                                    display: flex;
1038                                    gap: 0.5rem;
1039                                    align-items: center;
1040                                }
1041                            </style>
1042                            <div class="container-fluid mt-4">
1043                                <div class="automation-header">
1044                                    <h1>${t('Automation Studio')}</h1>
1045                                    <div class="automation-header-actions">
1046                                        <!-- Platzhalter für zukünftige Action-Buttons -->
1047                                    </div>
1048                                </div>
1049                                <div class="alert alert-warning">
1050                                    <i class="fas fa-exclamation-triangle"></i> ${t('Das Automation Studio Modul konnte nicht geladen werden.')}
1051                                    <br>
1052                                    <small>${t('Bitte aktualisieren Sie die Seite oder kontaktieren Sie den Support.')}</small>
1053                                </div>
1054                            </div>
1055                        `;
1056                    }
1057                    break;
1058                case 'workflows': // Original workflows case
1059                    console.log('Loading Workflows page');
1060                    // Keep the existing workflow UI
1061                    mainContent.innerHTML = `
1062                        <div class="container-fluid mt-4">
1063                            <div id="workflow-management-section">
1064                                <!-- Workflow UI will be initialized here -->
1065                                <div class="text-center mt-5">
1066                                    <div class="spinner-border" role="status">
1067                                        <span class="visually-hidden">Loading...</span>
1068                                    </div>
1069                                    <p>Lade Workflow-Modul...</p>
1070                                </div>
1071                            </div>
1072                        </div>
1073                    `;
1074                    // Initialize the Workflow UI
1075                    // Ensure the container exists before initializing
1076                    const workflowContainer = document.getElementById('workflow-management-section');
1077                    if (workflowContainer) {
1078                        const workflowUI = new WorkflowUI();
1079                        // workflowUI.loadWorkflows(); // You might call initial loading functions here
1080                        // workflowContainer.innerHTML = ''; // REMOVED: Clear spinner once JS is ready (or handle loading within WorkflowUI)
1081                        // TODO: Add more sophisticated loading/rendering logic within WorkflowUI class
1082                        // workflowContainer.innerHTML = '<p>Workflow-Verwaltung (Platzhalter).</p>'; // REMOVED: Simple placeholder text for now
1083                    } else {
1084                        console.error('Workflow container #workflow-management-section not found after creation');
1085                    }
1086                    break;
1087                case 'shop-payment-providers':
1088                    try {
1089                        mainContent.innerHTML = `
1090                            <div class="container-fluid mt-4">
1091                                <div class="d-flex justify-content-between align-items-center mb-4">
1092                                    <h4 class="mb-0">Zahlungsanbieter</h4>
1093                                </div>
1094                                <div id="shopPpLoading" class="text-center py-5"><div class="spinner-border"></div><p class="text-muted mt-2">Lade Konfiguration…</p></div>
1095                                <div id="shopPpContent" style="display:none"></div>
1096                            </div>`;
1097
1098                        const PP = window.ShopPaymentProviders = {};
1099                        PP.TENANT = '';
1100                        PP._esc = (s) => (s == null ? '' : String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'));
1101                        PP._fmtDate = (iso) => { if (!iso) return 'nie'; try { return new Date(iso).toLocaleString('de-DE'); } catch(e) { return iso; } };
1102
1103                        PP.render = async () => {
1104                            try {
1105                                const token = await Auth.getIdToken();
1106                                const r = await fetch(`/api/shop/admin/payment-providers`, { headers: { 'Authorization': `Bearer ${token}` } });
1107                                if (!r.ok) throw new Error(`HTTP ${r.status}`);
1108                                const data = await r.json();
1109                                PP._data = data;
1110                                const s = data.stripe || {};
1111                                const inv = data.invoice || { enabled: true };
1112                                const content = document.getElementById('shopPpContent');
1113                                content.innerHTML = `
1114                                    <div class="row g-4">
1115                                        <div class="col-lg-8">
1116                                            <div class="card shadow-sm border-0 mb-4">
1117                                                <div class="card-header bg-white border-bottom">
1118                                                    <div class="d-flex align-items-center justify-content-between">
1119                                                        <div>
1120                                                            <h5 class="mb-0"><span style="display:inline-block;background:#635BFF;color:#fff;font-weight:700;padding:2px 10px;border-radius:4px;font-size:0.85em;letter-spacing:0.3px;vertical-align:middle">stripe</span></h5>
1121                                                            <small class="text-muted">Kreditkarte, Apple Pay, Google Pay, PayPal, Klarna, SEPA u.v.m.</small>
1122                                                        </div>
1123                                                        <div class="form-check form-switch">
1124                                                            <input class="form-check-input" type="checkbox" id="stripeEnabled" ${s.enabled ? 'checked' : ''}>
1125                                                            <label class="form-check-label ms-2" for="stripeEnabled">Aktiv</label>
1126                                                        </div>
1127                                                    </div>
1128                                                </div>
1129                                                <div class="card-body">
1130                                                    <div class="mb-3">
1131                                                        <label class="form-label">Modus</label>
1132                                                        <div class="btn-group w-100" role="group">
1133                                                            <input type="radio" class="btn-check" name="stripeMode" id="stripeModeTest" value="test" ${(s.mode||'test')==='test' ? 'checked' : ''}>
1134                                                            <label class="btn btn-outline-warning" for="stripeModeTest"><i class="bi bi-bug"></i> Testmodus</label>
1135                                                            <input type="radio" class="btn-check" name="stripeMode" id="stripeModeLive" value="live" ${s.mode==='live' ? 'checked' : ''}>
1136                                                            <label class="btn btn-outline-success" for="stripeModeLive"><i class="bi bi-shield-check"></i> Livemodus</label>
1137                                                        </div>
1138                                                    </div>
1139
1140                                                    <div class="mb-3">
1141                                                        <label for="stripePk" class="form-label">Veroeffentlichbarer Schluessel <span class="text-muted">(Publishable Key)</span></label>
1142                                                        <input type="text" class="form-control font-monospace" id="stripePk" placeholder="pk_test_… oder pk_live_…" value="${PP._esc(s.publishable_key||'')}">
1143                                                        <div class="form-text">Fuer das Frontend. Ungefaehrlich, darf oeffentlich sein.</div>
1144                                                    </div>
1145
1146                                                    <div class="mb-3">
1147                                                        <label for="stripeSk" class="form-label">Geheimer Schluessel <span class="text-muted">(Secret Key)</span></label>
1148                                                        <input type="password" class="form-control font-monospace" id="stripeSk" placeholder="${s.secret_key_set ? 'Gespeichert: '+PP._esc(s.secret_key_masked)+' - leer lassen zum Beibehalten' : 'sk_test_… oder sk_live_…'}">
1149                                                        <div class="form-text">
1149Wird verschluesselt in Firestore abgelegt. Leer lassen zum Beibehalten.</div>
1150                                                    </div>
1151
1152                                                    <div class="mb-3">
1153                                                        <label for="stripeWh" class="form-label">Webhook-Geheimnis <span class="text-muted">(Signing Secret)</span></label>
1154                                                        <input type="password" class="form-control font-monospace" id="stripeWh" placeholder="${s.webhook_secret_set ? 'Gespeichert: '+PP._esc(s.webhook_secret_masked)+' - leer lassen zum Beibehalten' : 'whsec_…'}">
1155                                                        <div class="form-text">Wird gebraucht um Webhook-Events zu verifizieren.</div>
1156                                                    </div>
1157
1158                                                    <div class="d-flex gap-2 justify-content-end border-top pt-3">
1159                                                        <button type="button" class="btn btn-outline-secondary" id="shopPpTestBtn"><i class="bi bi-plug"></i> Verbindung pruefen</button>
1160                                                        <button type="button" class="btn btn-primary" id="shopPpSaveBtn"><i class="bi bi-save"></i> Speichern</button>
1161                                                    </div>
1162                                                    <div id="shopPpTestResult" class="mt-3"></div>
1163                                                </div>
1164                                            </div>
1165
1166                                            <div class="card shadow-sm border-0 mb-4">
1167                                                <div class="card-header bg-white border-bottom">
1168                                                    <div class="d-flex align-items-center justify-content-between">
1169                                                        <div>
1170                                                            <h5 class="mb-0"><i class="bi bi-file-earmark-text" style="color:#6c757d"></i> Kauf auf Rechnung</h5>
1171                                                            <small class="text-muted">Kunde bezahlt nach Erhalt per Ueberweisung. Zahlungsausfall liegt bei dir.</small>
1172                                                        </div>
1173                                                        <div class="form-check form-switch">
1174                                                            <input class="form-check-input" type="checkbox" id="invoiceEnabled" ${inv.enabled ? 'checked' : ''}>
1175                                                            <label class="form-check-label ms-2" for="invoiceEnabled">Aktiv</label>
1176                                                        </div>
1177                                                    </div>
1178                                                </div>
1179                                                <div class="card-body small text-muted">
1180                                                    <div class="d-flex align-items-start gap-2">
1181                                                        <i class="bi bi-info-circle text-primary mt-1"></i>
1182                                                        <div>
1183                                                            Wenn aktiv, koennen Kunden ohne Online-Zahlung bestellen. Empfehlung: Bei unbekannter Kundschaft eher deaktivieren und stattdessen Klarna Pay-Later via Stripe anbieten (Ausfallrisiko liegt dann bei Klarna).
1184                                                            <div class="mt-2">Letzte Aenderung: <strong>${PP._fmtDate(inv.updated_at)}</strong></div>
1185                                                        </div>
1186                                                    </div>
1187                                                    <div class="d-flex justify-content-end border-top pt-3 mt-3">
1188                                                        <button type="button" class="btn btn-primary btn-sm" id="shopPpSaveInvoiceBtn"><i class="bi bi-save"></i> Speichern</button>
1189                                                    </div>
1190                                                </div>
1191                                            </div>
1192
1193                                            <div class="card shadow-sm border-0">
1194                                                <div class="card-header bg-white border-bottom">
1195                                                    <h6 class="mb-0"><i class="bi bi-wallet2"></i> Zahlungsmethoden via Stripe</h6>
1196                                                </div>
1197                                                <div class="card-body">
1198                                                    <p class="text-muted small mb-3">Diese Methoden werden <strong>automatisch</strong> im Checkout angezeigt, sobald du sie im <a href="https://dashboard.stripe.com/settings/payment_methods" target="_blank" rel="noopener">Stripe-Dashboard</a> aktivierst. Keine Code-Aenderung noetig.</p>
1199                                                    <div class="row g-2">
1200                                                        ${[
1201                                                            {n:'Karten', d:'Visa, Mastercard, Amex', i:'bi-credit-card'},
1202                                                            {n:'Apple Pay', d:'iOS/Safari, Domain-Verifikation noetig', i:'bi-apple'},
1203                                                            {n:'Google Pay', d:'Android, Chrome', i:'bi-google'},
1204                                                            {n:'PayPal', d:'via Stripe, EU/DE verfuegbar', i:'bi-paypal'},
1205                                                            {n:'Klarna', d:'Rechnung, Raten, Sofort', i:'bi-bag'},
1206                                                            {n:'SEPA-Lastschrift', d:'Banklastschrift', i:'bi-bank'},
1207                                                            {n:'Sofort', d:'Online-Banking DE/AT', i:'bi-lightning'},
1208                                                            {n:'Link', d:'Stripe 1-Click-Checkout', i:'bi-link-45deg'},
1209                                                        ].map(m => `
1210                                                            <div class="col-md-6">
1211                                                                <div class="border rounded p-2 d-flex align-items-start">
1212                                                                    <i class="bi ${m.i} me-2 mt-1 text-primary"></i>
1213                                                                    <div><strong class="d-block">${m.n}</strong><small class="text-muted">${m.d}</small></div>
1214                                                                </div>
1215                                                            </div>`).join('')}
1216                                                    </div>
1217                                                </div>
1218                                            </div>
1219                                        </div>
1220
1221                                        <div class="col-lg-4">
1222                                            <div class="card shadow-sm border-0 mb-4">
1223                                                <div class="card-header bg-white border-bottom"><h6 class="mb-0"><i class="bi bi-globe2"></i> Shop-Domain</h6></div>
1224                                                <div class="card-body">
1225                                                    <label for="shopPpShopHost" class="form-label small mb-1">Deine Storefront-Domain (ohne <code>https://</code>):</label>
1226                                                    <input type="text" class="form-control form-control-sm font-monospace" id="shopPpShopHost" placeholder="z.B. www.meinshop.de" value="${PP._esc(data.shop_host || '')}">
1227                                                    <div class="form-text small">Wird fuer die Webhook-URL und Stripe-Return-URLs verwendet. Aenderungen erst nach <em>Speichern</em> wirksam.</div>
1228                                                </div>
1229                                            </div>
1230
1231                                            <div class="card shadow-sm border-0 mb-4">
1232                                                <div class="card-header bg-white border-bottom"><h6 class="mb-0"><i class="bi bi-link-45deg"></i> Webhook-URL</h6></div>
1233                                                <div class="card-body">
1234                                                    <p class="small text-muted mb-2">Im Stripe-Dashboard unter <strong>Developers &rarr; Webhooks</strong> eintragen:</p>
1235                                                    ${data.webhook_url ? `
1236                                                    <div class="input-group input-group-sm">
1237                                                        <input type="text" class="form-control font-monospace" id="shopPpWebhookUrl" readonly value="${PP._esc(data.webhook_url)}">
1238                                                        <button class="btn btn-outline-secondary" id="shopPpWebhookCopy" title="Kopieren"><i class="bi bi-clipboard"></i></button>
1239                                                    </div>` : `
1240                                                    <div class="alert alert-warning small mb-0 py-2">
1241                                                        <i class="bi bi-exclamation-triangle"></i> Bitte zuerst oben eine <strong>Shop-Domain</strong> eintragen und speichern.
1242                                                    </div>`}
1243                                                    <div class="mt-2 small">
1244                                                        <div class="text-muted mb-1">Events die aktiviert sein muessen:</div>
1245                                                        <ul class="small mb-0 ps-3">
1246                                                            <li><code>payment_intent.succeeded</code></li>
1247                                                            <li><code>payment_intent.payment_failed</code></li>
1248                                                            <li><code>charge.refunded</code></li>
1249                                                        </ul>
1250                                                    </div>
1251                                                    <div class="mt-3 small text-muted">
1252                                                        Zuletzt erfolgreicher Webhook-Call: <strong>${PP._fmtDate(s.last_webhook_at)}</strong>
1253                                                    </div>
1254                                                </div>
1255                                            </div>
1256
1257                                            <div class="card shadow-sm border-0 mb-4">
1258                                                <div class="card-header bg-white border-bottom"><h6 class="mb-0"><i class="bi bi-info-circle"></i> Status</h6></div>
1259                                                <div class="card-body small">
1260                                                    <dl class="row mb-0">
1261                                                        <dt class="col-6 text-muted">Modus</dt><dd class="col-6">${s.mode === 'live' ? '<span class="badge bg-success">Live</span>' : '<span class="badge bg-warning text-dark">Test</span>'}</dd>
1262                                                        <dt class="col-6 text-muted">Aktiv</dt><dd class="col-6">${s.enabled ? '<span class="badge bg-success">Ja</span>' : '<span class="badge bg-secondary">Nein</span>'}</dd>
1263                                                        <dt class="col-6 text-muted">Secret Key</dt><dd class="col-6">${s.secret_key_set ? '<span class="text-success">&#10003; gesetzt</span>' : '<span class="text-danger">fehlt</span>'}</dd>
1264                                                        <dt class="col-6 text-muted">Webhook Secret</dt><dd class="col-6">${s.webhook_secret_set ? '<span class="text-success">&#10003; gesetzt</span>' : '<span class="text-danger">fehlt</span>'}</dd>
1265                                                        <dt class="col-6 text-muted">
1265Letzte Aenderung</dt><dd class="col-6">${PP._fmtDate(s.updated_at)}</dd>
1266                                                    </dl>
1267                                                </div>
1268                                            </div>
1269
1270                                            <div class="card shadow-sm border-0 mb-4">
1271                                                <div class="card-header bg-white border-bottom"><h6 class="mb-0"><i class="bi bi-apple"></i> Apple Pay</h6></div>
1272                                                <div class="card-body small">
1273                                                    <p class="mb-2 text-muted">Apple Pay braucht eine Domain-Verifikation. Stripe stellt im Dashboard unter <strong>Settings &rarr; Payment methods &rarr; Apple Pay</strong> eine Datei bereit, die unter <code>/.well-known/apple-developer-merchantid-domain-association</code> ausgeliefert werden muss.</p>
1274                                                    <a href="https://dashboard.stripe.com/settings/payment_methods/apple_pay" target="_blank" rel="noopener" class="btn btn-outline-dark btn-sm w-100"><i class="bi bi-box-arrow-up-right"></i> Apple Pay einrichten</a>
1275                                                </div>
1276                                            </div>
1277
1278                                            <div class="card shadow-sm border-0">
1279                                                <div class="card-header bg-white border-bottom"><h6 class="mb-0"><i class="bi bi-life-preserver"></i> Hilfe</h6></div>
1280                                                <div class="card-body small">
1281                                                    <ul class="mb-0 ps-3">
1282                                                        <li><a href="https://dashboard.stripe.com/apikeys" target="_blank" rel="noopener">Stripe API-Keys holen</a></li>
1283                                                        <li><a href="https://dashboard.stripe.com/webhooks" target="_blank" rel="noopener">Webhooks verwalten</a></li>
1284                                                        <li><a href="https://dashboard.stripe.com/settings/payment_methods" target="_blank" rel="noopener">Zahlungsmethoden aktivieren</a></li>
1285                                                    </ul>
1286                                                </div>
1287                                            </div>
1288                                        </div>
1289                                    </div>`;
1290                                document.getElementById('shopPpLoading').style.display = 'none';
1291                                content.style.display = 'block';
1292
1293                                const copyBtn = document.getElementById('shopPpWebhookCopy');
1294                                if (copyBtn) {
1295                                    copyBtn.addEventListener('click', () => {
1296                                        const el = document.getElementById('shopPpWebhookUrl');
1297                                        if (!el) return;
1298                                        el.select();
1299                                        navigator.clipboard.writeText(el.value).then(() => {
1300                                            if (window.UI && UI.showSuccess) UI.showSuccess('Webhook-URL kopiert');
1301                                        });
1302                                    });
1303                                }
1304
1305                                document.getElementById('shopPpSaveBtn').addEventListener('click', (e) => { e.preventDefault(); PP.save(e); });
1306                                document.getElementById('shopPpTestBtn').addEventListener('click', (e) => { e.preventDefault(); PP.test(); });
1307                                const invBtn = document.getElementById('shopPpSaveInvoiceBtn');
1308                                if (invBtn) invBtn.addEventListener('click', (e) => { e.preventDefault(); PP.save(e); });
1309                            } catch (e) {
1310                                document.getElementById('shopPpLoading').innerHTML = `<div class="alert alert-danger">Fehler beim Laden: ${PP._esc(e.message)}</div>`;
1311                            }
1312                        };
1313
1314                        PP.save = async (ev) => {
1315                            const btn = (ev && ev.currentTarget) || document.getElementById('shopPpSaveBtn');
1316                            const originalHtml = btn ? btn.innerHTML : '<i class="bi bi-save"></i> Speichern';
1317                            if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Speichere…'; }
1318                            try {
1319                                const invoiceEl = document.getElementById('invoiceEnabled');
1320                                const shopHostEl = document.getElementById('shopPpShopHost');
1321                                const shopHostVal = shopHostEl ? shopHostEl.value.trim().replace(/^https?:\/\//i, '').replace(/\/+$/, '') : '';
1322                                const payload = {
1323                                    stripe: {
1324                                        enabled: document.getElementById('stripeEnabled').checked,
1325                                        mode: document.querySelector('input[name="stripeMode"]:checked').value,
1326                                        publishable_key: document.getElementById('stripePk').value.trim(),
1327                                        secret_key: document.getElementById('stripeSk').value.trim(),
1328                                        webhook_secret: document.getElementById('stripeWh').value.trim(),
1329                                    },
1330                                    invoice: {
1331                                        enabled: invoiceEl ? invoiceEl.checked : true,
1332                                    },
1333                                    shop_host: shopHostVal,
1334                                };
1335                                console.log('[PP.save] PUT payload:', JSON.stringify(payload));
1336                                const token = await Auth.getIdToken();
1337                                const r = await fetch('/api/shop/admin/payment-providers', {
1338                                    method: 'PUT',
1339                                    headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
1340                                    body: JSON.stringify(payload),
1341                                });
1342                                const responseText = await r.text();
1343                                console.log('[PP.save] response', r.status, responseText);
1344                                if (!r.ok) throw new Error(`HTTP ${r.status}: ${responseText}`);
1345                                if (window.UI && UI.showSuccess) UI.showSuccess('Zahlungsanbieter-Konfiguration gespeichert');
1346                                await PP.render();
1347                            } catch (e) {
1348                                console.error('[PP.save] error', e);
1349                                alert('Fehler beim Speichern: ' + e.message);
1350                                if (btn) { btn.disabled = false; btn.innerHTML = originalHtml; }
1351                            }
1352                        };
1353
1354                        PP.test = async () => {
1355                            const resBox = document.getElementById('shopPpTestResult');
1356                            resBox.innerHTML = '<div class="alert alert-info mb-0"><span class="spinner-border spinner-border-sm me-2"></span>Pruefe Verbindung zu Stripe…</div>';
1357                            try {
1358                                const token = await Auth.getIdToken();
1359                                const r = await fetch('/api/shop/admin/payment-providers/test', {
1360                                    method: 'POST',
1361                                    headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
1362                                    body: JSON.stringify({}),
1363                                });
1364                                const data = await r.json();
1365                                if (data.ok) {
1366                                    resBox.innerHTML = `<div class="alert alert-success mb-0"><i class="bi bi-check-circle"></i> Verbindung OK <span class="badge ${data.mode==='live'?'bg-success':'bg-warning text-dark'}">${data.mode}</span><br><small>Account: <code>${PP._esc(data.account_id||'-')}</code>${data.business_name?' - '+PP._esc(data.business_name):''}${data.country?' ('+PP._esc(data.country)+')':''}${data.charges_enabled?' - Charges aktiv':''}</small></div>`;
1367                                } else {
1368                                    resBox.innerHTML = `<div class="alert alert-danger mb-0"><i class="bi bi-x-circle"></i> Fehler: ${PP._esc(data.error||'unknown')}${data.detail?'<br><small class="text-muted">'+PP._esc(data.detail)+'</small>':''}</div>`;
1369                                }
1370                            } catch (e) {
1371                                resBox.innerHTML = `<div class="alert alert-danger mb-0">Fehler: ${PP._esc(e.message)}</div>`;
1372                            }
1373                        };
1374
1375                        PP.render();
1376                    } catch (error) {
1377                        console.error('Error loading payment-providers page:', error);
1378                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + error.message + '</div>';
1379                    }
1380                    break;
1381                case 'shop-dashboard':
1382                case 'shop-orders':
1383                case 'shop-categories':
1384                    try {
1385                        const shopSub = page.replace('shop-', '');
1386                        if (shopSub === 'orders') {
1387                            mainContent.innerHTML = `
1388                                <div class="container-fluid mt-4">
1389                                    <div class="d-flex justify-content-between align-items-center mb-4">
1390                                        <h4 class="mb-0">Shop-Bestellungen</h4>
1391                                        <div class="d-flex gap-2">
1392                                            <button id="shopOrdersReload" class="btn btn-primary">
1393                                                <i class="fas fa-sync-alt"></i> Aktualisieren
1394                                            </button>
1395                                        </div>
1396                                    </div>
1397                                    <div id="shopOrdersBulkBar"></div>
1398                                    <div id="shopOrdersList">
1399                                        <div class="text-center py-5"><div class="spinner-border" role="status"></div><p class="text-muted mt-2">Lade Bestellungen…</p></div>
1400                                    </div>
1401                                </div>
1402                            `;
1403                            window.ShopOrdersAdmin = window.ShopOrdersAdmin || {};
1404                            const Shop = window.ShopOrdersAdmin;
1405                            Shop.TENANT = '';
1406                            Shop._orders = [];
1407                            Shop._fmtCents = (c, cur) => {
1408                                if (c == null) return '-';
1409                                return new Intl.NumberFormat('de-DE', { style:'currency', currency: cur || 'EUR' }).format(c/100);
1410                            };
1411                            Shop._fmtDate = (iso) => {
1412                                if (!iso) return '-';
1413                                try { return new Date(iso).toLocaleStr
1413ing('de-DE'); } catch(e){ return iso; }
1414                            };
1415                            Shop._statusBadge = (s) => {
1416                                const map = { draft: 'secondary', pending: 'warning', confirmed: 'info', shipped: 'primary', delivered: 'success', cancelled: 'danger' };
1417                                const labelMap = { draft: 'Entwurf', pending: 'Offen', confirmed: 'Bestätigt', shipped: 'Versendet', delivered: 'Zugestellt', cancelled: 'Storniert' };
1418                                return `<span class="badge bg-${map[s]||'secondary'}">${labelMap[s]||s||'-'}</span>`;
1419                            };
1420                            Shop._statusLabel = (s) => ({ draft: 'Entwurf', pending: 'Offen', confirmed: 'Bestätigt', shipped: 'Versendet', delivered: 'Zugestellt', cancelled: 'Storniert' }[s] || s || '-');
1421                            Shop._paymentBadge = (o) => {
1422                                const ps = o.payment_status;
1423                                const m = o.payment_method || 'invoice';
1424                                const provider = o.payment_provider ? ' via ' + o.payment_provider : '';
1425                                if (ps === 'paid') return `<span class="badge bg-success" title="Bezahlt${provider}"><i class="fas fa-check-circle"></i> Bezahlt</span>`;
1426                                if (ps === 'awaiting_payment') return `<span class="badge bg-warning text-dark" title="Wartet auf Zahlung${provider}"><i class="fas fa-hourglass-half"></i> Wartet</span>`;
1427                                if (ps === 'failed') return `<span class="badge bg-danger" title="Zahlung fehlgeschlagen${provider}"><i class="fas fa-times-circle"></i> Fehlgeschlagen</span>`;
1428                                if (ps === 'refunded') return `<span class="badge bg-secondary" title="Erstattet${provider}"><i class="fas fa-undo"></i> Erstattet</span>`;
1429                                if (m === 'invoice') return `<span class="badge bg-light text-dark border"><i class="fas fa-receipt"></i> Rechnung</span>`;
1430                                return `<span class="badge bg-light text-dark border">-</span>`;
1431                            };
1432                            Shop._paymentMethodLabel = (o) => {
1433                                const m = (o.payment_method || '').toLowerCase();
1434                                if (m === 'stripe') return 'Kreditkarte (Stripe)';
1435                                if (m === 'invoice') return 'Kauf auf Rechnung';
1436                                return m ? (m.charAt(0).toUpperCase() + m.slice(1)) : '–';
1437                            };
1438                            Shop._esc = (s) => (s == null ? '' : String(s)
1439                                .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'));
1440
1441                            // ----- Bulk-Selection -------------------------------------------------
1442                            Shop._selected = Shop._selected || new Set();
1443                            Shop._selectionMap = Shop._selectionMap || new Map();
1444                            Shop._wireRowCheckboxes = () => {
1445                                document.querySelectorAll('.shop-order-row-cb').forEach(cb => {
1446                                    cb.addEventListener('change', (ev) => {
1447                                        const id = ev.target.getAttribute('data-id');
1448                                        if (!id) return;
1449                                        if (ev.target.checked) {
1450                                            Shop._selected.add(id);
1451                                            const row = (Shop._orders || []).find(o => o.id === id);
1452                                            if (row) Shop._selectionMap.set(id, row);
1453                                        } else {
1454                                            Shop._selected.delete(id);
1455                                            Shop._selectionMap.delete(id);
1456                                        }
1457                                        Shop._renderBulkBar();
1458                                        Shop._syncMasterCb();
1459                                    });
1460                                });
1461                                const master = document.getElementById('shopOrdersMasterCb');
1462                                if (master) {
1463                                    master.addEventListener('change', (ev) => {
1464                                        const checked = ev.target.checked;
1465                                        (Shop._orders || []).forEac
1465h(o => {
1466                                            if (checked) {
1467                                                Shop._selected.add(o.id);
1468                                                Shop._selectionMap.set(o.id, o);
1469                                            } else {
1470                                                Shop._selected.delete(o.id);
1471                                                Shop._selectionMap.delete(o.id);
1472                                            }
1473                                        });
1474                                        document.querySelectorAll('.shop-order-row-cb').forEach(cb => { cb.checked = checked; });
1475                                        Shop._renderBulkBar();
1476                                    });
1477                                }
1478                                // Pre-fill selection-map fuer schon-ausgewaehlte Zeilen (nach Reload)
1479                                (Shop._orders || []).forEach(o => {
1480                                    if (Shop._selected.has(o.id)) Shop._selectionMap.set(o.id, o);
1481                                });
1482                                Shop._syncMasterCb();
1483                            };
1484                            Shop._syncMasterCb = () => {
1485                                const master = document.getElementById('shopOrdersMasterCb');
1486                                if (!master) return;
1487                                const total = (Shop._orders || []).length;
1488                                const checked = (Shop._orders || []).filter(o => Shop._selected.has(o.id)).length;
1489                                master.checked = total > 0 && checked === total;
1490                                master.indeterminate = checked > 0 && checked < total;
1491                            };
1492                            Shop._renderBulkBar = () => {
1493                                const bar = document.getElementById('shopOrdersBulkBar');
1494                                if (!bar) return;
1495                                const n = Shop._selected.size;
1496                                if (n === 0) { bar.innerHTML = ''; return; }
1497                                bar.innerHTML = `
1498                                    <div class="alert alert-info d-flex flex-wrap align-items-center gap-2 mb-3" style="position:sticky;top:0;z-index:1020;">
1499                                        <strong class="me-2"><i class="fas fa-check-square me-1"></i>${n} ausgewählt</strong>
1500                                        <div class="btn-group btn-group-sm">
1501                                            <button class="btn btn-outline-success" onclick="window.ShopOrdersAdmin.bulkConvertToInvoice()" title="Aus den Bestellungen Rechnungen erstellen">
1502                                                <i class="fas fa-file-invoice-dollar me-1"></i>Rechnungen erstellen
1503                                            </button>
1504                                            <div class="btn-group btn-group-sm">
1505                                                <button class="btn btn-outline-info" onclick="window.ShopOrdersAdmin.bulkCreateDeliveryNotesPdf()" title="Lieferscheine erstellen und als zusammengefügtes PDF herunterladen">
1506                                                    <i class="fas fa-truck me-1"></i>Lieferscheine drucken
1507                                                </button>
1508                                                <button class="btn btn-outline-info dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" type="button"></button>
1509                                                <ul class="dropdown-menu">
1510                                                    <li><a class="dropdown-item" href="#" onclick="event.preventDefault(); window.ShopOrdersAdmin.bulkCreateDeliveryNotes()" title="Nur erstellen, keine PDF">Nur erstellen (kein PDF)</a></li>
1511                                                </ul>
1512                                            </div>
1513                                            <button class="btn btn-outline-success" onclick="window.ShopOrdersAdmin.bulkMarkPaid()" title="Zahlung erhalten markieren">
1514                                                <i class="fas fa-euro-sign me-1"></i>Als bezahlt
1515                                            </button>
1516                                            <div class="btn-group btn-group-sm">
1517                                                <button class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown" type="button">
1518                                                    <i class="fas fa-flag me-1"></i>Status setzen
1519                                                </button>
1520                                                <ul class="dropdown-menu">
1521                                                    <li><a class="dropdown-item" href="#" onclick="event.preventDefault(); window.ShopOrdersAdmin.bulkMarkStatus('confirmed')">Bestätigt</a></li>
1522                                                    <li><a class="dropdown-item" href="#" onclick="event.preventDefault(); window.ShopOrdersAdmin.bulkMarkStatus('shipped')">Versendet</a></li>
1523                                                    <li><a class="dropdown-item" href="#" onclick="event.preventDefault(); window.ShopOrdersAdmin.bulkMarkStatus('delivered')">Zugestellt</a></li>
1524                                                    <li><a class="dropdown-item" href="#" onclick="event.preventDefault(); window.ShopOrdersAdmin.bulkMarkStatus('cancelled')">Storniert</a></li>
1525                                                </ul>
1526                                            </div>
1527                                            <button class="btn btn-outline-danger" onclick="window.ShopOrdersAdmin.bulkDelete()" title="Alle löschen">
1528                                                <i class="fas fa-tras
1528h me-1"></i>Löschen
1529                                            </button>
1530                                        </div>
1531                                        <button class="btn btn-link btn-sm ms-auto text-decoration-none" onclick="window.ShopOrdersAdmin.bulkClear()">
1532                                            <i class="fas fa-times me-1"></i>Auswahl aufheben
1533                                        </button>
1534                                    </div>`;
1535                            };
1536                            Shop.bulkClear = () => {
1537                                Shop._selected.clear();
1538                                Shop._selectionMap.clear();
1539                                document.querySelectorAll('.shop-order-row-cb').forEach(cb => { cb.checked = false; });
1540                                Shop._syncMasterCb();
1541                                Shop._renderBulkBar();
1542                            };
1543                            Shop._bulkSelectedIds = () => Array.from(Shop._selected);
1544                            Shop._bulkPostJson = async (action, payload) => {
1545                                const ids = Shop._bulkSelectedIds();
1546                                const token = await Auth.getIdToken();
1547                                const r = await fetch('/api/shop/admin/orders/bulk', {
1548                                    method: 'POST',
1549                                    headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
1550                                    body: JSON.stringify({ action, ids, payload: payload || {} })
1551                                });
1552                                if (!r.ok) {
1553                                    let msg;
1554                                    try { msg = (await r.json()).error || 'Fehler'; } catch(e){ msg = `HTTP ${r.status}`; }
1555                                    throw new Error(msg);
1556                                }
1557                                return r.json();
1558                            };
1559                            Shop._bulkToast = (kind, summary, extra) => {
1560                                const parts = [];
1561                                if (summary.ok) parts.push(`${summary.ok} ${kind}`);
1562                                if (summary.skipped) parts.push(`${summary.skipped} übersprungen`);
1563                                if (summary.failed) parts.push(`${summary.failed} fehlgeschlagen`);
1564                                const msg = (extra ? extra + ' · ' : '') + (parts.join(' · ') || 'Keine Aktion');
1565                                if (window.UI && summary.failed) UI.showError ? UI.showError(msg) : alert(msg);
1566                                else if (window.UI && UI.showSuccess) UI.showSuccess(msg);
1567                                else alert(msg);
1568                            };
1569                            Shop.bulkDelete = async () => {
1570                                const n = Shop._selected.size;
1571                                if (n === 0) return;
1572                                if (!confirm(`${n} Bestellung(en) wirklich löschen? Das kann nicht rückgängig gemacht werden.`)) return;
1573                                try {
1574                                    const data = await Shop._bulkPostJson('delete');
1575                                    Shop._bulkToast('gelöscht', data.summary);
1576                                    Shop.bulkClear();
1577                                    await Shop.render();
1578                                } catch (e) { alert('Fehler: ' + e.message); }
1579                            };
1580                            Shop.bulkConvertToInvoice = async () => {
1581                                const n = Shop._selected.size;
1582                                if (n === 0) return;
1583                                if (!confirm(`Aus ${n} Bestellung(en) Rechnungen erstellen? Bereits konvertierte werden übersprungen.`)) return;
1584                                try {
1585                                    const data = await Shop._bulkPostJson('convert_to_invoice');
1586                                    Shop._bulkToast('Rechnungen erstellt', data.summary);
1587                                    Shop.bulkClear();
1588                                    await Shop.render();
1589                                } catch (e) { alert('Fehler: ' + e.message); }
1590                            };
1591                            Shop.bulkMarkPaid = async () => {
1592                                const n = Shop._selected.size;
1593                                if (n === 0) return;
1594                                if (!confirm(`${n} Bestellung(en) als bezahlt markieren?`)) return;
1595                                try {
1596                                    const data = await Shop._bulkPostJson('mark_paid');
1597                                    Shop._bulkToast('als bezahlt markiert', data.summary);
1598                                    Shop.bulkClear();
1599                                    await Shop.render();
1600                                } catch (e) { alert('Fehler: ' + e.message); }
1601                            };
1602                            Shop.bulkMarkStatus = async (status) => {
1603                                const n = Shop._selected.size;
1604                                if (n === 0) return;
1605                                const labelMap = { confirmed: 'Bestätigt', shipped: 'Versendet', delivered: 'Zugestellt', cancelled: 'Storniert' };
1606                                const label = labelMap[status] || status;
1607                                if (!confirm(`${n} Bestellung(en) auf Status "${label}" setzen?`)) return;
1608                                try {
1609                                    const data = await Shop._bulkPostJson('mark_status', { status });
1610                                    Shop._bulkToast(`auf ${label}`, data.summary);
1611                                    Shop.bulkClear();
1612                                    await Shop.render();
1613                                } catch (e) { alert('Fehler: ' + e.message); }
1614                            };
1615                            Shop.bulkCreateDeliveryNotes = async () => {
1616                                const n = Shop._selected.size;
1617                                if (n === 0) return;
1618                                if (!confirm(`Aus ${n} Bestellung(en) Lieferscheine erstellen?`)) return;
1619                                try {
1620                                    const data = await Shop._bulkPostJson('create_delivery_note');
1621                                    Shop._bulkToast('Lieferscheine erstellt', data.summary);
1622                                    Shop.bulkClear();
1623                                } catch (e) { alert('Fehler: ' + e.message); }
1624                            };
1625                            Shop.bulkCreateDeliveryNotesPdf = async () => {
1626                                const n = Shop._selected.size;
1627                                if (n === 0) return;
1628                                if (n > 100) { alert('Maximal 100 Bestellungen pro PDF-Bundle'); return; }
1629                                if (!confirm(`${n} Lieferschein(e) erstellen und als zusammengefügtes PDF herunterladen?`)) return;
1630                                try {
1631                                    const ids = Shop._bulkSelectedIds();
1632                                    const token = await Auth.getIdToken();
1633                                    const r = await fetch('/api/shop/admin/orders/bulk/delivery-notes/pdf', {
1634                                        method: 'POST',
1635                                        headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
1636                                        body: JSON.stringify({ ids })
1637                                    });
1638                                    if (!r.ok) {
1639                                        let msg;
1640                                        try { msg = (await r.json()).error || 'Fehler'; } catch(e){ msg = `HTTP ${r.status}`; }
1641                                        throw new Error(msg);
1642                                    }
1643                                    const blob = await r.blob();
1644                                    const url = URL.createObjectURL(blob);
1645                                    const a = document.createElement('a');
1646                                    a.href = url;
1647                                    a.download = `lieferscheine_${new Date().toISOString().slice(0,10)}.pdf`;
1648                                    document.body.appendChild(a);
1649                                    a.click();
1650                                    a.remove();
1651                                    URL.revokeObjectURL(url);
1652                                    const errCount = parseInt(r.headers.get('X-Bulk-Errors') || '0', 10);
1653                                    if (errCount > 0) {
1654                                        if (window.UI && UI.showError) UI.showError(`PDF erstellt – ${errCount} Bestellung(en) fehlgeschlagen`);
1655                                        else alert(`PDF erstellt – ${errCount} Bestellung(en) fehlgeschlagen`);
1656                                    } else {
1657                                        if (window.UI && UI.showSuccess) UI.showSuccess(`Lieferscheine PDF (${n}) heruntergeladen`);
1658                                    }
1659                                    Shop.bulkClear();
1660                                } catch (e) { alert('Fehler beim PDF-Bundle: ' + e.message); }
1661                            };
1662                            Shop.createDeliveryNote = async (orderId) => {
1663                                const o = (Shop._orders || []).find(x => x.id === orderId) || {};
1664                                const label = o.order_number || orderId;
1665                                const hasExisting = !!o.delivery_note_id;
1666                                const promptMsg = hasExisting
1667                                    ? `Lieferschein ${o.delivery_note_number||''} für Bestellung ${label} drucken?`
1668                                    : `Lieferschein für Bestellung ${label} erstellen und PDF öffnen?`;
1669                                if (!confirm(promptMsg)) return;
1670                                try {
1671                                    const token = await Auth.getIdToken();
1672                                    const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(orderId)}/create-delivery-note`, {
1673                                        method: 'POST',
1674                                        headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
1675                                    });
1676                                    if (!r.ok) {
1677                                        let msg;
1678                                        try { msg = (await r.json()).error || 'Fehler'; } catch(e){ msg = `HTTP ${r.status}`; }
1679                                        throw new Error(msg);
1680                                    }
1681                                    const data = await r.json();
1682                                    const dn = data.delivery_note || {};
1683                                    const successMsg = data.already_exists
1684                                        ? `Lieferschein ${dn.delivery_note_number||''} (bestehend)`
1685                                        : `Lieferschein ${dn.delivery_note_number||''} erstellt`;
1686                                    if (window.UI && UI.showSuccess) UI.showSuccess(successMsg);
1687                                    if (dn.id) {
1688                                        const pdfUrl = `/api/erp/delivery-notes/${encodeURIComponent(dn.id)}/pdf`;
1689                                        const tok2 = await Auth.getIdToken();
1690                                        const pr = await fetch(pdfUrl, { headers: { 'Authorization': `Bearer ${tok2}` } });
1691                                        if (pr.ok) {
1692                                            const blob = await pr.blob();
1693                                            const url = URL.createObjectURL(blob);
1694                                            window.open(url, '_blank');
1695                                            setTimeout(() => URL.revokeObjectURL(url), 60000);
1696                                        }
1697                                    }
1698                                    if (!data.already_exists) await Shop.render();
1699                                } catch (e) { alert('Fehler beim Lieferschein: ' + e.message); }
1700                            };
1701
1702                            Shop.render = async () => {
1703                                const listEl = document.getElementById('shopOrdersList');
1704                                listEl.innerHTML = '<div class="text-center py-5"><div class="spinner-border" role="status"></div></div>';
1705                                try {
1706                                    const token = await Auth.getIdToken();
1707                                    const resp = await fetch(`/api/shop/admin/orders?limit=200`, {
1708                                        headers: { 'Authorization': `Bearer ${token}` }
1709                                    });
1710                                    if (!resp.ok) {
1711                                        const err = await resp.text();
1712                                        listEl.innerHTML = `<div class="alert alert-danger">Fehler ${resp.status}: ${err}</div>`;
1713                                        return;
1714                                    }
1715                                    const data = await resp.json();
1716                                    Shop._orders = data.orders || [];
1717                                    if (Shop._orders.length === 0) {
1718                                        listEl.innerHTML = '<div class="alert alert-info">Noch keine Bestellungen eingegangen.</div>';
1719                                        return;
1720                                    }
1721                                    const rows = Shop._orders.map(o => {
1722                                        const addrLine1 = Shop._esc(o.shipping_street || '');
1723                                        const addrLine2 = Shop._esc([o.shipping_postal_code, o.shipping_city].filter(
1723Boolean).join(' '));
1724                                        const addrHtml = (addrLine1 || addrLine2)
1725                                            ? `${addrLine1 ? addrLine1+'<br>' : ''}<small class="text-muted">${addrLine2||'-'}</small>`
1726                                            : '<span class="text-muted">-</span>';
1727                                        const trackCell = o.tracking_number
1728                                            ? (o.tracking_url
1729                                                ? `<a href="${Shop._esc(o.tracking_url)}" target="_blank" rel="noopener">${Shop._esc(o.tracking_number)}</a>`
1730                                                : Shop._esc(o.tracking_number))
1731                                            : '<span class="text-muted">-</span>';
1732                                        return `
1733                                        <tr data-id="${Shop._esc(o.id)}">
1734                                            <td class="text-center"><input type="checkbox" class="shop-order-row-cb" data-id="${Shop._esc(o.id)}" ${Shop._selected.has(o.id) ? 'checked' : ''}></td>
1735                                            <td><strong>${Shop._esc(o.order_number||o.id)}</strong><br><small class="text-muted">${Shop._fmtDate(o.created_at)}</small></td>
1736                                            <td>${(o.customer_company||o.shipping_company) ? `<strong>${Shop._esc(o.customer_company||o.shipping_company)}</strong><br>${Shop._esc(o.customer_name||'')}` : Shop._esc(o.customer_name||'-')}<br><small class="text-muted">${Shop._esc(o.customer_email||'')}</small></td>
1737                                            <td>${addrHtml}</td>
1738                                            <td class="text-center">${o.item_count||0}</td>
1739                                            <td class="text-end"><strong>${Shop._fmtCents(o.total_cents, o.currency)}</strong></td>
1740                                            <td>${Shop._statusBadge(o.status)}</td>
1741                                            <td>${Shop._paymentBadge(o)}</td>
1742                                            <td>${trackCell}</td>
1743                                            <td class="text-end">
1744                                                <div class="btn-group btn-group-sm">
1745                                                    <button class="btn btn-outline-secondary" onclick="window.ShopOrdersAdmin.view('${Shop._esc(o.id)}')" title="Anzeigen">
1746                                                        <i class="fas fa-eye"></i>
1747                                                    </button>
1748                                                    <button class="btn btn-outline-primary" onclick="window.ShopOrdersAdmin.edit('${Shop._esc(o.id)}')" title="Bearbeiten">
1749                                                        <i class="fas fa-edit"></i>
1750                                                    </button>
1751                                                    <button class="btn ${o.invoice_id ? 'btn-success' : 'btn-outline-success'}" onclick="window.ShopOrdersAdmin.convertToInvoice('${Shop._esc(o.id)}')" title="${o.invoice_id ? 'Rechnung öffnen: ' + Shop._esc(o.invoice_number||'') : 'Rechnung erstellen'}">
1752                                                        <i class="fas fa-file-invoice-dollar"></i>
1753                                                    </button>
1754                                                    <button class="btn ${o.delivery_note_id ? 'btn-info' : 'btn-outline-info'}" onclick="window.ShopOrdersAdmin.createDeliveryNote('${Shop._esc(o.id)}')" title="${o.delivery_note_id ? 'Lieferschein drucken: ' + Shop._esc(o.delivery_note_number||'') : 'Lieferschein erstellen + drucken'}">
1755                                                        <i class="fas fa-truck"></i>
1756                                                    </button>
1757                                                    <button class="btn btn-outline-danger" onclick="window.ShopOrdersAdmin.del('${Shop._esc(o.id)}')" title="Löschen">
1758                                                        <i class="fas fa-trash"></i>
1759                                                    </button>
1760                                                </div>
1761                                            </td>
1762                                        </tr>`;
1763                                    }).join('');
1764                                    listEl.innerHTML = `
1765                                        <div class="table-responsive">
1766                                            <table class="table table-hover" style="table-layout:fixed; width:100%;">
1767                                                <colgroup>
1768                                                    <col style="width:3%"><!-- Checkbox -->
1769                                                    <col style="width:9%"><!-- Bestellung -->
1770                                                    <col style="width:13%"><!-- Kunde -->
1771                                                    <col style="width:10%"><!-- Lieferanschrift -->
1772                                                    <col style="width:5%"><!-- Artikel -->
1773                                                    <col style="width:8%"><!-- Summe -->
1774                                                    <col style="width:9%"><!-- Status -->
1775                                                    <col style="width:9%"><!-- Zahlung -->
1776                                                    <col style="width:10%"><!-- Sendung -->
1777                                                    <col style="width:24%"><!-- Aktionen -->
1778                                                </colgroup>
1779                                                <thead>
1780                                                    <tr>
1781                                                        <th class="text-center">
1781<input type="checkbox" id="shopOrdersMasterCb" title="Alle auswählen"></th>
1782                                                        <th>Bestellung</th>
1783                                                        <th>Kunde</th>
1784                                                        <th>Lieferanschrift</th>
1785                                                        <th class="text-center">Artikel</th>
1786                                                        <th class="text-end">Summe</th>
1787                                                        <th>Status</th>
1788                                                        <th>Zahlung</th>
1789                                                        <th>Sendung</th>
1790                                                        <th class="text-end">Aktionen</th>
1791                                                    </tr>
1792                                                </thead>
1793                                                <tbody>${rows}</tbody>
1794                                            </table>
1795                                        </div>
1796                                        <div class="text-muted small mt-2">${data.count} Bestellungen</div>
1797                                    `;
1798                                    Shop._wireRowCheckboxes();
1799                                    Shop._renderBulkBar();
1800                                } catch (e) {
1801                                    listEl.innerHTML = `<div class="alert alert-danger">Fehler beim Laden: ${e.message}</div>`;
1802                                }
1803                            };
1804                            Shop._fetchOne = async (orderId) => {
1805                                const token = await Auth.getIdToken();
1806                                const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(orderId)}`, {
1807                                    headers: { 'Authorization': `Bearer ${token}` }
1808                                });
1809                                if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
1810                                const j = await r.json();
1811                                return j.order;
1812                            };
1813                            Shop._ensureModal = () => {
1814                                let m = document.getElementById('shopOrderModal');
1815                                if (m) return m;
1816                                m = document.createElement('div');
1817                                m.id = 'shopOrderModal';
1818                                m.className = 'modal fade';
1819                                m.tabIndex = -1;
1820                                m.innerHTML = `
1821                                    <div class="modal-dialog modal-lg modal-dialog-scrollable">
1822                                        <div class="modal-content">
1823                                            <div class="modal-header">
1824                                                <h5 class="modal-title" id="shopOrderModalTitle">Bestellung</h5>
1825                                                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
1826                                            </div>
1827                                            <div class="modal-body" id="shopOrderModalBody"></div>
1828                                            <div class="modal-footer" id="shopOrderModalFooter"></div>
1829                                        </div>
1830                                    </div>`;
1831                                document.body.appendChild(m);
1832                                return m;
1833                            };
1834                            Shop._openModal = () => {
1835                                const el = Shop._ensureModal();
1836                                const inst = bootstrap.Modal.getOrCreateInstance(el);
1837                                inst.show();
1838                                return inst;
1839                            };
1840                            Shop._closeModal = () => {
1841                                const el = document.getElementById('shopOrderModal');
1842                                if (!el) return;
1843                                const inst = bootstrap.Modal.getInstance(el);
1844                                if (inst) inst.hide();
1845                            };
1846                            Shop._itemsTable = (items) => {
1847                                if (!items || !items.length) return '<p class="text-muted">Keine Positionen.</p>';
1848                                const rows = items.map(it => {
1849                                    const qty = it.quantity || 0;
1850                                    const unit = it.unit_price_cents || 0;
1851                                    const line = it.line_total_cents != null ? it.line_total_cents : qty*unit;
1852                                    const name = Shop._esc(it.name || it.product_id || '-');
1853                                    const vl = it.variant_label ? `<br><small class="text-muted">${Shop._esc(it.variant_label)}</small>` : '';
1854                                    return `<tr>
1855                                        <td>${name}${vl}</td>
1856                                        <td class="text-center">${qty}</td>
1857                                        <td class="text-end">${Shop._fmtCents(unit)}</td>
1858                                        <td class="text-end"><strong>${Shop._fmtCents(line)}</strong></td>
1859                                    </tr>`;
1860                                }).join('');
1861                                return `
1862                                    <div class="table-responsive">
1863                                        <table class="table table-sm mb-0">
1864                                            <thead><tr>
1865                                                <th>Produkt</th>
1866                                                <th class="text-center" style="width:10%">Menge</th>
1867                                                <th class="text-end" style="width:18%">Einzelpreis</th>
1868                                                <th class="text-end" style="width:18%">Zwischensumme</th>
1869                                            </tr></thead>
1870                                            <tbody>${rows}</tbody>
1871                                        </table>
1872                                    </div>`;
1873                            };
1874                            Shop.view = async (orderId) => {
1875                                Shop._openModal();
1876                                document.getElementById('shopOrderModalTitle').textContent = 'Bestellung';
1877                                document.getElementById('shopOrderModalBody').innerHTML = '<div class="text-center py-4"><div class="spinner-border" role="status"></div></div>';
1878                                document.getElementById('shopOrderModalFooter').innerHTML = '';
1879                                try {
1880                                    const o = await Shop._fetchOne(orderId);
1881                                    document.getElementById('shopOrderModalTitle').textContent = `Bestellung ${o.order_number || o.id}`;
1882                                    const addrLine = [o.shipping_street, [o.shipping_postal_code, o.shipping_city].filter(Boolean).join(' '), o.shipping_country].filter(Boolean).map(Shop._esc).join('<br>');
1883                                    document.getElementById('shopOrderModalBody').innerHTML = `
1884                                        <div class="row g-3">
1885                                            <div class="col-md-6">
1886                                                <h6 class="text-muted small mb-1">Status</h6>
1887                                                <div>${Shop._statusBadge(o.status)}</div>
1888                                            </div>
1889                                            <div class="col-md-6">
1890                                                <h6 class="text-muted small mb-1">Eingegangen</h6>
1891                                                <div>${Shop._fmtDate(o.created_at)}</div>
1892                                            </div>
1893                                            <div class="col-md-6">
1894                                                <h6 class="text-muted small mb-1">Kunde</h6>
1895                                                ${(o.customer_company || o.shipping_company) ? `<div><strong>${Shop._esc(o.customer_company || o.shipping_company)}</strong></div><div class="small">${Shop._esc(o.customer_name || '-')}</div>` : `<div><strong>${Shop._esc(o.customer_name || '-')}</strong></div>`}
1896                                                <div class="text-muted small">${Shop._esc(o.customer_email || '')}</div>
1897                                                ${o.customer_phone ? `<div class="text-muted small">${Shop._esc(o.customer_phone)}</div>` : ''}
1898                                            </div>
1899                                            <div class="col-md-6">
1900                                                <h6 class="text-muted small mb-1">Lieferanschrift</h6>
1901                                                <div>${addrLine || '<span class="text-muted">-</span>'}</div>
1902                                            </div>
1903                                            <div class="col-md-6">
1904                                                <h6 class="text-muted small mb-1">Zahlung</h6>
1905                                                <div>
1905${Shop._esc(Shop._paymentMethodLabel(o))} ${Shop._paymentBadge(o)}</div>
1906                                                ${o.payment_ref ? `<div class="text-muted small mt-1"><code style="font-size:11px">${Shop._esc(o.payment_ref)}</code></div>` : ''}
1907                                            </div>
1908                                            <div class="col-md-6">
1909                                                <h6 class="text-muted small mb-1">Summe</h6>
1910                                                <div><strong>${Shop._fmtCents(o.total_cents, o.currency)}</strong></div>
1911                                            </div>
1912                                            ${o.tracking_number ? `
1913                                            <div class="col-md-6">
1914                                                <h6 class="text-muted small mb-1">Sendung</h6>
1915                                                <div>${Shop._esc(o.tracking_carrier || '')} ${o.tracking_url ? `<a href="${Shop._esc(o.tracking_url)}" target="_blank" rel="noopener">${Shop._esc(o.tracking_number)}</a>` : Shop._esc(o.tracking_number)}</div>
1916                                            </div>` : ''}
1917                                            ${o.estimated_delivery ? `
1918                                            <div class="col-md-6">
1919                                                <h6 class="text-muted small mb-1">Voraussichtliche Lieferung</h6>
1920                                                <div>${Shop._esc(o.estimated_delivery)}</div>
1921                                            </div>` : ''}
1922                                            ${o.note ? `
1923                                            <div class="col-12">
1924                                                <h6 class="text-muted small mb-1">Kunden-Notiz</h6>
1925                                                <div class="border rounded p-2 bg-light">${Shop._esc(o.note)}</div>
1926                                            </div>` : ''}
1927                                            ${o.internal_note ? `
1928                                            <div class="col-12">
1929                                                <h6 class="text-muted small mb-1">Interne Notiz</h6>
1930                                                <div class="border rounded p-2 bg-light">${Shop._esc(o.internal_note)}</div>
1931                                            </div>` : ''}
1932                                            <div class="col-12">
1933                                                <h6 class="text-muted small mb-1">Positionen</h6>
1934                                                ${Shop._itemsTable(o.items)}
1935                                            </div>
1936                                        </div>`;
1937                                    document.getElementById('shopOrderModalFooter').innerHTML = `
1938                                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
1939                                        <button type="button" class="btn btn-primary" onclick="window.ShopOrdersAdmin.edit('${Shop._esc(o.id)}')">
1940                                            <i class="fas fa-edit"></i> Bearbeiten
1941                                        </button>`;
1942                                } catch (e) {
1943                                    document.getElementById('shopOrderModalBody').innerHTML = `<div class="alert alert-danger">${Shop._esc(e.message)}</div>`;
1944                                }
1945                            };
1946                            Shop.edit = async (orderId) => {
1947                                Shop._closeModal();
1948                                const mc = document.getElementById('mainContent');
1949                                if (!mc) return;
1950                                mc.innerHTML = '<div class="container-fluid mt-4"><div class="text-center py-5"><div class="spinner-border" role="status"></div><p class="text-muted mt-2">Lade Bestellung…</p></div></div>';
1951                                try {
1952                                    const o = await Shop._fetchOne(orderId);
1953                                    const statuses = ['draft','pending','confirmed','shipped','delivered','cancelled'];
1954                                    const trackUrl = o.tracking_url || (o.tracking_carrier && o.tracking_number && o.tracking_carrier.toLowerCase().includes('dhl') ? `https://www.dhl.de/de/privatkunden/pakete-empfangen/verfolgen.html?piececode=${encodeURIComponent(o.tracking_number)}` : '');
1955                                    const hasInvoice = !!o.invoice_id;
1956                                    const payMethod = o.payment_method ? String(o.payment_method).charAt(0).toUpperCase() + String(o.payment_method).slice(1) : '–';
1957                                    mc.innerHTML = `
1958                                        <div class="container-fluid py-4" id="shopOrderEditPage">
1959                                            <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
1960                                                <div class="d-flex align-items-center">
1961                                                    <button class="btn btn-link text-decoration-none text-secondary p-0 me-3" id="shopOrderBackBtn" title="Zurück zur Bestellungsliste">
1962                                                        <i class="bi bi-arrow-left fs-5"></i>
1963                                                    </button>
1964                                                    <div>
1965                                                        <div class="d-flex align-items-center flex-wrap gap-2">
1966                                                            <h3 class="mb-0">Bestellung ${Shop._esc(o.order_number || o.id)}</h3>
1967                                                            ${Shop._statusBadge(o.status)}
1968                                                            ${hasInvoice ? `<span class="badge bg-light text-dark border"><i class="fas fa-file-invoice-dollar text-success me-1"></i>${Shop._esc(o.invoice_number||'Rechnung')}</span>` : ''}
1969                                                        </div>
1970                                                        <p class="text-muted small mb-0 mt-1">Eingegangen am ${Shop._fmtDate(o.created_at)}${o.source ? ' · Quelle: ' + Shop._esc(o.source) : ''}</p>
1971                                                    </div>
1972                                                </div>
1973                                                <div class="d-flex align-items-center gap-2">
1974                                                    <label class="form-label mb-0 me-1 small text-muted">Status:</label>
1975                                                    <select class="form-select form-select-sm" id="soeStatusTop" style="width:auto;min-width:150px;">
1976                                                        ${statuses.map(s => `<option value="${s}" ${o.status===s?'selected':''}>${Shop._statusLabel(s)}</option>`).join('')}
1977                                                    </select>
1978                                                    <button class="btn btn-outline-secondary btn-sm" id="shopOrderCancelBtn">Abbrechen</button>
1979                                                    <button class="btn btn-primary btn-sm" id="shopOrderSaveBtn"><i class="fas fa-save me-1"></i>Speichern</button>
1980                                                </div>
1981                                            </div>
1982                                            <form id="shopOrderEditForm">
1983                                                <input type="hidden" id="soeId" value="${Shop._esc(o.id)}">
1984                                                <input type="hidden" id="soeStatus" value="${Shop._esc(o.status||'pending')}">
1985                                                <div class="row g-3">
1986                                                    <div class="col-lg-8">
1987                                                        <div class="card mb-3 shadow-sm">
1988                                                            <div class="card-header bg-white border-bottom py-2 d-flex justify-content-between align-items-center">
1989                                                                <strong><i class="fas fa-box me-2 text-primary"></i>Artikel</strong>
1990                                                                <sp
1990an class="text-muted small">${(o.items||[]).length} ${(o.items||[]).length === 1 ? 'Position' : 'Positionen'}</span>
1991                                                            </div>
1992                                                            <div class="card-body p-0">
1993                                                                ${Shop._itemsTable(o.items)}
1994                                                            </div>
1995                                                        </div>
1996                                                        <div class="card mb-3 shadow-sm">
1997                                                            <div class="card-header bg-white border-bottom py-2">
1998                                                                <strong><i class="fas fa-truck me-2 text-primary"></i>Versand &amp; Tracking</strong>
1999                                                            </div>
2000                                                            <div class="card-body">
2001                                                                <div class="row g-3">
2002                                                                    <div class="col-md-4">
2003                                                                        <label class="form-label small text-muted">Versender</label>
2004                                                                        <input type="text" class="form-control" id="soeTrackCarrier" value="${Shop._esc(o.tracking_carrier||'')}" placeholder="DHL, DPD, UPS …" list="carrierList">
2005                                                                        <datalist id="carrierList"><option value="DHL"><option value="DPD"><option value="UPS"><option value="Hermes"><option value="GLS"><option value="Post"></datalist>
2006                                                                    </div>
2007                                                                    <div class="col-md-4">
2008                                                                        <label class="form-label small text-muted">Sendungsnummer</label>
2009                                                                        <input type="text" class="form-control" id="soeTrackNumber" value="${Shop._esc(o.tracking_number||'')}">
2010                                                                    </div>
2011                                                                    <div class="col-md-4">
2012                                                                        <label class="form-label small text-muted">Vsl. Lieferung</label>
2013                                                                        <input type="text" class="form-control" id="soeEta" value="${Shop._esc(o.estimated_delivery||'')}" placeholder="2026-04-20">
2014                                                                    </div>
2015                                                                    <div class="col-12">
2016                                                                        <label class="form-label small text-muted">Tracking-URL <span class="text-muted">(automatisch bei DHL)</span></label>
2017                                                                        <input type="url" class="form-control" id="soeTrackUrl" value="${Shop._esc(trackUrl)}" placeholder="https://...">
2018                                                                    </div>
2019                                                                </div>
2020                                                            </div>
2021                                                        </div>
2022                                                        <div class="card mb-3 shadow-sm">
2023                                                            <div class="card-header bg-white border-bottom py-2">
2024                                                                <strong><i class="fas fa-sticky-note me-2 text-primary"></i>Notizen</strong>
2025                                                            </div>
2026                                                            <div class="card-body">
2027                                                                <div class="mb-3">
2028                                                                    <label class="form-label small text-muted"><i class="fas fa-comment me-1"></i>Kunden-Notiz <span class="text-muted">(bei Bestellung übermittelt)</span></label>
2029                                                                    <textarea class="form-control" id="soeNote" rows="2" placeholder="—">${Shop._esc(o.note||'')}</textarea>
2030                                                                </div>
2031                                                                <div>
2032                                                                    <label class="form-label small text-muted"><i class="fas fa-lock me-1"></i>Interne Notiz <span class="text-muted">(nicht sichtbar für Kunden)</span></label>
2033                                                                    <textarea class="form-control" id="soeInternalNote" rows="3" placeholder="z.B. Lager
2033stand, Sonderanfragen …">${Shop._esc(o.internal_note||'')}</textarea>
2034                                                                </div>
2035                                                            </div>
2036                                                        </div>
2037                                                    </div>
2038                                                    <div class="col-lg-4">
2039                                                        <div class="card mb-3 shadow-sm">
2040                                                            <div class="card-header bg-white border-bottom py-2">
2041                                                                <strong><i class="fas fa-receipt me-2 text-primary"></i>Zusammenfassung</strong>
2042                                                            </div>
2043                                                            <div class="card-body">
2044                                                                <div class="d-flex justify-content-between small"><span class="text-muted">Zwischensumme</span><span>${Shop._fmtCents(o.subtotal_cents||0)}</span></div>
2045                                                                ${o.shipping_cost_cents ? `<div class="d-flex justify-content-between small mt-1"><span class="text-muted">Versand</span><span>${Shop._fmtCents(o.shipping_cost_cents)}</span></div>` : ''}
2046                                                                ${o.tax_cents ? `<div class="d-flex justify-content-between small mt-1"><span class="text-muted">USt.</span><span>${Shop._fmtCents(o.tax_cents)}</span></div>` : ''}
2047                                                                <hr class="my-2">
2048                                                                <div class="d-flex justify-content-between"><strong>Gesamt</strong><strong class="fs-5 text-primary">${Shop._fmtCents(o.total_cents||0)}</strong></div>
2049                                                                <hr class="my-2">
2050                                                                <div class="d-flex justify-content-between small mb-1"><span class="text-muted">Zahlungsart</span><span>${Shop._esc(Shop._paymentMethodLabel(o))}</span></div>
2051                                                                <div class="d-flex justify-content-between small mb-1"><span class="text-muted">Zahlungsstatus</span><span>${Shop._paymentBadge(o)}</span></div>
2052                                                                ${o.payment_ref ? `<div class="d-flex justify-content-between small mb-1"><span class="text-muted">Referenz</span><span><code style="font-size:11px">${Shop._esc(o.payment_ref)}</code></span></div>` : ''}
2053                                                                ${o.paid_at ? `<div class="d-flex justify-content-between small"><span class="text-muted">Bezahlt am</span><span>${Shop._fmtDate(o.paid_at)}</span></div>` : ''}
2054                                                                ${o.payment_status !== 'paid' ? `<button type="button" class="btn btn-outline-success btn-sm w-100 mt-2" id="shopOrderMarkPaidBtn"><i class="fas fa-check-circle me-1"></i>Als bezahlt markieren</button>` : ''}
2055                                                            </div>
2056                                                        </div>
2057                                                        <div class="card mb-3 shadow-sm">
2058                                                            <div class="card-header bg-white border-bottom py-2 d-flex justify-content-between align-items-center">
2059                                                                <strong><i class="fas fa-user me-2 text-primary"></i>Kunde</strong>
2060                                                            </div>
2061                                                            <div class="card-body">
2062                                                                <div class="mb-2">
2063                                                                    <label class="form-label small text-muted">Firma <span class="text-muted">(optional)</span></label>
2064                                                                    <input type="text" class="form-control form-control-sm" id="soeCustomerCompany" value="${Shop._esc(o.customer_company||o.shipping_company||'')}" placeholder="z.B. Muster GmbH">
2065                                                                </div>
2066                                                                <div class="mb-2">
2067                                                                    <label class="form-label small text-muted">Name</label>
2068                                                                    <input type="text" class="form-control form-control-sm" id="soeCustomerName" value="${Shop._esc(o.customer_name||'')}">
2069                                                                </div>
2070                                                                <div class="mb-2">
2071                                                                    <label class="form-label small text-muted">E-Mail</label>
2072                                                                    <input type="email" class="form-control form-control-sm" id="soeCustomerEmail" value="${Shop._esc(o.customer_email||'')}">
2073                                                                </div>
2074                                                                <div>
2075                                                                    <label class="form-label small text-muted">Telefon</label>
2076                                                                    <input type="text" class="form-control form-control-sm" id="soeCustomerPhone" value="${Shop._esc(o.customer_phone||'')}">
2077                                                                </div>
2078                                                            </div>
2079                                                        </div>
2080                                                        <div class="card mb-3 shadow-sm">
2081                                                            <div class="card-header bg-white border-bottom py-2">
2082                                                                <strong><i class="fas fa-map-marker-alt me-2 text-primary"></i>Lieferanschrift</strong>
2083                                                            </div>
2084                                                            <div class="card-body">
2085                                                                <div class="mb-2">
2086                                                                    <label class="form-label small text-muted">Straße &amp; Hausnr.</label>
2087                                                                    <input type="text" class="form-control form-control-sm" id="soeShipStreet" value="${Shop._esc(o.shipping_street||'')}">
2088                                                                </div>
2089                                                                <div class="row g-2 mb-2">
2090                                                                    <div class="col-4">
2091                                                                        <label class="form-label small text-muted">PLZ</label>
2092                                                                        <input type="text" class="form-control form-control-sm" id="soeShipPlz" value="${Shop._esc(o.shipping_postal_code||'')}">
2093                                                                    </div>
2094                                                                    <div class="col-8">
2095                                                                        <label class="form-label small text-muted">Ort</label>
2096                                                                        <input type="text" class="form-control form-control-sm" id="soeShipCity" value="${Shop._esc(o.shipping_city||'')}">
2097                                                                    </div>
2098                                                                </div>
2099                                                                <div>
2100                                                                    <label class="form-label small text-muted">Land</label>
2101                                                                    <select class="form-select form-select-sm" id="soeShipCountry">
2102                                                                        <option value="DE" ${(o.shipping_country||'DE')==='DE'?'selected':''}>Deutschland</option>
2103                                                                        <option value="AT" ${o.shipping_country==='AT'?'selected':''}>Österreich</option>
2104                                                                        <option value="CH" ${o.shipping_country==='CH'?'selected':''}>Schweiz</option>
2105                                                                    </select>
2106                                                                </div>
2107                                                            </div>
2108                                                        </div>
2109                                                        <div class="card mb-3 shadow-sm">
2110                                                            <div class="card-header bg-white border-bottom py-2">
2111                                                                <strong><i class="fas fa-bolt me-2 text-primary"></i>Aktionen</strong>
2112                                                            </div>
2113                                                            <div class="card-body d-grid gap-2">
2114                                                                <button type="button" class="btn ${hasInvoice ? 'btn-success' : 'btn-outline-success'} btn-sm" id="shopOrderInvoiceBtn" title="${hasInvoice ? 'Zur Rechnung ' + Shop._esc(o.invoice_number||'') : 'Aus Bestellung Rechnung erstellen'}">
2115                                                                    <i class="fas fa-file-invoice-dollar me-1"></i>
2116                                                                    ${hasInvoice ? 'Rechnung öffnen (' + Shop._esc(o.invoice_number||'') + ')' : 'Rechnung erstellen'}
2117                                                                </button>
2118                                                                <button type="button" class="btn btn-outline-dark btn-sm" id="shopOrderDeliveryBtn" ${hasInvoice ? '' : 'disabled'} title="${hasInvoice ? 'Lieferschein als PDF öffnen' : 'Lieferschein benötigt erstellte Rechnung'}">
2119                                                                    <i class="fas fa-truck me-1"></i>Lieferschein drucken
2120                                                                </button>
2121                                                                <button type="button" class="btn btn-outline-danger btn-sm" id="shopOrderDeleteBtn">
2122                                                                    <i class="fas fa-tras
2122h me-1"></i>Bestellung löschen
2123                                                                </button>
2124                                                            </div>
2125                                                        </div>
2126                                                    </div>
2127                                                </div>
2128                                            </form>
2129                                        </div>`;
2130                                    const backToList = () => Pages.loadPage('shop-orders');
2131                                    const syncStatus = () => { document.getElementById('soeStatus').value = document.getElementById('soeStatusTop').value; };
2132                                    document.getElementById('shopOrderBackBtn').addEventListener('click', backToList);
2133                                    document.getElementById('shopOrderCancelBtn').addEventListener('click', backToList);
2134                                    document.getElementById('shopOrderStatusTop') && document.getElementById('shopOrderStatusTop').addEventListener('change', syncStatus);
2135                                    document.getElementById('soeStatusTop').addEventListener('change', syncStatus);
2136                                    document.getElementById('shopOrderSaveBtn').addEventListener('click', () => { syncStatus(); Shop._save(o.id); });
2137                                    document.getElementById('shopOrderInvoiceBtn').addEventListener('click', () => {
2138                                        if (hasInvoice) { Pages.loadPage('erp-invoices'); return; }
2139                                        Shop.convertToInvoice(o.id);
2140                                    });
2141                                    const delivBtn = document.getElementById('shopOrderDeliveryBtn');
2142                                    if (delivBtn && hasInvoice) {
2143                                        delivBtn.addEventListener('click', async () => {
2144                                            try {
2145                                                const token = await Auth.getIdToken();
2146                                                const r = await fetch(`/api/erp/invoices/${encodeURIComponent(o.invoice_id)}/delivery-note.pdf`, { headers: { 'Authorization': `Bearer ${token}` } });
2147                                                if (!r.ok) throw new Error(`HTTP ${r.status}`);
2148                                                const blob = await r.blob();
2149                                                const url = URL.createObjectURL(blob);
2150                                                window.open(url, '_blank');
2151                                                setTimeout(() => URL.revokeObjectURL(url), 10000);
2152                                            } catch (err) { alert('Lieferschein konnte nicht geladen werden: ' + err.message); }
2153                                        });
2154                                    }
2155                                    const markPaidBtn = document.getElementById('shopOrderMarkPaidBtn');
2156                                    if (markPaidBtn) {
2157                                        markPaidBtn.addEventListener('click', async () => {
2158                                            if (!confirm('Bestellung als bezahlt markieren? Diese Aktion sollte nur verwendet werden wenn die Zahlung außerhalb des Systems eingegangen ist (z.B. Überweisung).')) return;
2159                                            markPaidBtn.disabled = true;
2160                                            markPaidBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>…';
2161                                            try {
2162                                                const token = await Auth.getIdToken();
2163                                                const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(o.id)}`, {
2164                                                    method: 'PUT',
2165                                                    headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
2166                                                    body: JSON.stringify({ payment_status: 'paid' })
2167                                                });
2168                                                if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
2169                                                if (window.UI && UI.showSuccess) UI.showSuccess('Bestellung als bezahlt markiert');
2170                                                Shop.edit(o.id); // Seite neu rendern
2171                                            } catch (err) {
2172                                                alert('Fehler: ' + err.message);
2173                                                markPaidBtn.disabled = false;
2174                                                markPaidBtn.innerHTML = '<i class="fas fa-check-circle me-1"></i>Als bezahlt markieren';
2175                                            }
2176                                        });
2177                                    }
2178                                    document.getElementById('shopOrderDeleteBtn').addEventListener('click', async () => {
2179                                        const label = o.order_number || o.id;
2180                                        if (!confirm(`Bestellung ${label} wirklich löschen? Das kann nicht rückgängig gemacht werden.`)) return;
2181                                        try {
2182                                            const token = await Auth.getIdToken();
2183                                            const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(o.id)}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } });
2184                                            if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
2185                                            if (window.UI && UI.showSuccess) UI.showSuccess('Bestellung gelöscht');
2186                                            Pages.loadPage('shop-orders');
2187                                        } catch (err) { alert('Fehler beim Löschen: ' + err.message); }
2188                                    });
2189                                } catch (e) {
2190                                    mc.innerHTML = `<div class="container-fluid mt-4"><div class="alert alert-danger">Fehler beim Laden der Bestellung: ${Shop._esc(e.message)}</div><button class="btn btn-outline-secondary" onclick="Pages.loadPage('shop-orders')"><i class="bi bi-arrow-left"></i> Zurück zur Liste</button></div>`;
2191                                }
2192                            };
2193                            Shop._save = async (orderId) => {
2194                                const btns = ['shopOrderSaveBtn','shopOrderSaveBtnBottom'].map(id => document.getElementById(id)).filter(Boolean);
2195                                btns.forEach(b => { b.disabled = true; b.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Speichert…'; });
2196                                try {
2197                                    const payload = {
2198                                        status: document.getElementById('soeStatus').value,
2199                                        customer_company: document.getElementById('soeCustomerCompany').value,
2200                                        customer_name: document.getElementById('soeCustomerName').value,
2201                                        customer_email: document.getElementById('soeCustomerEmail').value,
2202                                        customer_phone: document.getElementById('soeCustomerPhone').value,
2203                                        shipping_street: document.getElementById('soeShipStreet').value,
2204                                        shipping_postal_code: document.getElementById('soeShipPlz').value,
2205                                        shipping_city: document.getElementById('soeShipCity').value,
2206                                        shipping_country: document.getElementById('soeShipCountry').value,
2207                                        tracking_carrier: document.getElementById('soeTrackCarrier').value,
2208                                        tracking_number: document.getElementById('soeTrackNumber').value,
2209                                        tracking_url: document.getElementById('soeTrackUrl').value,
2210                                        estimated_delivery: document.getElementById('soeEta').value,
2211                                        note: document.getElementById('soeNote').value,
2212                                        internal_note: document.getElementById('soeInternalNote').value,
2213                                    };
2214                                    const token = await Auth.getIdToken();
2215                                    const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(orderId)}`, {
2216                                        method: 'PUT',
2217                                        headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
2218                                        body: JSON.stringify(payload)
2219                                    });
2220                                    if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
2221                                    if (window.UI && UI.showSuccess) UI.showSuccess('Bestellung gespeichert');
2222                                    Pages.loadPage('shop-orders');
2223                                } catch (e) {
2224                                    alert('Fehler beim Speichern: ' + e.message);
2225                                    btns.forEach(b => { b.disabled = false; b.innerHTML = '<i class="fas fa-save"></i> Speichern'; });
2226                                }
2227                            };
2228                            Shop.convertToInvoice = async (orderId) => {
2229                                const o = (Shop._orders || []).find(x => x.id === orderId) || {};
2230                                // Already converted → jump to invoice list
2231                                if (o.invoice_id) {
2232                                    if (confirm(`Für ${o.order_number || orderId} existiert bereits Rechnung ${o.invoice_number||''}.\n\nZu Rechnungen wechseln?`)) {
2233                                        Pages.loadPage('erp-invoices');
2234                                    }
2235                                    return;
2236                                }
2237                                const label = o.order_number || orderId;
2238                                if (!confirm(`Aus Bestellung ${label} eine Rechnung erstellen?`)) return;
2239                                try {
2240                                    const token = await Auth.getIdToken();
2241                                    const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(orderId)}/convert-to-invoice`, {
2242                                        method: 'POST',
2243                                        headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
2244                                    });
2245                                    if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
2246                                    const data = await r.json();
2247                                    const msg = data.already_converted
2248                                        ? `Rechnung bereits vorhanden: ${data.invoice_number}`
2249                                        : `Rechnung ${data.invoice_number} erstellt`;
2250                                    if (window.UI && UI.showSuccess) UI.showSuccess(msg);
2251                                    else alert(msg);
2252                                    await Shop.render();
2253                                    if (confirm(`${msg}\n\nJetzt zur Rechnungsübersicht wechseln?`)) {
2254                                        Pages.loadPage('erp-invoices');
2255                                    }
2256                                } catch (e) {
2257                                    alert('Fehler beim Erstellen der Rechnung: ' + e.message);
2258                                }
2259                            };
2260
2261                            Shop.del = async (orderId) => {
2262                                const o = (Shop._orders || []).find(x => x.id === orderId) || {};
2263                                const label = o.order_number || orderId;
2264                                if (!confirm(`Bestellung ${label} wirklich löschen? Das kann nicht rückgängig gemacht werden.`)) return;
2265                                try {
2266                                    const token = await Auth.getIdToken();
2267                                    const r = await fetch(`/api/shop/admin/orders/${encodeURIComponent(orderId)}`, {
2268                                        method: 'DELETE',
2269                                        headers: { 'Authorization': `Bearer ${token}` }
2270                                    });
2271                                    if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
2272                                    await Shop.render();
2273                                    if (window.UI && UI.showSuccess) UI.showSuccess('Bestellung gelöscht');
2274                                } catch (e) {
2275                                    alert('Fehler beim Löschen: ' + e.message);
2276                                }
2277                            };
2278                            document.getElementById('shopOrdersReload').addEventListener('click', () => Shop.render());
2279                            Shop.render();
2280                        } else if (shopSub === 'dashboard') {
2281                            mainContent.innerHTML = `
2282                                <div class="container-fluid mt-4">
2283                                    <div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
2284                                        <h4 class="mb-0">
2284Shop-Übersicht</h4>
2285                                        <div class="d-flex gap-2 align-items-center">
2286                                            <div class="btn-group" role="group" id="shopDashRange">
2287                                                <button type="button" class="btn btn-outline-secondary btn-sm" data-range="7">7 Tage</button>
2288                                                <button type="button" class="btn btn-primary btn-sm" data-range="30">30 Tage</button>
2289                                                <button type="button" class="btn btn-outline-secondary btn-sm" data-range="90">90 Tage</button>
2290                                            </div>
2291                                            <button id="shopDashReload" class="btn btn-primary" title="Aktualisieren">
2292                                                <i class="fas fa-sync-alt"></i>
2293                                            </button>
2294                                        </div>
2295                                    </div>
2296                                    <div id="shopDashBody">
2297                                        <div class="text-center py-5"><div class="spinner-border" role="status"></div><p class="text-muted mt-2">Lade Kennzahlen…</p></div>
2298                                    </div>
2299                                </div>
2300                            `;
2301
2302                            window.ShopDashAdmin = window.ShopDashAdmin || {};
2303                            const SD = window.ShopDashAdmin;
2304                            SD.TENANT = '';
2305                            SD.range = SD.range || 30;
2306                            SD._charts = SD._charts || {};
2307
2308                            SD._fmtCents = (c, cur) => {
2309                                if (c == null) return '–';
2310                                return new Intl.NumberFormat('de-DE', { style:'currency', currency: cur || 'EUR' }).format((c||0)/100);
2311                            };
2312                            SD._fmtDate = (iso) => {
2313                                if (!iso) return '–';
2314                                try {
2315                                    return new Date(iso).toLocaleString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit' });
2316                                } catch(e) { return iso; }
2317                            };
2318                            SD._fmtDay = (iso) => {
2319                                try {
2320                                    const d = new Date(iso + 'T00:00:00Z');
2321                                    return d.toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit' });
2322                                } catch(e) { return iso; }
2323                            };
2324                            SD._statusBadge = (s) => {
2325                                const map = {
2326                                    'paid': 'bg-success',
2327                                    'confirmed': 'bg-success',
2328                                    'delivered': 'bg-success',
2329                                    'shipped': 'bg-info',
2330                                    'processing': 'bg-primary',
2331                                    'pending': 'bg-warning text-dark',
2332                                    'awaiting_payment': 'bg-warning text-dark',
2333                                    'cancelled': 'bg-secondary',
2334                                    'canceled': 'bg-secondary',
2335                                    'refunded': 'bg-secondary',
2336                                    'failed': 'bg-danger',
2337                                };
2338                                const cls = map[(s||'').toLowerCase()] || 'bg-secondary';
2339                                return `<span class="badge ${cls}">${s || '–'}</span>`;
2340                            };
2341                            SD._pct = (v) => {
2342                                if (v == null) return '<span class="text-muted">–</span>';
2343                                const cls = v >= 0 ? 'text-success' : 'text-danger';
2344                                const arrow = v >= 0 ? '▲' : '▼';
2345                                return `<span class="${cls} small fw-semibold">${arrow} ${Math.abs(v).toFixed(1)}%</span>`;
2346                            };
2347
2348                            SD._renderKpis = (d) => {
2349                                const k = d.kpis || {};
2350                                const range = d.range_days || 30;
2351                                return `
2352                                    <div class="row g-3 mb-4">
2353                                        <div class="col-md-6 col-lg-3">
2354                                            <div class="card border-0 shadow-sm h-100">
2355                                                <div class="card-body">
2356                                                    <div class="d-flex align-items-center text-muted small mb-2">
2357                                                        <i class="bi bi-bag-check me-2"></i>Bestellungen heute
2358                                                    </div>
2359                                                    <div class="display-6 fw-bold mb-1">${k.orders_today ?? 0}</div>
2360                                                    <div class="text-muted small">Umsatz heute: <span class="fw-semibold text-dark">${SD._fmtCents(k.revenue_today_cents)}</span></div>
2361                                                </div>
2362                                            </div>
2363                                        </div>
2364                                        <div class="col-md-6 col-lg-3">
2365                                            <div class="card border-0 shadow-sm h-100">
2366                                                <div class="card-body">
2367                                                    <div class="d-flex align-items-center text-muted small mb-2">
2368                                                        <i class="bi bi-hourglass-split me-2"></i>Wartet auf Zahlung
2369                                                    </div>
2370                                                    <div class="display-6 fw-bold mb-1">${k.pending_count ?? 0}</div>
2371                                                    <div class="text-muted small">Offen: <span class="fw-semibold text-dark">${SD._fmtCents(k.pending_cents)}</span></div>
2372                                                </div>
2373                                            </div>
2374                                        </div>
2375                                        <div class="col-md-6 col-lg-3">
2376                                            <div class="card border-0 shadow-sm h-100">
2377                                                <div class="card-body">
2378                                                    <div class="d-flex align-items-center text-muted small mb-2">
2379                                                        <i class="bi bi-cash-coin me-2"></i>Umsatz letzte ${range} Tage
2380                                                    </div>
2381                                                    <div class="display-6 fw-bold mb-1">${SD._fmtCents(k.revenue_range_cents)}</div>
2382                                                    <div class="small">${SD._pct(k.revenue_delta_pct)} <span class="text-muted ms-1">vs. Vorperiode</span></div>
2383                                                </div>
2384                                            </div>
2385                                        </div>
2386                                        <div class="col-md-6 col-lg-3">
2387                                            <div class="card border-0 shadow-sm h-100">
2388                                                <div class="card-body">
2389                                                    <div class="d-flex align-items-center text-muted small mb-2">
2390                                                        <i class="bi bi-graph-up-arrow me-2"></i>Bestellungen / Ø-Wert
2391                                                    </div>
2392                                                    <div class="display-6 fw-bold mb-1">${k.orders_range ?? 0}</div>
2393                                                    <div class="text-muted small">Ø-Bestellwert: <span class="fw-semibold text-dark">${SD._fmtCents(k.avg_order_cents)}</span> · ${SD._pct(k.orders_delta_pct)}</div>
2394                                                </div>
2395                                            </div>
2396                                        </div>
2397                                    </div>
2398                                `;
2399                            };
2400
2401                            SD._renderBody = (d) => {
2402                                const recent = d.recent_orders || [];
2403                                const top = d.top_products || [];
2404                                const hasRecent = recent.length > 0;
2405                                const hasTop = top.length > 0;
2406
2407                                return `
2408                                    ${SD._renderKpis(d)}
2409                                    <div class="row g-3 mb-4">
2410                                        <div class="col-lg-8">
2411                                            <div class="card border-0 shadow-sm h-100">
2412                                                <div class="card-header bg-white border-0 pt-3 pb-0">
2413                                                    <h6 class="mb-0 fw-bold"><i class="bi bi-bar-chart me-2 text-primary"></i>Umsatz-Verlauf (${d.range_days} Tage)</h6>
2414                                                </div>
2415                                                <div class="card-body">
2416                                                    <canvas id="shopDashRevenueChart" height="100"></canvas>
2417                                                </div>
2418                                            </div>
2419                                        </div>
2420                                        <div class="col-lg-4">
2421                                            <div class="card border-0 shadow-sm h-100">
2422                                                <div class="card-header bg-white border-0 pt-3 pb-0">
2423                                                    <h6 class="mb-0 fw-bold"><i class="bi bi-pie-chart me-2 text-primary"></i>Status-Verteilung</h6>
2424                                                </div>
2425                                                <div class="card-body d-flex align-items-center justify-content-center">
2426                                                    <canvas id="shopDashStatusChart" height="220"></canvas>
2427                                                </div>
2428                                            </div>
2429                                        </div>
2430                                    </div>
2431                                    <div class="row g-3">
2432                                        <div class="col-lg-7">
2433                                            <div class="card border-0 shadow-sm h-100">
2434                                                <div class="card-header bg-white border-0 pt-3 pb-2 d-flex justify-content-between align-items-center">
2435                                                    <h6 class="mb-0 fw-bold"><i class="bi bi-clock-history me-2 text-primary"></i>Letzte Bestellungen</h6>
2436                                                    <a href="#" onclick="Pages.loadPage('shop-orders');return false;" class="small text-decoration-none">Alle anzeigen →</a>
2437                                                </div>
2438                                                <div class="card-body p-0">
2439                                                    ${!hasRecent ? '<p class="text-muted text-center py-4 mb-0">Keine Bestellungen im Zeitraum.</p>' : `
2440                                                    <div class="table-responsive">
2441                                                        <table class="table table-sm mb-0 align-middle">
2442                                                            <thead class="small text-muted">
2443                                                                <tr>
2444                                                                    <th class="ps-3">Nr.</th>
2445                                                                    <th>Kunde</th>
2446                                                                    <th>Zeit</th>
2447                                                                    <th>Status</th>
2448                                                                    <th class="text-end pe-3">Betrag</th>
2449                                                                </tr>
2450                                                            </thead>
2451                                                            <tbody>
2452                                                                ${recent.map(o => `
2453                                                                    <tr style="cursor:pointer" onclick="Pages.loadPage('shop-orders')">
2454                                                                        <td class="ps-3 small fw-semibold">${o.order_number || o.id.slice(0,8)}</td>
2455                                                                        <td class="small">${(o.customer_name || o.customer_email || '–')}</td>
2456                                                                        <td class="small text-muted">${SD._fmtDate(o.created_at)}</td>
2457                                                                        <td>${SD._statusBadge(o.payment_status || o.status)}</td>
2458                                                                        <td class="text-end pe-3 fw-semibold">${SD._fmtCents(o.total_cents, o.currency)}</td>
2459                                                                    </tr>
2460                                                                `).join('')}
2461                                                            </tbody>
2462                                                        </table>
2463                                                    </div>
2464                                                    `}
2465                                                </div>
2466                                            </div>
2467                                        </div>
2468                                        <div class="col-lg-5">
2469                                            <div class="card border-0 shadow-sm h-100">
2470                                                <div class="card-header bg-white border-0 pt-3 pb-2">
2471                                                    <h6 class="mb-0 fw-bold"><i class="bi bi-trophy me-2 text-primary"></i>Top-Produkte (${d.range_days} Tage)</h6>
2472                                                </div>
2473                                                <div class="card-body p-0">
2474                                                    ${!hasTop ? '<p class="text-muted text-center py-4 mb-0">Noch keine Verkäufe im Zeitraum.</p>' : `
2475                                                    <div class="table-responsive">
2476                                                        <table class="table table-sm mb-0 align-middle">
2477                                                            <thead class="small text-muted">
2478                                                                <tr>
2479                                                                    <th class="ps-3">Produkt</th>
2480                                                                    <th class="text-end">Stück</th>
2481                                                                    <th class="text-end pe-3">Umsatz</th>
2482                                                                </tr>
2483                                                            </thead>
2484                                                            <tbody>
2485                                                                ${top.map(p => `
2486                                                                    <tr>
2487                                                                        <td class="ps-3 small">
2488                                                                            <div class="fw-semibold">${p.name || p.sku || 'Unbekannt'}</div>
2489                                                                            ${p.sku && p.sku !== p.name ? `<div class="text-muted" style="font-size:0.75rem">${p.sku}</div>` : ''}
2490                                                                        </td>
2491                                                                        <td class="text-end fw-semibold">${p.quantity}</td>
2492                                                                        <td class="text-end pe-3">${SD._fmtCents(p.revenue_cents)}</td>
2493                                                                    </tr>
2494                                                                `).join('')}
2495                                                            </tbody>
2496                                                        </table>
2497                                                    </div>
2498                                                    `}
2499                                                </div>
2500                                            </div>
2501                                        </div>
2502                                    </div>
2503                                `;
2504                            };
2505
2506                            SD._destroyCharts = () => {
2507                                Object.values(SD._charts).forEac
2507h(c => { try { c && c.destroy(); } catch(e){} });
2508                                SD._charts = {};
2509                            };
2510
2511                            SD._drawCharts = async (d) => {
2512                                SD._destroyCharts();
2513                                await window.loadLib('chart');
2514
2515                                const series = d.revenue_series || [];
2516                                const revCanvas = document.getElementById('shopDashRevenueChart');
2517                                if (revCanvas) {
2518                                    SD._charts.revenue = new Chart(revCanvas.getContext('2d'), {
2519                                        type: 'bar',
2520                                        data: {
2521                                            labels: series.map(s => SD._fmtDay(s.date)),
2522                                            datasets: [{
2523                                                label: 'Umsatz (€)',
2524                                                data: series.map(s => (s.revenue_cents||0)/100),
2525                                                backgroundColor: '#0d6efd',
2526                                                borderRadius: 4,
2527                                            }]
2528                                        },
2529                                        options: {
2530                                            responsive: true,
2531                                            maintainAspectRatio: false,
2532                                            plugins: {
2533                                                legend: { display: false },
2534                                                tooltip: {
2535                                                    callbacks: {
2536                                                        label: (ctx) => {
2537                                                            const s = series[ctx.dataIndex] || {};
2538                                                            return [
2539                                                                `Umsatz: ${new Intl.NumberFormat('de-DE',{style:'currency',currency:'EUR'}).format(ctx.parsed.y)}`,
2540                                                                `Bestellungen: ${s.orders || 0}`,
2541                                                            ];
2542                                                        }
2543                                                    }
2544                                                }
2545                                            },
2546                                            scales: {
2547                                                x: { grid: { display: false }, ticks: { font: { size: 10 } } },
2548                                                y: {
2549                                                    beginAtZero: true,
2550                                                    ticks: { callback: (v) => new Intl.NumberFormat('de-DE',{notation:'compact',style:'currency',currency:'EUR'}).format(v) }
2551                                                }
2552                                            }
2553                                        }
2554                                    });
2555                                }
2556
2557                                const status = d.status_breakdown || {};
2558                                const statusEntries = Object.entries(status).sort((a,b) => b[1] - a[1]);
2559                                const statusCanvas = document.getElementById('shopDashStatusChart');
2560                                if (statusCanvas && statusEntries.length > 0) {
2561                                    const colorMap = {
2562                                        'paid': '#198754', 'confirmed': '#198754', 'delivered': '#198754',
2563                                        'shipped': '#0dcaf0', 'processing': '#0d6efd',
2564                                        'pending': '#ffc107', 'awaiting_payment': '#ffc107',
2565                                        'cancelled': '#6c757d', 'canceled': '#6c757d', 'refunded': '#adb5bd',
2566                                        'failed': '#dc3545',
2567                                    };
2568                                    SD._charts.status = new Chart(statusCanvas.getContext('2d'), {
2569                                        type: 'doughnut',
2570                                        data: {
2571                                            labels: statusEntries.map(([k]) => k),
2572                                            datasets: [{
2573                                                data: statusEntries.map(([,v]) => v),
2574                                                backgroundColor: statusEntries.map(([k]) => colorMap[k.toLowerCase()] || '#6c757d'),
2575                                                borderWidth: 2,
2576                                                borderColor: '#fff',
2577                                            }]
2578                                        },
2579                                        options: {
2580                                            responsive: true,
2581                                            maintainAspectRatio: false,
2582                                            plugins: {
2583                                                legend: { position: 'bottom', labels: { font: { size: 11 }, boxWidth: 12 } }
2584                                            },
2585                                            cutout: '60%'
2586                                        }
2587                                    });
2588                                } else if (statusCanvas) {
2589                                    statusCanvas.parentElement.innerHTML = '<p class="text-muted text-center mb-0 py-4">Keine Daten.</p>';
2590                                }
2591                            };
2592
2593                            SD.render = async () => {
2594                                const body = document.getElementById('shopDashB
2594ody');
2595                                if (!body) return;
2596                                body.innerHTML = '<div class="text-center py-5"><div class="spinner-border" role="status"></div><p class="text-muted mt-2">Lade Kennzahlen…</p></div>';
2597                                try {
2598                                    const token = await Auth.getIdToken();
2599                                    const r = await fetch(`/api/shop/admin/dashboard?range=${SD.range}`, {
2600                                        headers: { 'Authorization': `Bearer ${token}` }
2601                                    });
2602                                    if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
2603                                    const data = await r.json();
2604                                    body.innerHTML = SD._renderBody(data);
2605                                    SD._drawCharts(data);
2606                                } catch (e) {
2607                                    body.innerHTML = `<div class="alert alert-danger">Fehler beim Laden: ${e.message}</div>`;
2608                                    console.error('Shop dashboard error:', e);
2609                                }
2610                            };
2611
2612                            document.querySelectorAll('#shopDashRange button').forEach(btn => {
2613                                btn.addEventListener('click', () => {
2614                                    SD.range = parseInt(btn.dataset.range, 10);
2615                                    document.querySelectorAll('#shopDashRange button').forEach(b => {
2616                                        b.classList.toggle('btn-primary', b === btn);
2617                                        b.classList.toggle('btn-outline-primary', b !== btn);
2618                                    });
2619                                    SD.render();
2620                                });
2621                            });
2622                            document.querySelectorAll('#shopDashRange button').forEach(b => {
2623                                const active = parseInt(b.dataset.range, 10) === SD.range;
2624                                b.classList.toggle('btn-primary', active);
2625                                b.classList.toggle('btn-outline-primary', !active);
2626                            });
2627                            document.getElementById('shopDashReload').addEventListener('click', () => SD.render());
2628                            SD.render();
2629                        } else {
2630                            const shopLabels = {
2631                                'categories': { title: 'Shop-Kategorien', icon: 'bi-tags', desc: 'Kategorien für die Gliederung der Shop-Produkte.' }
2632                            };
2633                            const info = shopLabels[shopSub] || { title: 'Shop', icon: 'bi-shop', desc: '' };
2634                            mainContent.innerHTML = `
2635                                <div class="container-fluid mt-4">
2636                                    <div class="d-flex align-items-center mb-4">
2637                                        <i class="bi ${info.icon}" style="font-size: 2rem; margin-right: 0.75rem; color: #0d6efd;"></i>
2638                                        <div>
2639                                            <h2 class="mb-0">${info.title}</h2>
2640                                            <p class="text-muted mb-0">${info.desc}</p>
2641                                        </div>
2642                                    </div>
2643                                    <div class="alert alert-info">
2644                                        Produkte werden über <a href="#" onclick="Pages.loadPage('erp-products');return false;">ERP &rarr; Produkte</a> gepflegt.
2645                                    </div>
2646                                </div>
2647                            `;
2648                        }
2649                    } catch (error) {
2650                        console.error('Error loading Shop page:', error);
2651                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler beim Laden der Shop-Seite: ' + error.message + '</div>';
2652                    }
2653                    break;
2654                /* GASTRO_PHASE_A_MISSION_CONTROL */
2655                case 'restaurant-mission': {
2656                    try {
2657                        const ts = new Date().getTime();
2658                        const mod = await import(`/static/modules/restaurantMissionControl.js?v=${ts}`);
2659                        const Klass = mod.default || window.RestaurantMissionControl;
2660                        if (!Klass) throw new Error('MissionControl module not found');
2661                        const inst = new Klass();
2662                        await inst.render(mainContent);
2663                    } catch (err) {
2664                        console.error('Mission Control load failed:', err);
2665                        mainContent.innerHTML = '<div class="container py-4"><div class="alert alert-danger">Fehler beim Laden von Mission Control: ' + (err.message || err) + '</div></div>';
2666                    }
2667                    return;
2668                }
2669                /* GASTRO_PHASE2_CASES (legacy, still routable internally) */
2670                case 'restaurant-reservations':
2671                case 'restaurant-floor-plan':
2672                case 'restaurant-walkins': {
2673                    try {
2674                        const view = pageName === 'restaurant-floor-plan' ? 'floorplan' :
2675                                     pageName === 'restaurant-walkins' ? 'walkins' : 'reservations';
2676                        const ts = new Date().getTime();
2677                        const mod = await import(`/static/modules/restaurantReservations.js?v=${ts}`);
2678                        const Klass = mod.RestaurantReservations || mod.default;
2679                        if (!Klass) throw new Error('RestaurantReservations module not found');
2680                        const inst = new Klass(view);
2681                        await inst.init();
2682                    } catch (err) {
2683                        console.error('Gastro page load failed:', err);
2684                        mainContent.innerHTML = '<div class="container py-4"><div class="alert alert-danger">Fehler beim Laden der Gastro-Seite: ' + (err.message || err) + '</div></div>';
2685                    }
2686                    return;
2687                }
2688                default:
2689                    console.error('Unknown page:', page);
2690                    mainContent.innerHTML = '<div class="alert alert-danger">Seite nicht gefunden</div>';
2691            }
2692        } catch (error) {
2693            console.error('Error loading page:', error);
2694            UI.showError('Fehler beim Laden der Seite: ' + error.message);
2695        }
2696    }
2697
2698    static updateActiveNavigation(page) {
2699        // Alle aktiven Links deaktivieren
2700        document.querySelectorAll('.nav-link.active').forEach(link => {
2701            link.classList.remove('active');
2702        });
2703
2704        // Den neuen aktiven Link setzen
2705        const navLink = document.querySelector(`.nav-link[data-page="${page}"]`);
2706        if (navLink) {
2707            navLink.classList.add('active');
2708        }
2709        
2710        // Übersetze die Menüpunkte nach einer Seitenänderung
2711        try {
2712            Language.translateMenuItems();
2713        } catch (error) {
2714            console.error('Fehler beim Übersetzen der Menüpunkte:', error);
2715        }
2716
2717        // Show main app container if not already visible
2718        const mainApp = document.getElementById('mainApp');
2719        const loginScreen = document.querySelector('#loginScreen, #loginScreen2');
2720        
2721        if (mainApp && loginScreen) {
2722            mainApp.classList.remove('d-none');
2723            loginScreen.classList.add('d-none');
2724        }
2725    }
2726
2727    static async loadRezeption() {
2728        try {
2729            const mainContent = document.getElementById('mainContent');
2730            if (!mainContent) { console.error('Main content container not found'); return; }
2731            mainContent.innerHTML = `<div id="rezeptionHost" class="container-fluid mt-2"></div>`;
2732            return await loadRezeptionPage();
2733        } catch (error) {
2734            console.error('Error loading rezeption:', error);
2735        }
2736    }
2737
2738    static async loadAnalytics(days) {
2739        try {
2740            const mainContent = document.getElementById('mainContent');
2741            if (!mainContent) {
2742                console.error('Main content container not found');
2743                return;
2744            }
2745            mainContent.innerHTML = `<div id="analyticsHost" class="container-fluid mt-2"></div>`;
2746            await loadAnalyticsPage(days);
2747        } catch (error) {
2748            console.error('Error loading analytics:', error);
2749            UI.showError('Fehler beim Laden der Auswertung');
2750        }
2751    }
2752
2753    static async loadDashboard() {
2754        try {
2755            const mainContent = document.getElementById('mainContent');
2756            if (!mainContent) {
2757                console.error('Main content container not found');
2758                return;
2759            }
2760
2761            // Create dashboard container
2762            mainContent.innerHTML = `
2763                <div id="dashboardStats">
2764                    <div class="text-center mt-5">
2765                        <div class="spinner-border" role="status">
2766                            <span class="visually-hidden">Loading...</span>
2767                        </div>
2768                    </div>
2769                </div>
2770            `;
2771
2772            // Load dashboard data
2773            await loadDashboardStats();
2774        } catch (error) {
2775            console.error('Error loading dashboard:', error);
2776            UI.showError('Fehler beim Laden des Dashboards');
2777        }
2778    }
2779
2780    static async loadDocumentation() {
2781        // Get main content container
2782        const mainContent = document.getElementById('mainContent');
2783        if (!mainContent) {
2784            console.error('Main content container not found');
2785            return;
2786        }
2787
2788        // Clear previous content
2789        mainContent.innerHTML = `
2790            <div id="documentationSection" class="content-section">
2791                <div class="container-fluid">
2792                    <div id="documentation-container">
2793                        <!-- Content will be dynamically added by Dokumentation.js -->
2794                    </div>
2795                </div>
2796            </div>
2797        `;
2798
2799        // Call the showDocumentation function
2800        if (typeof window.showDocumentation === 'function') {
2801            window.showDocumentation();
2802        } else {
2803            console.error('showDocumentation function not found');
2804        }
2805    }
2806
2807    static async loadContacts() {
2808        try {
2809            const mainContent = document.getElementById('mainContent');
2810            if (!mainContent) return;
2811
2812            // Mobile-Erkennung
2813            const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
2814
2815            if (isMobile) {
2816                // EINFACHE Mobile-Version - nur Container
2817                mainContent.innerHTML = `
2818                    <div class="contacts-overview">
2819                        <div style="padding: 15px; background: #f8f9fa; margin-bottom: 10px;">
2820                            <h1 style="font-size: 20px; margin: 0;">${_voPraxisAnsicht() ? 'Patienten' : 'Kontakte'}</h1>
2821                        </div>
2822                        <div id="contactsContainer">
2823                            <div style="text-align: center; padding: 20px;">
2824                                <div>Lade Kontakte...</div>
2825                            </div>
2826                        </div>
2827                    </div>
2828                `;
2829
2830                // Initialisiere Mobile-Kontakte
2831                if (window.contactsMobile && window.contactsMobile.init) {
2832                    await window.contactsMobile.init();
2833                }
2834                return;
2835            }
2836
2837            // Desktop-Version (unverändert)
2838            mainContent.innerHTML = `
2839                <style>
2840                    .contacts-header {
2841                        display: flex;
2842                        justify-content: space-between;
2843                        align-items: center;
2844                        margin-bottom: 2rem;
2845                        padding: 1.5rem;
2846                        background: linear-gradient(135deg, #ffffff 0%, #f5f5f5 100%);
2847                        border-radius: 8px;
2848                        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
2849                    }
2850                    .contacts-header h1 {
2851                        color: #2e2e2e;
2852                        font-size: 1.5rem;
2853                        margin: 0;
2854                    }
2855                    .contacts-actions {
2856                        display: flex;
2857                        gap: 10px;
2858                        align-items: center;
2859                    }
2860                </style>
2861                <div class="contacts-overview">
2862                    <div class="contacts-header">
2863                        <h1>${_voPraxisAnsicht() ? 'Patienten' : t('Kontakte')}</h1>
2864                        <div class="contacts-actions">
2865                            <!-- Actions will be added here if needed -->
2866                        </div>
2867                    </div>
2868                    
2869                    <div class="card mb-4">
2870                    <div class="search-filters">
2871                        <!-- Search Bar -->
2872                        <div class="search-bar mb-3 d-flex">
2873                            <div class="input-group">
2874                                <span class="input-group-text">
2875                                    <i class="fas fa-search"></i>
2876                                </span>
2877                                <input type="text" 
2878                                       class="form-control" 
2879                                       id="searchInput" 
2880                                       placeholder="${t('Suche nach Name, Firma, Telefon...')}"
2881                                >
2882                                <button class="btn btn-outline-secondary" id="clearSearchBtn">×</button>
2883                            </div>
2884                        </div>
2885
2886                        <!-- Filters Row -->
2887                        <div class="filters-row d-flex flex-column flex-md-row justify-content-between align-items-md-center">
2888                            <div class="filters d-flex flex-column flex-md-row gap-2 flex-grow-1 mb-3 mb-md-0">
2889                                <select class="form-select" id="leadStatusFilter">
2890                                    <option value="">${t('Lead-Status: Alle')}</option>
2891                                    <option value="Angerufen">Angerufen</option>
2892                                    <option value="Anrufen">Anrufen</option>
2893                                    <option value="In Bearbeitung">In Bearbeitung</option>
2894                                    <option value="Kein Bedarf">Kein Bedarf</option>
2895                                    <option value="Kunde">Kunde</option>
2896                                    <option value="Neu">Neu</option>
2897                                    <option value="Nicht Qualifiziert">Nicht Qualifiziert</option>
2898                                    <option value="Qualifiziert">Qualifiziert</option>
2899                                    <option value="Wiedervorlage">Wiedervorlage</option>
2900                                    <option value="Abschluss">Abschluss</option>
2901                                </select>
2902                                <button class="btn btn-outline-primary" id="addLeadStatusBtn" title="Neuen Lead-Status erstellen">
2903                                    <i class="fas fa-plus"></i>
2904                                </button>
2905                                
2906                                <select class="form-select" id="brancheFilter">
2907                                    <option value="">${t('Branche: Alle')}</option>
2908                                    ${DEFAULT_INDUSTRIES.map(industry =>
2909                                        `<option value="${industry}">${t(industry)}</option>`
2910                                    ).join('')}
2911                                </select>
2912
2913                                <select class="form-select" id="aufgabeFilter">
2914                                    <option value="">${t('Aufgabe: Alle')}</option>
2915                                    ${AUFGABE_OPTIONS.map(task =>
2916                                        `<option value="${task}">${t(task)}</option>`
2917                                    ).join('')}
2918                                </select>
2919                                
2920                                <!-- Datumsbereich Filter für letzte Änderung -->
2921                                <div class="d-flex gap-1 align-items-center">
2922                                    <input type="date" 
2923                                           class="form-control" 
2924                                           id="dateFromFilter" 
2925                                           style="width: 150px;"
2926                                           title="Geändert von">
2927                                    <span class="text-muted small">${t('bis')}</span>
2928                                    <input type="date" 
2929                                           class="form-control" 
2930                                           id="dateToFilter" 
2931                                           style="width: 150px;"
2932                                           title="Geändert bis">
2933                                    <button class="btn btn-outline-secondary btn-sm" 
2934                                            id="clearDateFilterBtn" 
2935                                            title="Datumsfilter zurücksetzen">
2936                                        <i class="fas fa-times"></i>
2937                                    </button>
2938                                </div>
2939                            </div>
2940
2941                            <div class="d-flex align-items-center gap-3">
2942                                <span class="text-muted">
2943                                    <i class="fas fa-filter"></i>
2944                                    <span id="contactCount"></span>
2945                                </span>
2946                            </div>
2947                            </div>
2948                        </div>
2949                    </div>
2950                    <div id="contactsContainer" class="mt-3">
2951                        <!-- Contacts will be loaded here -->
2952                    </div>
2953                </div>
2954            `;
2955            // Event Listener für den Lead-Status Button
2956            document.getElementById('addLeadStatusBtn')?.addEventListener('click', openLeadStatusModal);
2957            
2958            // Event Listener für den Erstellen-Button im Modal
2959            document.getElementById('createLeadStatusBtn')?.addEventListener('click', createCustomLeadStatus);
2960            
2961            // Initialisiere die Kontakte
2962            const { contacts } = await import('./contacts.js?v=20260807-noshow2-soap1-p5f1-rrev1-bill1-intake1-alarm1-tokfix1-dat1-mod1b-nam1-merk1--kal1-kont1--kd1-prax1-nsl1-nsl2-impauth1-tdup2-dnd1-kf1-kf3-kt3-it1');
2963            const user = await Auth.getCurrentUser();
2964            if (!user) {
2965                throw new Error('Kein Benutzer angemeldet');
2966            }
2967
2968            // Store the contacts manager instance globally for access from other components
2969            window.contactsInstance = contacts;
2970
2971            // Load and render contacts
2972            await contacts.init();
2973
2974        } catch (error) {
2975            console.error('Error loading contacts:', error);
2976            UI.showError('Fehler beim Laden der Kontakte: ' + error.message);
2977        }
2978    }
2979
2980    static async loadCalendar() {
2981        const { Calendar } = await import('./calendar.js?v=20260826-tdup2-badge1-dnd1-kalrw2-vcall1-vserie1-it1');
2982        return await Calendar.show();
2983    }
2984
2985    static async loadCalendarSettings() {
2986        const { Calendar } = await import('./calendar.js?v=20260826-tdup2-badge1-dnd1-kalrw2-vcall1-vserie1-it1');
2987        return await Calendar.showSettingsPage();
2988    }
2989
2990    static async loadCalendarNewEvent() {
2991        const { Calendar } = await import('./calendar.js?v=20260826-tdup2-badge1-dnd1-kalrw2-vcall1-vserie1-it1');
2992        return await Calendar.showNewEventPage();
2993    }
2994
2995    // pn-settings-ui-v1: Einstellungen des Benachrichtigungs-Motors
2996    static async loadAnamneseTemplates() {  // anamnese-route-v1
2997        const { AnamneseTemplates } = await import('./anamneseTemplates.js?v=20260807-anam2-anamfix1-intake1-intake1c-iv2-alarm1-nk1-nk2-tab1-merk1--fb1--fb2');
2998        return await AnamneseTemplates.show();
2999    }
3000
3001    // reviews-ui-v1: Zufriedenheit, Beschwerden, oeffentliche Bewertungen.
3002    static async loadReviews() {
3003        const { Reviews } = await import('./reviews.js?v=20260807-rev1-tokfix1-merk1');
3004        return await Reviews.show();
3005    }
3006
3007    // no-show-ui-v1: Ausfaelle erfassen, Risiko-Vorschau, Auswertung, Regeln.
3008    static async loadAiTeam(ziel) {
3009        const { AiTeam } = await import('./aiTeam.js?v=20260810-aiteam1b-takt1-ux1-ux2-dupfix1-tel1-org1-akttab1-akttab2-aus1-wz1-zug1-del1-bug1-chat1-chat2-board2-board2b-board3-board3b-board3c-board3d-akte1-akte1b-takt1-pf1-pf1b-stand1-mail1-mail2--abl1--abl2--abl3-einst1-einst2-pdf1-gpuoff1-mlh1-ftx1-ftx2--ext1--ext2-ct1-ct2-merk1-mailanh1-anlw1-anlw2-anlw3-anlw4-anlw5-bugfix1-bugfix2-loesefix1-app1-appurl1-spur1-spur2--paket1--paket2--paket3-auftrag1-auftrag2-wort1-wort2-wort3-mikro1-mikro2-mikro3-sendknop-sendknop2-convdel1-convdel2-appdaten1-rollen2-untern1-einstieg1b-routinen1-vier1-modellweg1-projekt1---gesicht1-gfx1-fadenverl1-chatanh1---nutz1---tok1---tok3---stuf1---stuf2-mit1---gesh1---gesh2-pffrisch1-rz1-ob1-adsui1-adso1-adso2-abbr1-fort1-ged1-untern2-adom1-dauto1-lohn1-supp1-mobil1-depot1-lim1-btntext1-hg1-hg1-neu1-mk2-pff2-kont1-ablknopf1-chatz1-dokdesk1-vertgrp1-vsliste1-piiui1-fw1-zug1-buero1-korb1-lz1-pfanl1-pfleer1-orga1-chef1-orgb1-kiteamob1-kiteamfix1-probez1-mail1-s4v1-karte1-verf1-bugfix1-zirk1-kiperm1-pford1-lauf1-wrang1-strat1-schlraus1-video1-kiinh1-kiinh3-chatlang1bc-sammeldel1-sammeldel2-absort1-fgtext1-fgtext2-abscroll1-mehrlauf1-spin1-dlspin1-fehlg1');
3010        return await AiTeam.show(ziel);
3011    }
3012
3013    static async loadNoShow() {
3014        const { NoShow } = await import('./noShow.js?v=20260807-p34fix-tokfix1-merk1-nstab1');
3015        return await NoShow.show();
3016    }
3017
3018    static async loadPatientNotifySettings() {
3019        const { PatientNotifySettings } = await import('./patientNotifySettings.js?v=20260807-cwout1-tokfix1-merk1');
3020        return await PatientNotifySettings.show();
3021    }
3022
3023    static async loadCalendarTeam() {
3024        const { Calendar } = await import('./calendar.js?v=20260826-tdup2-badge1-dnd1-kalrw2-vcall1-vserie1-it1');
3025        return await Calendar.showTeamPage();
3026    }
3027
3028    static async loadTasks() {
3029        const { Tasks } = await import('./tasks.js?v=20260728-kanbandrop-merk1-aufg1-aufgmob1-aufg2-impauth1-herrenlos1');
3030        return await Tasks.load();
3031    }
3032
3033    static async loadTickets() {
3034        const { TicketSystem } = await import('./ticketSystem.js?v=20260728-singleinst-merk1--tick1--tick2-fwtick1-tickprio1-tickui1-kf3-tsuche1b');
3035        const mainContent = document.getElementById('mainContent');
3036        if (!mainContent) return;
3037
3038        mainContent.innerHTML = `
3039            <div class="tickets-container">
3040                <div class="ticket-nav">
3041                    <a href="#" class="ticket-nav-item active" data-view="dashboard">
3042                        <i class="fas fa-tachometer-alt"></i> ${t('Dashboard')}
3043                    </a>
3044                    <a href="#" class="ticket-nav-item" data-view="tickets">
3045                        <i class="fas fa-list"></i> ${t('Alle Tickets')}
3046                    </a>
3047                    <a href="#" class="ticket-nav-item" data-view="new">
3048                        <i class="fas fa-plus"></i> ${t('Neues Ticket')}
3049                    </a>
3050                    <a href="#" class="ticket-nav-item" data-view="analytics">
3051                        <i class="fas fa-chart-bar"></i> ${t('Analytics')}
3052                    </a>
3053                </div>
3054                <div id="ticket-content">
3055                    <!-- Ticket content will be loaded here -->
3056                </div>
3057            </div>
3058        `;
3059
3060        // ticket-deeplink-v1: Sprung aus der digitalen Rezeption ("Details").
3061        // Die Absicht steht schon vor dem Seitenaufbau fest - sonst wirft der
3062        // Aufbau der Ticket-Seite die Detailansicht sofort aufs Dashboard zurueck.
3063        const _deepLink = window.__ticketDeepLink || null;
3064
3065        // deeplink-dupnav-v1: app.js laeuft in mehreren Modul-Instanzen (Tag mit
3066        // Stempel + '../app.js'-Importe ohne Stempel), dadurch feuert pro Klick
3067        // mehr als ein Navigations-Handler und diese Funktion laeuft doppelt.
3068        // Der erste Lauf verbraucht die Sprungabsicht, der zweite saehe sie nicht
3069        // mehr und wuerde die frisch geoeffnete Detailansicht wieder aufs
3070        // Dashboard zuruecksetzen. pages.js gibt es nur EINMAL - deshalb wird hier
3071        // gemerkt, dass gerade ein Sprung lief, und ein unmittelbar folgender
3072        // Zweitlauf laesst die Ansicht in Ruhe.
3073        if (_deepLink) window.__ticketDeepLinkAt = Date.now();
3074        const _recentDeepLink = !!window.__ticketDeepLinkAt &&
3075                                (Date.now() - window.__ticketDeepLinkAt) < 2500;
3076
3077        // Initialize ticket system if not already initialized
3078        // ticket-deeplink-v2: Erst- und Folgebesuch brauchen unterschiedliche
3079        // Behandlung. Bei einer FRISCHEN Instanz steht die Anmeldung noch nicht,
3080        // ein sofortiger Abruf lief in einen 401 und landete wieder im Dashboard.
3081        // Deshalb dort nur vormerken - init() oeffnet den Vorgang, sobald es kann.
3082        const _freshTicketSystem = !window.ticketSystem;
3083        if (_freshTicketSystem) {
3084            window.ticketSystem = new TicketSystem();
3085        } else if (!_deepLink && !_recentDeepLink) {
3086            // ticket-viewguard-v1: switchView statt loadDashboard - setzt zusaetzlich
3087            // die aktive Ansicht zurueck (Render-Guards in ticketSystem.js).
3088            window.ticketSystem.switchView('dashboard');
3089        }
3090
3091        if (_deepLink) {
3092            window.__ticketDeepLink = null;
3093            const _ts = window.ticketSystem;
3094            if (_ts && typeof _ts.loadTicketDetail === 'function') {
3095                _ts.currentView = 'detail';   // gegen den verzoegerten Dashboard-Render aus init()
3096                if (_freshTicketSystem) {
3097                    _ts.__pendingDeepLink = _deepLink;
3098                    // Auffangnetz, falls init() den Vorgang nicht abholt.
3099                    setTimeout(() => {
3100                        if (_ts.__pendingDeepLink === _deepLink) {
3101                            _ts.__pendingDeepLink = null;
3102                            _ts.currentView = 'detail';
3103                            _ts.loadTicketDetail(_deepLink);
3104                        }
3105                    }, 6000);
3106                } else {
3107                    _ts.loadTicketDetail(_deepLink);
3108                }
3109            }
3110        }
3111    }
3112
3113    static async loadVerordnungen() {
3114        const mainContent = document.getElementById('mainContent');
3115        if (!mainContent) return;
3116
3117        mainContent.innerHTML = '<div id="verordnungen-root"><div class="text-center p-5"><i class="fas fa-spinner fa-spin fa-2x"></i></div></div>';
3118
3119        try {
3120            const timestamp = new Date().getTime();
3121            const module = await import(`/static/modules/verordnungen.js?v=${timestamp}`);
3122            if (module.Verordnungen) {
3123                const v = new module.Verordnungen();
3124                await v.init();
3125            }
3126        } catch (err) {
3127            console.error('Error loading Verordnungen module:', err);
3128            mainContent.innerHTML = '<div class="alert alert-danger m-3">Fehler beim Laden des Verordnungs-Moduls: ' + err.message + '</div>';
3129        }
3130    }
3131
3132
3133
3134    // accounting-page-gate-v1: Zugriff auf eine Buchhaltungsseite.
3135    // Rolle admin darf immer; sonst entscheidet die Gruppe "accounting" im
3136    // Rechte-Schema. Bei jedem Fehler bleibt es geschlossen (fail-closed).
3137    static async accountingAccess(subPage) {
3138        const MAP = {
3139            dashboard: 'overview', settings: 'overview', bookings: 'bookings',
3140            receipts: 'receipts', reports: 'reports', bank: 'bank',
3141            tax: 'taxes', datev: 'datev',
3142        };
3143        try {
3144            if (await Auth.checkAdminAccess()) return true;
3145        } catch (e) { /* keine Rolle -> Rechte-Schema entscheidet */ }
3146        try {
3147            if (!Pages._accPerm) {
3148                const token = await Auth.getIdToken();
3149                Pages._accPerm = fetch('/api/permissions/me', {
3150                    credentials: 'same-origin',
3151                    headers: { Accept: 'application/json', Authorization: 'Bearer ' + token },
3152                }).then(r => (r.ok ? r.json() : null));
3153            }
3154            const res = await Pages._accPerm;
3155            const grp = res && res.effective && res.effective.groups
3156                && res.effective.groups.accounting;
3157            if (!grp || !grp.enabled || !grp.items) return false;
3158            return !!grp.items[MAP[subPage] || 'overview'];
3159        } catch (e) {
3160            return false;
3161        }
3162    }
3163
3164    static async loadAccountingPage(subPage) {
3165        const mainContent = document.getElementById("mainContent");
3166        if (!mainContent) return;
3167
3168        // accounting-page-gate-v1: Die Buchhaltung haengt nicht mehr an der
3169        // Rolle, sondern am regulaeren Rechte-Schema (Gruppe "accounting").
3170        // Der fruehere Rollen-Check hier hat die mandantenweise Freischaltung
3171        // ausgehebelt - der Menuepunkt war sichtbar, die Seite verweigerte.
3172        if (!(await Pages.accountingAccess(subPage))) {
3173            mainContent.innerHTML = `<div class="alert alert-warning mt-4">
3174                <i class="fas fa-lock me-2"></i>Für die Buchhaltung fehlt Ihnen die Berechtigung.
3175                Ein Administrator kann sie unter „Rechte &amp; Zugriffe“ freischalten.
3176            </div>`;
3177            return;
3178        }
3179
3180        try {
3181            switch (subPage) {
3182                case 'dashboard':
3183                    if (typeof AccountingDashboard !== 'undefined') {
3184                        await AccountingDashboard.loadPageContent(mainContent);
3185                    } else {
3186                        mainContent.innerHTML = '<div class="text-center py-5"><div class="spinner-border"></div><p class="mt-2">Buchhaltungs-Dashboard wird geladen...</p></div>';
3187                        const script = document.createElement('script');
3188                        script.src = '/static/modules/accountingDashboard.js?v=' + Date.now();
3189                        script.onload = () => AccountingDashboard.loadPageContent(mainContent);
3190                        document.head.appendChild(script);
3191                    }
3192                    break;
3193                case 'bookings':
3194                    if (typeof AccountingBookings !== 'undefined') {
3195                        await AccountingBookings.loadPageContent(mainContent);
3196                    } else {
3197                        mainContent.innerHTML = '<div class="text-center py-5"><div class="spinner-border"></div><p class="mt-2">Buchungen werden geladen...</p></div>';
3198                        const script = document.createElement('script');
3199                        script.src = '/static/modules/accountingBookings.js?v=' + Date.now();
3200                        script.onload = () => AccountingBookings.loadPageContent(mainContent);
3201                        document.head.appendChild(script);
3202                    }
3203                    break;
3204                case 'reports':
3205                    try {
3206                        const repMod = await import('./accountingReports.js?v=20260807-tokens-merk1');
3207                        repMod.loadAccountingReportsPage();
3208                    } catch (err) {
3209                        console.error('Error loading Reports:', err);
3210                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
3211                    }
3212                    break;
3213                case 'tax':
3214                    // accounting-tax-ui-v1: lud vorher accountingReports.js -
3215                    // drei Menuepunkte, eine Seite, kein eigener Inhalt.
3216                    try {
3217                        const taxMod = await import('./accountingTax.js?v=20260807-tokens-merk1');
3218                        taxMod.loadAccountingTaxPage();
3219                    } catch (err) {
3220                        console.error('Error loading Tax:', err);
3221                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
3222                    }
3223                    break;
3224                case 'datev':
3225                    // accounting-datev-ui-v1: eigene Seite statt der Auswertungen
3226                    try {
3227                        const datevMod = await import('./accountingDatev.js?v=20260807-tokens-merk1');
3228                        await datevMod.loadAccountingDatevPage();
3229                    } catch (err) {
3230                        console.error('Error loading DATEV:', err);
3231                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
3232                    }
3233                    break;
3234                case 'receipts':
3235                    try {
3236                        const recMod = await import('./accountingReceipts.js?v=20260807-fix-merk1-belegid1-fbtn1');
3237                        recMod.loadAccountingReceiptsPage();
3238                    } catch (err) {
3239                        console.error('Error loading Receipts:', err);
3240                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
3241                    }
3242                    break;
3243                case 'settings':
3244                    // accounting-settings-ui-v1: war ein Platzhalter, obwohl das
3245                    // Backend vollstaendig da war. Ohne diese Seite liessen sich
3246                    // Steuernummer und DATEV-Nummern gar nicht eintragen.
3247                    try {
3248                        const setMod = await import('./accountingSettings.js?v=20260807-tokens-merk1');
3249                        await setMod.loadAccountingSettingsPage();
3250                    } catch (err) {
3251                        console.error('Error loading Settings:', err);
3252                        mainContent.innerHTML = '<div class="alert alert-danger">Fehler: ' + err.message + '</div>';
3253                    }
3254                    break;
3255                default:
3256                    mainContent.innerHTML = '<div class="alert alert-warning mt-4">Seite nicht gefunden.</div>';
3257            }
3258        } catch (error) {
3259            console.error('Error loading accounting page:', error);
3260            mainContent.innerHTML = `<div class="alert alert-danger mt-4">Fehler beim Laden: ${error.message}</div>`;
3261        }
3262    }
3263
3264    static async loadAdsPage(page) {
3265        const mainContent = document.getElementById("mainContent");
3266        if (!mainContent) return;
3267
3268        try {
3269            // Try to load as JS module first with cache buster
3270            const timestamp = new Date().getTime();
3271            const modulePath = `/static/modules/${page}.js?v=${timestamp}`;
3272            
3273            try {
3274                const module = await import(modulePath);
3275                
3276                // Call the module's init function based on page name
3277                if (module.default) {
3278                    await module.default();
3279                } else if (page === 'ads-campaigns' && module.initAdsCampaigns) {
3280                    await module.initAdsCampaigns();
3281                } else if (page === 'ads-campaigns-new' && module.initCampaignCreatorV2) {
3282                    await module.initCampaignCreatorV2();
3283                } else if (page === 'ads-keywords' && module.initAdsKeywords) {
3284                    await module.initAdsKeywords();
3285                } else if (page === 'ads-alerts' && module.initAdsAlerts) {
3286                    await module.initAdsAlerts();
3287                } else if (page === 'ads-automation' && module.initAdsAutomation) {
3288                    await module.initAdsAutomation();
3289                } else if (page === 'ads-reports' && module.initAdsReports) {
3290                    await module.initAdsReports();
3291                    // Store the module reference for cleanup
3292                    this.currentPageModule = window.adsReports;
3293                } else if (page === 'ads-bulk' && module.initAdsBulk) {
3294                    await module.initAdsBulk();
3295                } else if (page === 'ads-dashboard' && module.initAdsDashboard) {
3296                    await module.initAdsDashboard();
3297                } else if (page === 'ads-setup' && module.initAdsSetup) {
3298                    await module.initAdsSetup();
3299                } else if (page === 'tracking-manager' && module.initTrackingManager) {
3300                    await module.initTrackingManager();
3301                } else if (page === 'tracking-dashboard' && module.initTrackingDashboard) {
3302                    await module.initTrackingDashboard();
3303                } else if (page === 'ticketSystem') {
3304                    // TicketSystem is a class, instantiate it
3305                    if (module.TicketSystem) {
3306                        window.ticketSystem = new module.TicketSystem();
3307                    } else if (module.initTicketSystem) {
3308                        await module.initTicketSystem();
3309                    }
3310                }
3311                
3312                console.log(`Loaded Google Ads JS module: ${page}`);
3313                return;
3314            } catch (moduleError) {
3315                // If JS module doesn't exist, try HTML fallback
3316                console.log(`JS module not found for ${page}, trying HTML...`);
3317            }
3318            
3319            // Fallback to HTML loading
3320            const response = await fetch(`/static/modules/${page}.html`);
3321            if (!response.ok) {
3322                throw new Error(`Failed to load ${page}`);
3323            }
3324            const html = await response.text();
3325            mainContent.innerHTML = html;
3326            
3327            // Execute scripts in the loaded HTML
3328            const scripts = mainContent.querySelectorAll("script");
3329            scripts.forEach(script => {
3330                try {
3331                    const newScript = document.createElement("script");
3332                    // Check if it's a module script
3333                    if (script.type === "module") {
3334                        newScript.type = "module";
3335                    }
3336                    newScript.textContent = script.textContent;
3337                    document.body.appendChild(newScript);
3338                    // Don't remove module scripts immediately
3339                    if (script.type !== "module") {
3340                        setTimeout(() => document.body.removeChild(newScript), 100);
3341                    }
3342                } catch (e) {
3343                    console.error("Error executing script:", e);
3344                }
3345            });
3346            
3347            console.log(`Loaded Google Ads HTML page: ${page}`);
3348        } catch (error) {
3349            console.error(`Error loading ${page}:`, error);
3350            mainContent.innerHTML = `<div class="alert alert-danger">Fehler beim Laden der Seite ${page}</div>`;
3351        }
3352    }
3353    static async loadCallDetail() {  // calldetail-v1
3354        const id = new URLSearchParams(window.location.search).get('id');
3355        const { CallDetail } = await import('./callDetail.js?v=20260805-calldetail5-merk1-sperr1-ri1');
3356        return await CallDetail.load(id);
3357    }
3358
3359    static async loadCallSummary() {
3360        const { CallSummary } = await import('./callSummary.js?v=20260805-calldetail5-soap1-p5f1-rrev1-bill1-intake1-alarm1-tokfix1-dat1-mod1b-nam1-merk1--kal1-kont1--kd1-chat1-chat2-voice1-prax1-nsl1-nsl2-impauth1-tdup2-testf1-waueb1-testsicht1-kteam1-kt3');
3361        await CallSummary.load();
3362    }
3363
3364    static async loadCallAnalysis() {
3365        const { CallAnalysis } = await import('./callAnalysis.js');
3366        // Wrap the load call in a setTimeout to ensure DOM is ready
3367        setTimeout(async () => {
3368            try {
3369                await CallAnalysis.load();
3370            } catch (error) {
3371                console.error("Error executing CallAnalysis.load() after timeout:", error);
3372                // Optionally display an error to the user in the UI
3373                const mainContent = document.getElementById('mainContent');
3374                if (mainContent) {
3375                    mainContent.innerHTML = `<div class="alert alert-danger">Fehler beim Initialisieren der Gesprächsanalyse: ${error.message}</div>`;
3376                }
3377            }
3378        }, 0); // Delay of 0ms is often sufficient
3379    }
3380
3381    static async loadWhitelabel() {
3382        console.log('Loading whitelabel page...');
3383        try {
3384            const isAdmin = await Auth.checkAdminAccess();
3385            console.log('Admin access check result:', isAdmin);
3386            
3387            if (!isAdmin) {
3388                console.log('Access denied: User is not an admin');
3389                return this.showNoAccessError();
3390            }
3391            
3392            console.log('Access granted: Loading whitelabel content');
3393            return await WhiteLabel.load();
3394        } catch (error) {
3395            console.error('Error in loadWhitelabel:', error);
3396            return this.showNoAccessError();
3397        }
3398    }
3399
3400    static showNoAccessError() {
3401        const mainContent = document.getElementById('mainContent');
3402        if (!mainContent) {
3403            console.error('Main content container not found');
3404            return;
3405        }
3406        
3407        mainContent.innerHTML = `
3408            <div class="container-fluid">
3409                <div class="alert alert-danger" role="alert">
3410                    <i class="fas fa-exclamation-triangle me-2"></i>
3411                    Sie haben keine Berechtigung für diese Seite.
3412                </div>
3413            </div>
3414        `;
3415    }
3416
3417    static async loadCreateUser() {
3418        const content = document.getElementById('content');
3419        content.innerHTML = `
3420            <div class="container-fluid">
3421                <h1>Benutzer anlegen</h1>
3422                
3423                <div class="card">
3424                    <div class="card-body">
3425                        <form id="createUserForm">
3426                            <div class="row g-3">
3427                                <div class="col-md-6">
3428                                    <label for="createUserEmail" class="form-label">E-Mail</label>
3429                                    <input type="email" class="form-control" id="createUserEmail" required>
3430                                </div>
3431                                <div class="col-md-6">
3432                                    <label for="createUserPassword" class="form-label">Passwort</label>
3433                                    <input type="password" class="form-control" id="createUserPassword" required>
3434                                </div>
3435                                <div class="col-md-6">
3436                                    <label for="createUserRole" class="form-label">Rolle</label>
3437                                    <select class="form-select" id="createUserRole" required>
3438                                        <option value="user">Benutzer</option>
3439                                        <option value="admin">Administrator</option>
3440                                    </select>
3441                                </div>
3442                            </div>
3443                            <button type="submit" class="btn btn-primary mt-3">
3444                                <i class="fas fa-plus"></i> Benutzer erstellen
3445                            </button>
3446                        </form>
3447                    </div>
3448                </div>
3449            </div>
3450        `;
3451        
3452        // Add form submit handler
3453        const createUserForm = document.getElementById('createUserForm');
3454        if (createUserForm) {
3455            createUserForm.addEventListener('submit', async (e) => {
3456                e.preventDefault();
3457                const email = document.getElementById('createUserEmail').value;
3458                const password = document.getElementById('createUserPassword').value;
3459                const role = document.getElementById('createUserRole').value;
3460                
3461                if (email && password && role) {
3462                    await Users.create(email, password, role);
3463                    createUserForm.reset();
3464                }
3465            });
3466        }
3467    }
3468
3469    static async loadMulticall() {
3470        const mainContent = document.getElementById('mainContent');
3471        mainContent.innerHTML = `
3472            <div class="container-fluid">
3473                <h1 class="mb-4">Multicall</h1>
3474                <div class="row">
3475                    <div class="col-12">
3476                        <!-- Multicall content will be added here -->
3477                        <div class="alert alert-info">
3478                            Multicall-Funktion wird geladen...
3479                        </div>
3480                    </div>
3481                </div>
3482            </div>
3483        `;
3484        // You can add more multicall-specific functionality here
3485    }
3486
3487    static async loadDeals() {
3488        const dealsManager = (await import("./deals.js?v=phase8-merk1-deal1-deal2-deal3-drag1-drag2-impauth1-kf3")).dealsManager;
3489        await dealsManager.init();
3490    }
3491
3492    static async loadGoogleContactsIntegration() {
3493        const mainContent = document.getElementById('mainContent');
3494        if (!mainContent) {
3495            throw new Error('Main content container not found');
3496        }
3497
3498        try {
3499            // Import required modules
3500            // showToast is not available, use UI methods instead
3501            const { Auth } = await import('./auth.js');
3502            
3503            // Lade die HTML-Seite
3504            const response = await fetch('/static/pages/google-contacts-integration.html');
3505            if (!response.ok) {
3506                throw new Error('Failed to load Google Contacts integration page');
3507            }
3508            
3509            const html = await response.text();
3510            
3511            // Parse HTML to extract script content
3512            const parser = new DOMParser();
3513            const doc = parser.parseFromString(html, 'text/html');
3514            
3515            // Extract script content before removing
3516            const scripts = doc.querySelectorAll('script[type="module"]');
3517            const scriptContents = [];
3518            scripts.forEach(script => {
3519                if (script.textContent && script.textContent.includes('translatePage')) {
3520                    scriptContents.push(script.textContent);
3521                }
3522                script.remove();
3523            });
3524            
3525            // Set the HTML content
3526            mainContent.innerHTML = doc.body.innerHTML;
3527            
3528            // Execute the translation script
3529            if (scriptContents.length > 0) {
3530                console.log('[Pages] Executing translation script from HTML');
3531                // Create and execute a new script module
3532                const moduleScript = document.createElement('script');
3533                moduleScript.type = 'module';
3534                moduleScript.textContent = scriptContents[0];
3535                document.body.appendChild(moduleScript);
3536                // Remove after execution
3537                setTimeout(() => moduleScript.remove(), 1000);
3538            }
3539            
3540            // Initialize Google People Integration using the imported module
3541            // Remove local duplicate - use imported module instead
3542            /*const GooglePeopleIntegration = {
3543                CLIENT_ID: '666681974357-5aifspupr1roo2dqi1bu9kovl41ouifg.apps.googleusercontent.com',
3544                SCOPES: 'https://www.googleapis.com/auth/contacts https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
3545                REDIRECT_URI: `${window.location.origin}/api/auth/google-people/callback`,
3546                connectionStatus: null,
3547                
3548                async init() {
3549                    console.log('Initialisiere Google People Integration');
3550                    await this.checkConnectionStatus();
3551                    this.setupEventListeners();
3552                },
3553                
3554                async checkConnectionStatus() {
3555                    try {
3556                        const response = await fetch('/api/auth/google-people/status', {
3557                            headers: {
3558                                'Authorization': `Bearer ${await Auth.getIdToken()}`
3559                            }
3560                        });
3561                        
3562                        if (response.ok) {
3563                            this.connectionStatus = await response.json();
3564                            this.updateUI();
3565                        }
3566                    } catch (error) {
3567                        console.error('Fehler beim Prüfen des Verbindungsstatus:', error);
3568                        this.updateUI();
3569                    }
3570                },
3571                
3572                updateUI() {
3573                    const container = document.getElementById('google-people-status');
3574                    if (!container) return;
3575                    
3576                    if (this.connectionStatus?.connected) {
3577                        container.innerHTML = `
3578                            <div class="integration-card connected">
3579                                <div class="integration-header">
3580                                    <img src="https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png" 
3581                                         alt="Google Contacts" class="integration-logo">
3582                                    <div class="integration-info">
3583                                        <h3>Google Contacts</h3>
3584                                        <p class="status">Verbunden als ${this.connectionStatus.email}</p>
3585                                    </div>
3586                                </div>
3587                                <div class="integration-details">
3588                                    <p><strong>Letzte Synchronisation:</strong> 
3589                                       ${this.connectionStatus.last_sync ? 
3590                                         new Date(this.connectionStatus.last_sync).toLocaleString('de-DE') : 
3591                                         'Noch nicht synchronisiert'}
3592                                    </p>
3593                                    <p><strong>Sync-Richtung:</strong> 
3594                                       ${this.getSyncDirectionLabel(this.connectionStatus.sync_direction)}
3595                                    </p>
3596                                </div>
3597                                <div class="integration-actions">
3598                                    <button onclick="window.GooglePeopleIntegration.startSync()" class="btn btn-secondary">
3599                                        <i class="fas fa-sync"></i> Jetzt synchronisieren
3600                                    </button>
3601                                    <button onclick="window.GooglePeopleIntegration.showSettings()" class="btn btn-secondary">
3602                                        <i class="fas fa-cog"></i> Einstellungen
3603                                    </button>
3604                                    <button onclick="window.GooglePeopleIntegration.disconnect()" class="btn btn-danger">
3605                                        <i class="fas fa-unlink"></i> Verbindung trennen
3606                                    </button>
3607                                </div>
3608                            </div>
3609                        `;
3610                    } else {
3611                        container.innerHTML = `
3612                            <div class="integration-card disconnected">
3613                                <div class="integration-header">
3614                                    <img src="https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png" 
3615                                         alt="Google Contacts" class="integration-logo">
3616                                    <div class="integration-info">
3617                                        <h3>Google Contacts</h3>
3618                                        <p class="status">Nicht verbunden</p>
3619                                    </div>
3620                                </div>
3621                                <div class="integration-description">
3622                                    <p>Verbinden Sie Ihre Google Kontakte für automatische Synchronisation:</p>
3623                                    <ul>
3624                                        <li>Automatischer Import Ihrer Google Kontakte</li>
3625                                        <li>Bidirektionale Synchronisation</li>
3626                                        <li>Feldmapping zwischen voiceOne und Google</li>
3627                                    </ul>
3628                                </div>
3629                                <div class="integration-actions">
3630                                    <button onclick="window.GooglePeopleIntegration.connect()" class="btn btn-primary">
3631                                        <i class="fab fa-google"></i> Mit Google Contacts verbinden
3632                                    </button>
3633                                </div>
3634                            </div>
3635                        `;
3636                    }
3637                },
3638                
3639                async connect() {
3640                    console.log('GooglePeopleIntegration.connect() called from pages.js');
3641                    try {
3642                        // Generiere state mit Service-Indikator
3643                        const stateData = {
3644                            service: 'people',
3645                            nonce: Math.random().toString(36).substring(7)
3646                        };
3647                        const state = btoa(JSON.stringify(stateData));
3648                        sessionStorage.setItem('google_people_oauth_state', state);
3649                        console.log('Saving OAuth state:', {
3650                            stateData: stateData,
3651                            encodedState: state,
3652                            stateLength: state.length,
3653                            verifyStorage: sessionStorage.getItem('google_people_oauth_state') === state
3654                        });
3655                        
3656                        const params = new URLSearchParams({
3657                            client_id: this.CLIENT_ID,
3658                            redirect_uri: this.REDIRECT_URI,
3659                            response_type: 'code',
3660                            scope: this.SCOPES,
3661                            access_type: 'offline',
3662                            prompt: 'consent',
3663                            state: state
3664                        });
3665                        
3666                        const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
3667                        
3668                        console.log('Starte Google People OAuth Flow...');
3669                        console.log('Leite um zu:', authUrl);
3670                        
3671                        // OAuth-Flow durch Weiterleitung starten
3672                        window.location.href = authUrl;
3673                        
3674                    } catch (error) {
3675                        console.error('Fehler beim Verbinden mit Google People:', error);
3676                        UI.showError('Fehler beim Verbinden mit Google Contacts');
3677                    }
3678                },
3679                
3680                async exchangeCodeForToken(code) {
3681                    try {
3682                        UI.showInfo('Verbinde mit Google Contacts...');
3683                        
3684                        const response = await fetch('/api/auth/google-people/token', {
3685                            method: 'POST',
3686                            headers: {
3687                                'Content-Type': 'application/json',
3688                                'Authorization': `Bearer ${await Auth.getIdToken()}`
3689                            },
3690                            body: JSON.stringify({
3691                                code: code,
3692                                redirect_uri: this.REDIRECT_URI,
3693                                client_id: this.CLIENT_ID
3694                            })
3695                        });
3696                        
3697                        if (response.ok) {
3698                            const data = await response.json();
3699                            UI.showSuccess('Erfolgreich mit Google Contacts verbunden');
3700                            await this.checkConnectionStatus();
3701                            this.startSync();
3702                        } else {
3703                            const error = await response.json();
3704                            console.error('Token exchange failed:', error);
3705                            UI.showError('Fehler beim Verbinden mit Google Contacts');
3706                        }
3707                    } catch (error) {
3708                        console.error('Fehler beim Token-Austausch:', error);
3709                        UI.showError('Fehler beim Verbinden mit Google Contacts');
3710                    }
3711                },
3712                
3713                async disconnect() {
3714                    if (!confirm(t('Möchten Sie die Verbindung zu Google Contacts wirklich trennen?'))) {
3715                        return;
3716                    }
3717                    
3718                    try {
3719                        const response = await fetch('/api/auth/google-people/disconnect', {
3720                            method: 'POST',
3721                            headers: {
3722                                'Authorization': `Bearer ${await Auth.getIdToken()}`
3723                            }
3724                        });
3725                        
3726                        if (response.ok) {
3727                            UI.showSuccess('Verbindung zu Google Contacts getrennt');
3728                            await this.checkConnectionStatus();
3729                        } else {
3730                            UI.showError('Fehler beim Trennen der Verbindung');
3731                        }
3732                    } catch (error) {
3733                        console.error('Fehler beim Trennen:', error);
3734                        UI.showError('Fehler beim Trennen der Verbindung');
3735                    }
3736                },
3737                
3738                async startSync() {
3739                    try {
3740                        UI.showInfo('Synchronisiere Kontakte...');
3741                        
3742                        const response = await fetch('/api/contacts/sync/google', {
3743                            method: 'POST',
3744                            headers: {
3745                                'Authorization': `Bearer ${await Auth.getIdToken()}`
3746                            }
3747                        });
3748                        
3749                        if (response.ok) {
3750                            const data = await response.json();
3751                            UI.showInfo('Synchronisation gestartet. Dies kann einige Minuten dauern.');
3752                            
3753                            setTimeout(() => {
3754                                this.checkConnectionStatus();
3755                            }, 5000);
3756                        } else {
3757                            const error = await response.json();
3758                            UI.showError(error.error || 'Fehler beim Starten der Synchronisation');
3759                        }
3760                        
3761                    } catch (error) {
3762                        console.error('Fehler beim Starten der Synchronisation:', error);
3763                        UI.showError('Fehler beim Starten der Synchronisation');
3764                    }
3765                },
3766                
3767                async showSettings() {
3768                    const modal = document.createElement('div');
3769                    modal.className = 'modal fade show';
3770                    modal.style.display = 'block';
3771                    modal.innerHTML = `
3772                        <div class="modal-dialog">
3773                            <div class="modal-content">
3774                                <div class="modal-header">
3775                                    <h5 class="modal-title">Google Contacts Einstellungen</h5>
3776                                    <button type="button" class="btn-close" onclick="this.closest('.modal').remove()"></button>
3777                                </div>
3778                                <div class="modal-body">
3779                                    <form id="google-people-settings-form">
3780                                        <div class="mb-3">
3781                                            <label class="form-label">Synchronisationsrichtung</label>
3782                                            <select class="form-select" id="sync-direction">
3783                                                <option value="bidirectional" ${this.connectionStatus?.sync_direction === 'bidirectional' ? 'selected' : ''}>
3784                                                    Bidirektional (Empfohlen)
3785                                                </option>
3786                                                <option value="from_google" ${this.connectionStatus?.sync_direction === 'from_google' ? 'selected' : ''}>
3787                                                    Nur von Google zu voiceOne
3788                                                </option>
3789                                                <option value="to_google" ${this.connectionStatus?.sync_direction === 'to_google' ? 'selected' : ''}>
3790                                                    Nur von voiceOne zu Google
3791                                                </option>
3792                                            </select>
3793                                        </div>
3794                                        <div class="mb-3">
3795                                            <div class="form-check">
3796                                                <input class="form-check-input" type="checkbox" id="sync-enabled" 
3797                                                       ${this.connectionStatus?.sync_enabled !== false ? 'checked' : ''}>
3798                                                <label class="form-check-label" for="sync-enabled">
3799                                                    Automatische Synchronisation aktiviert
3800                                                </label>
3801                                            </div>
3802                                        </div>
3803                                        <div class="alert alert-info">
3804                                            <i class="fas fa-info-circle"></i>
3805                                            Die automatische Synchronisation erfolgt alle 30 Minuten.
3806                                        </div>
3807                                    </form>
3808                                </div>
3809                                <div class="modal-footer">
3810                                    <button type="button" class="btn btn-secondary" onclick="this.closest('.modal').remove()">
3811                                        Abbrechen
3812                                    </button>
3813                                    <button type="button" class="btn btn-primary" onclick="window.GooglePeopleIntegration.saveSettings()">
3814                                        Speichern
3815                                    </button>
3816                                </div>
3817                            </div>
3818                        </div>
3819                    `;
3820                    document.body.appendChild(modal);
3821                    
3822                    // Add backdrop
3823                    const backdrop = document.createElement('div');
3824                    backdrop.className = 'modal-backdrop fade show';
3825                    document.body.appendChild(backdrop);
3826                    
3827                    // Remove backdrop when modal is closed
3828                    modal.querySelector('.btn-close').addEventListener('click', () => {
3829                        backdrop.remove();
3830                    });
3831                    modal.querySelector('.btn-secondary').addEventListener('click', () => {
3832                        backdrop.remove();
3833                    });
3834                },
3835                
3836                async saveSettings() {
3837                    const syncDirection = document.getElementById('sync-direction').value;
3838                    const syncEnabled = document.getElementById('sync-enabled').checked;
3839                    
3840                    try {
3841                        const response = await fetch('/api/auth/google-people/settings', {
3842                            method: 'POST',
3843                            headers: {
3844                                'Content-Type': 'application/json',
3845                                'Authorization': `Bearer ${await Auth.getIdToken()}`
3846                            },
3847                            body: JSON.stringify({
3848                                sync_direction: syncDirection,
3849                                sync_enabled: syncEnabled
3850                            })
3851                        });
3852                        
3853                        if (response.ok) {
3854                            UI.showSuccess('Einstellungen gespeichert');
3855                            // Remove modal and backdrop
3856                            document.querySelector('.modal').remove();
3857                            document.querySelector('.modal-backdrop').remove();
3858                            // Refresh connection status
3859                            await this.checkConnectionStatus();
3860                        } else {
3861                            const error = await response.json();
3862                            UI.showError(error.error || 'Fehler beim Speichern der Einstellungen');
3863                        }
3864                    } catch (error) {
3865                        console.error('Fehler beim Speichern der Einstellungen:', error);
3866                        UI.showError('Fehler beim Speichern der Einstellungen');
3867                    }
3868                },
3869                
3870                getSyncDirectionLabel(direction) {
3871                    const labels = {
3872                        'bidirectional': 'Bidirektional',
3873                        'from_google': 'Von Google zu voiceOne',
3874                        'to_google': 'Von voiceOne zu Google'
3875                    };
3876                    return labels[direction] || direction;
3877                },
3878                
3879                setupEventListeners() {
3880                    // Check if we're returning from OAuth callback
3881                    const urlParams = new URLSearchParams(window.location.search);
3882                    const code = urlParams.get('code');
3883                    const state = urlParams.get('state');
3884                    const error = urlParams.get('error');
3885                    
3886                    console.log('OAuth callback check:', {
3887                        hasCode: !!code,
3888                        hasState: !!state,
3889                        hasError: !!error,
3890                        rawURL: window.location.search,
3891                        decodedState: state
3892                    });
3893                    
3894                    if (code || error) {
3895                        // We're returning from OAuth, handle the redirect
3896                        this.handleRedirect(code, state, error);
3897                    }
3898                },
3899                
3900                async handleRedirect(code, state, error) {
3901                    try {
3902                        if (error) {
3903                            console.error('Google OAuth Error:', error);
3904                            UI.showError(`Fehler bei der Verbindung mit Google: ${error}`);
3905                            return;
3906                        }
3907                        
3908                        if (!code) {
3909                            console.error('Kein Autorisierungscode erhalten');
3910                            UI.showError('Fehler bei der Google-Verbindung: Kein Code erhalten');
3911                            return;
3912                        }
3913                        
3914                        // Validate state
3915                        const savedState = sessionStorage.getItem('google_people_oauth_state');
3916                        console.log('State validation:', {
3917                            savedState: savedState,
3918                            receivedState: state,
3919                            statesMatch: savedState === state,
3920                            savedStateLength: savedState ? savedState.length : 0,
3921                            receivedStateLength: state ? state.length : 0
3922                        });
3923                        
3924                        // If no saved state, check if the received state is valid for Google People
3925                        if (!savedState && state) {
3926                            try {
3927                                const decodedState = JSON.parse(atob(state));
3928                                if (decodedState.service === 'people') {
3929                                    console.log('No saved state, but received valid Google People state - proceeding');
3930                                    // Continue with the flow
3931                                } else {
3932                                    console.error('Invalid state service:', decodedState.service);
3933                                    UI.showError('Ungültiger OAuth State');
3934                                    return;
3935                                }
3936                            } catch (e) {
3937                                console.error('Failed to decode state:', e);
3938                                UI.showError('Sicherheitsfehler bei der Google-Verbindung');
3939                                return;
3940                            }
3941                        } else if (savedState !== state) {
3942                            console.error('OAuth state mismatch - saved:', savedState, 'received:', state);
3943                            UI.showError('Sicherheitsfehler bei der Google-Verbindung');
3944                            return;
3945                        }
3946                        
3947                        // Clear state from session
3948                        sessionStorage.removeItem('google_people_oauth_state');
3949                        
3950                        // Remove OAuth params from URL immediately to prevent code reuse
3951                        const newUrl = window.location.pathname + window.location.hash;
3952                        window.history.replaceState({}, document.title, newUrl);
3953                        
3954                        // Exchange code for token
3955                        await this.exchangeCodeForToken(code);
3956                        
3957                    } catch (error) {
3958                        console.error('Fehler beim Verarbeiten der OAuth-Weiterleitung:', error);
3959                        UI.showError('Fehler bei der Google-Verbindung');
3960                    }
3961                }
3962            };*/
3963            
3964            // Debug logging
3965            console.log('[DEBUG] Google Contacts Integration Page Loading');
3966            console.log('[DEBUG] GooglePeopleIntegration available?', typeof GooglePeopleIntegration);
3967            console.log('[DEBUG] GooglePeopleIntegration object:', GooglePeopleIntegration);
3968            
3969            // Use the imported GooglePeopleIntegration module
3970            // Already available globally through googlePeople.js
3971            
3972            try {
3973                // Initialize the imported module
3974                console.log('[DEBUG] Calling GooglePeopleIntegration.init()');
3975                await GooglePeopleIntegration.init();
3976                console.log('[DEBUG] GooglePeopleIntegration.init() completed');
3977            } catch (initError) {
3978                console.error('[DEBUG] Error initializing GooglePeopleIntegration:', initError);
3979                console.error('[DEBUG] Stack trace:', initError.stack);
3980            }
3981            
3982        } catch (error) {
3983            console.error('Error loading Google Contacts integration:', error);
3984            mainContent.innerHTML = `
3985                <div class="container py-4">
3986                    <div class="alert alert-danger">
3987                        Fehler beim Laden der Google Contacts Integration: ${error.message}
3988                    </div>
3989                    <button class="btn btn-secondary" onclick="Pages.loadPage('system-interfaces')">
3990                        <i class="fas fa-arrow-left"></i> Zurück
3991                    </button>
3992                </div>
3993            `;
3994        }
3995    }
3996}
3997
3998// Make Pages available globally
3999window.Pages = Pages;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.