From 29996c2282fc93ed66b05d7290c389cb5a2b098e Mon Sep 17 00:00:00 2001 From: William Li <52115161+Trollermaner@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:45:29 -0500 Subject: [PATCH 1/5] made status change on click and a confirmation modal --- frontend/src/components/ConfirmationModal.js | 45 +++++++++ .../TicketContent/SFAdminContentTable.js | 96 ++++++++++++++++--- frontend/src/constants.js | 2 +- 3 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/ConfirmationModal.js diff --git a/frontend/src/components/ConfirmationModal.js b/frontend/src/components/ConfirmationModal.js new file mode 100644 index 0000000..7bd7720 --- /dev/null +++ b/frontend/src/components/ConfirmationModal.js @@ -0,0 +1,45 @@ +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, + Button, +} from '@chakra-ui/react' + +const ConfirmationModal = ({ onConfirm, isOpen, onClose }) => { + return ( + + + + Submission (Placeholder) + + +
+ Are you sure you want to submit this, you cant undo bla + bla bla (PLACEHOLDER) +
+
+ + + + + +
+
+ ) +} + +export default ConfirmationModal diff --git a/frontend/src/components/TicketContent/SFAdminContentTable.js b/frontend/src/components/TicketContent/SFAdminContentTable.js index 756f0b2..33e6c1f 100644 --- a/frontend/src/components/TicketContent/SFAdminContentTable.js +++ b/frontend/src/components/TicketContent/SFAdminContentTable.js @@ -1,11 +1,66 @@ -import { Button, Center, Heading, VStack, Link } from '@chakra-ui/react' +import { + Button, + Center, + Heading, + VStack, + Link, + useDisclosure, +} from '@chakra-ui/react' import React from 'react' import { useGetPreserveParamsHref } from '../../hooks/hooks' import { useGetCurrentTicket } from '../../hooks/hooks' +import { TICKET_ENDPOINTS } from '../../constants' +import { useSetRecoilState } from 'recoil' +import { allTicketsState } from '../../state/atoms' +import { axiosPreset } from '../../axiosConfig' +import { getAllTickets } from '../../utils/globalSetters' +import ConfirmationModal from '../ConfirmationModal' const SFAdminContentTable = () => { const getPreserveParamsHref = useGetPreserveParamsHref() const currentTicket = useGetCurrentTicket() + const setAllTickets = useSetRecoilState(allTicketsState) + const { + isOpen: isConfirmationOpen, + onOpen: onOpenConfirmation, + onClose: onCloseConfirmation, + } = useDisclosure() + + const transitionStatusText = (status) => { + if (status === 'ALLOCATED') { + return 'Submit Claim' + } + if (status === 'CLAIM_SUBMITTED') { + return 'Confirm Reimbursement Submission' + } + if (status === 'SUBMITTED_TO_SF') { + return 'Confirm Reimbursement' + } + if (status === 'REIMBURSED') { + return 'Reimbursement Confirmed' + } + } + const nextStatus = (status) => { + if (status === 'ALLOCATED') { + return 'CLAIM_SUBMITTED' + } + if (status === 'CLAIM_SUBMITTED') { + return 'SUBMITTED_TO_SF' + } + if (status === 'SUBMITTED_TO_SF') { + return 'REIMBURSED' + } + } + const handleUpdateStatus = async (nextStatus) => { + const payload = { + status: nextStatus, + } + await axiosPreset.patch( + `${TICKET_ENDPOINTS.SF}/${currentTicket._id}`, + payload + ) + await getAllTickets(setAllTickets) + } return ( { Admin View {
- + {isConfirmationOpen && ( + + handleUpdateStatus( + nextStatus(currentTicket.status) + ) + } + /> + )} + {currentTicket.status !== 'REIMBURSED' && ( + + )} {/* can remove getPreserveParamsHref if it does not make sense to preserve params */} Date: Sat, 30 Dec 2023 21:07:43 -0500 Subject: [PATCH 2/5] added Sponsorship fund email flow --- backend/emails/emails.js | 67 ++++++++++++++++++++- backend/models/constants.js | 2 +- backend/service/sponsorshipfunds.service.js | 22 ++++++- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/backend/emails/emails.js b/backend/emails/emails.js index 73440e4..8b12c7c 100644 --- a/backend/emails/emails.js +++ b/backend/emails/emails.js @@ -15,7 +15,7 @@ const currencyFormatter = new Intl.NumberFormat('en-CA', { }) const getEmailToSection = async (reporter_id, recipients) => { - const emailToSet = new Set(['jw4he@watonomous.ca', 'v2zheng@watonomous.ca']) + const emailToSet = new Set(['william.li@watonomous.ca']) if (recipients.includes(EMAIL_RECIPIENTS.admin)) { // TODO: use ADMIN_IDENTIFIERS (rename to ADMIN_EMAILS) after migrating to new onboarding data @@ -86,6 +86,24 @@ const getUPRTicketInfoHTML = async (upr) => { ` } +const getSFTicketInfoHTML = async (sf) => { + const reporter = await getUserByUID(sf.reporter_id) + return ` +

+ Ticket Code: ${sf.code}
+ Sponsorship Fund: ${sf.name}
+ Allocated Funding: ${sf.funding_allocation}
+ Funding Spent: ${sf.funding_spent}
+ ${sf.proposal_url? `Proposal URL: ${sf.proposal_url}
` : ``} + ${sf.presentation_url? `Presentation URL: ${sf.presentation_url}
` : ``} + Status: ${sf.status}
+ Reporter: ${reporter.displayName} <${reporter.email}>
+ Created: ${new Date(sf.createdAt).toDateString()}
+ Claim Deadline: ${new Date(sf.claim_deadline).toDateString()}
+

+ ` +} + const getTicketLinkHTML = (ticketPath) => `

View the ticket here: @@ -255,6 +273,50 @@ const sendEmailPPRReimbursedToReporter = async (ppr) => { }) } +const sendEmailSFReimbursementRequestToCoordinator = async (sf) => { + const Subject = `[Action Needed] Submit Reimbursement Request ${sf.codename}` + const HTMLPart = getMainMessageHTML(`Claim has been submitted for ${sf.codename}! Please review it and submit a reimbursement request. Visit the ticket link below to confirm you have submitted the reimbursement request.`) + + (await getSFTicketInfoHTML(sf)) + getTicketLinkHTML(sf.path) + const To = await getEmailToSection(sf.reporter_id, [ + EMAIL_RECIPIENTS.coordinator, + ]) + await sendEmail({ + Subject, + HTMLPart, + To, + }) +} + +const sendEmailSFConfirmReimbursementSubmitToCoordinator = async (sf) => { + const Subject = `[Action Needed] Confirm Reimbursement Received ${sf.codename}` + const HTMLPart = getMainMessageHTML(`Please visit the ticket link below to confirm you have received the reimbursement for ${sf.codename}.`) + + (await getSFTicketInfoHTML(sf)) + getTicketLinkHTML(sf.path) + const To = await getEmailToSection(sf.reporter_id, [ + EMAIL_RECIPIENTS.coordinator, + ]) + await sendEmail({ + Subject, + HTMLPart, + To, + }) +} + +const sendEmailSFReimbursementReceivedToTeam = async (sf) => { + const Subject = `[Reimbursed] ${sf.codename}` + const HTMLPart = getMainMessageHTML(`${sf.codename} has been reimbursed.`) + + (await getSFTicketInfoHTML(sf)) + getTicketLinkHTML(sf.path) + const To = await getEmailToSection(sf.reporter_id, [ + EMAIL_RECIPIENTS.finance, + EMAIL_RECIPIENTS.coordinator, + EMAIL_RECIPIENTS.team_captain, + ]) + await sendEmail({ + Subject, + HTMLPart, + To, + }) +} + const PurchaseRequestInvalidated = (purchaseRequestDetails) => { const { issue, reporter } = purchaseRequestDetails @@ -584,6 +646,9 @@ module.exports = { sendEmailPPRCreatedToApprovers, sendEmailPPRPurchasedAndReceiptsSubmittedToCoordinator, sendEmailPPRReimbursedToReporter, + sendEmailSFReimbursementRequestToCoordinator, + sendEmailSFConfirmReimbursementSubmitToCoordinator, + sendEmailSFReimbursementReceivedToTeam, PurchaseRequestInvalidated, PersonalPurchaseApproved, UWFinancePurchaseApproved, diff --git a/backend/models/constants.js b/backend/models/constants.js index b28a442..834901d 100644 --- a/backend/models/constants.js +++ b/backend/models/constants.js @@ -46,7 +46,7 @@ const TICKET_ENDPOINTS = Object.freeze({ UPR: '/uwfinancepurchases', }) -const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he'] +const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he', 'william.li'] const TEAM_CAPTAIN_TITLES = ['Team Captain'] const DIRECTOR_TITLES = ['Director'] diff --git a/backend/service/sponsorshipfunds.service.js b/backend/service/sponsorshipfunds.service.js index 3ab1bde..e07f079 100644 --- a/backend/service/sponsorshipfunds.service.js +++ b/backend/service/sponsorshipfunds.service.js @@ -6,6 +6,7 @@ const { getAnnotatedUWFinancePurchasesByIdList, getAnnotatedSponsorshipFundsByIdList, } = require('./annotatedGetters') +const { sendEmailSFReimbursementRequestToCoordinator, sendEmailSFConfirmReimbursementSubmitToCoordinator, sendEmailSFReimbursementReceivedToTeam } = require('../emails/emails') const getAllSponsorshipFunds = () => { return getAnnotatedSponsorshipFundsByIdList() @@ -49,10 +50,27 @@ const createSponsorshipFund = (body) => { return newSponsorshipFund.save() } -const updateSponsorshipFund = (id, body) => { - return SponsorshipFund.findByIdAndUpdate(id, body, { +const updateSponsorshipFund = async (id, body) => { + const newSponsorshipFund = await SponsorshipFund.findByIdAndUpdate(id, body, { new: true, }) + + const annotatedSponsorshipFund = await getSponsorshipFund(id) + const status = annotatedSponsorshipFund.status + + if (status === "CLAIM_SUBMITTED") { + sendEmailSFReimbursementRequestToCoordinator(annotatedSponsorshipFund) + } + + if (status === "SUBMITTED_TO_SF") { + sendEmailSFConfirmReimbursementSubmitToCoordinator(annotatedSponsorshipFund) + } + + if (status === "REIMBURSED") { + sendEmailSFReimbursementReceivedToTeam(annotatedSponsorshipFund) + } + + return newSponsorshipFund } const deleteSponsorshipFund = async (id) => { From f528c893e04b3488b3329700f6277bd2403cabfe Mon Sep 17 00:00:00 2001 From: William Li <52115161+Trollermaner@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:10:11 -0500 Subject: [PATCH 3/5] ran prettier --- backend/emails/emails.js | 30 +++++++++++++++------ backend/service/sponsorshipfunds.service.js | 28 ++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/backend/emails/emails.js b/backend/emails/emails.js index 8b12c7c..0308f65 100644 --- a/backend/emails/emails.js +++ b/backend/emails/emails.js @@ -94,8 +94,12 @@ const getSFTicketInfoHTML = async (sf) => { Sponsorship Fund: ${sf.name}
Allocated Funding: ${sf.funding_allocation}
Funding Spent: ${sf.funding_spent}
- ${sf.proposal_url? `Proposal URL: ${sf.proposal_url}
` : ``} - ${sf.presentation_url? `Presentation URL: ${sf.presentation_url}
` : ``} + ${sf.proposal_url ? `Proposal URL: ${sf.proposal_url}
` : ``} + ${ + sf.presentation_url + ? `Presentation URL: ${sf.presentation_url}
` + : `` + } Status: ${sf.status}
Reporter: ${reporter.displayName} <${reporter.email}>
Created: ${new Date(sf.createdAt).toDateString()}
@@ -275,8 +279,12 @@ const sendEmailPPRReimbursedToReporter = async (ppr) => { const sendEmailSFReimbursementRequestToCoordinator = async (sf) => { const Subject = `[Action Needed] Submit Reimbursement Request ${sf.codename}` - const HTMLPart = getMainMessageHTML(`Claim has been submitted for ${sf.codename}! Please review it and submit a reimbursement request. Visit the ticket link below to confirm you have submitted the reimbursement request.`) - + (await getSFTicketInfoHTML(sf)) + getTicketLinkHTML(sf.path) + const HTMLPart = + getMainMessageHTML( + `Claim has been submitted for ${sf.codename}! Please review it and submit a reimbursement request. Visit the ticket link below to confirm you have submitted the reimbursement request.` + ) + + (await getSFTicketInfoHTML(sf)) + + getTicketLinkHTML(sf.path) const To = await getEmailToSection(sf.reporter_id, [ EMAIL_RECIPIENTS.coordinator, ]) @@ -289,8 +297,12 @@ const sendEmailSFReimbursementRequestToCoordinator = async (sf) => { const sendEmailSFConfirmReimbursementSubmitToCoordinator = async (sf) => { const Subject = `[Action Needed] Confirm Reimbursement Received ${sf.codename}` - const HTMLPart = getMainMessageHTML(`Please visit the ticket link below to confirm you have received the reimbursement for ${sf.codename}.`) - + (await getSFTicketInfoHTML(sf)) + getTicketLinkHTML(sf.path) + const HTMLPart = + getMainMessageHTML( + `Please visit the ticket link below to confirm you have received the reimbursement for ${sf.codename}.` + ) + + (await getSFTicketInfoHTML(sf)) + + getTicketLinkHTML(sf.path) const To = await getEmailToSection(sf.reporter_id, [ EMAIL_RECIPIENTS.coordinator, ]) @@ -303,8 +315,10 @@ const sendEmailSFConfirmReimbursementSubmitToCoordinator = async (sf) => { const sendEmailSFReimbursementReceivedToTeam = async (sf) => { const Subject = `[Reimbursed] ${sf.codename}` - const HTMLPart = getMainMessageHTML(`${sf.codename} has been reimbursed.`) - + (await getSFTicketInfoHTML(sf)) + getTicketLinkHTML(sf.path) + const HTMLPart = + getMainMessageHTML(`${sf.codename} has been reimbursed.`) + + (await getSFTicketInfoHTML(sf)) + + getTicketLinkHTML(sf.path) const To = await getEmailToSection(sf.reporter_id, [ EMAIL_RECIPIENTS.finance, EMAIL_RECIPIENTS.coordinator, diff --git a/backend/service/sponsorshipfunds.service.js b/backend/service/sponsorshipfunds.service.js index e07f079..9db03ca 100644 --- a/backend/service/sponsorshipfunds.service.js +++ b/backend/service/sponsorshipfunds.service.js @@ -6,7 +6,11 @@ const { getAnnotatedUWFinancePurchasesByIdList, getAnnotatedSponsorshipFundsByIdList, } = require('./annotatedGetters') -const { sendEmailSFReimbursementRequestToCoordinator, sendEmailSFConfirmReimbursementSubmitToCoordinator, sendEmailSFReimbursementReceivedToTeam } = require('../emails/emails') +const { + sendEmailSFReimbursementRequestToCoordinator, + sendEmailSFConfirmReimbursementSubmitToCoordinator, + sendEmailSFReimbursementReceivedToTeam, +} = require('../emails/emails') const getAllSponsorshipFunds = () => { return getAnnotatedSponsorshipFundsByIdList() @@ -51,22 +55,28 @@ const createSponsorshipFund = (body) => { } const updateSponsorshipFund = async (id, body) => { - const newSponsorshipFund = await SponsorshipFund.findByIdAndUpdate(id, body, { - new: true, - }) + const newSponsorshipFund = await SponsorshipFund.findByIdAndUpdate( + id, + body, + { + new: true, + } + ) const annotatedSponsorshipFund = await getSponsorshipFund(id) const status = annotatedSponsorshipFund.status - if (status === "CLAIM_SUBMITTED") { + if (status === 'CLAIM_SUBMITTED') { sendEmailSFReimbursementRequestToCoordinator(annotatedSponsorshipFund) } - - if (status === "SUBMITTED_TO_SF") { - sendEmailSFConfirmReimbursementSubmitToCoordinator(annotatedSponsorshipFund) + + if (status === 'SUBMITTED_TO_SF') { + sendEmailSFConfirmReimbursementSubmitToCoordinator( + annotatedSponsorshipFund + ) } - if (status === "REIMBURSED") { + if (status === 'REIMBURSED') { sendEmailSFReimbursementReceivedToTeam(annotatedSponsorshipFund) } From 0de604c208124cabe185247d1bc6de4f3d8a002b Mon Sep 17 00:00:00 2001 From: William Li <52115161+Trollermaner@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:31:23 -0500 Subject: [PATCH 4/5] fixed email recipients into ENV --- README.md | 1 + backend/emails/emails.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f61700a..1c72b5d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ ATLAS_URI= MAILJET_API_KEY= MAILJET_SECRET_KEY= FINANCE_EMAIL= +EMAIL_RECIPIENTS= CLIENT_URL=http://localhost:3000 ``` diff --git a/backend/emails/emails.js b/backend/emails/emails.js index 0308f65..8e24ac8 100644 --- a/backend/emails/emails.js +++ b/backend/emails/emails.js @@ -15,7 +15,7 @@ const currencyFormatter = new Intl.NumberFormat('en-CA', { }) const getEmailToSection = async (reporter_id, recipients) => { - const emailToSet = new Set(['william.li@watonomous.ca']) + const emailToSet = new Set(process.env.EMAIL_RECIPIENTS?.split(',')) if (recipients.includes(EMAIL_RECIPIENTS.admin)) { // TODO: use ADMIN_IDENTIFIERS (rename to ADMIN_EMAILS) after migrating to new onboarding data From 81d3a71440a497530db7fe66841d7c2213267c1c Mon Sep 17 00:00:00 2001 From: William Li <52115161+Trollermaner@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:33:51 -0500 Subject: [PATCH 5/5] added default emails --- backend/emails/emails.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/emails/emails.js b/backend/emails/emails.js index 8e24ac8..0264de0 100644 --- a/backend/emails/emails.js +++ b/backend/emails/emails.js @@ -15,7 +15,13 @@ const currencyFormatter = new Intl.NumberFormat('en-CA', { }) const getEmailToSection = async (reporter_id, recipients) => { - const emailToSet = new Set(process.env.EMAIL_RECIPIENTS?.split(',')) + const emailToSet = new Set( + process.env.EMAIL_RECIPIENTS?.split(',') || [ + 'jw4he@watonomous.ca', + 'v2zheng@watonomous.ca', + 'william.li@watonomous.ca', + ] + ) if (recipients.includes(EMAIL_RECIPIENTS.admin)) { // TODO: use ADMIN_IDENTIFIERS (rename to ADMIN_EMAILS) after migrating to new onboarding data