diff --git a/src/Components/AppRoutes/index.js b/src/Components/AppRoutes/index.js index 695fa6a..129887e 100644 --- a/src/Components/AppRoutes/index.js +++ b/src/Components/AppRoutes/index.js @@ -24,6 +24,7 @@ const ViewEquipmentDocumentations = lazy(() => const Consoles = lazy(() => import("../../Pages/Consoles")); const RoboticsRobots = lazy(() => import("../../Pages/Robotics/Robots")); const Crm = lazy(() => import("../../Pages/Crm")); +const CrmTest = lazy(() => import("../../Pages/CrmTest/CrmTest")); export default function AppRoutes({ userSession, setUserSession }) { const appContext = useAppContext(); @@ -248,10 +249,10 @@ export default function AppRoutes({ userSession, setUserSession }) { Constants.PERMISSIONS.CRM.SETTER_CLOSER.VIEW ) && ( - + } /> @@ -268,3 +269,24 @@ export default function AppRoutes({ userSession, setUserSession }) { ); } + +/* + + + + {hasOnePermission( + appContext.userPermissions, + Constants.PERMISSIONS.CRM.CUSTOMERS.VIEW, + Constants.PERMISSIONS.CRM.DMC_PIPELINE.VIEW, + Constants.PERMISSIONS.CRM.SETTER_CLOSER.VIEW + ) && ( + + + + } + /> + )} +*/ diff --git a/src/Components/SideMenu/index.js b/src/Components/SideMenu/index.js index 0ad1392..1d218a3 100644 --- a/src/Components/SideMenu/index.js +++ b/src/Components/SideMenu/index.js @@ -169,6 +169,19 @@ export function SideMenuContent({ children: [], }; + if ( + hasPermission( + appContext.userPermissions, + Constants.PERMISSIONS.CRM.CUSTOMERS.VIEW + ) + ) { + crmGroup.children.push({ + label: t("sideMenu.crm.customers"), + icon: , + key: Constants.ROUTE_PATHS.CRM_TEST, + }); + } + /* if ( hasPermission( appContext.userPermissions, @@ -207,7 +220,7 @@ export function SideMenuContent({ key: `${Constants.ROUTE_PATHS.CRM}${Constants.CRM_TYPE.SETTER_CLOSER}/1`, }); } - +*/ items.push(crmGroup); } diff --git a/src/Handlers/WebSocketMessageHandler.js b/src/Handlers/WebSocketMessageHandler.js index 1e96823..684b613 100644 --- a/src/Handlers/WebSocketMessageHandler.js +++ b/src/Handlers/WebSocketMessageHandler.js @@ -1061,6 +1061,8 @@ export function handleWebSocketMessage( crmContext.setCustomers((arr) => { const newArr = [...arr]; + console.log("before", arr); + const arrIndex = arr.findIndex((customer) => customer.Id === body.Id); if (arrIndex !== -1) { @@ -1068,10 +1070,14 @@ export function handleWebSocketMessage( for (const property in body) { if (body[property] !== undefined && property !== "Id") { newArr[arrIndex][property] = body[property]; + + console.log("updated", property, body, body[property]); } } } + console.log("after", newArr); + return newArr; }); diff --git a/src/Pages/Crm/index.js b/src/Pages/Crm/index.js index 288336e..8dc37de 100644 --- a/src/Pages/Crm/index.js +++ b/src/Pages/Crm/index.js @@ -301,6 +301,24 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) { }); }; + const handleRequestError = (status) => { + if (status === 409) { + notificationApi["error"]({ + message: t("crm.tabContent.request.duplicateCompanyError.message"), + description: t( + "crm.tabContent.request.duplicateCompanyError.description" + ), + }); + } else { + notificationApi["error"]({ + message: "test", + description: "here", + }); + } + + setIsOpen(true); + }; + useEffect(() => { if (crmContext.openDrawerCustomerId.current === null) return; @@ -315,25 +333,7 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) { crmContext.openDrawerCustomerId.current = false; handleCloseDrawer(); }) - .catch((status) => { - if (status === 409) { - notificationApi["error"]({ - message: t( - "crm.tabContent.request.duplicateCompanyError.message" - ), - description: t( - "crm.tabContent.request.duplicateCompanyError.description" - ), - }); - } else { - notificationApi["error"]({ - message: "test", - description: "here", - }); - } - - setIsOpen(true); - }); + .catch((status) => handleRequestError(status)); return; } @@ -362,31 +362,9 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) { "POST", updatedCustomer ) - .then(() => { - handleCloseDrawer(); - }) - .catch((status) => { - if (status === 409) { - notificationApi["error"]({ - message: t( - "crm.tabContent.request.duplicateCompanyError.message" - ), - description: t( - "crm.tabContent.request.duplicateCompanyError.description" - ), - }); - } else { - notificationApi["error"]({ - message: "test", - description: "here", - }); - } - - setIsOpen(true); - }); + .then(() => handleCloseDrawer()) + .catch((status) => handleRequestError(status)); } - - //handleCloseDrawer(); } const handleCustomerRequest = () => customerRequest(); diff --git a/src/Pages/CrmTest/CrmTest.js b/src/Pages/CrmTest/CrmTest.js new file mode 100644 index 0000000..c1223a4 --- /dev/null +++ b/src/Pages/CrmTest/CrmTest.js @@ -0,0 +1,816 @@ +import { + Button, + Col, + Collapse, + Drawer, + Form, + Input, + Row, + Segmented, + Select, + Space, + Table, + Tabs, + Typography, + notification, +} from "antd"; +import { useTranslation } from "react-i18next"; +import { + AppStyle, + FormatDatetime, + myFetch, + showUnkownErrorNotification, + wsConnectionCustomEventName, +} from "../../utils"; +import { useEffect, useState } from "react"; +import { PlusOutlined } 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"; + +const CRM_TYPE = { + CUSTOMERS: 0, + DMC_PIPELINE: 1, + SETTER_CLOSER: 2, +}; + +export default function CrmTest() { + const { t } = useTranslation(); + 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(0); + + 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 getTableContent = () => { + return [ + { + title: t("crm.table.firstName"), + dataIndex: "firstName", + key: "firstName", + }, + { + title: t("crm.table.lastName"), + dataIndex: "lastName", + key: "lastName", + }, + { + title: t("crm.table.createdAt"), + dataIndex: "createdAt", + key: "createdAt", + }, + { + title: t("crm.table.telephone"), + dataIndex: "telephone", + key: "telephone", + }, + { + title: t("crm.table.email"), + dataIndex: "email", + key: "email", + }, + { + title: t("crm.table.lastContact"), + dataIndex: "lastContact", + key: "lastContact", + }, + { + title: t("crm.table.createdBy"), + dataIndex: "createdBy", + key: "createdBy", + }, + ]; + }; + + 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, + id: item.Id, + firstName: item.FirstName, + lastName: item.LastName, + createdAt: FormatDatetime(item.CreatedAt), + telephone: item.Telephone, + email: item.Email, + lastContact: FormatDatetime(item.LastContact), + createdBy: item.CreatedBy, + }); + }); + + return items; + }; + + useEffect(() => { + const customersRequest = () => { + setIsRequesting(true); + + myFetch(`/crm/customers`, "GET").then((data) => { + if (data === undefined || data === null) return; + + 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 customers", + }, + { + value: CRM_TYPE.DMC_PIPELINE, + label: "DMC Pipeline", + }, + { + value: CRM_TYPE.SETTER_CLOSER, + label: "Setter Closer", + }, + ]} + 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: item, + }; + })} + 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 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) => { + crmContext.currentDrawerCustomerRef.current = data; + + formDealInfo.setFieldsValue({ + Pipeline: data.Pipeline, + DealPhase: data.DealPhase, + FirstName: data.FirstName, + LastName: data.LastName, + Telephone: data.Telephone, + Email: data.Email, + Company: data.Company, + ZipCode: data.ZipCode, + Address: data.Address, + City: data.City, + Country: data.Country, + FederalState: data.FederalState, + Website: data.Website, + LeadOrigin: data.LeadOrigin, + NumberOfEmployees: data.NumberOfEmployees, + NumberOfJobsSearchedFor: data.NumberOfJobsSearchedFor, + JobTitle: data.JobTitle, + NumberOfEmployeesRequired: data.NumberOfEmployeesRequired, + HowLongHadHeBeenSearching: data.HowLongHadHeBeenSearching, + Turnover: data.Turnover, + DateOfcompletion: data.DateOfcompletion, + OrderVolume: data.OrderVolume, + NumberOfInstallments: data.NumberOfInstallments, + AmountsOfTheInstallments: data.AmountsOfTheInstallments, + BookedPackages: data.BookedPackages, + AssignedEmployee: data.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(() => { + 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, + }); + return; + } + + let changedFields = []; + + Object.keys(values).forEach((key) => { + if (values[key] !== undefined) { + changedFields.push(key); + } + }); + + // check if something has changed (length 2 = only the pipeline and deal phase) + if (changedFields.length === 2) return; + + let newCustomer = {}; + + Object.keys(values).forEach((key) => { + if (values[key] !== undefined) { + newCustomer[key] = values[key]; + } + }); + + myFetch(`/crm/customer/create`, "POST", newCustomer) + .then(() => { + crmContext.openDrawerCustomerId.current = false; + handleCloseDrawer(); + }) + .catch((status) => handleRequestError(status)); + return; + } + + if (isOpen) { + customerRequest(); + } else { + let changedFields = []; + + Object.keys(values).forEach((key) => { + if ( + values[key] !== crmContext.currentDrawerCustomerRef.current[key] + ) { + changedFields.push(key); + } + }); + + // check if something has changed + if (changedFields.length > 0) { + // only updated changed fields + const updatedCustomer = {}; + + changedFields.forEach((key) => { + updatedCustomer[key] = values[key]; + }); + + myFetch( + `/crm/customer/update/${crmContext.openDrawerCustomerId.current}`, + "POST", + updatedCustomer + ) + .then(() => handleCloseDrawer()) + .catch((status) => handleRequestError(status)); + } + } + }) + .catch(() => {}); + + const handleCustomerRequest = () => customerRequest(); + + document.addEventListener( + wsConnectionCustomEventName, + handleCustomerRequest + ); + + return () => + document.removeEventListener( + wsConnectionCustomEventName, + handleCustomerRequest + ); + }, [isOpen]); + + const handleCloseDrawer = () => { + crmContext.openDrawerCustomerId.current = null; + crmContext.currentDrawerCustomerRef.current = null; + crmContext.changedDrawerCustomerFieldsRef.current = []; + + formDealInfo.resetFields(); + + setActiveTab(tabItems[0].key); + }; + + const title = + crmContext.currentDrawerCustomer === null + ? "loading..." + : `${crmContext.currentDrawerCustomer?.FirstName} ${crmContext.currentDrawerCustomer?.LastName}`; + + return ( + { + onClose(); + handleCloseDrawer(); + }} + > + {t("crm.buttonUndo")} + + } + > + 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 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 }) => ( + + + + ); + + return ( + +
+ + + + + { + return { + value: index + 1, + label: item, + }; + })} + /> + + + + + + + + + + + +
+ ); +} + +function TabContentActivities() { + return ( + +
Coming soon
+
+ ); +} + +function TabContentNotes({ notes }) { + const crmContext = useCrmContext(); + + return ( + + { + crmContext.setCurrentDrawerCustomer({ + ...crmContext.currentDrawerCustomer, + Notes: value, + }); + }} + plugins={[ + listsPlugin(), + quotePlugin(), + headingsPlugin(), + linkPlugin(), + linkDialogPlugin(), + tablePlugin(), + thematicBreakPlugin(), + frontmatterPlugin(), + markdownShortcutPlugin(), + toolbarPlugin({ + toolbarContents: () => ( + <> + + + + ), + }), + ]} + /> + + ); +} diff --git a/src/utils.js b/src/utils.js index 5af88a7..202ec09 100644 --- a/src/utils.js +++ b/src/utils.js @@ -91,8 +91,10 @@ export const Constants = { CONSOLES: "/consoles", ROBOTICS_ROBOTS: "/robotics/robots", CRM: "/crm/", + CRM_TEST: "/crm/test", }, CRM_TYPE: { + TEST_CUSTOMERS: "test-customers", CUSTOMERS: "customers", DMC_PIPELINE: "dmc-pipeline", SETTER_CLOSER: "setter-closer",