1789 lines
51 KiB
JavaScript
1789 lines
51 KiB
JavaScript
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 <Badge color={color} count={count} overflowCount={100000} showZero />;
|
|
}
|
|
|
|
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,
|
|
}) => (
|
|
<div
|
|
style={{
|
|
padding: 8,
|
|
}}
|
|
onKeyDown={(e) => e.stopPropagation()}
|
|
>
|
|
<Input
|
|
ref={searchInput}
|
|
placeholder={`${t("crm.placeholderSearch")} ${dataIndex}`}
|
|
value={selectedKeys[0]}
|
|
onChange={(e) =>
|
|
setSelectedKeys(e.target.value ? [e.target.value] : [])
|
|
}
|
|
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
|
|
style={{
|
|
marginBottom: 8,
|
|
display: "block",
|
|
}}
|
|
/>
|
|
<Space>
|
|
<Button
|
|
type="primary"
|
|
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
|
|
icon={<SearchOutlined />}
|
|
size="small"
|
|
style={{
|
|
width: 90,
|
|
}}
|
|
>
|
|
{t("common.button.search")}
|
|
</Button>
|
|
<Button
|
|
onClick={() => clearFilters && handleReset(clearFilters)}
|
|
size="small"
|
|
style={{
|
|
width: 100,
|
|
}}
|
|
>
|
|
{t("common.button.reset")}
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
onClick={() => {
|
|
confirm({
|
|
closeDropdown: false,
|
|
});
|
|
setSearchText(selectedKeys[0]);
|
|
setSearchedColumn(dataIndex);
|
|
}}
|
|
>
|
|
{t("common.button.filter")}
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
onClick={() => {
|
|
close();
|
|
}}
|
|
>
|
|
{t("common.button.close")}
|
|
</Button>
|
|
</Space>
|
|
</div>
|
|
),
|
|
filterIcon: (filtered) => (
|
|
<SearchOutlined
|
|
style={{
|
|
color: filtered ? "#1677ff" : undefined,
|
|
}}
|
|
/>
|
|
),
|
|
onFilter: (value, record) =>
|
|
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
|
|
onFilterDropdownOpenChange: (visible) => {
|
|
if (visible) {
|
|
setTimeout(() => searchInput.current?.select(), 100);
|
|
}
|
|
},
|
|
render: (text) =>
|
|
searchedColumn === dataIndex ? (
|
|
<Highlighter
|
|
highlightStyle={{
|
|
backgroundColor: "#ffc069",
|
|
padding: 0,
|
|
}}
|
|
searchWords={[searchText]}
|
|
autoEscape
|
|
textToHighlight={text ? text.toString() : ""}
|
|
/>
|
|
) : (
|
|
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 ? (
|
|
<>
|
|
<MyAvatar userId={item} allUsers={appContext.users} />{" "}
|
|
{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: (
|
|
<>
|
|
<Popover
|
|
placement="right"
|
|
trigger="hover"
|
|
content={
|
|
<MyAvatar
|
|
userId={item.AssignedEmployee}
|
|
allUsers={appContext.users}
|
|
avatarWidth={256}
|
|
/>
|
|
}
|
|
>
|
|
<>
|
|
<MyAvatar
|
|
userId={item.AssignedEmployee}
|
|
allUsers={appContext.users}
|
|
/>{" "}
|
|
{
|
|
appContext.users.find((u) => u.Id === item.AssignedEmployee)
|
|
?.Username
|
|
}
|
|
</>
|
|
</Popover>
|
|
</>
|
|
),
|
|
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: (
|
|
<>
|
|
<Popover
|
|
placement="right"
|
|
trigger="hover"
|
|
content={
|
|
<MyAvatar
|
|
userId={item.CreatedBy}
|
|
allUsers={appContext.users}
|
|
avatarWidth={256}
|
|
/>
|
|
}
|
|
>
|
|
<>
|
|
<MyAvatar userId={item.CreatedBy} allUsers={appContext.users} />{" "}
|
|
{
|
|
appContext.users.find((u) => u.Id === item.CreatedBy)
|
|
?.Username
|
|
}
|
|
</>
|
|
</Popover>
|
|
</>
|
|
),
|
|
});
|
|
});
|
|
|
|
return items;
|
|
};
|
|
|
|
const getSegmentedLabels = (item, index) => {
|
|
return (
|
|
<Space>
|
|
<span>{item}</span>
|
|
<MyBadge
|
|
count={
|
|
crmContext.customers.filter(
|
|
(customer) =>
|
|
customer.Pipeline ===
|
|
(selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE
|
|
? 1
|
|
: 2) && customer.DealPhase === index + 1
|
|
).length
|
|
}
|
|
color={
|
|
selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE
|
|
? segmentedDmcPipelineBadgeColors[index]
|
|
: segmentedSetterCloserBadgeColors[index]
|
|
}
|
|
/>
|
|
</Space>
|
|
);
|
|
};
|
|
|
|
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}
|
|
|
|
<Segmented
|
|
value={selectedSegmentedTypeValue}
|
|
onChange={(value) => {
|
|
setSelectedSegmentedTypeValue(value);
|
|
}}
|
|
options={[
|
|
{
|
|
value: CRM_TYPE.CUSTOMERS,
|
|
label: (
|
|
<Space>
|
|
<span>All Leads</span>
|
|
<MyBadge count={crmContext.customers.length} />
|
|
</Space>
|
|
),
|
|
},
|
|
{
|
|
value: CRM_TYPE.DMC_PIPELINE,
|
|
label: (
|
|
<Space>
|
|
<span>DMC Pipeline</span>
|
|
<MyBadge
|
|
count={
|
|
crmContext.customers.filter((item) => item.Pipeline === 1)
|
|
.length
|
|
}
|
|
/>
|
|
</Space>
|
|
),
|
|
},
|
|
{
|
|
value: CRM_TYPE.SETTER_CLOSER,
|
|
label: (
|
|
<Space>
|
|
<span>Setter / Closer</span>
|
|
<MyBadge
|
|
count={
|
|
crmContext.customers.filter((item) => item.Pipeline === 2)
|
|
.length
|
|
}
|
|
/>
|
|
</Space>
|
|
),
|
|
},
|
|
]}
|
|
block
|
|
/>
|
|
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
marginTop: 6,
|
|
}}
|
|
>
|
|
<Typography.Title level={4}>{t(title)}</Typography.Title>
|
|
|
|
<Button
|
|
type="primary"
|
|
icon={<PlusOutlined />}
|
|
onClick={() => {
|
|
crmContext.openDrawerCustomerId.current = "new";
|
|
crmContext.currentDrawerCustomerRef.current = null;
|
|
|
|
setIsDrawerOpen(true);
|
|
}}
|
|
>
|
|
{t("crm.buttonNew")}
|
|
</Button>
|
|
</div>
|
|
|
|
{selectedSegmentedTypeValue !== CRM_TYPE.CUSTOMERS ? (
|
|
<Segmented
|
|
value={
|
|
selectedSegmentedTypeValue === CRM_TYPE.DMC_PIPELINE
|
|
? selectedDmcPipelineSegmentedValue
|
|
: selectedSetterCloserSegmentedValue
|
|
}
|
|
onChange={(value) => {
|
|
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 }}
|
|
/>
|
|
) : (
|
|
<div style={{ height: 44 }} />
|
|
)}
|
|
|
|
<Table
|
|
scroll={{ x: "max-content" }}
|
|
columns={getTableContent()}
|
|
dataSource={getTableItems()}
|
|
loading={isRequesting}
|
|
pagination
|
|
onRow={(record) => {
|
|
return {
|
|
onClick: () => {
|
|
setIsDrawerOpen(true);
|
|
crmContext.openDrawerCustomerId.current = record.id;
|
|
},
|
|
};
|
|
}}
|
|
/>
|
|
|
|
<CustomerDrawer
|
|
isOpen={isDrawerOpen}
|
|
setIsOpen={setIsDrawerOpen}
|
|
onClose={() => 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: <TabContentDealInfo form={formDealInfo} />,
|
|
},
|
|
{
|
|
key: "1",
|
|
label: t("crm.tabs.activities"),
|
|
children: <TabContentActivities notificationApi={notificationApi} />,
|
|
},
|
|
{
|
|
key: "2",
|
|
label: t("crm.tabs.notes"),
|
|
children: (
|
|
<TabContentNotes
|
|
notes={
|
|
crmContext.currentDrawerCustomerRef.current === null ||
|
|
crmContext.currentDrawerCustomerRef.current.Notes === null
|
|
? ""
|
|
: crmContext.currentDrawerCustomerRef.current.Notes
|
|
}
|
|
/>
|
|
),
|
|
},
|
|
];
|
|
|
|
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 (
|
|
<Drawer
|
|
placement="right"
|
|
open={isOpen}
|
|
onClose={onClose}
|
|
width={720}
|
|
extra={
|
|
<Space>
|
|
<CallProtocolModal
|
|
formDealInfo={formDealInfo}
|
|
notificationApi={notificationApi}
|
|
/>
|
|
|
|
<Form form={formDealInfo} layout="inline">
|
|
<Form.Item name="AssignedEmployee" style={{ margin: 0 }}>
|
|
<Select style={{ minWidth: 120 }}>
|
|
{appContext.users.map((user) => (
|
|
<Select.Option key={user.Id} value={user.Id}>
|
|
<Space>
|
|
<MyAvatar
|
|
userId={user.Id}
|
|
allUsers={appContext.users}
|
|
avatarWidth={24}
|
|
/>
|
|
|
|
{user.Username}
|
|
</Space>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Form>
|
|
|
|
<Button
|
|
onClick={() => {
|
|
onClose();
|
|
handleCloseDrawer();
|
|
}}
|
|
>
|
|
{t("crm.buttonUndo")}
|
|
</Button>
|
|
</Space>
|
|
}
|
|
>
|
|
<Tabs
|
|
activeKey={activeTab}
|
|
onChange={(activeKey) => setActiveTab(activeKey)}
|
|
items={tabItems}
|
|
centered
|
|
/>
|
|
</Drawer>
|
|
);
|
|
}
|
|
|
|
function Content({ children }) {
|
|
return (
|
|
<Row gutter={16}>
|
|
<Col xs={24} sm={12}>
|
|
{children[0]}
|
|
</Col>
|
|
<Col xs={24} sm={12}>
|
|
{children[1]}
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
function CollapseContainer({ children, label }) {
|
|
return (
|
|
<Collapse
|
|
defaultActiveKey="1"
|
|
items={[
|
|
{
|
|
key: "1",
|
|
label: label,
|
|
children: children,
|
|
},
|
|
]}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function TabContentDealInfo({ form }) {
|
|
const pipeline = Form.useWatch("Pipeline", form);
|
|
const dealPhase = Form.useWatch("DealPhase", form);
|
|
|
|
const FormItem = ({ name, label }) => (
|
|
<Form.Item name={name} label={typeof label === "string" ? t(label) : label}>
|
|
<Input maxLength={250} />
|
|
</Form.Item>
|
|
);
|
|
|
|
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) => (
|
|
<Content key={index}>
|
|
{chunk.map((item) => (
|
|
<FormItem key={item.name} {...item} />
|
|
))}
|
|
</Content>
|
|
))}
|
|
</>
|
|
);
|
|
};
|
|
|
|
const CollapseContainerBlock = ({ label, items }) => (
|
|
<CollapseContainer label={t(label)}>
|
|
<ContentBlock items={items} />
|
|
</CollapseContainer>
|
|
);
|
|
|
|
// 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 (
|
|
<MySupsenseFallback>
|
|
<Form form={form} layout="vertical">
|
|
<Space direction="vertical" style={{ display: "flex" }}>
|
|
<CollapseContainer
|
|
label={t("crm.tabContent.dealInfo.collapseDealStatus.label")}
|
|
>
|
|
<Content>
|
|
<Form.Item
|
|
name="Pipeline"
|
|
label={t("crm.tabContent.dealInfo.collapseDealStatus.pipeline")}
|
|
>
|
|
<Select
|
|
options={[
|
|
{
|
|
value: 1,
|
|
label: t("crm.dmcPipeline.pageTitle"),
|
|
},
|
|
{
|
|
value: 2,
|
|
label: t("crm.setterCloser.pageTitle"),
|
|
},
|
|
]}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="DealPhase"
|
|
label={t(
|
|
"crm.tabContent.dealInfo.collapseDealStatus.dealPhase"
|
|
)}
|
|
>
|
|
<Select
|
|
options={t(
|
|
pipeline === 1
|
|
? "crm.dmcPipeline.segmentedOptions"
|
|
: "crm.setterCloser.segmentedOptions",
|
|
{ returnObjects: true }
|
|
).map((item, index) => {
|
|
return {
|
|
value: index + 1,
|
|
label: item,
|
|
};
|
|
})}
|
|
/>
|
|
</Form.Item>
|
|
</Content>
|
|
</CollapseContainer>
|
|
|
|
<CollapseContainerBlock
|
|
label="crm.tabContent.dealInfo.collapseMasterDataOfContact.label"
|
|
items={[
|
|
{
|
|
name: "FirstName",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.firstName",
|
|
},
|
|
{
|
|
name: "LastName",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.lastName",
|
|
},
|
|
{
|
|
name: "Telephone",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.telephone",
|
|
},
|
|
{
|
|
name: "Email",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.email",
|
|
},
|
|
{
|
|
name: "Company",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.company",
|
|
},
|
|
{
|
|
name: "ZipCode",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.zipCode",
|
|
},
|
|
{
|
|
name: "Address",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.address",
|
|
},
|
|
{
|
|
name: "City",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.city",
|
|
},
|
|
{
|
|
name: "Country",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.country",
|
|
},
|
|
{
|
|
name: "FederalState",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.federalState",
|
|
},
|
|
{
|
|
name: "Website",
|
|
label: (
|
|
<Space>
|
|
<span>
|
|
{t(
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.website"
|
|
)}
|
|
</span>
|
|
|
|
<ChromeOutlined
|
|
style={{ color: AppStyle.app.primary }}
|
|
onClick={() => {
|
|
const website = form.getFieldValue("Website");
|
|
|
|
if (
|
|
!website.startsWith("http://") &&
|
|
!website.startsWith("https://")
|
|
)
|
|
return;
|
|
|
|
window.open(
|
|
form.getFieldValue("Website"),
|
|
"_blank",
|
|
"noopener"
|
|
);
|
|
}}
|
|
/>
|
|
</Space>
|
|
),
|
|
},
|
|
{
|
|
name: "LeadOrigin",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseMasterDataOfContact.leadOrigin",
|
|
},
|
|
]}
|
|
/>
|
|
|
|
<CollapseContainerBlock
|
|
label="crm.tabContent.dealInfo.collapseSetterInfo.label"
|
|
items={[
|
|
{
|
|
name: "NumberOfEmployees",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseSetterInfo.numberOfEmployees",
|
|
},
|
|
{
|
|
name: "NumberOfJobsSearchedFor",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseSetterInfo.numberOfJobsSearchedFor",
|
|
},
|
|
{
|
|
name: "JobTitle",
|
|
label: "crm.tabContent.dealInfo.collapseSetterInfo.jobTitle",
|
|
},
|
|
{
|
|
name: "NumberOfEmployeesRequired",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseSetterInfo.numberOfEmployeesRequired",
|
|
},
|
|
{
|
|
name: "HowLongHadHeBeenSearching",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseSetterInfo.howLongHadHeBeenSearching",
|
|
},
|
|
{
|
|
name: "Turnover",
|
|
label: "crm.tabContent.dealInfo.collapseSetterInfo.turnover",
|
|
},
|
|
]}
|
|
/>
|
|
|
|
<CollapseContainerBlock
|
|
label="crm.tabContent.dealInfo.collapseDealProperties.label"
|
|
items={[
|
|
{
|
|
name: "DateOfcompletion",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseDealProperties.dateOfcompletion",
|
|
},
|
|
{
|
|
name: "OrderVolume",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseDealProperties.orderVolume",
|
|
},
|
|
{
|
|
name: "NumberOfInstallments",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseDealProperties.numberOfInstallments",
|
|
},
|
|
{
|
|
name: "AmountsOfTheInstallments",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseDealProperties.amountsOfTheInstallments",
|
|
},
|
|
{
|
|
name: "BookedPackages",
|
|
label:
|
|
"crm.tabContent.dealInfo.collapseDealProperties.bookedPackages",
|
|
},
|
|
]}
|
|
/>
|
|
</Space>
|
|
</Form>
|
|
</MySupsenseFallback>
|
|
);
|
|
}
|
|
|
|
function TabContentActivities({ notificationApi }) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<MySupsenseFallback>
|
|
<Tabs
|
|
items={[
|
|
{
|
|
key: 0,
|
|
label: t("crm.tabContent.activities.calls.title"),
|
|
children: (
|
|
<ActivityCallProtocols notificationApi={notificationApi} />
|
|
),
|
|
},
|
|
{
|
|
key: 1,
|
|
label: t("crm.tabContent.activities.emails.title"),
|
|
disabled: true,
|
|
},
|
|
]}
|
|
/>
|
|
</MySupsenseFallback>
|
|
);
|
|
}
|
|
|
|
function ActivityCallProtocols({ notificationApi }) {
|
|
const { t } = useTranslation();
|
|
|
|
const appContext = useAppContext();
|
|
const crmContext = useCrmContext();
|
|
|
|
if (crmContext.currentDrawerCallProtocols.length === 0) {
|
|
return <Empty />;
|
|
}
|
|
|
|
const sortedCallProtocols = crmContext.currentDrawerCallProtocols.sort(
|
|
(a, b) => new Date(b.CalledAt) - new Date(a.CalledAt)
|
|
);
|
|
|
|
return (
|
|
<Space direction="vertical" style={{ width: "100%" }}>
|
|
{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")}: ${(
|
|
<>
|
|
<Markdown>{item.Notes}</Markdown>
|
|
</>
|
|
)}`
|
|
: ""
|
|
}`}
|
|
*/
|
|
|
|
return (
|
|
<Card key={item.Id}>
|
|
<Card.Meta
|
|
avatar={
|
|
<MyAvatar
|
|
avatarWidth={56}
|
|
userId={item.CreatedBy}
|
|
allUsers={appContext.users}
|
|
/>
|
|
}
|
|
title={
|
|
<Flex justify="space-between">
|
|
<Typography.Title level={5} style={{ marginBottom: 0 }}>{`${
|
|
t("crm.callProtocolModal.callTypeOptions", {
|
|
returnObjects: true,
|
|
})[item.CallType - 1]
|
|
}: ${callResult.join(", ")}`}</Typography.Title>
|
|
|
|
<Popconfirm
|
|
placement="left"
|
|
title={t(
|
|
"crm.tabContent.activities.calls.deleteCallProtocolConfirm.title"
|
|
)}
|
|
cancelText={t("common.button.cancel")}
|
|
okText={t("common.button.confirm")}
|
|
onConfirm={() => {
|
|
myFetch(`/crm/calls/delete/${item.Id}`, "DELETE").catch(
|
|
() => showUnkownErrorNotification(notificationApi, t)
|
|
);
|
|
}}
|
|
>
|
|
<DeleteOutlined style={{ color: "darkred" }} />
|
|
</Popconfirm>
|
|
</Flex>
|
|
}
|
|
description={
|
|
<Typography.Paragraph
|
|
style={{ marginBottom: 0 }}
|
|
ellipsis={{
|
|
rows: 2,
|
|
expandable: true,
|
|
symbol: t("common.text.more"),
|
|
}}
|
|
>
|
|
{notes.join(" - ")}
|
|
</Typography.Paragraph>
|
|
}
|
|
/>
|
|
</Card>
|
|
);
|
|
})}
|
|
</Space>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<>
|
|
<Button
|
|
type="primary"
|
|
icon={<PhoneOutlined />}
|
|
onClick={() => setIsOpen(true)}
|
|
>
|
|
{t("crm.buttonCall")}
|
|
</Button>
|
|
|
|
<MyModal
|
|
title={t("crm.callProtocolModal.title")}
|
|
isOpen={isOpen}
|
|
onCancel={handleCancel}
|
|
footer={
|
|
<MyModalCloseCreateButtonFooter
|
|
onCancel={handleCancel}
|
|
isCreateButtonLoading={isRequesting}
|
|
onCreate={() => {
|
|
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);
|
|
});
|
|
}}
|
|
/>
|
|
}
|
|
>
|
|
<MySupsenseFallback spinnerCentered={false}>
|
|
<Form form={form} layout="vertical" requiredMark={false}>
|
|
<Content>
|
|
<Form.Item
|
|
name="callType"
|
|
label={t("crm.callProtocolModal.callType")}
|
|
>
|
|
<Select>
|
|
{t("crm.callProtocolModal.callTypeOptions", {
|
|
returnObjects: true,
|
|
}).map((item, index) => (
|
|
<Select.Option key={index} value={index + 1}>
|
|
{item}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
|
|
{callResult.finished ? (
|
|
<Form.Item
|
|
label={
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
gap: 20,
|
|
}}
|
|
>
|
|
<span>{t("crm.callProtocolModal.callResultText")}</span>
|
|
<a
|
|
onClick={() =>
|
|
setCallResult({
|
|
step: 0,
|
|
answers: [],
|
|
finished: false,
|
|
})
|
|
}
|
|
>
|
|
{t("common.button.reset")}
|
|
</a>
|
|
</div>
|
|
}
|
|
>
|
|
<div
|
|
style={{
|
|
borderRadius: 6,
|
|
border: 1,
|
|
paddingTop: 4,
|
|
paddingLeft: 10,
|
|
paddingRight: 10,
|
|
paddingBottom: 4,
|
|
borderColor: "#d8d9d8",
|
|
borderStyle: "solid",
|
|
}}
|
|
>
|
|
{callResult.answers.map((item, index) => {
|
|
return (
|
|
<span key={index}>
|
|
<span
|
|
style={{
|
|
color:
|
|
optionsCallResult[index].bothValid !==
|
|
undefined || item === 1
|
|
? "green"
|
|
: "red",
|
|
}}
|
|
>
|
|
{
|
|
optionsCallResult[index].result[
|
|
item === 1 ? 0 : 1
|
|
]
|
|
}
|
|
</span>
|
|
{index !== callResult.answers.length - 1 && (
|
|
<span>{" > "}</span>
|
|
)}
|
|
</span>
|
|
);
|
|
})}
|
|
</div>
|
|
</Form.Item>
|
|
) : (
|
|
<Form.Item
|
|
name="callProtocol"
|
|
label={
|
|
t("crm.callProtocolModal.callResult", {
|
|
returnObjects: true,
|
|
})[callResult.step].question
|
|
}
|
|
style={{ width: "100%", margin: 0 }}
|
|
>
|
|
<Space
|
|
styles={{ item: { width: "100%" } }}
|
|
style={{ width: "100%" }}
|
|
>
|
|
<Form.Item name={["callProtocol", "yes"]}>
|
|
<Button
|
|
block
|
|
style={{
|
|
backgroundColor: "#f5fcdc",
|
|
}}
|
|
onClick={() => {
|
|
if (optionsCallResult.length > callResult.step + 1) {
|
|
setCallResult({
|
|
...callResult,
|
|
step: callResult.step + 1,
|
|
answers: [...callResult.answers, 1],
|
|
});
|
|
} else {
|
|
setCallResult({
|
|
...callResult,
|
|
answers: [...callResult.answers, 1],
|
|
finished: true,
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
{buttonCallResultTextYes}
|
|
</Button>
|
|
</Form.Item>
|
|
<Form.Item name={["callProtocol", "no"]}>
|
|
<Button
|
|
block
|
|
style={{ backgroundColor: "#ebebf1" }}
|
|
onClick={() => {
|
|
if (optionsCallResult.length > callResult.step + 1) {
|
|
setCallResult({
|
|
...callResult,
|
|
step: callResult.step + 1,
|
|
answers: [...callResult.answers, 0],
|
|
finished:
|
|
optionsCallResult[callResult.step].bothValid !==
|
|
undefined
|
|
? false
|
|
: true,
|
|
});
|
|
} else {
|
|
setCallResult({
|
|
...callResult,
|
|
answers: [...callResult.answers, 0],
|
|
finished: true,
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
{buttonCallResultTextNo}
|
|
</Button>
|
|
</Form.Item>
|
|
</Space>
|
|
</Form.Item>
|
|
)}
|
|
</Content>
|
|
|
|
<Content>
|
|
<Form.Item
|
|
name="date"
|
|
label={t("crm.callProtocolModal.date")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("common.text.pleaseInput"),
|
|
},
|
|
]}
|
|
>
|
|
<DatePicker style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="time"
|
|
label="Zeit"
|
|
rules={[
|
|
{
|
|
type: "object",
|
|
required: true,
|
|
message: t("common.text.pleaseInput"),
|
|
},
|
|
]}
|
|
>
|
|
<TimePicker style={{ width: "100%" }} />
|
|
</Form.Item>
|
|
</Content>
|
|
|
|
<Form.Item
|
|
name="telephone"
|
|
label={t("crm.callProtocolModal.telephone")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("common.text.pleaseInput"),
|
|
},
|
|
]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
|
|
<Form.Item name="notes" label={t("crm.callProtocolModal.notes")}>
|
|
<MyMxEditor _key="call_protocol_notes" markdown={""} />
|
|
</Form.Item>
|
|
|
|
<Form.Item label={t("crm.callProtocolModal.infoQrCode")}>
|
|
{telephone !== "" ? (
|
|
<QRCode value={`tel:${telephone}`} />
|
|
) : (
|
|
<Empty />
|
|
)}
|
|
</Form.Item>
|
|
</Form>
|
|
</MySupsenseFallback>
|
|
</MyModal>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function TabContentNotes({ notes }) {
|
|
const crmContext = useCrmContext();
|
|
|
|
return (
|
|
<MySupsenseFallback>
|
|
<MyMxEditor
|
|
_key={crmContext.currentDrawerCustomerRef.current?.Id}
|
|
markdown={notes}
|
|
onChange={(value) => {
|
|
crmContext.currentDrawerCustomerNotesRef.current = value;
|
|
}}
|
|
/>
|
|
</MySupsenseFallback>
|
|
);
|
|
}
|
|
|
|
function MyMxEditor({ _key, markdown, onChange }) {
|
|
return (
|
|
<MDXEditor
|
|
key={_key}
|
|
className="mdx-editor"
|
|
markdown={markdown}
|
|
onChange={onChange}
|
|
plugins={[
|
|
listsPlugin(),
|
|
quotePlugin(),
|
|
headingsPlugin(),
|
|
linkPlugin(),
|
|
linkDialogPlugin(),
|
|
tablePlugin(),
|
|
thematicBreakPlugin(),
|
|
frontmatterPlugin(),
|
|
markdownShortcutPlugin(),
|
|
toolbarPlugin({
|
|
toolbarContents: () => (
|
|
<>
|
|
<UndoRedo />
|
|
<BoldItalicUnderlineToggles />
|
|
</>
|
|
),
|
|
}),
|
|
]}
|
|
/>
|
|
);
|
|
}
|