Split 1130-LOC document-generator page into _components and _constants modules. page.tsx now 243 LOC (wire-up only). Behavior preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
244 lines
10 KiB
TypeScript
244 lines
10 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react'
|
|
import { useSDK } from '@/lib/sdk'
|
|
import { useEinwilligungen, EinwilligungenProvider } from '@/lib/sdk/einwilligungen/context'
|
|
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
|
import { LegalTemplateResult } from '@/lib/sdk/types'
|
|
import { DataPointsPreview } from './components/DataPointsPreview'
|
|
import { DocumentValidation } from './components/DocumentValidation'
|
|
import { generateAllPlaceholders } from '@/lib/sdk/document-generator/datapoint-helpers'
|
|
import { loadAllTemplates } from './searchTemplates'
|
|
import { TemplateContext, EMPTY_CONTEXT } from './contextBridge'
|
|
import { CATEGORIES } from './_constants'
|
|
import TemplateLibrary from './_components/TemplateLibrary'
|
|
import GeneratorSection from './_components/GeneratorSection'
|
|
|
|
function DocumentGeneratorPageInner() {
|
|
const { state } = useSDK()
|
|
const { selectedDataPointsData } = useEinwilligungen()
|
|
|
|
// Library state
|
|
const [allTemplates, setAllTemplates] = useState<LegalTemplateResult[]>([])
|
|
const [isLoadingLibrary, setIsLoadingLibrary] = useState(true)
|
|
const [activeCategory, setActiveCategory] = useState<string>('all')
|
|
const [activeLanguage, setActiveLanguage] = useState<'all' | 'de' | 'en'>('all')
|
|
const [librarySearch, setLibrarySearch] = useState('')
|
|
const [expandedPreviewId, setExpandedPreviewId] = useState<string | null>(null)
|
|
|
|
// Generator state
|
|
const [activeTemplate, setActiveTemplate] = useState<LegalTemplateResult | null>(null)
|
|
const [context, setContext] = useState<TemplateContext>(EMPTY_CONTEXT)
|
|
const [extraPlaceholders, setExtraPlaceholders] = useState<Record<string, string>>({})
|
|
const [enabledModules, setEnabledModules] = useState<string[]>([])
|
|
const generatorRef = useRef<HTMLDivElement>(null)
|
|
|
|
const [totalCount, setTotalCount] = useState<number>(0)
|
|
|
|
// Load all templates on mount
|
|
useEffect(() => {
|
|
setIsLoadingLibrary(true)
|
|
loadAllTemplates(200).then((templates) => {
|
|
setAllTemplates(templates)
|
|
setTotalCount(templates.length)
|
|
setIsLoadingLibrary(false)
|
|
})
|
|
}, [])
|
|
|
|
// Pre-fill context from company profile + SDK state
|
|
useEffect(() => {
|
|
if (state?.companyProfile) {
|
|
const profile = state.companyProfile
|
|
const p = profile as unknown as Record<string, string>
|
|
setContext((prev) => ({
|
|
...prev,
|
|
PROVIDER: {
|
|
...prev.PROVIDER,
|
|
LEGAL_NAME: profile.companyName || prev.PROVIDER.LEGAL_NAME,
|
|
LEGAL_FORM: p.legalForm || prev.PROVIDER.LEGAL_FORM,
|
|
ADDRESS_LINE: p.addressLine || prev.PROVIDER.ADDRESS_LINE,
|
|
POSTAL_CODE: p.postalCode || prev.PROVIDER.POSTAL_CODE,
|
|
CITY: p.city || prev.PROVIDER.CITY,
|
|
COUNTRY: p.country || prev.PROVIDER.COUNTRY,
|
|
EMAIL: p.email || prev.PROVIDER.EMAIL,
|
|
PHONE: p.phone || prev.PROVIDER.PHONE,
|
|
WEBSITE_URL: p.websiteUrl || prev.PROVIDER.WEBSITE_URL,
|
|
CEO_NAME: p.representedByName || p.ceoName || prev.PROVIDER.CEO_NAME,
|
|
REGISTER_COURT: p.registerCourt || prev.PROVIDER.REGISTER_COURT,
|
|
REGISTER_NUMBER: p.registerNumber || prev.PROVIDER.REGISTER_NUMBER,
|
|
VAT_ID: p.vatId || prev.PROVIDER.VAT_ID,
|
|
},
|
|
PRIVACY: {
|
|
...prev.PRIVACY,
|
|
DPO_NAME: profile.dpoName || prev.PRIVACY.DPO_NAME,
|
|
DPO_EMAIL: profile.dpoEmail || prev.PRIVACY.DPO_EMAIL,
|
|
CONTACT_EMAIL: profile.dpoEmail || prev.PRIVACY.CONTACT_EMAIL,
|
|
SUPERVISORY_AUTHORITY_NAME: p.supervisoryAuthorityName || prev.PRIVACY.SUPERVISORY_AUTHORITY_NAME,
|
|
SUPERVISORY_AUTHORITY_ADDRESS: p.supervisoryAuthorityAddress || prev.PRIVACY.SUPERVISORY_AUTHORITY_ADDRESS,
|
|
},
|
|
FEATURES: {
|
|
...prev.FEATURES,
|
|
DATA_SUBJECT_REQUEST_CHANNEL: p.email
|
|
? `per E-Mail an ${p.email}`
|
|
: prev.FEATURES.DATA_SUBJECT_REQUEST_CHANNEL,
|
|
},
|
|
}))
|
|
}
|
|
}, [state?.companyProfile])
|
|
|
|
// Pre-fill extra placeholders from Einwilligungen data points
|
|
useEffect(() => {
|
|
if (selectedDataPointsData && selectedDataPointsData.length > 0) {
|
|
const generated = generateAllPlaceholders(selectedDataPointsData, 'de')
|
|
setExtraPlaceholders((prev) => ({ ...prev, ...generated }))
|
|
}
|
|
}, [selectedDataPointsData])
|
|
|
|
// Filtered templates (computed)
|
|
const filteredTemplates = useMemo(() => {
|
|
const category = CATEGORIES.find((c) => c.key === activeCategory)
|
|
return allTemplates.filter((t) => {
|
|
if (category && category.types !== null) {
|
|
if (!category.types.includes(t.templateType || '')) return false
|
|
}
|
|
if (activeLanguage !== 'all' && t.language !== activeLanguage) return false
|
|
if (librarySearch.trim()) {
|
|
const q = librarySearch.toLowerCase()
|
|
const title = (t.documentTitle || '').toLowerCase()
|
|
const type = (t.templateType || '').toLowerCase()
|
|
if (!title.includes(q) && !type.includes(q)) return false
|
|
}
|
|
return true
|
|
})
|
|
}, [allTemplates, activeCategory, activeLanguage, librarySearch])
|
|
|
|
const handleUseTemplate = useCallback((t: LegalTemplateResult) => {
|
|
setActiveTemplate(t)
|
|
setExpandedPreviewId(null)
|
|
setExtraPlaceholders({})
|
|
setEnabledModules([])
|
|
setTimeout(() => {
|
|
generatorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
}, 100)
|
|
}, [])
|
|
|
|
const handleContextChange = useCallback(
|
|
(section: keyof TemplateContext, key: string, value: unknown) => {
|
|
setContext((prev) => ({
|
|
...prev,
|
|
[section]: { ...(prev[section] as unknown as Record<string, unknown>), [key]: value },
|
|
}))
|
|
},
|
|
[]
|
|
)
|
|
|
|
const handleInsertPlaceholder = useCallback((placeholder: string) => {
|
|
if (!extraPlaceholders[placeholder]) {
|
|
const generated = generateAllPlaceholders(selectedDataPointsData || [], 'de')
|
|
const val = generated[placeholder as keyof typeof generated]
|
|
if (val) {
|
|
setExtraPlaceholders((prev) => ({ ...prev, [placeholder]: val }))
|
|
}
|
|
}
|
|
}, [extraPlaceholders, selectedDataPointsData])
|
|
|
|
const stepInfo = STEP_EXPLANATIONS['document-generator'] || {
|
|
title: 'Dokumentengenerator',
|
|
description: 'Generieren Sie rechtliche Dokumente aus lizenzkonformen Vorlagen',
|
|
explanation: 'Der Dokumentengenerator nutzt frei lizenzierte Textbausteine um Datenschutzerklaerungen, AGB und andere rechtliche Dokumente zu erstellen.',
|
|
tips: ['Wählen Sie eine Vorlage aus der Bibliothek aus', 'Füllen Sie die Platzhalter mit Ihren Unternehmensdaten'],
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<StepHeader
|
|
stepId="document-generator"
|
|
title="Dokumentengenerator"
|
|
description="Generieren Sie rechtliche Dokumente aus lizenzkonformen Vorlagen"
|
|
explanation={stepInfo.explanation}
|
|
tips={stepInfo.tips}
|
|
/>
|
|
|
|
{/* Status bar */}
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div className="bg-white rounded-xl border border-gray-200 p-5">
|
|
<div className="text-sm text-gray-500">Vorlagen gesamt</div>
|
|
<div className="text-3xl font-bold text-gray-900">{totalCount}</div>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-gray-200 p-5">
|
|
<div className="text-sm text-gray-500">Angezeigt</div>
|
|
<div className="text-3xl font-bold text-purple-600">{filteredTemplates.length}</div>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-gray-200 p-5">
|
|
<div className="text-sm text-gray-500">Aktive Vorlage</div>
|
|
<div className="text-sm font-medium text-gray-900 mt-1 truncate">
|
|
{activeTemplate ? activeTemplate.documentTitle : '—'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<TemplateLibrary
|
|
allTemplates={allTemplates}
|
|
filteredTemplates={filteredTemplates}
|
|
isLoadingLibrary={isLoadingLibrary}
|
|
activeCategory={activeCategory}
|
|
onCategoryChange={setActiveCategory}
|
|
activeLanguage={activeLanguage}
|
|
onLanguageChange={setActiveLanguage}
|
|
librarySearch={librarySearch}
|
|
onSearchChange={setLibrarySearch}
|
|
expandedPreviewId={expandedPreviewId}
|
|
onTogglePreview={(id) => setExpandedPreviewId((prev) => (prev === id ? null : id))}
|
|
onUseTemplate={handleUseTemplate}
|
|
/>
|
|
|
|
{activeTemplate && (
|
|
<div ref={generatorRef} className="space-y-6">
|
|
<GeneratorSection
|
|
template={activeTemplate}
|
|
context={context}
|
|
onContextChange={handleContextChange}
|
|
extraPlaceholders={extraPlaceholders}
|
|
onExtraChange={(key, value) => setExtraPlaceholders((prev) => ({ ...prev, [key]: value }))}
|
|
onClose={() => setActiveTemplate(null)}
|
|
enabledModules={enabledModules}
|
|
onModuleToggle={(mod, checked) =>
|
|
setEnabledModules((prev) =>
|
|
checked ? [...prev, mod] : prev.filter((m) => m !== mod)
|
|
)
|
|
}
|
|
/>
|
|
|
|
{selectedDataPointsData && selectedDataPointsData.length > 0 && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<div className="lg:col-span-2">
|
|
<DocumentValidation
|
|
dataPoints={selectedDataPointsData}
|
|
documentContent={activeTemplate.text}
|
|
language="de"
|
|
onInsertPlaceholder={handleInsertPlaceholder}
|
|
/>
|
|
</div>
|
|
<div className="lg:col-span-1">
|
|
<DataPointsPreview
|
|
dataPoints={selectedDataPointsData}
|
|
onInsertPlaceholder={handleInsertPlaceholder}
|
|
language="de"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function DocumentGeneratorPage() {
|
|
return (
|
|
<EinwilligungenProvider>
|
|
<DocumentGeneratorPageInner />
|
|
</EinwilligungenProvider>
|
|
)
|
|
}
|