- Progress Commit (Emailzzz)

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-04-02 21:53:07 -04:00
parent 69ac2f0a6c
commit 0803f5af35
6 changed files with 2825 additions and 101 deletions

View File

@@ -19,11 +19,9 @@ import {FaTasks} from "react-icons/fa";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setReconciliationContext: (context) => dispatch(setModalContext({
context: context,
@@ -33,17 +31,15 @@ const mapDispatchToProps = (dispatch) => ({
});
export function BillsListTableComponent({
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setPartsOrderContext,
setBillEnterContext,
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setBillEnterContext,
setReconciliationContext,
setTaskUpsertContext,
currentUser,
}) {
}) {
const { t } = useTranslation();
const [state, setState] = useState({

View File

@@ -123,7 +123,7 @@ export function TaskUpsertModalComponent({
]}
>
<JobSearchSelectComponent placeholder={t('tasks.placeholders.jobid')}
onSelect={changeJobId} onClear={changeJobId}/>
onSelect={changeJobId} onClear={changeJobId} autoFocus={false}/>
</Form.Item>
</Col>
</Row>

View File

@@ -16,6 +16,9 @@ import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors
import TaskUpsertModalComponent from "./task-upsert-modal.component";
import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js";
import {useNavigate} from "react-router-dom";
import axios from "axios";
import dayjs from '../../utils/day';
import {insertAuditTrail} from "../../redux/application/application.actions.js";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -24,6 +27,7 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
});
export function TaskUpsertModalContainer({
@@ -31,6 +35,7 @@ export function TaskUpsertModalContainer({
currentUser,
taskUpsert,
toggleModalVisible,
insertAuditTrail
}) {
const {t} = useTranslation();
const history = useNavigate();
@@ -62,7 +67,7 @@ export function TaskUpsertModalContainer({
skip: !taskId,
});
// This takes care of the ability to deep link a task from the URL (Fills in form fields)
// Use Effect to hydrate existing task if only a taskid is provided
useEffect(() => {
if (!taskLoading && !taskError && taskData && taskData?.tasks_by_pk) {
form.setFieldsValue(taskData.tasks_by_pk);
@@ -70,22 +75,25 @@ export function TaskUpsertModalContainer({
}
}, [taskLoading, taskError, taskData]);
/**
* Set the selected job id when the modal is opened and jobId is passed as a prop or when an existing task is passed as a prop
*/
// Use Effect to hydrate selected job details
useEffect(() => {
if (!loading && !error && data) {
setSelectedJobDetails(data.jobs_by_pk);
}
}, [loading, error, data]);
// Use Effect to toggle to set jobid state
useEffect(() => {
if (selectedJobId) {
setJobIdState(selectedJobId);
}
}, [selectedJobId]);
// Use Effect to hydrate form fields
useEffect(() => {
if (jobid || existingTask?.id) {
setSelectedJobId(jobid || existingTask.jobid);
}
return () => {
setSelectedJobId(null);
};
}, [jobid, existingTask]);
/**
* Set the form values when the modal is opened and an existing task is passed as a prop
*/
useEffect(() => {
if (existingTask && open) {
form.setFieldsValue(existingTask);
} else if (!existingTask && open) {
@@ -94,34 +102,131 @@ export function TaskUpsertModalContainer({
if (billid) form.setFieldsValue({billid});
if (partsorderid) form.setFieldsValue({partsorderid});
}
}, [existingTask, form, open, joblineid, billid, partsorderid]);
return () => {
setSelectedJobId(null);
};
}, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);
/**
* Set the job id state when the selected job id changes
* Remove the taskid from the URL
*/
useEffect(() => {
if (selectedJobId) {
// Update the state variable instead of calling useQuery
setJobIdState(selectedJobId);
}
}, [selectedJobId]);
/**
* Set the selected job details when the job details query is successful
*/
useEffect(() => {
if (!loading && !error && data) {
setSelectedJobDetails(data.jobs_by_pk);
}
}, [loading, error, data]);
const removeTaskIdFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has('taskid')) return;
urlParams.delete('taskid');
history(`${window.location.pathname}?${urlParams}`);
}
/**
* Handle existing task
* @param values
* @returns {Promise<void>}
*/
const handleExistingTask = async (values) => {
const isAssignedToDirty = values.assigned_to !== existingTask.assigned_to;
await updateTask({
variables: {
taskId: existingTask.id,
task: replaceUndefinedWithNull(values)
},
});
if (isAssignedToDirty) {
axios.post("/sendemail", {
from: {
name: bodyshop.shopname,
address: bodyshop.email,
},
ReplyTo: {
Email: 'noreply@imex.online'
},
to: values.assigned_to,
subject: `A Task has been re-assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateStrings: {
header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`,
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${existingTask.id}">Please sing in to your account to view the task details.</a>`
}
}).catch(e => console.error(`Something went wrong sending email to Assigned party on Task creation. ${e.message || ''}`));
}
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been created or edited.'},
}));
notification["success"]({
message: t("tasks.successes.updated"),
});
if (refetch) await refetch();
toggleModalVisible();
};
const handleNewTask = async (values) => {
const newTaskID = (await insertTask({
variables: {
taskInput: [
{
...values,
created_by: currentUser.email,
bodyshopid: bodyshop.id
},
],
},
// TODO: Consult Patrick, because this fails on relationship data, and an event emitter is just much easier to use
// update(cache) {
// cache.modify({
// fields: {
// tasks(existingTasks) {
// return [{
// ...values,
// jobid: selectedJobId || values.jobid,
// created_by: currentUser.email,
// bodyshopid: bodyshop.id
// }, ...existingTasks]
// },
// },
// });
// },
})).data.insert_tasks.returning[0].id;
if (refetch) await refetch();
form.resetFields();
toggleModalVisible();
// send notification to the assigned user
axios.post("/sendemail", {
from: {
name: bodyshop.shopname,
address: bodyshop.email,
},
replyTo: {
Email: 'noreply@imex.online'
},
to: values.assigned_to,
subject: `A new Task has been assigned to you on ${bodyshop.shopname} - ${values.title}`,
templateName: 'taskAssigned',
templateStrings: {
header: values.title,
subHeader: `Assigned by ${currentUser.email} ${values.due_at ? `| Due on ${dayjs(values.due_at).format('MM/DD/YYYY')}` : ''}`,
body: `<a href="${window.location.protocol}//${window.location.host}/manage/tasks/alltasks?taskid=${newTaskID}">Please sing in to your account to view the task details.</a>`
}
}).catch(e => console.error(`Something went wrong sending email to Assigned party on Task edit. ${e.message || ''}`));
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been created or edited.'},
}));
notification["success"]({
message: t("tasks.successes.created"),
});
};
/**
* Handle the form submit
* @param formValues
@@ -130,58 +235,9 @@ export function TaskUpsertModalContainer({
const handleFinish = async (formValues) => {
const {...values} = formValues;
if (existingTask) {
await updateTask({
variables: {
taskId: existingTask.id,
task: replaceUndefinedWithNull(values)
},
});
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been created or edited.'},
}));
notification["success"]({
message: t("tasks.successes.updated"),
});
if (refetch) await refetch();
toggleModalVisible();
await handleExistingTask(values);
} else {
await insertTask({
variables: {
taskInput: [
{
...values,
created_by: currentUser.email,
bodyshopid: bodyshop.id
},
],
},
// TODO: Consult Patrick, because this fails on relationship data, and an event emitter is just much easier to use
// update(cache) {
// cache.modify({
// fields: {
// tasks(existingTasks) {
// return [{
// ...values,
// jobid: selectedJobId || values.jobid,
// created_by: currentUser.email,
// bodyshopid: bodyshop.id
// }, ...existingTasks]
// },
// },
// });
// },
});
if (refetch) await refetch();
form.resetFields();
toggleModalVisible();
window.dispatchEvent(new CustomEvent('taskUpdated', {
detail: {message: 'A task has been created or edited.'},
}));
notification["success"]({
message: t("tasks.successes.created"),
});
await handleNewTask(values);
}
};
@@ -199,7 +255,6 @@ export function TaskUpsertModalContainer({
removeTaskIdFromUrl();
toggleModalVisible();
}}
destroyOnClose
>
<Form form={form} onFinish={handleFinish} layout="vertical">

View File

@@ -155,7 +155,6 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
})}
/>
}
This
>
<PaymentModalContainer />

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@ const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const {isObject} = require("lodash");
const generateEmailTemplate = require('./generateTemplate');
const ses = new aws.SES({
// The key apiVersion is no longer supported in v3, and can be removed.
@@ -88,7 +90,8 @@ exports.sendEmail = async (req, res) => {
replyTo: req.body.ReplyTo.Email,
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject
subject: req.body.subject,
templateStrings: req.body.templateStrings
});
let downloadedMedia = [];
@@ -104,6 +107,7 @@ exports.sendEmail = async (req, res) => {
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject,
templateStrings: req.body.templateStrings,
error
});
}
@@ -134,7 +138,7 @@ exports.sendEmail = async (req, res) => {
};
})
] || null,
html: req.body.html,
html: isObject(req.body?.templateStrings) ? generateEmailTemplate(req.body.templateStrings) : req.body.html,
ses: {
// optional extra arguments for SendRawEmail
Tags: [
@@ -153,7 +157,8 @@ exports.sendEmail = async (req, res) => {
replyTo: req.body.ReplyTo.Email,
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject
subject: req.body.subject,
templateStrings: req.body.templateStrings
// info,
});
logEmail(req, {
@@ -172,6 +177,7 @@ exports.sendEmail = async (req, res) => {
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject,
templateStrings: req.body.templateStrings,
error: err
});
logEmail(req, {