feat: Rollenkonzept module + Document Generator review integration (Phase 4-5)
- 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>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import type { DocumentReview, ReviewStats } from '../_types'
|
||||
|
||||
const API_BASE = '/api/sdk/v1/compliance/document-reviews'
|
||||
|
||||
async function apiFetch<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(url, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
...init,
|
||||
})
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export function useDocumentReviews() {
|
||||
const { projectId } = useSDK()
|
||||
const [reviews, setReviews] = useState<DocumentReview[]>([])
|
||||
const [stats, setStats] = useState<ReviewStats>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [statusFilter, setStatusFilter] = useState<string | null>(null)
|
||||
|
||||
const loadReviews = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const params = new URLSearchParams()
|
||||
if (projectId) params.set('project_id', projectId)
|
||||
if (statusFilter) params.set('status', statusFilter)
|
||||
const qs = params.toString() ? `?${params}` : ''
|
||||
|
||||
const [reviewsData, statsData] = await Promise.all([
|
||||
apiFetch<DocumentReview[]>(`${API_BASE}${qs}`),
|
||||
apiFetch<ReviewStats>(`${API_BASE}/stats${projectId ? `?project_id=${projectId}` : ''}`),
|
||||
])
|
||||
setReviews(reviewsData)
|
||||
setStats(statsData)
|
||||
} catch {
|
||||
// Silent — component shows empty state
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [projectId, statusFilter])
|
||||
|
||||
useEffect(() => { loadReviews() }, [loadReviews])
|
||||
|
||||
const approveReview = useCallback(async (reviewId: string) => {
|
||||
const updated = await apiFetch<DocumentReview>(`${API_BASE}/${reviewId}/approve`, { method: 'POST' })
|
||||
setReviews(prev => prev.map(r => r.id === reviewId ? updated : r))
|
||||
return updated
|
||||
}, [])
|
||||
|
||||
const rejectReview = useCallback(async (reviewId: string, comment: string) => {
|
||||
const updated = await apiFetch<DocumentReview>(`${API_BASE}/${reviewId}/reject`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ comment }),
|
||||
})
|
||||
setReviews(prev => prev.map(r => r.id === reviewId ? updated : r))
|
||||
return updated
|
||||
}, [])
|
||||
|
||||
const sendNotification = useCallback(async (reviewId: string) => {
|
||||
return apiFetch<{ sent: boolean }>(`${API_BASE}/${reviewId}/send`, { method: 'POST' })
|
||||
}, [])
|
||||
|
||||
return {
|
||||
reviews, stats, loading, statusFilter, setStatusFilter,
|
||||
loadReviews, approveReview, rejectReview, sendNotification,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user