Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,35 @@
|
||||
import { useMemo } from 'react';
|
||||
import { ApolloClient, InMemoryCache } from '@apollo/client';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
|
||||
import { ApolloMetadataClientContext } from '../context/ApolloClientMetadataContext';
|
||||
|
||||
export const ApolloMetadataClientProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [tokenPair] = useRecoilState(tokenPairState);
|
||||
const apolloMetadataClient = useMemo(() => {
|
||||
if (tokenPair?.accessToken.token) {
|
||||
return new ApolloClient({
|
||||
uri: `${REACT_APP_SERVER_BASE_URL}/metadata`,
|
||||
cache: new InMemoryCache(),
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokenPair.accessToken.token}`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [tokenPair]);
|
||||
|
||||
return (
|
||||
<ApolloMetadataClientContext.Provider value={apolloMetadataClient}>
|
||||
{children}
|
||||
</ApolloMetadataClientContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const ObjectMetadataItemsLoadEffect = () => {
|
||||
const { objectMetadataItems: newObjectMetadataItems } =
|
||||
useFindManyObjectMetadataItems();
|
||||
|
||||
const [objectMetadataItems, setObjectMetadataItems] = useRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeeplyEqual(objectMetadataItems, newObjectMetadataItems)) {
|
||||
setObjectMetadataItems(newObjectMetadataItems);
|
||||
}
|
||||
}, [newObjectMetadataItems, objectMetadataItems, setObjectMetadataItems]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect';
|
||||
import { ObjectMetadataItemsRelationPickerEffect } from '@/object-metadata/components/ObjectMetadataItemsRelationPickerEffect';
|
||||
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
||||
|
||||
export const ObjectMetadataItemsProvider = ({
|
||||
children,
|
||||
}: React.PropsWithChildren) => {
|
||||
useFindManyObjectMetadataItems();
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ObjectMetadataItemsLoadEffect />
|
||||
{objectMetadataItems.length < 1 && currentWorkspace ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
<ObjectMetadataItemsRelationPickerEffect />
|
||||
<RelationPickerScope relationPickerScopeId="relation-picker">
|
||||
{children}
|
||||
</RelationPickerScope>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,82 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
||||
import { IdentifiersMapper } from '@/object-record/relation-picker/types/IdentifiersMapper';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
export const ObjectMetadataItemsRelationPickerEffect = () => {
|
||||
const { setIdentifiersMapper, setSearchQuery } = useRelationPicker({
|
||||
relationPickerScopeId: 'relation-picker',
|
||||
});
|
||||
|
||||
const computeFilterFields = (relationPickerType: string) => {
|
||||
if (relationPickerType === 'company') {
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
if (['workspaceMember', 'person'].includes(relationPickerType)) {
|
||||
return ['name.firstName', 'name.lastName'];
|
||||
}
|
||||
|
||||
return ['name'];
|
||||
};
|
||||
|
||||
const identifierMapper: IdentifiersMapper = (
|
||||
record: any,
|
||||
objectMetadataItemSingularName: string,
|
||||
) => {
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (objectMetadataItemSingularName === 'company') {
|
||||
return {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
avatarUrl: getLogoUrlFromDomainName(record.domainName ?? ''),
|
||||
avatarType: 'squared',
|
||||
record: record,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
['workspaceMember', 'person'].includes(objectMetadataItemSingularName)
|
||||
) {
|
||||
return {
|
||||
id: record.id,
|
||||
name:
|
||||
(record.name?.firstName ?? '') + ' ' + (record.name?.lastName ?? ''),
|
||||
avatarUrl: record.avatarUrl,
|
||||
avatarType: 'rounded',
|
||||
record: record,
|
||||
};
|
||||
}
|
||||
|
||||
if (['opportunity'].includes(objectMetadataItemSingularName)) {
|
||||
return {
|
||||
id: record.id,
|
||||
name: record?.company?.name,
|
||||
avatarUrl: record.avatarUrl,
|
||||
avatarType: 'rounded',
|
||||
record: record,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
avatarUrl: record.avatarUrl,
|
||||
avatarType: 'rounded',
|
||||
record,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIdentifiersMapper(() => identifierMapper);
|
||||
setSearchQuery({
|
||||
computeFilterFields,
|
||||
});
|
||||
}, [setIdentifiersMapper, setSearchQuery]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { Icon123 } from '@/ui/input/constants/icons';
|
||||
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
|
||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
|
||||
export const ObjectMetadataNavItems = () => {
|
||||
const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();
|
||||
const navigate = useNavigate();
|
||||
const { icons } = useLazyLoadIcons();
|
||||
const currentPath = useLocation().pathname;
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeObjectMetadataItems.map((objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === 'opportunity' ? null : (
|
||||
<NavigationDrawerItem
|
||||
key={objectMetadataItem.id}
|
||||
label={objectMetadataItem.labelPlural}
|
||||
to={`/objects/${objectMetadataItem.namePlural}`}
|
||||
active={currentPath == `/objects/${objectMetadataItem.namePlural}`}
|
||||
Icon={
|
||||
objectMetadataItem.icon ? icons[objectMetadataItem.icon] : Icon123
|
||||
}
|
||||
onClick={() => {
|
||||
navigate(`/objects/${objectMetadataItem.namePlural}`);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
|
||||
|
||||
export const ApolloMetadataClientContext =
|
||||
createContext<ApolloClient<NormalizedCacheObject> | null>(null);
|
||||
@ -0,0 +1,13 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export class ObjectMetadataItemNotFoundError extends Error {
|
||||
constructor(objectName: string, objectMetadataItems: ObjectMetadataItem[]) {
|
||||
const message = `Object metadata item "${objectName}" cannot be found in an array of ${
|
||||
objectMetadataItems?.length ?? 0
|
||||
} elements`;
|
||||
|
||||
super(message);
|
||||
|
||||
Object.setPrototypeOf(this, ObjectMetadataItemNotFoundError.prototype);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) {
|
||||
createOneObject(input: $input) {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const CREATE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {
|
||||
createOneField(input: $input) {
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
defaultValue
|
||||
options
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const CREATE_ONE_RELATION_METADATA_ITEM = gql`
|
||||
mutation CreateOneRelationMetadata($input: CreateOneRelationInput!) {
|
||||
createOneRelation(input: $input) {
|
||||
id
|
||||
relationType
|
||||
fromObjectMetadataId
|
||||
toObjectMetadataId
|
||||
fromFieldMetadataId
|
||||
toFieldMetadataId
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
mutation UpdateOneFieldMetadataItem(
|
||||
$idToUpdate: ID!
|
||||
$updatePayload: UpdateFieldInput!
|
||||
) {
|
||||
updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
mutation UpdateOneObjectMetadataItem(
|
||||
$idToUpdate: ID!
|
||||
$updatePayload: UpdateObjectInput!
|
||||
) {
|
||||
updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
mutation DeleteOneObjectMetadataItem($idToDelete: ID!) {
|
||||
deleteOneObject(input: { id: $idToDelete }) {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DELETE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
mutation DeleteOneFieldMetadataItem($idToDelete: ID!) {
|
||||
deleteOneField(input: { id: $idToDelete }) {
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,86 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
query ObjectMetadataItems(
|
||||
$objectFilter: objectFilter
|
||||
$fieldFilter: fieldFilter
|
||||
) {
|
||||
objects(paging: { first: 1000 }, filter: $objectFilter) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
fields(paging: { first: 1000 }, filter: $fieldFilter) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
fromRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
toObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
toFieldMetadataId
|
||||
}
|
||||
toRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
fromObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
fromFieldMetadataId
|
||||
}
|
||||
defaultValue
|
||||
options
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,9 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ApolloMetadataClientContext } from '../context/ApolloClientMetadataContext';
|
||||
|
||||
export const useApolloMetadataClient = () => {
|
||||
const apolloMetadataClient = useContext(ApolloMetadataClientContext);
|
||||
|
||||
return apolloMetadataClient;
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { FieldMetadata } from '@/object-record/field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { Nullable } from '~/types/Nullable';
|
||||
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '../utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { formatFieldMetadataItemsAsFilterDefinitions } from '../utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { formatFieldMetadataItemsAsSortDefinitions } from '../utils/formatFieldMetadataItemsAsSortDefinitions';
|
||||
|
||||
export const useColumnDefinitionsFromFieldMetadata = (
|
||||
objectMetadataItem?: Nullable<ObjectMetadataItem>,
|
||||
) => {
|
||||
const activeFieldMetadataItems = useMemo(
|
||||
() =>
|
||||
objectMetadataItem
|
||||
? objectMetadataItem.fields.filter(
|
||||
({ isActive, isSystem }) => isActive && !isSystem,
|
||||
)
|
||||
: [],
|
||||
[objectMetadataItem],
|
||||
);
|
||||
|
||||
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = useMemo(
|
||||
() =>
|
||||
objectMetadataItem
|
||||
? activeFieldMetadataItems.map((field, index) =>
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
position: index,
|
||||
field,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
)
|
||||
: [],
|
||||
[activeFieldMetadataItems, objectMetadataItem],
|
||||
);
|
||||
|
||||
const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({
|
||||
fields: activeFieldMetadataItems,
|
||||
});
|
||||
|
||||
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
|
||||
fields: activeFieldMetadataItems,
|
||||
});
|
||||
|
||||
return {
|
||||
columnDefinitions,
|
||||
filterDefinitions,
|
||||
sortDefinitions,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,53 @@
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { FieldType } from '@/object-record/field/types/FieldType';
|
||||
import {
|
||||
CreateOneFieldMetadataItemMutation,
|
||||
CreateOneFieldMetadataItemMutationVariables,
|
||||
FieldMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { CREATE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
type CreateOneFieldMetadataItemArgs = Omit<
|
||||
CreateOneFieldMetadataItemMutationVariables['input']['field'],
|
||||
'type'
|
||||
> & {
|
||||
type: FieldType;
|
||||
};
|
||||
|
||||
export const useCreateOneFieldMetadataItem = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
CreateOneFieldMetadataItemMutation,
|
||||
CreateOneFieldMetadataItemMutationVariables
|
||||
>(CREATE_ONE_FIELD_METADATA_ITEM, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const createOneFieldMetadataItem = async (
|
||||
input: CreateOneFieldMetadataItemArgs,
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input: {
|
||||
field: {
|
||||
...input,
|
||||
type: input.type as FieldMetadataType, // Todo improve typing once we have aligned backend and frontend
|
||||
},
|
||||
},
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createOneFieldMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import {
|
||||
CreateOneObjectMetadataItemMutation,
|
||||
CreateOneObjectMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { CREATE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useCreateOneObjectRecordMetadataItem = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
CreateOneObjectMetadataItemMutation,
|
||||
CreateOneObjectMetadataItemMutationVariables
|
||||
>(CREATE_ONE_OBJECT_METADATA_ITEM, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const createOneObjectMetadataItem = async (
|
||||
input: CreateOneObjectMetadataItemMutationVariables['input']['object'],
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input: {
|
||||
object: {
|
||||
...input,
|
||||
},
|
||||
},
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createOneObjectMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import {
|
||||
CreateOneRelationMetadataMutation,
|
||||
CreateOneRelationMetadataMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { CREATE_ONE_RELATION_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
import {
|
||||
formatRelationMetadataInput,
|
||||
FormatRelationMetadataInputParams,
|
||||
} from '../utils/formatRelationMetadataInput';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useCreateOneRelationMetadataItem = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
CreateOneRelationMetadataMutation,
|
||||
CreateOneRelationMetadataMutationVariables
|
||||
>(CREATE_ONE_RELATION_METADATA_ITEM, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const createOneRelationMetadataItem = async (
|
||||
input: FormatRelationMetadataInputParams,
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: { input: { relation: formatRelationMetadataInput(input) } },
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createOneRelationMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import {
|
||||
DeleteOneFieldMetadataItemMutation,
|
||||
DeleteOneFieldMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { DELETE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useDeleteOneFieldMetadataItem = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
DeleteOneFieldMetadataItemMutation,
|
||||
DeleteOneFieldMetadataItemMutationVariables
|
||||
>(DELETE_ONE_FIELD_METADATA_ITEM, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const deleteOneFieldMetadataItem = async (
|
||||
idToDelete: DeleteOneFieldMetadataItemMutationVariables['idToDelete'],
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
deleteOneFieldMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import {
|
||||
DeleteOneObjectMetadataItemMutation,
|
||||
DeleteOneObjectMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { DELETE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useDeleteOneObjectMetadataItem = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
DeleteOneObjectMetadataItemMutation,
|
||||
DeleteOneObjectMetadataItemMutationVariables
|
||||
>(DELETE_ONE_OBJECT_METADATA_ITEM, {
|
||||
client: apolloMetadataClient ?? ({} as ApolloClient<any>),
|
||||
});
|
||||
|
||||
const deleteOneObjectMetadataItem = async (
|
||||
idToDelete: DeleteOneObjectMetadataItemMutationVariables['idToDelete'],
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
deleteOneObjectMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,72 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { FieldType } from '@/object-record/field/types/FieldType';
|
||||
import { Field } from '~/generated/graphql';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
import { FieldMetadataOption } from '../types/FieldMetadataOption';
|
||||
import { formatFieldMetadataItemInput } from '../utils/formatFieldMetadataItemInput';
|
||||
|
||||
import { useCreateOneFieldMetadataItem } from './useCreateOneFieldMetadataItem';
|
||||
import { useDeleteOneFieldMetadataItem } from './useDeleteOneFieldMetadataItem';
|
||||
import { useUpdateOneFieldMetadataItem } from './useUpdateOneFieldMetadataItem';
|
||||
|
||||
export const useFieldMetadataItem = () => {
|
||||
const { createOneFieldMetadataItem } = useCreateOneFieldMetadataItem();
|
||||
const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem();
|
||||
const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem();
|
||||
|
||||
const createMetadataField = (
|
||||
input: Pick<Field, 'label' | 'icon' | 'description'> & {
|
||||
objectMetadataId: string;
|
||||
options?: Omit<FieldMetadataOption, 'id'>[];
|
||||
type: FieldMetadataType;
|
||||
},
|
||||
) =>
|
||||
createOneFieldMetadataItem({
|
||||
...formatFieldMetadataItemInput(input),
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
type: input.type as FieldType,
|
||||
});
|
||||
|
||||
const editMetadataField = (
|
||||
input: Pick<Field, 'id' | 'label' | 'icon' | 'description'> & {
|
||||
options?: FieldMetadataOption[];
|
||||
},
|
||||
) =>
|
||||
updateOneFieldMetadataItem({
|
||||
fieldMetadataIdToUpdate: input.id,
|
||||
updatePayload: formatFieldMetadataItemInput({
|
||||
...input,
|
||||
// In Edit mode, all options need an id,
|
||||
// so we generate an id for newly created options.
|
||||
options: input.options?.map((option) =>
|
||||
option.id ? option : { ...option, id: v4() },
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const activateMetadataField = (metadataField: FieldMetadataItem) =>
|
||||
updateOneFieldMetadataItem({
|
||||
fieldMetadataIdToUpdate: metadataField.id,
|
||||
updatePayload: { isActive: true },
|
||||
});
|
||||
|
||||
const disableMetadataField = (metadataField: FieldMetadataItem) =>
|
||||
updateOneFieldMetadataItem({
|
||||
fieldMetadataIdToUpdate: metadataField.id,
|
||||
updatePayload: { isActive: false },
|
||||
});
|
||||
|
||||
const eraseMetadataField = (metadataField: FieldMetadataItem) =>
|
||||
deleteOneFieldMetadataItem(metadataField.id);
|
||||
|
||||
return {
|
||||
activateMetadataField,
|
||||
createMetadataField,
|
||||
disableMetadataField,
|
||||
eraseMetadataField,
|
||||
editMetadataField,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import {
|
||||
FieldFilter,
|
||||
ObjectFilter,
|
||||
ObjectMetadataItemsQuery,
|
||||
ObjectMetadataItemsQueryVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '../utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useFindManyObjectMetadataItems = ({
|
||||
skip,
|
||||
objectFilter,
|
||||
fieldFilter,
|
||||
}: {
|
||||
skip?: boolean;
|
||||
objectFilter?: ObjectFilter;
|
||||
fieldFilter?: FieldFilter;
|
||||
} = {}) => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const { data, loading, error } = useQuery<
|
||||
ObjectMetadataItemsQuery,
|
||||
ObjectMetadataItemsQueryVariables
|
||||
>(FIND_MANY_OBJECT_METADATA_ITEMS, {
|
||||
variables: {
|
||||
objectFilter,
|
||||
fieldFilter,
|
||||
},
|
||||
client: apolloMetadataClient ?? undefined,
|
||||
skip: skip || !apolloMetadataClient,
|
||||
onError: (error) => {
|
||||
logError('useFindManyObjectMetadataItems error : ' + error);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyObjectMetadataItems, ${error.message}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const objectMetadataItems = useMemo(() => {
|
||||
return mapPaginatedObjectMetadataItemsToObjectMetadataItems({
|
||||
pagedObjectMetadataItems: data,
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
objectMetadataItems,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { getObjectOrderByField } from '@/object-metadata/utils/getObjectOrderByField';
|
||||
|
||||
export const useGetObjectOrderByField = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
return (orderBy: OrderBy): OrderByField => {
|
||||
return getObjectOrderByField(objectMetadataItem, orderBy);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,108 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { FieldType } from '@/object-record/field/types/FieldType';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
|
||||
export const useMapFieldMetadataToGraphQLQuery = () => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const mapFieldMetadataToGraphQLQuery = (
|
||||
field: FieldMetadataItem,
|
||||
maxDepthForRelations: number = 2,
|
||||
): any => {
|
||||
if (maxDepthForRelations <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// TODO: parse
|
||||
const fieldType = field.type as FieldType;
|
||||
|
||||
const fieldIsSimpleValue = (
|
||||
[
|
||||
'UUID',
|
||||
'TEXT',
|
||||
'PHONE',
|
||||
'DATE_TIME',
|
||||
'EMAIL',
|
||||
'NUMBER',
|
||||
'BOOLEAN',
|
||||
] as FieldType[]
|
||||
).includes(fieldType);
|
||||
|
||||
if (fieldIsSimpleValue) {
|
||||
return field.name;
|
||||
} else if (
|
||||
fieldType === 'RELATION' &&
|
||||
field.toRelationMetadata?.relationType === 'ONE_TO_MANY'
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
|
||||
);
|
||||
|
||||
return `${field.name}
|
||||
{
|
||||
id
|
||||
${(relationMetadataItem?.fields ?? [])
|
||||
.filter((field) => field.type !== 'RELATION')
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphQLQuery(field, maxDepthForRelations - 1),
|
||||
)
|
||||
.join('\n')}
|
||||
}`;
|
||||
} else if (
|
||||
fieldType === 'RELATION' &&
|
||||
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.fromRelationMetadata as any)?.toObjectMetadata?.id,
|
||||
);
|
||||
|
||||
return `${field.name}
|
||||
{
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${(relationMetadataItem?.fields ?? [])
|
||||
.filter((field) => field.type !== 'RELATION')
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphQLQuery(field, maxDepthForRelations - 1),
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
} else if (fieldType === 'LINK') {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
label
|
||||
url
|
||||
}
|
||||
`;
|
||||
} else if (fieldType === 'CURRENCY') {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
`;
|
||||
} else if (fieldType === 'FULL_NAME') {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
return mapFieldMetadataToGraphQLQuery;
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
export const useMapToObjectRecordIdentifier = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
return (record: any): ObjectRecordIdentifier => {
|
||||
switch (objectMetadataItem.nameSingular) {
|
||||
case CoreObjectNameSingular.Opportunity:
|
||||
return {
|
||||
id: record.id,
|
||||
name: record?.company?.name,
|
||||
avatarUrl: record.avatarUrl,
|
||||
avatarType: 'rounded',
|
||||
};
|
||||
}
|
||||
|
||||
const labelIdentifierFieldMetadata = objectMetadataItem.fields.find(
|
||||
(field) =>
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId ||
|
||||
field.name === 'name',
|
||||
);
|
||||
|
||||
let labelIdentifierFieldValue = '';
|
||||
|
||||
switch (labelIdentifierFieldMetadata?.type) {
|
||||
case FieldMetadataType.FullName: {
|
||||
labelIdentifierFieldValue = `${record.name?.firstName ?? ''} ${
|
||||
record.name?.lastName ?? ''
|
||||
}`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
labelIdentifierFieldValue = labelIdentifierFieldMetadata
|
||||
? record[labelIdentifierFieldMetadata.name]
|
||||
: '';
|
||||
}
|
||||
|
||||
const imageIdentifierFieldMetadata = objectMetadataItem.fields.find(
|
||||
(field) => field.id === objectMetadataItem.imageIdentifierFieldMetadataId,
|
||||
);
|
||||
|
||||
const imageIdentifierFieldValue = imageIdentifierFieldMetadata
|
||||
? (record[imageIdentifierFieldMetadata.name] as string)
|
||||
: null;
|
||||
|
||||
const avatarType =
|
||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.Company
|
||||
? 'squared'
|
||||
: 'rounded';
|
||||
|
||||
const avatarUrl =
|
||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.Company
|
||||
? getLogoUrlFromDomainName(imageIdentifierFieldValue ?? '')
|
||||
: imageIdentifierFieldValue ?? null;
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
name: labelIdentifierFieldValue,
|
||||
avatarUrl,
|
||||
avatarType,
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,130 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
|
||||
import { useGetObjectOrderByField } from '@/object-metadata/hooks/useGetObjectOrderByField';
|
||||
import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier';
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { useGenerateCreateManyRecordMutation } from '@/object-record/hooks/useGenerateCreateManyRecordMutation';
|
||||
import { useGenerateCreateOneRecordMutation } from '@/object-record/hooks/useGenerateCreateOneRecordMutation';
|
||||
import { useGenerateFindManyRecordsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsQuery';
|
||||
import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery';
|
||||
import { useGenerateUpdateOneRecordMutation } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation';
|
||||
import { useGetRecordFromCache } from '@/object-record/hooks/useGetRecordFromCache';
|
||||
import { useModifyRecordFromCache } from '@/object-record/hooks/useModifyRecordFromCache';
|
||||
import { generateDeleteOneRecordMutation } from '@/object-record/utils/generateDeleteOneRecordMutation';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
|
||||
|
||||
export const EMPTY_QUERY = gql`
|
||||
query EmptyQuery {
|
||||
empty
|
||||
}
|
||||
`;
|
||||
|
||||
export const EMPTY_MUTATION = gql`
|
||||
mutation EmptyMutation {
|
||||
empty
|
||||
}
|
||||
`;
|
||||
|
||||
export const useObjectMetadataItem = (
|
||||
{ objectNameSingular }: ObjectMetadataItemIdentifier,
|
||||
depth?: number,
|
||||
) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const mockObjectMetadataItems = getObjectMetadataItemsMock();
|
||||
|
||||
let objectMetadataItem = useRecoilValue(
|
||||
objectMetadataItemFamilySelector({
|
||||
objectName: objectNameSingular,
|
||||
objectNameType: 'singular',
|
||||
}),
|
||||
);
|
||||
|
||||
let objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
if (!currentWorkspace) {
|
||||
objectMetadataItem =
|
||||
mockObjectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
) ?? null;
|
||||
objectMetadataItems = mockObjectMetadataItems;
|
||||
}
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new ObjectMetadataItemNotFoundError(
|
||||
objectNameSingular,
|
||||
objectMetadataItems,
|
||||
);
|
||||
}
|
||||
|
||||
const mapToObjectRecordIdentifier = useMapToObjectRecordIdentifier({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const getObjectOrderByField = useGetObjectOrderByField({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const getRecordFromCache = useGetRecordFromCache({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const modifyRecordFromCache = useModifyRecordFromCache({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const findManyRecordsQuery = useGenerateFindManyRecordsQuery({
|
||||
objectMetadataItem,
|
||||
depth,
|
||||
});
|
||||
|
||||
const findOneRecordQuery = useGenerateFindOneRecordQuery({
|
||||
objectMetadataItem,
|
||||
depth,
|
||||
});
|
||||
|
||||
const createOneRecordMutation = useGenerateCreateOneRecordMutation({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const createManyRecordsMutation = useGenerateCreateManyRecordMutation({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const updateOneRecordMutation = useGenerateUpdateOneRecordMutation({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const deleteOneRecordMutation = generateDeleteOneRecordMutation({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const labelIdentifierFieldMetadataId = objectMetadataItem.fields.find(
|
||||
({ name }) => name === 'name',
|
||||
)?.id;
|
||||
|
||||
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
|
||||
|
||||
return {
|
||||
labelIdentifierFieldMetadataId,
|
||||
basePathToShowPage,
|
||||
objectMetadataItem,
|
||||
getRecordFromCache,
|
||||
modifyRecordFromCache,
|
||||
findManyRecordsQuery,
|
||||
findOneRecordQuery,
|
||||
createOneRecordMutation,
|
||||
updateOneRecordMutation,
|
||||
deleteOneRecordMutation,
|
||||
createManyRecordsMutation,
|
||||
mapToObjectRecordIdentifier,
|
||||
getObjectOrderByField,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,95 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
import { formatObjectMetadataItemInput } from '../utils/formatObjectMetadataItemInput';
|
||||
import { getObjectSlug } from '../utils/getObjectSlug';
|
||||
|
||||
import { useCreateOneObjectRecordMetadataItem } from './useCreateOneObjectMetadataItem';
|
||||
import { useDeleteOneObjectMetadataItem } from './useDeleteOneObjectMetadataItem';
|
||||
import { useUpdateOneObjectMetadataItem } from './useUpdateOneObjectMetadataItem';
|
||||
|
||||
export const useObjectMetadataItemForSettings = () => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const activeObjectMetadataItems = objectMetadataItems.filter(
|
||||
({ isActive, isSystem }) => isActive && !isSystem,
|
||||
);
|
||||
const disabledObjectMetadataItems = objectMetadataItems.filter(
|
||||
({ isActive, isSystem }) => !isActive && !isSystem,
|
||||
);
|
||||
|
||||
const findActiveObjectMetadataItemBySlug = (slug: string) =>
|
||||
activeObjectMetadataItems.find(
|
||||
(activeObjectMetadataItem) =>
|
||||
getObjectSlug(activeObjectMetadataItem) === slug,
|
||||
);
|
||||
|
||||
const findObjectMetadataItemById = (id: string) =>
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === id,
|
||||
);
|
||||
|
||||
const findObjectMetadataItemByNamePlural = (namePlural: string) =>
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.namePlural === namePlural,
|
||||
);
|
||||
|
||||
const { createOneObjectMetadataItem } =
|
||||
useCreateOneObjectRecordMetadataItem();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
|
||||
|
||||
const createObjectMetadataItem = (
|
||||
input: Pick<
|
||||
ObjectMetadataItem,
|
||||
'labelPlural' | 'labelSingular' | 'icon' | 'description'
|
||||
>,
|
||||
) => createOneObjectMetadataItem(formatObjectMetadataItemInput(input));
|
||||
|
||||
const editObjectMetadataItem = (
|
||||
input: Pick<
|
||||
ObjectMetadataItem,
|
||||
'id' | 'labelPlural' | 'labelSingular' | 'icon' | 'description'
|
||||
>,
|
||||
) =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: input.id,
|
||||
updatePayload: formatObjectMetadataItemInput(input),
|
||||
});
|
||||
|
||||
const activateObjectMetadataItem = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'id'>,
|
||||
) =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: { isActive: true },
|
||||
});
|
||||
|
||||
const disableObjectMetadataItem = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'id'>,
|
||||
) =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: { isActive: false },
|
||||
});
|
||||
|
||||
const eraseObjectMetadataItem = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'id'>,
|
||||
) => deleteOneObjectMetadataItem(objectMetadataItem.id);
|
||||
|
||||
return {
|
||||
activateObjectMetadataItem,
|
||||
activeObjectMetadataItems,
|
||||
createObjectMetadataItem,
|
||||
disabledObjectMetadataItems,
|
||||
disableObjectMetadataItem,
|
||||
editObjectMetadataItem,
|
||||
eraseObjectMetadataItem,
|
||||
findActiveObjectMetadataItemBySlug,
|
||||
findObjectMetadataItemById,
|
||||
findObjectMetadataItemByNamePlural,
|
||||
objectMetadataItems,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useObjectNamePluralFromSingular = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const mockObjectMetadataItems = getObjectMetadataItemsMock();
|
||||
|
||||
let objectMetadataItem = useRecoilValue(
|
||||
objectMetadataItemFamilySelector({
|
||||
objectName: objectNameSingular,
|
||||
objectNameType: 'singular',
|
||||
}),
|
||||
);
|
||||
|
||||
if (!currentWorkspace) {
|
||||
objectMetadataItem =
|
||||
mockObjectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
) ?? null;
|
||||
}
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
`Object metadata item not found for ${objectNameSingular} object`,
|
||||
);
|
||||
}
|
||||
|
||||
return { objectNamePlural: objectMetadataItem.namePlural };
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useObjectNameSingularFromPlural = ({
|
||||
objectNamePlural,
|
||||
}: {
|
||||
objectNamePlural: string;
|
||||
}) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const mockObjectMetadataItems = getObjectMetadataItemsMock();
|
||||
|
||||
let objectMetadataItem = useRecoilValue(
|
||||
objectMetadataItemFamilySelector({
|
||||
objectName: objectNamePlural,
|
||||
objectNameType: 'plural',
|
||||
}),
|
||||
);
|
||||
|
||||
if (!currentWorkspace) {
|
||||
objectMetadataItem =
|
||||
mockObjectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.namePlural === objectNamePlural,
|
||||
) ?? null;
|
||||
}
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
`Object metadata item not found for ${objectNamePlural} object`,
|
||||
);
|
||||
}
|
||||
|
||||
return { objectNameSingular: objectMetadataItem.nameSingular };
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useObjectMetadataItemForSettings } from '../hooks/useObjectMetadataItemForSettings';
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
|
||||
export const useRelationMetadata = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem?: FieldMetadataItem;
|
||||
}) => {
|
||||
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
|
||||
|
||||
const relationMetadata =
|
||||
fieldMetadataItem?.fromRelationMetadata ||
|
||||
fieldMetadataItem?.toRelationMetadata;
|
||||
|
||||
const relationType =
|
||||
relationMetadata?.relationType === RelationMetadataType.OneToMany &&
|
||||
fieldMetadataItem?.toRelationMetadata
|
||||
? 'MANY_TO_ONE'
|
||||
: (relationMetadata?.relationType as RelationType | undefined);
|
||||
|
||||
const relationObjectMetadataId =
|
||||
relationMetadata && 'toObjectMetadata' in relationMetadata
|
||||
? relationMetadata.toObjectMetadata.id
|
||||
: relationMetadata?.fromObjectMetadata.id;
|
||||
|
||||
const relationObjectMetadataItem = relationObjectMetadataId
|
||||
? findObjectMetadataItemById(relationObjectMetadataId)
|
||||
: undefined;
|
||||
|
||||
const relationFieldMetadataId =
|
||||
relationMetadata && 'toFieldMetadataId' in relationMetadata
|
||||
? relationMetadata.toFieldMetadataId
|
||||
: relationMetadata?.fromFieldMetadataId;
|
||||
|
||||
const relationFieldMetadataItem = relationFieldMetadataId
|
||||
? relationObjectMetadataItem?.fields?.find(
|
||||
(field) => field.id === relationFieldMetadataId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
relationFieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
relationType,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import {
|
||||
UpdateOneFieldMetadataItemMutation,
|
||||
UpdateOneFieldMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { UPDATE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useUpdateOneFieldMetadataItem = () => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
UpdateOneFieldMetadataItemMutation,
|
||||
UpdateOneFieldMetadataItemMutationVariables
|
||||
>(UPDATE_ONE_FIELD_METADATA_ITEM, {
|
||||
client: apolloMetadataClient ?? undefined,
|
||||
});
|
||||
|
||||
const updateOneFieldMetadataItem = async ({
|
||||
fieldMetadataIdToUpdate,
|
||||
updatePayload,
|
||||
}: {
|
||||
fieldMetadataIdToUpdate: UpdateOneFieldMetadataItemMutationVariables['idToUpdate'];
|
||||
updatePayload: Pick<
|
||||
UpdateOneFieldMetadataItemMutationVariables['updatePayload'],
|
||||
'description' | 'icon' | 'isActive' | 'label' | 'name'
|
||||
>;
|
||||
}) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
idToUpdate: fieldMetadataIdToUpdate,
|
||||
updatePayload: {
|
||||
...updatePayload,
|
||||
label: updatePayload.label ?? undefined,
|
||||
},
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
updateOneFieldMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import {
|
||||
UpdateOneObjectMetadataItemMutation,
|
||||
UpdateOneObjectMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { UPDATE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '../graphql/queries';
|
||||
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
// TODO: Slice the Apollo store synchronously in the update function instead of subscribing, so we can use update after read in the same function call
|
||||
export const useUpdateOneObjectMetadataItem = () => {
|
||||
const apolloClientMetadata = useApolloMetadataClient();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
UpdateOneObjectMetadataItemMutation,
|
||||
UpdateOneObjectMetadataItemMutationVariables
|
||||
>(UPDATE_ONE_OBJECT_METADATA_ITEM, {
|
||||
client: apolloClientMetadata ?? undefined,
|
||||
});
|
||||
|
||||
const updateOneObjectMetadataItem = async ({
|
||||
idToUpdate,
|
||||
updatePayload,
|
||||
}: {
|
||||
idToUpdate: UpdateOneObjectMetadataItemMutationVariables['idToUpdate'];
|
||||
updatePayload: Pick<
|
||||
UpdateOneObjectMetadataItemMutationVariables['updatePayload'],
|
||||
| 'description'
|
||||
| 'icon'
|
||||
| 'isActive'
|
||||
| 'labelPlural'
|
||||
| 'labelSingular'
|
||||
| 'namePlural'
|
||||
| 'nameSingular'
|
||||
>;
|
||||
}) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
idToUpdate,
|
||||
updatePayload,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
updateOneObjectMetadataItem,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
type ObjectMetadataItemSelector = {
|
||||
objectName: string;
|
||||
objectNameType: 'singular' | 'plural';
|
||||
};
|
||||
|
||||
export const objectMetadataItemFamilySelector = selectorFamily<
|
||||
ObjectMetadataItem | null,
|
||||
ObjectMetadataItemSelector
|
||||
>({
|
||||
key: 'objectMetadataItemFamilySelector',
|
||||
get:
|
||||
({ objectNameType, objectName }: ObjectMetadataItemSelector) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
|
||||
if (objectNameType === 'singular') {
|
||||
return (
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectName,
|
||||
) ?? null
|
||||
);
|
||||
} else if (objectNameType === 'plural') {
|
||||
return (
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.namePlural === objectName,
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const objectMetadataItemsState = atom<ObjectMetadataItem[]>({
|
||||
key: 'objectMetadataItemsState',
|
||||
default: [],
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
export enum CoreObjectNameSingular {
|
||||
Company = 'company',
|
||||
Person = 'person',
|
||||
WorkspaceMember = 'workspaceMember',
|
||||
Opportunity = 'opportunity',
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
import { Field, Relation } from '~/generated-metadata/graphql';
|
||||
|
||||
export type FieldMetadataItem = Omit<
|
||||
Field,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'defaultValue' | 'options'
|
||||
> & {
|
||||
fromRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
||||
toObjectMetadata: Pick<
|
||||
Relation['toObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
toRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
|
||||
fromObjectMetadata: Pick<
|
||||
Relation['fromObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
defaultValue?: string;
|
||||
options?: {
|
||||
color: ThemeColor;
|
||||
id: string;
|
||||
label: string;
|
||||
position: number;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
|
||||
export type FieldMetadataOption = {
|
||||
color?: ThemeColor;
|
||||
id?: string;
|
||||
isDefault?: boolean;
|
||||
label: string;
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { Object as GeneratedObject } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from './FieldMetadataItem';
|
||||
|
||||
export type ObjectMetadataItem = Omit<
|
||||
GeneratedObject,
|
||||
'fields' | 'dataSourceId'
|
||||
> & {
|
||||
fields: FieldMetadataItem[];
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
export type ObjectMetadataItemIdentifier = {
|
||||
objectNameSingular: string;
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export type OrderBy =
|
||||
| 'AscNullsLast'
|
||||
| 'DescNullsLast'
|
||||
| 'AscNullsFirst'
|
||||
| 'DescNullsFirst';
|
||||
@ -0,0 +1,5 @@
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
|
||||
export type OrderByField = {
|
||||
[fieldName: string]: OrderBy | { [subFieldName: string]: OrderBy };
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
||||
import { FieldMetadata } from '@/object-record/field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
|
||||
import { parseFieldType } from './parseFieldType';
|
||||
|
||||
export const formatFieldMetadataItemAsColumnDefinition = ({
|
||||
position,
|
||||
field,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
position: number;
|
||||
field: FieldMetadataItem;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}): ColumnDefinition<FieldMetadata> => {
|
||||
const relationObjectMetadataItem =
|
||||
field.toRelationMetadata?.fromObjectMetadata;
|
||||
|
||||
return {
|
||||
position,
|
||||
fieldMetadataId: field.id,
|
||||
label: field.label,
|
||||
size: 100,
|
||||
type: parseFieldType(field.type),
|
||||
metadata: {
|
||||
fieldName: field.name,
|
||||
placeHolder: field.label,
|
||||
relationType: parseFieldRelationType(field),
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem?.nameSingular ?? '',
|
||||
relationObjectMetadataNamePlural:
|
||||
relationObjectMetadataItem?.namePlural ?? '',
|
||||
objectMetadataNameSingular: objectMetadataItem.nameSingular ?? '',
|
||||
},
|
||||
iconName: field.icon ?? 'Icon123',
|
||||
isVisible: true,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import toCamelCase from 'lodash.camelcase';
|
||||
import toSnakeCase from 'lodash.snakecase';
|
||||
|
||||
import { Field } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataOption } from '../types/FieldMetadataOption';
|
||||
|
||||
const getOptionValueFromLabel = (label: string) =>
|
||||
toSnakeCase(label.trim()).toUpperCase();
|
||||
|
||||
export const formatFieldMetadataItemInput = (
|
||||
input: Pick<Field, 'label' | 'icon' | 'description' | 'defaultValue'> & {
|
||||
options?: FieldMetadataOption[];
|
||||
},
|
||||
) => {
|
||||
const defaultOption = input.options?.find((option) => option.isDefault);
|
||||
|
||||
return {
|
||||
defaultValue: defaultOption
|
||||
? getOptionValueFromLabel(defaultOption.label)
|
||||
: undefined,
|
||||
description: input.description?.trim() ?? null,
|
||||
icon: input.icon,
|
||||
label: input.label.trim(),
|
||||
name: toCamelCase(input.label.trim()),
|
||||
options: input.options?.map((option, index) => ({
|
||||
color: option.color,
|
||||
id: option.id,
|
||||
label: option.label.trim(),
|
||||
position: index,
|
||||
value: getOptionValueFromLabel(option.label),
|
||||
})),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,71 @@
|
||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const formatFieldMetadataItemsAsFilterDefinitions = ({
|
||||
fields,
|
||||
}: {
|
||||
fields: Array<ObjectMetadataItem['fields'][0]>;
|
||||
}): FilterDefinition[] =>
|
||||
fields.reduce((acc, field) => {
|
||||
if (
|
||||
![
|
||||
FieldMetadataType.DateTime,
|
||||
FieldMetadataType.Text,
|
||||
FieldMetadataType.Email,
|
||||
FieldMetadataType.Number,
|
||||
FieldMetadataType.Link,
|
||||
FieldMetadataType.FullName,
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Currency,
|
||||
].includes(field.type)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Todo: remove once Rating fieldtype is implemented
|
||||
if (field.name === 'probability') {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.Relation) {
|
||||
if (field.fromRelationMetadata) {
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
return [...acc, formatFieldMetadataItemAsFilterDefinition({ field })];
|
||||
}, [] as FilterDefinition[]);
|
||||
|
||||
const formatFieldMetadataItemAsFilterDefinition = ({
|
||||
field,
|
||||
}: {
|
||||
field: ObjectMetadataItem['fields'][0];
|
||||
}): FilterDefinition => ({
|
||||
fieldMetadataId: field.id,
|
||||
label: field.label,
|
||||
iconName: field.icon ?? 'Icon123',
|
||||
relationObjectMetadataNamePlural:
|
||||
field.toRelationMetadata?.fromObjectMetadata.namePlural,
|
||||
relationObjectMetadataNameSingular:
|
||||
field.toRelationMetadata?.fromObjectMetadata.nameSingular,
|
||||
type:
|
||||
field.type === FieldMetadataType.DateTime
|
||||
? 'DATE_TIME'
|
||||
: field.type === FieldMetadataType.Link
|
||||
? 'LINK'
|
||||
: field.type === FieldMetadataType.FullName
|
||||
? 'FULL_NAME'
|
||||
: field.type === FieldMetadataType.Number
|
||||
? 'NUMBER'
|
||||
: field.type === FieldMetadataType.Currency
|
||||
? 'CURRENCY'
|
||||
: field.type === FieldMetadataType.Email
|
||||
? 'TEXT'
|
||||
: field.type === FieldMetadataType.Phone
|
||||
? 'TEXT'
|
||||
: field.type === FieldMetadataType.Relation
|
||||
? 'RELATION'
|
||||
: 'TEXT',
|
||||
});
|
||||
@ -0,0 +1,31 @@
|
||||
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const formatFieldMetadataItemsAsSortDefinitions = ({
|
||||
fields,
|
||||
}: {
|
||||
fields: Array<ObjectMetadataItem['fields'][0]>;
|
||||
}): SortDefinition[] =>
|
||||
fields.reduce((acc, field) => {
|
||||
if (
|
||||
![
|
||||
FieldMetadataType.DateTime,
|
||||
FieldMetadataType.Number,
|
||||
FieldMetadataType.Text,
|
||||
FieldMetadataType.Boolean,
|
||||
].includes(field.type)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
fieldMetadataId: field.id,
|
||||
label: field.label,
|
||||
iconName: field.icon ?? 'Icon123',
|
||||
},
|
||||
];
|
||||
}, [] as SortDefinition[]);
|
||||
@ -0,0 +1,17 @@
|
||||
import toCamelCase from 'lodash.camelcase';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const formatObjectMetadataItemInput = (
|
||||
input: Pick<
|
||||
ObjectMetadataItem,
|
||||
'labelPlural' | 'labelSingular' | 'icon' | 'description'
|
||||
>,
|
||||
) => ({
|
||||
description: input.description?.trim() ?? null,
|
||||
icon: input.icon,
|
||||
labelPlural: input.labelPlural.trim(),
|
||||
labelSingular: input.labelSingular.trim(),
|
||||
namePlural: toCamelCase(input.labelPlural.trim()),
|
||||
nameSingular: toCamelCase(input.labelSingular.trim()),
|
||||
});
|
||||
@ -0,0 +1,59 @@
|
||||
import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
CreateRelationInput,
|
||||
Field,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { formatFieldMetadataItemInput } from './formatFieldMetadataItemInput';
|
||||
|
||||
export type FormatRelationMetadataInputParams = {
|
||||
relationType: RelationType;
|
||||
field: Pick<Field, 'label' | 'icon' | 'description'>;
|
||||
objectMetadataId: string;
|
||||
connect: {
|
||||
field: Pick<Field, 'label' | 'icon'>;
|
||||
objectMetadataId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const formatRelationMetadataInput = (
|
||||
input: FormatRelationMetadataInputParams,
|
||||
): CreateRelationInput => {
|
||||
// /!\ MANY_TO_ONE does not exist on backend.
|
||||
// => Transform into ONE_TO_MANY and invert "from" and "to" data.
|
||||
const isManyToOne = input.relationType === 'MANY_TO_ONE';
|
||||
const relationType = isManyToOne
|
||||
? RelationMetadataType.OneToMany
|
||||
: (input.relationType as RelationMetadataType);
|
||||
const { field: fromField, objectMetadataId: fromObjectMetadataId } =
|
||||
isManyToOne ? input.connect : input;
|
||||
const { field: toField, objectMetadataId: toObjectMetadataId } = isManyToOne
|
||||
? input
|
||||
: input.connect;
|
||||
|
||||
const {
|
||||
description,
|
||||
icon: fromIcon,
|
||||
label: fromLabel,
|
||||
name: fromName,
|
||||
} = formatFieldMetadataItemInput(fromField);
|
||||
const {
|
||||
icon: toIcon,
|
||||
label: toLabel,
|
||||
name: toName,
|
||||
} = formatFieldMetadataItemInput(toField);
|
||||
|
||||
return {
|
||||
description,
|
||||
fromIcon,
|
||||
fromLabel,
|
||||
fromName,
|
||||
fromObjectMetadataId,
|
||||
relationType,
|
||||
toIcon,
|
||||
toLabel,
|
||||
toName,
|
||||
toObjectMetadataId,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import toKebabCase from 'lodash.kebabcase';
|
||||
|
||||
import { Field } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getFieldSlug = (metadataField: Pick<Field, 'label'>) =>
|
||||
toKebabCase(metadataField.label);
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getObjectOrderByField = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
orderBy: OrderBy,
|
||||
): OrderByField => {
|
||||
const labelIdentifierFieldMetadata = objectMetadataItem.fields.find(
|
||||
(field) =>
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId ||
|
||||
field.name === 'name',
|
||||
);
|
||||
|
||||
if (labelIdentifierFieldMetadata) {
|
||||
switch (labelIdentifierFieldMetadata.type) {
|
||||
case FieldMetadataType.FullName:
|
||||
return {
|
||||
[labelIdentifierFieldMetadata.name]: {
|
||||
firstName: orderBy,
|
||||
lastName: orderBy,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
[labelIdentifierFieldMetadata.name]: orderBy,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
createdAt: orderBy,
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
import toKebabCase from 'lodash.kebabcase';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const getObjectSlug = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'labelPlural'>,
|
||||
) => toKebabCase(objectMetadataItem.labelPlural);
|
||||
@ -0,0 +1,17 @@
|
||||
import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({
|
||||
pagedObjectMetadataItems,
|
||||
}: {
|
||||
pagedObjectMetadataItems: ObjectMetadataItemsQuery | undefined;
|
||||
}) => {
|
||||
const formattedObjects: ObjectMetadataItem[] =
|
||||
pagedObjectMetadataItems?.objects.edges.map((object) => ({
|
||||
...object.node,
|
||||
fields: object.node.fields.edges.map((field) => field.node),
|
||||
})) ?? [];
|
||||
|
||||
return formattedObjects;
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldDefinitionRelationType } from '@/object-record/field/types/FieldDefinition';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const parseFieldRelationType = (
|
||||
field: FieldMetadataItem | undefined,
|
||||
): FieldDefinitionRelationType | undefined => {
|
||||
if (!field || field.type !== FieldMetadataType.Relation) return;
|
||||
|
||||
const config: Record<
|
||||
RelationMetadataType,
|
||||
{ from: FieldDefinitionRelationType; to: FieldDefinitionRelationType }
|
||||
> = {
|
||||
[RelationMetadataType.ManyToMany]: {
|
||||
from: 'FROM_MANY_OBJECTS',
|
||||
to: 'TO_MANY_OBJECTS',
|
||||
},
|
||||
[RelationMetadataType.OneToMany]: {
|
||||
from: 'FROM_MANY_OBJECTS',
|
||||
to: 'TO_ONE_OBJECT',
|
||||
},
|
||||
[RelationMetadataType.OneToOne]: {
|
||||
from: 'FROM_ONE_OBJECT',
|
||||
to: 'TO_ONE_OBJECT',
|
||||
},
|
||||
};
|
||||
|
||||
if (
|
||||
isDefined(field.fromRelationMetadata) &&
|
||||
field.fromRelationMetadata.relationType in config
|
||||
) {
|
||||
return config[field.fromRelationMetadata.relationType].from;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(field.toRelationMetadata) &&
|
||||
field.toRelationMetadata.relationType in config
|
||||
) {
|
||||
return config[field.toRelationMetadata.relationType].to;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot determine field relation type for field : ${JSON.stringify(
|
||||
field,
|
||||
)}.`,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { FieldType } from '@/object-record/field/types/FieldType';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const parseFieldType = (fieldType: FieldMetadataType): FieldType => {
|
||||
if (fieldType === FieldMetadataType.Link) {
|
||||
return 'LINK';
|
||||
}
|
||||
|
||||
if (fieldType === FieldMetadataType.Currency) {
|
||||
return 'CURRENCY';
|
||||
}
|
||||
|
||||
return fieldType as FieldType;
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
const metadataLabelValidationPattern = /^[a-zA-Z][a-zA-Z0-9 ]*$/;
|
||||
|
||||
export const validateMetadataLabel = (value: string) =>
|
||||
!!value.match(metadataLabelValidationPattern);
|
||||
Reference in New Issue
Block a user