feat: delete pipeline stage (#1412)
* feat: delete pipeline stage Closes #1396 * refactor: code review - Use string literal instead of enum * docs: disable CircularProgressBar Chromatic snapshots
This commit is contained in:
@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_PIPELINE_STAGE = gql`
|
||||
mutation DeletePipelineStage($where: PipelineStageWhereUniqueInput!) {
|
||||
pipelineStage: deleteOnePipelineStage(where: $where) {
|
||||
id
|
||||
name
|
||||
color
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -2,7 +2,10 @@ import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import type { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
||||
import { useCreatePipelineStageMutation } from '~/generated/graphql';
|
||||
import {
|
||||
useCreatePipelineStageMutation,
|
||||
useDeletePipelineStageMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_PIPELINES } from '../graphql/queries/getPipelines';
|
||||
import { currentPipelineState } from '../states/currentPipelineState';
|
||||
@ -11,6 +14,7 @@ export const usePipelineStages = () => {
|
||||
const currentPipeline = useRecoilValue(currentPipelineState);
|
||||
|
||||
const [createPipelineStageMutation] = useCreatePipelineStageMutation();
|
||||
const [deletePipelineStageMutation] = useDeletePipelineStageMutation();
|
||||
|
||||
const handlePipelineStageAdd = async (boardColumn: BoardColumnDefinition) => {
|
||||
if (!currentPipeline?.id) return;
|
||||
@ -30,5 +34,14 @@ export const usePipelineStages = () => {
|
||||
});
|
||||
};
|
||||
|
||||
return { handlePipelineStageAdd };
|
||||
const handlePipelineStageDelete = async (boardColumnId: string) => {
|
||||
if (!currentPipeline?.id) return;
|
||||
|
||||
return deletePipelineStageMutation({
|
||||
variables: { where: { id: boardColumnId } },
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return { handlePipelineStageAdd, handlePipelineStageDelete };
|
||||
};
|
||||
|
||||
@ -54,6 +54,7 @@ const StyledNumChildren = styled.div`
|
||||
export type BoardColumnProps = {
|
||||
color: string;
|
||||
title: string;
|
||||
onDelete?: (id: string) => void;
|
||||
onTitleEdit: (title: string, color: string) => void;
|
||||
totalAmount?: number;
|
||||
children: React.ReactNode;
|
||||
@ -65,6 +66,7 @@ export type BoardColumnProps = {
|
||||
export function BoardColumn({
|
||||
color,
|
||||
title,
|
||||
onDelete,
|
||||
onTitleEdit,
|
||||
totalAmount,
|
||||
children,
|
||||
@ -102,6 +104,7 @@ export function BoardColumn({
|
||||
{isBoardColumnMenuOpen && (
|
||||
<BoardColumnMenu
|
||||
onClose={handleClose}
|
||||
onDelete={onDelete}
|
||||
onTitleEdit={onTitleEdit}
|
||||
title={title}
|
||||
color={color}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress';
|
||||
@ -7,7 +8,7 @@ import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSear
|
||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||
import { IconPencil, IconPlus } from '@/ui/icon';
|
||||
import { IconPencil, IconPlus, IconTrash } from '@/ui/icon';
|
||||
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||
@ -19,6 +20,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { boardColumnsState } from '../states/boardColumnsState';
|
||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||
|
||||
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||
@ -30,22 +32,30 @@ const StyledMenuContainer = styled.div`
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
color: string;
|
||||
onClose: () => void;
|
||||
onDelete?: (id: string) => void;
|
||||
onTitleEdit: (title: string, color: string) => void;
|
||||
stageId: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type Menu = 'actions' | 'add' | 'title';
|
||||
|
||||
export function BoardColumnMenu({
|
||||
onClose,
|
||||
onTitleEdit,
|
||||
title,
|
||||
color,
|
||||
onClose,
|
||||
onDelete,
|
||||
onTitleEdit,
|
||||
stageId,
|
||||
title,
|
||||
}: OwnProps) {
|
||||
const [openMenu, setOpenMenu] = useState('actions');
|
||||
const [currentMenu, setCurrentMenu] = useState('actions');
|
||||
|
||||
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||
|
||||
const boardColumnMenuRef = useRef(null);
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const createCompanyProgress = useCreateCompanyProgress();
|
||||
|
||||
@ -70,23 +80,31 @@ export function BoardColumnMenu({
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
goBackToPreviousHotkeyScope();
|
||||
onClose();
|
||||
}
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
function setMenu(menu: string) {
|
||||
const closeMenu = useCallback(() => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
onClose();
|
||||
}, [goBackToPreviousHotkeyScope, onClose]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
setBoardColumns((previousBoardColumns) =>
|
||||
previousBoardColumns.filter((column) => column.id !== stageId),
|
||||
);
|
||||
onDelete?.(stageId);
|
||||
closeMenu();
|
||||
}, [closeMenu, onDelete, setBoardColumns, stageId]);
|
||||
|
||||
function setMenu(menu: Menu) {
|
||||
if (menu === 'add') {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
);
|
||||
}
|
||||
setOpenMenu(menu);
|
||||
setCurrentMenu(menu);
|
||||
}
|
||||
const [searchFilter] = useRecoilScopedState(
|
||||
relationPickerSearchFilterScopedState,
|
||||
@ -108,19 +126,26 @@ export function BoardColumnMenu({
|
||||
return (
|
||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||
<StyledDropdownMenu>
|
||||
{openMenu === 'actions' && (
|
||||
{currentMenu === 'actions' && (
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<DropdownMenuSelectableItem onClick={() => setMenu('title')}>
|
||||
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
||||
Rename
|
||||
</DropdownMenuSelectableItem>
|
||||
<DropdownMenuSelectableItem
|
||||
disabled={boardColumns.length <= 1}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<IconTrash size={icon.size.md} stroke={icon.stroke.sm} />
|
||||
Delete
|
||||
</DropdownMenuSelectableItem>
|
||||
<DropdownMenuSelectableItem onClick={() => setMenu('add')}>
|
||||
<IconPlus size={icon.size.md} stroke={icon.stroke.sm} />
|
||||
New opportunity
|
||||
</DropdownMenuSelectableItem>
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
)}
|
||||
{openMenu === 'title' && (
|
||||
{currentMenu === 'title' && (
|
||||
<BoardColumnEditTitleMenu
|
||||
color={color}
|
||||
onClose={closeMenu}
|
||||
@ -128,8 +153,7 @@ export function BoardColumnMenu({
|
||||
title={title}
|
||||
/>
|
||||
)}
|
||||
|
||||
{openMenu === 'add' && (
|
||||
{currentMenu === 'add' && (
|
||||
<SingleEntitySelect
|
||||
onEntitySelected={(value) => handleCompanySelected(value)}
|
||||
onCancel={closeMenu}
|
||||
|
||||
@ -43,16 +43,18 @@ const StyledWrapper = styled.div`
|
||||
|
||||
export function EntityBoard({
|
||||
boardOptions,
|
||||
updateSorts,
|
||||
onColumnAdd,
|
||||
onColumnDelete,
|
||||
onEditColumnTitle,
|
||||
onStageAdd,
|
||||
updateSorts,
|
||||
}: {
|
||||
boardOptions: BoardOptions;
|
||||
onColumnAdd?: (boardColumn: BoardColumnDefinition) => void;
|
||||
onColumnDelete?: (boardColumnId: string) => void;
|
||||
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
|
||||
updateSorts: (
|
||||
sorts: Array<SelectedSortType<PipelineProgressOrderByWithRelationInput>>,
|
||||
) => void;
|
||||
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
|
||||
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
|
||||
}) {
|
||||
const [boardColumns] = useRecoilState(boardColumnsState);
|
||||
const setCardSelected = useSetCardSelected();
|
||||
@ -133,7 +135,7 @@ export function EntityBoard({
|
||||
viewIcon={<IconList size={theme.icon.size.md} />}
|
||||
availableSorts={boardOptions.sorts}
|
||||
onSortsUpdate={updateSorts}
|
||||
onStageAdd={onStageAdd}
|
||||
onStageAdd={onColumnAdd}
|
||||
context={CompanyBoardRecoilScopeContext}
|
||||
/>
|
||||
<ScrollWrapper>
|
||||
@ -148,7 +150,8 @@ export function EntityBoard({
|
||||
<EntityBoardColumn
|
||||
boardOptions={boardOptions}
|
||||
column={column}
|
||||
onEditColumnTitle={onEditColumnTitle}
|
||||
onTitleEdit={onEditColumnTitle}
|
||||
onDelete={onColumnDelete}
|
||||
/>
|
||||
</RecoilScope>
|
||||
</BoardColumnIdContext.Provider>
|
||||
|
||||
@ -50,11 +50,13 @@ const BoardColumnCardsContainer = ({
|
||||
export function EntityBoardColumn({
|
||||
column,
|
||||
boardOptions,
|
||||
onEditColumnTitle,
|
||||
onDelete,
|
||||
onTitleEdit,
|
||||
}: {
|
||||
column: BoardColumnDefinition;
|
||||
boardOptions: BoardOptions;
|
||||
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
|
||||
onDelete?: (columnId: string) => void;
|
||||
onTitleEdit: (columnId: string, title: string, color: string) => void;
|
||||
}) {
|
||||
const boardColumnId = useContext(BoardColumnIdContext) ?? '';
|
||||
|
||||
@ -66,15 +68,16 @@ export function EntityBoardColumn({
|
||||
boardCardIdsByColumnIdFamilyState(boardColumnId ?? ''),
|
||||
);
|
||||
|
||||
function handleEditColumnTitle(title: string, color: string) {
|
||||
onEditColumnTitle(boardColumnId, title, color);
|
||||
function handleTitleEdit(title: string, color: string) {
|
||||
onTitleEdit(boardColumnId, title, color);
|
||||
}
|
||||
|
||||
return (
|
||||
<Droppable droppableId={column.id}>
|
||||
{(droppableProvided) => (
|
||||
<BoardColumn
|
||||
onTitleEdit={handleEditColumnTitle}
|
||||
onTitleEdit={handleTitleEdit}
|
||||
onDelete={onDelete}
|
||||
title={column.title}
|
||||
color={column.colorCode}
|
||||
totalAmount={boardColumnTotal}
|
||||
|
||||
@ -11,27 +11,23 @@ const meta: Meta<typeof CircularProgressBar> = {
|
||||
args: {
|
||||
size: 50,
|
||||
},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof CircularProgressBar>;
|
||||
const args = {};
|
||||
const defaultArgTypes = {
|
||||
control: false,
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
args,
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const Catalog = {
|
||||
args: {
|
||||
...args,
|
||||
},
|
||||
argTypes: {
|
||||
strokeWidth: defaultArgTypes,
|
||||
segmentColor: defaultArgTypes,
|
||||
strokeWidth: { control: false },
|
||||
segmentColor: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
catalog: {
|
||||
|
||||
Reference in New Issue
Block a user