feat: deal status history table

This commit is contained in:
2025-09-20 10:06:33 +04:00
parent 30e0de5c5e
commit 6e445d5ebf
8 changed files with 250 additions and 20 deletions

View File

@ -1,7 +1,8 @@
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 { 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 useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema, ProjectSchema } from "@/lib/client";
@ -35,22 +36,24 @@ const DealEditorBody: FC<Props> = props => {
</Tabs.Panel>
);
const getModuleTabs = () =>
props.project?.builtInModules.map(module => {
const moduleRender = MODULES[module.key].renderInfo;
return (
const getTab = (key: string, label: string, icon: ReactNode) => (
<Tabs.Tab
key={moduleRender.key}
value={moduleRender.key}
leftSection={moduleRender.icon}>
key={key}
value={key}
leftSection={icon}>
<Box
style={{
justifyItems: "left",
}}>
<Text>{moduleRender.label}</Text>
<Text>{label}</Text>
</Box>
</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 = () =>
@ -66,15 +69,13 @@ const DealEditorBody: FC<Props> = props => {
mih={"97vh"}
classNames={{ tab: styles.tab }}>
<Tabs.List>
<Tabs.Tab
value="general"
leftSection={<IconEdit />}>
Общая информация
</Tabs.Tab>
{getTab("general", "Общая информация", <IconEdit />)}
{getTab("history", "История", <IconHistory />)}
{getModuleTabs()}
</Tabs.List>
{getTabPanel("general", <GeneralTab {...props} />)}
{getTabPanel("history", <DealStatusHistoryTab {...props} />)}
{getModuleTabPanels()}
</Tabs>
);

View File

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

View File

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

View File

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

View File

@ -42,6 +42,7 @@ import {
getServices,
getServicesKits,
getStatuses,
getStatusHistory,
updateBoard,
updateDeal,
updateDealProduct,
@ -138,6 +139,7 @@ import type {
GetServicesData,
GetServicesKitsData,
GetStatusesData,
GetStatusHistoryData,
UpdateBoardData,
UpdateBoardError,
UpdateBoardResponse2,
@ -807,6 +809,30 @@ export const updateStatusMutation = (
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 = (
options: Options<GetDealProductsData>
) => createQueryKey("getDealProducts", options);

View File

@ -98,6 +98,9 @@ import type {
GetStatusesData,
GetStatusesErrors,
GetStatusesResponses,
GetStatusHistoryData,
GetStatusHistoryErrors,
GetStatusHistoryResponses,
UpdateBoardData,
UpdateBoardErrors,
UpdateBoardResponses,
@ -196,6 +199,8 @@ import {
zGetServicesResponse2,
zGetStatusesData,
zGetStatusesResponse2,
zGetStatusHistoryData,
zGetStatusHistoryResponse2,
zUpdateBoardData,
zUpdateBoardResponse2,
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
*/

View File

@ -808,6 +808,16 @@ export type GetServicesResponse = {
items: Array<ServiceSchema>;
};
/**
* GetStatusHistoryResponse
*/
export type GetStatusHistoryResponse = {
/**
* Items
*/
items: Array<StatusHistorySchema>;
};
/**
* GetStatusesResponse
*/
@ -1078,6 +1088,26 @@ export type ServicesKitSchema = {
export type SortDir = "asc" | "desc";
/**
* StatusHistorySchema
*/
export type StatusHistorySchema = {
/**
* Id
*/
id: number;
/**
* Createdat
*/
createdAt: string;
fromStatus: StatusSchema;
toStatus: StatusSchema;
/**
* Dealid
*/
dealId: number;
};
/**
* StatusSchema
*/
@ -1998,6 +2028,38 @@ export type UpdateStatusResponses = {
export type UpdateStatusResponse2 =
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 = {
body?: never;
path: {

View File

@ -600,6 +600,26 @@ export const zGetServicesResponse = z.object({
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
*/
@ -1106,6 +1126,19 @@ export const zUpdateStatusData = z.object({
*/
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({
body: z.optional(z.never()),
path: z.object({