fix: Fehlende Dateien fuer Grenzen-Formular + Report-Export
Interview: LimitsFormSections, FormFields, SectionCard, _types Tech-File: ReportPrintView, report-types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
ReportData, rpz, plFromRpz, silFromRpz, riskLevelLabel, riskLevelColor,
|
||||
CATEGORY_LABELS, REDUCTION_LABELS, STATUS_LABELS,
|
||||
} from './report-types'
|
||||
|
||||
interface ReportPrintViewProps {
|
||||
data: ReportData
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
try { return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) }
|
||||
catch { return iso }
|
||||
}
|
||||
|
||||
const NORM_TYPE_LABELS: Record<string, string> = {
|
||||
a_norms: 'A-Normen (Grundnormen)',
|
||||
b1_norms: 'B1-Normen (Sicherheitsgrundnormen)',
|
||||
b2_norms: 'B2-Normen (Sicherheitsfachgrundnormen)',
|
||||
c_norms: 'C-Normen (Maschinenspezifisch)',
|
||||
}
|
||||
|
||||
/** Print-optimized CE report rendered as HTML for window.print(). */
|
||||
export function ReportPrintView({ data }: ReportPrintViewProps) {
|
||||
const { project, hazards, mitigations, norms, triggers, riskSummary } = data
|
||||
const sortedHazards = [...hazards].sort((a, b) => (b.r_inherent || 0) - (a.r_inherent || 0))
|
||||
const byDesign = mitigations.filter(m => m.reduction_type === 'design')
|
||||
const byProtection = mitigations.filter(m => m.reduction_type === 'protection')
|
||||
const byInfo = mitigations.filter(m => m.reduction_type === 'information')
|
||||
const openMitigations = mitigations.filter(m => m.status !== 'verified')
|
||||
const highRiskCount = (riskSummary.critical || 0) + (riskSummary.high || 0)
|
||||
|
||||
return (
|
||||
<div className="report-print-view">
|
||||
<style>{`
|
||||
.report-print-view {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
color: #1a1a1a;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
max-width: 210mm;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.report-print-view h1 { font-size: 20pt; margin: 0 0 4pt; color: #1e1b4b; }
|
||||
.report-print-view h2 {
|
||||
font-size: 13pt; margin: 20pt 0 8pt; padding-bottom: 4pt;
|
||||
border-bottom: 2pt solid #7c3aed; color: #1e1b4b;
|
||||
}
|
||||
.report-print-view h3 { font-size: 11pt; margin: 12pt 0 6pt; color: #374151; }
|
||||
.report-print-view table {
|
||||
width: 100%; border-collapse: collapse; margin: 8pt 0;
|
||||
font-size: 8.5pt; page-break-inside: auto;
|
||||
}
|
||||
.report-print-view th, .report-print-view td {
|
||||
border: 0.5pt solid #d1d5db; padding: 3pt 5pt; text-align: left;
|
||||
}
|
||||
.report-print-view th {
|
||||
background: #f3f4f6; font-weight: 600; color: #374151;
|
||||
}
|
||||
.report-print-view tr { page-break-inside: avoid; }
|
||||
.report-print-view .cover {
|
||||
text-align: center; padding: 60pt 20pt 40pt;
|
||||
border-bottom: 3pt solid #7c3aed;
|
||||
}
|
||||
.report-print-view .cover .subtitle {
|
||||
font-size: 14pt; color: #6b7280; margin-top: 8pt;
|
||||
}
|
||||
.report-print-view .cover .meta {
|
||||
margin-top: 30pt; font-size: 10pt; color: #374151;
|
||||
}
|
||||
.report-print-view .cover .meta td { border: none; padding: 2pt 8pt; }
|
||||
.report-print-view .cover .meta td:first-child { font-weight: 600; text-align: right; }
|
||||
.report-print-view .toc { margin: 16pt 0; }
|
||||
.report-print-view .toc li { padding: 3pt 0; color: #374151; }
|
||||
.report-print-view .risk-cell { font-weight: 600; text-align: center; }
|
||||
.report-print-view .badge {
|
||||
display: inline-block; padding: 1pt 6pt; border-radius: 3pt;
|
||||
font-size: 7.5pt; font-weight: 600;
|
||||
}
|
||||
.report-print-view .section-break { page-break-before: always; }
|
||||
.report-print-view .summary-box {
|
||||
border: 1pt solid #d1d5db; border-radius: 4pt; padding: 12pt;
|
||||
margin: 8pt 0; background: #f9fafb;
|
||||
}
|
||||
.report-print-view .footer-line {
|
||||
margin-top: 24pt; padding-top: 8pt; border-top: 1pt solid #d1d5db;
|
||||
font-size: 8pt; color: #9ca3af; text-align: center;
|
||||
}
|
||||
@media print {
|
||||
.report-print-view { margin: 0; max-width: none; }
|
||||
.report-print-view .section-break { page-break-before: always; }
|
||||
@page { size: A4; margin: 15mm 12mm 18mm; }
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* 1. Deckblatt */}
|
||||
<div className="cover">
|
||||
<h1>CE-Akte / Risikobeurteilung</h1>
|
||||
<div className="subtitle">{project.machine_name}</div>
|
||||
<table className="meta" style={{ margin: '30pt auto 0', textAlign: 'left' }}>
|
||||
<tbody>
|
||||
<tr><td>Maschinentyp:</td><td>{project.machine_type || '-'}</td></tr>
|
||||
<tr><td>Hersteller:</td><td>{project.manufacturer || '-'}</td></tr>
|
||||
<tr><td>Projektstatus:</td><td>{project.status}</td></tr>
|
||||
<tr><td>Erstelldatum:</td><td>{formatDate(project.created_at)}</td></tr>
|
||||
<tr><td>Letzte Aktualisierung:</td><td>{formatDate(project.updated_at)}</td></tr>
|
||||
<tr><td>Vollstaendigkeit:</td><td>{project.completeness_pct}%</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 2. Inhaltsverzeichnis */}
|
||||
<div className="section-break">
|
||||
<h2>Inhaltsverzeichnis</h2>
|
||||
<ol className="toc">
|
||||
<li>Maschinenbeschreibung</li>
|
||||
<li>Angewandte Normen</li>
|
||||
<li>Gefaehrdungsliste</li>
|
||||
<li>Risikobewertung</li>
|
||||
<li>Massnahmenliste</li>
|
||||
<li>Compliance-Hinweise</li>
|
||||
<li>Zusammenfassung</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{/* 3. Maschinenbeschreibung */}
|
||||
<div className="section-break">
|
||||
<h2>1. Maschinenbeschreibung</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td style={{ fontWeight: 600, width: '35%' }}>Maschinenbezeichnung</td><td>{project.machine_name}</td></tr>
|
||||
<tr><td style={{ fontWeight: 600 }}>Maschinentyp</td><td>{project.machine_type || '-'}</td></tr>
|
||||
<tr><td style={{ fontWeight: 600 }}>Hersteller</td><td>{project.manufacturer || '-'}</td></tr>
|
||||
<tr><td style={{ fontWeight: 600 }}>Anzahl Komponenten</td><td>{project.component_count}</td></tr>
|
||||
<tr><td style={{ fontWeight: 600 }}>Anzahl Gefaehrdungen</td><td>{project.hazard_count}</td></tr>
|
||||
<tr><td style={{ fontWeight: 600 }}>Anzahl Massnahmen</td><td>{project.mitigation_count}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 4. Angewandte Normen */}
|
||||
<div className="section-break">
|
||||
<h2>2. Angewandte Normen</h2>
|
||||
{norms && norms.total > 0 ? (
|
||||
(['a_norms', 'b1_norms', 'b2_norms', 'c_norms'] as const).map(key => {
|
||||
const items = norms[key]
|
||||
if (!items || items.length === 0) return null
|
||||
return (
|
||||
<div key={key}>
|
||||
<h3>{NORM_TYPE_LABELS[key]}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th style={{ width: '20%' }}>Nummer</th><th style={{ width: '45%' }}>Titel</th><th>Abschnitte / Grund</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((ns, i) => (
|
||||
<tr key={i}>
|
||||
<td>{ns.norm.number}</td>
|
||||
<td>{ns.norm.title_de}</td>
|
||||
<td>{ns.reason}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<p style={{ color: '#6b7280' }}>Keine Normenvorschlaege vorhanden.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 5. Gefaehrdungsliste */}
|
||||
<div className="section-break">
|
||||
<h2>3. Gefaehrdungsliste</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '5%' }}>Nr.</th>
|
||||
<th style={{ width: '15%' }}>Komponente</th>
|
||||
<th style={{ width: '20%' }}>Gefaehrdung</th>
|
||||
<th style={{ width: '12%' }}>Kategorie</th>
|
||||
<th style={{ width: '24%' }}>Szenario</th>
|
||||
<th style={{ width: '12%' }}>Lebensphase</th>
|
||||
<th style={{ width: '12%' }}>Risiko</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedHazards.map((h, i) => (
|
||||
<tr key={h.id}>
|
||||
<td>{i + 1}</td>
|
||||
<td>{h.component_name || '-'}</td>
|
||||
<td>{h.name}</td>
|
||||
<td>{CATEGORY_LABELS[h.category] || h.category}</td>
|
||||
<td>{h.possible_harm || h.trigger_event || '-'}</td>
|
||||
<td>{h.lifecycle_phase || '-'}</td>
|
||||
<td className="risk-cell" style={{ color: riskLevelColor(h.risk_level) }}>
|
||||
{riskLevelLabel(h.risk_level)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 6. Risikobewertung */}
|
||||
<div className="section-break">
|
||||
<h2>4. Risikobewertung</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nr.</th>
|
||||
<th>Gefaehrdung</th>
|
||||
<th style={{ textAlign: 'center' }}>S</th>
|
||||
<th style={{ textAlign: 'center' }}>E</th>
|
||||
<th style={{ textAlign: 'center' }}>P</th>
|
||||
<th style={{ textAlign: 'center' }}>RPZ</th>
|
||||
<th style={{ textAlign: 'center' }}>SIL</th>
|
||||
<th style={{ textAlign: 'center' }}>PL</th>
|
||||
<th>Risiko</th>
|
||||
<th style={{ textAlign: 'center' }}>Akzeptabel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedHazards.map((h, i) => {
|
||||
const r = rpz(h.severity, h.exposure, h.probability, h.avoidance)
|
||||
const sil = silFromRpz(r)
|
||||
const pl = plFromRpz(r)
|
||||
const acceptable = r <= 20
|
||||
return (
|
||||
<tr key={h.id}>
|
||||
<td>{i + 1}</td>
|
||||
<td>{h.name}</td>
|
||||
<td style={{ textAlign: 'center' }}>{h.severity}</td>
|
||||
<td style={{ textAlign: 'center' }}>{h.exposure}</td>
|
||||
<td style={{ textAlign: 'center' }}>{h.probability}</td>
|
||||
<td className="risk-cell" style={{ color: riskLevelColor(h.risk_level) }}>{r}</td>
|
||||
<td style={{ textAlign: 'center' }}>{sil}</td>
|
||||
<td style={{ textAlign: 'center' }}>{pl}</td>
|
||||
<td style={{ color: riskLevelColor(h.risk_level) }}>{riskLevelLabel(h.risk_level)}</td>
|
||||
<td style={{ textAlign: 'center', color: acceptable ? '#16a34a' : '#dc2626', fontWeight: 600 }}>
|
||||
{acceptable ? 'Ja' : 'Nein'}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 7. Massnahmenliste */}
|
||||
<div className="section-break">
|
||||
<h2>5. Massnahmenliste</h2>
|
||||
<p style={{ marginBottom: '8pt', color: '#374151' }}>
|
||||
Gesamt: {mitigations.length} Massnahmen
|
||||
(Design: {byDesign.length}, Schutz: {byProtection.length}, Information: {byInfo.length})
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '5%' }}>Nr.</th>
|
||||
<th style={{ width: '25%' }}>Massnahme</th>
|
||||
<th style={{ width: '15%' }}>Typ</th>
|
||||
<th style={{ width: '30%' }}>Zugeordnete Gefaehrdungen</th>
|
||||
<th style={{ width: '12%' }}>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mitigations.map((m, i) => (
|
||||
<tr key={m.id}>
|
||||
<td>{i + 1}</td>
|
||||
<td>{m.title}</td>
|
||||
<td>{REDUCTION_LABELS[m.reduction_type] || m.reduction_type}</td>
|
||||
<td>{m.linked_hazard_names?.join(', ') || '-'}</td>
|
||||
<td>
|
||||
<span className="badge" style={{
|
||||
background: m.status === 'verified' ? '#dcfce7' : m.status === 'implemented' ? '#dbeafe' : '#fef3c7',
|
||||
color: m.status === 'verified' ? '#166534' : m.status === 'implemented' ? '#1e40af' : '#92400e',
|
||||
}}>
|
||||
{STATUS_LABELS[m.status] || m.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 8. Compliance-Hinweise */}
|
||||
{triggers.length > 0 && (
|
||||
<div className="section-break">
|
||||
<h2>6. Compliance-Hinweise</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '12%' }}>Regulation</th>
|
||||
<th style={{ width: '12%' }}>Artikel</th>
|
||||
<th style={{ width: '25%' }}>Titel</th>
|
||||
<th style={{ width: '10%' }}>Schwere</th>
|
||||
<th>Grund</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{triggers.map((t) => (
|
||||
<tr key={t.id}>
|
||||
<td>{t.regulation}</td>
|
||||
<td>{t.article}</td>
|
||||
<td>{t.title}</td>
|
||||
<td>
|
||||
<span className="badge" style={{
|
||||
background: t.severity === 'high' ? '#fecaca' : t.severity === 'medium' ? '#fef3c7' : '#dbeafe',
|
||||
color: t.severity === 'high' ? '#991b1b' : t.severity === 'medium' ? '#92400e' : '#1e40af',
|
||||
}}>
|
||||
{t.severity === 'high' ? 'HOCH' : t.severity === 'medium' ? 'MITTEL' : 'NIEDRIG'}
|
||||
</span>
|
||||
</td>
|
||||
<td>{t.reason}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 9. Zusammenfassung */}
|
||||
<div className="section-break">
|
||||
<h2>7. Zusammenfassung</h2>
|
||||
<div className="summary-box">
|
||||
<h3 style={{ marginTop: 0 }}>Gesamtrisiko</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Risikostufe</th>
|
||||
<th style={{ textAlign: 'center' }}>Anzahl</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[
|
||||
['Kritisch / Sehr hoch', (riskSummary.critical || 0) + (riskSummary.high || 0), '#dc2626'],
|
||||
['Mittel', riskSummary.medium || 0, '#ca8a04'],
|
||||
['Niedrig', riskSummary.low || 0, '#16a34a'],
|
||||
].map(([label, count, color]) => (
|
||||
<tr key={String(label)}>
|
||||
<td style={{ color: String(color), fontWeight: 600 }}>{String(label)}</td>
|
||||
<td style={{ textAlign: 'center' }}>{String(count)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="summary-box">
|
||||
<h3 style={{ marginTop: 0 }}>Offene Massnahmen</h3>
|
||||
<p>{openMitigations.length} von {mitigations.length} Massnahmen noch nicht verifiziert.</p>
|
||||
</div>
|
||||
|
||||
<div className="summary-box">
|
||||
<h3 style={{ marginTop: 0 }}>Empfehlung</h3>
|
||||
<p style={{ fontWeight: 600, color: highRiskCount > 0 ? '#dc2626' : '#16a34a' }}>
|
||||
{highRiskCount > 0
|
||||
? `Es bestehen ${highRiskCount} Gefaehrdungen mit hohem/kritischem Risiko. Massnahmen muessen umgesetzt und verifiziert werden, bevor die Maschine in Verkehr gebracht werden darf.`
|
||||
: openMitigations.length > 0
|
||||
? 'Alle identifizierten Risiken liegen im akzeptablen Bereich. Offene Massnahmen sollten zeitnah abgeschlossen und verifiziert werden.'
|
||||
: 'Alle Risiken liegen im akzeptablen Bereich und alle Massnahmen sind verifiziert. Die Maschine kann in Verkehr gebracht werden.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer-line">
|
||||
Erstellt mit BreakPilot ComplAI am {formatDate(new Date().toISOString())} | CE-Akte: {project.machine_name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Types shared between ReportGenerator and ReportPrintView
|
||||
|
||||
export interface ProjectData {
|
||||
id: string
|
||||
machine_name: string
|
||||
machine_type: string
|
||||
manufacturer: string
|
||||
status: string
|
||||
completeness_pct: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
component_count: number
|
||||
hazard_count: number
|
||||
mitigation_count: number
|
||||
}
|
||||
|
||||
export interface HazardData {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
component_name: string | null
|
||||
category: string
|
||||
lifecycle_phase: string
|
||||
trigger_event: string
|
||||
affected_person: string
|
||||
possible_harm: string
|
||||
severity: number
|
||||
exposure: number
|
||||
probability: number
|
||||
avoidance: number
|
||||
r_inherent: number
|
||||
risk_level: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface MitigationData {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
reduction_type: 'design' | 'protection' | 'information'
|
||||
status: 'planned' | 'implemented' | 'verified'
|
||||
linked_hazard_ids: string[]
|
||||
linked_hazard_names: string[]
|
||||
}
|
||||
|
||||
export interface NormSuggestion {
|
||||
norm: {
|
||||
id: string
|
||||
number: string
|
||||
title_de: string
|
||||
norm_type: string
|
||||
scope_de: string
|
||||
mandatory: boolean
|
||||
}
|
||||
reason: string
|
||||
confidence: number
|
||||
}
|
||||
|
||||
export interface NormResult {
|
||||
a_norms: NormSuggestion[]
|
||||
b1_norms: NormSuggestion[]
|
||||
b2_norms: NormSuggestion[]
|
||||
c_norms: NormSuggestion[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface ComplianceTrigger {
|
||||
id: string
|
||||
regulation: string
|
||||
article: string
|
||||
title: string
|
||||
severity: 'high' | 'medium' | 'low'
|
||||
reason: string
|
||||
affected_hazard_count?: number
|
||||
module_path: string
|
||||
module_label: string
|
||||
}
|
||||
|
||||
export interface RiskSummary {
|
||||
critical?: number
|
||||
high?: number
|
||||
medium?: number
|
||||
low?: number
|
||||
total?: number
|
||||
}
|
||||
|
||||
export interface ReportData {
|
||||
project: ProjectData
|
||||
hazards: HazardData[]
|
||||
mitigations: MitigationData[]
|
||||
norms: NormResult | null
|
||||
triggers: ComplianceTrigger[]
|
||||
riskSummary: RiskSummary
|
||||
}
|
||||
|
||||
// Helpers shared by report views
|
||||
|
||||
export function rpz(s: number, e: number, p: number, a: number): number {
|
||||
return a >= 1 ? s * e * p * a : s * e * p
|
||||
}
|
||||
|
||||
export function plFromRpz(r: number): string {
|
||||
if (r > 300) return 'e'
|
||||
if (r >= 151) return 'd'
|
||||
if (r >= 61) return 'c'
|
||||
if (r >= 21) return 'b'
|
||||
return 'a'
|
||||
}
|
||||
|
||||
export function silFromRpz(r: number): number {
|
||||
if (r > 300) return 3
|
||||
if (r >= 151) return 2
|
||||
if (r >= 61) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
export function riskLevelLabel(level: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
not_acceptable: 'Nicht akzeptabel',
|
||||
very_high: 'Sehr hoch',
|
||||
critical: 'Kritisch',
|
||||
high: 'Hoch',
|
||||
medium: 'Mittel',
|
||||
low: 'Niedrig',
|
||||
}
|
||||
return labels[level] || level
|
||||
}
|
||||
|
||||
export function riskLevelColor(level: string): string {
|
||||
const colors: Record<string, string> = {
|
||||
not_acceptable: '#dc2626',
|
||||
very_high: '#dc2626',
|
||||
critical: '#dc2626',
|
||||
high: '#ea580c',
|
||||
medium: '#ca8a04',
|
||||
low: '#16a34a',
|
||||
}
|
||||
return colors[level] || '#6b7280'
|
||||
}
|
||||
|
||||
export const CATEGORY_LABELS: Record<string, string> = {
|
||||
mechanical: 'Mechanisch',
|
||||
electrical: 'Elektrisch',
|
||||
thermal: 'Thermisch',
|
||||
pneumatic_hydraulic: 'Pneumatik/Hydraulik',
|
||||
noise_vibration: 'Laerm/Vibration',
|
||||
ergonomic: 'Ergonomie',
|
||||
material_environmental: 'Stoffe/Umwelt',
|
||||
software_control: 'Software/Steuerung',
|
||||
cyber_network: 'Cyber/Netzwerk',
|
||||
ai_specific: 'KI-spezifisch',
|
||||
}
|
||||
|
||||
export const REDUCTION_LABELS: Record<string, string> = {
|
||||
design: 'Stufe 1: Design',
|
||||
protection: 'Stufe 2: Schutz',
|
||||
information: 'Stufe 3: Information',
|
||||
}
|
||||
|
||||
export const STATUS_LABELS: Record<string, string> = {
|
||||
planned: 'Geplant',
|
||||
implemented: 'Umgesetzt',
|
||||
verified: 'Verifiziert',
|
||||
}
|
||||
Reference in New Issue
Block a user