PageSourceSearch

https://app.medi-one.ai/modules/multiCall.js

js medi-one.ai collected 2026-10-05 14:13:05 UTC 67,667 bytes, 1,383 lines download raw bytes

1// // phase8-multicall-migrated
2import { multiCallApi } from './multiCallApi.js';
3import { Auth } from './auth.js';
4import UI from './ui.js?v=20260820-dd1-imperr1-bann1-impauth1';
5import { Language } from './language.js';
6import { db } from '../firebase-init.js';
7import { usersApi } from './usersApi.js';
8import { 
9    collection, 
10    query, 
11    where, 
12    getDocs, 
13    orderBy, 
14    doc,
15    getDoc,
16    deleteDoc,
17    addDoc,
18    serverTimestamp,
19    updateDoc
20} from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js';
21
22// Translation helper function
23function t(text) {
24    return Language.translate(text);
25}
26
27// Add global styles
28const styleSheet = document.createElement('style');
29styleSheet.textContent = `
30    /* Desktop Table Styles */
31    .multicall-container.table-view .multicall-table { /* Klasse für Desktop-Tabelle */
32        width: 100%;
33        border-collapse: collapse;
34        table-layout: fixed; /* Wichtig für feste Spaltenbreiten */
35    }
36    .multicall-container.table-view .multicall-table th,
37    .multicall-container.table-view .multicall-table td {
38        vertical-align: middle;
39        overflow: hidden;
40        word-wrap: break-word;
41        padding: 8px;
42        border-top: 1px solid #dee2e6; /* Linie hinzufügen */
43    }
44    .multicall-container.table-view .multicall-table thead th {
45        background-color: #f8f9fa; /* Leichter Hintergrund für Header */
46        border-bottom: 2px solid #dee2e6;
47        white-space: nowrap; /* Header-Text nicht umbrechen */
48    }
49    .multicall-container.table-view .multicall-table tbody tr:hover {
50         cursor: pointer;
51         background-color: rgba(0, 0, 0, 0.05);
52    }
53
54    /* Spaltenbreiten für Desktop - Spezifischere Selektoren */
55    .multicall-container.table-view .multicall-table .date-column { width: 10%; }
56    .multicall-container.table-view .multicall-table .task-column { width: 15%; }
57    .multicall-container.table-view .multicall-table .greeting-column { width: 15%; }
58    .multicall-container.table-view .multicall-table .multicall-message-column { width: 25%; }
59    .multicall-container.table-view .multicall-table .details-column { width: 25%; }
60    .multicall-container.table-view .multicall-table .status-column { width: 5%; text-align: center; }
61    .multicall-container.table-view .multicall-table .actions-column { width: 10%; text-align: right; }
62
63    /* Mobile Card Styles */
64    @media (max-width: 767.98px) {
65         /* Container für Karten */
66        .multicall-container.card-view {
67            display: flex;
68            flex-direction: column;
69            gap: 15px; /* Abstand zwischen Karten */
70        }
71
72        /* Einzelne Karte */
73        .multicall-card {
74            background-color: #fff;
75            border-radius: 8px;
76            border: 1px solid #dee2e6;
77            box-shadow: 0 2px 4px rgba(0,0,0,0.05);
78            overflow: hidden; /* Verhindert, dass Inhalte überlaufen */
79            cursor: pointer; /* Zeigt Klickbarkeit an */
80        }
81
82        /* Karten-Header */
83        .multicall-card .card-header {
84            display: flex;
85            justify-content: space-between;
86            align-items: center;
87            padding: 10px 15px;
88            background-color: #f8f9fa;
89            border-bottom: 1px solid #dee2e6;
90            font-size: 0.9em;
91        }
92        .multicall-card .card-date {
93            color: #6c757d;
94            flex-shrink: 0; /* Verhindert das Schrumpfen des Datums */
95            margin-right: 10px;
96        }
97        .multicall-card .card-task {
98            font-weight: 500;
99            white-space: nowrap;
100            overflow: hidden;
101            text-overflow: ellipsis;
102            flex-grow: 1; /* Nimmt verfügbaren Platz ein */
103             margin-right: 10px;
104        }
105        .multicall-card .card-status .badge {
106             font-size: 0.8em; /* Kleinere Badge Schrift */
107             padding: 0.3em 0.6em;
108        }
109
110        /* Karten-Body */
111        .multicall-card .card-body {
112            padding: 15px;
113        }
114        .multicall-card .card-section {
115             margin-bottom: 10px;
116        }
117         .multicall-card .card-section:last-child {
118             margin-bottom: 0;
119         }
120        .multicall-card .card-section strong {
121             display: block;
122             margin-bottom: 3px;
123             font-size: 0.85em;
124             color: #495057;
125        }
126        .multicall-card .card-section span {
127            font-size: 0.95em;
128            color: #343a40;
129            line-height: 1.4;
130            white-space: pre-wrap; /* Zeilenumbrüche beibehalten */
131            word-break: break-word; /* Lange Wörter umbrechen */
132            display: block; /* Sorgt für korrekten Umbruch */
133        }
134
135        /* Karten-Footer */
136        .multicall-card .card-footer {
137            display: flex;
138            justify-content: flex-end; /* Buttons rechts ausrichten */
139            align-items: center;
140            padding: 10px 15px;
141            background-color: #f8f9fa;
142            border-top: 1px solid #dee2e6;
143        }
144        .multicall-card .card-actions {
145            display: flex;
146            gap: 8px; /* Abstand zwischen Buttons */
147            align-items: center;
148        }
149        .multicall-card .card-actions .btn {
150             width: 32px; /* Feste Breite für Buttons */
151             height: 32px; /* Feste Höhe für Buttons */
152             padding: 0; /* Kein Innenabstand */
153             display: flex;
154             align-items: center;
155             justify-content: center;
156             font-size: 0.9rem; /* Icon-Größe anpassen */
157        }
158         .multicall-card .copy-btn {
159             background-color: #6b7280; /* Mittelgrau */
160         }
161          .multicall-card .delete-btn {
162             background-color: #374151; /* Dunkelgrau */
163         }
164          .multicall-card .edit-btn { /* Optionaler Edit-Button */
165             background-color: #2e2e2e; /* Hauptfarbe */
166         }
167
168         /* Hide table if card view is active */
169        .multicall-container.table-view .multicall-table {
170            display: none;
171        }
172    }
173
174    /* Styles für Textumbruch und Scrollbar (gelten für beide Ansichten, wo .text-wrap verwendet wird) */
175    .text-wrap {
176        white-space: pre-wrap;
177        word-break: break-word;
178        max-height: 100px; /* Begrenzung für Desktop */
179        overflow-y: auto;
180        display: block;
181    }
182    .text-wrap::-webkit-scrollbar {
183        width: 6px;
184    }
185    .text-wrap::-webkit-scrollbar-track {
186        background: #f1f1f1;
187        border-radius: 3px;
188    }
189    .text-wrap::-webkit-scrollbar-thumb {
190        background: #888;
191        border-radius: 3px;
192    }
193    .text-wrap::-webkit-scrollbar-thumb:hover {
194        background: #555;
195    }
196`;
197document.head.appendChild(styleSheet);
198
199export class MultiCall {
200    static isMobileView = window.matchMedia('(max-width: 767.98px)').matches; // Bildschirmgröße prüfen
201
202    // Outbound-Templates für MultiCall
203    static outboundTemplates = {
204        coldCall: {
205            task: 'Kaltakquise - Erstkontakt',
206            greeting: 'Guten Tag {name}, mein Name ist [Ihr Name] von VoiceOne. Ich hoffe, ich erwische Sie gerade nicht ungünstig?',
207            message: `## **Grund des Anrufs**
208Der Grund meines Anrufs: Wir arbeiten mit vielen Unternehmen in Ihrer Branche zusammen, die durch unsere KI-Telefonassistenten **bis zu 60% ihrer Anrufe automatisieren** und dadurch ihr Team massiv entlasten.
209
210## **Vorteile & Bedarf klären**
211Bei [Branche des Kunden] sehen wir oft diese Herausforderungen:
212- **Überlastete Telefonleitungen** in Stoßzeiten
213- **Wiederkehrende Anfragen** die viel Zeit kosten
214- **Mitarbeiter** die für wichtigere Aufgaben gebraucht werden
215
216Unsere Kunden berichten von:
217✓ **80% weniger Routineanfragen** am Telefon
218✓ **24/7 Erreichbarkeit** ohne Mehrkosten
219✓ **Zufriedenere Kunden** durch sofortige Antworten
220
221Wie handhaben Sie aktuell die telefonische Erreichbarkeit? Gibt es Zeiten, wo das Team überlastet ist?`,
222            message2: `## **Interesse & nächste Schritte**
223Eine **15-minütige Live-Demo** zeigt am besten, wie das konkret bei Ihnen aussehen könnte. Völlig unverbindlich natürlich. Hätten Sie diese Woche 15 Minuten - Dienstag oder Donnerstag?
224
225## **🛑 Einwandbehandlung**
226❌ **"Keine Zeit/kein Bedarf"**
227✅ "Verstehe ich. Nur aus Neugier: Wenn Sie **1 Stunde pro Tag** bei Telefonaten sparen könnten - wäre das interessant?"
228
229❌ **"Zu teuer/Budget"**
230✅ "Die Investition amortisiert sich meist in 2-3 Monaten durch die Zeitersparnis. Aber lassen Sie uns erst schauen, ob es überhaupt passt. Die Demo kostet nichts außer 15 Minuten Ihrer Zeit."
231
232❌ **"Haben bereits eine Lösung"**
233✅ "Super! Unsere KI ergänzt bestehende Systeme perfekt. Viele Kunden nutzen beides parallel. Soll ich Ihnen zeigen wie?"`
234        },
235        appointment: {
236            task: 'Terminbestätigung',
237            greeting: 'Guten Tag {name}, hier ist [Ihr Name] von VoiceOne. Ich rufe wegen unseres Termins an.',
238            message: `## **Terminbestätigung**
239Ich wollte unseren Termin für **[Datum] um [Uhrzeit]** bestätigen und sicherstellen, dass bei Ihnen alles passt.
240
241## **Vorbereitung**
242Für unser Gespräch habe ich vorbereitet:
243✓ **Live-Demo** unseres KI-Telefonassistenten
244✓ **Anpassungsmöglichkeiten** für Ihre Branche
245✓ **ROI-Berechnung** basierend auf Ihrem Anrufvolumen
246
247## **Wichtige Fragen vorab**
248Damit ich die Demo optimal auf Sie abstimmen kann:
249- Wie viele Anrufe erhalten Sie durchschnittlich pro Tag?
250- Welche Art von Anfragen wiederholen sich am häufigsten?
251- Nutzen Sie bereits eine Telefonanlage oder CRM-System?`,
252            message2: `## **Teilnehmer & Technik**
253Wer wird von Ihrer Seite am Gespräch teilnehmen? Gerne können Sie Kollegen einladen, die davon profitieren würden.
254
255Für die Demo sende ich Ihnen einen Link - funktioniert Zoom bei Ihnen oder bevorzugen Sie Teams?
256
257## **🛑 Mögliche Änderungen**
258❌ **"Termin verschieben"**
259✅ "Kein Problem! Wann würde es diese oder nächste Woche besser passen?"
260
261❌ **"Termin absagen"**
262✅ "Schade, aber ich verstehe. Darf ich fragen, was sich geändert hat? Vielleicht kann ich Ihre Bedenken vorab klären?"`
263        },
264        followUp: {
265            task: 'Nachfass-Anruf',
266            greeting: 'Guten Tag {name}, hier ist wieder [Ihr Name] von VoiceOne. Ich hoffe, es geht Ihnen gut!',
267            message: `## **Bezug zum letzten Gespräch**
268Nach unserer Demo letzte Woche wollte ich Ihnen Zeit geben, alles mit Ihrem Team zu besprechen.
269
270## **Häufige Überlegungen ansprechen**
271Erfahrungsgemäß beschäftigen unsere Interessenten diese Fragen:
272- **"Wie aufwendig ist die Einrichtung?"** → Wir übernehmen alles, Sie müssen nur die Anforderungen nennen
273- **"Funktioniert das mit unserem System?"** → Wir integrieren uns 
273in alle gängigen Telefonanlagen
274- **"Was kostet das langfristig?"** → Die meisten Kunden sparen ab Monat 2 mehr ein, als sie investieren
275
276## **Status & Interesse abfragen**
277Welche Fragen sind bei Ihnen aufgekommen? Was hat Ihr Team gesagt?`,
278            message2: `## **Nächste Schritte vorschlagen**
279Viele Kunden starten mit einem **Pilotprojekt** für einen Bereich - z.B. nur Terminvereinbarungen. So sehen Sie schnell den Nutzen ohne großes Risiko.
280
281Soll ich Ihnen ein unverbindliches Angebot für einen Testlauf erstellen?
282
283## **🛑 Typische Einwände**
284❌ **"Noch nicht entschieden"**
285✅ "Völlig verständlich! Was bräuchten Sie für eine Entscheidung? Referenzen? Ein Testlauf? Gespräch mit einem bestehenden Kunden?"
286
287❌ **"Kein Budget dieses Jahr"**
288✅ "Verstehe! Viele starten deshalb erst im neuen Geschäftsjahr. Soll ich Sie im [Monat] nochmal kontaktieren? Dann können wir rechtzeitig planen."`
289        },
290        reactivation: {
291            task: 'Kundenreaktivierung',
292            greeting: 'Guten Tag {name}, hier ist [Ihr Name] von VoiceOne. Es ist schon eine Weile her!',
293            message: `## **Grund der Kontaktaufnahme**
294Ich melde mich, weil wir in den letzten Monaten unser System komplett überarbeitet haben - basierend auf Feedback von Kunden wie Ihnen.
295
296## **Was ist neu?**
297Die wichtigsten Verbesserungen:
298✓ **Noch natürlichere Stimmen** - kaum vom Menschen zu unterscheiden
299✓ **Branchenspezifische Vorlagen** - sofort einsatzbereit
300✓ **50% günstigere Preise** durch effizientere Technologie
301✓ **Integration in 200+ Systeme** - CRM, Kalender, Ticketsysteme
302
303## **Interesse wecken**
304Damals war [genanntes Problem/Einwand] ein Thema. Das haben wir speziell adressiert. Wäre es interessant zu sehen, wie die Lösung jetzt aussieht?`,
305            message2: `## **Angebot unterbreiten**
306Als **"Welcome Back"-Angebot** bekommen ehemalige Interessenten:
307- **3 Monate 30% Rabatt**
308- **Kostenlose Premium-Features**
309- **Persönlicher Success Manager**
310
311Soll ich Ihnen die neuen Möglichkeiten in einer kurzen Demo zeigen?
312
313## **🛑 Reaktivierungs-Einwände**
314❌ **"Haben uns anders entschieden"**
315✅ "Das respektiere ich! Läuft die aktuelle Lösung gut? Oft ergänzen wir bestehende Systeme perfekt."
316
317❌ **"Schlechte Erfahrungen mit KI"**
318✅ "Das höre ich öfter. Die Technologie hat sich enorm entwickelt. Würde ein kurzer Test Sie überzeugen?"`
319        },
320        survey: {
321            task: 'Kundenzufriedenheitsumfrage',
322            greeting: 'Guten Tag {name}, hier ist [Ihr Name] vom VoiceOne Qualitätsteam.',
323            message: `## **Zweck des Anrufs**
324Wir führen gerade unsere jährliche Kundenbefragung durch und Ihre Meinung ist uns besonders wichtig.
325
326## **Was wir wissen möchten**
327Nur 3 kurze Fragen:
3281. **Wie zufrieden sind Sie** mit unserem KI-Assistenten? (Skala 1-10)
3292. **Was funktioniert besonders gut** in Ihrem Alltag?
3303. **Was könnten wir verbessern?**
331
332## **Incentive anbieten**
333Als Dankeschön für Ihre Zeit:
334✓ **Gutschein über 50€** für Ihren nächsten Monat
335✓ **Exklusiver Einblick** in neue Features vor allen anderen
336✓ **Persönliches Optimierungsgespräch** mit unserem Expertenteam
337
338Haben Sie jetzt 3 Minuten oder passt Ihnen ein anderer Zeitpunkt besser?`,
339            message2: `## **Zusätzliche Erkenntnisse**
340Übrigens: 87% unserer Kunden wissen gar nicht, dass sie diese Features nutzen können:
341- **Mehrsprachigkeit** (20+ Sprachen)
342- **SMS-Versand** nach Anrufen
343- **Automatische Termine** im Kalender
344
345Nutzen Sie diese Funktionen bereits?
346
347## **🛑 Umfrage-Einwände**
348❌ **"Keine Zeit für Umfragen"**
349✅ "Verstehe ich! Wie wäre es mit einer 60-Sekunden-Blitz-Umfrage? Nur eine Frage: Was ist Ihr größter Wunsch an uns?"
350
351❌ **"Nichts zu verbessern"**
352✅ "Das ist großartig! Dann würde uns interessieren: Was macht uns in Ihren Augen besonders? Das hilft uns, diese Stärken auszubauen."`
353        },
354        setter: {
355            task: 'Setter - Terminvereinbarung',
356            greeting: 'Guten Tag {name}, hier ist [Ihr Name]. Ich habe eine wichtige Information für Sie!',
357            message: `## **Exklusivität schaffen**
358Unser Geschäftsführer [Name] ist nächste Woche persönlich in [Stadt/Region] und hat noch **2 Termine** für Strategiegespräche frei.
359
360## **Qualifikation & Nutzen**
361Er trifft sich nur mit Unternehmen, die:
362- Mehr als **50 Anrufe täglich** erhalten
363- **Wachstumspläne** für die nächsten 12 Monate haben
364- Bereit sind, **innovative Lösungen** einzusetzen
365
366In diesem Gespräch erhalten Sie:
367✓ **Analyse Ihrer Telefonie-Kosten** (Wert: 500€)
368✓ **3 konkrete Optimierungsvorschläge**
369✓ **Branchen-Benchmark** Ihrer Erreichbarkeit
370
371## **Termin-Alternativen**
372Welcher Termin würde bei Ihnen passen:
373- Dienstag, 14:00 oder 16:00 Uhr?
374- Donnerstag, 10:00 oder 15:00 Uhr?`,
375            message2: `## **Dringlichkeit unterstreichen**
376Diese Termine sind wirklich die einzigen in den nächsten 3 Monaten. Normalerweise macht er nur Termine mit Bestandskunden.
377
378Soll ich Ihnen einen der Slots reservieren?
379
380## **🛑 Termin-Einwände**
381❌ **"Geschäftsführer hat keine Zeit"**
382✅ "Verstehe! Alternativ könnte auch Ihr Leiter Kundenservice teilnehmen. Hauptsache jemand, der die Telefonie-Herausforderungen kennt."
383
384❌ **"Erst mal Infos zusenden"**
385✅ "Mache ich gerne! Aber die wertvollsten Insights gibt's nur im persönlichen Gespräch. Die 30 Minuten lohnen sich - versprochen!"`
386        },
387        closer: {
388            task: 'Closer - Verkaufsabschluss',
389            greeting: 'Guten Tag {name}, schön dass wir heute zum Abschluss kommen können!',
390            message: `## **Zusammenfassung & Bestätigung**
391Lassen Sie uns kurz rekapitulieren, was wir besprochen haben:
392- Ihre aktuelle Herausforderung: **[Problem/Bedarf]**
393- Unsere Lösung: **[Produkt/Service]**
394- Ihr Nutzen: **[konkreter Vorteil]**
395- Zeitrahmen: **[Implementierung/Lieferung]**
396
397Habe ich alles richtig zusammengefasst? Gibt es noch Punkte, die unklar sind?
398
399## **Investment & Wertversprechen**
400Die Investition beträgt **[Betrag]**.
401Dafür erhalten Sie:
402✓ **[Hauptleistung]** im Wert von [Vergleichswert]
403✓ **[Zusatzleistung]** inklusive
404✓ **[Garantie/Support]** für [Zeitraum]
405
406Basierend auf Ihrer Situation rechnet sich das bereits nach **[ROI-Zeitraum]**.
407
408## **Entscheidung herbeiführen**
409Ich sehe drei Möglichkeiten für Sie:
4101. **Starter-Paket**: [Beschreibung] - ideal zum Testen
4112. **Professional**: [Beschreibung] - unser Bestseller
4123. **Premium**: [Beschreibung] - für maximale Ergebnisse
413
414Welche Variante passt am besten zu Ihren Zielen?`,
415            message2: `## **Verbindlichkeit schaffen**
416Perfekt! Für den Start brauche ich nur noch:
417- **Ihre Rechnungsadresse** für den Vertrag
418- **Gewünschter Starttermin**
419- **Ansprechpartner** für die Umsetzung
420
421Soll ich das gleich aufnehmen und Ihnen die Unterlagen heute noch zusenden?
422
423## **🛑 Universelle Abschluss-Einwände**
424❌ **"Muss nochmal drüber schlafen"**
425✅ "Absolut verständlich! Was genau beschäftigt Sie noch? Oft kann ich letzte Unsicherheiten direkt klären."
426
427❌ **"Budget ist knapp"**
428✅ "Das verstehe ich. Deshalb frage ich: Was kostet es Sie, wenn Sie NICHT handeln? Oft ist Nichtstun teurer als die Investition."
429
430❌ **"Muss Rücksprache halten"**
431✅ "Natürlich! Wer muss noch überzeugt werden? Ich stelle Ihnen gerne Unterlagen zusammen oder bin beim Gespräch dabei."
432
433❌ **"Ist mir zu schnell"**
434✅ "Kein Problem! Was würde Ihnen mehr Sicherheit geben? Referenzen? Testphase? Garantieverlängerung?"`
435        }
436    };
437
438    static async load() {
439        const mainContent = document.getElementById('mainContent');
440        // Initial-HTML abhängig von der Ansicht setzen
441        mainContent.innerHTML = `
442            <style>
443                .multicall-header {
444                    display: flex;
445                    justify-content: space-between;
446                    align-items: center;
447                    margin-bottom: 2rem;
448                    padding: 1.5rem;
449                    background: linear-gradient(135deg, #ffffff 0%, #f5f5f5 100%);
450                    border-radius: 8px;
451                    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
452                }
453                .multicall-header h1 {
454                    color: #2e2e2e;
455                    font-size: 1.5rem;
456                    margin: 0;
457                }
458                .multicall-actions {
459                    display: flex;
460                    gap: 10px;
461                    align-items: center;
462                }
463            </style>
464            <div class="container-fluid">
465                <div class="multicall-header">
466                    <h1>${t('Outbound Übersicht')}</h1>
467                    <div class="multicall-actions">
468                        <button class="btn btn-primary" onclick="MultiCall.showNewModal()">
469                            <i class="fas fa-plus"></i> ${t('Neuer Eintrag')}
470                        </button>
471                    </div>
472                </div>
473                
474                <div class="card shadow-sm mb-4" style="border-radius: 15px; background: white;">
475                    <div class="card-body p-4">
476                        <div class="d-flex justify-content-between align-items-center flex-wrap">
477                            <div class="flex-grow-1 me-3 mb-2 mb-md-0" style="min-width: 250px;">
478                                <div class="input-group">
479                                    <span class="input-group-text" style="background: #F8F9FA; border-right: none;">
480                                        <i class="fas fa-search text-muted"></i>
481                                    </span>
482                                    <input type="text" class="form-control border-start-0" id="multicallSearch" 
483                                           placeholder="${t('Suche in MultiCall Einträgen...')}"
484                                           style="border-left: none;">
485                                </div>
486                            </div>
487                        </div>
488                    </div>
489                </div>
490
491                <!-- Container für Einträge (Tabelle oder Karten) -->
492                <div id="multicallContainer" class="multicall-container">
493                    ${this.isMobileView ? 
494                        '<!-- Karten werden hier geladen -->' : 
495                        `<div class="table-responsive">
496                            <table class="table table-hover multicall-table">
497                                <thead>
498                                    <tr>
499                                        <th class="date-column">${t('Datum')}</th>
500                                        <th class="task-column">${t('Aufgabe')}</th>
501                                        <th class="greeting-column">${t('Begrüßung')}</th>
502                                        <th class="multicall-message-column">${t('Nachricht')}</th>
503                                        <th class="details-column">${t('Details')}</th>
504                                        <th class="status-column">${t('Status')}</th>
505                                        <th class="actions-column"></th>
506                                    </tr>
507                                </thead>
508                                <tbody>
509                                    <!-- Tabellenzeilen werden hier geladen -->
510                                </tbody>
511                            </table>
512                         </div>`
513                    }
514                </div>
515            </div>
516        `;
517        
518        // Add search event listener
519        const searchInput = document.getElementById('multicallSearch');
520        searchInput.addEventListener('input', () => {
521            this.search();
522        });
523
524        // Listener für Bildschirmgrößenänderung hinzufügen
525        window.matchMedia('(max-width: 767.98px)').addEventListener('change', async (e) => {
526            const isNowMobile = e.matches;
527            if (isNowMobile !== this.isMobileView) {
528                 console.log(`Ansicht geändert zu: ${isNowMobile ? 'Mobile' : 'Desktop'}. Lade MultiCall neu...`);
529                 this.isMobileView = isNowMobile;
530                 await this.load(); // Lade die gesamte Ansicht neu, um die Struktur korrekt zu ändern
531            }
532        });
533        
534        // Event-Listener für Klicks auf Einträge (Bearbeiten) und Buttons (Kopieren/Löschen)
535        this.bindEntryEvents();
536
537        await this.loadEntries();
538    }
539
540    // Funktion zum Binden der Event-Listener (Event Delegation)
541    static bindEntryEvents() {
542        const container = document.getElementById('multicallContainer');
543        if (!container) return;
544
545        // Alten Listener entfernen, falls vorhanden, um Duplikate zu vermeiden
546        container.removeEventListener('click', this.handleEntryClick); 
547        // Neuen Listener hinzufügen
548        container.addEventListener('click', this.handleEntryClick);
549    }
550
551    // Handler für Klick-Events im Container
552    static handleEntryClick = async (event) => { // Arrow function to keep 'this' context
553        const target = event.target;
554        const entryElement = target.closest('.multicall-row, .multicall-card'); // Finde Zeile oder Karte
555
556        if (!entryElement) return; // Klick war außerhalb eines Eintrags
557
558        const entryId = entryElement.dataset.entryId;
559        if (!entryId) return;
560
561        // Prüfen, ob auf einen Button geklickt wurde
562        const copyButton = target.closest('.copy-btn');
563        const deleteButton = target.closest('.delete-btn');
564
565        if (copyButton) {
566            event.stopPropagation(); // Verhindert, dass der Edit-Modal ausgelöst wird
567            await MultiCall.copy(entryId);
568        } else if (deleteButton) {
569            event.stopPropagation(); // Verhindert, dass der Edit-Modal ausgelöst wird
570            await MultiCall.delete(entryId);
571        } else {
572            // Welle 4: Backend-API statt direkter Firestore-Read
573            try {
574                const entries = await multiCallApi.list();
575                const entry = (entries || []).find(e => e && e.id === entryId);
576                if (entry) {
577                    MultiCall.showEditModal(entryId, entry);
578                } else {
579                    UI.showError('Eintrag nicht gefunden.');
580                }
581            } catch (error) {
582                console.error('Fehler beim Laden des Eintrags für Bearbeitung:', error);
583                UI.showError('Fehler beim Laden des Eintrags.');
584            }
585        }
586    }
587
588
589    static async loadEntries(filters = {}) {
590        this.isMobileView = window.matchMedia('(max-width: 767.98px)').matches; // Aktuellen Status prüfen
591        console.log(`Lade Einträge (isMobileView: ${this.isMobileView})`);
592
593        const user = Auth.getCurrentUser();
594        if (!user) {
595            UI.showError('Benutzer ist nicht angemeldet');
596            return;
597        }
598
599        try {
600            // Welle 4: Backend-API statt direkter Firestore-Read
601            const userData = await usersApi.me();
602            if (!userData) {
603                UI.showError('Benutzerdaten nicht gefunden');
604                return;
605            }
606            const tenantId = userData.tenant_id || userData.tenantID || userData.tenantId;
607            if (!tenantId) {
608                UI.showError('Keine Tenant-ID gefunden');
609                return;
610            }
611
612            /* phase8: was getDocs(multicall, tenantID==tenantId) */
613            const __mcArr = await multiCallApi.list();
614            const querySnapshot = { docs: __mcArr.map(m => ({ id: m.id, data: () => m })), forEach: function(cb) { this.docs.forEach(cb); } };
615            const container = document.getElementById('multicallContainer');
616            if (!container) {
617                console.error('Container #multicallContainer nicht gefunden');
618                return;
619            }
620
621            // Container leeren und Klasse entsprechend setzen
622            container.innerHTML = ''; // Leeren
623            container.className = 'multicall-container'; // Reset classes
624             if (this.isMobileView) {
625                 container.classList.add('card-view');
626             } else {
627                 container.classList.add('table-view');
628                 // Tabelle neu erstellen, wenn im Desktop-Modus
629                 container.innerHTML = `
630                    <div class="table-responsive">
631                         <table class="table table-hover multicall-table">
632                             <thead>
633                                 <tr>
634                                     <th class="date-column">${t('Datum')}</th>
635                                     <th class="task-column">${t('Aufgabe')}</th>
636                                     <th class="greeting-column">${t('Begrüßung')}</th>
637                                     <th class="multicall-message-column">${t('Nachricht')}</th>
638                                     <th class="details-column">${t('Details')}</th>
639                                     <th class="status-column">${t('Status')}</th>
640                                     <th class="actions-column"></th>
641                                 </tr>
642                             </thead>
643                             <tbody>
644                                 <!-- Zeilen werden hier eingefügt -->
645                             </tbody>
646                         </table>
647                     </div>`;
648            }
649
650            if (querySnapshot.empty) {
651                 // Nachricht in den passenden Container einfügen
652                 const emptyMessage = `<div class="text-center p-3">${t('Keine MultiCall-Einträge gefunden')}</div>`;
653                 if (this.isMobileView) {
654                     container.innerHTML = emptyMessage;
655                 } else {
656                     const tbody = container.querySelector('tbody');
657                     if (tbody) tbody.innerHTML = `<tr><td colspan="7" class="text-center">${t('Keine MultiCall-Einträge gefunden')}</td></tr>`;
658                     else container.innerHTML = emptyMessage; // Fallback
659                 }
660                return;
661            }
662
663            querySnapshot.forEach((doc) => {
664                const entry = doc.data();
665                const entryId = doc.id;
666
667                // Filter out entries with "Sprachbefehl:" in the task
668                if (entry.task && entry.task.startsWith('Sprachbefehl:')) {
669                    return; // Skip this entry
670                }
671
672                if (this.isMobileView) {
673                    const cardHtml = this.createEntryCardHTML(entryId, entry);
674                    container.insertAdjacentHTML('beforeend', cardHtml);
675                } else {
676                    const row = this.createEntryRow(entryId, entry);
677                    const tbody = container.querySelector('tbody');
678                     if(tbody) tbody.appendChild(row);
679                }
680            });
681
682            // Stelle sicher, dass die Event Listener (erneut) gebunden sind, falls die Struktur geändert wurde
683            this.bindEntryEvents(); 
684
685        } catch (error) {
686            console.error('Error loading multicall entries:', error);
687            UI.showError('Fehler beim Laden der MultiCall-Einträge');
688            // Bei Fehler ggf. Nachricht im Container anzeigen
689            const container = document.getElementById('multicallContainer');
690            if(container) container.innerHTML = '<div class="alert alert-danger">Fehler beim Laden der Daten.</div>';
691        }
692    }
693
694    // Behält die Logik zum Erstellen einer Tabellenzeile
695    static createEntryRow(entryId, entry) {
696        const date = entry.created_at?.seconds ? new Date(entry.created_at.seconds * 1000) : new Date();
697        const formattedDate = `${date.getDate().toString().padStart(2, '0')}.${(date.getMonth() + 1).toString().padStart(2, '0')}.${date.getFullYear()}, ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
698        
699        const row = document.createElement('tr');
700        row.className = 'multicall-row'; // Klasse hinzugefügt
701        row.dataset.entryId = entryId; // Datenattribut für ID
702        
703        // Die onclick-Logik wird jetzt über den Event-Listener am Container gehandhabt
704        // row.onclick = (e) => { ... }; // Entfernt
705        
706        row.innerHTML = `
707            <td class="date-column">${formattedDate}</td>
708            <td class="task-column"><div class="text-wrap">${entry.task || ''}</div></td>
709            <td class="greeting-column"><div class="text-wrap">${entry.greeting || ''}</div></td>
710            <td class="multicall-message-column"><div class="text-wrap">${entry.message || ''}</div></td>
711            <td class="details-column"><div class="text-wrap">${entry.message2 || ''}</div></td>
712            <td class="status-column">
713                <span class="badge ${entry.status === 'aktiv' ? 'bg-success' : 'bg-secondary'}">
714                    ${entry.status || ''}
715                </span>
716            </td>
717            <td class="actions-column text-end">
718                <button class="btn btn-sm copy-btn me-1" style="background-color: #6b7280; color: white; width: 32px; height: 32px; padding: 0;" title="Kopieren">
719                    <i class="fas fa-copy"></i>
720                </button>
721                <button class="btn btn-sm delete-btn" style="background-color: #374151; color: white; width: 32px; height: 32px; padding: 0;" title="Löschen">
722                    <i class="fas fa-trash"></i>
723                </button>
724            </td>
725        `;
726        
727        return row;
728    }
729
730     // NEUE FUNKTION zum Erstellen von Karten-HTML
731     static createEntryCardHTML(entryId, entry) {
732        const date = entry.created_at?.seconds ? new Date(entry.created_at.seconds * 1000) : new Date();
733        const formattedDate = `${date.getDate().toString().padStart(2, '0')}.${(date.getMonth() + 1).toString().padStart(2, '0')}.${date.getFullYear()}, ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
734        const statusBadgeClass = entry.status === 'aktiv' ? 'bg-success' : 'bg-secondary';
735        const statusText = entry.status || 'Unbekannt';
736
737        // Die onclick-Logik wird jetzt über den Event-Listener am Container gehandhabt
738        // onclick="MultiCall.showEditModal('${entryId}', JSON.parse('${JSON.stringify(entry).replace(/'/g, "\'")}'))" // Komplexe Übergabe, anfällig
739        return `
740            <div class="multicall-card" data-entry-id="${entryId}">
741                <div class="card-header">
742                    <span class="card-date">${formattedDate}</span>
743                    <span class="card-task">${entry.task || 'Unbenannte Aufgabe'}</span>
744                    <span class="card-status"><span class="badge ${statusBadgeClass}">${statusText}</span></span>
745                </div>
746                <div class="card-body">
747                    ${entry.greeting ? `<div class="card-section"><strong>Begrüßung:</strong><span>${entry.greeting}</span></div>` : ''}
748                    ${entry.message ? `<div class="card-section"><strong>Nachricht:</strong><span>${entry.message}</span></div>` : ''}
749                    ${entry.message2 ? `<div class="card-section"><strong>Details:</strong><span>${entry.message2}</span></div>` : ''}
750                </div>
751                <div class="card-footer">
752                     <div class="card-actions">
753                         <button class="btn copy-btn" title="Kopieren">
754                             <i class="fas fa-copy text-white"></i>
755                         </button>
756                         <button class="btn delete-btn" title="Löschen">
757                             <i class="fas fa-trash text-white"></i>
758                         </button>
759                    </div>
760                </div>
761            </div>
762        `;
763    }
764
765    // --- Bestehende Methoden (showEditModal, saveEdit, search, copy, delete, showNewModal, saveNew) bleiben weitgehend unverändert ---
766    // Es ist keine Anpassung in diesen Methoden nötig, da die Logik zum Laden der Daten für den Edit-Modal
767    // jetzt zentral im Event-Handler `handleEntryClick` liegt.
768
769    static async showEditModal(entryId, entry) {
770        const modalHtml = `
771            <div class="modal fade" id="editMultiCallModal" tabindex="-1" aria-labelledby="editMultiCallModalLabel" aria-hidden="true">
772                <div class="modal-dialog modal-lg">
773                    <div class="modal-content">
774                        <div class="modal-header">
775                            <h5 class="modal-title" id="editMultiCallModalLabel">
776                                <i class="fas fa-edit me-2"></i>${t('MultiCall bearbeiten')}
777                            </h5>
778                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
779                        </div>
780                        <div class="modal-body">
781                            <form id="editMultiCallForm">
782                                <input type="hidden" id="editEntryId" value="${entryId}">
783                                
784                                <div class="mb-3">
785                                    <label for="editMultiCallTask" class="form-label">${t('Aufgabe')} <span class="text-danger">*</span></label>
786                                    <textarea class="form-control" id="editMultiCallTask" rows="2" required>${entry.task || ''}</textarea>
787                                </div>
788
789                                <div class="mb-3">
790                                    <label for="editMultiCallGreeting" class="form-label">${t('Begrüßung')}</label>
791                                    <textarea class="form-control" id="editMultiCallGreeting" rows="2">${entry.greeting || ''}</textarea>
792                                    <small class="form-text text-muted">${t('Verwenden Sie {name} als Variable für den Namen des Kontakts')}</small>
793                                </div>
794
795                                <div class="mb-3">
796                                    <label for="editMultiCallMessage" class="form-label">${t('Nachricht')}</label>
797                                    <textarea class="form-control" id="editMultiCallMessage" rows="3">${entry.message || ''}</textarea>
798                                </div>
799
800                                <div class="mb-3">
801                                    <label for="editMultiCallMessage2" class="form-label">${t('Details')}</label>
802                                    <textarea class="form-control" id="editMultiCallMessage2" rows="3">${entry.message2 || ''}</textarea>
803                                </div>
804
805                                <div class="mb-3">
806                                    <label for="editMultiCallStatus" class="form-label">${t('Status')}</label>
807                                    <select class="form-select" id="editMultiCallStatus"> <!-- form-select statt form-control -->
808                                        <option value="aktiv" ${entry.status === 'aktiv' ? 'selected' : ''}>${t('Aktiv')}</option>
809                                        <option value="inaktiv" ${entry.status === 'inaktiv' ? 'selected' : ''}>Inaktiv</option>
810                                    </select>
811                                </div>
812
813                                <!-- Conversion Goal Section -->
814                                <div class="mb-3">
815                                    <label for="editMultiCallConversionGoal" class="form-label">${t('Conversion-Ziel')}</label>
816                                    <select class="form-select" id="editMultiCallConversionGoal">
817                                        <option value="appointment" ${entry.conversion_goal === 'appointment' ? 'selected' : ''}>${t('Termin vereinbaren')}</option>
818                                        <option value="email" ${entry.conversion_goal === 'email' ? 'selected' : ''}>E-Mail versenden</option>
819                                        <option value="callback" ${entry.conversion_goal === 'callback' ? 'selected' : ''}>Rückruf vereinbaren</option>
820                                        <option value="sale" ${entry.conversion_goal === 'sale' ? 'selected' : ''}>Verkaufsabschluss</option>
821                                        <option value="survey" ${entry.conversion_goal === 'survey' ? 'selected' : ''}>Umfrage durchführen</option>
822                                        <option value="info" ${entry.conversion_goal === 'info' ? 'selected' : ''}>Information übermitteln</option>
823                                        <option value="custom" ${entry.conversion_goal === 'custom' ? 'selected' : ''}>${t('Benutzerdefiniert')}</option>
824                                    </select>
825                                    <small class="form-text text-muted">${t('Definiert, was als erfolgreiche Conversion zählt')}</small>
826                                </div>
827                                
828                                <div class="mb-3" id="customConversionGoalDiv" style="display: ${entry.conversion_goal === 'custom' ? 'block' : 'none'};">
829                                    <label for="editMultiCallCustomGoal" class="form-label">Benutzerdefiniertes Ziel</label>
830                                    <input type="text" class="form-control" id="editMultiCallCustomGoal" 
831                                           value="${entry.custom_conversion_goal || ''}" 
832                                           placeholder="z.B. Webinar-Anmeldung">
833                                </div>
834                            </form>
835                        </div>
836                        <div class="modal-footer">
837                            <button type="button" class="btn btn-info" onclick="MultiCall.analyzeScript('${entryId}', '${(entry.task || '').replace(/'/g, "\\'")}')">
838                                <i class="fas fa-chart-line"></i> Script analysieren
839                            </button>
840                            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="Abbrechen">${t('Abbrechen')}</button>
841                            <button type="button" class="btn btn-primary" onclick="MultiCall.saveEdit()" data-i18n="Speichern">${t('Speichern')}</button>
842                        </div>
843                    </div>
844                </div>
845            </div>
846        `;
847
848        // Remove existing modal if it exists
849        const existingModal = document.getElementById('editMultiCallModal');
850        if (existingModal) {
851            const modalInstance = bootstrap.Modal.getInstance(existingModal);
852            if (modalInstance) {
853                modalInstance.hide(); // Verstecke zuerst, bevor es entfernt wird
854            }
855            existingModal.remove();
856        }
857
858        // Add modal to document
859        document.body.insertAdjacentHTML('beforeend', modalHtml);
860
861        // Show modal
862        const modalElement = document.getElementById('editMultiCallModal');
863        if (modalElement) {
864             const modal = new bootstrap.Modal(modalElement);
865             // Event listener hinzufügen, um das Modal aus dem DOM zu entfernen, nachdem es geschlossen wurde
866             modalElement.addEventListener('hidden.bs.modal', event => {
867                 modalElement.remove();
868             });
869             modal.show();
870             
871             // Add event listener for conversion goal change
872             const conversionGoalSelect = document.getElementById('editMultiCallConversionGoal');
873             const customGoalDiv = document.getElementById('customConversionGoalDiv');
874             
875             if (conversionGoalSelect && customGoalDiv) {
876                 conversionGoalSelect.addEventListener('change', function() {
877                     if (this.value === 'custom') {
878                         customGoalDiv.style.display = 'block';
879                     } else {
880                         customGoalDiv.style.display = 'none';
881                     }
882                 });
883             }
884        } else {
885             console.error("Konnte Modal-Element nicht finden nach dem Einfügen.");
886        }
887    }
888
889    static async saveEdit() {
890        try {
891            const entryId = document.getElementById('editEntryId').value;
892            const task = document.getElementById('editMultiCallTask').value;
893            const greeting = document.getElementById('editMultiCallGreeting').value;
894            const message = document.getElementById('editMultiCallMessage').value;
895            const message2 = document.getElementById('editMultiCallMessage2').value;
896            const status = document.getElementById('editMultiCallStatus').value;
897            const conversionGoal = document.getElementById('editMultiCallConversionGoal').value;
898            const customConversionGoal = document.getElementById('editMultiCallCustomGoal').value;
899
900            if (!task) {
901                UI.showError('Bitte geben Sie eine Aufgabe ein');
902                return;
903            }
904
905            const updateData = {
906                task,
907                greeting,
908                message,
909                message2,
910                status,
911                conversion_goal: conversionGoal,
912                updated_at: serverTimestamp()
913            };
914            
915            // Only add custom_conversion_goal if conversion_goal is 'custom'
916            if (conversionGoal === 'custom' && customConversionGoal) {
917                updateData.custom_conversion_goal = customConversionGoal;
918            }
919
920            await multiCallApi.update(entryId, updateData);  // phase8
921
922            // Close modal
923            const modalElement = document.getElementById('editMultiCallModal');
924            if (modalElement) {
925                const modal = bootstrap.Modal.getInstance(modalElement);
926                 if (modal) {
927                     modal.hide();
928                 } else {
929                     // Fallback, falls Instanz nicht gefunden wird
930                    modalElement.classList.remove('show');
931                    modalElement.style.display = 'none';
932                    document.body.classList.remove('modal-open');
933                    const backdrop = document.querySelector('.modal-backdrop');
934                    if(backdrop) backdrop.remove();
935                 }
936                 // Das Entfernen des Modals wird durch den 'hidden.bs.modal' Listener gehandhabt
937            }
938
939
940            // Show success message and refresh list
941            UI.showSuccess('MultiCall erfolgreich aktualisiert');
942            await this.loadEntries(); // Lädt die Einträge neu (aktualisiert Tabelle oder Karten)
943        } catch (error) {
944            console.error('Error updating multicall:', error);
945            UI.showError('Fehler beim Aktualisieren des MultiCall-Eintrags');
946        }
947    }
948
949     static search() {
950        const searchTerm = document.getElementById('multicallSearch').value.toLowerCase();
951        // Selektiert entweder Zeilen oder Karten, je nachdem was sichtbar ist
952        const items = document.querySelectorAll('#multicallContainer .multicall-row, #multicallContainer .multicall-card');
953
954        items.forEach(item => {
955            const text = item.textContent.toLowerCase();
956            // Prüft, ob das Element sichtbar ist (um versteckte Tabellenzeilen zu ignorieren, wenn Karten aktiv sind und umgekehrt)
957            const isVisible = item.offsetParent !== null;
958            if (isVisible) {
959                item.style.display = text.includes(searchTerm) ? '' : 'none';
960            } else if (!text.includes(searchTerm)) {
961                 // Wenn es versteckt ist UND nicht passt, explizit auf none setzen
962                 item.style.display = 'none';
963            } else {
964                 // Wenn es versteckt ist ABER passt, Display-Style nicht ändern (bleibt durch CSS versteckt)
965            }
966        });
967    }
968
969
970    static async copy(entryId) {
971        try {
972            // Welle 4: Backend-API statt direkter Firestore-Read
973            const entries = await multiCallApi.list();
974            const entry = (entries || []).find(e => e && e.id === entryId);
975            if (!entry) {
976                UI.showError('MultiCall-Eintrag nicht gefunden');
977                return;
978            }
979            
980            // Erstelle eine Kopie des Eintrags
981            const copiedEntry = {
982                task: `${entry.task} (Kopie)`,
983                greeting: entry.greeting,
984                message: entry.message,
985                message2: entry.message2,
986                status: entry.status,
987                mulitcall_id: Date.now(),
988                tenantID: entry.tenantID,
989                uid: entry.uid,
990                created_at: serverTimestamp()
991            };
992            
993            await multiCallApi.create(copiedEntry);  // phase8
994            
995            UI.showSuccess('MultiCall-Eintrag wurde erfolgreich kopiert');
996            await this.loadEntries(); // Lädt die Einträge neu
997        } catch (error) {
998            console.error('Error copying multicall entry:', error);
999            UI.showError('Fehler beim Kopieren des MultiCall-Eintrags');
1000        }
1001    }
1002
1003    static async delete(entryId) {
1004        try {
1005            // Bestätigungsdialog anzeigen
1006            const isConfirmed = confirm('Möchten Sie diesen MultiCall-Eintrag wirklich löschen?');
1007            
1008            if (!isConfirmed) {
1009                return; // Abbrechen wenn der Benutzer nicht bestätigt
1010            }
1011
1012            await multiCallApi.delete(entryId);  // phase8
1013            
1014             // Eintrag aus der UI entfernen (statt neu zu laden)
1015             const itemToRemove = document.querySelector(`.multicall-row[data-entry-id="${entryId}"], .multicall-card[data-entry-id="${entryId}"]`);
1016             if (itemToRemove) {
1017                 itemToRemove.remove();
1018                 // Prüfen, ob Container jetzt leer ist
1019                const container = document.getElementById('multicallContainer');
1020                if (container && !container.querySelector('.multicall-row, .multicall-card')) {
1021                     const emptyMessage = `<div class="text-center p-3">${t('Keine MultiCall-Einträge gefunden')}</div>`;
1022                    if (this.isMobileView) {
1023                         container.innerHTML = emptyMessage;
1024                     } else {
1025                         const tbody = container.querySelector('tbody');
1026                         if (tbody) tbody.innerHTML = `<tr><td colspan="7" class="text-center">${t('Keine MultiCall-Einträge gefunden')}</td></tr>`;
1027                         else container.innerHTML = emptyMessage; // Fallback
1028                     }
1029                }
1030             } else {
1031                // Fallback: Liste neu laden, wenn Element nicht gefunden wurde
1032                 await this.loadEntries();
1033             }
1034
1035            UI.showSuccess('MultiCall-Eintrag wurde erfolgreich gelöscht');
1036        } catch (error) {
1037            console.error('Error deleting multicall entry:', error);
1038            UI.showError('Fehler beim Löschen des MultiCall-Eintrags');
1039        }
1040    }
1041
1042    static showNewModal() {
1043        const modalHtml = `
1044            <div class="modal fade" id="newMultiCallModal" tabindex="-1" aria-labelledby="newMultiCallModalLabel" aria-hidden="true">
1045                <div class="modal-dialog modal-lg">
1046                    <div class="modal-content">
1047                        <div class="modal-header">
1048                            <h5 class="modal-title" id="newMultiCallModalLabel">
1049                                <i class="fas fa-plus me-2"></i>${t('Neuer MultiCall')}
1050                            </h5>
1051                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
1052                        </div>
1053                        <div class="modal-body">
1054                            <form id="newMultiCallForm">
1055                                <!-- Template-Auswahl -->
1056                                <div class="mb-4">
1057                                    <label class="form-label">${t('Outbound-Template wählen (optional)')}</label>
1058                                    <div class="row">
1059                                        <div class="col-md-6 mb-2">
1060                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1061                                                    onclick="MultiCall.applyTemplate('coldCall')">
1062                                                <i class="fas fa-phone me-2"></i>${t('Kaltakquise')}
1063                                            </button>
1064                                        </div>
1065                                        <div class="col-md-6 mb-2">
1066                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1067                                                    onclick="MultiCall.applyTemplate('appointment')">
1068                                                <i class="fas fa-calendar-alt me-2"></i>${t('Terminbestätigung')}
1069                                            </button>
1070                                        </div>
1071                                        <div class="col-md-6 mb-2">
1072                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1073                                                    onclick="MultiCall.applyTemplate('followUp')">
1074                                                <i class="fas fa-redo me-2"></i>${t('Follow-up')}
1075                                            </button>
1076                                        </div>
1077                                        <div class="col-md-6 mb-2">
1078                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1079                                                    onclick="MultiCall.applyTemplate('reactivation')">
1080                                                <i class="fas fa-user-clock me-2"></i>${t('Reaktivierung')}
1081                                            </button>
1082                                        </div>
1083                                        <div class="col-md-6 mb-2">
1084                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1085                                                    onclick="MultiCall.applyTemplate('survey')">
1086                                                <i class="fas fa-poll me-2"></i>${t('Umfrage')}
1087                                            </button>
1088                                        </div>
1089                                        <div class="col-md-6 mb-2">
1090                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1091                                                    onclick="MultiCall.applyTemplate('setter')">
1092                                                <i class="fas fa-handshake me-2"></i>${t('Setter (Terminvereinbarung)')}
1093                                            </button>
1094                                        </div>
1095                                        <div class="col-md-6 mb-2">
1096                                            <button type="button" class="btn btn-outline-primary w-100 text-start template-btn"
1097                                                    onclick="MultiCall.applyTemplate('closer')">
1098                                                <i class="fas fa-trophy me-2"></i>${t('Closer (Verkaufsabschluss)')}
1099                                            </button>
1100                                        </div>
1101                                    </div>
1102                                </div>
1103
1104                                <hr class="my-4">
1105
1106                                <div class="mb-3">
1107                                    <label for="newMultiCallTask" class="form-label">${t('Aufgabe')} <span class="text-danger">*</span></label>
1108                                    <textarea class="form-control" id="newMultiCallTask" rows="2" required
1109                                           placeholder="${t('z.B. Terminbestätigung')}">${''}</textarea>
1110                                    <small class="form-text text-muted">${t('Geben Sie hier den Zweck des MultiCalls ein')}</small>
1111                                </div>
1112
1113                                <div class="mb-3">
1114                                    <label for="newMultiCallGreeting" class="form-label">${t('Begrüßung')}</label>
1115                                    <textarea class="form-control" id="newMultiCallGreeting" rows="2"
1116                                              placeholder="${t('z.B. Guten Tag {name}')}">${t('Guten Tag {name}')}</textarea>
1117                                    <small class="form-text text-muted">${t('Verwenden Sie {name} als Variable für den Namen des Kontakts')}</small>
1118                                </div>
1119
1120                                <div class="mb-3">
1121                                    <label for="newMultiCallMessage" class="form-label">${t('Nachricht')}</label>
1122                                    <textarea class="form-control" id="newMultiCallMessage" rows="3"
1123                                              placeholder="${t('z.B. Ich möchte Sie an Ihren Termin morgen um 14:00 Uhr erinnern.')}"></textarea>
1124                                    <small class="form-text text-muted">${t('Geben Sie hier die Hauptnachricht ein')}</small>
1125                                </div>
1126
1127                                <div class="mb-3">
1128                                    <label for="newMultiCallMessage2" class="form-label">${t('Details')}</label>
1129                                    <textarea class="form-control" id="newMultiCallMessage2" rows="3"
1130                                              placeholder="${t('z.B. Bitte bringen Sie alle relevanten Unterlagen mit.')}"></textarea>
1131                                    <small class="form-text text-muted">${t('Fügen Sie hier zusätzliche Details oder Anweisungen hinzu')}</small>
1132                                </div>
1133
1134                                <div class="mb-3">
1135                                    <label for="newMultiCallStatus" class="form-label">${t('Status')}</label>
1136                                    <select class="form-select" id="newMultiCallStatus"> <!-- form-select statt form-control -->
1137                                        <option value="aktiv">${t('Aktiv')}</option>
1138                                        <option value="inaktiv">Inaktiv</option>
1139                                    </select>
1140                                    <small class="form-text text-muted">${t('Wählen Sie Aktiv für sofortige Aktivierung')}</small>
1141                                </div>
1142                            </form>
1143                        </div>
1144                        <div class="modal-footer">
1145                            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="Abbrechen">${t('Abbrechen')}</button>
1146                            <button type="button" class="btn btn-primary" onclick="MultiCall.saveNew()" data-i18n="Speichern">${t('Speichern')}</button>
1147                        </div>
1148                    </div>
1149                </div>
1150            </div>
1151        `;
1152
1153        // Remove existing modal if it exists
1154        const existingModal = document.getElementById('newMultiCallModal');
1155        if (existingModal) {
1156            const modalInstance = bootstrap.Modal.getInstance(existingModal);
1157            if(modalInstance) modalInstance.hide();
1158            existingModal.remove();
1159        }
1160
1161        // Add modal to document
1162        document.body.insertAdjacentHTML('beforeend', modalHtml);
1163
1164        // Show modal
1165        const modalElement = document.getElementById('newMultiCallModal');
1166        if (modalElement) {
1167             const modal = new bootstrap.Modal(modalElement);
1168             // Event listener hinzufügen, um das Modal aus dem DOM zu entfernen, nachdem es geschlossen wurde
1169             modalElement.addEventListener('hidden.bs.modal', event => {
1170                 modalElement.remove();
1171             });
1172             modal.show();
1173        } else {
1174             console.error("Konnte neues Modal-Element nicht finden nach dem Einfügen.");
1175        }
1176    }
1177
1178    static async saveNew() {
1179        try {
1180            const task = document.getElementById('newMultiCallTask').value;
1181            const greeting = document.getElementById('newMultiCallGreeting').value;
1182            const message = document.getElementById('newMultiCallMessage').value;
1183            const message2 = document.getElementById('newMultiCallMessage2').value;
1184            const status = document.getElementById('newMultiCallStatus').value;
1185
1186            if (!task) {
1187                UI.showError('Bitte geben Sie eine Aufgabe ein');
1188                return;
1189            }
1190
1191            const user = Auth.getCurrentUser();
1192            if (!user) {
1193                UI.showError('Benutzer ist nicht angemeldet');
1194                return;
1195            }
1196
1197            // Welle 4: Backend-API statt direkter Firestore-Read
1198            const userData = await usersApi.me();
1199            if (!userData) {
1200                UI.showError('Benutzerdaten nicht gefunden');
1201                return;
1202            }
1203            const tenantId = userData.tenant_id || userData.tenantID || userData.tenantId;
1204
1205            const newEntry = {
1206                task,
1207                greeting,
1208                message,
1209                message2,
1210                status,
1211                mulitcall_id: Date.now(),
1212                tenantID: tenantId.toString(),
1213                uid: user.uid,
1214                created_at: serverTimestamp()
1215            };
1216
1217            await multiCallApi.create(newEntry);  // phase8
1218
1219            // Close modal
1220            const modalElement = document.getElementById('newMultiCallModal');
1221            if (modalElement) {
1222                const modal = bootstrap.Modal.getInstance(modalElement);
1223                 if (modal) {
1224                     modal.hide();
1225                 } else {
1226                     // Fallback, falls Instanz nicht gefunden wird
1227                    modalElement.classList.remove('show');
1228                    modalElement.style.display = 'none';
1229                    document.body.classList.remove('modal-open');
1230                    const backdrop = document.querySelector('.modal-backdrop');
1231                    if(backdrop) backdrop.remove();
1232                 }
1233                 // Das Entfernen des Modals wird durch den 'hidden.bs.modal' Listener gehandhabt
1234            }
1235
1236            // Show success message and refresh list
1237            UI.showSuccess('MultiCall erfolgreich erstellt');
1238            await this.loadEntries(); // Lädt die Einträge neu
1239        } catch (error) {
1240            console.error('Error creating new multicall:', error);
1241            UI.showError('Fehler beim Erstellen des MultiCall-Eintrags');
1242        }
1243    }
1244
1245    // Funktion zum Anwenden eines Templates
1246    static applyTemplate(templateName) {
1247        const template = this.outboundTemplates[templateName];
1248        if (!template) {
1249            console.error('Template nicht gefunden:', templateName);
1250            return;
1251       
1251 }
1252
1253        // Felder mit Template-Daten füllen
1254        document.getElementById('newMultiCallTask').value = template.task;
1255        document.getElementById('newMultiCallGreeting').value = template.greeting;
1256        document.getElementById('newMultiCallMessage').value = template.message;
1257        document.getElementById('newMultiCallMessage2').value = template.message2;
1258
1259        // Visuelles Feedback: Markiere ausgewähltes Template
1260        const buttons = document.querySelectorAll('.template-btn');
1261        buttons.forEach(btn => btn.classList.remove('btn-primary'));
1262        buttons.forEach(btn => btn.classList.add('btn-outline-primary'));
1263        
1264        const clickedButton = event.target.closest('.template-btn');
1265        if (clickedButton) {
1266            clickedButton.classList.remove('btn-outline-primary');
1267            clickedButton.classList.add('btn-primary');
1268        }
1269
1270        // Zeige Success-Toast
1271        if (typeof Toastify === 'function') {
1272            Toastify({
1273                text: `Template "${template.task}" angewendet`,
1274                duration: 2000,
1275                gravity: "top",
1276                position: "right",
1277                backgroundColor: "#28a745"
1278            }).showToast();
1279        }
1280    }
1281    
1282    static async analyzeScript(entryId, templateName) {
1283        // Close the edit modal first
1284        const editModal = document.getElementById('editMultiCallModal');
1285        if (editModal) {
1286            const modalInstance = bootstrap.Modal.getInstance(editModal);
1287            if (modalInstance) {
1288                modalInstance.hide();
1289            }
1290        }
1291        
1292        // Get the conversion goal from the form before closing
1293        const conversionGoal = document.getElementById('editMultiCallConversionGoal')?.value || 'appointment';
1294        const customGoal = document.getElementById('editMultiCallCustomGoal')?.value || '';
1295        
1296        // Store the template name, entry ID and conversion goal for the script analysis page
1297        sessionStorage.setItem('analyzeTemplate', templateName);
1298        sessionStorage.setItem('analyzeEntryId', entryId);
1299        sessionStorage.setItem('analyzeConversionGoal', conversionGoal);
1300        if (conversionGoal === 'custom' && customGoal) {
1301            sessionStorage.setItem('analyzeCustomGoal', customGoal);
1302        }
1303        
1304        // Navigate to script analysis page
1305        window.location.hash = '#script-analysis';
1306    }
1307
1308    // New method for voice-initiated calls
1309    static async createAndStartCall(callData) {
1310        try {
1311            const user = Auth.getCurrentUser();
1312            if (!user) {
1313                throw new Error('User not authenticated');
1314            }
1315            
1316            const tenantId = await Auth.getCurrentTenantId();
1317            
1318            // Create MultiCall entry
1319            /* phase8: collection+addDoc -> multiCallApi.create */
1320            const newEntry = {
1321                ...callData,
1322                tenantID: tenantId.toString(),
1323                created_at: new Date().toISOString(),
1324                uid: user.uid,
1325                status: 'pending'
1326            };
1327            const __created2 = await multiCallApi.create(newEntry);
1328            const docRef = { id: __created2.id };
1329            
1330            // Welle 4: Backend-API statt direkter Firestore-Read
1331            const userData = (await usersApi.me()) || {};
1332            
1333            // Check if voice-specific workflow exists
1334            let workflowId = userData?.default_voice_workflow_id;
1335            
1336            if (!workflowId) {
1337                // Fall back to default outbound workflow
1338                workflowId = userData?.default_outbound_workflow_id;
1339            }
1340            
1341            if (workflowId) {
1342                // Trigger workflow through backend API
1343                const idToken = await user.getIdToken();
1344                const response = await fetch('/api/multicall/start', {
1345                    method: 'POST',
1346                    headers: {
1347                        'Authorization': `Bearer ${idToken}`,
1348                        'Content-Type': 'application/json'
1349                    },
1350                    body: JSON.stringify({
1351                        multicallId: docRef.id,
1352                        phoneNumber: callData.phone_number,
1353                        workflowId: workflowId,
1354                        agentPrompt: `${callData.greeting}\n\n${callData.message}`,
1355                        metadata: {
1356                            initiated_via: 'voice_command',
1357                            contact_id: callData.contact_id
1358                        }
1359                    })
1360                });
1361                
1362                if (!response.ok) {
1363                    const error = await response.json();
1364                    throw new Error(error.message || 'Failed to start call');
1365                }
1366                
1367                const result = await response.json();
1368                console.log('Voice-initiated call started:', result);
1369            } else {
1370                console.warn('No workflow configured for voice calls');
1371                UI.showWarning('Bitte konfigurieren Sie zuerst einen Workflow für ausgehende Anrufe.');
1372            }
1373            
1374            return docRef.id;
1375        } catch (error) {
1376            console.error('Error creating voice-initiated call:', error);
1377            throw error;
1378        }
1379    }
1380}
1381
1382// Add MultiCall to window object for event handlers
1383window.MultiCall = MultiCall;

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.