live sync

main
alex 2023-12-06 23:06:52 +01:00
parent f99a0ab573
commit 831fa8e0ab
7 changed files with 319 additions and 67 deletions

View File

@ -15,6 +15,7 @@ import { UsersProvider } from "./Contexts/UsersContext";
import HeaderProvider from "./Contexts/HeaderContext"; import HeaderProvider from "./Contexts/HeaderContext";
import ConsolesProvider from "./Contexts/ConsolesContext"; import ConsolesProvider from "./Contexts/ConsolesContext";
import ScannerProvider from "./Contexts/ScannerContext"; import ScannerProvider from "./Contexts/ScannerContext";
import { CrmProvider } from "./Contexts/CrmContext";
export default function App() { export default function App() {
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
@ -47,22 +48,24 @@ export default function App() {
<UsersProvider> <UsersProvider>
<ConsolesProvider> <ConsolesProvider>
<ScannerProvider> <ScannerProvider>
<WebSocketProvider <CrmProvider>
userSession={userSession} <WebSocketProvider
setUserSession={setUserSession}
isWebSocketReady={isWebSocketReady}
setIsWebSocketReady={setIsWebSocketReady}
notificationApi={notificationApi}
>
<ReconnectingView
isWebSocketReady={isWebSocketReady}
/>
<DashboardLayout
userSession={userSession} userSession={userSession}
setUserSession={setUserSession} setUserSession={setUserSession}
/> isWebSocketReady={isWebSocketReady}
</WebSocketProvider> setIsWebSocketReady={setIsWebSocketReady}
notificationApi={notificationApi}
>
<ReconnectingView
isWebSocketReady={isWebSocketReady}
/>
<DashboardLayout
userSession={userSession}
setUserSession={setUserSession}
/>
</WebSocketProvider>
</CrmProvider>
</ScannerProvider> </ScannerProvider>
</ConsolesProvider> </ConsolesProvider>
</UsersProvider> </UsersProvider>

View File

@ -272,7 +272,7 @@ export default function AppRoutes({ userSession, setUserSession }) {
Constants.PERMISSIONS.CRM.SETTER_CLOSER.VIEW Constants.PERMISSIONS.CRM.SETTER_CLOSER.VIEW
) && ( ) && (
<Route <Route
path={`${Constants.ROUTE_PATHS.CRM}:paramType`} path={`${Constants.ROUTE_PATHS.CRM}:paramType/:paramDealPhase`}
element={ element={
<SuspenseFallback> <SuspenseFallback>
<Crm /> <Crm />

View File

@ -178,7 +178,7 @@ export function SideMenuContent({
crmGroup.children.push({ crmGroup.children.push({
label: t("sideMenu.crm.customers"), label: t("sideMenu.crm.customers"),
icon: <FileTextOutlined />, icon: <FileTextOutlined />,
key: Constants.ROUTE_PATHS.CRM + Constants.CRM_TYPE.CUSTOMERS, key: `${Constants.ROUTE_PATHS.CRM}${Constants.CRM_TYPE.CUSTOMERS}/1`,
}); });
} }
@ -191,7 +191,7 @@ export function SideMenuContent({
crmGroup.children.push({ crmGroup.children.push({
label: t("sideMenu.crm.dmcPipeline"), label: t("sideMenu.crm.dmcPipeline"),
icon: <FileTextOutlined />, icon: <FileTextOutlined />,
key: Constants.ROUTE_PATHS.CRM + Constants.CRM_TYPE.DMC_PIPELINE, key: `${Constants.ROUTE_PATHS.CRM}${Constants.CRM_TYPE.DMC_PIPELINE}/1`,
}); });
} }
@ -204,7 +204,7 @@ export function SideMenuContent({
crmGroup.children.push({ crmGroup.children.push({
label: t("sideMenu.crm.setterCloser"), label: t("sideMenu.crm.setterCloser"),
icon: <FileTextOutlined />, icon: <FileTextOutlined />,
key: Constants.ROUTE_PATHS.CRM + Constants.CRM_TYPE.SETTER_CLOSER, key: `${Constants.ROUTE_PATHS.CRM}${Constants.CRM_TYPE.SETTER_CLOSER}/1`,
}); });
} }

View File

@ -0,0 +1,49 @@
import { createContext, useContext, useRef, useState } from "react";
const preview = {
paginationPage: 1,
paginationPageRef: null,
totalPages: 0,
customers: [],
openDrawerCustomerId: null,
currentDrawerCustomer: null,
currentDrawerCustomerRef: null,
};
const CrmContext = createContext(preview);
export const useCrmContext = () => useContext(CrmContext);
export function CrmProvider({ children }) {
const [paginationPage, setPaginationPage] = useState(1);
const paginationPageRef = useRef(paginationPage);
const [totalPages, setTotalPages] = useState(0);
const [customers, setCustomers] = useState([]);
// will be used to store the customer id that is currently being viewed in the drawer
const openDrawerCustomerId = useRef(null);
// will be used to store the customer object that is currently being viewed in the drawer
const [currentDrawerCustomer, setCurrentDrawerCustomer] = useState(null);
// will be set on drawer open and used to check if the customer has changed
const currentDrawerCustomerRef = useRef(null);
return (
<CrmContext.Provider
value={{
paginationPage,
setPaginationPage,
paginationPageRef,
totalPages,
setTotalPages,
customers,
setCustomers,
openDrawerCustomerId,
currentDrawerCustomer,
setCurrentDrawerCustomer,
currentDrawerCustomerRef,
}}
>
{children}
</CrmContext.Provider>
);
}

View File

@ -16,6 +16,7 @@ import { useUsersContext } from "./UsersContext";
import { useHeaderContext } from "./HeaderContext"; import { useHeaderContext } from "./HeaderContext";
import { useConsolesContext } from "./ConsolesContext"; import { useConsolesContext } from "./ConsolesContext";
import { useScannerContext } from "./ScannerContext"; import { useScannerContext } from "./ScannerContext";
import { useCrmContext } from "./CrmContext";
const WebSocketContext = createContext(null); const WebSocketContext = createContext(null);
@ -44,6 +45,7 @@ export default function WebSocketProvider({
const usersContext = useUsersContext(); const usersContext = useUsersContext();
const consolesContext = useConsolesContext(); const consolesContext = useConsolesContext();
const scannerContext = useScannerContext(); const scannerContext = useScannerContext();
const crmContext = useCrmContext();
if (wsConnectionEvent === null) { if (wsConnectionEvent === null) {
wsConnectionEvent = new CustomEvent(wsConnectionCustomEventName, { wsConnectionEvent = new CustomEvent(wsConnectionCustomEventName, {
@ -103,7 +105,8 @@ export default function WebSocketProvider({
adminAreaRolesContext, adminAreaRolesContext,
usersContext, usersContext,
consolesContext, consolesContext,
scannerContext scannerContext,
crmContext
); );
}; };

View File

@ -48,6 +48,7 @@ export const ReceivedMessagesCommands = {
AdminAreaManageCheckedForAvailableCategories: 44, AdminAreaManageCheckedForAvailableCategories: 44,
AdminAreaManageLogManagerServerConnectionAdded: 45, AdminAreaManageLogManagerServerConnectionAdded: 45,
AdminAreaManageLogManagerServerConnectionRemoved: 46, AdminAreaManageLogManagerServerConnectionRemoved: 46,
CrmCustomerUpdated: 47,
}; };
// commands sent to the backend server // commands sent to the backend server
@ -92,7 +93,8 @@ export function handleWebSocketMessage(
adminAreaRolesContext, adminAreaRolesContext,
usersContext, usersContext,
consolesContext, consolesContext,
scannerContext scannerContext,
crmContext
) { ) {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
@ -1027,6 +1029,32 @@ export function handleWebSocketMessage(
arr.filter((c) => c.Id !== body) arr.filter((c) => c.Id !== body)
); );
break; break;
case ReceivedMessagesCommands.CrmCustomerUpdated:
console.log("CrmCustomerUpdated", body);
console.log("current", crmContext.currentDrawerCustomerRef.current);
// update drawer customer if it is the same customer
if (crmContext.currentDrawerCustomerRef.current !== null) {
if (crmContext.currentDrawerCustomerRef.current.Id === body.Id) {
crmContext.setCurrentDrawerCustomer(body);
}
}
// update customers list
crmContext.setCustomers((arr) => {
const newArr = [...arr];
const arrIndex = arr.findIndex((customer) => customer.Id === body.Id);
if (arrIndex !== -1) {
newArr[arrIndex] = body;
}
return newArr;
});
break;
default: default:
console.error("unknown command", cmd); console.error("unknown command", cmd);

View File

@ -14,15 +14,25 @@ import {
Typography, Typography,
} from "antd"; } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { AppStyle, Constants } from "../../utils"; import {
AppStyle,
Constants,
myFetch,
wsConnectionCustomEventName,
} from "../../utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { PlusOutlined } from "@ant-design/icons"; import { PlusOutlined } from "@ant-design/icons";
import { t } from "i18next"; import { t } from "i18next";
import PageNotFound from "../PageNotFound";
import { useCrmContext } from "../../Contexts/CrmContext";
import MyPagination from "../../Components/MyPagination";
export default function Crm() { export default function Crm() {
const { t } = useTranslation(); const { t } = useTranslation();
const { paramType } = useParams(); const navigate = useNavigate();
const crmContext = useCrmContext();
const { paramType, paramDealPhase } = useParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const title = const title =
@ -41,9 +51,7 @@ export default function Crm() {
} }
); );
const [selectedSegmentedValue, setSelectedSegmentedValue] = useState( const [selectedSegmentedValue, setSelectedSegmentedValue] = useState([]);
segmentedOptions[0]
);
const getTableContent = () => { const getTableContent = () => {
return [ return [
@ -92,31 +100,72 @@ export default function Crm() {
}; };
const getTableItems = () => { const getTableItems = () => {
return [ let items = [];
{
key: "1", crmContext.customers.forEach((item) => {
id: "", items.push({
firstName: "Max", key: item.Id,
}, id: item.Id,
{ firstName: item.FirstName,
key: "2", lastName: item.LastName,
id: "", createdAt: item.CreatedAt,
firstName: "Peter", telephone: item.Telephone,
}, email: item.Email,
{ lastContact: item.LastContact,
key: "3", createdBy: item.CreatedBy,
id: "", notes: item.Notes,
firstName: "Anna", });
}, });
];
return items;
};
const onPaginationChange = (page) => {
crmContext.setPaginationPage(page);
crmContext.paginationPageRef.current = page;
}; };
useEffect(() => { useEffect(() => {
if (paramType === Constants.CRM_TYPE.CUSTOMERS) return; if (paramType === Constants.CRM_TYPE.CUSTOMERS) return;
setSelectedSegmentedValue(segmentedOptions[0]); setSelectedSegmentedValue(segmentedOptions[paramDealPhase - 1]);
}, [paramType]); }, [paramType]);
useEffect(() => {
const customersRequest = () =>
myFetch(
`/crm/pipeline/${paramType}/${paramDealPhase}?page=${crmContext.paginationPage}`,
"GET"
).then((data) => {
if (data.Customers !== undefined) {
crmContext.setCustomers(data.Customers);
}
if (data.TotalPages !== undefined) {
crmContext.setTotalPages(data.TotalPages);
}
});
customersRequest();
const handleCustomersRequest = () => customersRequest();
document.addEventListener(
wsConnectionCustomEventName,
handleCustomersRequest
);
return () =>
document.removeEventListener(
wsConnectionCustomEventName,
handleCustomersRequest
);
}, [paramType, paramDealPhase]);
if (paramDealPhase > segmentedOptions.length) {
return <PageNotFound />;
}
return ( return (
<> <>
<div <div
@ -135,8 +184,19 @@ export default function Crm() {
{paramType !== Constants.CRM_TYPE.CUSTOMERS && ( {paramType !== Constants.CRM_TYPE.CUSTOMERS && (
<Segmented <Segmented
value={selectedSegmentedValue} value={selectedSegmentedValue}
onChange={(value) => setSelectedSegmentedValue(value)} onChange={(value) => {
options={segmentedOptions} setSelectedSegmentedValue(value);
navigate(
`/crm/${paramType}/${segmentedOptions.indexOf(value) + 1}`,
true
);
}}
options={segmentedOptions.map((item) => {
return {
value: item,
label: item,
};
})}
style={{ marginBottom: AppStyle.app.margin }} style={{ marginBottom: AppStyle.app.margin }}
/> />
)} )}
@ -145,16 +205,23 @@ export default function Crm() {
scroll={{ x: "max-content" }} scroll={{ x: "max-content" }}
columns={getTableContent()} columns={getTableContent()}
dataSource={getTableItems()} dataSource={getTableItems()}
onRow={(record, rowIndex) => { pagination={false}
onRow={(record) => {
return { return {
onClick: (event) => { onClick: () => {
console.log("row", record, rowIndex);
setIsDrawerOpen(true); setIsDrawerOpen(true);
crmContext.openDrawerCustomerId.current = record.id;
}, },
}; };
}} }}
/> />
<MyPagination
paginationPage={crmContext.paginationPage}
setPaginationPage={(page) => onPaginationChange(page)}
totalPages={crmContext.totalPages}
/>
<CustomerDrawer <CustomerDrawer
isOpen={isDrawerOpen} isOpen={isDrawerOpen}
onClose={() => setIsDrawerOpen(false)} onClose={() => setIsDrawerOpen(false)}
@ -165,6 +232,7 @@ export default function Crm() {
function CustomerDrawer({ isOpen, onClose }) { function CustomerDrawer({ isOpen, onClose }) {
const { t } = useTranslation(); const { t } = useTranslation();
const crmContext = useCrmContext();
const tabItems = [ const tabItems = [
{ {
@ -184,14 +252,73 @@ function CustomerDrawer({ isOpen, onClose }) {
}, },
]; ];
useEffect(() => {
console.log("isOpen", isOpen);
if (crmContext.openDrawerCustomerId.current === null) return;
const customerRequest = () => {
if (!isOpen) return;
myFetch(
`/crm/customer/view/${crmContext.openDrawerCustomerId.current}`,
"GET"
).then((data) => {
console.log("customer", data);
crmContext.setCurrentDrawerCustomer(data);
crmContext.currentDrawerCustomerRef.current = data;
});
};
if (isOpen) {
customerRequest();
} else {
// check if something has changed
if (
JSON.stringify(crmContext.currentDrawerCustomer) !==
JSON.stringify(crmContext.currentDrawerCustomerRef.current)
) {
console.log("something has changed");
myFetch(
`/crm/customer/update/${crmContext.openDrawerCustomerId.current}`,
"POST",
crmContext.currentDrawerCustomer
).then((data) => {
console.log("update", data);
});
} else {
console.log("nothing has changed");
}
crmContext.openDrawerCustomerId.current = null;
crmContext.currentDrawerCustomer = null;
crmContext.currentDrawerCustomerRef.current = null;
}
const handleCustomerRequest = () => customerRequest();
document.addEventListener(
wsConnectionCustomEventName,
handleCustomerRequest
);
return () =>
document.removeEventListener(
wsConnectionCustomEventName,
handleCustomerRequest
);
}, [isOpen]);
return ( return (
<Drawer <Drawer
title={"loading..."} title={`${crmContext.currentDrawerCustomer?.FirstName} ${crmContext.currentDrawerCustomer?.LastName}`}
placement="right" placement="right"
open={isOpen} open={isOpen}
onClose={onClose} onClose={onClose}
width={720} width={720}
> >
{console.log("drawer", crmContext.openDrawerCustomerId.current)}
<Tabs defaultActiveKey="0" items={tabItems} centered /> <Tabs defaultActiveKey="0" items={tabItems} centered />
</Drawer> </Drawer>
); );
@ -228,8 +355,7 @@ function CollapseContainer({ children, label }) {
} }
function TabContentDealInfo() { function TabContentDealInfo() {
const [selectedPipeline, setSelectedPipeline] = useState(0); const crmContext = useCrmContext();
const [selectedDealPhase, setSelectedDealPhase] = useState(0);
return ( return (
<Space direction="vertical" style={{ display: "flex" }}> <Space direction="vertical" style={{ display: "flex" }}>
@ -239,21 +365,27 @@ function TabContentDealInfo() {
<Content> <Content>
<Form.Item <Form.Item
label={t("crm.tabContent.dealInfo.collapseDealStatus.pipeline")} label={t("crm.tabContent.dealInfo.collapseDealStatus.pipeline")}
initialValue={selectedPipeline} initialValue={crmContext.currentDrawerCustomer?.Pipeline}
> >
<Select <Select
value={selectedPipeline} value={crmContext.currentDrawerCustomer?.Pipeline}
onChange={(value) => { onChange={(value) => {
setSelectedPipeline(value); //setSelectedPipeline(value);
setSelectedDealPhase(0); //setSelectedDealPhase(0);
crmContext.setCurrentDrawerCustomer({
...crmContext.currentDrawerCustomer,
Pipeline: value,
DealPhase: 0,
});
}} }}
options={[ options={[
{ {
value: 0, value: 1,
label: t("crm.dmcPipeline.pageTitle"), label: t("crm.dmcPipeline.pageTitle"),
}, },
{ {
value: 1, value: 2,
label: t("crm.setterCloser.pageTitle"), label: t("crm.setterCloser.pageTitle"),
}, },
]} ]}
@ -262,19 +394,24 @@ function TabContentDealInfo() {
<Form.Item <Form.Item
label={t("crm.tabContent.dealInfo.collapseDealStatus.dealPhase")} label={t("crm.tabContent.dealInfo.collapseDealStatus.dealPhase")}
initialValue={selectedDealPhase} initialValue={crmContext.currentDrawerCustomer?.DealPhase}
> >
<Select <Select
value={selectedDealPhase} value={crmContext.currentDrawerCustomer?.DealPhase}
onChange={(value) => setSelectedDealPhase(value)} onChange={(value) =>
crmContext.setCurrentDrawerCustomer({
...crmContext.currentDrawerCustomer,
DealPhase: value,
})
}
options={t( options={t(
selectedPipeline === 0 crmContext.currentDrawerCustomer?.Pipeline === 1
? "crm.dmcPipeline.segmentedOptions" ? "crm.dmcPipeline.segmentedOptions"
: "crm.setterCloser.segmentedOptions", : "crm.setterCloser.segmentedOptions",
{ returnObjects: true } { returnObjects: true }
).map((item, index) => { ).map((item, index) => {
return { return {
value: index, value: index + 1,
label: item, label: item,
}; };
})} })}
@ -294,7 +431,15 @@ function TabContentDealInfo() {
"crm.tabContent.dealInfo.collapseMasterDataOfContact.firstName" "crm.tabContent.dealInfo.collapseMasterDataOfContact.firstName"
)} )}
> >
<Input /> <Input
value={crmContext.currentDrawerCustomer?.FirstName}
onChange={(e) =>
crmContext.setCurrentDrawerCustomer({
...crmContext.currentDrawerCustomer,
FirstName: e.target.value,
})
}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -302,7 +447,15 @@ function TabContentDealInfo() {
"crm.tabContent.dealInfo.collapseMasterDataOfContact.lastName" "crm.tabContent.dealInfo.collapseMasterDataOfContact.lastName"
)} )}
> >
<Input /> <Input
value={crmContext.currentDrawerCustomer?.LastName}
onChange={(e) =>
crmContext.setCurrentDrawerCustomer({
...crmContext.currentDrawerCustomer,
LastName: e.target.value,
})
}
/>
</Form.Item> </Form.Item>
</Content> </Content>
@ -312,7 +465,15 @@ function TabContentDealInfo() {
"crm.tabContent.dealInfo.collapseMasterDataOfContact.telephone" "crm.tabContent.dealInfo.collapseMasterDataOfContact.telephone"
)} )}
> >
<Input /> <Input
value={crmContext.currentDrawerCustomer?.Telephone}
onChange={(e) =>
crmContext.setCurrentDrawerCustomer({
...crmContext.currentDrawerCustomer,
Telephone: e.target.value,
})
}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -320,7 +481,15 @@ function TabContentDealInfo() {
"crm.tabContent.dealInfo.collapseMasterDataOfContact.email" "crm.tabContent.dealInfo.collapseMasterDataOfContact.email"
)} )}
> >
<Input /> <Input
value={crmContext.currentDrawerCustomer?.Email}
onChange={(e) =>
crmContext.setCurrentDrawerCustomer({
...crmContext.currentDrawerCustomer,
Email: e.target.value,
})
}
/>
</Form.Item> </Form.Item>
</Content> </Content>