This commit is contained in:
2024-07-21 10:58:51 +03:00
parent 54c9ca8908
commit af05b51d1c
188 changed files with 1155 additions and 555 deletions

View File

@@ -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>

View File

@@ -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>}
/>
)

View File

@@ -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: "Название должности"
}
], []);
}

View File

@@ -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}

View File

@@ -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",

View File

@@ -27,7 +27,6 @@ const UserFormModal = ({context, id, innerProps}: ContextModalProps<Props>) => {
}).isValid && 'Неверно указан номер телефона',
}
});
console.log(form.getInputProps('isAdmin'))
return (<BaseFormModal
form={form}
closeOnSubmit

View File

@@ -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;

View File

@@ -12,6 +12,7 @@ const UsersTab = () => {
data: {
...user,
positionKey: user.position?.key,
roleKey: user.role.key
}
}
}).then(async ({ok, message}) => {

View File

@@ -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>}
/>
</>
)
}

View File

@@ -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>
,
}

View File

@@ -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;

View 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: "Должность"
},
], []);
}

View File

@@ -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)}>

View File

@@ -20,6 +20,7 @@ const AddDealServiceModal = ({
initialValues: isEditing ? innerProps.element : {
service: undefined,
quantity: 1,
employees: []
},
validate: {
service: (service?: DealServiceSchema['service']) => service !== undefined ? null : "Необходимо выбрать услугу",

View File

@@ -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,

View File

@@ -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;

View File

@@ -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>
</>
)
}

View File

@@ -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>

View File

@@ -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
/>

View File

@@ -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 (<>

View File

@@ -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']}>

View File

@@ -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}