@ -1,9 +1,3 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { ReactNode, useContext, useState } from 'react';
|
|
||||||
import { useInView } from 'react-intersection-observer';
|
|
||||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
|
||||||
import { AvatarChipVariant, IconEye } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||||
@ -15,15 +9,24 @@ import {
|
|||||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||||
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
|
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
|
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
||||||
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
||||||
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
||||||
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
||||||
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { ReactNode, useContext, useState } from 'react';
|
||||||
|
import { useInView } from 'react-intersection-observer';
|
||||||
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
import { AvatarChipVariant, IconEye } from 'twenty-ui';
|
||||||
|
import { useAddNewCard } from '../../record-board-column/hooks/useAddNewCard';
|
||||||
|
|
||||||
const StyledBoardCard = styled.div<{ selected: boolean }>`
|
const StyledBoardCard = styled.div<{ selected: boolean }>`
|
||||||
background-color: ${({ theme, selected }) =>
|
background-color: ${({ theme, selected }) =>
|
||||||
@ -61,6 +64,14 @@ const StyledBoardCard = styled.div<{ selected: boolean }>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledTextInput = styled(TextInput)`
|
||||||
|
backdrop-filter: blur(12px) saturate(200%) contrast(50%) brightness(130%);
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||||
|
width: ${({ theme }) => theme.spacing(53)};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledBoardCardWrapper = styled.div`
|
const StyledBoardCardWrapper = styled.div`
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -130,7 +141,21 @@ const StyledRecordInlineCellPlaceholder = styled.div`
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordBoardCard = () => {
|
const StyledRecordInlineCell = styled(RecordInlineCell)`
|
||||||
|
height: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RecordBoardCard = ({
|
||||||
|
isCreating = false,
|
||||||
|
onCreateSuccess,
|
||||||
|
position,
|
||||||
|
}: {
|
||||||
|
isCreating?: boolean;
|
||||||
|
onCreateSuccess?: () => void;
|
||||||
|
position?: 'first' | 'last';
|
||||||
|
}) => {
|
||||||
|
const [newLabelValue, setNewLabelValue] = useState('');
|
||||||
|
const { handleBlur, handleInputEnter } = useAddNewCard();
|
||||||
const { recordId } = useContext(RecordBoardCardContext);
|
const { recordId } = useContext(RecordBoardCardContext);
|
||||||
const { updateOneRecord, objectMetadataItem } =
|
const { updateOneRecord, objectMetadataItem } =
|
||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
@ -139,7 +164,6 @@ export const RecordBoardCard = () => {
|
|||||||
isRecordBoardCardSelectedFamilyState,
|
isRecordBoardCardSelectedFamilyState,
|
||||||
visibleFieldDefinitionsState,
|
visibleFieldDefinitionsState,
|
||||||
} = useRecordBoardStates();
|
} = useRecordBoardStates();
|
||||||
|
|
||||||
const isCompactModeActive = useRecoilValue(isCompactModeActiveState);
|
const isCompactModeActive = useRecoilValue(isCompactModeActiveState);
|
||||||
|
|
||||||
const [isCardInCompactMode, setIsCardInCompactMode] = useState(true);
|
const [isCardInCompactMode, setIsCardInCompactMode] = useState(true);
|
||||||
@ -205,66 +229,106 @@ export const RecordBoardCard = () => {
|
|||||||
rootMargin: '1000px',
|
rootMargin: '1000px',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const visibleFieldDefinitionsFiltered = visibleFieldDefinitions.filter(
|
const visibleFieldDefinitionsFiltered = visibleFieldDefinitions.filter(
|
||||||
(boardField) => !boardField.isLabelIdentifier,
|
(boardField) => !boardField.isLabelIdentifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const labelIdentifierField = visibleFieldDefinitions.find(
|
||||||
|
(field) => field.isLabelIdentifier,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBoardCardWrapper onContextMenu={handleContextMenu}>
|
<StyledBoardCardWrapper onContextMenu={handleContextMenu}>
|
||||||
<RecordValueSetterEffect recordId={recordId} />
|
{!isCreating && <RecordValueSetterEffect recordId={recordId} />}
|
||||||
<StyledBoardCard
|
<StyledBoardCard
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
selected={isCurrentCardSelected}
|
selected={isCurrentCardSelected}
|
||||||
onMouseLeave={onMouseLeaveBoard}
|
onMouseLeave={onMouseLeaveBoard}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsCurrentCardSelected(!isCurrentCardSelected);
|
if (!isCreating) {
|
||||||
|
setIsCurrentCardSelected(!isCurrentCardSelected);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
|
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
|
||||||
<RecordIdentifierChip
|
{isCreating && position !== undefined ? (
|
||||||
objectNameSingular={objectMetadataItem.nameSingular}
|
<RecordInlineCellEditMode>
|
||||||
record={record}
|
<StyledTextInput
|
||||||
variant={AvatarChipVariant.Transparent}
|
autoFocus
|
||||||
/>
|
value={newLabelValue}
|
||||||
{isCompactModeActive && (
|
onInputEnter={() =>
|
||||||
<StyledCompactIconContainer className="compact-icon-container">
|
handleInputEnter(
|
||||||
<LightIconButton
|
labelIdentifierField?.label ?? '',
|
||||||
Icon={IconEye}
|
newLabelValue,
|
||||||
accent="tertiary"
|
position,
|
||||||
onClick={(e) => {
|
onCreateSuccess,
|
||||||
e.stopPropagation();
|
)
|
||||||
setIsCardInCompactMode(false);
|
}
|
||||||
}}
|
onBlur={() =>
|
||||||
|
handleBlur(
|
||||||
|
labelIdentifierField?.label ?? '',
|
||||||
|
newLabelValue,
|
||||||
|
position,
|
||||||
|
onCreateSuccess,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChange={(text: string) => setNewLabelValue(text)}
|
||||||
|
placeholder={labelIdentifierField?.label}
|
||||||
/>
|
/>
|
||||||
</StyledCompactIconContainer>
|
</RecordInlineCellEditMode>
|
||||||
)}
|
) : (
|
||||||
<StyledCheckboxContainer className="checkbox-container">
|
<RecordIdentifierChip
|
||||||
<Checkbox
|
objectNameSingular={objectMetadataItem.nameSingular}
|
||||||
hoverable
|
record={record as ObjectRecord}
|
||||||
checked={isCurrentCardSelected}
|
variant={AvatarChipVariant.Transparent}
|
||||||
onChange={() => setIsCurrentCardSelected(!isCurrentCardSelected)}
|
|
||||||
variant={CheckboxVariant.Secondary}
|
|
||||||
/>
|
/>
|
||||||
</StyledCheckboxContainer>
|
)}
|
||||||
|
|
||||||
|
{!isCreating && (
|
||||||
|
<>
|
||||||
|
{isCompactModeActive && (
|
||||||
|
<StyledCompactIconContainer className="compact-icon-container">
|
||||||
|
<LightIconButton
|
||||||
|
Icon={IconEye}
|
||||||
|
accent="tertiary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsCardInCompactMode(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledCompactIconContainer>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<StyledCheckboxContainer className="checkbox-container">
|
||||||
|
<Checkbox
|
||||||
|
hoverable
|
||||||
|
checked={isCurrentCardSelected}
|
||||||
|
onChange={() =>
|
||||||
|
setIsCurrentCardSelected(!isCurrentCardSelected)
|
||||||
|
}
|
||||||
|
variant={CheckboxVariant.Secondary}
|
||||||
|
/>
|
||||||
|
</StyledCheckboxContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</StyledBoardCardHeader>
|
</StyledBoardCardHeader>
|
||||||
<StyledBoardCardBody>
|
|
||||||
<AnimatedEaseInOut
|
<AnimatedEaseInOut
|
||||||
isOpen={!isCardInCompactMode || !isCompactModeActive}
|
isOpen={!isCardInCompactMode || !isCompactModeActive}
|
||||||
initial={false}
|
initial={false}
|
||||||
>
|
>
|
||||||
|
<StyledBoardCardBody>
|
||||||
{visibleFieldDefinitionsFiltered.map((fieldDefinition) => (
|
{visibleFieldDefinitionsFiltered.map((fieldDefinition) => (
|
||||||
<PreventSelectOnClickContainer
|
<PreventSelectOnClickContainer
|
||||||
key={fieldDefinition.fieldMetadataId}
|
key={fieldDefinition.fieldMetadataId}
|
||||||
>
|
>
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
value={{
|
value={{
|
||||||
recordId,
|
recordId: isCreating ? '' : recordId,
|
||||||
maxWidth: 156,
|
maxWidth: 156,
|
||||||
recoilScopeId: recordId + fieldDefinition.fieldMetadataId,
|
recoilScopeId:
|
||||||
|
(isCreating ? 'new' : recordId) +
|
||||||
|
fieldDefinition.fieldMetadataId,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
fieldDefinition: {
|
fieldDefinition: {
|
||||||
disableTooltip: false,
|
disableTooltip: false,
|
||||||
@ -284,15 +348,15 @@ export const RecordBoardCard = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{inView ? (
|
{inView ? (
|
||||||
<RecordInlineCell />
|
<StyledRecordInlineCell />
|
||||||
) : (
|
) : (
|
||||||
<StyledRecordInlineCellPlaceholder />
|
<StyledRecordInlineCellPlaceholder />
|
||||||
)}
|
)}
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
</PreventSelectOnClickContainer>
|
</PreventSelectOnClickContainer>
|
||||||
))}
|
))}
|
||||||
</AnimatedEaseInOut>
|
</StyledBoardCardBody>
|
||||||
</StyledBoardCardBody>
|
</AnimatedEaseInOut>
|
||||||
</StyledBoardCard>
|
</StyledBoardCard>
|
||||||
</StyledBoardCardWrapper>
|
</StyledBoardCardWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useContext } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Draggable, DroppableProvided } from '@hello-pangea/dnd';
|
import { Draggable, DroppableProvided } from '@hello-pangea/dnd';
|
||||||
|
import { useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
@ -110,7 +110,7 @@ export const RecordBoardColumnCardsContainer = ({
|
|||||||
CoreObjectNameSingular.Opportunity ? (
|
CoreObjectNameSingular.Opportunity ? (
|
||||||
<RecordBoardColumnNewOpportunityButton />
|
<RecordBoardColumnNewOpportunityButton />
|
||||||
) : (
|
) : (
|
||||||
<RecordBoardColumnNewButton />
|
<RecordBoardColumnNewButton columnId={columnDefinition.id} />
|
||||||
)}
|
)}
|
||||||
</StyledNewButtonContainer>
|
</StyledNewButtonContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,10 +4,11 @@ import { IconDotsVertical, IconPlus, Tag } from 'twenty-ui';
|
|||||||
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
|
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||||
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
|
||||||
import { useAddNewOpportunity } from '@/object-record/record-board/record-board-column/hooks/useAddNewOpportunity';
|
import { useAddNewOpportunity } from '@/object-record/record-board/record-board-column/hooks/useAddNewOpportunity';
|
||||||
|
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
|
||||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||||
import { RecordBoardColumnDefinitionType } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
|
import { RecordBoardColumnDefinitionType } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
|
||||||
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
||||||
@ -94,16 +95,16 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
handleCancel,
|
handleCancel,
|
||||||
handleEntitySelect,
|
handleEntitySelect,
|
||||||
} = useAddNewOpportunity('first');
|
} = useAddNewOpportunity('first');
|
||||||
const { handleAddNewCardClick } = useAddNewCard('first');
|
|
||||||
|
const { newRecord, handleNewButtonClick, handleCreateSuccess } =
|
||||||
|
useColumnNewCardActions(columnDefinition.id);
|
||||||
|
|
||||||
const isOpportunity =
|
const isOpportunity =
|
||||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity;
|
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity;
|
||||||
|
|
||||||
const handleClick = isOpportunity
|
const handleClick = isOpportunity
|
||||||
? handleAddNewOpportunityClick
|
? handleAddNewOpportunityClick
|
||||||
: () => {
|
: () => handleNewButtonClick('first');
|
||||||
handleAddNewCardClick();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -164,6 +165,13 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
stageId={columnDefinition.id}
|
stageId={columnDefinition.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{newRecord?.isCreating && newRecord.position === 'first' && (
|
||||||
|
<RecordBoardCard
|
||||||
|
isCreating={true}
|
||||||
|
onCreateSuccess={() => handleCreateSuccess('first')}
|
||||||
|
position="first"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isCreatingCard && (
|
{isCreatingCard && (
|
||||||
<SingleEntitySelect
|
<SingleEntitySelect
|
||||||
disableBackgroundBlur
|
disableBackgroundBlur
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||||
|
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconPlus } from 'twenty-ui';
|
import { IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
const StyledNewButton = styled.button`
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: baseline;
|
align-self: baseline;
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
@ -15,19 +15,35 @@ const StyledButton = styled.button`
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
padding: ${({ theme }) => theme.spacing(1)};
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${({ theme }) => theme.background.tertiary};
|
background-color: ${({ theme }) => theme.background.tertiary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordBoardColumnNewButton = () => {
|
export const RecordBoardColumnNewButton = ({
|
||||||
|
columnId,
|
||||||
|
}: {
|
||||||
|
columnId: string;
|
||||||
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { handleAddNewCardClick } = useAddNewCard('last');
|
|
||||||
|
const { newRecord, handleNewButtonClick, handleCreateSuccess } =
|
||||||
|
useColumnNewCardActions(columnId);
|
||||||
|
|
||||||
|
if (newRecord.isCreating && newRecord.position === 'last') {
|
||||||
|
return (
|
||||||
|
<RecordBoardCard
|
||||||
|
isCreating={true}
|
||||||
|
onCreateSuccess={() => handleCreateSuccess('last')}
|
||||||
|
position="last"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButton onClick={handleAddNewCardClick}>
|
<StyledNewButton onClick={() => handleNewButtonClick('last')}>
|
||||||
<IconPlus size={theme.icon.size.md} />
|
<IconPlus size={theme.icon.size.md} />
|
||||||
New
|
New
|
||||||
</StyledButton>
|
</StyledNewButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,20 +1,135 @@
|
|||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
import { useContext } from 'react';
|
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
|
||||||
|
import { useCallback, useContext } from 'react';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
export const useAddNewCard = (position: string) => {
|
export const useAddNewCard = () => {
|
||||||
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
const columnContext = useContext(RecordBoardColumnContext);
|
||||||
const { createOneRecord, selectFieldMetadataItem } =
|
const { createOneRecord, selectFieldMetadataItem } =
|
||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
|
|
||||||
const handleAddNewCardClick = () => {
|
const getColumnDefinitionId = useCallback(
|
||||||
createOneRecord({
|
(columnId?: string) => {
|
||||||
[selectFieldMetadataItem.name]: columnDefinition.value,
|
const columnDefinitionId = columnId || columnContext?.columnDefinition.id;
|
||||||
position: position,
|
if (!columnDefinitionId) {
|
||||||
});
|
throw new Error('Column ID is required');
|
||||||
|
}
|
||||||
|
return columnDefinitionId;
|
||||||
|
},
|
||||||
|
[columnContext],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addNewCard = useCallback(
|
||||||
|
(set: any, columnDefinitionId: string, position: 'first' | 'last') => {
|
||||||
|
set(
|
||||||
|
recordBoardNewRecordByColumnIdSelector({
|
||||||
|
familyKey: columnDefinitionId,
|
||||||
|
scopeId: columnDefinitionId,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
columnId: columnDefinitionId,
|
||||||
|
isCreating: true,
|
||||||
|
position,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const createRecord = useCallback(
|
||||||
|
(
|
||||||
|
labelIdentifier: string,
|
||||||
|
labelValue: string,
|
||||||
|
position: 'first' | 'last',
|
||||||
|
) => {
|
||||||
|
if (labelValue !== '') {
|
||||||
|
createOneRecord({
|
||||||
|
[selectFieldMetadataItem.name]: columnContext?.columnDefinition.value,
|
||||||
|
position,
|
||||||
|
[labelIdentifier.toLowerCase()]: labelValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[createOneRecord, columnContext, selectFieldMetadataItem],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddNewCardClick = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
(
|
||||||
|
labelIdentifier: string,
|
||||||
|
labelValue: string,
|
||||||
|
position: 'first' | 'last',
|
||||||
|
columnId?: string,
|
||||||
|
): void => {
|
||||||
|
const columnDefinitionId = getColumnDefinitionId(columnId);
|
||||||
|
addNewCard(set, columnDefinitionId, position);
|
||||||
|
createRecord(labelIdentifier, labelValue, position);
|
||||||
|
},
|
||||||
|
[addNewCard, createRecord, getColumnDefinitionId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreateSuccess = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
(position: 'first' | 'last', columnId?: string): void => {
|
||||||
|
const columnDefinitionId = getColumnDefinitionId(columnId);
|
||||||
|
set(
|
||||||
|
recordBoardNewRecordByColumnIdSelector({
|
||||||
|
familyKey: columnDefinitionId,
|
||||||
|
scopeId: columnDefinitionId,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
columnId: columnDefinitionId,
|
||||||
|
isCreating: false,
|
||||||
|
position,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[getColumnDefinitionId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreate = (
|
||||||
|
labelIdentifier: string,
|
||||||
|
labelValue: string,
|
||||||
|
position: 'first' | 'last',
|
||||||
|
onCreateSuccess?: () => void,
|
||||||
|
) => {
|
||||||
|
if (labelValue.trim() !== '' && position !== undefined) {
|
||||||
|
handleAddNewCardClick(labelIdentifier, labelValue.trim(), position);
|
||||||
|
onCreateSuccess?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = (
|
||||||
|
labelIdentifier: string,
|
||||||
|
labelValue: string,
|
||||||
|
position: 'first' | 'last',
|
||||||
|
onCreateSuccess?: () => void,
|
||||||
|
) => {
|
||||||
|
if (labelValue.trim() === '') {
|
||||||
|
onCreateSuccess?.();
|
||||||
|
} else {
|
||||||
|
handleCreate(labelIdentifier, labelValue, position, onCreateSuccess);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputEnter = (
|
||||||
|
labelIdentifier: string,
|
||||||
|
labelValue: string,
|
||||||
|
position: 'first' | 'last',
|
||||||
|
onCreateSuccess?: () => void,
|
||||||
|
) => {
|
||||||
|
handleCreate(labelIdentifier, labelValue, position, onCreateSuccess);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleAddNewCardClick,
|
handleAddNewCardClick,
|
||||||
|
handleCreateSuccess,
|
||||||
|
handleCreate,
|
||||||
|
handleBlur,
|
||||||
|
handleInputEnter,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
|
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
||||||
|
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
export const useColumnNewCardActions = (columnId: string) => {
|
||||||
|
const { visibleFieldDefinitionsState } = useRecordBoardStates();
|
||||||
|
const visibleFieldDefinitions = useRecoilValue(
|
||||||
|
visibleFieldDefinitionsState(),
|
||||||
|
);
|
||||||
|
const labelIdentifierField = visibleFieldDefinitions.find(
|
||||||
|
(field) => field.isLabelIdentifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { handleAddNewCardClick, handleCreateSuccess } = useAddNewCard();
|
||||||
|
|
||||||
|
const newRecord = useRecoilValue(
|
||||||
|
recordBoardNewRecordByColumnIdSelector({
|
||||||
|
familyKey: columnId,
|
||||||
|
scopeId: columnId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNewButtonClick = (position: 'first' | 'last') => {
|
||||||
|
handleAddNewCardClick(
|
||||||
|
labelIdentifierField?.label ?? '',
|
||||||
|
'',
|
||||||
|
position,
|
||||||
|
columnId,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
newRecord,
|
||||||
|
handleNewButtonClick,
|
||||||
|
handleCreateSuccess,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
|
||||||
|
|
||||||
|
export type NewCard = {
|
||||||
|
id: string;
|
||||||
|
columnId: string;
|
||||||
|
isCreating: boolean;
|
||||||
|
position: 'first' | 'last';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const recordBoardNewRecordByColumnIdComponentFamilyState =
|
||||||
|
createComponentFamilyState<NewCard, string>({
|
||||||
|
key: 'recordBoardNewRecordByColumnIdComponentFamilyState',
|
||||||
|
defaultValue: {
|
||||||
|
id: '',
|
||||||
|
columnId: '',
|
||||||
|
isCreating: false,
|
||||||
|
position: 'last',
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
|
||||||
|
import {
|
||||||
|
NewCard,
|
||||||
|
recordBoardNewRecordByColumnIdComponentFamilyState,
|
||||||
|
} from '../recordBoardNewRecordByColumnIdComponentFamilyState';
|
||||||
|
|
||||||
|
export const recordBoardNewRecordByColumnIdSelector =
|
||||||
|
createComponentFamilySelector<NewCard, string>({
|
||||||
|
key: 'recordBoardNewRecordByColumnIdSelector',
|
||||||
|
get:
|
||||||
|
({ familyKey, scopeId }: { familyKey: string; scopeId: string }) =>
|
||||||
|
({ get }) => {
|
||||||
|
return get(
|
||||||
|
recordBoardNewRecordByColumnIdComponentFamilyState({
|
||||||
|
familyKey,
|
||||||
|
scopeId,
|
||||||
|
}),
|
||||||
|
) as NewCard;
|
||||||
|
},
|
||||||
|
set:
|
||||||
|
({ familyKey, scopeId }: { familyKey: string; scopeId: string }) =>
|
||||||
|
({ set }, newValue) => {
|
||||||
|
set(
|
||||||
|
recordBoardNewRecordByColumnIdComponentFamilyState({
|
||||||
|
familyKey,
|
||||||
|
scopeId,
|
||||||
|
}),
|
||||||
|
newValue as NewCard,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
|
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
||||||
import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
|
import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
|
||||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
@ -37,8 +38,15 @@ export const RecordIndexPageKanbanAddButton = () => {
|
|||||||
RecordIndexRootPropsContext,
|
RecordIndexRootPropsContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { columnIdsState } = useRecordBoardStates(recordIndexId);
|
const { columnIdsState, visibleFieldDefinitionsState } =
|
||||||
|
useRecordBoardStates(recordIndexId);
|
||||||
const columnIds = useRecoilValue(columnIdsState);
|
const columnIds = useRecoilValue(columnIdsState);
|
||||||
|
const visibleFieldDefinitions = useRecoilValue(
|
||||||
|
visibleFieldDefinitionsState(),
|
||||||
|
);
|
||||||
|
const labelIdentifierField = visibleFieldDefinitions.find(
|
||||||
|
(field) => field.isLabelIdentifier,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
@ -50,14 +58,12 @@ export const RecordIndexPageKanbanAddButton = () => {
|
|||||||
|
|
||||||
const { closeDropdown } = useDropdown(dropdownId);
|
const { closeDropdown } = useDropdown(dropdownId);
|
||||||
|
|
||||||
const {
|
const { selectFieldMetadataItem, isOpportunity, createOpportunity } =
|
||||||
selectFieldMetadataItem,
|
useRecordIndexPageKanbanAddButton({
|
||||||
isOpportunity,
|
objectNamePlural,
|
||||||
createOpportunity,
|
});
|
||||||
createRecordWithoutCompany,
|
|
||||||
} = useRecordIndexPageKanbanAddButton({
|
const { handleAddNewCardClick } = useAddNewCard();
|
||||||
objectNamePlural,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleItemClick = useCallback(
|
const handleItemClick = useCallback(
|
||||||
(columnDefinition: RecordBoardColumnDefinition) => {
|
(columnDefinition: RecordBoardColumnDefinition) => {
|
||||||
@ -68,18 +74,23 @@ export const RecordIndexPageKanbanAddButton = () => {
|
|||||||
RelationPickerHotkeyScope.RelationPicker,
|
RelationPickerHotkeyScope.RelationPicker,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
createRecordWithoutCompany(columnDefinition);
|
handleAddNewCardClick(
|
||||||
|
labelIdentifierField?.label ?? '',
|
||||||
|
'',
|
||||||
|
'first',
|
||||||
|
columnDefinition.id,
|
||||||
|
);
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
isOpportunity,
|
isOpportunity,
|
||||||
createRecordWithoutCompany,
|
handleAddNewCardClick,
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
closeDropdown,
|
closeDropdown,
|
||||||
|
labelIdentifierField,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEntitySelect = useCallback(
|
const handleEntitySelect = useCallback(
|
||||||
(company?: EntityForSelect) => {
|
(company?: EntityForSelect) => {
|
||||||
setIsSelectingCompany(false);
|
setIsSelectingCompany(false);
|
||||||
|
|||||||
@ -45,21 +45,9 @@ export const useRecordIndexPageKanbanAddButton = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createRecordWithoutCompany = (
|
|
||||||
columnDefinition: RecordBoardColumnDefinition,
|
|
||||||
) => {
|
|
||||||
if (isDefined(selectFieldMetadataItem)) {
|
|
||||||
createOneRecord({
|
|
||||||
[selectFieldMetadataItem.name]: columnDefinition?.value,
|
|
||||||
position: 'first',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectFieldMetadataItem,
|
selectFieldMetadataItem,
|
||||||
isOpportunity,
|
isOpportunity,
|
||||||
createOpportunity,
|
createOpportunity,
|
||||||
createRecordWithoutCompany,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -89,7 +89,6 @@ export const TextInput = ({
|
|||||||
onInputEnter?.();
|
onInputEnter?.();
|
||||||
|
|
||||||
if (isDefined(inputRef) && 'current' in inputRef) {
|
if (isDefined(inputRef) && 'current' in inputRef) {
|
||||||
inputRef.current?.blur();
|
|
||||||
setIsFocused(false);
|
setIsFocused(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user