E-Mail generierung
This commit is contained in:
Binary file not shown.
@@ -20,7 +20,7 @@
|
|||||||
wunsch3?: { id: number; name: string };
|
wunsch3?: { id: number; name: string };
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
id: number;
|
id: number;
|
||||||
status?: 'pending' | 'accepted' | 'rejected'; // processing entfernt
|
status?: 'pending' | 'accepted' | 'rejected';
|
||||||
assignedDienststelle?: { id: number; name: string };
|
assignedDienststelle?: { id: number; name: string };
|
||||||
processedBy?: string;
|
processedBy?: string;
|
||||||
processedAt?: number;
|
processedAt?: number;
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
let isLoading = true;
|
let isLoading = true;
|
||||||
let error = '';
|
let error = '';
|
||||||
|
|
||||||
// Filter für Status (processing entfernt)
|
// Filter für Status
|
||||||
let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' = 'all';
|
let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' = 'all';
|
||||||
let filteredAnmeldungen: Anmeldung[] = [];
|
let filteredAnmeldungen: Anmeldung[] = [];
|
||||||
|
|
||||||
@@ -63,7 +63,16 @@ Ihr Praktikumsteam`;
|
|||||||
let isLoadingEmailConfig = false;
|
let isLoadingEmailConfig = false;
|
||||||
let isSavingEmailConfig = false;
|
let isSavingEmailConfig = false;
|
||||||
|
|
||||||
// Status-Badge Funktionen (processing entfernt)
|
// E-Mail Preview Modal State
|
||||||
|
let showEmailPreview = false;
|
||||||
|
let emailPreviewData: {
|
||||||
|
to: string;
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
} | null = null;
|
||||||
|
let emailCopied = false;
|
||||||
|
|
||||||
|
// Status-Badge Funktionen
|
||||||
function getStatusColor(status: string): string {
|
function getStatusColor(status: string): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending': return 'bg-yellow-100 text-yellow-800';
|
case 'pending': return 'bg-yellow-100 text-yellow-800';
|
||||||
@@ -110,7 +119,6 @@ Ihr Praktikumsteam`;
|
|||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
// Prüfen ob es ein Array ist
|
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
console.error('❌ Antwort ist kein Array:', data);
|
console.error('❌ Antwort ist kein Array:', data);
|
||||||
throw new Error('Antwort vom Server ist kein Array');
|
throw new Error('Antwort vom Server ist kein Array');
|
||||||
@@ -143,7 +151,6 @@ Ihr Praktikumsteam`;
|
|||||||
emailTemplate = config.template;
|
emailTemplate = config.template;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fehler beim Laden der E-Mail-Konfiguration:', err);
|
console.error('Fehler beim Laden der E-Mail-Konfiguration:', err);
|
||||||
// Fallback auf Standard-Werte bei Fehler
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingEmailConfig = false;
|
isLoadingEmailConfig = false;
|
||||||
}
|
}
|
||||||
@@ -208,8 +215,8 @@ Ihr Praktikumsteam`;
|
|||||||
|
|
||||||
showDialog = false;
|
showDialog = false;
|
||||||
|
|
||||||
// E-Mail senden nach erfolgreichem Annehmen
|
// E-Mail Vorschau öffnen nach erfolgreichem Annehmen
|
||||||
await sendAcceptanceEmail(selectedAnmeldungId, event.detail.dienststelleId);
|
openEmailPreview(selectedAnmeldungId, event.detail.dienststelleId);
|
||||||
|
|
||||||
selectedAnmeldungId = null;
|
selectedAnmeldungId = null;
|
||||||
await loadAnmeldungen();
|
await loadAnmeldungen();
|
||||||
@@ -219,13 +226,15 @@ Ihr Praktikumsteam`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendAcceptanceEmail(anmeldungId: number, dienststelleId: number) {
|
function openEmailPreview(anmeldungId: number, dienststelleId: number) {
|
||||||
const anmeldung = anmeldungen.find(a => a.id === anmeldungId);
|
const anmeldung = anmeldungen.find(a => a.id === anmeldungId);
|
||||||
if (!anmeldung) return;
|
if (!anmeldung) return;
|
||||||
|
|
||||||
// Dienststelle finden
|
// Dienststelle finden
|
||||||
const dienststelle = availableWishes.find(w => w.id === dienststelleId);
|
const dienststelle = availableWishes.find(w => w.id === dienststelleId);
|
||||||
const dienststelleName = dienststelle ? dienststelle.name.replace(/^\d+\.\s*Wunsch:\s*/, '') : 'Unbekannte Dienststelle';
|
const dienststelleName = dienststelle
|
||||||
|
? dienststelle.name.replace(/^\d+\.\s*Wunsch:\s*/, '')
|
||||||
|
: 'Unbekannte Dienststelle';
|
||||||
|
|
||||||
// E-Mail Text personalisieren
|
// E-Mail Text personalisieren
|
||||||
const personalizedEmail = emailTemplate
|
const personalizedEmail = emailTemplate
|
||||||
@@ -234,11 +243,43 @@ Ihr Praktikumsteam`;
|
|||||||
.replace(/\{nachname\}/g, anmeldung.nachname)
|
.replace(/\{nachname\}/g, anmeldung.nachname)
|
||||||
.replace(/\{dienststelle\}/g, dienststelleName);
|
.replace(/\{dienststelle\}/g, dienststelleName);
|
||||||
|
|
||||||
// mailto: Link erstellen
|
// Modal mit Vorschau öffnen
|
||||||
const mailtoLink = `mailto:${anmeldung.email}?subject=${encodeURIComponent(emailSubject)}&body=${encodeURIComponent(personalizedEmail)}`;
|
emailPreviewData = {
|
||||||
|
to: anmeldung.email,
|
||||||
|
subject: emailSubject,
|
||||||
|
body: personalizedEmail
|
||||||
|
};
|
||||||
|
emailCopied = false;
|
||||||
|
showEmailPreview = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Standard E-Mail Client öffnen
|
async function copyAndOpenMail() {
|
||||||
|
if (!emailPreviewData) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(emailPreviewData.body);
|
||||||
|
emailCopied = true;
|
||||||
|
|
||||||
|
// Kurz warten, dann Mail öffnen
|
||||||
|
setTimeout(() => {
|
||||||
|
const mailtoLink = `mailto:${emailPreviewData!.to}?subject=${encodeURIComponent(emailPreviewData!.subject)}`;
|
||||||
window.location.href = mailtoLink;
|
window.location.href = mailtoLink;
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Modal nach 2 Sekunden schließen
|
||||||
|
setTimeout(() => {
|
||||||
|
closeEmailPreview();
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Clipboard-Fehler:', err);
|
||||||
|
error = 'Konnte Text nicht kopieren. Bitte manuell markieren und kopieren.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEmailPreview() {
|
||||||
|
showEmailPreview = false;
|
||||||
|
emailPreviewData = null;
|
||||||
|
emailCopied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleReject(event: CustomEvent<{id: number}>) {
|
async function handleReject(event: CustomEvent<{id: number}>) {
|
||||||
@@ -305,7 +346,7 @@ Ihr Praktikumsteam`;
|
|||||||
<main class="max-w-7xl mx-auto px-4 py-6">
|
<main class="max-w-7xl mx-auto px-4 py-6">
|
||||||
<!-- Filter und E-Mail Konfiguration -->
|
<!-- Filter und E-Mail Konfiguration -->
|
||||||
<div class="mb-6 flex justify-between items-center">
|
<div class="mb-6 flex justify-between items-center">
|
||||||
<!-- Status Filter (processing entfernt) -->
|
<!-- Status Filter -->
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<label for="status-filter" class="text-sm font-medium text-gray-700">Filter:</label>
|
<label for="status-filter" class="text-sm font-medium text-gray-700">Filter:</label>
|
||||||
<select
|
<select
|
||||||
@@ -337,7 +378,7 @@ Ihr Praktikumsteam`;
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Übersicht (processing entfernt) -->
|
<!-- Status Übersicht -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
<div class="bg-white rounded-lg border border-gray-200 p-4">
|
<div class="bg-white rounded-lg border border-gray-200 p-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -504,6 +545,7 @@ Ihr Praktikumsteam`;
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Dienststellen Dialog -->
|
||||||
{#if showDialog}
|
{#if showDialog}
|
||||||
<DienststellenDialog
|
<DienststellenDialog
|
||||||
wishes={availableWishes}
|
wishes={availableWishes}
|
||||||
@@ -512,3 +554,118 @@ Ihr Praktikumsteam`;
|
|||||||
on:cancel={closeDialog}
|
on:cancel={closeDialog}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- E-Mail Vorschau Modal -->
|
||||||
|
{#if showEmailPreview && emailPreviewData}
|
||||||
|
<div class="fixed inset-0 z-50 overflow-y-auto">
|
||||||
|
<div class="flex min-h-screen items-center justify-center p-4">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
|
||||||
|
on:click={closeEmailPreview}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="relative bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center bg-white">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||||
|
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">E-Mail Vorschau</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
on:click={closeEmailPreview}
|
||||||
|
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||||||
|
>
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="px-6 py-4 overflow-y-auto max-h-[60vh]">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Empfänger -->
|
||||||
|
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||||
|
<span class="text-sm font-medium text-gray-500 w-16">An:</span>
|
||||||
|
<span class="text-sm text-gray-900">{emailPreviewData.to}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Betreff -->
|
||||||
|
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||||
|
<span class="text-sm font-medium text-gray-500 w-16">Betreff:</span>
|
||||||
|
<span class="text-sm text-gray-900">{emailPreviewData.subject}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nachricht (editierbar) -->
|
||||||
|
<div>
|
||||||
|
<label for="email-body" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Nachricht
|
||||||
|
<span class="font-normal text-gray-500">(kann bearbeitet werden)</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="email-body"
|
||||||
|
bind:value={emailPreviewData.body}
|
||||||
|
rows="14"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-3 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
|
||||||
|
disabled={emailCopied}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
||||||
|
{#if emailCopied}
|
||||||
|
<div class="flex items-center justify-center py-3">
|
||||||
|
<div class="flex items-center space-x-3 text-green-600">
|
||||||
|
<div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span class="font-medium">Text kopiert – Outlook öffnet sich...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div class="flex items-start space-x-2 text-sm text-gray-600">
|
||||||
|
<svg class="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
Der Text wird kopiert und Outlook geöffnet.<br class="hidden sm:inline" />
|
||||||
|
Dann mit <kbd class="px-1.5 py-0.5 bg-gray-200 rounded text-xs font-mono">Strg</kbd> + <kbd class="px-1.5 py-0.5 bg-gray-200 rounded text-xs font-mono">V</kbd> einfügen.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-3 w-full sm:w-auto">
|
||||||
|
<button
|
||||||
|
on:click={closeEmailPreview}
|
||||||
|
class="flex-1 sm:flex-none px-4 py-2.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={copyAndOpenMail}
|
||||||
|
class="flex-1 sm:flex-none bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-lg text-sm font-medium inline-flex items-center justify-center transition-colors"
|
||||||
|
>
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
|
||||||
|
</svg>
|
||||||
|
Kopieren & E-Mail öffnen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
Reference in New Issue
Block a user