Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,73 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useCreateManyRecords = <T>({
|
||||
objectNameSingular,
|
||||
}: ObjectMetadataItemIdentifier) => {
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItem, createManyRecordsMutation } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { generateEmptyRecord } = useGenerateEmptyRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createManyRecords = async (data: Record<string, any>[]) => {
|
||||
const withIds = data.map((record) => ({
|
||||
...record,
|
||||
id: (record.id as string) ?? v4(),
|
||||
}));
|
||||
|
||||
withIds.forEach((record) => {
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
generateEmptyRecord({ id: record.id }),
|
||||
);
|
||||
});
|
||||
|
||||
const createdObjects = await apolloClient.mutate({
|
||||
mutation: createManyRecordsMutation,
|
||||
variables: {
|
||||
data: withIds,
|
||||
},
|
||||
optimisticResponse: {
|
||||
[`create${capitalize(objectMetadataItem.namePlural)}`]: withIds.map(
|
||||
(record) => generateEmptyRecord({ id: record.id }),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
if (!createdObjects.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const createdRecords =
|
||||
(createdObjects.data[
|
||||
`create${capitalize(objectMetadataItem.namePlural)}`
|
||||
] as T[]) ?? [];
|
||||
|
||||
createdRecords.forEach((record) => {
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
record,
|
||||
);
|
||||
});
|
||||
|
||||
return createdRecords;
|
||||
};
|
||||
|
||||
return { createManyRecords };
|
||||
};
|
||||
@ -0,0 +1,83 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useCreateOneRecordProps = {
|
||||
objectNameSingular: string;
|
||||
refetchFindManyQuery?: boolean;
|
||||
};
|
||||
|
||||
export const useCreateOneRecord = <T>({
|
||||
objectNameSingular,
|
||||
refetchFindManyQuery = false,
|
||||
}: useCreateOneRecordProps) => {
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItem, createOneRecordMutation, findManyRecordsQuery } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { generateEmptyRecord } = useGenerateEmptyRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const createOneRecord = async (input: Record<string, any>) => {
|
||||
const recordId = v4();
|
||||
|
||||
const generatedEmptyRecord = generateEmptyRecord({
|
||||
id: recordId,
|
||||
...input,
|
||||
});
|
||||
|
||||
if (generatedEmptyRecord) {
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
generatedEmptyRecord,
|
||||
);
|
||||
}
|
||||
|
||||
const createdObject = await apolloClient.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: { id: recordId, ...input },
|
||||
},
|
||||
optimisticResponse: {
|
||||
[`create${capitalize(objectMetadataItem.nameSingular)}`]:
|
||||
generateEmptyRecord({ id: recordId, ...input }),
|
||||
},
|
||||
refetchQueries: refetchFindManyQuery
|
||||
? [getOperationName(findManyRecordsQuery) ?? '']
|
||||
: [],
|
||||
});
|
||||
|
||||
if (!createdObject.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
createdObject.data[
|
||||
`create${capitalize(objectMetadataItem.nameSingular)}`
|
||||
],
|
||||
);
|
||||
|
||||
return createdObject.data[
|
||||
`create${capitalize(objectMetadataItem.nameSingular)}`
|
||||
] as T;
|
||||
};
|
||||
|
||||
return {
|
||||
createOneRecord,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,73 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useDeleteOneRecordProps = {
|
||||
objectNameSingular: string;
|
||||
refetchFindManyQuery?: boolean;
|
||||
};
|
||||
|
||||
export const useDeleteOneRecord = <T>({
|
||||
objectNameSingular,
|
||||
refetchFindManyQuery = false,
|
||||
}: useDeleteOneRecordProps) => {
|
||||
const { performOptimisticEvict } = useOptimisticEvict();
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItem, deleteOneRecordMutation, findManyRecordsQuery } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const deleteOneRecord = useCallback(
|
||||
async (idToDelete: string) => {
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
undefined,
|
||||
[idToDelete],
|
||||
);
|
||||
|
||||
performOptimisticEvict(
|
||||
capitalize(objectMetadataItem.nameSingular),
|
||||
'id',
|
||||
idToDelete,
|
||||
);
|
||||
|
||||
const deletedRecord = await apolloClient.mutate({
|
||||
mutation: deleteOneRecordMutation,
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
refetchQueries: refetchFindManyQuery
|
||||
? [getOperationName(findManyRecordsQuery) ?? '']
|
||||
: [],
|
||||
});
|
||||
|
||||
return deletedRecord.data[
|
||||
`create${capitalize(objectMetadataItem.nameSingular)}`
|
||||
] as T;
|
||||
},
|
||||
[
|
||||
triggerOptimisticEffects,
|
||||
objectMetadataItem.nameSingular,
|
||||
performOptimisticEvict,
|
||||
apolloClient,
|
||||
deleteOneRecordMutation,
|
||||
refetchFindManyQuery,
|
||||
findManyRecordsQuery,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
deleteOneRecord,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,81 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
|
||||
export const useFieldContext = ({
|
||||
objectNameSingular,
|
||||
fieldMetadataName,
|
||||
objectRecordId,
|
||||
fieldPosition,
|
||||
forceRefetch,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
objectRecordId: string;
|
||||
fieldMetadataName: string;
|
||||
fieldPosition: number;
|
||||
forceRefetch?: boolean;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const fieldMetadataItem = objectMetadataItem?.fields.find(
|
||||
(field) => field.name === fieldMetadataName,
|
||||
);
|
||||
|
||||
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => {
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const updateEntity = ({
|
||||
variables,
|
||||
}: {
|
||||
variables: {
|
||||
where: { id: string };
|
||||
data: {
|
||||
[fieldName: string]: any;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id,
|
||||
input: variables.data,
|
||||
forceRefetch,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
const FieldContextProvider =
|
||||
fieldMetadataItem && objectMetadataItem
|
||||
? ({ children }: { children: ReactNode }) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: fieldPosition,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateEntityMutation: useUpdateOneObjectMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FieldContext.Provider>
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
FieldContextProvider,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,227 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
|
||||
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { logError } from '~/utils/logError';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { cursorFamilyState } from '../states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
|
||||
import { isFetchingMoreRecordsFamilyState } from '../states/isFetchingMoreRecordsFamilyState';
|
||||
import { PaginatedRecordType } from '../types/PaginatedRecordType';
|
||||
import {
|
||||
PaginatedRecordTypeEdge,
|
||||
PaginatedRecordTypeResults,
|
||||
} from '../types/PaginatedRecordTypeResults';
|
||||
import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToRecords';
|
||||
|
||||
export const useFindManyRecords = <
|
||||
RecordType extends { id: string } & Record<string, any>,
|
||||
>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit = DEFAULT_SEARCH_REQUEST_LIMIT,
|
||||
onCompleted,
|
||||
skip,
|
||||
}: ObjectMetadataItemIdentifier & {
|
||||
filter?: any;
|
||||
orderBy?: OrderByField;
|
||||
limit?: number;
|
||||
onCompleted?: (data: PaginatedRecordTypeResults<RecordType>) => void;
|
||||
skip?: boolean;
|
||||
}) => {
|
||||
const findManyQueryStateIdentifier =
|
||||
objectNameSingular +
|
||||
JSON.stringify(filter) +
|
||||
JSON.stringify(orderBy) +
|
||||
limit;
|
||||
|
||||
const [lastCursor, setLastCursor] = useRecoilState(
|
||||
cursorFamilyState(findManyQueryStateIdentifier),
|
||||
);
|
||||
|
||||
const [hasNextPage, setHasNextPage] = useRecoilState(
|
||||
hasNextPageFamilyState(findManyQueryStateIdentifier),
|
||||
);
|
||||
|
||||
const setIsFetchingMoreObjects = useSetRecoilState(
|
||||
isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier),
|
||||
);
|
||||
|
||||
const { objectMetadataItem, findManyRecordsQuery } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { registerOptimisticEffect } = useOptimisticEffect({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
|
||||
const { data, loading, error, fetchMore } = useQuery<
|
||||
PaginatedRecordType<RecordType>
|
||||
>(findManyRecordsQuery, {
|
||||
skip: skip || !objectMetadataItem || !currentWorkspace,
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
limit: limit,
|
||||
orderBy: orderBy ?? {},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (objectMetadataItem) {
|
||||
registerOptimisticEffect({
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
limit: limit,
|
||||
},
|
||||
definition: getRecordOptimisticEffectDefinition({
|
||||
objectMetadataItem,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
onCompleted?.(data[objectMetadataItem.namePlural]);
|
||||
|
||||
if (data?.[objectMetadataItem.namePlural]) {
|
||||
setLastCursor(
|
||||
data?.[objectMetadataItem.namePlural]?.pageInfo.endCursor,
|
||||
);
|
||||
setHasNextPage(
|
||||
data?.[objectMetadataItem.namePlural]?.pageInfo.hasNextPage,
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const fetchMoreRecords = useCallback(async () => {
|
||||
if (hasNextPage) {
|
||||
setIsFetchingMoreObjects(true);
|
||||
|
||||
try {
|
||||
await fetchMore({
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
|
||||
},
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
const previousEdges = prev?.[objectMetadataItem.namePlural]?.edges;
|
||||
const nextEdges =
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
|
||||
|
||||
let newEdges: PaginatedRecordTypeEdge<RecordType>[] = [];
|
||||
|
||||
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
|
||||
newEdges = filterUniqueRecordEdgesByCursor([
|
||||
...prev?.[objectMetadataItem.namePlural]?.edges,
|
||||
...fetchMoreResult?.[objectMetadataItem.namePlural]?.edges,
|
||||
]);
|
||||
}
|
||||
|
||||
if (data?.[objectMetadataItem.namePlural]) {
|
||||
setLastCursor(
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo
|
||||
.endCursor,
|
||||
);
|
||||
setHasNextPage(
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo
|
||||
.hasNextPage,
|
||||
);
|
||||
}
|
||||
|
||||
onCompleted?.({
|
||||
__typename: `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Connection`,
|
||||
edges: newEdges,
|
||||
pageInfo:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
|
||||
});
|
||||
|
||||
return Object.assign({}, prev, {
|
||||
[objectMetadataItem.namePlural]: {
|
||||
__typename: `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Connection`,
|
||||
edges: newEdges,
|
||||
pageInfo:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
|
||||
},
|
||||
} as PaginatedRecordType<RecordType>);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logError(
|
||||
`fetchMoreObjects for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during fetchMoreObjects for "${objectMetadataItem.namePlural}", ${error}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
setIsFetchingMoreObjects(false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
hasNextPage,
|
||||
setIsFetchingMoreObjects,
|
||||
fetchMore,
|
||||
filter,
|
||||
orderBy,
|
||||
lastCursor,
|
||||
objectMetadataItem.namePlural,
|
||||
objectMetadataItem.nameSingular,
|
||||
onCompleted,
|
||||
data,
|
||||
setLastCursor,
|
||||
setHasNextPage,
|
||||
enqueueSnackBar,
|
||||
]);
|
||||
|
||||
const records = useMemo(
|
||||
() =>
|
||||
mapPaginatedRecordsToRecords({
|
||||
pagedRecords: data,
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
}),
|
||||
[data, objectMetadataItem],
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
records: records as RecordType[],
|
||||
loading,
|
||||
error,
|
||||
fetchMoreRecords,
|
||||
queryStateIdentifier: findManyQueryStateIdentifier,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
|
||||
export const useFindOneRecord = <
|
||||
ObjectType extends { id: string } & Record<string, any>,
|
||||
>({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
onCompleted,
|
||||
depth,
|
||||
skip,
|
||||
}: ObjectMetadataItemIdentifier & {
|
||||
objectRecordId: string | undefined;
|
||||
onCompleted?: (data: ObjectType) => void;
|
||||
skip?: boolean;
|
||||
depth?: number;
|
||||
}) => {
|
||||
const { objectMetadataItem, findOneRecordQuery } = useObjectMetadataItem(
|
||||
{
|
||||
objectNameSingular,
|
||||
},
|
||||
depth,
|
||||
);
|
||||
|
||||
const { data, loading, error } = useQuery<
|
||||
{ [nameSingular: string]: ObjectType },
|
||||
{ objectRecordId: string }
|
||||
>(findOneRecordQuery, {
|
||||
skip: !objectMetadataItem || !objectRecordId || skip,
|
||||
variables: {
|
||||
objectRecordId: objectRecordId ?? '',
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (onCompleted) {
|
||||
onCompleted(data[objectNameSingular]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const record = data ? data[objectNameSingular] : undefined;
|
||||
|
||||
return {
|
||||
record,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGenerateCreateManyRecordMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_MUTATION;
|
||||
}
|
||||
|
||||
return gql`
|
||||
mutation Create${capitalize(
|
||||
objectMetadataItem.namePlural,
|
||||
)}($data: [${capitalize(objectMetadataItem.nameSingular)}CreateInput!]!) {
|
||||
create${capitalize(objectMetadataItem.namePlural)}(data: $data) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
}`;
|
||||
};
|
||||
@ -0,0 +1,31 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGenerateCreateOneRecordMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_MUTATION;
|
||||
}
|
||||
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
return gql`
|
||||
mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) {
|
||||
create${capitalizedObjectName}(data: $input) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,169 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const useGenerateEmptyRecord = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
// Todo fix typing once we generate the return base on Metadata
|
||||
const generateEmptyRecord = <T>(input: Partial<T> & { id: string }) => {
|
||||
// Todo replace this by runtime typing
|
||||
const validatedInput = input as { id: string } & { [key: string]: any };
|
||||
|
||||
if (objectMetadataItem.nameSingular === 'company') {
|
||||
return {
|
||||
id: validatedInput.id,
|
||||
domainName: '',
|
||||
accountOwnerId: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
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: new Date().toISOString(),
|
||||
employees: null,
|
||||
accountOwner: null,
|
||||
name: '',
|
||||
linkedinLink: {
|
||||
label: '',
|
||||
url: '',
|
||||
__typename: 'Link',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
__typename: 'FavoriteConnection',
|
||||
},
|
||||
opportunities: {
|
||||
edges: [],
|
||||
__typename: 'OpportunityConnection',
|
||||
},
|
||||
__typename: 'Company',
|
||||
} as T;
|
||||
}
|
||||
|
||||
if (objectMetadataItem.nameSingular === 'person') {
|
||||
return {
|
||||
id: validatedInput.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: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
city: '',
|
||||
linkedinLink: {
|
||||
label: '',
|
||||
url: '',
|
||||
__typename: 'Link',
|
||||
},
|
||||
__typename: 'Person',
|
||||
} as T;
|
||||
}
|
||||
|
||||
if (objectMetadataItem.nameSingular === 'opportunity') {
|
||||
return {
|
||||
id: validatedInput.id,
|
||||
pipelineStepId: validatedInput.pipelineStepId,
|
||||
closeDate: null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
pipelineStep: null,
|
||||
probability: '0',
|
||||
pointOfContactId: null,
|
||||
personId: null,
|
||||
amount: {
|
||||
amountMicros: null,
|
||||
currencyCode: null,
|
||||
__typename: 'Currency',
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
pointOfContact: null,
|
||||
person: null,
|
||||
company: null,
|
||||
companyId: validatedInput.companyId,
|
||||
__typename: 'Opportunity',
|
||||
} as T;
|
||||
}
|
||||
|
||||
if (objectMetadataItem.nameSingular === 'opportunity') {
|
||||
return {
|
||||
id: validatedInput.id,
|
||||
pipelineStepId: validatedInput.pipelineStepId,
|
||||
closeDate: null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
pipelineStep: null,
|
||||
probability: '0',
|
||||
pointOfContactId: null,
|
||||
personId: null,
|
||||
amount: {
|
||||
amountMicros: null,
|
||||
currencyCode: null,
|
||||
__typename: 'Currency',
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
pointOfContact: null,
|
||||
person: null,
|
||||
company: null,
|
||||
companyId: validatedInput.companyId,
|
||||
__typename: 'Opportunity',
|
||||
} as T;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
generateEmptyRecord: generateEmptyRecord,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_QUERY } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGenerateFindManyRecordsQuery = ({
|
||||
objectMetadataItem,
|
||||
depth,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
depth?: number;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_QUERY;
|
||||
}
|
||||
|
||||
return gql`
|
||||
query FindMany${capitalize(
|
||||
objectMetadataItem.namePlural,
|
||||
)}($filter: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput, $orderBy: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}OrderByInput, $lastCursor: String, $limit: Float = 30) {
|
||||
${
|
||||
objectMetadataItem.namePlural
|
||||
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field, depth))
|
||||
.join('\n')}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_QUERY } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const useGenerateFindOneRecordQuery = ({
|
||||
objectMetadataItem,
|
||||
depth,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
depth?: number;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_QUERY;
|
||||
}
|
||||
|
||||
return gql`
|
||||
query FindOne${objectMetadataItem.nameSingular}($objectRecordId: UUID!) {
|
||||
${objectMetadataItem.nameSingular}(filter: {
|
||||
id: {
|
||||
eq: $objectRecordId
|
||||
}
|
||||
}){
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field, depth))
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getUpdateOneRecordMutationGraphQLField = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `update${capitalize(objectNameSingular)}`;
|
||||
};
|
||||
|
||||
export const useGenerateUpdateOneRecordMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_MUTATION;
|
||||
}
|
||||
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
const graphQLFieldForUpdateOneRecordMutation =
|
||||
getUpdateOneRecordMutationGraphQLField({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
return gql`
|
||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||
${graphQLFieldForUpdateOneRecordMutation}(id: $idToUpdate, data: $input) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { gql, useApolloClient } from '@apollo/client';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useGetRecordFromCache = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
return (recordId: string) => {
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_MUTATION;
|
||||
}
|
||||
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
const cacheReadFragment = gql`
|
||||
fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
`;
|
||||
|
||||
const cache = apolloClient.cache;
|
||||
const cachedRecordId = cache.identify({
|
||||
__typename: capitalize(objectMetadataItem.nameSingular),
|
||||
id: recordId,
|
||||
});
|
||||
|
||||
return cache.readFragment({
|
||||
id: cachedRecordId,
|
||||
fragment: cacheReadFragment,
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,31 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { Modifiers } from '@apollo/client/cache';
|
||||
|
||||
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useModifyRecordFromCache = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
return (recordId: string, fieldModifiers: Modifiers) => {
|
||||
if (!objectMetadataItem) {
|
||||
return EMPTY_MUTATION;
|
||||
}
|
||||
|
||||
const cache = apolloClient.cache;
|
||||
const cachedRecordId = cache.identify({
|
||||
__typename: capitalize(objectMetadataItem.nameSingular),
|
||||
id: recordId,
|
||||
});
|
||||
|
||||
cache.modify({
|
||||
id: cachedRecordId,
|
||||
fields: fieldModifiers,
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -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 { turnFiltersIntoWhereClause } from '@/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
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 as any, // TODO: finish typing
|
||||
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,
|
||||
};
|
||||
};
|
||||
@ -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 { turnFiltersIntoWhereClause } from '@/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
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 as any, // TODO: finish typing
|
||||
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,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,62 @@
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { turnFiltersIntoWhereClause } from '@/object-record/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { signInBackgroundMockCompanies } from '@/sign-in-background-mock/constants/signInBackgroundMockCompanies';
|
||||
|
||||
import { useFindManyRecords } from './useFindManyRecords';
|
||||
|
||||
export const useObjectRecordTable = () => {
|
||||
const { scopeId: objectNamePlural, setRecordTableData } = useRecordTable();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
||||
{
|
||||
objectNameSingular,
|
||||
},
|
||||
);
|
||||
const { tableFiltersState, tableSortsState, tableLastRowVisibleState } =
|
||||
useRecordTableScopedStates();
|
||||
|
||||
const tableFilters = useRecoilValue(tableFiltersState);
|
||||
const tableSorts = useRecoilValue(tableSortsState);
|
||||
const setLastRowVisible = useSetRecoilState(tableLastRowVisibleState);
|
||||
|
||||
const filter = turnFiltersIntoWhereClause(
|
||||
tableFilters,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
// TODO: finish typing
|
||||
const orderBy = turnSortsIntoOrderBy(
|
||||
tableSorts,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
) as any;
|
||||
|
||||
const { records, loading, fetchMoreRecords, queryStateIdentifier } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
onCompleted: () => {
|
||||
setLastRowVisible(false);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
records: currentWorkspace ? records : signInBackgroundMockCompanies,
|
||||
loading,
|
||||
fetchMoreRecords,
|
||||
queryStateIdentifier,
|
||||
setRecordTableData,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,155 @@
|
||||
import { useCallback } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||
import { selectedRowIdsSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsSelector';
|
||||
import { IconHeart, IconHeartOff, IconTrash } from '@/ui/display/icon';
|
||||
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
||||
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
||||
import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
|
||||
type useRecordTableContextMenuEntriesProps = {
|
||||
recordTableScopeId?: string;
|
||||
};
|
||||
|
||||
// TODO: refactor this
|
||||
export const useRecordTableContextMenuEntries = (
|
||||
props?: useRecordTableContextMenuEntriesProps,
|
||||
) => {
|
||||
const scopeId = useAvailableScopeIdOrThrow(
|
||||
RecordTableScopeInternalContext,
|
||||
props?.recordTableScopeId,
|
||||
);
|
||||
|
||||
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
||||
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
|
||||
|
||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
|
||||
|
||||
const { scopeId: objectNamePlural, resetTableRowSelection } = useRecordTable({
|
||||
recordTableScopeId: scopeId,
|
||||
});
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { createFavorite, deleteFavorite, favorites } = useFavorites({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
|
||||
const selectedRowIds = snapshot
|
||||
.getLoadable(selectedRowIdsSelector)
|
||||
.getValue();
|
||||
|
||||
const selectedRowId = selectedRowIds.length === 1 ? selectedRowIds[0] : '';
|
||||
|
||||
const isFavorite =
|
||||
!!selectedRowId &&
|
||||
!!favorites?.find((favorite) => favorite.recordId === selectedRowId);
|
||||
|
||||
resetTableRowSelection();
|
||||
|
||||
if (isFavorite) {
|
||||
deleteFavorite(selectedRowId);
|
||||
} else {
|
||||
createFavorite(selectedRowId);
|
||||
}
|
||||
});
|
||||
|
||||
const { deleteOneRecord } = useDeleteOneRecord({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const handleDeleteClick = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async () => {
|
||||
const rowIdsToDelete = snapshot
|
||||
.getLoadable(selectedRowIdsSelector)
|
||||
.getValue();
|
||||
|
||||
resetTableRowSelection();
|
||||
await Promise.all(
|
||||
rowIdsToDelete.map(async (rowId) => {
|
||||
await deleteOneRecord(rowId);
|
||||
}),
|
||||
);
|
||||
},
|
||||
[deleteOneRecord, resetTableRowSelection],
|
||||
);
|
||||
|
||||
return {
|
||||
setContextMenuEntries: useCallback(() => {
|
||||
const selectedRowId =
|
||||
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
|
||||
|
||||
const isFavorite =
|
||||
isNonEmptyString(selectedRowId) &&
|
||||
!!favorites?.find((favorite) => favorite.recordId === selectedRowId);
|
||||
|
||||
const contextMenuEntries = [
|
||||
// {
|
||||
// label: 'New task',
|
||||
// Icon: IconCheckbox,
|
||||
// onClick: () => {},
|
||||
// },
|
||||
// {
|
||||
// label: 'New note',
|
||||
// Icon: IconNotes,
|
||||
// onClick: () => {},
|
||||
// },
|
||||
|
||||
{
|
||||
label: 'Delete',
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
onClick: () => handleDeleteClick(),
|
||||
},
|
||||
] as ContextMenuEntry[];
|
||||
|
||||
if (selectedRowIds.length === 1) {
|
||||
contextMenuEntries.unshift({
|
||||
label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
|
||||
Icon: isFavorite ? IconHeartOff : IconHeart,
|
||||
onClick: () => handleFavoriteButtonClick(),
|
||||
});
|
||||
}
|
||||
|
||||
setContextMenuEntries(contextMenuEntries);
|
||||
}, [
|
||||
selectedRowIds,
|
||||
favorites,
|
||||
handleDeleteClick,
|
||||
handleFavoriteButtonClick,
|
||||
setContextMenuEntries,
|
||||
]),
|
||||
|
||||
setActionBarEntries: useRecoilCallback(() => () => {
|
||||
setActionBarEntriesState([
|
||||
// {
|
||||
// label: 'Task',
|
||||
// Icon: IconCheckbox,
|
||||
// onClick: () => {},
|
||||
// },
|
||||
// {
|
||||
// label: 'Note',
|
||||
// Icon: IconNotes,
|
||||
// onClick: () => {},
|
||||
// },
|
||||
{
|
||||
label: 'Delete',
|
||||
Icon: IconTrash,
|
||||
accent: 'danger',
|
||||
onClick: () => handleDeleteClick(),
|
||||
},
|
||||
]);
|
||||
}),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,68 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useUpdateOneRecordProps = {
|
||||
objectNameSingular: string;
|
||||
refetchFindManyQuery?: boolean;
|
||||
};
|
||||
|
||||
export const useUpdateOneRecord = <T>({
|
||||
objectNameSingular,
|
||||
refetchFindManyQuery = false,
|
||||
}: useUpdateOneRecordProps) => {
|
||||
const {
|
||||
objectMetadataItem,
|
||||
updateOneRecordMutation,
|
||||
getRecordFromCache,
|
||||
findManyRecordsQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const updateOneRecord = async ({
|
||||
idToUpdate,
|
||||
input,
|
||||
}: {
|
||||
idToUpdate: string;
|
||||
input: Record<string, any>;
|
||||
forceRefetch?: boolean;
|
||||
}) => {
|
||||
const cachedRecord = getRecordFromCache(idToUpdate);
|
||||
|
||||
const updatedRecord = await apolloClient.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate: idToUpdate,
|
||||
input: {
|
||||
...input,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
[`update${capitalize(objectMetadataItem.nameSingular)}`]: {
|
||||
...(cachedRecord ?? {}),
|
||||
...input,
|
||||
},
|
||||
},
|
||||
refetchQueries: refetchFindManyQuery
|
||||
? [getOperationName(findManyRecordsQuery) ?? '']
|
||||
: [],
|
||||
});
|
||||
|
||||
if (!updatedRecord?.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return updatedRecord.data[
|
||||
`update${capitalize(objectMetadataItem.nameSingular)}`
|
||||
] as T;
|
||||
};
|
||||
|
||||
return {
|
||||
updateOneRecord,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user