This commit is contained in:
2024-04-28 04:55:19 +03:00
parent c4e106576e
commit d0a32b938c
25 changed files with 274 additions and 58 deletions

View File

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

View 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;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ const DealStatusChangeTable: FC<Props> = (props: Props) => {
enableBottomToolbar: false, enableBottomToolbar: false,
enableColumnFilters: false, enableColumnFilters: false,
enableColumnVisibilityToggle: false, enableColumnVisibilityToggle: false,
layoutMode:"grid"
}} }}
/> />
); );

View File

@@ -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>
,
} }
], []); ], []);

View File

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

View File

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

View File

@@ -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 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); const dealId = parseInt(result.draggableId);
if (isNaN(dealId)) return;
const request: DealSummaryReorderRequest = {}
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']}>
<div className={styles['boards']}> <DragDropContext
<DragDropContext onDragEnd={onDragEnd}> onDragStart={() => {
setIsDragEnded(false);
}}
onDragEnd={onDragEnd}>
<div className={styles['boards']}>
<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

View File

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

View File

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

View File

@@ -9,5 +9,4 @@
.container { .container {
padding: rem(20); padding: rem(20);
display: flex;
} }

View File

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

View File

@@ -7,7 +7,6 @@
.body-container { .body-container {
padding: rem(5);
} }
.top-panel { .top-panel {

View File

@@ -9,4 +9,19 @@ export const dateWithoutTimezone = (date: Date) => {
export const getDigitsCount = (num: number): number => { 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;
}
}; };