k
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import styles from './AdminPage.module.css';
|
||||
import {Tabs} from "@mantine/core";
|
||||
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
||||
import {IconBriefcase, IconUser, IconUsersGroup} from "@tabler/icons-react";
|
||||
import {IconBriefcase, IconUser} from "@tabler/icons-react";
|
||||
import RolesAndPositionsTab from "./tabs/RolesAndPositions/RolesAndPositionsTab.tsx";
|
||||
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
||||
import {motion} from "framer-motion";
|
||||
@@ -17,27 +17,28 @@ const AdminPage = () => {
|
||||
Пользователи
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value={"rolesAndPositions"} leftSection={<IconBriefcase/>}>
|
||||
Роли и должности
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value={"employees"} leftSection={<IconUsersGroup/>}>
|
||||
Сотрудники
|
||||
Должности
|
||||
</Tabs.Tab>
|
||||
{/*<Tabs.Tab value={"employees"} leftSection={<IconUsersGroup/>}>*/}
|
||||
{/* Сотрудники*/}
|
||||
{/*</Tabs.Tab>*/}
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"users"}>
|
||||
<motion.div
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.2}}
|
||||
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}>
|
||||
>
|
||||
<UsersTab/>
|
||||
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"rolesAndPositions"}>
|
||||
<motion.div
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.2}}
|
||||
>
|
||||
<RolesAndPositionsTab/>
|
||||
</motion.div>
|
||||
|
||||
@@ -3,36 +3,76 @@ import {PositionSchema} from "../../../../client";
|
||||
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {usePositionsTableColumns} from "./columns.tsx";
|
||||
import {FC} from "react";
|
||||
import {Button, Flex, rem} from "@mantine/core";
|
||||
import {ActionIcon, Button, Flex, rem, Text, Tooltip} from "@mantine/core";
|
||||
import {modals} from "@mantine/modals";
|
||||
import {IconTrash} from "@tabler/icons-react";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
|
||||
type Props = CRUDTableProps<PositionSchema>;
|
||||
|
||||
const PositionsTable: FC<Props> = ({items}) => {
|
||||
const PositionsTable: FC<Props> = ({items, onCreate, onDelete}) => {
|
||||
const columns = usePositionsTableColumns();
|
||||
|
||||
const onCreateClick = () => {
|
||||
if (!onCreate) return;
|
||||
modals.openContextModal({
|
||||
modal: "positionForm",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: onCreate
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onDeleteClick = (position: PositionSchema) => {
|
||||
if (!onDelete) return;
|
||||
modals.openConfirmModal({
|
||||
title: 'Удаление должности',
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить должность {position.name}
|
||||
</Text>
|
||||
),
|
||||
labels: {confirm: 'Да', cancel: "Нет"},
|
||||
confirmProps: {color: 'red'},
|
||||
onConfirm: () => onDelete(position)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableTopToolbar: true,
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
renderTopToolbar: () => (
|
||||
enableRowActions: true,
|
||||
renderTopToolbar: (
|
||||
<Flex p={rem(10)}>
|
||||
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={() => {
|
||||
|
||||
}}
|
||||
onClick={() => onCreateClick()}
|
||||
>
|
||||
Создать должность
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
),
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => onDeleteClick(row.original)} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)
|
||||
}}
|
||||
data={items}
|
||||
columns={columns}
|
||||
} as MRT_TableOptions<PositionSchema>}
|
||||
|
||||
/>
|
||||
|
||||
)
|
||||
|
||||
@@ -4,13 +4,14 @@ import {PositionSchema} from "../../../../client";
|
||||
|
||||
export const usePositionsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<PositionSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Название должности"
|
||||
},
|
||||
{
|
||||
accessorKey: "key",
|
||||
header: "Ключ"
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Название должности"
|
||||
}
|
||||
|
||||
], []);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const UsersTable: FC<Props> = ({items, onChange, onDelete}) => {
|
||||
if (!onDelete) return;
|
||||
modals.openConfirmModal({
|
||||
title: 'Удаление пользователя',
|
||||
centered: true,
|
||||
// centered: true,
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить пользователя {user.firstName} {user.secondName}
|
||||
|
||||
@@ -6,8 +6,12 @@ import {IconCheck, IconX} from "@tabler/icons-react";
|
||||
export const useUsersTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<UserSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "ID"
|
||||
accessorKey: "firstName",
|
||||
header: "Имя"
|
||||
},
|
||||
{
|
||||
accessorKey: "secondName",
|
||||
header: "Фамилия"
|
||||
},
|
||||
{
|
||||
accessorKey: "telegramId",
|
||||
|
||||
@@ -27,7 +27,6 @@ const UserFormModal = ({context, id, innerProps}: ContextModalProps<Props>) => {
|
||||
}).isValid && 'Неверно указан номер телефона',
|
||||
}
|
||||
});
|
||||
console.log(form.getInputProps('isAdmin'))
|
||||
return (<BaseFormModal
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import {Tabs} from "@mantine/core";
|
||||
import PositionsTable from "../../components/PositionsTable/PositionsTable.tsx";
|
||||
import usePositionsList from "../../hooks/usePositionsList.tsx";
|
||||
import {motion} from "framer-motion";
|
||||
import {PositionSchema, PositionService} from "../../../../client";
|
||||
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||
|
||||
const RolesAndPositionsTab = () => {
|
||||
const {objects: positions} = usePositionsList();
|
||||
const {objects: positions, refetch} = usePositionsList();
|
||||
const onCreate = (position: PositionSchema) => {
|
||||
PositionService.createPosition({
|
||||
requestBody: {
|
||||
data: position
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
const onDelete = (position: PositionSchema) => {
|
||||
PositionService.deletePosition({
|
||||
requestBody: {
|
||||
positionKey: position.key
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs w={"100%"}
|
||||
variant={"default"}
|
||||
keepMounted={false}
|
||||
defaultValue={"roles"}
|
||||
>
|
||||
<Tabs.List grow>
|
||||
<Tabs.Tab value={"roles"}>Роли</Tabs.Tab>
|
||||
<Tabs.Tab value={"positions"}>Должности</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"roles"}>
|
||||
<motion.div
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}
|
||||
>
|
||||
<PositionsTable items={positions}/>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"positions"}>
|
||||
<motion.div
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}
|
||||
>
|
||||
<PositionsTable items={positions}/>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
<PositionsTable
|
||||
items={positions}
|
||||
onCreate={onCreate}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default RolesAndPositionsTab;
|
||||
@@ -12,6 +12,7 @@ const UsersTab = () => {
|
||||
data: {
|
||||
...user,
|
||||
positionKey: user.position?.key,
|
||||
roleKey: user.role.key
|
||||
}
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
|
||||
@@ -23,6 +23,8 @@ const DealServicesTable: FC<Props> = (
|
||||
tableRef
|
||||
}) => {
|
||||
const serviceIds = items.map(item => item.service.id);
|
||||
|
||||
|
||||
const columns = useDealServicesTableColumns({
|
||||
data: items
|
||||
});
|
||||
@@ -50,59 +52,67 @@ const DealServicesTable: FC<Props> = (
|
||||
}
|
||||
})
|
||||
}
|
||||
return (
|
||||
<BaseTable
|
||||
ref={tableRef}
|
||||
data={items}
|
||||
columns={columns}
|
||||
onSelectionChange={onSelectionChange}
|
||||
restProps={{
|
||||
enableGrouping: true,
|
||||
initialState: {grouping: ["service.category"]},
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableBottomToolbar: true,
|
||||
enableRowActions: true,
|
||||
enableRowSelection: true,
|
||||
renderBottomToolbar: ({table}) => (
|
||||
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
|
||||
{(onMultipleDelete && table.getSelectedRowModel().rows.length > 0) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onMultipleDelete(table.getSelectedRowModel().rows.map(row => row.original))
|
||||
}}
|
||||
variant={"filled"}
|
||||
color={"red"}
|
||||
>
|
||||
Удалить выбранные
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onCreateClick} variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
|
||||
</Flex>
|
||||
),
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDelete(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon onClick={() => {
|
||||
onEditClick(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)
|
||||
} as MRT_TableOptions<DealServiceSchema>}
|
||||
/>
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseTable
|
||||
ref={tableRef}
|
||||
data={items}
|
||||
columns={columns}
|
||||
onSelectionChange={onSelectionChange}
|
||||
restProps={{
|
||||
enableGrouping: true,
|
||||
initialState: {grouping: ["service.category"]},
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableBottomToolbar: true,
|
||||
enableRowActions: true,
|
||||
enableRowSelection: true,
|
||||
renderBottomToolbar: ({table}) => (
|
||||
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
|
||||
{(onMultipleDelete && table.getSelectedRowModel().rows.length > 0) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onMultipleDelete(table.getSelectedRowModel().rows.map(row => row.original))
|
||||
}}
|
||||
variant={"filled"}
|
||||
color={"red"}
|
||||
>
|
||||
Удалить выбранные
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onCreateClick} variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
|
||||
</Flex>
|
||||
),
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDelete(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon onClick={() => {
|
||||
onEditClick(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
</Flex>
|
||||
)
|
||||
} as MRT_TableOptions<DealServiceSchema>}
|
||||
/>
|
||||
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ export const useDealStatusChangeTableColumns = () => {
|
||||
accessorFn: (row) => new Date(row.changedAt).toLocaleString('ru-RU'),
|
||||
},
|
||||
{
|
||||
accessorKey: "user.id",
|
||||
header: "ID пользователя",
|
||||
header: "Пользователь",
|
||||
accessorFn: (row) => `${row.user.firstName} ${row.user.secondName}`
|
||||
},
|
||||
{
|
||||
accessorKey: "fromStatus",
|
||||
@@ -29,11 +29,11 @@ export const useDealStatusChangeTableColumns = () => {
|
||||
accessorKey: "comment",
|
||||
header: "Комментарий",
|
||||
Cell: ({row}) =>
|
||||
<Spoiler maxHeight={80} showLabel={"Показать весь"} hideLabel={"Скрыть"}>
|
||||
<Text style={{wordWrap: "break-word", wordBreak: "break-all", whiteSpace: "normal"}} span>
|
||||
{row.original.comment}<br/>
|
||||
</Text>
|
||||
</Spoiler>
|
||||
<Spoiler maxHeight={80} showLabel={"Показать весь"} hideLabel={"Скрыть"}>
|
||||
<Text style={{wordWrap: "break-word", wordBreak: "break-all", whiteSpace: "normal"}} span>
|
||||
{row.original.comment}<br/>
|
||||
</Text>
|
||||
</Spoiler>
|
||||
,
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {UserSchema} from "../../../../client";
|
||||
import {FC} from "react";
|
||||
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {useSimpleUsersTableColumns} from "./columns.tsx";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
import {ActionIcon, Button, Flex, rem, Tooltip} from "@mantine/core";
|
||||
import {modals} from "@mantine/modals";
|
||||
import {IconTrash} from "@tabler/icons-react";
|
||||
|
||||
export type SimpleUsersTableProps = {
|
||||
items: UserSchema[],
|
||||
onChange: (items: UserSchema[]) => void
|
||||
}
|
||||
|
||||
const SimpleUsersTable: FC<SimpleUsersTableProps> = ({items, onChange}) => {
|
||||
const columns = useSimpleUsersTableColumns();
|
||||
|
||||
const onAddClick = () => {
|
||||
const userIds = items.map(user => user.id);
|
||||
modals.openContextModal({
|
||||
title: "Выберите сотрудника",
|
||||
modal: "employeeSelect",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
selectProps: {
|
||||
filterBy: (user) => !userIds.includes(user.id) //&& user.roleKey === UserRoleEnum.EMPLOYEE
|
||||
},
|
||||
onSelect: (user) => onChange([...items, user])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableBottomToolbar: true,
|
||||
renderBottomToolbar: (
|
||||
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
|
||||
<Button onClick={() => onAddClick()} variant={"default"}>
|
||||
Добавить сотрудника к услуге
|
||||
</Button>
|
||||
</Flex>
|
||||
),
|
||||
enableRowActions: true,
|
||||
renderRowActions: ({row}) => (
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
onChange(items.filter(item => item.id !== row.original.id));
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>),
|
||||
} as MRT_TableOptions<UserSchema>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SimpleUsersTable;
|
||||
20
src/pages/LeadsPage/components/SimpleUsersTable/columns.tsx
Normal file
20
src/pages/LeadsPage/components/SimpleUsersTable/columns.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import {useMemo} from "react";
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {UserSchema} from "../../../../client";
|
||||
|
||||
export const useSimpleUsersTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<UserSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "firstName",
|
||||
header: "Имя"
|
||||
},
|
||||
{
|
||||
accessorKey: "secondName",
|
||||
header: "Фамилия"
|
||||
},
|
||||
{
|
||||
accessorKey: "position.name",
|
||||
header: "Должность"
|
||||
},
|
||||
], []);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import DealStatusChangeTable from "../../components/DealStatusChangeTable/DealSt
|
||||
import DealEditDrawerGeneralTab from "./tabs/DealEditDrawerGeneralTab.tsx";
|
||||
import {useQueryClient} from "@tanstack/react-query";
|
||||
import ProductAndServiceTab from "../../tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||
import {motion} from "framer-motion";
|
||||
// import styles from './DealEditDrawer.module.css';
|
||||
|
||||
const useDealServicesTableState = () => {
|
||||
@@ -317,7 +318,7 @@ const DealEditDrawer: FC = () => {
|
||||
}, [isVisible]);
|
||||
return (
|
||||
<Drawer
|
||||
size={"95%"}
|
||||
size={"calc(100vw - 150px)"}
|
||||
position={"right"}
|
||||
onClose={onClose}
|
||||
removeScrollProps={{allowPinchZoom: true}}
|
||||
@@ -329,7 +330,10 @@ const DealEditDrawer: FC = () => {
|
||||
defaultValue={"general"}
|
||||
h={'100%'}
|
||||
variant={"outline"}
|
||||
orientation={"vertical"}>
|
||||
orientation={"vertical"}
|
||||
keepMounted={false}
|
||||
|
||||
>
|
||||
<Tabs.List
|
||||
>
|
||||
<Tabs.Tab value={"general"} leftSection={<IconSettings/>}>
|
||||
@@ -343,19 +347,43 @@ const DealEditDrawer: FC = () => {
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"general"}>
|
||||
<Box h={"100%"} w={"100%"} p={rem(10)}>
|
||||
<DealEditDrawerGeneralTab/>
|
||||
</Box>
|
||||
<motion.div
|
||||
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.2}}
|
||||
>
|
||||
|
||||
<Box h={"100%"} w={"100%"} p={rem(10)}>
|
||||
<DealEditDrawerGeneralTab/>
|
||||
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"history"}>
|
||||
<Box p={rem(10)}>
|
||||
<DealEditDrawerStatusChangeTable/>
|
||||
</Box>
|
||||
<motion.div
|
||||
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.2}}
|
||||
>
|
||||
<Box p={rem(10)}>
|
||||
<DealEditDrawerStatusChangeTable/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"servicesAndProducts"}>
|
||||
<Box p={rem(10)}>
|
||||
<ProductAndServiceTab/>
|
||||
</Box>
|
||||
<motion.div
|
||||
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.2}}
|
||||
>
|
||||
<Box p={rem(10)}>
|
||||
<ProductAndServiceTab/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"services"}>
|
||||
<Box p={rem(10)}>
|
||||
|
||||
@@ -20,6 +20,7 @@ const AddDealServiceModal = ({
|
||||
initialValues: isEditing ? innerProps.element : {
|
||||
service: undefined,
|
||||
quantity: 1,
|
||||
employees: []
|
||||
},
|
||||
validate: {
|
||||
service: (service?: DealServiceSchema['service']) => service !== undefined ? null : "Необходимо выбрать услугу",
|
||||
|
||||
@@ -20,7 +20,8 @@ const ProductServiceFormModal = ({
|
||||
const isEditing = 'onChange' in innerProps;
|
||||
const initialValues: Partial<DealProductServiceSchema> = isEditing ? innerProps.element : {
|
||||
service: undefined,
|
||||
price: undefined
|
||||
price: undefined,
|
||||
employees: []
|
||||
}
|
||||
const form = useForm<Partial<DealProductServiceSchema>>({
|
||||
initialValues,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import {CRUDTableProps} from "../../../../../../types/CRUDTable.tsx";
|
||||
import {DealServiceSchema} from "../../../../../../client";
|
||||
import {FC} from "react";
|
||||
import {ActionIcon, Button, Flex, NumberInput, rem, Text, Title, Tooltip} from "@mantine/core";
|
||||
import {IconTrash} from "@tabler/icons-react";
|
||||
import {DealServiceSchema, UserSchema} from "../../../../../../client";
|
||||
import {FC, useState} from "react";
|
||||
import {ActionIcon, Button, Flex, Modal, NumberInput, rem, Text, Title, Tooltip} from "@mantine/core";
|
||||
import {IconTrash, IconUsersGroup} from "@tabler/icons-react";
|
||||
import {modals} from "@mantine/modals";
|
||||
import {isNumber} from "lodash";
|
||||
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
||||
|
||||
type Props = CRUDTableProps<DealServiceSchema>;
|
||||
const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange}) => {
|
||||
|
||||
const [currentService, setCurrentService] = useState<DealServiceSchema | undefined>();
|
||||
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
||||
|
||||
const onDeleteClick = (item: DealServiceSchema) => {
|
||||
if (!onDelete) return;
|
||||
onDelete(item);
|
||||
@@ -38,73 +43,119 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange}) =>
|
||||
price
|
||||
})
|
||||
}
|
||||
|
||||
const onEmployeeClick = (item: DealServiceSchema) => {
|
||||
if (!onChange) return;
|
||||
setCurrentService(item);
|
||||
setEmployeesModalVisible(true);
|
||||
}
|
||||
const onEmployeeModalClose = () => {
|
||||
setEmployeesModalVisible(false);
|
||||
setCurrentService(undefined);
|
||||
}
|
||||
const getCurrentEmployees = (): UserSchema[] => {
|
||||
if (!currentService) return [];
|
||||
const item = items.find(i => i.service.id === currentService.service.id)
|
||||
if (!item) return [];
|
||||
return item.employees;
|
||||
}
|
||||
const onEmployeesChange = (items: UserSchema[]) => {
|
||||
if (!currentService || !onChange) return;
|
||||
onChange({
|
||||
...currentService,
|
||||
employees: items
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
h={"100%"}
|
||||
|
||||
>
|
||||
<>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
h={"100%"}
|
||||
direction={"column"}>
|
||||
<Title
|
||||
order={3}
|
||||
w={"100%"}
|
||||
style={{textAlign: "center"}}
|
||||
mb={rem(10)}
|
||||
>Общие услуги</Title>
|
||||
|
||||
>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
h={"100%"}
|
||||
direction={"column"}>
|
||||
<Title
|
||||
order={3}
|
||||
w={"100%"}
|
||||
style={{textAlign: "center"}}
|
||||
mb={rem(10)}
|
||||
>Общие услуги</Title>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
|
||||
{items.map(service => (
|
||||
<Flex
|
||||
key={service.service.id}
|
||||
w={"100%"}
|
||||
gap={rem(10)}
|
||||
align={"center"}
|
||||
>
|
||||
<Tooltip
|
||||
onClick={() => onDeleteClick(service)}
|
||||
label="Удалить услугу">
|
||||
<ActionIcon
|
||||
variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Text
|
||||
flex={1}
|
||||
>{service.service.name}</Text>
|
||||
<NumberInput
|
||||
suffix={" шт."}
|
||||
onChange={event => isNumber(event) && onQuantityChange(service, event)}
|
||||
value={service.quantity}
|
||||
/>
|
||||
<NumberInput
|
||||
onChange={event => isNumber(event) && onPriceChange(service, event)}
|
||||
suffix={"₽"}
|
||||
value={service.price}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
{items.map(service => (
|
||||
<Flex
|
||||
key={service.service.id}
|
||||
w={"100%"}
|
||||
gap={rem(10)}
|
||||
align={"center"}
|
||||
>
|
||||
<Tooltip
|
||||
onClick={() => onDeleteClick(service)}
|
||||
label="Удалить услугу">
|
||||
<ActionIcon
|
||||
variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Сотрудники">
|
||||
<ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}>
|
||||
<IconUsersGroup/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Text
|
||||
flex={1}
|
||||
>{service.service.name}</Text>
|
||||
<NumberInput
|
||||
suffix={" шт."}
|
||||
onChange={event => isNumber(event) && onQuantityChange(service, event)}
|
||||
value={service.quantity}
|
||||
/>
|
||||
<NumberInput
|
||||
onChange={event => isNumber(event) && onPriceChange(service, event)}
|
||||
suffix={"₽"}
|
||||
value={service.price}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
<Title
|
||||
style={{textAlign: "end"}}
|
||||
mt={rem(10)}
|
||||
order={3}
|
||||
>Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}₽</Title>
|
||||
</Flex>
|
||||
<Flex pb={rem(10)} mt={"auto"}>
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
fullWidth
|
||||
variant={"default"}
|
||||
>Добавить услугу</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Modal
|
||||
title={"Добавление сотрудника к услуге"}
|
||||
opened={employeesModalVisible}
|
||||
onClose={onEmployeeModalClose}
|
||||
size={"xl"}
|
||||
>
|
||||
<Flex direction={"column"} gap={rem(10)}>
|
||||
<SimpleUsersTable
|
||||
items={getCurrentEmployees()}
|
||||
onChange={onEmployeesChange}
|
||||
|
||||
/>
|
||||
|
||||
</Flex>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
<Title
|
||||
style={{textAlign: "end"}}
|
||||
mt={rem(10)}
|
||||
order={3}
|
||||
>Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}₽</Title>
|
||||
</Flex>
|
||||
<Flex pb={rem(10)} mt={"auto"}>
|
||||
<Button
|
||||
onClick={onCreateClick}
|
||||
fullWidth
|
||||
variant={"default"}
|
||||
>Добавить услугу</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
export default DealServicesTable;
|
||||
@@ -1,21 +1,26 @@
|
||||
import {CRUDTableProps} from "../../../../../../types/CRUDTable.tsx";
|
||||
import {DealProductServiceSchema} from "../../../../../../client";
|
||||
import {FC} from "react";
|
||||
import {DealProductServiceSchema, UserSchema} from "../../../../../../client";
|
||||
import {FC, useState} from "react";
|
||||
import useProductServicesTableColumns from "./columns.tsx";
|
||||
import {BaseTable} from "../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
import {ActionIcon, Button, Flex, rem, Tooltip} from "@mantine/core";
|
||||
import {IconEdit, IconTrash} from "@tabler/icons-react";
|
||||
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";
|
||||
|
||||
type RestProps = {
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
type Props = CRUDTableProps<DealProductServiceSchema> & RestProps;
|
||||
const ProductServicesTable: FC<Props> = ({items, quantity, onCreate, onDelete, onChange}) => {
|
||||
const columns = useProductServicesTableColumns({data: items, quantity});
|
||||
const serviceIds = items.map(service => service.service.id);
|
||||
|
||||
const [currentService, setCurrentService] = useState<DealProductServiceSchema | undefined>();
|
||||
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
||||
|
||||
const onCreateClick = () => {
|
||||
if (!onCreate) return;
|
||||
modals.openContextModal({
|
||||
@@ -30,10 +35,7 @@ const ProductServicesTable: FC<Props> = ({items, quantity, onCreate, onDelete, o
|
||||
}
|
||||
|
||||
const onChangeClick = (item: DealProductServiceSchema) => {
|
||||
console.log('change click')
|
||||
|
||||
if (!onChange) return;
|
||||
console.log('change click1')
|
||||
modals.openContextModal({
|
||||
modal: "productServiceForm",
|
||||
innerProps: {
|
||||
@@ -45,52 +47,93 @@ const ProductServicesTable: FC<Props> = ({items, quantity, onCreate, onDelete, o
|
||||
withCloseButton: false
|
||||
})
|
||||
}
|
||||
|
||||
const onEmployeeClick = (item: DealProductServiceSchema) => {
|
||||
if (!onChange) return;
|
||||
setCurrentService(item);
|
||||
setEmployeesModalVisible(true);
|
||||
}
|
||||
const onEmployeeModalClose = () => {
|
||||
setEmployeesModalVisible(false);
|
||||
setCurrentService(undefined);
|
||||
}
|
||||
const getCurrentEmployees = (): UserSchema[] => {
|
||||
if (!currentService) return [];
|
||||
const item = items.find(i => i.service.id === currentService.service.id)
|
||||
if (!item) return [];
|
||||
return item.employees;
|
||||
}
|
||||
const onEmployeesChange = (items: UserSchema[]) => {
|
||||
if (!currentService || !onChange) return;
|
||||
onChange({
|
||||
...currentService,
|
||||
employees: items
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableRowActions: true,
|
||||
enableBottomToolbar: true,
|
||||
renderBottomToolbar: (
|
||||
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
|
||||
<>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={rem(10)}
|
||||
>
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableColumnActions: false,
|
||||
enableSorting: false,
|
||||
enableRowActions: true,
|
||||
enableBottomToolbar: true,
|
||||
renderBottomToolbar: (
|
||||
<Flex justify={"flex-end"} gap={rem(10)} p={rem(10)}>
|
||||
|
||||
<Button onClick={onCreateClick} variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
<Button onClick={onCreateClick} variant={"default"}>
|
||||
Добавить услугу
|
||||
</Button>
|
||||
|
||||
</Flex>
|
||||
),
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onChangeClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDelete(row.original);
|
||||
// console.log(row);
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)
|
||||
} as MRT_TableOptions<DealProductServiceSchema>}
|
||||
/>
|
||||
{/*<Button variant={"default"}>Добавить услугу</Button>*/}
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon onClick={() => {
|
||||
if (onDelete) onDelete(row.original);
|
||||
}} variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onChangeClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Сотрудники">
|
||||
<ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}>
|
||||
<IconUsersGroup/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)
|
||||
} as MRT_TableOptions<DealProductServiceSchema>}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
title={"Добавление сотрудника к услуге"}
|
||||
opened={employeesModalVisible}
|
||||
onClose={onEmployeeModalClose}
|
||||
size={"xl"}
|
||||
>
|
||||
<Flex direction={"column"} gap={rem(10)}>
|
||||
<SimpleUsersTable
|
||||
items={getCurrentEmployees()}
|
||||
onChange={onEmployeesChange}
|
||||
|
||||
/>
|
||||
|
||||
</Flex>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ const ProductView: FC<Props> = ({product, onDelete, onChange}) => {
|
||||
{Object.entries(product.product).map(([key, value]) => {
|
||||
const fieldName = ProductFieldNames[key as keyof ProductSchema];
|
||||
if (!fieldName || isNil(value) || value === '') return;
|
||||
return (<Text>{fieldName}: {value.toString()} </Text>)
|
||||
return (<Text key={fieldName}>{fieldName}: {value.toString()} </Text>)
|
||||
})}
|
||||
<Text>Штрихкоды: {product.product.barcodes.join(', ')}</Text>
|
||||
</Spoiler>
|
||||
|
||||
@@ -96,88 +96,89 @@ export const LeadsPage: FC = () => {
|
||||
<>
|
||||
<DealPageContextProvider>
|
||||
|
||||
<PageBlock fullHeight>
|
||||
<div className={styles['container']}>
|
||||
<DragDropContext
|
||||
onDragStart={() => {
|
||||
setIsDragEnded(false);
|
||||
}}
|
||||
onDragEnd={onDragEnd}>
|
||||
<div className={styles['boards']}>
|
||||
<Board
|
||||
withCreateButton
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
|
||||
title={"Ожидает приемки"}
|
||||
droppableId={"AWAITING_ACCEPTANCE"}
|
||||
color={'#4A90E2'}
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.PACKAGING)}
|
||||
title={"Упаковка"}
|
||||
droppableId={"PACKAGING"}
|
||||
color={'#F5A623'}
|
||||
<PageBlock fullHeight style={{
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}>
|
||||
<DragDropContext
|
||||
onDragStart={() => {
|
||||
setIsDragEnded(false);
|
||||
}}
|
||||
onDragEnd={onDragEnd}>
|
||||
<div className={styles['boards']}>
|
||||
<Board
|
||||
withCreateButton
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
|
||||
title={"Ожидает приемки"}
|
||||
droppableId={"AWAITING_ACCEPTANCE"}
|
||||
color={'#4A90E2'}
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.PACKAGING)}
|
||||
title={"Упаковка"}
|
||||
droppableId={"PACKAGING"}
|
||||
color={'#F5A623'}
|
||||
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
|
||||
title={"Ожидает отгрузки"}
|
||||
droppableId={"AWAITING_SHIPMENT"}
|
||||
color={'#7ED321'}
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
|
||||
title={"Ожидает отгрузки"}
|
||||
droppableId={"AWAITING_SHIPMENT"}
|
||||
color={'#7ED321'}
|
||||
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
|
||||
title={"Ожидает оплаты"}
|
||||
droppableId={"AWAITING_PAYMENT"}
|
||||
color={'#D0021B'}
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
|
||||
title={"Ожидает оплаты"}
|
||||
droppableId={"AWAITING_PAYMENT"}
|
||||
color={'#D0021B'}
|
||||
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.COMPLETED)}
|
||||
title={"Завершена"}
|
||||
droppableId={"COMPLETED"}
|
||||
color={'#417505'}
|
||||
/>
|
||||
</div>
|
||||
<Flex justify={"flex-end"}>
|
||||
<div
|
||||
className={
|
||||
classNames(
|
||||
styles['delete'],
|
||||
isDragEnded && styles['delete-hidden']
|
||||
)
|
||||
}
|
||||
>
|
||||
<Droppable droppableId={"DELETE"}>
|
||||
{(provided) => (
|
||||
<>
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{!isDragEnded &&
|
||||
<span>
|
||||
/>
|
||||
<Board
|
||||
summaries={summaries
|
||||
.filter(summary => summary.status == DealStatus.COMPLETED)}
|
||||
title={"Завершена"}
|
||||
droppableId={"COMPLETED"}
|
||||
color={'#417505'}
|
||||
/>
|
||||
</div>
|
||||
<Flex justify={"flex-end"}>
|
||||
<div
|
||||
className={
|
||||
classNames(
|
||||
styles['delete'],
|
||||
isDragEnded && styles['delete-hidden']
|
||||
)
|
||||
}
|
||||
>
|
||||
<Droppable droppableId={"DELETE"}>
|
||||
{(provided) => (
|
||||
<>
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{!isDragEnded &&
|
||||
<span>
|
||||
Удалить
|
||||
</span>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
{provided.placeholder}
|
||||
</>
|
||||
</div>
|
||||
{provided.placeholder}
|
||||
</>
|
||||
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</Flex>
|
||||
</DragDropContext>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
</Flex>
|
||||
</DragDropContext>
|
||||
|
||||
</div>
|
||||
</PageBlock>
|
||||
<DealEditDrawer
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import {FC} from "react";
|
||||
import {useNavigate} from "@tanstack/react-router";
|
||||
import {Navigate} from "@tanstack/react-router";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../redux/store.ts";
|
||||
|
||||
const MainPage: FC = () => {
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
const navigate = useNavigate();
|
||||
if (authState.isAuthorized) {
|
||||
navigate({to: "/leads"})
|
||||
return (<></>)
|
||||
return (<Navigate to={"/leads"}/>)
|
||||
}
|
||||
return (<>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {FC, ReactNode} from "react";
|
||||
import {AppShell} from "@mantine/core";
|
||||
import {Navbar} from "../../components/Navbar/Navbar.tsx";
|
||||
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";
|
||||
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
@@ -13,15 +13,22 @@ const PageWrapper: FC<Props> = ({children}) => {
|
||||
return (
|
||||
<AppShell
|
||||
layout={"alt"}
|
||||
withBorder={false}
|
||||
navbar={authState.isAuthorized ? {
|
||||
width: '5%',
|
||||
width: "130px",
|
||||
breakpoint: "sm"
|
||||
} : undefined}
|
||||
>
|
||||
|
||||
<AppShell.Navbar>
|
||||
{authState.isAuthorized &&
|
||||
<Navbar/>
|
||||
<Flex className={styles['main-container']} h={"100%"} w={"100%"}
|
||||
pl={rem(20)}
|
||||
py={rem(20)}
|
||||
>
|
||||
<Navbar/>
|
||||
|
||||
</Flex>
|
||||
}
|
||||
</AppShell.Navbar>
|
||||
<AppShell.Main className={styles['main-container']}>
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ProductsPage: FC = () => {
|
||||
const onDeleteClick = (product: ProductSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Удаление товара',
|
||||
centered: true,
|
||||
// centered: true,
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить товар {product.name}
|
||||
|
||||
Reference in New Issue
Block a user