From b5753ed3a2860bdaf61773ed190bcfb9c27916d2 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Fri, 22 Aug 2025 17:04:59 +0400 Subject: [PATCH] feat: drawers registry --- .../deals/components/shared/Board/Board.tsx | 3 +- .../components/shared/BoardMenu/BoardMenu.tsx | 11 +- .../deals/components/shared/Header/Header.tsx | 42 ++++++-- .../StatusColumnHeader/StatusColumnHeader.tsx | 10 +- .../shared/StatusMenu/StatusMenu.tsx | 34 ++++-- src/app/deals/contexts/ProjectsContext.tsx | 8 +- src/app/deals/contexts/StatusesContext.tsx | 38 +------ .../BoardStatusesEditorDrawer.tsx | 80 ++++---------- .../components/CreateStatusButton.tsx | 7 +- .../components/ProjectsDrawerBody.tsx | 32 ++++++ .../components/StatusMobile.tsx | 20 ++-- .../components/StatusesDrawerBody.tsx | 68 ++++++++++++ .../contexts/BoardStatusesContext.tsx | 78 ++++++++++++++ .../ProjectBoardsEditorDrawer.tsx | 80 ++++---------- .../components/BoardMobile.tsx | 10 +- .../components/BoardsDrawerBody.tsx | 71 ++++++++++++ .../components/CreateBoardButton.tsx | 12 ++- .../contexts/ProjectBoardsContext.tsx | 77 +++++++++++++ .../ProjectsEditorDrawer.tsx | 37 +++---- .../components/CreateProjectButton.tsx | 3 +- .../components/ProjectMobile.tsx | 13 ++- src/app/deals/hooks/useDealsAndStatusesDnd.ts | 13 +-- src/app/deals/page.tsx | 24 ++--- src/app/layout.tsx | 45 ++++---- src/drawers/DrawersContext.tsx | 101 ++++++++++++++++++ src/drawers/drawersRegistry.tsx | 11 ++ src/drawers/types.ts | 24 +++++ src/hooks/useProjectsList.ts | 13 ++- 28 files changed, 680 insertions(+), 285 deletions(-) create mode 100644 src/app/deals/drawers/BoardStatusesEditorDrawer/components/ProjectsDrawerBody.tsx create mode 100644 src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusesDrawerBody.tsx create mode 100644 src/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext.tsx create mode 100644 src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardsDrawerBody.tsx create mode 100644 src/app/deals/drawers/ProjectBoardsEditorDrawer/contexts/ProjectBoardsContext.tsx create mode 100644 src/drawers/DrawersContext.tsx create mode 100644 src/drawers/drawersRegistry.tsx create mode 100644 src/drawers/types.ts diff --git a/src/app/deals/components/shared/Board/Board.tsx b/src/app/deals/components/shared/Board/Board.tsx index 60da2a4..60c7104 100644 --- a/src/app/deals/components/shared/Board/Board.tsx +++ b/src/app/deals/components/shared/Board/Board.tsx @@ -13,7 +13,7 @@ type Props = { }; const Board: FC = ({ board }) => { - const { selectedBoard, onUpdateBoard } = useBoardsContext(); + const { selectedBoard, onUpdateBoard, onDeleteBoard } = useBoardsContext(); const isMobile = useIsMobile(); const [isHovered, setIsHovered] = useState(false); @@ -49,6 +49,7 @@ const Board: FC = ({ board }) => { isHovered={ selectedBoard?.id === board.id || isHovered } + onDeleteBoard={onDeleteBoard} board={board} startEditing={startEditing} /> diff --git a/src/app/deals/components/shared/BoardMenu/BoardMenu.tsx b/src/app/deals/components/shared/BoardMenu/BoardMenu.tsx index 134481c..5418283 100644 --- a/src/app/deals/components/shared/BoardMenu/BoardMenu.tsx +++ b/src/app/deals/components/shared/BoardMenu/BoardMenu.tsx @@ -1,18 +1,21 @@ import React, { FC } from "react"; import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react"; import { Box, Group, Menu, Text } from "@mantine/core"; -import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { BoardSchema } from "@/lib/client"; type Props = { board: BoardSchema; startEditing: () => void; + onDeleteBoard: (board: BoardSchema) => void; isHovered?: boolean; }; -const BoardMenu: FC = ({ board, startEditing, isHovered = true }) => { - const { onDeleteBoard } = useBoardsContext(); - +const BoardMenu: FC = ({ + board, + startEditing, + onDeleteBoard, + isHovered = true, +}) => { return ( diff --git a/src/app/deals/components/shared/Header/Header.tsx b/src/app/deals/components/shared/Header/Header.tsx index 27fa351..4f3710c 100644 --- a/src/app/deals/components/shared/Header/Header.tsx +++ b/src/app/deals/components/shared/Header/Header.tsx @@ -6,17 +6,16 @@ import Boards from "@/app/deals/components/shared/Boards/Boards"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; import ProjectSelect from "@/components/selects/ProjectSelect/ProjectSelect"; +import { useDrawersContext } from "@/drawers/DrawersContext"; import useIsMobile from "@/hooks/useIsMobile"; const Header = () => { - const { - projects, - setSelectedProjectId, - selectedProject, - setIsEditorDrawerOpened: setIsProjectsDrawerOpened, - } = useProjectsContext(); - const { setIsEditorDrawerOpened } = useBoardsContext(); + const { projects, setSelectedProjectId, refetchProjects, selectedProject } = + useProjectsContext(); + const { refetchBoards } = useBoardsContext(); + const { openDrawer } = useDrawersContext(); const isMobile = useIsMobile(); + const getDesktopHeader = () => { return ( { ); }; + const selectProjectId = async (projectId: number | null) => { + await refetchProjects(); + setSelectedProjectId(projectId); + }; + + const openProjectsEditorDrawer = () => { + openDrawer({ + key: "projectsEditorDrawer", + props: { + setSelectedProjectId: selectProjectId, + }, + }); + }; + + const openBoardsEditorDrawer = () => { + if (!selectedProject) return; + openDrawer({ + key: "projectBoardsEditorDrawer", + props: { + project: selectedProject, + }, + onClose: refetchBoards, + }); + }; + const getMobileHeader = () => { return ( <> setIsProjectsDrawerOpened(true)}> + onClick={openProjectsEditorDrawer}> {selectedProject?.name} setIsEditorDrawerOpened(true)}> + onClick={openBoardsEditorDrawer}> diff --git a/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx b/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx index 1244a1a..39e9be5 100644 --- a/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx +++ b/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx @@ -1,10 +1,11 @@ import React, { FC } from "react"; import { Group, Text } from "@mantine/core"; -import styles from "./StatusColumnHeader.module.css"; import StatusMenu from "@/app/deals/components/shared/StatusMenu/StatusMenu"; +import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput"; import { StatusSchema } from "@/lib/client"; +import styles from "./StatusColumnHeader.module.css"; type Props = { status: StatusSchema; @@ -12,7 +13,9 @@ type Props = { }; const StatusColumnHeader: FC = ({ status, isDragging }) => { - const { onUpdateStatus } = useStatusesContext(); + const { onUpdateStatus, onDeleteStatus, refetchStatuses } = + useStatusesContext(); + const { selectedBoard } = useBoardsContext(); const handleSave = (value: string) => { const newValue = value.trim(); @@ -48,8 +51,11 @@ const StatusColumnHeader: FC = ({ status, isDragging }) => { {status.name} )} diff --git a/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx b/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx index af2b532..25d6327 100644 --- a/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx +++ b/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx @@ -6,26 +6,35 @@ import { IconTrash, } from "@tabler/icons-react"; import { Box, Group, Menu, Text } from "@mantine/core"; -import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; +import { useDrawersContext } from "@/drawers/DrawersContext"; import useIsMobile from "@/hooks/useIsMobile"; -import { StatusSchema } from "@/lib/client"; +import { BoardSchema, StatusSchema } from "@/lib/client"; type Props = { status: StatusSchema; handleEdit: () => void; + board: BoardSchema | null; + onDeleteStatus: (status: StatusSchema) => void; + refetchStatuses?: () => void; + withChangeOrderButton?: boolean; }; -const StatusMenu: FC = ({ status, handleEdit }) => { +const StatusMenu: FC = ({ + status, + handleEdit, + board, + onDeleteStatus, + refetchStatuses, + withChangeOrderButton = true, +}) => { const isMobile = useIsMobile(); - const { onDeleteStatus, setIsEditorDrawerOpened } = useStatusesContext(); + const { openDrawer } = useDrawersContext(); return ( e.stopPropagation()}> @@ -51,11 +60,18 @@ const StatusMenu: FC = ({ status, handleEdit }) => { Удалить - {isMobile && ( + {isMobile && withChangeOrderButton && ( { e.stopPropagation(); - setIsEditorDrawerOpened(true); + if (!board) return; + openDrawer({ + key: "boardStatusesEditorDrawer", + props: { + board, + }, + onClose: refetchStatuses, + }); }}> diff --git a/src/app/deals/contexts/ProjectsContext.tsx b/src/app/deals/contexts/ProjectsContext.tsx index c8fd6d8..7a2980c 100644 --- a/src/app/deals/contexts/ProjectsContext.tsx +++ b/src/app/deals/contexts/ProjectsContext.tsx @@ -8,12 +8,11 @@ import { ProjectSchema, UpdateProjectSchema } from "@/lib/client"; type ProjectsContextState = { selectedProject: ProjectSchema | null; setSelectedProjectId: React.Dispatch>; + refetchProjects: () => Promise; projects: ProjectSchema[]; onCreateProject: (name: string) => void; onUpdateProject: (projectId: number, project: UpdateProjectSchema) => void; onDeleteProject: (project: ProjectSchema) => void; - isEditorDrawerOpened: boolean; - setIsEditorDrawerOpened: React.Dispatch>; }; const ProjectsContext = createContext( @@ -21,8 +20,6 @@ const ProjectsContext = createContext( ); const useProjectsContextState = () => { - const [isEditorDrawerOpened, setIsEditorDrawerOpened] = - useState(false); const { projects, setProjects, @@ -49,12 +46,11 @@ const useProjectsContextState = () => { return { projects, selectedProject, + refetchProjects, setSelectedProjectId, onCreateProject, onUpdateProject, onDeleteProject, - isEditorDrawerOpened, - setIsEditorDrawerOpened, }; }; diff --git a/src/app/deals/contexts/StatusesContext.tsx b/src/app/deals/contexts/StatusesContext.tsx index 35f1482..f5afa69 100644 --- a/src/app/deals/contexts/StatusesContext.tsx +++ b/src/app/deals/contexts/StatusesContext.tsx @@ -1,36 +1,18 @@ "use client"; -import React, { createContext, FC, useContext, useState } from "react"; -import { useMutation, UseMutationResult } from "@tanstack/react-query"; -import { AxiosError } from "axios"; +import React, { createContext, FC, useContext } from "react"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import useStatusesList from "@/hooks/useStatusesList"; import { useStatusesOperations } from "@/hooks/useStatusesOperations"; -import { - HttpValidationError, - Options, - StatusSchema, - UpdateStatusData, - UpdateStatusResponse, - UpdateStatusSchema, -} from "@/lib/client"; -import { updateStatusMutation } from "@/lib/client/@tanstack/react-query.gen"; -import { notifications } from "@/lib/notifications"; +import { StatusSchema, UpdateStatusSchema } from "@/lib/client"; type StatusesContextState = { statuses: StatusSchema[]; setStatuses: React.Dispatch>; - updateStatus: UseMutationResult< - UpdateStatusResponse, - AxiosError, - Options - >; refetchStatuses: () => void; onCreateStatus: (name: string) => void; onUpdateStatus: (statusId: number, status: UpdateStatusSchema) => void; onDeleteStatus: (status: StatusSchema) => void; - isEditorDrawerOpened: boolean; - setIsEditorDrawerOpened: React.Dispatch>; }; const StatusesContext = createContext( @@ -46,19 +28,6 @@ const useStatusesContextState = () => { } = useStatusesList({ boardId: selectedBoard?.id, }); - const [isEditorDrawerOpened, setIsEditorDrawerOpened] = - useState(false); - - const updateStatus = useMutation({ - ...updateStatusMutation(), - onError: error => { - console.error(error); - notifications.error({ - message: error.response?.data?.detail as string | undefined, - }); - refetchStatuses(); - }, - }); const { onCreateStatus, onUpdateStatus, onDeleteStatus } = useStatusesOperations({ @@ -71,13 +40,10 @@ const useStatusesContextState = () => { return { statuses, setStatuses, - updateStatus, refetchStatuses, onCreateStatus, onUpdateStatus, onDeleteStatus, - isEditorDrawerOpened, - setIsEditorDrawerOpened, }; }; diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/BoardStatusesEditorDrawer.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/BoardStatusesEditorDrawer.tsx index b92ff24..fa755e3 100644 --- a/src/app/deals/drawers/BoardStatusesEditorDrawer/BoardStatusesEditorDrawer.tsx +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/BoardStatusesEditorDrawer.tsx @@ -1,47 +1,21 @@ "use client"; -import React, { FC, ReactNode } from "react"; -import { IconChevronLeft, IconGripVertical } from "@tabler/icons-react"; -import { Box, Center, Divider, Drawer, Group, rem, Text } from "@mantine/core"; -import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; -import CreateStatusButton from "@/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton"; -import StatusMobile from "@/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile"; -import SortableDnd from "@/components/dnd/SortableDnd"; -import { StatusSchema } from "@/lib/client"; +import React, { FC } from "react"; +import { Drawer, rem } from "@mantine/core"; +import StatusesDrawerBody from "@/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusesDrawerBody"; +import { BoardStatusesContextProvider } from "@/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext"; +import { DrawerProps } from "@/drawers/types"; +import { BoardSchema } from "@/lib/client"; -const BoardStatusesEditorDrawer: FC = () => { - const { - statuses, - onUpdateStatus, - isEditorDrawerOpened, - setIsEditorDrawerOpened, - } = useStatusesContext(); - const { selectedBoard } = useBoardsContext(); - const onClose = () => setIsEditorDrawerOpened(false); - - const renderDraggable = () => ( - - - - ); - - const renderStatus = ( - status: StatusSchema, - renderDraggable?: (item: StatusSchema) => ReactNode - ) => { - return ( - - {renderDraggable && renderDraggable(status)} - - - ); - }; - - const onDragEnd = (itemId: number, newLexorank: string) => { - onUpdateStatus(itemId, { lexorank: newLexorank }); - }; +type Props = { + board: BoardSchema; +}; +const BoardStatusesEditorDrawer: FC> = ({ + opened, + onClose, + props: { board }, +}) => { return ( { onClose={onClose} removeScrollProps={{ allowPinchZoom: true }} withCloseButton={false} - opened={isEditorDrawerOpened} + opened={opened} trapFocus={false} styles={{ body: { @@ -59,27 +33,9 @@ const BoardStatusesEditorDrawer: FC = () => { gap: rem(10), }, }}> - - - - -
- {selectedBoard?.name} -
- -
- - - + + +
); }; diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx index 7cbedd4..ec1abd9 100644 --- a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton.tsx @@ -1,10 +1,11 @@ +import { FC } from "react"; import { IconPlus } from "@tabler/icons-react"; import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; -import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; +import { useBoardStatusesContext } from "@/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext"; -const CreateStatusButton = () => { - const { onCreateStatus } = useStatusesContext(); +const CreateStatusButton: FC = () => { + const { onCreateStatus } = useBoardStatusesContext(); const onStartCreating = () => { modals.openContextModal({ diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/ProjectsDrawerBody.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/ProjectsDrawerBody.tsx new file mode 100644 index 0000000..6c3b594 --- /dev/null +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/ProjectsDrawerBody.tsx @@ -0,0 +1,32 @@ +import React, { FC } from "react"; +import { Stack } from "@mantine/core"; +import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; +import CreateProjectButton from "@/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton"; +import ProjectMobile from "@/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile"; + +type Props = { + setSelectedProjectId: (projectId: number | null) => void; + onClose: () => void; +}; + +const ProjectsDrawerBody: FC = ({ setSelectedProjectId, onClose }) => { + const { projects } = useProjectsContext(); + + return ( + <> + + {projects.map((project, index) => ( + + ))} + + + + ); +}; + +export default ProjectsDrawerBody; diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx index d975dab..6bd6ea7 100644 --- a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile.tsx @@ -1,16 +1,17 @@ import React, { FC } from "react"; import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; -import BoardMenu from "@/app/deals/components/shared/BoardMenu/BoardMenu"; -import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; -import { StatusSchema } from "@/lib/client"; +import StatusMenu from "@/app/deals/components/shared/StatusMenu/StatusMenu"; +import { useBoardStatusesContext } from "@/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext"; +import { BoardSchema, StatusSchema } from "@/lib/client"; type Props = { status: StatusSchema; + board: BoardSchema; }; -const StatusMobile: FC = ({ status }) => { - const { onUpdateStatus } = useStatusesContext(); +const StatusMobile: FC = ({ status, board }) => { + const { onUpdateStatus, onDeleteStatus } = useBoardStatusesContext(); const startEditing = () => { modals.openContextModal({ @@ -34,9 +35,12 @@ const StatusMobile: FC = ({ status }) => { {status.name} -
); diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusesDrawerBody.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusesDrawerBody.tsx new file mode 100644 index 0000000..6b71467 --- /dev/null +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusesDrawerBody.tsx @@ -0,0 +1,68 @@ +import React, { FC, ReactNode } from "react"; +import { IconChevronLeft, IconGripVertical } from "@tabler/icons-react"; +import { Box, Center, Divider, Group, Text } from "@mantine/core"; +import CreateStatusButton from "@/app/deals/drawers/BoardStatusesEditorDrawer/components/CreateStatusButton"; +import StatusMobile from "@/app/deals/drawers/BoardStatusesEditorDrawer/components/StatusMobile"; +import { useBoardStatusesContext } from "@/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext"; +import SortableDnd from "@/components/dnd/SortableDnd"; +import { StatusSchema } from "@/lib/client"; + +type Props = { + onClose: () => void; +}; + +const StatusesDrawerBody: FC = ({ onClose }) => { + const { onUpdateStatus, board, statuses } = useBoardStatusesContext(); + + const renderDraggable = () => ( + + + + ); + + const renderStatus = ( + status: StatusSchema, + renderDraggable?: (item: StatusSchema) => ReactNode + ) => { + return ( + + {renderDraggable && renderDraggable(status)} + + + ); + }; + + const onDragEnd = (itemId: number, newLexorank: string) => + onUpdateStatus(itemId, { lexorank: newLexorank }); + + return ( + <> + + + + +
+ {board.name} +
+ +
+ + + + + ); +}; + +export default StatusesDrawerBody; diff --git a/src/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext.tsx b/src/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext.tsx new file mode 100644 index 0000000..b88e546 --- /dev/null +++ b/src/app/deals/drawers/BoardStatusesEditorDrawer/contexts/BoardStatusesContext.tsx @@ -0,0 +1,78 @@ +"use client"; + +import React, { createContext, FC, useContext } from "react"; +import useStatusesList from "@/hooks/useStatusesList"; +import { useStatusesOperations } from "@/hooks/useStatusesOperations"; +import { BoardSchema, StatusSchema, UpdateStatusSchema } from "@/lib/client"; + +type BoardStatusesContextState = { + board: BoardSchema; + statuses: StatusSchema[]; + setStatuses: React.Dispatch>; + refetchStatuses: () => void; + onCreateStatus: (name: string) => void; + onUpdateStatus: (statusId: number, status: UpdateStatusSchema) => void; + onDeleteStatus: (status: StatusSchema) => void; +}; + +const BoardStatusesContext = createContext< + BoardStatusesContextState | undefined +>(undefined); + +type Props = { + board: BoardSchema; +}; + +const useBoardStatusesContextState = ({ board }: Props) => { + const { + statuses, + setStatuses, + refetch: refetchStatuses, + } = useStatusesList({ + boardId: board.id, + }); + + const { onCreateStatus, onUpdateStatus, onDeleteStatus } = + useStatusesOperations({ + statuses, + setStatuses, + refetchStatuses, + boardId: board.id, + }); + + return { + board, + statuses, + setStatuses, + refetchStatuses, + onCreateStatus, + onUpdateStatus, + onDeleteStatus, + }; +}; + +type BoardStatusesContextProviderProps = { + children: React.ReactNode; +} & Props; + +export const BoardStatusesContextProvider: FC< + BoardStatusesContextProviderProps +> = ({ children, ...props }) => { + const state = useBoardStatusesContextState(props); + + return ( + + {children} + + ); +}; + +export const useBoardStatusesContext = () => { + const context = useContext(BoardStatusesContext); + if (!context) { + throw new Error( + "useBoardStatusesContext must be used within a BoardStatusesContextProvider" + ); + } + return context; +}; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx index d471f2f..a72a967 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/ProjectBoardsEditorDrawer.tsx @@ -1,47 +1,21 @@ "use client"; -import React, { FC, ReactNode } from "react"; -import { IconChevronLeft, IconGripVertical } from "@tabler/icons-react"; -import { Box, Center, Divider, Drawer, Group, rem, Text } from "@mantine/core"; -import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; -import BoardMobile from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile"; -import CreateBoardButton from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton"; -import SortableDnd from "@/components/dnd/SortableDnd"; -import { BoardSchema } from "@/lib/client"; +import React, { FC } from "react"; +import { Drawer, rem } from "@mantine/core"; +import BoardsDrawerBody from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardsDrawerBody"; +import { ProjectBoardsContextProvider } from "@/app/deals/drawers/ProjectBoardsEditorDrawer/contexts/ProjectBoardsContext"; +import { DrawerProps } from "@/drawers/types"; +import { ProjectSchema } from "@/lib/client"; -const ProjectBoardsEditorDrawer: FC = () => { - const { - boards, - onUpdateBoard, - isEditorDrawerOpened, - setIsEditorDrawerOpened, - } = useBoardsContext(); - const { selectedProject } = useProjectsContext(); - const onClose = () => setIsEditorDrawerOpened(false); - - const renderDraggable = () => ( - - - - ); - - const renderBoard = ( - board: BoardSchema, - renderDraggable?: (item: BoardSchema) => ReactNode - ) => { - return ( - - {renderDraggable && renderDraggable(board)} - - - ); - }; - - const onDragEnd = (itemId: number, newLexorank: string) => { - onUpdateBoard(itemId, { lexorank: newLexorank }); - }; +type Props = { + project: ProjectSchema; +}; +const ProjectBoardsEditorDrawer: FC> = ({ + onClose, + opened, + props: { project }, +}) => { return ( { onClose={onClose} removeScrollProps={{ allowPinchZoom: true }} withCloseButton={false} - opened={isEditorDrawerOpened} + opened={opened} trapFocus={false} styles={{ body: { @@ -59,27 +33,9 @@ const ProjectBoardsEditorDrawer: FC = () => { gap: rem(10), }, }}> - - - - -
- {selectedProject?.name} -
- -
- - - + + +
); }; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx index 4725ea8..132fd38 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile.tsx @@ -2,16 +2,15 @@ import React, { FC } from "react"; import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; import BoardMenu from "@/app/deals/components/shared/BoardMenu/BoardMenu"; -import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -import { BoardSchema } from "@/lib/client"; +import { BoardSchema, UpdateBoardSchema } from "@/lib/client"; type Props = { board: BoardSchema; + onUpdateBoard: (boardId: number, board: UpdateBoardSchema) => void; + onDeleteBoard: (board: BoardSchema) => void; }; -const BoardMobile: FC = ({ board }) => { - const { onUpdateBoard } = useBoardsContext(); - +const BoardMobile: FC = ({ board, onUpdateBoard, onDeleteBoard }) => { const startEditing = () => { modals.openContextModal({ modal: "enterNameModal", @@ -37,6 +36,7 @@ const BoardMobile: FC = ({ board }) => { ); diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardsDrawerBody.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardsDrawerBody.tsx new file mode 100644 index 0000000..8d55e58 --- /dev/null +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardsDrawerBody.tsx @@ -0,0 +1,71 @@ +import React, { FC, ReactNode } from "react"; +import { IconChevronLeft, IconGripVertical } from "@tabler/icons-react"; +import { Box, Center, Divider, Group, Text } from "@mantine/core"; +import BoardMobile from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/BoardMobile"; +import CreateBoardButton from "@/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton"; +import { useProjectBoardsContext } from "@/app/deals/drawers/ProjectBoardsEditorDrawer/contexts/ProjectBoardsContext"; +import SortableDnd from "@/components/dnd/SortableDnd"; +import { BoardSchema } from "@/lib/client"; + +type Props = { + onClose: () => void; +}; + +const BoardsDrawerBody: FC = ({ onClose }) => { + const { boards, onUpdateBoard, onDeleteBoard, project, onCreateBoard } = + useProjectBoardsContext(); + + const renderDraggable = () => ( + + + + ); + + const renderBoard = ( + board: BoardSchema, + renderDraggable?: (item: BoardSchema) => ReactNode + ) => { + return ( + + {renderDraggable && renderDraggable(board)} + + + ); + }; + + const onDragEnd = (itemId: number, newLexorank: string) => { + onUpdateBoard(itemId, { lexorank: newLexorank }); + }; + + return ( + <> + + + + +
+ {project.name} +
+ +
+ + + + + ); +}; + +export default BoardsDrawerBody; diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx index f53c162..36fe736 100644 --- a/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/components/CreateBoardButton.tsx @@ -1,11 +1,13 @@ +import { FC } from "react"; import { IconPlus } from "@tabler/icons-react"; import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; -import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -const CreateBoardButton = () => { - const { onCreateBoard } = useBoardsContext(); +type Props = { + onCreateBoard: (name: string) => void; +}; +const CreateBoardButton: FC = ({ onCreateBoard }) => { const onStartCreating = () => { modals.openContextModal({ modal: "enterNameModal", @@ -18,7 +20,9 @@ const CreateBoardButton = () => { }; return ( - + Создать доску diff --git a/src/app/deals/drawers/ProjectBoardsEditorDrawer/contexts/ProjectBoardsContext.tsx b/src/app/deals/drawers/ProjectBoardsEditorDrawer/contexts/ProjectBoardsContext.tsx new file mode 100644 index 0000000..1ac17a5 --- /dev/null +++ b/src/app/deals/drawers/ProjectBoardsEditorDrawer/contexts/ProjectBoardsContext.tsx @@ -0,0 +1,77 @@ +"use client"; + +import React, { createContext, FC, useContext } from "react"; +import useBoardsList from "@/hooks/useBoardsList"; +import { useBoardsOperations } from "@/hooks/useBoardsOperations"; +import { BoardSchema, ProjectSchema, UpdateBoardSchema } from "@/lib/client"; + +type ProjectBoardsContextState = { + boards: BoardSchema[]; + setBoards: React.Dispatch>; + project: ProjectSchema; + refetchBoards: () => void; + onCreateBoard: (name: string) => void; + onUpdateBoard: (boardId: number, board: UpdateBoardSchema) => void; + onDeleteBoard: (board: BoardSchema) => void; +}; + +const ProjectBoardsContext = createContext< + ProjectBoardsContextState | undefined +>(undefined); + +type Props = { + project: ProjectSchema; +}; + +const useProjectBoardsContextState = ({ project }: Props) => { + const { + boards, + setBoards, + refetch: refetchBoards, + } = useBoardsList({ projectId: project?.id }); + + const { onCreateBoard, onUpdateBoard, onDeleteBoard } = useBoardsOperations( + { + boards, + setBoards, + refetchBoards, + projectId: project?.id, + } + ); + + return { + boards, + setBoards, + project, + refetchBoards, + onCreateBoard, + onUpdateBoard, + onDeleteBoard, + }; +}; + +type ProjectBoardsContextProviderProps = { + children: React.ReactNode; +} & Props; + +export const ProjectBoardsContextProvider: FC< + ProjectBoardsContextProviderProps +> = ({ children, ...props }) => { + const state = useProjectBoardsContextState(props); + + return ( + + {children} + + ); +}; + +export const useProjectBoardsContext = () => { + const context = useContext(ProjectBoardsContext); + if (!context) { + throw new Error( + "useProjectBoardsContext must be used within a ProjectBoardsContextProvider" + ); + } + return context; +}; diff --git a/src/app/deals/drawers/ProjectsEditorDrawer/ProjectsEditorDrawer.tsx b/src/app/deals/drawers/ProjectsEditorDrawer/ProjectsEditorDrawer.tsx index 8e50550..aa7ba1e 100644 --- a/src/app/deals/drawers/ProjectsEditorDrawer/ProjectsEditorDrawer.tsx +++ b/src/app/deals/drawers/ProjectsEditorDrawer/ProjectsEditorDrawer.tsx @@ -1,16 +1,20 @@ "use client"; import React, { FC } from "react"; -import { Center, Divider, Drawer, rem, Stack, Text } from "@mantine/core"; -import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; -import CreateProjectButton from "@/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton"; -import ProjectMobile from "@/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile"; +import { Center, Divider, Drawer, rem, Text } from "@mantine/core"; +import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext"; +import ProjectsDrawerBody from "@/app/deals/drawers/BoardStatusesEditorDrawer/components/ProjectsDrawerBody"; +import { DrawerProps } from "@/drawers/types"; -const ProjectsEditorDrawer: FC = () => { - const { projects, isEditorDrawerOpened, setIsEditorDrawerOpened } = - useProjectsContext(); - const onClose = () => setIsEditorDrawerOpened(false); +type Props = { + setSelectedProjectId: (projectId: number | null) => void; +}; +const ProjectsEditorDrawer: FC> = ({ + opened, + onClose, + props: { setSelectedProjectId }, +}) => { return ( { onClose={onClose} removeScrollProps={{ allowPinchZoom: true }} withCloseButton={false} - opened={isEditorDrawerOpened} + opened={opened} trapFocus={false} styles={{ body: { @@ -32,15 +36,12 @@ const ProjectsEditorDrawer: FC = () => { Проекты - - {projects.map((project, index) => ( - - ))} - - + + + ); }; diff --git a/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx b/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx index b7b9e90..bb07c9f 100644 --- a/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx +++ b/src/app/deals/drawers/ProjectsEditorDrawer/components/CreateProjectButton.tsx @@ -1,9 +1,10 @@ +import { FC } from "react"; import { IconPlus } from "@tabler/icons-react"; import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; -const CreateProjectButton = () => { +const CreateProjectButton: FC = () => { const { onCreateProject } = useProjectsContext(); const onStartCreating = () => { diff --git a/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx b/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx index 330ab58..7c8ffce 100644 --- a/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx +++ b/src/app/deals/drawers/ProjectsEditorDrawer/components/ProjectMobile.tsx @@ -8,11 +8,16 @@ import styles from "./../ProjectsEditorDrawer.module.css"; type Props = { project: ProjectSchema; + setSelectedProjectId: (projectId: number | null) => void; + closeDrawer: () => void; }; -const ProjectMobile: FC = ({ project }) => { - const { onUpdateProject, setSelectedProjectId, setIsEditorDrawerOpened } = - useProjectsContext(); +const ProjectMobile: FC = ({ + project, + setSelectedProjectId, + closeDrawer, +}) => { + const { onUpdateProject } = useProjectsContext(); const startEditing = () => { modals.openContextModal({ @@ -28,7 +33,7 @@ const ProjectMobile: FC = ({ project }) => { const onClick = () => { setSelectedProjectId(project.id); - setIsEditorDrawerOpened(false); + closeDrawer(); }; return ( diff --git a/src/app/deals/hooks/useDealsAndStatusesDnd.ts b/src/app/deals/hooks/useDealsAndStatusesDnd.ts index e26e10b..b03aefb 100644 --- a/src/app/deals/hooks/useDealsAndStatusesDnd.ts +++ b/src/app/deals/hooks/useDealsAndStatusesDnd.ts @@ -24,7 +24,7 @@ const useDealsAndStatusesDnd = (): ReturnType => { const swiperRef = useRef(null); const [activeDeal, setActiveDeal] = useState(null); const [activeStatus, setActiveStatus] = useState(null); - const { statuses, setStatuses, updateStatus } = useStatusesContext(); + const { statuses, setStatuses, onUpdateStatus } = useStatusesContext(); const { deals, setDeals, updateDeal } = useDealsContext(); const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]); const isMobile = useIsMobile(); @@ -229,16 +229,7 @@ const useDealsAndStatusesDnd = (): ReturnType => { }; const onStatusDragEnd = (statusId: number, lexorank: string) => { - updateStatus.mutate({ - path: { - statusId, - }, - body: { - status: { - lexorank, - }, - }, - }); + onUpdateStatus(statusId, { lexorank }); }; const handleDealDragEnd = (activeId: number | string, over: Over) => { diff --git a/src/app/deals/page.tsx b/src/app/deals/page.tsx index 94b900a..69ae860 100644 --- a/src/app/deals/page.tsx +++ b/src/app/deals/page.tsx @@ -1,34 +1,28 @@ +import { Space } from "@mantine/core"; import Funnel from "@/app/deals/components/shared/Funnel/Funnel"; import Header from "@/app/deals/components/shared/Header/Header"; import { BoardsContextProvider } from "@/app/deals/contexts/BoardsContext"; import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext"; import { StatusesContextProvider } from "@/app/deals/contexts/StatusesContext"; -import BoardStatusesEditorDrawer from "@/app/deals/drawers/BoardStatusesEditorDrawer"; -import ProjectBoardsEditorDrawer from "@/app/deals/drawers/ProjectBoardsEditorDrawer"; -import ProjectsEditorDrawer from "@/app/deals/drawers/ProjectsEditorDrawer"; import PageBlock from "@/components/layout/PageBlock/PageBlock"; import PageContainer from "@/components/layout/PageContainer/PageContainer"; import { DealsContextProvider } from "./contexts/DealsContext"; -import { Space } from "@mantine/core"; export default function DealsPage() { return ( - - -
- - + + + +
+ - - - - - - + + + ); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b07d8cf..a549e62 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -19,6 +19,7 @@ import AppShellMainWrapper from "@/components/layout/AppShellWrappers/AppShellMa import AppShellNavbarWrapper from "@/components/layout/AppShellWrappers/AppShellNavbarWrapper"; import Footer from "@/components/layout/Footer/Footer"; import Navbar from "@/components/layout/Navbar/Navbar"; +import { DrawersContextProvider } from "@/drawers/DrawersContext"; import { modals } from "@/modals/modals"; import { ReactQueryProvider } from "@/providers/ReactQueryProvider"; import ReduxProvider from "@/providers/ReduxProvider"; @@ -64,27 +65,29 @@ export default function RootLayout({ children }: Props) { - - - - - - {children} - - -