issue 19582 (#19749)
* fixed paginate subgroups + nav * render matched sub groups and fix pagination when searching
This commit is contained in:
parent
0ddc71d987
commit
3b65f1163d
1 changed files with 168 additions and 121 deletions
|
@ -61,6 +61,8 @@ export const GroupPickerDialog = ({
|
||||||
const [max, setMax] = useState(10);
|
const [max, setMax] = useState(10);
|
||||||
const [first, setFirst] = useState(0);
|
const [first, setFirst] = useState(0);
|
||||||
|
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
const currentGroup = () => navigation[navigation.length - 1];
|
const currentGroup = () => navigation[navigation.length - 1];
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
|
@ -68,13 +70,14 @@ export const GroupPickerDialog = ({
|
||||||
let group;
|
let group;
|
||||||
let groups;
|
let groups;
|
||||||
let existingUserGroups;
|
let existingUserGroups;
|
||||||
|
let count = 0;
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
groups = await adminClient.groups.find({
|
groups = await adminClient.groups.find({
|
||||||
first,
|
first,
|
||||||
max: max + 1,
|
max: max + (isSearching ? 0 : 1),
|
||||||
search: isSearching ? filter : "",
|
search: isSearching ? filter : "",
|
||||||
});
|
});
|
||||||
} else {
|
} else if (!navigation.map(({ id }) => id).includes(groupId)) {
|
||||||
group = await adminClient.groups.findOne({ id: groupId });
|
group = await adminClient.groups.findOne({ id: groupId });
|
||||||
if (!group) {
|
if (!group) {
|
||||||
throw new Error(t("common:notFound"));
|
throw new Error(t("common:notFound"));
|
||||||
|
@ -82,24 +85,32 @@ export const GroupPickerDialog = ({
|
||||||
groups = group.subGroups!;
|
groups = group.subGroups!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSearching) {
|
||||||
|
count = (await adminClient.groups.count({ search: filter, top: true }))
|
||||||
|
.count;
|
||||||
|
}
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
existingUserGroups = await adminClient.users.listGroups({
|
existingUserGroups = await adminClient.users.listGroups({
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { group, groups, existingUserGroups };
|
return { group, groups, existingUserGroups, count };
|
||||||
},
|
},
|
||||||
async ({ group: selectedGroup, groups, existingUserGroups }) => {
|
async ({ group: selectedGroup, groups, existingUserGroups, count }) => {
|
||||||
setJoinedGroups(existingUserGroups || []);
|
setJoinedGroups(existingUserGroups || []);
|
||||||
if (selectedGroup) {
|
if (selectedGroup) {
|
||||||
setNavigation([...navigation, selectedGroup]);
|
setNavigation([...navigation, selectedGroup]);
|
||||||
}
|
}
|
||||||
|
|
||||||
groups.forEach((group: SelectableGroup) => {
|
if (groups) {
|
||||||
group.checked = !!selectedRows.find((r) => r.id === group.id);
|
groups.forEach((group: SelectableGroup) => {
|
||||||
});
|
group.checked = !!selectedRows.find((r) => r.id === group.id);
|
||||||
setGroups(groups);
|
});
|
||||||
|
setGroups(groups);
|
||||||
|
}
|
||||||
|
setCount(count);
|
||||||
},
|
},
|
||||||
[groupId, filter, first, max]
|
[groupId, filter, first, max]
|
||||||
);
|
);
|
||||||
|
@ -111,25 +122,6 @@ export const GroupPickerDialog = ({
|
||||||
].some((group) => group === row?.id);
|
].some((group) => group === row?.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSubgroups = (group: GroupRepresentation) =>
|
|
||||||
group.subGroups?.length !== 0;
|
|
||||||
|
|
||||||
const expandGroup = (
|
|
||||||
group: GroupRepresentation,
|
|
||||||
name: string
|
|
||||||
): GroupRepresentation => {
|
|
||||||
if (group.name?.includes(name)) {
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
if (group.subGroups) {
|
|
||||||
for (const g of group.subGroups) {
|
|
||||||
const found = expandGroup(g, name);
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
variant={isSearching ? ModalVariant.medium : ModalVariant.small}
|
variant={isSearching ? ModalVariant.medium : ModalVariant.small}
|
||||||
|
@ -161,7 +153,10 @@ export const GroupPickerDialog = ({
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<PaginatingTableToolbar
|
<PaginatingTableToolbar
|
||||||
count={groups.length}
|
count={
|
||||||
|
(isSearching ? count : groups.length) -
|
||||||
|
(groupId || isSearching ? first : 0)
|
||||||
|
}
|
||||||
first={first}
|
first={first}
|
||||||
max={max}
|
max={max}
|
||||||
onNextClick={setFirst}
|
onNextClick={setFirst}
|
||||||
|
@ -189,6 +184,8 @@ export const GroupPickerDialog = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setGroupId(undefined);
|
setGroupId(undefined);
|
||||||
setNavigation([]);
|
setNavigation([]);
|
||||||
|
setFirst(0);
|
||||||
|
setMax(10);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("groups")}
|
{t("groups")}
|
||||||
|
@ -203,6 +200,8 @@ export const GroupPickerDialog = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setGroupId(group.id);
|
setGroupId(group.id);
|
||||||
setNavigation([...navigation].slice(0, i));
|
setNavigation([...navigation].slice(0, i));
|
||||||
|
setFirst(0);
|
||||||
|
setMax(10);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{group.name}
|
{group.name}
|
||||||
|
@ -213,100 +212,40 @@ export const GroupPickerDialog = ({
|
||||||
))}
|
))}
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<DataList aria-label={t("groups")} isCompact>
|
<DataList aria-label={t("groups")} isCompact>
|
||||||
{groups.slice(0, max).map((group: SelectableGroup) => (
|
{groups
|
||||||
<DataListItem
|
.slice(groupId ? first : 0, max + (groupId ? first : 0))
|
||||||
className={`join-group-dialog-row-${
|
.map((group: SelectableGroup) => (
|
||||||
isRowDisabled(group) ? "disabled" : ""
|
<>
|
||||||
}`}
|
<GroupRow
|
||||||
aria-labelledby={group.name}
|
key={group.id}
|
||||||
key={group.id}
|
group={group}
|
||||||
id={group.id}
|
isRowDisabled={isRowDisabled}
|
||||||
onClick={(e) => {
|
onSelect={setGroupId}
|
||||||
const g = isSearching ? expandGroup(group, filter) : group;
|
type={type}
|
||||||
if (isRowDisabled(g)) return;
|
isSearching={isSearching}
|
||||||
if (type === "selectOne") {
|
setIsSearching={setIsSearching}
|
||||||
setGroupId(g.id);
|
selectedRows={selectedRows}
|
||||||
} else if (
|
setSelectedRows={setSelectedRows}
|
||||||
hasSubgroups(group) &&
|
canBrowse={canBrowse}
|
||||||
(e.target as HTMLInputElement).type !== "checkbox"
|
|
||||||
) {
|
|
||||||
setGroupId(
|
|
||||||
isSearching ? expandGroup(group, filter).id : group.id
|
|
||||||
);
|
|
||||||
setIsSearching(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DataListItemRow
|
|
||||||
className={`join-group-dialog-row-${
|
|
||||||
isRowDisabled(group) ? "m-disabled" : ""
|
|
||||||
}`}
|
|
||||||
data-testid={group.name}
|
|
||||||
>
|
|
||||||
{type === "selectMany" && (
|
|
||||||
<DataListCheck
|
|
||||||
className="kc-join-group-modal-check"
|
|
||||||
data-testid={`${group.name}-check`}
|
|
||||||
aria-label={group.name}
|
|
||||||
checked={group.checked}
|
|
||||||
isDisabled={isRowDisabled(group)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
group.checked = checked;
|
|
||||||
let newSelectedRows: SelectableGroup[] = [];
|
|
||||||
if (!group.checked) {
|
|
||||||
newSelectedRows = selectedRows.filter(
|
|
||||||
(r) => r.id !== group.id
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
newSelectedRows = [
|
|
||||||
...selectedRows,
|
|
||||||
isSearching ? expandGroup(group, filter) : group,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedRows(newSelectedRows);
|
|
||||||
}}
|
|
||||||
aria-labelledby={`select-${group.name}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DataListItemCells
|
|
||||||
dataListCells={[
|
|
||||||
<DataListCell
|
|
||||||
key={`name-${group.id}`}
|
|
||||||
className="keycloak-groups-group-path"
|
|
||||||
>
|
|
||||||
{isSearching ? (
|
|
||||||
<GroupPath
|
|
||||||
id={`select-${group.name}`}
|
|
||||||
group={expandGroup(group, filter)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span id={`select-${group.name}`}>{group.name}</span>
|
|
||||||
)}
|
|
||||||
</DataListCell>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
<DataListAction
|
{isSearching &&
|
||||||
id="actions"
|
group.subGroups?.length !== 0 &&
|
||||||
aria-labelledby={`select-${group.name}`}
|
group.subGroups!.map((g) => (
|
||||||
aria-label={t("groupName")}
|
<GroupRow
|
||||||
isPlainButtonAction
|
key={g.id}
|
||||||
>
|
group={g}
|
||||||
{((hasSubgroups(group) && canBrowse) ||
|
isRowDisabled={isRowDisabled}
|
||||||
type === "selectOne") && (
|
onSelect={setGroupId}
|
||||||
<Button
|
type={type}
|
||||||
isDisabled
|
isSearching={isSearching}
|
||||||
variant="link"
|
setIsSearching={setIsSearching}
|
||||||
aria-label={t("common:select")}
|
selectedRows={selectedRows}
|
||||||
>
|
setSelectedRows={setSelectedRows}
|
||||||
<AngleRightIcon />
|
canBrowse={canBrowse}
|
||||||
</Button>
|
/>
|
||||||
)}
|
))}
|
||||||
</DataListAction>
|
</>
|
||||||
</DataListItemRow>
|
))}
|
||||||
</DataListItem>
|
|
||||||
))}
|
|
||||||
</DataList>
|
</DataList>
|
||||||
{groups.length === 0 && !isSearching && (
|
{groups.length === 0 && !isSearching && (
|
||||||
<ListEmptyState
|
<ListEmptyState
|
||||||
|
@ -325,3 +264,111 @@ export const GroupPickerDialog = ({
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GroupRowProps = {
|
||||||
|
group: SelectableGroup;
|
||||||
|
type: "selectOne" | "selectMany";
|
||||||
|
isRowDisabled: (row?: GroupRepresentation) => boolean;
|
||||||
|
isSearching: boolean;
|
||||||
|
setIsSearching: (value: boolean) => void;
|
||||||
|
onSelect: (groupId: string) => void;
|
||||||
|
selectedRows: SelectableGroup[];
|
||||||
|
setSelectedRows: (groups: SelectableGroup[]) => void;
|
||||||
|
canBrowse: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GroupRow = ({
|
||||||
|
group,
|
||||||
|
type,
|
||||||
|
isRowDisabled,
|
||||||
|
isSearching,
|
||||||
|
setIsSearching,
|
||||||
|
onSelect,
|
||||||
|
selectedRows,
|
||||||
|
setSelectedRows,
|
||||||
|
canBrowse,
|
||||||
|
}: GroupRowProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const hasSubgroups = (group: GroupRepresentation) =>
|
||||||
|
group.subGroups?.length !== 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataListItem
|
||||||
|
className={`join-group-dialog-row-${
|
||||||
|
isRowDisabled(group) ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
aria-labelledby={group.name}
|
||||||
|
key={group.id}
|
||||||
|
id={group.id}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (isRowDisabled(group)) return;
|
||||||
|
if (type === "selectOne") {
|
||||||
|
onSelect(group.id!);
|
||||||
|
} else if (
|
||||||
|
hasSubgroups(group) &&
|
||||||
|
(e.target as HTMLInputElement).type !== "checkbox"
|
||||||
|
) {
|
||||||
|
onSelect(group.id!);
|
||||||
|
setIsSearching(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataListItemRow
|
||||||
|
className={`join-group-dialog-row-${
|
||||||
|
isRowDisabled(group) ? "m-disabled" : ""
|
||||||
|
}`}
|
||||||
|
data-testid={group.name}
|
||||||
|
>
|
||||||
|
{type === "selectMany" && (
|
||||||
|
<DataListCheck
|
||||||
|
className="kc-join-group-modal-check"
|
||||||
|
data-testid={`${group.name}-check`}
|
||||||
|
aria-label={group.name}
|
||||||
|
checked={group.checked}
|
||||||
|
isDisabled={isRowDisabled(group)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
group.checked = checked;
|
||||||
|
let newSelectedRows: SelectableGroup[] = [];
|
||||||
|
if (!group.checked) {
|
||||||
|
newSelectedRows = selectedRows.filter((r) => r.id !== group.id);
|
||||||
|
} else {
|
||||||
|
newSelectedRows = [...selectedRows, group];
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
}}
|
||||||
|
aria-labelledby={`select-${group.name}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell
|
||||||
|
key={`name-${group.id}`}
|
||||||
|
className="keycloak-groups-group-path"
|
||||||
|
>
|
||||||
|
{isSearching ? (
|
||||||
|
<GroupPath id={`select-${group.name}`} group={group} />
|
||||||
|
) : (
|
||||||
|
<span id={`select-${group.name}`}>{group.name}</span>
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<DataListAction
|
||||||
|
id="actions"
|
||||||
|
aria-labelledby={`select-${group.name}`}
|
||||||
|
aria-label={t("groupName")}
|
||||||
|
isPlainButtonAction
|
||||||
|
>
|
||||||
|
{((hasSubgroups(group) && canBrowse) || type === "selectOne") && (
|
||||||
|
<Button isDisabled variant="link" aria-label={t("common:select")}>
|
||||||
|
<AngleRightIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DataListAction>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue