Changes from Realm Roles UX Review [List] (#433)
* realm roles UX review progress wip * filter realm roles on Enter key press, add filter functionality * remove chip group filters * clean up * format * filterChips logic now in table toolbar * fix lint and format * save with erik * remove filter chips functionality * fix check-types * fix realm roles cypress test * format * revert changes to group attributes * cypress test * use filter * remove log * remove unused prop
This commit is contained in:
parent
6c399c1484
commit
bf4cae6735
16 changed files with 294 additions and 70 deletions
|
@ -73,8 +73,6 @@ describe("Realm roles test", function () {
|
|||
|
||||
masthead.checkNotificationMessage("Role created");
|
||||
|
||||
cy.wait(100);
|
||||
|
||||
// Add associated realm role
|
||||
|
||||
associatedRolesPage.addAssociatedRealmRole();
|
||||
|
|
|
@ -29,7 +29,7 @@ export default class AssociatedRolesPage {
|
|||
|
||||
cy.wait(100);
|
||||
|
||||
cy.get(this.checkbox).eq(1).check();
|
||||
cy.get(this.checkbox).eq(2).check();
|
||||
|
||||
cy.get(this.addAssociatedRolesModalButton).contains("Add").click();
|
||||
|
||||
|
@ -53,7 +53,7 @@ export default class AssociatedRolesPage {
|
|||
|
||||
cy.wait(2500);
|
||||
|
||||
cy.get(this.checkbox).eq(40).check({ force: true });
|
||||
cy.get(this.checkbox).eq(12).check({ force: true });
|
||||
|
||||
cy.get(this.addAssociatedRolesModalButton).contains("Add").click();
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export default class CreateRealmRolePage {
|
|||
this.realmRoleNameError = "#kc-name-helper";
|
||||
this.realmRoleDescriptionInput = "#kc-role-description";
|
||||
|
||||
this.saveBtn = '[type="submit"]';
|
||||
this.saveBtn = 'realm-roles-save-button';
|
||||
this.cancelBtn = '[type="button"]';
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ export default class CreateRealmRolePage {
|
|||
//#endregion
|
||||
|
||||
save() {
|
||||
cy.get(this.saveBtn).click();
|
||||
cy.getId(this.saveBtn).click();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -58,9 +58,9 @@ export const attributesToArray = (attributes?: {
|
|||
|
||||
export const AttributesForm = ({
|
||||
form: { handleSubmit, register, formState, errors, watch },
|
||||
save,
|
||||
array: { fields, append, remove },
|
||||
reset,
|
||||
save,
|
||||
}: AttributesFormProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
|
||||
|
@ -96,7 +96,7 @@ export const AttributesForm = ({
|
|||
>
|
||||
<TextInput
|
||||
name={`attributes[${rowIndex}].key`}
|
||||
ref={register({ required: true })}
|
||||
ref={register()}
|
||||
aria-label="key-input"
|
||||
defaultValue={attribute.key}
|
||||
validated={
|
||||
|
|
|
@ -139,7 +139,7 @@ export function KeycloakDataTable<T>({
|
|||
|
||||
const [max, setMax] = useState(10);
|
||||
const [first, setFirst] = useState(0);
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
@ -172,7 +172,7 @@ export function KeycloakDataTable<T>({
|
|||
},
|
||||
handleError
|
||||
);
|
||||
}, [key, first, max]);
|
||||
}, [key, first, max, search]);
|
||||
|
||||
const getNodeText = (node: keyof T | JSX.Element): string => {
|
||||
if (["string", "number"].includes(typeof node)) {
|
||||
|
@ -197,6 +197,7 @@ export function KeycloakDataTable<T>({
|
|||
)
|
||||
)
|
||||
);
|
||||
setSearch;
|
||||
};
|
||||
|
||||
const convertAction = () =>
|
||||
|
@ -214,13 +215,6 @@ export function KeycloakDataTable<T>({
|
|||
return action;
|
||||
});
|
||||
|
||||
const searchOnChange = (value: string) => {
|
||||
if (value === "") {
|
||||
refresh();
|
||||
}
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
const Loading = () => (
|
||||
<div className="pf-u-text-align-center">
|
||||
<Spinner />
|
||||
|
@ -258,8 +252,7 @@ export function KeycloakDataTable<T>({
|
|||
inputGroupName={
|
||||
searchPlaceholderKey ? `${ariaLabelKey}input` : undefined
|
||||
}
|
||||
inputGroupOnChange={searchOnChange}
|
||||
inputGroupOnClick={refresh}
|
||||
inputGroupOnEnter={setSearch}
|
||||
inputGroupPlaceholder={t(searchPlaceholderKey || "")}
|
||||
searchTypeComponent={searchTypeComponent}
|
||||
toolbarItem={toolbarItem}
|
||||
|
@ -291,8 +284,7 @@ export function KeycloakDataTable<T>({
|
|||
inputGroupName={
|
||||
searchPlaceholderKey ? `${ariaLabelKey}input` : undefined
|
||||
}
|
||||
inputGroupOnChange={searchOnChange}
|
||||
inputGroupOnClick={() => filter(search)}
|
||||
inputGroupOnEnter={(search) => filter(search)}
|
||||
inputGroupPlaceholder={t(searchPlaceholderKey || "")}
|
||||
toolbarItem={toolbarItem}
|
||||
searchTypeComponent={searchTypeComponent}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { MouseEventHandler } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
Pagination,
|
||||
ToggleTemplateProps,
|
||||
|
@ -22,7 +22,7 @@ type TableToolbarProps = {
|
|||
newInput: string,
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) => void;
|
||||
inputGroupOnClick?: MouseEventHandler;
|
||||
inputGroupOnEnter?: (value: string) => void;
|
||||
};
|
||||
|
||||
export const PaginatingTableToolbar = ({
|
||||
|
@ -38,7 +38,7 @@ export const PaginatingTableToolbar = ({
|
|||
inputGroupName,
|
||||
inputGroupPlaceholder,
|
||||
inputGroupOnChange,
|
||||
inputGroupOnClick,
|
||||
inputGroupOnEnter,
|
||||
}: TableToolbarProps) => {
|
||||
const page = Math.round(first / max);
|
||||
const pagination = (variant: "top" | "bottom" = "top") => (
|
||||
|
@ -59,24 +59,23 @@ export const PaginatingTableToolbar = ({
|
|||
/>
|
||||
);
|
||||
|
||||
if (count === 0) {
|
||||
<>{children}</>;
|
||||
}
|
||||
return (
|
||||
<TableToolbar
|
||||
searchTypeComponent={searchTypeComponent}
|
||||
toolbarItem={
|
||||
<>
|
||||
{toolbarItem}
|
||||
{count !== 0 && (
|
||||
<ToolbarItem variant="pagination">{pagination()}</ToolbarItem>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
toolbarItemFooter={
|
||||
count !== 0 && <ToolbarItem>{pagination("bottom")}</ToolbarItem>
|
||||
}
|
||||
toolbarItemFooter={<ToolbarItem>{pagination("bottom")}</ToolbarItem>}
|
||||
inputGroupName={inputGroupName}
|
||||
inputGroupPlaceholder={inputGroupPlaceholder}
|
||||
inputGroupOnChange={inputGroupOnChange}
|
||||
inputGroupOnClick={inputGroupOnClick}
|
||||
inputGroupOnEnter={inputGroupOnEnter}
|
||||
>
|
||||
{children}
|
||||
</TableToolbar>
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import React, {
|
||||
FormEvent,
|
||||
Fragment,
|
||||
MouseEventHandler,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import React, { FormEvent, Fragment, ReactNode } from "react";
|
||||
import {
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
|
@ -28,7 +23,7 @@ type TableToolbarProps = {
|
|||
newInput: string,
|
||||
event: FormEvent<HTMLInputElement>
|
||||
) => void;
|
||||
inputGroupOnClick?: MouseEventHandler;
|
||||
inputGroupOnEnter?: (value: string) => void;
|
||||
};
|
||||
|
||||
export const TableToolbar = ({
|
||||
|
@ -39,9 +34,35 @@ export const TableToolbar = ({
|
|||
inputGroupName,
|
||||
inputGroupPlaceholder,
|
||||
inputGroupOnChange,
|
||||
inputGroupOnClick,
|
||||
inputGroupOnEnter,
|
||||
}: TableToolbarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchValue, setSearchValue] = React.useState<string>("");
|
||||
|
||||
const onSearch = () => {
|
||||
if (searchValue !== "") {
|
||||
setSearchValue(searchValue);
|
||||
inputGroupOnEnter && inputGroupOnEnter(searchValue);
|
||||
} else {
|
||||
setSearchValue("");
|
||||
inputGroupOnEnter && inputGroupOnEnter("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
onSearch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (
|
||||
value: string,
|
||||
event: FormEvent<HTMLInputElement>
|
||||
) => {
|
||||
inputGroupOnChange && inputGroupOnChange(value, event);
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar>
|
||||
|
@ -59,12 +80,13 @@ export const TableToolbar = ({
|
|||
type="search"
|
||||
aria-label={t("search")}
|
||||
placeholder={inputGroupPlaceholder}
|
||||
onChange={inputGroupOnChange}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Button
|
||||
variant={ButtonVariant.control}
|
||||
aria-label={t("search")}
|
||||
onClick={inputGroupOnClick}
|
||||
onClick={onSearch}
|
||||
>
|
||||
<SearchIcon />
|
||||
</Button>
|
||||
|
|
|
@ -123,8 +123,6 @@ export const AssociatedRolesTab = ({
|
|||
);
|
||||
};
|
||||
|
||||
console.log(inheritanceMap);
|
||||
|
||||
const toggleModal = () => setOpen(!open);
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
|
@ -173,7 +171,7 @@ export const AssociatedRolesTab = ({
|
|||
const goToCreate = () => history.push(`${url}/add-role`);
|
||||
return (
|
||||
<>
|
||||
<PageSection variant="light">
|
||||
<PageSection variant="light" padding={{ default: "noPadding" }}>
|
||||
<DeleteConfirm />
|
||||
<DeleteAssociatedRolesConfirm />
|
||||
<AssociatedRolesModal
|
||||
|
|
|
@ -9,28 +9,28 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UseFormMethods } from "react-hook-form";
|
||||
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { RoleFormType } from "./RealmRoleTabs";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
|
||||
export type RealmRoleFormProps = {
|
||||
form: UseFormMethods<RoleFormType>;
|
||||
save: (role: RoleFormType) => void;
|
||||
save: () => void;
|
||||
editMode: boolean;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const RealmRoleForm = ({
|
||||
form,
|
||||
form: { handleSubmit, errors, register },
|
||||
save,
|
||||
editMode,
|
||||
reset,
|
||||
}: RealmRoleFormProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
|
||||
return (
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
onSubmit={form.handleSubmit(save)}
|
||||
onSubmit={handleSubmit(save)}
|
||||
role="manage-realm"
|
||||
className="pf-u-mt-lg"
|
||||
>
|
||||
|
@ -38,11 +38,11 @@ export const RealmRoleForm = ({
|
|||
label={t("roleName")}
|
||||
fieldId="kc-name"
|
||||
isRequired
|
||||
validated={form.errors.name ? "error" : "default"}
|
||||
validated={errors.name ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={form.register({ required: !editMode })}
|
||||
ref={register({ required: !editMode })}
|
||||
type="text"
|
||||
id="kc-name"
|
||||
name="name"
|
||||
|
@ -53,15 +53,13 @@ export const RealmRoleForm = ({
|
|||
label={t("common:description")}
|
||||
fieldId="kc-description"
|
||||
validated={
|
||||
form.errors.description
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
errors.description ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={form.errors.description?.message}
|
||||
helperTextInvalid={errors.description?.message}
|
||||
>
|
||||
<TextArea
|
||||
name="description"
|
||||
ref={form.register({
|
||||
ref={register({
|
||||
maxLength: {
|
||||
value: 255,
|
||||
message: t("common:maxLength", { length: 255 }),
|
||||
|
@ -69,7 +67,7 @@ export const RealmRoleForm = ({
|
|||
})}
|
||||
type="text"
|
||||
validated={
|
||||
form.errors.description
|
||||
errors.description
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
|
@ -77,7 +75,11 @@ export const RealmRoleForm = ({
|
|||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button variant="primary" type="submit">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={save}
|
||||
data-testid="realm-roles-save-button"
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button onClick={() => reset()} variant="link">
|
||||
|
|
|
@ -93,8 +93,21 @@ export const RealmRoleTabs = () => {
|
|||
|
||||
useEffect(() => append({ key: "", value: "" }), [append, role]);
|
||||
|
||||
const save = async (role: RoleFormType) => {
|
||||
const save = async () => {
|
||||
try {
|
||||
const role = form.getValues();
|
||||
if (
|
||||
role.attributes &&
|
||||
role.attributes[role.attributes.length - 1].key === ""
|
||||
) {
|
||||
form.setValue(
|
||||
"attributes",
|
||||
role.attributes.slice(0, role.attributes.length - 1)
|
||||
);
|
||||
}
|
||||
if (!(await form.trigger())) {
|
||||
return;
|
||||
}
|
||||
const { attributes, ...rest } = role;
|
||||
const roleRepresentation: RoleRepresentation = rest;
|
||||
if (id) {
|
||||
|
|
|
@ -28,3 +28,8 @@
|
|||
padding-right: var(--pf-global--spacer--xs);
|
||||
}
|
||||
|
||||
.pf-c-chip-group.kc-filter-chip-group__table {
|
||||
margin-left: var(--pf-global--spacer--md);
|
||||
margin-bottom: var(--pf-global--spacer--md);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,51 @@
|
|||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { PageSection } from "@patternfly/react-core";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient";
|
||||
import { RolesList } from "./RolesList";
|
||||
import { useErrorHandler } from "react-error-boundary";
|
||||
|
||||
export const RealmRolesSection = () => {
|
||||
const adminClient = useAdminClient();
|
||||
const [listRoles, setListRoles] = useState(false);
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
useEffect(() => {
|
||||
return asyncStateFetch(
|
||||
() => {
|
||||
return Promise.all([adminClient.roles.find()]);
|
||||
},
|
||||
|
||||
(response) => {
|
||||
setListRoles(!(response[0] && response[0].length > 0));
|
||||
},
|
||||
handleError
|
||||
);
|
||||
}, []);
|
||||
|
||||
const loader = async (first?: number, max?: number, search?: string) => {
|
||||
const params: { [name: string]: string | number } = {
|
||||
first: first!,
|
||||
max: max!,
|
||||
search: search!,
|
||||
};
|
||||
|
||||
const searchParam = search || "";
|
||||
|
||||
if (searchParam) {
|
||||
params.search = searchParam;
|
||||
}
|
||||
|
||||
if (listRoles) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return await adminClient.roles.find(params);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader titleKey="roles:title" subKey="roles:roleExplain" />
|
||||
<PageSection variant="light">
|
||||
<PageSection variant="light" padding={{ default: "noPadding" }}>
|
||||
<RolesList loader={loader} />
|
||||
</PageSection>
|
||||
</>
|
||||
|
|
160
src/realm-roles/RoleAttributes.tsx
Normal file
160
src/realm-roles/RoleAttributes.tsx
Normal file
|
@ -0,0 +1,160 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ArrayField, UseFormMethods } from "react-hook-form";
|
||||
import { ActionGroup, Button, TextInput } from "@patternfly/react-core";
|
||||
import {
|
||||
TableComposable,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { RoleFormType } from "./RealmRoleTabs";
|
||||
|
||||
import "./RealmRolesSection.css";
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
type RoleAttributesProps = {
|
||||
form: UseFormMethods<RoleFormType>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
array: {
|
||||
fields: Partial<ArrayField<Record<string, any>, "id">>[];
|
||||
append: (
|
||||
value: Partial<Record<string, any>> | Partial<Record<string, any>>[],
|
||||
shouldFocus?: boolean | undefined
|
||||
) => void;
|
||||
remove: (index?: number | number[] | undefined) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const RoleAttributes = ({
|
||||
form: { register, formState, errors, watch },
|
||||
save,
|
||||
array: { fields, append, remove },
|
||||
reset,
|
||||
}: RoleAttributesProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
|
||||
const columns = ["Key", "Value"];
|
||||
const watchFirstKey = watch("attributes[0].key", "");
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormAccess role="manage-realm">
|
||||
<TableComposable
|
||||
className="kc-role-attributes__table"
|
||||
aria-label="Role attribute keys and values"
|
||||
variant="compact"
|
||||
borders={false}
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th id="key" width={40}>
|
||||
{columns[0]}
|
||||
</Th>
|
||||
<Th id="value" width={40}>
|
||||
{columns[1]}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{fields.map((attribute, rowIndex) => (
|
||||
<Tr key={attribute.id}>
|
||||
<Td
|
||||
key={`${attribute.id}-key`}
|
||||
id={`text-input-${rowIndex}-key`}
|
||||
dataLabel={columns[0]}
|
||||
>
|
||||
<TextInput
|
||||
name={`attributes[${rowIndex}].key`}
|
||||
ref={register()}
|
||||
aria-label="key-input"
|
||||
defaultValue={attribute.key}
|
||||
validated={
|
||||
errors.attributes && errors.attributes[rowIndex]
|
||||
? "error"
|
||||
: "default"
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td
|
||||
key={`${attribute}-value`}
|
||||
id={`text-input-${rowIndex}-value`}
|
||||
dataLabel={columns[1]}
|
||||
>
|
||||
<TextInput
|
||||
name={`attributes[${rowIndex}].value`}
|
||||
ref={register()}
|
||||
aria-label="value-input"
|
||||
defaultValue={attribute.value}
|
||||
validated={errors.description ? "error" : "default"}
|
||||
/>
|
||||
</Td>
|
||||
{rowIndex !== fields.length - 1 && fields.length - 1 !== 0 && (
|
||||
<Td
|
||||
key="minus-button"
|
||||
id={`kc-minus-button-${rowIndex}`}
|
||||
dataLabel={columns[2]}
|
||||
>
|
||||
<Button
|
||||
id={`minus-button-${rowIndex}`}
|
||||
aria-label={`remove ${attribute.key} with value ${attribute.value} `}
|
||||
variant="link"
|
||||
className="kc-role-attributes__minus-icon"
|
||||
onClick={() => remove(rowIndex)}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</Td>
|
||||
)}
|
||||
{rowIndex === fields.length - 1 && (
|
||||
<Td key="add-button" id="add-button" dataLabel={columns[2]}>
|
||||
{fields[rowIndex].key === "" && (
|
||||
<Button
|
||||
id={`minus-button-${rowIndex}`}
|
||||
aria-label={`remove ${attribute.key} with value ${attribute.value} `}
|
||||
variant="link"
|
||||
className="kc-role-attributes__minus-icon"
|
||||
onClick={() => remove(rowIndex)}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
aria-label={t("roles:addAttributeText")}
|
||||
id="plus-icon"
|
||||
variant="link"
|
||||
className="kc-role-attributes__plus-icon"
|
||||
onClick={() => append({ key: "", value: "" })}
|
||||
icon={<PlusCircleIcon />}
|
||||
isDisabled={!formState.isValid}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
<ActionGroup className="kc-role-attributes__action-group">
|
||||
<Button
|
||||
data-testid="realm-roles-save-button"
|
||||
variant="primary"
|
||||
isDisabled={!watchFirstKey}
|
||||
onClick={save}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button onClick={reset} variant="link">
|
||||
{t("common:reload")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -79,6 +79,7 @@ export const RolesList = ({
|
|||
});
|
||||
|
||||
const goToCreate = () => history.push(`${url}/add-role`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
"userName": "Username",
|
||||
"email": "Email",
|
||||
"lastName": "Last name",
|
||||
"firstName": "First name"
|
||||
"firstName": "First name",
|
||||
"clearAllFilters": "Clear all filters"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ export const UsersSection = () => {
|
|||
const [listUsers, setListUsers] = useState(false);
|
||||
const [initialSearch, setInitialSearch] = useState("");
|
||||
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const [key, setKey] = useState("");
|
||||
const refresh = () => setKey(`${new Date().getTime()}`);
|
||||
|
@ -87,6 +88,7 @@ export const UsersSection = () => {
|
|||
const searchParam = search || initialSearch || "";
|
||||
if (searchParam) {
|
||||
params.search = searchParam;
|
||||
setSearch(searchParam);
|
||||
}
|
||||
|
||||
if (!listUsers && !searchParam) {
|
||||
|
@ -192,12 +194,16 @@ export const UsersSection = () => {
|
|||
canSelectAll
|
||||
onSelect={(rows) => setSelectedRows([...rows])}
|
||||
emptyState={
|
||||
!search ? (
|
||||
<ListEmptyState
|
||||
message={t("noUsersFound")}
|
||||
instructions={t("emptyInstructions")}
|
||||
primaryActionText={t("createNewUser")}
|
||||
onPrimaryAction={goToCreate}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
}
|
||||
toolbarItem={
|
||||
<>
|
||||
|
|
Loading…
Reference in a new issue