You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
16 KiB

  1. // material-ui
  2. import {
  3. Button,
  4. Grid, Typography, Stack, Box
  5. } from '@mui/material';
  6. import { useEffect, useState, useRef, lazy } from "react";
  7. import axios from "axios";
  8. import { useParams } from "react-router-dom";
  9. import {
  10. GeneralConfirmWindow,
  11. getDeletedRecordWithRefList,
  12. getIdList,
  13. notifyActionError,
  14. notifyDeleteSuccess,
  15. notifySaveSuccess
  16. } from "../../utils/CommonFunction";
  17. import { POST_AND_UPDATE_USER_GROUP, GET_GROUP_LIST_PATH } from "utils/ApiPathConst";
  18. import Loadable from 'components/Loadable';
  19. const LoadingComponent = Loadable(lazy(() => import('../extra-pages/LoadingComponent')));
  20. const GroupAuthCard = Loadable(lazy(() => import('./GroupAuthCard')));
  21. const UserGroupInfoCard = Loadable(lazy(() => import('./UserGroupInfoCard')));
  22. const UserAddCard = Loadable(lazy(() => import('./UserAddCard')));
  23. import { useNavigate } from "react-router";
  24. import ForwardIcon from '@mui/icons-material/Forward';
  25. import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'
  26. import { isGrantedAny } from "auth/utils";
  27. const BackgroundHead = {
  28. backgroundImage: `url(${titleBackgroundImg})`,
  29. width: '100%',
  30. height: '100%',
  31. backgroundSize: 'contain',
  32. backgroundRepeat: 'no-repeat',
  33. backgroundColor: '#0C489E',
  34. backgroundPosition: 'right'
  35. }
  36. // ==============================|| DASHBOARD - DEFAULT ||============================== //
  37. const UserMaintainPage = () => {
  38. const params = useParams();
  39. const navigate = useNavigate();
  40. const [onReady, setOnReady] = useState(false);
  41. const [editMode, setEditMode] = useState(false);
  42. const [isCollectData, setIsCollectData] = useState(false);
  43. const [editedGroupData, setEditedGroupData] = useState({});
  44. const [userGroupData, setUserGroupData] = useState([]);
  45. const saveInProgressRef = useRef(false);
  46. const userAuthDataRef = useRef([]);
  47. const deletedAuthListRef = useRef([]);
  48. const groupMemberRef = useRef([]);
  49. const deletedUserListRef = useRef([]);
  50. const [isNewRecord, setIsNewRecord] = useState(false);
  51. const [isWindowOpen, setIsWindowOpen] = useState(false);
  52. const handleClose = () => {
  53. setIsWindowOpen(false);
  54. };
  55. const handleDeleteClick = () => {
  56. setIsWindowOpen(true);
  57. };
  58. function deleteData() {
  59. axios.delete(`${GET_GROUP_LIST_PATH}/${params.id}`,
  60. )
  61. .then((response) => {
  62. if (response.status === 204) {
  63. notifyDeleteSuccess()
  64. setIsWindowOpen(false);
  65. navigate('/usergroupSearchview');
  66. }
  67. })
  68. .catch(error => {
  69. console.log(error);
  70. return false;
  71. });
  72. }
  73. function updateGroupObject(groupData) {
  74. setEditedGroupData(groupData);
  75. }
  76. function updateGroupMember(groupMember) {
  77. const currentList = groupMember.currentList || [];
  78. const deletedList = groupMember.deletedList || [];
  79. groupMemberRef.current = currentList;
  80. deletedUserListRef.current = deletedList;
  81. }
  82. function updateUserAuthList(authData) {
  83. const currentList = authData.currentList || [];
  84. const deletedList = authData.deletedList || [];
  85. userAuthDataRef.current = currentList;
  86. deletedAuthListRef.current = deletedList;
  87. }
  88. const submitData = async () => {
  89. if (!onReady || saveInProgressRef.current) {
  90. return;
  91. }
  92. saveInProgressRef.current = true;
  93. setIsCollectData(!isCollectData);
  94. try {
  95. const isNameValid = await validateGroupName();
  96. if (!isNameValid) {
  97. return;
  98. }
  99. const latestGroupFormData = getLatestGroupFormData();
  100. const latestGroupMember = groupMemberRef.current;
  101. const latestAuthIds = userAuthDataRef.current;
  102. const latestDeletedAuthIds = deletedAuthListRef.current;
  103. const finalDeletedUserList = getDeletedRecordWithRefList(
  104. deletedUserListRef.current,
  105. getIdList(latestGroupMember)
  106. );
  107. const response = await axios.post(POST_AND_UPDATE_USER_GROUP, {
  108. id: parseInt(params.id) !== -1 ? parseInt(params.id) : null,
  109. name: latestGroupFormData.userGroupName,
  110. description: latestGroupFormData.description,
  111. addUserIds: getIdList(latestGroupMember),
  112. removeUserIds: finalDeletedUserList,
  113. addAuthIds: latestAuthIds,
  114. removeAuthIds: latestDeletedAuthIds,
  115. });
  116. if (response.status === 200) {
  117. notifySaveSuccess();
  118. const savedId = response.data?.id;
  119. const currentId = parseInt(params.id);
  120. if ((currentId === -1 || Number.isNaN(currentId)) && savedId) {
  121. navigate(`/userGroup/${savedId}`, { replace: true });
  122. await loadGroupData(savedId);
  123. } else {
  124. await loadGroupData(currentId);
  125. }
  126. setIsNewRecord(false);
  127. setEditMode(false);
  128. deletedUserListRef.current = [];
  129. deletedAuthListRef.current = [];
  130. }
  131. } catch (error) {
  132. console.log(error);
  133. notifyActionError(error?.response?.data?.message || "Save failed.");
  134. } finally {
  135. saveInProgressRef.current = false;
  136. }
  137. };
  138. const normalizeName = (name) => (name || "").trim().toLowerCase();
  139. const getLatestGroupFormData = () => {
  140. const nameEl = document.getElementById("groupName");
  141. const descEl = document.getElementById("description");
  142. // Prefer what the user actually typed. Parent `editedGroupData` can still be stale on the
  143. // first Save click (sync runs after `isCollectData` toggles in a child effect).
  144. return {
  145. userGroupName: nameEl != null ? nameEl.value : (editedGroupData?.userGroupName ?? ""),
  146. description: descEl != null ? descEl.value : (editedGroupData?.description ?? "")
  147. };
  148. };
  149. const validateGroupName = async () => {
  150. const latestGroupFormData = getLatestGroupFormData();
  151. const groupName = (latestGroupFormData.userGroupName || "").trim();
  152. if (groupName.length === 0) {
  153. notifyActionError("User Group Name is required.");
  154. return false;
  155. }
  156. try {
  157. const response = await axios.get(GET_GROUP_LIST_PATH, {
  158. params: {
  159. name: groupName,
  160. start: 0,
  161. limit: 1000
  162. }
  163. });
  164. const records = response?.data?.records || [];
  165. const currentId = parseInt(params.id);
  166. const isDuplicateName = records.some((record) =>
  167. normalizeName(record?.name) === normalizeName(groupName) &&
  168. parseInt(record?.id) !== currentId
  169. );
  170. if (isDuplicateName) {
  171. notifyActionError(`User Group Name "${groupName}" already exists.`);
  172. return false;
  173. }
  174. } catch (error) {
  175. console.log(error);
  176. notifyActionError("Unable to validate User Group Name. Please try again.");
  177. return false;
  178. }
  179. return true;
  180. };
  181. const loadGroupData = async (groupId) => {
  182. const response = await axios.get(`${GET_GROUP_LIST_PATH}/${groupId}`);
  183. if (response.status === 200) {
  184. setUserGroupData(response.data);
  185. const loadedAuthIds = response.data?.authIds || [];
  186. userAuthDataRef.current = loadedAuthIds;
  187. }
  188. return response;
  189. };
  190. useEffect(() => {
  191. if (params.id > 0) {
  192. loadGroupData(params.id)
  193. .catch(error => {
  194. console.log(error);
  195. return false;
  196. });
  197. }
  198. else {
  199. //new record case
  200. setUserGroupData(
  201. {
  202. "authIds": [],
  203. "data": {},
  204. "userIds": []
  205. }
  206. );
  207. setIsNewRecord(true);
  208. setEditMode(true);
  209. }
  210. }, []);
  211. useEffect(() => {
  212. if (Object.keys(userGroupData).length > 0 && userGroupData !== undefined) {
  213. setOnReady(true);
  214. }
  215. else if (isNewRecord) {
  216. setOnReady(true);
  217. }
  218. }, [userGroupData]);
  219. return (
  220. !onReady ?
  221. <Grid container sx={{ minHeight: '87vh', mb: 3 }} direction="column" justifyContent="center" alignItems="center">
  222. <Grid item>
  223. <LoadingComponent />
  224. </Grid>
  225. </Grid>
  226. :
  227. <Grid container sx={{ backgroundColor: "backgroundColor.default" }}>
  228. <Grid item xs={12}>
  229. <div style={BackgroundHead}>
  230. <Stack direction="row" height='70px' justifyContent="flex-start" alignItems="center">
  231. <Typography ml={15} color='#FFF' variant="h4" sx={{ "textShadow": "0px 0px 25px #0c489e" }}>{isNewRecord ? "Create User Group" : "Maintain User Group"}</Typography>
  232. </Stack>
  233. </div>
  234. </Grid>
  235. <Grid item xs={12}>
  236. <Button title="Back" sx={{ ml: 3.5, mt: 2 }} style={{ border: '2px solid' }} variant="outlined" onClick={() => { navigate("/usergroupSearchview") }}>
  237. <ForwardIcon style={{ height: 30, width: 50, transform: "rotate(180deg)" }} />
  238. </Button>
  239. </Grid>
  240. {/*top button*/}
  241. {
  242. isGrantedAny("MAINTAIN_GROUP")?
  243. <Grid item s={12} md={12} lg={12} alignItems={"start"} justifyContent="center">
  244. <Grid container maxWidth justifyContent="flex-start" sx={{ mt: 1 }}>
  245. {editMode ?
  246. <>
  247. <Grid item sx={{ ml: 3, mr: 3 }}>
  248. <Button
  249. size="large"
  250. variant="contained"
  251. type="submit"
  252. sx={{
  253. textTransform: 'capitalize',
  254. alignItems: 'end'
  255. }}
  256. onClick={() => { location.reload() }}
  257. color="secondary"
  258. >
  259. <Typography variant="h5">Reset & Back</Typography>
  260. </Button>
  261. </Grid>
  262. <Grid item sx={{ ml: 3, mr: 3 }}>
  263. <Button
  264. size="large"
  265. variant="contained"
  266. type="submit"
  267. sx={{
  268. textTransform: 'capitalize',
  269. alignItems: 'end'
  270. }}
  271. onClick={submitData}
  272. >
  273. <Typography variant="h5">Save</Typography>
  274. </Button>
  275. </Grid>
  276. </>
  277. :
  278. <>
  279. <Grid item sx={{ ml: 3, mr: 3 }}>
  280. <Button
  281. size="large"
  282. variant="contained"
  283. type="submit"
  284. sx={{
  285. textTransform: 'capitalize',
  286. alignItems: 'end'
  287. }}
  288. onClick={() => { setEditMode(true) }}
  289. >
  290. <Typography variant="h5">Edit</Typography>
  291. </Button>
  292. </Grid>
  293. <Grid item sx={{ ml: 3, mr: 3 }}>
  294. <Button
  295. size="large"
  296. variant="contained"
  297. sx={{
  298. textTransform: 'capitalize',
  299. alignItems: 'end'
  300. }}
  301. color="error"
  302. disabled={isNewRecord}
  303. onClick={handleDeleteClick}
  304. >
  305. <Typography variant="h5">Delete User Group</Typography>
  306. </Button>
  307. <GeneralConfirmWindow
  308. isWindowOpen={isWindowOpen}
  309. title={"Attention"}
  310. content={`Confirm to delete User Group "${userGroupData.data.name}" ?`}
  311. onNormalClose={handleClose}
  312. onConfirmClose={deleteData}
  313. />
  314. </Grid>
  315. </>
  316. }
  317. </Grid>
  318. </Grid>
  319. :<></>
  320. }
  321. {/*col 1*/}
  322. <Grid item xs={12} md={5} lg={5}>
  323. <Grid container>
  324. <Grid item xs={12} md={12} lg={12}>
  325. <Box xs={12} ml={0} mt={-1} mr={0} sx={{ p: 1, borderRadius: '10px' }}>
  326. <UserGroupInfoCard
  327. updateGroupObject={updateGroupObject}
  328. userGroupData={userGroupData}
  329. isCollectData={isCollectData}
  330. isNewRecord={isNewRecord}
  331. editMode={editMode}
  332. />
  333. </Box>
  334. </Grid>
  335. <Grid item xs={12} md={12} lg={12} sx={{ mt: 3 }}>
  336. <Box xs={12} ml={0} mt={-5} mr={0} sx={{ p: 1, borderRadius: '10px' }}>
  337. <UserAddCard
  338. updateGroupMember={updateGroupMember}
  339. userGroupData={userGroupData}
  340. isCollectData={isCollectData}
  341. isNewRecord={isNewRecord}
  342. editMode={editMode}
  343. />
  344. </Box>
  345. </Grid>
  346. </Grid>
  347. </Grid>
  348. {/*col 2*/}
  349. <Grid item xs={12} md={7} lg={7}>
  350. <Box xs={12} ml={-2} mt={-1} mr={0} sx={{ p: 1, borderRadius: '10px' }}>
  351. <GroupAuthCard
  352. updateUserAuthList={updateUserAuthList}
  353. userGroupData={userGroupData}
  354. isCollectData={isCollectData}
  355. isNewRecord={isNewRecord}
  356. editMode={editMode}
  357. />
  358. </Box>
  359. </Grid>
  360. </Grid>
  361. );
  362. };
  363. export default UserMaintainPage;