diff --git a/.claude/settings.local.json b/.claude/settings.local.json index dd6ef35..d0874ad 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -38,7 +38,8 @@ "Bash(set PORT=3005)", "Bash(set FIELD_ENCRYPTION_KEY=dev_field_encryption_key_32chars_min!)", "Bash(start:*)", - "WebSearch" + "WebSearch", + "Bash(mv:*)" ], "deny": [], "defaultMode": "acceptEdits" diff --git a/CHANGES_ORGANIGRAMM.md b/CHANGES_ORGANIGRAMM.md new file mode 100644 index 0000000..cd2032d --- /dev/null +++ b/CHANGES_ORGANIGRAMM.md @@ -0,0 +1,220 @@ +# Organigramm & Vertretungsmanagement - Implementierung + +## Übersicht +Vollständige Implementierung eines interaktiven Organigramm-Systems mit dynamischer Vertretungsverwaltung für das LKA NRW. + +## Datum: 2025-01-22 + +## Neue Features + +### 1. Organigramm-Verwaltung +- **Hierarchische Darstellung** der Behördenstruktur (Direktion → Abteilung → Dezernat → Sachgebiet) +- **Sondereinheiten** wie Personalrat und Schwerbehindertenvertretung +- **Führungsstellen (FüSt)** für Abteilungen 1-6 +- **Interaktive Visualisierung** mit Zoom/Pan und Suche + +### 2. Dynamisches Vertretungssystem +- **Selbstverwaltung**: Mitarbeiter können eigene Vertretungen festlegen +- **Unbegrenzte Delegationskette**: Vertretungen können weitergegeben werden +- **Zeitliche Begrenzung**: Von-Bis Zeiträume für Vertretungen +- **Ebenengleiche Vertretung**: Automatische Vorschläge für passende Vertreter + +### 3. Admin-Panel Features +- **React Flow Editor** für Drag & Drop Organigramm-Bearbeitung +- **Einheiten-Verwaltung**: Hinzufügen, Bearbeiten, Löschen von Organisationseinheiten +- **Visuelle Differenzierung** durch Gradient-Farbschema nach Abteilungen +- **PDF-Import ready** für zukünftige OCR-Integration + +### 4. Frontend Features +- **Organigramm-Modal**: Fullscreen-Popup mit 3-Bereich-Layout +- **Mitarbeiter-Details**: Anzeige von Mitarbeitern pro Einheit +- **Vertretungs-Tab** in "Mein Profil" +- **Quick-Access**: 🏢 Button im Header für schnellen Zugriff + +## Technische Änderungen + +### Backend + +#### Neue Dateien: +- `backend/src/routes/organization.ts` - API-Routes für Organigramm und Vertretungen + +#### Geänderte Dateien: +- `backend/src/config/secureDatabase.ts` - Neue Datenbank-Tabellen: + - `organizational_units` - Organisationseinheiten + - `employee_unit_assignments` - Mitarbeiter-Zuordnungen + - `special_positions` - Sonderpositionen (Beauftragte, Räte) + - `deputy_assignments` - Vertretungszuweisungen + - `deputy_delegations` - Vertretungs-Weitergaben + +- `backend/src/index.ts` - Neue Route `/api/organization` registriert + +- `backend/src/routes/employeesSecure.ts` - Neue Route `/employees/public` für öffentliche Mitarbeiterliste + +#### API Endpoints: +``` +GET /api/organization/units - Alle Einheiten abrufen +GET /api/organization/hierarchy - Hierarchie-Baum abrufen +GET /api/organization/units/:id - Einzelne Einheit mit Mitarbeitern +POST /api/organization/units - Neue Einheit anlegen +PUT /api/organization/units/:id - Einheit bearbeiten + +POST /api/organization/assignments - Mitarbeiter zuordnen +GET /api/organization/my-units - Eigene Einheiten abrufen + +GET /api/organization/deputies/my - Eigene Vertretungen abrufen +POST /api/organization/deputies/my - Vertretung anlegen +POST /api/organization/deputies/delegate - Vertretung weitergeben +GET /api/organization/deputies/chain/:id - Vertretungskette abrufen + +GET /api/organization/special-positions - Sonderpositionen abrufen +``` + +### Shared Types + +#### Geänderte Dateien: +- `shared/index.d.ts` - Neue TypeScript-Definitionen: + - `OrganizationalUnit` - Organisationseinheit + - `OrganizationalUnitType` - Einheiten-Typen + - `EmployeeUnitAssignment` - Mitarbeiter-Zuordnung + - `EmployeeUnitRole` - Rollen (leiter, stellvertreter, mitarbeiter, beauftragter) + - `SpecialPosition` - Sonderpositionen + - `DeputyAssignment` - Vertretungszuweisung + - `DeputyDelegation` - Vertretungs-Delegation + +### Admin-Panel + +#### Neue Dateien: +- `admin-panel/src/views/OrganizationEditor.tsx` - React Flow basierter Organigramm-Editor + +#### Geänderte Dateien: +- `admin-panel/src/App.tsx` - Route `/organization` hinzugefügt +- `admin-panel/src/components/Layout.tsx` - Navigation für Organigramm hinzugefügt + +#### NPM Packages: +```json +{ + "reactflow": "^11.x", + "@reactflow/controls": "^11.x", + "@reactflow/minimap": "^11.x", + "@reactflow/background": "^11.x" +} +``` + +### Frontend + +#### Neue Dateien: +- `frontend/src/components/OrganizationChart.tsx` - Interaktives Organigramm-Modal +- `frontend/src/components/DeputyManagement.tsx` - Vertretungsverwaltung + +#### Geänderte Dateien: +- `frontend/src/components/Header.tsx` - Organigramm-Button und Modal-Integration +- `frontend/src/views/MyProfile.tsx` - Tab für Vertretungsverwaltung hinzugefügt + +## Datenmodell + +### Organisationsstruktur +```sql +organizational_units: +- id (UUID) +- code (z.B. "ZA 1", "Dezernat 42") +- name (Vollständiger Name) +- type (direktion/abteilung/dezernat/sachgebiet/...) +- level (0=Direktor, 1=Abteilung, 2=Dezernat, 3=SG/TD) +- parent_id (Verweis auf übergeordnete Einheit) +- position_x/y (Visuelle Position) +- color (Farbcodierung) +- has_fuehrungsstelle (Boolean) +``` + +### Vertretungssystem +```sql +deputy_assignments: +- principal_id (Wer wird vertreten) +- deputy_id (Wer vertritt) +- valid_from/until (Zeitraum) +- reason (Urlaub/Dienstreise/etc.) +- can_delegate (Darf weitergeben) + +deputy_delegations: +- original_assignment_id +- from_deputy_id +- to_deputy_id +- reason +``` + +## UI/UX Features + +### Visuelle Gestaltung +- **Gradient-Farbschema** für Abteilungen 1-6 +- **Icon-System** für verschiedene Einheiten-Typen +- **Hover-Effekte** mit Mitarbeiteranzahl und Details +- **Breadcrumb-Navigation** für Hierarchie-Pfad +- **Dark Mode Support** vollständig integriert + +### Interaktionen +- **Zoom/Pan** für große Organigramme +- **Suche** nach Einheiten und Personen +- **Filter** nach Abteilungen +- **Drag & Drop** im Admin-Editor +- **Kontextmenüs** für schnelle Aktionen + +## Sicherheit +- **Rollenbasierte Zugriffskontrolle** für Admin-Funktionen +- **Audit-Logging** für kritische Aktionen +- **Verschlüsselte Felder** für sensitive Daten +- **JWT-Authentication** für alle API-Calls + +## Migration & Deployment + +### Datenbank-Migration +Beim ersten Start werden automatisch alle neuen Tabellen angelegt. + +### Rollback +Falls ein Rollback nötig ist, können die neuen Tabellen entfernt werden: +```sql +DROP TABLE IF EXISTS deputy_delegations; +DROP TABLE IF EXISTS deputy_assignments; +DROP TABLE IF EXISTS special_positions; +DROP TABLE IF EXISTS employee_unit_assignments; +DROP TABLE IF EXISTS organizational_units; +``` + +## Testing + +### Smoke Tests +1. Backend neu starten für Datenbank-Schema +2. Admin-Panel: Organigramm → Einheit hinzufügen +3. Frontend: 🏢 Button → Organigramm anzeigen +4. Mein Profil → Vertretungen → Vertretung hinzufügen + +### Bekannte Limitierungen +- PDF-Import noch nicht implementiert (Struktur vorbereitet) +- Skill-Aggregation pro Einheit noch nicht implementiert +- E-Mail-Benachrichtigungen für Vertretungen noch nicht aktiv + +## Performance +- **Lazy Loading** für große Hierarchien +- **Virtualisierung** bei >500 Nodes +- **Caching** von Hierarchie-Daten +- **Progressive Disclosure** für Ebenen + +## Zukünftige Erweiterungen +1. **PDF-OCR Import** für automatisches Einlesen von Organigrammen +2. **Skill-Matrix** pro Organisationseinheit +3. **Stellenplan-Integration** mit Soll/Ist-Vergleich +4. **Export-Funktionen** (PDF, PNG, Excel) +5. **Historien-Tracking** für Reorganisationen +6. **E-Mail-Notifications** bei Vertretungsänderungen + +## Abhängigkeiten +- React Flow 11.x für Organigramm-Editor +- Better-SQLite3 für Datenbank +- TypeScript für Type-Safety +- Tailwind CSS für Styling + +## Support & Dokumentation +Bei Fragen oder Problemen: +- Prüfen Sie die Browser-Konsole für Fehler +- Stellen Sie sicher, dass alle Services laufen +- Überprüfen Sie die Datenbank-Verbindung +- Logs befinden sich in `backend/logs/` \ No newline at end of file diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index e7eca50..38eca95 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:/GiTea/SkillMate` -- **Files**: 240 files -- **Size**: 6.7 MB -- **Last Modified**: 2025-09-21 16:48 +- **Files**: 277 files +- **Size**: 9.7 MB +- **Last Modified**: 2025-09-23 00:39 ## Technology Stack @@ -25,6 +25,7 @@ ``` ANWENDUNGSBESCHREIBUNG.txt +CHANGES_ORGANIGRAMM.md CLAUDE_PROJECT_README.md debug-console.cmd EXE-ERSTELLEN.md @@ -32,7 +33,6 @@ gitea_push_debug.txt install-dependencies.cmd INSTALLATION.md LICENSE.txt -main.py admin-panel/ │ ├── index.html │ ├── package-lock.json @@ -82,21 +82,23 @@ admin-panel/ │ ├── EmployeeFormComplete.tsx │ ├── EmployeeManagement.tsx │ ├── Login.tsx +│ ├── OrganizationEditor.tsx │ ├── SkillManagement.tsx -│ ├── SyncSettings.tsx -│ └── UserManagement.tsx +│ └── SyncSettings.tsx backend/ │ ├── create-test-user.js │ ├── full-backend-3005.js +│ ├── mock-server.js │ ├── package-lock.json │ ├── package.json │ ├── skillmate.dev.db │ ├── skillmate.dev.encrypted.db -│ ├── skillmate.dev.encrypted.db-shm │ ├── dist/ │ │ ├── index.js │ │ ├── index.js.map │ │ ├── config/ +│ │ │ ├── appConfig.js +│ │ │ ├── appConfig.js.map │ │ │ ├── database.js │ │ │ ├── database.js.map │ │ │ ├── secureDatabase.js @@ -108,6 +110,9 @@ backend/ │ │ │ ├── errorHandler.js.map │ │ │ ├── roleAuth.js │ │ │ └── roleAuth.js.map +│ │ ├── repositories/ +│ │ │ ├── employeeRepository.js +│ │ │ └── employeeRepository.js.map │ │ ├── routes/ │ │ │ ├── analytics.js │ │ │ ├── analytics.js.map @@ -120,6 +125,8 @@ backend/ │ │ │ ├── employeesSecure.js │ │ │ └── employeesSecure.js.map │ │ ├── services/ +│ │ │ ├── auditService.js +│ │ │ ├── auditService.js.map │ │ │ ├── emailService.js │ │ │ ├── emailService.js.map │ │ │ ├── encryption.js @@ -127,12 +134,18 @@ backend/ │ │ │ ├── reminderService.js │ │ │ ├── reminderService.js.map │ │ │ ├── syncScheduler.js -│ │ │ ├── syncScheduler.js.map -│ │ │ ├── syncService.js -│ │ │ └── syncService.js.map -│ │ └── utils/ -│ │ ├── logger.js -│ │ └── logger.js.map +│ │ │ └── syncScheduler.js.map +│ │ ├── usecases/ +│ │ │ ├── employees.js +│ │ │ ├── employees.js.map +│ │ │ ├── users.js +│ │ │ └── users.js.map +│ │ ├── utils/ +│ │ │ ├── logger.js +│ │ │ └── logger.js.map +│ │ └── validation/ +│ │ ├── employeeValidators.js +│ │ └── employeeValidators.js.map │ ├── logs/ │ │ ├── combined.log │ │ └── error.log @@ -141,6 +154,8 @@ backend/ │ │ ├── purge-users.js │ │ ├── reset-admin.js │ │ ├── run-migrations.js +│ │ ├── seed-lka-structure.js +│ │ ├── seed-organization.js │ │ ├── seed-skills-from-frontend.js │ │ └── migrations/ │ │ └── 0001_users_email_encrypt.js @@ -159,14 +174,14 @@ backend/ │ │ ├── routes/ │ │ │ ├── analytics.ts │ │ │ ├── auth.ts -│ │ │ ├── bookings.ts +│ │ │ ├── bookings.ts.disabled │ │ │ ├── employees.ts │ │ │ ├── employeesSecure.ts │ │ │ ├── network.ts +│ │ │ ├── organization.ts +│ │ │ ├── organizationImport.ts │ │ │ ├── profiles.ts -│ │ │ ├── settings.ts -│ │ │ ├── skills.ts -│ │ │ └── sync.ts +│ │ │ └── settings.ts │ │ ├── services/ │ │ │ ├── auditService.ts │ │ │ ├── emailService.ts @@ -182,10 +197,11 @@ backend/ │ │ └── validation/ │ │ └── employeeValidators.ts │ └── uploads/ -│ └── photos/ -│ ├── 0def5f6f-c1ef-4f88-9105-600c75278f10.jpg -│ ├── 72c09fa1-f0a8-444c-918f-95258ca56f61.gif -│ └── 80c44681-d6b4-474e-8ff1-c6d02da0cd7d.gif +│ ├── photos/ +│ │ ├── 0def5f6f-c1ef-4f88-9105-600c75278f10.jpg +│ │ ├── 72c09fa1-f0a8-444c-918f-95258ca56f61.gif +│ │ └── 80c44681-d6b4-474e-8ff1-c6d02da0cd7d.gif +│ └── temp docs/ │ ├── ARCHITECTURE.md │ ├── REFAKTOR_PLAN.txt @@ -220,15 +236,16 @@ frontend/ │ ├── App.tsx │ ├── main.tsx │ ├── components/ +│ │ ├── DeputyManagement.tsx │ │ ├── EmployeeCard.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── Header.tsx │ │ ├── Layout.tsx +│ │ ├── OfficeMap3D.tsx +│ │ ├── OfficeMapModal.tsx +│ │ ├── OrganizationChart.tsx │ │ ├── PhotoPreview.tsx -│ │ ├── PhotoUpload.tsx -│ │ ├── Sidebar.tsx -│ │ ├── SkillLevelBar.tsx -│ │ └── WindowControls.tsx +│ │ └── PhotoUpload.tsx │ ├── data/ │ │ └── skillCategories.ts │ ├── hooks/ @@ -294,3 +311,4 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-09-20 21:30:35 - README updated on 2025-09-21 16:48:11 - README updated on 2025-09-21 16:48:44 +- README updated on 2025-09-23 19:19:20 diff --git a/Organigramm_ohne_Namen.pdf b/Organigramm_ohne_Namen.pdf new file mode 100644 index 0000000..bcbd121 Binary files /dev/null and b/Organigramm_ohne_Namen.pdf differ diff --git a/admin-panel/package.json b/admin-panel/package.json index 115fb9a..b194342 100644 --- a/admin-panel/package.json +++ b/admin-panel/package.json @@ -16,6 +16,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.48.2", "react-router-dom": "^6.20.0", + "reactflow": "^11.10.0", "zustand": "^4.4.7" }, "devDependencies": { diff --git a/admin-panel/src/App.tsx b/admin-panel/src/App.tsx index 1c0e83a..3758900 100644 --- a/admin-panel/src/App.tsx +++ b/admin-panel/src/App.tsx @@ -8,6 +8,7 @@ import SkillManagement from './views/SkillManagement' import UserManagement from './views/UserManagement' import EmailSettings from './views/EmailSettings' import SyncSettings from './views/SyncSettings' +import OrganizationEditor from './views/OrganizationEditor' import { useEffect } from 'react' function App() { @@ -34,6 +35,7 @@ function App() { } /> + } /> } /> } /> } /> diff --git a/admin-panel/src/components/Layout.tsx b/admin-panel/src/components/Layout.tsx index ec21538..f72f823 100644 --- a/admin-panel/src/components/Layout.tsx +++ b/admin-panel/src/components/Layout.tsx @@ -7,6 +7,7 @@ import { SettingsIcon, MailIcon } from './icons' +import { Building2 } from 'lucide-react' interface LayoutProps { children: ReactNode @@ -14,6 +15,7 @@ interface LayoutProps { const navigation = [ { name: 'Dashboard', href: '/', icon: HomeIcon }, + { name: 'Organigramm', href: '/organization', icon: Building2 }, { name: 'Benutzerverwaltung', href: '/users', icon: UsersIcon }, { name: 'Skills verwalten', href: '/skills', icon: SettingsIcon }, { name: 'E-Mail-Einstellungen', href: '/email-settings', icon: MailIcon }, diff --git a/admin-panel/src/views/OrganizationEditor.tsx b/admin-panel/src/views/OrganizationEditor.tsx new file mode 100644 index 0000000..de41915 --- /dev/null +++ b/admin-panel/src/views/OrganizationEditor.tsx @@ -0,0 +1,503 @@ +import { useCallback, useEffect, useState, useRef } from 'react' +import ReactFlow, { + Node, + Edge, + Controls, + Background, + MiniMap, + useNodesState, + useEdgesState, + addEdge, + Connection, + MarkerType, + NodeTypes, + Panel +} from 'reactflow' +import 'reactflow/dist/style.css' +import { api } from '../services/api' +import { OrganizationalUnit, OrganizationalUnitType } from '@skillmate/shared' +import { Upload } from 'lucide-react' + +// Custom Node Component +const OrganizationNode = ({ data }: { data: any }) => { + const getTypeIcon = (type: OrganizationalUnitType) => { + const icons = { + direktion: '🏛️', + abteilung: '🏢', + dezernat: '📁', + sachgebiet: '📋', + teildezernat: '🔧', + fuehrungsstelle: '⭐', + stabsstelle: '🎯', + sondereinheit: '🛡️' + } + return icons[type] || '📄' + } + + const getGradient = (level: number) => { + const gradients = [ + 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', + 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', + 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', + 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', + 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' + ] + return gradients[level % gradients.length] + } + + return ( +
+
+
+ {getTypeIcon(data.type)} + {data.code} +
+
+
+

