feat: deals filters indicator and refactoring

This commit is contained in:
2025-09-01 18:50:29 +04:00
parent 48d539154c
commit a6d8948e9d
6 changed files with 81 additions and 69 deletions

View File

@ -6,7 +6,7 @@ import { useDealsContext } from "@/app/deals/contexts/DealsContext";
import BaseTable from "@/components/ui/BaseTable/BaseTable"; import BaseTable from "@/components/ui/BaseTable/BaseTable";
const DealsTable: FC = () => { const DealsTable: FC = () => {
const { deals, paginationInfo, page, setPage, dealsFilters } = const { deals, paginationInfo, page, setPage, dealsFiltersForm } =
useDealsContext(); useDealsContext();
const columns = useDealsTableColumns(); const columns = useDealsTableColumns();
@ -18,12 +18,18 @@ const DealsTable: FC = () => {
records={[...deals]} records={[...deals]}
columns={columns} columns={columns}
sortStatus={{ sortStatus={{
columnAccessor: dealsFilters.sortingField, columnAccessor: dealsFiltersForm.values.sortingField ?? "",
direction: dealsFilters.sortingDirection, direction: dealsFiltersForm.values.sortingDirection,
}} }}
onSortStatusChange={sorting => { onSortStatusChange={sorting => {
dealsFilters.setSortingField(sorting.columnAccessor); dealsFiltersForm.setFieldValue(
dealsFilters.setSortingDirection(sorting.direction); "sortingField",
sorting.columnAccessor
);
dealsFiltersForm.setFieldValue(
"sortingDirection",
sorting.direction
);
}} }}
emptyState={ emptyState={
<Group <Group

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { IconEdit, IconFilter, IconPlus } from "@tabler/icons-react"; import { IconEdit, IconFilter, IconPlus } from "@tabler/icons-react";
import { Flex, Group } from "@mantine/core"; import { Flex, Group, Indicator } from "@mantine/core";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import ToolPanelAction from "@/app/deals/components/desktop/ToolPanelAction/ToolPanelAction"; import ToolPanelAction from "@/app/deals/components/desktop/ToolPanelAction/ToolPanelAction";
import ViewSelector from "@/app/deals/components/desktop/ViewSelector/ViewSelector"; import ViewSelector from "@/app/deals/components/desktop/ViewSelector/ViewSelector";
@ -14,7 +14,7 @@ import { useDrawersContext } from "@/drawers/DrawersContext";
import useIsMobile from "@/hooks/utils/useIsMobile"; import useIsMobile from "@/hooks/utils/useIsMobile";
const TopToolPanel = () => { const TopToolPanel = () => {
const { dealsFilters } = useDealsContext(); const { dealsFiltersForm, isChangedFilters } = useDealsContext();
const { view } = useViewContext(); const { view } = useViewContext();
const { projects, setSelectedProjectId, selectedProject, projectsCrud } = const { projects, setSelectedProjectId, selectedProject, projectsCrud } =
useProjectsContext(); useProjectsContext();
@ -52,11 +52,16 @@ const TopToolPanel = () => {
gap={"sm"}> gap={"sm"}>
<DealsTableFiltersModal <DealsTableFiltersModal
getOpener={onFiltersClick => ( getOpener={onFiltersClick => (
<Indicator
disabled={!isChangedFilters}
offset={3}
size={8}>
<ToolPanelAction onClick={onFiltersClick}> <ToolPanelAction onClick={onFiltersClick}>
<IconFilter /> <IconFilter />
</ToolPanelAction> </ToolPanelAction>
</Indicator>
)} )}
filters={dealsFilters} filtersForm={dealsFiltersForm}
selectedProject={selectedProject} selectedProject={selectedProject}
boardAndStatusEnabled={view === "table"} boardAndStatusEnabled={view === "table"}
/> />

View File

@ -1,8 +1,9 @@
"use client"; "use client";
import React from "react"; import React from "react";
import { UseFormReturnType } from "@mantine/form";
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
import { DealsFilters } from "@/app/deals/hooks/useDealsFilters"; import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import { DealsCrud, useDealsCrud } from "@/hooks/cruds/useDealsCrud"; import { DealsCrud, useDealsCrud } from "@/hooks/cruds/useDealsCrud";
import useDealsList from "@/hooks/lists/useDealsList"; import useDealsList from "@/hooks/lists/useDealsList";
import { DealSchema, PaginationInfoSchema } from "@/lib/client"; import { DealSchema, PaginationInfoSchema } from "@/lib/client";
@ -16,7 +17,8 @@ type DealsContextState = {
paginationInfo?: PaginationInfoSchema; paginationInfo?: PaginationInfoSchema;
page: number; page: number;
setPage: React.Dispatch<React.SetStateAction<number>>; setPage: React.Dispatch<React.SetStateAction<number>>;
dealsFilters: DealsFilters; dealsFiltersForm: UseFormReturnType<DealsFiltersForm>;
isChangedFilters: boolean;
}; };
type Props = { type Props = {

View File

@ -1,52 +1,48 @@
import { Dispatch, SetStateAction, useState } from "react"; import { isEqual } from "lodash";
import { useForm, UseFormReturnType } from "@mantine/form";
import { useDebouncedValue } from "@mantine/hooks"; import { useDebouncedValue } from "@mantine/hooks";
import { BoardSchema, SortDir, StatusSchema } from "@/lib/client"; import { BoardSchema, SortDir, StatusSchema } from "@/lib/client";
export type DealsFilters = { export type DealsFiltersForm = {
id?: number; id?: number;
debouncedId?: number; name: string;
setId: Dispatch<SetStateAction<number | undefined>>;
name?: string;
debouncedName?: string;
setName: Dispatch<SetStateAction<string | undefined>>;
board: BoardSchema | null; board: BoardSchema | null;
setBoard: Dispatch<SetStateAction<BoardSchema | null>>;
status: StatusSchema | null; status: StatusSchema | null;
setStatus: Dispatch<SetStateAction<StatusSchema | null>>; sortingField?: string;
sortingField: string;
setSortingField: Dispatch<SetStateAction<string>>;
sortingDirection: SortDir; sortingDirection: SortDir;
setSortingDirection: Dispatch<SetStateAction<SortDir>>;
}; };
const useDealsFilters = (): DealsFilters => { type ReturnType = {
const [id, setId] = useState<number>(); debouncedId: number | undefined;
const [debouncedId] = useDebouncedValue(id, 300); debouncedName: string;
form: UseFormReturnType<DealsFiltersForm>;
isChangedFilters: boolean;
};
const [name, setName] = useState<string>(); const useDealsFilters = (): ReturnType => {
const [debouncedName] = useDebouncedValue(name, 300); const initialValues = {
id: undefined,
board: null,
status: null,
name: "",
sortingField: "createdAt",
sortingDirection: "asc" as SortDir,
};
const [board, setBoard] = useState<BoardSchema | null>(null); const form = useForm<DealsFiltersForm>({
const [status, setStatus] = useState<StatusSchema | null>(null); initialValues,
});
const [sortingField, setSortingField] = useState("createdAt"); const isChangedFilters = !isEqual(form.values, initialValues);
const [sortingDirection, setSortingDirection] = useState<SortDir>("asc");
const [debouncedId] = useDebouncedValue(form.values.id, 300);
const [debouncedName] = useDebouncedValue(form.values.name, 300);
return { return {
id,
setId,
debouncedId, debouncedId,
name,
setName,
debouncedName, debouncedName,
board, form,
setBoard, isChangedFilters,
status,
setStatus,
sortingField,
setSortingField,
sortingDirection,
setSortingDirection,
}; };
}; };

View File

@ -2,21 +2,22 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { Flex, Modal, NumberInput, rem, TextInput } from "@mantine/core"; import { Flex, Modal, NumberInput, rem, TextInput } from "@mantine/core";
import { UseFormReturnType } from "@mantine/form";
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import { DealsFilters } from "@/app/deals/hooks/useDealsFilters"; import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import BoardSelect from "@/components/selects/BoardSelect/BoardSelect"; import BoardSelect from "@/components/selects/BoardSelect/BoardSelect";
import StatusSelect from "@/components/selects/StatusSelect/StatusSelect"; import StatusSelect from "@/components/selects/StatusSelect/StatusSelect";
import { ProjectSchema } from "@/lib/client"; import { ProjectSchema } from "@/lib/client";
type Props = { type Props = {
filters: DealsFilters; filtersForm: UseFormReturnType<DealsFiltersForm>;
selectedProject: ProjectSchema | null; selectedProject: ProjectSchema | null;
boardAndStatusEnabled: boolean; boardAndStatusEnabled: boolean;
getOpener: (open: () => void) => ReactNode; getOpener: (open: () => void) => ReactNode;
}; };
const DealsTableFiltersModal = ({ const DealsTableFiltersModal = ({
filters, filtersForm,
selectedProject, selectedProject,
boardAndStatusEnabled, boardAndStatusEnabled,
getOpener, getOpener,
@ -36,34 +37,32 @@ const DealsTableFiltersModal = ({
<NumberInput <NumberInput
label={"ID"} label={"ID"}
placeholder={"Введите ID"} placeholder={"Введите ID"}
value={filters.id} {...filtersForm.getInputProps("id")}
value={filtersForm.values.id}
onChange={value => onChange={value =>
typeof value === "number" typeof value === "number"
? filters.setId(Number(value)) ? filtersForm.setFieldValue("id", Number(value))
: filters.setId(undefined) : filtersForm.setFieldValue("id", undefined)
} }
min={1} min={1}
/> />
<TextInput <TextInput
label={"Название"} label={"Название"}
placeholder={"Введите название"} placeholder={"Введите название"}
defaultValue={filters.name} {...filtersForm.getInputProps("name")}
onChange={event => filters.setName(event.target.value)}
/> />
{boardAndStatusEnabled && ( {boardAndStatusEnabled && (
<> <>
<BoardSelect <BoardSelect
label={"Доска"} label={"Доска"}
value={filters.board} {...filtersForm.getInputProps("board")}
onChange={filters.setBoard}
projectId={selectedProject?.id} projectId={selectedProject?.id}
clearable clearable
/> />
<StatusSelect <StatusSelect
label={"Статус"} label={"Статус"}
value={filters.status} {...filtersForm.getInputProps("status")}
onChange={filters.setStatus} boardId={filtersForm.values.board?.id}
boardId={filters.board?.id}
clearable clearable
/> />
</> </>

View File

@ -1,7 +1,8 @@
import { Dispatch, SetStateAction, useState } from "react"; import { Dispatch, SetStateAction, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { UseFormReturnType } from "@mantine/form";
import useDealsFilters, { import useDealsFilters, {
DealsFilters, DealsFiltersForm,
} from "@/app/deals/hooks/useDealsFilters"; } from "@/app/deals/hooks/useDealsFilters";
import { DealSchema, GetDealsData, PaginationInfoSchema } from "@/lib/client"; import { DealSchema, GetDealsData, PaginationInfoSchema } from "@/lib/client";
import { import {
@ -18,7 +19,8 @@ type Props = {
type ReturnType = { type ReturnType = {
deals: DealSchema[]; deals: DealSchema[];
setDeals: (deals: DealSchema[]) => void; setDeals: (deals: DealSchema[]) => void;
dealsFilters: DealsFilters; dealsFiltersForm: UseFormReturnType<DealsFiltersForm>;
isChangedFilters: boolean;
refetchDeals: () => void; refetchDeals: () => void;
page: number; page: number;
setPage: Dispatch<SetStateAction<number>>; setPage: Dispatch<SetStateAction<number>>;
@ -34,21 +36,22 @@ const useDealsList = ({
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const itemsPerPage = 10; const itemsPerPage = 10;
const dealsFilters = useDealsFilters(); const { form, debouncedName, debouncedId, isChangedFilters } =
useDealsFilters();
const options: Omit<GetDealsData, "url"> = { const options: Omit<GetDealsData, "url"> = {
query: { query: {
page: withPagination ? page : null, page: withPagination ? page : null,
itemsPerPage: withPagination ? itemsPerPage : null, itemsPerPage: withPagination ? itemsPerPage : null,
sortingField: withPagination ? dealsFilters.sortingField : null, sortingField: withPagination ? form.values.sortingField : null,
sortingDirection: withPagination sortingDirection: withPagination
? dealsFilters.sortingDirection ? form.values.sortingDirection
: null, : null,
projectId: withPagination ? projectId : null, projectId: withPagination ? projectId : null,
boardId: withPagination ? dealsFilters.board?.id : boardId, boardId: withPagination ? form.values.board?.id : boardId,
statusId: withPagination ? dealsFilters.status?.id : null, statusId: withPagination ? form.values.status?.id : null,
name: dealsFilters.debouncedName, name: debouncedName,
id: dealsFilters.debouncedId, id: debouncedId,
}, },
}; };
@ -68,7 +71,8 @@ const useDealsList = ({
return { return {
deals: data?.items ?? [], deals: data?.items ?? [],
setDeals, setDeals,
dealsFilters, dealsFiltersForm: form,
isChangedFilters,
refetchDeals: refetch, refetchDeals: refetch,
page, page,
setPage, setPage,