diff --git a/src/app/deals/components/mobile/GroupMenu/GroupMenu.tsx b/src/app/deals/components/mobile/GroupMenu/GroupMenu.tsx new file mode 100644 index 0000000..5932697 --- /dev/null +++ b/src/app/deals/components/mobile/GroupMenu/GroupMenu.tsx @@ -0,0 +1,41 @@ +import React, { FC } from "react"; +import { IconCheckbox, IconDotsVertical, IconTrash } from "@tabler/icons-react"; +import { Box, Menu } from "@mantine/core"; +import DropdownMenuItem from "@/components/ui/DropdownMenuItem/DropdownMenuItem"; +import ThemeIcon from "@/components/ui/ThemeIcon/ThemeIcon"; + +type Props = { + onDelete: () => void; + startDealsSelecting: () => void; +}; + +const GroupMenu: FC = ({ onDelete, startDealsSelecting }) => { + return ( + + + e.stopPropagation()}> + + + + + + + } + label={"Удалить группу"} + /> + } + label={"Добавить/удалить сделки"} + /> + + + ); +}; + +export default GroupMenu; diff --git a/src/app/deals/components/shared/DealCard/DealCard.tsx b/src/app/deals/components/shared/DealCard/DealCard.tsx index e5be5a6..e5e499e 100644 --- a/src/app/deals/components/shared/DealCard/DealCard.tsx +++ b/src/app/deals/components/shared/DealCard/DealCard.tsx @@ -1,10 +1,11 @@ import { IconCategoryPlus } from "@tabler/icons-react"; import classNames from "classnames"; import { useContextMenu } from "mantine-contextmenu"; -import { Box, Card, Group, Pill, Stack, Text } from "@mantine/core"; +import { Box, Card, Group, Stack, Text } from "@mantine/core"; import { useDealsContext } from "@/app/deals/contexts/DealsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; import { useDrawersContext } from "@/drawers/DrawersContext"; +import useIsMobile from "@/hooks/utils/useIsMobile"; import { DealSchema } from "@/lib/client"; import { ModuleNames } from "@/modules/modules"; import styles from "./DealCard.module.css"; @@ -18,11 +19,11 @@ const DealCard = ({ deal, isInGroup = false }: Props) => { const { selectedProject, modulesSet } = useProjectsContext(); const { dealsCrud, refetchDeals, groupDealsSelection } = useDealsContext(); const { openDrawer } = useDrawersContext(); + const isMobile = useIsMobile(); const onClick = () => { if (groupDealsSelection.isDealsSelecting) { - if (groupDealsSelection.selectedBaseDealId !== deal.id) - groupDealsSelection.toggleDeal(deal.id); + groupDealsSelection.toggleDeal(deal); return; } @@ -40,17 +41,18 @@ const DealCard = ({ deal, isInGroup = false }: Props) => { const { showContextMenu } = useContextMenu(); - const dealContextMenu = deal.group - ? [] - : [ - { - key: "startGroupForming", - onClick: () => - groupDealsSelection.startSelectingWithDeal(deal.id), - title: "Создать группу", - icon: , - }, - ]; + const dealContextMenu = + deal.group || isMobile + ? [] + : [ + { + key: "startGroupForming", + onClick: () => + groupDealsSelection.startSelectingWithDeal(deal.id), + title: "Создать группу", + icon: , + }, + ]; const getSelectedStyles = () => { if (groupDealsSelection.selectedBaseDealId === deal.id) { @@ -98,10 +100,6 @@ const DealCard = ({ deal, isInGroup = false }: Props) => { )} - - Срочно - Бесплатно - ); diff --git a/src/app/deals/components/shared/DealsGroup/DealsGroup.tsx b/src/app/deals/components/shared/DealsGroup/DealsGroup.tsx index d4088e2..1fdb491 100644 --- a/src/app/deals/components/shared/DealsGroup/DealsGroup.tsx +++ b/src/app/deals/components/shared/DealsGroup/DealsGroup.tsx @@ -4,8 +4,10 @@ import classNames from "classnames"; import { useContextMenu } from "mantine-contextmenu"; import { Flex, Stack, TextInput } from "@mantine/core"; import { useDebouncedValue } from "@mantine/hooks"; +import GroupMenu from "@/app/deals/components/mobile/GroupMenu/GroupMenu"; import DealCard from "@/app/deals/components/shared/DealCard/DealCard"; import { useDealsContext } from "@/app/deals/contexts/DealsContext"; +import useIsMobile from "@/hooks/utils/useIsMobile"; import GroupWithDealsSchema from "@/types/GroupWithDealsSchema"; import styles from "./DealsGroup.module.css"; @@ -16,36 +18,43 @@ type Props = { const DealsGroup: FC = ({ group }) => { const [groupName, setGroupName] = useState(group.name ?? ""); const [debouncedGroupName] = useDebouncedValue(groupName, 600); - const { groupsCrud, groupDealsSelection } = useDealsContext(); + const { + groupsCrud, + groupDealsSelection: { + startSelectingWithExistingGroup, + selectedGroupId, + }, + } = useDealsContext(); const { showContextMenu } = useContextMenu(); + const isMobile = useIsMobile(); useEffect(() => { if (debouncedGroupName === group.name) return; groupsCrud.onUpdate(group.id, { name: debouncedGroupName }); }, [debouncedGroupName]); - const dealContextMenu = [ - { - key: "delete", - onClick: () => groupsCrud.onDelete(group.id), - title: "Удалить группу", - icon: , - }, - { - key: "startDealsSelecting", - onClick: () => - groupDealsSelection.startSelectingWithExistingGroup(group), - title: "Добавить/удалить сделки", - icon: , - }, - ]; + const dealContextMenu = isMobile + ? [] + : [ + { + key: "delete", + onClick: () => groupsCrud.onDelete(group.id), + title: "Удалить группу", + icon: , + }, + { + key: "startDealsSelecting", + onClick: () => startSelectingWithExistingGroup(group), + title: "Добавить/удалить сделки", + icon: , + }, + ]; return ( = ({ group }) => { onContextMenu={showContextMenu(dealContextMenu)}> + align={"center"} + w={"100%"}> setGroupName(e.target.value)} variant={"unstyled"} onKeyDown={e => e.stopPropagation()} + flex={1} /> + {isMobile && ( + + startSelectingWithExistingGroup(group) + } + onDelete={() => groupsCrud.onDelete(group.id)} + /> + )} {group.items.map(deal => ( { const { groupDealsSelection: { - selectedBaseDealId, - selectedGroupId, finishDealsSelecting, cancelDealsSelecting, + isDealsSelecting, }, } = useDealsContext(); @@ -19,7 +18,7 @@ const GroupDealsSelectionAffix = () => { + mounted={isDealsSelecting}> {transitionStyles => ( { Отмена - + Сохранить diff --git a/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx b/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx index 85123b9..9f1b43b 100644 --- a/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx +++ b/src/app/deals/components/shared/StatusColumnHeader/StatusColumnHeader.tsx @@ -2,6 +2,7 @@ import React, { FC } from "react"; import { Group, Text } from "@mantine/core"; import StatusMenu from "@/app/deals/components/shared/StatusMenu/StatusMenu"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; +import { useDealsContext } from "@/app/deals/contexts/DealsContext"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import InPlaceInput from "@/components/ui/InPlaceInput/InPlaceInput"; import { StatusSchema } from "@/lib/client"; @@ -14,6 +15,7 @@ type Props = { const StatusColumnHeader: FC = ({ status, isDragging }) => { const { statusesCrud, refetchStatuses } = useStatusesContext(); const { selectedBoard } = useBoardsContext(); + const { groupDealsSelection } = useDealsContext(); const handleSave = (value: string) => { const newValue = value.trim(); @@ -59,6 +61,9 @@ const StatusColumnHeader: FC = ({ status, isDragging }) => { } refetchStatuses={refetchStatuses} onDeleteStatus={statusesCrud.onDelete} + startDealsSelecting={ + groupDealsSelection.startSelecting + } /> )} diff --git a/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx b/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx index 78e5697..be2d1e9 100644 --- a/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx +++ b/src/app/deals/components/shared/StatusMenu/StatusMenu.tsx @@ -1,5 +1,6 @@ import React, { FC } from "react"; import { + IconCheckbox, IconDotsVertical, IconEdit, IconExchange, @@ -21,6 +22,7 @@ type Props = { onStatusColorChange: (color: string) => void; board: BoardSchema | null; onDeleteStatus: (status: StatusSchema) => void; + startDealsSelecting?: () => void; refetchStatuses?: () => void; withChangeOrderButton?: boolean; }; @@ -31,6 +33,7 @@ const StatusMenu: FC = ({ onStatusColorChange, board, onDeleteStatus, + startDealsSelecting, refetchStatuses, withChangeOrderButton = true, }) => { @@ -96,6 +99,13 @@ const StatusMenu: FC = ({ label={"Изменить порядок"} /> )} + {isMobile && startDealsSelecting && ( + } + label={"Создать группу сделок"} + /> + )} ); diff --git a/src/app/deals/drawers/StatusesMobileEditorDrawer/components/StatusMobile.tsx b/src/app/deals/drawers/StatusesMobileEditorDrawer/components/StatusMobile.tsx index 5901f89..8a03365 100644 --- a/src/app/deals/drawers/StatusesMobileEditorDrawer/components/StatusMobile.tsx +++ b/src/app/deals/drawers/StatusesMobileEditorDrawer/components/StatusMobile.tsx @@ -2,6 +2,7 @@ import React, { FC } from "react"; import { Box, Group, Text } from "@mantine/core"; import { modals } from "@mantine/modals"; import StatusMenu from "@/app/deals/components/shared/StatusMenu/StatusMenu"; +import { useDealsContext } from "@/app/deals/contexts/DealsContext"; import { useStatusesMobileContext } from "@/app/deals/drawers/StatusesMobileEditorDrawer/contexts/BoardStatusesContext"; import { BoardSchema, StatusSchema } from "@/lib/client"; @@ -12,6 +13,7 @@ type Props = { const StatusMobile: FC = ({ status, board }) => { const { statusesCrud } = useStatusesMobileContext(); + const { groupDealsSelection } = useDealsContext(); const startEditing = () => { modals.openContextModal({ @@ -40,8 +42,11 @@ const StatusMobile: FC = ({ status, board }) => { board={board} onDeleteStatus={statusesCrud.onDelete} handleEdit={startEditing} - onStatusColorChange={color => statusesCrud.onUpdate(status.id, { color })} + onStatusColorChange={color => + statusesCrud.onUpdate(status.id, { color }) + } withChangeOrderButton={false} + startDealsSelecting={groupDealsSelection.startSelecting} /> ); diff --git a/src/app/deals/hooks/useGroupDealsSelection.tsx b/src/app/deals/hooks/useGroupDealsSelection.tsx index 653fd9f..a0629eb 100644 --- a/src/app/deals/hooks/useGroupDealsSelection.tsx +++ b/src/app/deals/hooks/useGroupDealsSelection.tsx @@ -1,5 +1,6 @@ import { Dispatch, SetStateAction, useState } from "react"; import { GroupsCrud } from "@/hooks/cruds/useDealGroupCrud"; +import { DealSchema } from "@/lib/client"; import GroupWithDealsSchema from "@/types/GroupWithDealsSchema"; type Props = { @@ -12,9 +13,10 @@ export type GroupDealsSelection = { startSelectingWithDeal: (dealId: number) => void; selectedGroupId: number | null; startSelectingWithExistingGroup: (group: GroupWithDealsSchema) => void; + startSelecting: () => void; selectedDealIds: Set; setSelectedDealIds: Dispatch>>; - toggleDeal: (dealId: number) => void; + toggleDeal: (deal: DealSchema) => void; finishDealsSelecting: () => void; cancelDealsSelecting: () => void; }; @@ -29,11 +31,18 @@ const useGroupDealsSelection = ({ groupsCrud }: Props): GroupDealsSelection => { ); const [selectedGroupId, setSelectedGroupId] = useState(null); - const toggleDeal = (dealId: number) => { - if (selectedDealIds.has(dealId)) { - selectedDealIds.delete(dealId); + const toggleDeal = (deal: DealSchema) => { + if (selectedBaseDealId === deal.id) return; + + if (selectedDealIds.has(deal.id)) { + selectedDealIds.delete(deal.id); } else { - selectedDealIds.add(dealId); + if (!selectedBaseDealId && !selectedGroupId) { + if (deal.group) return; + setSelectedBaseDealId(deal.id); + return; + } + selectedDealIds.add(deal.id); } setSelectedDealIds(new Set(selectedDealIds)); }; @@ -63,15 +72,27 @@ const useGroupDealsSelection = ({ groupsCrud }: Props): GroupDealsSelection => { setIsDealsSelecting(false); }; + // For editing group const startSelectingWithExistingGroup = (group: GroupWithDealsSchema) => { setSelectedDealIds(new Set(group.items.map(item => item.id))); + setSelectedBaseDealId(null); setSelectedGroupId(group.id); setIsDealsSelecting(true); }; + // For creating group on desktop const startSelectingWithDeal = (dealId: number) => { setSelectedDealIds(new Set([dealId])); setSelectedBaseDealId(dealId); + setSelectedGroupId(null); + setIsDealsSelecting(true); + }; + + // For creating group on mobile + const startSelecting = () => { + setSelectedDealIds(new Set()); + setSelectedBaseDealId(null); + setSelectedGroupId(null); setIsDealsSelecting(true); }; @@ -81,6 +102,7 @@ const useGroupDealsSelection = ({ groupsCrud }: Props): GroupDealsSelection => { startSelectingWithDeal, selectedGroupId, startSelectingWithExistingGroup, + startSelecting, selectedDealIds, setSelectedDealIds, toggleDeal,