diff --git a/src/groups/GroupsCreateModal.tsx b/src/groups/GroupsCreateModal.tsx new file mode 100644 index 0000000000..d89077d3e6 --- /dev/null +++ b/src/groups/GroupsCreateModal.tsx @@ -0,0 +1,99 @@ +import React, { useContext } from "react"; +import { + AlertVariant, + Button, + Form, + FormGroup, + Modal, + ModalVariant, + TextInput, +} from "@patternfly/react-core"; +import { useTranslation } from "react-i18next"; +import { HttpClientContext } from "../context/http-service/HttpClientContext"; +import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAlerts } from "../components/alert/Alerts"; +import { useForm } from "react-hook-form"; + +type GroupsCreateModalProps = { + handleModalToggle: () => void; + isCreateModalOpen: boolean; + setIsCreateModalOpen: (isCreateModalOpen: boolean) => void; + createGroupName: string; + setCreateGroupName: (createGroupName: string) => void; + refresh: () => void; +}; + +export const GroupsCreateModal = ({ + handleModalToggle, + isCreateModalOpen, + setIsCreateModalOpen, + createGroupName, + setCreateGroupName, + refresh, +}: GroupsCreateModalProps) => { + const { t } = useTranslation("groups"); + const httpClient = useContext(HttpClientContext)!; + const { realm } = useContext(RealmContext); + const { addAlert } = useAlerts(); + const form = useForm(); + const { register, errors } = form; + + const valueChange = (createGroupName: string) => { + setCreateGroupName(createGroupName); + }; + + const submitForm = async () => { + if (await form.trigger()) { + try { + await httpClient.doPost(`/admin/realms/${realm}/groups`, { + name: createGroupName, + }); + setIsCreateModalOpen(false); + setCreateGroupName(""); + refresh(); + addAlert(t("groupCreated"), AlertVariant.success); + } catch (error) { + addAlert( + `${t("couldNotCreateGroup")} ': '${error}'`, + AlertVariant.danger + ); + } + } + }; + + return ( + <> + submitForm()}> + {t("create")} + , + ]} + > +
+ + + +
+
+ + ); +}; diff --git a/src/groups/GroupsList.tsx b/src/groups/GroupsList.tsx index 9bc687b018..9b1b6a19e1 100644 --- a/src/groups/GroupsList.tsx +++ b/src/groups/GroupsList.tsx @@ -5,14 +5,17 @@ import { TableBody, TableVariant, } from "@patternfly/react-table"; -import { Button } from "@patternfly/react-core"; +import { Button, AlertVariant } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; import { GroupRepresentation } from "./models/groups"; import { UsersIcon } from "@patternfly/react-icons"; import { HttpClientContext } from "../context/http-service/HttpClientContext"; +import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAlerts } from "../components/alert/Alerts"; -type GroupsListProps = { +export type GroupsListProps = { list?: GroupRepresentation[]; + refresh: () => void; }; type FormattedData = { @@ -20,11 +23,13 @@ type FormattedData = { selected: boolean; }; -export const GroupsList = ({ list }: GroupsListProps) => { +export const GroupsList = ({ list, refresh }: GroupsListProps) => { const { t } = useTranslation("groups"); const httpClient = useContext(HttpClientContext)!; const columnGroupName: keyof GroupRepresentation = "name"; const columnGroupNumber: keyof GroupRepresentation = "membersLength"; + const { realm } = useContext(RealmContext); + const { addAlert } = useAlerts(); const [formattedData, setFormattedData] = useState([]); const formatData = (data: GroupRepresentation[]) => @@ -67,15 +72,6 @@ export const GroupsList = ({ list }: GroupsListProps) => { } } - // Delete individual rows using the action in the table - function onDelete(rowIndex: number) { - const localFilteredData = [...list!]; - httpClient.doDelete( - `/admin/realms/master/groups/${localFilteredData[rowIndex].id}` - ); - // TO DO update the state - } - const tableHeader = [{ title: t("groupName") }, { title: t("members") }]; const actions = [ { @@ -84,7 +80,20 @@ export const GroupsList = ({ list }: GroupsListProps) => { }, { title: t("common:Delete"), - onClick: () => onDelete, + onClick: async ( + _: React.MouseEvent, + rowId: number + ) => { + try { + await httpClient.doDelete( + `/admin/realms/${realm}/groups/${list![rowId].id}` + ); + refresh(); + addAlert(t("Group deleted"), AlertVariant.success); + } catch (error) { + addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger); + } + }, }, ]; diff --git a/src/groups/GroupsSection.tsx b/src/groups/GroupsSection.tsx index f7e231a10b..6b438930dd 100644 --- a/src/groups/GroupsSection.tsx +++ b/src/groups/GroupsSection.tsx @@ -2,24 +2,23 @@ import React, { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { HttpClientContext } from "../context/http-service/HttpClientContext"; import { GroupsList } from "./GroupsList"; +import { GroupsCreateModal } from "./GroupsCreateModal"; import { GroupRepresentation } from "./models/groups"; import { ServerGroupsArrayRepresentation, ServerGroupMembersRepresentation, } from "./models/server-info"; import { TableToolbar } from "../components/table-toolbar/TableToolbar"; +import { ViewHeader } from "../components/view-header/ViewHeader"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { Button, - Divider, Dropdown, DropdownItem, KebabToggle, PageSection, PageSectionVariants, Spinner, - Title, - TitleSizes, ToolbarItem, } from "@patternfly/react-core"; import "./GroupsSection.css"; @@ -30,39 +29,43 @@ export const GroupsSection = () => { const [rawData, setRawData] = useState<{ [key: string]: any }[]>(); const [filteredData, setFilteredData] = useState(); const [isKebabOpen, setIsKebabOpen] = useState(false); + const [createGroupName, setCreateGroupName] = useState(""); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const columnID: keyof GroupRepresentation = "id"; const membersLength: keyof GroupRepresentation = "membersLength"; const columnGroupName: keyof GroupRepresentation = "name"; + const loader = async () => { + const groups = await httpClient.doGet( + "/admin/realms/master/groups" + ); + const groupsData = groups.data!; + + const getMembers = async (id: number) => { + const response = await httpClient.doGet< + ServerGroupMembersRepresentation[] + >(`/admin/realms/master/groups/${id}/members`); + const responseData = response.data!; + return responseData.length; + }; + + const memberPromises = groupsData.map((group: { [key: string]: any }) => + getMembers(group[columnID]) + ); + const memberData = await Promise.all(memberPromises); + const updatedObject = groupsData.map( + (group: { [key: string]: any }, i: number) => { + const object = Object.assign({}, group); + object[membersLength] = memberData[i]; + return object; + } + ); + + setRawData(updatedObject); + }; + useEffect(() => { - (async () => { - const groups = await httpClient.doGet( - "/admin/realms/master/groups" - ); - const groupsData = groups.data!; - - const getMembers = async (id: number) => { - const response = await httpClient.doGet< - ServerGroupMembersRepresentation[] - >(`/admin/realms/master/groups/${id}/members`); - const responseData = response.data!; - return responseData.length; - }; - - const memberPromises = groupsData.map((group: { [key: string]: any }) => - getMembers(group[columnID]) - ); - const memberData = await Promise.all(memberPromises); - const updatedObject = groupsData.map( - (group: { [key: string]: any }, i: number) => { - const object = Object.assign({}, group); - object[membersLength] = memberData[i]; - return object; - } - ); - - setRawData(updatedObject); - })(); + loader(); }, []); // Filter groups @@ -83,15 +86,13 @@ export const GroupsSection = () => { setIsKebabOpen(!isKebabOpen); }; - return ( - - - - {t("groups")} - - - + const handleModalToggle = () => { + setIsCreateModalOpen(!isCreateModalOpen); + }; + return ( + <> + {!rawData && (
@@ -99,43 +100,61 @@ export const GroupsSection = () => {
)} {rawData && rawData.length > 0 ? ( - - - - - - } - isOpen={isKebabOpen} - isPlain - dropdownItems={[ - - {t("delete")} - , - ]} - /> - - - } - > - {rawData && ( - - )} - {filteredData && filteredData.length === 0 && ( - - )} - + <> + + + + + + } + isOpen={isKebabOpen} + isPlain + dropdownItems={[ + + {t("delete")} + , + ]} + /> + + + } + > + {rawData && ( + + )} + {filteredData && filteredData.length === 0 && ( + + )} + + + ) : ( { /> )}
-
+ ); }; diff --git a/src/groups/messages.json b/src/groups/messages.json index 047b98793c..aebc6ed485 100644 --- a/src/groups/messages.json +++ b/src/groups/messages.json @@ -10,6 +10,12 @@ "moveTo": "Move to", "delete": "Delete", "tableOfGroups": "Table of groups", + "name": "Name", + "groupsDescription": "Description goes here", + "groupCreated": "Group created", + "couldNotCreateGroup": "Could not create group", + "createAGroup": "Create a group", + "create": "Create", "noSearchResults": "No search results", "noSearchResultsInstructions" : "Click on the search bar above to search for groups", "noGroupsInThisRealm" : "No groups in this Realm",