diff --git a/src/app/deals/components/desktop/DealsTable/DealsTable.tsx b/src/app/deals/components/desktop/DealsTable/DealsTable.tsx index ac016fb..4640742 100644 --- a/src/app/deals/components/desktop/DealsTable/DealsTable.tsx +++ b/src/app/deals/components/desktop/DealsTable/DealsTable.tsx @@ -9,14 +9,8 @@ import { useDrawersContext } from "@/drawers/DrawersContext"; import { DealSchema } from "@/lib/client"; const DealsTable: FC = () => { - const { - deals, - paginationInfo, - page, - setPage, - dealsFiltersForm, - dealsCrud, - } = useDealsContext(); + const { deals, paginationInfo, page, setPage, sortingForm, dealsCrud } = + useDealsContext(); const { selectedProject } = useProjectsContext(); const { openDrawer } = useDrawersContext(); @@ -40,20 +34,20 @@ const DealsTable: FC = () => { return ( + h={"calc(100vh - var(--mantine-spacing-xl) * 4)"}> { - dealsFiltersForm.setFieldValue( + sortingForm.setFieldValue( "sortingField", sorting.columnAccessor ); - dealsFiltersForm.setFieldValue( + sortingForm.setFieldValue( "sortingDirection", sorting.direction ); diff --git a/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx b/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx index 3287418..1062e58 100644 --- a/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx +++ b/src/app/deals/components/desktop/TopToolPanel/TopToolPanel.tsx @@ -35,7 +35,7 @@ const TopToolPanel: FC = ({ view, setView }) => { title: "Создание проекта", withCloseButton: true, innerProps: { - onComplete: projectsCrud.onCreate, + onChange: values => projectsCrud.onCreate(values.name), }, }); }; @@ -49,9 +49,15 @@ const TopToolPanel: FC = ({ view, setView }) => { }); }; + const viewFiltersModalMap = { + table: "dealsTableFiltersModal", + board: "dealsBoardFiltersModal", + schedule: "dealsScheduleFiltersModal", + }; + const onFiltersClick = () => { modals.openContextModal({ - modal: "dealsFiltersModal", + modal: viewFiltersModalMap[view], title: "Фильтры", withCloseButton: true, innerProps: { diff --git a/src/app/deals/components/shared/Board/Board.tsx b/src/app/deals/components/shared/Board/Board.tsx index a682fd6..c16a5a4 100644 --- a/src/app/deals/components/shared/Board/Board.tsx +++ b/src/app/deals/components/shared/Board/Board.tsx @@ -29,8 +29,8 @@ const Board: FC = ({ board }) => { onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> + value={board.name} + onChange={value => boardsCrud.onUpdate(board.id, { name: value }) } inputStyles={{ diff --git a/src/app/deals/components/shared/CreateBoardButton/CreateBoardButton.tsx b/src/app/deals/components/shared/CreateBoardButton/CreateBoardButton.tsx index 3c5b92d..c3b8aed 100644 --- a/src/app/deals/components/shared/CreateBoardButton/CreateBoardButton.tsx +++ b/src/app/deals/components/shared/CreateBoardButton/CreateBoardButton.tsx @@ -11,7 +11,7 @@ const CreateBoardButton = () => { ( { className={styles["inner-container"]}> (
= ({ status, isDragging }) => { mb={"xs"} className={styles.header}> handleSave(value)} + value={status.name} + onChange={value => handleSave(value)} inputStyles={{ input: { height: 25, diff --git a/src/app/deals/contexts/DealsContext.tsx b/src/app/deals/contexts/DealsContext.tsx index 1b305a1..c0f751c 100644 --- a/src/app/deals/contexts/DealsContext.tsx +++ b/src/app/deals/contexts/DealsContext.tsx @@ -6,6 +6,7 @@ import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters"; import { DealsCrud, useDealsCrud } from "@/hooks/cruds/useDealsCrud"; import useDealsList from "@/hooks/lists/useDealsList"; +import { SortingForm } from "@/hooks/utils/useSorting"; import { DealSchema, PaginationInfoSchema } from "@/lib/client"; import makeContext from "@/lib/contextFactory/contextFactory"; @@ -19,6 +20,7 @@ type DealsContextState = { setPage: React.Dispatch>; dealsFiltersForm: UseFormReturnType; isChangedFilters: boolean; + sortingForm: UseFormReturnType; }; type Props = { diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx index 9ae6dce..2f66201 100644 --- a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx @@ -13,7 +13,7 @@ const CreateStatusButton: FC = () => { title: "Создание колонки", withCloseButton: true, innerProps: { - onComplete: statusesCrud.onCreate, + onChange: values => statusesCrud.onCreate(values.name), }, }); }; diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx index 92acfaf..61d5308 100644 --- a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx @@ -19,8 +19,8 @@ const StatusMobile: FC = ({ status, board }) => { title: "Редактирование статуса", withCloseButton: true, innerProps: { - onComplete: name => statusesCrud.onUpdate(status.id, { name }), - defaultValue: status.name, + onChange: values => statusesCrud.onUpdate(status.id, values), + value: status, }, }); }; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx index 6325169..584d9a0 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx @@ -18,8 +18,8 @@ const BoardMobile: FC = ({ board }) => { title: "Редактирование доски", withCloseButton: true, innerProps: { - onComplete: name => boardsCrud.onUpdate(board.id, { name }), - defaultValue: board.name, + onChange: values => boardsCrud.onUpdate(board.id, values), + value: board, }, }); }; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx index 36fe736..f30099b 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx @@ -14,7 +14,7 @@ const CreateBoardButton: FC = ({ onCreateBoard }) => { title: "Создание доски", withCloseButton: true, innerProps: { - onComplete: onCreateBoard, + onChange: values => onCreateBoard(values.name), }, }); }; diff --git a/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx b/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx index 5503bdd..e88de67 100644 --- a/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx +++ b/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx @@ -13,7 +13,7 @@ const CreateProjectButton: FC = () => { title: "Создание проекта", withCloseButton: true, innerProps: { - onComplete: projectsCrud.onCreate, + onChange: values => projectsCrud.onCreate(values.name), }, }); }; diff --git a/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx b/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx index 62b244b..c8fddb1 100644 --- a/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx +++ b/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx @@ -25,8 +25,8 @@ const ProjectMobile: FC = ({ title: "Редактирование проекта", withCloseButton: true, innerProps: { - onComplete: name => projectsCrud.onUpdate(project.id, { name }), - defaultValue: project.name, + onChange: values => projectsCrud.onUpdate(project.id, values), + value: project, }, }); }; diff --git a/src/app/deals/hooks/useDealsFilters.ts b/src/app/deals/hooks/useDealsFilters.ts index ac397a3..37091d4 100644 --- a/src/app/deals/hooks/useDealsFilters.ts +++ b/src/app/deals/hooks/useDealsFilters.ts @@ -1,14 +1,12 @@ import { isEqual } from "lodash"; import { useForm, UseFormReturnType } from "@mantine/form"; -import { BoardSchema, SortDir, StatusSchema } from "@/lib/client"; +import { BoardSchema, StatusSchema } from "@/lib/client"; export type DealsFiltersForm = { id: number | null; name: string; board: BoardSchema | null; status: StatusSchema | null; - sortingField?: string; - sortingDirection: SortDir; }; type ReturnType = { @@ -17,20 +15,18 @@ type ReturnType = { }; const useDealsFilters = (): ReturnType => { - const initialValues = { + const initialFilters = { id: null, board: null, status: null, name: "", - sortingField: "createdAt", - sortingDirection: "asc" as SortDir, }; const form = useForm({ - initialValues, + initialValues: initialFilters, }); - const isChangedFilters = !isEqual(form.values, initialValues); + const isChangedFilters = !isEqual(form.values, initialFilters); return { form, diff --git a/src/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal.tsx b/src/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal.tsx new file mode 100644 index 0000000..a41ea95 --- /dev/null +++ b/src/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { + Button, + Flex, + NumberInput, + rem, + Space, + Text, + TextInput, +} from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { ContextModalProps } from "@mantine/modals"; +import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters"; +import { ProjectSchema } from "@/lib/client"; + +type Props = { + value: DealsFiltersForm; + onChange: (values: DealsFiltersForm) => void; + project: ProjectSchema | null; +}; + +const DealsBoardFiltersModal = ({ + id, + context, + innerProps, +}: ContextModalProps) => { + const filtersForm = useForm({ + initialValues: innerProps.value, + }); + + const onSubmit = (values: DealsFiltersForm) => { + innerProps.onChange(values); + context.closeModal(id); + }; + + return ( +
+ + + typeof value === "number" + ? filtersForm.setFieldValue("id", Number(value)) + : filtersForm.setFieldValue("id", null) + } + hideControls + min={1} + /> + + + + +
+ ); +}; + +export default DealsBoardFiltersModal; diff --git a/src/app/deals/modals/DealsFiltersModal/DealsFiltersModal.tsx b/src/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal.tsx similarity index 71% rename from src/app/deals/modals/DealsFiltersModal/DealsFiltersModal.tsx rename to src/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal.tsx index 862f120..5525d41 100644 --- a/src/app/deals/modals/DealsFiltersModal/DealsFiltersModal.tsx +++ b/src/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal.tsx @@ -20,10 +20,9 @@ type Props = { value: DealsFiltersForm; onChange: (values: DealsFiltersForm) => void; project: ProjectSchema | null; - boardAndStatusEnabled: boolean; }; -const DealsFiltersModal = ({ +const DealsScheduleFiltersModal = ({ id, context, innerProps, @@ -60,23 +59,19 @@ const DealsFiltersModal = ({ placeholder={"Введите название"} {...filtersForm.getInputProps("name")} /> - {innerProps.boardAndStatusEnabled && ( - <> - - - - )} + + + + + ); +}; + +export default DealsTableFiltersModal; diff --git a/src/components/ui/InPlaceInput/InPlaceInput.tsx b/src/components/ui/InPlaceInput/InPlaceInput.tsx index cd4cefc..74266a4 100644 --- a/src/components/ui/InPlaceInput/InPlaceInput.tsx +++ b/src/components/ui/InPlaceInput/InPlaceInput.tsx @@ -5,8 +5,8 @@ import InPlaceInputDesktop from "./InPlaceInputDesktop"; import InPlaceInputMobile from "./InPlaceInputMobile"; type Props = { - defaultValue?: string; - onComplete: (value: string) => void; + value?: string; + onChange: (value: string) => void; placeholder?: string; getChildren: (startEditing: () => void) => ReactNode; inputStyles?: Styles; diff --git a/src/components/ui/InPlaceInput/InPlaceInputDesktop.tsx b/src/components/ui/InPlaceInput/InPlaceInputDesktop.tsx index 62870c2..c3eb5f6 100644 --- a/src/components/ui/InPlaceInput/InPlaceInputDesktop.tsx +++ b/src/components/ui/InPlaceInput/InPlaceInputDesktop.tsx @@ -3,22 +3,22 @@ import { TextInput } from "@mantine/core"; import { Styles } from "@mantine/core/lib/core/styles-api/styles-api.types"; type Props = { - defaultValue?: string; - onComplete: (value: string) => void; + value?: string; + onChange: (value: string) => void; placeholder?: string; getChildren: (startEditing: () => void) => ReactNode; inputStyles?: Styles; }; const InPlaceInputDesktop: FC = ({ - onComplete, + onChange, placeholder, inputStyles, getChildren, - defaultValue = "", + value = "", }) => { const [isWriting, setIsWriting] = useState(false); - const [value, setValue] = useState(defaultValue); + const [localValue, setLocalValue] = useState(value); const inputRef = useRef(null); useEffect(() => { @@ -41,22 +41,22 @@ const InPlaceInputDesktop: FC = ({ document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); - }, [isWriting, value]); + }, [isWriting, localValue]); const onStartCreating = () => { - setValue(defaultValue); + setLocalValue(localValue); setIsWriting(true); }; const onCancelCreating = () => { - setValue(defaultValue); + setLocalValue(localValue); setIsWriting(false); }; const onCompleteCreating = () => { - const localValue = value.trim(); - if (localValue) { - onComplete(localValue); + const val = localValue.trim(); + if (val) { + onChange(val); } setIsWriting(false); }; @@ -67,8 +67,8 @@ const InPlaceInputDesktop: FC = ({ ref={inputRef} placeholder={placeholder} variant={"unstyled"} - value={value} - onChange={e => setValue(e.target.value)} + value={localValue} + onChange={e => setLocalValue(e.target.value)} onKeyDown={e => { e.stopPropagation(); if (e.key === "Enter") onCompleteCreating(); diff --git a/src/components/ui/InPlaceInput/InPlaceInputMobile.tsx b/src/components/ui/InPlaceInput/InPlaceInputMobile.tsx index 109de3f..feed9da 100644 --- a/src/components/ui/InPlaceInput/InPlaceInputMobile.tsx +++ b/src/components/ui/InPlaceInput/InPlaceInputMobile.tsx @@ -2,17 +2,17 @@ import { FC, ReactNode } from "react"; import { modals } from "@mantine/modals"; type Props = { - defaultValue?: string; - onComplete: (value: string) => void; + value?: string; + onChange: (value: string) => void; getChildren: (startEditing: () => void) => ReactNode; modalTitle?: string; }; const InPlaceInputMobile: FC = ({ - onComplete, + onChange, getChildren, modalTitle = "", - defaultValue = "", + value = "", }) => { const onStartCreating = () => { modals.openContextModal({ @@ -20,8 +20,8 @@ const InPlaceInputMobile: FC = ({ title: modalTitle, withCloseButton: true, innerProps: { - onComplete, - defaultValue, + onChange: values => onChange(values.name), + value: { name: value }, }, }); }; diff --git a/src/hooks/lists/useDealsList.ts b/src/hooks/lists/useDealsList.ts index d304ee0..98d11b0 100644 --- a/src/hooks/lists/useDealsList.ts +++ b/src/hooks/lists/useDealsList.ts @@ -5,6 +5,7 @@ import { useDebouncedValue } from "@mantine/hooks"; import useDealsFilters, { DealsFiltersForm, } from "@/app/deals/hooks/useDealsFilters"; +import useSorting, { SortingForm } from "@/hooks/utils/useSorting"; import { DealSchema, GetDealsData, PaginationInfoSchema } from "@/lib/client"; import { getDealsOptions, @@ -22,6 +23,7 @@ type ReturnType = { setDeals: (deals: DealSchema[]) => void; dealsFiltersForm: UseFormReturnType; isChangedFilters: boolean; + sortingForm: UseFormReturnType; refetchDeals: () => void; page: number; setPage: Dispatch>; @@ -37,22 +39,32 @@ const useDealsList = ({ const queryClient = useQueryClient(); const [page, setPage] = useState(1); const itemsPerPage = 10; - const { form, isChangedFilters } = useDealsFilters(); + const { form: dealsFiltersForm, isChangedFilters } = useDealsFilters(); + const { form: sortingForm } = useSorting(); - const [debouncedId] = useDebouncedValue(form.values.id, 300); - const [debouncedName] = useDebouncedValue(form.values.name, 300); + const [debouncedId] = useDebouncedValue(dealsFiltersForm.values.id, 300); + const [debouncedName] = useDebouncedValue( + dealsFiltersForm.values.name, + 300 + ); const options: Omit = { query: { page: withPagination ? page : null, itemsPerPage: withPagination ? itemsPerPage : null, - sortingField: withPagination ? form.values.sortingField : null, + sortingField: withPagination + ? sortingForm.values.sortingField + : null, sortingDirection: withPagination - ? form.values.sortingDirection + ? sortingForm.values.sortingDirection : null, projectId: withPagination ? projectId : null, - boardId: withPagination ? form.values.board?.id : boardId, - statusId: withPagination ? form.values.status?.id : null, + boardId: withPagination + ? dealsFiltersForm.values.board?.id + : boardId, + statusId: withPagination + ? dealsFiltersForm.values.status?.id + : null, name: debouncedName, id: debouncedId, }, @@ -72,7 +84,8 @@ const useDealsList = ({ return { deals: data?.items ?? [], setDeals, - dealsFiltersForm: form, + dealsFiltersForm, + sortingForm, isChangedFilters, refetchDeals: refetch, page, diff --git a/src/hooks/utils/useSorting.ts b/src/hooks/utils/useSorting.ts new file mode 100644 index 0000000..09d2f84 --- /dev/null +++ b/src/hooks/utils/useSorting.ts @@ -0,0 +1,28 @@ +import { useForm, UseFormReturnType } from "@mantine/form"; +import { SortDir } from "@/lib/client"; + +export type SortingForm = { + sortingField?: string; + sortingDirection: SortDir; +}; + +type ReturnType = { + form: UseFormReturnType; +}; + +const useSorting = (): ReturnType => { + const initialFilters = { + sortingField: "createdAt", + sortingDirection: "asc" as SortDir, + }; + + const form = useForm({ + initialValues: initialFilters, + }); + + return { + form, + }; +}; + +export default useSorting; diff --git a/src/modals/EnterNameModal/EnterNameModal.tsx b/src/modals/EnterNameModal/EnterNameModal.tsx index 7a803a8..3059f9d 100644 --- a/src/modals/EnterNameModal/EnterNameModal.tsx +++ b/src/modals/EnterNameModal/EnterNameModal.tsx @@ -4,23 +4,23 @@ import { Button, Flex, rem, TextInput } from "@mantine/core"; import { useForm } from "@mantine/form"; import { ContextModalProps } from "@mantine/modals"; -type Props = { - onComplete: (value: string) => void; - defaultValue?: string; -}; - type FormType = { name: string; }; +type Props = { + onChange: (value: FormType) => void; + value?: FormType; +}; + const EnterNameModal = ({ id, context, innerProps, }: ContextModalProps) => { const form = useForm({ - initialValues: { - name: innerProps.defaultValue ?? "", + initialValues: innerProps.value || { + name: "", }, validate: { name: name => !name && "Введите название", @@ -28,7 +28,7 @@ const EnterNameModal = ({ }); const onSubmit = (values: FormType) => { - innerProps.onComplete(values.name); + innerProps.onChange(values); context.closeModal(id); }; diff --git a/src/modals/modals.ts b/src/modals/modals.ts index 261f119..e8dd16f 100644 --- a/src/modals/modals.ts +++ b/src/modals/modals.ts @@ -1,7 +1,11 @@ -import DealsFiltersModal from "@/app/deals/modals/DealsFiltersModal/DealsFiltersModal"; +import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal"; +import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal"; +import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal"; import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal"; export const modals = { enterNameModal: EnterNameModal, - dealsFiltersModal: DealsFiltersModal, + dealsTableFiltersModal: DealsTableFiltersModal, + dealsBoardFiltersModal: DealsBoardFiltersModal, + dealsScheduleFiltersModal: DealsScheduleFiltersModal, };