Agent-completed splits committed after agents hit rate limits before committing their work. All 4 pages now under 500 LOC: - consent-management: 1303 -> 193 LOC (+ 7 _components, _hooks, _data, _types) - control-library: 1210 -> 298 LOC (+ _components, _types) - incidents: 1150 -> 373 LOC (+ _components) - training: 1127 -> 366 LOC (+ _components) Verification: next build clean (142 pages generated). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
139 lines
6.1 KiB
TypeScript
139 lines
6.1 KiB
TypeScript
'use client'
|
|
|
|
import type { ApiEmailTemplate, EmailTemplateData } from '../_types'
|
|
import { emailTemplates, emailCategories } from '../_data'
|
|
import { ApiTemplateEditor } from './ApiTemplateEditor'
|
|
|
|
export function EmailsTab({
|
|
apiEmailTemplates,
|
|
templatesLoading,
|
|
savingTemplateId,
|
|
savedTemplates,
|
|
setShowCreateTemplateModal,
|
|
saveApiEmailTemplate,
|
|
setPreviewTemplate,
|
|
setEditingTemplate,
|
|
}: {
|
|
apiEmailTemplates: ApiEmailTemplate[]
|
|
templatesLoading: boolean
|
|
savingTemplateId: string | null
|
|
savedTemplates: Record<string, EmailTemplateData>
|
|
setShowCreateTemplateModal: (v: boolean) => void
|
|
saveApiEmailTemplate: (t: { id: string; subject: string; body: string }) => void
|
|
setPreviewTemplate: (t: EmailTemplateData | null) => void
|
|
setEditingTemplate: (t: EmailTemplateData | null) => void
|
|
}) {
|
|
return (
|
|
<div className="p-6">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-slate-900">E-Mail Vorlagen</h2>
|
|
<p className="text-sm text-slate-500 mt-1">
|
|
{apiEmailTemplates.length > 0
|
|
? `${apiEmailTemplates.length} DSGVO-Vorlagen aus der Datenbank`
|
|
: '16 Lifecycle-Vorlagen fuer automatisierte Kommunikation'}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => setShowCreateTemplateModal(true)}
|
|
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-sm font-medium"
|
|
>
|
|
+ Neue Vorlage
|
|
</button>
|
|
</div>
|
|
|
|
{/* API-backed templates section */}
|
|
{templatesLoading ? (
|
|
<div className="text-center py-8 text-slate-500">Lade Vorlagen aus der Datenbank...</div>
|
|
) : apiEmailTemplates.length > 0 ? (
|
|
<div className="space-y-4 mb-8">
|
|
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3">DSGVO-Pflichtvorlagen</h3>
|
|
{apiEmailTemplates.map((template) => (
|
|
<ApiTemplateEditor
|
|
key={template.id}
|
|
template={template}
|
|
saving={savingTemplateId === template.id}
|
|
onSave={(subject, body) => saveApiEmailTemplate({ id: template.id, subject, body })}
|
|
onPreview={(subject, body) => setPreviewTemplate({ key: template.template_key, subject, body })}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
|
|
{/* Category Filter for static templates */}
|
|
{apiEmailTemplates.length === 0 && (
|
|
<>
|
|
<div className="flex flex-wrap gap-2 mb-6">
|
|
<span className="text-sm text-slate-500 py-1">Filter:</span>
|
|
{emailCategories.map((cat) => (
|
|
<span key={cat.key} className={`px-3 py-1 rounded-full text-xs font-medium ${cat.color}`}>
|
|
{cat.label}
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
{/* Templates grouped by category (fallback when no API data) */}
|
|
{emailCategories.map((category) => (
|
|
<div key={category.key} className="mb-8">
|
|
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3 flex items-center gap-2">
|
|
<span className={`w-2 h-2 rounded-full ${category.color.split(' ')[0]}`}></span>
|
|
{category.label}
|
|
</h3>
|
|
<div className="grid gap-3">
|
|
{emailTemplates
|
|
.filter((t) => t.category === category.key)
|
|
.map((template) => (
|
|
<div
|
|
key={template.key}
|
|
className="border border-slate-200 rounded-lg p-4 flex items-center justify-between hover:border-purple-300 transition-colors bg-white"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className={`w-10 h-10 rounded-lg ${category.color} flex items-center justify-center text-lg`}>
|
|
</div>
|
|
<div>
|
|
<h4 className="font-medium text-slate-900">{template.name}</h4>
|
|
<p className="text-sm text-slate-500">{template.description}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">
|
|
{savedTemplates[template.key] ? 'Angepasst' : 'Aktiv'}
|
|
</span>
|
|
<button
|
|
onClick={() => {
|
|
const existing = savedTemplates[template.key]
|
|
setEditingTemplate({
|
|
key: template.key,
|
|
subject: existing?.subject || `Betreff: ${template.name}`,
|
|
body: existing?.body || `Sehr geehrte(r) {{name}},\n\n${template.description}\n\nMit freundlichen Gruessen\nIhr Datenschutz-Team`,
|
|
})
|
|
}}
|
|
className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:border-slate-400"
|
|
>
|
|
Bearbeiten
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
const existing = savedTemplates[template.key]
|
|
setPreviewTemplate({
|
|
key: template.key,
|
|
subject: existing?.subject || `Betreff: ${template.name}`,
|
|
body: existing?.body || `Sehr geehrte(r) {{name}},\n\n${template.description}\n\nMit freundlichen Gruessen\nIhr Datenschutz-Team`,
|
|
})
|
|
}}
|
|
className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:border-slate-400"
|
|
>
|
|
Vorschau
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|