feat: deal status history table
This commit is contained in:
@ -1,7 +1,8 @@
|
|||||||
import React, { FC, ReactNode } from "react";
|
import React, { FC, ReactNode } from "react";
|
||||||
import { IconEdit } from "@tabler/icons-react";
|
import { IconEdit, IconHistory } from "@tabler/icons-react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Box, Tabs, Text } from "@mantine/core";
|
import { Box, Tabs, Text } from "@mantine/core";
|
||||||
|
import DealStatusHistoryTab from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/DealStatusHistoryTab";
|
||||||
import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab";
|
import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab";
|
||||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||||
import { DealSchema, ProjectSchema } from "@/lib/client";
|
import { DealSchema, ProjectSchema } from "@/lib/client";
|
||||||
@ -35,22 +36,24 @@ const DealEditorBody: FC<Props> = props => {
|
|||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
);
|
);
|
||||||
|
|
||||||
const getModuleTabs = () =>
|
const getTab = (key: string, label: string, icon: ReactNode) => (
|
||||||
props.project?.builtInModules.map(module => {
|
|
||||||
const moduleRender = MODULES[module.key].renderInfo;
|
|
||||||
return (
|
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
key={moduleRender.key}
|
key={key}
|
||||||
value={moduleRender.key}
|
value={key}
|
||||||
leftSection={moduleRender.icon}>
|
leftSection={icon}>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
justifyItems: "left",
|
justifyItems: "left",
|
||||||
}}>
|
}}>
|
||||||
<Text>{moduleRender.label}</Text>
|
<Text>{label}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getModuleTabs = () =>
|
||||||
|
props.project?.builtInModules.map(module => {
|
||||||
|
const info = MODULES[module.key].renderInfo;
|
||||||
|
return getTab(info.key, info.label, info.icon);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getModuleTabPanels = () =>
|
const getModuleTabPanels = () =>
|
||||||
@ -66,15 +69,13 @@ const DealEditorBody: FC<Props> = props => {
|
|||||||
mih={"97vh"}
|
mih={"97vh"}
|
||||||
classNames={{ tab: styles.tab }}>
|
classNames={{ tab: styles.tab }}>
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab
|
{getTab("general", "Общая информация", <IconEdit />)}
|
||||||
value="general"
|
{getTab("history", "История", <IconHistory />)}
|
||||||
leftSection={<IconEdit />}>
|
|
||||||
Общая информация
|
|
||||||
</Tabs.Tab>
|
|
||||||
{getModuleTabs()}
|
{getModuleTabs()}
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
||||||
{getTabPanel("general", <GeneralTab {...props} />)}
|
{getTabPanel("general", <GeneralTab {...props} />)}
|
||||||
|
{getTabPanel("history", <DealStatusHistoryTab {...props} />)}
|
||||||
{getModuleTabPanels()}
|
{getModuleTabPanels()}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { useDealStatusHistoryTableColumns } from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/columns";
|
||||||
|
import useStatusHistoryList from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/useStatusHistoryList";
|
||||||
|
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||||
|
import { DealSchema } from "@/lib/client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: DealSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DealStatusHistoryTab: FC<Props> = ({ value }) => {
|
||||||
|
const { history } = useStatusHistoryList({ dealId: value.id });
|
||||||
|
const columns = useDealStatusHistoryTableColumns();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseTable
|
||||||
|
records={history}
|
||||||
|
columns={columns}
|
||||||
|
groups={undefined}
|
||||||
|
withTableBorder
|
||||||
|
style={{
|
||||||
|
margin: "var(--mantine-spacing-md)",
|
||||||
|
}}
|
||||||
|
verticalSpacing={"md"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DealStatusHistoryTab;
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { DataTableColumn } from "mantine-datatable";
|
||||||
|
import { StatusHistorySchema } from "@/lib/client";
|
||||||
|
import { utcDateTimeToLocalString } from "@/utils/datetime";
|
||||||
|
|
||||||
|
export const useDealStatusHistoryTableColumns = () => {
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
accessor: "createdAt",
|
||||||
|
title: "Дата",
|
||||||
|
render: row =>
|
||||||
|
utcDateTimeToLocalString(new Date(row.createdAt)),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Пользователь",
|
||||||
|
// cell: row => `${row.user.firstName} ${row.user.secondName}`,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
accessor: "fromStatus.name",
|
||||||
|
title: "Из статуса",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "toStatus.name",
|
||||||
|
title: "В статус",
|
||||||
|
},
|
||||||
|
] as DataTableColumn<StatusHistorySchema>[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getStatusHistoryOptions } from "@/lib/client/@tanstack/react-query.gen";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dealId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStatusHistoryList = ({ dealId }: Props) => {
|
||||||
|
const options = {
|
||||||
|
path: { dealId },
|
||||||
|
};
|
||||||
|
const { data, refetch } = useQuery(getStatusHistoryOptions(options));
|
||||||
|
|
||||||
|
return {
|
||||||
|
history: data?.items ?? [],
|
||||||
|
refetch,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useStatusHistoryList;
|
||||||
@ -42,6 +42,7 @@ import {
|
|||||||
getServices,
|
getServices,
|
||||||
getServicesKits,
|
getServicesKits,
|
||||||
getStatuses,
|
getStatuses,
|
||||||
|
getStatusHistory,
|
||||||
updateBoard,
|
updateBoard,
|
||||||
updateDeal,
|
updateDeal,
|
||||||
updateDealProduct,
|
updateDealProduct,
|
||||||
@ -138,6 +139,7 @@ import type {
|
|||||||
GetServicesData,
|
GetServicesData,
|
||||||
GetServicesKitsData,
|
GetServicesKitsData,
|
||||||
GetStatusesData,
|
GetStatusesData,
|
||||||
|
GetStatusHistoryData,
|
||||||
UpdateBoardData,
|
UpdateBoardData,
|
||||||
UpdateBoardError,
|
UpdateBoardError,
|
||||||
UpdateBoardResponse2,
|
UpdateBoardResponse2,
|
||||||
@ -807,6 +809,30 @@ export const updateStatusMutation = (
|
|||||||
return mutationOptions;
|
return mutationOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getStatusHistoryQueryKey = (
|
||||||
|
options: Options<GetStatusHistoryData>
|
||||||
|
) => createQueryKey("getStatusHistory", options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Status History
|
||||||
|
*/
|
||||||
|
export const getStatusHistoryOptions = (
|
||||||
|
options: Options<GetStatusHistoryData>
|
||||||
|
) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: async ({ queryKey, signal }) => {
|
||||||
|
const { data } = await getStatusHistory({
|
||||||
|
...options,
|
||||||
|
...queryKey[0],
|
||||||
|
signal,
|
||||||
|
throwOnError: true,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
queryKey: getStatusHistoryQueryKey(options),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const getDealProductsQueryKey = (
|
export const getDealProductsQueryKey = (
|
||||||
options: Options<GetDealProductsData>
|
options: Options<GetDealProductsData>
|
||||||
) => createQueryKey("getDealProducts", options);
|
) => createQueryKey("getDealProducts", options);
|
||||||
|
|||||||
@ -98,6 +98,9 @@ import type {
|
|||||||
GetStatusesData,
|
GetStatusesData,
|
||||||
GetStatusesErrors,
|
GetStatusesErrors,
|
||||||
GetStatusesResponses,
|
GetStatusesResponses,
|
||||||
|
GetStatusHistoryData,
|
||||||
|
GetStatusHistoryErrors,
|
||||||
|
GetStatusHistoryResponses,
|
||||||
UpdateBoardData,
|
UpdateBoardData,
|
||||||
UpdateBoardErrors,
|
UpdateBoardErrors,
|
||||||
UpdateBoardResponses,
|
UpdateBoardResponses,
|
||||||
@ -196,6 +199,8 @@ import {
|
|||||||
zGetServicesResponse2,
|
zGetServicesResponse2,
|
||||||
zGetStatusesData,
|
zGetStatusesData,
|
||||||
zGetStatusesResponse2,
|
zGetStatusesResponse2,
|
||||||
|
zGetStatusHistoryData,
|
||||||
|
zGetStatusHistoryResponse2,
|
||||||
zUpdateBoardData,
|
zUpdateBoardData,
|
||||||
zUpdateBoardResponse2,
|
zUpdateBoardResponse2,
|
||||||
zUpdateDealData,
|
zUpdateDealData,
|
||||||
@ -658,6 +663,29 @@ export const updateStatus = <ThrowOnError extends boolean = false>(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Status History
|
||||||
|
*/
|
||||||
|
export const getStatusHistory = <ThrowOnError extends boolean = false>(
|
||||||
|
options: Options<GetStatusHistoryData, ThrowOnError>
|
||||||
|
) => {
|
||||||
|
return (options.client ?? _heyApiClient).get<
|
||||||
|
GetStatusHistoryResponses,
|
||||||
|
GetStatusHistoryErrors,
|
||||||
|
ThrowOnError
|
||||||
|
>({
|
||||||
|
requestValidator: async data => {
|
||||||
|
return await zGetStatusHistoryData.parseAsync(data);
|
||||||
|
},
|
||||||
|
responseType: "json",
|
||||||
|
responseValidator: async data => {
|
||||||
|
return await zGetStatusHistoryResponse2.parseAsync(data);
|
||||||
|
},
|
||||||
|
url: "/status/history/{dealId}",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Deal Products
|
* Get Deal Products
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -808,6 +808,16 @@ export type GetServicesResponse = {
|
|||||||
items: Array<ServiceSchema>;
|
items: Array<ServiceSchema>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetStatusHistoryResponse
|
||||||
|
*/
|
||||||
|
export type GetStatusHistoryResponse = {
|
||||||
|
/**
|
||||||
|
* Items
|
||||||
|
*/
|
||||||
|
items: Array<StatusHistorySchema>;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetStatusesResponse
|
* GetStatusesResponse
|
||||||
*/
|
*/
|
||||||
@ -1078,6 +1088,26 @@ export type ServicesKitSchema = {
|
|||||||
|
|
||||||
export type SortDir = "asc" | "desc";
|
export type SortDir = "asc" | "desc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StatusHistorySchema
|
||||||
|
*/
|
||||||
|
export type StatusHistorySchema = {
|
||||||
|
/**
|
||||||
|
* Id
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* Createdat
|
||||||
|
*/
|
||||||
|
createdAt: string;
|
||||||
|
fromStatus: StatusSchema;
|
||||||
|
toStatus: StatusSchema;
|
||||||
|
/**
|
||||||
|
* Dealid
|
||||||
|
*/
|
||||||
|
dealId: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StatusSchema
|
* StatusSchema
|
||||||
*/
|
*/
|
||||||
@ -1998,6 +2028,38 @@ export type UpdateStatusResponses = {
|
|||||||
export type UpdateStatusResponse2 =
|
export type UpdateStatusResponse2 =
|
||||||
UpdateStatusResponses[keyof UpdateStatusResponses];
|
UpdateStatusResponses[keyof UpdateStatusResponses];
|
||||||
|
|
||||||
|
export type GetStatusHistoryData = {
|
||||||
|
body?: never;
|
||||||
|
path: {
|
||||||
|
/**
|
||||||
|
* Dealid
|
||||||
|
*/
|
||||||
|
dealId: number;
|
||||||
|
};
|
||||||
|
query?: never;
|
||||||
|
url: "/status/history/{dealId}";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetStatusHistoryErrors = {
|
||||||
|
/**
|
||||||
|
* Validation Error
|
||||||
|
*/
|
||||||
|
422: HttpValidationError;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetStatusHistoryError =
|
||||||
|
GetStatusHistoryErrors[keyof GetStatusHistoryErrors];
|
||||||
|
|
||||||
|
export type GetStatusHistoryResponses = {
|
||||||
|
/**
|
||||||
|
* Successful Response
|
||||||
|
*/
|
||||||
|
200: GetStatusHistoryResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetStatusHistoryResponse2 =
|
||||||
|
GetStatusHistoryResponses[keyof GetStatusHistoryResponses];
|
||||||
|
|
||||||
export type GetDealProductsData = {
|
export type GetDealProductsData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path: {
|
path: {
|
||||||
|
|||||||
@ -600,6 +600,26 @@ export const zGetServicesResponse = z.object({
|
|||||||
items: z.array(zServiceSchema),
|
items: z.array(zServiceSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StatusHistorySchema
|
||||||
|
*/
|
||||||
|
export const zStatusHistorySchema = z.object({
|
||||||
|
id: z.int(),
|
||||||
|
createdAt: z.iso.datetime({
|
||||||
|
offset: true,
|
||||||
|
}),
|
||||||
|
fromStatus: zStatusSchema,
|
||||||
|
toStatus: zStatusSchema,
|
||||||
|
dealId: z.int(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetStatusHistoryResponse
|
||||||
|
*/
|
||||||
|
export const zGetStatusHistoryResponse = z.object({
|
||||||
|
items: z.array(zStatusHistorySchema),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetStatusesResponse
|
* GetStatusesResponse
|
||||||
*/
|
*/
|
||||||
@ -1106,6 +1126,19 @@ export const zUpdateStatusData = z.object({
|
|||||||
*/
|
*/
|
||||||
export const zUpdateStatusResponse2 = zUpdateStatusResponse;
|
export const zUpdateStatusResponse2 = zUpdateStatusResponse;
|
||||||
|
|
||||||
|
export const zGetStatusHistoryData = z.object({
|
||||||
|
body: z.optional(z.never()),
|
||||||
|
path: z.object({
|
||||||
|
dealId: z.int(),
|
||||||
|
}),
|
||||||
|
query: z.optional(z.never()),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Successful Response
|
||||||
|
*/
|
||||||
|
export const zGetStatusHistoryResponse2 = zGetStatusHistoryResponse;
|
||||||
|
|
||||||
export const zGetDealProductsData = z.object({
|
export const zGetDealProductsData = z.object({
|
||||||
body: z.optional(z.never()),
|
body: z.optional(z.never()),
|
||||||
path: z.object({
|
path: z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user