Uniformize folder structure (#693)
* Uniformize folder structure * Fix icons * Fix icons * Fix tests * Fix tests
This commit is contained in:
@ -0,0 +1,49 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { IconTrash } from '@/ui/icon/index';
|
||||
import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton';
|
||||
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
|
||||
|
||||
import { GET_PIPELINES } from '../queries';
|
||||
import { boardState } from '../states/boardState';
|
||||
import { selectedBoardCardsState } from '../states/selectedBoardCardsState';
|
||||
|
||||
export function BoardActionBarButtonDeletePipelineProgress() {
|
||||
const [selectedBoardItems, setSelectedBoardItems] = useRecoilState(
|
||||
selectedBoardCardsState,
|
||||
);
|
||||
const [board, setBoard] = useRecoilState(boardState);
|
||||
|
||||
const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
|
||||
async function handleDeleteClick() {
|
||||
setBoard(
|
||||
board?.map((pipelineStage) => ({
|
||||
...pipelineStage,
|
||||
pipelineProgressIds: pipelineStage.pipelineProgressIds.filter(
|
||||
(pipelineProgressId) =>
|
||||
!selectedBoardItems.includes(pipelineProgressId),
|
||||
),
|
||||
})),
|
||||
);
|
||||
|
||||
setSelectedBoardItems([]);
|
||||
await deletePipelineProgress({
|
||||
variables: {
|
||||
ids: selectedBoardItems,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTableActionBarButton
|
||||
label="Delete"
|
||||
icon={<IconTrash size={16} />}
|
||||
type="warning"
|
||||
onClick={handleDeleteClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
87
front/src/modules/pipeline/components/EntityBoard.tsx
Normal file
87
front/src/modules/pipeline/components/EntityBoard.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { useCallback } from 'react';
|
||||
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
import {
|
||||
PipelineProgress,
|
||||
PipelineStage,
|
||||
useUpdateOnePipelineProgressStageMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import {
|
||||
getOptimisticlyUpdatedBoard,
|
||||
StyledBoard,
|
||||
} from '../../ui/board/components/Board';
|
||||
import { BoardColumnContext } from '../states/BoardColumnContext';
|
||||
import { boardState } from '../states/boardState';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
import { EntityBoardColumn } from './EntityBoardColumn';
|
||||
|
||||
export function EntityBoard({ boardOptions }: { boardOptions: BoardOptions }) {
|
||||
const [board, setBoard] = useRecoilState(boardState);
|
||||
const [updatePipelineProgressStage] =
|
||||
useUpdateOnePipelineProgressStageMutation();
|
||||
|
||||
const updatePipelineProgressStageInDB = useCallback(
|
||||
async (
|
||||
pipelineProgressId: NonNullable<PipelineProgress['id']>,
|
||||
pipelineStageId: NonNullable<PipelineStage['id']>,
|
||||
) => {
|
||||
updatePipelineProgressStage({
|
||||
variables: {
|
||||
id: pipelineProgressId,
|
||||
pipelineStageId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[updatePipelineProgressStage],
|
||||
);
|
||||
|
||||
const onDragEnd: OnDragEndResponder = useCallback(
|
||||
async (result) => {
|
||||
if (!board) return;
|
||||
const newBoard = getOptimisticlyUpdatedBoard(board, result);
|
||||
if (!newBoard) return;
|
||||
setBoard(newBoard);
|
||||
try {
|
||||
const draggedEntityId = result.draggableId;
|
||||
const destinationColumnId = result.destination?.droppableId;
|
||||
draggedEntityId &&
|
||||
destinationColumnId &&
|
||||
updatePipelineProgressStageInDB &&
|
||||
(await updatePipelineProgressStageInDB(
|
||||
draggedEntityId,
|
||||
destinationColumnId,
|
||||
));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
[board, updatePipelineProgressStageInDB, setBoard],
|
||||
);
|
||||
|
||||
const sortedBoard = board
|
||||
? [...board].sort((a, b) => {
|
||||
return a.index - b.index;
|
||||
})
|
||||
: [];
|
||||
|
||||
return (board?.length ?? 0) > 0 ? (
|
||||
<StyledBoard>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{sortedBoard.map((column) => (
|
||||
<RecoilScope
|
||||
SpecificContext={BoardColumnContext}
|
||||
key={column.pipelineStageId}
|
||||
>
|
||||
<EntityBoardColumn boardOptions={boardOptions} column={column} />
|
||||
</RecoilScope>
|
||||
))}
|
||||
</DragDropContext>
|
||||
</StyledBoard>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActionBar } from '@/ui/action-bar/components/ActionBar';
|
||||
|
||||
import { selectedBoardCardsState } from '../states/selectedBoardCardsState';
|
||||
|
||||
type OwnProps = {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
};
|
||||
|
||||
export function EntityBoardActionBar({ children }: OwnProps) {
|
||||
const selectedBoardCards = useRecoilValue(selectedBoardCardsState);
|
||||
return <ActionBar selectedIds={selectedBoardCards}>{children}</ActionBar>;
|
||||
}
|
||||
45
front/src/modules/pipeline/components/EntityBoardCard.tsx
Normal file
45
front/src/modules/pipeline/components/EntityBoardCard.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
|
||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { BoardCardContext } from '../states/BoardCardContext';
|
||||
import { pipelineProgressIdScopedState } from '../states/pipelineProgressIdScopedState';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
export function EntityBoardCard({
|
||||
boardOptions,
|
||||
pipelineProgressId,
|
||||
index,
|
||||
}: {
|
||||
boardOptions: BoardOptions;
|
||||
pipelineProgressId: string;
|
||||
index: number;
|
||||
}) {
|
||||
const [pipelineProgressIdFromRecoil, setPipelineProgressId] =
|
||||
useRecoilScopedState(pipelineProgressIdScopedState, BoardCardContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (pipelineProgressIdFromRecoil !== pipelineProgressId) {
|
||||
setPipelineProgressId(pipelineProgressId);
|
||||
}
|
||||
}, [pipelineProgressId, setPipelineProgressId, pipelineProgressIdFromRecoil]);
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={pipelineProgressId}
|
||||
draggableId={pipelineProgressId}
|
||||
index={index}
|
||||
>
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
ref={draggableProvided?.innerRef}
|
||||
{...draggableProvided?.dragHandleProps}
|
||||
{...draggableProvided?.draggableProps}
|
||||
>
|
||||
{boardOptions.cardComponent}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
111
front/src/modules/pipeline/components/EntityBoardColumn.tsx
Normal file
111
front/src/modules/pipeline/components/EntityBoardColumn.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import { useEffect } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
import { Droppable, DroppableProvided } from '@hello-pangea/dnd';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { BoardPipelineStageColumn } from '@/ui/board/components/Board';
|
||||
import { BoardColumn } from '@/ui/board/components/BoardColumn';
|
||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useUpdatePipelineStageMutation } from '~/generated/graphql';
|
||||
|
||||
import { GET_PIPELINES } from '../queries';
|
||||
import { BoardCardContext } from '../states/BoardCardContext';
|
||||
import { BoardColumnContext } from '../states/BoardColumnContext';
|
||||
import { boardColumnTotalsFamilySelector } from '../states/boardColumnTotalsFamilySelector';
|
||||
import { pipelineStageIdScopedState } from '../states/pipelineStageIdScopedState';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
import { EntityBoardCard } from './EntityBoardCard';
|
||||
|
||||
const StyledPlaceholder = styled.div`
|
||||
min-height: 1px;
|
||||
`;
|
||||
|
||||
const StyledNewCardButtonContainer = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(40)};
|
||||
`;
|
||||
|
||||
const BoardColumnCardsContainer = ({
|
||||
children,
|
||||
droppableProvided,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
droppableProvided: DroppableProvided;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
ref={droppableProvided?.innerRef}
|
||||
{...droppableProvided?.droppableProps}
|
||||
>
|
||||
{children}
|
||||
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function EntityBoardColumn({
|
||||
column,
|
||||
boardOptions,
|
||||
}: {
|
||||
column: BoardPipelineStageColumn;
|
||||
boardOptions: BoardOptions;
|
||||
}) {
|
||||
const [pipelineStageId, setPipelineStageId] = useRecoilScopedState(
|
||||
pipelineStageIdScopedState,
|
||||
BoardColumnContext,
|
||||
);
|
||||
const boardColumnTotal = useRecoilValue(
|
||||
boardColumnTotalsFamilySelector(column.pipelineStageId),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (pipelineStageId !== column.pipelineStageId) {
|
||||
setPipelineStageId(column.pipelineStageId);
|
||||
}
|
||||
}, [column, setPipelineStageId, pipelineStageId]);
|
||||
|
||||
const [updatePipelineStage] = useUpdatePipelineStageMutation();
|
||||
function handleEditColumnTitle(value: string) {
|
||||
updatePipelineStage({
|
||||
variables: {
|
||||
id: pipelineStageId,
|
||||
name: value,
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) || ''],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Droppable droppableId={column.pipelineStageId}>
|
||||
{(droppableProvided) => (
|
||||
<BoardColumn
|
||||
onTitleEdit={handleEditColumnTitle}
|
||||
title={column.title}
|
||||
colorCode={column.colorCode}
|
||||
pipelineStageId={column.pipelineStageId}
|
||||
totalAmount={boardColumnTotal}
|
||||
>
|
||||
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
|
||||
{column.pipelineProgressIds.map((pipelineProgressId, index) => (
|
||||
<RecoilScope
|
||||
SpecificContext={BoardCardContext}
|
||||
key={pipelineProgressId}
|
||||
>
|
||||
<EntityBoardCard
|
||||
index={index}
|
||||
pipelineProgressId={pipelineProgressId}
|
||||
boardOptions={boardOptions}
|
||||
/>
|
||||
</RecoilScope>
|
||||
))}
|
||||
</BoardColumnCardsContainer>
|
||||
<StyledNewCardButtonContainer>
|
||||
<RecoilScope>{boardOptions.newCardComponent}</RecoilScope>
|
||||
</StyledNewCardButtonContainer>
|
||||
</BoardColumn>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user