feat: attributes page
This commit is contained in:
37
src/app/attributes/components/AttributesHeader.tsx
Normal file
37
src/app/attributes/components/AttributesHeader.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Group, TextInput } from "@mantine/core";
|
||||
import { useAttributesContext } from "@/app/attributes/contexts/AttributesContext";
|
||||
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||
|
||||
const AttributesHeader: FC = () => {
|
||||
const {
|
||||
attributesActions: { onCreate },
|
||||
search,
|
||||
setSearch,
|
||||
} = useAttributesContext();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Group
|
||||
wrap={"nowrap"}
|
||||
mt={isMobile ? "xs" : ""}
|
||||
mx={isMobile ? "xs" : ""}>
|
||||
<InlineButton
|
||||
onClick={onCreate}
|
||||
w={isMobile ? "100%" : "auto"}>
|
||||
Создать атрибут
|
||||
</InlineButton>
|
||||
<TextInput
|
||||
value={search}
|
||||
onChange={e => setSearch(e.currentTarget.value)}
|
||||
w={isMobile ? "100%" : "auto"}
|
||||
placeholder={"Поиск..."}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributesHeader;
|
||||
40
src/app/attributes/components/AttributesTable.tsx
Normal file
40
src/app/attributes/components/AttributesTable.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { IconMoodSad } from "@tabler/icons-react";
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { useAttributesContext } from "@/app/attributes/contexts/AttributesContext";
|
||||
import useAttributesTableColumns from "@/app/attributes/hooks/useAttributesTableColumns";
|
||||
import BaseTable from "@/components/ui/BaseTable/BaseTable";
|
||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||
|
||||
const AttributesTable: FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { attributes } = useAttributesContext();
|
||||
const columns = useAttributesTableColumns();
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
withTableBorder
|
||||
columns={columns}
|
||||
records={attributes}
|
||||
verticalSpacing={"md"}
|
||||
emptyState={
|
||||
<Group mt={attributes.length === 0 ? "xl" : 0}>
|
||||
<Text>Нет атрибутов</Text>
|
||||
<IconMoodSad />
|
||||
</Group>
|
||||
}
|
||||
groups={undefined}
|
||||
styles={{
|
||||
table: {
|
||||
width: "100%",
|
||||
},
|
||||
header: { zIndex: 1 },
|
||||
}}
|
||||
mx={isMobile ? "xs" : 0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributesTable;
|
||||
26
src/app/attributes/components/PageBody.tsx
Normal file
26
src/app/attributes/components/PageBody.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import AttributesHeader from "@/app/attributes/components/AttributesHeader";
|
||||
import AttributesTable from "@/app/attributes/components/AttributesTable";
|
||||
import PageBlock from "@/components/layout/PageBlock/PageBlock";
|
||||
|
||||
const PageBody = () => (
|
||||
<PageBlock
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
fullScreenMobile>
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--mantine-spacing-md)",
|
||||
}}>
|
||||
<AttributesHeader />
|
||||
<div style={{ flex: 1, overflow: "auto" }}>
|
||||
<AttributesTable />
|
||||
</div>
|
||||
</div>
|
||||
</PageBlock>
|
||||
);
|
||||
|
||||
export default PageBody;
|
||||
40
src/app/attributes/contexts/AttributesContext.tsx
Normal file
40
src/app/attributes/contexts/AttributesContext.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import useFilteredAttributes from "@/app/attributes/hooks/useFilteredAttributes";
|
||||
import useAttributesActions, {
|
||||
AttributesActions,
|
||||
} from "@/app/module-editor/[moduleId]/hooks/useAttributesActions";
|
||||
import useAttributesList from "@/app/module-editor/[moduleId]/hooks/useAttributesList";
|
||||
import { AttributeSchema } from "@/lib/client";
|
||||
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||
|
||||
type AttributesContextState = {
|
||||
attributes: AttributeSchema[];
|
||||
refetchAttributes: () => void;
|
||||
attributesActions: AttributesActions;
|
||||
search: string;
|
||||
setSearch: Dispatch<SetStateAction<string>>;
|
||||
};
|
||||
|
||||
const useAttributesContextState = (): AttributesContextState => {
|
||||
const { attributes, refetch } = useAttributesList();
|
||||
const attributesActions = useAttributesActions({
|
||||
refetchAttributes: refetch,
|
||||
});
|
||||
|
||||
const { search, setSearch, filteredAttributes } = useFilteredAttributes({
|
||||
attributes,
|
||||
});
|
||||
|
||||
return {
|
||||
attributes: filteredAttributes,
|
||||
refetchAttributes: refetch,
|
||||
attributesActions,
|
||||
search,
|
||||
setSearch,
|
||||
};
|
||||
};
|
||||
|
||||
export const [AttributesContextProvider, useAttributesContext] =
|
||||
makeContext<AttributesContextState>(useAttributesContextState, "Attribute");
|
||||
72
src/app/attributes/hooks/useAttributesTableColumns.tsx
Normal file
72
src/app/attributes/hooks/useAttributesTableColumns.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||
import { DataTableColumn } from "mantine-datatable";
|
||||
import { Box, Center } from "@mantine/core";
|
||||
import AttributeTableActions from "@/app/module-editor/[moduleId]/components/shared/AttributeTableActions/AttributeTableActions";
|
||||
import AttributeDefaultValue from "@/components/ui/AttributeDefaultValue/AttributeDefaultValue";
|
||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||
import { AttributeSchema } from "@/lib/client";
|
||||
import { useAttributesContext } from "../contexts/AttributesContext";
|
||||
|
||||
const useAttributesTableColumns = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { attributesActions } = useAttributesContext();
|
||||
const renderCheck = (value: boolean) => (value ? <IconCheck /> : <IconX />);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
title: "Название атрибута",
|
||||
accessor: "label",
|
||||
},
|
||||
{
|
||||
title: "Тип",
|
||||
accessor: "type.name",
|
||||
render: attr =>
|
||||
attr.type.type === "select"
|
||||
? `Выбор "${attr.label}"`
|
||||
: attr.type.name,
|
||||
},
|
||||
{
|
||||
title: "Значение по умолчанию",
|
||||
accessor: "defaultValue",
|
||||
render: attr => <AttributeDefaultValue attribute={attr} />,
|
||||
},
|
||||
{
|
||||
title: isMobile
|
||||
? "Синх. в группе"
|
||||
: "Синхронизировано в группе",
|
||||
accessor: "isApplicableToGroup",
|
||||
render: attr => renderCheck(attr.isApplicableToGroup),
|
||||
},
|
||||
{
|
||||
title: "Может быть пустым",
|
||||
accessor: "isNullable",
|
||||
render: attr => renderCheck(attr.isNullable),
|
||||
},
|
||||
{
|
||||
title: "Описаниие",
|
||||
accessor: "description",
|
||||
render: attr => <Box>{attr.description}</Box>,
|
||||
},
|
||||
{
|
||||
accessor: "actions",
|
||||
title: <Center>Действия</Center>,
|
||||
width: "0%",
|
||||
render: attribute => (
|
||||
<AttributeTableActions
|
||||
attribute={attribute}
|
||||
onUpdate={attributesActions.onUpdate}
|
||||
onDelete={attributesActions.onDelete}
|
||||
/>
|
||||
),
|
||||
},
|
||||
] as DataTableColumn<AttributeSchema>[],
|
||||
[isMobile]
|
||||
);
|
||||
};
|
||||
|
||||
export default useAttributesTableColumns;
|
||||
29
src/app/attributes/hooks/useFilteredAttributes.ts
Normal file
29
src/app/attributes/hooks/useFilteredAttributes.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { AttributeSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
attributes: AttributeSchema[];
|
||||
};
|
||||
|
||||
const useFilteredAttributes = ({ attributes }: Props) => {
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
const filteredAttributes = useMemo(
|
||||
() =>
|
||||
attributes.filter(
|
||||
attr =>
|
||||
attr.type.name.includes(search) ||
|
||||
attr.label.includes(search) ||
|
||||
attr.description.includes(search)
|
||||
),
|
||||
[attributes, search]
|
||||
);
|
||||
|
||||
return {
|
||||
search,
|
||||
setSearch,
|
||||
filteredAttributes,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFilteredAttributes;
|
||||
22
src/app/attributes/page.tsx
Normal file
22
src/app/attributes/page.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Suspense } from "react";
|
||||
import { Center, Loader } from "@mantine/core";
|
||||
import PageBody from "@/app/attributes/components/PageBody";
|
||||
import { AttributesContextProvider } from "@/app/attributes/contexts/AttributesContext";
|
||||
import PageContainer from "@/components/layout/PageContainer/PageContainer";
|
||||
|
||||
export default async function AttributesPage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<Center h="50vh">
|
||||
<Loader size="lg" />
|
||||
</Center>
|
||||
}>
|
||||
<PageContainer>
|
||||
<AttributesContextProvider>
|
||||
<PageBody />
|
||||
</AttributesContextProvider>
|
||||
</PageContainer>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user