This commit is contained in:
2024-03-19 09:02:58 +03:00
parent cc14105276
commit c9f3d4ee12
56 changed files with 995 additions and 121 deletions

View File

@@ -1,17 +1,18 @@
import {FC} from "react";
import styles from './ClientsPage.module.css';
import ClientsTable from "./components/ClientsTable/ClientsTable.tsx";
import {ClientService} from "../../client";
import {useQuery} from "@tanstack/react-query";
import useClientsList from "./hooks/useClientsList.tsx";
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
const ClientsPage: FC = () => {
const {data} = useQuery({queryKey: ['clients'], queryFn: ClientService.getAllClients})
const {clients} = useClientsList();
return (
<div className={styles['container']}>
<ClientsTable data={data?.clients || []}/>
</div>
<>
<PageBlock>
<ClientsTable data={clients}/>
</PageBlock>
</>
)
}
export default ClientsPage;

View File

@@ -1,52 +1,28 @@
import {Checkbox, rem, Table} from "@mantine/core";
import {columns} from "./columns.tsx";
import React, {FC} from "react";
import {Client} from "../../../../types/Client.ts";
import {IconEdit} from "@tabler/icons-react";
import {notifications} from "../../../../shared/lib/notifications.ts";
import {FC} from "react";
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
import {useClientsTableColumns} from "./columns.tsx";
import {MantineReactTable, MRT_Table, MRT_TableOptions, useMantineReactTable} from "mantine-react-table";
import {ClientSchema} from "../../../../client";
type Props = {
data: Client[];
data: ClientSchema[];
}
const ClientsTable: FC<Props> = ({data}) => {
const columns = useClientsTableColumns();
return (
<Table.ScrollContainer minWidth={rem(50)}>
<Table striped>
<Table.Thead>
<Table.Tr>
<Table.Th/>
{columns.map(column => (
<Table.Th>{column.header}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{data.map(client => (
<Table.Tr key={client.name}>
<Table.Td>
<IconEdit
onClick={() => {
notifications.success({message: `Редактирую ${client.id}`})
}}
style={{cursor: 'pointer'}}
/>
</Table.Td>
{columns.map(column => (
<Table.Td>{client[column.accessorKey]}</Table.Td>
))}
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
<BaseTable
striped
data={data}
columns={columns}
restProps={{
enableSorting: false,
enableColumnActions: false,
enableRowSelection: true,
enableColumnResizing: true,
layoutMode: "grid"
} as MRT_TableOptions<ClientSchema>}
/>
)
}

View File

@@ -1,12 +1,25 @@
import {Client} from "../../../../types/Client.ts";
import {useMemo} from "react";
import {MRT_ColumnDef} from "mantine-react-table";
import {ClientSchema} from "../../../../client";
type Column = {
accessorKey: keyof Client;
header: string;
export const useClientsTableColumns = () => {
return useMemo<MRT_ColumnDef<ClientSchema>[]>(() => [
{
accessorKey: "name",
header: "Имя",
},
{
accessorKey: "details.address",
header: "Адрес"
},
{
accessorKey: "details.email",
header: "EMAIL"
},
{
accessorKey: "details.phone_number",
header: "Телефон"
}
], []);
}
export const columns: Column[] = [
{
accessorKey: 'name',
header: 'Название'
}
]

View File

@@ -0,0 +1,13 @@
import {ClientService} from "../../../client";
import {useQuery} from "@tanstack/react-query";
const useClientsList = () => {
const {isPending, error, data, refetch} = useQuery({
queryKey: ['getAllClients'],
queryFn: ClientService.getAllClients
});
const clients = isPending || error || !data ? [] : data.clients;
return {clients, refetch}
}
export default useClientsList;

View File

@@ -0,0 +1,15 @@
import {useQuery} from "@tanstack/react-query";
import {DealService, DealSummary} from "../../../client";
export const useDealSummaries = (): DealSummary[] => {
const {data: summaries = []} = useQuery({
queryKey: ['getDealSummaries'],
queryFn: DealService.getDealSummaries,
select: data => data.summaries || [] // Трансформируем полученные данные
});
// Теперь summaries будет содержать либо трансформированные данные, либо пустой массив по умолчанию
// isLoading и isError могут быть использованы для отображения индикаторов загрузки или ошибки
return summaries;
}

View File

@@ -2,6 +2,7 @@
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
}

View File

@@ -1,31 +1,70 @@
import {FC, useEffect} from "react";
import React, {FC, useEffect, useState} from "react";
import styles from './LeadsPage.module.css';
import Board from "../../../components/Dnd/Board/Board.tsx";
import {Button, TextInput} from "@mantine/core";
import {DragDropContext} from "@hello-pangea/dnd";
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
import {DealStatus} from "../../../shared/enums/DealStatus.ts";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
export const LeadsPage: FC = () => {
const summariesRaw = useDealSummaries();
const [summaries, setSummaries] = useState(summariesRaw);
useEffect(() => {
// dispatch(setIsLoading(true));
}, []);
setSummaries(summariesRaw);
}, [summariesRaw]);
const onDragEnd = () => {
// if (!result.destination) return;
//
// const newStatus = getDealStatusByName(
// result.destination.droppableId
// );
// const summaryId = parseInt(result.draggableId);
//
// return;
}
return (
<>
<div className={styles['container']}>
<div className={styles['boards']}>
<DragDropContext onDragEnd={() => {
}}>
<Board title={"Ожидает приемки"} withCreateButton droppableId={"AWAITING_ACCEPTANCE"}/>
<Board title={"Упаковка"} droppableId={"PACKAGING"}/>
<Board title={"Ожидает отгрузки"} droppableId={"AWAITING_SHIPMENT"}/>
<Board title={"Ожидает оплаты"} droppableId={"AWAITING_PAYMENT"}/>
<Board title={"Завершена"} droppableId={"COMPLETED"}/>
</DragDropContext>
<PageBlock>
<div className={styles['container']}>
<div className={styles['boards']}>
<DragDropContext onDragEnd={onDragEnd}>
<Board
withCreateButton
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
title={"Ожидает приемки"}
droppableId={"AWAITING_ACCEPTANCE"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.PACKAGING)}
title={"Упаковка"}
droppableId={"PACKAGING"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
title={"Ожидает отгрузки"}
droppableId={"AWAITING_SHIPMENT"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
title={"Ожидает оплаты"}
droppableId={"AWAITING_PAYMENT"}
/>
<Board
summaries={summaries
.filter(summary => summary.status == DealStatus.COMPLETED)}
title={"Завершена"}
droppableId={"COMPLETED"}
/>
</DragDropContext>
</div>
</div>
</div>
</PageBlock>
</>
)

View File

@@ -0,0 +1,14 @@
.main-container {
@mixin dark {
background-color: var(--mantine-color-dark-6);
}
@mixin light {
background-color: var(--mantine-color-gray-0);
}
}
.container {
padding: rem(20);
display: flex;
min-height: 100vh;
}

View File

@@ -1,31 +1,35 @@
import {FC, ReactNode} from "react";
import {Flex, rem} from "@mantine/core";
import {AppShell, rem} from "@mantine/core";
import {Navbar} from "../../components/Navbar/Navbar.tsx";
import {useSelector} from "react-redux";
import {RootState} from "../../redux/store.ts";
import Header from "../../components/Header/Header.tsx";
import styles from './PageWrapper.module.css';
export type Props = {
children: ReactNode;
}
const PageWrapper: FC<Props> = ({children}) => {
const authState = useSelector((state: RootState) => state.auth);
return (<Flex style={{flex: 1}}>
{authState.isAuthorized &&
return (
<AppShell
layout={"alt"}
navbar={{width: rem('80px'), breakpoint: "sm"}}
// header={{height:rem(60)}}
>
{/*<AppShell.Header>*/}
{/* <Header/>*/}
{/*</AppShell.Header>*/}
<AppShell.Navbar>
<Navbar/>
}
<div style={{flex: 1, display: 'flex', flexDirection: 'column'}}>
<Header/>
<div style={{padding:rem(20), flex:1}}>
</AppShell.Navbar>
<AppShell.Main className={styles['main-container']}>
<div className={styles['container']}>
{children}
</div>
</div>
</Flex>
</AppShell.Main>
</AppShell>
)
}
export default PageWrapper;

View File

@@ -0,0 +1,37 @@
import {Select} from "@mantine/core";
import useServiceCategoriesList from "../../hooks/useServiceCategoriesList.tsx";
import {ServiceCategorySchema} from "../../../../client";
import {FC, ReactNode} from "react";
type Props = {
fullWidth?: boolean,
defaultValue?: ServiceCategorySchema
onChange: (category: ServiceCategorySchema) => void
error?: ReactNode
}
const ServiceCategorySelect: FC<Props> = ({defaultValue, onChange, fullWidth, error}) => {
const {categories} = useServiceCategoriesList();
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()}
/>
)
}
export default ServiceCategorySelect;

View File

@@ -0,0 +1,30 @@
import {ServiceSchema} from "../../../../client";
import {FC, RefObject} from "react";
import {useServicesTableColumns} from "./columns.tsx";
import {BaseTable, BaseTableRef} from "../../../../components/BaseTable/BaseTable.tsx";
import {MRT_TableOptions} from "mantine-react-table";
type Props = {
services: ServiceSchema[];
tableRef?: RefObject<BaseTableRef<ServiceSchema>>
}
const ServicesTable: FC<Props> = ({services, tableRef}) => {
const columns = useServicesTableColumns();
return (
<BaseTable
ref={tableRef}
data={services}
columns={columns}
restProps={{
enableGrouping: true,
initialState: {grouping: ["category"]},
enableColumnActions: false,
mantineCreateRowModalProps: {
transitionProps: {transition: 'rotate-left', duration: 300}
},
} as MRT_TableOptions<ServiceSchema>}
/>
)
}
export default ServicesTable;

View File

@@ -0,0 +1,31 @@
import {useMemo} from "react";
import {MRT_ColumnDef} from "mantine-react-table";
import {ServiceSchema} from "../../../../client";
export const useServicesTableColumns = () => {
return useMemo<MRT_ColumnDef<ServiceSchema>[]>(() => [
{
accessorKey: "category",
header: "Категория",
enableGrouping: false,
enableSorting: false,
accessorFn: (row) => row.category.name
},
{
accessorKey: "name",
header: "Услуга",
enableGrouping: false,
enableSorting: false,
},
{
accessorKey: "price",
header: "Цена",
enableGrouping: false,
enableSorting: false,
},
], []);
}

View File

@@ -0,0 +1,14 @@
import {useQuery} from "@tanstack/react-query";
import {ServiceService} from "../../../client";
const useServiceCategoriesList = () => {
const {isPending, error, data, refetch} = useQuery({
queryKey: ['getAllServiceCategories'],
queryFn: ServiceService.getAllServiceCategories,
});
const categories = isPending || error || !data ? [] : data.categories;
return {categories, refetch}
}
export default useServiceCategoriesList;

View File

@@ -0,0 +1,13 @@
import {useQuery} from "@tanstack/react-query";
import {ServiceService} from "../../../client";
const useServicesList = () => {
const {isPending, error, data, refetch} = useQuery({
queryKey: ['getAllServices'],
queryFn: ServiceService.getAllServices
});
const services = isPending || error || !data ? [] : data.services;
return {services, refetch}
}
export default useServicesList;

View File

@@ -0,0 +1 @@
export {ServicesPage} from './ui/ServicesPage.tsx';

View File

@@ -0,0 +1,52 @@
import {ServiceCategorySchema} from "../../../client";
import {Button, Flex, rem, TextInput} from "@mantine/core";
import {useForm} from "@mantine/form";
import {ContextModalProps} from "@mantine/modals";
type Props = {
onCreate: (category: ServiceCategorySchema) => void
}
const CreateServiceCategoryModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const form = useForm({
initialValues: {
name: ''
},
validate: {
name: (name) => name.trim() !== '' ? null : "Необходимо ввести название категории",
}
})
const onSubmit = (values: { name: string }) => {
innerProps.onCreate({name: values.name, id: -1});
context.closeContextModal(id);
}
const onCancelClick = () => {
context.closeContextModal(id);
}
return (
<>
<form onSubmit={form.onSubmit((values) => onSubmit(values))}>
<Flex gap={rem(10)} direction={"column"}>
<TextInput
placeholder={"Введите название категори"}
label={"Название категории"}
{...form.getInputProps('name')}
/>
<Flex justify={"center"} mt={rem(5)} gap={rem(10)}>
<Button onClick={() => onCancelClick()} variant={"subtle"}>Отменить</Button>
<Button type={"submit"} variant={"default"}>Сохранить</Button>
</Flex>
</Flex>
</form>
</>
)
}
export default CreateServiceCategoryModal;

View File

@@ -0,0 +1,76 @@
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";
type Props = {
onCreate: (service: ServiceSchema) => void
}
const CreateServiceModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const form = useForm({
initialValues: {
category: {
id: -1,
name: ''
},
name: '',
price: NaN
},
validate: {
category: (category) => category.id >= 0 ? null : "Необходимо выбрать категорию",
name: (name) => name.trim() !== '' ? null : "Необходимо ввести название услуги",
price: (price) => !isNaN(price) ? 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"}>
<ServiceCategorySelect
fullWidth
onChange={event => {
form.setFieldValue("category", event)
}}
error={form.getInputProps("category").error}
/>
<TextInput
placeholder={"Введите название услуги"}
label={"Название услуги"}
{...form.getInputProps('name')}
/>
<NumberInput
placeholder={"Введите стоимость услуги"}
label={"Стоимость услуги"}
hideControls
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>
</>
)
}
export default CreateServiceModal;

View File

@@ -0,0 +1,12 @@
.container {
display: flex;
flex-direction: column;
flex: 1;
gap: rem(10);
}
.top-panel {
padding: rem(5);
gap: rem(10);
display: flex;
}

View File

@@ -0,0 +1,67 @@
import {FC, useRef} 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 {ServiceCategorySchema, ServiceSchema, ServiceService} from "../../../client";
import {notifications} from "../../../shared/lib/notifications.ts";
import {modals} from "@mantine/modals";
export const ServicesPage: FC = () => {
const {services, refetch} = useServicesList();
const tableRef = useRef<BaseTableRef<ServiceSchema>>(null);
const onCreateClick = () => {
modals.openContextModal({
modal: 'createService',
title: 'Создание услуги',
withCloseButton: false,
innerProps: {
onCreate
}
})
}
const onCreate = (values: ServiceSchema) => {
ServiceService.createService({requestBody: {service: values}})
.then(({ok, message}) => {
notifications.guess(ok, {message: message});
if (!ok) return;
refetch();
})
}
const onCreateCategoryClick = () => {
modals.openContextModal({
modal: "createServiceCategory",
title: 'Создание категории',
withCloseButton: false,
innerProps: {
onCreate: onCategoryCreate
}
})
}
const onCategoryCreate = (category: ServiceCategorySchema) => {
ServiceService.createServiceCategory({requestBody: {category: category}})
.then(({ok, message}) =>
notifications.guess(ok, {message: message}))
}
return (
<div className={styles['container']}>
<PageBlock>
<div className={styles['top-panel']}>
<Button onClick={onCreateClick} variant={"default"}>Создать услугу</Button>
<Button onClick={onCreateCategoryClick} variant={"default"}>Создать категорию</Button>
</div>
</PageBlock>
<PageBlock>
<ServicesTable
tableRef={tableRef}
services={services}
/>
</PageBlock>
</div>
)
}