310 lines
13 KiB
TypeScript
310 lines
13 KiB
TypeScript
import { FC, useState } from "react";
|
||
import { useCardPageContext } from "../../contexts/CardPageContext.tsx";
|
||
import { Button, Checkbox, Divider, Fieldset, Flex, Group, rem, Textarea, TextInput } from "@mantine/core";
|
||
import { useForm } from "@mantine/form";
|
||
import {
|
||
CardSchema,
|
||
CardService,
|
||
ClientService,
|
||
ProjectSchema,
|
||
ShippingWarehouseSchema,
|
||
StatusSchema,
|
||
} from "../../../../client";
|
||
import { isEqual } from "lodash";
|
||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||
import { useQueryClient } from "@tanstack/react-query";
|
||
import ShippingWarehouseAutocomplete
|
||
from "../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||
import { ButtonCopyControlled } from "../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
|
||
import { useClipboard } from "@mantine/hooks";
|
||
import ManagerSelect from "../../../../components/ManagerSelect/ManagerSelect.tsx";
|
||
import ProjectSelect from "../../../../components/ProjectSelect/ProjectSelect.tsx";
|
||
import BoardSelect from "../../../../components/BoardSelect/BoardSelect.tsx";
|
||
import DealStatusSelect from "../../../../components/DealStatusSelect/DealStatusSelect.tsx";
|
||
import CardAttributeFields from "../../../../components/CardAttributeFields/CardAttributeFields.tsx";
|
||
import getAttributesFromCard from "../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
|
||
import isModuleInProject, { Modules } from "../../utils/isModuleInProject.ts";
|
||
import PaymentLinkButton from "./components/PaymentLinkButton.tsx";
|
||
import PrintDealBarcodesButton from "./components/PrintDealBarcodesButton.tsx";
|
||
|
||
type Props = {
|
||
card: CardSchema;
|
||
};
|
||
|
||
type Attributes = {
|
||
[key: string]: number | boolean | string;
|
||
};
|
||
|
||
export type CardGeneralFormType = Omit<CardSchema, "statusHistory" | "services" | "products">;
|
||
|
||
const Content: FC<Props> = ({ card }) => {
|
||
const { setSelectedCard } = useCardPageContext();
|
||
const clipboard = useClipboard();
|
||
const queryClient = useQueryClient();
|
||
const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
|
||
|
||
const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, card.board.project);
|
||
|
||
const getInitialValues = (card: CardSchema): CardGeneralFormType => {
|
||
return {
|
||
...card,
|
||
...getAttributesFromCard(card),
|
||
};
|
||
};
|
||
|
||
let initialValues = getInitialValues(card);
|
||
|
||
const form = useForm<CardGeneralFormType>({
|
||
initialValues,
|
||
validate: {
|
||
name: (value: string) =>
|
||
value.length > 0
|
||
? null
|
||
: "Название не может быть пустым",
|
||
status: (value: StatusSchema) =>
|
||
!value && "Статус не выбран",
|
||
},
|
||
});
|
||
|
||
const updateCardInfo = async (values: CardGeneralFormType) => {
|
||
console.log("Updated attributes:");
|
||
console.log(values);
|
||
const formCardAttrs = values as unknown as Attributes;
|
||
|
||
const attributes = project?.attributes.reduce((attrs, projectAttr) => {
|
||
return {
|
||
...attrs,
|
||
[projectAttr.name]: formCardAttrs[projectAttr.name],
|
||
};
|
||
}, {});
|
||
|
||
return CardService.updateCardGeneralInfo({
|
||
requestBody: {
|
||
cardId: card.id,
|
||
data: {
|
||
...values,
|
||
statusId: values.status.id,
|
||
boardId: values.board.id,
|
||
shippingWarehouse: values.shippingWarehouse?.toString(),
|
||
attributes,
|
||
},
|
||
},
|
||
}).then(({ ok, message }) => {
|
||
notifications.guess(ok, { message });
|
||
if (!ok) return;
|
||
CardService.getCardById({ cardId: card.id }).then(data => {
|
||
console.log(data);
|
||
setSelectedCard(data);
|
||
initialValues = getInitialValues(data);
|
||
form.setValues(initialValues);
|
||
queryClient.invalidateQueries({
|
||
queryKey: ["getCardSummaries"],
|
||
});
|
||
});
|
||
});
|
||
};
|
||
const updateClientInfo = async (values: CardGeneralFormType) => {
|
||
return ClientService.updateClient({
|
||
requestBody: {
|
||
data: values.client,
|
||
},
|
||
}).then(({ ok, message }) => notifications.guess(ok, { message }));
|
||
};
|
||
const handleSubmit = async (values: CardGeneralFormType) => {
|
||
// Updating client info if there changes
|
||
if (!isEqual(values.client, card.client)) {
|
||
await updateClientInfo(values);
|
||
}
|
||
|
||
const shippingWarehouse = isShippingWarehouse(values.shippingWarehouse) ? values.shippingWarehouse.name : values.shippingWarehouse;
|
||
await updateCardInfo(
|
||
{
|
||
...values,
|
||
shippingWarehouse,
|
||
},
|
||
);
|
||
};
|
||
|
||
const isShippingWarehouse = (
|
||
value: ShippingWarehouseSchema | string | null | undefined,
|
||
): value is ShippingWarehouseSchema => {
|
||
return !["string", "null", "undefined"].includes(typeof value);
|
||
};
|
||
|
||
const onCopyGuestUrlClick = () => {
|
||
CardService.createDealGuestUrl({
|
||
requestBody: {
|
||
cardId: card.id,
|
||
},
|
||
}).then(({ ok, message, url }) => {
|
||
if (!ok) notifications.guess(ok, { message });
|
||
clipboard.copy(`${window.location.origin}/${url}`);
|
||
});
|
||
};
|
||
|
||
return (
|
||
<form onSubmit={form.onSubmit(values => handleSubmit(values))}>
|
||
<Flex
|
||
direction={"column"}
|
||
justify={"space-between"}
|
||
h={"100%"}>
|
||
<Fieldset legend={`Общие параметры [ID: ${card.id}]`}>
|
||
<Flex
|
||
direction={"column"}
|
||
gap={rem(10)}
|
||
>
|
||
<TextInput
|
||
placeholder={"Название сделки"}
|
||
label={"Название сделки"}
|
||
{...form.getInputProps("name")}
|
||
/>
|
||
<TextInput
|
||
disabled
|
||
placeholder={"Дата создания"}
|
||
label={"Дата создания"}
|
||
value={new Date(card.createdAt).toLocaleString(
|
||
"ru-RU",
|
||
)}
|
||
/>
|
||
<ProjectSelect
|
||
value={project}
|
||
onChange={setProject}
|
||
label={"Проект"}
|
||
disabled
|
||
/>
|
||
<BoardSelect
|
||
project={project}
|
||
{...form.getInputProps("board")}
|
||
label={"Доска"}
|
||
/>
|
||
<DealStatusSelect
|
||
board={form.values.board}
|
||
{...form.getInputProps("status")}
|
||
label={"Статус"}
|
||
/>
|
||
<Textarea
|
||
h={rem(150)}
|
||
styles={{
|
||
wrapper: { height: "90%" },
|
||
input: { height: "90%" },
|
||
}}
|
||
label={"Коментарий"}
|
||
placeholder={"Введите коментарий"}
|
||
{...form.getInputProps("comment")}
|
||
/>
|
||
<ShippingWarehouseAutocomplete
|
||
placeholder={"Введите склад отгрузки"}
|
||
label={"Склад отгрузки"}
|
||
value={
|
||
isShippingWarehouse(
|
||
form.values.shippingWarehouse,
|
||
)
|
||
? form.values.shippingWarehouse
|
||
: undefined
|
||
}
|
||
onChange={event => {
|
||
if (isShippingWarehouse(event)) {
|
||
form.getInputProps(
|
||
"shippingWarehouse",
|
||
).onChange(event.name);
|
||
return;
|
||
}
|
||
form.getInputProps(
|
||
"shippingWarehouse",
|
||
).onChange(event);
|
||
}}
|
||
/>
|
||
<ManagerSelect
|
||
placeholder={"Укажите менеджера"}
|
||
label={"Менеджер"}
|
||
{...form.getInputProps("manager")}
|
||
/>
|
||
{project && (
|
||
<CardAttributeFields
|
||
project={project}
|
||
form={form}
|
||
/>
|
||
)}
|
||
</Flex>
|
||
</Fieldset>
|
||
<Flex
|
||
mt={"md"}
|
||
gap={rem(10)}
|
||
align={"center"}
|
||
justify={"flex-end"}>
|
||
<Flex
|
||
align={"center"}
|
||
gap={rem(10)}
|
||
justify={"center"}>
|
||
<Flex
|
||
gap={rem(10)}
|
||
align={"center"}
|
||
justify={"space-between"}>
|
||
{isServicesAndProductsIncluded && (
|
||
<PrintDealBarcodesButton card={card}/>
|
||
)}
|
||
<Flex gap={rem(10)}>
|
||
{isServicesAndProductsIncluded && (
|
||
<PaymentLinkButton card={card} />
|
||
)}
|
||
<ButtonCopyControlled
|
||
onCopyClick={onCopyGuestUrlClick}
|
||
onCopiedLabel={
|
||
"Ссылка скопирована в буфер обмена"
|
||
}
|
||
copied={clipboard.copied}
|
||
>
|
||
Ссылка на редактирование
|
||
</ButtonCopyControlled>
|
||
</Flex>
|
||
</Flex>
|
||
<Flex gap={rem(10)}>
|
||
{isServicesAndProductsIncluded && (
|
||
<Checkbox
|
||
label={"Оплачен"}
|
||
checked={card.billRequest?.paid || card.group?.billRequest?.paid || false}
|
||
disabled
|
||
/>
|
||
)}
|
||
<Checkbox
|
||
label={"Завершена"}
|
||
{...form.getInputProps("isCompleted", { type: "checkbox" })}
|
||
/>
|
||
<Checkbox
|
||
label={"Удалена"}
|
||
{...form.getInputProps("isDeleted", { type: "checkbox" })}
|
||
/>
|
||
</Flex>
|
||
</Flex>
|
||
<Divider orientation={"vertical"} />
|
||
<Group
|
||
align={"center"}
|
||
justify={"center"}>
|
||
<Button
|
||
color={"red"}
|
||
type={"reset"}
|
||
disabled={isEqual(initialValues, form.values)}
|
||
onClick={() => form.reset()}>
|
||
Отменить изменения
|
||
</Button>
|
||
<Button
|
||
variant={"default"}
|
||
type={"submit"}
|
||
disabled={isEqual(initialValues, form.values)}>
|
||
Сохранить изменения
|
||
</Button>
|
||
</Group>
|
||
</Flex>
|
||
</Flex>
|
||
</form>
|
||
);
|
||
};
|
||
|
||
const GeneralTab: FC = () => {
|
||
const { selectedCard } = useCardPageContext();
|
||
if (!selectedCard) return <>No card selected</>;
|
||
return <Content card={selectedCard} />;
|
||
};
|
||
|
||
export default GeneralTab;
|