feat: ud service, create product in deal details
This commit is contained in:
@@ -1,35 +1,55 @@
|
||||
import {Select} from "@mantine/core";
|
||||
import {Select, SelectProps} from "@mantine/core";
|
||||
import useServiceCategoriesList from "../../hooks/useServiceCategoriesList.tsx";
|
||||
import {ServiceCategorySchema} from "../../../../client";
|
||||
import {FC, ReactNode} from "react";
|
||||
import {FC, useEffect, useMemo, useState} from "react";
|
||||
|
||||
type Props = {
|
||||
type ControlledValueProps = {
|
||||
value: ServiceCategorySchema
|
||||
onChange: (value: ServiceCategorySchema) => void
|
||||
}
|
||||
|
||||
type RestProps = {
|
||||
fullWidth?: boolean,
|
||||
defaultValue?: ServiceCategorySchema
|
||||
onChange: (category: ServiceCategorySchema) => void
|
||||
error?: ReactNode
|
||||
}
|
||||
const ServiceCategorySelect: FC<Props> = ({defaultValue, onChange, fullWidth, error}) => {
|
||||
type Props = (RestProps & Partial<ControlledValueProps>) & Omit<SelectProps, 'value' | 'onChange'>;
|
||||
const ServiceCategorySelect: FC<Props> = (props: Props) => {
|
||||
const {categories} = useServiceCategoriesList();
|
||||
|
||||
const data = useMemo(() => categories.reduce((acc, category) => {
|
||||
acc.push({
|
||||
label: category.name,
|
||||
value: category.id.toString()
|
||||
});
|
||||
return acc;
|
||||
}, [] as { label: string, value: string }[]), [categories]);
|
||||
|
||||
const isControlled = 'value' in props;
|
||||
const [internalValue, setInternalValue] = useState<ServiceCategorySchema | undefined>(props.defaultValue);
|
||||
const value = isControlled ? props.value : internalValue;
|
||||
|
||||
const handleOnChange = (event: string | null) => {
|
||||
if (!event) return;
|
||||
const category = categories.find(category => parseInt(event) == category.id);
|
||||
if (!category) return;
|
||||
if (isControlled) {
|
||||
props.onChange(category);
|
||||
return;
|
||||
}
|
||||
setInternalValue(category);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isControlled || !internalValue) return;
|
||||
props.onChange(internalValue);
|
||||
}, [internalValue]);
|
||||
return (
|
||||
<Select
|
||||
error={error}
|
||||
w={fullWidth ? "100%" : undefined}
|
||||
checkIconPosition={"right"}
|
||||
label={"Категория услуги"}
|
||||
placeholder={"Выберите категорию услуги"}
|
||||
data={categories.map(category => ({
|
||||
label: category.name,
|
||||
value: category.id.toString()
|
||||
}))}
|
||||
onChange={event => {
|
||||
if (!event) return;
|
||||
const category = categories.find(category => category.id == parseInt(event))
|
||||
if (!category) return;
|
||||
onChange(category)
|
||||
}}
|
||||
value={defaultValue && defaultValue.id.toString()}
|
||||
{...props}
|
||||
value={value?.id.toString()}
|
||||
onChange={handleOnChange}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,55 @@
|
||||
import {ServiceSchema} from "../../../../client";
|
||||
import {FC, RefObject} from "react";
|
||||
import {FC} from "react";
|
||||
import {useServicesTableColumns} from "./columns.tsx";
|
||||
import {BaseTable, BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
|
||||
import {ActionIcon, Flex, Tooltip} from "@mantine/core";
|
||||
import {IconEdit, IconTrash} from "@tabler/icons-react";
|
||||
import {modals} from "@mantine/modals";
|
||||
|
||||
type Props = {
|
||||
services: ServiceSchema[];
|
||||
tableRef?: RefObject<BaseTableRef<ServiceSchema>>
|
||||
}
|
||||
const ServicesTable: FC<Props> = ({services, tableRef}) => {
|
||||
const ServicesTable: FC<CRUDTableProps<ServiceSchema>> = ({items, onDelete, onChange}) => {
|
||||
const columns = useServicesTableColumns();
|
||||
|
||||
const onEditClick = (service: ServiceSchema) => {
|
||||
if (!onChange) return;
|
||||
modals.openContextModal({
|
||||
modal: "createService",
|
||||
title: 'Создание услуги',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: (newService) => onChange(newService),
|
||||
element: service,
|
||||
}
|
||||
})
|
||||
}
|
||||
return (
|
||||
<BaseTable
|
||||
ref={tableRef}
|
||||
data={services}
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableGrouping: true,
|
||||
initialState: {grouping: ["category"]},
|
||||
enableColumnActions: false,
|
||||
mantineCreateRowModalProps: {
|
||||
transitionProps: {transition: 'rotate-left', duration: 300}
|
||||
},
|
||||
enableRowActions: true,
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onEditClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDelete(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)
|
||||
} as MRT_TableOptions<ServiceSchema>}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,51 +1,60 @@
|
||||
import {ServiceSchema} from "../../../client";
|
||||
import {Button, Flex, NumberInput, rem, TextInput} from "@mantine/core";
|
||||
import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {ContextModalProps} from "@mantine/modals";
|
||||
import BaseFormModal, {CreateEditFormProps} from "../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import {NumberInput, TextInput} from "@mantine/core";
|
||||
import ServiceCategorySelect from "../components/ServiceCategorySelect/ServiceCategorySelect.tsx";
|
||||
|
||||
type Props = {
|
||||
onCreate: (service: ServiceSchema) => void
|
||||
}
|
||||
type Props = CreateEditFormProps<ServiceSchema>
|
||||
const CreateServiceModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
category: {
|
||||
id: -1,
|
||||
name: ''
|
||||
},
|
||||
name: '',
|
||||
price: NaN
|
||||
},
|
||||
|
||||
const isEditing = 'onChange' in innerProps;
|
||||
const initialValues: ServiceSchema = isEditing ? {
|
||||
id: innerProps.element.id,
|
||||
name: innerProps.element.name,
|
||||
price: innerProps.element.price,
|
||||
category: innerProps.element.category,
|
||||
} : {
|
||||
id: -1,
|
||||
name: '',
|
||||
price: 0,
|
||||
category: {
|
||||
id: -1,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
const form = useForm<ServiceSchema>({
|
||||
initialValues: initialValues,
|
||||
validate: {
|
||||
category: (category) => category.id >= 0 ? null : "Необходимо выбрать категорию",
|
||||
name: (name) => name.trim() !== '' ? null : "Необходимо ввести название услуги",
|
||||
price: (price) => !isNaN(price) ? null : "Небходимо ввести стоимость услуги"
|
||||
name: (name: string) => name.trim() !== '' ? null : "Необходимо ввести название услуги",
|
||||
price: (price: number) => price > 0 ? null : "Цена должна быть больше 0",
|
||||
category: (category: {
|
||||
id: number,
|
||||
name: string
|
||||
}) => category.id !== -1 ? null : "Необходимо выбрать категорию"
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit = (values: { category: { id: number; name: string; }; name: string; price: number; }) => {
|
||||
innerProps.onCreate({...values, id: -1});
|
||||
context.closeContextModal(id);
|
||||
}
|
||||
const onCancelClick = () => {
|
||||
context.closeContextModal(id);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={form.onSubmit((values) => onSubmit(values))}>
|
||||
<Flex gap={rem(10)} direction={"column"}>
|
||||
|
||||
<BaseFormModal
|
||||
{...innerProps}
|
||||
closeOnSubmit
|
||||
form={form}
|
||||
onClose={onCancelClick}
|
||||
>
|
||||
<BaseFormModal.Body>
|
||||
<>
|
||||
<ServiceCategorySelect
|
||||
fullWidth
|
||||
onChange={event => {
|
||||
form.setFieldValue("category", event)
|
||||
}}
|
||||
error={form.getInputProps("category").error}
|
||||
placeholder={"Выберите категорию"}
|
||||
label={"Категория услуги"}
|
||||
{...form.getInputProps('category')}
|
||||
/>
|
||||
<TextInput
|
||||
|
||||
@@ -60,16 +69,9 @@ const CreateServiceModal = ({
|
||||
decimalScale={2}
|
||||
{...form.getInputProps('price')}
|
||||
/>
|
||||
|
||||
<Flex justify={"center"} mt={rem(5)} gap={rem(10)}>
|
||||
<Button onClick={() => onCancelClick()} variant={"subtle"}>Отменить</Button>
|
||||
<Button type={"submit"} variant={"default"}>Сохранить</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
</form>
|
||||
|
||||
</>
|
||||
</>
|
||||
</BaseFormModal.Body>
|
||||
</BaseFormModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {FC, useRef} from "react";
|
||||
import {FC} from "react";
|
||||
import ServicesTable from "../components/ServicesTable/ServicesTable.tsx";
|
||||
import useServicesList from "../hooks/useServicesList.tsx";
|
||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||
import styles from './ServicesPage.module.css';
|
||||
import {Button} from "@mantine/core";
|
||||
import {BaseTableRef} from "../../../components/BaseTable/BaseTable.tsx";
|
||||
import {Button, Text} from "@mantine/core";
|
||||
import {ServiceCategorySchema, ServiceSchema, ServiceService} from "../../../client";
|
||||
import {notifications} from "../../../shared/lib/notifications.ts";
|
||||
import {modals} from "@mantine/modals";
|
||||
@@ -12,8 +11,7 @@ import {modals} from "@mantine/modals";
|
||||
export const ServicesPage: FC = () => {
|
||||
const {services, refetch} = useServicesList();
|
||||
|
||||
const tableRef = useRef<BaseTableRef<ServiceSchema>>(null);
|
||||
|
||||
// region Service create
|
||||
const onCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: 'createService',
|
||||
@@ -32,6 +30,9 @@ export const ServicesPage: FC = () => {
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Category create
|
||||
const onCreateCategoryClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "createServiceCategory",
|
||||
@@ -47,7 +48,42 @@ export const ServicesPage: FC = () => {
|
||||
.then(({ok, message}) =>
|
||||
notifications.guess(ok, {message: message}))
|
||||
}
|
||||
// endregion
|
||||
|
||||
const onServiceDelete = (service: ServiceSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Удаление услуги',
|
||||
children: (<Text>
|
||||
Вы уверены, что хотите удалить услугу "{service.name}"?
|
||||
</Text>),
|
||||
onConfirm: () => {
|
||||
ServiceService.deleteService({requestBody: {serviceId: service.id}})
|
||||
.then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
},
|
||||
labels: {
|
||||
confirm: 'Удалить',
|
||||
cancel: 'Отмена'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onServiceUpdate = (service: ServiceSchema) => {
|
||||
ServiceService
|
||||
.updateService({
|
||||
requestBody: {
|
||||
data: service
|
||||
}
|
||||
})
|
||||
.then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message: message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<PageBlock>
|
||||
@@ -58,8 +94,9 @@ export const ServicesPage: FC = () => {
|
||||
</PageBlock>
|
||||
<PageBlock>
|
||||
<ServicesTable
|
||||
tableRef={tableRef}
|
||||
services={services}
|
||||
onDelete={onServiceDelete}
|
||||
onChange={onServiceUpdate}
|
||||
items={services}
|
||||
/>
|
||||
</PageBlock>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user