From db5dfb3bdfef58663a1c4d17f54f3f47bc63562a Mon Sep 17 00:00:00 2001 From: Emilien Chauvet Date: Mon, 3 Jul 2023 14:11:39 -0700 Subject: [PATCH] Enable opportunity card deletion (#490) * Add checkbox * Add state management for selected opportunities * Use recoil for selected items state, show action bar * Deduplicate code * Add delete action * Enable delete * Add color for selected cards * update board state on delete * Add stories * Enable empty board * Fix story * Handle dark mdoe * Nits * Rename module * Better naming * Fix naming confusion process<>progress --- front/src/generated/graphql.tsx | 40 ++++++++++++ .../components/Board.tsx | 30 +++++++-- ...dActionBarButtonDeletePipelineProgress.tsx | 49 ++++++++++++++ .../components/CompanyBoardCard.tsx | 24 +++++-- .../components/EntityBoardActionBar.tsx | 15 +++++ .../components/NewButton.tsx | 10 +-- .../components/NewCompanyBoardCard.tsx | 0 .../components/__stories__/Board.stories.tsx | 0 .../__stories__/CompanyBoardCard.stories.tsx | 16 ++++- .../components/__stories__/mock-data.ts | 0 .../hooks/useBoard.ts | 0 .../queries/index.ts | 0 .../pipeline-progress/services/index.ts | 1 + .../pipeline-progress/services/update.ts | 9 +++ .../states/boardColumnsState.ts | 0 .../states/boardItemsState.ts | 0 .../states/selectedBoardItemsState.ts | 6 ++ .../ui/components/action-bar/ActionBar.tsx | 65 +++++++++++++++++++ .../table/action-bar/EntityTableActionBar.tsx | 59 ++--------------- front/src/modules/ui/themes/themes.ts | 4 ++ .../src/pages/opportunities/Opportunities.tsx | 25 ++++--- server/src/ability/ability.factory.ts | 3 + 22 files changed, 275 insertions(+), 81 deletions(-) rename front/src/modules/{opportunities => pipeline-progress}/components/Board.tsx (82%) create mode 100644 front/src/modules/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress.tsx rename front/src/modules/{opportunities => pipeline-progress}/components/CompanyBoardCard.tsx (78%) create mode 100644 front/src/modules/pipeline-progress/components/EntityBoardActionBar.tsx rename front/src/modules/{opportunities => pipeline-progress}/components/NewButton.tsx (93%) rename front/src/modules/{opportunities => pipeline-progress}/components/NewCompanyBoardCard.tsx (100%) rename front/src/modules/{opportunities => pipeline-progress}/components/__stories__/Board.stories.tsx (100%) rename front/src/modules/{opportunities => pipeline-progress}/components/__stories__/CompanyBoardCard.stories.tsx (59%) rename front/src/modules/{opportunities => pipeline-progress}/components/__stories__/mock-data.ts (100%) rename front/src/modules/{opportunities => pipeline-progress}/hooks/useBoard.ts (100%) rename front/src/modules/{opportunities => pipeline-progress}/queries/index.ts (100%) create mode 100644 front/src/modules/pipeline-progress/services/index.ts create mode 100644 front/src/modules/pipeline-progress/services/update.ts rename front/src/modules/{opportunities => pipeline-progress}/states/boardColumnsState.ts (100%) rename front/src/modules/{opportunities => pipeline-progress}/states/boardItemsState.ts (100%) create mode 100644 front/src/modules/pipeline-progress/states/selectedBoardItemsState.ts create mode 100644 front/src/modules/ui/components/action-bar/ActionBar.tsx diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 9b653e294..b81a4ca4e 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -1704,6 +1704,13 @@ export type CreateOnePipelineProgressMutationVariables = Exact<{ export type CreateOnePipelineProgressMutation = { __typename?: 'Mutation', createOnePipelineProgress: { __typename?: 'PipelineProgress', id: string } }; +export type DeleteManyPipelineProgressMutationVariables = Exact<{ + ids?: InputMaybe | Scalars['String']>; +}>; + + +export type DeleteManyPipelineProgressMutation = { __typename?: 'Mutation', deleteManyPipelineProgress: { __typename?: 'AffectedRows', count: number } }; + export type GetPeopleQueryVariables = Exact<{ orderBy?: InputMaybe | PersonOrderByWithRelationInput>; where?: InputMaybe; @@ -2600,6 +2607,39 @@ export function useCreateOnePipelineProgressMutation(baseOptions?: Apollo.Mutati export type CreateOnePipelineProgressMutationHookResult = ReturnType; export type CreateOnePipelineProgressMutationResult = Apollo.MutationResult; export type CreateOnePipelineProgressMutationOptions = Apollo.BaseMutationOptions; +export const DeleteManyPipelineProgressDocument = gql` + mutation DeleteManyPipelineProgress($ids: [String!]) { + deleteManyPipelineProgress(where: {id: {in: $ids}}) { + count + } +} + `; +export type DeleteManyPipelineProgressMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteManyPipelineProgressMutation__ + * + * To run a mutation, you first call `useDeleteManyPipelineProgressMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteManyPipelineProgressMutation` 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 [deleteManyPipelineProgressMutation, { data, loading, error }] = useDeleteManyPipelineProgressMutation({ + * variables: { + * ids: // value for 'ids' + * }, + * }); + */ +export function useDeleteManyPipelineProgressMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteManyPipelineProgressDocument, options); + } +export type DeleteManyPipelineProgressMutationHookResult = ReturnType; +export type DeleteManyPipelineProgressMutationResult = Apollo.MutationResult; +export type DeleteManyPipelineProgressMutationOptions = Apollo.BaseMutationOptions; export const GetPeopleDocument = gql` query GetPeople($orderBy: [PersonOrderByWithRelationInput!], $where: PersonWhereInput, $limit: Int) { people: findManyPerson(orderBy: $orderBy, where: $where, take: $limit) { diff --git a/front/src/modules/opportunities/components/Board.tsx b/front/src/modules/pipeline-progress/components/Board.tsx similarity index 82% rename from front/src/modules/opportunities/components/Board.tsx rename to front/src/modules/pipeline-progress/components/Board.tsx index 5c51656c8..205bf5ef4 100644 --- a/front/src/modules/opportunities/components/Board.tsx +++ b/front/src/modules/pipeline-progress/components/Board.tsx @@ -19,6 +19,7 @@ import { } from '../../ui/components/board/Board'; import { boardColumnsState } from '../states/boardColumnsState'; import { boardItemsState } from '../states/boardItemsState'; +import { selectedBoardItemsState } from '../states/selectedBoardItemsState'; import { CompanyBoardCard } from './CompanyBoardCard'; import { NewButton } from './NewButton'; @@ -69,19 +70,22 @@ export function Board({ pipelineId, }: BoardProps) { const [board, setBoard] = useRecoilState(boardColumnsState); - const [items, setItems] = useRecoilState(boardItemsState); + const [boardItems, setBoardItems] = useRecoilState(boardItemsState); + const [selectedBoardItems, setSelectedBoardItems] = useRecoilState( + selectedBoardItemsState, + ); const [isInitialBoardLoaded, setIsInitialBoardLoaded] = useState(false); useEffect(() => { - if (Object.keys(initialItems).length === 0 || isInitialBoardLoaded) return; setBoard(initialBoard); - setItems(initialItems); + if (Object.keys(initialItems).length === 0 || isInitialBoardLoaded) return; + setBoardItems(initialItems); setIsInitialBoardLoaded(true); }, [ initialBoard, setBoard, initialItems, - setItems, + setBoardItems, setIsInitialBoardLoaded, isInitialBoardLoaded, ]); @@ -105,6 +109,16 @@ export function Board({ [board, onUpdate, setBoard], ); + function handleSelect(itemKey: string) { + if (selectedBoardItems.includes(itemKey)) { + setSelectedBoardItems( + selectedBoardItems.filter((key) => key !== itemKey), + ); + } else { + setSelectedBoardItems([...selectedBoardItems, itemKey]); + } + } + return board.length > 0 ? ( @@ -117,7 +131,7 @@ export function Board({ > {board[columnIndex].itemKeys.map( (itemKey, index) => - items[itemKey] && ( + boardItems[itemKey] && ( - + handleSelect(itemKey)} + /> )} diff --git a/front/src/modules/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress.tsx b/front/src/modules/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress.tsx new file mode 100644 index 000000000..eba747e55 --- /dev/null +++ b/front/src/modules/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress.tsx @@ -0,0 +1,49 @@ +import { getOperationName } from '@apollo/client/utilities'; +import { useRecoilState } from 'recoil'; + +import { EntityTableActionBarButton } from '@/ui/components/table/action-bar/EntityTableActionBarButton'; +import { IconTrash } from '@/ui/icons/index'; +import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql'; + +import { GET_PIPELINES } from '../queries'; +import { boardItemsState } from '../states/boardItemsState'; +import { selectedBoardItemsState } from '../states/selectedBoardItemsState'; + +export function BoardActionBarButtonDeletePipelineProgress() { + const [selectedBoardItems, setSelectedBoardItems] = useRecoilState( + selectedBoardItemsState, + ); + const [boardItems, setBoardItems] = useRecoilState(boardItemsState); + + const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({ + refetchQueries: [getOperationName(GET_PIPELINES) ?? ''], + }); + + async function handleDeleteClick() { + await deletePipelineProgress({ + variables: { + ids: selectedBoardItems, + }, + }); + + console.log('boardItems', boardItems); + + setBoardItems( + Object.fromEntries( + Object.entries(boardItems).filter( + ([key]) => !selectedBoardItems.includes(key), + ), + ), + ); + setSelectedBoardItems([]); + } + + return ( + } + type="warning" + onClick={handleDeleteClick} + /> + ); +} diff --git a/front/src/modules/opportunities/components/CompanyBoardCard.tsx b/front/src/modules/pipeline-progress/components/CompanyBoardCard.tsx similarity index 78% rename from front/src/modules/opportunities/components/CompanyBoardCard.tsx rename to front/src/modules/pipeline-progress/components/CompanyBoardCard.tsx index ac779a4a5..f1dcd4e8e 100644 --- a/front/src/modules/opportunities/components/CompanyBoardCard.tsx +++ b/front/src/modules/pipeline-progress/components/CompanyBoardCard.tsx @@ -3,15 +3,21 @@ import styled from '@emotion/styled'; import { Company } from '../../../generated/graphql'; import { PersonChip } from '../../people/components/PersonChip'; +import { Checkbox } from '../../ui/components/form/Checkbox'; import { IconCalendarEvent, IconUser, IconUsers } from '../../ui/icons'; import { getLogoUrlFromDomainName, humanReadableDate } from '../../utils/utils'; -const StyledBoardCard = styled.div` - background: ${({ theme }) => theme.background.secondary}; +const StyledBoardCard = styled.div<{ selected: boolean }>` + background-color: ${({ theme, selected }) => + selected ? theme.selectedCard : theme.background.secondary}; border: 1px solid ${({ theme }) => theme.border.color.medium}; border-radius: 4px; box-shadow: ${({ theme }) => theme.boxShadow.light}; color: ${({ theme }) => theme.font.color.primary}; + &:hover { + background-color: ${({ theme, selected }) => + selected ? theme.selectedCardHover : theme.background.tertiary}; + } cursor: pointer; `; @@ -56,17 +62,27 @@ type CompanyProp = Pick< 'id' | 'name' | 'domainName' | 'employees' | 'createdAt' | 'accountOwner' >; -export function CompanyBoardCard({ company }: { company: CompanyProp }) { +export function CompanyBoardCard({ + company, + selected, + onSelect, +}: { + company: CompanyProp; + selected: boolean; + onSelect: (company: CompanyProp) => void; +}) { const theme = useTheme(); return ( - + {`${company.name}-company-logo`} {company.name} +
+ onSelect(company)} /> diff --git a/front/src/modules/pipeline-progress/components/EntityBoardActionBar.tsx b/front/src/modules/pipeline-progress/components/EntityBoardActionBar.tsx new file mode 100644 index 000000000..83f078d33 --- /dev/null +++ b/front/src/modules/pipeline-progress/components/EntityBoardActionBar.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useRecoilValue } from 'recoil'; + +import { ActionBar } from '@/ui/components/action-bar/ActionBar'; + +import { selectedBoardItemsState } from '../states/selectedBoardItemsState'; + +type OwnProps = { + children: React.ReactNode | React.ReactNode[]; +}; + +export function EntityBoardActionBar({ children }: OwnProps) { + const selectedBoardItems = useRecoilValue(selectedBoardItemsState); + return {children}; +} diff --git a/front/src/modules/opportunities/components/NewButton.tsx b/front/src/modules/pipeline-progress/components/NewButton.tsx similarity index 93% rename from front/src/modules/opportunities/components/NewButton.tsx rename to front/src/modules/pipeline-progress/components/NewButton.tsx index b69f96714..1bbbe070b 100644 --- a/front/src/modules/opportunities/components/NewButton.tsx +++ b/front/src/modules/pipeline-progress/components/NewButton.tsx @@ -24,7 +24,7 @@ type OwnProps = { export function NewButton({ pipelineId, columnId }: OwnProps) { const [isCreatingCard, setIsCreatingCard] = useState(false); const [board, setBoard] = useRecoilState(boardColumnsState); - const [items, setItems] = useRecoilState(boardItemsState); + const [boardItems, setBoardItems] = useRecoilState(boardItemsState); const [createOnePipelineProgress] = useCreateOnePipelineProgressMutation(); const onEntitySelect = useCallback( @@ -36,8 +36,8 @@ export function NewButton({ pipelineId, columnId }: OwnProps) { (column: Column) => column.id === columnId, ); newBoard[destinationColumnIndex].itemKeys.push(newUuid); - setItems({ - ...items, + setBoardItems({ + ...boardItems, [newUuid]: { id: company.id, name: company.name, @@ -62,8 +62,8 @@ export function NewButton({ pipelineId, columnId }: OwnProps) { pipelineId, board, setBoard, - items, - setItems, + boardItems, + setBoardItems, ], ); diff --git a/front/src/modules/opportunities/components/NewCompanyBoardCard.tsx b/front/src/modules/pipeline-progress/components/NewCompanyBoardCard.tsx similarity index 100% rename from front/src/modules/opportunities/components/NewCompanyBoardCard.tsx rename to front/src/modules/pipeline-progress/components/NewCompanyBoardCard.tsx diff --git a/front/src/modules/opportunities/components/__stories__/Board.stories.tsx b/front/src/modules/pipeline-progress/components/__stories__/Board.stories.tsx similarity index 100% rename from front/src/modules/opportunities/components/__stories__/Board.stories.tsx rename to front/src/modules/pipeline-progress/components/__stories__/Board.stories.tsx diff --git a/front/src/modules/opportunities/components/__stories__/CompanyBoardCard.stories.tsx b/front/src/modules/pipeline-progress/components/__stories__/CompanyBoardCard.stories.tsx similarity index 59% rename from front/src/modules/opportunities/components/__stories__/CompanyBoardCard.stories.tsx rename to front/src/modules/pipeline-progress/components/__stories__/CompanyBoardCard.stories.tsx index 6d95fdc78..c101df446 100644 --- a/front/src/modules/opportunities/components/__stories__/CompanyBoardCard.stories.tsx +++ b/front/src/modules/pipeline-progress/components/__stories__/CompanyBoardCard.stories.tsx @@ -1,4 +1,4 @@ -import { StrictMode } from 'react'; +import { StrictMode, useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { Company } from '../../../../generated/graphql'; @@ -13,10 +13,22 @@ const meta: Meta = { export default meta; type Story = StoryObj; +const FakeSelectableCompanyBoardCard = () => { + const [selected, setSelected] = useState(false); + + return ( + setSelected(!selected)} + /> + ); +}; + export const CompanyCompanyBoardCard: Story = { render: () => ( - + ), }; diff --git a/front/src/modules/opportunities/components/__stories__/mock-data.ts b/front/src/modules/pipeline-progress/components/__stories__/mock-data.ts similarity index 100% rename from front/src/modules/opportunities/components/__stories__/mock-data.ts rename to front/src/modules/pipeline-progress/components/__stories__/mock-data.ts diff --git a/front/src/modules/opportunities/hooks/useBoard.ts b/front/src/modules/pipeline-progress/hooks/useBoard.ts similarity index 100% rename from front/src/modules/opportunities/hooks/useBoard.ts rename to front/src/modules/pipeline-progress/hooks/useBoard.ts diff --git a/front/src/modules/opportunities/queries/index.ts b/front/src/modules/pipeline-progress/queries/index.ts similarity index 100% rename from front/src/modules/opportunities/queries/index.ts rename to front/src/modules/pipeline-progress/queries/index.ts diff --git a/front/src/modules/pipeline-progress/services/index.ts b/front/src/modules/pipeline-progress/services/index.ts new file mode 100644 index 000000000..c37c258c7 --- /dev/null +++ b/front/src/modules/pipeline-progress/services/index.ts @@ -0,0 +1 @@ +export * from './update'; diff --git a/front/src/modules/pipeline-progress/services/update.ts b/front/src/modules/pipeline-progress/services/update.ts new file mode 100644 index 000000000..6214e00cb --- /dev/null +++ b/front/src/modules/pipeline-progress/services/update.ts @@ -0,0 +1,9 @@ +import { gql } from '@apollo/client'; + +export const DELETE_PIPELINE_PROGRESS = gql` + mutation DeleteManyPipelineProgress($ids: [String!]) { + deleteManyPipelineProgress(where: { id: { in: $ids } }) { + count + } + } +`; diff --git a/front/src/modules/opportunities/states/boardColumnsState.ts b/front/src/modules/pipeline-progress/states/boardColumnsState.ts similarity index 100% rename from front/src/modules/opportunities/states/boardColumnsState.ts rename to front/src/modules/pipeline-progress/states/boardColumnsState.ts diff --git a/front/src/modules/opportunities/states/boardItemsState.ts b/front/src/modules/pipeline-progress/states/boardItemsState.ts similarity index 100% rename from front/src/modules/opportunities/states/boardItemsState.ts rename to front/src/modules/pipeline-progress/states/boardItemsState.ts diff --git a/front/src/modules/pipeline-progress/states/selectedBoardItemsState.ts b/front/src/modules/pipeline-progress/states/selectedBoardItemsState.ts new file mode 100644 index 000000000..b8871d569 --- /dev/null +++ b/front/src/modules/pipeline-progress/states/selectedBoardItemsState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const selectedBoardItemsState = atom({ + key: 'selectedBoardItemsState', + default: [], +}); diff --git a/front/src/modules/ui/components/action-bar/ActionBar.tsx b/front/src/modules/ui/components/action-bar/ActionBar.tsx new file mode 100644 index 000000000..0ef79b4cf --- /dev/null +++ b/front/src/modules/ui/components/action-bar/ActionBar.tsx @@ -0,0 +1,65 @@ +import React, { useEffect } from 'react'; +import styled from '@emotion/styled'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; + +import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState'; +import { PositionType } from '@/ui/types/PositionType'; + +type OwnProps = { + children: React.ReactNode | React.ReactNode[]; + selectedIds: string[]; +}; + +type StyledContainerProps = { + position: PositionType; +}; + +const StyledContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.light}; + border-radius: 8px; + bottom: ${(props) => (props.position.x ? 'auto' : '38px')}; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; + display: flex; + height: 48px; + + left: ${(props) => (props.position.x ? `${props.position.x}px` : '50%')}; + padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; + position: ${(props) => (props.position.x ? 'fixed' : 'absolute')}; + top: ${(props) => (props.position.y ? `${props.position.y}px` : 'auto')}; + + transform: translateX(-50%); + z-index: 1; +`; + +export function ActionBar({ children, selectedIds }: OwnProps) { + const position = useRecoilValue(contextMenuPositionState); + const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (!(event.target as HTMLElement).closest('.action-bar')) { + setContextMenuPosition({ x: null, y: null }); + } + } + + document.addEventListener('mousedown', handleClickOutside); + + // Cleanup the event listener when the component unmounts + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [setContextMenuPosition]); + + if (selectedIds.length === 0) { + return null; + } + + return ( + + {children} + + ); +} diff --git a/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx b/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx index a51725ce3..9d8f97729 100644 --- a/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx +++ b/front/src/modules/ui/components/table/action-bar/EntityTableActionBar.tsx @@ -1,66 +1,15 @@ -import React, { useEffect } from 'react'; -import styled from '@emotion/styled'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import React from 'react'; +import { useRecoilValue } from 'recoil'; -import { contextMenuPositionState } from '@/ui/tables/states/contextMenuPositionState'; +import { ActionBar } from '@/ui/components/action-bar/ActionBar'; import { selectedRowIdsState } from '@/ui/tables/states/selectedRowIdsState'; -import { PositionType } from '@/ui/types/PositionType'; type OwnProps = { children: React.ReactNode | React.ReactNode[]; }; -type StyledContainerProps = { - position: PositionType; -}; - -const StyledContainer = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.light}; - border-radius: 8px; - bottom: ${(props) => (props.position.x ? 'auto' : '38px')}; - box-shadow: ${({ theme }) => theme.boxShadow.strong}; - display: flex; - height: 48px; - - left: ${(props) => (props.position.x ? `${props.position.x}px` : '50%')}; - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; - position: ${(props) => (props.position.x ? 'fixed' : 'absolute')}; - top: ${(props) => (props.position.y ? `${props.position.y}px` : 'auto')}; - - transform: translateX(-50%); - z-index: 1; -`; - export function EntityTableActionBar({ children }: OwnProps) { const selectedRowIds = useRecoilValue(selectedRowIdsState); - const position = useRecoilValue(contextMenuPositionState); - const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if (!(event.target as HTMLElement).closest('.action-bar')) { - setContextMenuPosition({ x: null, y: null }); - } - } - - document.addEventListener('mousedown', handleClickOutside); - - // Cleanup the event listener when the component unmounts - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [setContextMenuPosition]); - - if (selectedRowIds.length === 0) { - return null; - } - - return ( - - {children} - - ); + return {children}; } diff --git a/front/src/modules/ui/themes/themes.ts b/front/src/modules/ui/themes/themes.ts index 2392d6e8e..c969d37ee 100644 --- a/front/src/modules/ui/themes/themes.ts +++ b/front/src/modules/ui/themes/themes.ts @@ -29,6 +29,8 @@ export const lightTheme = { background: backgroundLight, border: borderLight, boxShadow: boxShadowLight, + selectedCardHover: color.blue20, + selectedCard: color.blue10, font: fontLight, }, }; @@ -40,6 +42,8 @@ export const darkTheme: ThemeType = { background: backgroundDark, border: borderDark, boxShadow: boxShadowDark, + selectedCardHover: color.blue70, + selectedCard: color.blue80, font: fontDark, }, }; diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index 5cbb81b9d..f7d36c9e3 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -1,6 +1,8 @@ import { useCallback, useMemo } from 'react'; import { useTheme } from '@emotion/react'; +import { BoardActionBarButtonDeletePipelineProgress } from '@/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress'; +import { EntityBoardActionBar } from '@/pipeline-progress/components/EntityBoardActionBar'; import { IconTargetArrow } from '@/ui/icons/index'; import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'; @@ -10,8 +12,8 @@ import { useGetPipelinesQuery, useUpdateOnePipelineProgressMutation, } from '../../generated/graphql'; -import { Board } from '../../modules/opportunities/components/Board'; -import { useBoard } from '../../modules/opportunities/hooks/useBoard'; +import { Board } from '../../modules/pipeline-progress/components/Board'; +import { useBoard } from '../../modules/pipeline-progress/hooks/useBoard'; export function Opportunities() { const theme = useTheme(); @@ -49,13 +51,18 @@ export function Opportunities() { icon={} > {items && pipelineId ? ( - + <> + + + + + ) : ( <> )} diff --git a/server/src/ability/ability.factory.ts b/server/src/ability/ability.factory.ts index cc3f55236..f5d909a5d 100644 --- a/server/src/ability/ability.factory.ts +++ b/server/src/ability/ability.factory.ts @@ -108,6 +108,9 @@ export class AbilityFactory { can(AbilityAction.Update, 'PipelineProgress', { workspaceId: workspace.id, }); + can(AbilityAction.Delete, 'PipelineProgress', { + workspaceId: workspace.id, + }); return build(); }