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:
@ -1016,6 +1016,7 @@ export type Mutation = {
|
|||||||
deleteManyView: AffectedRows;
|
deleteManyView: AffectedRows;
|
||||||
deleteManyViewFilter: AffectedRows;
|
deleteManyViewFilter: AffectedRows;
|
||||||
deleteManyViewSort: AffectedRows;
|
deleteManyViewSort: AffectedRows;
|
||||||
|
deleteOnePipelineStage: PipelineStage;
|
||||||
deleteOneView: View;
|
deleteOneView: View;
|
||||||
deleteUserAccount: User;
|
deleteUserAccount: User;
|
||||||
deleteWorkspaceMember: WorkspaceMember;
|
deleteWorkspaceMember: WorkspaceMember;
|
||||||
@ -1186,6 +1187,11 @@ export type MutationDeleteManyViewSortArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationDeleteOnePipelineStageArgs = {
|
||||||
|
where: PipelineStageWhereUniqueInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationDeleteOneViewArgs = {
|
export type MutationDeleteOneViewArgs = {
|
||||||
where: ViewWhereUniqueInput;
|
where: ViewWhereUniqueInput;
|
||||||
};
|
};
|
||||||
@ -3325,6 +3331,13 @@ export type DeleteManyPipelineProgressMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type DeleteManyPipelineProgressMutation = { __typename?: 'Mutation', deleteManyPipelineProgress: { __typename?: 'AffectedRows', count: number } };
|
export type DeleteManyPipelineProgressMutation = { __typename?: 'Mutation', deleteManyPipelineProgress: { __typename?: 'AffectedRows', count: number } };
|
||||||
|
|
||||||
|
export type DeletePipelineStageMutationVariables = Exact<{
|
||||||
|
where: PipelineStageWhereUniqueInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type DeletePipelineStageMutation = { __typename?: 'Mutation', pipelineStage: { __typename?: 'PipelineStage', id: string, name: string, color: string } };
|
||||||
|
|
||||||
export type UpdateOnePipelineProgressMutationVariables = Exact<{
|
export type UpdateOnePipelineProgressMutationVariables = Exact<{
|
||||||
data: PipelineProgressUpdateInput;
|
data: PipelineProgressUpdateInput;
|
||||||
where: PipelineProgressWhereUniqueInput;
|
where: PipelineProgressWhereUniqueInput;
|
||||||
@ -5554,6 +5567,41 @@ export function useDeleteManyPipelineProgressMutation(baseOptions?: Apollo.Mutat
|
|||||||
export type DeleteManyPipelineProgressMutationHookResult = ReturnType<typeof useDeleteManyPipelineProgressMutation>;
|
export type DeleteManyPipelineProgressMutationHookResult = ReturnType<typeof useDeleteManyPipelineProgressMutation>;
|
||||||
export type DeleteManyPipelineProgressMutationResult = Apollo.MutationResult<DeleteManyPipelineProgressMutation>;
|
export type DeleteManyPipelineProgressMutationResult = Apollo.MutationResult<DeleteManyPipelineProgressMutation>;
|
||||||
export type DeleteManyPipelineProgressMutationOptions = Apollo.BaseMutationOptions<DeleteManyPipelineProgressMutation, DeleteManyPipelineProgressMutationVariables>;
|
export type DeleteManyPipelineProgressMutationOptions = Apollo.BaseMutationOptions<DeleteManyPipelineProgressMutation, DeleteManyPipelineProgressMutationVariables>;
|
||||||
|
export const DeletePipelineStageDocument = gql`
|
||||||
|
mutation DeletePipelineStage($where: PipelineStageWhereUniqueInput!) {
|
||||||
|
pipelineStage: deleteOnePipelineStage(where: $where) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type DeletePipelineStageMutationFn = Apollo.MutationFunction<DeletePipelineStageMutation, DeletePipelineStageMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useDeletePipelineStageMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useDeletePipelineStageMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useDeletePipelineStageMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [deletePipelineStageMutation, { data, loading, error }] = useDeletePipelineStageMutation({
|
||||||
|
* variables: {
|
||||||
|
* where: // value for 'where'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useDeletePipelineStageMutation(baseOptions?: Apollo.MutationHookOptions<DeletePipelineStageMutation, DeletePipelineStageMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<DeletePipelineStageMutation, DeletePipelineStageMutationVariables>(DeletePipelineStageDocument, options);
|
||||||
|
}
|
||||||
|
export type DeletePipelineStageMutationHookResult = ReturnType<typeof useDeletePipelineStageMutation>;
|
||||||
|
export type DeletePipelineStageMutationResult = Apollo.MutationResult<DeletePipelineStageMutation>;
|
||||||
|
export type DeletePipelineStageMutationOptions = Apollo.BaseMutationOptions<DeletePipelineStageMutation, DeletePipelineStageMutationVariables>;
|
||||||
export const UpdateOnePipelineProgressDocument = gql`
|
export const UpdateOnePipelineProgressDocument = gql`
|
||||||
mutation UpdateOnePipelineProgress($data: PipelineProgressUpdateInput!, $where: PipelineProgressWhereUniqueInput!) {
|
mutation UpdateOnePipelineProgress($data: PipelineProgressUpdateInput!, $where: PipelineProgressWhereUniqueInput!) {
|
||||||
updateOnePipelineProgress(where: $where, data: $data) {
|
updateOnePipelineProgress(where: $where, data: $data) {
|
||||||
|
|||||||
@ -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 { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import type { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
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 { GET_PIPELINES } from '../graphql/queries/getPipelines';
|
||||||
import { currentPipelineState } from '../states/currentPipelineState';
|
import { currentPipelineState } from '../states/currentPipelineState';
|
||||||
@ -11,6 +14,7 @@ export const usePipelineStages = () => {
|
|||||||
const currentPipeline = useRecoilValue(currentPipelineState);
|
const currentPipeline = useRecoilValue(currentPipelineState);
|
||||||
|
|
||||||
const [createPipelineStageMutation] = useCreatePipelineStageMutation();
|
const [createPipelineStageMutation] = useCreatePipelineStageMutation();
|
||||||
|
const [deletePipelineStageMutation] = useDeletePipelineStageMutation();
|
||||||
|
|
||||||
const handlePipelineStageAdd = async (boardColumn: BoardColumnDefinition) => {
|
const handlePipelineStageAdd = async (boardColumn: BoardColumnDefinition) => {
|
||||||
if (!currentPipeline?.id) return;
|
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 = {
|
export type BoardColumnProps = {
|
||||||
color: string;
|
color: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
onDelete?: (id: string) => void;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
totalAmount?: number;
|
totalAmount?: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -65,6 +66,7 @@ export type BoardColumnProps = {
|
|||||||
export function BoardColumn({
|
export function BoardColumn({
|
||||||
color,
|
color,
|
||||||
title,
|
title,
|
||||||
|
onDelete,
|
||||||
onTitleEdit,
|
onTitleEdit,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
children,
|
children,
|
||||||
@ -102,6 +104,7 @@ export function BoardColumn({
|
|||||||
{isBoardColumnMenuOpen && (
|
{isBoardColumnMenuOpen && (
|
||||||
<BoardColumnMenu
|
<BoardColumnMenu
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
onDelete={onDelete}
|
||||||
onTitleEdit={onTitleEdit}
|
onTitleEdit={onTitleEdit}
|
||||||
title={title}
|
title={title}
|
||||||
color={color}
|
color={color}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress';
|
import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress';
|
||||||
@ -7,7 +8,7 @@ import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSear
|
|||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
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 { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
|
||||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { boardColumnsState } from '../states/boardColumnsState';
|
||||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||||
|
|
||||||
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||||
@ -30,22 +32,30 @@ const StyledMenuContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onClose: () => void;
|
|
||||||
title: string;
|
|
||||||
color: string;
|
color: string;
|
||||||
|
onClose: () => void;
|
||||||
|
onDelete?: (id: string) => void;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
stageId: string;
|
stageId: string;
|
||||||
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Menu = 'actions' | 'add' | 'title';
|
||||||
|
|
||||||
export function BoardColumnMenu({
|
export function BoardColumnMenu({
|
||||||
onClose,
|
|
||||||
onTitleEdit,
|
|
||||||
title,
|
|
||||||
color,
|
color,
|
||||||
|
onClose,
|
||||||
|
onDelete,
|
||||||
|
onTitleEdit,
|
||||||
stageId,
|
stageId,
|
||||||
|
title,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [openMenu, setOpenMenu] = useState('actions');
|
const [currentMenu, setCurrentMenu] = useState('actions');
|
||||||
|
|
||||||
|
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||||
|
|
||||||
const boardColumnMenuRef = useRef(null);
|
const boardColumnMenuRef = useRef(null);
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const createCompanyProgress = useCreateCompanyProgress();
|
const createCompanyProgress = useCreateCompanyProgress();
|
||||||
|
|
||||||
@ -70,23 +80,31 @@ export function BoardColumnMenu({
|
|||||||
closeMenu();
|
closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMenu() {
|
|
||||||
goBackToPreviousHotkeyScope();
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
goBackToPreviousHotkeyScope,
|
goBackToPreviousHotkeyScope,
|
||||||
} = usePreviousHotkeyScope();
|
} = 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') {
|
if (menu === 'add') {
|
||||||
setHotkeyScopeAndMemorizePreviousScope(
|
setHotkeyScopeAndMemorizePreviousScope(
|
||||||
RelationPickerHotkeyScope.RelationPicker,
|
RelationPickerHotkeyScope.RelationPicker,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setOpenMenu(menu);
|
setCurrentMenu(menu);
|
||||||
}
|
}
|
||||||
const [searchFilter] = useRecoilScopedState(
|
const [searchFilter] = useRecoilScopedState(
|
||||||
relationPickerSearchFilterScopedState,
|
relationPickerSearchFilterScopedState,
|
||||||
@ -108,19 +126,26 @@ export function BoardColumnMenu({
|
|||||||
return (
|
return (
|
||||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||||
<StyledDropdownMenu>
|
<StyledDropdownMenu>
|
||||||
{openMenu === 'actions' && (
|
{currentMenu === 'actions' && (
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuSelectableItem onClick={() => setMenu('title')}>
|
<DropdownMenuSelectableItem onClick={() => setMenu('title')}>
|
||||||
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
||||||
Rename
|
Rename
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
|
<DropdownMenuSelectableItem
|
||||||
|
disabled={boardColumns.length <= 1}
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
<IconTrash size={icon.size.md} stroke={icon.stroke.sm} />
|
||||||
|
Delete
|
||||||
|
</DropdownMenuSelectableItem>
|
||||||
<DropdownMenuSelectableItem onClick={() => setMenu('add')}>
|
<DropdownMenuSelectableItem onClick={() => setMenu('add')}>
|
||||||
<IconPlus size={icon.size.md} stroke={icon.stroke.sm} />
|
<IconPlus size={icon.size.md} stroke={icon.stroke.sm} />
|
||||||
New opportunity
|
New opportunity
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
)}
|
)}
|
||||||
{openMenu === 'title' && (
|
{currentMenu === 'title' && (
|
||||||
<BoardColumnEditTitleMenu
|
<BoardColumnEditTitleMenu
|
||||||
color={color}
|
color={color}
|
||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
@ -128,8 +153,7 @@ export function BoardColumnMenu({
|
|||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{currentMenu === 'add' && (
|
||||||
{openMenu === 'add' && (
|
|
||||||
<SingleEntitySelect
|
<SingleEntitySelect
|
||||||
onEntitySelected={(value) => handleCompanySelected(value)}
|
onEntitySelected={(value) => handleCompanySelected(value)}
|
||||||
onCancel={closeMenu}
|
onCancel={closeMenu}
|
||||||
|
|||||||
@ -43,16 +43,18 @@ const StyledWrapper = styled.div`
|
|||||||
|
|
||||||
export function EntityBoard({
|
export function EntityBoard({
|
||||||
boardOptions,
|
boardOptions,
|
||||||
updateSorts,
|
onColumnAdd,
|
||||||
|
onColumnDelete,
|
||||||
onEditColumnTitle,
|
onEditColumnTitle,
|
||||||
onStageAdd,
|
updateSorts,
|
||||||
}: {
|
}: {
|
||||||
boardOptions: BoardOptions;
|
boardOptions: BoardOptions;
|
||||||
|
onColumnAdd?: (boardColumn: BoardColumnDefinition) => void;
|
||||||
|
onColumnDelete?: (boardColumnId: string) => void;
|
||||||
|
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
|
||||||
updateSorts: (
|
updateSorts: (
|
||||||
sorts: Array<SelectedSortType<PipelineProgressOrderByWithRelationInput>>,
|
sorts: Array<SelectedSortType<PipelineProgressOrderByWithRelationInput>>,
|
||||||
) => void;
|
) => void;
|
||||||
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
|
|
||||||
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
|
|
||||||
}) {
|
}) {
|
||||||
const [boardColumns] = useRecoilState(boardColumnsState);
|
const [boardColumns] = useRecoilState(boardColumnsState);
|
||||||
const setCardSelected = useSetCardSelected();
|
const setCardSelected = useSetCardSelected();
|
||||||
@ -133,7 +135,7 @@ export function EntityBoard({
|
|||||||
viewIcon={<IconList size={theme.icon.size.md} />}
|
viewIcon={<IconList size={theme.icon.size.md} />}
|
||||||
availableSorts={boardOptions.sorts}
|
availableSorts={boardOptions.sorts}
|
||||||
onSortsUpdate={updateSorts}
|
onSortsUpdate={updateSorts}
|
||||||
onStageAdd={onStageAdd}
|
onStageAdd={onColumnAdd}
|
||||||
context={CompanyBoardRecoilScopeContext}
|
context={CompanyBoardRecoilScopeContext}
|
||||||
/>
|
/>
|
||||||
<ScrollWrapper>
|
<ScrollWrapper>
|
||||||
@ -148,7 +150,8 @@ export function EntityBoard({
|
|||||||
<EntityBoardColumn
|
<EntityBoardColumn
|
||||||
boardOptions={boardOptions}
|
boardOptions={boardOptions}
|
||||||
column={column}
|
column={column}
|
||||||
onEditColumnTitle={onEditColumnTitle}
|
onTitleEdit={onEditColumnTitle}
|
||||||
|
onDelete={onColumnDelete}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
</BoardColumnIdContext.Provider>
|
</BoardColumnIdContext.Provider>
|
||||||
|
|||||||
@ -50,11 +50,13 @@ const BoardColumnCardsContainer = ({
|
|||||||
export function EntityBoardColumn({
|
export function EntityBoardColumn({
|
||||||
column,
|
column,
|
||||||
boardOptions,
|
boardOptions,
|
||||||
onEditColumnTitle,
|
onDelete,
|
||||||
|
onTitleEdit,
|
||||||
}: {
|
}: {
|
||||||
column: BoardColumnDefinition;
|
column: BoardColumnDefinition;
|
||||||
boardOptions: BoardOptions;
|
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) ?? '';
|
const boardColumnId = useContext(BoardColumnIdContext) ?? '';
|
||||||
|
|
||||||
@ -66,15 +68,16 @@ export function EntityBoardColumn({
|
|||||||
boardCardIdsByColumnIdFamilyState(boardColumnId ?? ''),
|
boardCardIdsByColumnIdFamilyState(boardColumnId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleEditColumnTitle(title: string, color: string) {
|
function handleTitleEdit(title: string, color: string) {
|
||||||
onEditColumnTitle(boardColumnId, title, color);
|
onTitleEdit(boardColumnId, title, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable droppableId={column.id}>
|
<Droppable droppableId={column.id}>
|
||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<BoardColumn
|
<BoardColumn
|
||||||
onTitleEdit={handleEditColumnTitle}
|
onTitleEdit={handleTitleEdit}
|
||||||
|
onDelete={onDelete}
|
||||||
title={column.title}
|
title={column.title}
|
||||||
color={column.colorCode}
|
color={column.colorCode}
|
||||||
totalAmount={boardColumnTotal}
|
totalAmount={boardColumnTotal}
|
||||||
|
|||||||
@ -11,27 +11,23 @@ const meta: Meta<typeof CircularProgressBar> = {
|
|||||||
args: {
|
args: {
|
||||||
size: 50,
|
size: 50,
|
||||||
},
|
},
|
||||||
|
parameters: {
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof CircularProgressBar>;
|
type Story = StoryObj<typeof CircularProgressBar>;
|
||||||
const args = {};
|
|
||||||
const defaultArgTypes = {
|
|
||||||
control: false,
|
|
||||||
};
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args,
|
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Catalog = {
|
export const Catalog = {
|
||||||
args: {
|
|
||||||
...args,
|
|
||||||
},
|
|
||||||
argTypes: {
|
argTypes: {
|
||||||
strokeWidth: defaultArgTypes,
|
strokeWidth: { control: false },
|
||||||
segmentColor: defaultArgTypes,
|
segmentColor: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
catalog: {
|
catalog: {
|
||||||
|
|||||||
@ -44,7 +44,8 @@ export function Opportunities() {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { handlePipelineStageAdd } = usePipelineStages();
|
const { handlePipelineStageAdd, handlePipelineStageDelete } =
|
||||||
|
usePipelineStages();
|
||||||
|
|
||||||
const [updatePipelineStage] = useUpdatePipelineStageMutation();
|
const [updatePipelineStage] = useUpdatePipelineStageMutation();
|
||||||
|
|
||||||
@ -89,7 +90,8 @@ export function Opportunities() {
|
|||||||
boardOptions={opportunitiesBoardOptions}
|
boardOptions={opportunitiesBoardOptions}
|
||||||
updateSorts={updateSorts}
|
updateSorts={updateSorts}
|
||||||
onEditColumnTitle={handleEditColumnTitle}
|
onEditColumnTitle={handleEditColumnTitle}
|
||||||
onStageAdd={handlePipelineStageAdd}
|
onColumnAdd={handlePipelineStageAdd}
|
||||||
|
onColumnDelete={handlePipelineStageDelete}
|
||||||
/>
|
/>
|
||||||
<EntityBoardActionBar />
|
<EntityBoardActionBar />
|
||||||
<EntityBoardContextMenu />
|
<EntityBoardContextMenu />
|
||||||
|
|||||||
@ -128,6 +128,7 @@ export class AbilityFactory {
|
|||||||
can(AbilityAction.Read, 'PipelineStage', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'PipelineStage', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Create, 'PipelineStage', { workspaceId: workspace.id });
|
can(AbilityAction.Create, 'PipelineStage', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Update, 'PipelineStage', { workspaceId: workspace.id });
|
can(AbilityAction.Update, 'PipelineStage', { workspaceId: workspace.id });
|
||||||
|
can(AbilityAction.Delete, 'PipelineStage', { workspaceId: workspace.id });
|
||||||
|
|
||||||
// PipelineProgress
|
// PipelineProgress
|
||||||
can(AbilityAction.Read, 'PipelineProgress', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'PipelineProgress', { workspaceId: workspace.id });
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import { Resolver, Args, Query, Mutation } from '@nestjs/graphql';
|
import { Resolver, Args, Query, Mutation } from '@nestjs/graphql';
|
||||||
import { UseGuards } from '@nestjs/common';
|
import {
|
||||||
|
ForbiddenException,
|
||||||
|
NotFoundException,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { accessibleBy } from '@casl/prisma';
|
import { accessibleBy } from '@casl/prisma';
|
||||||
import { Prisma, Workspace } from '@prisma/client';
|
import { Prisma, Workspace } from '@prisma/client';
|
||||||
@ -12,6 +16,7 @@ import { AbilityGuard } from 'src/guards/ability.guard';
|
|||||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||||
import {
|
import {
|
||||||
CreatePipelineStageAbilityHandler,
|
CreatePipelineStageAbilityHandler,
|
||||||
|
DeletePipelineStageAbilityHandler,
|
||||||
ReadPipelineStageAbilityHandler,
|
ReadPipelineStageAbilityHandler,
|
||||||
UpdatePipelineStageAbilityHandler,
|
UpdatePipelineStageAbilityHandler,
|
||||||
} from 'src/ability/handlers/pipeline-stage.ability-handler';
|
} from 'src/ability/handlers/pipeline-stage.ability-handler';
|
||||||
@ -24,6 +29,7 @@ import {
|
|||||||
import { UpdateOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/update-one-pipeline-stage.args';
|
import { UpdateOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/update-one-pipeline-stage.args';
|
||||||
import { CreateOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/create-one-pipeline-stage.args';
|
import { CreateOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/create-one-pipeline-stage.args';
|
||||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
|
import { DeleteOnePipelineStageArgs } from 'src/core/@generated/pipeline-stage/delete-one-pipeline-stage.args';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Resolver(() => PipelineStage)
|
@Resolver(() => PipelineStage)
|
||||||
@ -90,4 +96,54 @@ export class PipelineStageResolver {
|
|||||||
select: prismaSelect.value,
|
select: prismaSelect.value,
|
||||||
} as Prisma.PipelineProgressUpdateArgs);
|
} as Prisma.PipelineProgressUpdateArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => PipelineStage, {
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
@UseGuards(AbilityGuard)
|
||||||
|
@CheckAbilities(DeletePipelineStageAbilityHandler)
|
||||||
|
async deleteOnePipelineStage(
|
||||||
|
@Args() args: DeleteOnePipelineStageArgs,
|
||||||
|
): Promise<PipelineStage> {
|
||||||
|
const pipelineStageToDelete = await this.pipelineStageService.findUnique({
|
||||||
|
where: args.where,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pipelineStageToDelete) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pipelineId } = pipelineStageToDelete;
|
||||||
|
|
||||||
|
const remainingPipelineStages = await this.pipelineStageService.findMany({
|
||||||
|
orderBy: { index: 'asc' },
|
||||||
|
where: {
|
||||||
|
pipelineId,
|
||||||
|
NOT: { id: pipelineStageToDelete.id },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!remainingPipelineStages.length) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
`Deleting last pipeline stage is not allowed`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedPipelineStage = await this.pipelineStageService.delete({
|
||||||
|
where: args.where,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
remainingPipelineStages.map((pipelineStage, index) => {
|
||||||
|
if (pipelineStage.index === index) return;
|
||||||
|
|
||||||
|
return this.pipelineStageService.update({
|
||||||
|
data: { index },
|
||||||
|
where: { id: pipelineStage.id },
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return deletedPipelineStage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user