4c92b17617
- New /sdk/rollenkonzept/ module with 3 tabs (Rollen, Zuordnung, Reviews) - 7 standard compliance roles (DSB, GF, IT-Leiter, HR, Marketing, Compliance, Einkauf) - Inline role editing with test email via Mailpit - Document-to-role mapping table (editable per tenant) - Review list with status filters and approve/reject workflow - ReviewAssignmentPanel in Document Generator preview tab - "Zur Pruefung senden" button creates reviews + sends notification emails - Approval notification sent to all affected roles after document sign-off - Sidebar navigation link added Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
107 lines
4.3 KiB
TypeScript
107 lines
4.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { OrgRole, DefaultRole } from '../_types'
|
|
import { ROLE_ICONS } from '../_types'
|
|
|
|
interface RoleCardProps {
|
|
role: OrgRole | DefaultRole
|
|
onSave: (roleId: string, data: Partial<OrgRole>) => Promise<void>
|
|
onSendTest: (roleId: string) => Promise<{ sent: boolean; email: string }>
|
|
}
|
|
|
|
export function RoleCard({ role, onSave, onSendTest }: RoleCardProps) {
|
|
const isAssigned = 'id' in role
|
|
const [editing, setEditing] = useState(false)
|
|
const [name, setName] = useState((role as OrgRole).person_name || '')
|
|
const [email, setEmail] = useState((role as OrgRole).person_email || '')
|
|
const [dept, setDept] = useState((role as OrgRole).department || '')
|
|
const [sending, setSending] = useState(false)
|
|
const [testResult, setTestResult] = useState<string | null>(null)
|
|
|
|
const handleSave = async () => {
|
|
if (!isAssigned) return
|
|
await onSave((role as OrgRole).id, { person_name: name, person_email: email, department: dept })
|
|
setEditing(false)
|
|
}
|
|
|
|
const handleTest = async () => {
|
|
if (!isAssigned) return
|
|
setSending(true)
|
|
setTestResult(null)
|
|
try {
|
|
const result = await onSendTest((role as OrgRole).id)
|
|
setTestResult(result.sent ? `Gesendet an ${result.email}` : 'Fehler')
|
|
} catch {
|
|
setTestResult('Fehler beim Senden')
|
|
} finally {
|
|
setSending(false)
|
|
}
|
|
}
|
|
|
|
const icon = ROLE_ICONS[role.role_key] || '\u{1F464}'
|
|
|
|
return (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-4 space-y-3">
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-2xl">{icon}</span>
|
|
<div className="flex-1">
|
|
<h3 className="font-semibold text-gray-900 text-sm">{role.role_label}</h3>
|
|
{isAssigned && (role as OrgRole).person_name && !editing && (
|
|
<p className="text-xs text-gray-500">{(role as OrgRole).person_name}</p>
|
|
)}
|
|
</div>
|
|
{isAssigned && !editing && (
|
|
<button onClick={() => setEditing(true)} className="text-xs text-purple-600 hover:underline">
|
|
Bearbeiten
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{editing ? (
|
|
<div className="space-y-2">
|
|
<input value={name} onChange={e => setName(e.target.value)} placeholder="Name"
|
|
className="w-full px-3 py-1.5 text-sm border border-gray-200 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500" />
|
|
<input value={email} onChange={e => setEmail(e.target.value)} placeholder="E-Mail" type="email"
|
|
className="w-full px-3 py-1.5 text-sm border border-gray-200 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500" />
|
|
<input value={dept} onChange={e => setDept(e.target.value)} placeholder="Abteilung"
|
|
className="w-full px-3 py-1.5 text-sm border border-gray-200 rounded-lg focus:ring-1 focus:ring-purple-500 focus:border-purple-500" />
|
|
<div className="flex gap-2">
|
|
<button onClick={handleSave} className="px-3 py-1 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700">
|
|
Speichern
|
|
</button>
|
|
<button onClick={() => setEditing(false)} className="px-3 py-1 text-xs text-gray-500 hover:text-gray-700">
|
|
Abbrechen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{isAssigned && (role as OrgRole).person_email && (
|
|
<div className="text-xs text-gray-500 space-y-0.5">
|
|
<div>{(role as OrgRole).person_email}</div>
|
|
{(role as OrgRole).department && <div>{(role as OrgRole).department}</div>}
|
|
</div>
|
|
)}
|
|
{!isAssigned && (
|
|
<p className="text-xs text-gray-400 italic">Noch nicht zugewiesen</p>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{isAssigned && (role as OrgRole).person_email && !editing && (
|
|
<button onClick={handleTest} disabled={sending}
|
|
className="w-full px-3 py-1.5 text-xs bg-blue-50 text-blue-600 border border-blue-200 rounded-lg hover:bg-blue-100 disabled:opacity-50">
|
|
{sending ? 'Sende...' : 'Test-E-Mail senden'}
|
|
</button>
|
|
)}
|
|
|
|
{testResult && (
|
|
<p className={`text-xs ${testResult.startsWith('Gesendet') ? 'text-green-600' : 'text-red-600'}`}>
|
|
{testResult}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|