mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-28 01:09:11 +03:00
Deprecate old board (#4352)
* Deprecate old board * Fix tests * Fix tests
This commit is contained in:
parent
4f4ce1c655
commit
9190bd8d7f
@ -1,24 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { CompanyBoard } from '../board/components/CompanyBoard';
|
||||
|
||||
const DoNotRenderEffect = () => <></>;
|
||||
|
||||
const meta: Meta<typeof CompanyBoard> = {
|
||||
title: 'Modules/Companies/Board',
|
||||
component: DoNotRenderEffect,
|
||||
decorators: [ComponentWithRouterDecorator, SnackBarDecorator],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CompanyBoard>;
|
||||
|
||||
// FIXME: CompanyBoard is re-rendering so much and exceeding the maximum update depth for some reason.
|
||||
export const OneColumnBoard: Story = {};
|
@ -1,30 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
|
||||
import { CompanyChip } from '../components/CompanyChip';
|
||||
|
||||
const meta: Meta<typeof CompanyChip> = {
|
||||
title: 'Modules/Companies/CompanyChip',
|
||||
component: CompanyChip,
|
||||
decorators: [ComponentWithRouterDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CompanyChip>;
|
||||
|
||||
export const SmallName: Story = {
|
||||
args: {
|
||||
opportunityId: 'airbnb',
|
||||
companyName: 'Airbnb',
|
||||
avatarUrl: 'https://api.faviconkit.com/airbnb.com/144',
|
||||
},
|
||||
};
|
||||
|
||||
export const BigName: Story = {
|
||||
args: {
|
||||
opportunityId: 'google',
|
||||
companyName: 'Google with a real big name to overflow the cell',
|
||||
avatarUrl: 'https://api.faviconkit.com/google.com/144',
|
||||
},
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
export const pipelineSteps = [
|
||||
{
|
||||
id: 'pipeline-stage-1',
|
||||
name: 'New',
|
||||
position: 0,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
id: 'pipeline-stage-2',
|
||||
name: 'Screening',
|
||||
position: 1,
|
||||
color: 'purple',
|
||||
},
|
||||
{
|
||||
id: 'pipeline-stage-3',
|
||||
name: 'Meeting',
|
||||
position: 2,
|
||||
color: 'sky',
|
||||
},
|
||||
{
|
||||
id: 'pipeline-stage-4',
|
||||
name: 'Proposal',
|
||||
position: 3,
|
||||
color: 'turquoise',
|
||||
},
|
||||
{
|
||||
id: 'pipeline-stage-5',
|
||||
name: 'Customer',
|
||||
position: 4,
|
||||
color: 'yellow',
|
||||
},
|
||||
] as PipelineStep[];
|
@ -1,92 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import {
|
||||
RecordBoardDeprecated,
|
||||
RecordBoardDeprecatedProps,
|
||||
} from '@/object-record/record-board-deprecated/components/RecordBoardDeprecated';
|
||||
import { RecordBoardDeprecatedEffect } from '@/object-record/record-board-deprecated/components/RecordBoardDeprecatedEffect';
|
||||
import { BOARD_OPTIONS_DROPDOWN_ID } from '@/object-record/record-board-deprecated/constants/BoardOptionsDropdownId';
|
||||
import { RecordBoardDeprecatedOptionsDropdown } from '@/object-record/record-board-deprecated/options/components/RecordBoardDeprecatedOptionsDropdown';
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { useViewFields } from '@/views/hooks/internal/useViewFields';
|
||||
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
|
||||
|
||||
import { HooksCompanyBoardEffect } from '../../components/HooksCompanyBoardEffect';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type CompanyBoardProps = Pick<
|
||||
RecordBoardDeprecatedProps,
|
||||
'onColumnAdd' | 'onColumnDelete' | 'onEditColumnTitle'
|
||||
>;
|
||||
|
||||
export const CompanyBoard = ({
|
||||
onColumnAdd,
|
||||
onColumnDelete,
|
||||
onEditColumnTitle,
|
||||
}: CompanyBoardProps) => {
|
||||
const viewBarId = 'company-board-view';
|
||||
const recordBoardId = 'company-board';
|
||||
|
||||
const { persistViewFields } = useViewFields(viewBarId);
|
||||
|
||||
const { createOneRecord } = useCreateOneRecord({
|
||||
objectNameSingular: 'pipelineStep',
|
||||
});
|
||||
|
||||
const onStageAdd = useCallback(
|
||||
(stage: BoardColumnDefinition) => {
|
||||
createOneRecord({
|
||||
name: stage.title,
|
||||
color: stage.colorCode,
|
||||
position: stage.position,
|
||||
id: stage.id,
|
||||
});
|
||||
},
|
||||
[createOneRecord],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ViewBar
|
||||
viewBarId={viewBarId}
|
||||
optionsDropdownButton={
|
||||
<RecordBoardDeprecatedOptionsDropdown
|
||||
recordBoardId={recordBoardId}
|
||||
onStageAdd={onStageAdd}
|
||||
/>
|
||||
}
|
||||
optionsDropdownScopeId={BOARD_OPTIONS_DROPDOWN_ID}
|
||||
/>
|
||||
|
||||
<HooksCompanyBoardEffect
|
||||
viewBarId={viewBarId}
|
||||
recordBoardId={recordBoardId}
|
||||
/>
|
||||
<RecordBoardDeprecatedEffect
|
||||
recordBoardId={recordBoardId}
|
||||
onFieldsChange={(fields) => {
|
||||
persistViewFields(mapBoardFieldDefinitionsToViewFields(fields));
|
||||
}}
|
||||
/>
|
||||
|
||||
<RecordBoardDeprecated
|
||||
recordBoardId={recordBoardId}
|
||||
boardOptions={opportunitiesBoardOptions}
|
||||
onColumnAdd={onColumnAdd}
|
||||
onColumnDelete={onColumnDelete}
|
||||
onEditColumnTitle={onEditColumnTitle}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -1,255 +0,0 @@
|
||||
import { ReactNode, useContext } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { BoardCardIdContext } from '@/object-record/record-board-deprecated/contexts/BoardCardIdContext';
|
||||
import { useCurrentRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useCurrentRecordBoardDeprecatedCardSelectedInternal';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { isRecordBoardDeprecatedCardInCompactViewFamilyState } from '@/object-record/record-board-deprecated/states/isRecordBoardDeprecatedCardInCompactViewFamilyState';
|
||||
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 { EntityChipVariant } from '@/ui/display/chip/components/EntityChip';
|
||||
import { IconEye } from '@/ui/display/icon/index';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
||||
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
|
||||
|
||||
import { CompanyChip } from './CompanyChip';
|
||||
|
||||
const StyledBoardCard = styled.div<{ selected: boolean }>`
|
||||
background-color: ${({ theme, selected }) =>
|
||||
selected ? theme.accent.quaternary : theme.background.secondary};
|
||||
border: 1px solid
|
||||
${({ theme, selected }) =>
|
||||
selected ? theme.accent.secondary : theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
&:hover {
|
||||
background-color: ${({ theme, selected }) =>
|
||||
selected && theme.accent.tertiary};
|
||||
border: 1px solid
|
||||
${({ theme, selected }) =>
|
||||
selected ? theme.accent.primary : theme.border.color.medium};
|
||||
}
|
||||
cursor: pointer;
|
||||
|
||||
.checkbox-container {
|
||||
transition: all ease-in-out 160ms;
|
||||
opacity: ${({ selected }) => (selected ? 1 : 0)};
|
||||
}
|
||||
|
||||
&:hover .checkbox-container {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.compact-icon-container {
|
||||
transition: all ease-in-out 160ms;
|
||||
opacity: 0;
|
||||
}
|
||||
&:hover .compact-icon-container {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledBoardCardWrapper = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBoardCardHeader = styled.div<{
|
||||
showCompactView: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
height: 24px;
|
||||
padding-bottom: ${({ theme, showCompactView }) =>
|
||||
theme.spacing(showCompactView ? 0 : 1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
transition: padding ease-in-out 160ms;
|
||||
|
||||
img {
|
||||
height: ${({ theme }) => theme.icon.size.md}px;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
object-fit: cover;
|
||||
width: ${({ theme }) => theme.icon.size.md}px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledBoardCardBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2.5)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
span {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
svg {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCheckboxContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: end;
|
||||
`;
|
||||
|
||||
const StyledFieldContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledCompactIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const CompanyBoardCard = () => {
|
||||
const { isCurrentCardSelected, setCurrentCardSelected } =
|
||||
useCurrentRecordBoardDeprecatedCardSelectedInternal();
|
||||
const boardCardId = useContext(BoardCardIdContext);
|
||||
|
||||
const [companyProgress] = useRecoilState(
|
||||
companyProgressesFamilyState(boardCardId ?? ''),
|
||||
);
|
||||
|
||||
const { isCompactViewEnabledState, visibleBoardCardFieldsSelector } =
|
||||
useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
const [isCompactViewEnabled] = useRecoilState(isCompactViewEnabledState);
|
||||
|
||||
const [isCardInCompactView, setIsCardInCompactView] = useRecoilState(
|
||||
isRecordBoardDeprecatedCardInCompactViewFamilyState(boardCardId ?? ''),
|
||||
);
|
||||
|
||||
const showCompactView = isCompactViewEnabled && isCardInCompactView;
|
||||
|
||||
const { opportunity, company } = companyProgress ?? {};
|
||||
|
||||
const visibleBoardCardFields = useRecoilValue(visibleBoardCardFieldsSelector);
|
||||
|
||||
const useUpdateOneRecordMutation: RecordUpdateHook = () => {
|
||||
const { updateOneRecord: updateOneOpportunity } = useUpdateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
||||
});
|
||||
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneOpportunity?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
// boardCardId check can be moved to a wrapper to avoid unnecessary logic above
|
||||
if (!company || !opportunity || !boardCardId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const PreventSelectOnClickContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) => (
|
||||
<StyledFieldContainer
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledFieldContainer>
|
||||
);
|
||||
|
||||
const OnMouseLeaveBoard = () => {
|
||||
setIsCardInCompactView(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBoardCardWrapper>
|
||||
<StyledBoardCard
|
||||
selected={isCurrentCardSelected}
|
||||
onMouseLeave={OnMouseLeaveBoard}
|
||||
onClick={() => setCurrentCardSelected(!isCurrentCardSelected)}
|
||||
>
|
||||
<StyledBoardCardHeader showCompactView={showCompactView}>
|
||||
<CompanyChip
|
||||
opportunityId={opportunity.id}
|
||||
companyName={company.name}
|
||||
avatarUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||
variant={EntityChipVariant.Transparent}
|
||||
/>
|
||||
{showCompactView && (
|
||||
<StyledCompactIconContainer className="compact-icon-container">
|
||||
<LightIconButton
|
||||
Icon={IconEye}
|
||||
accent="tertiary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsCardInCompactView(false);
|
||||
}}
|
||||
/>
|
||||
</StyledCompactIconContainer>
|
||||
)}
|
||||
<StyledCheckboxContainer className="checkbox-container">
|
||||
<Checkbox
|
||||
checked={isCurrentCardSelected}
|
||||
onChange={() => setCurrentCardSelected(!isCurrentCardSelected)}
|
||||
variant={CheckboxVariant.Secondary}
|
||||
/>
|
||||
</StyledCheckboxContainer>
|
||||
</StyledBoardCardHeader>
|
||||
<StyledBoardCardBody>
|
||||
<AnimatedEaseInOut isOpen={!showCompactView} initial={false}>
|
||||
{visibleBoardCardFields.map((viewField) => (
|
||||
<PreventSelectOnClickContainer key={viewField.fieldMetadataId}>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: boardCardId,
|
||||
maxWidth: 156,
|
||||
recoilScopeId: boardCardId + viewField.fieldMetadataId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: viewField.fieldMetadataId,
|
||||
label: viewField.label,
|
||||
iconName: viewField.iconName,
|
||||
type: viewField.type,
|
||||
metadata: viewField.metadata,
|
||||
},
|
||||
useUpdateRecord: useUpdateOneRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
</PreventSelectOnClickContainer>
|
||||
))}
|
||||
</AnimatedEaseInOut>
|
||||
</StyledBoardCardBody>
|
||||
</StyledBoardCard>
|
||||
</StyledBoardCardWrapper>
|
||||
);
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
import {
|
||||
EntityChip,
|
||||
EntityChipVariant,
|
||||
} from '@/ui/display/chip/components/EntityChip';
|
||||
|
||||
type CompanyChipProps = {
|
||||
opportunityId: string;
|
||||
companyName: string;
|
||||
avatarUrl?: string;
|
||||
variant?: EntityChipVariant;
|
||||
};
|
||||
|
||||
export const CompanyChip = ({
|
||||
opportunityId,
|
||||
companyName,
|
||||
avatarUrl,
|
||||
variant = EntityChipVariant.Regular,
|
||||
}: CompanyChipProps) => (
|
||||
<EntityChip
|
||||
entityId={opportunityId}
|
||||
linkToEntity={`/object/opportunity/${opportunityId}`}
|
||||
name={companyName}
|
||||
avatarType="squared"
|
||||
avatarUrl={avatarUrl}
|
||||
variant={variant}
|
||||
/>
|
||||
);
|
@ -1,134 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { availableRecordBoardDeprecatedCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/availableRecordBoardDeprecatedCardFieldsScopedState';
|
||||
import { recordBoardCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedCardFieldsScopedState';
|
||||
import { recordBoardFiltersScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedFiltersScopedState';
|
||||
import { recordBoardSortsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedSortsScopedState';
|
||||
import { useSetRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useSetRecoilScopedStateV2';
|
||||
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
|
||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
||||
import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions';
|
||||
|
||||
type HooksCompanyBoardEffectProps = {
|
||||
viewBarId: string;
|
||||
recordBoardId: string;
|
||||
};
|
||||
|
||||
export const HooksCompanyBoardEffect = ({
|
||||
viewBarId,
|
||||
recordBoardId,
|
||||
}: HooksCompanyBoardEffectProps) => {
|
||||
const {
|
||||
setAvailableFilterDefinitions,
|
||||
setAvailableSortDefinitions,
|
||||
setAvailableFieldDefinitions,
|
||||
setViewObjectMetadataId,
|
||||
} = useViewBar({ viewBarId });
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
||||
});
|
||||
|
||||
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
|
||||
const setAvailableBoardCardFields = useSetRecoilScopedStateV2(
|
||||
availableRecordBoardDeprecatedCardFieldsScopedState,
|
||||
'company-board',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!objectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
setAvailableFilterDefinitions?.(filterDefinitions);
|
||||
setAvailableSortDefinitions?.(sortDefinitions);
|
||||
setAvailableFieldDefinitions?.(columnDefinitions);
|
||||
}, [
|
||||
columnDefinitions,
|
||||
filterDefinitions,
|
||||
objectMetadataItem,
|
||||
setAvailableFieldDefinitions,
|
||||
setAvailableFilterDefinitions,
|
||||
setAvailableSortDefinitions,
|
||||
sortDefinitions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setAvailableBoardCardFields(columnDefinitions);
|
||||
}, [columnDefinitions, setAvailableBoardCardFields]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!objectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
setViewObjectMetadataId?.(objectMetadataItem.id);
|
||||
}, [objectMetadataItem, setViewObjectMetadataId]);
|
||||
|
||||
const {
|
||||
currentViewFieldsState,
|
||||
currentViewFiltersState,
|
||||
currentViewSortsState,
|
||||
} = useViewScopedStates({ viewScopeId: viewBarId });
|
||||
|
||||
const currentViewFields = useRecoilValue(currentViewFieldsState);
|
||||
const currentViewFilters = useRecoilValue(currentViewFiltersState);
|
||||
const currentViewSorts = useRecoilValue(currentViewSortsState);
|
||||
|
||||
//TODO: Modify to use scopeId
|
||||
const setBoardCardFields = useSetRecoilScopedStateV2(
|
||||
recordBoardCardFieldsScopedState,
|
||||
'company-board',
|
||||
);
|
||||
const setBoardCardFilters = useSetRecoilScopedStateV2(
|
||||
recordBoardFiltersScopedState,
|
||||
'company-board',
|
||||
);
|
||||
|
||||
const setBoardCardSorts = useSetRecoilScopedStateV2(
|
||||
recordBoardSortsScopedState,
|
||||
'company-board',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentViewFields) {
|
||||
setBoardCardFields(
|
||||
mapViewFieldsToBoardFieldDefinitions(
|
||||
currentViewFields,
|
||||
columnDefinitions,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, [columnDefinitions, currentViewFields, setBoardCardFields]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentViewFilters) {
|
||||
setBoardCardFilters(currentViewFilters);
|
||||
}
|
||||
}, [currentViewFilters, setBoardCardFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentViewSorts) {
|
||||
setBoardCardSorts(currentViewSorts);
|
||||
}
|
||||
}, [currentViewSorts, setBoardCardSorts]);
|
||||
|
||||
const { setEntityCountInCurrentView } = useViewBar({ viewBarId });
|
||||
|
||||
const { savedOpportunitiesState } = useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: recordBoardId,
|
||||
});
|
||||
|
||||
const savedOpportunities = useRecoilValue(savedOpportunitiesState);
|
||||
|
||||
useEffect(() => {
|
||||
setEntityCountInCurrentView(savedOpportunities.length);
|
||||
}, [savedOpportunities.length, setEntityCountInCurrentView]);
|
||||
|
||||
return <></>;
|
||||
};
|
@ -1,69 +0,0 @@
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { NewButton } from '@/object-record/record-board-deprecated/components/NewButton';
|
||||
import { BoardColumnContext } from '@/object-record/record-board-deprecated/contexts/BoardColumnContext';
|
||||
import { useCreateOpportunity } from '@/object-record/record-board-deprecated/hooks/internal/useCreateOpportunity';
|
||||
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
|
||||
export const NewOpportunityButton = () => {
|
||||
const [isCreatingCard, setIsCreatingCard] = useState(false);
|
||||
const column = useContext(BoardColumnContext);
|
||||
|
||||
const pipelineStepId = column?.columnDefinition.id || '';
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const createOpportunity = useCreateOpportunity();
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleEntitySelect = (company: any) => {
|
||||
setIsCreatingCard(false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
|
||||
if (!pipelineStepId) {
|
||||
enqueueSnackBar('Pipeline stage id is not defined', {
|
||||
variant: 'error',
|
||||
});
|
||||
|
||||
throw new Error('Pipeline stage id is not defined');
|
||||
}
|
||||
|
||||
createOpportunity(company.id, pipelineStepId);
|
||||
};
|
||||
|
||||
const handleNewClick = useCallback(() => {
|
||||
setIsCreatingCard(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
);
|
||||
}, [setIsCreatingCard, setHotkeyScopeAndMemorizePreviousScope]);
|
||||
|
||||
const handleCancel = () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
setIsCreatingCard(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCreatingCard ? (
|
||||
<SingleEntitySelect
|
||||
disableBackgroundBlur
|
||||
onCancel={handleCancel}
|
||||
onEntitySelected={handleEntitySelect}
|
||||
relationObjectNameSingular={CoreObjectNameSingular.Company}
|
||||
relationPickerScopeId="relation-picker"
|
||||
selectedRelationRecordIds={[]}
|
||||
/>
|
||||
) : (
|
||||
<NewButton onClick={handleNewClick} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,102 +0,0 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
|
||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||
import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStepsState';
|
||||
import { IconChevronDown } from '@/ui/display/icon';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
|
||||
export type OpportunityPickerProps = {
|
||||
companyId: string | null;
|
||||
onSubmit: (
|
||||
newCompany: EntityForSelect | null,
|
||||
newPipelineStepId: string | null,
|
||||
) => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
export const OpportunityPicker = ({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: OpportunityPickerProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] =
|
||||
useState(false);
|
||||
|
||||
const [selectedPipelineStepId, setSelectedPipelineStepId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const currentPipelineSteps = useRecoilValue(currentPipelineStepsState);
|
||||
|
||||
const handlePipelineStepChange = (newPipelineStepId: string) => {
|
||||
setSelectedPipelineStepId(newPipelineStepId);
|
||||
setIsProgressSelectionUnfolded(false);
|
||||
};
|
||||
|
||||
const handleEntitySelected = async (
|
||||
selectedCompany: EntityForSelect | null | undefined,
|
||||
) => {
|
||||
onSubmit(selectedCompany ?? null, selectedPipelineStepId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPipelineSteps?.[0]?.id) {
|
||||
setSelectedPipelineStepId(currentPipelineSteps?.[0]?.id);
|
||||
}
|
||||
}, [currentPipelineSteps]);
|
||||
|
||||
const selectedPipelineStep = useMemo(
|
||||
() =>
|
||||
currentPipelineSteps.find(
|
||||
(pipelineStep: any) => pipelineStep.id === selectedPipelineStepId,
|
||||
),
|
||||
[currentPipelineSteps, selectedPipelineStepId],
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
ref={containerRef}
|
||||
data-testid={`company-progress-dropdown-menu`}
|
||||
>
|
||||
{isProgressSelectionUnfolded ? (
|
||||
<DropdownMenuItemsContainer>
|
||||
{currentPipelineSteps.map((pipelineStep: any, index: number) => (
|
||||
<MenuItem
|
||||
key={pipelineStep.id}
|
||||
testId={`select-pipeline-stage-${index}`}
|
||||
onClick={() => {
|
||||
handlePipelineStepChange(pipelineStep.id);
|
||||
}}
|
||||
text={pipelineStep.name}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
testId="selected-pipeline-stage"
|
||||
EndIcon={IconChevronDown}
|
||||
onClick={() => setIsProgressSelectionUnfolded(true)}
|
||||
>
|
||||
{selectedPipelineStep?.name}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<SingleEntitySelectMenuItemsWithSearch
|
||||
onCancel={onCancel}
|
||||
onEntitySelected={handleEntitySelected}
|
||||
relationObjectNameSingular={CoreObjectNameSingular.Company}
|
||||
selectedRelationRecordIds={[]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
export enum EditableFieldHotkeyScope {
|
||||
EditableField = 'editable-field',
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import { CompanyProgress } from '@/companies/types/CompanyProgress';
|
||||
|
||||
export const companyProgressesFamilyState = atomFamily<
|
||||
CompanyProgress | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'companyProgressesFamilyState',
|
||||
default: undefined,
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const CompanyBoardViewBarRecoilScopeContext = createContext<
|
||||
string | null
|
||||
>(null);
|
@ -1,13 +0,0 @@
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
|
||||
export type CompanyForBoard = Pick<Company, 'id' | 'name' | 'domainName'>;
|
||||
|
||||
export type CompanyProgress = {
|
||||
company: CompanyForBoard;
|
||||
opportunity: Opportunity;
|
||||
};
|
||||
|
||||
export type CompanyProgressDict = {
|
||||
[key: string]: CompanyProgress;
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useObjectRecordBoardDeprecated } from '@/object-record/hooks/useObjectRecordBoardDeprecated';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
|
||||
const recordBoardId = '783932a0-28c7-4607-b2ce-6543fa2be892';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={recordBoardId}>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<MockedProvider addTypename={false}>{children}</MockedProvider>
|
||||
</SnackBarProviderScope>
|
||||
</RecordBoardDeprecatedScope>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
describe('useObjectRecordBoardDeprecated', () => {
|
||||
it('should skip fetch if currentWorkspace is undefined', async () => {
|
||||
const { result } = renderHook(() => useObjectRecordBoardDeprecated(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(Array.isArray(result.current.opportunities)).toBe(true);
|
||||
});
|
||||
});
|
@ -1,105 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
import { useFindManyRecords } from './useFindManyRecords';
|
||||
|
||||
export const useObjectRecordBoardDeprecated = () => {
|
||||
const objectNameSingular = 'opportunity';
|
||||
|
||||
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
||||
{
|
||||
objectNameSingular,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
isBoardLoadedState,
|
||||
boardFiltersState,
|
||||
boardSortsState,
|
||||
savedCompaniesState,
|
||||
savedOpportunitiesState,
|
||||
savedPipelineStepsState,
|
||||
} = useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
const setIsBoardLoaded = useSetRecoilState(isBoardLoadedState);
|
||||
|
||||
const boardFilters = useRecoilValue(boardFiltersState);
|
||||
const boardSorts = useRecoilValue(boardSortsState);
|
||||
|
||||
const setSavedCompanies = useSetRecoilState(savedCompaniesState);
|
||||
|
||||
const [savedOpportunities] = useRecoilState(savedOpportunitiesState);
|
||||
|
||||
const [savedPipelineSteps, setSavedPipelineSteps] = useRecoilState(
|
||||
savedPipelineStepsState,
|
||||
);
|
||||
|
||||
const filter = turnObjectDropdownFilterIntoQueryFilter(
|
||||
boardFilters,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
const orderBy = turnSortsIntoOrderBy(
|
||||
boardSorts,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.PipelineStep,
|
||||
filter,
|
||||
onCompleted: useCallback(
|
||||
(data: ObjectRecordConnection<PipelineStep>) => {
|
||||
setSavedPipelineSteps(data.edges.map((edge) => edge.node));
|
||||
},
|
||||
[setSavedPipelineSteps],
|
||||
),
|
||||
});
|
||||
|
||||
const {
|
||||
records: opportunities,
|
||||
loading,
|
||||
fetchMoreRecords: fetchMoreOpportunities,
|
||||
} = useFindManyRecords<Opportunity>({
|
||||
skip: !savedPipelineSteps.length,
|
||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
||||
filter,
|
||||
orderBy,
|
||||
onCompleted: useCallback(() => {
|
||||
setIsBoardLoaded(true);
|
||||
}, [setIsBoardLoaded]),
|
||||
});
|
||||
|
||||
const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({
|
||||
skip: !savedOpportunities.length,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
filter: {
|
||||
id: {
|
||||
in: savedOpportunities.map(
|
||||
(opportunity) => opportunity.companyId || '',
|
||||
),
|
||||
},
|
||||
},
|
||||
onCompleted: useCallback(
|
||||
(data: ObjectRecordConnection<Company>) => {
|
||||
setSavedCompanies(data.edges.map((edge) => edge.node));
|
||||
},
|
||||
[setSavedCompanies],
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
opportunities,
|
||||
loading,
|
||||
fetchMoreOpportunities,
|
||||
fetchMoreCompanies,
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar';
|
||||
|
||||
export const RecordBoardDeprecatedActionBar = () => {
|
||||
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
||||
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
||||
|
||||
if (!selectedCardIds.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ActionBar />;
|
||||
};
|
@ -1,36 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconPlus } from '@/ui/display/icon/index';
|
||||
|
||||
const StyledButton = styled.button`
|
||||
align-items: center;
|
||||
align-self: baseline;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border: none;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.background.tertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
type NewButtonProps = {
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const NewButton = ({ onClick }: NewButtonProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledButton onClick={onClick}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
New
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
@ -1,162 +0,0 @@
|
||||
import { useCallback, useRef } from '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 { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordBoardDeprecatedActionBar } from '@/object-record/record-board-deprecated/action-bar/components/RecordBoardDeprecatedActionBar';
|
||||
import { RecordBoardDeprecatedInternalEffect } from '@/object-record/record-board-deprecated/components/RecordBoardDeprecatedInternalEffect';
|
||||
import { RecordBoardDeprecatedContextMenu } from '@/object-record/record-board-deprecated/context-menu/components/RecordBoardDeprecatedContextMenu';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { useSetRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useSetRecordBoardDeprecatedCardSelectedInternal';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
import { RecordBoardDeprecatedColumn } from './RecordBoardDeprecatedColumn';
|
||||
|
||||
export type RecordBoardDeprecatedProps = {
|
||||
recordBoardId: string;
|
||||
boardOptions: BoardOptions;
|
||||
onColumnAdd?: (boardColumn: BoardColumnDefinition) => void;
|
||||
onColumnDelete?: (boardColumnId: string) => void;
|
||||
onEditColumnTitle: (params: {
|
||||
columnId: string;
|
||||
title: string;
|
||||
color: string;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
const StyledBoard = styled.div`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBoardHeader = styled.div`
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
export const RecordBoardDeprecated = ({
|
||||
recordBoardId,
|
||||
boardOptions,
|
||||
onColumnDelete,
|
||||
onEditColumnTitle,
|
||||
}: RecordBoardDeprecatedProps) => {
|
||||
const recordBoardScopeId = recordBoardId;
|
||||
|
||||
const { boardColumnsState } = useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId,
|
||||
});
|
||||
const boardColumns = useRecoilValue(boardColumnsState);
|
||||
|
||||
const { updateOneRecord: updateOneOpportunity } =
|
||||
useUpdateOneRecord<Opportunity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
||||
});
|
||||
|
||||
const { unselectAllActiveCards, setCardSelected } =
|
||||
useSetRecordBoardDeprecatedCardSelectedInternal({ recordBoardScopeId });
|
||||
|
||||
const updatePipelineProgressStageInDB = useCallback(
|
||||
async (pipelineProgressId: string, pipelineStepId: string) => {
|
||||
await updateOneOpportunity?.({
|
||||
idToUpdate: pipelineProgressId,
|
||||
updateOneRecordInput: {
|
||||
pipelineStepId: pipelineStepId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[updateOneOpportunity],
|
||||
);
|
||||
|
||||
useListenClickOutsideByClassName({
|
||||
classNames: ['entity-board-card'],
|
||||
excludeClassNames: ['action-bar', 'context-menu'],
|
||||
callback: unselectAllActiveCards,
|
||||
});
|
||||
|
||||
const onDragEnd: OnDragEndResponder = useCallback(
|
||||
async (result) => {
|
||||
if (!boardColumns) return;
|
||||
|
||||
try {
|
||||
const draggedEntityId = result.draggableId;
|
||||
const destinationColumnId = result.destination?.droppableId;
|
||||
|
||||
if (
|
||||
draggedEntityId &&
|
||||
destinationColumnId &&
|
||||
updatePipelineProgressStageInDB
|
||||
) {
|
||||
await updatePipelineProgressStageInDB(
|
||||
draggedEntityId,
|
||||
destinationColumnId,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
},
|
||||
[boardColumns, updatePipelineProgressStageInDB],
|
||||
);
|
||||
|
||||
const sortedBoardColumns = [...boardColumns].sort((a, b) => {
|
||||
return a.position - b.position;
|
||||
});
|
||||
|
||||
const boardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={recordBoardId}>
|
||||
<RecordBoardDeprecatedContextMenu />
|
||||
<RecordBoardDeprecatedActionBar />
|
||||
<RecordBoardDeprecatedInternalEffect />
|
||||
|
||||
<StyledWrapper>
|
||||
<StyledBoardHeader />
|
||||
<ScrollWrapper>
|
||||
<StyledBoard ref={boardRef}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{sortedBoardColumns.map((column) => (
|
||||
<RecordBoardDeprecatedColumn
|
||||
key={column.id}
|
||||
recordBoardColumnId={column.id}
|
||||
columnDefinition={column}
|
||||
recordBoardColumnTotal={sortedBoardColumns.length}
|
||||
recordBoardOptions={boardOptions}
|
||||
onDelete={onColumnDelete}
|
||||
onTitleEdit={onEditColumnTitle}
|
||||
/>
|
||||
))}
|
||||
</DragDropContext>
|
||||
</StyledBoard>
|
||||
</ScrollWrapper>
|
||||
<DragSelect
|
||||
dragSelectable={boardRef}
|
||||
onDragSelectionChange={setCardSelected}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
</RecordBoardDeprecatedScope>
|
||||
);
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
||||
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
||||
|
||||
import { useCurrentRecordBoardDeprecatedCardSelectedInternal } from '../hooks/internal/useCurrentRecordBoardDeprecatedCardSelectedInternal';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
export const RecordBoardDeprecatedCard = ({
|
||||
recordBoardOptions,
|
||||
cardId,
|
||||
index,
|
||||
}: {
|
||||
recordBoardOptions: BoardOptions;
|
||||
cardId: string;
|
||||
index: number;
|
||||
}) => {
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||
|
||||
const { setCurrentCardSelected } =
|
||||
useCurrentRecordBoardDeprecatedCardSelectedInternal();
|
||||
|
||||
const handleContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setCurrentCardSelected(true);
|
||||
setContextMenuPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
setContextMenuOpenState(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable key={cardId} draggableId={cardId} 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={cardId}
|
||||
data-select-disable
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{<recordBoardOptions.CardComponent />}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
@ -1,143 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Draggable, Droppable, DroppableProvided } from '@hello-pangea/dnd';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { RecordBoardDeprecatedCard } from '@/object-record/record-board-deprecated/components/RecordBoardDeprecatedCard';
|
||||
import { RecordBoardDeprecatedColumnHeader } from '@/object-record/record-board-deprecated/components/RecordBoardDeprecatedColumnHeader';
|
||||
import { BoardCardIdContext } from '@/object-record/record-board-deprecated/contexts/BoardCardIdContext';
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
|
||||
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '../states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
const StyledPlaceholder = styled.div`
|
||||
min-height: 1px;
|
||||
`;
|
||||
|
||||
const StyledNewCardButtonContainer = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledColumnCardsContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-left: 1px solid
|
||||
${({ theme, isFirstColumn }) =>
|
||||
isFirstColumn ? 'none' : theme.border.color.light};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 200px;
|
||||
min-width: 200px;
|
||||
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type BoardColumnCardsContainerProps = {
|
||||
children: React.ReactNode;
|
||||
droppableProvided: DroppableProvided;
|
||||
};
|
||||
|
||||
type RecordBoardDeprecatedColumnProps = {
|
||||
recordBoardColumnId: string;
|
||||
columnDefinition: BoardColumnDefinition;
|
||||
recordBoardOptions: BoardOptions;
|
||||
recordBoardColumnTotal: number;
|
||||
onDelete?: (columnId: string) => void;
|
||||
onTitleEdit: (params: {
|
||||
columnId: string;
|
||||
title: string;
|
||||
color: string;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
const BoardColumnCardsContainer = ({
|
||||
children,
|
||||
droppableProvided,
|
||||
}: BoardColumnCardsContainerProps) => {
|
||||
return (
|
||||
<StyledColumnCardsContainer
|
||||
ref={droppableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...droppableProvided?.droppableProps}
|
||||
>
|
||||
{children}
|
||||
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
|
||||
</StyledColumnCardsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const RecordBoardDeprecatedColumn = ({
|
||||
recordBoardColumnId,
|
||||
columnDefinition,
|
||||
recordBoardOptions,
|
||||
recordBoardColumnTotal,
|
||||
onDelete,
|
||||
onTitleEdit,
|
||||
}: RecordBoardDeprecatedColumnProps) => {
|
||||
const cardIds = useRecoilValue(
|
||||
recordBoardCardIdsByColumnIdFamilyState(recordBoardColumnId),
|
||||
);
|
||||
|
||||
const isFirstColumn = columnDefinition.position === 0;
|
||||
|
||||
return (
|
||||
<BoardColumnContext.Provider
|
||||
value={{
|
||||
id: recordBoardColumnId,
|
||||
columnDefinition: columnDefinition,
|
||||
isFirstColumn: columnDefinition.position === 0,
|
||||
isLastColumn: columnDefinition.position === recordBoardColumnTotal - 1,
|
||||
onTitleEdit: ({ title, color }) =>
|
||||
onTitleEdit({ columnId: recordBoardColumnId, title, color }),
|
||||
}}
|
||||
>
|
||||
<Droppable droppableId={recordBoardColumnId}>
|
||||
{(droppableProvided) => (
|
||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||
<RecordBoardDeprecatedColumnHeader
|
||||
recordBoardColumnId={recordBoardColumnId}
|
||||
columnDefinition={columnDefinition}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
|
||||
{cardIds.map((cardId, index) => (
|
||||
<BoardCardIdContext.Provider value={cardId} key={cardId}>
|
||||
<RecordBoardDeprecatedCard
|
||||
index={index}
|
||||
cardId={cardId}
|
||||
recordBoardOptions={recordBoardOptions}
|
||||
/>
|
||||
</BoardCardIdContext.Provider>
|
||||
))}
|
||||
<Draggable
|
||||
draggableId={`new-${recordBoardColumnId}`}
|
||||
index={cardIds.length}
|
||||
isDragDisabled={true}
|
||||
>
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
ref={draggableProvided?.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided?.draggableProps}
|
||||
>
|
||||
<StyledNewCardButtonContainer>
|
||||
{recordBoardOptions.newCardComponent}
|
||||
</StyledNewCardButtonContainer>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
</BoardColumnCardsContainer>
|
||||
</StyledColumn>
|
||||
)}
|
||||
</Droppable>
|
||||
</BoardColumnContext.Provider>
|
||||
);
|
||||
};
|
@ -1,149 +0,0 @@
|
||||
import { useCallback, useContext, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { IconArrowLeft, IconArrowRight, IconPencil } from '@/ui/display/icon';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
||||
import { useBoardColumnsInternal } from '../hooks/internal/useRecordBoardDeprecatedColumnsInternal';
|
||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||
|
||||
import { RecordBoardDeprecatedColumnEditTitleMenu } from './RecordBoardDeprecatedColumnEditTitleMenu';
|
||||
const StyledMenuContainer = styled.div`
|
||||
position: absolute;
|
||||
top: ${({ theme }) => theme.spacing(10)};
|
||||
width: 200px;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type RecordBoardDeprecatedColumnDropdownMenuProps = {
|
||||
onClose: () => void;
|
||||
onDelete?: (id: string) => void;
|
||||
stageId: string;
|
||||
};
|
||||
|
||||
type Menu = 'actions' | 'add' | 'title';
|
||||
|
||||
export const RecordBoardDeprecatedColumnDropdownMenu = ({
|
||||
onClose,
|
||||
onDelete,
|
||||
stageId,
|
||||
}: RecordBoardDeprecatedColumnDropdownMenuProps) => {
|
||||
const [currentMenu, setCurrentMenu] = useState('actions');
|
||||
const column = useContext(BoardColumnContext);
|
||||
|
||||
const boardColumnMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { handleMoveBoardColumn } = useBoardColumnsInternal();
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
onClose();
|
||||
}, [goBackToPreviousHotkeyScope, onClose]);
|
||||
|
||||
const setMenu = (menu: Menu) => {
|
||||
if (menu === 'add') {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
);
|
||||
}
|
||||
setCurrentMenu(menu);
|
||||
};
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [boardColumnMenuRef],
|
||||
callback: closeMenu,
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape, Key.Enter],
|
||||
() => {
|
||||
closeMenu();
|
||||
},
|
||||
BoardColumnHotkeyScope.BoardColumn,
|
||||
[],
|
||||
);
|
||||
|
||||
if (!column) return <></>;
|
||||
|
||||
const { isFirstColumn, isLastColumn, columnDefinition } = column;
|
||||
|
||||
const handleColumnMoveLeft = () => {
|
||||
closeMenu();
|
||||
if (isFirstColumn) {
|
||||
return;
|
||||
}
|
||||
handleMoveBoardColumn('left', columnDefinition);
|
||||
};
|
||||
|
||||
const handleColumnMoveRight = () => {
|
||||
closeMenu();
|
||||
if (isLastColumn) {
|
||||
return;
|
||||
}
|
||||
handleMoveBoardColumn('right', columnDefinition);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||
<DropdownMenu data-select-disable>
|
||||
{currentMenu === 'actions' && (
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => setMenu('title')}
|
||||
LeftIcon={IconPencil}
|
||||
text="Edit"
|
||||
/>
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowLeft}
|
||||
onClick={handleColumnMoveLeft}
|
||||
text="Move left"
|
||||
/>
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowRight}
|
||||
onClick={handleColumnMoveRight}
|
||||
text="Move right"
|
||||
/>
|
||||
{/* <MenuItem
|
||||
onClick={() => setMenu('add')}
|
||||
LeftIcon={IconPlus}
|
||||
text="New opportunity"
|
||||
/> */}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
{currentMenu === 'title' && (
|
||||
<RecordBoardDeprecatedColumnEditTitleMenu
|
||||
color={columnDefinition.colorCode ?? 'gray'}
|
||||
onClose={closeMenu}
|
||||
title={columnDefinition.title}
|
||||
onDelete={onDelete}
|
||||
stageId={stageId}
|
||||
/>
|
||||
)}
|
||||
{currentMenu === 'add' && (
|
||||
<div>add</div>
|
||||
// <SingleEntitySelect
|
||||
// disableBackgroundBlur
|
||||
// entitiesToSelect={companies.entitiesToSelect}
|
||||
// loading={companies.loading}
|
||||
// onCancel={closeMenu}
|
||||
// onEntitySelected={handleCompanySelected}
|
||||
// selectedEntity={companies.selectedEntities[0]}
|
||||
// />
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</StyledMenuContainer>
|
||||
);
|
||||
};
|
@ -1,133 +0,0 @@
|
||||
import { ChangeEvent, useCallback, useContext, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor';
|
||||
import {
|
||||
MAIN_COLOR_NAMES,
|
||||
ThemeColor,
|
||||
} from '@/ui/theme/constants/MainColorNames';
|
||||
import { TEXT_INPUT_STYLE } from '@/ui/theme/constants/TextInputStyle';
|
||||
|
||||
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
||||
import { useRecordBoardDeprecated } from '../hooks/useRecordBoardDeprecated';
|
||||
|
||||
const StyledEditTitleContainer = styled.div`
|
||||
--vertical-padding: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(36px - 2 * var(--vertical-padding));
|
||||
padding: var(--vertical-padding) 0;
|
||||
|
||||
width: calc(100%);
|
||||
`;
|
||||
|
||||
const StyledEditModeInput = styled.input`
|
||||
${TEXT_INPUT_STYLE}
|
||||
|
||||
background: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border-color: ${({ theme }) => theme.color.blue};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-shadow: 0px 0px 0px 3px rgba(25, 97, 237, 0.1);
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type RecordBoardDeprecatedColumnEditTitleMenuProps = {
|
||||
onClose: () => void;
|
||||
onDelete?: (id: string) => void;
|
||||
title: string;
|
||||
color: ThemeColor;
|
||||
stageId: string;
|
||||
};
|
||||
|
||||
export const RecordBoardDeprecatedColumnEditTitleMenu = ({
|
||||
onClose,
|
||||
onDelete,
|
||||
stageId,
|
||||
title,
|
||||
color,
|
||||
}: RecordBoardDeprecatedColumnEditTitleMenuProps) => {
|
||||
const [internalValue, setInternalValue] = useState(title);
|
||||
const { onTitleEdit } = useContext(BoardColumnContext) || {};
|
||||
|
||||
const { setBoardColumns } = useRecordBoardDeprecated({
|
||||
recordBoardScopeId: 'company-board',
|
||||
});
|
||||
|
||||
const debouncedOnUpdateTitle = debounce(
|
||||
(newTitle) => onTitleEdit?.({ title: newTitle, color }),
|
||||
200,
|
||||
);
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const title = event.target.value;
|
||||
setInternalValue(title);
|
||||
debouncedOnUpdateTitle(title);
|
||||
|
||||
setBoardColumns((previousBoardColumns) =>
|
||||
previousBoardColumns.map((column) =>
|
||||
column.id === stageId ? { ...column, title: title } : column,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleColorChange = (newColor: ThemeColor) => {
|
||||
onTitleEdit?.({ title, color: newColor });
|
||||
onClose();
|
||||
setBoardColumns((previousBoardColumns) =>
|
||||
previousBoardColumns.map((column) =>
|
||||
column.id === stageId
|
||||
? { ...column, colorCode: newColor ? newColor : 'gray' }
|
||||
: column,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
setBoardColumns((previousBoardColumns) =>
|
||||
previousBoardColumns.filter((column) => column.id !== stageId),
|
||||
);
|
||||
onDelete?.(stageId);
|
||||
onClose();
|
||||
}, [onClose, onDelete, setBoardColumns, stageId]);
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledEditTitleContainer>
|
||||
<StyledEditModeInput
|
||||
value={internalValue}
|
||||
onChange={handleChange}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
/>
|
||||
</StyledEditTitleContainer>
|
||||
<DropdownMenuSeparator />
|
||||
{MAIN_COLOR_NAMES.map((colorName) => (
|
||||
<MenuItemSelectColor
|
||||
key={colorName}
|
||||
onClick={() => handleColorChange(colorName)}
|
||||
color={colorName}
|
||||
selected={colorName === color}
|
||||
variant="pipeline"
|
||||
/>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<MenuItem
|
||||
onClick={handleDelete}
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
@ -1,129 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { recordBoardColumnTotalsFamilySelector } from '@/object-record/record-board-deprecated/states/selectors/recordBoardDeprecatedColumnTotalsFamilySelector';
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
import { IconDotsVertical } from '@/ui/display/icon';
|
||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '../states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||
|
||||
import { RecordBoardDeprecatedColumnDropdownMenu } from './RecordBoardDeprecatedColumnDropdownMenu';
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 24px;
|
||||
justify-content: left;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledAmount = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledNumChildren = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.tertiary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
height: 20px;
|
||||
justify-content: center;
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||
margin-left: auto;
|
||||
width: 16px;
|
||||
`;
|
||||
|
||||
const StyledHeaderActions = styled.div`
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
type RecordBoardDeprecatedColumnHeaderProps = {
|
||||
recordBoardColumnId: string;
|
||||
columnDefinition: BoardColumnDefinition;
|
||||
onDelete?: (columnId: string) => void;
|
||||
};
|
||||
|
||||
export const RecordBoardDeprecatedColumnHeader = ({
|
||||
recordBoardColumnId,
|
||||
columnDefinition,
|
||||
onDelete,
|
||||
}: RecordBoardDeprecatedColumnHeaderProps) => {
|
||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
||||
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleBoardColumnMenuOpen = () => {
|
||||
setIsBoardColumnMenuOpen(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
|
||||
goto: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBoardColumnMenuClose = () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
setIsBoardColumnMenuOpen(false);
|
||||
};
|
||||
|
||||
const boardColumnTotal = useRecoilValue(
|
||||
recordBoardColumnTotalsFamilySelector(recordBoardColumnId),
|
||||
);
|
||||
|
||||
const cardIds = useRecoilValue(
|
||||
recordBoardCardIdsByColumnIdFamilyState(recordBoardColumnId),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledHeader
|
||||
onMouseEnter={() => setIsHeaderHovered(true)}
|
||||
onMouseLeave={() => setIsHeaderHovered(false)}
|
||||
>
|
||||
<Tag
|
||||
onClick={handleBoardColumnMenuOpen}
|
||||
color={columnDefinition.colorCode ?? 'gray'}
|
||||
text={columnDefinition.title}
|
||||
/>
|
||||
{!!boardColumnTotal && <StyledAmount>${boardColumnTotal}</StyledAmount>}
|
||||
{!isHeaderHovered && (
|
||||
<StyledNumChildren>{cardIds.length}</StyledNumChildren>
|
||||
)}
|
||||
{isHeaderHovered && (
|
||||
<StyledHeaderActions>
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconDotsVertical}
|
||||
onClick={handleBoardColumnMenuOpen}
|
||||
/>
|
||||
{/* <LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconPlus}
|
||||
onClick={() => {}}
|
||||
/> */}
|
||||
</StyledHeaderActions>
|
||||
)}
|
||||
</StyledHeader>
|
||||
{isBoardColumnMenuOpen && (
|
||||
<RecordBoardDeprecatedColumnDropdownMenu
|
||||
onClose={handleBoardColumnMenuClose}
|
||||
onDelete={onDelete}
|
||||
stageId={recordBoardColumnId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useRecordBoardDeprecated } from '@/object-record/record-board-deprecated/hooks/useRecordBoardDeprecated';
|
||||
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
type RecordBoardDeprecatedEffectProps = {
|
||||
recordBoardId: string;
|
||||
onFieldsChange: (fields: BoardFieldDefinition<FieldMetadata>[]) => void;
|
||||
};
|
||||
|
||||
export const RecordBoardDeprecatedEffect = ({
|
||||
recordBoardId,
|
||||
onFieldsChange,
|
||||
}: RecordBoardDeprecatedEffectProps) => {
|
||||
const { setOnFieldsChange } = useRecordBoardDeprecated({
|
||||
recordBoardScopeId: recordBoardId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setOnFieldsChange(() => onFieldsChange);
|
||||
}, [onFieldsChange, setOnFieldsChange]);
|
||||
|
||||
return <></>;
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useObjectRecordBoardDeprecated } from '@/object-record/hooks/useObjectRecordBoardDeprecated';
|
||||
import { useRecordBoardDeprecatedActionBarEntriesInternal } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedActionBarEntriesInternal';
|
||||
import { useRecordBoardDeprecatedContextMenuEntriesInternal } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedContextMenuEntriesInternal';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { useUpdateCompanyBoardColumnsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useUpdateCompanyBoardColumnsInternal';
|
||||
import { isNonNullable } from '~/utils/isNonNullable';
|
||||
|
||||
export type RecordBoardDeprecatedInternalEffectProps = {
|
||||
onFieldsChange: (fields: any) => void;
|
||||
};
|
||||
|
||||
export const RecordBoardDeprecatedInternalEffect = () => {
|
||||
const updateCompanyColumnsBoardInternal =
|
||||
useUpdateCompanyBoardColumnsInternal();
|
||||
const { setActionBarEntries } =
|
||||
useRecordBoardDeprecatedActionBarEntriesInternal();
|
||||
const { setContextMenuEntries } =
|
||||
useRecordBoardDeprecatedContextMenuEntriesInternal();
|
||||
|
||||
const {
|
||||
savedPipelineStepsState,
|
||||
savedOpportunitiesState,
|
||||
savedCompaniesState,
|
||||
} = useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
const { fetchMoreOpportunities, fetchMoreCompanies, opportunities } =
|
||||
useObjectRecordBoardDeprecated();
|
||||
|
||||
const [savedOpportunities, setSavedOpportunities] = useRecoilState(
|
||||
savedOpportunitiesState,
|
||||
);
|
||||
const savedPipelineSteps = useRecoilValue(savedPipelineStepsState);
|
||||
const savedCompanies = useRecoilValue(savedCompaniesState);
|
||||
|
||||
useEffect(() => {
|
||||
setSavedOpportunities(opportunities);
|
||||
}, [opportunities, setSavedOpportunities]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNonNullable(fetchMoreOpportunities)) {
|
||||
fetchMoreOpportunities();
|
||||
}
|
||||
}, [fetchMoreOpportunities]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNonNullable(fetchMoreCompanies)) {
|
||||
fetchMoreCompanies();
|
||||
}
|
||||
}, [fetchMoreCompanies]);
|
||||
|
||||
useEffect(() => {
|
||||
if (savedOpportunities && savedCompanies) {
|
||||
setActionBarEntries();
|
||||
setContextMenuEntries();
|
||||
|
||||
updateCompanyColumnsBoardInternal(
|
||||
savedPipelineSteps,
|
||||
savedOpportunities,
|
||||
savedCompanies,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
savedCompanies,
|
||||
savedOpportunities,
|
||||
savedPipelineSteps,
|
||||
setActionBarEntries,
|
||||
setContextMenuEntries,
|
||||
updateCompanyColumnsBoardInternal,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { RecordBoardDeprecatedColumnEditTitleMenu } from '../RecordBoardDeprecatedColumnEditTitleMenu';
|
||||
|
||||
const meta: Meta<typeof RecordBoardDeprecatedColumnEditTitleMenu> = {
|
||||
title: 'UI/Layout/Board/BoardColumnMenu',
|
||||
component: RecordBoardDeprecatedColumnEditTitleMenu,
|
||||
decorators: [ComponentDecorator],
|
||||
args: { color: 'green', title: 'Column title' },
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RecordBoardDeprecatedColumnEditTitleMenu>;
|
||||
|
||||
export const AllTags: Story = {};
|
@ -1 +0,0 @@
|
||||
export const BOARD_OPTIONS_DROPDOWN_ID = 'board-options-dropdown-id';
|
@ -1,14 +0,0 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu';
|
||||
|
||||
export const RecordBoardDeprecatedContextMenu = () => {
|
||||
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
||||
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
||||
|
||||
if (!selectedCardIds.length) {
|
||||
return null;
|
||||
}
|
||||
return <ContextMenu />;
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const BoardCardIdContext = createContext<string | null>(null);
|
@ -1,15 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||
|
||||
type BoardColumnContextProps = {
|
||||
id: string;
|
||||
columnDefinition: BoardColumnDefinition;
|
||||
isFirstColumn: boolean;
|
||||
isLastColumn: boolean;
|
||||
onTitleEdit: (params: { title: string; color: string }) => void;
|
||||
};
|
||||
|
||||
export const BoardColumnContext = createContext<BoardColumnContextProps | null>(
|
||||
null,
|
||||
);
|
@ -1,97 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { useRecordBoardDeprecated } from '@/object-record/record-board-deprecated/hooks/useRecordBoardDeprecated';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
const recordBoardScopeId = 'recordBoardScopeId';
|
||||
|
||||
const renderHookConfig = {
|
||||
wrapper: Wrapper,
|
||||
};
|
||||
|
||||
const useRecordBoardDeprecatedHook = () => {
|
||||
const recordBoard = useRecordBoardDeprecated({ recordBoardScopeId });
|
||||
const { isBoardLoadedState, boardColumnsState, onFieldsChangeState } =
|
||||
useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: recordBoardScopeId,
|
||||
});
|
||||
const isBoardLoaded = useRecoilValue(isBoardLoadedState);
|
||||
const boardColumns = useRecoilValue(boardColumnsState);
|
||||
const onFieldsChange = useRecoilValue(onFieldsChangeState);
|
||||
|
||||
return {
|
||||
recordBoard,
|
||||
isBoardLoaded,
|
||||
boardColumns,
|
||||
onFieldsChange,
|
||||
};
|
||||
};
|
||||
|
||||
describe('useRecordBoardDeprecated', () => {
|
||||
it('should set isBoardLoadedState', async () => {
|
||||
const { result } = renderHook(
|
||||
() => useRecordBoardDeprecatedHook(),
|
||||
renderHookConfig,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.recordBoard.setIsBoardLoaded(true);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isBoardLoaded).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set boardColumnsState', async () => {
|
||||
const columns = [
|
||||
{
|
||||
id: '1',
|
||||
title: '1',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
title: '1',
|
||||
position: 1,
|
||||
},
|
||||
];
|
||||
const { result } = renderHook(
|
||||
() => useRecordBoardDeprecatedHook(),
|
||||
renderHookConfig,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.recordBoard.setBoardColumns(columns);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.boardColumns).toEqual(columns);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set setOnFieldsChange', async () => {
|
||||
const onFieldsChangeFunction = () => {};
|
||||
const onFieldsChange = jest.fn(() => onFieldsChangeFunction);
|
||||
const { result } = renderHook(
|
||||
() => useRecordBoardDeprecatedHook(),
|
||||
renderHookConfig,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.recordBoard.setOnFieldsChange(onFieldsChange);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.onFieldsChange).toEqual(onFieldsChangeFunction);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,78 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import gql from 'graphql-tag';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useCreateOpportunity } from '@/object-record/record-board-deprecated/hooks/internal/useCreateOpportunity';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board-deprecated/states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
|
||||
const mockedUuid = 'mocked-uuid';
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => mockedUuid,
|
||||
}));
|
||||
|
||||
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
|
||||
useMapFieldMetadataToGraphQLQuery: () => () => '\n',
|
||||
}));
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
mutation CreateOneOpportunity($input: OpportunityCreateInput!) {
|
||||
createOpportunity(data: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
id: mockedUuid,
|
||||
pipelineStepId: 'pipelineStepId',
|
||||
companyId: 'New Opportunity',
|
||||
},
|
||||
},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: { createOpportunity: { id: '' } },
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
const renderHookConfig = {
|
||||
wrapper: Wrapper,
|
||||
};
|
||||
|
||||
describe('useCreateOpportunity', () => {
|
||||
it('should create opportunity successfully', () => {
|
||||
const companyIdname = 'New Opportunity';
|
||||
const opportunityPipelineStepId = 'pipelineStepId';
|
||||
|
||||
const { result } = renderHook(
|
||||
() => ({
|
||||
createOpportunity: useCreateOpportunity(),
|
||||
recordBoardCardIdsByColumnId: useRecoilValue(
|
||||
recordBoardCardIdsByColumnIdFamilyState(opportunityPipelineStepId),
|
||||
),
|
||||
}),
|
||||
renderHookConfig,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.createOpportunity(
|
||||
companyIdname,
|
||||
opportunityPipelineStepId,
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.recordBoardCardIdsByColumnId).toStrictEqual([
|
||||
mockedUuid,
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,57 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { BoardCardIdContext } from '@/object-record/record-board-deprecated/contexts/BoardCardIdContext';
|
||||
import { useCurrentRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useCurrentRecordBoardDeprecatedCardSelectedInternal';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const boardCardId = 'boardCardId';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<BoardCardIdContext.Provider value={boardCardId}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</BoardCardIdContext.Provider>
|
||||
</RecordBoardDeprecatedScope>
|
||||
);
|
||||
|
||||
describe('useCurrentRecordBoardDeprecatedCardSelectedInternal', () => {
|
||||
it('should update the data when selecting and deselecting the cardId', () => {
|
||||
const { result } = renderHook(
|
||||
() => ({
|
||||
currentCardSelect:
|
||||
useCurrentRecordBoardDeprecatedCardSelectedInternal(),
|
||||
activeCardIdsState: useRecoilValue(
|
||||
useRecordBoardDeprecatedScopedStates().activeCardIdsState,
|
||||
),
|
||||
actionBarOpenState: useRecoilValue(actionBarOpenState),
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.activeCardIdsState).toStrictEqual([]);
|
||||
expect(result.current.actionBarOpenState).toBe(false);
|
||||
expect(result.current.currentCardSelect.isCurrentCardSelected).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.currentCardSelect.setCurrentCardSelected(true);
|
||||
});
|
||||
|
||||
expect(result.current.activeCardIdsState).toStrictEqual([boardCardId]);
|
||||
expect(result.current.actionBarOpenState).toBe(true);
|
||||
expect(result.current.currentCardSelect.isCurrentCardSelected).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.currentCardSelect.setCurrentCardSelected(false);
|
||||
});
|
||||
|
||||
expect(result.current.activeCardIdsState).toStrictEqual([]);
|
||||
expect(result.current.actionBarOpenState).toBe(false);
|
||||
expect(result.current.currentCardSelect.isCurrentCardSelected).toBe(false);
|
||||
});
|
||||
});
|
@ -1,124 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import gql from 'graphql-tag';
|
||||
import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { BoardCardIdContext } from '@/object-record/record-board-deprecated/contexts/BoardCardIdContext';
|
||||
import { useCreateOpportunity } from '@/object-record/record-board-deprecated/hooks/internal/useCreateOpportunity';
|
||||
import { useCurrentRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useCurrentRecordBoardDeprecatedCardSelectedInternal';
|
||||
import { useDeleteSelectedRecordBoardDeprecatedCardsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useDeleteSelectedRecordBoardDeprecatedCardsInternal';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board-deprecated/states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
|
||||
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
|
||||
useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'),
|
||||
}));
|
||||
|
||||
const mockedUuid = 'mocked-uuid';
|
||||
jest.mock('uuid', () => ({ v4: () => mockedUuid }));
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
mutation DeleteManyOpportunities($filter: OpportunityFilterInput!) {
|
||||
deleteOpportunities(filter: $filter) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { filter: { id: { in: [mockedUuid] } } },
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: { deleteOpportunities: { id: '' } },
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
mutation CreateOneOpportunity($input: OpportunityCreateInput!) {
|
||||
createOpportunity(data: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
id: mockedUuid,
|
||||
pipelineStepId: 'pipelineStepId',
|
||||
companyId: 'New Opportunity',
|
||||
},
|
||||
},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: { createOpportunity: { id: '' } },
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<BoardCardIdContext.Provider value={mockedUuid}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</BoardCardIdContext.Provider>
|
||||
</RecordBoardDeprecatedScope>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
describe('useDeleteSelectedRecordBoardDeprecatedCardsInternal', () => {
|
||||
it('should run apollo mutation and update recoil state when delete selected cards', async () => {
|
||||
const companyIdname = 'New Opportunity';
|
||||
const opportunityPipelineStepId = 'pipelineStepId';
|
||||
|
||||
const { result } = renderHook(
|
||||
() => ({
|
||||
createOpportunity: useCreateOpportunity(),
|
||||
deleteSelectedCards:
|
||||
useDeleteSelectedRecordBoardDeprecatedCardsInternal(),
|
||||
setBoardColumns: useSetRecoilState(
|
||||
useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: scopeId,
|
||||
}).boardColumnsState,
|
||||
),
|
||||
recordBoardCardIdsByColumnId: useRecoilValue(
|
||||
recordBoardCardIdsByColumnIdFamilyState(opportunityPipelineStepId),
|
||||
),
|
||||
currentSelect: useCurrentRecordBoardDeprecatedCardSelectedInternal(),
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.currentSelect.setCurrentCardSelected(true);
|
||||
result.current.setBoardColumns([
|
||||
{
|
||||
id: opportunityPipelineStepId,
|
||||
title: '1',
|
||||
position: 1,
|
||||
},
|
||||
]);
|
||||
result.current.createOpportunity(
|
||||
companyIdname,
|
||||
opportunityPipelineStepId,
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.recordBoardCardIdsByColumnId).toStrictEqual([
|
||||
mockedUuid,
|
||||
]);
|
||||
await act(async () => {
|
||||
await result.current.deleteSelectedCards();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.recordBoardCardIdsByColumnId).toStrictEqual([]);
|
||||
expect(mocks[0].result).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,54 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useDeleteSelectedRecordBoardDeprecatedCardsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useDeleteSelectedRecordBoardDeprecatedCardsInternal';
|
||||
import { useRecordBoardDeprecatedActionBarEntriesInternal } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedActionBarEntriesInternal';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider>
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordBoardDeprecatedScope>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
const renderHookConfig = {
|
||||
wrapper: Wrapper,
|
||||
};
|
||||
|
||||
describe('useRecordBoardDeprecatedActionBarEntriesInternal', () => {
|
||||
it('should update actionBarEntries', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
const deleteSelectedBoardCards =
|
||||
useDeleteSelectedRecordBoardDeprecatedCardsInternal();
|
||||
const newActionBarEntry = {
|
||||
label: 'Delete',
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
onClick: deleteSelectedBoardCards,
|
||||
};
|
||||
return {
|
||||
setActionBarEntries: useRecordBoardDeprecatedActionBarEntriesInternal(),
|
||||
actionBarEntries: useRecoilValue(actionBarEntriesState),
|
||||
newActionBarEntry,
|
||||
};
|
||||
}, renderHookConfig);
|
||||
|
||||
expect(result.current.actionBarEntries).toStrictEqual([]);
|
||||
|
||||
act(() => {
|
||||
result.current.setActionBarEntries.setActionBarEntries();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(JSON.stringify(result.current.actionBarEntries)).toBe(
|
||||
JSON.stringify([result.current.newActionBarEntry]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,117 +0,0 @@
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedCardFieldsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedCardFieldsInternal';
|
||||
import { onFieldsChangeScopedState } from '@/object-record/record-board-deprecated/states/onFieldsChangeScopedState';
|
||||
import { recordBoardCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedCardFieldsScopedState';
|
||||
import { savedRecordBoardDeprecatedCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/savedRecordBoardDeprecatedCardFieldsScopedState';
|
||||
import { FieldType } from '@/object-record/record-field/types/FieldType';
|
||||
|
||||
const recordBoardScopeId = 'recordBoardScopeId';
|
||||
|
||||
const renderHookConfig = {
|
||||
wrapper: RecoilRoot,
|
||||
};
|
||||
|
||||
describe('useRecordBoardDeprecatedCardFieldsInternal', () => {
|
||||
it('should toggle field visibility', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
const [cardFieldsList, setCardFieldsList] = useRecoilState(
|
||||
recordBoardCardFieldsScopedState({ scopeId: recordBoardScopeId }),
|
||||
);
|
||||
return {
|
||||
boardCardFields: useRecordBoardDeprecatedCardFieldsInternal({
|
||||
recordBoardScopeId,
|
||||
}),
|
||||
cardFieldsList,
|
||||
setCardFieldsList,
|
||||
};
|
||||
}, renderHookConfig);
|
||||
|
||||
const field = {
|
||||
position: 0,
|
||||
fieldMetadataId: 'id',
|
||||
label: 'label',
|
||||
iconName: 'icon',
|
||||
type: 'TEXT' as FieldType,
|
||||
metadata: {
|
||||
fieldName: 'fieldName',
|
||||
},
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.setCardFieldsList([field]);
|
||||
});
|
||||
|
||||
expect(result.current.cardFieldsList[0].isVisible).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.boardCardFields.handleFieldVisibilityChange({
|
||||
...field,
|
||||
isVisible: true,
|
||||
});
|
||||
});
|
||||
|
||||
waitFor(() => {
|
||||
expect(result.current.cardFieldsList[0].isVisible).toBe(false);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.boardCardFields.handleFieldVisibilityChange({
|
||||
...field,
|
||||
isVisible: false,
|
||||
});
|
||||
});
|
||||
|
||||
waitFor(() => {
|
||||
expect(result.current.cardFieldsList[0].isVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the onFieldsChange callback and update board card states', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
const [onFieldsChange, setOnFieldsChange] = useRecoilState(
|
||||
onFieldsChangeScopedState({ scopeId: recordBoardScopeId }),
|
||||
);
|
||||
return {
|
||||
boardCardFieldsHook: useRecordBoardDeprecatedCardFieldsInternal({
|
||||
recordBoardScopeId,
|
||||
}),
|
||||
boardCardFieldsList: useRecoilValue(
|
||||
recordBoardCardFieldsScopedState({ scopeId: recordBoardScopeId }),
|
||||
),
|
||||
savedBoardCardFieldsList: useRecoilValue(
|
||||
savedRecordBoardDeprecatedCardFieldsScopedState({
|
||||
scopeId: recordBoardScopeId,
|
||||
}),
|
||||
),
|
||||
onFieldsChange,
|
||||
setOnFieldsChange,
|
||||
};
|
||||
}, renderHookConfig);
|
||||
|
||||
const field = {
|
||||
position: 0,
|
||||
fieldMetadataId: 'id',
|
||||
label: 'label',
|
||||
iconName: 'icon',
|
||||
type: 'TEXT' as FieldType,
|
||||
metadata: {
|
||||
fieldName: 'fieldName',
|
||||
},
|
||||
isVisible: true,
|
||||
};
|
||||
const onChangeFunction = jest.fn();
|
||||
|
||||
await act(async () => {
|
||||
result.current.setOnFieldsChange(() => onChangeFunction);
|
||||
result.current.boardCardFieldsHook.handleFieldsReorder([field]);
|
||||
});
|
||||
|
||||
expect(onChangeFunction).toHaveBeenCalledWith([field]);
|
||||
expect(result.current.savedBoardCardFieldsList).toStrictEqual([field]);
|
||||
expect(result.current.boardCardFieldsList).toStrictEqual([field]);
|
||||
});
|
||||
});
|
@ -1,128 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import gql from 'graphql-tag';
|
||||
import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useBoardColumnsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedColumnsInternal';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
|
||||
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
|
||||
useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'),
|
||||
}));
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
mutation UpdateOnePipelineStep(
|
||||
$idToUpdate: ID!
|
||||
$input: PipelineStepUpdateInput!
|
||||
) {
|
||||
updatePipelineStep(id: $idToUpdate, data: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { idToUpdate: '1', input: { position: 0 } },
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: { updatePipelineStep: { id: '' } },
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordBoardDeprecatedScope>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
const renderHookConfig = {
|
||||
wrapper: Wrapper,
|
||||
};
|
||||
|
||||
describe('useBoardColumnsInternal', () => {
|
||||
it('should update boardColumns state when moving to left and right', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
const [boardColumnsList, setBoardColumnsList] = useRecoilState(
|
||||
useRecordBoardDeprecatedScopedStates().boardColumnsState,
|
||||
);
|
||||
return {
|
||||
boardColumns: useBoardColumnsInternal(),
|
||||
boardColumnsList,
|
||||
setBoardColumnsList,
|
||||
};
|
||||
}, renderHookConfig);
|
||||
const columns: BoardColumnDefinition[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '1',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '2',
|
||||
position: 1,
|
||||
},
|
||||
];
|
||||
act(() => {
|
||||
result.current.setBoardColumnsList(columns);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.boardColumns.handleMoveBoardColumn('right', columns[0]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.boardColumnsList).toStrictEqual([
|
||||
{ id: '2', title: '2', position: 0, index: 0 },
|
||||
{ id: '1', title: '1', position: 1, index: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.boardColumns.handleMoveBoardColumn('left', columns[0]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.boardColumnsList).toStrictEqual([
|
||||
{ id: '1', title: '1', position: 0, index: 0 },
|
||||
{ id: '2', title: '2', position: 1, index: 1 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call apollo mutation after persistBoardColumns', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
return {
|
||||
boardColumns: useBoardColumnsInternal(),
|
||||
setBoardColumnsList: useSetRecoilState(
|
||||
useRecordBoardDeprecatedScopedStates().boardColumnsState,
|
||||
),
|
||||
};
|
||||
}, renderHookConfig);
|
||||
const columns: BoardColumnDefinition[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '1',
|
||||
position: 0,
|
||||
},
|
||||
];
|
||||
act(() => {
|
||||
result.current.setBoardColumnsList(columns);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.boardColumns.persistBoardColumns();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks[0].result).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useDeleteSelectedRecordBoardDeprecatedCardsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useDeleteSelectedRecordBoardDeprecatedCardsInternal';
|
||||
import { useRecordBoardDeprecatedContextMenuEntriesInternal } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedContextMenuEntriesInternal';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider>
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordBoardDeprecatedScope>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
describe('useRecordBoardDeprecatedContextMenuEntriesInternal', () => {
|
||||
it('should update contextEntries', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const deleteSelectedBoardCards =
|
||||
useDeleteSelectedRecordBoardDeprecatedCardsInternal();
|
||||
const newContextEntry = {
|
||||
label: 'Delete',
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
onClick: deleteSelectedBoardCards,
|
||||
};
|
||||
return {
|
||||
setContextEntries:
|
||||
useRecordBoardDeprecatedContextMenuEntriesInternal(),
|
||||
contextEntries: useRecoilValue(contextMenuEntriesState),
|
||||
newContextEntry,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.contextEntries).toStrictEqual([]);
|
||||
|
||||
act(() => {
|
||||
result.current.setContextEntries.setContextMenuEntries();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(JSON.stringify(result.current.contextEntries)).toBe(
|
||||
JSON.stringify([result.current.newContextEntry]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useSetRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useSetRecordBoardDeprecatedCardSelectedInternal';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { isRecordBoardDeprecatedCardSelectedFamilyState } from '@/object-record/record-board-deprecated/states/isRecordBoardDeprecatedCardSelectedFamilyState';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const boardCardId = 'boardCardId';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordBoardDeprecatedScope>
|
||||
);
|
||||
|
||||
const recordBoardScopeId = 'recordBoardScopeId';
|
||||
|
||||
describe('useSetRecordBoardDeprecatedCardSelectedInternal', () => {
|
||||
it('should update the data when selecting and deselecting the cardId', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return {
|
||||
cardSelect: useSetRecordBoardDeprecatedCardSelectedInternal({
|
||||
recordBoardScopeId,
|
||||
}),
|
||||
isSelected: useRecoilValue(
|
||||
isRecordBoardDeprecatedCardSelectedFamilyState(boardCardId),
|
||||
),
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.isSelected).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.cardSelect.setCardSelected(boardCardId, true);
|
||||
});
|
||||
|
||||
expect(result.current.isSelected).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.cardSelect.setCardSelected(boardCardId, false);
|
||||
});
|
||||
|
||||
expect(result.current.isSelected).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.cardSelect.setCardSelected(boardCardId, true);
|
||||
result.current.cardSelect.unselectAllActiveCards();
|
||||
});
|
||||
|
||||
expect(result.current.isSelected).toBe(false);
|
||||
});
|
||||
});
|
@ -1,113 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { CompanyForBoard } from '@/companies/types/CompanyProgress';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { useUpdateCompanyBoardColumnsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useUpdateCompanyBoardColumnsInternal';
|
||||
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board-deprecated/states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStepsState';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecordBoardDeprecatedScope recordBoardScopeId={scopeId}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RecordBoardDeprecatedScope>
|
||||
);
|
||||
|
||||
describe('useUpdateCompanyBoardColumnsInternal', () => {
|
||||
it('should update recoil state after updateCompanyBoardColumns call ', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return {
|
||||
updateCompanyBoardColumns: useUpdateCompanyBoardColumnsInternal(),
|
||||
currentPipeline: useRecoilValue(currentPipelineStepsState),
|
||||
boardColumns: useRecoilValue(
|
||||
useRecordBoardDeprecatedScopedStates().boardColumnsState,
|
||||
),
|
||||
savedBoardColumns: useRecoilValue(
|
||||
useRecordBoardDeprecatedScopedStates().savedBoardColumnsState,
|
||||
),
|
||||
idsByColumnId: useRecoilValue(
|
||||
recordBoardCardIdsByColumnIdFamilyState('1'),
|
||||
),
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const pipelineSteps: PipelineStep[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Step 1',
|
||||
color: 'red',
|
||||
position: 1,
|
||||
createdAt: '2024-01-12',
|
||||
updatedAt: '2024-01-12',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Step 2',
|
||||
color: 'blue',
|
||||
position: 1,
|
||||
createdAt: '2024-01-12',
|
||||
updatedAt: '2024-01-12',
|
||||
},
|
||||
];
|
||||
const opportunity: Opportunity = {
|
||||
id: '123',
|
||||
amount: {
|
||||
amountMicros: 1000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
closeDate: new Date('2024-02-01'),
|
||||
probability: 0.75,
|
||||
pipelineStepId: '1',
|
||||
pipelineStep: pipelineSteps[0],
|
||||
pointOfContactId: '456',
|
||||
pointOfContact: {
|
||||
id: '456',
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
},
|
||||
};
|
||||
|
||||
const companyForBoard: CompanyForBoard = {
|
||||
id: '789',
|
||||
name: 'Acme Inc.',
|
||||
domainName: 'acme.com',
|
||||
};
|
||||
|
||||
expect(result.current.currentPipeline).toStrictEqual([]);
|
||||
expect(result.current.savedBoardColumns).toStrictEqual([]);
|
||||
expect(result.current.boardColumns).toStrictEqual([]);
|
||||
expect(result.current.idsByColumnId).toStrictEqual([]);
|
||||
|
||||
act(() => {
|
||||
result.current.updateCompanyBoardColumns(
|
||||
pipelineSteps,
|
||||
[opportunity],
|
||||
[companyForBoard],
|
||||
);
|
||||
});
|
||||
|
||||
const expectedBoardColumns = [
|
||||
{ id: '1', title: 'Step 1', colorCode: 'red', position: 1 },
|
||||
{ id: '2', title: 'Step 2', colorCode: 'blue', position: 1 },
|
||||
];
|
||||
|
||||
expect(result.current.currentPipeline).toStrictEqual(pipelineSteps);
|
||||
expect(result.current.savedBoardColumns).toStrictEqual(
|
||||
expectedBoardColumns,
|
||||
);
|
||||
expect(result.current.boardColumns).toStrictEqual(expectedBoardColumns);
|
||||
expect(result.current.idsByColumnId).toStrictEqual([opportunity.id]);
|
||||
});
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board-deprecated/states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
|
||||
export const useCreateOpportunity = () => {
|
||||
const { createOneRecord: createOneOpportunity } =
|
||||
useCreateOneRecord<Opportunity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
||||
});
|
||||
|
||||
const createOpportunity = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async (companyId: string, pipelineStepId: string) => {
|
||||
const newUuid = v4();
|
||||
|
||||
set(
|
||||
recordBoardCardIdsByColumnIdFamilyState(pipelineStepId),
|
||||
(oldValue) => [...oldValue, newUuid],
|
||||
);
|
||||
|
||||
await createOneOpportunity?.({
|
||||
id: newUuid,
|
||||
name: 'Opportunity',
|
||||
pipelineStepId,
|
||||
companyId: companyId,
|
||||
});
|
||||
},
|
||||
[createOneOpportunity],
|
||||
);
|
||||
|
||||
return createOpportunity;
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
|
||||
|
||||
import { BoardCardIdContext } from '../../contexts/BoardCardIdContext';
|
||||
import { isRecordBoardDeprecatedCardSelectedFamilyState } from '../../states/isRecordBoardDeprecatedCardSelectedFamilyState';
|
||||
|
||||
export const useCurrentRecordBoardDeprecatedCardSelectedInternal = () => {
|
||||
const currentCardId = useContext(BoardCardIdContext);
|
||||
|
||||
const isCurrentCardSelected = useRecoilValue(
|
||||
isRecordBoardDeprecatedCardSelectedFamilyState(currentCardId ?? ''),
|
||||
);
|
||||
|
||||
const { activeCardIdsState } = useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
const setActiveCardIds = useSetRecoilState(activeCardIdsState);
|
||||
|
||||
const setCurrentCardSelected = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(selected: boolean) => {
|
||||
if (!currentCardId) return;
|
||||
|
||||
set(
|
||||
isRecordBoardDeprecatedCardSelectedFamilyState(currentCardId),
|
||||
selected,
|
||||
);
|
||||
set(actionBarOpenState, selected);
|
||||
|
||||
if (selected) {
|
||||
setActiveCardIds((prevActiveCardIds) => [
|
||||
...prevActiveCardIds,
|
||||
currentCardId,
|
||||
]);
|
||||
} else {
|
||||
setActiveCardIds((prevActiveCardIds) =>
|
||||
prevActiveCardIds.filter((id) => id !== currentCardId),
|
||||
);
|
||||
}
|
||||
},
|
||||
[currentCardId, setActiveCardIds],
|
||||
);
|
||||
|
||||
return {
|
||||
isCurrentCardSelected,
|
||||
setCurrentCardSelected,
|
||||
};
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
|
||||
import { useRemoveRecordBoardDeprecatedCardIdsInternal } from './useRemoveRecordBoardDeprecatedCardIdsInternal';
|
||||
|
||||
export const useDeleteSelectedRecordBoardDeprecatedCardsInternal = () => {
|
||||
const removeCardIds = useRemoveRecordBoardDeprecatedCardIdsInternal();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { deleteManyRecords: deleteManyOpportunities } = useDeleteManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Opportunity,
|
||||
});
|
||||
|
||||
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
const deleteSelectedBoardCards = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async () => {
|
||||
const selectedCardIds = snapshot
|
||||
.getLoadable(selectedCardIdsSelector)
|
||||
.getValue();
|
||||
|
||||
await deleteManyOpportunities?.(selectedCardIds);
|
||||
removeCardIds(selectedCardIds);
|
||||
selectedCardIds.forEach((id) => {
|
||||
apolloClient.cache.evict({ id: `Opportunity:${id}` });
|
||||
});
|
||||
},
|
||||
[
|
||||
selectedCardIdsSelector,
|
||||
removeCardIds,
|
||||
deleteManyOpportunities,
|
||||
apolloClient.cache,
|
||||
],
|
||||
);
|
||||
|
||||
return deleteSelectedBoardCards;
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useDeleteSelectedRecordBoardDeprecatedCardsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useDeleteSelectedRecordBoardDeprecatedCardsInternal';
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
||||
|
||||
export const useRecordBoardDeprecatedActionBarEntriesInternal = () => {
|
||||
const setActionBarEntriesRecoil = useSetRecoilState(actionBarEntriesState);
|
||||
|
||||
const deleteSelectedBoardCards =
|
||||
useDeleteSelectedRecordBoardDeprecatedCardsInternal();
|
||||
|
||||
const setActionBarEntries = useCallback(() => {
|
||||
setActionBarEntriesRecoil([
|
||||
{
|
||||
label: 'Delete',
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
onClick: deleteSelectedBoardCards,
|
||||
},
|
||||
]);
|
||||
}, [deleteSelectedBoardCards, setActionBarEntriesRecoil]);
|
||||
|
||||
return {
|
||||
setActionBarEntries,
|
||||
};
|
||||
};
|
@ -1,97 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { RecordBoardDeprecatedScopeInternalContext } from '@/object-record/record-board-deprecated/scopes/scope-internal-context/RecordBoardDeprecatedScopeInternalContext';
|
||||
import { onFieldsChangeScopedState } from '@/object-record/record-board-deprecated/states/onFieldsChangeScopedState';
|
||||
import { recordBoardCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedCardFieldsScopedState';
|
||||
import { savedRecordBoardDeprecatedCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/savedRecordBoardDeprecatedCardFieldsScopedState';
|
||||
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
|
||||
type useRecordBoardDeprecatedCardFieldsInternalProps = {
|
||||
recordBoardScopeId?: string;
|
||||
};
|
||||
|
||||
export const useRecordBoardDeprecatedCardFieldsInternal = (
|
||||
props?: useRecordBoardDeprecatedCardFieldsInternalProps,
|
||||
) => {
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
RecordBoardDeprecatedScopeInternalContext,
|
||||
props?.recordBoardScopeId,
|
||||
);
|
||||
|
||||
const setBoardCardFields = useSetRecoilState(
|
||||
recordBoardCardFieldsScopedState({ scopeId }),
|
||||
);
|
||||
|
||||
const setSavedBoardCardFields = useSetRecoilState(
|
||||
savedRecordBoardDeprecatedCardFieldsScopedState({ scopeId }),
|
||||
);
|
||||
|
||||
const handleFieldVisibilityChange = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (
|
||||
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
|
||||
) => {
|
||||
const existingFields = snapshot
|
||||
.getLoadable(recordBoardCardFieldsScopedState({ scopeId }))
|
||||
.getValue();
|
||||
|
||||
const fieldIndex = existingFields.findIndex(
|
||||
({ fieldMetadataId }) => field.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
const fields = [...existingFields];
|
||||
|
||||
if (fieldIndex === -1) {
|
||||
fields.push({ ...field, position: existingFields.length });
|
||||
} else {
|
||||
fields[fieldIndex] = {
|
||||
...field,
|
||||
isVisible: !field.isVisible,
|
||||
position: existingFields.length,
|
||||
};
|
||||
}
|
||||
|
||||
setSavedBoardCardFields(fields);
|
||||
setBoardCardFields(fields);
|
||||
|
||||
const onFieldsChange = snapshot
|
||||
.getLoadable(onFieldsChangeScopedState({ scopeId }))
|
||||
.getValue();
|
||||
|
||||
onFieldsChange?.(fields);
|
||||
},
|
||||
[scopeId, setBoardCardFields, setSavedBoardCardFields],
|
||||
);
|
||||
|
||||
const handleFieldsChange = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (fields: BoardFieldDefinition<FieldMetadata>[]) => {
|
||||
setSavedBoardCardFields(fields);
|
||||
setBoardCardFields(fields);
|
||||
|
||||
const onFieldsChange = snapshot
|
||||
.getLoadable(onFieldsChangeScopedState({ scopeId }))
|
||||
.getValue();
|
||||
|
||||
await onFieldsChange?.(fields);
|
||||
},
|
||||
[scopeId, setBoardCardFields, setSavedBoardCardFields],
|
||||
);
|
||||
|
||||
const handleFieldsReorder = useCallback(
|
||||
async (fields: BoardFieldDefinition<FieldMetadata>[]) => {
|
||||
const updatedFields = fields.map((column, index) => ({
|
||||
...column,
|
||||
position: index,
|
||||
}));
|
||||
|
||||
await handleFieldsChange(updatedFields);
|
||||
},
|
||||
[handleFieldsChange],
|
||||
);
|
||||
|
||||
return { handleFieldVisibilityChange, handleFieldsReorder };
|
||||
};
|
@ -1,58 +0,0 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
|
||||
|
||||
import { BoardColumnDefinition } from '../../types/BoardColumnDefinition';
|
||||
|
||||
export const useBoardColumnsInternal = () => {
|
||||
const { boardColumnsState } = useRecordBoardDeprecatedScopedStates();
|
||||
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||
|
||||
const { handleColumnMove } = useMoveViewColumns();
|
||||
|
||||
const { updateOneRecord: updateOnePipelineStep } =
|
||||
useUpdateOneRecord<PipelineStep>({
|
||||
objectNameSingular: CoreObjectNameSingular.PipelineStep,
|
||||
});
|
||||
|
||||
const updatedPipelineSteps = (stages: BoardColumnDefinition[]) => {
|
||||
if (!stages.length) return;
|
||||
|
||||
return Promise.all(
|
||||
stages.map(
|
||||
(stage) =>
|
||||
updateOnePipelineStep?.({
|
||||
idToUpdate: stage.id,
|
||||
updateOneRecordInput: {
|
||||
position: stage.position,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const persistBoardColumns = async () => {
|
||||
await updatedPipelineSteps(boardColumns);
|
||||
};
|
||||
|
||||
const handleMoveBoardColumn = (
|
||||
direction: 'left' | 'right',
|
||||
column: BoardColumnDefinition,
|
||||
) => {
|
||||
const currentColumnArrayIndex = boardColumns.findIndex(
|
||||
(tableColumn) => tableColumn.id === column.id,
|
||||
);
|
||||
const columns = handleColumnMove(
|
||||
direction,
|
||||
currentColumnArrayIndex,
|
||||
boardColumns,
|
||||
);
|
||||
setBoardColumns(columns);
|
||||
};
|
||||
|
||||
return { handleMoveBoardColumn, persistBoardColumns };
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useDeleteSelectedRecordBoardDeprecatedCardsInternal } from '@/object-record/record-board-deprecated/hooks/internal/useDeleteSelectedRecordBoardDeprecatedCardsInternal';
|
||||
import { IconTrash } from '@/ui/display/icon';
|
||||
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
||||
|
||||
export const useRecordBoardDeprecatedContextMenuEntriesInternal = () => {
|
||||
const setContextMenuEntriesRecoil = useSetRecoilState(
|
||||
contextMenuEntriesState,
|
||||
);
|
||||
|
||||
const deleteSelectedBoardCards =
|
||||
useDeleteSelectedRecordBoardDeprecatedCardsInternal();
|
||||
|
||||
const setContextMenuEntries = useCallback(() => {
|
||||
setContextMenuEntriesRecoil([
|
||||
{
|
||||
label: 'Delete',
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
onClick: deleteSelectedBoardCards,
|
||||
},
|
||||
]);
|
||||
}, [deleteSelectedBoardCards, setContextMenuEntriesRecoil]);
|
||||
|
||||
return {
|
||||
setContextMenuEntries,
|
||||
};
|
||||
};
|
@ -1,59 +0,0 @@
|
||||
import { RecordBoardDeprecatedScopeInternalContext } from '@/object-record/record-board-deprecated/scopes/scope-internal-context/RecordBoardDeprecatedScopeInternalContext';
|
||||
import { getRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/utils/getRecordBoardDeprecatedScopedStates';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
|
||||
type useRecordBoardDeprecatedScopedStatesProps = {
|
||||
recordBoardScopeId?: string;
|
||||
};
|
||||
|
||||
export const useRecordBoardDeprecatedScopedStates = (
|
||||
args?: useRecordBoardDeprecatedScopedStatesProps,
|
||||
) => {
|
||||
const { recordBoardScopeId } = args ?? {};
|
||||
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
RecordBoardDeprecatedScopeInternalContext,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const {
|
||||
activeCardIdsState,
|
||||
availableBoardCardFieldsState,
|
||||
boardColumnsState,
|
||||
isBoardLoadedState,
|
||||
isCompactViewEnabledState,
|
||||
savedBoardColumnsState,
|
||||
boardFiltersState,
|
||||
boardSortsState,
|
||||
onFieldsChangeState,
|
||||
boardCardFieldsByKeySelector,
|
||||
hiddenBoardCardFieldsSelector,
|
||||
selectedCardIdsSelector,
|
||||
visibleBoardCardFieldsSelector,
|
||||
savedCompaniesState,
|
||||
savedOpportunitiesState,
|
||||
savedPipelineStepsState,
|
||||
} = getRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: scopeId,
|
||||
});
|
||||
|
||||
return {
|
||||
scopeId,
|
||||
activeCardIdsState,
|
||||
availableBoardCardFieldsState,
|
||||
boardColumnsState,
|
||||
isBoardLoadedState,
|
||||
isCompactViewEnabledState,
|
||||
savedBoardColumnsState,
|
||||
boardFiltersState,
|
||||
boardSortsState,
|
||||
onFieldsChangeState,
|
||||
boardCardFieldsByKeySelector,
|
||||
hiddenBoardCardFieldsSelector,
|
||||
selectedCardIdsSelector,
|
||||
visibleBoardCardFieldsSelector,
|
||||
savedCompaniesState,
|
||||
savedOpportunitiesState,
|
||||
savedPipelineStepsState,
|
||||
};
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
// 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 { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '../../states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
|
||||
export const useRemoveRecordBoardDeprecatedCardIdsInternal = () => {
|
||||
const { boardColumnsState } = useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
return useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(cardIdToRemove: string[]) => {
|
||||
const boardColumns = snapshot.getLoadable(boardColumnsState).getValue();
|
||||
|
||||
boardColumns.forEach((boardColumn) => {
|
||||
const columnCardIds = snapshot
|
||||
.getLoadable(
|
||||
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
|
||||
)
|
||||
.getValue();
|
||||
set(
|
||||
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
|
||||
columnCardIds.filter((cardId) => !cardIdToRemove.includes(cardId)),
|
||||
);
|
||||
});
|
||||
},
|
||||
[boardColumnsState],
|
||||
);
|
||||
};
|
@ -1,62 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { RecordBoardDeprecatedScopeInternalContext } from '@/object-record/record-board-deprecated/scopes/scope-internal-context/RecordBoardDeprecatedScopeInternalContext';
|
||||
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
|
||||
import { isRecordBoardDeprecatedCardSelectedFamilyState } from '../../states/isRecordBoardDeprecatedCardSelectedFamilyState';
|
||||
|
||||
export const useSetRecordBoardDeprecatedCardSelectedInternal = (props: any) => {
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
RecordBoardDeprecatedScopeInternalContext,
|
||||
props?.recordBoardScopeId,
|
||||
);
|
||||
const { activeCardIdsState } = useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: scopeId,
|
||||
});
|
||||
|
||||
const setCardSelected = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(cardId: string, selected: boolean) => {
|
||||
const activeCardIds = snapshot
|
||||
.getLoadable(activeCardIdsState)
|
||||
.getValue();
|
||||
|
||||
set(isRecordBoardDeprecatedCardSelectedFamilyState(cardId), selected);
|
||||
set(actionBarOpenState, selected || activeCardIds.length > 0);
|
||||
|
||||
if (selected) {
|
||||
set(activeCardIdsState, [...activeCardIds, cardId]);
|
||||
} else {
|
||||
set(
|
||||
activeCardIdsState,
|
||||
activeCardIds.filter((id: string) => id !== cardId),
|
||||
);
|
||||
}
|
||||
},
|
||||
[activeCardIdsState],
|
||||
);
|
||||
|
||||
const unselectAllActiveCards = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
const activeCardIds = snapshot
|
||||
.getLoadable(activeCardIdsState)
|
||||
.getValue();
|
||||
|
||||
activeCardIds.forEach((cardId: string) => {
|
||||
set(isRecordBoardDeprecatedCardSelectedFamilyState(cardId), false);
|
||||
});
|
||||
|
||||
set(activeCardIdsState, []);
|
||||
set(actionBarOpenState, false);
|
||||
},
|
||||
[activeCardIdsState],
|
||||
);
|
||||
|
||||
return {
|
||||
setCardSelected,
|
||||
unselectAllActiveCards,
|
||||
};
|
||||
};
|
@ -1,148 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '@/object-record/record-board-deprecated/states/recordBoardCardIdsByColumnIdFamilyState';
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStepsState';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
import { companyProgressesFamilyState } from '../../../../companies/states/companyProgressesFamilyState';
|
||||
import {
|
||||
CompanyForBoard,
|
||||
CompanyProgressDict,
|
||||
} from '../../../../companies/types/CompanyProgress';
|
||||
|
||||
export const useUpdateCompanyBoardColumnsInternal = () => {
|
||||
const { boardColumnsState, savedBoardColumnsState } =
|
||||
useRecordBoardDeprecatedScopedStates();
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(
|
||||
pipelineSteps: PipelineStep[],
|
||||
opportunities: Opportunity[],
|
||||
companies: CompanyForBoard[],
|
||||
) => {
|
||||
const indexCompanyByIdReducer = (
|
||||
acc: { [key: string]: CompanyForBoard },
|
||||
company: CompanyForBoard,
|
||||
) => ({
|
||||
...acc,
|
||||
[company.id]: company,
|
||||
});
|
||||
|
||||
const companiesDict =
|
||||
companies.reduce(
|
||||
indexCompanyByIdReducer,
|
||||
{} as { [key: string]: CompanyForBoard },
|
||||
) ?? {};
|
||||
|
||||
const indexOpportunityByIdReducer = (
|
||||
acc: CompanyProgressDict,
|
||||
opportunity: Opportunity,
|
||||
) => {
|
||||
const company =
|
||||
opportunity.companyId && companiesDict[opportunity.companyId];
|
||||
|
||||
if (!company) return acc;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[opportunity.id]: {
|
||||
opportunity,
|
||||
company,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const companyBoardIndex = opportunities.reduce(
|
||||
indexOpportunityByIdReducer,
|
||||
{} as CompanyProgressDict,
|
||||
);
|
||||
|
||||
for (const [id, companyProgress] of Object.entries(companyBoardIndex)) {
|
||||
const currentCompanyProgress = snapshot
|
||||
.getLoadable(companyProgressesFamilyState(id))
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) {
|
||||
set(companyProgressesFamilyState(id), companyProgress);
|
||||
set(recordStoreFamilyState(id), companyProgress.opportunity);
|
||||
}
|
||||
}
|
||||
|
||||
const currentPipelineSteps = snapshot
|
||||
.getLoadable(currentPipelineStepsState)
|
||||
.getValue();
|
||||
|
||||
const currentBoardColumns = snapshot
|
||||
.getLoadable(boardColumnsState)
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(pipelineSteps, currentPipelineSteps)) {
|
||||
set(currentPipelineStepsState, pipelineSteps);
|
||||
}
|
||||
|
||||
const orderedPipelineSteps = [...pipelineSteps].sort((a, b) => {
|
||||
if (!a.position || !b.position) return 0;
|
||||
return a.position - b.position;
|
||||
});
|
||||
|
||||
const newBoardColumns: BoardColumnDefinition[] =
|
||||
orderedPipelineSteps?.map((pipelineStep) => {
|
||||
const colorValidationResult = themeColorSchema.safeParse(
|
||||
pipelineStep.color,
|
||||
);
|
||||
|
||||
if (!colorValidationResult.success) {
|
||||
logError(
|
||||
`Color ${pipelineStep.color} is not recognized in useUpdateCompanyBoard.`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: pipelineStep.id,
|
||||
title: pipelineStep.name,
|
||||
colorCode: colorValidationResult.success
|
||||
? colorValidationResult.data
|
||||
: undefined,
|
||||
position: pipelineStep.position ?? 0,
|
||||
};
|
||||
});
|
||||
if (
|
||||
currentBoardColumns.length === 0 &&
|
||||
!isDeeplyEqual(newBoardColumns, currentBoardColumns)
|
||||
) {
|
||||
set(boardColumnsState, newBoardColumns);
|
||||
set(savedBoardColumnsState, newBoardColumns);
|
||||
}
|
||||
for (const boardColumn of newBoardColumns) {
|
||||
const boardCardIds = opportunities
|
||||
.filter(
|
||||
(opportunityToFilter) =>
|
||||
opportunityToFilter.pipelineStepId === boardColumn.id,
|
||||
)
|
||||
.map((opportunity) => opportunity.id);
|
||||
|
||||
const currentBoardCardIds = snapshot
|
||||
.getLoadable(
|
||||
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(currentBoardCardIds, boardCardIds)) {
|
||||
set(
|
||||
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
|
||||
boardCardIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[boardColumnsState, savedBoardColumnsState],
|
||||
);
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useCreateOpportunity } from '@/object-record/record-board-deprecated/hooks/internal/useCreateOpportunity';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import { RecordBoardDeprecatedScopeInternalContext } from '@/object-record/record-board-deprecated/scopes/scope-internal-context/RecordBoardDeprecatedScopeInternalContext';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
|
||||
type useRecordBoardDeprecatedProps = {
|
||||
recordBoardScopeId?: string;
|
||||
};
|
||||
|
||||
export const useRecordBoardDeprecated = (
|
||||
props?: useRecordBoardDeprecatedProps,
|
||||
) => {
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
RecordBoardDeprecatedScopeInternalContext,
|
||||
props?.recordBoardScopeId,
|
||||
);
|
||||
|
||||
const { isBoardLoadedState, boardColumnsState, onFieldsChangeState } =
|
||||
useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: scopeId,
|
||||
});
|
||||
const setIsBoardLoaded = useSetRecoilState(isBoardLoadedState);
|
||||
|
||||
const setBoardColumns = useSetRecoilState(boardColumnsState);
|
||||
|
||||
const createOpportunity = useCreateOpportunity();
|
||||
|
||||
const setOnFieldsChange = useSetRecoilState(onFieldsChangeState);
|
||||
|
||||
return {
|
||||
scopeId,
|
||||
setIsBoardLoaded,
|
||||
setBoardColumns,
|
||||
createOpportunity,
|
||||
setOnFieldsChange,
|
||||
};
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
import { BOARD_OPTIONS_DROPDOWN_ID } from '@/object-record/record-board-deprecated/constants/BoardOptionsDropdownId';
|
||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
||||
|
||||
import { Dropdown } from '../../../../ui/layout/dropdown/components/Dropdown';
|
||||
import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope';
|
||||
|
||||
import { RecordBoardDeprecatedOptionsDropdownButton } from './RecordBoardDeprecatedOptionsDropdownButton';
|
||||
import {
|
||||
RecordBoardDeprecatedOptionsDropdownContent,
|
||||
RecordBoardDeprecatedOptionsDropdownContentProps,
|
||||
} from './RecordBoardDeprecatedOptionsDropdownContent';
|
||||
|
||||
type RecordBoardDeprecatedOptionsDropdownProps = Pick<
|
||||
RecordBoardDeprecatedOptionsDropdownContentProps,
|
||||
'onStageAdd' | 'recordBoardId'
|
||||
>;
|
||||
|
||||
export const RecordBoardDeprecatedOptionsDropdown = ({
|
||||
onStageAdd,
|
||||
recordBoardId,
|
||||
}: RecordBoardDeprecatedOptionsDropdownProps) => {
|
||||
const { setViewEditMode } = useViewBar();
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={BOARD_OPTIONS_DROPDOWN_ID}
|
||||
clickableComponent={<RecordBoardDeprecatedOptionsDropdownButton />}
|
||||
dropdownComponents={
|
||||
<RecordBoardDeprecatedOptionsDropdownContent
|
||||
onStageAdd={onStageAdd}
|
||||
recordBoardId={recordBoardId}
|
||||
/>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
|
||||
onClickOutside={() => setViewEditMode('none')}
|
||||
dropdownMenuWidth={170}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
import { BOARD_OPTIONS_DROPDOWN_ID } from '@/object-record/record-board-deprecated/constants/BoardOptionsDropdownId';
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
|
||||
export const RecordBoardDeprecatedOptionsDropdownButton = () => {
|
||||
const { isDropdownOpen, toggleDropdown } = useDropdown(
|
||||
BOARD_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledHeaderDropdownButton
|
||||
isUnfolded={isDropdownOpen}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Options
|
||||
</StyledHeaderDropdownButton>
|
||||
);
|
||||
};
|
@ -1,240 +0,0 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { BOARD_OPTIONS_DROPDOWN_ID } from '@/object-record/record-board-deprecated/constants/BoardOptionsDropdownId';
|
||||
import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-board-deprecated/hooks/internal/useRecordBoardDeprecatedScopedStates';
|
||||
import {
|
||||
IconBaselineDensitySmall,
|
||||
IconChevronLeft,
|
||||
IconLayoutKanban,
|
||||
IconPlus,
|
||||
IconTag,
|
||||
} from '@/ui/display/icon';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate';
|
||||
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
|
||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
|
||||
import { useRecordBoardDeprecatedCardFieldsInternal } from '../../hooks/internal/useRecordBoardDeprecatedCardFieldsInternal';
|
||||
import { BoardColumnDefinition } from '../../types/BoardColumnDefinition';
|
||||
import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope';
|
||||
|
||||
export type RecordBoardDeprecatedOptionsDropdownContentProps = {
|
||||
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
|
||||
recordBoardId: string;
|
||||
};
|
||||
|
||||
type BoardOptionsMenu = 'fields' | 'stage-creation' | 'stages';
|
||||
|
||||
export const RecordBoardDeprecatedOptionsDropdownContent = ({
|
||||
onStageAdd,
|
||||
recordBoardId,
|
||||
}: RecordBoardDeprecatedOptionsDropdownContentProps) => {
|
||||
const { setViewEditMode, handleViewNameSubmit } = useViewBar();
|
||||
const { viewEditModeState, currentViewSelector } = useViewScopedStates();
|
||||
|
||||
const viewEditMode = useRecoilValue(viewEditModeState);
|
||||
const currentView = useRecoilValue(currentViewSelector);
|
||||
|
||||
const stageInputRef = useRef<HTMLInputElement>(null);
|
||||
const viewEditInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [currentMenu, setCurrentMenu] = useState<
|
||||
BoardOptionsMenu | undefined
|
||||
>();
|
||||
|
||||
const {
|
||||
boardColumnsState,
|
||||
isCompactViewEnabledState,
|
||||
hiddenBoardCardFieldsSelector,
|
||||
visibleBoardCardFieldsSelector,
|
||||
} = useRecordBoardDeprecatedScopedStates({
|
||||
recordBoardScopeId: recordBoardId,
|
||||
});
|
||||
|
||||
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||
const [isCompactViewEnabled, setIsCompactViewEnabled] = useRecoilState(
|
||||
isCompactViewEnabledState,
|
||||
);
|
||||
|
||||
const hiddenBoardCardFields = useRecoilValue(hiddenBoardCardFieldsSelector);
|
||||
const hasHiddenFields = hiddenBoardCardFields.length > 0;
|
||||
|
||||
const visibleBoardCardFields = useRecoilValue(visibleBoardCardFieldsSelector);
|
||||
const hasVisibleFields = visibleBoardCardFields.length > 0;
|
||||
|
||||
const handleStageSubmit = () => {
|
||||
if (currentMenu !== 'stage-creation' || !stageInputRef?.current?.value)
|
||||
return;
|
||||
|
||||
const columnToCreate: BoardColumnDefinition = {
|
||||
id: v4(),
|
||||
colorCode: 'gray',
|
||||
position: boardColumns.length,
|
||||
title: stageInputRef.current.value,
|
||||
};
|
||||
|
||||
setBoardColumns((previousBoardColumns) => [
|
||||
...previousBoardColumns,
|
||||
columnToCreate,
|
||||
]);
|
||||
onStageAdd?.(columnToCreate);
|
||||
};
|
||||
|
||||
const resetMenu = () => setCurrentMenu(undefined);
|
||||
|
||||
const handleMenuNavigate = (menu: BoardOptionsMenu) => {
|
||||
handleViewNameSubmit();
|
||||
setCurrentMenu(menu);
|
||||
};
|
||||
|
||||
const { handleFieldVisibilityChange, handleFieldsReorder } =
|
||||
useRecordBoardDeprecatedCardFieldsInternal({
|
||||
recordBoardScopeId: recordBoardId,
|
||||
});
|
||||
|
||||
const { closeDropdown } = useDropdown(BOARD_OPTIONS_DROPDOWN_ID);
|
||||
|
||||
const handleReorderField: OnDragEndResponder = useCallback(
|
||||
(result) => {
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reorderedFields = moveArrayItem(visibleBoardCardFields, {
|
||||
fromIndex: result.source.index - 1,
|
||||
toIndex: result.destination.index - 1,
|
||||
});
|
||||
|
||||
handleFieldsReorder(reorderedFields);
|
||||
},
|
||||
[handleFieldsReorder, visibleBoardCardFields],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
setViewEditMode('none');
|
||||
closeDropdown();
|
||||
},
|
||||
BoardOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
const name = viewEditInputRef.current?.value;
|
||||
resetMenu();
|
||||
setViewEditMode('none');
|
||||
handleStageSubmit();
|
||||
handleViewNameSubmit(name);
|
||||
closeDropdown();
|
||||
},
|
||||
BoardOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!currentMenu && (
|
||||
<>
|
||||
<DropdownMenuInput
|
||||
ref={viewEditInputRef}
|
||||
autoFocus={viewEditMode !== 'none'}
|
||||
placeholder={
|
||||
viewEditMode === 'create'
|
||||
? 'New view'
|
||||
: viewEditMode === 'edit'
|
||||
? 'View name'
|
||||
: ''
|
||||
}
|
||||
defaultValue={viewEditMode === 'create' ? '' : currentView?.name}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemNavigate
|
||||
onClick={() => handleMenuNavigate('fields')}
|
||||
LeftIcon={IconTag}
|
||||
text="Fields"
|
||||
/>
|
||||
<MenuItemNavigate
|
||||
onClick={() => handleMenuNavigate('stages')}
|
||||
LeftIcon={IconLayoutKanban}
|
||||
text="Stages"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconBaselineDensitySmall}
|
||||
onToggleChange={setIsCompactViewEnabled}
|
||||
toggled={isCompactViewEnabled}
|
||||
text="Compact view"
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
{currentMenu === 'stages' && (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||
Stages
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => setCurrentMenu('stage-creation')}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add stage"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
{currentMenu === 'stage-creation' && (
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
placeholder="New stage"
|
||||
ref={stageInputRef}
|
||||
/>
|
||||
)}
|
||||
{currentMenu === 'fields' && (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||
Fields
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
{hasVisibleFields && (
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Visible"
|
||||
fields={visibleBoardCardFields}
|
||||
isDraggable
|
||||
onDragEnd={handleReorderField}
|
||||
onVisibilityChange={handleFieldVisibilityChange}
|
||||
/>
|
||||
)}
|
||||
{hasVisibleFields && hasHiddenFields && <DropdownMenuSeparator />}
|
||||
{hasHiddenFields && (
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Hidden"
|
||||
fields={hiddenBoardCardFields}
|
||||
isDraggable={false}
|
||||
onVisibilityChange={handleFieldVisibilityChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RecordBoardDeprecatedScopeInternalContext } from '@/object-record/record-board-deprecated/scopes/scope-internal-context/RecordBoardDeprecatedScopeInternalContext';
|
||||
|
||||
type RecordBoardDeprecatedScopeProps = {
|
||||
children: ReactNode;
|
||||
recordBoardScopeId: string;
|
||||
};
|
||||
|
||||
export const RecordBoardDeprecatedScope = ({
|
||||
children,
|
||||
recordBoardScopeId,
|
||||
}: RecordBoardDeprecatedScopeProps) => {
|
||||
return (
|
||||
<RecordBoardDeprecatedScopeInternalContext.Provider
|
||||
value={{
|
||||
scopeId: recordBoardScopeId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecordBoardDeprecatedScopeInternalContext.Provider>
|
||||
);
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
import { StateScopeMapKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/StateScopeMapKey';
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
|
||||
type RecordBoardDeprecatedScopeInternalContextProps = StateScopeMapKey;
|
||||
|
||||
export const RecordBoardDeprecatedScopeInternalContext =
|
||||
createScopeInternalContext<RecordBoardDeprecatedScopeInternalContextProps>();
|
@ -1,7 +0,0 @@
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const activeRecordBoardDeprecatedCardIdsScopedState =
|
||||
createStateScopeMap<string[]>({
|
||||
key: 'activeRecordBoardDeprecatedCardIdsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
|
||||
|
||||
export const availableRecordBoardDeprecatedCardFieldsScopedState =
|
||||
createStateScopeMap<BoardFieldDefinition<FieldMetadata>[]>({
|
||||
key: 'availableRecordBoardDeprecatedCardFieldsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,6 +0,0 @@
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const isCompactViewEnabledScopedState = createStateScopeMap<boolean>({
|
||||
key: 'isCompactViewEnabledScopedState',
|
||||
defaultValue: false,
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isRecordBoardDeprecatedCardInCompactViewFamilyState = atomFamily<
|
||||
boolean,
|
||||
string
|
||||
>({
|
||||
key: 'isRecordBoardDeprecatedCardInCompactViewFamilyState',
|
||||
default: true,
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isRecordBoardDeprecatedCardSelectedFamilyState = atomFamily<
|
||||
boolean,
|
||||
string
|
||||
>({
|
||||
key: 'isRecordBoardDeprecatedCardSelectedFamilyState',
|
||||
default: false,
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const isRecordBoardDeprecatedLoadedScopedState =
|
||||
createStateScopeMap<boolean>({
|
||||
key: 'isRecordBoardDeprecatedLoadedScopedState',
|
||||
defaultValue: false,
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const onFieldsChangeScopedState = createStateScopeMap<
|
||||
(fields: BoardFieldDefinition<FieldMetadata>[]) => void
|
||||
>({
|
||||
key: 'onFieldsChangeScopedState',
|
||||
defaultValue: () => {},
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const recordBoardCardIdsByColumnIdFamilyState = atomFamily<
|
||||
string[],
|
||||
string
|
||||
>({
|
||||
key: 'recordBoardCardIdsByColumnIdFamilyState',
|
||||
default: [],
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const recordBoardColumnsScopedState = createStateScopeMap<
|
||||
BoardColumnDefinition[]
|
||||
>({
|
||||
key: 'recordBoardColumnsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
|
||||
|
||||
export const recordBoardCardFieldsScopedState = createStateScopeMap<
|
||||
BoardFieldDefinition<FieldMetadata>[]
|
||||
>({
|
||||
key: 'recordBoardCardFieldsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const recordBoardFiltersScopedState = createStateScopeMap<Filter[]>({
|
||||
key: 'recordBoardFiltersScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
import { Sort } from '../../object-sort-dropdown/types/Sort';
|
||||
|
||||
export const recordBoardSortsScopedState = createStateScopeMap<Sort[]>({
|
||||
key: 'recordBoardSortsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const savedOpportunitiesScopedState = createStateScopeMap<Opportunity[]>(
|
||||
{
|
||||
key: 'savedOpportunitiesScopedState',
|
||||
defaultValue: [],
|
||||
},
|
||||
);
|
@ -1,9 +0,0 @@
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const savedPipelineStepsScopedState = createStateScopeMap<
|
||||
PipelineStep[]
|
||||
>({
|
||||
key: 'savedPipelineStepsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
|
||||
|
||||
export const savedRecordBoardDeprecatedCardFieldsScopedState =
|
||||
createStateScopeMap<BoardFieldDefinition<FieldMetadata>[]>({
|
||||
key: 'savedRecordBoardDeprecatedCardFieldsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||
|
||||
export const savedRecordBoardDeprecatedColumnsScopedState = createStateScopeMap<
|
||||
BoardColumnDefinition[]
|
||||
>({
|
||||
key: 'savedRecordBoardDeprecatedColumnsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { createStateScopeMap } from '@/ui/utilities/recoil-scope/utils/createStateScopeMap';
|
||||
|
||||
export const savedRecordsScopedState = createStateScopeMap<Company[]>({
|
||||
key: 'savedRecordsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
@ -1,24 +0,0 @@
|
||||
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
|
||||
|
||||
import { availableRecordBoardDeprecatedCardFieldsScopedState } from '../availableRecordBoardDeprecatedCardFieldsScopedState';
|
||||
import { recordBoardCardFieldsScopedState } from '../recordBoardDeprecatedCardFieldsScopedState';
|
||||
|
||||
export const hiddenRecordBoardDeprecatedCardFieldsScopedSelector =
|
||||
createSelectorReadOnlyScopeMap({
|
||||
key: 'hiddenRecordBoardDeprecatedCardFieldsScopedSelector',
|
||||
get:
|
||||
({ scopeId }) =>
|
||||
({ get }) => {
|
||||
const fields = get(recordBoardCardFieldsScopedState({ scopeId }));
|
||||
const fieldKeys = fields.map(({ fieldMetadataId }) => fieldMetadataId);
|
||||
|
||||
const otherAvailableKeys = get(
|
||||
availableRecordBoardDeprecatedCardFieldsScopedState({ scopeId }),
|
||||
).filter(({ fieldMetadataId }) => !fieldKeys.includes(fieldMetadataId));
|
||||
|
||||
return [
|
||||
...fields.filter((field) => !field.isVisible),
|
||||
...otherAvailableKeys,
|
||||
];
|
||||
},
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
import { BoardFieldDefinition } from '../../types/BoardFieldDefinition';
|
||||
import { recordBoardCardFieldsScopedState } from '../recordBoardDeprecatedCardFieldsScopedState';
|
||||
|
||||
export const recordBoardCardFieldsByKeyScopedSelector = selectorFamily({
|
||||
key: 'recordBoardCardFieldsByKeyScopedSelector',
|
||||
get:
|
||||
(scopeId: string) =>
|
||||
({ get }) =>
|
||||
get(recordBoardCardFieldsScopedState({ scopeId })).reduce<
|
||||
Record<string, BoardFieldDefinition<FieldMetadata>>
|
||||
>((result, field) => ({ ...result, [field.fieldMetadataId]: field }), {}),
|
||||
});
|
@ -1,32 +0,0 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
|
||||
import { amountFormat } from '~/utils/format/amountFormat';
|
||||
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '../recordBoardCardIdsByColumnIdFamilyState';
|
||||
|
||||
// TODO: this state should be computed during the synchronization web-hook and put in a generic
|
||||
// boardColumnTotalsFamilyState indexed by columnId.
|
||||
export const recordBoardColumnTotalsFamilySelector = selectorFamily({
|
||||
key: 'recordBoardColumnTotalsFamilySelector',
|
||||
get:
|
||||
(pipelineStepId: string) =>
|
||||
({ get }) => {
|
||||
const cardIds = get(
|
||||
recordBoardCardIdsByColumnIdFamilyState(pipelineStepId),
|
||||
);
|
||||
|
||||
const opportunities = cardIds.map((opportunityId: string) =>
|
||||
get(companyProgressesFamilyState(opportunityId)),
|
||||
);
|
||||
|
||||
const pipelineStepTotal: number =
|
||||
opportunities?.reduce(
|
||||
(acc: number, curr: any) =>
|
||||
acc + curr?.opportunity.amount.amountMicros / 1000000,
|
||||
0,
|
||||
) || 0;
|
||||
|
||||
return amountFormat(pipelineStepTotal);
|
||||
},
|
||||
});
|
@ -1,27 +0,0 @@
|
||||
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
|
||||
|
||||
import { isRecordBoardDeprecatedCardSelectedFamilyState } from '../isRecordBoardDeprecatedCardSelectedFamilyState';
|
||||
import { recordBoardCardIdsByColumnIdFamilyState } from '../recordBoardCardIdsByColumnIdFamilyState';
|
||||
import { recordBoardColumnsScopedState } from '../recordBoardColumnsScopedState';
|
||||
|
||||
export const selectedRecordBoardDeprecatedCardIdsScopedSelector =
|
||||
createSelectorReadOnlyScopeMap<string[]>({
|
||||
key: 'selectedRecordBoardDeprecatedCardIdsScopedSelector',
|
||||
get:
|
||||
({ scopeId }) =>
|
||||
({ get }) => {
|
||||
const boardColumns = get(recordBoardColumnsScopedState({ scopeId }));
|
||||
|
||||
const cardIds = boardColumns.flatMap((boardColumn) =>
|
||||
get(recordBoardCardIdsByColumnIdFamilyState(boardColumn.id)),
|
||||
);
|
||||
|
||||
const selectedCardIds = cardIds.filter(
|
||||
(cardId) =>
|
||||
get(isRecordBoardDeprecatedCardSelectedFamilyState(cardId)) ===
|
||||
true,
|
||||
);
|
||||
|
||||
return selectedCardIds;
|
||||
},
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
|
||||
|
||||
import { recordBoardCardFieldsScopedState } from '../recordBoardDeprecatedCardFieldsScopedState';
|
||||
|
||||
export const visibleRecordBoardDeprecatedCardFieldsScopedSelector =
|
||||
createSelectorReadOnlyScopeMap({
|
||||
key: 'visibleRecordBoardDeprecatedCardFieldsScopedSelector',
|
||||
get:
|
||||
({ scopeId }) =>
|
||||
({ get }) =>
|
||||
get(recordBoardCardFieldsScopedState({ scopeId }))
|
||||
.filter((field) => field.isVisible)
|
||||
.sort((a, b) => a.position - b.position),
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/MainColorNames';
|
||||
|
||||
export type BoardColumnDefinition = {
|
||||
id: string;
|
||||
title: string;
|
||||
position: number;
|
||||
colorCode?: ThemeColor;
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
export enum BoardColumnHotkeyScope {
|
||||
BoardColumn = 'board-column',
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
export type BoardFieldDefinition<T extends FieldMetadata> =
|
||||
FieldDefinition<T> & {
|
||||
position: number;
|
||||
isVisible?: boolean;
|
||||
viewFieldId?: string;
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
export type BoardOptions = {
|
||||
newCardComponent: React.ReactNode;
|
||||
CardComponent: ComponentType;
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
export enum BoardOptionsHotkeyScope {
|
||||
Dropdown = 'board-options-dropdown',
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export enum ColumnHotkeyScope {
|
||||
EditColumnName = 'EditColumnNameHotkeyScope',
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
import { activeRecordBoardDeprecatedCardIdsScopedState } from '@/object-record/record-board-deprecated/states/activeRecordBoardDeprecatedCardIdsScopedState';
|
||||
import { availableRecordBoardDeprecatedCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/availableRecordBoardDeprecatedCardFieldsScopedState';
|
||||
import { isCompactViewEnabledScopedState } from '@/object-record/record-board-deprecated/states/isCompactViewEnabledScopedState';
|
||||
import { isRecordBoardDeprecatedLoadedScopedState } from '@/object-record/record-board-deprecated/states/isRecordBoardDeprecatedLoadedScopedState';
|
||||
import { onFieldsChangeScopedState } from '@/object-record/record-board-deprecated/states/onFieldsChangeScopedState';
|
||||
import { recordBoardColumnsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardColumnsScopedState';
|
||||
import { recordBoardFiltersScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedFiltersScopedState';
|
||||
import { recordBoardSortsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedSortsScopedState';
|
||||
import { savedOpportunitiesScopedState } from '@/object-record/record-board-deprecated/states/savedOpportunitiesScopedState';
|
||||
import { savedPipelineStepsScopedState } from '@/object-record/record-board-deprecated/states/savedPipelineStepsScopedState';
|
||||
import { savedRecordBoardDeprecatedColumnsScopedState } from '@/object-record/record-board-deprecated/states/savedRecordBoardDeprecatedColumnsScopedState';
|
||||
import { savedRecordsScopedState } from '@/object-record/record-board-deprecated/states/savedRecordsScopedState';
|
||||
import { hiddenRecordBoardDeprecatedCardFieldsScopedSelector } from '@/object-record/record-board-deprecated/states/selectors/hiddenRecordBoardDeprecatedCardFieldsScopedSelector';
|
||||
import { recordBoardCardFieldsByKeyScopedSelector } from '@/object-record/record-board-deprecated/states/selectors/recordBoardDeprecatedCardFieldsByKeyScopedSelector';
|
||||
import { selectedRecordBoardDeprecatedCardIdsScopedSelector } from '@/object-record/record-board-deprecated/states/selectors/selectedRecordBoardDeprecatedCardIdsScopedSelector';
|
||||
import { visibleRecordBoardDeprecatedCardFieldsScopedSelector } from '@/object-record/record-board-deprecated/states/selectors/visibleRecordBoardDeprecatedCardFieldsScopedSelector';
|
||||
import { getScopedStateDeprecated } from '@/ui/utilities/recoil-scope/utils/getScopedStateDeprecated';
|
||||
|
||||
export const getRecordBoardDeprecatedScopedStates = ({
|
||||
recordBoardScopeId,
|
||||
}: {
|
||||
recordBoardScopeId: string;
|
||||
}) => {
|
||||
const activeCardIdsState = getScopedStateDeprecated(
|
||||
activeRecordBoardDeprecatedCardIdsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const availableBoardCardFieldsState = getScopedStateDeprecated(
|
||||
availableRecordBoardDeprecatedCardFieldsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const boardColumnsState = getScopedStateDeprecated(
|
||||
recordBoardColumnsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const isBoardLoadedState = getScopedStateDeprecated(
|
||||
isRecordBoardDeprecatedLoadedScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const isCompactViewEnabledState = getScopedStateDeprecated(
|
||||
isCompactViewEnabledScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const savedBoardColumnsState = getScopedStateDeprecated(
|
||||
savedRecordBoardDeprecatedColumnsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const boardFiltersState = getScopedStateDeprecated(
|
||||
recordBoardFiltersScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const boardSortsState = getScopedStateDeprecated(
|
||||
recordBoardSortsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const savedCompaniesState = getScopedStateDeprecated(
|
||||
savedRecordsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const savedOpportunitiesState = getScopedStateDeprecated(
|
||||
savedOpportunitiesScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const savedPipelineStepsState = getScopedStateDeprecated(
|
||||
savedPipelineStepsScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
const onFieldsChangeState = getScopedStateDeprecated(
|
||||
onFieldsChangeScopedState,
|
||||
recordBoardScopeId,
|
||||
);
|
||||
|
||||
// TODO: Family scoped selector
|
||||
const boardCardFieldsByKeySelector =
|
||||
recordBoardCardFieldsByKeyScopedSelector(recordBoardScopeId);
|
||||
|
||||
const hiddenBoardCardFieldsSelector =
|
||||
hiddenRecordBoardDeprecatedCardFieldsScopedSelector({
|
||||
scopeId: recordBoardScopeId,
|
||||
});
|
||||
|
||||
const selectedCardIdsSelector =
|
||||
selectedRecordBoardDeprecatedCardIdsScopedSelector({
|
||||
scopeId: recordBoardScopeId,
|
||||
});
|
||||
|
||||
const visibleBoardCardFieldsSelector =
|
||||
visibleRecordBoardDeprecatedCardFieldsScopedSelector({
|
||||
scopeId: recordBoardScopeId,
|
||||
});
|
||||
|
||||
return {
|
||||
activeCardIdsState,
|
||||
availableBoardCardFieldsState,
|
||||
boardColumnsState,
|
||||
isBoardLoadedState,
|
||||
isCompactViewEnabledState,
|
||||
savedBoardColumnsState,
|
||||
boardFiltersState,
|
||||
boardSortsState,
|
||||
onFieldsChangeState,
|
||||
boardCardFieldsByKeySelector,
|
||||
hiddenBoardCardFieldsSelector,
|
||||
selectedCardIdsSelector,
|
||||
visibleBoardCardFieldsSelector,
|
||||
savedCompaniesState,
|
||||
savedOpportunitiesState,
|
||||
savedPipelineStepsState,
|
||||
};
|
||||
};
|
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
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 { BoardColumnHotkeyScope } from '@/object-record/record-board-deprecated/types/BoardColumnHotkeyScope';
|
||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||
import { IconDotsVertical } from '@/ui/display/icon';
|
||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
@ -57,9 +57,12 @@ export const RecordBoardColumnHeader = () => {
|
||||
|
||||
const handleBoardColumnMenuOpen = () => {
|
||||
setIsBoardColumnMenuOpen(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
|
||||
goto: false,
|
||||
});
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
RecordBoardColumnHotkeyScope.BoardColumn,
|
||||
{
|
||||
goto: false,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleBoardColumnMenuClose = () => {
|
||||
|
@ -0,0 +1,3 @@
|
||||
export enum RecordBoardColumnHotkeyScope {
|
||||
BoardColumn = 'board-column',
|
||||
}
|
@ -2,7 +2,6 @@ import { useCallback, useMemo } from 'react';
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||
@ -12,6 +11,7 @@ import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefin
|
||||
import { useViewFields } from '@/views/hooks/internal/useViewFields';
|
||||
import { useViews } from '@/views/hooks/internal/useViews';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/views/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
@ -1,78 +0,0 @@
|
||||
import { OpportunityPicker } from '@/companies/components/OpportunityPicker';
|
||||
import { useCreateOpportunity } from '@/object-record/record-board-deprecated/hooks/internal/useCreateOpportunity';
|
||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { IconPlus } from '@/ui/display/icon/index';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
export const PipelineAddButton = () => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const { closeDropdown, toggleDropdown } = useDropdown(
|
||||
'add-pipeline-progress',
|
||||
);
|
||||
|
||||
const createOpportunity = useCreateOpportunity();
|
||||
|
||||
const handleCompanySelected = (
|
||||
selectedCompany: EntityForSelect | null,
|
||||
selectedPipelineStepId: string | null,
|
||||
) => {
|
||||
if (!selectedCompany?.id) {
|
||||
enqueueSnackBar(
|
||||
'There was a problem with the company selection, please retry.',
|
||||
{ variant: 'error' },
|
||||
);
|
||||
|
||||
logError('There was a problem with the company selection, please retry.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedPipelineStepId) {
|
||||
enqueueSnackBar(
|
||||
'There was a problem with the pipeline stage selection, please retry.',
|
||||
{ variant: 'error' },
|
||||
);
|
||||
|
||||
logError('There was a problem with the pipeline step selection.');
|
||||
return;
|
||||
}
|
||||
closeDropdown();
|
||||
createOpportunity(selectedCompany.id, selectedPipelineStepId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId="add-pipeline-progress"
|
||||
clickableComponent={
|
||||
<IconButton
|
||||
Icon={IconPlus}
|
||||
size="medium"
|
||||
dataTestId="add-company-progress-button"
|
||||
accent="default"
|
||||
variant="secondary"
|
||||
onClick={toggleDropdown}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<OpportunityPicker
|
||||
companyId={null}
|
||||
onSubmit={handleCompanySelected}
|
||||
onCancel={closeDropdown}
|
||||
/>
|
||||
}
|
||||
hotkey={{
|
||||
key: 'c',
|
||||
scope: PageHotkeyScope.OpportunitiesPage,
|
||||
}}
|
||||
dropdownHotkeyScope={{
|
||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,60 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const query = gql`
|
||||
mutation CreateOnePipelineStep($input: PipelineStepCreateInput!) {
|
||||
createPipelineStep(data: $input) {
|
||||
id
|
||||
name
|
||||
id
|
||||
createdAt
|
||||
opportunities {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
position
|
||||
color
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const deleteQuery = gql`
|
||||
mutation DeleteOnePipelineStep($idToDelete: ID!) {
|
||||
deletePipelineStep(id: $idToDelete) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const mockId = '8f3b2121-f194-4ba4-9fbf-2d5a37126806';
|
||||
export const currentPipelineId = 'f088c8c9-05d2-4276-b065-b863cc7d0b33';
|
||||
|
||||
const data = {
|
||||
color: 'yellow',
|
||||
id: mockId,
|
||||
position: 1,
|
||||
name: 'Column Title',
|
||||
};
|
||||
|
||||
export const variables = {
|
||||
input: data,
|
||||
};
|
||||
|
||||
export const deleteVariables = { idToDelete: 'columnId' };
|
||||
|
||||
export const responseData = {
|
||||
...data,
|
||||
createdAt: '',
|
||||
opportunities: {
|
||||
edges: [],
|
||||
},
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export const deleteResponseData = {
|
||||
id: 'columnId',
|
||||
};
|
@ -1,104 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
||||
|
||||
import {
|
||||
currentPipelineId,
|
||||
deleteQuery,
|
||||
deleteResponseData,
|
||||
deleteVariables,
|
||||
mockId,
|
||||
query,
|
||||
responseData,
|
||||
variables,
|
||||
} from '../__mocks__/usePipelineSteps';
|
||||
import { usePipelineSteps } from '../usePipelineSteps';
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query,
|
||||
variables,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
createPipelineStep: responseData,
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: deleteQuery,
|
||||
variables: deleteVariables,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
deletePipelineStep: deleteResponseData,
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() => mockId),
|
||||
}));
|
||||
|
||||
describe('usePipelineSteps', () => {
|
||||
it('should handlePipelineStepAdd successfully', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setCurrentPipeline = useSetRecoilState(currentPipelineState);
|
||||
setCurrentPipeline({ id: currentPipelineId });
|
||||
return usePipelineSteps();
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const boardColumn: BoardColumnDefinition = {
|
||||
id: mockId,
|
||||
title: 'Column Title',
|
||||
colorCode: 'yellow',
|
||||
position: 1,
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.handlePipelineStepAdd(boardColumn);
|
||||
expect(res).toEqual(responseData);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handlePipelineStepDelete successfully', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const setCurrentPipeline = useSetRecoilState(currentPipelineState);
|
||||
setCurrentPipeline({ id: currentPipelineId });
|
||||
return usePipelineSteps();
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const boardColumnId = 'columnId';
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.handlePipelineStepDelete(boardColumnId);
|
||||
expect(res).toEqual(deleteResponseData);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { BoardColumnDefinition } from '@/object-record/record-board-deprecated/types/BoardColumnDefinition';
|
||||
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
export const usePipelineSteps = () => {
|
||||
const { createOneRecord: createOnePipelineStep } =
|
||||
useCreateOneRecord<PipelineStep>({
|
||||
objectNameSingular: CoreObjectNameSingular.PipelineStep,
|
||||
});
|
||||
|
||||
const { deleteOneRecord: deleteOnePipelineStep } = useDeleteOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.PipelineStep,
|
||||
});
|
||||
|
||||
const handlePipelineStepAdd = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(boardColumn: BoardColumnDefinition) => {
|
||||
const currentPipeline = snapshot
|
||||
.getLoadable(currentPipelineState)
|
||||
.getValue();
|
||||
if (!currentPipeline?.id) return;
|
||||
|
||||
return createOnePipelineStep?.({
|
||||
color: boardColumn.colorCode ?? 'gray',
|
||||
id: boardColumn.id,
|
||||
position: boardColumn.position,
|
||||
name: boardColumn.title,
|
||||
});
|
||||
},
|
||||
[createOnePipelineStep],
|
||||
);
|
||||
|
||||
const handlePipelineStepDelete = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(boardColumnId: string) => {
|
||||
const currentPipeline = snapshot
|
||||
.getLoadable(currentPipelineState)
|
||||
.getValue();
|
||||
if (!currentPipeline?.id) return;
|
||||
|
||||
return deleteOnePipelineStep?.(boardColumnId);
|
||||
},
|
||||
[deleteOnePipelineStep],
|
||||
);
|
||||
|
||||
return { handlePipelineStepAdd, handlePipelineStepDelete };
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
import { undefined } from 'zod';
|
||||
|
||||
export const currentPipelineState = atom<any | undefined>({
|
||||
key: 'currentPipelineState',
|
||||
default: undefined,
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
export const currentPipelineStepsState = atom<PipelineStep[]>({
|
||||
key: 'currentPipelineStepsState',
|
||||
default: [],
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
import { Person } from '@/people/types/Person';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
export type Opportunity = {
|
||||
[key: string]: any;
|
||||
id: string;
|
||||
amount: {
|
||||
amountMicros: number;
|
||||
currencyCode: string;
|
||||
};
|
||||
closeDate: Date;
|
||||
probability: number;
|
||||
pipelineStepId: string;
|
||||
pipelineStep: PipelineStep;
|
||||
pointOfContactId: string;
|
||||
pointOfContact: Pick<Person, 'id' | 'name' | 'avatarUrl'>;
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
export type PipelineStep = {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
position: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
|
||||
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
|
||||
@ -10,5 +10,5 @@ export type ViewField = {
|
||||
size: number;
|
||||
definition:
|
||||
| ColumnDefinition<FieldMetadata>
|
||||
| BoardFieldDefinition<FieldMetadata>;
|
||||
| RecordBoardFieldDefinition<FieldMetadata>;
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
|
||||
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/views/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
|
||||
describe('mapBoardFieldDefinitionsToViewFields', () => {
|
||||
it('should map board field definitions to view fields', () => {
|
||||
const fieldDefinitions: BoardFieldDefinition<FieldMetadata>[] = [
|
||||
const fieldDefinitions: RecordBoardFieldDefinition<FieldMetadata>[] = [
|
||||
{
|
||||
fieldMetadataId: 'fieldMetadataId',
|
||||
label: 'label',
|
@ -1,6 +1,5 @@
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
|
||||
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
@ -8,7 +7,6 @@ import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||
import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions';
|
||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||
@ -192,84 +190,6 @@ describe('mapViewFieldsToColumnDefinitions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapViewFieldsToBoardFieldDefinitions', () => {
|
||||
it('should map visible ViewFields to BoardFieldDefinitions and filter out missing fieldMetadata', () => {
|
||||
const viewFields = [
|
||||
{
|
||||
id: 1,
|
||||
fieldMetadataId: 1,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldMetadataId: 2,
|
||||
position: 2,
|
||||
isVisible: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldMetadataId: 3,
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
},
|
||||
];
|
||||
|
||||
const fieldsMetadata = [
|
||||
{
|
||||
fieldMetadataId: 1,
|
||||
label: 'Field 1',
|
||||
metadata: {},
|
||||
position: 1,
|
||||
infoTooltipContent: 'Tooltip content for Field 1',
|
||||
iconName: 'icon-field-1',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 3,
|
||||
label: 'Field 3',
|
||||
metadata: {},
|
||||
position: 3,
|
||||
infoTooltipContent: 'Tooltip for Field 3',
|
||||
iconName: 'icon-field-3',
|
||||
type: 'number',
|
||||
},
|
||||
];
|
||||
|
||||
const expectedBoardFieldDefinitions = [
|
||||
{
|
||||
fieldMetadataId: 1,
|
||||
label: 'Field 1',
|
||||
metadata: {},
|
||||
position: 1,
|
||||
infoTooltipContent: 'Tooltip content for Field 1',
|
||||
iconName: 'icon-field-1',
|
||||
type: 'string',
|
||||
isVisible: true,
|
||||
viewFieldId: 1,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: 3,
|
||||
label: 'Field 3',
|
||||
metadata: {},
|
||||
position: 3,
|
||||
infoTooltipContent: 'Tooltip for Field 3',
|
||||
iconName: 'icon-field-3',
|
||||
type: 'number',
|
||||
isVisible: true,
|
||||
viewFieldId: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const actualBoardFieldDefinitions = mapViewFieldsToBoardFieldDefinitions(
|
||||
viewFields as unknown as ViewField[],
|
||||
fieldsMetadata as unknown as BoardFieldDefinition<FieldMetadata>[],
|
||||
);
|
||||
|
||||
expect(actualBoardFieldDefinitions).toEqual(expectedBoardFieldDefinitions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapColumnDefinitionsToViewFields', () => {
|
||||
it('should map ColumnDefinitions to ViewFields, setting defaults and using viewFieldId if present', () => {
|
||||
const columnDefinitions = [
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user