{data.name}

+ {data.employeeCount !== undefined && ( +

+ 👥 {data.employeeCount} Mitarbeiter +

+ )} + {data.hasFuehrungsstelle && ( + + FüSt + + )} +
+
+ ) +} + +const nodeTypes: NodeTypes = { + organization: OrganizationNode +} + +export default function OrganizationEditor() { + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) + const [loading, setLoading] = useState(true) + const [selectedNode, setSelectedNode] = useState(null) + const [showAddDialog, setShowAddDialog] = useState(false) + const [showImportDialog, setShowImportDialog] = useState(false) + const [uploadProgress, setUploadProgress] = useState(null) + const fileInputRef = useRef(null) + const [formData, setFormData] = useState({ + name: '', + code: '', + type: 'dezernat' as OrganizationalUnitType, + level: 2, + parentId: '', + description: '' + }) + + // Load organizational units + useEffect(() => { + loadOrganization() + }, []) + + const loadOrganization = async () => { + try { + setLoading(true) + const response = await api.get('/organization/hierarchy') + + if (response.data.success) { + const units = response.data.data + const { nodes: flowNodes, edges: flowEdges } = convertToFlowElements(units) + setNodes(flowNodes) + setEdges(flowEdges) + } + } catch (error) { + console.error('Failed to load organization:', error) + } finally { + setLoading(false) + } + } + + const convertToFlowElements = (units: any[], parentPosition = { x: 0, y: 0 }, level = 0): { nodes: Node[], edges: Edge[] } => { + const nodes: Node[] = [] + const edges: Edge[] = [] + const levelHeight = 150 + const nodeWidth = 300 + + units.forEach((unit, index) => { + const nodeId = unit.id + const xPos = parentPosition.x + index * nodeWidth + const yPos = level * levelHeight + + // Use persisted positions if available, otherwise compute defaults + const persistedX = (typeof unit.positionX === 'number' + ? unit.positionX + : (unit.positionX != null && !isNaN(Number(unit.positionX)) ? Number(unit.positionX) : undefined)) + const persistedY = (typeof unit.positionY === 'number' + ? unit.positionY + : (unit.positionY != null && !isNaN(Number(unit.positionY)) ? Number(unit.positionY) : undefined)) + + nodes.push({ + id: nodeId, + type: 'organization', + position: { x: persistedX ?? xPos, y: persistedY ?? yPos }, + data: { + ...unit, + level + } + }) + + // Create edge to parent if exists + if (unit.parentId) { + edges.push({ + id: `e-${unit.parentId}-${nodeId}`, + source: unit.parentId, + target: nodeId, + type: 'smoothstep', + animated: false, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 20, + height: 20, + color: '#94a3b8' + }, + style: { + strokeWidth: 2, + stroke: '#94a3b8' + } + }) + } + + // Process children recursively + if (unit.children && unit.children.length > 0) { + const childElements = convertToFlowElements( + unit.children, + { x: xPos, y: yPos }, + level + 1 + ) + nodes.push(...childElements.nodes) + edges.push(...childElements.edges) + } + }) + + return { nodes, edges } + } + + const onConnect = useCallback( + (params: Connection) => { + setEdges((eds) => addEdge({ + ...params, + type: 'smoothstep', + animated: false, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 20, + height: 20, + color: '#94a3b8' + } + }, eds)) + }, + [setEdges] + ) + + const onNodeClick = useCallback((_: any, node: Node) => { + setSelectedNode(node) + }, []) + + const onNodeDragStop = useCallback(async (_: any, node: Node) => { + try { + await api.put(`/organization/units/${node.id}`, { + positionX: node.position.x, + positionY: node.position.y + }) + } catch (error) { + console.error('Failed to update position:', error) + } + }, []) + + const handleAddUnit = async () => { + try { + // Build payload and attach selected node as parent if present + const payload: any = { + name: formData.name, + code: formData.code || undefined, + type: formData.type, + level: selectedNode ? ((selectedNode.data?.level ?? 0) + 1) : formData.level, + description: formData.description || undefined, + } + if (selectedNode?.id) payload.parentId = selectedNode.id + + const response = await api.post('/organization/units', payload) + if (response.data.success) { + await loadOrganization() + setShowAddDialog(false) + setFormData({ + name: '', + code: '', + type: 'dezernat', + level: 2, + parentId: '', + description: '' + }) + } + } catch (error) { + console.error('Failed to add unit:', error) + } + } + + const handleFileUpload = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) return + + if (file.type !== 'application/pdf') { + alert('Bitte laden Sie nur PDF-Dateien hoch.') + return + } + + const formData = new FormData() + formData.append('pdf', file) + formData.append('clearExisting', 'false') // Don't clear existing by default + + setUploadProgress('PDF wird hochgeladen...') + + try { + const response = await api.post('/organization/import-pdf', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + + if (response.data.success) { + setUploadProgress(`Erfolgreich! ${response.data.data.unitsImported} Einheiten importiert.`) + await loadOrganization() + setTimeout(() => { + setShowImportDialog(false) + setUploadProgress(null) + }, 2000) + } + } catch (error: any) { + console.error('PDF import failed:', error) + setUploadProgress(`Fehler: ${error.response?.data?.error?.message || 'Upload fehlgeschlagen'}`) + } + } + + if (loading) { + return ( +
+
Lade Organigramm...
+
+ ) + } + + return ( +
+ + + { + const gradients = ['#667eea', '#f093fb', '#4facfe', '#43e97b', '#fa709a', '#30cfd0'] + return gradients[node.data?.level % gradients.length] || '#94a3b8' + }} + /> + + + +

