feat: tags page for mobiles

This commit is contained in:
2025-10-19 20:47:53 +04:00
parent 3a1d8e23e3
commit e44691d118
12 changed files with 222 additions and 10 deletions

View File

@ -1,12 +1,14 @@
"use client"; "use client";
import { useMemo } from "react"; import { RefObject, useMemo, useRef } from "react";
import { IconTag } from "@tabler/icons-react";
import { SimpleGrid, Stack } from "@mantine/core"; import { SimpleGrid, Stack } from "@mantine/core";
import Action from "@/app/actions/components/Action/Action"; import Action from "@/app/actions/components/Action/Action";
import mobileButtonsData from "@/app/actions/data/mobileButtonsData"; import mobileButtonsData from "@/app/actions/data/mobileButtonsData";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import PageBlock from "@/components/layout/PageBlock/PageBlock"; import PageBlock from "@/components/layout/PageBlock/PageBlock";
import ProjectSelect from "@/components/selects/ProjectSelect/ProjectSelect"; import ProjectSelect from "@/components/selects/ProjectSelect/ProjectSelect";
import BuiltInLinkData from "@/types/BuiltInLinkData";
const PageBody = () => { const PageBody = () => {
const { selectedProject, setSelectedProjectId, projects, modulesSet } = const { selectedProject, setSelectedProjectId, projects, modulesSet } =
@ -20,6 +22,14 @@ const PageBody = () => {
[modulesSet] [modulesSet]
); );
const commonActionsData: RefObject<BuiltInLinkData[]> = useRef([
{
icon: IconTag,
label: "Теги",
href: "/tags",
},
]);
return ( return (
<PageBlock fullScreenMobile> <PageBlock fullScreenMobile>
<Stack p={"xs"}> <Stack p={"xs"}>
@ -33,7 +43,10 @@ const PageBody = () => {
<SimpleGrid <SimpleGrid
type={"container"} type={"container"}
cols={2}> cols={2}>
{filteredMobileButtonsData.map((data, index) => ( {[
...commonActionsData.current,
...filteredMobileButtonsData,
].map((data, index) => (
<Action <Action
linkData={data} linkData={data}
key={index} key={index}

View File

@ -33,8 +33,6 @@ const useProjectsContextState = (): ProjectsContextState => {
[projects, selectedProjectId] [projects, selectedProjectId]
); );
console.log(selectedProject);
const modulesSet = useMemo( const modulesSet = useMemo(
() => () =>
new Set( new Set(

View File

@ -0,0 +1,31 @@
"use client";
import { Center, Divider, Stack, Text } from "@mantine/core";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import TagsPageHeader from "@/app/tags/components/TagsPageHeader/TagsPageHeader";
import TagsTable from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/components/TagsTable";
import { DealTagsContextProvider } from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/contexts/DealTagsContext";
const PageBody = () => {
const { selectedProject } = useProjectsContext();
if (!selectedProject) {
return (
<Center>
<Text>Проект не найден</Text>
</Center>
);
}
return (
<DealTagsContextProvider project={selectedProject}>
<Stack gap={"md"}>
<TagsPageHeader project={selectedProject} />
<Divider />
<TagsTable />
</Stack>
</DealTagsContextProvider>
);
};
export default PageBody;

View File

@ -0,0 +1,32 @@
import { FC } from "react";
import Link from "next/link";
import { IconChevronLeft, IconPlus } from "@tabler/icons-react";
import { Box, Group, Title } from "@mantine/core";
import useDealTagActions from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/hooks/useDealTagActions";
import { ProjectSchema } from "@/lib/client";
type Props = {
project: ProjectSchema;
};
const TagsPageHeader: FC<Props> = ({ project }) => {
const { onCreateClick } = useDealTagActions();
return (
<Group
mx={"xs"}
mt={"md"}
wrap={"nowrap"}
justify={"space-between"}>
<Link href={"/actions"}>
<IconChevronLeft />
</Link>
<Title order={5}>Теги проекта "{project.name}"</Title>
<Box onClick={onCreateClick}>
<IconPlus />
</Box>
</Group>
);
};
export default TagsPageHeader;

25
src/app/tags/page.tsx Normal file
View File

@ -0,0 +1,25 @@
import { Suspense } from "react";
import { Center, Loader } from "@mantine/core";
import PageBody from "@/app/tags/components/PageBody/PageBody";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
import PageContainer from "@/components/layout/PageContainer/PageContainer";
/*
* Page for mobiles only
*/
export default async function TagsPage() {
return (
<Suspense
fallback={
<Center h="50vh">
<Loader size="lg" />
</Center>
}>
<PageContainer>
<PageBlock fullScreenMobile>
<PageBody />
</PageBlock>
</PageContainer>
</Suspense>
);
}

View File

@ -1,4 +1,4 @@
"use dealTag"; "use client";
import { DealTagsCrud, useDealTagsCrud } from "@/hooks/cruds/useDealTagsCrud"; import { DealTagsCrud, useDealTagsCrud } from "@/hooks/cruds/useDealTagsCrud";
import useDealTagsList from "@/hooks/lists/useDealTagsList"; import useDealTagsList from "@/hooks/lists/useDealTagsList";
@ -8,7 +8,6 @@ import makeContext from "@/lib/contextFactory/contextFactory";
type DealTagsContextState = { type DealTagsContextState = {
dealTags: DealTagSchema[]; dealTags: DealTagSchema[];
refetchDealTags: () => void; refetchDealTags: () => void;
project: ProjectSchema;
dealTagsCrud: DealTagsCrud; dealTagsCrud: DealTagsCrud;
}; };
@ -27,7 +26,6 @@ const useDealTagsContextState = ({ project }: Props): DealTagsContextState => {
return { return {
dealTags: dealTagsList.dealTags, dealTags: dealTagsList.dealTags,
refetchDealTags: dealTagsList.refetch, refetchDealTags: dealTagsList.refetch,
project,
dealTagsCrud, dealTagsCrud,
}; };
}; };

View File

@ -28,17 +28,14 @@ const useTagsTableColumns = ({ onDelete, onChange }: Props) => {
{ {
title: "Название", title: "Название",
accessor: "name", accessor: "name",
width: 2,
}, },
{ {
title: "Цвет", title: "Цвет",
accessor: "tagColor.label", accessor: "tagColor.label",
width: 2,
}, },
{ {
title: "Пример", title: "Пример",
accessor: "tagColor", accessor: "tagColor",
width: 3,
render: tag => <DealTag tag={tag} />, render: tag => <DealTag tag={tag} />,
}, },
] as DataTableColumn<DealTagSchema>[], ] as DataTableColumn<DealTagSchema>[],

View File

@ -60,6 +60,7 @@ import {
getMarketplaces, getMarketplaces,
getProductBarcodePdf, getProductBarcodePdf,
getProducts, getProducts,
getProject,
getProjects, getProjects,
getServiceCategories, getServiceCategories,
getServices, getServices,
@ -214,6 +215,7 @@ import type {
GetProductsData, GetProductsData,
GetProductsError, GetProductsError,
GetProductsResponse2, GetProductsResponse2,
GetProjectData,
GetProjectsData, GetProjectsData,
GetServiceCategoriesData, GetServiceCategoriesData,
GetServicesData, GetServicesData,
@ -2714,6 +2716,27 @@ export const deleteProjectMutation = (
return mutationOptions; return mutationOptions;
}; };
export const getProjectQueryKey = (options: Options<GetProjectData>) =>
createQueryKey("getProject", options);
/**
* Get Project
*/
export const getProjectOptions = (options: Options<GetProjectData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getProject({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getProjectQueryKey(options),
});
};
/** /**
* Update Project * Update Project
*/ */

View File

@ -147,6 +147,9 @@ import type {
GetProductsData, GetProductsData,
GetProductsErrors, GetProductsErrors,
GetProductsResponses, GetProductsResponses,
GetProjectData,
GetProjectErrors,
GetProjectResponses,
GetProjectsData, GetProjectsData,
GetProjectsResponses, GetProjectsResponses,
GetServiceCategoriesData, GetServiceCategoriesData,
@ -317,6 +320,8 @@ import {
zGetProductBarcodePdfResponse2, zGetProductBarcodePdfResponse2,
zGetProductsData, zGetProductsData,
zGetProductsResponse2, zGetProductsResponse2,
zGetProjectData,
zGetProjectResponse2,
zGetProjectsData, zGetProjectsData,
zGetProjectsResponse2, zGetProjectsResponse2,
zGetServiceCategoriesData, zGetServiceCategoriesData,
@ -2090,6 +2095,29 @@ export const deleteProject = <ThrowOnError extends boolean = false>(
}); });
}; };
/**
* Get Project
*/
export const getProject = <ThrowOnError extends boolean = false>(
options: Options<GetProjectData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).get<
GetProjectResponses,
GetProjectErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zGetProjectData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetProjectResponse2.parseAsync(data);
},
url: "/crm/v1/project/{pk}",
...options,
});
};
/** /**
* Update Project * Update Project
*/ */

View File

@ -1400,6 +1400,13 @@ export type GetProductsResponse = {
paginationInfo: PaginationInfoSchema; paginationInfo: PaginationInfoSchema;
}; };
/**
* GetProjectResponse
*/
export type GetProjectResponse = {
entity: ProjectSchema;
};
/** /**
* GetProjectsResponse * GetProjectsResponse
*/ */
@ -4451,6 +4458,37 @@ export type DeleteProjectResponses = {
export type DeleteProjectResponse2 = export type DeleteProjectResponse2 =
DeleteProjectResponses[keyof DeleteProjectResponses]; DeleteProjectResponses[keyof DeleteProjectResponses];
export type GetProjectData = {
body?: never;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/project/{pk}";
};
export type GetProjectErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type GetProjectError = GetProjectErrors[keyof GetProjectErrors];
export type GetProjectResponses = {
/**
* Successful Response
*/
200: GetProjectResponse;
};
export type GetProjectResponse2 =
GetProjectResponses[keyof GetProjectResponses];
export type UpdateProjectData = { export type UpdateProjectData = {
body: UpdateProjectRequest; body: UpdateProjectRequest;
path: { path: {

View File

@ -957,6 +957,13 @@ export const zGetProductsResponse = z.object({
paginationInfo: zPaginationInfoSchema, paginationInfo: zPaginationInfoSchema,
}); });
/**
* GetProjectResponse
*/
export const zGetProjectResponse = z.object({
entity: zProjectSchema,
});
/** /**
* GetProjectsResponse * GetProjectsResponse
*/ */
@ -2329,6 +2336,19 @@ export const zDeleteProjectData = z.object({
*/ */
export const zDeleteProjectResponse2 = zDeleteProjectResponse; export const zDeleteProjectResponse2 = zDeleteProjectResponse;
export const zGetProjectData = z.object({
body: z.optional(z.never()),
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetProjectResponse2 = zGetProjectResponse;
export const zUpdateProjectData = z.object({ export const zUpdateProjectData = z.object({
body: zUpdateProjectRequest, body: zUpdateProjectRequest,
path: z.object({ path: z.object({

View File

@ -0,0 +1,9 @@
import { IconPlus } from "@tabler/icons-react";
type BuiltInLinkData = {
icon: typeof IconPlus;
label: string;
href: string;
};
export default BuiltInLinkData;