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)}
}
onClick={() => {
crmContext.openDrawerCustomerId.current = "new";
crmContext.currentDrawerCustomerRef.current = null;
setIsDrawerOpen(true);
}}
>
{t("crm.buttonNew")}
{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 (
);
}
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 (
<>
}
onClick={() => setIsOpen(true)}
>
{t("crm.buttonCall")}
{
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);
});
}}
/>
}
>
{telephone !== "" ? (
) : (
)}
>
);
}
function TabContentNotes({ notes }) {
const crmContext = useCrmContext();
return (
{
crmContext.currentDrawerCustomerNotesRef.current = value;
}}
/>
);
}
function MyMxEditor({ _key, markdown, onChange }) {
return (
(
<>
>
),
}),
]}
/>
);
}