Skip to content

Commit

Permalink
OH2-300 | Implement delete user feature (#681)
Browse files Browse the repository at this point in the history
* feature(OH2-300): Implement delete user feature

* styles: Reformat code

* fix: Fix translations and show success dialog after user deletion

---------

Co-authored-by: SteveGT96 <[email protected]>
  • Loading branch information
SteveGT96 and SteveGT96 authored Oct 28, 2024
1 parent f3bda7a commit a90d68f
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 25 deletions.
13 changes: 12 additions & 1 deletion src/components/accessories/admin/users/Users.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Tab, Tabs } from "@mui/material";
import React from "react";
import React, { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router";

import Button from "../../button/Button";
import UserGroupsTable from "./userGroupsTable";
import UsersTable from "./usersTable";

import { useAppDispatch } from "libraries/hooks/redux";
import { deleteUser } from "state/users";
import { PATHS } from "../../../../consts";
import { UserDTO, UserGroupDTO } from "../../../../generated";

Expand All @@ -18,6 +20,7 @@ export enum TabOptions {
export const Users = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const dispatch = useAppDispatch();

const { state }: { state: { tab?: TabOptions } } = useLocation();
const setTab = (tab: TabOptions) =>
Expand All @@ -33,6 +36,13 @@ export const Users = () => {
state: row,
});

const handleDeleteUser = useCallback(
(row: UserDTO) => {
dispatch(deleteUser(row.userName ?? ""));
},
[dispatch]
);

return (
<>
<Tabs
Expand All @@ -58,6 +68,7 @@ export const Users = () => {
</Button>
}
onEdit={handleEditUser}
onDelete={handleDeleteUser}
/>
) : (
<UserGroupsTable
Expand Down
85 changes: 63 additions & 22 deletions src/components/accessories/admin/users/usersTable/UsersTable.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,57 @@
import { CircularProgress } from "@mui/material";
import React, { ReactNode, useEffect } from "react";
import React, { ReactNode, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";

import { useAppDispatch, useAppSelector } from "libraries/hooks/redux";
import { usePermission } from "libraries/permissionUtils/usePermission";
import checkIcon from "../../../../../assets/check-icon.png";
import { UserDTO } from "../../../../../generated";
import { getUsers } from "../../../../../state/users";
import { deleteUserReset, getUsers } from "../../../../../state/users";
import ConfirmationDialog from "../../../confirmationDialog/ConfirmationDialog";
import InfoBox from "../../../infoBox/InfoBox";
import Table from "../../../table/Table";
import { TFilterField } from "../../../table/filter/types";

import { scrollToElement } from "libraries/uiUtils/scrollToElement";
import { getUserGroups } from "state/usergroups";
import classes from "./UsersTable.module.scss";

interface IOwnProps {
headerActions: ReactNode;
onEdit: (row: UserDTO) => void;
onDelete: (row: UserDTO) => void;
}

export const UsersTable = ({ headerActions, onEdit }: IOwnProps) => {
export const UsersTable = ({ headerActions, onEdit, onDelete }: IOwnProps) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const infoBoxRef = useRef<HTMLDivElement>(null);

const canUpdate = usePermission("users.update");
const canDelete = usePermission("users.update");

const deleteUser = useAppSelector((state) => state.users.delete);

useEffect(() => {
dispatch(getUsers({}));
dispatch(getUserGroups());

return () => {
dispatch(deleteUserReset());
};
}, [dispatch]);

const header = ["userName", "userGroupName", "desc"];
useEffect(() => {
if (deleteUser.status === "FAIL") {
scrollToElement(infoBoxRef.current);
}

if (deleteUser.hasSucceeded) {
dispatch(getUsers({}));
}
}, [deleteUser.status, dispatch]);

const header = ["userName", "userGroupName", "desc"];
const label = {
userName: t("user.username"),
userGroupName: t("user.groups"),
Expand Down Expand Up @@ -65,7 +87,7 @@ export const UsersTable = ({ headerActions, onEdit }: IOwnProps) => {
userGroupName:
item.userGroupName?.desc ?? item.userGroupName?.code ?? "",
desc: item.desc ?? "",
passwd: item.passwd ?? ""
passwd: item.passwd ?? "",
};
});
};
Expand All @@ -85,23 +107,42 @@ export const UsersTable = ({ headerActions, onEdit }: IOwnProps) => {

case "SUCCESS":
return (
<Table
rowData={formatDataToDisplay(data ?? [])}
tableHeader={header}
labelData={label}
columnsOrder={order}
rowsPerPage={10}
filterColumns={filters}
manualFilter={false}
isCollapsabile={false}
rawData={(data ?? []).map((user) => ({
...user,
userGroupName: user.userGroupName?.code,
}))}
rowKey="userName"
headerActions={headerActions}
onEdit={canUpdate ? onEdit: undefined}
/>
<>
{deleteUser.status === "FAIL" && (
<div ref={infoBoxRef} className="info-box-container">
<InfoBox type="error" message={deleteUser.error?.message} />
</div>
)}
<Table
rowData={formatDataToDisplay(data ?? [])}
tableHeader={header}
labelData={label}
columnsOrder={order}
rowsPerPage={10}
filterColumns={filters}
manualFilter={false}
isCollapsabile={false}
rawData={(data ?? []).map((user) => ({
...user,
userGroupName: user.userGroupName?.code,
}))}
rowKey="userName"
headerActions={headerActions}
onEdit={canUpdate ? onEdit : undefined}
onDelete={canDelete ? onDelete : undefined}
/>
<ConfirmationDialog
isOpen={!!deleteUser.hasSucceeded}
title={t("user.deleted")}
icon={checkIcon}
info={t("user.deleteSuccess")}
primaryButtonLabel="Ok"
handlePrimaryButtonClick={() => {
dispatch(deleteUserReset());
}}
handleSecondaryButtonClick={() => ({})}
/>
</>
);
case "SUCCESS_EMPTY":
return <InfoBox type="info" message={t("common.emptydata")} />;
Expand Down
10 changes: 10 additions & 0 deletions src/mockServer/routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export const userRoutes = (server) => {
server.put("/:username").intercept((_req, res) => {
res.status(200).json(_req.jsonBody());
});
server.delete("/:username").intercept((req, res) => {
const username = req.params.username;
switch (username) {
case "FAIL":
res.status(400).json({ message: "Fail to delete user" });
break;
default:
res.status(204);
}
});
server.get("/:username/settings/dashboard").intercept((req, res) => {
res.status(200).json(dashboardSettingDTO);
});
Expand Down
4 changes: 2 additions & 2 deletions src/mockServer/routes/wards.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export const wardsRoutes = (server) => {
const code = req.params.code;
switch (code) {
case "FAIL":
res.status(400).json({ message: "Fail to update ward" });
res.status(400).json({ message: "Fail to delete ward" });
break;
default:
res.status(200);
res.status(204);
}
});
});
Expand Down
2 changes: 2 additions & 0 deletions src/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"editGroup": "Edit group",
"description": "Description",
"lastlogin": "Last login",
"deleted": "User deleted",
"groupCreated": "Group created",
"groupDeleted": "Group deleted",
"groupUpdated": "Group updated",
Expand All @@ -41,6 +42,7 @@
"createdSuccessTitle": "User created",
"createdSuccessMessage": "User has been created successfully!",
"updatedSuccessTitle": "User updated",
"deleteSuccess": "User has been deleted successfully!",
"updatedSuccessMessage": "User has been updated successfully!",
"validateUserNeedsGroup": "Each user should belong to a group",
"validatePasswordNeeded": "No password provided.",
Expand Down

0 comments on commit a90d68f

Please sign in to comment.