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,125 @@
|
||||
'use client'
|
||||
|
||||
interface TextInputProps {
|
||||
label: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
placeholder?: string
|
||||
helpText?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function TextInput({ label, value, onChange, placeholder, helpText, disabled }: TextInputProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{label}</label>
|
||||
{helpText && <p className="text-xs text-gray-400 mb-1.5">{helpText}</p>}
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-purple-500 focus:border-purple-500 disabled:bg-gray-50 dark:disabled:bg-gray-800 disabled:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface TextAreaProps {
|
||||
label: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
placeholder?: string
|
||||
helpText?: string
|
||||
rows?: number
|
||||
}
|
||||
|
||||
export function TextArea({ label, value, onChange, placeholder, helpText, rows = 3 }: TextAreaProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{label}</label>
|
||||
{helpText && <p className="text-xs text-gray-400 mb-1.5">{helpText}</p>}
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
rows={rows}
|
||||
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-y"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface SelectInputProps {
|
||||
label: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
options: string[]
|
||||
placeholder?: string
|
||||
helpText?: string
|
||||
}
|
||||
|
||||
export function SelectInput({ label, value, onChange, options, placeholder, helpText }: SelectInputProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{label}</label>
|
||||
{helpText && <p className="text-xs text-gray-400 mb-1.5">{helpText}</p>}
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
||||
>
|
||||
<option value="">{placeholder || '-- Bitte waehlen --'}</option>
|
||||
{options.map((opt) => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface CheckboxGroupProps {
|
||||
label: string
|
||||
values: string[]
|
||||
onChange: (values: string[]) => void
|
||||
options: string[]
|
||||
helpText?: string
|
||||
}
|
||||
|
||||
export function CheckboxGroup({ label, values, onChange, options, helpText }: CheckboxGroupProps) {
|
||||
const toggle = (opt: string) => {
|
||||
if (values.includes(opt)) {
|
||||
onChange(values.filter((v) => v !== opt))
|
||||
} else {
|
||||
onChange([...values, opt])
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{label}</label>
|
||||
{helpText && <p className="text-xs text-gray-400 mb-1.5">{helpText}</p>}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{options.map((opt) => (
|
||||
<label
|
||||
key={opt}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg border cursor-pointer transition-colors ${
|
||||
values.includes(opt)
|
||||
? 'bg-purple-50 border-purple-300 text-purple-700 dark:bg-purple-900/40 dark:border-purple-600 dark:text-purple-300'
|
||||
: 'bg-white border-gray-200 text-gray-700 hover:bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={values.includes(opt)}
|
||||
onChange={() => toggle(opt)}
|
||||
className="w-3.5 h-3.5 text-purple-600 rounded"
|
||||
/>
|
||||
{opt}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
'use client'
|
||||
|
||||
import { SectionCard } from './SectionCard'
|
||||
import { TextInput, TextArea, SelectInput, CheckboxGroup } from './FormFields'
|
||||
import {
|
||||
FORM_SECTIONS,
|
||||
AREA_OF_USE_OPTIONS,
|
||||
OPERATING_MODE_OPTIONS,
|
||||
PERSON_GROUP_OPTIONS,
|
||||
type LimitsFormData,
|
||||
} from '../_types'
|
||||
|
||||
interface LimitsFormSectionsProps {
|
||||
data: LimitsFormData
|
||||
onChange: (field: keyof LimitsFormData, value: string | string[]) => void
|
||||
prefilled: { machine_name?: string; machine_type?: string; manufacturer?: string }
|
||||
}
|
||||
|
||||
export function LimitsFormSections({ data, onChange, prefilled }: LimitsFormSectionsProps) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* Section 1: Allgemeine Produktbeschreibung */}
|
||||
<SectionCard section={FORM_SECTIONS[0]} defaultOpen>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<TextInput
|
||||
label="Maschinenbezeichnung *"
|
||||
value={data.machine_designation || prefilled.machine_name || ''}
|
||||
onChange={(v) => onChange('machine_designation', v)}
|
||||
placeholder="z.B. Roboterzelle RZ-500"
|
||||
helpText={prefilled.machine_name ? `Vorausgefuellt aus Projekt: ${prefilled.machine_name}` : undefined}
|
||||
/>
|
||||
<TextInput
|
||||
label="Maschinentyp *"
|
||||
value={data.machine_type || prefilled.machine_type || ''}
|
||||
onChange={(v) => onChange('machine_type', v)}
|
||||
placeholder="z.B. Roboterzelle / CNC-Maschine"
|
||||
helpText={prefilled.machine_type ? `Vorausgefuellt aus Projekt: ${prefilled.machine_type}` : undefined}
|
||||
/>
|
||||
<TextInput
|
||||
label="Hersteller *"
|
||||
value={data.manufacturer || prefilled.manufacturer || ''}
|
||||
onChange={(v) => onChange('manufacturer', v)}
|
||||
placeholder="z.B. Mueller Maschinenbau GmbH"
|
||||
helpText={prefilled.manufacturer ? `Vorausgefuellt aus Projekt: ${prefilled.manufacturer}` : undefined}
|
||||
/>
|
||||
<TextInput
|
||||
label="Baujahr"
|
||||
value={data.year_of_construction}
|
||||
onChange={(v) => onChange('year_of_construction', v)}
|
||||
placeholder="z.B. 2026"
|
||||
/>
|
||||
<TextInput
|
||||
label="Seriennummer"
|
||||
value={data.serial_number}
|
||||
onChange={(v) => onChange('serial_number', v)}
|
||||
placeholder="z.B. SN-2026-001"
|
||||
/>
|
||||
</div>
|
||||
<TextArea
|
||||
label="Allgemeine Beschreibung *"
|
||||
value={data.general_description}
|
||||
onChange={(v) => onChange('general_description', v)}
|
||||
placeholder="Die EIGENBAU-Zelle ist ein Arbeitstisch mit integriertem Roboterarm, der Bauteile aus einem Magazin entnimmt und dem Bearbeitungszentrum zufuehrt..."
|
||||
helpText="Beschreiben Sie Aufbau, Funktion und Arbeitsweise der Maschine/Anlage ausfuehrlich."
|
||||
rows={5}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
{/* Section 2: Bestimmungsgemasse Verwendung */}
|
||||
<SectionCard section={FORM_SECTIONS[1]}>
|
||||
<TextArea
|
||||
label="Verwendungszweck *"
|
||||
value={data.intended_purpose}
|
||||
onChange={(v) => onChange('intended_purpose', v)}
|
||||
placeholder="Zum Einsatz an Bearbeitungszentren, zur Zufuehrung von Bauteilen aus einem Magazin in die Bearbeitungsmaschine..."
|
||||
helpText="Beschreiben Sie den bestimmungsgemassen Einsatzzweck der Maschine."
|
||||
rows={4}
|
||||
/>
|
||||
<SelectInput
|
||||
label="Einsatzbereich *"
|
||||
value={data.area_of_use}
|
||||
onChange={(v) => onChange('area_of_use', v)}
|
||||
options={AREA_OF_USE_OPTIONS}
|
||||
/>
|
||||
<CheckboxGroup
|
||||
label="Betriebsarten"
|
||||
values={data.operating_modes}
|
||||
onChange={(v) => onChange('operating_modes', v)}
|
||||
options={OPERATING_MODE_OPTIONS}
|
||||
helpText="Waehlen Sie alle zutreffenden Betriebsarten."
|
||||
/>
|
||||
<TextArea
|
||||
label="Varianten"
|
||||
value={data.variants}
|
||||
onChange={(v) => onChange('variants', v)}
|
||||
placeholder="Variante A: nicht-kollaborierend mit Schutzzaun Variante B: kollaborierend mit Kraft-/Leistungsbegrenzung"
|
||||
helpText="Beschreiben Sie verschiedene Ausbauvarianten oder Konfigurationen der Maschine."
|
||||
rows={3}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
{/* Section 3: Vorhersehbare Fehlanwendung */}
|
||||
<SectionCard section={FORM_SECTIONS[2]}>
|
||||
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-3 mb-2">
|
||||
<p className="text-xs text-amber-700 dark:text-amber-300">
|
||||
Dokumentieren Sie alle vernuenftigerweise vorhersehbaren Fehlanwendungen gemaess ISO 12100 Abschnitt 5.4. Beruecksichtigen Sie dabei reflexartiges Verhalten, mangelnde Konzentration und Verhaltensweisen nach dem Grundsatz des geringsten Widerstandes.
|
||||
</p>
|
||||
</div>
|
||||
<TextArea
|
||||
label="Vorhersehbare Fehlanwendungen *"
|
||||
value={data.foreseeable_misuses}
|
||||
onChange={(v) => onChange('foreseeable_misuses', v)}
|
||||
placeholder="- Eingriff in laufende Maschine bei Stoerung - Umgehung von Schutzeinrichtungen (Tuerschalter ueberbrueckt) - Betrieb mit offenem Schutzzaun - Unqualifiziertes Personal fuehrt Wartungsarbeiten durch - Verwendung nicht freigegebener Werkzeuge/Materialien"
|
||||
helpText="Jeweils eine Fehlanwendung pro Zeile, mit Stichpunkt-Aufzaehlung."
|
||||
rows={6}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
{/* Section 4: Grenzen der Maschine */}
|
||||
<SectionCard section={FORM_SECTIONS[3]}>
|
||||
<TextArea
|
||||
label="Raeumliche Grenzen *"
|
||||
value={data.spatial_limits}
|
||||
onChange={(v) => onChange('spatial_limits', v)}
|
||||
placeholder="Abmessungen: 2000 x 1500 x 1800 mm (LxBxH) Arbeitsraum Roboter: Radius 850mm Zugangsbereich: nur von vorne Sicherheitsabstand: min. 500mm zum Schutzzaun"
|
||||
helpText="Abmessungen, Arbeitsraum, Zugangsbereich, Sicherheitsabstaende."
|
||||
rows={4}
|
||||
/>
|
||||
<TextArea
|
||||
label="Zeitliche Grenzen"
|
||||
value={data.temporal_limits}
|
||||
onChange={(v) => onChange('temporal_limits', v)}
|
||||
placeholder="Geplante Lebensdauer: 15 Jahre Wartungsintervall: alle 2000 Betriebsstunden Max. Betriebsdauer pro Tag: 16 Stunden (2-Schicht)"
|
||||
helpText="Lebensdauer, Wartungsintervalle, Nutzungsdauer pro Tag/Woche."
|
||||
rows={3}
|
||||
/>
|
||||
<TextArea
|
||||
label="Betriebsbedingungen"
|
||||
value={data.operating_conditions}
|
||||
onChange={(v) => onChange('operating_conditions', v)}
|
||||
placeholder="Temperatur: +5 bis +40 Grad C Luftfeuchtigkeit: max. 80% (nicht kondensierend) Hoehenlage: bis 1000m ue.NN Keine explosionsgefaehrdete Atmosphaere"
|
||||
helpText="Temperatur, Feuchtigkeit, Staub, Vibrationen, besondere Umgebungsbedingungen."
|
||||
rows={4}
|
||||
/>
|
||||
<TextArea
|
||||
label="Energieversorgung"
|
||||
value={data.energy_supply}
|
||||
onChange={(v) => onChange('energy_supply', v)}
|
||||
placeholder="Elektrisch: 400V/50Hz, 32A Absicherung Druckluft: 6 bar, oelfrei Pneumatik: 6 bar Betriebsdruck"
|
||||
helpText="Spannung, Absicherung, Druckluftversorgung, weitere Energiequellen."
|
||||
rows={3}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
{/* Section 5: Schnittstellen */}
|
||||
<SectionCard section={FORM_SECTIONS[4]}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<TextArea
|
||||
label="Mechanische Schnittstellen"
|
||||
value={data.mechanical_interfaces}
|
||||
onChange={(v) => onChange('mechanical_interfaces', v)}
|
||||
placeholder="- Flanschverbindung zum Bearbeitungszentrum - Magazin-Andockstation - Greifer-Wechselsystem"
|
||||
rows={3}
|
||||
/>
|
||||
<TextArea
|
||||
label="Elektrische Schnittstellen"
|
||||
value={data.electrical_interfaces}
|
||||
onChange={(v) => onChange('electrical_interfaces', v)}
|
||||
placeholder="- ProfiNET Steuerungsbus - 24V Sicherheitskreis - E/A-Module fuer Sensorik"
|
||||
rows={3}
|
||||
/>
|
||||
<TextArea
|
||||
label="Software-Schnittstellen"
|
||||
value={data.software_interfaces}
|
||||
onChange={(v) => onChange('software_interfaces', v)}
|
||||
placeholder="- OPC UA Server - REST API fuer MES-Anbindung - HMI Webinterface"
|
||||
rows={3}
|
||||
/>
|
||||
<TextArea
|
||||
label="Pneumatische/Hydraulische Schnittstellen"
|
||||
value={data.pneumatic_hydraulic_interfaces}
|
||||
onChange={(v) => onChange('pneumatic_hydraulic_interfaces', v)}
|
||||
placeholder="- 6mm Druckluftanschluss - Wartungseinheit mit Filter/Regler - Abluft ueber Schalldaempfer"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* Section 6: Betroffene Personen */}
|
||||
<SectionCard section={FORM_SECTIONS[5]}>
|
||||
<CheckboxGroup
|
||||
label="Personengruppen"
|
||||
values={data.person_groups}
|
||||
onChange={(v) => onChange('person_groups', v)}
|
||||
options={PERSON_GROUP_OPTIONS}
|
||||
helpText="Waehlen Sie alle Personengruppen, die mit der Maschine in Beruehrung kommen koennen."
|
||||
/>
|
||||
<TextArea
|
||||
label="Qualifikationsanforderungen"
|
||||
value={data.qualification_requirements}
|
||||
onChange={(v) => onChange('qualification_requirements', v)}
|
||||
placeholder="Bedienpersonal: Unterweisung gemaess Betriebsanleitung, min. 18 Jahre Einrichter: Facharbeiter Mechatronik + Herstellerschulung Wartungspersonal: Elektrofachkraft fuer Elektroanschluss, Mechatroniker fuer mechanische Wartung"
|
||||
helpText="Mindestqualifikation je Personengruppe mit Verweis auf erforderliche Schulungen."
|
||||
rows={4}
|
||||
/>
|
||||
</SectionCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
'use client'
|
||||
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import type { FormSection } from '../_types'
|
||||
|
||||
function SectionIcon({ icon, className }: { icon: FormSection['icon']; className?: string }) {
|
||||
const cls = className || 'w-5 h-5'
|
||||
switch (icon) {
|
||||
case 'clipboard':
|
||||
return (
|
||||
<svg className={cls} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
)
|
||||
case 'target':
|
||||
return (
|
||||
<svg className={cls} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
)
|
||||
case 'alert':
|
||||
return (
|
||||
<svg className={cls} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
)
|
||||
case 'box':
|
||||
return (
|
||||
<svg className={cls} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
</svg>
|
||||
)
|
||||
case 'link':
|
||||
return (
|
||||
<svg className={cls} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
)
|
||||
case 'users':
|
||||
return (
|
||||
<svg className={cls} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
interface SectionCardProps {
|
||||
section: FormSection
|
||||
defaultOpen?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function SectionCard({ section, defaultOpen = false, children }: SectionCardProps) {
|
||||
const [open, setOpen] = useState(defaultOpen)
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className="w-full flex items-center gap-4 px-6 py-4 text-left hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors"
|
||||
>
|
||||
<div className="w-10 h-10 bg-purple-50 dark:bg-purple-900/30 rounded-lg flex items-center justify-center text-purple-600 flex-shrink-0">
|
||||
<SectionIcon icon={section.icon} className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{section.number}. {section.title}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{section.description}
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-400 transition-transform flex-shrink-0 ${open ? 'rotate-180' : ''}`}
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="px-6 pb-6 pt-2 border-t border-gray-100 dark:border-gray-700 space-y-4">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,85 +1,142 @@
|
||||
// IACE Interview Types — structured questions based on CE risk assessment document structure
|
||||
// IACE Limits & Intended Use Form Types — CE Risk Assessment Step 3
|
||||
// Based on ISO 12100 Sections 5.3 (Intended Use) and 5.4 (Limits)
|
||||
|
||||
export interface InterviewQuestion {
|
||||
id: string
|
||||
section: number
|
||||
sectionTitle: string
|
||||
question: string
|
||||
type: 'text' | 'textarea' | 'select' | 'multiselect' | 'number'
|
||||
options?: string[]
|
||||
placeholder?: string
|
||||
helpText?: string
|
||||
required?: boolean
|
||||
/** Full form data structure stored in project metadata.limits_form */
|
||||
export interface LimitsFormData {
|
||||
// Section 1: Allgemeine Produktbeschreibung
|
||||
machine_designation: string
|
||||
machine_type: string
|
||||
manufacturer: string
|
||||
year_of_construction: string
|
||||
serial_number: string
|
||||
general_description: string
|
||||
|
||||
// Section 2: Bestimmungsgemasse Verwendung
|
||||
intended_purpose: string
|
||||
area_of_use: string
|
||||
operating_modes: string[]
|
||||
variants: string
|
||||
|
||||
// Section 3: Vorhersehbare Fehlanwendung
|
||||
foreseeable_misuses: string
|
||||
|
||||
// Section 4: Grenzen der Maschine
|
||||
spatial_limits: string
|
||||
temporal_limits: string
|
||||
operating_conditions: string
|
||||
energy_supply: string
|
||||
|
||||
// Section 5: Schnittstellen
|
||||
mechanical_interfaces: string
|
||||
electrical_interfaces: string
|
||||
software_interfaces: string
|
||||
pneumatic_hydraulic_interfaces: string
|
||||
|
||||
// Section 6: Betroffene Personen
|
||||
person_groups: string[]
|
||||
qualification_requirements: string
|
||||
}
|
||||
|
||||
export interface InterviewAnswer {
|
||||
questionId: string
|
||||
value: string | string[] | number
|
||||
export const EMPTY_LIMITS_FORM: LimitsFormData = {
|
||||
machine_designation: '',
|
||||
machine_type: '',
|
||||
manufacturer: '',
|
||||
year_of_construction: '',
|
||||
serial_number: '',
|
||||
general_description: '',
|
||||
intended_purpose: '',
|
||||
area_of_use: '',
|
||||
operating_modes: [],
|
||||
variants: '',
|
||||
foreseeable_misuses: '',
|
||||
spatial_limits: '',
|
||||
temporal_limits: '',
|
||||
operating_conditions: '',
|
||||
energy_supply: '',
|
||||
mechanical_interfaces: '',
|
||||
electrical_interfaces: '',
|
||||
software_interfaces: '',
|
||||
pneumatic_hydraulic_interfaces: '',
|
||||
person_groups: [],
|
||||
qualification_requirements: '',
|
||||
}
|
||||
|
||||
export const INTERVIEW_QUESTIONS: InterviewQuestion[] = [
|
||||
// Section 1: Maschinenbeschreibung
|
||||
{ id: 'machine_name', section: 1, sectionTitle: 'Maschinenbeschreibung', question: 'Wie heisst die Maschine / Anlage?', type: 'text', placeholder: 'z.B. Kniehebelpresse HP-500', required: true },
|
||||
{ id: 'machine_type', section: 1, sectionTitle: 'Maschinenbeschreibung', question: 'Welcher Maschinentyp ist es?', type: 'select', options: ['Presse', 'Roboter', 'CNC-Maschine', 'Foerderanlage', 'Verpackungsmaschine', 'Schweissanlage', 'Montageanlage', 'Sondermaschine'], required: true },
|
||||
{ id: 'manufacturer', section: 1, sectionTitle: 'Maschinenbeschreibung', question: 'Wer ist der Hersteller?', type: 'text', placeholder: 'z.B. Mueller Maschinenbau GmbH' },
|
||||
{ id: 'description', section: 1, sectionTitle: 'Maschinenbeschreibung', question: 'Beschreiben Sie die Anlage und ihre Funktion:', type: 'textarea', placeholder: 'Die Anlage ist eine vollautomatische...', helpText: 'Beschreiben Sie den Zweck, die Arbeitsweise und den Aufbau der Maschine.', required: true },
|
||||
{ id: 'components', section: 1, sectionTitle: 'Maschinenbeschreibung', question: 'Aus welchen Baugruppen besteht die Anlage?', type: 'multiselect', options: ['Zufuehrung', 'Presse/Umformung', 'Transferanlage', 'Foerderband', 'Roboter', 'Absaugung', 'Schmieranlage', 'Schutzumhausung', 'Aufzug/Hubwerk', 'Schaltschrank/Steuerung', 'Kuehlung', 'Heizung', 'Hydraulik', 'Pneumatik'] },
|
||||
|
||||
// Section 2: Lebensphasen
|
||||
{ id: 'lifecycle_operation', section: 2, sectionTitle: 'Lebensphasen', question: 'Wie laeuft der Normalbetrieb ab?', type: 'textarea', placeholder: 'Die Bearbeitung erfolgt vollautomatisch...', helpText: 'Beschreiben Sie den typischen Produktionszyklus.' },
|
||||
{ id: 'lifecycle_setup', section: 2, sectionTitle: 'Lebensphasen', question: 'Welche Arbeiten fallen beim Einrichten/Umruesten an?', type: 'textarea', placeholder: 'Werkzeugwechsel, Parameteranpassung...' },
|
||||
{ id: 'lifecycle_maintenance', section: 2, sectionTitle: 'Lebensphasen', question: 'Welche Wartungs- und Reinigungsarbeiten sind noetig?', type: 'textarea', placeholder: 'Woechentliche Schmierung, Filter reinigen...' },
|
||||
|
||||
// Section 3: Bestimmungsgemäße Verwendung
|
||||
{ id: 'intended_use', section: 3, sectionTitle: 'Bestimmungsgemäße Verwendung', question: 'Wozu dient die Maschine (bestimmungsgemäße Verwendung)?', type: 'textarea', placeholder: 'Die Anlage dient der automatischen...', required: true },
|
||||
|
||||
// Section 4: Vorhersehbare Fehlanwendung
|
||||
{ id: 'misuse', section: 4, sectionTitle: 'Vorhersehbare Fehlanwendung', question: 'Welche vorhersehbaren Fehlanwendungen sind moeglich?', type: 'multiselect', options: ['Ueberschreiten von Belastungsgrenzen', 'Verwendung ungeeigneter Materialien', 'Betrieb in explosionsgefaehrdeter Atmosphaere', 'Betrieb bei Leckagen', 'Betrieb ohne PSA', 'Umgehung von Sicherheitseinrichtungen', 'Bedienung ohne Einweisung', 'Manipulation der Steuerung'], helpText: 'Waehlen Sie alle zutreffenden oder ergaenzen Sie.' },
|
||||
|
||||
// Section 5: Qualifikation
|
||||
{ id: 'operator_qualification', section: 5, sectionTitle: 'Qualifikation der Benutzer', question: 'Welche Qualifikation hat das Bedienpersonal?', type: 'select', options: ['Eingewiesenes Personal ohne Fachkenntnisse', 'Angelernte Mitarbeiter', 'Facharbeiter mit Berufsausbildung', 'Ingenieure/Techniker', 'Elektrofachkraefte'] },
|
||||
{ id: 'maintenance_qualification', section: 5, sectionTitle: 'Qualifikation der Benutzer', question: 'Wer fuehrt Wartung/Instandhaltung durch?', type: 'select', options: ['Eigenes Fachpersonal', 'Hersteller-Service', 'Fremdfirma', 'Nicht separat betrachtet (CE-Erklaerung Lieferant)'] },
|
||||
|
||||
// Section 6: Grenzen
|
||||
{ id: 'spatial_limits', section: 6, sectionTitle: 'Raeumliche und zeitliche Grenzen', question: 'Welche Gefahrenbereiche gibt es?', type: 'textarea', placeholder: 'Werkzeugeinbauraum, Zufuehrbereich, Auslaufbereich...', helpText: 'Listen Sie alle Bereiche auf, in denen Personen gefaehrdet sein koennten.' },
|
||||
{ id: 'safety_measures_org', section: 6, sectionTitle: 'Raeumliche und zeitliche Grenzen', question: 'Welche organisatorischen Schutzmassnahmen gelten?', type: 'multiselect', options: ['Sicherheitsschuhe Pflicht', 'Gehoerschutz Pflicht', 'Handschuhe Pflicht', 'Schutzbrille Pflicht', 'Zutrittsbeschraenkung', 'Unterweisung vor Zugang'] },
|
||||
|
||||
// Section 7: Technische Daten
|
||||
{ id: 'force_pressure', section: 7, sectionTitle: 'Technische Daten', question: 'Welche Kraefte/Druecke wirken? (kN, bar, Tonnen)', type: 'text', placeholder: 'z.B. 20000 kN, 250 bar' },
|
||||
{ id: 'voltage', section: 7, sectionTitle: 'Technische Daten', question: 'Welche Spannungen sind vorhanden? (V)', type: 'text', placeholder: 'z.B. 400V Hauptstrom, 24V Steuerung' },
|
||||
{ id: 'temperature', section: 7, sectionTitle: 'Technische Daten', question: 'Treten erhoehte Temperaturen auf? (°C)', type: 'text', placeholder: 'z.B. 130°C Werkstuecktemperatur' },
|
||||
{ id: 'speed_rpm', section: 7, sectionTitle: 'Technische Daten', question: 'Welche Geschwindigkeiten/Drehzahlen gibt es? (/min, m/s)', type: 'text', placeholder: 'z.B. 736 /min Schwungrad, 36 Huebe/min' },
|
||||
{ id: 'energy', section: 7, sectionTitle: 'Technische Daten', question: 'Welches Arbeitsvermoegen hat die Maschine? (kJ, kW)', type: 'text', placeholder: 'z.B. 400 kJ, 3 kW Motor' },
|
||||
|
||||
// Section 8: Umgebung
|
||||
{ id: 'environment', section: 8, sectionTitle: 'Umgebungsbedingungen', question: 'Unter welchen Umgebungsbedingungen wird die Maschine betrieben?', type: 'textarea', placeholder: '+5 bis +40°C, max. 95% Luftfeuchte, bis 1000m ueNN', helpText: 'Temperatur, Luftfeuchte, Hoehenlage, besondere Bedingungen.' },
|
||||
export const AREA_OF_USE_OPTIONS = [
|
||||
'Industriell',
|
||||
'Gewerblich',
|
||||
'Privat',
|
||||
'Oeffentlich',
|
||||
]
|
||||
|
||||
export function answersToNarrativeText(answers: InterviewAnswer[]): string {
|
||||
const parts: string[] = []
|
||||
const getVal = (id: string) => {
|
||||
const a = answers.find(a => a.questionId === id)
|
||||
if (!a) return ''
|
||||
return Array.isArray(a.value) ? (a.value as string[]).join(', ') : String(a.value)
|
||||
}
|
||||
export const OPERATING_MODE_OPTIONS = [
|
||||
'Automatikbetrieb',
|
||||
'Einrichtbetrieb',
|
||||
'Handbetrieb',
|
||||
'Sonderbetrieb',
|
||||
'Reinigung',
|
||||
'Wartung',
|
||||
]
|
||||
|
||||
parts.push(`Maschinenname: ${getVal('machine_name')}. Maschinentyp: ${getVal('machine_type')}. Hersteller: ${getVal('manufacturer')}.`)
|
||||
if (getVal('description')) parts.push(getVal('description'))
|
||||
if (getVal('components')) parts.push(`Baugruppen: ${getVal('components')}.`)
|
||||
if (getVal('lifecycle_operation')) parts.push(`Betrieb: ${getVal('lifecycle_operation')}`)
|
||||
if (getVal('lifecycle_setup')) parts.push(`Einrichten: ${getVal('lifecycle_setup')}`)
|
||||
if (getVal('lifecycle_maintenance')) parts.push(`Wartung: ${getVal('lifecycle_maintenance')}`)
|
||||
if (getVal('intended_use')) parts.push(`Bestimmungsgemäße Verwendung: ${getVal('intended_use')}`)
|
||||
if (getVal('misuse')) parts.push(`Vorhersehbare Fehlanwendung: ${getVal('misuse')}`)
|
||||
if (getVal('operator_qualification')) parts.push(`Bedienpersonal: ${getVal('operator_qualification')}`)
|
||||
if (getVal('spatial_limits')) parts.push(`Gefahrenbereiche: ${getVal('spatial_limits')}`)
|
||||
if (getVal('safety_measures_org')) parts.push(`Organisatorische Massnahmen: ${getVal('safety_measures_org')}`)
|
||||
if (getVal('force_pressure')) parts.push(getVal('force_pressure'))
|
||||
if (getVal('voltage')) parts.push(getVal('voltage'))
|
||||
if (getVal('temperature')) parts.push(getVal('temperature'))
|
||||
if (getVal('speed_rpm')) parts.push(getVal('speed_rpm'))
|
||||
if (getVal('energy')) parts.push(getVal('energy'))
|
||||
if (getVal('environment')) parts.push(`Umgebung: ${getVal('environment')}`)
|
||||
export const PERSON_GROUP_OPTIONS = [
|
||||
'Bedienpersonal',
|
||||
'Einrichter',
|
||||
'Wartungspersonal',
|
||||
'Reinigungspersonal',
|
||||
'Auszubildende',
|
||||
'Besucher',
|
||||
'Fremdfirmenpersonal',
|
||||
]
|
||||
|
||||
return parts.join('\n')
|
||||
/** Section definition for rendering collapsible form cards */
|
||||
export interface FormSection {
|
||||
id: string
|
||||
number: number
|
||||
title: string
|
||||
description: string
|
||||
icon: 'clipboard' | 'target' | 'alert' | 'box' | 'link' | 'users'
|
||||
}
|
||||
|
||||
export const FORM_SECTIONS: FormSection[] = [
|
||||
{
|
||||
id: 'product_description',
|
||||
number: 1,
|
||||
title: 'Allgemeine Produktbeschreibung',
|
||||
description: 'Grundlegende Angaben zur Maschine/Anlage',
|
||||
icon: 'clipboard',
|
||||
},
|
||||
{
|
||||
id: 'intended_use',
|
||||
number: 2,
|
||||
title: 'Bestimmungsgemasse Verwendung',
|
||||
description: 'Verwendungszweck, Einsatzbereich und Betriebsarten',
|
||||
icon: 'target',
|
||||
},
|
||||
{
|
||||
id: 'foreseeable_misuse',
|
||||
number: 3,
|
||||
title: 'Vorhersehbare Fehlanwendung',
|
||||
description: 'Vernuenftigerweise vorhersehbare Fehlanwendungen gemaess ISO 12100 Abschnitt 5.4',
|
||||
icon: 'alert',
|
||||
},
|
||||
{
|
||||
id: 'machine_limits',
|
||||
number: 4,
|
||||
title: 'Grenzen der Maschine',
|
||||
description: 'Raeumliche, zeitliche und betriebliche Grenzen',
|
||||
icon: 'box',
|
||||
},
|
||||
{
|
||||
id: 'interfaces',
|
||||
number: 5,
|
||||
title: 'Schnittstellen',
|
||||
description: 'Mechanische, elektrische, Software- und pneumatische/hydraulische Schnittstellen',
|
||||
icon: 'link',
|
||||
},
|
||||
{
|
||||
id: 'affected_persons',
|
||||
number: 6,
|
||||
title: 'Betroffene Personen',
|
||||
description: 'Personengruppen und Qualifikationsanforderungen',
|
||||
icon: 'users',
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user