Merge commit 'cd3a32e55503dc1e6b9873d812dd401bf7d51045' into context-menu-vertical
This commit is contained in:
@ -1,24 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
|
||||
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
|
||||
|
||||
import { BoardCardEditableFieldInternal } from './BoardCardEditableFieldInternal';
|
||||
|
||||
type OwnProps = {
|
||||
editModeContent: ReactElement;
|
||||
nonEditModeContent: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
editHotkeyScope?: HotkeyScope;
|
||||
};
|
||||
|
||||
export function BoardCardEditableField(props: OwnProps) {
|
||||
return (
|
||||
<RecoilScope SpecificContext={BoardCardFieldContext}>
|
||||
<BoardCardEditableFieldInternal {...props} />
|
||||
</RecoilScope>
|
||||
);
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { BoardCardEditableField } from './BoardCardEditableField';
|
||||
import { BoardCardEditableFieldDateEditMode } from './BoardCardEditableFieldDateEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
value: Date;
|
||||
onChange: (newValue: Date) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function BoardCardEditableFieldDate({
|
||||
value,
|
||||
onChange,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
const debouncedOnChange = useMemo(() => {
|
||||
return debounce(onChange, 200);
|
||||
}, [onChange]);
|
||||
return (
|
||||
<BoardCardEditableField
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<BoardCardEditableFieldDateEditMode
|
||||
value={internalValue}
|
||||
onChange={(date: Date) => {
|
||||
setInternalValue(date);
|
||||
debouncedOnChange(date);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={<DateInputDisplay value={value} />}
|
||||
></BoardCardEditableField>
|
||||
);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
|
||||
|
||||
type OwnProps = {
|
||||
value: Date;
|
||||
onChange: (newValue: Date) => void;
|
||||
};
|
||||
|
||||
export function BoardCardEditableFieldDateEditMode({
|
||||
value,
|
||||
onChange,
|
||||
}: OwnProps) {
|
||||
function handleDateChange(newDate: Date) {
|
||||
onChange(newDate);
|
||||
}
|
||||
|
||||
return <DateInputEdit value={value} onChange={handleDateChange} />;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const BoardCardFieldDisplayModeOuterContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const BoardCardFieldDisplayModeInnerContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function BoardCardEditableFieldDisplayMode({
|
||||
children,
|
||||
}: React.PropsWithChildren<unknown>) {
|
||||
return (
|
||||
<BoardCardFieldDisplayModeOuterContainer>
|
||||
<BoardCardFieldDisplayModeInnerContainer>
|
||||
{children}
|
||||
</BoardCardFieldDisplayModeInnerContainer>
|
||||
</BoardCardFieldDisplayModeOuterContainer>
|
||||
);
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
import { ReactElement, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { overlayBackground } from '@/ui/theme/constants/effects';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope';
|
||||
|
||||
export const BoardCardFieldEditModeContainer = styled.div<
|
||||
Omit<OwnProps, 'onExit'>
|
||||
>`
|
||||
align-items: center;
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
left: ${(props) =>
|
||||
props.editModeHorizontalAlign === 'right' ? 'auto' : '0'};
|
||||
margin-left: -2px;
|
||||
min-height: 100%;
|
||||
min-width: calc(100% + 20px);
|
||||
position: absolute;
|
||||
|
||||
right: ${(props) =>
|
||||
props.editModeHorizontalAlign === 'right' ? '0' : 'auto'};
|
||||
top: ${(props) => (props.editModeVerticalPosition === 'over' ? '0' : '100%')};
|
||||
z-index: 1;
|
||||
${overlayBackground}
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
children: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
onExit: () => void;
|
||||
};
|
||||
|
||||
export function BoardCardEditableFieldEditMode({
|
||||
editModeHorizontalAlign,
|
||||
editModeVerticalPosition,
|
||||
children,
|
||||
onExit,
|
||||
}: OwnProps) {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [wrapperRef],
|
||||
callback: () => {
|
||||
onExit();
|
||||
},
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
'enter',
|
||||
() => {
|
||||
onExit();
|
||||
},
|
||||
BoardCardFieldHotkeyScope.BoardCardFieldEditMode,
|
||||
[onExit],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
onExit();
|
||||
},
|
||||
BoardCardFieldHotkeyScope.BoardCardFieldEditMode,
|
||||
[onExit],
|
||||
);
|
||||
|
||||
return (
|
||||
<BoardCardFieldEditModeContainer
|
||||
data-testid="editable-cell-edit-mode-container"
|
||||
ref={wrapperRef}
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
>
|
||||
{children}
|
||||
</BoardCardFieldEditModeContainer>
|
||||
);
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { useBoardCardField } from '../hooks/useBoardCardField';
|
||||
import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope';
|
||||
|
||||
import { BoardCardEditableFieldDisplayMode } from './BoardCardEditableFieldDisplayMode';
|
||||
import { BoardCardEditableFieldEditMode } from './BoardCardEditableFieldEditMode';
|
||||
|
||||
export const BoardCardFieldContainer = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
editModeContent: ReactElement;
|
||||
nonEditModeContent: ReactElement;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
editModeVerticalPosition?: 'over' | 'below';
|
||||
editHotkeyScope?: HotkeyScope;
|
||||
};
|
||||
|
||||
export function BoardCardEditableFieldInternal({
|
||||
editModeHorizontalAlign = 'left',
|
||||
editModeVerticalPosition = 'over',
|
||||
editModeContent,
|
||||
nonEditModeContent,
|
||||
editHotkeyScope,
|
||||
}: OwnProps) {
|
||||
const { openBoardCardField, isBoardCardFieldInEditMode } =
|
||||
useBoardCardField();
|
||||
|
||||
const { closeBoardCardField } = useBoardCardField();
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
function handleOnClick() {
|
||||
if (!isBoardCardFieldInEditMode) {
|
||||
openBoardCardField();
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
editHotkeyScope?.scope ??
|
||||
BoardCardFieldHotkeyScope.BoardCardFieldEditMode,
|
||||
editHotkeyScope?.customScopes ?? {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEditModeExit() {
|
||||
goBackToPreviousHotkeyScope();
|
||||
closeBoardCardField();
|
||||
}
|
||||
|
||||
return (
|
||||
<BoardCardFieldContainer onClick={handleOnClick}>
|
||||
{isBoardCardFieldInEditMode ? (
|
||||
<BoardCardEditableFieldEditMode
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
onExit={handleEditModeExit}
|
||||
>
|
||||
{editModeContent}
|
||||
</BoardCardEditableFieldEditMode>
|
||||
) : (
|
||||
<BoardCardEditableFieldDisplayMode>
|
||||
{nonEditModeContent}
|
||||
</BoardCardEditableFieldDisplayMode>
|
||||
)}
|
||||
</BoardCardFieldContainer>
|
||||
);
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
|
||||
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
|
||||
import { StyledInput } from '@/ui/table/editable-cell/type/components/TextCellEdit';
|
||||
import { debounce } from '~/utils/debounce';
|
||||
|
||||
import { BoardCardEditableField } from './BoardCardEditableField';
|
||||
|
||||
type OwnProps = {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
editModeHorizontalAlign?: 'left' | 'right';
|
||||
};
|
||||
|
||||
export function BoardCardEditableFieldText({
|
||||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
editModeHorizontalAlign,
|
||||
}: OwnProps) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
const debouncedOnChange = useMemo(() => {
|
||||
return debounce(onChange, 200);
|
||||
}, [onChange]);
|
||||
|
||||
return (
|
||||
<BoardCardEditableField
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeContent={
|
||||
<StyledInput
|
||||
placeholder={placeholder || ''}
|
||||
autoFocus
|
||||
value={internalValue}
|
||||
autoComplete="off"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInternalValue(event.target.value);
|
||||
debouncedOnChange(event.target.value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
nonEditModeContent={<TextInputDisplay>{value}</TextInputDisplay>}
|
||||
></BoardCardEditableField>
|
||||
);
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
|
||||
import { isBoardCardFieldInEditModeScopedState } from '../states/isBoardCardFieldInEditModeScopedState';
|
||||
|
||||
export function useBoardCardField() {
|
||||
const [isBoardCardFieldInEditMode, setIsBoardCardFieldInEditMode] =
|
||||
useRecoilScopedState(
|
||||
isBoardCardFieldInEditModeScopedState,
|
||||
BoardCardFieldContext,
|
||||
);
|
||||
|
||||
function openBoardCardField() {
|
||||
setIsBoardCardFieldInEditMode(true);
|
||||
}
|
||||
|
||||
function closeBoardCardField() {
|
||||
setIsBoardCardFieldInEditMode(false);
|
||||
}
|
||||
|
||||
return {
|
||||
isBoardCardFieldInEditMode,
|
||||
openBoardCardField,
|
||||
closeBoardCardField,
|
||||
};
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const BoardCardFieldContext = createContext<string | null>(null);
|
||||
@ -1,9 +0,0 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isBoardCardFieldInEditModeScopedState = atomFamily<
|
||||
boolean,
|
||||
string
|
||||
>({
|
||||
key: 'isBoardCardFieldInEditModeScopedState',
|
||||
default: false,
|
||||
});
|
||||
@ -1,3 +0,0 @@
|
||||
export enum BoardCardFieldHotkeyScope {
|
||||
BoardCardFieldEditMode = 'board-card-field-edit-mode',
|
||||
}
|
||||
@ -1,53 +1,40 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { GET_PIPELINES } from '@/pipeline/queries';
|
||||
import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry';
|
||||
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
|
||||
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
|
||||
import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState';
|
||||
import { IconTrash } from '@/ui/icon/index';
|
||||
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
|
||||
|
||||
export function BoardActionBarButtonDeleteBoardCard({
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete: (deletedCardIds: string[]) => void;
|
||||
}) {
|
||||
const deleteBoardCardIds = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
const boardCardIdsToDelete = snapshot
|
||||
.getLoadable(selectedBoardCardIdsState)
|
||||
.getValue();
|
||||
import { useRemoveCardIds } from '../hooks/useRemoveCardIds';
|
||||
import { selectedCardIdsSelector } from '../states/selectedCardIdsSelector';
|
||||
|
||||
const boardColumns = snapshot.getLoadable(boardColumnsState).getValue();
|
||||
export function BoardActionBarButtonDeleteBoardCard() {
|
||||
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
||||
const removeCardIds = useRemoveCardIds();
|
||||
|
||||
for (const boardColumn of boardColumns) {
|
||||
const boardColumnCardIds = snapshot
|
||||
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
|
||||
.getValue();
|
||||
const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
|
||||
const newBoardColumnCardIds = boardColumnCardIds.filter(
|
||||
(cardId) => !boardCardIdsToDelete.includes(cardId),
|
||||
);
|
||||
|
||||
if (newBoardColumnCardIds.length !== boardColumnCardIds.length) {
|
||||
set(
|
||||
boardCardIdsByColumnIdFamilyState(boardColumn.id),
|
||||
newBoardColumnCardIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set(selectedBoardCardIdsState, []);
|
||||
|
||||
return boardCardIdsToDelete;
|
||||
async function handleDelete() {
|
||||
await deletePipelineProgress({
|
||||
variables: {
|
||||
ids: selectedCardIds,
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
async function handleDeleteClick() {
|
||||
const deletedCardIds = deleteBoardCardIds();
|
||||
|
||||
onDelete(deletedCardIds);
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
deleteManyPipelineProgress: {
|
||||
count: selectedCardIds.length,
|
||||
},
|
||||
},
|
||||
update: (cache) => {
|
||||
removeCardIds(selectedCardIds);
|
||||
selectedCardIds.forEach((id) => {
|
||||
cache.evict({ id: `PipelineProgress:${id}` });
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
@ -55,7 +42,7 @@ export function BoardActionBarButtonDeleteBoardCard({
|
||||
label="Delete"
|
||||
icon={<IconTrash size={16} />}
|
||||
type="danger"
|
||||
onClick={handleDeleteClick}
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,16 +4,14 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
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 { IconList } from '@tabler/icons-react';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
|
||||
import { GET_PIPELINE_PROGRESS } from '@/pipeline/queries';
|
||||
import { BoardHeader } from '@/ui/board/components/BoardHeader';
|
||||
import { StyledBoard } from '@/ui/board/components/StyledBoard';
|
||||
import { useUpdateBoardCardIds } from '@/ui/board/hooks/useUpdateBoardCardIds';
|
||||
import { BoardColumnIdContext } from '@/ui/board/states/BoardColumnIdContext';
|
||||
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { actionBarOpenState } from '@/ui/table/states/ActionBarIsOpenState';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import {
|
||||
@ -23,9 +21,10 @@ import {
|
||||
useUpdateOnePipelineProgressStageMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { useSetCardSelected } from '../hooks/useSetCardSelected';
|
||||
import { useUpdateBoardCardIds } from '../hooks/useUpdateBoardCardIds';
|
||||
import { BoardColumnContext } from '../states/BoardColumnContext';
|
||||
import { boardColumnsState } from '../states/boardColumnsState';
|
||||
import { selectedBoardCardIdsState } from '../states/selectedBoardCardIdsState';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
import { EntityBoardColumn } from './EntityBoardColumn';
|
||||
@ -49,6 +48,7 @@ export function EntityBoard({
|
||||
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
|
||||
}) {
|
||||
const [boardColumns] = useRecoilState(boardColumnsState);
|
||||
const setCardSelected = useSetCardSelected();
|
||||
|
||||
const theme = useTheme();
|
||||
const [updatePipelineProgressStage] =
|
||||
@ -105,21 +105,6 @@ export function EntityBoard({
|
||||
});
|
||||
|
||||
const boardRef = useRef(null);
|
||||
const [selectedBoardCards, setSelectedBoardCards] = useRecoilState(
|
||||
selectedBoardCardIdsState,
|
||||
);
|
||||
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
|
||||
|
||||
function setRowSelectedState(boardCardId: string, selected: boolean) {
|
||||
if (selected && !selectedBoardCards.includes(boardCardId)) {
|
||||
setSelectedBoardCards([...selectedBoardCards, boardCardId ?? '']);
|
||||
setActionBarOpenState(true);
|
||||
} else if (!selected && selectedBoardCards.includes(boardCardId)) {
|
||||
setSelectedBoardCards(
|
||||
selectedBoardCards.filter((id) => id !== boardCardId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (boardColumns?.length ?? 0) > 0 ? (
|
||||
<StyledBoardWithHeader>
|
||||
@ -147,7 +132,7 @@ export function EntityBoard({
|
||||
</StyledBoard>
|
||||
<DragSelect
|
||||
dragSelectable={boardRef}
|
||||
onDragSelectionChange={setRowSelectedState}
|
||||
onDragSelectionChange={setCardSelected}
|
||||
/>
|
||||
</StyledBoardWithHeader>
|
||||
) : (
|
||||
|
||||
@ -3,9 +3,9 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActionBar } from '@/ui/action-bar/components/ActionBar';
|
||||
|
||||
import { selectedBoardCardIdsState } from '../states/selectedBoardCardIdsState';
|
||||
import { selectedCardIdsSelector } from '../states/selectedCardIdsSelector';
|
||||
|
||||
export function EntityBoardActionBar() {
|
||||
const selectedBoardCards = useRecoilValue(selectedBoardCardIdsState);
|
||||
const selectedBoardCards = useRecoilValue(selectedCardIdsSelector);
|
||||
return <ActionBar selectedIds={selectedBoardCards}></ActionBar>;
|
||||
}
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
// TODO: refactor this test with Recoil
|
||||
describe('getOptimisticlyUpdatedBoard', () => {
|
||||
it('should return a new board with the updated cell', () => {
|
||||
// const initialColumn1: string[] = ['item-1', 'item-2', 'item-3'];
|
||||
// const initialColumn2: string[] = ['item-4', 'item-5'];
|
||||
// const finalColumn1: string[] = ['item-2', 'item-3'];
|
||||
// const finalColumn2: string[] = ['item-4', 'item-1', 'item-5'];
|
||||
// const dropResult = {
|
||||
// source: {
|
||||
// droppableId: 'column-1',
|
||||
// index: 0,
|
||||
// },
|
||||
// destination: {
|
||||
// droppableId: 'column-2',
|
||||
// index: 1,
|
||||
// },
|
||||
// } as DropResult;
|
||||
// const initialBoard = [
|
||||
// {
|
||||
// id: 'column-1',
|
||||
// title: 'My Column',
|
||||
// pipelineStageId: 'column-1',
|
||||
// pipelineProgressIds: initialColumn1,
|
||||
// },
|
||||
// {
|
||||
// id: 'column-2',
|
||||
// title: 'My Column',
|
||||
// pipelineStageId: 'column-2',
|
||||
// pipelineProgressIds: initialColumn2,
|
||||
// },
|
||||
// ];
|
||||
// const updatedBoard = u(
|
||||
// initialBoard,
|
||||
// dropResult,
|
||||
// );
|
||||
// const finalBoard = [
|
||||
// {
|
||||
// id: 'column-1',
|
||||
// title: 'My Column',
|
||||
// pipelineStageId: 'column-1',
|
||||
// pipelineProgressIds: finalColumn1,
|
||||
// },
|
||||
// {
|
||||
// id: 'column-2',
|
||||
// title: 'My Column',
|
||||
// pipelineStageId: 'column-2',
|
||||
// pipelineProgressIds: finalColumn2,
|
||||
// },
|
||||
// ];
|
||||
// expect(updatedBoard).toEqual(finalBoard);
|
||||
// expect(updatedBoard).not.toBe(initialBoard);
|
||||
});
|
||||
});
|
||||
@ -1,30 +1,13 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { GET_PIPELINES } from '@/pipeline/queries';
|
||||
import { actionBarEntriesState } from '@/ui/table/states/ActionBarEntriesState';
|
||||
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
|
||||
|
||||
import { BoardActionBarButtonDeleteBoardCard } from '../components/BoardActionBarButtonDeleteBoardCard';
|
||||
|
||||
export function useOpenActionBar() {
|
||||
const setActionBarEntries = useSetRecoilState(actionBarEntriesState);
|
||||
|
||||
const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
|
||||
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
|
||||
});
|
||||
|
||||
async function handleDelete(cardIdsToDelete: string[]) {
|
||||
await deletePipelineProgress({
|
||||
variables: {
|
||||
ids: cardIdsToDelete,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
setActionBarEntries([
|
||||
<BoardActionBarButtonDeleteBoardCard onDelete={handleDelete} />,
|
||||
]);
|
||||
setActionBarEntries([<BoardActionBarButtonDeleteBoardCard />]);
|
||||
};
|
||||
}
|
||||
|
||||
28
front/src/modules/ui/board/hooks/useCurrentCardSelected.ts
Normal file
28
front/src/modules/ui/board/hooks/useCurrentCardSelected.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||
|
||||
import { BoardCardIdContext } from '../states/BoardCardIdContext';
|
||||
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
|
||||
|
||||
export function useCurrentCardSelected() {
|
||||
const currentCardId = useContext(BoardCardIdContext);
|
||||
|
||||
const [isCardSelected] = useRecoilState(
|
||||
isCardSelectedFamilyState(currentCardId ?? ''),
|
||||
);
|
||||
|
||||
const setCurrentCardSelected = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(selected: boolean) => {
|
||||
if (!currentCardId) return;
|
||||
|
||||
set(isCardSelectedFamilyState(currentCardId), selected);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
currentCardSelected: isCardSelected,
|
||||
setCurrentCardSelected,
|
||||
};
|
||||
}
|
||||
27
front/src/modules/ui/board/hooks/useRemoveCardIds.ts
Normal file
27
front/src/modules/ui/board/hooks/useRemoveCardIds.ts
Normal file
@ -0,0 +1,27 @@
|
||||
// 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 { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState';
|
||||
import { boardColumnsState } from '../states/boardColumnsState';
|
||||
|
||||
export function useRemoveCardIds() {
|
||||
return useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(cardIdToRemove: string[]) => {
|
||||
const boardColumns = snapshot
|
||||
.getLoadable(boardColumnsState)
|
||||
.valueOrThrow();
|
||||
|
||||
boardColumns.forEach((boardColumn) => {
|
||||
const columnCardIds = snapshot
|
||||
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
|
||||
.valueOrThrow();
|
||||
set(
|
||||
boardCardIdsByColumnIdFamilyState(boardColumn.id),
|
||||
columnCardIds.filter((cardId) => !cardIdToRemove.includes(cardId)),
|
||||
);
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
27
front/src/modules/ui/board/hooks/useResetCardSelection.ts
Normal file
27
front/src/modules/ui/board/hooks/useResetCardSelection.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState';
|
||||
import { boardColumnsState } from '../states/boardColumnsState';
|
||||
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
|
||||
|
||||
export function useResetCardSelection() {
|
||||
return useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const boardColumns = snapshot
|
||||
.getLoadable(boardColumnsState)
|
||||
.valueOrThrow();
|
||||
|
||||
const cardIds = boardColumns.flatMap((boardColumn) =>
|
||||
snapshot
|
||||
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
|
||||
.valueOrThrow(),
|
||||
);
|
||||
|
||||
for (const cardId of cardIds) {
|
||||
set(isCardSelectedFamilyState(cardId), false);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
9
front/src/modules/ui/board/hooks/useSetCardSelected.ts
Normal file
9
front/src/modules/ui/board/hooks/useSetCardSelected.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
|
||||
|
||||
export function useSetCardSelected() {
|
||||
return useRecoilCallback(({ set }) => (cardId: string, selected: boolean) => {
|
||||
set(isCardSelectedFamilyState(cardId), selected);
|
||||
});
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { FieldDefinition } from '@/ui/editable-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/ui/editable-field/types/FieldMetadata';
|
||||
import {
|
||||
FieldMetadata,
|
||||
FieldType,
|
||||
} from '@/ui/editable-field/types/FieldMetadata';
|
||||
|
||||
export const FieldDefinitionContext = createContext<
|
||||
FieldDefinition<FieldMetadata>
|
||||
@ -9,6 +12,6 @@ export const FieldDefinitionContext = createContext<
|
||||
id: '',
|
||||
label: '',
|
||||
icon: undefined,
|
||||
type: '',
|
||||
type: 'unknown' satisfies FieldType,
|
||||
metadata: {} as FieldMetadata,
|
||||
});
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isCardSelectedFamilyState = atomFamily<boolean, string>({
|
||||
key: 'isCardSelectedFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const selectedBoardCardIdsState = atom<string[]>({
|
||||
key: 'selectedBoardCardIdsState',
|
||||
default: [],
|
||||
});
|
||||
22
front/src/modules/ui/board/states/selectedCardIdsSelector.ts
Normal file
22
front/src/modules/ui/board/states/selectedCardIdsSelector.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { selector } from 'recoil';
|
||||
|
||||
import { boardCardIdsByColumnIdFamilyState } from './boardCardIdsByColumnIdFamilyState';
|
||||
import { boardColumnsState } from './boardColumnsState';
|
||||
import { isCardSelectedFamilyState } from './isCardSelectedFamilyState';
|
||||
|
||||
export const selectedCardIdsSelector = selector<string[]>({
|
||||
key: 'selectedCardIdsSelector',
|
||||
get: ({ get }) => {
|
||||
const boardColumns = get(boardColumnsState);
|
||||
|
||||
const cardIds = boardColumns.flatMap((boardColumn) =>
|
||||
get(boardCardIdsByColumnIdFamilyState(boardColumn.id)),
|
||||
);
|
||||
|
||||
const selectedCardIds = cardIds.filter(
|
||||
(cardId) => get(isCardSelectedFamilyState(cardId)) === true,
|
||||
);
|
||||
|
||||
return selectedCardIds;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user