Allow Card field update and card drag on new record board (#3661)
This commit is contained in:
@ -1,14 +1,14 @@
|
||||
import { getActivitySummary } from '../getActivitySummary';
|
||||
|
||||
describe('getActivitySummary', () => {
|
||||
it('should work for empty body', () => {
|
||||
it('should work for empty body ""', () => {
|
||||
const activityBody = {};
|
||||
|
||||
const res = getActivitySummary(JSON.stringify(activityBody));
|
||||
|
||||
expect(res).toEqual('');
|
||||
});
|
||||
it('should work for empty body', () => {
|
||||
it('should work for empty body {}', () => {
|
||||
const activityBody = '';
|
||||
|
||||
const res = getActivitySummary(JSON.stringify(activityBody));
|
||||
@ -109,4 +109,34 @@ describe('getActivitySummary', () => {
|
||||
|
||||
expect(res).toEqual('');
|
||||
});
|
||||
|
||||
it('should work for table as first block', () => {
|
||||
const activityBody = [
|
||||
{
|
||||
id: '591c3aa1-9e51-465d-bb59-611ef60344fb',
|
||||
type: 'table',
|
||||
props: { textColor: 'default', backgroundColor: 'default' },
|
||||
content: {
|
||||
type: 'tableContent',
|
||||
rows: [{ cells: [[], [], []] }, { cells: [[], [], []] }],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: '1899ab29-0122-4890-bb50-4d7cf2802f98',
|
||||
type: 'paragraph',
|
||||
props: {
|
||||
textColor: 'default',
|
||||
backgroundColor: 'default',
|
||||
textAlignment: 'left',
|
||||
},
|
||||
content: [],
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
const res = getActivitySummary(JSON.stringify(activityBody));
|
||||
|
||||
expect(res).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,9 +1,25 @@
|
||||
import { isArray } from '@sniptt/guards';
|
||||
|
||||
export const getActivitySummary = (activityBody: string) => {
|
||||
const noteBody = activityBody ? JSON.parse(activityBody) : [];
|
||||
|
||||
return (
|
||||
noteBody[0]?.content?.text ||
|
||||
noteBody[0]?.content?.map((content: any) => content?.text).join(' ') ||
|
||||
''
|
||||
);
|
||||
if (!noteBody.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const firstNoteBlockContent = noteBody[0].content;
|
||||
|
||||
if (!firstNoteBlockContent) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (firstNoteBlockContent.text) {
|
||||
return noteBody[0].content.text;
|
||||
}
|
||||
|
||||
if (isArray(firstNoteBlockContent)) {
|
||||
return firstNoteBlockContent.map((content: any) => content.text).join(' ');
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { useRef } from 'react';
|
||||
import { useContext, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { DragDropContext } 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 { useRecoilValue } from 'recoil';
|
||||
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 { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn';
|
||||
import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export type RecordBoardProps = {
|
||||
recordBoardId: string;
|
||||
@ -38,11 +40,50 @@ const StyledBoardHeader = styled.div`
|
||||
`;
|
||||
|
||||
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||
const { updateOneRecord, objectMetadataItem } =
|
||||
useContext(RecordBoardContext);
|
||||
const boardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { getColumnIdsState } = useRecordBoardStates(recordBoardId);
|
||||
const { getColumnIdsState, columnsFamilySelector } =
|
||||
useRecordBoardStates(recordBoardId);
|
||||
const columnIds = useRecoilValue(getColumnIdsState());
|
||||
|
||||
const selectFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(field) => field.type === FieldMetadataType.Select,
|
||||
);
|
||||
|
||||
const onDragEnd: OnDragEndResponder = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (result) => {
|
||||
const draggedRecordId = result.draggableId;
|
||||
const destinationColumnId = result.destination?.droppableId;
|
||||
|
||||
if (!destinationColumnId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const column = await snapshot
|
||||
.getLoadable(columnsFamilySelector(destinationColumnId))
|
||||
.getValue();
|
||||
|
||||
if (!column) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectFieldMetadataItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOneRecord({
|
||||
idToUpdate: draggedRecordId,
|
||||
updateOneRecordInput: {
|
||||
[selectFieldMetadataItem.name]: column.value,
|
||||
},
|
||||
});
|
||||
},
|
||||
[columnsFamilySelector, selectFieldMetadataItem, updateOneRecord],
|
||||
);
|
||||
|
||||
return (
|
||||
<RecordBoardScope
|
||||
recordBoardScopeId={getScopeIdFromComponentId(recordBoardId)}
|
||||
@ -53,7 +94,7 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||
<StyledBoardHeader />
|
||||
<ScrollWrapper>
|
||||
<StyledContainer ref={boardRef}>
|
||||
<DragDropContext onDragEnd={() => {}}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{columnIds.map((columnId) => (
|
||||
<RecordBoardColumn
|
||||
key={columnId}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
type RecordBoardContextProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
createOneRecord: (recordInput: Partial<ObjectRecord>) => void;
|
||||
updateOneRecord: ({
|
||||
idToUpdate,
|
||||
updateOneRecordInput,
|
||||
}: {
|
||||
idToUpdate: string;
|
||||
updateOneRecordInput: Partial<Omit<ObjectRecord, 'id'>>;
|
||||
}) => void;
|
||||
deleteOneRecord: (idToDelete: string) => Promise<unknown>;
|
||||
};
|
||||
|
||||
export const RecordBoardContext = createContext<RecordBoardContextProps>(
|
||||
{} as RecordBoardContextProps,
|
||||
);
|
||||
@ -1,17 +1,24 @@
|
||||
import { ReactNode, useContext, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import {
|
||||
FieldContext,
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { IconEye } from '@/ui/display/icon/index';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
||||
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
||||
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
||||
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
||||
|
||||
const StyledBoardCard = styled.div<{ selected: boolean }>`
|
||||
@ -105,7 +112,7 @@ const StyledCheckboxContainer = styled.div`
|
||||
const StyledFieldContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
const StyledCompactIconContainer = styled.div`
|
||||
@ -116,7 +123,7 @@ const StyledCompactIconContainer = styled.div`
|
||||
|
||||
export const RecordBoardCard = () => {
|
||||
const { recordId } = useContext(RecordBoardCardContext);
|
||||
|
||||
const { updateOneRecord } = useContext(RecordBoardContext);
|
||||
const {
|
||||
getObjectSingularNameState,
|
||||
getIsCompactModeActiveState,
|
||||
@ -139,6 +146,19 @@ export const RecordBoardCard = () => {
|
||||
|
||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||
|
||||
const handleContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setIsCurrentCardSelected(true);
|
||||
setContextMenuPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
setContextMenuOpenState(true);
|
||||
};
|
||||
|
||||
const PreventSelectOnClickContainer = ({
|
||||
children,
|
||||
}: {
|
||||
@ -159,16 +179,29 @@ export const RecordBoardCard = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const useUpdateOneRecordHook: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
if (!objectNameSingular || !record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledBoardCardWrapper>
|
||||
<StyledBoardCardWrapper onContextMenu={handleContextMenu}>
|
||||
<StyledBoardCard
|
||||
selected={isCurrentCardSelected}
|
||||
onMouseLeave={onMouseLeaveBoard}
|
||||
onClick={() => setIsCurrentCardSelected(!isCurrentCardSelected)}
|
||||
onClick={() => {
|
||||
setIsCurrentCardSelected(!isCurrentCardSelected);
|
||||
}}
|
||||
>
|
||||
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
|
||||
<RecordChip objectNameSingular={objectNameSingular} record={record} />
|
||||
@ -212,6 +245,7 @@ export const RecordBoardCard = () => {
|
||||
type: fieldDefinition.type,
|
||||
metadata: fieldDefinition.metadata,
|
||||
},
|
||||
useUpdateRecord: useUpdateOneRecordHook,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
|
||||
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||
|
||||
export const RecordBoardCardDraggableContainer = ({
|
||||
recordId,
|
||||
index,
|
||||
}: {
|
||||
recordId: string;
|
||||
index: number;
|
||||
}) => {
|
||||
return (
|
||||
<Draggable key={recordId} draggableId={recordId} index={index}>
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
ref={draggableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.dragHandleProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.draggableProps}
|
||||
className="entity-board-card"
|
||||
data-selectable-id={recordId}
|
||||
data-select-disable
|
||||
>
|
||||
<RecordBoardCard />
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
@ -3,8 +3,6 @@ import { Droppable } from '@hello-pangea/dnd';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
import { RecordBoardColumnCardsContainer } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer';
|
||||
import { RecordBoardColumnHeader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeader';
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
@ -52,7 +50,7 @@ export const RecordBoardColumn = ({
|
||||
recordBoardRecordIdsByColumnIdFamilyState(recordBoardColumnId),
|
||||
);
|
||||
|
||||
if (!columnDefinition || !recordIds) {
|
||||
if (!columnDefinition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -70,16 +68,8 @@ export const RecordBoardColumn = ({
|
||||
<RecordBoardColumnHeader />
|
||||
<RecordBoardColumnCardsContainer
|
||||
droppableProvided={droppableProvided}
|
||||
>
|
||||
{recordIds.map((recordId) => (
|
||||
<RecordBoardCardContext.Provider
|
||||
value={{ recordId }}
|
||||
key={recordId}
|
||||
>
|
||||
<RecordBoardCard />
|
||||
</RecordBoardCardContext.Provider>
|
||||
))}
|
||||
</RecordBoardColumnCardsContainer>
|
||||
recordIds={recordIds}
|
||||
/>
|
||||
</StyledColumn>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
@ -2,6 +2,8 @@ import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { DroppableProvided } from '@hello-pangea/dnd';
|
||||
|
||||
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
|
||||
|
||||
const StyledPlaceholder = styled.div`
|
||||
min-height: 1px;
|
||||
`;
|
||||
@ -13,12 +15,12 @@ const StyledColumnCardsContainer = styled.div`
|
||||
`;
|
||||
|
||||
type RecordBoardColumnCardsContainerProps = {
|
||||
children: React.ReactNode;
|
||||
recordIds: string[];
|
||||
droppableProvided: DroppableProvided;
|
||||
};
|
||||
|
||||
export const RecordBoardColumnCardsContainer = ({
|
||||
children,
|
||||
recordIds,
|
||||
droppableProvided,
|
||||
}: RecordBoardColumnCardsContainerProps) => {
|
||||
return (
|
||||
@ -27,7 +29,7 @@ export const RecordBoardColumnCardsContainer = ({
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...droppableProvided?.droppableProps}
|
||||
>
|
||||
{children}
|
||||
<RecordBoardColumnCardsMemo recordIds={recordIds} />
|
||||
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
|
||||
</StyledColumnCardsContainer>
|
||||
);
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import { RecordBoardCardDraggableContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
|
||||
type RecordBoardColumnCardsMemoProps = {
|
||||
recordIds: string[];
|
||||
};
|
||||
|
||||
export const RecordBoardColumnCardsMemo = React.memo(
|
||||
({ recordIds }: RecordBoardColumnCardsMemoProps) => {
|
||||
return recordIds.map((recordId, index) => (
|
||||
<RecordBoardCardContext.Provider value={{ recordId }} key={recordId}>
|
||||
<RecordBoardCardDraggableContainer recordId={recordId} index={index} />
|
||||
</RecordBoardCardContext.Provider>
|
||||
));
|
||||
},
|
||||
);
|
||||
@ -1,4 +1,9 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordBoard } from '@/object-record/record-board/components/RecordBoard';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
|
||||
type RecordIndexBoardContainerProps = {
|
||||
recordBoardId: string;
|
||||
@ -9,6 +14,24 @@ type RecordIndexBoardContainerProps = {
|
||||
|
||||
export const RecordIndexBoardContainer = ({
|
||||
recordBoardId,
|
||||
objectNameSingular,
|
||||
}: RecordIndexBoardContainerProps) => {
|
||||
return <RecordBoard recordBoardId={recordBoardId} />;
|
||||
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
|
||||
|
||||
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
|
||||
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
|
||||
const { createOneRecord } = useCreateOneRecord({ objectNameSingular });
|
||||
|
||||
return (
|
||||
<RecordBoardContext.Provider
|
||||
value={{
|
||||
objectMetadataItem,
|
||||
createOneRecord,
|
||||
updateOneRecord,
|
||||
deleteOneRecord,
|
||||
}}
|
||||
>
|
||||
<RecordBoard recordBoardId={recordBoardId} />
|
||||
</RecordBoardContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user