diff --git a/admin-panel/src/views/OrganizationEditor.tsx b/admin-panel/src/views/OrganizationEditor.tsx
index bdfbc32..b3af480 100644
--- a/admin-panel/src/views/OrganizationEditor.tsx
+++ b/admin-panel/src/views/OrganizationEditor.tsx
@@ -47,7 +47,7 @@ const OrganizationNode = ({ data }: { data: any }) => {
}
return (
-
+
(null)
const [clearExisting, setClearExisting] = useState(false)
const [issues, setIssues] = useState<{ orphans: Set
}>({ orphans: new Set() })
- const [showValidation, setShowValidation] = useState(false)
- const [reparentMode, setReparentMode] = useState<{ source: Node | null }>({ source: null })
const [preview, setPreview] = useState(null)
const [pendingOverrides, setPendingOverrides] = useState>({})
const [rememberRules, setRememberRules] = useState(true)
@@ -212,13 +210,7 @@ export default function OrganizationEditor() {
)
const onNodeClick = useCallback((_: any, node: Node) => {
- if (reparentMode.source && reparentMode.source.id !== node.id) {
- // Reparent action: set node as new parent
- handleReparent(reparentMode.source, node)
- setReparentMode({ source: null })
- } else {
- setSelectedNode(node)
- }
+ setSelectedNode(node)
}, [])
const onNodeDragStop = useCallback(async (_: any, node: Node) => {
@@ -379,12 +371,24 @@ export default function OrganizationEditor() {
const topYDirector = 10
const topYRootRow = topYDirector + yGap
// Spaltenstart mit Abstand unterhalb der Top-Row, um Überlappungen zu vermeiden
- const yStart = topYRootRow + yGap
+ // yStart wird nach Messung der tatsächlichen Kartenhöhen berechnet
+ let yStart = topYRootRow + yGap
const newNodes: Node[] = nodes.map(n => ({ ...n, position: { ...n.position } }))
const byId: Record = {}
newNodes.forEach(n => { byId[n.id] = n })
+ // Hilfsfunktion: tatsächliche Höhe einer Node messen (Fallback 130px)
+ const getNodeHeight = (id: string): number => {
+ const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null
+ if (el) {
+ const rect = el.getBoundingClientRect()
+ // Mindestens 100, damit sehr kleine Wrapper nicht zu nahe rücken
+ return Math.max(100, Math.ceil(rect.height))
+ }
+ return 130
+ }
+
const getLaneKey = (n: Node): string => {
const code: string = n.data?.code || ''
const type: string = n.data?.type || ''
@@ -457,26 +461,36 @@ export default function OrganizationEditor() {
// 1) Direktion zentriert ganz oben
const directorIndex = rootNodes.findIndex(n => (String(n.data?.type).toLowerCase() === 'direktion') || String(n.data?.code).toUpperCase() === 'DIR')
let placedIndices = new Set()
+ let directorRowHeight = 0
if (directorIndex >= 0) {
const centerX = xMargin + totalWidth / 2 - cardHalfWidth
const x = Math.max(xMargin, centerX)
rootNodes[directorIndex].position = { x, y: topYDirector }
placedIndices.add(directorIndex)
+ directorRowHeight = getNodeHeight(rootNodes[directorIndex].id)
}
// 2) Übrige ROOT-Knoten in einer zweiten Top-Reihe gleichmäßig verteilen
const others: Node[] = rootNodes.filter((_, idx) => !placedIndices.has(idx))
+ let rootRowHeight = 0
if (others.length === 1) {
const x = Math.max(xMargin, xMargin + totalWidth / 2 - cardHalfWidth)
others[0].position = { x, y: topYRootRow }
+ rootRowHeight = getNodeHeight(others[0].id)
} else if (others.length > 1) {
const step = others.length > 1 ? (totalWidth / (others.length - 1)) : 0
others.forEach((n, i) => {
const xi = xMargin + i * step - cardHalfWidth
n.position = { x: Math.max(xMargin, xi), y: topYRootRow }
+ rootRowHeight = Math.max(rootRowHeight, getNodeHeight(n.id))
})
}
+ // Spaltenstart abhängig von den tatsächlichen Höhen berechnen
+ if (directorRowHeight || rootRowHeight) {
+ yStart = topYDirector + (directorRowHeight || 0) + yGap + (rootRowHeight ? (rootRowHeight + yGap) : 0) + 16
+ }
+
// Arrange lanes
let laneIndex = 0
laneOrder.forEach(key => {
@@ -490,19 +504,19 @@ export default function OrganizationEditor() {
let yCursor = yStart
if (abteilung) {
abteilung.position = { x: xBase, y: yCursor }
- yCursor += yGap
+ yCursor += getNodeHeight(abteilung.id) + yGap
}
// Place Dezernate
const dezernate = laneNodes.filter(n => n.data?.type === 'dezernat').sort(dezOrder)
dezernate.forEach(dez => {
dez.position = { x: xBase, y: yCursor }
- yCursor += yGap
+ yCursor += getNodeHeight(dez.id) + yGap
// children SG/TD under this dez vertically
const children = laneNodes.filter(n => (n.data?.type === 'sachgebiet' || n.data?.type === 'teildezernat') && n.data?.parentId === dez.id).sort(childOrder)
children.forEach((c) => {
c.position = { x: xBase + 40, y: yCursor }
- yCursor += yGap
+ yCursor += getNodeHeight(c.id) + yGap
})
// small gap after each dezernat block
yCursor += 12
@@ -511,7 +525,7 @@ export default function OrganizationEditor() {
// Any remaining nodes (fallback)
laneNodes.filter(n => !['abteilung','dezernat','sachgebiet','teildezernat'].includes(n.data?.type || '')).forEach(n => {
n.position = { x: xBase, y: yCursor }
- yCursor += yGap
+ yCursor += getNodeHeight(n.id) + yGap
})
laneIndex += 1
@@ -521,35 +535,7 @@ export default function OrganizationEditor() {
}
// Reparenting: select source then target
- const handleStartReparent = () => {
- setReparentMode({ source: selectedNode })
- }
-
- const handleReparent = async (child: Node, newParent: Node) => {
- try {
- await api.put(`/organization/units/${child.id}`, {
- parentId: newParent.id,
- level: (newParent.data?.level ?? 0) + 1
- })
- await loadOrganization()
- } catch (error: any) {
- alert(error?.response?.data?.error?.message || 'Konnte Parent nicht ändern')
- console.error('Failed to reparent:', error)
- }
- }
-
- const persistPositions = async () => {
- try {
- await Promise.all(nodes.map(n => api.put(`/organization/units/${n.id}`, {
- positionX: Math.round(n.position.x),
- positionY: Math.round(n.position.y)
- })))
- alert('Positionen gespeichert')
- } catch (error) {
- console.error('Failed to persist positions:', error)
- alert('Fehler beim Speichern der Positionen')
- }
- }
+
if (loading) {
return (
@@ -604,25 +590,7 @@ export default function OrganizationEditor() {
>
Auto anordnen
-
-
-
+
@@ -894,32 +862,7 @@ export default function OrganizationEditor() {
)}
- {/* Validation Dialog */}
- {showValidation && (
-
-
-
Validierung
-
-
-
Waisen-Knoten (Parent fehlt)
- {issues.orphans.size === 0 ? (
-
Keine
- ) : (
-
- {Array.from(issues.orphans).map(id => {
- const n = nodes.find(n=>n.id===id)
- return - {n?.data?.code || id} – {n?.data?.name}
- })}
-
- )}
-
-
-
-
-
-
-
- )}
+
)
}