@@ -66,7 +116,7 @@ const AccountNotification = ({ notificationBlock, today, removeEntry }) => {
{/* No accordion for 1 account notification */}
- {accounts?.[0]?.email}{' '}
+ {accounts?.[0]?.firstName} ({accounts?.[0]?.email}){' '}
is requesting account approval...
@@ -81,6 +131,9 @@ const AccountNotification = ({ notificationBlock, today, removeEntry }) => {
declineCallback={() => {
declineAll(accounts);
}}
+ undoCallback={() => {
+ undoAll(accounts);
+ }}
pl="3.25rem"
/>
>
@@ -89,7 +142,7 @@ const AccountNotification = ({ notificationBlock, today, removeEntry }) => {
{/* Accordion for >1 account notification in block */}
- {accounts?.[0]?.email} and {accounts?.length - 1} other
+ {accounts?.[0]?.firstName} and {accounts?.length - 1} other
{accounts?.length - 1 > 1 && 's'}{' '}
@@ -118,15 +171,22 @@ const AccountNotification = ({ notificationBlock, today, removeEntry }) => {
setDisableChildrenButtons(true);
declineAll(accounts);
}}
+ undoCallback={() => {
+ setDisableChildrenButtons(false);
+ undoAll(accounts);
+ }}
/>
- {accounts?.map(({ id, email, approveCallback, declineCallback }) => (
+ {accounts?.map(({ id, firstName, email, approveCallback, declineCallback, undoCallback }) => (
-
- {email}
+
+ {firstName}{' '}
+
+
+ ({email})
@@ -135,27 +195,12 @@ const AccountNotification = ({ notificationBlock, today, removeEntry }) => {
declineText="Decline"
acceptCallback={async () => {
await approveCallback();
- toast({
- title: `Approved ${email}.`,
- status: 'success',
- duration: 9000,
- isClosable: true,
- });
- setAccounts(accounts =>
- accounts.filter(account => account.id !== id),
- );
}}
declineCallback={async () => {
await declineCallback();
- toast({
- title: `Declined ${email}.`,
- status: 'info',
- duration: 9000,
- isClosable: true,
- });
- setAccounts(accounts =>
- accounts.filter(account => account.id !== id),
- );
+ }}
+ undoCallback={() => {
+ undoCallback();
}}
disableChildrenButtons={disableChildrenButtons}
/>
@@ -185,6 +230,11 @@ AccountNotification.propTypes = {
notificationBlock: PropTypes.instanceOf(AccountNotificationBlock),
today: PropTypes.instanceOf(Date),
removeEntry: PropTypes.func,
+ approveAfterTimer: PropTypes.bool,
+ idToRemove: PropTypes.string,
+ setApproveAfterTimer: PropTypes.func,
+ declineAfterTimer: PropTypes.bool,
+ setDeclineAfterTimer: PropTypes.func,
};
const AccountButtonGroup = ({
@@ -192,47 +242,96 @@ const AccountButtonGroup = ({
acceptCallback,
declineText,
declineCallback,
+ undoCallback,
disableChildrenButtons,
...chakraProps
}) => {
const [acceptState, setAcceptState] = useState(undefined);
const [declineState, setDeclineState] = useState(undefined);
+ const [approveClick, setApproveClicked] = useState(false);
+ const [declineClick, setDeclineClicked] = useState(false);
return (
-
-
-
-
+ <>
+ {!approveClick && !declineClick ? (
+
+
+
+
+ ) : (
+ <>
+ {approveClick ? (
+
+
+ Accepted
+ {
+ setAcceptState(undefined);
+ setDeclineState(undefined);
+ setApproveClicked(false);
+ undoCallback();
+ }}
+ >
+ Undo
+
+
+
+ ) : (
+
+
+ Declined
+ {
+ setAcceptState(undefined);
+ setDeclineState(undefined);
+ setDeclineClicked(false);
+ undoCallback();
+ }}
+ >
+ Undo
+
+
+
+ )}
+ >
+ )}
+ >
);
};
AccountButtonGroup.propTypes = {
@@ -240,6 +339,7 @@ AccountButtonGroup.propTypes = {
acceptCallback: PropTypes.func,
declineText: PropTypes.string,
declineCallback: PropTypes.func,
+ undoCallback: PropTypes.func,
disableChildrenButtons: PropTypes.bool,
chakraProps: PropTypes.any,
};
diff --git a/src/components/Notifications/NotificationElement.js b/src/components/Notifications/NotificationElement.js
index a0d0967..0d6c8f4 100644
--- a/src/components/Notifications/NotificationElement.js
+++ b/src/components/Notifications/NotificationElement.js
@@ -30,12 +30,14 @@ class AccountNotificationBlock extends NotificationBlock {
this.pendingAccounts = [];
}
- addPendingAccount(id, email, approveCallback, declineCallback) {
+ addPendingAccount(id, firstName, email, approveCallback, declineCallback, undoCallback) {
this.pendingAccounts.push({
id: id,
+ firstName: firstName,
email: email,
approveCallback: approveCallback,
declineCallback: declineCallback,
+ undoCallback: undoCallback,
});
}
diff --git a/src/components/Notifications/Notifications.jsx b/src/components/Notifications/Notifications.jsx
index b9f76cf..88d493c 100644
--- a/src/components/Notifications/Notifications.jsx
+++ b/src/components/Notifications/Notifications.jsx
@@ -18,22 +18,44 @@ const getDateFromISOString = isoString => {
const Notifications = () => {
const [notificationList, setNotificationList] = useState([]);
+ const [approveAfterTimer, setApproveAfterTimer] = useState(false);
+ const [declineAfterTimer, setDeclineAfterTimer] = useState(false);
+ const [idToRemove, setidToRemove] = useState(undefined);
+ let timeoutId = undefined;
+
const today = useMemo(() => new Date(), []);
+ const undoChanges = () => {
+ clearTimeout(timeoutId);
+ timeoutId = undefined;
+ };
+
const approveAccount = async id => {
- try {
- await NPOBackend.put(`/users/approve/${id}`);
- } catch (e) {
- console.log(e);
- }
+ // Start timer
+ const timeId = setTimeout(async () => {
+ try {
+ await NPOBackend.put(`/users/approve/${id}`);
+ } catch (e) {
+ console.log(e);
+ }
+ setidToRemove(id);
+ setApproveAfterTimer(true);
+ }, 5000); // 5 second delay
+ timeoutId = timeId;
};
const declineAccount = async id => {
- try {
- await NPOBackend.delete(`/users/${id}`);
- } catch (e) {
- console.log(e);
- }
+ // Start timer
+ const timeId = setTimeout(async () => {
+ try {
+ await NPOBackend.delete(`/users/${id}`);
+ } catch (e) {
+ console.log(e);
+ }
+ setidToRemove(id);
+ setDeclineAfterTimer(true);
+ }, 5000); // 5 second delay
+ timeoutId = timeId;
};
const removeNotificationEntry = key => {
@@ -53,7 +75,7 @@ const Notifications = () => {
// Map MM-DD-YY string to AccountNotificationBlock
const accountsMap = new Map();
- pendingAccounts.forEach(({ approvedOn, email, id }) => {
+ pendingAccounts.forEach(({ approvedOn, email, id, firstName }) => {
const { dateObject, formattedDateString } = getDateFromISOString(approvedOn);
let notificationBlock;
@@ -68,6 +90,7 @@ const Notifications = () => {
notificationBlock.addPendingAccount(
id,
+ firstName,
email,
async () => {
await approveAccount(id);
@@ -75,6 +98,7 @@ const Notifications = () => {
async () => {
await declineAccount(id);
},
+ undoChanges,
);
accountsMap.set(formattedDateString, notificationBlock);
});
@@ -125,6 +149,11 @@ const Notifications = () => {
notificationBlock={notificationBlock}
today={today}
removeEntry={removeNotificationEntry}
+ approveAfterTimer={approveAfterTimer}
+ idToRemove={idToRemove}
+ setApproveAfterTimer={setApproveAfterTimer}
+ declineAfterTimer={declineAfterTimer}
+ setDeclineAfterTimer={setDeclineAfterTimer}
/>
)}
{notificationType === 'event' && (
diff --git a/src/components/Planner/PlannerEvents/PlannerEvents.jsx b/src/components/Planner/PlannerEvents/PlannerEvents.jsx
index 48fc1a4..83e5210 100644
--- a/src/components/Planner/PlannerEvents/PlannerEvents.jsx
+++ b/src/components/Planner/PlannerEvents/PlannerEvents.jsx
@@ -128,9 +128,8 @@ const PlannerEvents = ({ onClose }) => {
isDisabled={!plannedEvents.length}
onClick={closeModal}
>
- Finish Day
+ Save and Exit
-
diff --git a/src/components/StatTable/StatTable.css b/src/components/StatTable/StatTable.css
new file mode 100644
index 0000000..a3f220d
--- /dev/null
+++ b/src/components/StatTable/StatTable.css
@@ -0,0 +1,57 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.select-container {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ display: flex;
+ justify-content: center;
+}
+
+.table-container {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 1px solid #e1e4e8;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.table-container th {
+ background-color: #0e509c;
+ color: white;
+ padding: 15px;
+ text-align: center;
+}
+
+.table-container tbody td {
+ border-bottom: 1px solid #e1e4e8;
+ padding: 15px;
+ text-align: center;
+}
+
+.table-container tbody tr:last-child td:not(:first-child) {
+ background-color: #f0f8ff;
+ color: blue;
+}
+
+.table-container tbody tr:not(:last-child) td:last-child {
+ background-color: #f0f8ff;
+ color: blue;
+}
+
+.table-container tbody tr:last-child td:last-child {
+ border: none;
+}
+
+.table-body tr:nth-child(even) {
+ background-color: #f8f9fa;
+}
+
+.table-body tr:hover {
+ background-color: #e2f3ff;
+ transition: background-color 0.3s ease;
+}
diff --git a/src/components/StatTable/StatTable.jsx b/src/components/StatTable/StatTable.jsx
new file mode 100644
index 0000000..12d4837
--- /dev/null
+++ b/src/components/StatTable/StatTable.jsx
@@ -0,0 +1,100 @@
+import { useState, useEffect } from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
+import { NPOBackend } from '../../utils/auth_utils';
+import { Box, Select } from '@chakra-ui/react';
+import './StatTable.css';
+
+const StatTable = ({ season, allSeasons }) => {
+ const [stats, setStats] = useState([]);
+ const [selectedSeason, setSelectedSeason] = useState(season);
+
+ useEffect(() => {
+ const fetchStats = async () => {
+ try {
+ const curSeason = selectedSeason.split(' ')[0];
+ const curYear = selectedSeason.split(' ')[1];
+ const response = await NPOBackend.get(`/published-schedule/stats?season=${curSeason.toLowerCase()}&year=${curYear}`);
+ const data = response.data;
+ setStats(data);
+ } catch (error) {
+ console.error('Error fetching statistics:', error);
+ }
+ };
+
+ fetchStats();
+ }, [selectedSeason]);
+
+ const transformData = () => {
+ const transformedData = {};
+
+ stats.forEach(stat => {
+ const eventType = stat.eventType;
+ const subject = stat.subject;
+ const totalCount = parseInt(stat.totalCount);
+
+ if (!transformedData[eventType]) {
+ transformedData[eventType] = {};
+ }
+
+ transformedData[eventType][subject] = totalCount;
+ });
+
+ return transformedData;
+ };
+
+ const transformedStats = transformData();
+
+ return (
+