Split components into object-metadata and object-record (#2425)
* Split components into object-metadata and object-record * Fix seed
This commit is contained in:
164
front/src/modules/object-record/components/RecordShowPage.tsx
Normal file
164
front/src/modules/object-record/components/RecordShowPage.tsx
Normal file
@ -0,0 +1,164 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
||||
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||
import { FieldContext } from '@/ui/object/field/contexts/FieldContext';
|
||||
import { entityFieldsFamilyState } from '@/ui/object/field/states/entityFieldsFamilyState';
|
||||
import { RecordInlineCell } from '@/ui/object/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/ui/object/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
|
||||
import { useFindOneObjectRecord } from '../hooks/useFindOneObjectRecord';
|
||||
import { useUpdateOneObjectRecord } from '../hooks/useUpdateOneObjectRecord';
|
||||
|
||||
export const RecordShowPage = () => {
|
||||
const { objectNameSingular, objectMetadataId } = useParams<{
|
||||
objectNameSingular: string;
|
||||
objectMetadataId: string;
|
||||
}>();
|
||||
|
||||
const { icons } = useLazyLoadIcons();
|
||||
|
||||
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const [, setEntityFields] = useRecoilState(
|
||||
entityFieldsFamilyState(objectMetadataId ?? ''),
|
||||
);
|
||||
|
||||
const { object } = useFindOneObjectRecord({
|
||||
objectMetadataId: objectMetadataId,
|
||||
objectNameSingular,
|
||||
onCompleted: (data) => {
|
||||
setEntityFields(data);
|
||||
},
|
||||
});
|
||||
|
||||
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => {
|
||||
const { updateOneObject } = useUpdateOneObjectRecord({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const updateEntity = ({
|
||||
variables,
|
||||
}: {
|
||||
variables: {
|
||||
where: { id: string };
|
||||
data: {
|
||||
[fieldName: string]: any;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
updateOneObject?.({
|
||||
idToUpdate: variables.where.id,
|
||||
input: variables.data,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
const handleFavoriteButtonClick = async () => {
|
||||
//
|
||||
};
|
||||
|
||||
if (!object) return <></>;
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitle title={object.name || 'No Name'} />
|
||||
<PageHeader
|
||||
title={object.name ?? ''}
|
||||
hasBackButton
|
||||
Icon={IconBuildingSkyscraper}
|
||||
>
|
||||
<PageFavoriteButton
|
||||
isFavorite={false}
|
||||
onClick={handleFavoriteButtonClick}
|
||||
/>
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: object.id,
|
||||
type: ActivityTargetableEntityType.Company,
|
||||
}}
|
||||
/>
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={object.id}
|
||||
logoOrAvatar={''}
|
||||
title={object.name ?? 'No name'}
|
||||
date={object.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => <></>}
|
||||
avatarType="squared"
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
{foundObjectMetadataItem &&
|
||||
[...foundObjectMetadataItem.fields]
|
||||
.sort((a, b) =>
|
||||
DateTime.fromISO(a.createdAt)
|
||||
.diff(DateTime.fromISO(b.createdAt))
|
||||
.toMillis(),
|
||||
)
|
||||
.map((metadataField, index) => {
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
key={object.id + metadataField.id}
|
||||
value={{
|
||||
entityId: object.id,
|
||||
recoilScopeId: object.id + metadataField.id,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: metadataField,
|
||||
position: index,
|
||||
objectMetadataItem: foundObjectMetadataItem,
|
||||
icons,
|
||||
}),
|
||||
useUpdateEntityMutation: useUpdateOneObjectMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
})}
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
entity={{
|
||||
id: object.id,
|
||||
type: ActivityTargetableEntityType.Company,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,107 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
|
||||
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { TableOptionsDropdown } from '@/ui/object/record-table/options/components/TableOptionsDropdown';
|
||||
import { RecordTableScope } from '@/ui/object/record-table/scopes/RecordTableScope';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { useViewFields } from '@/views/hooks/internal/useViewFields';
|
||||
import { useView } from '@/views/hooks/useView';
|
||||
import { ViewScope } from '@/views/scopes/ViewScope';
|
||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||
|
||||
import { useUpdateOneObjectRecord } from '../hooks/useUpdateOneObjectRecord';
|
||||
|
||||
import { RecordTableEffect } from './RecordTableEffect';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
export const RecordTableContainer = ({
|
||||
objectNamePlural,
|
||||
}: {
|
||||
objectNamePlural: string;
|
||||
}) => {
|
||||
const { columnDefinitions, foundObjectMetadataItem } =
|
||||
useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { updateOneObject } = useUpdateOneObjectRecord({
|
||||
objectNamePlural,
|
||||
objectNameSingular: foundObjectMetadataItem?.nameSingular,
|
||||
});
|
||||
|
||||
const tableScopeId = objectNamePlural ?? '';
|
||||
const viewScopeId = objectNamePlural ?? '';
|
||||
|
||||
const { persistViewFields } = useViewFields(viewScopeId);
|
||||
|
||||
const { setTableFilters, setTableSorts, setTableColumns } = useRecordTable({
|
||||
recordTableScopeId: tableScopeId,
|
||||
});
|
||||
|
||||
const { setEntityCountInCurrentView } = useView({ viewScopeId });
|
||||
|
||||
const updateEntity = ({
|
||||
variables,
|
||||
}: {
|
||||
variables: {
|
||||
where: { id: string };
|
||||
data: {
|
||||
[fieldName: string]: any;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
updateOneObject?.({
|
||||
idToUpdate: variables.where.id,
|
||||
input: variables.data,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewScope
|
||||
viewScopeId={viewScopeId}
|
||||
onViewFieldsChange={(viewFields) => {
|
||||
setTableColumns(
|
||||
mapViewFieldsToColumnDefinitions(viewFields, columnDefinitions),
|
||||
);
|
||||
}}
|
||||
onViewFiltersChange={(viewFilters) => {
|
||||
setTableFilters(mapViewFiltersToFilters(viewFilters));
|
||||
}}
|
||||
onViewSortsChange={(viewSorts) => {
|
||||
setTableSorts(mapViewSortsToSorts(viewSorts));
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<RecordTableScope
|
||||
recordTableScopeId={tableScopeId}
|
||||
onColumnsChange={useRecoilCallback(() => (columns) => {
|
||||
persistViewFields(mapColumnDefinitionsToViewFields(columns));
|
||||
})}
|
||||
onEntityCountChange={(entityCount) => {
|
||||
setEntityCountInCurrentView(entityCount);
|
||||
}}
|
||||
>
|
||||
<ViewBar
|
||||
optionsDropdownButton={<TableOptionsDropdown />}
|
||||
optionsDropdownScopeId={TableOptionsDropdownId}
|
||||
/>
|
||||
<RecordTableEffect />
|
||||
<RecordTable updateEntityMutation={updateEntity} />
|
||||
</RecordTableScope>
|
||||
</StyledContainer>
|
||||
</ViewScope>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,57 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { useView } from '@/views/hooks/useView';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
export const RecordTableEffect = () => {
|
||||
const { scopeId: objectNamePlural, setAvailableTableColumns } =
|
||||
useRecordTable();
|
||||
|
||||
const {
|
||||
columnDefinitions,
|
||||
filterDefinitions,
|
||||
sortDefinitions,
|
||||
foundObjectMetadataItem,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const {
|
||||
setAvailableSortDefinitions,
|
||||
setAvailableFilterDefinitions,
|
||||
setAvailableFieldDefinitions,
|
||||
setViewType,
|
||||
setViewObjectMetadataId,
|
||||
} = useView();
|
||||
|
||||
useRecordTable();
|
||||
|
||||
useEffect(() => {
|
||||
if (!foundObjectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
setViewObjectMetadataId?.(foundObjectMetadataItem.id);
|
||||
setViewType?.(ViewType.Table);
|
||||
|
||||
setAvailableSortDefinitions?.(sortDefinitions);
|
||||
setAvailableFilterDefinitions?.(filterDefinitions);
|
||||
setAvailableFieldDefinitions?.(columnDefinitions);
|
||||
|
||||
setAvailableTableColumns(columnDefinitions);
|
||||
}, [
|
||||
setViewObjectMetadataId,
|
||||
setViewType,
|
||||
columnDefinitions,
|
||||
setAvailableSortDefinitions,
|
||||
setAvailableFilterDefinitions,
|
||||
setAvailableFieldDefinitions,
|
||||
foundObjectMetadataItem,
|
||||
sortDefinitions,
|
||||
filterDefinitions,
|
||||
setAvailableTableColumns,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,68 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
|
||||
import { RecordTableActionBar } from '@/ui/object/record-table/action-bar/components/RecordTableActionBar';
|
||||
import { RecordTableContextMenu } from '@/ui/object/record-table/context-menu/components/RecordTableContextMenu';
|
||||
|
||||
import { useCreateOneObjectRecord } from '../hooks/useCreateOneObjectRecord';
|
||||
|
||||
import { RecordTableContainer } from './RecordTableContainer';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export type RecordTablePageProps = Pick<
|
||||
ObjectMetadataItemIdentifier,
|
||||
'objectNamePlural'
|
||||
>;
|
||||
|
||||
export const RecordTablePage = () => {
|
||||
const objectNamePlural = useParams().objectNamePlural ?? '';
|
||||
|
||||
const { objectNotFoundInMetadata, loading } = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && objectNotFoundInMetadata) {
|
||||
navigate('/');
|
||||
}
|
||||
}, [objectNotFoundInMetadata, loading, navigate]);
|
||||
|
||||
const { createOneObject } = useCreateOneObjectRecord({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const handleAddButtonClick = async () => {
|
||||
createOneObject?.({});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader title="Objects" Icon={IconBuildingSkyscraper}>
|
||||
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
|
||||
<PageAddButton onClick={handleAddButtonClick} />
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<StyledTableContainer>
|
||||
<RecordTableContainer objectNamePlural={objectNamePlural} />
|
||||
</StyledTableContainer>
|
||||
<RecordTableActionBar />
|
||||
<RecordTableContextMenu />
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { OptimisticEffectDefinition } from '@/apollo/optimistic-effect/types/OptimisticEffectDefinition';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjectTypeResults';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getRecordOptimisticEffectDefinition = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) =>
|
||||
({
|
||||
key: `record-create-optimistic-effect-definition-${objectMetadataItem.nameSingular}`,
|
||||
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
resolver: ({
|
||||
currentData,
|
||||
newData,
|
||||
}: {
|
||||
currentData: unknown;
|
||||
newData: unknown;
|
||||
}) => {
|
||||
const newRecordPaginatedCacheField = produce<
|
||||
PaginatedObjectTypeResults<any>
|
||||
>(currentData as PaginatedObjectTypeResults<any>, (draft) => {
|
||||
draft.edges.unshift({ node: newData, cursor: '' });
|
||||
});
|
||||
|
||||
return newRecordPaginatedCacheField;
|
||||
},
|
||||
isUsingFlexibleBackend: true,
|
||||
objectMetadataItem,
|
||||
} satisfies OptimisticEffectDefinition);
|
||||
@ -0,0 +1,69 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { Currency, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
const defaultFieldValues: Record<FieldMetadataType, unknown> = {
|
||||
[FieldMetadataType.Money]: { amount: null, currency: Currency.Usd },
|
||||
[FieldMetadataType.Boolean]: false,
|
||||
[FieldMetadataType.Date]: null,
|
||||
[FieldMetadataType.Email]: '',
|
||||
[FieldMetadataType.Enum]: null,
|
||||
[FieldMetadataType.Number]: null,
|
||||
[FieldMetadataType.Relation]: null,
|
||||
[FieldMetadataType.Phone]: '',
|
||||
[FieldMetadataType.Text]: '',
|
||||
[FieldMetadataType.Url]: { link: '', text: '' },
|
||||
[FieldMetadataType.Uuid]: '',
|
||||
};
|
||||
|
||||
export const useCreateOneObjectRecord = ({
|
||||
objectNamePlural,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'>) => {
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect();
|
||||
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
objectNotFoundInMetadata,
|
||||
createOneMutation,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const [mutate] = useMutation(createOneMutation);
|
||||
|
||||
const createOneObject = foundObjectMetadataItem
|
||||
? async (input: Record<string, any>) => {
|
||||
const createdObject = await mutate({
|
||||
variables: {
|
||||
input: {
|
||||
...foundObjectMetadataItem.fields.reduce(
|
||||
(result, field) => ({
|
||||
...result,
|
||||
[field.name]: defaultFieldValues[field.type],
|
||||
}),
|
||||
{},
|
||||
),
|
||||
...input,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(foundObjectMetadataItem.nameSingular)}Edge`,
|
||||
createdObject.data[
|
||||
`create${capitalize(foundObjectMetadataItem.nameSingular)}`
|
||||
],
|
||||
);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
createOneObject,
|
||||
objectNotFoundInMetadata,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
|
||||
export const useDeleteOneObjectRecord = ({
|
||||
objectNamePlural,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'>) => {
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
objectNotFoundInMetadata,
|
||||
findManyQuery,
|
||||
deleteOneMutation,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const [mutate] = useMutation(deleteOneMutation);
|
||||
|
||||
const deleteOneObject = foundObjectMetadataItem
|
||||
? (input: Record<string, any>) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
deleteOneObject,
|
||||
objectNotFoundInMetadata,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,181 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
|
||||
import { logError } from '~/utils/logError';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { cursorFamilyState } from '../states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
|
||||
import { isFetchingMoreObjectsFamilyState } from '../states/isFetchingMoreObjectsFamilyState';
|
||||
import { PaginatedObjectType } from '../types/PaginatedObjectType';
|
||||
import {
|
||||
PaginatedObjectTypeEdge,
|
||||
PaginatedObjectTypeResults,
|
||||
} from '../types/PaginatedObjectTypeResults';
|
||||
import { formatPagedObjectsToObjects } from '../utils/formatPagedObjectsToObjects';
|
||||
|
||||
// TODO: test with a wrong name
|
||||
// TODO: add zod to validate that we have at least id on each object
|
||||
export const useFindManyObjectRecords = <
|
||||
ObjectType extends { id: string } & Record<string, any>,
|
||||
>({
|
||||
objectNamePlural,
|
||||
filter,
|
||||
orderBy,
|
||||
onCompleted,
|
||||
skip,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'> & {
|
||||
filter?: any;
|
||||
orderBy?: any;
|
||||
onCompleted?: (data: PaginatedObjectTypeResults<ObjectType>) => void;
|
||||
skip?: boolean;
|
||||
}) => {
|
||||
const [lastCursor, setLastCursor] = useRecoilState(
|
||||
cursorFamilyState(objectNamePlural),
|
||||
);
|
||||
|
||||
const [hasNextPage, setHasNextPage] = useRecoilState(
|
||||
hasNextPageFamilyState(objectNamePlural),
|
||||
);
|
||||
|
||||
const [, setIsFetchingMoreObjects] = useRecoilState(
|
||||
isFetchingMoreObjectsFamilyState(objectNamePlural),
|
||||
);
|
||||
|
||||
const { foundObjectMetadataItem, objectNotFoundInMetadata, findManyQuery } =
|
||||
useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
skip,
|
||||
});
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const { data, loading, error, fetchMore } = useQuery<
|
||||
PaginatedObjectType<ObjectType>
|
||||
>(findManyQuery, {
|
||||
skip: skip || !foundObjectMetadataItem || !objectNamePlural,
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (objectNamePlural) {
|
||||
onCompleted?.(data[objectNamePlural]);
|
||||
|
||||
if (objectNamePlural && data?.[objectNamePlural]) {
|
||||
setLastCursor(data?.[objectNamePlural]?.pageInfo.endCursor);
|
||||
setHasNextPage(data?.[objectNamePlural]?.pageInfo.hasNextPage);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useFindManyObjectRecords for "${objectNamePlural}" error : ` + error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyObjectRecords for "${objectNamePlural}", ${error.message}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const fetchMoreObjects = useCallback(async () => {
|
||||
if (objectNamePlural && hasNextPage) {
|
||||
setIsFetchingMoreObjects(true);
|
||||
|
||||
try {
|
||||
await fetchMore({
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
|
||||
},
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
const uniqueByCursor = (
|
||||
a: PaginatedObjectTypeEdge<ObjectType>[],
|
||||
) => {
|
||||
const seenCursors = new Set();
|
||||
|
||||
return a.filter((item) => {
|
||||
const currentCursor = item.cursor;
|
||||
|
||||
return seenCursors.has(currentCursor)
|
||||
? false
|
||||
: seenCursors.add(currentCursor);
|
||||
});
|
||||
};
|
||||
|
||||
const previousEdges = prev?.[objectNamePlural]?.edges;
|
||||
const nextEdges = fetchMoreResult?.[objectNamePlural]?.edges;
|
||||
|
||||
let newEdges: any[] = [];
|
||||
|
||||
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
|
||||
newEdges = uniqueByCursor([
|
||||
...prev?.[objectNamePlural]?.edges,
|
||||
...fetchMoreResult?.[objectNamePlural]?.edges,
|
||||
]);
|
||||
}
|
||||
|
||||
return Object.assign({}, prev, {
|
||||
[objectNamePlural]: {
|
||||
__typename: `${capitalize(
|
||||
foundObjectMetadataItem?.nameSingular ?? '',
|
||||
)}Connection`,
|
||||
edges: newEdges,
|
||||
pageInfo: fetchMoreResult?.[objectNamePlural].pageInfo,
|
||||
},
|
||||
} as PaginatedObjectType<ObjectType>);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logError(`fetchMoreObjects for "${objectNamePlural}" error : ` + error);
|
||||
enqueueSnackBar(
|
||||
`Error during fetchMoreObjects for "${objectNamePlural}", ${error}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
setIsFetchingMoreObjects(false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
objectNamePlural,
|
||||
lastCursor,
|
||||
fetchMore,
|
||||
filter,
|
||||
orderBy,
|
||||
foundObjectMetadataItem,
|
||||
hasNextPage,
|
||||
setIsFetchingMoreObjects,
|
||||
enqueueSnackBar,
|
||||
]);
|
||||
|
||||
const objects = useMemo(
|
||||
() =>
|
||||
objectNamePlural
|
||||
? formatPagedObjectsToObjects({
|
||||
pagedObjects: data,
|
||||
objectNamePlural,
|
||||
})
|
||||
: [],
|
||||
[data, objectNamePlural],
|
||||
);
|
||||
|
||||
return {
|
||||
objects,
|
||||
loading,
|
||||
error,
|
||||
objectNotFoundInMetadata,
|
||||
fetchMoreObjects,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,45 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
|
||||
export const useFindOneObjectRecord = <
|
||||
ObjectType extends { id: string } & Record<string, any>,
|
||||
>({
|
||||
objectNameSingular,
|
||||
objectMetadataId,
|
||||
onCompleted,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'> & {
|
||||
objectMetadataId: string | undefined;
|
||||
onCompleted?: (data: ObjectType) => void;
|
||||
}) => {
|
||||
const { foundObjectMetadataItem, objectNotFoundInMetadata, findOneQuery } =
|
||||
useFindOneObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { data, loading, error } = useQuery<
|
||||
{ [nameSingular: string]: ObjectType },
|
||||
{ objectMetadataId: string }
|
||||
>(findOneQuery, {
|
||||
skip: !foundObjectMetadataItem || !objectMetadataId,
|
||||
variables: {
|
||||
objectMetadataId: objectMetadataId ?? '',
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (onCompleted && objectNameSingular) {
|
||||
onCompleted(data[objectNameSingular]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const object =
|
||||
objectNameSingular && data ? data[objectNameSingular] : undefined;
|
||||
|
||||
return {
|
||||
object,
|
||||
loading,
|
||||
error,
|
||||
objectNotFoundInMetadata,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { turnFiltersIntoWhereClauseV2 } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClauseV2';
|
||||
import { turnSortsIntoOrderByV2 } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2';
|
||||
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
|
||||
import { getRecordOptimisticEffectDefinition } from '../graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
|
||||
|
||||
import { useFindManyObjectRecords } from './useFindManyObjectRecords';
|
||||
|
||||
export const useObjectRecordTable = () => {
|
||||
const { scopeId: objectNamePlural } = useRecordTable();
|
||||
|
||||
const { registerOptimisticEffect } = useOptimisticEffect();
|
||||
|
||||
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { setRecordTableData } = useRecordTable();
|
||||
|
||||
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
|
||||
|
||||
const tableFilters = useRecoilValue(tableFiltersState);
|
||||
const tableSorts = useRecoilValue(tableSortsState);
|
||||
|
||||
const filter = turnFiltersIntoWhereClauseV2(
|
||||
tableFilters,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
const orderBy = turnSortsIntoOrderByV2(
|
||||
tableSorts,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
const { objects, loading, fetchMoreObjects } = useFindManyObjectRecords({
|
||||
objectNamePlural,
|
||||
filter,
|
||||
orderBy,
|
||||
onCompleted: (data) => {
|
||||
const entities = data.edges.map((edge) => edge.node) ?? [];
|
||||
|
||||
setRecordTableData(entities);
|
||||
|
||||
if (foundObjectMetadataItem) {
|
||||
registerOptimisticEffect({
|
||||
variables: { orderBy, filter },
|
||||
definition: getRecordOptimisticEffectDefinition({
|
||||
objectMetadataItem: foundObjectMetadataItem,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
objects,
|
||||
loading,
|
||||
fetchMoreObjects,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
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 { useView } from '@/views/hooks/useView';
|
||||
|
||||
export const useSetRecordTableData = () => {
|
||||
const { resetTableRowSelection } = useRecordTable();
|
||||
const { setEntityCountInCurrentView } = useView();
|
||||
|
||||
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],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,45 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
|
||||
export const useUpdateOneObjectRecord = ({
|
||||
objectNamePlural,
|
||||
objectNameSingular,
|
||||
}: ObjectMetadataItemIdentifier) => {
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
objectNotFoundInMetadata,
|
||||
updateOneMutation,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const [mutate] = useMutation(updateOneMutation);
|
||||
|
||||
const updateOneObject = foundObjectMetadataItem
|
||||
? ({
|
||||
idToUpdate,
|
||||
input,
|
||||
}: {
|
||||
idToUpdate: string;
|
||||
input: Record<string, any>;
|
||||
}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
idToUpdate: idToUpdate,
|
||||
input: {
|
||||
...input,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
updateOneObject,
|
||||
objectNotFoundInMetadata,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const cursorFamilyState = atomFamily<string, string | undefined>({
|
||||
key: 'cursorFamilyState',
|
||||
default: '',
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const hasNextPageFamilyState = atomFamily<boolean, string | undefined>({
|
||||
key: 'hasNextPageFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isFetchingMoreObjectsFamilyState = atomFamily<
|
||||
boolean,
|
||||
string | undefined
|
||||
>({
|
||||
key: 'isFetchingMoreObjectsFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import { PaginatedObjectTypeResults } from './PaginatedObjectTypeResults';
|
||||
|
||||
export type PaginatedObjectType<ObjectType extends { id: string }> = {
|
||||
[objectNamePlural: string]: PaginatedObjectTypeResults<ObjectType>;
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
export type PaginatedObjectTypeEdge<ObjectType extends { id: string }> = {
|
||||
node: ObjectType;
|
||||
cursor: string;
|
||||
};
|
||||
|
||||
export type PaginatedObjectTypeResults<ObjectType extends { id: string }> = {
|
||||
__typename?: string;
|
||||
edges: PaginatedObjectTypeEdge<ObjectType>[];
|
||||
pageInfo: {
|
||||
hasNextPage: boolean;
|
||||
startCursor: string;
|
||||
endCursor: string;
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
export const formatPagedObjectsToObjects = <
|
||||
ObjectType extends { id: string } & Record<string, any>,
|
||||
ObjectTypeQuery extends {
|
||||
[objectNamePlural: string]: {
|
||||
edges: ObjectEdge[];
|
||||
};
|
||||
},
|
||||
ObjectEdge extends {
|
||||
node: ObjectType;
|
||||
},
|
||||
>({
|
||||
pagedObjects,
|
||||
objectNamePlural,
|
||||
}: {
|
||||
pagedObjects: ObjectTypeQuery | undefined;
|
||||
objectNamePlural: string;
|
||||
}) => {
|
||||
const formattedObjects: ObjectType[] =
|
||||
pagedObjects?.[objectNamePlural].edges.map((objectEdge: ObjectEdge) => ({
|
||||
...objectEdge.node,
|
||||
})) ?? [];
|
||||
|
||||
return formattedObjects;
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const generateCreateOneObjectMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
return gql`
|
||||
mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) {
|
||||
create${capitalizedObjectName}(data: $input) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map(mapFieldMetadataToGraphQLQuery)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const generateDeleteOneObjectMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
return gql`
|
||||
mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) {
|
||||
delete${capitalizedObjectName}(id: $idToDelete) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const generateFindManyCustomObjectsQuery = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
return gql`
|
||||
query FindMany${capitalize(
|
||||
objectMetadataItem.namePlural,
|
||||
)}($filter: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput, $orderBy: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}OrderByInput, $lastCursor: String) {
|
||||
${
|
||||
objectMetadataItem.namePlural
|
||||
}(filter: $filter, orderBy: $orderBy, first: 30, after: $lastCursor){
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map(mapFieldMetadataToGraphQLQuery)
|
||||
.join('\n')}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
||||
|
||||
export const generateFindOneCustomObjectQuery = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
return gql`
|
||||
query FindOne${objectMetadataItem.nameSingular}($objectMetadataId: UUID!) {
|
||||
${objectMetadataItem.nameSingular}(filter: {
|
||||
id: {
|
||||
eq: $objectMetadataId
|
||||
}
|
||||
}){
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map(mapFieldMetadataToGraphQLQuery)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getUpdateOneObjectMutationGraphQLField = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `update${capitalize(objectNameSingular)}`;
|
||||
};
|
||||
|
||||
export const generateUpdateOneObjectMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
const graphQLFieldForUpdateOneObjectMutation =
|
||||
getUpdateOneObjectMutationGraphQLField({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
return gql`
|
||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||
${graphQLFieldForUpdateOneObjectMutation}(id: $idToUpdate, data: $input) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map(mapFieldMetadataToGraphQLQuery)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
Reference in New Issue
Block a user