diff --git a/package.json b/package.json index dc07a84..20cae99 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@tanstack/react-query": "^5.83.0", "axios": "^1.11.0", "classnames": "^2.5.1", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "dayjs": "^1.11.15", @@ -33,7 +34,7 @@ "i18n-iso-countries": "^7.14.0", "lexorank": "^1.0.5", "libphonenumber-js": "^1.12.10", - "mantine-react-table": "^2.0.0-beta.9", + "mantine-datatable": "^8.2.0", "next": "15.4.7", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/src/app/deals/components/desktop/DealsTable/DealsTable.tsx b/src/app/deals/components/desktop/DealsTable/DealsTable.tsx index 05f7386..93f17d0 100644 --- a/src/app/deals/components/desktop/DealsTable/DealsTable.tsx +++ b/src/app/deals/components/desktop/DealsTable/DealsTable.tsx @@ -1,63 +1,39 @@ -import { IconEdit } from "@tabler/icons-react"; -import { MRT_TableOptions } from "mantine-react-table"; -import { ActionIcon, Group, Pagination, Stack, Tooltip } from "@mantine/core"; +import { FC } from "react"; +import { IconMoodSad } from "@tabler/icons-react"; +import { Group, Pagination, Stack, Text } from "@mantine/core"; import useDealsTableColumns from "@/app/deals/components/desktop/DealsTable/useDealsTableColumns"; import { useDealsContext } from "@/app/deals/contexts/DealsContext"; import BaseTable from "@/components/ui/BaseTable/BaseTable"; -import { useDrawersContext } from "@/drawers/DrawersContext"; -import { DealSchema } from "@/lib/client"; -const DealsTable = () => { - const { deals, paginationInfo, page, setPage, dealsCrud } = +const DealsTable: FC = () => { + const { deals, paginationInfo, page, setPage, dealsFilters } = useDealsContext(); - const { openDrawer } = useDrawersContext(); const columns = useDealsTableColumns(); - const defaultSorting = [{ id: "createdAt", desc: false }]; - - const onEditDeal = (deal: DealSchema) => { - openDrawer({ - key: "dealEditorDrawer", - props: { - deal, - dealsCrud, - }, - }); - }; return ( ( - - onEditDeal(row.original)} - variant={"default"}> - - - - ), - } as MRT_TableOptions + sortStatus={{ + columnAccessor: dealsFilters.sortingField, + direction: dealsFilters.sortingDirection, + }} + onSortStatusChange={sorting => { + dealsFilters.setSortingField(sorting.columnAccessor); + dealsFilters.setSortingDirection(sorting.direction); + }} + emptyState={ + + Нет сделок + + } + groups={undefined} /> {paginationInfo && paginationInfo.totalPages > 1 && ( diff --git a/src/app/deals/components/desktop/DealsTable/useDealsTableColumns.tsx b/src/app/deals/components/desktop/DealsTable/useDealsTableColumns.tsx index 2c1e3c3..6e5f2a6 100644 --- a/src/app/deals/components/desktop/DealsTable/useDealsTableColumns.tsx +++ b/src/app/deals/components/desktop/DealsTable/useDealsTableColumns.tsx @@ -1,33 +1,67 @@ -import { useMemo } from "react"; -import { MRT_ColumnDef } from "mantine-react-table"; +import { useCallback, useMemo } from "react"; +import { IconEdit } from "@tabler/icons-react"; +import { DataTableColumn } from "mantine-datatable"; +import { ActionIcon, Tooltip } from "@mantine/core"; +import { useDealsContext } from "@/app/deals/contexts/DealsContext"; +import { useDrawersContext } from "@/drawers/DrawersContext"; import { DealSchema } from "@/lib/client"; import { utcDateTimeToLocalString } from "@/utils/datetime"; const useDealsTableColumns = () => { - return useMemo[]>( - () => [ - { - accessorKey: "id", - header: "Номер", - size: 20, - }, - { - accessorKey: "name", - header: "Название", - enableSorting: false, - }, - { - header: "Дата создания", - accessorKey: "createdAt", - Cell: ({ row }) => - utcDateTimeToLocalString(row.original.createdAt), - enableSorting: true, - sortingFn: (rowA, rowB) => - new Date(rowB.original.createdAt).getTime() - - new Date(rowA.original.createdAt).getTime(), - }, - ], - [] + const { dealsCrud } = useDealsContext(); + const { openDrawer } = useDrawersContext(); + + const onEditDeal = useCallback( + (deal: DealSchema) => { + openDrawer({ + key: "dealEditorDrawer", + props: { + deal, + dealsCrud, + }, + }); + }, + [openDrawer, dealsCrud] + ); + + return useMemo( + () => + [ + { + accessor: "actions", + title: "Действия", + sortable: false, + textAlign: "center", + width: "0%", + render: deal => ( + + onEditDeal(deal)} + variant={"default"}> + + + + ), + }, + { + accessor: "id", + title: "Номер", + sortable: true, + }, + { + accessor: "name", + title: "Название", + }, + { + title: "Дата создания", + accessor: "createdAt", + render: deal => utcDateTimeToLocalString(deal.createdAt), + sortable: true, + }, + ] as DataTableColumn[], + [onEditDeal] ); }; diff --git a/src/app/deals/components/desktop/ProjectAction/ProjectAction.module.css b/src/app/deals/components/desktop/ToolPanelAction/ToolPanelAction.module.css similarity index 100% rename from src/app/deals/components/desktop/ProjectAction/ProjectAction.module.css rename to src/app/deals/components/desktop/ToolPanelAction/ToolPanelAction.module.css diff --git a/src/app/deals/components/desktop/ProjectAction/ProjectAction.tsx b/src/app/deals/components/desktop/ToolPanelAction/ToolPanelAction.tsx similarity index 76% rename from src/app/deals/components/desktop/ProjectAction/ProjectAction.tsx rename to src/app/deals/components/desktop/ToolPanelAction/ToolPanelAction.tsx index 4703c89..0fec137 100644 --- a/src/app/deals/components/desktop/ProjectAction/ProjectAction.tsx +++ b/src/app/deals/components/desktop/ToolPanelAction/ToolPanelAction.tsx @@ -1,12 +1,12 @@ import { FC, PropsWithChildren } from "react"; import { ActionIcon, Box } from "@mantine/core"; -import style from "./ProjectAction.module.css"; +import style from "./ToolPanelAction.module.css"; type Props = { onClick: () => void; }; -const ProjectAction: FC> = ({ +const ToolPanelAction: FC> = ({ onClick, children, }) => { @@ -23,4 +23,4 @@ const ProjectAction: FC> = ({ ); }; -export default ProjectAction; +export default ToolPanelAction; diff --git a/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx b/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx index 10ef434..36a1a16 100644 --- a/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx +++ b/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx @@ -1,16 +1,21 @@ "use client"; -import { IconEdit, IconPlus } from "@tabler/icons-react"; +import { IconEdit, IconFilter, IconPlus } from "@tabler/icons-react"; import { Flex, Group } from "@mantine/core"; import { modals } from "@mantine/modals"; -import ProjectAction from "@/app/deals/components/desktop/ProjectAction/ProjectAction"; +import ToolPanelAction from "@/app/deals/components/desktop/ToolPanelAction/ToolPanelAction"; import ViewSelector from "@/app/deals/components/desktop/ViewSelector/ViewSelector"; +import { useDealsContext } from "@/app/deals/contexts/DealsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; +import { useViewContext } from "@/app/deals/contexts/ViewContext"; +import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal"; import ProjectSelect from "@/components/selects/ProjectSelect/ProjectSelect"; import { useDrawersContext } from "@/drawers/DrawersContext"; import useIsMobile from "@/hooks/utils/useIsMobile"; const TopToolPanel = () => { + const { dealsFilters } = useDealsContext(); + const { view } = useViewContext(); const { projects, setSelectedProjectId, selectedProject, projectsCrud } = useProjectsContext(); const { openDrawer } = useDrawersContext(); @@ -45,12 +50,22 @@ const TopToolPanel = () => { wrap={"nowrap"} align={"center"} gap={"sm"}> - + ( + + + + )} + filters={dealsFilters} + selectedProject={selectedProject} + boardAndStatusEnabled={view === "table"} + /> + - - + + - + ( + + + + + +); + +const TableView = () => ( + + + +); + +const ScheduleView = () => -; const PageBody = () => { const { selectedBoard } = useBoardsContext(); const { selectedProject } = useProjectsContext(); const { view } = useViewContext(); - if (view === "board") { - return ( - <> - - - - - - - ); - } + const getViewContent = () => { + switch (view) { + case "board": + return ; + case "table": + return ; + default: + return ; + } + }; - if (view === "table") { - return ( - - - - - - ); - } + const getContextProps = () => { + if (view === "table") { + return { withPagination: true, projectId: selectedProject?.id }; + } + return { boardId: selectedBoard?.id }; + }; - return <>-; + return ( + + + {getViewContent()} + + ); }; export default PageBody; diff --git a/src/app/deals/contexts/DealsContext.tsx b/src/app/deals/contexts/DealsContext.tsx index c6b606a..9173391 100644 --- a/src/app/deals/contexts/DealsContext.tsx +++ b/src/app/deals/contexts/DealsContext.tsx @@ -2,6 +2,7 @@ import React from "react"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; +import { DealsFilters } from "@/app/deals/hooks/useDealsFilters"; import { DealsCrud, useDealsCrud } from "@/hooks/cruds/useDealsCrud"; import useDealsList from "@/hooks/lists/useDealsList"; import { DealSchema, PaginationInfoSchema } from "@/lib/client"; @@ -15,6 +16,7 @@ type DealsContextState = { paginationInfo?: PaginationInfoSchema; page: number; setPage: React.Dispatch>; + dealsFilters: DealsFilters; }; type Props = { @@ -24,11 +26,12 @@ type Props = { }; const useDealsContextState = ({ - withPagination = false, boardId, projectId, + withPagination = false, }: Props): DealsContextState => { const { statuses } = useStatusesContext(); + const dealsListObjects = useDealsList({ boardId, projectId, diff --git a/src/app/deals/drawers/DealEditorDrawer/components/GeneralTab.tsx b/src/app/deals/drawers/DealEditorDrawer/components/GeneralTab.tsx index df51b3c..bee81fd 100644 --- a/src/app/deals/drawers/DealEditorDrawer/components/GeneralTab.tsx +++ b/src/app/deals/drawers/DealEditorDrawer/components/GeneralTab.tsx @@ -1,33 +1,33 @@ import { FC, useState } from "react"; import { isEqual } from "lodash"; -import { Button, Group, Stack, TextInput } from "@mantine/core"; +import { Button, Group, Stack, Text, TextInput } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { ProjectsCrud } from "@/hooks/cruds/useProjectsCrud"; -import { ProjectSchema } from "@/lib/client"; - +import { DealsCrud } from "@/hooks/cruds/useDealsCrud"; +import { DealSchema } from "@/lib/client"; +import { utcDateTimeToLocalString } from "@/utils/datetime"; type Props = { - projectsCrud: ProjectsCrud; - project: ProjectSchema; + dealsCrud: DealsCrud; + deal: DealSchema; onClose: () => void; }; -const GeneralTab: FC = ({ project, projectsCrud, onClose }) => { - const [initialValues, setInitialValues] = useState(project); - const form = useForm({ +const GeneralTab: FC = ({ deal, dealsCrud, onClose }) => { + const [initialValues, setInitialValues] = useState(deal); + const form = useForm({ initialValues, validate: { name: value => !value && "Введите название", }, }); - const onSubmit = (values: ProjectSchema) => { - projectsCrud.onUpdate(project.id, values); + const onSubmit = (values: DealSchema) => { + dealsCrud.onUpdate(deal.id, values); setInitialValues(values); }; const onDelete = () => { - projectsCrud.onDelete(project, onClose); + dealsCrud.onDelete(deal, onClose); }; return ( @@ -37,6 +37,7 @@ const GeneralTab: FC = ({ project, projectsCrud, onClose }) => { label={"Название"} {...form.getInputProps("name")} /> + Создано: {utcDateTimeToLocalString(deal.createdAt)} diff --git a/src/app/deals/drawers/SelectedProjectEditorDrawer/DealEditorDrawer.module.css b/src/app/deals/drawers/SelectedProjectEditorDrawer/SelectedProjectEditorDrawer.module.css similarity index 100% rename from src/app/deals/drawers/SelectedProjectEditorDrawer/DealEditorDrawer.module.css rename to src/app/deals/drawers/SelectedProjectEditorDrawer/SelectedProjectEditorDrawer.module.css diff --git a/src/app/deals/drawers/SelectedProjectEditorDrawer/components/GeneralTab.tsx b/src/app/deals/drawers/SelectedProjectEditorDrawer/components/GeneralTab.tsx index bee81fd..df51b3c 100644 --- a/src/app/deals/drawers/SelectedProjectEditorDrawer/components/GeneralTab.tsx +++ b/src/app/deals/drawers/SelectedProjectEditorDrawer/components/GeneralTab.tsx @@ -1,33 +1,33 @@ import { FC, useState } from "react"; import { isEqual } from "lodash"; -import { Button, Group, Stack, Text, TextInput } from "@mantine/core"; +import { Button, Group, Stack, TextInput } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { DealsCrud } from "@/hooks/cruds/useDealsCrud"; -import { DealSchema } from "@/lib/client"; -import { utcDateTimeToLocalString } from "@/utils/datetime"; +import { ProjectsCrud } from "@/hooks/cruds/useProjectsCrud"; +import { ProjectSchema } from "@/lib/client"; + type Props = { - dealsCrud: DealsCrud; - deal: DealSchema; + projectsCrud: ProjectsCrud; + project: ProjectSchema; onClose: () => void; }; -const GeneralTab: FC = ({ deal, dealsCrud, onClose }) => { - const [initialValues, setInitialValues] = useState(deal); - const form = useForm({ +const GeneralTab: FC = ({ project, projectsCrud, onClose }) => { + const [initialValues, setInitialValues] = useState(project); + const form = useForm({ initialValues, validate: { name: value => !value && "Введите название", }, }); - const onSubmit = (values: DealSchema) => { - dealsCrud.onUpdate(deal.id, values); + const onSubmit = (values: ProjectSchema) => { + projectsCrud.onUpdate(project.id, values); setInitialValues(values); }; const onDelete = () => { - dealsCrud.onDelete(deal, onClose); + projectsCrud.onDelete(project, onClose); }; return ( @@ -37,7 +37,6 @@ const GeneralTab: FC = ({ deal, dealsCrud, onClose }) => { label={"Название"} {...form.getInputProps("name")} /> - Создано: {utcDateTimeToLocalString(deal.createdAt)} diff --git a/src/app/deals/drawers/SelectedProjectEditorDrawer/components/ProjectEditorBody.tsx b/src/app/deals/drawers/SelectedProjectEditorDrawer/components/ProjectEditorBody.tsx index f2301f2..ac29074 100644 --- a/src/app/deals/drawers/SelectedProjectEditorDrawer/components/ProjectEditorBody.tsx +++ b/src/app/deals/drawers/SelectedProjectEditorDrawer/components/ProjectEditorBody.tsx @@ -1,10 +1,10 @@ import { FC } from "react"; import { IconEdit } from "@tabler/icons-react"; import { Tabs } from "@mantine/core"; -import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/components/GeneralTab"; +import GeneralTab from "@/app/deals/drawers/SelectedProjectEditorDrawer/components/GeneralTab"; import { ProjectsCrud } from "@/hooks/cruds/useProjectsCrud"; import { ProjectSchema } from "@/lib/client"; -import styles from "../DealEditorDrawer.module.css"; +import styles from "../SelectedProjectEditorDrawer.module.css"; type Props = { projectsCrud: ProjectsCrud; diff --git a/src/app/deals/hooks/useDealsFilters.ts b/src/app/deals/hooks/useDealsFilters.ts new file mode 100644 index 0000000..f124c99 --- /dev/null +++ b/src/app/deals/hooks/useDealsFilters.ts @@ -0,0 +1,53 @@ +import { Dispatch, SetStateAction, useState } from "react"; +import { useDebouncedValue } from "@mantine/hooks"; +import { BoardSchema, SortDir, StatusSchema } from "@/lib/client"; + +export type DealsFilters = { + id?: number; + debouncedId?: number; + setId: Dispatch>; + name?: string; + debouncedName?: string; + setName: Dispatch>; + board: BoardSchema | null; + setBoard: Dispatch>; + status: StatusSchema | null; + setStatus: Dispatch>; + sortingField: string; + setSortingField: Dispatch>; + sortingDirection: SortDir; + setSortingDirection: Dispatch>; +}; + +const useDealsFilters = (): DealsFilters => { + const [id, setId] = useState(); + const [debouncedId] = useDebouncedValue(id, 300); + + const [name, setName] = useState(); + const [debouncedName] = useDebouncedValue(name, 300); + + const [board, setBoard] = useState(null); + const [status, setStatus] = useState(null); + + const [sortingField, setSortingField] = useState("createdAt"); + const [sortingDirection, setSortingDirection] = useState("asc"); + + return { + id, + setId, + debouncedId, + name, + setName, + debouncedName, + board, + setBoard, + status, + setStatus, + sortingField, + setSortingField, + sortingDirection, + setSortingDirection, + }; +}; + +export default useDealsFilters; diff --git a/src/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal.tsx b/src/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal.tsx new file mode 100644 index 0000000..9e47901 --- /dev/null +++ b/src/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { ReactNode } from "react"; +import { Flex, Modal, NumberInput, rem, TextInput } from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { DealsFilters } from "@/app/deals/hooks/useDealsFilters"; +import BoardSelect from "@/components/selects/BoardSelect/BoardSelect"; +import StatusSelect from "@/components/selects/StatusSelect/StatusSelect"; +import { ProjectSchema } from "@/lib/client"; + +type Props = { + filters: DealsFilters; + selectedProject: ProjectSchema | null; + boardAndStatusEnabled: boolean; + getOpener: (open: () => void) => ReactNode; +}; + +const DealsTableFiltersModal = ({ + filters, + selectedProject, + boardAndStatusEnabled, + getOpener, +}: Props) => { + const [opened, { open, close }] = useDisclosure(); + + return ( + <> + {getOpener(open)} + + + + typeof value === "number" + ? filters.setId(Number(value)) + : filters.setId(undefined) + } + min={1} + /> + filters.setName(event.target.value)} + /> + {boardAndStatusEnabled && ( + <> + + + + )} + + + + ); +}; + +export default DealsTableFiltersModal; diff --git a/src/app/deals/page.tsx b/src/app/deals/page.tsx index e820813..44080dd 100644 --- a/src/app/deals/page.tsx +++ b/src/app/deals/page.tsx @@ -1,31 +1,22 @@ +import { Suspense } from "react"; import { dehydrate, HydrationBoundary, QueryClient, } from "@tanstack/react-query"; -import { Suspense } from "react"; -import { Loader, Center } from "@mantine/core"; -import dynamic from "next/dynamic"; +import { Center, Loader } from "@mantine/core"; +import PageBody from "@/app/deals/components/shared/PageBody/PageBody"; +import { BoardsContextProvider } from "@/app/deals/contexts/BoardsContext"; +import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext"; +import { StatusesContextProvider } from "@/app/deals/contexts/StatusesContext"; +import { ViewContextProvider } from "@/app/deals/contexts/ViewContext"; +import PageContainer from "@/components/layout/PageContainer/PageContainer"; import { getBoardsOptions, getProjectsOptions, } from "@/lib/client/@tanstack/react-query.gen"; import { combineProviders } from "@/utils/combineProviders"; -// Dynamic imports for better code splitting -const PageBody = dynamic(() => import("@/app/deals/components/shared/PageBody/PageBody"), { - loading: () =>
-}); -const BoardsContextProvider = dynamic(() => import("@/app/deals/contexts/BoardsContext").then(mod => ({ default: mod.BoardsContextProvider }))); -const ProjectsContextProvider = dynamic(() => import("@/app/deals/contexts/ProjectsContext").then(mod => ({ default: mod.ProjectsContextProvider }))); -const StatusesContextProvider = dynamic(() => import("@/app/deals/contexts/StatusesContext").then(mod => ({ default: mod.StatusesContextProvider }))); -const ViewContextProvider = dynamic(() => import("@/app/deals/contexts/ViewContext").then(mod => ({ default: mod.ViewContextProvider }))); -const PageBlock = dynamic(() => import("@/components/layout/PageBlock/PageBlock")); -const PageContainer = dynamic(() => import("@/components/layout/PageContainer/PageContainer")); -const TopToolPanel = dynamic(() => import("./components/desktop/TopToolPanel/TopToolPanel"), { - loading: () =>
-}); - async function prefetchData() { const queryClient = new QueryClient(); const projectsData = await queryClient.fetchQuery(getProjectsOptions()); @@ -53,16 +44,14 @@ export default async function DealsPage() { return ( - - - - }> + + + + }> - - - - + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4c1e817..e25a20c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,7 @@ import "@mantine/core/styles.css"; +import "mantine-datatable/styles.layer.css"; import "@mantine/notifications/styles.css"; import "@mantine/dates/styles.css"; -import "mantine-react-table/styles.css"; import "swiper/css"; import "swiper/css/pagination"; import "swiper/css/scrollbar"; diff --git a/src/app/loading.tsx b/src/app/loading.tsx deleted file mode 100644 index eb62715..0000000 --- a/src/app/loading.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Center, Loader } from "@mantine/core"; - -export default function Loading() { - return ( -
- -
- ); -} diff --git a/src/components/selects/BoardSelect/BoardSelect.tsx b/src/components/selects/BoardSelect/BoardSelect.tsx new file mode 100644 index 0000000..bcd9d89 --- /dev/null +++ b/src/components/selects/BoardSelect/BoardSelect.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { FC } from "react"; +import ObjectSelect, { + ObjectSelectProps, +} from "@/components/selects/ObjectSelect/ObjectSelect"; +import useBoardsList from "@/hooks/lists/useBoardsList"; +import { BoardSchema } from "@/lib/client"; + +type Props = Omit< + ObjectSelectProps, + "data" | "getLabelFn" | "getValueFn" +> & { + projectId?: number; +}; + +const BoardSelect: FC = ({ projectId, ...props }) => { + const onClear = () => props.onChange(null); + + const { boards } = useBoardsList({ projectId }); + + return ( + + ); +}; + +export default BoardSelect; diff --git a/src/components/selects/StatusSelect/StatusSelect.tsx b/src/components/selects/StatusSelect/StatusSelect.tsx new file mode 100644 index 0000000..9ad3036 --- /dev/null +++ b/src/components/selects/StatusSelect/StatusSelect.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { FC, useEffect } from "react"; +import ObjectSelect, { + ObjectSelectProps, +} from "@/components/selects/ObjectSelect/ObjectSelect"; +import useStatusesList from "@/hooks/lists/useStatusesList"; +import { BoardSchema } from "@/lib/client"; + +type Props = Omit< + ObjectSelectProps, + "data" | "getLabelFn" | "getValueFn" +> & { + boardId?: number; +}; + +const StatusSelect: FC = ({ boardId, ...props }) => { + const onClear = () => props.onChange(null); + + const { statuses } = useStatusesList({ boardId }); + + useEffect(() => { + if (!boardId) props.onChange(null); + }, [boardId]); + + return ( + + ); +}; + +export default StatusSelect; diff --git a/src/components/ui/BaseTable/BaseTable.tsx b/src/components/ui/BaseTable/BaseTable.tsx index 59c95ee..7a99702 100644 --- a/src/components/ui/BaseTable/BaseTable.tsx +++ b/src/components/ui/BaseTable/BaseTable.tsx @@ -1,59 +1,18 @@ -import React, { useEffect, useImperativeHandle } from "react"; -import { - MantineReactTable, - MRT_ColumnDef, - MRT_RowData, - MRT_TableInstance, - MRT_TableOptions, - useMantineReactTable, -} from "mantine-react-table"; -import { MRT_Localization_RU } from "mantine-react-table/locales/ru"; +import React from "react"; +import { DataTable, DataTableProps } from "mantine-datatable"; -type Props = { - data: T[]; - onSelectionChange?: (selectedRows: T[]) => void; - columns: MRT_ColumnDef[]; - restProps?: MRT_TableOptions; - striped?: boolean | "odd" | "even"; -}; - -export type BaseTableRef = { - getTable: () => MRT_TableInstance; -}; - -function BaseTableInner( - { data, columns, restProps, onSelectionChange, striped = false }: Props, - ref: React.Ref> -) { - const table = useMantineReactTable({ - localization: MRT_Localization_RU, - enablePagination: false, - data, - columns, - mantineTableProps: { - striped, - highlightOnHover: false, - }, - enableTopToolbar: false, - enableBottomToolbar: false, - enableRowSelection: onSelectionChange !== undefined, - ...restProps, - }); - - useEffect(() => { - if (!onSelectionChange) return; - onSelectionChange( - table.getSelectedRowModel().rows.map(r => r.original) - ); - }, [onSelectionChange, table.getState().rowSelection]); - - useImperativeHandle(ref, () => ({ getTable: () => table })); - - return ; +function BaseTable(props: DataTableProps) { + return ( + + ); } -const BaseTable = React.forwardRef(BaseTableInner) as ( - props: Props & { ref?: React.Ref> } -) => React.ReactElement | null; - export default BaseTable; diff --git a/src/hooks/lists/useDealsList.ts b/src/hooks/lists/useDealsList.ts index a8519ec..8e1fd9a 100644 --- a/src/hooks/lists/useDealsList.ts +++ b/src/hooks/lists/useDealsList.ts @@ -1,6 +1,9 @@ -import { useState } from "react"; +import { Dispatch, SetStateAction, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { DealSchema } from "@/lib/client"; +import useDealsFilters, { + DealsFilters, +} from "@/app/deals/hooks/useDealsFilters"; +import { DealSchema, GetDealsData, PaginationInfoSchema } from "@/lib/client"; import { getDealsOptions, getDealsQueryKey, @@ -12,26 +15,45 @@ type Props = { withPagination: boolean; }; +type ReturnType = { + deals: DealSchema[]; + setDeals: (deals: DealSchema[]) => void; + dealsFilters: DealsFilters; + refetchDeals: () => void; + page: number; + setPage: Dispatch>; + paginationInfo?: PaginationInfoSchema; + queryKey: any[]; +}; + const useDealsList = ({ withPagination, projectId = null, boardId = null, -}: Props) => { +}: Props): ReturnType => { const queryClient = useQueryClient(); const [page, setPage] = useState(1); const itemsPerPage = 10; + const dealsFilters = useDealsFilters(); - const options = { + const options: Omit = { query: { - boardId: boardId ?? null, - projectId: projectId ?? null, page: withPagination ? page : null, itemsPerPage: withPagination ? itemsPerPage : null, + sortingField: withPagination ? dealsFilters.sortingField : null, + sortingDirection: withPagination + ? dealsFilters.sortingDirection + : null, + projectId: withPagination ? projectId : null, + boardId: withPagination ? dealsFilters.board?.id : boardId, + statusId: withPagination ? dealsFilters.status?.id : null, + name: dealsFilters.debouncedName, + id: dealsFilters.debouncedId, }, }; + const { data, refetch } = useQuery({ ...getDealsOptions(options), - enabled: !!boardId || !!projectId, }); const queryKey = getDealsQueryKey(options); @@ -46,6 +68,7 @@ const useDealsList = ({ return { deals: data?.items ?? [], setDeals, + dealsFilters, refetchDeals: refetch, page, setPage, diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index 30c1e60..eed5e41 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -178,6 +178,10 @@ export type DealSchema = { * Statusid */ statusId: number; + /** + * Boardid + */ + boardId: number; /** * Createdat */ @@ -303,6 +307,8 @@ export type ProjectSchema = { name: string; }; +export type SortDir = "asc" | "desc"; + /** * StatusSchema */ @@ -585,14 +591,26 @@ export type GetDealsData = { body?: never; path?: never; query?: { + /** + * Projectid + */ + projectId?: number | null; /** * Boardid */ boardId?: number | null; /** - * Projectid + * Statusid */ - projectId?: number | null; + statusId?: number | null; + /** + * Id + */ + id?: number | null; + /** + * Name + */ + name?: string | null; /** * Page */ @@ -601,6 +619,14 @@ export type GetDealsData = { * Itemsperpage */ itemsPerPage?: number | null; + /** + * Sortingfield + */ + sortingField?: string | null; + /** + * Sortingdirection + */ + sortingDirection?: SortDir | null; }; url: "/deal/"; }; diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts index 716514c..8bc3534 100644 --- a/src/lib/client/zod.gen.ts +++ b/src/lib/client/zod.gen.ts @@ -60,6 +60,7 @@ export const zDealSchema = z.object({ name: z.string(), lexorank: z.string(), statusId: z.int(), + boardId: z.int(), createdAt: z.iso.datetime({ offset: true, }), @@ -217,6 +218,8 @@ export const zHttpValidationError = z.object({ detail: z.optional(z.array(zValidationError)), }); +export const zSortDir = z.enum(["asc", "desc"]); + /** * UpdateBoardSchema */ @@ -360,10 +363,15 @@ export const zGetDealsData = z.object({ path: z.optional(z.never()), query: z.optional( z.object({ - boardId: z.optional(z.union([z.int(), z.null()])), projectId: z.optional(z.union([z.int(), z.null()])), + boardId: z.optional(z.union([z.int(), z.null()])), + statusId: z.optional(z.union([z.int(), z.null()])), + id: z.optional(z.union([z.int(), z.null()])), + name: z.optional(z.union([z.string(), z.null()])), page: z.optional(z.union([z.int(), z.null()])), itemsPerPage: z.optional(z.union([z.int(), z.null()])), + sortingField: z.optional(z.union([z.string(), z.null()])), + sortingDirection: z.optional(z.union([zSortDir, z.null()])), }) ), }); diff --git a/yarn.lock b/yarn.lock index 41fb077..59dddea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3701,15 +3701,6 @@ __metadata: languageName: node linkType: hard -"@tanstack/match-sorter-utils@npm:8.19.4": - version: 8.19.4 - resolution: "@tanstack/match-sorter-utils@npm:8.19.4" - dependencies: - remove-accents: "npm:0.5.0" - checksum: 10c0/935022e3d639f19472131d289f3e1202253ff34301717c337e9bac0eeae6a0bd56450ed8ae2f7eb7ac9dfefa7ceaa7d126d8c5441021968b4a9eabc3ac4f8ba1 - languageName: node - linkType: hard - "@tanstack/query-core@npm:5.83.0": version: 5.83.0 resolution: "@tanstack/query-core@npm:5.83.0" @@ -3728,44 +3719,6 @@ __metadata: languageName: node linkType: hard -"@tanstack/react-table@npm:8.20.5": - version: 8.20.5 - resolution: "@tanstack/react-table@npm:8.20.5" - dependencies: - "@tanstack/table-core": "npm:8.20.5" - peerDependencies: - react: ">=16.8" - react-dom: ">=16.8" - checksum: 10c0/574fa62fc6868a3b1113dbd043323f8b73aeb60555609caa164d5137a14636d4502784a961191afde2ec46f33f8c2bbfc4561d27a701c3d084e899a632dda3c8 - languageName: node - linkType: hard - -"@tanstack/react-virtual@npm:3.11.2": - version: 3.11.2 - resolution: "@tanstack/react-virtual@npm:3.11.2" - dependencies: - "@tanstack/virtual-core": "npm:3.11.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/de446ce517d0855b3d58e79b6a75a37be40b4529baf0a5c3ffa2662dea80aba03409e06545aea27aa9e3a36fc2a2e3005ecbfce16a4659991d66930ea3bd62d4 - languageName: node - linkType: hard - -"@tanstack/table-core@npm:8.20.5": - version: 8.20.5 - resolution: "@tanstack/table-core@npm:8.20.5" - checksum: 10c0/3c27b5debd61b6bd9bfbb40bfc7c5d5af90873ae1a566b20e3bf2d2f4f2e9a78061c081aacc5259a00e256f8df506ec250eb5472f5c01ff04baf9918b554982b - languageName: node - linkType: hard - -"@tanstack/virtual-core@npm:3.11.2": - version: 3.11.2 - resolution: "@tanstack/virtual-core@npm:3.11.2" - checksum: 10c0/38f1047127c6b1d07fe95becb7a12e66fb7c59d37ec0359e4ab339f837c6b906e1adff026ebd12849ba851d3f118d491014205c6b3c6ed8568cc232a798aeaaf - languageName: node - linkType: hard - "@testing-library/dom@npm:10.4.0, @testing-library/dom@npm:^10.4.0": version: 10.4.0 resolution: "@testing-library/dom@npm:10.4.0" @@ -6183,6 +6136,7 @@ __metadata: axios: "npm:^1.11.0" babel-loader: "npm:^10.0.0" classnames: "npm:^2.5.1" + clsx: "npm:^2.1.1" date-fns: "npm:^4.1.0" date-fns-tz: "npm:^3.2.0" dayjs: "npm:^1.11.15" @@ -6197,7 +6151,7 @@ __metadata: jest-environment-jsdom: "npm:^30.0.0" lexorank: "npm:^1.0.5" libphonenumber-js: "npm:^1.12.10" - mantine-react-table: "npm:^2.0.0-beta.9" + mantine-datatable: "npm:^8.2.0" next: "npm:15.4.7" postcss: "npm:^8.5.6" postcss-preset-mantine: "npm:1.17.0" @@ -10161,23 +10115,16 @@ __metadata: languageName: node linkType: hard -"mantine-react-table@npm:^2.0.0-beta.9": - version: 2.0.0-beta.9 - resolution: "mantine-react-table@npm:2.0.0-beta.9" - dependencies: - "@tanstack/match-sorter-utils": "npm:8.19.4" - "@tanstack/react-table": "npm:8.20.5" - "@tanstack/react-virtual": "npm:3.11.2" +"mantine-datatable@npm:^8.2.0": + version: 8.2.0 + resolution: "mantine-datatable@npm:8.2.0" peerDependencies: - "@mantine/core": ^7.9 - "@mantine/dates": ^7.9 - "@mantine/hooks": ^7.9 - "@tabler/icons-react": ">=2.23.0" + "@mantine/core": ">=8.1" + "@mantine/hooks": ">=8.1" clsx: ">=2" - dayjs: ">=1.11" - react: ">=18.0" - react-dom: ">=18.0" - checksum: 10c0/8a560096d4a6ecc3f0eb16ea171c3d2589125f53152e0ed8ac1853977b6fa35994cf7b4f553b92b9b0333010805f8c0e42f002330408925cbc8050b03528df0b + react: ">=19" + react-dom: ">=19" + checksum: 10c0/0e307f8d73fdfc01563227d92f54a182be1cb5eed947d0d37b80bbb5260b864c5dd5942396ea408ccbc6f98df67870205e4c61913b893edb4367112248b38ffa languageName: node linkType: hard @@ -12083,13 +12030,6 @@ __metadata: languageName: node linkType: hard -"remove-accents@npm:0.5.0": - version: 0.5.0 - resolution: "remove-accents@npm:0.5.0" - checksum: 10c0/a75321aa1b53d9abe82637115a492770bfe42bb38ed258be748bf6795871202bc8b4badff22013494a7029f5a241057ad8d3f72adf67884dbe15a9e37e87adc4 - languageName: node - linkType: hard - "renderkid@npm:^3.0.0": version: 3.0.0 resolution: "renderkid@npm:3.0.0"