2426 timebox refactor board with the new scope architecture (#2789)

* scoped states: wip

* scoped states: wip

* wip

* wip

* create boardFiltersScopedState and boardSortsScopedState

* wip

* reorganize hooks

* update hooks

* wip

* wip

* fix options dropdown

* clean unused selectors

* fields are working

* fix filter an sort

* fix entity count

* rename hooks

* rename states

* clean unused context

* fix recoil scope bug

* objectNameSingular instead of objectNamePlural
This commit is contained in:
bosiraphael 2023-12-05 12:15:20 +01:00 committed by GitHub
parent 5c0ad30186
commit 95a1cfeec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 1204 additions and 765 deletions

View File

@ -5,17 +5,12 @@ import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { CompanyBoard } from '../board/components/CompanyBoard'; import { CompanyBoard } from '../board/components/CompanyBoard';
import { CompanyBoardRecoilScopeContext } from '../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
const meta: Meta<typeof CompanyBoard> = { const meta: Meta<typeof CompanyBoard> = {
title: 'Modules/Companies/Board', title: 'Modules/Companies/Board',
component: CompanyBoard, component: CompanyBoard,
decorators: [ decorators: [
(Story) => ( (Story) => <Story />,
<CompanyBoardRecoilScopeContext.Provider value="opportunities">
<Story />
</CompanyBoardRecoilScopeContext.Provider>
),
ComponentWithRouterDecorator, ComponentWithRouterDecorator,
SnackBarDecorator, SnackBarDecorator,
], ],

View File

@ -1,21 +1,17 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields'; import { mapBoardFieldDefinitionsToViewFields } from '@/companies/utils/mapBoardFieldDefinitionsToViewFields';
import { RecordBoardActionBar } from '@/ui/object/record-board/action-bar/components/RecordBoardActionBar';
import { BoardOptionsDropdownId } from '@/ui/object/record-board/components/constants/BoardOptionsDropdownId';
import { import {
RecordBoard, RecordBoard,
RecordBoardProps, RecordBoardProps,
} from '@/ui/object/record-board/components/RecordBoard'; } from '@/ui/object/record-board/components/RecordBoard';
import { RecordBoardContextMenu } from '@/ui/object/record-board/context-menu/components/RecordBoardContextMenu'; import { RecordBoardEffect } from '@/ui/object/record-board/components/RecordBoardEffect';
import { BoardOptionsDropdown } from '@/ui/object/record-board/options/components/BoardOptionsDropdown'; import { RecordBoardOptionsDropdown } from '@/ui/object/record-board/options/components/RecordBoardOptionsDropdown';
import { ViewBar } from '@/views/components/ViewBar'; import { ViewBar } from '@/views/components/ViewBar';
import { useViewFields } from '@/views/hooks/internal/useViewFields'; import { useViewFields } from '@/views/hooks/internal/useViewFields';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { HooksCompanyBoardEffect } from '../../components/HooksCompanyBoardEffect'; import { HooksCompanyBoardEffect } from '../../components/HooksCompanyBoardEffect';
import { CompanyBoardRecoilScopeContext } from '../../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
@ -36,34 +32,38 @@ export const CompanyBoard = ({
onEditColumnTitle, onEditColumnTitle,
}: CompanyBoardProps) => { }: CompanyBoardProps) => {
const viewBarId = 'company-board-view'; const viewBarId = 'company-board-view';
const recordBoardId = 'company-board';
const { persistViewFields } = useViewFields(viewBarId); const { persistViewFields } = useViewFields(viewBarId);
return ( return (
<StyledContainer> <StyledContainer>
<BoardContext.Provider <ViewBar
value={{ viewBarId={viewBarId}
BoardRecoilScopeContext: CompanyBoardRecoilScopeContext, optionsDropdownButton={
onFieldsChange: (fields) => { <RecordBoardOptionsDropdown recordBoardId={recordBoardId} />
persistViewFields(mapBoardFieldDefinitionsToViewFields(fields)); }
}, optionsDropdownScopeId={recordBoardId}
/>
<HooksCompanyBoardEffect
viewBarId={viewBarId}
recordBoardId={recordBoardId}
/>
<RecordBoardEffect
recordBoardId={recordBoardId}
onFieldsChange={(fields) => {
persistViewFields(mapBoardFieldDefinitionsToViewFields(fields));
}} }}
> />
<ViewBar
viewBarId={viewBarId} <RecordBoard
optionsDropdownButton={<BoardOptionsDropdown />} recordBoardId={recordBoardId}
optionsDropdownScopeId={BoardOptionsDropdownId} boardOptions={opportunitiesBoardOptions}
/> onColumnAdd={onColumnAdd}
<HooksCompanyBoardEffect viewBarId={viewBarId} /> onColumnDelete={onColumnDelete}
<RecordBoard onEditColumnTitle={onEditColumnTitle}
boardOptions={opportunitiesBoardOptions} />
onColumnAdd={onColumnAdd}
onColumnDelete={onColumnDelete}
onEditColumnTitle={onEditColumnTitle}
/>
<RecordBoardActionBar />
<RecordBoardContextMenu />
</BoardContext.Provider>
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -1,6 +1,6 @@
import { ReactNode, useContext } from 'react'; import { ReactNode, useContext } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { EntityChipVariant } from '@/ui/display/chip/components/EntityChip'; import { EntityChipVariant } from '@/ui/display/chip/components/EntityChip';
@ -9,15 +9,12 @@ import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox'; import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
import { FieldContext } from '@/ui/object/field/contexts/FieldContext'; import { FieldContext } from '@/ui/object/field/contexts/FieldContext';
import { BoardCardIdContext } from '@/ui/object/record-board/contexts/BoardCardIdContext'; import { BoardCardIdContext } from '@/ui/object/record-board/contexts/BoardCardIdContext';
import { useBoardContext } from '@/ui/object/record-board/hooks/useBoardContext'; import { useCurrentRecordBoardCardSelectedInternal } from '@/ui/object/record-board/hooks/internal/useCurrentRecordBoardCardSelectedInternal';
import { useCurrentCardSelected } from '@/ui/object/record-board/hooks/useCurrentCardSelected'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { isCardInCompactViewState } from '@/ui/object/record-board/states/isCardInCompactViewState'; import { isRecordBoardCardInCompactViewFamilyState } from '@/ui/object/record-board/states/isRecordBoardCardInCompactViewFamilyState';
import { isCompactViewEnabledState } from '@/ui/object/record-board/states/isCompactViewEnabledState';
import { visibleBoardCardFieldsScopedSelector } from '@/ui/object/record-board/states/selectors/visibleBoardCardFieldsScopedSelector';
import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell'; import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell';
import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope'; import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope';
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut'; import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState'; import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
@ -125,30 +122,28 @@ const StyledCompactIconContainer = styled.div`
`; `;
export const CompanyBoardCard = () => { export const CompanyBoardCard = () => {
const { BoardRecoilScopeContext } = useBoardContext();
const { isCurrentCardSelected, setCurrentCardSelected } = const { isCurrentCardSelected, setCurrentCardSelected } =
useCurrentCardSelected(); useCurrentRecordBoardCardSelectedInternal();
const boardCardId = useContext(BoardCardIdContext); const boardCardId = useContext(BoardCardIdContext);
const [companyProgress] = useRecoilState( const [companyProgress] = useRecoilState(
companyProgressesFamilyState(boardCardId ?? ''), companyProgressesFamilyState(boardCardId ?? ''),
); );
const { isCompactViewEnabledState, visibleBoardCardFieldsSelector } =
useRecordBoardScopedStates();
const [isCompactViewEnabled] = useRecoilState(isCompactViewEnabledState); const [isCompactViewEnabled] = useRecoilState(isCompactViewEnabledState);
const [isCardInCompactView, setIsCardInCompactView] = useRecoilState( const [isCardInCompactView, setIsCardInCompactView] = useRecoilState(
isCardInCompactViewState(boardCardId ?? ''), isRecordBoardCardInCompactViewFamilyState(boardCardId ?? ''),
); );
const showCompactView = isCompactViewEnabled && isCardInCompactView; const showCompactView = isCompactViewEnabled && isCardInCompactView;
const { opportunity, company } = companyProgress ?? {}; const { opportunity, company } = companyProgress ?? {};
const visibleBoardCardFields = useRecoilScopedValue( const visibleBoardCardFields = useRecoilValue(visibleBoardCardFieldsSelector);
visibleBoardCardFieldsScopedSelector,
BoardRecoilScopeContext,
);
const useUpdateOneRecordMutation: () => [(params: any) => any, any] = () => { const useUpdateOneRecordMutation: () => [(params: any) => any, any] = () => {
const { updateOneRecord: updateOneOpportunity } = useUpdateOneRecord({ const { updateOneRecord: updateOneOpportunity } = useUpdateOneRecord({

View File

@ -1,64 +1,36 @@
import { useCallback, useEffect, useState } from 'react'; import { useEffect } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Company } from '@/companies/types/Company';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { Opportunity } from '@/pipeline/types/Opportunity'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { PipelineStep } from '@/pipeline/types/PipelineStep'; import { availableRecordBoardCardFieldsScopedState } from '@/ui/object/record-board/states/availableRecordBoardCardFieldsScopedState';
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; import { recordBoardCardFieldsScopedState } from '@/ui/object/record-board/states/recordBoardCardFieldsScopedState';
import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { recordBoardFiltersScopedState } from '@/ui/object/record-board/states/recordBoardFiltersScopedState';
import { useBoardActionBarEntries } from '@/ui/object/record-board/hooks/useBoardActionBarEntries'; import { recordBoardSortsScopedState } from '@/ui/object/record-board/states/recordBoardSortsScopedState';
import { useBoardContext } from '@/ui/object/record-board/hooks/useBoardContext';
import { useBoardContextMenuEntries } from '@/ui/object/record-board/hooks/useBoardContextMenuEntries';
import { availableBoardCardFieldsScopedState } from '@/ui/object/record-board/states/availableBoardCardFieldsScopedState';
import { boardCardFieldsScopedState } from '@/ui/object/record-board/states/boardCardFieldsScopedState';
import { isBoardLoadedState } from '@/ui/object/record-board/states/isBoardLoadedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useSetRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useSetRecoilScopedStateV2'; import { useSetRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useSetRecoilScopedStateV2';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'; import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar'; import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions'; import { mapViewFieldsToBoardFieldDefinitions } from '@/views/utils/mapViewFieldsToBoardFieldDefinitions';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { isDefined } from '~/utils/isDefined';
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
type HooksCompanyBoardEffectProps = { type HooksCompanyBoardEffectProps = {
viewBarId: string; viewBarId: string;
recordBoardId: string;
}; };
export const HooksCompanyBoardEffect = ({ export const HooksCompanyBoardEffect = ({
viewBarId, viewBarId,
recordBoardId,
}: HooksCompanyBoardEffectProps) => { }: HooksCompanyBoardEffectProps) => {
const { const {
setAvailableFilterDefinitions, setAvailableFilterDefinitions,
setAvailableSortDefinitions, setAvailableSortDefinitions,
setAvailableFieldDefinitions, setAvailableFieldDefinitions,
setEntityCountInCurrentView,
setViewObjectMetadataId, setViewObjectMetadataId,
setViewType, setViewType,
} = useViewBar({ viewBarId: viewBarId }); } = useViewBar({ viewBarId });
const {
currentViewFieldsState,
currentViewFiltersState,
currentViewSortsState,
} = useViewScopedStates({ viewScopeId: viewBarId });
const [pipelineSteps, setPipelineSteps] = useState<PipelineStep[]>([]);
const [opportunities, setOpportunities] = useState<Opportunity[]>([]);
const [companies, setCompanies] = useState<Company[]>([]);
const currentViewFields = useRecoilValue(currentViewFieldsState);
const currentViewFilters = useRecoilValue(currentViewFiltersState);
const currentViewSorts = useRecoilValue(currentViewSortsState);
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
@ -67,88 +39,11 @@ export const HooksCompanyBoardEffect = ({
const { columnDefinitions, filterDefinitions, sortDefinitions } = const { columnDefinitions, filterDefinitions, sortDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem); useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
const { BoardRecoilScopeContext } = useBoardContext();
const [, setBoardCardFields] = useRecoilScopedState(
boardCardFieldsScopedState,
BoardRecoilScopeContext,
);
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
const updateCompanyBoard = useUpdateCompanyBoard();
const setAvailableBoardCardFields = useSetRecoilScopedStateV2( const setAvailableBoardCardFields = useSetRecoilScopedStateV2(
availableBoardCardFieldsScopedState, availableRecordBoardCardFieldsScopedState,
'company-board-view', 'company-board-view',
); );
useFindManyRecords({
objectNameSingular: 'pipelineStep',
filter: {},
onCompleted: useCallback(
(data: PaginatedRecordTypeResults<PipelineStep>) => {
setPipelineSteps(data.edges.map((edge) => edge.node));
},
[],
),
});
const filter = turnFiltersIntoWhereClause(
mapViewFiltersToFilters(currentViewFilters),
objectMetadataItem?.fields ?? [],
);
const orderBy = turnSortsIntoOrderBy(
mapViewSortsToSorts(currentViewSorts),
objectMetadataItem?.fields ?? [],
);
const { fetchMoreRecords: fetchMoreOpportunities } = useFindManyRecords({
skip: !pipelineSteps.length,
objectNameSingular: 'opportunity',
filter: filter,
orderBy: orderBy,
onCompleted: useCallback(
(data: PaginatedRecordTypeResults<Opportunity>) => {
const pipelineProgresses: Array<Opportunity> = data.edges.map(
(edge) => edge.node,
);
updateCompanyBoardCardIds(pipelineProgresses);
setOpportunities(pipelineProgresses);
setIsBoardLoaded(true);
},
[setIsBoardLoaded, updateCompanyBoardCardIds],
),
});
useEffect(() => {
if (isDefined(fetchMoreOpportunities)) {
fetchMoreOpportunities();
}
}, [fetchMoreOpportunities]);
const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({
skip: !opportunities.length,
objectNameSingular: 'company',
filter: {
id: {
in: opportunities.map((opportunity) => opportunity.companyId || ''),
},
},
onCompleted: useCallback((data: PaginatedRecordTypeResults<Company>) => {
setCompanies(data.edges.map((edge) => edge.node));
}, []),
});
useEffect(() => {
if (isDefined(fetchMoreCompanies)) {
fetchMoreCompanies();
}
}, [fetchMoreCompanies]);
useEffect(() => { useEffect(() => {
if (!objectMetadataItem) { if (!objectMetadataItem) {
return; return;
@ -182,26 +77,30 @@ export const HooksCompanyBoardEffect = ({
setViewType?.(ViewType.Kanban); setViewType?.(ViewType.Kanban);
}, [objectMetadataItem, setViewObjectMetadataId, setViewType]); }, [objectMetadataItem, setViewObjectMetadataId, setViewType]);
const { setActionBarEntries } = useBoardActionBarEntries(); const {
const { setContextMenuEntries } = useBoardContextMenuEntries(); currentViewFieldsState,
currentViewFiltersState,
currentViewSortsState,
} = useViewScopedStates({ viewScopeId: viewBarId });
useEffect(() => { const currentViewFields = useRecoilValue(currentViewFieldsState);
if (opportunities && companies) { const currentViewFilters = useRecoilValue(currentViewFiltersState);
setActionBarEntries(); const currentViewSorts = useRecoilValue(currentViewSortsState);
setContextMenuEntries();
updateCompanyBoard(pipelineSteps, opportunities, companies); //TODO: Modify to use scopeId
setEntityCountInCurrentView(opportunities.length); const setBoardCardFields = useSetRecoilScopedStateV2(
} recordBoardCardFieldsScopedState,
}, [ 'company-board',
companies, );
opportunities, const setBoardCardFilters = useSetRecoilScopedStateV2(
pipelineSteps, recordBoardFiltersScopedState,
setActionBarEntries, 'company-board',
setContextMenuEntries, );
setEntityCountInCurrentView,
updateCompanyBoard, const setBoardCardSorts = useSetRecoilScopedStateV2(
]); recordBoardSortsScopedState,
'company-board',
);
useEffect(() => { useEffect(() => {
if (currentViewFields) { if (currentViewFields) {
@ -214,5 +113,29 @@ export const HooksCompanyBoardEffect = ({
} }
}, [columnDefinitions, currentViewFields, setBoardCardFields]); }, [columnDefinitions, currentViewFields, setBoardCardFields]);
useEffect(() => {
if (currentViewFilters) {
setBoardCardFilters(currentViewFilters);
}
}, [currentViewFilters, setBoardCardFilters]);
useEffect(() => {
if (currentViewSorts) {
setBoardCardSorts(currentViewSorts);
}
}, [currentViewSorts, setBoardCardSorts]);
const { setEntityCountInCurrentView } = useViewBar({ viewBarId });
const { savedOpportunitiesState } = useRecordBoardScopedStates({
recordBoardScopeId: recordBoardId,
});
const savedOpportunities = useRecoilValue(savedOpportunitiesState);
useEffect(() => {
setEntityCountInCurrentView(savedOpportunities.length);
}, [savedOpportunities.length, setEntityCountInCurrentView]);
return <></>; return <></>;
}; };

View File

@ -1,7 +1,6 @@
import { useCallback, useContext, useState } from 'react'; import { useCallback, useContext, useState } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useCreateOpportunity } from '@/companies/hooks/useCreateOpportunity';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@ -11,6 +10,7 @@ import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picke
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { NewButton } from '@/ui/object/record-board/components/NewButton'; import { NewButton } from '@/ui/object/record-board/components/NewButton';
import { BoardColumnContext } from '@/ui/object/record-board/contexts/BoardColumnContext'; import { BoardColumnContext } from '@/ui/object/record-board/contexts/BoardColumnContext';
import { useCreateOpportunity } from '@/ui/object/record-board/hooks/internal/useCreateOpportunity';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
@ -21,7 +21,7 @@ export const NewOpportunityButton = () => {
const pipelineStepId = column?.columnDefinition.id || ''; const pipelineStepId = column?.columnDefinition.id || '';
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const { createOpportunity } = useCreateOpportunity(); const createOpportunity = useCreateOpportunity();
const { const {
goBackToPreviousHotkeyScope, goBackToPreviousHotkeyScope,

View File

@ -1,27 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/object/record-board/states/boardColumnsState';
export const useUpdateCompanyBoardCardIds = () =>
useRecoilCallback(
({ snapshot, set }) =>
(pipelineProgresses: Pick<Opportunity, 'pipelineStepId' | 'id'>[]) => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
for (const boardColumn of boardColumns) {
const boardCardIds = pipelineProgresses
.filter(
(pipelineProgressToFilter) =>
pipelineProgressToFilter.pipelineStepId === boardColumn.id,
)
.map((pipelineProgress) => pipelineProgress.id);
set(boardCardIdsByColumnIdFamilyState(boardColumn.id), boardCardIds);
}
},
[],
);

View File

@ -1,13 +0,0 @@
import { createContext } from 'react';
import { RecoilScopeContext } from '@/types/RecoilScopeContext';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition';
export const BoardContext = createContext<{
BoardRecoilScopeContext: RecoilScopeContext;
onFieldsChange: (fields: BoardFieldDefinition<FieldMetadata>[]) => void;
}>({
BoardRecoilScopeContext: createContext<string | null>(null),
onFieldsChange: () => {},
});

View File

@ -1,5 +0,0 @@
import { createContext } from 'react';
export const CompanyBoardRecoilScopeContext = createContext<string | null>(
null,
);

View File

@ -0,0 +1,118 @@
import { useCallback } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { Company } from '@/companies/types/Company';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { useUpdateCompanyBoardCardIdsInternal } from '@/ui/object/record-board/hooks/internal/useUpdateCompanyBoardCardIdsInternal';
import { useFindManyRecords } from './useFindManyRecords';
export const useObjectRecordBoard = () => {
const objectNameSingular = 'opportunity';
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIdsInternal();
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{
objectNameSingular,
},
);
const {
isBoardLoadedState,
boardFiltersState,
boardSortsState,
savedCompaniesState,
savedOpportunitiesState,
savedPipelineStepsState,
} = useRecordBoardScopedStates();
const setIsBoardLoaded = useSetRecoilState(isBoardLoadedState);
const boardFilters = useRecoilValue(boardFiltersState);
const boardSorts = useRecoilValue(boardSortsState);
const setSavedCompanies = useSetRecoilState(savedCompaniesState);
const [savedOpportunities, setSavedOpportunities] = useRecoilState(
savedOpportunitiesState,
);
const [savedPipelineSteps, setSavedPipelineSteps] = useRecoilState(
savedPipelineStepsState,
);
const filter = turnFiltersIntoWhereClause(
boardFilters,
foundObjectMetadataItem?.fields ?? [],
);
const orderBy = turnSortsIntoOrderBy(
boardSorts,
foundObjectMetadataItem?.fields ?? [],
);
useFindManyRecords({
objectNameSingular: 'pipelineStep',
filter: {},
onCompleted: useCallback(
(data: PaginatedRecordTypeResults<PipelineStep>) => {
setSavedPipelineSteps(data.edges.map((edge) => edge.node));
},
[setSavedPipelineSteps],
),
});
const {
records: opportunities,
loading,
fetchMoreRecords: fetchMoreOpportunities,
} = useFindManyRecords({
skip: !savedPipelineSteps.length,
objectNameSingular: 'opportunity',
filter: filter,
orderBy: orderBy,
onCompleted: useCallback(
(data: PaginatedRecordTypeResults<Opportunity>) => {
const pipelineProgresses: Array<Opportunity> = data.edges.map(
(edge) => edge.node,
);
updateCompanyBoardCardIds(pipelineProgresses);
setSavedOpportunities(pipelineProgresses);
setIsBoardLoaded(true);
},
[setIsBoardLoaded, setSavedOpportunities, updateCompanyBoardCardIds],
),
});
const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({
skip: !savedOpportunities.length,
objectNameSingular: 'company',
filter: {
id: {
in: savedOpportunities.map(
(opportunity) => opportunity.companyId || '',
),
},
},
onCompleted: useCallback(
(data: PaginatedRecordTypeResults<Company>) => {
setSavedCompanies(data.edges.map((edge) => edge.node));
},
[setSavedCompanies],
),
});
return {
opportunities,
loading,
fetchMoreOpportunities,
fetchMoreCompanies,
};
};

View File

@ -1,5 +1,4 @@
import { OpportunityPicker } from '@/companies/components/OpportunityPicker'; import { OpportunityPicker } from '@/companies/components/OpportunityPicker';
import { useCreateOpportunity } from '@/companies/hooks/useCreateOpportunity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { IconPlus } from '@/ui/display/icon/index'; import { IconPlus } from '@/ui/display/icon/index';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@ -9,6 +8,7 @@ import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/Rela
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
import { useCreateOpportunity } from '@/ui/object/record-board/hooks/internal/useCreateOpportunity';
import { logError } from '~/utils/logError'; import { logError } from '~/utils/logError';
export const PipelineAddButton = () => { export const PipelineAddButton = () => {
@ -18,7 +18,7 @@ export const PipelineAddButton = () => {
dropdownScopeId: 'add-pipeline-progress', dropdownScopeId: 'add-pipeline-progress',
}); });
const { createOpportunity } = useCreateOpportunity(); const createOpportunity = useCreateOpportunity();
const handleCompanySelected = ( const handleCompanySelected = (
selectedCompany: EntityForSelect | null, selectedCompany: EntityForSelect | null,

View File

@ -13,7 +13,10 @@ export const useEntitySelectSearch = () => {
); );
const [relationPickerSearchFilter, setRelationPickerSearchFilter] = const [relationPickerSearchFilter, setRelationPickerSearchFilter] =
useRecoilScopedState(relationPickerSearchFilterScopedState); useRecoilScopedState(
relationPickerSearchFilterScopedState,
RelationPickerRecoilScopeContext,
);
const debouncedSetSearchFilter = debounce( const debouncedSetSearchFilter = debounce(
setRelationPickerSearchFilter, setRelationPickerSearchFilter,

View File

@ -2,10 +2,10 @@ import React from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar'; import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { selectedCardIdsSelector } from '../../states/selectors/selectedCardIdsSelector';
export const RecordBoardActionBar = () => { export const RecordBoardActionBar = () => {
const { selectedCardIdsSelector } = useRecordBoardScopedStates();
const selectedCardIds = useRecoilValue(selectedCardIdsSelector); const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
return <ActionBar selectedIds={selectedCardIds}></ActionBar>; return <ActionBar selectedIds={selectedCardIds}></ActionBar>;
}; };

View File

@ -6,24 +6,26 @@ import { useRecoilValue } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { Opportunity } from '@/pipeline/types/Opportunity'; import { Opportunity } from '@/pipeline/types/Opportunity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { BoardColumnContext } from '@/ui/object/record-board/contexts/BoardColumnContext'; import { RecordBoardActionBar } from '@/ui/object/record-board/action-bar/components/RecordBoardActionBar';
import { useSetCardSelected } from '@/ui/object/record-board/hooks/useSetCardSelected'; import { RecordBoardInternalEffect } from '@/ui/object/record-board/components/RecordBoardInternalEffect';
import { useUpdateBoardCardIds } from '@/ui/object/record-board/hooks/useUpdateBoardCardIds'; import { RecordBoardContextMenu } from '@/ui/object/record-board/context-menu/components/RecordBoardContextMenu';
import { boardColumnsState } from '@/ui/object/record-board/states/boardColumnsState'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { useSetRecordBoardCardSelectedInternal } from '@/ui/object/record-board/hooks/internal/useSetRecordBoardCardSelectedInternal';
import { useUpdateRecordBoardCardIdsInternal } from '@/ui/object/record-board/hooks/internal/useUpdateRecordBoardCardIdsInternal';
import { RecordBoardScope } from '@/ui/object/record-board/scopes/RecordBoardScope';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { logError } from '~/utils/logError'; import { logError } from '~/utils/logError';
import { BoardColumnRecoilScopeContext } from '../states/recoil-scope-contexts/BoardColumnRecoilScopeContext';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition'; import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
import { BoardOptions } from '../types/BoardOptions'; import { BoardOptions } from '../types/BoardOptions';
import { RecordBoardColumn } from './RecordBoardColumn'; import { RecordBoardColumn } from './RecordBoardColumn';
export type RecordBoardProps = { export type RecordBoardProps = {
recordBoardId: string;
boardOptions: BoardOptions; boardOptions: BoardOptions;
onColumnAdd?: (boardColumn: BoardColumnDefinition) => void; onColumnAdd?: (boardColumn: BoardColumnDefinition) => void;
onColumnDelete?: (boardColumnId: string) => void; onColumnDelete?: (boardColumnId: string) => void;
@ -54,10 +56,16 @@ const StyledBoardHeader = styled.div`
`; `;
export const RecordBoard = ({ export const RecordBoard = ({
recordBoardId,
boardOptions, boardOptions,
onColumnDelete, onColumnDelete,
onEditColumnTitle, onEditColumnTitle,
}: RecordBoardProps) => { }: RecordBoardProps) => {
const recordBoardScopeId = recordBoardId;
const { boardColumnsState } = useRecordBoardScopedStates({
recordBoardScopeId,
});
const boardColumns = useRecoilValue(boardColumnsState); const boardColumns = useRecoilValue(boardColumnsState);
const { updateOneRecord: updateOneOpportunity } = const { updateOneRecord: updateOneOpportunity } =
@ -65,7 +73,8 @@ export const RecordBoard = ({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
}); });
const { unselectAllActiveCards, setCardSelected } = useSetCardSelected(); const { unselectAllActiveCards, setCardSelected } =
useSetRecordBoardCardSelectedInternal({ recordBoardScopeId });
const updatePipelineProgressStageInDB = useCallback( const updatePipelineProgressStageInDB = useCallback(
async (pipelineProgressId: string, pipelineStepId: string) => { async (pipelineProgressId: string, pipelineStepId: string) => {
@ -85,7 +94,9 @@ export const RecordBoard = ({
callback: unselectAllActiveCards, callback: unselectAllActiveCards,
}); });
const updateBoardCardIds = useUpdateBoardCardIds(); const updateBoardCardIds = useUpdateRecordBoardCardIdsInternal({
recordBoardScopeId,
});
const onDragEnd: OnDragEndResponder = useCallback( const onDragEnd: OnDragEndResponder = useCallback(
async (result) => { async (result) => {
@ -128,41 +139,35 @@ export const RecordBoard = ({
); );
return ( return (
<StyledWrapper> <RecordBoardScope recordBoardScopeId={recordBoardId}>
<StyledBoardHeader /> <RecordBoardContextMenu />
<ScrollWrapper> <RecordBoardActionBar />
<StyledBoard ref={boardRef}> <RecordBoardInternalEffect />
<DragDropContext onDragEnd={onDragEnd}>
{sortedBoardColumns.map((column) => ( <StyledWrapper>
<BoardColumnContext.Provider <StyledBoardHeader />
key={column.id} <ScrollWrapper>
value={{ <StyledBoard ref={boardRef}>
id: column.id, <DragDropContext onDragEnd={onDragEnd}>
columnDefinition: column, {sortedBoardColumns.map((column) => (
isFirstColumn: column.position === 0, <RecordBoardColumn
isLastColumn:
column.position === sortedBoardColumns.length - 1,
}}
>
<RecoilScope
CustomRecoilScopeContext={BoardColumnRecoilScopeContext}
key={column.id} key={column.id}
> recordBoardColumnId={column.id}
<RecordBoardColumn columnDefinition={column}
boardOptions={boardOptions} recordBoardColumnTotal={sortedBoardColumns.length}
onDelete={onColumnDelete} recordBoardOptions={boardOptions}
onTitleEdit={onEditColumnTitle} onDelete={onColumnDelete}
/> onTitleEdit={onEditColumnTitle}
</RecoilScope> />
</BoardColumnContext.Provider> ))}
))} </DragDropContext>
</DragDropContext> </StyledBoard>
</StyledBoard> </ScrollWrapper>
</ScrollWrapper> <DragSelect
<DragSelect dragSelectable={boardRef}
dragSelectable={boardRef} onDragSelectionChange={setCardSelected}
onDragSelectionChange={setCardSelected} />
/> </StyledWrapper>
</StyledWrapper> </RecordBoardScope>
); );
}; };

View File

@ -4,22 +4,23 @@ import { useSetRecoilState } from 'recoil';
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected'; import { useCurrentRecordBoardCardSelectedInternal } from '../hooks/internal/useCurrentRecordBoardCardSelectedInternal';
import { BoardOptions } from '../types/BoardOptions'; import { BoardOptions } from '../types/BoardOptions';
export const RecordBoardCard = ({ export const RecordBoardCard = ({
boardOptions, recordBoardOptions,
cardId, cardId,
index, index,
}: { }: {
boardOptions: BoardOptions; recordBoardOptions: BoardOptions;
cardId: string; cardId: string;
index: number; index: number;
}) => { }) => {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
const { setCurrentCardSelected } = useCurrentCardSelected(); const { setCurrentCardSelected } =
useCurrentRecordBoardCardSelectedInternal();
const handleContextMenu = (event: React.MouseEvent) => { const handleContextMenu = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
@ -45,7 +46,7 @@ export const RecordBoardCard = ({
data-select-disable data-select-disable
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
> >
{<boardOptions.CardComponent />} {<recordBoardOptions.CardComponent />}
</div> </div>
)} )}
</Draggable> </Draggable>

View File

@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react'; import React, { useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Draggable, Droppable, DroppableProvided } from '@hello-pangea/dnd'; import { Draggable, Droppable, DroppableProvided } from '@hello-pangea/dnd';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@ -8,12 +8,12 @@ import { Tag } from '@/ui/display/tag/components/Tag';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { RecordBoardCard } from '@/ui/object/record-board/components/RecordBoardCard'; import { RecordBoardCard } from '@/ui/object/record-board/components/RecordBoardCard';
import { BoardCardIdContext } from '@/ui/object/record-board/contexts/BoardCardIdContext'; import { BoardCardIdContext } from '@/ui/object/record-board/contexts/BoardCardIdContext';
import { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { BoardColumnContext } from '../contexts/BoardColumnContext'; import { BoardColumnContext } from '../contexts/BoardColumnContext';
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState'; import { recordBoardCardIdsByColumnIdFamilyState } from '../states/recordBoardCardIdsByColumnIdFamilyState';
import { boardColumnTotalsFamilySelector } from '../states/selectors/boardColumnTotalsFamilySelector'; import { recordBoardColumnTotalsFamilySelector } from '../states/selectors/recordBoardColumnTotalsFamilySelector';
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
import { BoardOptions } from '../types/BoardOptions'; import { BoardOptions } from '../types/BoardOptions';
@ -87,7 +87,10 @@ type BoardColumnCardsContainerProps = {
}; };
type RecordBoardColumnProps = { type RecordBoardColumnProps = {
boardOptions: BoardOptions; recordBoardColumnId: string;
columnDefinition: BoardColumnDefinition;
recordBoardOptions: BoardOptions;
recordBoardColumnTotal: number;
onDelete?: (columnId: string) => void; onDelete?: (columnId: string) => void;
onTitleEdit: (columnId: string, title: string, color: string) => void; onTitleEdit: (columnId: string, title: string, color: string) => void;
}; };
@ -109,12 +112,13 @@ const BoardColumnCardsContainer = ({
}; };
export const RecordBoardColumn = ({ export const RecordBoardColumn = ({
boardOptions, recordBoardColumnId,
columnDefinition,
recordBoardOptions,
recordBoardColumnTotal,
onDelete, onDelete,
onTitleEdit, onTitleEdit,
}: RecordBoardColumnProps) => { }: RecordBoardColumnProps) => {
const column = useContext(BoardColumnContext);
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false); const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
const [isHeaderHovered, setIsHeaderHovered] = useState(false); const [isHeaderHovered, setIsHeaderHovered] = useState(false);
@ -135,96 +139,110 @@ export const RecordBoardColumn = ({
setIsBoardColumnMenuOpen(false); setIsBoardColumnMenuOpen(false);
}; };
const boardColumnId = column?.id || '';
const boardColumnTotal = useRecoilValue( const boardColumnTotal = useRecoilValue(
boardColumnTotalsFamilySelector(boardColumnId), recordBoardColumnTotalsFamilySelector(recordBoardColumnId),
); );
const cardIds = useRecoilValue( const cardIds = useRecoilValue(
boardCardIdsByColumnIdFamilyState(boardColumnId), recordBoardCardIdsByColumnIdFamilyState(recordBoardColumnId),
); );
const handleTitleEdit = (title: string, color: string) => { const handleTitleEdit = (title: string, color: string) => {
onTitleEdit(boardColumnId, title, color); onTitleEdit(recordBoardColumnId, title, color);
}; };
if (!column) return <></>; const isFirstColumn = columnDefinition.position === 0;
const { isFirstColumn, columnDefinition } = column;
return ( return (
<Droppable droppableId={column.id}> <BoardColumnContext.Provider
{(droppableProvided) => ( value={{
<StyledColumn isFirstColumn={isFirstColumn}> id: recordBoardColumnId,
<StyledHeader columnDefinition: columnDefinition,
onMouseEnter={() => setIsHeaderHovered(true)} isFirstColumn: columnDefinition.position === 0,
onMouseLeave={() => setIsHeaderHovered(false)} isLastColumn: columnDefinition.position === recordBoardColumnTotal - 1,
> }}
<Tag >
onClick={handleBoardColumnMenuOpen} <Droppable droppableId={recordBoardColumnId}>
color={columnDefinition.colorCode ?? 'gray'} {(droppableProvided) => (
text={columnDefinition.title} <StyledColumn isFirstColumn={isFirstColumn}>
/> <StyledHeader
{!!boardColumnTotal && ( onMouseEnter={() => setIsHeaderHovered(true)}
<StyledAmount>${boardColumnTotal}</StyledAmount> onMouseLeave={() => setIsHeaderHovered(false)}
)} >
{!isHeaderHovered && ( <Tag
<StyledNumChildren>{cardIds.length}</StyledNumChildren> onClick={handleBoardColumnMenuOpen}
)} color={columnDefinition.colorCode ?? 'gray'}
{isHeaderHovered && ( text={columnDefinition.title}
<StyledHeaderActions> />
<LightIconButton {!!boardColumnTotal && (
accent="tertiary" <StyledAmount>${boardColumnTotal}</StyledAmount>
Icon={IconDotsVertical} )}
onClick={handleBoardColumnMenuOpen} {!isHeaderHovered && (
/> <StyledNumChildren>{cardIds.length}</StyledNumChildren>
{/* <LightIconButton )}
{isHeaderHovered && (
<StyledHeaderActions>
<LightIconButton
accent="tertiary"
Icon={IconDotsVertical}
onClick={handleBoardColumnMenuOpen}
/>
{/* <LightIconButton
accent="tertiary" accent="tertiary"
Icon={IconPlus} Icon={IconPlus}
onClick={() => {}} onClick={() => {}}
/> */} /> */}
</StyledHeaderActions> </StyledHeaderActions>
)}
</StyledHeader>
{isBoardColumnMenuOpen && (
<RecordBoardColumnDropdownMenu
onClose={handleBoardColumnMenuClose}
onDelete={onDelete}
onTitleEdit={handleTitleEdit}
stageId={boardColumnId}
/>
)}
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
{cardIds.map((cardId, index) => (
<BoardCardIdContext.Provider value={cardId} key={cardId}>
<RecordBoardCard
index={index}
cardId={cardId}
boardOptions={boardOptions}
/>
</BoardCardIdContext.Provider>
))}
<Draggable
draggableId={`new-${column.id}`}
index={cardIds.length}
isDragDisabled={true}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
>
<StyledNewCardButtonContainer>
<RecoilScope>{boardOptions.newCardComponent}</RecoilScope>
</StyledNewCardButtonContainer>
</div>
)} )}
</Draggable> </StyledHeader>
</BoardColumnCardsContainer> {isBoardColumnMenuOpen && (
</StyledColumn> <RecordBoardColumnDropdownMenu
)} onClose={handleBoardColumnMenuClose}
</Droppable> onDelete={onDelete}
onTitleEdit={handleTitleEdit}
stageId={recordBoardColumnId}
/>
)}
{isBoardColumnMenuOpen && (
<RecordBoardColumnDropdownMenu
onClose={handleBoardColumnMenuClose}
onDelete={onDelete}
onTitleEdit={handleTitleEdit}
stageId={recordBoardColumnId}
/>
)}
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
{cardIds.map((cardId, index) => (
<BoardCardIdContext.Provider value={cardId} key={cardId}>
<RecordBoardCard
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>
); );
}; };

View File

@ -12,7 +12,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { BoardColumnContext } from '../contexts/BoardColumnContext'; import { BoardColumnContext } from '../contexts/BoardColumnContext';
import { useBoardColumns } from '../hooks/useBoardColumns'; import { useBoardColumnsInternal } from '../hooks/internal/useRecordBoardColumnsInternal';
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
import { RecordBoardColumnEditTitleMenu } from './RecordBoardColumnEditTitleMenu'; import { RecordBoardColumnEditTitleMenu } from './RecordBoardColumnEditTitleMenu';
@ -43,7 +43,7 @@ export const RecordBoardColumnDropdownMenu = ({
const boardColumnMenuRef = useRef<HTMLDivElement>(null); const boardColumnMenuRef = useRef<HTMLDivElement>(null);
const { handleMoveBoardColumn } = useBoardColumns(); const { handleMoveBoardColumn } = useBoardColumnsInternal();
const { const {
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,

View File

@ -1,18 +1,16 @@
import { ChangeEvent, useCallback, useState } from 'react'; import { ChangeEvent, useCallback, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { IconTrash } from '@/ui/display/icon'; import { IconTrash } from '@/ui/display/icon';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor'; import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor';
import { useRecordBoard } from '@/ui/object/record-board/hooks/useRecordBoard';
import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors'; import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors';
import { textInputStyle } from '@/ui/theme/constants/effects'; import { textInputStyle } from '@/ui/theme/constants/effects';
import { debounce } from '~/utils/debounce'; import { debounce } from '~/utils/debounce';
import { boardColumnsState } from '../states/boardColumnsState';
const StyledEditTitleContainer = styled.div` const StyledEditTitleContainer = styled.div`
--vertical-padding: ${({ theme }) => theme.spacing(1)}; --vertical-padding: ${({ theme }) => theme.spacing(1)};
@ -58,7 +56,9 @@ export const RecordBoardColumnEditTitleMenu = ({
color, color,
}: RecordBoardColumnEditTitleMenuProps) => { }: RecordBoardColumnEditTitleMenuProps) => {
const [internalValue, setInternalValue] = useState(title); const [internalValue, setInternalValue] = useState(title);
const [, setBoardColumns] = useRecoilState(boardColumnsState);
const { setBoardColumns } = useRecordBoard();
const debouncedOnUpdateTitle = debounce( const debouncedOnUpdateTitle = debounce(
(newTitle) => onTitleEdit(newTitle, color), (newTitle) => onTitleEdit(newTitle, color),
200, 200,

View File

@ -0,0 +1,25 @@
import { useEffect } from 'react';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { useRecordBoard } from '@/ui/object/record-board/hooks/useRecordBoard';
import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition';
type RecordBoardEffectProps = {
recordBoardId: string;
onFieldsChange: (fields: BoardFieldDefinition<FieldMetadata>[]) => void;
};
export const RecordBoardEffect = ({
recordBoardId,
onFieldsChange,
}: RecordBoardEffectProps) => {
const { setOnFieldsChange } = useRecordBoard({
recordBoardScopeId: recordBoardId,
});
useEffect(() => {
setOnFieldsChange(() => onFieldsChange);
}, [onFieldsChange, setOnFieldsChange]);
return <></>;
};

View File

@ -0,0 +1,66 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { useObjectRecordBoard } from '@/object-record/hooks/useObjectRecordBoard';
import { useRecordBoardActionBarEntriesInternal } from '@/ui/object/record-board/hooks/internal/useRecordBoardActionBarEntriesInternal';
import { useRecordBoardContextMenuEntriesInternal } from '@/ui/object/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { useUpdateCompanyBoardColumnsInternal } from '@/ui/object/record-board/hooks/internal/useUpdateCompanyBoardColumnsInternal';
import { isDefined } from '~/utils/isDefined';
export type RecordBoardInternalEffectProps = {
onFieldsChange: (fields: any) => void;
};
export const RecordBoardInternalEffect = ({}) => {
const updateCompanyColumnsBoardInternal =
useUpdateCompanyBoardColumnsInternal();
const { setActionBarEntries } = useRecordBoardActionBarEntriesInternal();
const { setContextMenuEntries } = useRecordBoardContextMenuEntriesInternal();
const { fetchMoreOpportunities, fetchMoreCompanies } = useObjectRecordBoard();
useEffect(() => {
if (isDefined(fetchMoreOpportunities)) {
fetchMoreOpportunities();
}
}, [fetchMoreOpportunities]);
useEffect(() => {
if (isDefined(fetchMoreCompanies)) {
fetchMoreCompanies();
}
}, [fetchMoreCompanies]);
const {
savedPipelineStepsState,
savedOpportunitiesState,
savedCompaniesState,
} = useRecordBoardScopedStates();
const savedPipelineSteps = useRecoilValue(savedPipelineStepsState);
const savedOpportunities = useRecoilValue(savedOpportunitiesState);
const savedCompanies = useRecoilValue(savedCompaniesState);
useEffect(() => {
if (savedOpportunities && savedCompanies) {
setActionBarEntries();
setContextMenuEntries();
updateCompanyColumnsBoardInternal(
savedPipelineSteps,
savedOpportunities,
savedCompanies,
);
}
}, [
savedCompanies,
savedOpportunities,
savedPipelineSteps,
setActionBarEntries,
setContextMenuEntries,
updateCompanyColumnsBoardInternal,
]);
return <></>;
};

View File

@ -1,11 +1,10 @@
import React from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu'; import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { selectedCardIdsSelector } from '../../states/selectors/selectedCardIdsSelector';
export const RecordBoardContextMenu = () => { export const RecordBoardContextMenu = () => {
const { selectedCardIdsSelector } = useRecordBoardScopedStates();
const selectedCardIds = useRecoilValue(selectedCardIdsSelector); const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
return <ContextMenu selectedIds={selectedCardIds}></ContextMenu>; return <ContextMenu selectedIds={selectedCardIds}></ContextMenu>;
}; };

View File

@ -1,5 +0,0 @@
import { createContext } from 'react';
import { BoardOptions } from '@/ui/object/record-board/types/BoardOptions';
export const BoardOptionsContext = createContext<BoardOptions | null>(null);

View File

@ -3,7 +3,7 @@ import { v4 } from 'uuid';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { Opportunity } from '@/pipeline/types/Opportunity'; import { Opportunity } from '@/pipeline/types/Opportunity';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/boardCardIdsByColumnIdFamilyState'; import { recordBoardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/recordBoardCardIdsByColumnIdFamilyState';
export const useCreateOpportunity = () => { export const useCreateOpportunity = () => {
const { createOneRecord: createOneOpportunity } = const { createOneRecord: createOneOpportunity } =
@ -16,10 +16,10 @@ export const useCreateOpportunity = () => {
async (companyId: string, pipelineStepId: string) => { async (companyId: string, pipelineStepId: string) => {
const newUuid = v4(); const newUuid = v4();
set(boardCardIdsByColumnIdFamilyState(pipelineStepId), (oldValue) => [ set(
...oldValue, recordBoardCardIdsByColumnIdFamilyState(pipelineStepId),
newUuid, (oldValue) => [...oldValue, newUuid],
]); );
await createOneOpportunity?.({ await createOneOpportunity?.({
id: newUuid, id: newUuid,
@ -30,5 +30,5 @@ export const useCreateOpportunity = () => {
[createOneOpportunity], [createOneOpportunity],
); );
return { createOpportunity }; return createOpportunity;
}; };

View File

@ -2,18 +2,20 @@ import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
import { activeCardIdsState } from '@/ui/object/record-board/states/activeCardIdsState'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { BoardCardIdContext } from '../contexts/BoardCardIdContext'; import { BoardCardIdContext } from '../../contexts/BoardCardIdContext';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState'; import { isRecordBoardCardSelectedFamilyState } from '../../states/isRecordBoardCardSelectedFamilyState';
export const useCurrentCardSelected = () => { export const useCurrentRecordBoardCardSelectedInternal = () => {
const currentCardId = useContext(BoardCardIdContext); const currentCardId = useContext(BoardCardIdContext);
const isCurrentCardSelected = useRecoilValue( const isCurrentCardSelected = useRecoilValue(
isCardSelectedFamilyState(currentCardId ?? ''), isRecordBoardCardSelectedFamilyState(currentCardId ?? ''),
); );
const { activeCardIdsState } = useRecordBoardScopedStates();
const setActiveCardIds = useSetRecoilState(activeCardIdsState); const setActiveCardIds = useSetRecoilState(activeCardIdsState);
const setCurrentCardSelected = useRecoilCallback( const setCurrentCardSelected = useRecoilCallback(
@ -21,7 +23,7 @@ export const useCurrentCardSelected = () => {
(selected: boolean) => { (selected: boolean) => {
if (!currentCardId) return; if (!currentCardId) return;
set(isCardSelectedFamilyState(currentCardId), selected); set(isRecordBoardCardSelectedFamilyState(currentCardId), selected);
set(actionBarOpenState, selected); set(actionBarOpenState, selected);
if (selected) { if (selected) {

View File

@ -3,13 +3,12 @@ import { useRecoilCallback } from 'recoil';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { Opportunity } from '@/pipeline/types/Opportunity'; import { Opportunity } from '@/pipeline/types/Opportunity';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { selectedCardIdsSelector } from '../states/selectors/selectedCardIdsSelector'; import { useRemoveRecordBoardCardIdsInternal } from './useRemoveRecordBoardCardIdsInternal';
import { useRemoveCardIds } from './useRemoveCardIds'; export const useDeleteSelectedRecordBoardCardsInternal = () => {
const removeCardIds = useRemoveRecordBoardCardIdsInternal();
export const useDeleteSelectedBoardCards = () => {
const removeCardIds = useRemoveCardIds();
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const { deleteOneRecord: deleteOneOpportunity } = const { deleteOneRecord: deleteOneOpportunity } =
@ -17,6 +16,8 @@ export const useDeleteSelectedBoardCards = () => {
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
}); });
const { selectedCardIdsSelector } = useRecordBoardScopedStates();
const deleteSelectedBoardCards = useRecoilCallback( const deleteSelectedBoardCards = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
async () => { async () => {
@ -34,7 +35,12 @@ export const useDeleteSelectedBoardCards = () => {
apolloClient.cache.evict({ id: `Opportunity:${id}` }); apolloClient.cache.evict({ id: `Opportunity:${id}` });
}); });
}, },
[apolloClient.cache, removeCardIds, deleteOneOpportunity], [
selectedCardIdsSelector,
removeCardIds,
deleteOneOpportunity,
apolloClient.cache,
],
); );
return deleteSelectedBoardCards; return deleteSelectedBoardCards;

View File

@ -3,12 +3,12 @@ import { useSetRecoilState } from 'recoil';
import { IconTrash } from '@/ui/display/icon'; import { IconTrash } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { useDeleteSelectedBoardCards } from '@/ui/object/record-board/hooks/useDeleteSelectedBoardCards'; import { useDeleteSelectedRecordBoardCardsInternal } from '@/ui/object/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal';
export const useBoardActionBarEntries = () => { export const useRecordBoardActionBarEntriesInternal = () => {
const setActionBarEntriesRecoil = useSetRecoilState(actionBarEntriesState); const setActionBarEntriesRecoil = useSetRecoilState(actionBarEntriesState);
const deleteSelectedBoardCards = useDeleteSelectedBoardCards(); const deleteSelectedBoardCards = useDeleteSelectedRecordBoardCardsInternal();
const setActionBarEntries = useCallback(() => { const setActionBarEntries = useCallback(() => {
setActionBarEntriesRecoil([ setActionBarEntriesRecoil([

View File

@ -0,0 +1,73 @@
import { useCallback } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { RecordBoardScopeInternalContext } from '@/ui/object/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { onFieldsChangeScopedState } from '@/ui/object/record-board/states/onFieldsChangeScopedState';
import { recordBoardCardFieldsScopedState } from '@/ui/object/record-board/states/recordBoardCardFieldsScopedState';
import { savedRecordBoardCardFieldsScopedState } from '@/ui/object/record-board/states/savedRecordBoardCardFieldsScopedState';
import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
type useRecordBoardCardFieldsInternalProps = {
recordBoardScopeId?: string;
};
export const useRecordBoardCardFieldsInternal = (
props?: useRecordBoardCardFieldsInternalProps,
) => {
const scopeId = useAvailableScopeIdOrThrow(
RecordBoardScopeInternalContext,
props?.recordBoardScopeId,
);
const setBoardCardFields = useSetRecoilState(
recordBoardCardFieldsScopedState({ scopeId }),
);
const setSavedBoardCardFields = useSetRecoilState(
savedRecordBoardCardFieldsScopedState({ scopeId }),
);
const handleFieldVisibilityChange = (
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
) => {
setBoardCardFields((previousFields) =>
previousFields.map((previousField) =>
previousField.fieldMetadataId === field.fieldMetadataId
? { ...previousField, isVisible: !field.isVisible }
: previousField,
),
);
};
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 };
};

View File

@ -2,12 +2,13 @@ import { useRecoilState } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { PipelineStep } from '@/pipeline/types/PipelineStep'; import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns'; import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
import { boardColumnsState } from '../states/boardColumnsState'; import { BoardColumnDefinition } from '../../types/BoardColumnDefinition';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
export const useBoardColumns = () => { export const useBoardColumnsInternal = () => {
const { boardColumnsState } = useRecordBoardScopedStates();
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState); const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
const { handleColumnMove } = useMoveViewColumns(); const { handleColumnMove } = useMoveViewColumns();

View File

@ -3,14 +3,14 @@ import { useSetRecoilState } from 'recoil';
import { IconTrash } from '@/ui/display/icon'; import { IconTrash } from '@/ui/display/icon';
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
import { useDeleteSelectedBoardCards } from '@/ui/object/record-board/hooks/useDeleteSelectedBoardCards'; import { useDeleteSelectedRecordBoardCardsInternal } from '@/ui/object/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal';
export const useBoardContextMenuEntries = () => { export const useRecordBoardContextMenuEntriesInternal = () => {
const setContextMenuEntriesRecoil = useSetRecoilState( const setContextMenuEntriesRecoil = useSetRecoilState(
contextMenuEntriesState, contextMenuEntriesState,
); );
const deleteSelectedBoardCards = useDeleteSelectedBoardCards(); const deleteSelectedBoardCards = useDeleteSelectedRecordBoardCardsInternal();
const setContextMenuEntries = useCallback(() => { const setContextMenuEntries = useCallback(() => {
setContextMenuEntriesRecoil([ setContextMenuEntriesRecoil([

View File

@ -0,0 +1,59 @@
import { RecordBoardScopeInternalContext } from '@/ui/object/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { getRecordBoardScopedStates } from '@/ui/object/record-board/utils/getRecordBoardScopedStates';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
type useRecordBoardScopedStates = {
recordBoardScopeId?: string;
};
export const useRecordBoardScopedStates = (
args?: useRecordBoardScopedStates,
) => {
const { recordBoardScopeId } = args ?? {};
const scopeId = useAvailableScopeIdOrThrow(
RecordBoardScopeInternalContext,
recordBoardScopeId,
);
const {
activeCardIdsState,
availableBoardCardFieldsState,
boardColumnsState,
isBoardLoadedState,
isCompactViewEnabledState,
savedBoardColumnsState,
boardFiltersState,
boardSortsState,
onFieldsChangeState,
boardCardFieldsByKeySelector,
hiddenBoardCardFieldsSelector,
selectedCardIdsSelector,
visibleBoardCardFieldsSelector,
savedCompaniesState,
savedOpportunitiesState,
savedPipelineStepsState,
} = getRecordBoardScopedStates({
recordBoardScopeId: scopeId,
});
return {
scopeId,
activeCardIdsState,
availableBoardCardFieldsState,
boardColumnsState,
isBoardLoadedState,
isCompactViewEnabledState,
savedBoardColumnsState,
boardFiltersState,
boardSortsState,
onFieldsChangeState,
boardCardFieldsByKeySelector,
hiddenBoardCardFieldsSelector,
selectedCardIdsSelector,
visibleBoardCardFieldsSelector,
savedCompaniesState,
savedOpportunitiesState,
savedPipelineStepsState,
};
};

View File

@ -1,11 +1,14 @@
// 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 // 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 { useRecoilCallback } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { boardColumnsState } from '../states/boardColumnsState';
export const useRemoveCardIds = () => import { recordBoardCardIdsByColumnIdFamilyState } from '../../states/recordBoardCardIdsByColumnIdFamilyState';
useRecoilCallback(
export const useRemoveRecordBoardCardIdsInternal = () => {
const { boardColumnsState } = useRecordBoardScopedStates();
return useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
(cardIdToRemove: string[]) => { (cardIdToRemove: string[]) => {
const boardColumns = snapshot const boardColumns = snapshot
@ -14,13 +17,16 @@ export const useRemoveCardIds = () =>
boardColumns.forEach((boardColumn) => { boardColumns.forEach((boardColumn) => {
const columnCardIds = snapshot const columnCardIds = snapshot
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) .getLoadable(
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
)
.valueOrThrow(); .valueOrThrow();
set( set(
boardCardIdsByColumnIdFamilyState(boardColumn.id), recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
columnCardIds.filter((cardId) => !cardIdToRemove.includes(cardId)), columnCardIds.filter((cardId) => !cardIdToRemove.includes(cardId)),
); );
}); });
}, },
[], [boardColumnsState],
); );
};

View File

@ -1,17 +1,27 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { RecordBoardScopeInternalContext } from '@/ui/object/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { activeCardIdsState } from '../states/activeCardIdsState'; import { isRecordBoardCardSelectedFamilyState } from '../../states/isRecordBoardCardSelectedFamilyState';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export const useSetRecordBoardCardSelectedInternal = (props: any) => {
const scopeId = useAvailableScopeIdOrThrow(
RecordBoardScopeInternalContext,
props?.recordBoardScopeId,
);
const { activeCardIdsState } = useRecordBoardScopedStates({
recordBoardScopeId: scopeId,
});
export const useSetCardSelected = () => {
const setCardSelected = useRecoilCallback( const setCardSelected = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
(cardId: string, selected: boolean) => { (cardId: string, selected: boolean) => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents; const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;
set(isCardSelectedFamilyState(cardId), selected); set(isRecordBoardCardSelectedFamilyState(cardId), selected);
set(actionBarOpenState, selected || activeCardIds.length > 0); set(actionBarOpenState, selected || activeCardIds.length > 0);
if (selected) { if (selected) {
@ -23,6 +33,7 @@ export const useSetCardSelected = () => {
); );
} }
}, },
[activeCardIdsState],
); );
const unselectAllActiveCards = useRecoilCallback( const unselectAllActiveCards = useRecoilCallback(
@ -31,13 +42,13 @@ export const useSetCardSelected = () => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents; const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;
activeCardIds.forEach((cardId: string) => { activeCardIds.forEach((cardId: string) => {
set(isCardSelectedFamilyState(cardId), false); set(isRecordBoardCardSelectedFamilyState(cardId), false);
}); });
set(activeCardIdsState, []); set(activeCardIdsState, []);
set(actionBarOpenState, false); set(actionBarOpenState, false);
}, },
[], [activeCardIdsState],
); );
return { return {

View File

@ -0,0 +1,32 @@
import { useRecoilCallback } from 'recoil';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { recordBoardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/recordBoardCardIdsByColumnIdFamilyState';
export const useUpdateCompanyBoardCardIdsInternal = () => {
const { boardColumnsState } = useRecordBoardScopedStates();
return useRecoilCallback(
({ snapshot, set }) =>
(pipelineProgresses: Pick<Opportunity, 'pipelineStepId' | 'id'>[]) => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
for (const boardColumn of boardColumns) {
const boardCardIds = pipelineProgresses
.filter((pipelineProgressToFilter) => {
return pipelineProgressToFilter.pipelineStepId === boardColumn.id;
})
.map((pipelineProgress) => pipelineProgress.id);
set(
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
boardCardIds,
);
}
},
[boardColumnsState],
);
};

View File

@ -4,19 +4,24 @@ import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStep
import { Opportunity } from '@/pipeline/types/Opportunity'; import { Opportunity } from '@/pipeline/types/Opportunity';
import { PipelineStep } from '@/pipeline/types/PipelineStep'; import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState'; import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/boardCardIdsByColumnIdFamilyState'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { boardColumnsState } from '@/ui/object/record-board/states/boardColumnsState'; import { recordBoardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/recordBoardCardIdsByColumnIdFamilyState';
import { savedBoardColumnsState } from '@/ui/object/record-board/states/savedBoardColumnsState';
import { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition'; import { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition';
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema'; import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { logError } from '~/utils/logError'; import { logError } from '~/utils/logError';
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState'; import { companyProgressesFamilyState } from '../../../../../companies/states/companyProgressesFamilyState';
import { CompanyForBoard, CompanyProgressDict } from '../types/CompanyProgress'; import {
CompanyForBoard,
CompanyProgressDict,
} from '../../../../../companies/types/CompanyProgress';
export const useUpdateCompanyBoard = () => export const useUpdateCompanyBoardColumnsInternal = () => {
useRecoilCallback( const { boardColumnsState, savedBoardColumnsState } =
useRecordBoardScopedStates();
return useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
( (
pipelineSteps: PipelineStep[], pipelineSteps: PipelineStep[],
@ -125,16 +130,19 @@ export const useUpdateCompanyBoard = () =>
.map((opportunity) => opportunity.id); .map((opportunity) => opportunity.id);
const currentBoardCardIds = snapshot const currentBoardCardIds = snapshot
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) .getLoadable(
recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
)
.valueOrThrow(); .valueOrThrow();
if (!isDeeplyEqual(currentBoardCardIds, boardCardIds)) { if (!isDeeplyEqual(currentBoardCardIds, boardCardIds)) {
set( set(
boardCardIdsByColumnIdFamilyState(boardColumn.id), recordBoardCardIdsByColumnIdFamilyState(boardColumn.id),
boardCardIds, boardCardIds,
); );
} }
} }
}, },
[], [boardColumnsState, savedBoardColumnsState],
); );
};

View File

@ -1,12 +1,30 @@
import { DropResult } 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 { DropResult } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState'; import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { boardColumnsState } from '../states/boardColumnsState'; import { RecordBoardScopeInternalContext } from '@/ui/object/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
export const useUpdateBoardCardIds = () => import { recordBoardCardIdsByColumnIdFamilyState } from '../../states/recordBoardCardIdsByColumnIdFamilyState';
useRecoilCallback( import { BoardColumnDefinition } from '../../types/BoardColumnDefinition';
type useUpdateRecordBoardCardIdsInternalProps = {
recordBoardScopeId?: string;
};
export const useUpdateRecordBoardCardIdsInternal = (
props: useUpdateRecordBoardCardIdsInternalProps,
) => {
const scopeId = useAvailableScopeIdOrThrow(
RecordBoardScopeInternalContext,
props?.recordBoardScopeId,
);
const { boardColumnsState } = useRecordBoardScopedStates({
recordBoardScopeId: scopeId,
});
return useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
(result: DropResult) => { (result: DropResult) => {
const currentBoardColumns = snapshot const currentBoardColumns = snapshot
@ -37,14 +55,16 @@ export const useUpdateBoardCardIds = () =>
const sourceCardIds = [ const sourceCardIds = [
...snapshot ...snapshot
.getLoadable(boardCardIdsByColumnIdFamilyState(sourceColumn.id)) .getLoadable(
recordBoardCardIdsByColumnIdFamilyState(sourceColumn.id),
)
.valueOrThrow(), .valueOrThrow(),
]; ];
const destinationCardIds = [ const destinationCardIds = [
...snapshot ...snapshot
.getLoadable( .getLoadable(
boardCardIdsByColumnIdFamilyState(destinationColumn.id), recordBoardCardIdsByColumnIdFamilyState(destinationColumn.id),
) )
.valueOrThrow(), .valueOrThrow(),
]; ];
@ -60,7 +80,7 @@ export const useUpdateBoardCardIds = () =>
sourceCardIds.splice(destinationIndex, 0, deletedCardId); sourceCardIds.splice(destinationIndex, 0, deletedCardId);
set( set(
boardCardIdsByColumnIdFamilyState(sourceColumn.id), recordBoardCardIdsByColumnIdFamilyState(sourceColumn.id),
sourceCardIds, sourceCardIds,
); );
} else { } else {
@ -69,17 +89,18 @@ export const useUpdateBoardCardIds = () =>
destinationCardIds.splice(destinationIndex, 0, removedCardId); destinationCardIds.splice(destinationIndex, 0, removedCardId);
set( set(
boardCardIdsByColumnIdFamilyState(sourceColumn.id), recordBoardCardIdsByColumnIdFamilyState(sourceColumn.id),
sourceCardIds, sourceCardIds,
); );
set( set(
boardCardIdsByColumnIdFamilyState(destinationColumn.id), recordBoardCardIdsByColumnIdFamilyState(destinationColumn.id),
destinationCardIds, destinationCardIds,
); );
} }
return newBoardColumns; return newBoardColumns;
}, },
[], [boardColumnsState],
); );
};

View File

@ -1,61 +0,0 @@
import { useCallback } from 'react';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { savedBoardCardFieldsFamilyState } from '@/ui/object/record-board/states/savedBoardCardFieldsFamilyState';
import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition';
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { useBoardContext } from './useBoardContext';
export const useBoardCardFields = () => {
const { BoardRecoilScopeContext, onFieldsChange } = useBoardContext();
const [, setBoardCardFields] = useRecoilScopedState(
boardCardFieldsScopedState,
BoardRecoilScopeContext,
);
const [, setSavedBoardCardFields] = useRecoilScopedState(
savedBoardCardFieldsFamilyState,
BoardRecoilScopeContext,
);
const handleFieldVisibilityChange = (
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
) => {
setBoardCardFields((previousFields) =>
previousFields.map((previousField) =>
previousField.fieldMetadataId === field.fieldMetadataId
? { ...previousField, isVisible: !field.isVisible }
: previousField,
),
);
};
const handleFieldsChange = useCallback(
async (fields: BoardFieldDefinition<FieldMetadata>[]) => {
setSavedBoardCardFields(fields);
setBoardCardFields(fields);
await onFieldsChange?.(fields);
},
[setBoardCardFields, setSavedBoardCardFields, onFieldsChange],
);
const handleFieldsReorder = useCallback(
async (fields: BoardFieldDefinition<FieldMetadata>[]) => {
const updatedFields = fields.map((column, index) => ({
...column,
position: index,
}));
await handleFieldsChange(updatedFields);
},
[handleFieldsChange],
);
return { handleFieldVisibilityChange, handleFieldsReorder };
};

View File

@ -1,7 +0,0 @@
import { useContext } from 'react';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
export const useBoardContext = () => {
return useContext(BoardContext);
};

View File

@ -0,0 +1,37 @@
import { useSetRecoilState } from 'recoil';
import { useCreateOpportunity } from '@/ui/object/record-board/hooks/internal/useCreateOpportunity';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { RecordBoardScopeInternalContext } from '@/ui/object/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
type useRecordBoardProps = {
recordBoardScopeId?: string;
};
export const useRecordBoard = (props?: useRecordBoardProps) => {
const scopeId = useAvailableScopeIdOrThrow(
RecordBoardScopeInternalContext,
props?.recordBoardScopeId,
);
const { isBoardLoadedState, boardColumnsState, onFieldsChangeState } =
useRecordBoardScopedStates({
recordBoardScopeId: scopeId,
});
const setIsBoardLoaded = useSetRecoilState(isBoardLoadedState);
const setBoardColumns = useSetRecoilState(boardColumnsState);
const createOpportunity = useCreateOpportunity();
const setOnFieldsChange = useSetRecoilState(onFieldsChangeState);
return {
scopeId,
setIsBoardLoaded,
setBoardColumns,
createOpportunity,
setOnFieldsChange,
};
};

View File

@ -5,28 +5,32 @@ import { DropdownScope } from '../../../../layout/dropdown/scopes/DropdownScope'
import { BoardOptionsDropdownId } from '../../components/constants/BoardOptionsDropdownId'; import { BoardOptionsDropdownId } from '../../components/constants/BoardOptionsDropdownId';
import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope'; import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope';
import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton'; import { RecordBoardOptionsDropdownButton } from './RecordBoardOptionsDropdownButton';
import { import {
BoardOptionsDropdownContent, RecordBoardOptionsDropdownContent,
BoardOptionsDropdownContentProps, RecordBoardOptionsDropdownContentProps,
} from './BoardOptionsDropdownContent'; } from './RecordBoardOptionsDropdownContent';
type BoardOptionsDropdownProps = Pick< type RecordBoardOptionsDropdownProps = Pick<
BoardOptionsDropdownContentProps, RecordBoardOptionsDropdownContentProps,
'onStageAdd' 'onStageAdd' | 'recordBoardId'
>; >;
export const BoardOptionsDropdown = ({ export const RecordBoardOptionsDropdown = ({
onStageAdd, onStageAdd,
}: BoardOptionsDropdownProps) => { recordBoardId,
}: RecordBoardOptionsDropdownProps) => {
const { setViewEditMode } = useViewBar(); const { setViewEditMode } = useViewBar();
return ( return (
<DropdownScope dropdownScopeId={BoardOptionsDropdownId}> <DropdownScope dropdownScopeId={BoardOptionsDropdownId}>
<Dropdown <Dropdown
clickableComponent={<BoardOptionsDropdownButton />} clickableComponent={<RecordBoardOptionsDropdownButton />}
dropdownComponents={ dropdownComponents={
<BoardOptionsDropdownContent onStageAdd={onStageAdd} /> <RecordBoardOptionsDropdownContent
onStageAdd={onStageAdd}
recordBoardId={recordBoardId}
/>
} }
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }} dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onClickOutside={() => setViewEditMode('none')} onClickOutside={() => setViewEditMode('none')}

View File

@ -1,7 +1,7 @@
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
export const BoardOptionsDropdownButton = () => { export const RecordBoardOptionsDropdownButton = () => {
const { isDropdownOpen, toggleDropdown } = useDropdown(); const { isDropdownOpen, toggleDropdown } = useDropdown();
const handleClick = () => { const handleClick = () => {

View File

@ -1,10 +1,9 @@
import { useCallback, useContext, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import { OnDragEndResponder } from '@hello-pangea/dnd'; import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { import {
IconBaselineDensitySmall, IconBaselineDensitySmall,
IconChevronLeft, IconChevronLeft,
@ -21,23 +20,20 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate'; import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate';
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle'; import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
import { ThemeColor } from '@/ui/theme/constants/colors'; import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection'; import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates'; import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar'; import { useViewBar } from '@/views/hooks/useViewBar';
import { useBoardCardFields } from '../../hooks/useBoardCardFields'; import { useRecordBoardCardFieldsInternal } from '../../hooks/internal/useRecordBoardCardFieldsInternal';
import { boardColumnsState } from '../../states/boardColumnsState';
import { isCompactViewEnabledState } from '../../states/isCompactViewEnabledState';
import { hiddenBoardCardFieldsScopedSelector } from '../../states/selectors/hiddenBoardCardFieldsScopedSelector';
import { visibleBoardCardFieldsScopedSelector } from '../../states/selectors/visibleBoardCardFieldsScopedSelector';
import { BoardColumnDefinition } from '../../types/BoardColumnDefinition'; import { BoardColumnDefinition } from '../../types/BoardColumnDefinition';
import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope'; import { BoardOptionsHotkeyScope } from '../../types/BoardOptionsHotkeyScope';
export type BoardOptionsDropdownContentProps = { export type RecordBoardOptionsDropdownContentProps = {
onStageAdd?: (boardColumn: BoardColumnDefinition) => void; onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
recordBoardId: string;
}; };
type BoardOptionsMenu = 'fields' | 'stage-creation' | 'stages'; type BoardOptionsMenu = 'fields' | 'stage-creation' | 'stages';
@ -49,12 +45,12 @@ type ColumnForCreate = {
title: string; title: string;
}; };
export const BoardOptionsDropdownContent = ({ export const RecordBoardOptionsDropdownContent = ({
onStageAdd, onStageAdd,
}: BoardOptionsDropdownContentProps) => { recordBoardId,
}: RecordBoardOptionsDropdownContentProps) => {
const { setViewEditMode, handleViewNameSubmit } = useViewBar(); const { setViewEditMode, handleViewNameSubmit } = useViewBar();
const { viewEditModeState, currentViewSelector } = useViewScopedStates(); const { viewEditModeState, currentViewSelector } = useViewScopedStates();
const { BoardRecoilScopeContext } = useContext(BoardContext);
const viewEditMode = useRecoilValue(viewEditModeState); const viewEditMode = useRecoilValue(viewEditModeState);
const currentView = useRecoilValue(currentViewSelector); const currentView = useRecoilValue(currentViewSelector);
@ -66,21 +62,22 @@ export const BoardOptionsDropdownContent = ({
BoardOptionsMenu | undefined BoardOptionsMenu | undefined
>(); >();
const {
boardColumnsState,
isCompactViewEnabledState,
hiddenBoardCardFieldsSelector,
visibleBoardCardFieldsSelector,
} = useRecordBoardScopedStates({ recordBoardScopeId: recordBoardId });
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState); const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
const [isCompactViewEnabled, setIsCompactViewEnabled] = useRecoilState( const [isCompactViewEnabled, setIsCompactViewEnabled] = useRecoilState(
isCompactViewEnabledState, isCompactViewEnabledState,
); );
const hiddenBoardCardFields = useRecoilScopedValue( const hiddenBoardCardFields = useRecoilValue(hiddenBoardCardFieldsSelector);
hiddenBoardCardFieldsScopedSelector,
BoardRecoilScopeContext,
);
const hasHiddenFields = hiddenBoardCardFields.length > 0; const hasHiddenFields = hiddenBoardCardFields.length > 0;
const visibleBoardCardFields = useRecoilScopedValue(
visibleBoardCardFieldsScopedSelector, const visibleBoardCardFields = useRecoilValue(visibleBoardCardFieldsSelector);
BoardRecoilScopeContext,
);
const hasVisibleFields = visibleBoardCardFields.length > 0; const hasVisibleFields = visibleBoardCardFields.length > 0;
const handleStageSubmit = () => { const handleStageSubmit = () => {
@ -109,7 +106,9 @@ export const BoardOptionsDropdownContent = ({
}; };
const { handleFieldVisibilityChange, handleFieldsReorder } = const { handleFieldVisibilityChange, handleFieldsReorder } =
useBoardCardFields(); useRecordBoardCardFieldsInternal({
recordBoardScopeId: recordBoardId,
});
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown();

View File

@ -0,0 +1,23 @@
import { ReactNode } from 'react';
import { RecordBoardScopeInternalContext } from '@/ui/object/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext';
type RecordBoardScopeProps = {
children: ReactNode;
recordBoardScopeId: string;
};
export const RecordBoardScope = ({
children,
recordBoardScopeId,
}: RecordBoardScopeProps) => {
return (
<RecordBoardScopeInternalContext.Provider
value={{
scopeId: recordBoardScopeId,
}}
>
{children}
</RecordBoardScopeInternalContext.Provider>
);
};

View File

@ -0,0 +1,7 @@
import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey';
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
type RecordBoardScopeInternalContext = ScopedStateKey;
export const RecordBoardScopeInternalContext =
createScopeInternalContext<RecordBoardScopeInternalContext>();

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const activeCardIdsState = atom<string[]>({
key: 'activeCardIdsState',
default: [],
});

View File

@ -0,0 +1,6 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const activeRecordBoardCardIdsScopedState = createScopedState<string[]>({
key: 'activeRecordBoardCardIdsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,11 @@
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
export const availableRecordBoardCardFieldsScopedState = createScopedState<
BoardFieldDefinition<FieldMetadata>[]
>({
key: 'availableRecordBoardCardFieldsScopedState',
defaultValue: [],
});

View File

@ -1,13 +0,0 @@
import { atomFamily } from 'recoil';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
export const boardCardFieldsScopedState = atomFamily<
BoardFieldDefinition<FieldMetadata>[],
string
>({
key: 'boardCardFieldsScopedState',
default: [],
});

View File

@ -1,6 +0,0 @@
import { atomFamily } from 'recoil';
export const boardCardIdsByColumnIdFamilyState = atomFamily<string[], string>({
key: 'boardCardIdsByColumnIdFamilyState',
default: [],
});

View File

@ -1,8 +0,0 @@
import { atom } from 'recoil';
import { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition';
export const boardColumnsState = atom<BoardColumnDefinition[]>({
key: 'boardColumnsState',
default: [],
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const isBoardLoadedState = atom<boolean>({
key: 'isBoardLoadedState',
default: false,
});

View File

@ -1,6 +0,0 @@
import { atomFamily } from 'recoil';
export const isCardInCompactViewState = atomFamily<boolean, string>({
key: 'isCardInCompactViewState',
default: true,
});

View File

@ -1,6 +0,0 @@
import { atomFamily } from 'recoil';
export const isCardSelectedFamilyState = atomFamily<boolean, string>({
key: 'isCardSelectedFamilyState',
default: false,
});

View File

@ -0,0 +1,6 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const isCompactViewEnabledScopedState = createScopedState<boolean>({
key: 'isCompactViewEnabledScopedState',
defaultValue: false,
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const isCompactViewEnabledState = atom<boolean>({
key: 'isCompactViewEnabledState',
default: false,
});

View File

@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
export const isRecordBoardCardInCompactViewFamilyState = atomFamily<
boolean,
string
>({
key: 'isRecordBoardCardInCompactViewFamilyState',
default: true,
});

View File

@ -0,0 +1,8 @@
import { atomFamily } from 'recoil';
export const isRecordBoardCardSelectedFamilyState = atomFamily<boolean, string>(
{
key: 'isRecordBoardCardSelectedFamilyState',
default: false,
},
);

View File

@ -0,0 +1,6 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const isRecordBoardLoadedScopedState = createScopedState<boolean>({
key: 'isRecordBoardLoadedScopedState',
defaultValue: false,
});

View File

@ -0,0 +1,10 @@
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const onFieldsChangeScopedState = createScopedState<
(fields: BoardFieldDefinition<FieldMetadata>[]) => void
>({
key: 'onFieldsChangeScopedState',
defaultValue: () => {},
});

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const BoardColumnRecoilScopeContext = createContext<string | null>(null);

View File

@ -3,9 +3,9 @@ import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScope
import { BoardFieldDefinition } from '../types/BoardFieldDefinition'; import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
export const availableBoardCardFieldsScopedState = createScopedState< export const recordBoardCardFieldsScopedState = createScopedState<
BoardFieldDefinition<FieldMetadata>[] BoardFieldDefinition<FieldMetadata>[]
>({ >({
key: 'availableBoardCardFieldsScopedState', key: 'recordBoardCardFieldsScopedState',
defaultValue: [], defaultValue: [],
}); });

View File

@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
export const recordBoardCardIdsByColumnIdFamilyState = atomFamily<
string[],
string
>({
key: 'recordBoardCardIdsByColumnIdFamilyState',
default: [],
});

View File

@ -0,0 +1,9 @@
import { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const recordBoardColumnsScopedState = createScopedState<
BoardColumnDefinition[]
>({
key: 'recordBoardColumnsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,7 @@
import { Filter } from '@/ui/object/object-filter-dropdown/types/Filter';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const recordBoardFiltersScopedState = createScopedState<Filter[]>({
key: 'recordBoardFiltersScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,8 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { Sort } from '../../object-sort-dropdown/types/Sort';
export const recordBoardSortsScopedState = createScopedState<Sort[]>({
key: 'recordBoardSortsScopedState',
defaultValue: [],
});

View File

@ -1,13 +0,0 @@
import { atomFamily } from 'recoil';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
export const savedBoardCardFieldsFamilyState = atomFamily<
BoardFieldDefinition<FieldMetadata>[],
string | undefined
>({
key: 'savedBoardCardFieldsFamilyState',
default: [],
});

View File

@ -1,8 +0,0 @@
import { atom } from 'recoil';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
export const savedBoardColumnsState = atom<BoardColumnDefinition[]>({
key: 'savedBoardColumnsState',
default: [],
});

View File

@ -0,0 +1,7 @@
import { Opportunity } from '@/pipeline/types/Opportunity';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const savedOpportunitiesScopedState = createScopedState<Opportunity[]>({
key: 'savedOpportunitiesScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,7 @@
import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const savedPipelineStepsScopedState = createScopedState<PipelineStep[]>({
key: 'savedPipelineStepsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,11 @@
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { BoardFieldDefinition } from '../types/BoardFieldDefinition';
export const savedRecordBoardCardFieldsScopedState = createScopedState<
BoardFieldDefinition<FieldMetadata>[]
>({
key: 'savedRecordBoardCardFieldsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,10 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
export const savedRecordBoardColumnsScopedState = createScopedState<
BoardColumnDefinition[]
>({
key: 'savedRecordBoardColumnsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,7 @@
import { Company } from '@/companies/types/Company';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const savedRecordsScopedState = createScopedState<Company[]>({
key: 'savedRecordsScopedState',
defaultValue: [],
});

View File

@ -1,23 +0,0 @@
import { selectorFamily } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
import { savedBoardCardFieldsFamilyState } from '../savedBoardCardFieldsFamilyState';
export const canPersistBoardCardFieldsScopedFamilySelector = selectorFamily({
key: 'canPersistBoardCardFieldsScopedFamilySelector',
get:
({
recoilScopeId,
viewId,
}: {
recoilScopeId: string;
viewId: string | undefined;
}) =>
({ get }) =>
!isDeeplyEqual(
get(savedBoardCardFieldsFamilyState(viewId)),
get(boardCardFieldsScopedState(recoilScopeId)),
),
});

View File

@ -1,12 +0,0 @@
import { selector } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { boardColumnsState } from '../boardColumnsState';
import { savedBoardColumnsState } from '../savedBoardColumnsState';
export const canPersistBoardColumnsSelector = selector<boolean>({
key: 'canPersistBoardCardFieldsScopedFamilySelector',
get: ({ get }) =>
!isDeeplyEqual(get(boardColumnsState), get(savedBoardColumnsState)),
});

View File

@ -1,22 +0,0 @@
import { selectorFamily } from 'recoil';
import { availableBoardCardFieldsScopedState } from '../availableBoardCardFieldsScopedState';
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
export const hiddenBoardCardFieldsScopedSelector = selectorFamily({
key: 'hiddenBoardCardFieldsScopedSelector',
get:
(scopeId: string) =>
({ get }) => {
const fields = get(boardCardFieldsScopedState(scopeId));
const fieldKeys = fields.map(({ fieldMetadataId }) => fieldMetadataId);
const otherAvailableKeys = get(
availableBoardCardFieldsScopedState({ scopeId }),
).filter(({ fieldMetadataId }) => !fieldKeys.includes(fieldMetadataId));
return [
...fields.filter((field) => !field.isVisible),
...otherAvailableKeys,
];
},
});

View File

@ -0,0 +1,22 @@
import { createScopedSelector } from '@/ui/utilities/recoil-scope/utils/createScopedSelector';
import { availableRecordBoardCardFieldsScopedState } from '../availableRecordBoardCardFieldsScopedState';
import { recordBoardCardFieldsScopedState } from '../recordBoardCardFieldsScopedState';
export const hiddenRecordBoardCardFieldsScopedSelector = createScopedSelector({
key: 'hiddenRecordBoardCardFieldsScopedSelector',
get:
({ scopeId }) =>
({ get }) => {
const fields = get(recordBoardCardFieldsScopedState({ scopeId }));
const fieldKeys = fields.map(({ fieldMetadataId }) => fieldMetadataId);
const otherAvailableKeys = get(
availableRecordBoardCardFieldsScopedState({ scopeId }),
).filter(({ fieldMetadataId }) => !fieldKeys.includes(fieldMetadataId));
return [
...fields.filter((field) => !field.isVisible),
...otherAvailableKeys,
];
},
});

View File

@ -3,14 +3,14 @@ import { selectorFamily } from 'recoil';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { BoardFieldDefinition } from '../../types/BoardFieldDefinition'; import { BoardFieldDefinition } from '../../types/BoardFieldDefinition';
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState'; import { recordBoardCardFieldsScopedState } from '../recordBoardCardFieldsScopedState';
export const boardCardFieldsByKeyScopedSelector = selectorFamily({ export const recordBoardCardFieldsByKeyScopedSelector = selectorFamily({
key: 'boardCardFieldsByKeyScopedSelector', key: 'recordBoardCardFieldsByKeyScopedSelector',
get: get:
(scopeId: string) => (scopeId: string) =>
({ get }) => ({ get }) =>
get(boardCardFieldsScopedState(scopeId)).reduce< get(recordBoardCardFieldsScopedState({ scopeId })).reduce<
Record<string, BoardFieldDefinition<FieldMetadata>> Record<string, BoardFieldDefinition<FieldMetadata>>
>((result, field) => ({ ...result, [field.fieldMetadataId]: field }), {}), >((result, field) => ({ ...result, [field.fieldMetadataId]: field }), {}),
}); });

View File

@ -2,16 +2,18 @@ import { selectorFamily } from 'recoil';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
import { boardCardIdsByColumnIdFamilyState } from '../boardCardIdsByColumnIdFamilyState'; import { recordBoardCardIdsByColumnIdFamilyState } from '../recordBoardCardIdsByColumnIdFamilyState';
// TODO: this state should be computed during the synchronization web-hook and put in a generic // TODO: this state should be computed during the synchronization web-hook and put in a generic
// boardColumnTotalsFamilyState indexed by columnId. // boardColumnTotalsFamilyState indexed by columnId.
export const boardColumnTotalsFamilySelector = selectorFamily({ export const recordBoardColumnTotalsFamilySelector = selectorFamily({
key: 'boardColumnTotalsFamilySelector', key: 'recordBoardColumnTotalsFamilySelector',
get: get:
(pipelineStepId: string) => (pipelineStepId: string) =>
({ get }) => { ({ get }) => {
const cardIds = get(boardCardIdsByColumnIdFamilyState(pipelineStepId)); const cardIds = get(
recordBoardCardIdsByColumnIdFamilyState(pipelineStepId),
);
const opportunities = cardIds.map((opportunityId: string) => const opportunities = cardIds.map((opportunityId: string) =>
get(companyProgressesFamilyState(opportunityId)), get(companyProgressesFamilyState(opportunityId)),

View File

@ -1,16 +0,0 @@
import { selectorFamily } from 'recoil';
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
import { BoardFieldDefinition } from '../../types/BoardFieldDefinition';
import { savedBoardCardFieldsFamilyState } from '../savedBoardCardFieldsFamilyState';
export const savedBoardCardFieldsByKeyFamilySelector = selectorFamily({
key: 'savedBoardCardFieldsByKeyFamilySelector',
get:
(viewId: string | undefined) =>
({ get }) =>
get(savedBoardCardFieldsFamilyState(viewId)).reduce<
Record<string, BoardFieldDefinition<FieldMetadata>>
>((result, field) => ({ ...result, [field.fieldMetadataId]: field }), {}),
});

View File

@ -1,22 +0,0 @@
import { selector } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from '../boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '../boardColumnsState';
import { isCardSelectedFamilyState } from '../isCardSelectedFamilyState';
export const selectedCardIdsSelector = selector<string[]>({
key: 'selectedCardIdsSelector',
get: ({ get }) => {
const boardColumns = get(boardColumnsState);
const cardIds = boardColumns.flatMap((boardColumn) =>
get(boardCardIdsByColumnIdFamilyState(boardColumn.id)),
);
const selectedCardIds = cardIds.filter(
(cardId) => get(isCardSelectedFamilyState(cardId)) === true,
);
return selectedCardIds;
},
});

View File

@ -0,0 +1,26 @@
import { createScopedSelector } from '@/ui/utilities/recoil-scope/utils/createScopedSelector';
import { isRecordBoardCardSelectedFamilyState } from '../isRecordBoardCardSelectedFamilyState';
import { recordBoardCardIdsByColumnIdFamilyState } from '../recordBoardCardIdsByColumnIdFamilyState';
import { recordBoardColumnsScopedState } from '../recordBoardColumnsScopedState';
export const selectedRecordBoardCardIdsScopedSelector = createScopedSelector<
string[]
>({
key: 'selectedRecordBoardCardIdsScopedSelector',
get:
({ scopeId }) =>
({ get }) => {
const boardColumns = get(recordBoardColumnsScopedState({ scopeId }));
const cardIds = boardColumns.flatMap((boardColumn) =>
get(recordBoardCardIdsByColumnIdFamilyState(boardColumn.id)),
);
const selectedCardIds = cardIds.filter(
(cardId) => get(isRecordBoardCardSelectedFamilyState(cardId)) === true,
);
return selectedCardIds;
},
});

View File

@ -1,13 +0,0 @@
import { selectorFamily } from 'recoil';
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
export const visibleBoardCardFieldsScopedSelector = selectorFamily({
key: 'visibleBoardCardFieldsScopedSelector',
get:
(scopeId: string) =>
({ get }) =>
get(boardCardFieldsScopedState(scopeId))
.filter((field) => field.isVisible)
.sort((a, b) => a.position - b.position),
});

View File

@ -0,0 +1,13 @@
import { createScopedSelector } from '@/ui/utilities/recoil-scope/utils/createScopedSelector';
import { recordBoardCardFieldsScopedState } from '../recordBoardCardFieldsScopedState';
export const visibleRecordBoardCardFieldsScopedSelector = createScopedSelector({
key: 'visibleRecordBoardCardFieldsScopedSelector',
get:
({ scopeId }) =>
({ get }) =>
get(recordBoardCardFieldsScopedState({ scopeId }))
.filter((field) => field.isVisible)
.sort((a, b) => a.position - b.position),
});

View File

@ -0,0 +1,120 @@
import { activeRecordBoardCardIdsScopedState } from '@/ui/object/record-board/states/activeRecordBoardCardIdsScopedState';
import { availableRecordBoardCardFieldsScopedState } from '@/ui/object/record-board/states/availableRecordBoardCardFieldsScopedState';
import { isCompactViewEnabledScopedState } from '@/ui/object/record-board/states/isCompactViewEnabledScopedState';
import { isRecordBoardLoadedScopedState } from '@/ui/object/record-board/states/isRecordBoardLoadedScopedState';
import { onFieldsChangeScopedState } from '@/ui/object/record-board/states/onFieldsChangeScopedState';
import { recordBoardColumnsScopedState } from '@/ui/object/record-board/states/recordBoardColumnsScopedState';
import { recordBoardFiltersScopedState } from '@/ui/object/record-board/states/recordBoardFiltersScopedState';
import { recordBoardSortsScopedState } from '@/ui/object/record-board/states/recordBoardSortsScopedState';
import { savedOpportunitiesScopedState } from '@/ui/object/record-board/states/savedOpportunitiesScopedState';
import { savedPipelineStepsScopedState } from '@/ui/object/record-board/states/savedPipelineStepsScopedState';
import { savedRecordBoardColumnsScopedState } from '@/ui/object/record-board/states/savedRecordBoardColumnsScopedState';
import { savedRecordsScopedState } from '@/ui/object/record-board/states/savedRecordsScopedState';
import { hiddenRecordBoardCardFieldsScopedSelector } from '@/ui/object/record-board/states/selectors/hiddenRecordBoardCardFieldsScopedSelector';
import { recordBoardCardFieldsByKeyScopedSelector } from '@/ui/object/record-board/states/selectors/recordBoardCardFieldsByKeyScopedSelector';
import { selectedRecordBoardCardIdsScopedSelector } from '@/ui/object/record-board/states/selectors/selectedRecordBoardCardIdsScopedSelector';
import { visibleRecordBoardCardFieldsScopedSelector } from '@/ui/object/record-board/states/selectors/visibleRecordBoardCardFieldsScopedSelector';
import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState';
export const getRecordBoardScopedStates = ({
recordBoardScopeId,
}: {
recordBoardScopeId: string;
}) => {
const activeCardIdsState = getScopedState(
activeRecordBoardCardIdsScopedState,
recordBoardScopeId,
);
const availableBoardCardFieldsState = getScopedState(
availableRecordBoardCardFieldsScopedState,
recordBoardScopeId,
);
const boardColumnsState = getScopedState(
recordBoardColumnsScopedState,
recordBoardScopeId,
);
const isBoardLoadedState = getScopedState(
isRecordBoardLoadedScopedState,
recordBoardScopeId,
);
const isCompactViewEnabledState = getScopedState(
isCompactViewEnabledScopedState,
recordBoardScopeId,
);
const savedBoardColumnsState = getScopedState(
savedRecordBoardColumnsScopedState,
recordBoardScopeId,
);
const boardFiltersState = getScopedState(
recordBoardFiltersScopedState,
recordBoardScopeId,
);
const boardSortsState = getScopedState(
recordBoardSortsScopedState,
recordBoardScopeId,
);
const savedCompaniesState = getScopedState(
savedRecordsScopedState,
recordBoardScopeId,
);
const savedOpportunitiesState = getScopedState(
savedOpportunitiesScopedState,
recordBoardScopeId,
);
const savedPipelineStepsState = getScopedState(
savedPipelineStepsScopedState,
recordBoardScopeId,
);
const onFieldsChangeState = getScopedState(
onFieldsChangeScopedState,
recordBoardScopeId,
);
// TODO: Family scoped selector
const boardCardFieldsByKeySelector =
recordBoardCardFieldsByKeyScopedSelector(recordBoardScopeId);
const hiddenBoardCardFieldsSelector =
hiddenRecordBoardCardFieldsScopedSelector({
scopeId: recordBoardScopeId,
});
const selectedCardIdsSelector = selectedRecordBoardCardIdsScopedSelector({
scopeId: recordBoardScopeId,
});
const visibleBoardCardFieldsSelector =
visibleRecordBoardCardFieldsScopedSelector({
scopeId: recordBoardScopeId,
});
return {
activeCardIdsState,
availableBoardCardFieldsState,
boardColumnsState,
isBoardLoadedState,
isCompactViewEnabledState,
savedBoardColumnsState,
boardFiltersState,
boardSortsState,
onFieldsChangeState,
boardCardFieldsByKeySelector,
hiddenBoardCardFieldsSelector,
selectedCardIdsSelector,
visibleBoardCardFieldsSelector,
savedCompaniesState,
savedOpportunitiesState,
savedPipelineStepsState,
};
};

View File

@ -1,7 +1,6 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { CompanyBoard } from '@/companies/board/components/CompanyBoard'; import { CompanyBoard } from '@/companies/board/components/CompanyBoard';
import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton'; import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton';
import { usePipelineSteps } from '@/pipeline/hooks/usePipelineSteps'; import { usePipelineSteps } from '@/pipeline/hooks/usePipelineSteps';
@ -10,9 +9,6 @@ import { IconTargetArrow } from '@/ui/display/icon';
import { PageBody } from '@/ui/layout/page/PageBody'; import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageHeader } from '@/ui/layout/page/PageHeader'; import { PageHeader } from '@/ui/layout/page/PageHeader';
import { BoardOptionsContext } from '@/ui/object/record-board/contexts/BoardOptionsContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
const StyledBoardContainer = styled.div` const StyledBoardContainer = styled.div`
display: flex; display: flex;
@ -45,24 +41,18 @@ export const Opportunities = () => {
return ( return (
<PageContainer> <PageContainer>
<RecoilScope> <PageHeader title="Opportunities" Icon={IconTargetArrow}>
<PageHeader title="Opportunities" Icon={IconTargetArrow}> <PipelineAddButton />
<PipelineAddButton /> </PageHeader>
</PageHeader> <PageBody>
<PageBody> <StyledBoardContainer>
<BoardOptionsContext.Provider value={opportunitiesBoardOptions}> <CompanyBoard
<CompanyBoardRecoilScopeContext.Provider value="opportunities"> onColumnAdd={handlePipelineStepAdd}
<StyledBoardContainer> onColumnDelete={handlePipelineStepDelete}
<CompanyBoard onEditColumnTitle={handleEditColumnTitle}
onColumnAdd={handlePipelineStepAdd} />
onColumnDelete={handlePipelineStepDelete} </StyledBoardContainer>
onEditColumnTitle={handleEditColumnTitle} </PageBody>
/>
</StyledBoardContainer>
</CompanyBoardRecoilScopeContext.Provider>
</BoardOptionsContext.Provider>
</PageBody>
</RecoilScope>
</PageContainer> </PageContainer>
); );
}; };

View File

@ -1,8 +1,13 @@
import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard'; import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard';
import { NewOpportunityButton } from '@/companies/components/NewOpportunityButton'; import { NewOpportunityButton } from '@/companies/components/NewOpportunityButton';
import { BoardOptions } from '@/ui/object/record-board/types/BoardOptions'; import { BoardOptions } from '@/ui/object/record-board/types/BoardOptions';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
export const opportunitiesBoardOptions: BoardOptions = { export const opportunitiesBoardOptions: BoardOptions = {
newCardComponent: <NewOpportunityButton />, newCardComponent: (
<RecoilScope>
<NewOpportunityButton />
</RecoilScope>
),
CardComponent: CompanyBoardCard, CardComponent: CompanyBoardCard,
}; };