main
alex 2024-03-09 10:53:06 +01:00
parent 24b111d87c
commit 71d5993a0b
10 changed files with 735 additions and 75 deletions

1
package-lock.json generated
View File

@ -16,6 +16,7 @@
"@yudiel/react-qr-scanner": "^1.1.10",
"antd": "^5.15.1",
"buffer": "^6.0.3",
"dayjs": "^1.11.10",
"i18next": "^23.2.3",
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.1",

View File

@ -11,6 +11,7 @@
"@yudiel/react-qr-scanner": "^1.1.10",
"antd": "^5.15.1",
"buffer": "^6.0.3",
"dayjs": "^1.11.10",
"i18next": "^23.2.3",
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.1",

View File

@ -33,7 +33,10 @@
"copyToClipboard": "In die Zwischenablage kopieren",
"show": "Anzeigen",
"hide": "Verbergen",
"reload": "Neu laden"
"reload": "Neu laden",
"yes": "Ja",
"no": "Nein",
"more": "Mehr"
},
"request": {
"unknownError": {
@ -290,6 +293,7 @@
"placeholderSearch": "Suche nach",
"buttonNew": "Neu",
"buttonUndo": "Rückgängig machen",
"buttonCall": "Anrufen",
"tabs": {
"dealInfo": "Deal-Informationen",
"activities": "Aktivitäten",
@ -341,8 +345,51 @@
"bookedPackages": "Gebuchte Pakete",
"assignedEmployee": "Zugewiesener Mitarbeiter"
}
},
"activities": {
"calls": {
"title": "Anrufe",
"deleteCallProtocolConfirm": {
"title": "Sind Sie sicher, dass Sie das Anrufprotokoll löschen wollen?"
}
},
"emails": {
"title": "E-Mails"
}
}
}
},
"callProtocolModal": {
"title": "Anruf protokollieren",
"callType": "Anruftyp",
"callTypeOptions": ["Opening-Call", "Setting-Call", "Closing-Call"],
"callResult": [
{
"question": "Erreicht?",
"result": ["Erreicht", "Nicht erreicht"]
},
{
"question": "Wer hat abgenommen?",
"yes": "Gatekeeper",
"no": "Entscheider",
"bothValid": true,
"result": ["Gatekeeper", "Entscheider"]
},
{
"question": "Interesse bekundet?",
"result": ["Interesse bekundet", "Kein Interesse bekundet"]
},
{
"question": "Terminiert?",
"result": ["Terminiert", "Nicht terminiert"]
}
],
"callResultText": "Anrufergebnis",
"date": "Datum",
"time": "Zeit",
"telephone": "Telefon",
"notes": "Notizen",
"infoQrCode": "Mit dem Smartphone scannen zum Anrufen"
}
},
"logCard": {
"popover": {
@ -482,7 +529,7 @@
"displayName": "Anzeigename",
"address": "Adresse"
}
}
}
}
},
"userProfile": {

View File

@ -33,7 +33,10 @@
"copyToClipboard": "Copy to clipboard",
"show": "Show",
"hide": "Hide",
"reload": "Reload"
"reload": "Reload",
"yes": "Yes",
"no": "No",
"more": "More"
},
"request": {
"unknownError": {
@ -290,6 +293,7 @@
"placeholderSearch": "Search for",
"buttonNew": "New",
"buttonUndo": "Undo",
"buttonCall": "Call",
"tabs": {
"dealInfo": "Deal Info",
"activities": "Activities",
@ -341,7 +345,55 @@
"bookedPackages": "Booked packages",
"assignedEmployee": "Assigned employee"
}
},
"activities": {
"calls": {
"title": "Calls",
"deleteCallProtocolConfirm": {
"title": "Are you sure you want to delete this call?"
}
},
"emails": {
"title": "Emails"
}
}
},
"callProtocolModal": {
"title": "Call Protocol",
"callType": "Call Type",
"callTypeOptions": [
"Opening-Call",
"Setting-Call",
"Closing-Call"
],
"callResult": [
{
"question": "Reached?",
"result": ["Reached", "Not reached"]
},
{
"question": "Who picked up the call?",
"yes": "Gatekeeper",
"no": "Decision maker",
"bothValid": true,
"result": ["Gatekeeper", "Decision maker"]
},
{
"question": "Expressed interest?",
"result": ["Expressed interest", "No interest"]
},
{
"question": "Scheduled?",
"result": ["Scheduled", "Not scheduled"]
}
],
"callResultText": "Call Result",
"date": "Date",
"time": "Time",
"telephone": "Telephone",
"notes": "Notes" ,
"infoQrCode": "Scan with your smartphone to call"
}
},
"logCard": {

View File

@ -0,0 +1,10 @@
import { Spin } from "antd";
import MyCenteredContainer from "../MyContainer";
export default function MyCenteredSpin({ fullHeight = false }) {
return (
<MyCenteredContainer fullHeight>
<Spin size="large" />
</MyCenteredContainer>
);
}

View File

@ -0,0 +1,20 @@
export default function MyCenteredContainer({
children,
fullHeight = false,
height = "100vh",
}) {
return (
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignContent: "center",
alignItems: "center",
height: fullHeight ? height : "85.3vh",
}}
>
{children}
</div>
);
}

