Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m9s
Build + Deploy / build-backend-compliance (push) Failing after 3m24s
Build + Deploy / build-ai-sdk (push) Successful in 52s
Build + Deploy / build-developer-portal (push) Successful in 1m15s
Build + Deploy / build-tts (push) Successful in 1m23s
Build + Deploy / build-document-crawler (push) Successful in 38s
Build + Deploy / build-dsms-gateway (push) Successful in 27s
CI / branch-name (push) Has been skipped
Build + Deploy / trigger-orca (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 18s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m42s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 41s
CI / test-python-backend (push) Successful in 41s
CI / test-python-document-crawler (push) Successful in 26s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 16s
Backend gibt variables manchmal als {} (Objekt) statt [] (Array)
zurueck. (template.variables || []).map() greift nicht weil {}
truthy ist. Fix: Array.isArray() Check in TemplateCard + EditorTab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
6.5 KiB
TypeScript
163 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
import { CATEGORIES, EmailTemplate, STATUS_BADGE, TemplateVersion } from '../_types'
|
|
|
|
interface EditorTabProps {
|
|
template: EmailTemplate | null
|
|
version: TemplateVersion | null
|
|
subject: string
|
|
html: string
|
|
previewHtml: string | null
|
|
saving: boolean
|
|
onSubjectChange: (v: string) => void
|
|
onHtmlChange: (v: string) => void
|
|
onSave: () => void
|
|
onPublish: () => void
|
|
onPreview: () => void
|
|
onBack: () => void
|
|
onSubmitForReview?: () => void
|
|
onApprove?: (comment?: string) => void
|
|
onReject?: (comment: string) => void
|
|
onSendTest?: (email: string) => void
|
|
}
|
|
|
|
export function EditorTab({
|
|
template, version, subject, html, previewHtml, saving,
|
|
onSubjectChange, onHtmlChange, onSave, onPublish, onPreview, onBack,
|
|
onSubmitForReview, onApprove, onReject, onSendTest,
|
|
}: EditorTabProps) {
|
|
if (!template) {
|
|
return (
|
|
<div className="text-center py-12 text-gray-500">
|
|
Waehlen Sie ein Template aus der Liste.
|
|
<br />
|
|
<button onClick={onBack} className="mt-2 text-purple-600 underline">Zurueck zur Liste</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const cat = CATEGORIES[template.category] || CATEGORIES.general
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<button onClick={onBack} className="text-gray-500 hover:text-gray-700">← Zurueck</button>
|
|
<h2 className="text-lg font-semibold">{template.name}</h2>
|
|
<span className={`px-2 py-0.5 rounded text-xs ${cat.bgColor} ${cat.color}`}>{cat.label}</span>
|
|
{version && (
|
|
<span className={`px-2 py-0.5 rounded text-xs ${(STATUS_BADGE[version.status] || STATUS_BADGE.draft).color}`}>
|
|
{(STATUS_BADGE[version.status] || STATUS_BADGE.draft).label}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-2 flex-wrap">
|
|
{/* Save — always available for draft/review */}
|
|
{(!version || version.status === 'draft' || version.status === 'review') && (
|
|
<button onClick={onSave} disabled={saving}
|
|
className="px-3 py-1.5 bg-purple-600 text-white rounded-lg text-sm hover:bg-purple-700 disabled:opacity-50">
|
|
{saving ? 'Speichern...' : 'Version speichern'}
|
|
</button>
|
|
)}
|
|
{/* Submit for Review — only for draft */}
|
|
{version && version.status === 'draft' && onSubmitForReview && (
|
|
<button onClick={onSubmitForReview} disabled={saving}
|
|
className="px-3 py-1.5 bg-yellow-500 text-white rounded-lg text-sm hover:bg-yellow-600 disabled:opacity-50">
|
|
Zur Pruefung einreichen
|
|
</button>
|
|
)}
|
|
{/* Approve — only for review status (DSB) */}
|
|
{version && version.status === 'review' && onApprove && (
|
|
<button onClick={() => onApprove()} disabled={saving}
|
|
className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 disabled:opacity-50">
|
|
Genehmigen
|
|
</button>
|
|
)}
|
|
{/* Reject — only for review status (DSB) */}
|
|
{version && version.status === 'review' && onReject && (
|
|
<button onClick={() => { const c = prompt('Ablehnungsgrund:'); if (c) onReject(c) }} disabled={saving}
|
|
className="px-3 py-1.5 bg-red-500 text-white rounded-lg text-sm hover:bg-red-600 disabled:opacity-50">
|
|
Ablehnen
|
|
</button>
|
|
)}
|
|
{/* Publish — only for approved */}
|
|
{version && version.status === 'approved' && (
|
|
<button onClick={onPublish} disabled={saving}
|
|
className="px-3 py-1.5 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 disabled:opacity-50">
|
|
Publizieren
|
|
</button>
|
|
)}
|
|
{/* Preview + Test — always when version exists */}
|
|
{version && (
|
|
<>
|
|
<button onClick={onPreview}
|
|
className="px-3 py-1.5 border border-gray-300 text-gray-700 rounded-lg text-sm hover:bg-gray-50">
|
|
Vorschau
|
|
</button>
|
|
{onSendTest && (
|
|
<button onClick={() => { const e = prompt('Test-E-Mail an:'); if (e) onSendTest(e) }}
|
|
className="px-3 py-1.5 border border-blue-300 text-blue-700 rounded-lg text-sm hover:bg-blue-50">
|
|
Test senden
|
|
</button>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Variables */}
|
|
<div className="flex flex-wrap gap-1.5">
|
|
<span className="text-xs text-gray-500 mr-1">Variablen:</span>
|
|
{(Array.isArray(template.variables) ? template.variables : []).map(v => (
|
|
<button
|
|
key={v}
|
|
onClick={() => onHtmlChange(html + `{{${v}}}`)}
|
|
className="px-2 py-0.5 bg-purple-50 text-purple-700 rounded text-xs font-mono hover:bg-purple-100 transition-colors"
|
|
>
|
|
{`{{${v}}}`}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Split View */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
{/* Editor */}
|
|
<div className="space-y-3">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Betreff</label>
|
|
<input
|
|
type="text"
|
|
value={subject}
|
|
onChange={e => onSubjectChange(e.target.value)}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
placeholder="E-Mail Betreff..."
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">HTML-Inhalt</label>
|
|
<textarea
|
|
value={html}
|
|
onChange={e => onHtmlChange(e.target.value)}
|
|
rows={20}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm font-mono focus:ring-2 focus:ring-purple-500 focus:border-transparent resize-y"
|
|
placeholder="<p>Sehr geehrte(r) {{user_name}},</p>"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preview */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Vorschau</label>
|
|
<div className="border border-gray-200 rounded-lg bg-white p-4 min-h-[400px]">
|
|
{previewHtml ? (
|
|
<div dangerouslySetInnerHTML={{ __html: previewHtml }} />
|
|
) : (
|
|
<div dangerouslySetInnerHTML={{ __html: html }} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|