import { DownCircleFilled } from "@ant-design/icons"; import { useApolloClient, useMutation } from "@apollo/client/react"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; import { Button, Card, Dropdown, Form, Input, Modal, Popconfirm, Popover, Select, Space } from "antd"; import axios from "axios"; import parsePhoneNumber from "libphonenumber-js"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useNavigate } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { useSocket } from "../../contexts/SocketIO/useSocket.js"; import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { setEmailOptions } from "../../redux/email/email.actions"; import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import { DateTimeFormatter } from "../../utils/DateFormatter"; import { TemplateList } from "../../utils/TemplateConstants"; import dayjs from "../../utils/day"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component"; import LockerWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, jobRO: selectJobReadOnly, currentUser: selectCurrentUser }); const mapDispatchToProps = (dispatch) => ({ setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })), setBillEnterContext: (context) => dispatch( setModalContext({ context: context, modal: "billEnter" }) ), setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })), setJobCostingContext: (context) => dispatch( setModalContext({ context: context, modal: "jobCosting" }) ), setTimeTicketContext: (context) => dispatch( setModalContext({ context: context, modal: "timeTicket" }) ), setCardPaymentContext: (context) => dispatch( setModalContext({ context: context, modal: "cardPayment" }) ), insertAuditTrail: ({ jobid, operation, type }) => dispatch( insertAuditTrail({ jobid, operation, type }) ), setTimeTicketTaskContext: (context) => dispatch( setModalContext({ context: context, modal: "timeTicketTask" }) ), setTaskUpsertContext: (context) => dispatch( setModalContext({ context: context, modal: "taskUpsert" }) ), setEmailOptions: (e) => dispatch(setEmailOptions(e)), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), setMessage: (text) => dispatch(setMessage(text)) }); export function JobsDetailHeaderActions({ job, bodyshop, currentUser, refetch, setScheduleContext, setBillEnterContext, setPaymentContext, setJobCostingContext, jobRO, setTimeTicketContext, setCardPaymentContext, insertAuditTrail, setEmailOptions, openChatByPhone, setMessage, setTimeTicketTaskContext, setTaskUpsertContext }) { const { t } = useTranslation(); const client = useApolloClient(); const history = useNavigate(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false); const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false); const [insertAppointment] = useMutation(INSERT_MANUAL_APPT); const [deleteJob] = useMutation(DELETE_JOB); const [insertCsi] = useMutation(INSERT_CSI); const [updateJob] = useMutation(UPDATE_JOB); const [voidJob] = useMutation(VOID_JOB); const [cancelAllAppointments] = useMutation(CANCEL_APPOINTMENTS_BY_JOB_ID); const { socket } = useSocket(); const notification = useNotification(); const isDevEnv = import.meta.env.DEV; const isProdEnv = import.meta.env.PROD; const userEmail = currentUser?.email || ""; const devEmails = ["imex.dev", "rome.dev"]; const prodEmails = ["imex.prod", "rome.prod", "imex.test", "rome.test"]; const hasValidEmail = (emails) => emails.some((email) => userEmail.endsWith(email)); const canSubmitForTesting = (isDevEnv && hasValidEmail(devEmails)) || (isProdEnv && hasValidEmail(prodEmails)); const { treatments: { ImEXPay } } = useTreatmentsWithConfig({ attributes: {}, names: ["ImEXPay"], splitKey: bodyshop && bodyshop.imexshopid }); const jobInProduction = useMemo(() => { return bodyshop.md_ro_statuses.production_statuses.includes(job.status); }, [job, bodyshop.md_ro_statuses.production_statuses]); const [visibility, setVisibility] = useState(false); const jobInPreProduction = useMemo(() => { return bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status); }, [job.status, bodyshop.md_ro_statuses.pre_production_statuses]); const jobInPostProduction = useMemo(() => { return bodyshop.md_ro_statuses.post_production_statuses.includes(job.status); }, [job.status, bodyshop.md_ro_statuses.post_production_statuses]); // Function to show modal const showCancelScheduleModal = () => { setIsCancelScheduleModalVisible(true); }; // Function to handle Cancel const handleCancelScheduleModalCancel = () => { setIsCancelScheduleModalVisible(false); }; const handleDuplicate = () => DuplicateJob({ apolloClient: client, jobId: job.id, config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, completionCallback: (newJobId) => { history(`/manage/jobs/${newJobId}`); notification.success({ message: t("jobs.successes.duplicated") }); }, keepJobLines: true, currentUser }); const handleDuplicateConfirm = () => DuplicateJob({ apolloClient: client, jobId: job.id, config: { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, completionCallback: (newJobId) => { history(`/manage/jobs/${newJobId}`); notification.success({ message: t("jobs.successes.duplicated") }); }, keepJobLines: false, currentUser }); const handleFinish = async (values) => { logImEXEvent("schedule_manual_event"); setLoading(true); try { insertAppointment({ variables: { apt: { ...values, isintake: false, jobid: job.id, bodyshopid: bodyshop.id } }, refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"] }); notification.open({ type: "success", message: t("appointments.successes.created") }); } catch (error) { notification.open({ type: "error", message: t("appointments.errors.saving", { error: error.message }) }); } finally { setLoading(false); setVisibility(false); } }; const handleDeleteJob = async () => { //delete the job. const result = await deleteJob({ variables: { id: job.id } }); if (!result.errors) { notification.success({ message: t("jobs.successes.delete") }); //go back to jobs list. history(`/manage/`); } else { notification.error({ message: t("jobs.errors.deleted", { error: JSON.stringify(result.errors) }) }); } }; const handleCreateCsi = async (e) => { logImEXEvent("job_create_csi"); //Is there already a CSI? if (!job.csiinvites || job.csiinvites.length === 0) { const questionSetResult = await client.query({ query: GET_CURRENT_QUESTIONSET_ID }); if (questionSetResult.data.csiquestions.length > 0) { const result = await insertCsi({ variables: { csiInput: { jobid: job.id, bodyshopid: bodyshop.id, questionset: questionSetResult.data.csiquestions[0].id, relateddata: { job: { id: job.id, ownr_fn: job.ownr_fn, ro_number: job.ro_number, v_model_yr: job.v_model_yr, v_make_desc: job.v_make_desc, v_model_desc: job.v_model_desc }, bodyshop: { city: bodyshop.city, email: bodyshop.email, state: bodyshop.state, country: bodyshop.country, address1: bodyshop.address1, address2: bodyshop.address2, shopname: bodyshop.shopname, zip_post: bodyshop.zip_post, logo_img_path: bodyshop.logo_img_path } } } }, refetchQueries: ["GET_JOB_BY_PK"], awaitRefetchQueries: true }); if (!result.errors) { notification.success({ message: t("csi.successes.created") }); } else { notification.error({ message: t("csi.errors.creating", { message: JSON.stringify(result.errors) }) }); insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.jobvoid(), type: "jobvoid" }); return; } if (e.key === "email") setEmailOptions({ jobid: job.id, messageOptions: { to: [job.ownr_ea], replyTo: bodyshop.email }, template: { name: TemplateList("job_special").csi_invitation_action.key, variables: { id: result.data.insert_csi.returning[0].id } } }); if (e.key === "text") { const p = parsePhoneNumber(job.ownr_ph1, "CA"); if (p && p.isValid()) { openChatByPhone({ phone_num: p.formatInternational(), jobid: job.id, socket }); setMessage( `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` ); } else { notification.error({ message: t("messaging.error.invalidphone") }); } } if (e.key === "generate") { //copy it to clipboard. navigator.clipboard.writeText( `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` ); } } else { notification.error({ message: t("csi.errors.notconfigured") }); } } else { if (e.key === "email") setEmailOptions({ jobid: job.id, messageOptions: { to: [job.ownr_ea], replyTo: bodyshop.email }, template: { name: TemplateList("job_special").csi_invitation_action.key, variables: { id: job.csiinvites[0].id } } }); if (e.key === "text") { const p = parsePhoneNumber(job.ownr_ph1, "CA"); if (p && p.isValid()) { openChatByPhone({ phone_num: p.formatInternational(), jobid: job.id, socket }); setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`); } else { notification.error({ message: t("messaging.error.invalidphone") }); } } if (e.key === "generate") { //copy it to clipboard. navigator.clipboard.writeText( `${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}` ); } } }; const handleVoidJob = async () => { //delete the job. const result = await voidJob({ variables: { jobId: job.id, job: { status: bodyshop.md_ro_statuses.default_void, voided: true, scheduled_in: null, scheduled_completion: null, inproduction: false, date_void: new Date() }, note: [ { jobid: job.id, created_by: currentUser.email, audit: true, text: t("jobs.labels.voidnote") } ] } }); if (!result.errors) { notification.success({ message: t("jobs.successes.voided") }); insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.jobvoid(), type: "jobvoid" }); //go back to jobs list. history(`/manage/`); } else { notification.error({ message: t("jobs.errors.voiding", { error: JSON.stringify(result.errors) }) }); } }; const handleExportCustData = async () => { logImEXEvent("job_export_cust_data"); let PartnerResponse; if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { PartnerResponse = await axios.post(`/qbo/receivables`, { jobIds: [job.id], custDataOnly: true }); } else { //Default is QBD let QbXmlResponse; try { QbXmlResponse = await axios.post( "/accounting/qbxml/receivables", { jobIds: [job.id], custDataOnly: true }, { headers: { Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } } ); console.log("handle -> XML", QbXmlResponse); } catch (error) { console.log("Error getting QBXML from Server.", error); notification.error({ message: t("jobs.errors.exporting", { error: "Unable to retrieve QBXML. " + JSON.stringify(error.message) }) }); return; } //let PartnerResponse; try { PartnerResponse = await axios.post("http://localhost:1337/qb/", QbXmlResponse.data, { headers: { Authorization: `Bearer ${await auth.currentUser.getIdToken()}` } }); } catch (error) { console.log("Error connecting to quickbooks or partner.", error); notification.error({ message: t("jobs.errors.exporting-partner") }); return; } } //Check to see if any of them failed. If they didn't execute the update. const failedTransactions = PartnerResponse.data.filter((r) => !r.success); if (failedTransactions.length > 0) { //Uh oh. At least one was no good. failedTransactions.forEach((ft) => { //insert failed export log notification.open({ // key: "failedexports", type: "error", message: t("jobs.errors.exporting", { error: ft.errorMessage || "" }) }); }); //Handle Failures. } else { //Insert success export log. notification.open({ type: "success", key: "jobsuccessexport", message: t("jobs.successes.exported") }); } }; const handleAlertToggle = () => { logImEXEvent("production_toggle_alert"); //e.stopPropagation(); updateJob({ variables: { jobId: job.id, job: { production_vars: { ...job.production_vars, alert: !!job.production_vars && !!job.production_vars.alert ? !job.production_vars.alert : true } } } }); insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.alertToggle( !!job.production_vars && !!job.production_vars.alert ? !job.production_vars.alert : true ), type: "alertToggle" }); }; const handleSuspend = () => { logImEXEvent("production_toggle_alert"); //e.stopPropagation(); updateJob({ variables: { jobId: job.id, job: { suspended: !job.suspended } } }); insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.jobsuspend(job.suspended ? !job.suspended : true), type: "jobsuspend" }); }; // Function to handle OK const handleCancelScheduleOK = async () => { await form.submit(); // Assuming 'form' is the Form instance from useForm() setIsCancelScheduleModalVisible(false); }; const handleLostSaleFinish = async ({ lost_sale_reason }) => { const jobUpdate = await cancelAllAppointments({ variables: { jobid: job.id, job: { date_scheduled: null, scheduled_in: null, scheduled_completion: null, lost_sale_reason, date_lost_sale: new Date(), status: bodyshop.md_ro_statuses.default_imported } } }); if (!jobUpdate.errors) { notification.success({ message: t("appointments.successes.canceled") }); insertAuditTrail({ jobid: job.id, operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), type: "appointmentcancel" }); } }; const popOverContent = (
{ const start = form.getFieldValue("start"); form.setFieldsValue({ end: start?.add(30, "minutes") }); }} /> ({ async validator(rule, value) { if (value) { const { start } = form.getFieldsValue(); if (dayjs(start).isAfter(dayjs(value))) { return Promise.reject(t("employees.labels.endmustbeafterstart")); } else { return Promise.resolve(); } } else { return Promise.resolve(); } } }) ]} >
); const menuItems = [ { key: "schedule", id: "job-actions-schedule", disabled: !jobInPreProduction || !job.converted || jobRO, label: t("jobs.actions.schedule"), onClick: () => { logImEXEvent("job_header_schedule"); setScheduleContext({ actions: { refetch: refetch }, context: { jobId: job.id, job: job, alt_transport: job.alt_transport, scheduled_in: job.scheduled_in, scheduled_completion: job.scheduled_completion } }); } }, { key: "cancelallappointments", id: "job-actions-cancelallappointments", onClick: () => { if (job.status !== bodyshop.md_ro_statuses.default_scheduled) { return; } showCancelScheduleModal(); }, disabled: job.status !== bodyshop.md_ro_statuses.default_scheduled, label: t("menus.jobsactions.cancelallappointments") }, { key: "intake", id: "job-actions-intake", disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO, label: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? ( {t("jobs.actions.intake")} ) : ( {t("jobs.actions.intake")} ) }, { key: "deliver", id: "job-actions-deliver", disabled: !jobInProduction || jobRO, label: !jobInProduction ? ( {t("jobs.actions.deliver")} ) : ( {t("jobs.actions.deliver")} ) }, { key: "checklist", id: "job-actions-checklist", disabled: !job.converted, label: ( {t("jobs.actions.viewchecklist")} ) }, { key: "toggleproduction", id: "job-actions-toggleproduction", disabled: !job.converted || jobRO, label: ( setDropdownOpen(false)} /> ) }, { key: "entertimetickets", id: "job-actions-entertimetickets", disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), label: ( {t("timetickets.actions.enter")} ), onClick: () => { logImEXEvent("job_header_enter_time_ticekts"); HasFeatureAccess({ featureName: "timetickets", bodyshop }) && setTimeTicketContext({ actions: {}, context: { jobId: job.id, created_by: currentUser.displayName ? currentUser.email.concat(" | ", currentUser.displayName) : currentUser.email } }); } } ]; if (bodyshop.md_tasks_presets.enable_tasks) { menuItems.push({ key: "claimtimetickettasks", id: "job-actions-claimtimetickettasks", disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), onClick: () => { setTimeTicketTaskContext({ actions: {}, context: { jobid: job.id } }); }, label: t("timetickets.actions.claimtasks") }); } menuItems.push({ key: "enterpayments", id: "job-actions-enterpayments", disabled: !job.converted, label: t("menus.header.enterpayment"), onClick: () => { logImEXEvent("job_header_enter_payment"); setPaymentContext({ actions: {}, context: { jobid: job.id } }); } }); if (ImEXPay.treatment === "on") { menuItems.push({ key: "entercardpayments", id: "job-actions-entercardpayments", disabled: !job.converted, label: t("menus.header.entercardpayment"), onClick: () => { logImEXEvent("job_header_enter_card_payment"); setCardPaymentContext({ actions: { refetch }, context: { jobid: job.id } }); } }); } menuItems.push({ key: "cccontract", id: "job-actions-cccontract", disabled: jobRO || !job.converted, label: ( {t("menus.jobsactions.newcccontract")} ) }); menuItems.push({ key: "createtask", id: "job-actions-createtask", label: t("menus.header.create_task"), onClick: () => setTaskUpsertContext({ actions: {}, context: { jobid: job.id } }) }); menuItems.push( job.inproduction ? { key: "removefromproduction", id: "job-actions-removefromproduction", disabled: !job.converted, label: t("jobs.actions.removefromproduction"), onClick: () => AddToProduction(client, job.id, refetch, true, notification) } : { key: "addtoproduction", id: "job-actions-addtoproduction", disabled: !job.converted, label: t("jobs.actions.addtoproduction"), onClick: () => AddToProduction(client, job.id, refetch, false, notification) } ); menuItems.push( { key: "togglesuspend", id: "job-actions-togglesuspend", onClick: handleSuspend, label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend") }, { key: "toggleAlert", onClick: handleAlertToggle, id: "job-actions-togglealert", label: job.production_vars && job.production_vars.alert ? t("production.labels.alertoff") : t("production.labels.alerton") }, { key: "dupe", label: t("menus.jobsactions.duplicate"), children: [ { key: "duplicate", id: "job-actions-duplicate", label: ( e.stopPropagation()} onConfirm={handleDuplicate} getPopupContainer={(trigger) => trigger.parentNode} > {t("menus.jobsactions.duplicate")} ) }, { key: "duplicatenolines", id: "job-actions-duplicatenolines", label: ( e.stopPropagation()} onConfirm={handleDuplicateConfirm} getPopupContainer={(trigger) => trigger.parentNode} > {t("menus.jobsactions.duplicatenolines")} ) } ] }, { key: "postbills", id: "job-actions-postbills", disabled: !job.converted, label: {t("jobs.actions.postbills")}, onClick: () => { logImEXEvent("job_header_enter_bills"); HasFeatureAccess({ featureName: "bills", bodyshop }) && setBillEnterContext({ actions: { refetch: refetch }, context: { job: job } }); } }, { key: "addtopartsqueue", id: "job-actions-addtopartsqueue", disabled: !job.converted || !jobInProduction || jobRO, label: t("jobs.actions.addtopartsqueue"), onClick: async () => { const result = await updateJob({ variables: { jobId: job.id, job: { queued_for_parts: true } } }); if (!result.errors) { notification.success({ message: t("jobs.successes.partsqueue") }); } else { notification.error({ message: t("jobs.errors.saving", { error: JSON.stringify(result.errors) }) }); } } }, { key: "closejob", id: "job-actions-closejob", disabled: !jobInPostProduction, label: !jobInPostProduction ? ( t("menus.jobsactions.closejob") ) : ( {t("menus.jobsactions.closejob")} ) }, { key: "admin", id: "job-actions-admin", label: ( {t("menus.jobsactions.admin")} ) } ); if (bodyshop?.md_functionality_toggles?.teams) { menuItems.push({ key: "sharetoteams", id: "job-actions-sharetoteams", label: }); } menuItems.push({ key: "exportcustdata", id: "job-actions-exportcustdata", disabled: !(job.converted && HasFeatureAccess({ bodyshop, featureName: "export" })), label: {t("jobs.actions.exportcustdata")}, onClick: handleExportCustData }); const children = [ { key: "email", id: "job-actions-email", disabled: !(job.ownr_ea && HasFeatureAccess({ bodyshop, featureName: "csi" })), label: {t("general.labels.email")}, onClick: handleCreateCsi }, { key: "text", id: "job-actions-text", disabled: !(job.ownr_ph1 && HasFeatureAccess({ bodyshop, featureName: "csi" })), label: {t("general.labels.text")}, onClick: handleCreateCsi }, { key: "generate", id: "job-actions-generate", disabled: job.csiinvites?.length > 0 || !HasFeatureAccess({ bodyshop, featureName: "csi" }), label: {t("jobs.actions.generatecsi")}, onClick: handleCreateCsi } ]; if (job?.csiinvites?.length) { children.push( { type: "divider" }, ...job.csiinvites.map((item, idx) => { return item.completedon ? { key: idx, label: ( {item.completedon} ) } : { key: idx, onClick: () => { navigator.clipboard.writeText(`${window.location.protocol}//${window.location.host}/csi/${item.id}`); }, label: t("general.actions.copylink") }; }) ); } menuItems.push({ key: "sendcsi", id: "job-actions-sendcsi", label: {t("jobs.actions.sendcsi")}, disabled: !job.converted, children }); menuItems.push({ key: "jobcosting", id: "job-actions-jobcosting", disabled: !job.converted, label: t("jobs.labels.jobcosting"), onClick: () => { logImEXEvent("job_header_job_costing"); setJobCostingContext({ actions: { refetch: refetch }, context: { jobId: job.id } }); } }); if (job && !job.converted) { menuItems.push({ key: "deletejob", id: "job-actions-deletejob", label: job.job_watchers.length === 0 ? ( e.stopPropagation()} onConfirm={handleDeleteJob} > {t("menus.jobsactions.deletejob")} ) : ( e.stopPropagation()} showCancel={false}> {t("menus.jobsactions.deletejob")} ) }); } menuItems.push({ key: "manualevent", id: "job-actions-manualevent", onClick: () => { setVisibility(true); }, label: t("appointments.labels.manualevent") }); if (!jobRO && job.converted) { menuItems.push({ key: "voidjob", id: "job-actions-voidjob", label: ( e.stopPropagation()} onConfirm={handleVoidJob} > {t("menus.jobsactions.void")} ) }); } if (canSubmitForTesting) { menuItems.push({ key: "submitfortesting", id: "job-actions-submitfortesting", label: t("menus.jobsactions.submit-for-testing"), onClick: async () => { try { await axios.post("/job/totals-recorder", { id: job.id }); notification.success({ message: t("general.messages.submit-for-testing") }); } catch (err) { console.error(`Error submitting job for testing: ${err?.message}`); notification.error({ message: t("general.errors.submit-for-testing-error") }); } } }); } const menu = { items: menuItems, key: "popovermenu" }; return ( <> {t("general.actions.cancel")} , ]} >
{ console.log(s); handleLostSaleFinish(s); }} >