diff --git a/src/components/Accounts/ApprovedAccounts.jsx b/src/components/Accounts/ApprovedAccounts.jsx index d2de376..73a38c6 100644 --- a/src/components/Accounts/ApprovedAccounts.jsx +++ b/src/components/Accounts/ApprovedAccounts.jsx @@ -1,177 +1,223 @@ import { NPOBackend } from '../../utils/auth_utils.js'; import { useEffect, useState, useCallback } from 'react'; -import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Button, Checkbox, useDisclosure } from '@chakra-ui/react' -import { CloseIcon } from '@chakra-ui/icons' +import { + Box, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Button, + Checkbox, + useDisclosure, +} from '@chakra-ui/react'; +import { CloseIcon } from '@chakra-ui/icons'; import DeleteAccountModal from './DeleteAccountModal.jsx'; import PropTypes from 'prop-types'; -import PaginationFooter from "../../components/Catalog/PaginationFooter/PaginationFooter"; +import PaginationFooter from '../../components/Catalog/PaginationFooter/PaginationFooter'; import { usePagination } from '@ajna/pagination'; +import { useAuthContext } from '../../common/AuthContext.jsx'; -const ApprovedAccounts = ( {accountType, searchQuery} ) => { - const [approvedAccounts, setApprovedAccounts] = useState([]); - const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); - const [deleteItemId, setDeleteItemId] = useState([]); - const [totalRowCount, setTotalRowCount] = useState(0); - const { currentPage, setCurrentPage, pagesCount, offset, pageSize, setPageSize} = usePagination({ - initialState: { currentPage: 1, pageSize: 10 }, - total: totalRowCount, - }); - const [individualChecked, setIndividualChecked] = useState(new Array(approvedAccounts.length).fill(false)); - const [checkedAccountIds, setCheckedAccountIds] = useState([]); - const [dataShouldRevalidate, setDataShouldRevalidate] = useState(false); +const ApprovedAccounts = ({ accountType, searchQuery }) => { + const [approvedAccounts, setApprovedAccounts] = useState([]); + const { isOpen: isDeleteOpen, onOpen: onDeleteOpen, onClose: onDeleteClose } = useDisclosure(); + const [deleteItemId, setDeleteItemId] = useState([]); + const [totalRowCount, setTotalRowCount] = useState(0); + const { currentPage, setCurrentPage, pagesCount, offset, pageSize, setPageSize } = usePagination({ + initialState: { currentPage: 1, pageSize: 10 }, + total: totalRowCount, + }); + const [individualChecked, setIndividualChecked] = useState( + new Array(approvedAccounts.length).fill(false), + ); + const [checkedAccountIds, setCheckedAccountIds] = useState([]); + const [dataShouldRevalidate, setDataShouldRevalidate] = useState(false); + const { currentUser } = useAuthContext(); + const [isSelectAll, setIsSelectAll] = useState(false); - const fetchTableData = useCallback(async () => { - try { - const { data } = await NPOBackend.get(`/users/approved-accounts`, { - params: { - keyword: (searchQuery && searchQuery.length) && searchQuery, - page: currentPage, - limit: pageSize, - accountType: accountType} - }); - setApprovedAccounts(data.accounts); - setTotalRowCount(Number(data.count[0].count)); - } catch (error) { - console.error('Error fetching data:', error); - } - }, [searchQuery, currentPage, pageSize, accountType]); + const fetchTableData = useCallback(async () => { + try { + const { data } = await NPOBackend.get(`/users/approved-accounts`, { + params: { + keyword: searchQuery && searchQuery.length && searchQuery, + page: currentPage, + limit: pageSize, + accountType: accountType, + }, + }); + setApprovedAccounts(data.accounts); + setTotalRowCount(Number(data.count[0].count)); + } catch (error) { + console.error('Error fetching data:', error); + } + }, [searchQuery, currentPage, pageSize, accountType]); - useEffect(() => { - fetchTableData(); - }, [fetchTableData]); + useEffect(() => { + fetchTableData(); + }, [fetchTableData]); - useEffect(() => { - if (dataShouldRevalidate) { - fetchTableData(); - setDataShouldRevalidate(false); - setIndividualChecked(new Array(totalRowCount).fill(false)); - setCheckedAccountIds([]); - } - }, [dataShouldRevalidate, fetchTableData, totalRowCount]); + useEffect(() => { + if (dataShouldRevalidate) { + fetchTableData(); + setDataShouldRevalidate(false); + setIndividualChecked(new Array(totalRowCount).fill(false)); + setCheckedAccountIds([]); + } + }, [dataShouldRevalidate, fetchTableData, totalRowCount]); - useEffect(() => { - setCurrentPage(1); - }, [searchQuery, setCurrentPage, pageSize]); + useEffect(() => { + setCurrentPage(1); + }, [searchQuery, setCurrentPage, pageSize]); - useEffect(() => { - setIndividualChecked(new Array(totalRowCount).fill(false)); - setCheckedAccountIds([]); - }, [searchQuery, currentPage, totalRowCount]); + useEffect(() => { + setIndividualChecked(new Array(totalRowCount).fill(false)); + setCheckedAccountIds([]); + setIsSelectAll(false); + }, [searchQuery, currentPage, totalRowCount]); - const handleDeleteClick = id => { - setDeleteItemId(id); - onDeleteOpen(); - } + const handleDeleteClick = id => { + setDeleteItemId(id); + onDeleteOpen(); + }; - const updateAllCheckedAccountIds = (e) => { - setIndividualChecked(new Array(approvedAccounts.length).fill(e.target.checked)); - if (e.target.checked) { - let allIds = []; - for (let i = 0; i < approvedAccounts.length; i++) { - allIds.push(approvedAccounts[i].id) - } - setCheckedAccountIds(allIds); + const updateAllCheckedAccountIds = e => { + let newIndividualChecked = new Array(approvedAccounts.length).fill(e.target.checked); + if (e.target.checked) { + let allIds = []; + for (let i = 0; i < approvedAccounts.length; i++) { + if (approvedAccounts[i].id != currentUser.id) { + allIds.push(approvedAccounts[i].id); } else { - setCheckedAccountIds([]); + newIndividualChecked[i] = false; } + } + setCheckedAccountIds(allIds); + } else { + setCheckedAccountIds([]); } + setIndividualChecked(newIndividualChecked); + setIsSelectAll(e.target.checked); + }; - const updateIndividualCheckedAccountIds = (e, id, index) => { - const newIndividualChecked = [...individualChecked]; - newIndividualChecked[index] = e.target.checked; - setIndividualChecked(newIndividualChecked); - let newCheckedAccountIds = [... checkedAccountIds]; - if (e.target.checked) { - newCheckedAccountIds.push(id); - setCheckedAccountIds(newCheckedAccountIds); - } else { - let index = newCheckedAccountIds.indexOf(id); - newCheckedAccountIds.splice(index, 1); - setCheckedAccountIds(newCheckedAccountIds); - } + const updateIndividualCheckedAccountIds = (e, id, index) => { + const newIndividualChecked = [...individualChecked]; + newIndividualChecked[index] = e.target.checked; + setIndividualChecked(newIndividualChecked); + let newCheckedAccountIds = [...checkedAccountIds]; + if (e.target.checked) { + newCheckedAccountIds.push(id); + setCheckedAccountIds(newCheckedAccountIds); + } else { + let index = newCheckedAccountIds.indexOf(id); + newCheckedAccountIds.splice(index, 1); + setCheckedAccountIds(newCheckedAccountIds); } + }; - return ( - - - - - - - - - - {checkedAccountIds.length > 0 && - - } - - - - { - approvedAccounts.map((account, i) => ( - - - - - {checkedAccountIds.length > 0 && - - } - - - )) - } - -
{ updateAllCheckedAccountIds(e) }}/>NameEmailDeactivate - -
- { updateIndividualCheckedAccountIds(e, account.id, i)}}> - - {account.firstName} {account.lastName}{account.email} - -
- -
- -
- ) -} + return ( + + + + + + + + + + {checkedAccountIds.length > 0 && ( + + )} + + + + {approvedAccounts.map((account, i) => ( + + + + + {checkedAccountIds.length > 0 && } + + + ))} + +
+ { + updateAllCheckedAccountIds(e); + }} + /> + NameEmail + Deactivate + + +
+ { + updateIndividualCheckedAccountIds(e, account.id, i); + }} + > + + {account.firstName} {account.lastName} + {account.email} + +
+ +
+ +
+ ); +}; ApprovedAccounts.propTypes = { - accountType: PropTypes.string.isRequired, - searchQuery: PropTypes.string + accountType: PropTypes.string.isRequired, + searchQuery: PropTypes.string, }; export default ApprovedAccounts; diff --git a/src/components/Accounts/PendingAccounts.jsx b/src/components/Accounts/PendingAccounts.jsx index 52e71c8..043f47a 100644 --- a/src/components/Accounts/PendingAccounts.jsx +++ b/src/components/Accounts/PendingAccounts.jsx @@ -1,197 +1,285 @@ import { NPOBackend } from '../../utils/auth_utils.js'; -import { useEffect, useState } from 'react'; -import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Button } from '@chakra-ui/react' -import { Checkbox } from '@chakra-ui/react' +import { useEffect, useState, useRef } from 'react'; +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Button, + Checkbox, + Text, + HStack, + Spacer, +} from '@chakra-ui/react'; import PropTypes from 'prop-types'; -const PendingAccounts = ( {accountType, setHasPendingAccounts} ) => { - const [pendingAccounts, setPendingAccounts] = useState([]); - const [individualChecked, setIndividualChecked] = useState(new Array(pendingAccounts.length).fill(false)); - const [checkedAccountIds, setCheckedAccountIds] = useState([]); - const [accountStatus, setAccountStatus] = useState({}); +const PendingAccounts = ({ accountType, setHasPendingAccounts }) => { + const [pendingAccounts, setPendingAccounts] = useState([]); + const [checkedAccountIds, setCheckedAccountIds] = useState([]); + const [accountStatus, setAccountStatus] = useState({}); + const [timeoutIds, setTimeoutIds] = useState({}); + const [isMultiSelect, setIsMultiSelect] = useState(false); + const statusRef = useRef({}); + const timeoutIdsRef = useRef({}); + useEffect(() => { + const renderTable = async () => { + const { data } = await NPOBackend.get('/users/pending-accounts', { + params: { accountType: accountType }, + }); + setPendingAccounts(data); + setHasPendingAccounts(data.length !== 0); + }; + renderTable(); + }, [accountType, setHasPendingAccounts]); - useEffect(() => { - const renderTable = async () => { - const { data } = await NPOBackend.get('/users/pending-accounts', {params: {accountType: accountType}}); - setPendingAccounts(data); - setHasPendingAccounts(data.length !== 0); - }; - renderTable(); - }, [accountType, setHasPendingAccounts]) + useEffect(() => { + const newAccountStatus = {}; + for (let i = 0; i < pendingAccounts.length; i++) { + newAccountStatus[pendingAccounts[i]['id']] = 'pending'; + } + setAccountStatus(newAccountStatus); + }, [pendingAccounts]); - useEffect(() => { - const newAccountStatus = {} - for (let i = 0; i < pendingAccounts.length; i++) { - newAccountStatus[pendingAccounts[i]["id"]] = "pending"; - } - setAccountStatus(newAccountStatus); - }, [pendingAccounts]) + useEffect(() => { + statusRef.current = accountStatus; + }, [accountStatus]); - const changeStatus = ((accountid, status) => { - let newAccountStatus = {...accountStatus}; - newAccountStatus[accountid] = status; - setAccountStatus(newAccountStatus); - }); + useEffect(() => { + timeoutIdsRef.current = timeoutIds; + }, [timeoutIds]); - const handleApproveDeclineUser = async (ids, option) => { - try { - for (let i = 0; i < ids.length; i++) { - if (option === "approve-option") { - await NPOBackend.put(`/users/approve/${ids[i]}`); - } - else if (option === "decline-option") { - await NPOBackend.delete(`/users/${ids[i]}`); - } - } - setIndividualChecked(new Array(pendingAccounts.length).fill(false)); - } catch (error) { - console.log(error); - } + const undoChanges = (ids) => { + let timeoutKey = ids.toString(); + const {timeoutId, isMulti} = timeoutIds[timeoutKey]; + clearTimeout(timeoutId); + const newTimeoutIds = {...timeoutIds}; + delete newTimeoutIds[timeoutKey]; + setTimeoutIds(newTimeoutIds); + const newAccountStatus = {...accountStatus}; + for (let i = 0; i < ids.length; i++) { + newAccountStatus[ids[i]] = 'pending'; } - - const updateAllIndividualChecked = (e) => { - let newIndividualChecked = []; - for (let i = 0; i < pendingAccounts.length; i++) { - if (accountStatus[pendingAccounts[i].id] === "pending") { - newIndividualChecked.push(e.target.checked); - } - else { - newIndividualChecked.push(false); - } - } - setIndividualChecked(newIndividualChecked); + setAccountStatus(newAccountStatus); + if (isMulti) { + setIsMultiSelect(false); } + } - const updateAllCheckedAccountIds = (e) => { - if (e.target.checked) { - let allIds = []; - for (let i = 0; i < pendingAccounts.length; i++) { - if (accountStatus[pendingAccounts[i].id] === "pending") { - allIds.push(pendingAccounts[i].id); - } - } - setCheckedAccountIds(allIds); - } else { - setCheckedAccountIds([]); + const handleApproveDeclineUser = async (ids, option, isMulti=false) => { + const timeoutId = setTimeout(async () => { + try { + let newCheckedAccountIds = checkedAccountIds; + for (let i = 0; i < ids.length; i++) { + if (option === 'approve-option') { + await NPOBackend.put(`/users/approve/${ids[i]}`); + } else if (option === 'decline-option') { + await NPOBackend.delete(`/users/${ids[i]}`); + } + // removes account that was approved/decline if it is in checked list + let index = newCheckedAccountIds.indexOf(ids[i]); + if (index > -1) { + newCheckedAccountIds.splice(index, 1); + } } + setCheckedAccountIds(newCheckedAccountIds); + } catch (error) { + // error should occur if you try to delete an account that has already been deleted (individual delete then multi-delete) + console.log(error); + // clear checked list if there is error for good measure + setCheckedAccountIds([]); + } + const newAccountStatus = statusRef.current; + for (let i = 0; i < ids.length; i++) { + newAccountStatus[ids[i]] = option === 'approve-option' ? 'approved' : 'declined'; + } + setAccountStatus(newAccountStatus); + const newTimeoutIds = {...timeoutIdsRef.current}; + delete newTimeoutIds[ids.toString()]; + setTimeoutIds(newTimeoutIds); + + if (isMulti) { + setIsMultiSelect(false); + } + }, 5000); + if(isMulti) { + setIsMultiSelect(true); + } + // Initiate waiting state + const waitingAccountStatus = { ...accountStatus }; + for (let i = 0; i < ids.length; i++) { + waitingAccountStatus[ids[i]] = option === 'approve-option' ? 'waiting-approved' : 'waiting-declined'; } + setAccountStatus(waitingAccountStatus); + const newTimeoutIds = {... timeoutIds}; + newTimeoutIds[ids.toString()] = {timeoutId, isMulti}; + setTimeoutIds(newTimeoutIds); + }; - const updateIndividualCheckedAccountIds = (e, id, index) => { - const newIndividualChecked = [...individualChecked]; - newIndividualChecked[index] = e.target.checked; - setIndividualChecked(newIndividualChecked); - let newCheckedAccountIds = [... checkedAccountIds]; - if (e.target.checked) { - newCheckedAccountIds.push(id); - setCheckedAccountIds(newCheckedAccountIds); - } else { - let index = newCheckedAccountIds.indexOf(id); - newCheckedAccountIds.splice(index, 1); - setCheckedAccountIds(newCheckedAccountIds); + const updateAllCheckedAccountIds = e => { + if (e.target.checked) { + let allIds = []; + for (let i = 0; i < pendingAccounts.length; i++) { + if (accountStatus[pendingAccounts[i].id] === 'pending') { + allIds.push(pendingAccounts[i].id); } + } + setCheckedAccountIds(allIds); + } else { + setCheckedAccountIds([]); } + }; - const acceptDeclineAllClick = (option) => { - handleApproveDeclineUser(checkedAccountIds, option); - const newAccountStatus = {... accountStatus}; - for (let i = 0; i < checkedAccountIds.length; i++) { - newAccountStatus[checkedAccountIds[i]] = option === "approve-option" ? "approved" : "declined"; - } - setAccountStatus(newAccountStatus); - setCheckedAccountIds([]); + const updateIndividualCheckedAccountIds = (e, id) => { + let newCheckedAccountIds = [...checkedAccountIds]; + if (e.target.checked) { + newCheckedAccountIds.push(id); + setCheckedAccountIds(newCheckedAccountIds); + } else { + let index = newCheckedAccountIds.indexOf(id); + newCheckedAccountIds.splice(index, 1); + setCheckedAccountIds(newCheckedAccountIds); } + }; - return ( - - - - - - - - - {checkedAccountIds.length > 0 && - - } - - - - { - pendingAccounts.map((account, i) => ( - - - - - {checkedAccountIds.length > 0 && - - } - { - accountStatus[account.id] === "pending" ? ( - - ) : accountStatus[account.id] === "approved" ? ( - - ) : ( - - ) - } - - ) - ) - } - -
{updateAllIndividualChecked(e); updateAllCheckedAccountIds(e);}}/>NameEmailAction - - -
{updateIndividualCheckedAccountIds(e, account.id, i);}}> - - {account.firstName} {account.lastName}{account.email} - - - ApprovedDeclined
-
- ) -} + return ( + + + + + + + + + { isMultiSelect ? ( + + ) : checkedAccountIds.length > 0 && ( + + )} + + + + {pendingAccounts.map((account, i) => ( + + + + + {checkedAccountIds.length > 0 && } + {accountStatus[account.id] === 'pending' ? ( + + ) : ( + + )} + + ))} + +
+ { + updateAllCheckedAccountIds(e); + }} + /> + NameEmail + Action + + + + + +
+ { + updateIndividualCheckedAccountIds(e, account.id); + }} + > + + {account.firstName} {account.lastName} + + {account.email} + + + + + + + {accountStatus[account.id] === 'approved' || accountStatus[account.id] === 'waiting-approved' ? + Approved + : + Declined + } + {account.id in timeoutIds && + + } + +
+
+ ); +}; PendingAccounts.propTypes = { - accountType: PropTypes.string.isRequired, - setHasPendingAccounts: PropTypes.func.isRequired + accountType: PropTypes.string.isRequired, + setHasPendingAccounts: PropTypes.func.isRequired, }; export default PendingAccounts; diff --git a/src/pages/Accounts/Accounts.jsx b/src/pages/Accounts/Accounts.jsx index 8d14e15..4b0cdbd 100644 --- a/src/pages/Accounts/Accounts.jsx +++ b/src/pages/Accounts/Accounts.jsx @@ -1,88 +1,122 @@ -import PendingAccounts from "../../components/Accounts/PendingAccounts"; -import ApprovedAccounts from "../../components/Accounts/ApprovedAccounts"; -import { Box, Heading, Tabs, TabList, TabPanels, Tab, TabPanel, Input, InputGroup, InputLeftElement, HStack, Center } from '@chakra-ui/react' +import PendingAccounts from '../../components/Accounts/PendingAccounts'; +import ApprovedAccounts from '../../components/Accounts/ApprovedAccounts'; +import { + Box, + Heading, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Input, + InputGroup, + InputLeftElement, + HStack, + Spacer, +} from '@chakra-ui/react'; import { SearchIcon } from '@chakra-ui/icons'; import { useState } from 'react'; const Accounts = () => { - const [approvedAdminKeyword, setApprovedAdminKeyword] = useState(""); - const [approvedStudentKeyword, setApprovedStudentKeyword] = useState(""); - const [hasAdminPendingAccounts, setHasAdminPendingAccounts] = useState(true); - const [hasStudentPendingAccounts, setHasStudentPendingAccounts] = useState(true); + const [approvedAdminKeyword, setApprovedAdminKeyword] = useState(''); + const [approvedStudentKeyword, setApprovedStudentKeyword] = useState(''); + const [hasAdminPendingAccounts, setHasAdminPendingAccounts] = useState(true); + const [hasStudentPendingAccounts, setHasStudentPendingAccounts] = useState(true); - return ( - -
- - - Admins - Students - + return ( + + + + Admins + Students + - - - { hasAdminPendingAccounts ? ( - <> - - Pending - - - - ) : <> - } - - Accounts - - - - - - setApprovedAdminKeyword(e.target.value)} - /> - - - - - - - { hasStudentPendingAccounts ? ( - - Pending - - - ): <> - } - - Accounts - - - - - - setApprovedStudentKeyword(e.target.value)} - /> - - - - - - - -
-
- ); -} + + + {hasAdminPendingAccounts ? ( + <> + + + Pending + + + + + ) : ( + <> + )} + + + + Accounts + + + + + + + + + setApprovedAdminKeyword(e.target.value)} + /> + + + + + + + {hasStudentPendingAccounts ? ( + + + Pending + + + + ) : ( + <> + )} + + + + Accounts + + + + + + + + + setApprovedStudentKeyword(e.target.value)} + /> + + + + + + + + + ); +}; export default Accounts;