9f4c4abb84
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>
131 lines
4.7 KiB
TypeScript
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>
|
|
)
|
|
}
|