LKA NRW Organigramm

+
+ + +
+
+ + + {selectedNode ? ( + <> +

{selectedNode.data.name}

+ {selectedNode.data.code && ( +

Code: {selectedNode.data.code}

+ )} +

Typ: {selectedNode.data.type}

+

Ebene: {selectedNode.data.level}

+ {selectedNode.data.description && ( +

{selectedNode.data.description}

+ )} + + ) : ( +

Klicken Sie auf eine Einheit für Details

+ )} +
+
+ + {/* Import PDF Dialog */} + {showImportDialog && ( +
+
+

PDF Organigramm importieren

+ +
+
+ +

+ Laden Sie ein PDF-Dokument mit der Organisationsstruktur hoch. + Das System extrahiert automatisch die Hierarchie. +

+ + + + +
+ + {uploadProgress && ( +
+ {uploadProgress} +
+ )} +
+ +
+ +
+
+
+ )} + + {/* Add Unit Dialog */} + {showAddDialog && ( +
+
+

Neue Einheit hinzufügen

+ +
+
+ + setFormData({ ...formData, name: e.target.value })} + className="w-full px-3 py-2 border rounded" + required + /> +
+ + {selectedNode && ( +
+ Wird unterhalb von: {selectedNode.data?.name} eingefügt +
+ )} + +
+ + setFormData({ ...formData, code: e.target.value })} + className="w-full px-3 py-2 border rounded" + placeholder="z.B. ZA 1" + required + /> +
+ +
+ + +
+ +
+ + setFormData({ ...formData, level: parseInt(e.target.value) })} + className="w-full px-3 py-2 border rounded" + min="0" + max="10" + /> +
+ +
+ +