feat: Entferne Auf-/Zuklapp-Schalter und aktualisiere Kartenstil im Organigramm; Version auf 1.1.1 erhöhen

This commit is contained in:
Torsten Brendgen
2026-08-24 23:01:03 +02:00
parent 983a4af63c
commit 783d4f929c
11 changed files with 15 additions and 50 deletions
+3 -3
View File
@@ -8,8 +8,7 @@ SPFx-1.4.1-WebPart für SharePoint Server Subscription Edition. Es erstellt aus
- Startperson über einen SharePoint-People-Picker direkt im WebPart auswählen
- rekursives Laden aller darunterliegenden Ebenen über `DirectReports`
- konfigurierbare maximale Tiefe und Personenzahl
- standardmäßig vollständig aufgeklappter Baum
- einzelne Zweige können auf- und zugeklappt werden
- vollständig sichtbarer Baum ohne separate Auf-/Zuklapp-Schalter
- Profilbild, Anzeigename, Position, Abteilung und optional Kontaktdaten
- Teams-ähnliche Personenkarten mit Text links und Profilbild rechts
- aufklappbare Personendetails direkt in der Karte
@@ -17,7 +16,8 @@ SPFx-1.4.1-WebPart für SharePoint Server Subscription Edition. Es erstellt aus
- Verlinkung auf die SharePoint-Profilseite
- kurzzeitiger In-Memory-Profilcache zur Reduzierung wiederholter REST-Aufrufe
- responsive Baumdarstellung mit automatisch kompakteren Karten bei breiten Organisationsebenen
- durchgehende Verbindungslinien und SharePoint-Chevrons zum Auf- und Zuklappen
- gerade Verbindungslinien zwischen den Hierarchieebenen
- schattenlose Karten im Teams-Stil
## Datenquelle und Voraussetzungen
+1 -1
View File
@@ -4,7 +4,7 @@
"name": "organization-chart-webpart",
"title": "organization-chart-webpart",
"id": "b2f88ea8-f7da-4d0e-a945-6973bf1acd1e",
"version": "1.1.0.0",
"version": "1.1.1.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "organization-chart-webpart",
"version": "1.1.0",
"version": "1.1.1",
"private": true,
"main": "lib/index.js",
"engines": { "node": ">=6.9.0 <9.0.0" },
@@ -68,8 +68,6 @@ export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrg
cardAccentColor: this.properties.cardAccentColor || '',
roundedCards: this.properties.roundedCards === true,
reportsLabel: strings.ReportsLabel,
collapseLabel: strings.CollapseLabel,
expandLabel: strings.ExpandLabel,
expandDetailsLabel: strings.ExpandDetailsLabel,
collapseDetailsLabel: strings.CollapseDetailsLabel,
departmentLabel: strings.DepartmentLabel,
@@ -9,8 +9,6 @@ export interface IOrganizationChartProps {
cardAccentColor: string;
roundedCards: boolean;
reportsLabel: string;
collapseLabel: string;
expandLabel: string;
expandDetailsLabel: string;
collapseDetailsLabel: string;
departmentLabel: string;
@@ -22,14 +22,12 @@
.tree li:only-child { padding-top: 0; }
.tree > ul > li:only-child { padding-top: 0; }
.tree li:first-child::before, .tree li:last-child::after { border: 0; }
.tree li:last-child::before { border-right: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; border-radius: 0 4px 0 0; }
.tree li:first-child::after { border-radius: 4px 0 0 0; }
.tree li:last-child::before { border-right: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; }
.tree ul ul::before { border-left: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; content: ''; height: 22px; left: 50%; position: absolute; top: 0; }
.nodeWrap { display: inline-block; position: relative; }
.personCard {
background: "[theme:white, default:#ffffff]";
border: 1px solid "[theme:neutralLight, default:#edebe9]";
box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
color: "[theme:bodyText, default:#323130]";
display: block;
border-bottom: 2px solid "[theme:themePrimary, default:#0078d4]";
@@ -39,7 +37,7 @@
text-decoration: none;
width: 230px;
}
.personCard:hover { box-shadow: 0 2px 6px rgba(0, 0, 0, .18); color: "[theme:bodyText, default:#323130]"; }
.personCard:hover { color: "[theme:bodyText, default:#323130]"; }
.cardContent { align-items: center; background: transparent; border: 0; color: inherit; cursor: pointer; display: flex; font-family: inherit; min-height: 92px; padding: 12px; position: relative; text-align: left; width: 100%; }
.cardContent:hover { background: rgba(0, 0, 0, .03); }
.cardContent:focus { outline: 2px solid "[theme:focusBorder, default:#605e5c]"; outline-offset: -2px; }
@@ -56,9 +54,6 @@
.detailLabel { color: "[theme:neutralSecondary, default:#605e5c]"; display: block; font-size: 10px; line-height: 13px; }
.profileLink { color: "[theme:themePrimary, default:#0078d4]"; display: inline-block; font-size: 12px; margin-top: 3px; text-decoration: none; }
.profileLink:hover { text-decoration: underline; }
.toggle { align-items: center; background: "[theme:white, default:#ffffff]"; border: 1px solid "[theme:neutralTertiary, default:#a19f9d]"; border-radius: 50%; bottom: -14px; color: "[theme:themePrimary, default:#0078d4]"; cursor: pointer; display: flex; height: 28px; justify-content: center; left: calc(50% - 14px); padding: 0; position: absolute; width: 28px; z-index: 2; }
.toggle:hover, .toggle:focus { background: "[theme:neutralLighter, default:#f3f2f1]"; outline: none; }
.toggleIcon { font-family: 'FabricMDL2Icons'; font-size: 12px; line-height: 1; }
.status { color: "[theme:bodyText, default:#323130]"; padding: 16px; }
.error { color: "[theme:errorText, default:#a80000]"; padding: 16px; }
@@ -87,5 +82,4 @@
.tree li::before, .tree li::after, .tree ul ul::before { display: none; }
.tree > ul > li, .tree li:only-child { padding-top: 0; }
.personCard { max-width: 100%; width: 260px; }
.toggle { bottom: auto; left: -14px; top: 34px; }
}
@@ -4,7 +4,6 @@ import styles from './OrganizationChart.module.scss';
import { IOrganizationChartProps } from './IOrganizationChartProps';
export interface IOrganizationChartState {
collapsed: { [account: string]: boolean };
expandedDetails: { [account: string]: boolean };
cardWidth: number;
}
@@ -15,7 +14,7 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
public constructor(props: IOrganizationChartProps) {
super(props);
this.state = { collapsed: {}, expandedDetails: {}, cardWidth: 230 };
this.state = { expandedDetails: {}, cardWidth: 230 };
}
public componentDidMount(): void {
@@ -55,24 +54,14 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
private renderNode(node: IOrganizationNode, level: number): JSX.Element {
const profile: IUserProfile = node.profile;
const key: string = profile.accountName.toLowerCase();
const collapsed: boolean = this.state.collapsed[key] === true;
const hasChildren: boolean = node.children.length > 0;
return (
<li key={profile.accountName} role='treeitem' aria-level={level}
aria-selected={false} aria-expanded={hasChildren ? !collapsed : undefined}>
aria-selected={false}>
<div className={styles.nodeWrap}>
{this.renderPerson(profile)}
{hasChildren && <button type='button' className={styles.toggle}
title={collapsed ? this.props.expandLabel : this.props.collapseLabel}
aria-label={(collapsed ? this.props.expandLabel : this.props.collapseLabel) + ': ' + profile.displayName}
aria-expanded={!collapsed} onClick={(): void => this.toggle(key)}>
{collapsed
? <span className={styles.toggleIcon} aria-hidden='true'>&#xE70D;</span>
: <span className={styles.toggleIcon} aria-hidden='true'>&#xE70E;</span>}
</button>}
</div>
{hasChildren && !collapsed &&
{hasChildren &&
<ul role='group'>
{node.children.map((child: IOrganizationNode): JSX.Element => this.renderNode(child, level + 1))}
</ul>}
@@ -208,23 +197,11 @@ export default class OrganizationChart extends React.Component<IOrganizationChar
private countVisibleNodes(node: IOrganizationNode, level: number, countsByLevel: number[]): void {
countsByLevel[level] = (countsByLevel[level] || 0) + 1;
const key: string = node.profile.accountName.toLowerCase();
if (this.state.collapsed[key] === true) { return; }
for (let i: number = 0; i < node.children.length; i++) {
this.countVisibleNodes(node.children[i], level + 1, countsByLevel);
}
}
private toggle(key: string): void {
const collapsed: { [account: string]: boolean } = {};
const existingKeys: string[] = Object.keys(this.state.collapsed);
for (let i: number = 0; i < existingKeys.length; i++) {
collapsed[existingKeys[i]] = this.state.collapsed[existingKeys[i]];
}
collapsed[key] = !collapsed[key];
this.setState({ collapsed: collapsed });
}
private initials(displayName: string): string {
const parts: string[] = String(displayName || '?').trim().split(/\s+/);
return (parts[0].charAt(0) + (parts.length > 1 ? parts[parts.length - 1].charAt(0) : '')).toUpperCase();
@@ -26,8 +26,6 @@ define([], function() {
LoadErrorMessage: 'Das Organigramm konnte nicht aus dem User Profile Service geladen werden.',
DefaultTitle: 'Organigramm',
ReportsLabel: 'Mitarbeiter',
CollapseLabel: 'Untergeordnete Personen zuklappen',
ExpandLabel: 'Untergeordnete Personen aufklappen',
ExpandDetailsLabel: 'Personendetails anzeigen',
CollapseDetailsLabel: 'Personendetails ausblenden',
DepartmentLabel: 'Abteilung',
@@ -26,8 +26,6 @@ define([], function() {
LoadErrorMessage: 'The organization chart could not be loaded from the User Profile Service.',
DefaultTitle: 'Organization Chart',
ReportsLabel: 'reports',
CollapseLabel: 'Collapse reports',
ExpandLabel: 'Expand reports',
ExpandDetailsLabel: 'Show person details',
CollapseDetailsLabel: 'Hide person details',
DepartmentLabel: 'Department',
-2
View File
@@ -25,8 +25,6 @@ declare interface IOrganizationChartWebPartStrings {
LoadErrorMessage: string;
DefaultTitle: string;
ReportsLabel: string;
CollapseLabel: string;
ExpandLabel: string;
ExpandDetailsLabel: string;
CollapseDetailsLabel: string;
DepartmentLabel: string;
+5 -1
View File
@@ -44,7 +44,11 @@ assert(component.indexOf("role='tree'") >= 0 && component.indexOf("role='treeite
assert(component.indexOf('aria-expanded') >= 0 && component.indexOf('aria-selected={false}') >= 0,
'Accessible tree state is missing.');
assert(component.indexOf('peopleCountLabel') < 0 && component.indexOf('&#xE70D;') >= 0 &&
component.indexOf('&#xE70E;') >= 0, 'Header count removal or modern toggle icons are missing.');
component.indexOf('&#xE70E;') >= 0, 'Header count removal or detail chevrons are missing.');
assert(component.indexOf('styles.toggle') < 0 && styles.indexOf('.toggle {') < 0 &&
component.indexOf('collapsed') < 0, 'Branch toggle circles must not be rendered.');
assert(styles.indexOf('box-shadow') < 0 && styles.indexOf('border-radius: 0 4px') < 0,
'Cards must be shadowless and connectors must use straight corners.');
assert(styles.indexOf('.tree li:only-child { padding-top: 0; }') >= 0,
'Single-child connector spacing must remain continuous.');
assert(component.indexOf('getMaximumVisibleBreadth') >= 0 && component.indexOf('getBreadthCardWidth') >= 0 &&