Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 

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