feat: billing guest access
This commit is contained in:
1
src/pages/DealPage/index.ts
Normal file
1
src/pages/DealPage/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {DealPage} from './ui/DealPage'
|
||||
38
src/pages/DealPage/ui/DealPage.tsx
Normal file
38
src/pages/DealPage/ui/DealPage.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import {useParams} from "@tanstack/react-router";
|
||||
import {DealPageContextProvider, useDealPageContext} from "../../LeadsPage/contexts/DealPageContext.tsx";
|
||||
import ProductAndServiceTab from "../../LeadsPage/tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||
import {FC, useEffect} from "react";
|
||||
import {DealService} from "../../../client";
|
||||
|
||||
export type Props = {
|
||||
dealId: number;
|
||||
}
|
||||
const DealPageContent: FC<Props> = ({dealId}) => {
|
||||
const {setSelectedDeal} = useDealPageContext();
|
||||
useEffect(() => {
|
||||
DealService.getDealById({dealId}).then(deal => {
|
||||
setSelectedDeal(deal);
|
||||
})
|
||||
}, []);
|
||||
return (
|
||||
<ProductAndServiceTab/>
|
||||
)
|
||||
}
|
||||
const DealPageWrapper: FC<{ children: React.ReactNode }> = ({children}) => {
|
||||
return (
|
||||
<DealPageContextProvider>
|
||||
{children}
|
||||
</DealPageContextProvider>
|
||||
)
|
||||
}
|
||||
export const DealPage = () => {
|
||||
const {dealId} = useParams({strict: false});
|
||||
return (
|
||||
|
||||
|
||||
<DealPageWrapper>
|
||||
<DealPageContent dealId={parseInt(dealId || "-1")}/>
|
||||
</DealPageWrapper>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import ServiceWithPriceInput from "../../../../components/ServiceWithPriceInput/
|
||||
import {isNumber} from "lodash";
|
||||
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||
import {IconTrash} from "@tabler/icons-react";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../../../redux/store.ts";
|
||||
|
||||
type RestProps = {
|
||||
quantity: number;
|
||||
@@ -13,6 +15,8 @@ type RestProps = {
|
||||
type Props = BaseFormInputProps<DealProductServiceSchema[]> & RestProps;
|
||||
const DealProductServiceTable: FC<Props> = (props: Props) => {
|
||||
const {value, onChange, quantity, error} = props;
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const [innerValue, setInnerValue] = useState<Partial<DealProductServiceSchema>[]>(value || []);
|
||||
const onServiceChange = (idx: number, value: ServiceSchema) => {
|
||||
setInnerValue(oldValue => oldValue.map((item, i) => i === idx ? {...item, service: value} : item));
|
||||
@@ -68,8 +72,8 @@ const DealProductServiceTable: FC<Props> = (props: Props) => {
|
||||
placeholder: "Введите стоимость",
|
||||
hideControls: true,
|
||||
style: {width: "100%"},
|
||||
suffix: "₽"
|
||||
|
||||
suffix: "₽",
|
||||
disabled: authState.isGuest
|
||||
}}
|
||||
containerProps={{w: "100%"}}
|
||||
quantity={quantity}
|
||||
|
||||
@@ -9,6 +9,9 @@ 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 ButtonCopy from "../../../../../components/ButtonCopy/ButtonCopy.tsx";
|
||||
|
||||
type Props = {
|
||||
deal: DealSchema
|
||||
@@ -18,6 +21,7 @@ type FormType = Omit<DealSchema, 'statusHistory' | 'services' | 'products'>
|
||||
|
||||
const Content: FC<Props> = ({deal}) => {
|
||||
const {setSelectedDeal} = useDealPageContext();
|
||||
const clipboard = useClipboard();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const initialValues: FormType = deal;
|
||||
@@ -68,6 +72,20 @@ const Content: FC<Props> = ({deal}) => {
|
||||
return !["string", "null", "undefined"].includes((typeof value));
|
||||
|
||||
}
|
||||
|
||||
const onCopyGuestUrlClick = () => {
|
||||
DealService.createDealGuestUrl({
|
||||
requestBody: {
|
||||
dealId: deal.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'}>
|
||||
@@ -138,15 +156,41 @@ const Content: FC<Props> = ({deal}) => {
|
||||
</Fieldset>
|
||||
<Flex mt={'md'} gap={rem(10)} align={'center'} justify={'flex-end'}>
|
||||
<Flex align={'center'} gap={rem(10)} justify={'center'}>
|
||||
<Checkbox
|
||||
label={"Сделка завершена"}
|
||||
{...form.getInputProps('isCompleted')}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label={"Сделка удалена"}
|
||||
{...form.getInputProps('isDeleted')}
|
||||
/>
|
||||
<Flex gap={rem(10)}>
|
||||
{(deal.billRequest && deal.billRequest.pdfUrl) &&
|
||||
<ButtonCopy
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
value={deal.billRequest.pdfUrl}
|
||||
>
|
||||
Ссылка на оплату
|
||||
</ButtonCopy>
|
||||
}
|
||||
<ButtonCopyControlled
|
||||
onCopyClick={onCopyGuestUrlClick}
|
||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||
copied={clipboard.copied}>
|
||||
Ссылка на редактирование
|
||||
</ButtonCopyControlled>
|
||||
</Flex>
|
||||
<Flex gap={rem(10)}>
|
||||
|
||||
<Checkbox
|
||||
label={"Оплачен"}
|
||||
checked={deal.billRequest?.paid || false}
|
||||
disabled
|
||||
/>
|
||||
<Checkbox
|
||||
label={"Сделка завершена"}
|
||||
{...form.getInputProps('isCompleted')}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label={"Сделка удалена"}
|
||||
{...form.getInputProps('isDeleted')}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
<Divider
|
||||
orientation={'vertical'}
|
||||
|
||||
@@ -5,6 +5,8 @@ import {useForm} from "@mantine/form";
|
||||
import {ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter} from "@mantine/core";
|
||||
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
||||
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../../redux/store.ts";
|
||||
|
||||
type RestProps = {
|
||||
serviceIds?: number[];
|
||||
@@ -15,6 +17,8 @@ const AddDealServiceModal = ({
|
||||
id,
|
||||
innerProps
|
||||
}: ContextModalProps<Props>) => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const isEditing = 'element' in innerProps;
|
||||
const form = useForm<Partial<DealServiceSchema>>({
|
||||
initialValues: isEditing ? innerProps.element : {
|
||||
@@ -62,7 +66,8 @@ const AddDealServiceModal = ({
|
||||
...form.getInputProps('price'),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
style: {width: '100%'}
|
||||
style: {width: '100%'},
|
||||
disabled: authState.isGuest
|
||||
}}
|
||||
quantity={form.values.quantity || 1}
|
||||
containerProps={{
|
||||
|
||||
@@ -6,6 +6,8 @@ import {isNil, isNumber} from "lodash";
|
||||
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
||||
import {Flex} from "@mantine/core";
|
||||
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../../redux/store.ts";
|
||||
|
||||
type RestProps = {
|
||||
quantity: number;
|
||||
@@ -17,6 +19,8 @@ const ProductServiceFormModal = ({
|
||||
context,
|
||||
id, innerProps
|
||||
}: ContextModalProps<Props>) => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const isEditing = 'onChange' in innerProps;
|
||||
const initialValues: Partial<DealProductServiceSchema> = isEditing ? innerProps.element : {
|
||||
service: undefined,
|
||||
@@ -57,8 +61,8 @@ const ProductServiceFormModal = ({
|
||||
...form.getInputProps('price'),
|
||||
label: "Цена",
|
||||
placeholder: "Введите цену",
|
||||
style: {width: "100%"}
|
||||
|
||||
style: {width: "100%"},
|
||||
disabled: authState.isGuest
|
||||
}}
|
||||
filterType={ServiceType.PRODUCT_SERVICE}
|
||||
containerProps={{
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.container-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.products-list {
|
||||
width: 60%;
|
||||
display: flex;
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import {FC} from "react";
|
||||
import styles from './ProductAndServiceTab.module.css';
|
||||
import ProductView from "./components/ProductView/ProductView.tsx";
|
||||
import {Button, Flex, ScrollArea, Title} from "@mantine/core";
|
||||
import {Button, Divider, Flex, rem, ScrollArea, Text, Title} from "@mantine/core";
|
||||
import DealServicesTable from "./components/DealServicesTable/DealServicesTable.tsx";
|
||||
import useDealProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
|
||||
import {modals} from "@mantine/modals";
|
||||
import {DealProductSchema, DealService, GetServiceKitSchema} from "../../../../client";
|
||||
import {
|
||||
BillingService,
|
||||
DealProductSchema,
|
||||
DealService,
|
||||
GetServiceKitSchema,
|
||||
ProductSchema,
|
||||
ProductService
|
||||
} from "../../../../client";
|
||||
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||
import {CreateProductRequest} from "../../../ProductsPage/types.ts";
|
||||
import classNames from "classnames";
|
||||
|
||||
const ProductAndServiceTab: FC = () => {
|
||||
const {dealState, dealServicesState, dealProductsState} = useDealProductAndServiceTabState();
|
||||
|
||||
const onCreateProductClick = () => {
|
||||
const onAddProductClick = () => {
|
||||
if (!dealProductsState.onCreate || !dealState.deal) return;
|
||||
const productIds = dealState.deal.products.map(product => product.product.id);
|
||||
modals.openContextModal({
|
||||
@@ -88,14 +97,79 @@ const ProductAndServiceTab: FC = () => {
|
||||
await dealState.refetch();
|
||||
});
|
||||
}
|
||||
|
||||
const onCreateProduct = (newProduct: CreateProductRequest) => {
|
||||
ProductService.createProduct({
|
||||
requestBody: newProduct
|
||||
}).then(({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
})
|
||||
}
|
||||
const onCreateProductClick = () => {
|
||||
if (!dealState.deal) return;
|
||||
modals.openContextModal({
|
||||
modal: "createProduct",
|
||||
title: 'Создание товара',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
clientId: dealState.deal.clientId,
|
||||
onCreate: onCreateProduct
|
||||
}
|
||||
})
|
||||
}
|
||||
const onProductEdit = (product: ProductSchema) => {
|
||||
ProductService.updateProduct({requestBody: {product}})
|
||||
.then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message});
|
||||
if (!ok) return;
|
||||
await dealState.refetch();
|
||||
})
|
||||
}
|
||||
|
||||
const onCreateBillClick = () => {
|
||||
if (!dealState.deal) return;
|
||||
const dealId = dealState.deal.id;
|
||||
modals.openConfirmModal({
|
||||
title: "Выставление счета",
|
||||
size: "xl",
|
||||
children:
|
||||
<Text style={{textAlign: "justify"}}>
|
||||
Создание заявки на выставление счета, подтвержденное нажатием кнопки "Выставить", заблокирует
|
||||
возможность
|
||||
редактирования товаров и услуг сделки. Пожалуйста, проверьте всю информацию на точность и полноту
|
||||
перед подтверждением.
|
||||
</Text>,
|
||||
onConfirm: () => {
|
||||
BillingService.createDealBill({
|
||||
requestBody: {
|
||||
dealId
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message});
|
||||
if (ok) notifications.success({message: "Ссылка на оплату доступна во вкладе общее"});
|
||||
await dealState.refetch();
|
||||
})
|
||||
},
|
||||
labels: {
|
||||
confirm: "Выставить",
|
||||
cancel: "Отмена"
|
||||
}
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<div
|
||||
className={
|
||||
classNames(styles['container'],
|
||||
dealState.deal?.billRequest && styles['container-disabled']
|
||||
)
|
||||
}>
|
||||
|
||||
<div className={styles['products-list']}>
|
||||
<ScrollArea offsetScrollbars>
|
||||
|
||||
{dealState.deal?.products.map(product => (
|
||||
<ProductView
|
||||
onProductEdit={onProductEdit}
|
||||
onKitAdd={onKitAdd}
|
||||
onCopyServices={onCopyServicesClick}
|
||||
key={product.product.id}
|
||||
@@ -113,12 +187,25 @@ const ProductAndServiceTab: FC = () => {
|
||||
onKitAdd={onDealKitAdd}
|
||||
{...dealServicesState}
|
||||
/>
|
||||
<Divider my={rem(15)}/>
|
||||
<div className={styles['deal-container-buttons']}>
|
||||
<Button
|
||||
variant={"default"}
|
||||
fullWidth
|
||||
onClick={onCreateProductClick}
|
||||
>Создать товар</Button>
|
||||
<Button
|
||||
onClick={onAddProductClick}
|
||||
variant={"default"}
|
||||
fullWidth>Добавить товар</Button>
|
||||
</div>
|
||||
<Divider my={rem(15)}/>
|
||||
<div className={styles['deal-container-buttons']}>
|
||||
<Button
|
||||
onClick={onCreateBillClick}
|
||||
variant={"default"}
|
||||
fullWidth>Выставить счет</Button>
|
||||
</div>
|
||||
</Flex>
|
||||
<Flex direction={"column"} className={styles['deal-container-wrapper']}>
|
||||
<Title order={3}>Общая стоимость всех услуг: {getTotalPrice().toLocaleString("ru")}₽</Title>
|
||||
|
||||
@@ -7,12 +7,15 @@ import {modals} from "@mantine/modals";
|
||||
import {isNumber} from "lodash";
|
||||
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
||||
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../../../../../redux/store.ts";
|
||||
|
||||
type RestProps = {
|
||||
onKitAdd?: (kit: GetServiceKitSchema) => void
|
||||
};
|
||||
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
|
||||
const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKitAdd}) => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const [currentService, setCurrentService] = useState<DealServiceSchema | undefined>();
|
||||
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
||||
@@ -118,11 +121,13 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Сотрудники">
|
||||
<ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}>
|
||||
<IconUsersGroup/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{!authState.isGuest &&
|
||||
<Tooltip label="Сотрудники">
|
||||
<ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}>
|
||||
<IconUsersGroup/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
}
|
||||
<Text
|
||||
flex={1}
|
||||
>{service.service.name}</Text>
|
||||
@@ -135,6 +140,7 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
|
||||
onChange={event => isNumber(event) && onPriceChange(service, event)}
|
||||
suffix={"₽"}
|
||||
value={service.price}
|
||||
disabled={authState.isGuest}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
@@ -146,7 +152,7 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
|
||||
order={3}
|
||||
>Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}₽</Title>
|
||||
</Flex>
|
||||
<Flex direction={"column"} gap={rem(10)} pb={rem(10)} mt={"auto"}>
|
||||
<Flex direction={"column"} gap={rem(10)} mt={"auto"}>
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
fullWidth
|
||||
|
||||
@@ -8,6 +8,8 @@ import {ActionIcon, Button, Flex, Modal, rem, Tooltip} from "@mantine/core";
|
||||
import {IconEdit, IconTrash, IconUsersGroup} from "@tabler/icons-react";
|
||||
import {modals} from "@mantine/modals";
|
||||
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../../../../../redux/store.ts";
|
||||
|
||||
type RestProps = {
|
||||
quantity: number;
|
||||
@@ -25,6 +27,8 @@ const ProductServicesTable: FC<Props> = ({
|
||||
onCopyServices,
|
||||
onKitAdd
|
||||
}) => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
|
||||
const columns = useProductServicesTableColumns({data: items, quantity});
|
||||
const serviceIds = items.map(service => service.service.id);
|
||||
|
||||
@@ -124,13 +128,15 @@ const ProductServicesTable: FC<Props> = ({
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Сотрудники">
|
||||
<ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}>
|
||||
<IconUsersGroup/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{!authState.isGuest &&
|
||||
<Tooltip label="Сотрудники">
|
||||
<ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}>
|
||||
<IconUsersGroup/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
}
|
||||
</Flex>
|
||||
)
|
||||
),
|
||||
} as MRT_TableOptions<DealProductServiceSchema>}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {useMemo} from "react";
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {DealProductServiceSchema} from "../../../../../../client";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../../../../../redux/store.ts";
|
||||
|
||||
type Props = {
|
||||
data: DealProductServiceSchema[];
|
||||
@@ -8,14 +10,19 @@ type Props = {
|
||||
}
|
||||
const useProductServicesTableColumns = (props: Props) => {
|
||||
const {data, quantity} = props;
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
const totalPrice = useMemo(() => data.reduce((acc, row) => acc + (row.price * quantity), 0), [data, quantity]);
|
||||
const totalCost = useMemo(() => data.reduce((acc, row) => acc + ((row.service.cost || 0) * quantity), 0), [data, quantity]);
|
||||
const hideGuestColumns = [
|
||||
"service.cost"
|
||||
]
|
||||
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "service.name",
|
||||
header: "Услуга",
|
||||
},
|
||||
{
|
||||
enableHiding: true,
|
||||
accessorKey: "service.cost",
|
||||
header: "Себестоимость",
|
||||
Footer: () => <>Итоговая себестоимость: {totalCost.toLocaleString("ru")}₽</>,
|
||||
@@ -25,7 +32,7 @@ const useProductServicesTableColumns = (props: Props) => {
|
||||
header: "Цена",
|
||||
Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}₽</>,
|
||||
}
|
||||
], [totalPrice]);
|
||||
], [totalPrice]).filter(columnDef => !(hideGuestColumns.includes(columnDef.accessorKey || "") && authState.isGuest));
|
||||
}
|
||||
|
||||
export default useProductServicesTableColumns;
|
||||
@@ -9,7 +9,7 @@ import styles from './ProductView.module.css';
|
||||
import {ActionIcon, Flex, Image, NumberInput, rem, Spoiler, Text, Title, Tooltip} from '@mantine/core';
|
||||
import ProductServicesTable from "../ProductServicesTable/ProductServicesTable.tsx";
|
||||
import {isNil, isNumber} from "lodash";
|
||||
import {IconBarcode, IconTrash} from "@tabler/icons-react";
|
||||
import {IconBarcode, IconEdit, IconTrash} from "@tabler/icons-react";
|
||||
import {modals} from "@mantine/modals";
|
||||
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts";
|
||||
|
||||
@@ -19,6 +19,7 @@ type Props = {
|
||||
onDelete?: (item: DealProductSchema) => void
|
||||
onCopyServices?: (item: DealProductSchema) => void;
|
||||
onKitAdd?: (item: DealProductSchema, kit: GetServiceKitSchema) => void;
|
||||
onProductEdit: (product: ProductSchema) => void;
|
||||
}
|
||||
type ProductFieldNames = {
|
||||
[K in keyof ProductSchema]: string
|
||||
@@ -31,7 +32,14 @@ export const ProductFieldNames: Partial<ProductFieldNames> = {
|
||||
composition: "Состав",
|
||||
additionalInfo: "Доп. информация",
|
||||
}
|
||||
const ProductView: FC<Props> = ({product, onDelete, onChange, onCopyServices, onKitAdd}) => {
|
||||
const ProductView: FC<Props> = ({
|
||||
product,
|
||||
onDelete,
|
||||
onChange,
|
||||
onCopyServices,
|
||||
onKitAdd,
|
||||
onProductEdit
|
||||
}) => {
|
||||
const onDeleteClick = () => {
|
||||
if (!onDelete) return;
|
||||
onDelete(product);
|
||||
@@ -91,6 +99,18 @@ const ProductView: FC<Props> = ({product, onDelete, onChange, onCopyServices, on
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const onProductEditClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "createProduct",
|
||||
title: 'Редактирование товара',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: (newProduct) => onProductEdit(newProduct),
|
||||
product: product.product,
|
||||
}
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<div className={styles['data-container']}>
|
||||
@@ -145,6 +165,14 @@ const ProductView: FC<Props> = ({product, onDelete, onChange, onCopyServices, on
|
||||
<IconBarcode/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
onClick={onProductEditClick}
|
||||
label="Редактировать товар">
|
||||
<ActionIcon
|
||||
variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip onClick={onDeleteClick} label="Удалить товар">
|
||||
<ActionIcon
|
||||
variant={"default"}>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {AppShell, Flex, rem} from "@mantine/core";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../redux/store.ts";
|
||||
import styles from './PageWrapper.module.css';
|
||||
import { Navbar } from "../../components/Navbar/Navbar.tsx";
|
||||
import {Navbar} from "../../components/Navbar/Navbar.tsx";
|
||||
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
@@ -14,14 +14,14 @@ const PageWrapper: FC<Props> = ({children}) => {
|
||||
<AppShell
|
||||
layout={"alt"}
|
||||
withBorder={false}
|
||||
navbar={authState.isAuthorized ? {
|
||||
navbar={(authState.isAuthorized && !authState.isGuest) ? {
|
||||
width: "130px",
|
||||
breakpoint: "sm"
|
||||
} : undefined}
|
||||
>
|
||||
|
||||
<AppShell.Navbar>
|
||||
{authState.isAuthorized &&
|
||||
{(authState.isAuthorized && !authState.isGuest) &&
|
||||
<Flex className={styles['main-container']} h={"100%"} w={"100%"}
|
||||
pl={rem(20)}
|
||||
py={rem(20)}
|
||||
@@ -31,8 +31,16 @@ const PageWrapper: FC<Props> = ({children}) => {
|
||||
</Flex>
|
||||
}
|
||||
</AppShell.Navbar>
|
||||
<AppShell.Main className={styles['main-container']}>
|
||||
<div className={styles['container']}>
|
||||
<AppShell.Main
|
||||
style={
|
||||
authState.isGuest ? {backgroundColor: "var(--mantine-color-dark-8)"} : {}
|
||||
}
|
||||
className={styles['main-container']}
|
||||
>
|
||||
<div
|
||||
className={styles['container']}
|
||||
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</AppShell.Main>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import {useMatch, useMatches} from "@tanstack/react-router";
|
||||
import {useMatch, useMatches, useSearch} from "@tanstack/react-router";
|
||||
import {useEffect} from "react";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../redux/store.ts";
|
||||
import {RootState, useAppDispatch} from "../../redux/store.ts";
|
||||
import {OpenAPI} from "../../client";
|
||||
import PageWrapper from "../PageWrapper/PageWrapper.tsx";
|
||||
import {LoadingOverlay} from "@mantine/core";
|
||||
import {AnimatePresence} from "framer-motion";
|
||||
import AnimatedOutlet from "../../components/AnimatedOutlet/au.tsx";
|
||||
import {SearchParams} from "../../shared/lib/general.ts";
|
||||
import {login} from "../../features/authSlice.ts";
|
||||
|
||||
const RootPage = () => {
|
||||
|
||||
const search: SearchParams = useSearch({strict: false});
|
||||
const dispatch = useAppDispatch();
|
||||
const matches = useMatches();
|
||||
const match = useMatch({strict: false});
|
||||
const nextMatchIndex = matches.findIndex((d) => d.id === match.id) + 1;
|
||||
@@ -27,6 +32,10 @@ const RootPage = () => {
|
||||
rewriteLocalStorage();
|
||||
setOpenApiToken();
|
||||
}, [authState]);
|
||||
useEffect(() => {
|
||||
if (!search.accessToken) return;
|
||||
dispatch(login({accessToken: search.accessToken.toString()}))
|
||||
}, [search])
|
||||
return (
|
||||
<>
|
||||
<LoadingOverlay visible={uiState.isLoading}/>
|
||||
|
||||
Reference in New Issue
Block a user