View File

@ -1,23 +1,25 @@
import { Spin } from "antd";
import { Suspense } from "react";
import MyCenteredSpin from "../MyCenteredSpin";
export function MySupsenseFallback({ children }) {
export function MySupsenseFallback({ children, spinnerCentered = true }) {
return (
<Suspense
fallback={
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignContent: "center",
alignItems: "center",
textAlign: "center",
height: "98.3vh",
}}
>
<Spin size="large" />
</div>
spinnerCentered ? (
<MyCenteredSpin fullHeight />
) : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
paddingTop: 50,
}}
>
<Spin size="large" />
</div>
)
}
>
{children}

View File

@ -10,6 +10,8 @@ const preview = {
currentDrawerCustomerRef: null,
currentDrawerCustomerNotesRef: null,
changedDrawerCustomerFieldsRef: null,
currentDrawerCallProtocols: [],
setCurrentDrawerCallProtocols: () => {},
};
const CrmContext = createContext(preview);
@ -31,6 +33,9 @@ export function CrmProvider({ children }) {
// this will be used to store the updates noted and will be compared to currentDrawerCustomerRef to see if there are any changes
const currentDrawerCustomerNotesRef = useRef(null);
const changedDrawerCustomerFieldsRef = useRef([]);
const [currentDrawerCallProtocols, setCurrentDrawerCallProtocols] = useState(
[]
);
return (
<CrmContext.Provider
@ -48,6 +53,8 @@ export function CrmProvider({ children }) {
currentDrawerCustomerNotesRef,
currentDrawerCustomerRef,
changedDrawerCustomerFieldsRef,
currentDrawerCallProtocols,
setCurrentDrawerCallProtocols,
}}
>
{children}

View File

@ -50,6 +50,8 @@ export const ReceivedMessagesCommands = {
AdminAreaManageLogManagerServerConnectionRemoved: 46,
CrmCustomerUpdated: 47,
CrmCustomerCreated: 48,
CrmCallProtocolCreated: 49,
CrmCallProtocolDeleted: 50,
};
// commands sent to the backend server
@ -1077,11 +1079,28 @@ export function handleWebSocketMessage(
break;
case ReceivedMessagesCommands.CrmCustomerCreated:
console.log("test");
crmContext.setCustomers((arr) => [...arr, body]);
break;
case ReceivedMessagesCommands.CrmCallProtocolCreated:
if (crmContext.currentDrawerCustomerRef.current !== null) {
if (
crmContext.currentDrawerCustomerRef.current.Id === body.CustomerId
) {
crmContext.setCurrentDrawerCallProtocols((arr) => [...arr, body]);
}
}
break;
case ReceivedMessagesCommands.CrmCallProtocolDeleted:
if (crmContext.currentDrawerCustomerRef.current !== null) {
if (
crmContext.currentDrawerCustomerRef.current.Id === body.CustomerId
) {
crmContext.setCurrentDrawerCallProtocols((arr) =>
arr.filter((callProtocol) => callProtocol.Id !== body.Id)
);
}
}
break;
default:
console.error("unknown command", cmd);
break;

View File

@ -15,6 +15,14 @@ import {
notification,
Badge,
Popover,
DatePicker,
TimePicker,
QRCode,
Card,
Empty,
Flex,
Popconfirm,
Affix,
} from "antd";
import { useTranslation } from "react-i18next";
import {
@ -30,6 +38,8 @@ import {
PlusOutlined,
ChromeOutlined,
SearchOutlined,
PhoneOutlined,
DeleteOutlined,
} from "@ant-design/icons";
import { t } from "i18next";
import { useCrmContext } from "../../Contexts/CrmContext";
@ -51,6 +61,10 @@ 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";
const CRM_TYPE = {
CUSTOMERS: 0,
@ -626,7 +640,7 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) {
{
key: "1",
label: t("crm.tabs.activities"),
children: <TabContentActivities />,
children: <TabContentActivities notificationApi={notificationApi} />,
},
{
key: "2",
@ -653,35 +667,39 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) {
`/crm/customer/view/${crmContext.openDrawerCustomerId.current}`,
"GET"
).then((data) => {
crmContext.currentDrawerCustomerRef.current = data;
const customer = data.Customer;
crmContext.currentDrawerCustomerRef.current = customer;
crmContext.setCurrentDrawerCallProtocols(data.CallProtocols);
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,
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,
});
});
};
@ -721,6 +739,8 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) {
DealPhase: 1,
AssignedEmployee: appContext.userId.current,
});
crmContext.setCurrentDrawerCallProtocols([]);
return;
}
@ -839,9 +859,14 @@ function CustomerDrawer({ isOpen, setIsOpen, onClose, notificationApi }) {
width={720}
extra={
<Space>
<CallProtocolModal
formDealInfo={formDealInfo}
notificationApi={notificationApi}
/>
<Form form={formDealInfo} layout="inline">
<Form.Item name="AssignedEmployee">
<Select style={{ minWidth: 100 }}>
<Form.Item name="AssignedEmployee" style={{ margin: 0 }}>
<Select style={{ minWidth: 120 }}>
{appContext.users.map((user) => (
<Select.Option key={user.Id} value={user.Id}>
<Space>
@ -1174,46 +1199,522 @@ function TabContentDealInfo({ form }) {
);
}
function TabContentActivities() {
function TabContentActivities({ notificationApi }) {
const { t } = useTranslation();
return (
<MySupsenseFallback>
<div>Coming soon</div>
<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;
}
});
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")
.then(() => {
console.log("deleted");
})
.catch(() => {
showUnkownErrorNotification(notificationApi, t);
});
}}
>
<DeleteOutlined />
</Popconfirm>
</Flex>
}
description={
<Typography.Paragraph
style={{ marginBottom: 0 }}
ellipsis={{
rows: 2,
expandable: true,
symbol: t("common.text.more"),
}}
>
{`${FormatDatetime(item.CalledAt)} Uhr${
item.Telephone !== "" ? ` - ${item.Telephone}` : ""
} ${
item.Notes !== ""
? `- ${t("crm.callProtocolModal.notes")}: ${item.Notes}`
: ""
}`}
</Typography.Paragraph>
}
/>
</Card>
);
})}
</Space>
);
}
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({
step: 0,
answers: [],
finished: false, // if click cancel or answered all questions
});
const telephone = Form.useWatch("telephone", form);
const handleCancel = () => {
setIsOpen(false);
};
useEffect(() => {
if (isOpen) {
const currentDate = new Date();
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);
// 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);
})
.catch(() => {
setIsRequesting(false);
showUnkownErrorNotification(notificationApi, t);
});
})
.catch(() => {});
}}
/>
}
>
<MySupsenseFallback spinnerCentered={false}>
<Form form={form} layout="vertical">
<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="reached"
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={["erreicht", "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={["erreicht", "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")}>
<DatePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item
name="time"
label="Zeit"
rules={[
{
type: "object",
required: true,
message: "Please select time!",
},
]}
>
<TimePicker style={{ width: "100%" }} />
</Form.Item>
</Content>
<Form.Item
name="telephone"
label={t("crm.callProtocolModal.telephone")}
>
<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>
<MDXEditor
key={crmContext.currentDrawerCustomerRef.current?.Id}
className="mdx-editor"
<MyMxEditor
_key={crmContext.currentDrawerCustomerRef.current?.Id}
markdown={notes}
onChange={(value) => {
crmContext.currentDrawerCustomerNotesRef.current = value;
}}
plugins={[
listsPlugin(),
quotePlugin(),
headingsPlugin(),
linkPlugin(),
linkDialogPlugin(),
tablePlugin(),
thematicBreakPlugin(),
frontmatterPlugin(),
markdownShortcutPlugin(),
toolbarPlugin({
toolbarContents: () => (
<>
<UndoRedo />
<BoldItalicUnderlineToggles />
</>
),
}),
]}
/>
</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 />
</>
),
}),
]}
/>
);
}