Files
breakpilot-compliance/admin-compliance/app/sdk/workflow/page.tsx
Sharang Parnerkar 82a5a388b8 refactor(admin): split workflow page.tsx into colocated components
Split 1175-LOC workflow page into _components, _hooks and _types modules.
page.tsx now 256 LOC (wire-up only). Behavior preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:50:29 +02:00

257 lines
9.0 KiB
TypeScript

'use client'
/**
* Document Workflow Page (SDK Version)
*
* Split-view editor for legal documents with synchronized scrolling:
* - Left: Current published version (read-only)
* - Right: Draft/new version (editable)
* - Approval workflow: Draft -> Review -> Approved -> Published
* - DOCX upload with Word conversion
* - Rich text editor with formatting toolbar
*/
import { useState, useEffect, useRef } from 'react'
import { useSDK } from '@/lib/sdk'
import StepHeader from '@/components/sdk/StepHeader/StepHeader'
import { Document, Version } from './_types'
import DocumentSelectorBar from './_components/DocumentSelectorBar'
import WorkflowStatusBar from './_components/WorkflowStatusBar'
import RichTextToolbar from './_components/RichTextToolbar'
import SplitViewEditor from './_components/SplitViewEditor'
import HistoryPanel from './_components/HistoryPanel'
import CompareView from './_components/CompareView'
import NewDocumentModal from './_components/NewDocumentModal'
import ApprovalModal from './_components/ApprovalModal'
import { useWorkflowActions } from './_hooks/useWorkflowActions'
import { useRichTextEditor } from './_hooks/useRichTextEditor'
import { useSyncScroll } from './_hooks/useSyncScroll'
export default function WorkflowPage() {
useSDK()
const [documents, setDocuments] = useState<Document[]>([])
const [versions, setVersions] = useState<Version[]>([])
const [selectedDocument, setSelectedDocument] = useState<Document | null>(null)
const [currentVersion, setCurrentVersion] = useState<Version | null>(null)
const [draftVersion, setDraftVersion] = useState<Version | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [editedContent, setEditedContent] = useState('')
const [editedTitle, setEditedTitle] = useState('')
const [editedSummary, setEditedSummary] = useState('')
const [showHistory, setShowHistory] = useState(false)
const [showCompareView, setShowCompareView] = useState(false)
const leftPanelRef = useRef<HTMLDivElement>(null)
const rightPanelRef = useRef<HTMLDivElement>(null)
const {
editorRef, fileInputRef, uploading, uploadError, setUploadError,
formatDoc, formatBlock, insertLink, updateEditorContent,
handleWordUpload, handlePaste,
} = useRichTextEditor(setEditedContent)
useSyncScroll(leftPanelRef, rightPanelRef, [currentVersion, draftVersion])
const loadDocuments = async () => {
setLoading(true)
try {
const res = await fetch('/api/admin/consent/documents')
if (res.ok) {
const data = await res.json()
setDocuments(data.documents || [])
if (data.documents?.length > 0 && !selectedDocument) {
setSelectedDocument(data.documents[0])
}
}
} catch {
setError('Fehler beim Laden der Dokumente')
} finally {
setLoading(false)
}
}
const loadVersions = async (docId: string) => {
try {
const res = await fetch(`/api/admin/consent/documents/${docId}/versions`)
if (res.ok) {
const data = await res.json()
const versionList = Array.isArray(data) ? data : (data.versions || [])
setVersions(versionList)
const published = versionList.find((v: Version) => v.status === 'published')
setCurrentVersion(published || null)
const draft = versionList.find((v: Version) =>
v.status === 'draft' || v.status === 'review' || v.status === 'approved'
)
if (draft) {
setDraftVersion(draft)
setEditedContent(draft.content)
setEditedTitle(draft.title)
setEditedSummary(draft.summary || '')
} else {
setDraftVersion(null)
setEditedContent(published?.content || '')
setEditedTitle(published?.title || '')
setEditedSummary(published?.summary || '')
}
}
} catch {
setError('Fehler beim Laden der Versionen')
}
}
useEffect(() => {
loadDocuments()
}, [])
useEffect(() => {
if (selectedDocument) {
loadVersions(selectedDocument.id)
}
}, [selectedDocument])
const actions = useWorkflowActions({
selectedDocument, setDocuments, setSelectedDocument,
draftVersion, currentVersion, versions,
editedTitle, editedContent, editedSummary,
loadVersions, setError,
})
const isEditable = draftVersion?.status === 'draft' || !draftVersion
return (
<div className="space-y-6">
<StepHeader stepId="workflow" showProgress={true} />
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 flex items-center justify-between">
<span className="text-red-700">{error}</span>
<button onClick={() => setError(null)} className="text-red-500 hover:text-red-700">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
)}
<DocumentSelectorBar
documents={documents}
selectedDocument={selectedDocument}
onSelectDocument={setSelectedDocument}
currentVersion={currentVersion}
draftVersion={draftVersion}
onNewDocument={() => actions.setShowNewDocModal(true)}
onCompareView={() => setShowCompareView(true)}
onToggleHistory={() => {
setShowHistory(!showHistory)
if (draftVersion && !showHistory) {
actions.loadApprovalHistory(draftVersion.id)
}
}}
/>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600" />
</div>
) : !selectedDocument ? (
<div className="bg-white rounded-xl shadow-sm border p-12 text-center text-slate-500">
Bitte waehlen Sie ein Dokument aus
</div>
) : (
<>
<WorkflowStatusBar
draftVersion={draftVersion}
saving={actions.saving}
onCreateNewDraft={actions.createNewDraft}
onSaveDraft={actions.saveDraft}
onSubmitForReview={actions.submitForReview}
onShowApprovalModal={actions.setShowApprovalModal}
onPublishVersion={actions.publishVersion}
/>
{isEditable && (
<RichTextToolbar
fileInputRef={fileInputRef}
uploading={uploading}
onFormatDoc={formatDoc}
onFormatBlock={formatBlock}
onInsertLink={insertLink}
onWordUpload={handleWordUpload}
/>
)}
{uploadError && (
<div className="bg-red-50 border border-red-200 rounded p-3 text-sm text-red-700 flex items-center justify-between">
<span>{uploadError}</span>
<button onClick={() => setUploadError(null)} className="ml-4 text-red-500 hover:text-red-700"></button>
</div>
)}
<SplitViewEditor
leftPanelRef={leftPanelRef}
rightPanelRef={rightPanelRef}
editorRef={editorRef}
currentVersion={currentVersion}
draftVersion={draftVersion}
isEditable={isEditable}
editedTitle={editedTitle}
editedContent={editedContent}
onTitleChange={setEditedTitle}
onUpdateEditorContent={updateEditorContent}
onPaste={handlePaste}
/>
{showHistory && (
<HistoryPanel
approvalHistory={actions.approvalHistory}
versions={versions}
draftVersion={draftVersion}
currentVersion={currentVersion}
/>
)}
</>
)}
{showCompareView && (
<CompareView
currentVersion={currentVersion}
draftVersion={draftVersion}
editedContent={editedContent}
onClose={() => setShowCompareView(false)}
onSaveDraft={actions.saveDraft}
onSubmitForReview={actions.submitForReview}
onShowApprovalModal={actions.setShowApprovalModal}
onPublishVersion={actions.publishVersion}
/>
)}
{actions.showNewDocModal && (
<NewDocumentModal
newDocForm={actions.newDocForm}
onChange={actions.setNewDocForm}
onClose={() => actions.setShowNewDocModal(false)}
onCreate={actions.createDocument}
creatingDoc={actions.creatingDoc}
/>
)}
{actions.showApprovalModal && (
<ApprovalModal
mode={actions.showApprovalModal}
approvalComment={actions.approvalComment}
onCommentChange={actions.setApprovalComment}
onCancel={() => {
actions.setShowApprovalModal(null)
actions.setApprovalComment('')
}}
onConfirm={actions.showApprovalModal === 'approve' ? actions.approveVersion : actions.rejectVersion}
saving={actions.saving}
/>
)}
</div>
)
}