fix(admin): resolve all 266 TypeScript errors, enable strict build

Eliminate the pre-existing TS errors that were masked by
next.config.js `typescript.ignoreBuildErrors: true`, then turn the flag
OFF so the compiler is a real safety net for future changes. `next build`
and `tsc --noEmit` now pass with 0 errors.

The errors were not cosmetic — several exposed real latent bugs hidden by
the flag, e.g. the drafting-engine ConstraintEnforcer read non-existent
fields (`t.rule.dsfaRequired`, `d.required`, `r.title`), so its DSFA hard
gate and risk-flag checks were silently no-ops; scopeDefaults read
snake_case CompanyProfile fields that never matched the camelCase type
(generator defaults never populated). Both fixed by aligning code to the
current types.

Highlights:
- Vitest globals: add vitest-globals.d.ts (config already had globals:true)
  so the test files type-check; exclude Playwright specs from vitest.
- Add a minimal ambient `pg` module declaration (no @types/pg installed).
- Fix Next 15 route handlers to await Promise params.
- Reconcile drifted types across loeschfristen, compliance-scope, document-
  generator, drafting-engine, vendor-compliance, agent and more.

Pre-existing (NOT caused here, proven by stashing the diff): 3 vitest
logic tests still fail — getNextStep (2) and buildDocumentScope priority (1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-11 00:42:44 +02:00
parent bb9aacc3d3
commit a28db8f8f0
76 changed files with 280 additions and 190 deletions
@@ -55,7 +55,7 @@ export function DeletionLogicSection({
{policy.deletionTrigger === 'RETENTION_DRIVER' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungstreiber</label>
<select value={policy.retentionDriver}
<select value={policy.retentionDriver ?? ""}
onChange={(e) => {
const driver = e.target.value as RetentionDriverType
const meta = RETENTION_DRIVER_META[driver]
@@ -78,13 +78,13 @@ export function DeletionLogicSection({
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungsdauer</label>
<input type="number" min={0} value={policy.retentionDuration}
<input type="number" min={0} value={policy.retentionDuration ?? ""}
onChange={(e) => set('retentionDuration', parseInt(e.target.value) || 0)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Einheit</label>
<select value={policy.retentionUnit} onChange={(e) => set('retentionUnit', e.target.value as RetentionUnit)}
<select value={policy.retentionUnit ?? ""} onChange={(e) => set('retentionUnit', e.target.value as RetentionUnit)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
<option value="DAYS">Tage</option>
<option value="MONTHS">Monate</option>
@@ -232,7 +232,7 @@ export function StorageSection({
className="text-purple-600 focus:ring-purple-500 rounded" />
</td>
<td className="px-3 py-2">
<input type="text" value={loc.provider}
<input type="text" value={loc.provider ?? ""}
onChange={(e) => updateStorageLocationItem(idx, (s) => ({ ...s, provider: e.target.value }))}
placeholder="Anbieter"
className="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:ring-1 focus:ring-purple-500" />
@@ -235,12 +235,12 @@ function ComplianceResultView({
{issue.recommendation && (
<p className="text-xs text-gray-500 mt-1 italic">Empfehlung: {issue.recommendation}</p>
)}
{issue.affectedPolicyId && (
{issue.policyId && (
<button
onClick={() => { setEditingId(issue.affectedPolicyId!); setTab('editor') }}
onClick={() => { setEditingId(issue.policyId!); setTab('editor') }}
className="text-xs text-purple-600 hover:text-purple-800 font-medium mt-1"
>
Zur Loeschfrist: {issue.affectedPolicyId}
Zur Loeschfrist: {issue.policyId}
</button>
)}
</div>
@@ -98,7 +98,7 @@ function GeneratedPreview({
<div className="flex flex-wrap gap-1">
{renderTriggerBadge(getEffectiveDeletionTrigger(gp))}
<span className="inline-block text-xs font-medium px-2 py-0.5 rounded-full bg-blue-100 text-blue-800">
{formatRetentionDuration(gp)}
{formatRetentionDuration(gp.retentionDuration, gp.retentionUnit)}
</span>
{gp.retentionDriver && (
<span className="inline-block text-xs font-medium px-2 py-0.5 rounded-full bg-gray-100 text-gray-600">
@@ -157,7 +157,7 @@ function ProfilingWizard({
const totalSteps = PROFILING_STEPS.length
const progress = getProfilingProgress(profilingAnswers)
const allComplete = PROFILING_STEPS.every((step, idx) =>
isStepComplete(step, profilingAnswers.filter((a) => a.stepIndex === idx)),
isStepComplete(profilingAnswers.filter((a) => a.stepIndex === idx), step.id),
)
const currentStep: ProfilingStep | undefined = PROFILING_STEPS[profilingStep]
@@ -200,7 +200,7 @@ function ProfilingWizard({
return (
<div key={question.id} className="border-t border-gray-100 pt-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
{question.label}
{question.question}
{question.helpText && (
<span className="block text-xs text-gray-400 font-normal mt-0.5">{question.helpText}</span>
)}
@@ -245,7 +245,7 @@ function ProfilingWizard({
{question.type === 'multi' && question.options && (
<div className="space-y-2">
{question.options.map((opt) => {
const selectedValues: string[] = currentAnswer?.value || []
const selectedValues: string[] = Array.isArray(currentAnswer?.value) ? currentAnswer.value : []
const isSelected = selectedValues.includes(opt.value)
return (
<label key={opt.value}
@@ -271,7 +271,7 @@ function ProfilingWizard({
)}
{question.type === 'number' && (
<input type="number" value={currentAnswer?.value ?? ''}
<input type="number" value={(currentAnswer?.value ?? '') as string | number}
onChange={(e) => handleProfilingAnswer(profilingStep, question.id, e.target.value ? parseInt(e.target.value) : '')}
min={0} placeholder="Bitte Zahl eingeben"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
@@ -187,7 +187,7 @@ export function UebersichtTab({
<div className="flex flex-wrap gap-1.5 mb-3">
{renderTriggerBadge(trigger)}
<span className="inline-block text-xs font-medium px-2 py-0.5 rounded-full bg-blue-100 text-blue-800">
{formatRetentionDuration(p)}
{formatRetentionDuration(p.retentionDuration, p.retentionUnit)}
</span>
{renderStatusBadge(p.status)}
{overdue && (