Files
breakpilot-compliance/admin-compliance/app/sdk/document-generator/_components/RecommendedDocuments.tsx
T
Benjamin Admin 9f4c4abb84 feat: Document recommendation UI in generator
New RecommendedDocuments component shown above the template library:
- Evaluates scope answers + compliance level (L1-L4)
- Groups templates into required/recommended/optional
- Shows profile label (Startup/KMU/Extended/Enterprise)
- Cards link to actual templates — click opens in generator
- Optional section collapsed by default
- Only visible when scope has been completed

Renders as purple gradient panel with grid cards, each showing
template name and availability status.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 11:06:56 +02:00

131 lines
4.7 KiB
TypeScript

'use client'
import { useMemo, useState } from 'react'
import { useSDK } from '@/lib/sdk'
import { evaluateTemplateRecommendations, type TemplateRecommendation } from '../templateRecommendations'
import { getProfileLabel } from '../scopeDefaults'
import type { LegalTemplateResult } from '@/lib/sdk/types'
import type { ComplianceDepthLevel } from '@/lib/sdk/compliance-scope-types/core-levels'
interface Props {
allTemplates: LegalTemplateResult[]
onUseTemplate: (t: LegalTemplateResult) => void
}
export default function RecommendedDocuments({ allTemplates, onUseTemplate }: Props) {
const { state } = useSDK()
const [showOptional, setShowOptional] = useState(false)
const level = state?.complianceScope?.determinedLevel as ComplianceDepthLevel | undefined
const scopeAnswers = state?.complianceScope?.answers || []
const recommendations = useMemo(() => {
if (!level) return null
return evaluateTemplateRecommendations(
scopeAnswers,
level,
(state?.companyProfile as Record<string, unknown>) || {},
)
}, [level, scopeAnswers, state?.companyProfile])
if (!level || !recommendations || recommendations.length === 0) return null
// Match recommendations to actual templates in the library
const templateMap = new Map<string, LegalTemplateResult>()
for (const t of allTemplates) {
if (t.templateType) templateMap.set(t.templateType, t)
}
const required = recommendations.filter((r) => r.requirement === 'required')
const recommended = recommendations.filter((r) => r.requirement === 'recommended')
const optional = recommendations.filter((r) => r.requirement === 'optional')
const renderCard = (rec: TemplateRecommendation) => {
const template = templateMap.get(rec.templateType)
const exists = !!template
return (
<div
key={rec.templateType}
className={`rounded-lg border p-3 text-sm ${
exists
? 'border-gray-200 bg-white hover:border-purple-300 cursor-pointer'
: 'border-dashed border-gray-300 bg-gray-50'
}`}
onClick={() => exists && template && onUseTemplate(template)}
>
<div className="font-medium text-gray-900 truncate">{rec.label}</div>
<div className="text-xs text-gray-500 mt-1">
{exists ? (
<span className="text-purple-600">Vorlage verfuegbar</span>
) : (
<span className="text-gray-400">Noch nicht erstellt</span>
)}
</div>
</div>
)
}
return (
<div className="bg-gradient-to-br from-purple-50 to-white rounded-xl border border-purple-200 p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-gray-900">
Empfohlene Dokumente fuer Ihr Unternehmen
</h3>
<p className="text-sm text-gray-500 mt-1">
Basierend auf Ihrem Compliance-Profil ({getProfileLabel(level)})
</p>
</div>
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700">
{level}
</span>
</div>
{/* Required */}
{required.length > 0 && (
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-medium text-red-700">Pflicht</span>
<span className="text-xs text-gray-400">({required.length})</span>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2">
{required.map(renderCard)}
</div>
</div>
)}
{/* Recommended */}
{recommended.length > 0 && (
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-medium text-amber-700">Empfohlen</span>
<span className="text-xs text-gray-400">({recommended.length})</span>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2">
{recommended.map(renderCard)}
</div>
</div>
)}
{/* Optional (collapsed by default) */}
{optional.length > 0 && (
<div>
<button
onClick={() => setShowOptional(!showOptional)}
className="text-sm text-gray-500 hover:text-purple-600 flex items-center gap-1"
>
<span>{showOptional ? '▼' : '▶'}</span>
<span>Optional ({optional.length})</span>
</button>
{showOptional && (
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 mt-2">
{optional.map(renderCard)}
</div>
)}
</div>
)}
</div>
)
}