Fix optimistic rendering issues on board and table (#2846)
* Fix optimistic rendering issues on board and table * Remove dead code * Improve re-renders of Table * Remove re-renders on board
This commit is contained in:
@ -49,12 +49,14 @@ export const useOptimisticEffect = ({
|
|||||||
const optimisticEffectWriter = ({
|
const optimisticEffectWriter = ({
|
||||||
cache,
|
cache,
|
||||||
newData,
|
newData,
|
||||||
|
deletedRecordIds,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}: {
|
}: {
|
||||||
cache: ApolloCache<unknown>;
|
cache: ApolloCache<unknown>;
|
||||||
newData: unknown;
|
newData: unknown;
|
||||||
|
deletedRecordIds?: string[];
|
||||||
variables: OperationVariables;
|
variables: OperationVariables;
|
||||||
query: DocumentNode;
|
query: DocumentNode;
|
||||||
isUsingFlexibleBackend?: boolean;
|
isUsingFlexibleBackend?: boolean;
|
||||||
@ -79,6 +81,7 @@ export const useOptimisticEffect = ({
|
|||||||
objectMetadataItem.namePlural
|
objectMetadataItem.namePlural
|
||||||
],
|
],
|
||||||
newData,
|
newData,
|
||||||
|
deletedRecordIds,
|
||||||
variables,
|
variables,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -116,7 +119,7 @@ export const useOptimisticEffect = ({
|
|||||||
|
|
||||||
const triggerOptimisticEffects = useRecoilCallback(
|
const triggerOptimisticEffects = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
(typename: string, newData: unknown) => {
|
(typename: string, newData: unknown, deletedRecordIds?: string[]) => {
|
||||||
const optimisticEffects = snapshot
|
const optimisticEffects = snapshot
|
||||||
.getLoadable(optimisticEffectState)
|
.getLoadable(optimisticEffectState)
|
||||||
.getValue();
|
.getValue();
|
||||||
@ -135,6 +138,7 @@ export const useOptimisticEffect = ({
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
query: optimisticEffect.query ?? ({} as DocumentNode),
|
query: optimisticEffect.query ?? ({} as DocumentNode),
|
||||||
newData: formattedNewData,
|
newData: formattedNewData,
|
||||||
|
deletedRecordIds,
|
||||||
variables: optimisticEffect.variables,
|
variables: optimisticEffect.variables,
|
||||||
isUsingFlexibleBackend: optimisticEffect.isUsingFlexibleBackend,
|
isUsingFlexibleBackend: optimisticEffect.isUsingFlexibleBackend,
|
||||||
objectMetadataItem: optimisticEffect.objectMetadataItem,
|
objectMetadataItem: optimisticEffect.objectMetadataItem,
|
||||||
|
|||||||
@ -3,9 +3,11 @@ import { OperationVariables } from '@apollo/client';
|
|||||||
export type OptimisticEffectResolver = ({
|
export type OptimisticEffectResolver = ({
|
||||||
currentData,
|
currentData,
|
||||||
newData,
|
newData,
|
||||||
|
deletedRecordIds,
|
||||||
variables,
|
variables,
|
||||||
}: {
|
}: {
|
||||||
currentData: any; //TODO: Change when decommissioning v1
|
currentData: any; //TODO: Change when decommissioning v1
|
||||||
newData: any; //TODO: Change when decommissioning v1
|
newData: any; //TODO: Change when decommissioning v1
|
||||||
|
deletedRecordIds?: string[];
|
||||||
variables: OperationVariables;
|
variables: OperationVariables;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ type OptimisticEffectWriter<T> = ({
|
|||||||
cache: ApolloCache<T>;
|
cache: ApolloCache<T>;
|
||||||
query: DocumentNode;
|
query: DocumentNode;
|
||||||
newData: T;
|
newData: T;
|
||||||
|
deletedRecordIds?: string[];
|
||||||
variables: OperationVariables;
|
variables: OperationVariables;
|
||||||
objectMetadataItem?: ObjectMetadataItem;
|
objectMetadataItem?: ObjectMetadataItem;
|
||||||
isUsingFlexibleBackend?: boolean;
|
isUsingFlexibleBackend?: boolean;
|
||||||
|
|||||||
@ -3,7 +3,9 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
|
import { Activity } from '@/activities/types/Activity';
|
||||||
import { CommandMenuSelectableListEffect } from '@/command-menu/components/CommandMenuSelectableListEffect';
|
import { CommandMenuSelectableListEffect } from '@/command-menu/components/CommandMenuSelectableListEffect';
|
||||||
|
import { Company } from '@/companies/types/Company';
|
||||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { Person } from '@/people/types/Person';
|
import { Person } from '@/people/types/Person';
|
||||||
@ -130,7 +132,7 @@ export const CommandMenu = () => {
|
|||||||
limit: 3,
|
limit: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { records: companies } = useFindManyRecords<Person>({
|
const { records: companies } = useFindManyRecords<Company>({
|
||||||
skip: !isCommandMenuOpened,
|
skip: !isCommandMenuOpened,
|
||||||
objectNameSingular: 'company',
|
objectNameSingular: 'company',
|
||||||
filter: {
|
filter: {
|
||||||
@ -139,7 +141,7 @@ export const CommandMenu = () => {
|
|||||||
limit: 3,
|
limit: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { records: activities } = useFindManyRecords<Person>({
|
const { records: activities } = useFindManyRecords<Activity>({
|
||||||
skip: !isCommandMenuOpened,
|
skip: !isCommandMenuOpened,
|
||||||
objectNameSingular: 'activity',
|
objectNameSingular: 'activity',
|
||||||
filter: {
|
filter: {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
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 { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
|
import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
|
||||||
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
|
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
|
||||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||||
@ -12,8 +13,6 @@ import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToC
|
|||||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||||
|
|
||||||
import { useUpdateOneRecord } from '../hooks/useUpdateOneRecord';
|
|
||||||
|
|
||||||
import { RecordTableEffect } from './RecordTableEffect';
|
import { RecordTableEffect } from './RecordTableEffect';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
|||||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||||
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
|
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
|
||||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||||
@ -15,8 +16,6 @@ import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
|
|||||||
import { RecordTableActionBar } from '@/ui/object/record-table/action-bar/components/RecordTableActionBar';
|
import { RecordTableActionBar } from '@/ui/object/record-table/action-bar/components/RecordTableActionBar';
|
||||||
import { RecordTableContextMenu } from '@/ui/object/record-table/context-menu/components/RecordTableContextMenu';
|
import { RecordTableContextMenu } from '@/ui/object/record-table/context-menu/components/RecordTableContextMenu';
|
||||||
|
|
||||||
import { useCreateOneRecord } from '../hooks/useCreateOneRecord';
|
|
||||||
|
|
||||||
import { RecordTableContainer } from './RecordTableContainer';
|
import { RecordTableContainer } from './RecordTableContainer';
|
||||||
|
|
||||||
const StyledTableContainer = styled.div`
|
const StyledTableContainer = styled.div`
|
||||||
|
|||||||
@ -16,26 +16,49 @@ export const getRecordOptimisticEffectDefinition = ({
|
|||||||
resolver: ({
|
resolver: ({
|
||||||
currentData,
|
currentData,
|
||||||
newData,
|
newData,
|
||||||
|
deletedRecordIds,
|
||||||
}: {
|
}: {
|
||||||
currentData: unknown;
|
currentData: unknown;
|
||||||
newData: unknown;
|
newData: { id: string } & Record<string, any>;
|
||||||
|
deletedRecordIds?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const newRecordPaginatedCacheField = produce<
|
const newRecordPaginatedCacheField = produce<
|
||||||
PaginatedRecordTypeResults<any>
|
PaginatedRecordTypeResults<any>
|
||||||
>(currentData as PaginatedRecordTypeResults<any>, (draft) => {
|
>(currentData as PaginatedRecordTypeResults<any>, (draft) => {
|
||||||
if (!draft) {
|
if (newData) {
|
||||||
return {
|
if (!draft) {
|
||||||
edges: [{ node: newData, cursor: '' }],
|
return {
|
||||||
pageInfo: {
|
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||||
endCursor: '',
|
edges: [{ node: newData, cursor: '' }],
|
||||||
hasNextPage: false,
|
pageInfo: {
|
||||||
hasPreviousPage: false,
|
endCursor: '',
|
||||||
startCursor: '',
|
hasNextPage: false,
|
||||||
},
|
hasPreviousPage: false,
|
||||||
};
|
startCursor: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingRecord = draft.edges.find(
|
||||||
|
(edge) => edge.node.id === newData.id,
|
||||||
|
);
|
||||||
|
if (existingRecord) {
|
||||||
|
existingRecord.node = newData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.edges.unshift({
|
||||||
|
node: newData,
|
||||||
|
cursor: '',
|
||||||
|
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
draft.edges.unshift({ node: newData, cursor: '' });
|
if (deletedRecordIds) {
|
||||||
|
draft.edges = draft.edges.filter(
|
||||||
|
(edge) => !deletedRecordIds.includes(edge.node.id),
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return newRecordPaginatedCacheField;
|
return newRecordPaginatedCacheField;
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||||
|
import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord';
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
export const useCreateOneRecord = <T>({
|
export const useCreateOneRecord = <T>({
|
||||||
@ -20,21 +21,42 @@ export const useCreateOneRecord = <T>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO: type this with a minimal type at least with Record<string, any>
|
// TODO: type this with a minimal type at least with Record<string, any>
|
||||||
const [mutate] = useMutation(createOneRecordMutation);
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
|
const { generateEmptyRecord } = useGenerateEmptyRecord({
|
||||||
|
objectMetadataItem,
|
||||||
|
});
|
||||||
|
|
||||||
const createOneRecord = async (input: Record<string, any>) => {
|
const createOneRecord = async (input: Record<string, any>) => {
|
||||||
const createdObject = await mutate({
|
const recordId = v4();
|
||||||
|
|
||||||
|
triggerOptimisticEffects(
|
||||||
|
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||||
|
generateEmptyRecord(recordId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const createdObject = await apolloClient.mutate({
|
||||||
|
mutation: createOneRecordMutation,
|
||||||
variables: {
|
variables: {
|
||||||
input: { ...input, id: v4() },
|
input: { ...input, id: recordId },
|
||||||
|
},
|
||||||
|
optimisticResponse: {
|
||||||
|
[`create${capitalize(objectMetadataItem.nameSingular)}`]:
|
||||||
|
generateEmptyRecord(recordId),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!createdObject.data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
triggerOptimisticEffects(
|
triggerOptimisticEffects(
|
||||||
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||||
createdObject.data[
|
createdObject.data[
|
||||||
`create${capitalize(objectMetadataItem.nameSingular)}`
|
`create${capitalize(objectMetadataItem.nameSingular)}`
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return createdObject.data[
|
return createdObject.data[
|
||||||
`create${capitalize(objectMetadataItem.nameSingular)}`
|
`create${capitalize(objectMetadataItem.nameSingular)}`
|
||||||
] as T;
|
] as T;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
|
|
||||||
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict';
|
import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||||
@ -10,6 +11,9 @@ export const useDeleteOneRecord = <T>({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
}: ObjectMetadataItemIdentifier) => {
|
}: ObjectMetadataItemIdentifier) => {
|
||||||
const { performOptimisticEvict } = useOptimisticEvict();
|
const { performOptimisticEvict } = useOptimisticEvict();
|
||||||
|
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
const { objectMetadataItem, deleteOneRecordMutation } = useObjectMetadataItem(
|
const { objectMetadataItem, deleteOneRecordMutation } = useObjectMetadataItem(
|
||||||
{
|
{
|
||||||
@ -17,16 +21,15 @@ export const useDeleteOneRecord = <T>({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: type this with a minimal type at least with Record<string, any>
|
const apolloClient = useApolloClient();
|
||||||
const [mutate] = useMutation(deleteOneRecordMutation);
|
|
||||||
|
|
||||||
const deleteOneRecord = useCallback(
|
const deleteOneRecord = useCallback(
|
||||||
async (idToDelete: string) => {
|
async (idToDelete: string) => {
|
||||||
const deletedRecord = await mutate({
|
triggerOptimisticEffects(
|
||||||
variables: {
|
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||||
idToDelete,
|
undefined,
|
||||||
},
|
[idToDelete],
|
||||||
});
|
);
|
||||||
|
|
||||||
performOptimisticEvict(
|
performOptimisticEvict(
|
||||||
capitalize(objectMetadataItem.nameSingular),
|
capitalize(objectMetadataItem.nameSingular),
|
||||||
@ -34,11 +37,24 @@ export const useDeleteOneRecord = <T>({
|
|||||||
idToDelete,
|
idToDelete,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const deletedRecord = await apolloClient.mutate({
|
||||||
|
mutation: deleteOneRecordMutation,
|
||||||
|
variables: {
|
||||||
|
idToDelete,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return deletedRecord.data[
|
return deletedRecord.data[
|
||||||
`create${capitalize(objectMetadataItem.nameSingular)}`
|
`create${capitalize(objectMetadataItem.nameSingular)}`
|
||||||
] as T;
|
] as T;
|
||||||
},
|
},
|
||||||
[performOptimisticEvict, objectMetadataItem, mutate],
|
[
|
||||||
|
triggerOptimisticEffects,
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
performOptimisticEvict,
|
||||||
|
apolloClient,
|
||||||
|
deleteOneRecordMutation,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -214,7 +214,7 @@ export const useFindManyRecords = <
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
records,
|
records: records as RecordType[],
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
fetchMoreRecords,
|
fetchMoreRecords,
|
||||||
|
|||||||
177
front/src/modules/object-record/hooks/useGenerateEmptyRecord.ts
Normal file
177
front/src/modules/object-record/hooks/useGenerateEmptyRecord.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
|
export const useGenerateEmptyRecord = ({
|
||||||
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
|
const generateEmptyRecord = (id: string) => {
|
||||||
|
if (objectMetadataItem.nameSingular === 'company') {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
domainName: '',
|
||||||
|
accountOwnerId: null,
|
||||||
|
createdAt: '2023-12-05T16:04:42.261Z',
|
||||||
|
address: '',
|
||||||
|
people: [
|
||||||
|
{
|
||||||
|
edges: [],
|
||||||
|
__typename: 'PersonConnection',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
__typename: 'Link',
|
||||||
|
},
|
||||||
|
attachments: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'AttachmentConnection',
|
||||||
|
},
|
||||||
|
activityTargets: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'ActivityTargetConnection',
|
||||||
|
},
|
||||||
|
idealCustomerProfile: null,
|
||||||
|
annualRecurringRevenue: {
|
||||||
|
amountMicros: null,
|
||||||
|
currencyCode: null,
|
||||||
|
__typename: 'Currency',
|
||||||
|
},
|
||||||
|
updatedAt: '2023-12-05T16:04:42.261Z',
|
||||||
|
employees: null,
|
||||||
|
accountOwner: null,
|
||||||
|
name: '',
|
||||||
|
linkedinLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
__typename: 'Link',
|
||||||
|
},
|
||||||
|
favorites: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'FavoriteConnection',
|
||||||
|
},
|
||||||
|
opportunities: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'OpportunityConnection',
|
||||||
|
},
|
||||||
|
__typename: 'Company',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectMetadataItem.nameSingular === 'person') {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
activityTargets: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'ActivityTargetConnection',
|
||||||
|
},
|
||||||
|
opportunities: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'OpportunityConnection',
|
||||||
|
},
|
||||||
|
companyId: null,
|
||||||
|
favorites: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'FavoriteConnection',
|
||||||
|
},
|
||||||
|
phone: '',
|
||||||
|
company: null,
|
||||||
|
xLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
__typename: 'Link',
|
||||||
|
},
|
||||||
|
jobTitle: '',
|
||||||
|
pointOfContactForOpportunities: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'OpportunityConnection',
|
||||||
|
},
|
||||||
|
email: '',
|
||||||
|
attachments: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'AttachmentConnection',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
__typename: 'FullName',
|
||||||
|
},
|
||||||
|
avatarUrl: '',
|
||||||
|
updatedAt: '2023-12-05T16:45:11.840Z',
|
||||||
|
createdAt: '2023-12-05T16:45:11.840Z',
|
||||||
|
city: '',
|
||||||
|
linkedinLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
__typename: 'Link',
|
||||||
|
},
|
||||||
|
__typename: 'Person',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectMetadataItem.nameSingular === 'opportunity') {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02',
|
||||||
|
closeDate: null,
|
||||||
|
companyId: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||||
|
updatedAt: '2023-12-05T16:46:27.621Z',
|
||||||
|
pipelineStep: {
|
||||||
|
id: '30b14887-d592-427d-bd97-6e670158db02',
|
||||||
|
position: 2,
|
||||||
|
name: 'Meeting',
|
||||||
|
updatedAt: '2023-12-05T11:29:21.485Z',
|
||||||
|
createdAt: '2023-12-05T11:29:21.485Z',
|
||||||
|
color: 'sky',
|
||||||
|
__typename: 'PipelineStep',
|
||||||
|
},
|
||||||
|
probability: '0',
|
||||||
|
pointOfContactId: null,
|
||||||
|
personId: null,
|
||||||
|
amount: {
|
||||||
|
amountMicros: null,
|
||||||
|
currencyCode: null,
|
||||||
|
__typename: 'Currency',
|
||||||
|
},
|
||||||
|
createdAt: '2023-12-05T16:46:27.621Z',
|
||||||
|
pointOfContact: null,
|
||||||
|
person: null,
|
||||||
|
company: {
|
||||||
|
id: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||||
|
domainName: 'qonto.com',
|
||||||
|
accountOwnerId: null,
|
||||||
|
createdAt: '2023-12-05T11:29:21.484Z',
|
||||||
|
address: '',
|
||||||
|
xLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
__typename: 'Link',
|
||||||
|
},
|
||||||
|
idealCustomerProfile: null,
|
||||||
|
annualRecurringRevenue: {
|
||||||
|
amountMicros: null,
|
||||||
|
currencyCode: null,
|
||||||
|
__typename: 'Currency',
|
||||||
|
},
|
||||||
|
updatedAt: '2023-12-05T11:29:21.484Z',
|
||||||
|
employees: null,
|
||||||
|
name: 'Qonto',
|
||||||
|
linkedinLink: {
|
||||||
|
label: '',
|
||||||
|
url: '',
|
||||||
|
__typename: 'Link',
|
||||||
|
},
|
||||||
|
__typename: 'Company',
|
||||||
|
},
|
||||||
|
__typename: 'Opportunity',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
generateEmptyRecord: generateEmptyRecord,
|
||||||
|
};
|
||||||
|
};
|
||||||
104
front/src/modules/object-record/hooks/useObjectRecordBoard.1.ts
Normal file
104
front/src/modules/object-record/hooks/useObjectRecordBoard.1.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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 { useFindManyRecords } from './useFindManyRecords';
|
||||||
|
|
||||||
|
export const useObjectRecordBoard = () => {
|
||||||
|
const objectNameSingular = 'opportunity';
|
||||||
|
|
||||||
|
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] = 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<Opportunity>({
|
||||||
|
skip: !savedPipelineSteps.length,
|
||||||
|
objectNameSingular: 'opportunity',
|
||||||
|
filter: filter,
|
||||||
|
orderBy: orderBy,
|
||||||
|
onCompleted: useCallback(() => {
|
||||||
|
setIsBoardLoaded(true);
|
||||||
|
}, [setIsBoardLoaded]),
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -9,13 +9,11 @@ import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
|||||||
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
||||||
import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
|
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||||
import { useUpdateCompanyBoardCardIdsInternal } from '@/ui/object/record-board/hooks/internal/useUpdateCompanyBoardCardIdsInternal';
|
|
||||||
|
|
||||||
import { useFindManyRecords } from './useFindManyRecords';
|
import { useFindManyRecords } from './useFindManyRecords';
|
||||||
|
|
||||||
export const useObjectRecordBoard = () => {
|
export const useObjectRecordBoard = () => {
|
||||||
const objectNameSingular = 'opportunity';
|
const objectNameSingular = 'opportunity';
|
||||||
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIdsInternal();
|
|
||||||
|
|
||||||
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
||||||
{
|
{
|
||||||
@ -71,24 +69,14 @@ export const useObjectRecordBoard = () => {
|
|||||||
records: opportunities,
|
records: opportunities,
|
||||||
loading,
|
loading,
|
||||||
fetchMoreRecords: fetchMoreOpportunities,
|
fetchMoreRecords: fetchMoreOpportunities,
|
||||||
} = useFindManyRecords({
|
} = useFindManyRecords<Opportunity>({
|
||||||
skip: !savedPipelineSteps.length,
|
skip: !savedPipelineSteps.length,
|
||||||
objectNameSingular: 'opportunity',
|
objectNameSingular: 'opportunity',
|
||||||
filter: filter,
|
filter: filter,
|
||||||
orderBy: orderBy,
|
orderBy: orderBy,
|
||||||
onCompleted: useCallback(
|
onCompleted: useCallback(() => {
|
||||||
(data: PaginatedRecordTypeResults<Opportunity>) => {
|
setIsBoardLoaded(true);
|
||||||
const pipelineProgresses: Array<Opportunity> = data.edges.map(
|
}, [setIsBoardLoaded]),
|
||||||
(edge) => edge.node,
|
|
||||||
);
|
|
||||||
|
|
||||||
updateCompanyBoardCardIds(pipelineProgresses);
|
|
||||||
|
|
||||||
setSavedOpportunities(pipelineProgresses);
|
|
||||||
setIsBoardLoaded(true);
|
|
||||||
},
|
|
||||||
[setIsBoardLoaded, setSavedOpportunities, updateCompanyBoardCardIds],
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({
|
const { fetchMoreRecords: fetchMoreCompanies } = useFindManyRecords({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
||||||
@ -8,8 +7,6 @@ import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/tur
|
|||||||
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
||||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||||
|
|
||||||
import { getRecordOptimisticEffectDefinition } from '../graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
|
|
||||||
|
|
||||||
import { useFindManyRecords } from './useFindManyRecords';
|
import { useFindManyRecords } from './useFindManyRecords';
|
||||||
|
|
||||||
export const useObjectRecordTable = () => {
|
export const useObjectRecordTable = () => {
|
||||||
@ -25,10 +22,6 @@ export const useObjectRecordTable = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { registerOptimisticEffect } = useOptimisticEffect({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
|
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
|
||||||
|
|
||||||
const tableFilters = useRecoilValue(tableFiltersState);
|
const tableFilters = useRecoilValue(tableFiltersState);
|
||||||
@ -47,25 +40,12 @@ export const useObjectRecordTable = () => {
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
filter,
|
filter,
|
||||||
orderBy,
|
orderBy,
|
||||||
onCompleted: (data) => {
|
|
||||||
const entities = data.edges.map((edge) => edge.node) ?? [];
|
|
||||||
|
|
||||||
setRecordTableData(entities);
|
|
||||||
|
|
||||||
if (foundObjectMetadataItem) {
|
|
||||||
registerOptimisticEffect({
|
|
||||||
variables: { orderBy, filter, limit: 60 },
|
|
||||||
definition: getRecordOptimisticEffectDefinition({
|
|
||||||
objectMetadataItem: foundObjectMetadataItem,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
records,
|
records,
|
||||||
loading,
|
loading,
|
||||||
fetchMoreRecords,
|
fetchMoreRecords,
|
||||||
|
setRecordTableData,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenu
|
|||||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||||
import { RecordTableScopeInternalContext } from '@/ui/object/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
import { RecordTableScopeInternalContext } from '@/ui/object/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||||
import { selectedRowIdsSelector } from '@/ui/object/record-table/states/selectors/selectedRowIdsSelector';
|
import { selectedRowIdsSelector } from '@/ui/object/record-table/states/selectors/selectedRowIdsSelector';
|
||||||
import { tableRowIdsState } from '@/ui/object/record-table/states/tableRowIdsState';
|
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
|
|
||||||
type useRecordTableContextMenuEntriesProps = {
|
type useRecordTableContextMenuEntriesProps = {
|
||||||
@ -31,7 +30,6 @@ export const useRecordTableContextMenuEntries = (
|
|||||||
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
||||||
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
|
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
|
||||||
|
|
||||||
const setTableRowIds = useSetRecoilState(tableRowIdsState);
|
|
||||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
|
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
|
||||||
|
|
||||||
const { scopeId: objectNamePlural, resetTableRowSelection } = useRecordTable({
|
const { scopeId: objectNamePlural, resetTableRowSelection } = useRecordTable({
|
||||||
@ -76,16 +74,11 @@ export const useRecordTableContextMenuEntries = (
|
|||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
|
await Promise.all(
|
||||||
if (deleteOneRecord) {
|
rowIdsToDelete.map(async (rowId) => {
|
||||||
for (const rowId of rowIdsToDelete) {
|
|
||||||
await deleteOneRecord(rowId);
|
await deleteOneRecord(rowId);
|
||||||
}
|
}),
|
||||||
|
);
|
||||||
setTableRowIds((tableRowIds) =>
|
|
||||||
tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
|
|
||||||
import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState';
|
|
||||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
|
||||||
import { isFetchingRecordTableDataState } from '@/ui/object/record-table/states/isFetchingRecordTableDataState';
|
|
||||||
import { numberOfTableRowsState } from '@/ui/object/record-table/states/numberOfTableRowsState';
|
|
||||||
import { tableRowIdsState } from '@/ui/object/record-table/states/tableRowIdsState';
|
|
||||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
|
||||||
|
|
||||||
export const useSetRecordTableData = () => {
|
|
||||||
const { resetTableRowSelection } = useRecordTable();
|
|
||||||
const { setEntityCountInCurrentView } = useViewBar();
|
|
||||||
|
|
||||||
return useRecoilCallback(
|
|
||||||
({ set, snapshot }) =>
|
|
||||||
<T extends { id: string } & Record<string, any>>(newEntityArray: T[]) => {
|
|
||||||
for (const entity of newEntityArray) {
|
|
||||||
const currentEntity = snapshot
|
|
||||||
.getLoadable(entityFieldsFamilyState(entity.id))
|
|
||||||
.valueOrThrow();
|
|
||||||
|
|
||||||
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
|
|
||||||
set(entityFieldsFamilyState(entity.id), entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityIds = newEntityArray.map((entity) => entity.id);
|
|
||||||
|
|
||||||
set(tableRowIdsState, (currentRowIds) => {
|
|
||||||
if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
|
|
||||||
return entityIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentRowIds;
|
|
||||||
});
|
|
||||||
|
|
||||||
resetTableRowSelection();
|
|
||||||
|
|
||||||
set(numberOfTableRowsState, entityIds.length);
|
|
||||||
setEntityCountInCurrentView(entityIds.length);
|
|
||||||
|
|
||||||
set(isFetchingRecordTableDataState, false);
|
|
||||||
},
|
|
||||||
[resetTableRowSelection, setEntityCountInCurrentView],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||||
@ -17,7 +16,7 @@ export const useUpdateOneRecord = <T>({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [mutateUpdateOneRecord] = useMutation(updateOneRecordMutation);
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const updateOneRecord = async ({
|
const updateOneRecord = async ({
|
||||||
idToUpdate,
|
idToUpdate,
|
||||||
@ -30,7 +29,8 @@ export const useUpdateOneRecord = <T>({
|
|||||||
}) => {
|
}) => {
|
||||||
const cachedRecord = getRecordFromCache(idToUpdate);
|
const cachedRecord = getRecordFromCache(idToUpdate);
|
||||||
|
|
||||||
const updatedRecord = await mutateUpdateOneRecord({
|
const updatedRecord = await apolloClient.mutate({
|
||||||
|
mutation: updateOneRecordMutation,
|
||||||
variables: {
|
variables: {
|
||||||
idToUpdate: idToUpdate,
|
idToUpdate: idToUpdate,
|
||||||
input: {
|
input: {
|
||||||
@ -43,12 +43,12 @@ export const useUpdateOneRecord = <T>({
|
|||||||
...input,
|
...input,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: forceRefetch
|
|
||||||
? [getOperationName(findManyRecordsQuery) ?? '']
|
|
||||||
: undefined,
|
|
||||||
awaitRefetchQueries: forceRefetch,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!updatedRecord?.data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return updatedRecord.data[
|
return updatedRecord.data[
|
||||||
`update${capitalize(objectMetadataItem.nameSingular)}`
|
`update${capitalize(objectMetadataItem.nameSingular)}`
|
||||||
] as T;
|
] as T;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export type PaginatedRecordTypeEdge<
|
|||||||
> = {
|
> = {
|
||||||
node: RecordType;
|
node: RecordType;
|
||||||
cursor: string;
|
cursor: string;
|
||||||
|
__typename?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PaginatedRecordTypeResults<
|
export type PaginatedRecordTypeResults<
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { RecordBoardInternalEffect } from '@/ui/object/record-board/components/R
|
|||||||
import { RecordBoardContextMenu } from '@/ui/object/record-board/context-menu/components/RecordBoardContextMenu';
|
import { RecordBoardContextMenu } from '@/ui/object/record-board/context-menu/components/RecordBoardContextMenu';
|
||||||
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
|
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||||
import { useSetRecordBoardCardSelectedInternal } from '@/ui/object/record-board/hooks/internal/useSetRecordBoardCardSelectedInternal';
|
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 { 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';
|
||||||
@ -94,21 +93,14 @@ export const RecordBoard = ({
|
|||||||
callback: unselectAllActiveCards,
|
callback: unselectAllActiveCards,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateBoardCardIds = useUpdateRecordBoardCardIdsInternal({
|
|
||||||
recordBoardScopeId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onDragEnd: OnDragEndResponder = useCallback(
|
const onDragEnd: OnDragEndResponder = useCallback(
|
||||||
async (result) => {
|
async (result) => {
|
||||||
if (!boardColumns) return;
|
if (!boardColumns) return;
|
||||||
|
|
||||||
updateBoardCardIds(result);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const draggedEntityId = result.draggableId;
|
const draggedEntityId = result.draggableId;
|
||||||
const destinationColumnId = result.destination?.droppableId;
|
const destinationColumnId = result.destination?.droppableId;
|
||||||
|
|
||||||
// TODO: abstract
|
|
||||||
if (
|
if (
|
||||||
draggedEntityId &&
|
draggedEntityId &&
|
||||||
destinationColumnId &&
|
destinationColumnId &&
|
||||||
@ -123,7 +115,7 @@ export const RecordBoard = ({
|
|||||||
logError(e);
|
logError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[boardColumns, updatePipelineProgressStageInDB, updateBoardCardIds],
|
[boardColumns, updatePipelineProgressStageInDB],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedBoardColumns = [...boardColumns].sort((a, b) => {
|
const sortedBoardColumns = [...boardColumns].sort((a, b) => {
|
||||||
|
|||||||
@ -1,24 +1,17 @@
|
|||||||
import React, { useState } from 'react';
|
import React 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';
|
||||||
|
|
||||||
import { IconDotsVertical } from '@/ui/display/icon';
|
|
||||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
|
||||||
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 { RecordBoardColumnHeader } from '@/ui/object/record-board/components/RecordBoardColumnHeader';
|
||||||
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 { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
|
||||||
|
|
||||||
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
import { BoardColumnContext } from '../contexts/BoardColumnContext';
|
||||||
import { recordBoardCardIdsByColumnIdFamilyState } from '../states/recordBoardCardIdsByColumnIdFamilyState';
|
import { recordBoardCardIdsByColumnIdFamilyState } from '../states/recordBoardCardIdsByColumnIdFamilyState';
|
||||||
import { recordBoardColumnTotalsFamilySelector } from '../states/selectors/recordBoardColumnTotalsFamilySelector';
|
|
||||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
|
||||||
import { BoardOptions } from '../types/BoardOptions';
|
import { BoardOptions } from '../types/BoardOptions';
|
||||||
|
|
||||||
import { RecordBoardColumnDropdownMenu } from './RecordBoardColumnDropdownMenu';
|
|
||||||
|
|
||||||
const StyledPlaceholder = styled.div`
|
const StyledPlaceholder = styled.div`
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
`;
|
`;
|
||||||
@ -47,40 +40,6 @@ const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
|||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledHeader = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
height: 24px;
|
|
||||||
justify-content: left;
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledAmount = styled.div`
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledNumChildren = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
background-color: ${({ theme }) => theme.background.tertiary};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
display: flex;
|
|
||||||
height: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
|
||||||
margin-left: auto;
|
|
||||||
width: 16px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledHeaderActions = styled.div`
|
|
||||||
display: flex;
|
|
||||||
margin-left: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type BoardColumnCardsContainerProps = {
|
type BoardColumnCardsContainerProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
droppableProvided: DroppableProvided;
|
droppableProvided: DroppableProvided;
|
||||||
@ -119,30 +78,6 @@ export const RecordBoardColumn = ({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onTitleEdit,
|
onTitleEdit,
|
||||||
}: RecordBoardColumnProps) => {
|
}: RecordBoardColumnProps) => {
|
||||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
|
||||||
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
|
||||||
|
|
||||||
const {
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope,
|
|
||||||
goBackToPreviousHotkeyScope,
|
|
||||||
} = usePreviousHotkeyScope();
|
|
||||||
|
|
||||||
const handleBoardColumnMenuOpen = () => {
|
|
||||||
setIsBoardColumnMenuOpen(true);
|
|
||||||
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
|
|
||||||
goto: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBoardColumnMenuClose = () => {
|
|
||||||
goBackToPreviousHotkeyScope();
|
|
||||||
setIsBoardColumnMenuOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const boardColumnTotal = useRecoilValue(
|
|
||||||
recordBoardColumnTotalsFamilySelector(recordBoardColumnId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const cardIds = useRecoilValue(
|
const cardIds = useRecoilValue(
|
||||||
recordBoardCardIdsByColumnIdFamilyState(recordBoardColumnId),
|
recordBoardCardIdsByColumnIdFamilyState(recordBoardColumnId),
|
||||||
);
|
);
|
||||||
@ -165,53 +100,12 @@ export const RecordBoardColumn = ({
|
|||||||
<Droppable droppableId={recordBoardColumnId}>
|
<Droppable droppableId={recordBoardColumnId}>
|
||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||||
<StyledHeader
|
<RecordBoardColumnHeader
|
||||||
onMouseEnter={() => setIsHeaderHovered(true)}
|
recordBoardColumnId={recordBoardColumnId}
|
||||||
onMouseLeave={() => setIsHeaderHovered(false)}
|
columnDefinition={columnDefinition}
|
||||||
>
|
onDelete={onDelete}
|
||||||
<Tag
|
onTitleEdit={handleTitleEdit}
|
||||||
onClick={handleBoardColumnMenuOpen}
|
/>
|
||||||
color={columnDefinition.colorCode ?? 'gray'}
|
|
||||||
text={columnDefinition.title}
|
|
||||||
/>
|
|
||||||
{!!boardColumnTotal && (
|
|
||||||
<StyledAmount>${boardColumnTotal}</StyledAmount>
|
|
||||||
)}
|
|
||||||
{!isHeaderHovered && (
|
|
||||||
<StyledNumChildren>{cardIds.length}</StyledNumChildren>
|
|
||||||
)}
|
|
||||||
{isHeaderHovered && (
|
|
||||||
<StyledHeaderActions>
|
|
||||||
<LightIconButton
|
|
||||||
accent="tertiary"
|
|
||||||
Icon={IconDotsVertical}
|
|
||||||
onClick={handleBoardColumnMenuOpen}
|
|
||||||
/>
|
|
||||||
{/* <LightIconButton
|
|
||||||
accent="tertiary"
|
|
||||||
Icon={IconPlus}
|
|
||||||
onClick={() => {}}
|
|
||||||
/> */}
|
|
||||||
</StyledHeaderActions>
|
|
||||||
)}
|
|
||||||
</StyledHeader>
|
|
||||||
{isBoardColumnMenuOpen && (
|
|
||||||
<RecordBoardColumnDropdownMenu
|
|
||||||
onClose={handleBoardColumnMenuClose}
|
|
||||||
onDelete={onDelete}
|
|
||||||
onTitleEdit={handleTitleEdit}
|
|
||||||
stageId={recordBoardColumnId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isBoardColumnMenuOpen && (
|
|
||||||
<RecordBoardColumnDropdownMenu
|
|
||||||
onClose={handleBoardColumnMenuClose}
|
|
||||||
onDelete={onDelete}
|
|
||||||
onTitleEdit={handleTitleEdit}
|
|
||||||
stageId={recordBoardColumnId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
|
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
|
||||||
{cardIds.map((cardId, index) => (
|
{cardIds.map((cardId, index) => (
|
||||||
<BoardCardIdContext.Provider value={cardId} key={cardId}>
|
<BoardCardIdContext.Provider value={cardId} key={cardId}>
|
||||||
|
|||||||
@ -0,0 +1,136 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { IconDotsVertical } from '@/ui/display/icon';
|
||||||
|
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||||
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
|
import { recordBoardColumnTotalsFamilySelector } from '@/ui/object/record-board/states/selectors/recordBoardColumnTotalsFamilySelector';
|
||||||
|
import { BoardColumnDefinition } from '@/ui/object/record-board/types/BoardColumnDefinition';
|
||||||
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
|
||||||
|
import { recordBoardCardIdsByColumnIdFamilyState } from '../states/recordBoardCardIdsByColumnIdFamilyState';
|
||||||
|
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||||
|
|
||||||
|
import { RecordBoardColumnDropdownMenu } from './RecordBoardColumnDropdownMenu';
|
||||||
|
|
||||||
|
const StyledHeader = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 24px;
|
||||||
|
justify-content: left;
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledAmount = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledNumChildren = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
background-color: ${({ theme }) => theme.background.tertiary};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||||
|
margin-left: auto;
|
||||||
|
width: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledHeaderActions = styled.div`
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type RecordBoardColumnHeaderProps = {
|
||||||
|
recordBoardColumnId: string;
|
||||||
|
columnDefinition: BoardColumnDefinition;
|
||||||
|
onDelete?: (columnId: string) => void;
|
||||||
|
onTitleEdit: (columnId: string, title: string, color: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordBoardColumnHeader = ({
|
||||||
|
recordBoardColumnId,
|
||||||
|
columnDefinition,
|
||||||
|
onDelete,
|
||||||
|
onTitleEdit,
|
||||||
|
}: RecordBoardColumnHeaderProps) => {
|
||||||
|
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
||||||
|
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
|
goBackToPreviousHotkeyScope,
|
||||||
|
} = usePreviousHotkeyScope();
|
||||||
|
|
||||||
|
const handleBoardColumnMenuOpen = () => {
|
||||||
|
setIsBoardColumnMenuOpen(true);
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
|
||||||
|
goto: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBoardColumnMenuClose = () => {
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
setIsBoardColumnMenuOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const boardColumnTotal = useRecoilValue(
|
||||||
|
recordBoardColumnTotalsFamilySelector(recordBoardColumnId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const cardIds = useRecoilValue(
|
||||||
|
recordBoardCardIdsByColumnIdFamilyState(recordBoardColumnId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTitleEdit = (title: string, color: string) => {
|
||||||
|
onTitleEdit(recordBoardColumnId, title, color);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledHeader
|
||||||
|
onMouseEnter={() => setIsHeaderHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHeaderHovered(false)}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
onClick={handleBoardColumnMenuOpen}
|
||||||
|
color={columnDefinition.colorCode ?? 'gray'}
|
||||||
|
text={columnDefinition.title}
|
||||||
|
/>
|
||||||
|
{!!boardColumnTotal && <StyledAmount>${boardColumnTotal}</StyledAmount>}
|
||||||
|
{!isHeaderHovered && (
|
||||||
|
<StyledNumChildren>{cardIds.length}</StyledNumChildren>
|
||||||
|
)}
|
||||||
|
{isHeaderHovered && (
|
||||||
|
<StyledHeaderActions>
|
||||||
|
<LightIconButton
|
||||||
|
accent="tertiary"
|
||||||
|
Icon={IconDotsVertical}
|
||||||
|
onClick={handleBoardColumnMenuOpen}
|
||||||
|
/>
|
||||||
|
{/* <LightIconButton
|
||||||
|
accent="tertiary"
|
||||||
|
Icon={IconPlus}
|
||||||
|
onClick={() => {}}
|
||||||
|
/> */}
|
||||||
|
</StyledHeaderActions>
|
||||||
|
)}
|
||||||
|
</StyledHeader>
|
||||||
|
{isBoardColumnMenuOpen && (
|
||||||
|
<RecordBoardColumnDropdownMenu
|
||||||
|
onClose={handleBoardColumnMenuClose}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onTitleEdit={handleTitleEdit}
|
||||||
|
stageId={recordBoardColumnId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useObjectRecordBoard } from '@/object-record/hooks/useObjectRecordBoard';
|
import { useObjectRecordBoard } from '@/object-record/hooks/useObjectRecordBoard.1';
|
||||||
import { useRecordBoardActionBarEntriesInternal } from '@/ui/object/record-board/hooks/internal/useRecordBoardActionBarEntriesInternal';
|
import { useRecordBoardActionBarEntriesInternal } from '@/ui/object/record-board/hooks/internal/useRecordBoardActionBarEntriesInternal';
|
||||||
import { useRecordBoardContextMenuEntriesInternal } from '@/ui/object/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal';
|
import { useRecordBoardContextMenuEntriesInternal } from '@/ui/object/record-board/hooks/internal/useRecordBoardContextMenuEntriesInternal';
|
||||||
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
|
import { useRecordBoardScopedStates } from '@/ui/object/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||||
@ -18,7 +18,24 @@ export const RecordBoardInternalEffect = ({}) => {
|
|||||||
const { setActionBarEntries } = useRecordBoardActionBarEntriesInternal();
|
const { setActionBarEntries } = useRecordBoardActionBarEntriesInternal();
|
||||||
const { setContextMenuEntries } = useRecordBoardContextMenuEntriesInternal();
|
const { setContextMenuEntries } = useRecordBoardContextMenuEntriesInternal();
|
||||||
|
|
||||||
const { fetchMoreOpportunities, fetchMoreCompanies } = useObjectRecordBoard();
|
const {
|
||||||
|
savedPipelineStepsState,
|
||||||
|
savedOpportunitiesState,
|
||||||
|
savedCompaniesState,
|
||||||
|
} = useRecordBoardScopedStates();
|
||||||
|
|
||||||
|
const { fetchMoreOpportunities, fetchMoreCompanies, opportunities } =
|
||||||
|
useObjectRecordBoard();
|
||||||
|
|
||||||
|
const [savedOpportunities, setSavedOpportunities] = useRecoilState(
|
||||||
|
savedOpportunitiesState,
|
||||||
|
);
|
||||||
|
const savedPipelineSteps = useRecoilValue(savedPipelineStepsState);
|
||||||
|
const savedCompanies = useRecoilValue(savedCompaniesState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSavedOpportunities(opportunities);
|
||||||
|
}, [opportunities, setSavedOpportunities]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDefined(fetchMoreOpportunities)) {
|
if (isDefined(fetchMoreOpportunities)) {
|
||||||
@ -32,16 +49,6 @@ export const RecordBoardInternalEffect = ({}) => {
|
|||||||
}
|
}
|
||||||
}, [fetchMoreCompanies]);
|
}, [fetchMoreCompanies]);
|
||||||
|
|
||||||
const {
|
|
||||||
savedPipelineStepsState,
|
|
||||||
savedOpportunitiesState,
|
|
||||||
savedCompaniesState,
|
|
||||||
} = useRecordBoardScopedStates();
|
|
||||||
|
|
||||||
const savedPipelineSteps = useRecoilValue(savedPipelineStepsState);
|
|
||||||
const savedOpportunities = useRecoilValue(savedOpportunitiesState);
|
|
||||||
const savedCompanies = useRecoilValue(savedCompaniesState);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (savedOpportunities && savedCompanies) {
|
if (savedOpportunities && savedCompanies) {
|
||||||
setActionBarEntries();
|
setActionBarEntries();
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
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],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
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 { 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 { recordBoardCardIdsByColumnIdFamilyState } from '../../states/recordBoardCardIdsByColumnIdFamilyState';
|
|
||||||
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 }) =>
|
|
||||||
(result: DropResult) => {
|
|
||||||
const currentBoardColumns = snapshot
|
|
||||||
.getLoadable(boardColumnsState)
|
|
||||||
.valueOrThrow();
|
|
||||||
|
|
||||||
const newBoardColumns = [...currentBoardColumns];
|
|
||||||
|
|
||||||
const { destination, source } = result;
|
|
||||||
|
|
||||||
if (!destination) return;
|
|
||||||
|
|
||||||
const sourceColumnIndex = newBoardColumns.findIndex(
|
|
||||||
(boardColumn: BoardColumnDefinition) =>
|
|
||||||
boardColumn.id === source.droppableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const sourceColumn = newBoardColumns[sourceColumnIndex];
|
|
||||||
|
|
||||||
const destinationColumnIndex = newBoardColumns.findIndex(
|
|
||||||
(boardColumn: BoardColumnDefinition) =>
|
|
||||||
boardColumn.id === destination.droppableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const destinationColumn = newBoardColumns[destinationColumnIndex];
|
|
||||||
|
|
||||||
if (!destinationColumn || !sourceColumn) return;
|
|
||||||
|
|
||||||
const sourceCardIds = [
|
|
||||||
...snapshot
|
|
||||||
.getLoadable(
|
|
||||||
recordBoardCardIdsByColumnIdFamilyState(sourceColumn.id),
|
|
||||||
)
|
|
||||||
.valueOrThrow(),
|
|
||||||
];
|
|
||||||
|
|
||||||
const destinationCardIds = [
|
|
||||||
...snapshot
|
|
||||||
.getLoadable(
|
|
||||||
recordBoardCardIdsByColumnIdFamilyState(destinationColumn.id),
|
|
||||||
)
|
|
||||||
.valueOrThrow(),
|
|
||||||
];
|
|
||||||
|
|
||||||
const destinationIndex =
|
|
||||||
destination.index >= destinationCardIds.length
|
|
||||||
? destinationCardIds.length - 1
|
|
||||||
: destination.index;
|
|
||||||
|
|
||||||
if (sourceColumn.id === destinationColumn.id) {
|
|
||||||
const [deletedCardId] = sourceCardIds.splice(source.index, 1);
|
|
||||||
|
|
||||||
sourceCardIds.splice(destinationIndex, 0, deletedCardId);
|
|
||||||
|
|
||||||
set(
|
|
||||||
recordBoardCardIdsByColumnIdFamilyState(sourceColumn.id),
|
|
||||||
sourceCardIds,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const [removedCardId] = sourceCardIds.splice(source.index, 1);
|
|
||||||
|
|
||||||
destinationCardIds.splice(destinationIndex, 0, removedCardId);
|
|
||||||
|
|
||||||
set(
|
|
||||||
recordBoardCardIdsByColumnIdFamilyState(sourceColumn.id),
|
|
||||||
sourceCardIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
set(
|
|
||||||
recordBoardCardIdsByColumnIdFamilyState(destinationColumn.id),
|
|
||||||
destinationCardIds,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBoardColumns;
|
|
||||||
},
|
|
||||||
[boardColumnsState],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,9 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
|
||||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
||||||
import {
|
import {
|
||||||
RecordTableRow,
|
RecordTableRow,
|
||||||
@ -11,33 +8,34 @@ import {
|
|||||||
} from '@/ui/object/record-table/components/RecordTableRow';
|
} from '@/ui/object/record-table/components/RecordTableRow';
|
||||||
import { RowIdContext } from '@/ui/object/record-table/contexts/RowIdContext';
|
import { RowIdContext } from '@/ui/object/record-table/contexts/RowIdContext';
|
||||||
import { RowIndexContext } from '@/ui/object/record-table/contexts/RowIndexContext';
|
import { RowIndexContext } from '@/ui/object/record-table/contexts/RowIndexContext';
|
||||||
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||||
import { isFetchingRecordTableDataState } from '@/ui/object/record-table/states/isFetchingRecordTableDataState';
|
import { isFetchingRecordTableDataState } from '@/ui/object/record-table/states/isFetchingRecordTableDataState';
|
||||||
|
import { tableRowIdsState } from '@/ui/object/record-table/states/tableRowIdsState';
|
||||||
import { useRecordTable } from '../hooks/useRecordTable';
|
import { getRecordTableScopedStates } from '@/ui/object/record-table/utils/getRecordTableScopedStates';
|
||||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
|
||||||
|
|
||||||
export const RecordTableBody = () => {
|
export const RecordTableBody = () => {
|
||||||
const { ref: lastTableRowRef, inView: lastTableRowIsVisible } = useInView();
|
const { scopeId } = useRecordTable();
|
||||||
|
|
||||||
|
const onLastRowVisible = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
async (inView: boolean) => {
|
||||||
|
const { tableLastRowVisibleState } = getRecordTableScopedStates({
|
||||||
|
recordTableScopeId: scopeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
set(tableLastRowVisibleState, inView);
|
||||||
|
},
|
||||||
|
[scopeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { ref: lastTableRowRef } = useInView({
|
||||||
|
onChange: onLastRowVisible,
|
||||||
|
});
|
||||||
|
|
||||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||||
|
|
||||||
const { scopeId: objectNamePlural } = useRecordTable();
|
|
||||||
const { tableLastRowVisibleState } = useRecordTableScopedStates();
|
|
||||||
const setTableLastRowVisible = useSetRecoilState(tableLastRowVisibleState);
|
|
||||||
|
|
||||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
|
||||||
objectNamePlural,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
|
||||||
{
|
|
||||||
objectNameSingular,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isFetchingMoreObjects] = useRecoilState(
|
const [isFetchingMoreObjects] = useRecoilState(
|
||||||
isFetchingMoreRecordsFamilyState(foundObjectMetadataItem?.namePlural),
|
isFetchingMoreRecordsFamilyState(scopeId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetchingRecordTableData = useRecoilValue(
|
const isFetchingRecordTableData = useRecoilValue(
|
||||||
@ -45,10 +43,6 @@ export const RecordTableBody = () => {
|
|||||||
);
|
);
|
||||||
const lastRowId = tableRowIds[tableRowIds.length - 1];
|
const lastRowId = tableRowIds[tableRowIds.length - 1];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTableLastRowVisible(lastTableRowIsVisible);
|
|
||||||
}, [lastTableRowIsVisible, setTableLastRowVisible]);
|
|
||||||
|
|
||||||
if (isFetchingRecordTableData) {
|
if (isFetchingRecordTableData) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -60,7 +54,11 @@ export const RecordTableBody = () => {
|
|||||||
<RowIndexContext.Provider value={rowIndex}>
|
<RowIndexContext.Provider value={rowIndex}>
|
||||||
<RecordTableRow
|
<RecordTableRow
|
||||||
key={rowId}
|
key={rowId}
|
||||||
ref={rowId === lastRowId ? lastTableRowRef : undefined}
|
ref={
|
||||||
|
rowId === lastRowId && rowIndex > 30
|
||||||
|
? lastTableRowRef
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
rowId={rowId}
|
rowId={rowId}
|
||||||
/>
|
/>
|
||||||
</RowIndexContext.Provider>
|
</RowIndexContext.Provider>
|
||||||
|
|||||||
@ -6,10 +6,18 @@ import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/inter
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const RecordTableBodyEffect = () => {
|
export const RecordTableBodyEffect = () => {
|
||||||
const { fetchMoreRecords: fetchMoreObjects } = useObjectRecordTable();
|
const {
|
||||||
|
fetchMoreRecords: fetchMoreObjects,
|
||||||
|
records,
|
||||||
|
setRecordTableData,
|
||||||
|
} = useObjectRecordTable();
|
||||||
const { tableLastRowVisibleState } = useRecordTableScopedStates();
|
const { tableLastRowVisibleState } = useRecordTableScopedStates();
|
||||||
const tableLastRowVisible = useRecoilValue(tableLastRowVisibleState);
|
const tableLastRowVisible = useRecoilValue(tableLastRowVisibleState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRecordTableData(records);
|
||||||
|
}, [records, setRecordTableData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tableLastRowVisible && isDefined(fetchMoreObjects)) {
|
if (tableLastRowVisible && isDefined(fetchMoreObjects)) {
|
||||||
fetchMoreObjects();
|
fetchMoreObjects();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState';
|
import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState';
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
import { isFetchingRecordTableDataState } from '../../states/isFetchingRecordTableDataState';
|
import { isFetchingRecordTableDataState } from '../../states/isFetchingRecordTableDataState';
|
||||||
import { numberOfTableRowsState } from '../../states/numberOfTableRowsState';
|
import { numberOfTableRowsState } from '../../states/numberOfTableRowsState';
|
||||||
@ -29,16 +30,13 @@ export const useSetRecordTableData = ({
|
|||||||
set(entityFieldsFamilyState(entity.id), entity);
|
set(entityFieldsFamilyState(entity.id), entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const currentRowIds = snapshot.getLoadable(tableRowIdsState).getValue();
|
||||||
|
|
||||||
const entityIds = newEntityArray.map((entity) => entity.id);
|
const entityIds = newEntityArray.map((entity) => entity.id);
|
||||||
|
|
||||||
set(tableRowIdsState, (currentRowIds) => {
|
if (!isDeeplyEqual(currentRowIds, entityIds)) {
|
||||||
if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
|
set(tableRowIdsState, entityIds);
|
||||||
return entityIds;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return currentRowIds;
|
|
||||||
});
|
|
||||||
|
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
|
|
||||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
|
||||||
|
|
||||||
// Used only in company table and people table
|
|
||||||
// Remove after refactoring
|
|
||||||
|
|
||||||
export const useUpsertTableRowId = () =>
|
|
||||||
useRecoilCallback(
|
|
||||||
({ set, snapshot }) =>
|
|
||||||
(rowId: string) => {
|
|
||||||
const currentRowIds = snapshot
|
|
||||||
.getLoadable(tableRowIdsState)
|
|
||||||
.valueOrThrow();
|
|
||||||
|
|
||||||
set(tableRowIdsState, Array.from(new Set([rowId, ...currentRowIds])));
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
Reference in New Issue
Block a user