Skip to content

Commit f5f5cda

Browse files
committed
Add support for individual admins
1 parent 96cb97d commit f5f5cda

2 files changed

Lines changed: 116 additions & 18 deletions

File tree

src/components/EditableConfigList.tsx

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,28 @@ export abstract class GenericEditableConfigList<
230230
);
231231
}
232232

233+
protected getAssociatedItems(item: any): Array<any> | undefined {
234+
return item?.libraries;
235+
}
236+
237+
protected formatAssociatedCount(count: number): string {
238+
return count === 0
239+
? "no libraries"
240+
: count === 1
241+
? "1 library"
242+
: `${count} libraries`;
243+
}
244+
245+
protected renderAssociatedSection(item: any): JSX.Element {
246+
return this.renderAssociatedLibraries(item);
247+
}
248+
233249
renderLi(item, index): JSX.Element {
234250
const AdditionalContent = this.AdditionalContent || null;
235-
const libraries: Array<{ short_name: string }> | undefined =
236-
item?.libraries;
237-
const libraryCount = Array.isArray(libraries) ? libraries.length : null;
251+
const associatedItems = this.getAssociatedItems(item);
252+
const libraryCount = Array.isArray(associatedItems)
253+
? associatedItems.length
254+
: null;
238255
const itemKey = String(item[this.identifierKey]);
239256
const isExpanded = libraryCount > 0 && !!this.state.expandedItems[itemKey];
240257

@@ -261,13 +278,7 @@ export abstract class GenericEditableConfigList<
261278
{libraryCount !== null && (
262279
<span className="library-count">
263280
{" "}
264-
(
265-
{libraryCount === 0
266-
? "no libraries"
267-
: libraryCount === 1
268-
? "1 library"
269-
: `${libraryCount} libraries`}
270-
)
281+
({this.formatAssociatedCount(libraryCount)})
271282
</span>
272283
)}
273284
</span>
@@ -301,7 +312,7 @@ export abstract class GenericEditableConfigList<
301312
/>
302313
)}
303314
</div>
304-
{isExpanded && this.renderAssociatedLibraries(item)}
315+
{isExpanded && this.renderAssociatedSection(item)}
305316
{AdditionalContent && (
306317
<AdditionalContent
307318
type={this.itemTypeName}
@@ -325,18 +336,19 @@ export abstract class GenericEditableConfigList<
325336

326337
toggleAllLibraries(): void {
327338
const items: any[] = (this.props.data as any)?.[this.listDataKey] || [];
328-
const itemsWithLibraries = items.filter(
329-
(item) => Array.isArray(item.libraries) && item.libraries.length > 0
330-
);
331-
if (itemsWithLibraries.length === 0) return;
339+
const itemsWithAssociations = items.filter((item) => {
340+
const associated = this.getAssociatedItems(item);
341+
return Array.isArray(associated) && associated.length > 0;
342+
});
343+
if (itemsWithAssociations.length === 0) return;
332344

333-
const anyCollapsed = itemsWithLibraries.some(
345+
const anyCollapsed = itemsWithAssociations.some(
334346
(item) => !this.state.expandedItems[String(item[this.identifierKey])]
335347
);
336348

337349
const newExpandedItems: Record<string, boolean> = {};
338350
if (anyCollapsed) {
339-
for (const item of itemsWithLibraries) {
351+
for (const item of itemsWithAssociations) {
340352
newExpandedItems[String(item[this.identifierKey])] = true;
341353
}
342354
}

src/components/IndividualAdmins.tsx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import EditableConfigList, {
77
} from "./EditableConfigList";
88
import { connect } from "react-redux";
99
import ActionCreator from "../actions";
10-
import { IndividualAdminsData, IndividualAdminData } from "../interfaces";
10+
import {
11+
IndividualAdminsData,
12+
IndividualAdminData,
13+
AdminRoleData,
14+
} from "../interfaces";
1115
import Admin from "../models/Admin";
1216
import IndividualAdminEditForm from "./IndividualAdminEditForm";
1317

@@ -30,6 +34,88 @@ export class IndividualAdmins extends EditableConfigList<
3034
admin: PropTypes.object.isRequired,
3135
};
3236

37+
private getRolesSummary(
38+
item: IndividualAdminData
39+
): Array<{ label: string; suffix?: string; href?: string }> {
40+
const roles: AdminRoleData[] = item.roles || [];
41+
42+
if (roles.some((r) => r.role === "system")) {
43+
return [{ label: "sysadmin" }];
44+
}
45+
46+
const allLibraries: Array<{
47+
short_name: string;
48+
name?: string;
49+
uuid?: string;
50+
}> = (this.props.data as any)?.allLibraries || [];
51+
52+
const getLibraryName = (shortName: string) =>
53+
allLibraries.find((l) => l.short_name === shortName)?.name || shortName;
54+
55+
const getLibraryHref = (shortName: string) => {
56+
const uuid = allLibraries.find((l) => l.short_name === shortName)?.uuid;
57+
return uuid ? `/admin/web/config/libraries/edit/${uuid}` : undefined;
58+
};
59+
60+
const result: Array<{ label: string; suffix?: string; href?: string }> = [];
61+
62+
if (roles.some((r) => r.role === "manager-all")) {
63+
result.push({ label: "All libraries", suffix: " - Manager" });
64+
} else if (roles.some((r) => r.role === "librarian-all")) {
65+
result.push({ label: "All libraries", suffix: " - Librarian" });
66+
}
67+
68+
const libraryHighestRole: Record<string, "manager" | "librarian"> = {};
69+
for (const r of roles) {
70+
if (r.library) {
71+
if (r.role === "manager") {
72+
libraryHighestRole[r.library] = "manager";
73+
} else if (
74+
r.role === "librarian" &&
75+
libraryHighestRole[r.library] !== "manager"
76+
) {
77+
libraryHighestRole[r.library] = "librarian";
78+
}
79+
}
80+
}
81+
82+
for (const [shortName, role] of Object.entries(libraryHighestRole)) {
83+
result.push({
84+
label: getLibraryName(shortName),
85+
suffix: role === "manager" ? " - Manager" : " - Librarian",
86+
href: getLibraryHref(shortName),
87+
});
88+
}
89+
90+
result.sort((a, b) => a.label.localeCompare(b.label));
91+
return result;
92+
}
93+
94+
protected getAssociatedItems(
95+
item: IndividualAdminData
96+
): Array<any> | undefined {
97+
if (!item.roles) return undefined;
98+
return this.getRolesSummary(item);
99+
}
100+
101+
protected formatAssociatedCount(count: number): string {
102+
return count === 0 ? "no roles" : count === 1 ? "1 role" : `${count} roles`;
103+
}
104+
105+
protected renderAssociatedSection(item: IndividualAdminData): JSX.Element {
106+
const summary = this.getRolesSummary(item);
107+
return (
108+
<ul className="associated-libraries">
109+
{summary.map((entry, i) => (
110+
<li key={i}>
111+
{entry.href ? <a href={entry.href}>{entry.label}</a> : entry.label}
112+
{entry.suffix}
113+
</li>
114+
))}
115+
</ul>
116+
);
117+
}
118+
33119
canCreate() {
34120
return (
35121
this.context.admin && this.context.admin.isLibraryManagerOfSomeLibrary()

0 commit comments

Comments
 (0)