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 ( - <Box> - <TableContainer border="1px solid #ededed" borderRadius="10px"> - <Table variant='simple'> - <Thead> - <Tr> - <Th w="5%" h="50px"><Checkbox onChange={(e) => { updateAllCheckedAccountIds(e) }}/></Th> - <Th w="30%">Name</Th> - <Th w="30%">Email</Th> - <Th w="0" textAlign="right" >Deactivate</Th> - {checkedAccountIds.length > 0 && - <Th w="20%" textAlign="right"> - <Button isDisabled={checkedAccountIds.length === 0} - onClick={() => { handleDeleteClick(checkedAccountIds) }} - size="xs" - variant="outline" - borderRadius="4px" - borderWidth="1px" - padding="0" - borderColor={checkedAccountIds.length != 0 ? 'red' : 'gray.500'}> - <CloseIcon w="10px" h="10px" color={checkedAccountIds.length != 0 ? 'red' : 'gray'}/> - </Button> - </Th> - } - </Tr> - </Thead> - <Tbody> - { - approvedAccounts.map((account, i) => ( - <Tr key={i}> - <Td> - <Checkbox - isChecked={individualChecked[i]} - onChange={(e) => { updateIndividualCheckedAccountIds(e, account.id, i)}}> - </Checkbox> - </Td> - <Td>{account.firstName} {account.lastName}</Td> - <Td>{account.email}</Td> - {checkedAccountIds.length > 0 && - <Td></Td> - } - <Td textAlign="right"> - <Button - onClick={() => { handleDeleteClick([account.id]) }} - size="xs" - variant="outline" - borderColor="gray.500" - borderRadius="4px" - borderWidth="1px" - padding="0" - > - <CloseIcon w="10px" h="10px" color="gray.500"/> - </Button> - </Td> - </Tr> - )) - } - </Tbody> - </Table> - <PaginationFooter - pagesCount={pagesCount} - totalRowCount={totalRowCount} - setPageSize={setPageSize} - currentPage={currentPage} - setCurrentPage={setCurrentPage} - rangeString={`${offset + 1} - ${offset + approvedAccounts.length}`} - /> - </TableContainer> - <DeleteAccountModal - isOpen={isDeleteOpen} - onClose={onDeleteClose} - deleteItemId={deleteItemId} - setDataShouldRevalidate={setDataShouldRevalidate} - /> - </Box> - ) -} + return ( + <Box> + <TableContainer border="1px solid #ededed" borderRadius="10px"> + <Table variant="simple"> + <Thead> + <Tr> + <Th w="5%" h="50px"> + <Checkbox + isChecked={isSelectAll} + onChange={e => { + updateAllCheckedAccountIds(e); + }} + /> + </Th> + <Th w="30%">Name</Th> + <Th w="30%">Email</Th> + <Th w="0" textAlign="right"> + Deactivate + </Th> + {checkedAccountIds.length > 0 && ( + <Th w="20%" textAlign="right"> + <Button + isDisabled={checkedAccountIds.length === 0} + onClick={() => { + handleDeleteClick(checkedAccountIds); + }} + size="xs" + variant="outline" + borderRadius="4px" + borderWidth="1px" + padding="0" + borderColor={checkedAccountIds.length != 0 ? 'red' : 'gray.500'} + > + <CloseIcon + w="10px" + h="10px" + color={checkedAccountIds.length != 0 ? 'red' : 'gray'} + /> + </Button> + </Th> + )} + </Tr> + </Thead> + <Tbody> + {approvedAccounts.map((account, i) => ( + <Tr key={i}> + <Td> + <Checkbox + isDisabled={account.id === currentUser.id} + isChecked={individualChecked[i]} + onChange={e => { + updateIndividualCheckedAccountIds(e, account.id, i); + }} + ></Checkbox> + </Td> + <Td color={account.id === currentUser.id ? 'gray' : 'black'}> + {account.firstName} {account.lastName} + </Td> + <Td color={account.id === currentUser.id ? 'gray' : 'black'}>{account.email}</Td> + {checkedAccountIds.length > 0 && <Td></Td>} + <Td textAlign="right"> + <Button + onClick={() => { + handleDeleteClick([account.id]); + }} + size="xs" + variant="outline" + borderColor="gray.500" + borderRadius="4px" + borderWidth="1px" + padding="0" + isDisabled={account.id === currentUser.id} + > + <CloseIcon w="10px" h="10px" color="gray.500" /> + </Button> + </Td> + </Tr> + ))} + </Tbody> + </Table> + <PaginationFooter + pagesCount={pagesCount} + totalRowCount={totalRowCount} + setPageSize={setPageSize} + currentPage={currentPage} + setCurrentPage={setCurrentPage} + rangeString={`${offset + 1} - ${offset + approvedAccounts.length}`} + /> + </TableContainer> + <DeleteAccountModal + isOpen={isDeleteOpen} + onClose={onDeleteClose} + deleteItemId={deleteItemId} + setDataShouldRevalidate={setDataShouldRevalidate} + /> + </Box> + ); +}; 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 ( - <TableContainer border="1px solid #ededed" borderRadius="10px"> - <Table variant='simple'> - <Thead> - <Tr> - <Th w="5%" h="50px"><Checkbox onChange={(e) => {updateAllIndividualChecked(e); updateAllCheckedAccountIds(e);}}/></Th> - <Th w="30%">Name</Th> - <Th w="30%">Email</Th> - <Th w="0" textAlign="right">Action</Th> - {checkedAccountIds.length > 0 && - <Th w="20%" textAlign="right"> - <Button - onClick={ () => acceptDeclineAllClick("approve-option") } - mr={3} - colorScheme='blue' - fontSize="sm" - h="6" - w="16" - fontWeight="400" - > - Accept - </Button> - <Button - onClick={ () => acceptDeclineAllClick("decline-option") } - fontSize="sm" - h="6" - w="16" - fontWeight="400" - > - Decline - </Button> - </Th> - } - </Tr> - </Thead> - <Tbody> - { - pendingAccounts.map((account, i) => ( - <Tr key={i}> - <Td><Checkbox isDisabled={accountStatus[account.id] != "pending"} isChecked={individualChecked[i]} onChange={(e) => {updateIndividualCheckedAccountIds(e, account.id, i);}}> - </Checkbox> - </Td> - <Td color={accountStatus[account.id] === "pending" ? "black" : "gray"}>{account.firstName} {account.lastName}</Td> - <Td color={accountStatus[account.id] === "pending" ? "black" : "gray"}>{account.email}</Td> - {checkedAccountIds.length > 0 && - <Td></Td> - } - { - accountStatus[account.id] === "pending" ? ( - <Td textAlign="right"> - <Button - onClick={() => { handleApproveDeclineUser([account.id], "approve-option"); changeStatus(account.id, "approved");}} - mr={3} - colorScheme='blue' - fontSize="sm" - h="6" - w="16" - fontWeight="400" - > - Accept - </Button> - <Button - onClick={() => { handleApproveDeclineUser([account.id], "decline-option"); changeStatus(account.id, "declined");}} - fontSize="sm" - h="6" - w="16" - fontWeight="400" - > - Decline - </Button> - </Td> - ) : accountStatus[account.id] === "approved" ? ( - <Td textAlign="right" color="green">Approved</Td> - ) : ( - <Td textAlign="right" color="red">Declined</Td> - ) - } - </Tr> - ) - ) - } - </Tbody> - </Table> - </TableContainer> - ) -} + return ( + <TableContainer border="1px solid #ededed" borderRadius="10px"> + <Table variant="simple"> + <Thead> + <Tr> + <Th w="5%" h="50px"> + <Checkbox + onChange={e => { + updateAllCheckedAccountIds(e); + }} + /> + </Th> + <Th w="30%">Name</Th> + <Th w="30%">Email</Th> + <Th w="0" textAlign="right"> + Action + </Th> + { isMultiSelect ? ( + <Th w="20%" textAlign="right"> + <Button + onClick={() => { undoChanges([...checkedAccountIds]) }} + fontSize="sm" + h="6" + w="16" + fontWeight="400" + > + Undo + </Button> + </Th> + ) : checkedAccountIds.length > 0 && ( + <Th w="20%" textAlign="right"> + <Button + onClick={() => handleApproveDeclineUser([...checkedAccountIds], 'approve-option', true)} + mr={3} + colorScheme="blue" + fontSize="sm" + h="6" + w="16" + fontWeight="400" + > + Accept + </Button> + <Button + onClick={() => handleApproveDeclineUser([...checkedAccountIds], 'decline-option', true)} + fontSize="sm" + h="6" + w="16" + fontWeight="400" + > + Decline + </Button> + </Th> + )} + </Tr> + </Thead> + <Tbody> + {pendingAccounts.map((account, i) => ( + <Tr key={i}> + <Td> + <Checkbox + isDisabled={accountStatus[account.id] != 'pending'} + isChecked={checkedAccountIds.includes(account.id)} + onChange={e => { + updateIndividualCheckedAccountIds(e, account.id); + }} + ></Checkbox> + </Td> + <Td color={accountStatus[account.id] === 'pending' ? 'black' : 'gray'}> + {account.firstName} {account.lastName} + </Td> + <Td color={accountStatus[account.id] === 'pending' ? 'black' : 'gray'}> + {account.email} + </Td> + {checkedAccountIds.length > 0 && <Td></Td>} + {accountStatus[account.id] === 'pending' ? ( + <Td textAlign="right"> + <Button + onClick={() => { + handleApproveDeclineUser([account.id], 'approve-option'); + }} + mr={3} + colorScheme="blue" + fontSize="sm" + h="6" + w="16" + fontWeight="400" + > + Accept + </Button> + <Button + onClick={() => { + handleApproveDeclineUser([account.id], 'decline-option'); + }} + fontSize="sm" + h="6" + w="16" + fontWeight="400" + > + Decline + </Button> + </Td> + ) : ( + <Td> + <HStack> + <Spacer /> + {accountStatus[account.id] === 'approved' || accountStatus[account.id] === 'waiting-approved' ? + <Text color="green">Approved</Text> + : + <Text color="red">Declined</Text> + } + {account.id in timeoutIds && + <Button + onClick={() => { undoChanges([account.id]) }} + fontSize="sm" + h="6" + w="16" + fontWeight="400" + > + Undo + </Button> + } + </HStack> + </Td> + )} + </Tr> + ))} + </Tbody> + </Table> + </TableContainer> + ); +}; 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 ( - <Box marginTop='4vh' marginBottom='8vh'> - <Center> - <Tabs> - <TabList marginBottom='2vh'> - <Tab>Admins</Tab> - <Tab>Students</Tab> - </TabList> + return ( + <Box margin="4vh 10vw 8vh 10vw"> + <Tabs> + <TabList marginBottom="2vh"> + <Tab>Admins</Tab> + <Tab>Students</Tab> + </TabList> - <TabPanels> - <TabPanel> - { hasAdminPendingAccounts ? ( - <> - <Box marginBottom="6vh"> - <Heading marginBottom="2vh" fontSize="24px">Pending</Heading> - <PendingAccounts accountType="admin" setHasPendingAccounts={setHasAdminPendingAccounts}/> - </Box> - </> - ) : <></> - } - <HStack spacing='90vh' align="start"> - <Box><Heading marginBottom="2vh" fontSize="24px">Accounts</Heading></Box> - <Box> - <InputGroup width="315px"> - <InputLeftElement pointerEvents="none" h="full"> - <SearchIcon /> - </InputLeftElement> - <Input - type="text" - placeholder="Search..." - variant="filled" - bgColor="blackAlpha.200" - h="30px" - onChange={ (e) => setApprovedAdminKeyword(e.target.value)} - /> - </InputGroup> - </Box> - </HStack> - <ApprovedAccounts accountType="admin" searchQuery={approvedAdminKeyword}/> - </TabPanel> - <TabPanel> - { hasStudentPendingAccounts ? ( - <Box marginBottom="6vh"> - <Heading marginBottom="2vh" fontSize="24px">Pending</Heading> - <PendingAccounts accountType="student" setHasPendingAccounts={setHasStudentPendingAccounts} marginBottom="4vh" /> - </Box> - ): <></> - } - <HStack spacing='90vh' align="start"> - <Box><Heading marginBottom="2vh" fontSize="24px">Accounts</Heading></Box> - <Box> - <InputGroup width="315px"> - <InputLeftElement pointerEvents="none" h="full"> - <SearchIcon /> - </InputLeftElement> - <Input - type="text" - placeholder="Search..." - variant="filled" - bgColor="blackAlpha.200" - h="30px" - onChange={ (e) => setApprovedStudentKeyword(e.target.value)} - /> - </InputGroup> - </Box> - </HStack> - <Box><ApprovedAccounts accountType="student" searchQuery={approvedStudentKeyword}/></Box> - </TabPanel> - </TabPanels> - </Tabs> - </Center> - </Box> - ); -} + <TabPanels> + <TabPanel> + {hasAdminPendingAccounts ? ( + <> + <Box marginBottom="6vh"> + <Heading marginBottom="2vh" fontSize="24px"> + Pending + </Heading> + <PendingAccounts + accountType="admin" + setHasPendingAccounts={setHasAdminPendingAccounts} + /> + </Box> + </> + ) : ( + <></> + )} + <HStack align="start"> + <Box> + <Heading marginBottom="2vh" fontSize="24px"> + Accounts + </Heading> + </Box> + <Spacer /> + <Box> + <InputGroup width="20vw"> + <InputLeftElement pointerEvents="none" h="full"> + <SearchIcon /> + </InputLeftElement> + <Input + type="text" + placeholder="Search..." + variant="filled" + bgColor="blackAlpha.200" + h="30px" + onChange={e => setApprovedAdminKeyword(e.target.value)} + /> + </InputGroup> + </Box> + </HStack> + <ApprovedAccounts accountType="admin" searchQuery={approvedAdminKeyword} /> + </TabPanel> + <TabPanel> + {hasStudentPendingAccounts ? ( + <Box marginBottom="6vh"> + <Heading marginBottom="2vh" fontSize="24px"> + Pending + </Heading> + <PendingAccounts + accountType="student" + setHasPendingAccounts={setHasStudentPendingAccounts} + marginBottom="4vh" + /> + </Box> + ) : ( + <></> + )} + <HStack align="start"> + <Box> + <Heading marginBottom="2vh" fontSize="24px"> + Accounts + </Heading> + </Box> + <Spacer /> + <Box> + <InputGroup width="20vw"> + <InputLeftElement pointerEvents="none" h="full"> + <SearchIcon /> + </InputLeftElement> + <Input + type="text" + placeholder="Search..." + variant="filled" + bgColor="blackAlpha.200" + h="30px" + onChange={e => setApprovedStudentKeyword(e.target.value)} + /> + </InputGroup> + </Box> + </HStack> + <ApprovedAccounts accountType="student" searchQuery={approvedStudentKeyword} /> + </TabPanel> + </TabPanels> + </Tabs> + </Box> + ); +}; export default Accounts;