import { Button, Col, Collapse, Drawer, Form, Input, Row, Segmented, Select, Space, Table, Tabs, Typography, notification, Badge, Popover, DatePicker, TimePicker, QRCode, Card, Empty, Flex, Popconfirm, } from "antd"; import { useTranslation } from "react-i18next"; import { AppStyle, Constants, FormatDatetime, myFetch, showInputsInvalidNotification, showUnkownErrorNotification, wsConnectionCustomEventName, } from "../../utils"; import { useEffect, useRef, useState } from "react"; import { PlusOutlined, ChromeOutlined, SearchOutlined, PhoneOutlined, DeleteOutlined, } from "@ant-design/icons"; import { t } from "i18next"; import { useCrmContext } from "../../Contexts/CrmContext"; import "@mdxeditor/editor/style.css"; import { MDXEditor } from "@mdxeditor/editor/MDXEditor"; import { headingsPlugin } from "@mdxeditor/editor/plugins/headings"; import { listsPlugin } from "@mdxeditor/editor/plugins/lists"; import { quotePlugin } from "@mdxeditor/editor/plugins/quote"; import { thematicBreakPlugin } from "@mdxeditor/editor/plugins/thematic-break"; import { toolbarPlugin } from "@mdxeditor/editor/plugins/toolbar"; import { BoldItalicUnderlineToggles } from "@mdxeditor/editor/plugins/toolbar/components/BoldItalicUnderlineToggles"; import { UndoRedo } from "@mdxeditor/editor/plugins/toolbar/components/UndoRedo"; import { linkPlugin } from "@mdxeditor/editor/plugins/link"; import { markdownShortcutPlugin } from "@mdxeditor/editor/plugins/markdown-shortcut"; import { MySupsenseFallback } from "../../Components/MySupsenseFallback"; import { linkDialogPlugin } from "@mdxeditor/editor"; import { tablePlugin } from "@mdxeditor/editor"; import { frontmatterPlugin } from "@mdxeditor/editor"; import { MyAvatar } from "../../Components/MyAvatar"; import { useAppContext } from "../../Contexts/AppContext"; import Highlighter from "react-highlight-words"; import MyModal, { MyModalCloseCreateButtonFooter, } from "../../Components/MyModal"; import dayjs from "dayjs"; import Markdown from "react-markdown"; const CRM_TYPE = { CUSTOMERS: 0, DMC_PIPELINE: 1, SETTER_CLOSER: 2, }; function MyBadge({ count, color = "blue" }) { return ; } const segmentedDmcPipelineBadgeColors = [ "grey", "yellow", "purple", "blue", "orange", "green", "red", ]; const segmentedSetterCloserBadgeColors = [ "grey", "yellow", "purple", "blue", "orange", "green", "red", ]; export default function CrmTest() { const { t } = useTranslation(); const appContext = useAppContext(); const crmContext = useCrmContext(); const [isRequesting, setIsRequesting] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [notificationApi, notificationContextHolder] = notification.useNotification(); // used for the segmented component on top of the page const [selectedSegmentedTypeValue, setSelectedSegmentedTypeValue] = useState(1); const filterAssignedEmployeeRef = useRef([]); const [searchText, setSearchText] = useState(""); const [searchedColumn, setSearchedColumn] = useState(""); const searchInput = useRef(null); const title = selectedSegmentedTypeValue === CRM_TYPE.CUSTOMERS ? "crm.customers.pageTitle" : selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE ? "crm.dmcPipeline.pageTitle" : "crm.setterCloser.pageTitle"; const segmentedOptions = t( selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE ? "crm.dmcPipeline.segmentedOptions" : "crm.setterCloser.segmentedOptions", { returnObjects: true, } ); const [ selectedDmcPipelineSegmentedValue, setSelectedDmcPipelineSegmentedValue, ] = useState(0); const [ selectedSetterCloserSegmentedValue, setSelectedSetterCloserSegmentedValue, ] = useState(0); const sorter = (a, b) => { if (a < b) { return -1; } if (a > b) { return 1; } return 0; }; const sorterDates = (a, b) => { return a === Constants.TEXT_EMPTY_PLACEHOLDER ? 0 : new Date(a) - b === Constants.TEXT_EMPTY_PLACEHOLDER ? 0 : new Date(b); }; const handleSearch = (selectedKeys, confirm, dataIndex) => { confirm(); setSearchText(selectedKeys[0]); setSearchedColumn(dataIndex); }; const handleReset = (clearFilters) => { clearFilters(); setSearchText(""); }; const getColumnSearchProps = (dataIndex) => ({ filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close, }) => (
e.stopPropagation()} > setSelectedKeys(e.target.value ? [e.target.value] : []) } onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)} style={{ marginBottom: 8, display: "block", }} />
), filterIcon: (filtered) => ( ), onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()), onFilterDropdownOpenChange: (visible) => { if (visible) { setTimeout(() => searchInput.current?.select(), 100); } }, render: (text) => searchedColumn === dataIndex ? ( ) : ( text ), }); const getTableContent = () => { return [ { title: t("crm.table.assignedEmployee"), dataIndex: "assignedEmployee", key: "assignedEmployee", sorter: (a, b) => sorter(a._assignedEmployee, b._assignedEmployee), filters: filterAssignedEmployeeRef.current.map((item) => { return { text: appContext.users.find((u) => u.Id === item) !== undefined ? ( <> {" "} {appContext.users.find((u) => u.Id === item)?.Username} ) : ( item ), value: item, }; }), onFilter: (value, record) => record._assignedEmployee === value, ellipsis: true, }, { title: t("crm.table.firstName"), dataIndex: "firstName", key: "firstName", sorter: (a, b) => sorter(a.firstName, b.firstName), ...getColumnSearchProps("firstName"), }, { title: t("crm.table.lastName"), dataIndex: "lastName", key: "lastName", sorter: (a, b) => sorter(a.lastName, b.lastName), ...getColumnSearchProps("lastName"), }, { title: t("crm.table.company"), dataIndex: "company", key: "company", sorter: (a, b) => sorter(a.company, b.company), ...getColumnSearchProps("company"), }, { title: t("crm.table.createdAt"), dataIndex: "createdAt", key: "createdAt", sorter: (a, b) => sorterDates(a.createdAt, b.createdAt), sortDirections: ["descend"], }, { title: t("crm.table.telephone"), dataIndex: "telephone", key: "telephone", sorter: (a, b) => sorter(a.telephone, b.telephone), ...getColumnSearchProps("telephone"), }, { title: t("crm.table.email"), dataIndex: "email", key: "email", sorter: (a, b) => sorter(a.email, b.email), ...getColumnSearchProps("email"), }, { title: t("crm.table.lastContact"), dataIndex: "lastContact", key: "lastContact", sorter: (a, b) => sorterDates(a.lastContact, b.lastContact), sortDirections: ["descend"], }, { title: t("crm.table.updatedAt"), dataIndex: "updatedAt", key: "updatedAt", sorter: (a, b) => sorterDates(a.updatedAt, b.updatedAt), sortDirections: ["descend"], }, { title: t("crm.table.createdBy"), dataIndex: "createdBy", key: "createdBy", sorter: (a, b) => a.createdBy - b.createdBy, sortDirections: ["descend"], }, ]; }; const getTableItems = () => { let data = crmContext.customers; let items = []; if (selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE) { data = data.filter((item) => item.Pipeline === 1); data = data.filter( (item) => item.DealPhase === selectedDmcPipelineSegmentedValue + 1 ); } else if (selectedSegmentedTypeValue === CRM_TYPE.SETTER_CLOSER) { data = data.filter((item) => item.Pipeline === 2); data = data.filter( (item) => item.DealPhase === selectedSetterCloserSegmentedValue + 1 ); } data.forEach((item) => { items.push({ key: item.Id, _assignedEmployee: item.AssignedEmployee, assignedEmployee: ( <> } > <> {" "} { appContext.users.find((u) => u.Id === item.AssignedEmployee) ?.Username } ), id: item.Id, firstName: item.FirstName, lastName: item.LastName, company: item.Company, createdAt: FormatDatetime(item.CreatedAt), telephone: item.Telephone, email: item.Email, lastContact: FormatDatetime(item.LastContact), updatedAt: FormatDatetime(item.UpdatedAt), createdBy: ( <> } > <> {" "} { appContext.users.find((u) => u.Id === item.CreatedBy) ?.Username } ), }); }); return items; }; const getSegmentedLabels = (item, index) => { return ( {item} customer.Pipeline === (selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE ? 1 : 2) && customer.DealPhase === index + 1 ).length } color={ selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE ? segmentedDmcPipelineBadgeColors[index] : segmentedSetterCloserBadgeColors[index] } /> ); }; useEffect(() => { const customersRequest = () => { setIsRequesting(true); myFetch(`/crm/customers`, "GET").then((data) => { if (data === undefined || data === null) return; data.forEach((item) => { if ( !filterAssignedEmployeeRef.current.includes(item.AssignedEmployee) ) { filterAssignedEmployeeRef.current.push(item.AssignedEmployee); } }); crmContext.setCustomers(data); setIsRequesting(false); }); }; customersRequest(); const handleCustomersRequest = () => customersRequest(); document.addEventListener( wsConnectionCustomEventName, handleCustomersRequest ); return () => document.removeEventListener( wsConnectionCustomEventName, handleCustomersRequest ); }, []); return ( <> {notificationContextHolder} { setSelectedSegmentedTypeValue(value); }} options={[ { value: CRM_TYPE.CUSTOMERS, label: ( All Leads ), }, { value: CRM_TYPE.DMC_PIPELINE, label: ( DMC Pipeline item.Pipeline === 1) .length } /> ), }, { value: CRM_TYPE.SETTER_CLOSER, label: ( Setter / Closer item.Pipeline === 2) .length } /> ), }, ]} block />
{t(title)}
{selectedSegmentedTypeValue !== CRM_TYPE.CUSTOMERS ? ( { if (selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE) { setSelectedDmcPipelineSegmentedValue(value); } else { setSelectedSetterCloserSegmentedValue(value); } }} options={segmentedOptions.map((item, index) => { return { value: index, label: getSegmentedLabels(item, index), }; })} style={{ marginBottom: AppStyle.app.margin }} /> ) : (
)} { return { onClick: () => { setIsDrawerOpen(true); crmContext.openDrawerCustomerId.current = record.id; }, }; }} /> setIsDrawerOpen(false)} notificationApi={notificationApi} /> ); } function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) { const { t } = useTranslation(); const appContext = useAppContext(); const crmContext = useCrmContext(); const [formDealInfo] = Form.useForm(); const tabItems = [ { key: "0", label: t("crm.tabs.dealInfo"), children: , }, { key: "1", label: t("crm.tabs.activities"), children: , }, { key: "2", label: t("crm.tabs.notes"), children: ( ), }, ]; const [activeTab, setActiveTab] = useState(tabItems[0].key); const customerRequest = () => { if (!isOpen) return; myFetch( `/crm/customer/view/${crmContext.openDrawerCustomerId.current}`, "GET" ).then((data) => { const customer = data.Customer; crmContext.currentDrawerCustomerRef.current = customer; crmContext.setCurrentDrawerCallProtocols(data.CallProtocols); formDealInfo.setFieldsValue({ Pipeline: customer.Pipeline, DealPhase: customer.DealPhase, FirstName: customer.FirstName, LastName: customer.LastName, Telephone: customer.Telephone, Email: customer.Email, Company: customer.Company, ZipCode: customer.ZipCode, Address: customer.Address, City: customer.City, Country: customer.Country, FederalState: customer.FederalState, Website: customer.Website, LeadOrigin: customer.LeadOrigin, NumberOfEmployees: customer.NumberOfEmployees, NumberOfJobsSearchedFor: customer.NumberOfJobsSearchedFor, JobTitle: customer.JobTitle, NumberOfEmployeesRequired: customer.NumberOfEmployeesRequired, HowLongHadHeBeenSearching: customer.HowLongHadHeBeenSearching, Turnover: customer.Turnover, DateOfcompletion: customer.DateOfcompletion, OrderVolume: customer.OrderVolume, NumberOfInstallments: customer.NumberOfInstallments, AmountsOfTheInstallments: customer.AmountsOfTheInstallments, BookedPackages: customer.BookedPackages, AssignedEmployee: customer.AssignedEmployee, }); }); }; const handleRequestError = (status) => { if (status === 409) { notificationApi["error"]({ message: t("crm.tabContent.request.duplicateCompanyError.message"), description: t( "crm.tabContent.request.duplicateCompanyError.description" ), }); } else { showUnkownErrorNotification(notificationApi, t); } setIsOpen(true); }; useEffect(() => { // set the active tab to the first one if the drawer is closed if (!isOpen) { setActiveTab(tabItems[0].key); } if (crmContext.openDrawerCustomerId.current === null) return; formDealInfo .validateFields() .then((values) => { if (crmContext.openDrawerCustomerId.current === "new") { if (isOpen) { formDealInfo.resetFields(); formDealInfo.setFieldsValue({ Pipeline: 1, DealPhase: 1, AssignedEmployee: appContext.userId.current, }); crmContext.setCurrentDrawerCallProtocols([]); return; } let changedFields = []; let newCustomer = {}; Object.keys(values).forEach((key) => { if (values[key] !== undefined) { changedFields.push(key); } }); // check notes if (crmContext.currentDrawerCustomerNotesRef.current !== null) { changedFields.push("Notes"); } // check if something has changed (length 2 = only the pipeline, deal phase and assigned employee) if (changedFields.length === 3) return; Object.keys(values).forEach((key) => { if (values[key] !== undefined) { newCustomer[key] = values[key]; } }); if (changedFields.includes("Notes")) { newCustomer.Notes = crmContext.currentDrawerCustomerNotesRef.current; } myFetch(`/crm/customer/create`, "POST", newCustomer) .then(() => { crmContext.openDrawerCustomerId.current = false; handleCloseDrawer(); }) .catch((status) => handleRequestError(status)); return; } if (isOpen) { customerRequest(); } else { let changedFields = []; const updatedCustomer = {}; Object.keys(values).forEach((key) => { if ( values[key] !== crmContext.currentDrawerCustomerRef.current[key] ) { changedFields.push(key); } }); // check notes if ( crmContext.currentDrawerCustomerNotesRef.current !== null && crmContext.currentDrawerCustomerNotesRef.current !== crmContext.currentDrawerCustomerRef.current.Notes ) { changedFields.push("Notes"); } // check if something has changed if (changedFields.length > 0) { changedFields.forEach((key) => { updatedCustomer[key] = values[key]; }); if (changedFields.includes("Notes")) { updatedCustomer.Notes = crmContext.currentDrawerCustomerNotesRef.current; } myFetch( `/crm/customer/update/${crmContext.openDrawerCustomerId.current}`, "POST", updatedCustomer ) .then(() => handleCloseDrawer()) .catch((status) => handleRequestError(status)); } } }) .catch((err) => console.log("error", err)); const handleCustomerRequest = () => customerRequest(); document.addEventListener( wsConnectionCustomEventName, handleCustomerRequest ); return () => document.removeEventListener( wsConnectionCustomEventName, handleCustomerRequest ); }, [isOpen]); const handleCloseDrawer = () => { crmContext.openDrawerCustomerId.current = null; crmContext.currentDrawerCustomerRef.current = null; crmContext.currentDrawerCustomerNotesRef.current = null; formDealInfo.resetFields(); }; return (
} > setActiveTab(activeKey)} items={tabItems} centered />
); } function Content({ children }) { return (
{children[0]} {children[1]} ); } function CollapseContainer({ children, label }) { return ( ); } function TabContentDealInfo({ form }) { const pipeline = Form.useWatch("Pipeline", form); const dealPhase = Form.useWatch("DealPhase", form); const FormItem = ({ name, label }) => ( ); const ContentBlock = ({ items }) => { // Split items into chunks of two const chunks = []; for (let i = 0; i < items.length; i += 2) { chunks.push(items.slice(i, i + 2)); } // Render a Content component for each chunk return ( <> {chunks.map((chunk, index) => ( {chunk.map((item) => ( ))} ))} ); }; const CollapseContainerBlock = ({ label, items }) => ( ); // set deal phase to 1 when the pipeline changes // without this the user could set a deal phase that is not available for the selected pipeline useEffect(() => { const options = t( pipeline === 1 ? "crm.dmcPipeline.segmentedOptions" : "crm.setterCloser.segmentedOptions", { returnObjects: true } ); if (dealPhase > options.length) { form.setFieldsValue({ DealPhase: options.length, }); } }, [pipeline]); return (
{ return { value: index + 1, label: item, }; })} /> {t( "crm.tabContent.dealInfo.collapseMasterDataOfContact.website" )} { const website = form.getFieldValue("Website"); if ( !website.startsWith("http://") && !website.startsWith("https://") ) return; window.open( form.getFieldValue("Website"), "_blank", "noopener" ); }} /> ), }, { name: "LeadOrigin", label: "crm.tabContent.dealInfo.collapseMasterDataOfContact.leadOrigin", }, ]} />
); } function TabContentActivities({ notificationApi }) { const { t } = useTranslation(); return ( ), }, { key: 1, label: t("crm.tabContent.activities.emails.title"), disabled: true, }, ]} /> ); } function ActivityCallProtocols({ notificationApi }) { const { t } = useTranslation(); const appContext = useAppContext(); const crmContext = useCrmContext(); if (crmContext.currentDrawerCallProtocols.length === 0) { return ; } const sortedCallProtocols = crmContext.currentDrawerCallProtocols.sort( (a, b) => new Date(b.CalledAt) - new Date(a.CalledAt) ); return ( {sortedCallProtocols.map((item) => { const optionsCallResult = t("crm.callProtocolModal.callResult", { returnObjects: true, }); const callResult = []; let alreadyLast = false; let results = [ "ResultReached", "ResultWhoPickedUp", "ResultExpressedInterest", "ResultScheduled", ]; results.forEach((result, index) => { if (alreadyLast) return; callResult.push( optionsCallResult[index].result[item[result] === 1 ? 0 : 1] ); if (item[result] === 0) { // stop if the result is not set alreadyLast = true; } }); const notes = []; notes.push(`${FormatDatetime(item.CalledAt)} Uhr`); if (item.Telephone !== "") { notes.push(`Tel: ${item.Telephone}`); } if (item.Notes !== "") { notes.push(`${t("crm.callProtocolModal.notes")}: ${item.Notes}`); } /* {`${FormatDatetime(item.CalledAt)} Uhr${ item.Telephone !== "" ? ` - Tel: ${item.Telephone}` : "" } ${ item.Notes !== "" ? `- ${t("crm.callProtocolModal.notes")}: ${( <> {item.Notes} )}` : "" }`} */ return ( } title={ {`${ t("crm.callProtocolModal.callTypeOptions", { returnObjects: true, })[item.CallType - 1] }: ${callResult.join(", ")}`} { myFetch(`/crm/calls/delete/${item.Id}`, "DELETE").catch( () => showUnkownErrorNotification(notificationApi, t) ); }} > } description={ {notes.join(" - ")} } /> ); })} ); } const typeCallResult = { step: 0, answers: [], finished: false, // if click cancel or answered all questions }; function CallProtocolModal({ formDealInfo, notificationApi }) { const { t } = useTranslation(); const crmContext = useCrmContext(); const [isOpen, setIsOpen] = useState(false); const [form] = Form.useForm(); const [isRequesting, setIsRequesting] = useState(false); const [callResult, setCallResult] = useState(typeCallResult); const telephone = Form.useWatch("telephone", form); // used to check if the customer has changed and reset the form const lastCustomerIdRef = useRef(null); const handleCancel = () => setIsOpen(false); useEffect(() => { if (isOpen) { const currentDate = new Date(); // check if the customer has changed and reset the form if ( lastCustomerIdRef.current !== crmContext.openDrawerCustomerId.current ) { lastCustomerIdRef.current = crmContext.openDrawerCustomerId.current; setCallResult(typeCallResult); form.resetFields(); } form.setFieldsValue({ callType: 1, reached: "Ja", date: dayjs( `${currentDate.getFullYear()}-${(currentDate.getMonth() + 1) .toString() .padStart(2, "0")}-${currentDate .getDate() .toString() .padStart(2, "0")}`, "YYYY-MM-DD" ), time: dayjs( `${currentDate.getHours().toString().padStart(2, "0")}:${currentDate .getMinutes() .toString() .padStart(2, "0")}-${currentDate .getSeconds() .toString() .padStart(2, "0")}`, "HH:mm:ss" ), telephone: formDealInfo.getFieldValue("Telephone"), }); } }, [isOpen]); const optionsCallResult = t("crm.callProtocolModal.callResult", { returnObjects: true, }); const buttonCallResultTextYes = optionsCallResult[callResult.step].yes !== undefined ? t("crm.callProtocolModal.callResult", { returnObjects: true, })[callResult.step].yes : t("common.text.yes"); const buttonCallResultTextNo = optionsCallResult[callResult.step].no !== undefined ? t("crm.callProtocolModal.callResult", { returnObjects: true, })[callResult.step].no : t("common.text.no"); return ( <> { form .validateFields() .then((values) => { setIsRequesting(true); // check if all questions are answered and the last answer is not no if ( callResult.answers.length < optionsCallResult.length && callResult.answers[callResult.answers.length - 1] !== 0 ) { setIsRequesting(false); showInputsInvalidNotification(notificationApi, t); return; } // create datetime by combining date and time const date = values.date; const time = values.time; const datetime = new Date( date.year(), date.month(), date.date(), time.hour(), time.minute(), time.second() ); myFetch("/crm/calls/create", "POST", { CustomerId: crmContext.openDrawerCustomerId.current, CallType: values.callType, CalledAt: datetime, Telephone: values.telephone, Notes: values.notes, ResultReached: callResult.answers[0] === null ? 0 : callResult.answers[0], ResultWhoPickedUp: callResult.answers[1] === null ? 0 : callResult.answers[1], ResultExpressedInterest: callResult.answers[2] === null ? 0 : callResult.answers[2], ResultScheduled: callResult.answers[3] === null ? 0 : callResult.answers[3], }) .then(() => { setIsRequesting(false); setIsOpen(false); setCallResult(typeCallResult); }) .catch(() => { setIsRequesting(false); showUnkownErrorNotification(notificationApi, t); }); }) .catch(() => { setIsRequesting(false); showInputsInvalidNotification(notificationApi, t); }); }} /> } >
{callResult.finished ? ( {t("crm.callProtocolModal.callResultText")} setCallResult({ step: 0, answers: [], finished: false, }) } > {t("common.button.reset")} } >
{callResult.answers.map((item, index) => { return ( { optionsCallResult[index].result[ item === 1 ? 0 : 1 ] } {index !== callResult.answers.length - 1 && ( {" > "} )} ); })}
) : ( )}
{telephone !== "" ? ( ) : ( )}
); } function TabContentNotes({ notes }) { const crmContext = useCrmContext(); return ( { crmContext.currentDrawerCustomerNotesRef.current = value; }} /> ); } function MyMxEditor({ _key, markdown, onChange }) { return ( ( <> ), }), ]} /> ); }