import { useMutation, useQuery } from "@apollo/client"; import { Form, Modal } from "antd"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries"; import { GET_JOB_BY_PK, QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectTaskUpsert } from "../../redux/modals/modals.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import TaskUpsertModalComponent from "./task-upsert-modal.component"; import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js"; import { useLocation, useNavigate } from "react-router-dom"; import { insertAuditTrail } from "../../redux/application/application.actions.js"; import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; import { isEqual } from "lodash"; import refetchRouteMappings from "./task-upsert-modal.route.mappings"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { logImEXEvent } from "../../firebase/firebase.utils.js"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, bodyshop: selectBodyshop, taskUpsert: selectTaskUpsert }); const mapDispatchToProps = (dispatch) => ({ toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")), insertAuditTrail: ({ jobid, billid, operation, type }) => dispatch(insertAuditTrail({ jobid, billid, operation, type })) }); export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) { const { t } = useTranslation(); const history = useNavigate(); const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK); const [updateTask] = useMutation(MUTATION_UPDATE_TASK); const { open, context } = taskUpsert; const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query, view } = context; const [form] = Form.useForm(); const [selectedJobId, setSelectedJobId] = useState(null); const [selectedJobDetails, setSelectedJobDetails] = useState(null); const [jobIdState, setJobIdState] = useState(null); const [isTouched, setIsTouched] = useState(false); const location = useLocation(); const notification = useNotification(); const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, { variables: { id: jobIdState }, skip: !jobIdState }); const { loading: taskLoading, error: taskError, data: taskData } = useQuery(QUERY_GET_TASK_BY_ID, { variables: { id: taskId }, skip: !taskId }); // 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); setSelectedJobId(taskData.tasks_by_pk.jobid); } }, [taskLoading, taskError, taskData, form]); // 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); } if (existingTask && open) { form.setFieldsValue(existingTask); } else if (!existingTask && open) { form.resetFields(); if (joblineid) form.setFieldsValue({ joblineid }); if (billid) form.setFieldsValue({ billid }); if (partsorderid) form.setFieldsValue({ partsorderid }); } return () => { setSelectedJobId(null); setIsTouched(false); }; }, [jobid, existingTask, form, open, joblineid, billid, partsorderid]); /** * Remove the taskid from the URL */ const removeTaskIdFromUrl = () => { const urlParams = new URLSearchParams(window.location.search); if (!urlParams.has("taskid")) return; urlParams.delete("taskid"); history(`${window.location.pathname}?${urlParams}`); }; /** * Generate refetch queries * @param jobId * @returns {*[]} */ const generateRefetchQueries = (jobId) => { const refetchQueries = []; if (location.pathname.includes("/manage/jobs") && jobId) { refetchQueries.push({ query: GET_JOB_BY_PK, variables: { id: jobId } }); } // We have a specified query if (query && Object.keys(query).length) { refetchQueries.push(Object.keys(query)[0]); } // We don't have a specified query, check the page else { refetchRouteMappings.forEach((mapping) => { if (location.pathname.includes(mapping.route)) { refetchQueries.push(mapping.query); } }); } return refetchQueries; }; /** * Handle existing task * @param id * @param jobId * @param values * @returns {Promise} */ const handleExistingTask = async (id, jobId, values) => { const task = replaceUndefinedWithNull(values); // Remind at is dirty so lets clear remind_at_sent if (task?.remind_at) { task.remind_at_sent = null; } const taskObject = { variables: { taskId: id, task } }; taskObject.refetchQueries = generateRefetchQueries(jobId); const taskData = await updateTask(taskObject); if (!taskData.errors) { const oldTask = taskData?.data?.update_tasks?.returning[0]; insertAuditTrail({ jobid: oldTask.jobid, operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email), type: "tasksUpdated" }); } notification["success"]({ message: t("tasks.successes.updated") }); logImEXEvent("task_update", {}); toggleModalVisible(); }; /** * Handle new task * @param values * @returns {Promise} */ const handleNewTask = async (values) => { const taskObject = { variables: { taskInput: [ { ...values, created_by: currentUser.email, bodyshopid: bodyshop.id } ] } }; taskObject.refetchQueries = generateRefetchQueries(values.jobid); const newTaskData = await insertTask(taskObject); const newTask = newTaskData?.data?.insert_tasks?.returning[0]; if (!newTaskData.errors) { insertAuditTrail({ jobid: newTask.jobid, operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email), type: "tasksCreated" }); } form.resetFields(); toggleModalVisible(); logImEXEvent("task_insert", {}); notification["success"]({ message: t("tasks.successes.created") }); }; /** * Handle the form submit * @param formValues * @returns {Promise<[{jobid, bodyshopid, created_by},...*]>} */ const handleFinish = async (formValues) => { if (existingTask || taskData?.tasks_by_pk) { const taskSource = existingTask || taskData?.tasks_by_pk; const dirtyValues = Object.keys(formValues).reduce((acc, key) => { if (!isEqual(formValues[key], taskSource[key])) { acc[key] = formValues[key]; } return acc; }, {}); try { await handleExistingTask(taskSource.id, taskSource.jobid, dirtyValues); } catch { notification["error"]({ message: t("tasks.failures.updated") }); } } else { try { await handleNewTask(formValues); } catch { notification["error"]({ message: t("tasks.failures.created") }); } } }; return ( {view ? t("tasks.actions.view") : existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")} } open={open} okText={t("general.actions.save")} width="50%" cancelText={!isTouched ? t("general.actions.ok") : t("general.actions.cancel")} onOk={() => { removeTaskIdFromUrl(); form.submit(); }} onCancel={() => { removeTaskIdFromUrl(); toggleModalVisible(); }} okButtonProps={{ disabled: !isTouched }} destroyOnHidden >
{ setIsTouched(true); }} >
); } export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);