v0.1
This commit is contained in:
@@ -32,6 +32,8 @@ export type { DealDeleteProductRequest } from './models/DealDeleteProductRequest
|
|||||||
export type { DealDeleteProductResponse } from './models/DealDeleteProductResponse';
|
export type { DealDeleteProductResponse } from './models/DealDeleteProductResponse';
|
||||||
export type { DealDeleteProductsRequest } from './models/DealDeleteProductsRequest';
|
export type { DealDeleteProductsRequest } from './models/DealDeleteProductsRequest';
|
||||||
export type { DealDeleteProductsResponse } from './models/DealDeleteProductsResponse';
|
export type { DealDeleteProductsResponse } from './models/DealDeleteProductsResponse';
|
||||||
|
export type { DealDeleteRequest } from './models/DealDeleteRequest';
|
||||||
|
export type { DealDeleteResponse } from './models/DealDeleteResponse';
|
||||||
export type { DealDeleteServiceRequest } from './models/DealDeleteServiceRequest';
|
export type { DealDeleteServiceRequest } from './models/DealDeleteServiceRequest';
|
||||||
export type { DealDeleteServiceResponse } from './models/DealDeleteServiceResponse';
|
export type { DealDeleteServiceResponse } from './models/DealDeleteServiceResponse';
|
||||||
export type { DealDeleteServicesRequest } from './models/DealDeleteServicesRequest';
|
export type { DealDeleteServicesRequest } from './models/DealDeleteServicesRequest';
|
||||||
@@ -46,7 +48,6 @@ export type { DealServiceSchema } from './models/DealServiceSchema';
|
|||||||
export type { DealStatusHistorySchema } from './models/DealStatusHistorySchema';
|
export type { DealStatusHistorySchema } from './models/DealStatusHistorySchema';
|
||||||
export type { DealSummary } from './models/DealSummary';
|
export type { DealSummary } from './models/DealSummary';
|
||||||
export type { DealSummaryReorderRequest } from './models/DealSummaryReorderRequest';
|
export type { DealSummaryReorderRequest } from './models/DealSummaryReorderRequest';
|
||||||
export type { DealSummaryReorderResponse } from './models/DealSummaryReorderResponse';
|
|
||||||
export type { DealSummaryResponse } from './models/DealSummaryResponse';
|
export type { DealSummaryResponse } from './models/DealSummaryResponse';
|
||||||
export type { DealUpdateGeneralInfoRequest } from './models/DealUpdateGeneralInfoRequest';
|
export type { DealUpdateGeneralInfoRequest } from './models/DealUpdateGeneralInfoRequest';
|
||||||
export type { DealUpdateGeneralInfoResponse } from './models/DealUpdateGeneralInfoResponse';
|
export type { DealUpdateGeneralInfoResponse } from './models/DealUpdateGeneralInfoResponse';
|
||||||
|
|||||||
8
src/client/models/DealDeleteRequest.ts
Normal file
8
src/client/models/DealDeleteRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do no edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealDeleteRequest = {
|
||||||
|
dealId: number;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type DealSummaryReorderResponse = {
|
export type DealDeleteResponse = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
@@ -8,6 +8,7 @@ export type DealStatusHistorySchema = {
|
|||||||
changedAt: string;
|
changedAt: string;
|
||||||
fromStatus: number;
|
fromStatus: number;
|
||||||
toStatus: number;
|
toStatus: number;
|
||||||
nextStatusDeadline: string;
|
nextStatusDeadline: (string | null);
|
||||||
|
comment?: (string | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type DealSummary = {
|
|||||||
name: string;
|
name: string;
|
||||||
clientName: string;
|
clientName: string;
|
||||||
changedAt: string;
|
changedAt: string;
|
||||||
|
deadline: string;
|
||||||
status: number;
|
status: number;
|
||||||
totalPrice: number;
|
totalPrice: number;
|
||||||
rank: number;
|
rank: number;
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type DealSummaryReorderRequest = {
|
export type DealSummaryReorderRequest = {
|
||||||
dealId: number;
|
dealId: number;
|
||||||
newStatus: number;
|
status: number;
|
||||||
rank: number;
|
index: number;
|
||||||
nextStatusDeadline: string;
|
deadline: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import type { DealDeleteProductRequest } from '../models/DealDeleteProductReques
|
|||||||
import type { DealDeleteProductResponse } from '../models/DealDeleteProductResponse';
|
import type { DealDeleteProductResponse } from '../models/DealDeleteProductResponse';
|
||||||
import type { DealDeleteProductsRequest } from '../models/DealDeleteProductsRequest';
|
import type { DealDeleteProductsRequest } from '../models/DealDeleteProductsRequest';
|
||||||
import type { DealDeleteProductsResponse } from '../models/DealDeleteProductsResponse';
|
import type { DealDeleteProductsResponse } from '../models/DealDeleteProductsResponse';
|
||||||
|
import type { DealDeleteRequest } from '../models/DealDeleteRequest';
|
||||||
|
import type { DealDeleteResponse } from '../models/DealDeleteResponse';
|
||||||
import type { DealDeleteServiceRequest } from '../models/DealDeleteServiceRequest';
|
import type { DealDeleteServiceRequest } from '../models/DealDeleteServiceRequest';
|
||||||
import type { DealDeleteServiceResponse } from '../models/DealDeleteServiceResponse';
|
import type { DealDeleteServiceResponse } from '../models/DealDeleteServiceResponse';
|
||||||
import type { DealDeleteServicesRequest } from '../models/DealDeleteServicesRequest';
|
import type { DealDeleteServicesRequest } from '../models/DealDeleteServicesRequest';
|
||||||
@@ -24,7 +26,6 @@ import type { DealQuickCreateRequest } from '../models/DealQuickCreateRequest';
|
|||||||
import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse';
|
import type { DealQuickCreateResponse } from '../models/DealQuickCreateResponse';
|
||||||
import type { DealSchema } from '../models/DealSchema';
|
import type { DealSchema } from '../models/DealSchema';
|
||||||
import type { DealSummaryReorderRequest } from '../models/DealSummaryReorderRequest';
|
import type { DealSummaryReorderRequest } from '../models/DealSummaryReorderRequest';
|
||||||
import type { DealSummaryReorderResponse } from '../models/DealSummaryReorderResponse';
|
|
||||||
import type { DealSummaryResponse } from '../models/DealSummaryResponse';
|
import type { DealSummaryResponse } from '../models/DealSummaryResponse';
|
||||||
import type { DealUpdateGeneralInfoRequest } from '../models/DealUpdateGeneralInfoRequest';
|
import type { DealUpdateGeneralInfoRequest } from '../models/DealUpdateGeneralInfoRequest';
|
||||||
import type { DealUpdateGeneralInfoResponse } from '../models/DealUpdateGeneralInfoResponse';
|
import type { DealUpdateGeneralInfoResponse } from '../models/DealUpdateGeneralInfoResponse';
|
||||||
@@ -56,6 +57,26 @@ export class DealService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Delete
|
||||||
|
* @returns DealDeleteResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteDeal({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: DealDeleteRequest,
|
||||||
|
}): CancelablePromise<DealDeleteResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/delete',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Quick Create
|
* Quick Create
|
||||||
* @returns DealQuickCreateResponse Successful Response
|
* @returns DealQuickCreateResponse Successful Response
|
||||||
@@ -109,14 +130,14 @@ export class DealService {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Reorder
|
* Reorder
|
||||||
* @returns DealSummaryReorderResponse Successful Response
|
* @returns DealSummaryResponse Successful Response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static reorderDealSummaries({
|
public static reorderDealSummaries({
|
||||||
requestBody,
|
requestBody,
|
||||||
}: {
|
}: {
|
||||||
requestBody: DealSummaryReorderRequest,
|
requestBody: DealSummaryReorderRequest,
|
||||||
}): CancelablePromise<DealSummaryReorderResponse> {
|
}): CancelablePromise<DealSummaryResponse> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/deal/summaries/reorder',
|
url: '/deal/summaries/reorder',
|
||||||
|
|||||||
@@ -6,23 +6,29 @@ import CreateDealButton from "../CreateDealButton/CreateDealButton.tsx";
|
|||||||
import {DealSummary} from "../../../client";
|
import {DealSummary} from "../../../client";
|
||||||
import DealSummaryCard from "../DealSummaryCard/DealSummaryCard.tsx";
|
import DealSummaryCard from "../DealSummaryCard/DealSummaryCard.tsx";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import {getPluralForm} from "../../../shared/lib/utils.ts";
|
||||||
|
import {sum} from "lodash";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
droppableId: string;
|
droppableId: string;
|
||||||
title: string;
|
title: string;
|
||||||
withCreateButton?: boolean;
|
withCreateButton?: boolean;
|
||||||
summaries: DealSummary[];
|
summaries: DealSummary[];
|
||||||
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Board: FC<Props> = ({droppableId, title, summaries, withCreateButton = false}) => {
|
export const Board: FC<Props> = ({droppableId, title, summaries, color, withCreateButton = false}) => {
|
||||||
|
const getDealsText = () => {
|
||||||
|
const pluralForm = getPluralForm(summaries.length, 'сделка', 'сделки', 'сделок');
|
||||||
|
return `${summaries.length} ${pluralForm}: ${sum(summaries.map(summary => summary.totalPrice))}₽`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["container"]}>
|
<div className={styles["container"]}>
|
||||||
<div className={styles["header"]}>
|
<div className={styles["header"]}>
|
||||||
<Title size={"h4"}>{title}</Title>
|
<Title size={"h4"}>{title}</Title>
|
||||||
<Text>12 сделок: 500р</Text>
|
<Text>{getDealsText()}</Text>
|
||||||
<Divider size={"xl"} my={10} color={"blue"}/>
|
<Divider size={"xl"} my={10} color={color}/>
|
||||||
</div>
|
</div>
|
||||||
<Droppable
|
<Droppable
|
||||||
droppableId={droppableId}
|
droppableId={droppableId}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {Text, Transition} from '@mantine/core';
|
|||||||
import CreateDealFrom from "../CreateDealForm/CreateDealFrom.tsx";
|
import CreateDealFrom from "../CreateDealForm/CreateDealFrom.tsx";
|
||||||
import {DealService} from "../../../client";
|
import {DealService} from "../../../client";
|
||||||
import {dateWithoutTimezone} from "../../../shared/lib/utils.ts";
|
import {dateWithoutTimezone} from "../../../shared/lib/utils.ts";
|
||||||
|
import {useQueryClient} from "@tanstack/react-query";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -12,6 +13,8 @@ type Props = {
|
|||||||
const CreateDealButton: FC<Props> = () => {
|
const CreateDealButton: FC<Props> = () => {
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles['container']}
|
<div className={styles['container']}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -41,6 +44,9 @@ const CreateDealButton: FC<Props> = () => {
|
|||||||
...quickDeal,
|
...quickDeal,
|
||||||
acceptanceDate: dateWithoutTimezone(quickDeal.acceptanceDate)
|
acceptanceDate: dateWithoutTimezone(quickDeal.acceptanceDate)
|
||||||
}
|
}
|
||||||
|
}).then(async () => {
|
||||||
|
await queryClient.invalidateQueries({queryKey: ['getDealSummaries']});
|
||||||
|
setIsCreating(false);
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,12 +18,26 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
|
|||||||
setSelectedDeal(deal);
|
setSelectedDeal(deal);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const getDeadlineTextColor = (deadline: string) => {
|
||||||
|
// generate three colors, yellow for 1 day, red for 0 days, green for more than 1 day
|
||||||
|
const deadlineDate = new Date(deadline);
|
||||||
|
const currentDate = new Date();
|
||||||
|
const diff = deadlineDate.getTime() - currentDate.getTime();
|
||||||
|
const diffDays = Math.ceil(diff / (1000 * 3600 * 24));
|
||||||
|
if (diffDays === 1) {
|
||||||
|
return 'yellow.8';
|
||||||
|
}
|
||||||
|
if (diffDays === 0) {
|
||||||
|
return 'red.8';
|
||||||
|
}
|
||||||
|
return 'green.8';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div onClick={() => onDealSummaryClick()} className={styles['container']}>
|
<div onClick={() => onDealSummaryClick()} className={styles['container']}>
|
||||||
<div className={styles['flex-row']}>
|
<div className={styles['flex-row']}>
|
||||||
<div className={styles['flex-item']}>
|
<div className={styles['flex-item']}>
|
||||||
<Text size={"sm"} c={"gray.6"}>
|
<Text size={"sm"} c={"gray.6"}>
|
||||||
{dealSummary.clientName}
|
Клиент: {dealSummary.clientName}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['flex-item']}>
|
<div className={styles['flex-item']}>
|
||||||
@@ -38,12 +52,12 @@ const DealSummaryCard: FC<Props> = ({dealSummary}) => {
|
|||||||
<div className={classNames(styles['flex-row'], styles['flex-row-right'])}>
|
<div className={classNames(styles['flex-row'], styles['flex-row-right'])}>
|
||||||
<div className={styles['flex-item']}>
|
<div className={styles['flex-item']}>
|
||||||
<Text size={"sm"} c={"gray.6"}>
|
<Text size={"sm"} c={"gray.6"}>
|
||||||
{new Date(dealSummary.changedAt).toLocaleString('ru-RU')}
|
{/*Создана: {new Date(dealSummary.changedAt).toLocaleString('ru-RU')}*/}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['flex-item']}>
|
<div className={styles['flex-item']}>
|
||||||
<Text size={"sm"} c={"yellow.8"}>
|
<Text size={"sm"} c={getDeadlineTextColor(dealSummary.deadline)}>
|
||||||
Нет задач
|
{new Date(dealSummary.deadline).toLocaleString('ru-RU').slice(0, -3)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ function NavbarLink(props: NavbarLinkProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mockdata = [
|
const mockdata = [
|
||||||
{
|
// {
|
||||||
icon: IconHome2,
|
// icon: IconHome2,
|
||||||
label: 'Главная',
|
// label: 'Главная',
|
||||||
href: '/'
|
// href: '/'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
icon: IconCash,
|
icon: IconCash,
|
||||||
label: 'Сделки',
|
label: 'Сделки',
|
||||||
@@ -83,7 +83,7 @@ export function Navbar() {
|
|||||||
<nav className={classes.navbar}>
|
<nav className={classes.navbar}>
|
||||||
<Center>
|
<Center>
|
||||||
<Image
|
<Image
|
||||||
style={{filter: "drop-shadow(0 0 30px #fff)"}}
|
// style={{filter: "drop-shadow(0 0 30px #fff)"}}
|
||||||
src={colorScheme == "dark" ? "/icons/logo-light.png" : "/icons/logo.png"}
|
src={colorScheme == "dark" ? "/icons/logo-light.png" : "/icons/logo.png"}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
.container {
|
.container {
|
||||||
border-radius: rem(20);
|
border-radius: rem(20);
|
||||||
background-color: var(--mantine-color-body);
|
background-color: var(--mantine-color-body);
|
||||||
padding: rem(10);
|
padding: rem(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container-full-height {
|
||||||
|
height: calc(100vh - (rem(20) * 2));
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import {FC, ReactNode} from "react";
|
import {CSSProperties, FC, ReactNode} from "react";
|
||||||
import styles from './PageBlock.module.css';
|
import styles from './PageBlock.module.css';
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
fluid?: boolean;
|
fluid?: boolean;
|
||||||
|
style?: CSSProperties;
|
||||||
|
fullHeight?: boolean;
|
||||||
}
|
}
|
||||||
export const PageBlock: FC<Props> = ({children, fluid = true}) => {
|
export const PageBlock: FC<Props> = ({children, style, fluid = true, fullHeight = false}) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles['container'], fluid && styles['container-fluid'])}>
|
<div style={style}
|
||||||
|
className={classNames(styles['container'], fluid && styles['container-fluid'], fullHeight && styles['container-full-height'])}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import {Button, Flex, rem, Textarea} from "@mantine/core";
|
|||||||
import {DateTimePicker, DateValue} from "@mantine/dates";
|
import {DateTimePicker, DateValue} from "@mantine/dates";
|
||||||
import {useForm} from "@mantine/form";
|
import {useForm} from "@mantine/form";
|
||||||
import {DealSummaryReorderRequest} from "../../client";
|
import {DealSummaryReorderRequest} from "../../client";
|
||||||
|
import {dateWithoutTimezone} from "../../shared/lib/utils.ts";
|
||||||
|
|
||||||
type Deadline = {
|
type Deadline = {
|
||||||
datetime: DateValue,
|
deadline: DateValue,
|
||||||
comment: string
|
comment: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
request:DealSummaryReorderRequest,
|
request: Partial<DealSummaryReorderRequest>,
|
||||||
onSubmit: (
|
onSubmit: (
|
||||||
deadline: Deadline,
|
|
||||||
request: DealSummaryReorderRequest,
|
request: DealSummaryReorderRequest,
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
@@ -24,28 +24,37 @@ const EnterDeadlineModal = ({
|
|||||||
}: ContextModalProps<Props>) => {
|
}: ContextModalProps<Props>) => {
|
||||||
const form = useForm<Deadline>({
|
const form = useForm<Deadline>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
datetime: null,
|
deadline: null,
|
||||||
comment: '',
|
comment: '',
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
datetime: (datetime) => datetime !== null ? null : 'Необходимо ввести дедлайн',
|
deadline: (datetime) => datetime !== null ? null : 'Необходимо ввести дедлайн',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const onCancelClick = () => {
|
const onCancelClick = () => {
|
||||||
context.closeModal(id);
|
context.closeModal(id);
|
||||||
}
|
}
|
||||||
const onSubmit = (values: Deadline) => {
|
const onSubmit = (values: Deadline) => {
|
||||||
innerProps.onSubmit(values, innerProps.request);
|
const {deadline, comment} = values;
|
||||||
|
if (!deadline) return;
|
||||||
|
innerProps.onSubmit({
|
||||||
|
...innerProps.request,
|
||||||
|
deadline: dateWithoutTimezone(deadline),
|
||||||
|
comment
|
||||||
|
} as unknown as DealSummaryReorderRequest);
|
||||||
|
context.closeModal(id);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit((values) => onSubmit(values))}>
|
<form onSubmit={form.onSubmit((values) => onSubmit(values))}>
|
||||||
|
|
||||||
<Flex direction={'column'} gap={10}>
|
<Flex direction={'column'} gap={10}>
|
||||||
<Flex direction={'column'} gap={rem(10)}>
|
<Flex direction={'column'} gap={rem(10)}>
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
required
|
required
|
||||||
label={'Дата и время'}
|
label={'Дата и время'}
|
||||||
placeholder={'Введите дату и время'}
|
placeholder={'Введите дату и время'}
|
||||||
{...form.getInputProps('date')}
|
minDate={new Date()}
|
||||||
|
{...form.getInputProps('deadline')}
|
||||||
/>
|
/>
|
||||||
<Textarea
|
<Textarea
|
||||||
label={'Коментарий'}
|
label={'Коментарий'}
|
||||||
@@ -59,7 +68,9 @@ const EnterDeadlineModal = ({
|
|||||||
variant={'default'}
|
variant={'default'}
|
||||||
onClick={onCancelClick}
|
onClick={onCancelClick}
|
||||||
>Отменить</Button>
|
>Отменить</Button>
|
||||||
<Button>Сохранить</Button>
|
<Button
|
||||||
|
type={'submit'}
|
||||||
|
>Сохранить</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const DealStatusChangeTable: FC<Props> = (props: Props) => {
|
|||||||
enableBottomToolbar: false,
|
enableBottomToolbar: false,
|
||||||
enableColumnFilters: false,
|
enableColumnFilters: false,
|
||||||
enableColumnVisibilityToggle: false,
|
enableColumnVisibilityToggle: false,
|
||||||
|
layoutMode:"grid"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {useMemo} from "react";
|
|||||||
import {MRT_ColumnDef} from "mantine-react-table";
|
import {MRT_ColumnDef} from "mantine-react-table";
|
||||||
import {DealStatusHistorySchema} from "../../../../client";
|
import {DealStatusHistorySchema} from "../../../../client";
|
||||||
import {DealStatus, DealStatusDictionary} from "../../../../shared/enums/DealStatus.ts";
|
import {DealStatus, DealStatusDictionary} from "../../../../shared/enums/DealStatus.ts";
|
||||||
|
import {Spoiler, Text} from "@mantine/core";
|
||||||
|
|
||||||
export const useDealStatusChangeTableColumns = () => {
|
export const useDealStatusChangeTableColumns = () => {
|
||||||
return useMemo<MRT_ColumnDef<DealStatusHistorySchema>[]>(() => [
|
return useMemo<MRT_ColumnDef<DealStatusHistorySchema>[]>(() => [
|
||||||
@@ -23,6 +24,17 @@ export const useDealStatusChangeTableColumns = () => {
|
|||||||
accessorKey: "toStatus",
|
accessorKey: "toStatus",
|
||||||
header: "В статус",
|
header: "В статус",
|
||||||
accessorFn: (row) => DealStatusDictionary[row.toStatus as DealStatus],
|
accessorFn: (row) => DealStatusDictionary[row.toStatus as DealStatus],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "comment",
|
||||||
|
header: "Комментарий",
|
||||||
|
Cell: ({row}) =>
|
||||||
|
<Spoiler onDoubleClick={()=>{console.log("double click")}} maxHeight={80} showLabel={"Показать весь"} hideLabel={"Скрыть"}>
|
||||||
|
<Text style={{wordWrap: "break-word", wordBreak: "break-all", whiteSpace: "normal"}} span>
|
||||||
|
{row.original.comment}<br/>
|
||||||
|
</Text>
|
||||||
|
</Spoiler>
|
||||||
|
,
|
||||||
|
|
||||||
}
|
}
|
||||||
], []);
|
], []);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {DealService, DealSummary} from "../../../client";
|
import {DealService} from "../../../client";
|
||||||
|
|
||||||
export const useDealSummaries = (): DealSummary[] => {
|
export const useDealSummaries = () => {
|
||||||
const {data: summaries = []} = useQuery({
|
const {data: summariesRaw = [], refetch} = useQuery({
|
||||||
queryKey: ['getDealSummaries'],
|
queryKey: ['getDealSummaries'],
|
||||||
queryFn: DealService.getDealSummaries,
|
queryFn: DealService.getDealSummaries,
|
||||||
select: data => data.summaries || [] // Трансформируем полученные данные
|
select: data => data.summaries || [] // Трансформируем полученные данные
|
||||||
@@ -11,5 +11,5 @@ export const useDealSummaries = (): DealSummary[] => {
|
|||||||
// Теперь summaries будет содержать либо трансформированные данные, либо пустой массив по умолчанию
|
// Теперь summaries будет содержать либо трансформированные данные, либо пустой массив по умолчанию
|
||||||
// isLoading и isError могут быть использованы для отображения индикаторов загрузки или ошибки
|
// isLoading и isError могут быть использованы для отображения индикаторов загрузки или ошибки
|
||||||
|
|
||||||
return summaries;
|
return {summariesRaw, refetch};
|
||||||
}
|
}
|
||||||
@@ -20,3 +20,22 @@
|
|||||||
padding-right: 5%;
|
padding-right: 5%;
|
||||||
padding-left: 5%;
|
padding-left: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
border-color: var(--mantine-color-gray-1);
|
||||||
|
}
|
||||||
|
@mixin dark {
|
||||||
|
border-color: var(--mantine-color-dark-5);
|
||||||
|
}
|
||||||
|
border: dashed var(--item-border-size) var(--mantine-color-default-border);
|
||||||
|
border-radius: var(--item-border-radius);
|
||||||
|
padding: rem(30);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-hidden {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,77 @@
|
|||||||
import {FC, useEffect, useState} from "react";
|
import {FC, useEffect, useState} from "react";
|
||||||
import styles from './LeadsPage.module.css';
|
import styles from './LeadsPage.module.css';
|
||||||
import Board from "../../../components/Dnd/Board/Board.tsx";
|
import Board from "../../../components/Dnd/Board/Board.tsx";
|
||||||
import {DragDropContext, DropResult, OnDragEndResponder, ResponderProvided} from "@hello-pangea/dnd";
|
import {DragDropContext, Droppable, DropResult} from "@hello-pangea/dnd";
|
||||||
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
|
import {useDealSummaries} from "../hooks/useDealSummaries.tsx";
|
||||||
import {DealStatus} from "../../../shared/enums/DealStatus.ts";
|
import {DealStatus, getDealStatusByName} from "../../../shared/enums/DealStatus.ts";
|
||||||
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||||
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
|
import DealEditDrawer from "../drawers/DealEditDrawer/DealEditDrawer.tsx";
|
||||||
import {DealPageContextProvider} from "../contexts/DealPageContext.tsx";
|
import {DealPageContextProvider} from "../contexts/DealPageContext.tsx";
|
||||||
import {modals} from "@mantine/modals";
|
import {modals} from "@mantine/modals";
|
||||||
import {DealSummaryReorderRequest} from "../../../client";
|
import {DealService, DealSummaryReorderRequest} from "../../../client";
|
||||||
|
import {Flex} from "@mantine/core";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import {notifications} from "../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
|
||||||
export const LeadsPage: FC = () => {
|
export const LeadsPage: FC = () => {
|
||||||
const summariesRaw = useDealSummaries();
|
const {summariesRaw, refetch} = useDealSummaries();
|
||||||
const [summaries, setSummaries] = useState(summariesRaw);
|
const [summaries, setSummaries] = useState(summariesRaw);
|
||||||
|
const [isDragEnded, setIsDragEnded] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSummaries(summariesRaw);
|
setSummaries(summariesRaw);
|
||||||
}, [summariesRaw]);
|
}, [summariesRaw]);
|
||||||
const onDragEnd = async (result: DropResult, provided: ResponderProvided) => {
|
|
||||||
const dealId = parseInt(result.draggableId);
|
|
||||||
|
|
||||||
const request: DealSummaryReorderRequest = {}
|
const onDelete = (dealId: number) => {
|
||||||
|
const summary = summaries.find(summary => summary.id == dealId);
|
||||||
|
if (!summary) return;
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Удаление сделки",
|
||||||
|
children:
|
||||||
|
<Flex>
|
||||||
|
Вы действительно хотите удалить сделку {summary.name}?
|
||||||
|
</Flex>,
|
||||||
|
onConfirm: () => {
|
||||||
|
DealService.deleteDeal({requestBody: {dealId: dealId}})
|
||||||
|
.then(async ({ok, message}) => {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
if (!ok) return;
|
||||||
|
await refetch();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
confirm: "Удалить",
|
||||||
|
cancel: "Отмена"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const onDragEnd = async (result: DropResult) => {
|
||||||
|
setIsDragEnded(true);
|
||||||
|
if (!result.destination || result.destination == result.source) return;
|
||||||
|
const dealId = parseInt(result.draggableId);
|
||||||
|
if (isNaN(dealId)) return;
|
||||||
|
|
||||||
|
const droppableId = result.destination.droppableId;
|
||||||
|
if (droppableId === 'DELETE') {
|
||||||
|
onDelete(dealId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request: Partial<DealSummaryReorderRequest> = {
|
||||||
|
dealId: dealId,
|
||||||
|
index: result.destination.index,
|
||||||
|
status: getDealStatusByName(droppableId)
|
||||||
|
}
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: 'enterDeadline',
|
modal: 'enterDeadline',
|
||||||
title: "Необходимо указать дедлайн",
|
title: "Необходимо указать дедлайн",
|
||||||
innerProps: {
|
innerProps: {
|
||||||
onSubmit: (event) => console.log(event)
|
onSubmit: (event) => DealService.reorderDealSummaries({requestBody: event})
|
||||||
|
.then(async response => {
|
||||||
|
setSummaries(response.summaries);
|
||||||
|
await refetch();
|
||||||
|
}),
|
||||||
|
request: request
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,43 +80,89 @@ export const LeadsPage: FC = () => {
|
|||||||
<>
|
<>
|
||||||
<DealPageContextProvider>
|
<DealPageContextProvider>
|
||||||
|
|
||||||
<PageBlock>
|
<PageBlock fullHeight>
|
||||||
<div className={styles['container']}>
|
<div className={styles['container']}>
|
||||||
|
<DragDropContext
|
||||||
|
onDragStart={() => {
|
||||||
|
setIsDragEnded(false);
|
||||||
|
}}
|
||||||
|
onDragEnd={onDragEnd}>
|
||||||
<div className={styles['boards']}>
|
<div className={styles['boards']}>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
|
||||||
<Board
|
<Board
|
||||||
withCreateButton
|
withCreateButton
|
||||||
summaries={summaries
|
summaries={summaries
|
||||||
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
|
.filter(summary => summary.status == DealStatus.AWAITING_ACCEPTANCE)}
|
||||||
title={"Ожидает приемки"}
|
title={"Ожидает приемки"}
|
||||||
droppableId={"AWAITING_ACCEPTANCE"}
|
droppableId={"AWAITING_ACCEPTANCE"}
|
||||||
|
color={'#4A90E2'}
|
||||||
/>
|
/>
|
||||||
<Board
|
<Board
|
||||||
summaries={summaries
|
summaries={summaries
|
||||||
.filter(summary => summary.status == DealStatus.PACKAGING)}
|
.filter(summary => summary.status == DealStatus.PACKAGING)}
|
||||||
title={"Упаковка"}
|
title={"Упаковка"}
|
||||||
droppableId={"PACKAGING"}
|
droppableId={"PACKAGING"}
|
||||||
|
color={'#F5A623'}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<Board
|
<Board
|
||||||
summaries={summaries
|
summaries={summaries
|
||||||
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
|
.filter(summary => summary.status == DealStatus.AWAITING_SHIPMENT)}
|
||||||
title={"Ожидает отгрузки"}
|
title={"Ожидает отгрузки"}
|
||||||
droppableId={"AWAITING_SHIPMENT"}
|
droppableId={"AWAITING_SHIPMENT"}
|
||||||
|
color={'#7ED321'}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<Board
|
<Board
|
||||||
summaries={summaries
|
summaries={summaries
|
||||||
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
|
.filter(summary => summary.status == DealStatus.AWAITING_PAYMENT)}
|
||||||
title={"Ожидает оплаты"}
|
title={"Ожидает оплаты"}
|
||||||
droppableId={"AWAITING_PAYMENT"}
|
droppableId={"AWAITING_PAYMENT"}
|
||||||
|
color={'#D0021B'}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<Board
|
<Board
|
||||||
summaries={summaries
|
summaries={summaries
|
||||||
.filter(summary => summary.status == DealStatus.COMPLETED)}
|
.filter(summary => summary.status == DealStatus.COMPLETED)}
|
||||||
title={"Завершена"}
|
title={"Завершена"}
|
||||||
droppableId={"COMPLETED"}
|
droppableId={"COMPLETED"}
|
||||||
|
color={'#417505'}
|
||||||
/>
|
/>
|
||||||
</DragDropContext>
|
|
||||||
</div>
|
</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}
|
||||||
|
</>
|
||||||
|
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</Flex>
|
||||||
|
</DragDropContext>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
<DealEditDrawer
|
<DealEditDrawer
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const LoginPage = () => {
|
|||||||
const authState = useSelector((state: RootState) => state.auth);
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
if (authState.isAuthorized) {
|
if (authState.isAuthorized) {
|
||||||
navigate({to: "/"})
|
navigate({to: "/leads"})
|
||||||
return (<></>)
|
return (<></>)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import {FC} from "react";
|
import {FC} from "react";
|
||||||
|
import {useNavigate} from "@tanstack/react-router";
|
||||||
|
|
||||||
const MainPage: FC = () => {
|
const MainPage: FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
navigate({to: '/leads'});
|
||||||
return (<>
|
return (<>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -9,5 +9,4 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: rem(20);
|
padding: rem(20);
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ const PageWrapper: FC<Props> = ({children}) => {
|
|||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
layout={"alt"}
|
layout={"alt"}
|
||||||
navbar={{
|
navbar={authState.isAuthorized ? {
|
||||||
width: '5%',
|
width: '5%',
|
||||||
breakpoint: "sm"
|
breakpoint: "sm"
|
||||||
}}
|
} : undefined}
|
||||||
>
|
>
|
||||||
|
|
||||||
<AppShell.Navbar>
|
<AppShell.Navbar>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
|
|
||||||
.body-container {
|
.body-container {
|
||||||
padding: rem(5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-panel {
|
.top-panel {
|
||||||
|
|||||||
@@ -10,3 +10,18 @@ export const getDigitsCount = (num: number): number => {
|
|||||||
if (num === 0) return 1;
|
if (num === 0) return 1;
|
||||||
return Math.floor(Math.log10(Math.abs(num))) + 1;
|
return Math.floor(Math.log10(Math.abs(num))) + 1;
|
||||||
};
|
};
|
||||||
|
export const getPluralForm = (amount: number, one: string, twoFour: string, many: string): string => {
|
||||||
|
if (amount % 100 >= 11 && amount % 100 <= 14) {
|
||||||
|
return many;
|
||||||
|
}
|
||||||
|
switch (amount % 10) {
|
||||||
|
case 1:
|
||||||
|
return one;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
return twoFour;
|
||||||
|
default:
|
||||||
|
return many;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user