diff --git a/package-lock.json b/package-lock.json index 89cbd0f1b6..f269e0d053 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8903,6 +8903,12 @@ "@types/node": "*" } }, + "@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -9159,6 +9165,27 @@ "redux": "^4.0.0" } }, + "@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/react-table": { "version": "6.8.10", "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.10.tgz", diff --git a/package.json b/package.json index 31200c93de..410d0a1c69 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^10.4.9", "@testing-library/user-event": "^12.0.14", + "@types/react-router-dom": "^5.3.3", "babel-eslint": "^10.1.0", "cross-env": "^5.2.1", "enzyme": "^3.10.0", diff --git a/src/actions/sendEmails.js b/src/actions/sendEmails.js index fdaf359185..6ff843a9d3 100644 --- a/src/actions/sendEmails.js +++ b/src/actions/sendEmails.js @@ -156,4 +156,4 @@ export const removeNonHgnUserEmailSubscription = async (email = '') => { return { success: false, error: error }; } -}; \ No newline at end of file +}; diff --git a/src/actions/totalOrgSummary.js b/src/actions/totalOrgSummary.js index 994536ad2d..8817212ebb 100644 --- a/src/actions/totalOrgSummary.js +++ b/src/actions/totalOrgSummary.js @@ -32,7 +32,7 @@ export const fetchTotalOrgSummaryReportError = error => ({ export const getTaskAndProjectStats = (startDate, endDate) => { const url = ENDPOINTS.HOURS_TOTAL_ORG_SUMMARY(startDate, endDate); return async dispatch => { - dispatch(fetchTotalOrgSummaryReportBegin()); + await dispatch(fetchTotalOrgSummaryReportBegin()); try { const response = await axios.get(url); dispatch(fetchTotalOrgSummaryReportSuccess(response.data)); diff --git a/src/components/Announcements/index.jsx b/src/components/Announcements/index.jsx index 92cc7160cb..836efab263 100644 --- a/src/components/Announcements/index.jsx +++ b/src/components/Announcements/index.jsx @@ -6,9 +6,10 @@ import { sendEmail, broadcastEmailsToAll } from '../../actions/sendEmails'; import { boxStyle, boxStyleDark } from 'styles'; import { toast } from 'react-toastify'; -function Announcements() { +function Announcements({title, email}) { const darkMode = useSelector(state => state.theme.darkMode); const dispatch = useDispatch(); + const [emailTo, setEmailTo] = useState(''); const [emailList, setEmailList] = useState([]); const [emailContent, setEmailContent] = useState(''); const [headerContent, setHeaderContent] = useState(''); @@ -79,11 +80,19 @@ function Announcements() { content_css: darkMode ? 'dark' : 'default', } + useEffect(() => { + if (email) { + const trimmedEmail = email.trim(); + setEmailTo(email); + setEmailList(trimmedEmail.split(',')); + } + }, [email]); + const handleEmailListChange = e => { const emails = e.target.value.split(','); setEmailList(emails); }; - + const handleHeaderContentChange = e => { setHeaderContent(e.target.value); } @@ -130,22 +139,22 @@ function Announcements() { const handleSendEmails = () => { const htmlContent = emailContent; - + if (emailList.length === 0 || emailList.every(email => !email.trim())) { toast.error('Error: Empty Email List. Please enter AT LEAST One email.'); return; } - + const invalidEmails = emailList.filter(email => !validateEmail(email.trim())); - + if (invalidEmails.length > 0) { toast.error(`Error: Invalid email addresses: ${invalidEmails.join(', ')}`); return; } - - dispatch(sendEmail(emailList.join(','), 'Weekly Update', htmlContent)); + + dispatch(sendEmail(emailList.join(','), title ? 'Anniversary congrats' : 'Weekly update', htmlContent)); }; - + const handleBroadcastEmails = () => { const htmlContent = ` @@ -160,7 +169,12 @@ function Announcements() {
-

Weekly Progress Editor

+ { title ? ( +

{title}

+ ) + :(

Weekly Progress Editor

) + } +
{showEditor && } + { + title ? ( + "" + ) : ( + ) + } +
- - + { + title ? ( +

Email

+ ) : ( + + + ) + } + +
diff --git a/src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.jsx b/src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.jsx new file mode 100644 index 0000000000..a5d40ba10c --- /dev/null +++ b/src/components/TotalOrgSummary/AnniversaryCelebrated/AnniversaryCelebrated.jsx @@ -0,0 +1,100 @@ +import { useHistory } from 'react-router-dom'; +import { getTotalOrgSummary } from 'actions/totalOrgSummary'; +import { useEffect, useState } from 'react'; +import { IoPersonOutline } from 'react-icons/io5'; +import { SiGmail } from 'react-icons/si'; +import { useDispatch } from 'react-redux'; + +export default function AnniversaryCelebrated({ + fromDate, + toDate, + fromOverDate, + toOverDate, + darkMode, +}) { + const dispatch = useDispatch(); + const history = useHistory(); + const [anniversaryStatsOnSetDate, setAnniversaryStatsOnSetDate] = useState([]); + const [anniversaryStatsOnLastDate, setAnniversaryStatsOnLastDate] = useState([]); + const [anniversaryStatsOnSetDateQuantity, setAnniversaryStatsOnSetDateQuantity] = useState(0); + const [anniversaryStatsOnLastDateQuantity, setAnniversaryStatsOnSLastDateQuantity] = useState(0); + const percentageChange = ( + (anniversaryStatsOnSetDateQuantity / anniversaryStatsOnLastDateQuantity - 1) * + 100 + ).toFixed(2); + const isPositive = percentageChange >= 0; + const sign = isPositive ? '+' : ''; + + useEffect(() => { + setAnniversaryStatsOnSetDateQuantity(anniversaryStatsOnSetDate.length); + }, [anniversaryStatsOnSetDate, anniversaryStatsOnSetDateQuantity]); + + useEffect(() => { + setAnniversaryStatsOnSLastDateQuantity(anniversaryStatsOnLastDate.length); + }, [anniversaryStatsOnLastDate, anniversaryStatsOnLastDateQuantity]); + + useEffect(() => { + const fectchOnSetDate = async () => { + const response = await dispatch(getTotalOrgSummary(fromDate, toDate)); + setAnniversaryStatsOnSetDate(response.data.anniversaryStats); + }; + fectchOnSetDate(); + }, [fromDate, toDate]); + + useEffect(() => { + const fectchOnLastDate = async () => { + const res = await dispatch(getTotalOrgSummary(fromOverDate, toOverDate)); + setAnniversaryStatsOnLastDate(res.data.anniversaryStats); + }; + fectchOnLastDate(); + }, [fromOverDate, toOverDate]); + + const handleEmailClick = email => { + history.push('/sendemail', { state: { email } }); + }; + + return ( +
+

+ Anniversary Celebrated +

+ + {sign} + {percentageChange}% week over week + +
    + {Array.isArray(anniversaryStatsOnSetDate) && anniversaryStatsOnSetDate.length > 0 ? ( + anniversaryStatsOnSetDate.map(item => ( +
  • +
    + {item.profilePic ? ( + profile + ) : ( + + )} + handleEmailClick(item.email)} + /> +

    {`${item.firstName} ${item.lastName}`}

    +
    +
  • + )) + ) : ( +

    There are no Anniversaries in this period

    + )} +
+
+ ); +} diff --git a/src/components/TotalOrgSummary/TotalOrgSummary.jsx b/src/components/TotalOrgSummary/TotalOrgSummary.jsx index 751c52f173..a3fa126def 100644 --- a/src/components/TotalOrgSummary/TotalOrgSummary.jsx +++ b/src/components/TotalOrgSummary/TotalOrgSummary.jsx @@ -24,6 +24,7 @@ import HoursCompletedBarChart from './HoursCompleted/HoursCompletedBarChart'; import HoursWorkList from './HoursWorkList/HoursWorkList'; import NumbersVolunteerWorked from './NumbersVolunteerWorked/NumbersVolunteerWorked'; import Loading from '../common/Loading'; +import AnniversaryCelebrated from './AnniversaryCelebrated/AnniversaryCelebrated'; import RoleDistributionPieChart from './VolunteerRolesTeamDynamics/RoleDistributionPieChart'; import WorkDistributionBarChart from './VolunteerRolesTeamDynamics/WorkDistributionBarChart'; @@ -314,12 +315,19 @@ function TotalOrgSummary(props) {
- +

Volunteer Trends by time

+ Work in progres...
- +
diff --git a/src/components/common/EmailSender/EmailSender.jsx b/src/components/common/EmailSender/EmailSender.jsx new file mode 100644 index 0000000000..e2023b5ee6 --- /dev/null +++ b/src/components/common/EmailSender/EmailSender.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import { useLocation } from 'react-router-dom' +import Announcements from 'components/Announcements' + +export const EmailSender = () => { + + const location = useLocation(); + const email = location.state.state.email; + + return ( + + ) +}; diff --git a/src/routes.js b/src/routes.js index 622395923e..0823aed610 100644 --- a/src/routes.js +++ b/src/routes.js @@ -27,6 +27,7 @@ import ForgotPassword from './components/Login/ForgotPassword'; import Inventory from './components/Inventory'; import EmailSubscribeForm from './components/EmailSubscribeForm'; import UnsubscribeForm from './components/EmailSubscribeForm/Unsubscribe'; +import { EmailSender } from './components/common/EmailSender/EmailSender'; import Collaboration from './components/Collaboration'; // BM Dashboard @@ -243,6 +244,13 @@ export default ( component={Announcements} routePermissions={RoutePermissions.announcements} /> +