({
- query: FIND_MANY_METADATA_OBJECTS,
- });
-
- const formattedObjects: MetadataObject[] =
- objects.data.objects.edges.map((object) => ({
- ...object.node,
- fields: object.node.fields.edges.map((field) => field.node),
- }));
-
- setMetadataObjects(formattedObjects);
- } catch (error) {
- // eslint-disable-next-line no-console
- console.log(error);
- }
- }
- }
- })();
- }, [
- isFlexibleBackendEnabled,
- metadataObjects,
- setMetadataObjects,
- apolloMetadataClient,
- seedCustomObjectsTemp,
- ]);
-
- return <>>;
-};
diff --git a/front/src/modules/metadata/components/MetadataObjectNavItems.tsx b/front/src/modules/metadata/components/MetadataObjectNavItems.tsx
index 18dcb4c61..179fcfe85 100644
--- a/front/src/modules/metadata/components/MetadataObjectNavItems.tsx
+++ b/front/src/modules/metadata/components/MetadataObjectNavItems.tsx
@@ -1,29 +1,58 @@
+import { useNavigate } from 'react-router-dom';
+
+import { IconArchive } from '@/ui/display/icon';
import { IconBuildingSkyscraper } from '@/ui/display/icon';
+import { Button } from '@/ui/input/button/components/Button';
+import { IconButton } from '@/ui/input/button/components/IconButton';
import NavItem from '@/ui/navigation/navbar/components/NavItem';
-import { useGetClientConfigQuery } from '~/generated/graphql';
import { capitalize } from '~/utils/string/capitalize';
+import { useCreateNewTempsCustomObject } from '../hooks/useCreateNewTempCustomObject';
+import { useDeleteOneMetadataObject } from '../hooks/useDeleteOneMetadataObject';
import { useFindManyMetadataObjects } from '../hooks/useFindManyMetadataObjects';
export const MetadataObjectNavItems = () => {
- const { data } = useGetClientConfigQuery();
-
const { metadataObjects } = useFindManyMetadataObjects();
- const isFlexibleBackendEnabled = data?.clientConfig?.flexibleBackendEnabled;
+ // eslint-disable-next-line no-console
+ console.log({
+ metadataObjects,
+ });
- if (!isFlexibleBackendEnabled) return <>>;
+ const createNewTempCustomObject = useCreateNewTempsCustomObject();
+
+ const { deleteOneMetadataObject } = useDeleteOneMetadataObject();
+
+ const navigate = useNavigate();
return (
<>
- {metadataObjects.map((metadataObject) => (
-
- ))}
+
+ {metadataObjects
+ .filter((metadataObject) => !!metadataObject.isActive)
+ .map((metadataObject) => (
+
+ {
+ deleteOneMetadataObject(metadataObject.id);
+ }}
+ />
+ {
+ navigate(`/objects/${metadataObject.namePlural}`);
+ }}
+ />
+
+ ))}
>
);
};
diff --git a/front/src/modules/metadata/components/ObjectDataTableEffect.tsx b/front/src/modules/metadata/components/ObjectDataTableEffect.tsx
index fd51fd0dd..91af7e66d 100644
--- a/front/src/modules/metadata/components/ObjectDataTableEffect.tsx
+++ b/front/src/modules/metadata/components/ObjectDataTableEffect.tsx
@@ -21,15 +21,17 @@ export const ObjectDataTableEffect = ({
}: ObjectDataTableEffectProps) => {
const setDataTableData = useSetObjectDataTableData();
- const { objects } = useFindManyObjects({
+ const { objects, loading } = useFindManyObjects({
objectNamePlural,
});
useEffect(() => {
- const entities = objects ?? [];
+ if (!loading) {
+ const entities = objects ?? [];
- setDataTableData(entities);
- }, [objects, setDataTableData]);
+ setDataTableData(entities);
+ }
+ }, [objects, setDataTableData, loading]);
const [searchParams] = useSearchParams();
const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext);
@@ -61,8 +63,10 @@ export const ObjectDataTableEffect = ({
const viewId = searchParams.get('view');
if (viewId) {
handleViewSelect(viewId);
+ } else {
+ handleViewSelect(objectNamePlural);
}
- }, [handleViewSelect, searchParams]);
+ }, [handleViewSelect, searchParams, objectNamePlural]);
return <>>;
};
diff --git a/front/src/modules/metadata/components/ObjectTable.tsx b/front/src/modules/metadata/components/ObjectTable.tsx
index c0525104e..e597a02a7 100644
--- a/front/src/modules/metadata/components/ObjectTable.tsx
+++ b/front/src/modules/metadata/components/ObjectTable.tsx
@@ -1,10 +1,9 @@
-import { suppliersAvailableColumnDefinitions } from '@/companies/constants/companiesAvailableColumnDefinitions';
import { DataTable } from '@/ui/data/data-table/components/DataTable';
import { TableContext } from '@/ui/data/data-table/contexts/TableContext';
import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { ViewBarContext } from '@/ui/data/view-bar/contexts/ViewBarContext';
-import { useTableViews } from '@/views/hooks/useTableViews';
+import { useMetadataTableViews } from '../hooks/useMetadataTableViews';
import { useUpdateOneObject } from '../hooks/useUpdateOneObject';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
@@ -14,10 +13,7 @@ export type ObjectTableProps = MetadataObjectIdentifier;
export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => {
const { createView, deleteView, submitCurrentView, updateView } =
- useTableViews({
- objectId: 'company',
- columnDefinitions: suppliersAvailableColumnDefinitions,
- });
+ useMetadataTableViews();
const { updateOneObject } = useUpdateOneObject({
objectNamePlural,
diff --git a/front/src/pages/companies/ObjectsTable.tsx b/front/src/modules/metadata/components/ObjectTablePage.tsx
similarity index 65%
rename from front/src/pages/companies/ObjectsTable.tsx
rename to front/src/modules/metadata/components/ObjectTablePage.tsx
index 20c17e571..6f7993763 100644
--- a/front/src/pages/companies/ObjectsTable.tsx
+++ b/front/src/modules/metadata/components/ObjectTablePage.tsx
@@ -1,4 +1,5 @@
-import { useParams } from 'react-router-dom';
+import { useEffect } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { ObjectTable } from '@/metadata/components/ObjectTable';
@@ -14,6 +15,10 @@ import { PageHeader } from '@/ui/layout/page/PageHeader';
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
+import { useCreateOneObject } from '../hooks/useCreateOneObject';
+import { useFindOneMetadataObject } from '../hooks/useFindOneMetadataObject';
+import { MetadataObjectScope } from '../scopes/MetadataObjectScope';
+
const StyledTableContainer = styled.div`
display: flex;
width: 100%;
@@ -24,8 +29,26 @@ export type ObjectTablePageProps = MetadataObjectIdentifier;
export const ObjectTablePage = () => {
const objectNamePlural = useParams().objectNamePlural ?? '';
+ const { objectNotFoundInMetadata, loading } = useFindOneMetadataObject({
+ objectNamePlural,
+ });
+
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!loading && objectNotFoundInMetadata) {
+ navigate('/');
+ }
+ }, [objectNotFoundInMetadata, loading, navigate]);
+
+ const { createOneObject } = useCreateOneObject({
+ objectNamePlural,
+ });
+
const handleAddButtonClick = async () => {
- //
+ createOneObject?.({
+ name: 'Test',
+ });
};
return (
@@ -36,11 +59,13 @@ export const ObjectTablePage = () => {
-
+
+
+
diff --git a/front/src/modules/metadata/graphql/mutations.ts b/front/src/modules/metadata/graphql/mutations.ts
index 3b9cb4080..b40ef9115 100644
--- a/front/src/modules/metadata/graphql/mutations.ts
+++ b/front/src/modules/metadata/graphql/mutations.ts
@@ -4,6 +4,17 @@ export const CREATE_ONE_METADATA_OBJECT = gql`
mutation CreateOneMetadataObject($input: CreateOneObjectInput!) {
createOneObject(input: $input) {
id
+ dataSourceId
+ nameSingular
+ namePlural
+ labelSingular
+ labelPlural
+ description
+ icon
+ isCustom
+ isActive
+ createdAt
+ updatedAt
}
}
`;
@@ -34,6 +45,17 @@ export const UPDATE_ONE_METADATA_FIELD = gql`
) {
updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {
id
+ type
+ name
+ label
+ description
+ icon
+ placeholder
+ isCustom
+ isActive
+ isNullable
+ createdAt
+ updatedAt
}
}
`;
@@ -45,6 +67,55 @@ export const UPDATE_ONE_METADATA_OBJECT = gql`
) {
updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {
id
+ dataSourceId
+ nameSingular
+ namePlural
+ labelSingular
+ labelPlural
+ description
+ icon
+ isCustom
+ isActive
+ createdAt
+ updatedAt
+ }
+ }
+`;
+
+export const DELETE_ONE_METADATA_OBJECT = gql`
+ mutation DeleteOneMetadataObject($idToDelete: ID!) {
+ deleteOneObject(input: { id: $idToDelete }) {
+ id
+ dataSourceId
+ nameSingular
+ namePlural
+ labelSingular
+ labelPlural
+ description
+ icon
+ isCustom
+ isActive
+ createdAt
+ updatedAt
+ }
+ }
+`;
+
+export const DELETE_ONE_METADATA_FIELD = gql`
+ mutation DeleteOneMetadataField($idToDelete: ID!) {
+ deleteOneField(input: { id: $idToDelete }) {
+ id
+ type
+ name
+ label
+ description
+ icon
+ placeholder
+ isCustom
+ isActive
+ isNullable
+ createdAt
+ updatedAt
}
}
`;
diff --git a/front/src/modules/metadata/hooks/useCreateNewTempCustomObject.ts b/front/src/modules/metadata/hooks/useCreateNewTempCustomObject.ts
new file mode 100644
index 000000000..ecbcf8242
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useCreateNewTempCustomObject.ts
@@ -0,0 +1,160 @@
+import { getOperationName } from '@apollo/client/utilities';
+
+import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
+import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
+import { FieldType } from '@/ui/data/field/types/FieldType';
+import { IconBrandLinkedin } from '@/ui/display/icon';
+import { GET_VIEW_FIELDS } from '@/views/graphql/queries/getViewFields';
+import { GET_VIEWS } from '@/views/graphql/queries/getViews';
+import { toViewFieldInput } from '@/views/hooks/useTableViewFields';
+import {
+ useCreateViewFieldsMutation,
+ useCreateViewMutation,
+ ViewType,
+} from '~/generated/graphql';
+
+import { useCreateOneMetadataField } from './useCreateOneMetadataField';
+import { useCreateOneMetadataObject } from './useCreateOneMetadataObject';
+import { useUpdateOneMetadataField } from './useUpdateOneMetadataField';
+import { useUpdateOneMetadataObject } from './useUpdateOneMetadataObject';
+
+export const useCreateNewTempsCustomObject = () => {
+ const { createOneMetadataObject } = useCreateOneMetadataObject();
+ const { createOneMetadataField } = useCreateOneMetadataField();
+
+ const { updateOneMetadataObject } = useUpdateOneMetadataObject();
+ const { updateOneMetadataField } = useUpdateOneMetadataField();
+
+ const [createViewMutation] = useCreateViewMutation();
+ const [createViewFieldsMutation] = useCreateViewFieldsMutation();
+
+ return async () => {
+ const date = new Date().toISOString().replace(/[\/:\.\-\_]/g, '');
+
+ const { data: createdMetadataObject } = await createOneMetadataObject({
+ labelPlural: 'Suppliers' + date,
+ labelSingular: 'Supplier' + date,
+ nameSingular: 'supplier' + date,
+ namePlural: 'suppliers' + date,
+ description: 'Suppliers' + date,
+ icon: 'IconBuilding',
+ });
+
+ const supplierObjectId = createdMetadataObject?.createOneObject?.id ?? '';
+
+ if (!createdMetadataObject) {
+ throw new Error('Could not create metadata object');
+ }
+
+ await updateOneMetadataObject({
+ idToUpdate: supplierObjectId,
+ updatePayload: {
+ isActive: true,
+ },
+ });
+
+ const { data: nameFieldData } = await createOneMetadataField({
+ objectId: supplierObjectId,
+ name: 'name',
+ type: 'text',
+ description: 'Name',
+ label: 'Name',
+ icon: 'IconBuilding',
+ });
+
+ if (!nameFieldData || !nameFieldData.createOneField.name) {
+ throw new Error('Could not create metadata field');
+ }
+
+ await updateOneMetadataField({
+ fieldIdToUpdate: nameFieldData?.createOneField?.id ?? '',
+ updatePayload: {
+ isActive: true,
+ },
+ });
+
+ const { data: cityFieldData } = await createOneMetadataField({
+ objectId: supplierObjectId,
+ label: 'City',
+ name: 'city',
+ type: 'text',
+ description: 'City',
+ icon: 'IconMap',
+ });
+
+ if (!cityFieldData || !cityFieldData.createOneField.name) {
+ throw new Error('Could not create metadata field');
+ }
+
+ await updateOneMetadataField({
+ fieldIdToUpdate: cityFieldData?.createOneField?.id ?? '',
+ updatePayload: {
+ isActive: true,
+ },
+ });
+
+ const { data: emailFieldData } = await createOneMetadataField({
+ objectId: supplierObjectId,
+ label: 'Email',
+ name: 'email',
+ type: 'url',
+ description: 'Email',
+ icon: 'IconMap',
+ });
+
+ if (!emailFieldData || !emailFieldData.createOneField.name) {
+ throw new Error('Could not create metadata field');
+ }
+
+ await updateOneMetadataField({
+ fieldIdToUpdate: emailFieldData?.createOneField?.id ?? '',
+ updatePayload: {
+ isActive: true,
+ },
+ });
+
+ const objectId = 'suppliers' + date;
+
+ const { data: newView } = await createViewMutation({
+ variables: {
+ data: {
+ name: 'Default',
+ objectId: objectId,
+ type: ViewType.Table,
+ },
+ },
+ refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
+ });
+
+ const createdFields = [
+ emailFieldData.createOneField,
+ nameFieldData.createOneField,
+ cityFieldData.createOneField,
+ ];
+
+ const tempColumnDefinitions: ColumnDefinition[] =
+ createdFields.map((field, index) => ({
+ index,
+ key: field.name,
+ name: field.label,
+ size: 100,
+ type: field.type as FieldType,
+ metadata: {
+ fieldName: field.name,
+ placeHolder: field.label,
+ },
+ Icon: IconBrandLinkedin,
+ isVisible: true,
+ })) ?? [];
+
+ await createViewFieldsMutation({
+ variables: {
+ data: tempColumnDefinitions.map((column) => ({
+ ...toViewFieldInput(objectId, column),
+ viewId: newView?.view.id ?? '',
+ })),
+ },
+ refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
+ });
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useCreateOneMetadataField.ts b/front/src/modules/metadata/hooks/useCreateOneMetadataField.ts
index 65f65f1c8..ac956c153 100644
--- a/front/src/modules/metadata/hooks/useCreateOneMetadataField.ts
+++ b/front/src/modules/metadata/hooks/useCreateOneMetadataField.ts
@@ -1,6 +1,7 @@
import { ApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
+import { FieldType } from '@/ui/data/field/types/FieldType';
import {
CreateOneMetadataFieldMutation,
CreateOneMetadataFieldMutationVariables,
@@ -11,6 +12,11 @@ import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
+type CreateOneMetadataFieldArgs =
+ CreateOneMetadataFieldMutationVariables['input']['field'] & {
+ type: FieldType;
+ };
+
export const useCreateOneMetadataField = () => {
const apolloMetadataClient = useApolloMetadataClient();
@@ -21,10 +27,8 @@ export const useCreateOneMetadataField = () => {
client: apolloMetadataClient ?? ({} as ApolloClient),
});
- const createOneMetadataField = (
- input: CreateOneMetadataFieldMutationVariables['input']['field'],
- ) =>
- mutate({
+ const createOneMetadataField = async (input: CreateOneMetadataFieldArgs) => {
+ return await mutate({
variables: {
input: {
field: {
@@ -32,8 +36,10 @@ export const useCreateOneMetadataField = () => {
},
},
},
+ awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
+ };
return {
createOneMetadataField,
diff --git a/front/src/modules/metadata/hooks/useCreateOneMetadataObject.ts b/front/src/modules/metadata/hooks/useCreateOneMetadataObject.ts
index 5de321fa9..bd8a7ce18 100644
--- a/front/src/modules/metadata/hooks/useCreateOneMetadataObject.ts
+++ b/front/src/modules/metadata/hooks/useCreateOneMetadataObject.ts
@@ -21,10 +21,10 @@ export const useCreateOneMetadataObject = () => {
client: apolloMetadataClient ?? ({} as ApolloClient),
});
- const createOneMetadataObject = (
+ const createOneMetadataObject = async (
input: CreateOneMetadataObjectMutationVariables['input']['object'],
- ) =>
- mutate({
+ ) => {
+ return await mutate({
variables: {
input: {
object: {
@@ -32,8 +32,10 @@ export const useCreateOneMetadataObject = () => {
},
},
},
+ awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
+ };
return {
createOneMetadataObject,
diff --git a/front/src/modules/metadata/hooks/useCreateOneObject.ts b/front/src/modules/metadata/hooks/useCreateOneObject.ts
index e7bbe8e91..0f22cbd0c 100644
--- a/front/src/modules/metadata/hooks/useCreateOneObject.ts
+++ b/front/src/modules/metadata/hooks/useCreateOneObject.ts
@@ -1,30 +1,24 @@
-import { gql, useMutation } from '@apollo/client';
+import { useMutation } from '@apollo/client';
+import { getOperationName } from '@apollo/client/utilities';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
-import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation';
import { useFindOneMetadataObject } from './useFindOneMetadataObject';
export const useCreateOneObject = ({
objectNamePlural,
}: MetadataObjectIdentifier) => {
- const { foundMetadataObject, objectNotFoundInMetadata } =
- useFindOneMetadataObject({
- objectNamePlural,
- });
-
- const generatedMutation = foundMetadataObject
- ? generateCreateOneObjectMutation({
- metadataObject: foundMetadataObject,
- })
- : gql`
- mutation EmptyMutation {
- empty
- }
- `;
+ const {
+ foundMetadataObject,
+ objectNotFoundInMetadata,
+ findManyQuery,
+ createOneMutation,
+ } = useFindOneMetadataObject({
+ objectNamePlural,
+ });
// TODO: type this with a minimal type at least with Record
- const [mutate] = useMutation(generatedMutation);
+ const [mutate] = useMutation(createOneMutation);
const createOneObject = foundMetadataObject
? (input: Record) => {
@@ -34,6 +28,7 @@ export const useCreateOneObject = ({
...input,
},
},
+ refetchQueries: [getOperationName(findManyQuery) ?? ''],
});
}
: undefined;
diff --git a/front/src/modules/metadata/hooks/useDeleteOneMetadataField.ts b/front/src/modules/metadata/hooks/useDeleteOneMetadataField.ts
new file mode 100644
index 000000000..592604438
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useDeleteOneMetadataField.ts
@@ -0,0 +1,39 @@
+import { ApolloClient, useMutation } from '@apollo/client';
+import { getOperationName } from '@apollo/client/utilities';
+
+import {
+ DeleteOneMetadataFieldMutation,
+ DeleteOneMetadataFieldMutationVariables,
+} from '~/generated-metadata/graphql';
+
+import { DELETE_ONE_METADATA_FIELD } from '../graphql/mutations';
+import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
+
+import { useApolloMetadataClient } from './useApolloMetadataClient';
+
+export const useDeleteOneMetadataField = () => {
+ const apolloMetadataClient = useApolloMetadataClient();
+
+ const [mutate] = useMutation<
+ DeleteOneMetadataFieldMutation,
+ DeleteOneMetadataFieldMutationVariables
+ >(DELETE_ONE_METADATA_FIELD, {
+ client: apolloMetadataClient ?? ({} as ApolloClient),
+ });
+
+ const deleteOneMetadataField = async (
+ idToDelete: DeleteOneMetadataFieldMutationVariables['idToDelete'],
+ ) => {
+ return await mutate({
+ variables: {
+ idToDelete,
+ },
+ awaitRefetchQueries: true,
+ refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
+ });
+ };
+
+ return {
+ deleteOneMetadataField,
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useDeleteOneMetadataObject.ts b/front/src/modules/metadata/hooks/useDeleteOneMetadataObject.ts
new file mode 100644
index 000000000..e8603ba0e
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useDeleteOneMetadataObject.ts
@@ -0,0 +1,39 @@
+import { ApolloClient, useMutation } from '@apollo/client';
+import { getOperationName } from '@apollo/client/utilities';
+
+import {
+ DeleteOneMetadataObjectMutation,
+ DeleteOneMetadataObjectMutationVariables,
+} from '~/generated-metadata/graphql';
+
+import { DELETE_ONE_METADATA_OBJECT } from '../graphql/mutations';
+import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
+
+import { useApolloMetadataClient } from './useApolloMetadataClient';
+
+export const useDeleteOneMetadataObject = () => {
+ const apolloMetadataClient = useApolloMetadataClient();
+
+ const [mutate] = useMutation<
+ DeleteOneMetadataObjectMutation,
+ DeleteOneMetadataObjectMutationVariables
+ >(DELETE_ONE_METADATA_OBJECT, {
+ client: apolloMetadataClient ?? ({} as ApolloClient),
+ });
+
+ const deleteOneMetadataObject = async (
+ idToDelete: DeleteOneMetadataObjectMutationVariables['idToDelete'],
+ ) => {
+ return await mutate({
+ variables: {
+ idToDelete,
+ },
+ awaitRefetchQueries: true,
+ refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
+ });
+ };
+
+ return {
+ deleteOneMetadataObject,
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useDeleteOneObject.ts b/front/src/modules/metadata/hooks/useDeleteOneObject.ts
new file mode 100644
index 000000000..034c68dff
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useDeleteOneObject.ts
@@ -0,0 +1,40 @@
+import { useMutation } from '@apollo/client';
+import { getOperationName } from '@apollo/client/utilities';
+
+import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
+
+import { useFindOneMetadataObject } from './useFindOneMetadataObject';
+
+export const useDeleteOneObject = ({
+ objectNamePlural,
+}: MetadataObjectIdentifier) => {
+ const {
+ foundMetadataObject,
+ objectNotFoundInMetadata,
+ findManyQuery,
+ deleteOneMutation,
+ } = useFindOneMetadataObject({
+ objectNamePlural,
+ });
+
+ // TODO: type this with a minimal type at least with Record
+ const [mutate] = useMutation(deleteOneMutation);
+
+ const deleteOneObject = foundMetadataObject
+ ? (input: Record) => {
+ return mutate({
+ variables: {
+ input: {
+ ...input,
+ },
+ },
+ refetchQueries: [getOperationName(findManyQuery) ?? ''],
+ });
+ }
+ : undefined;
+
+ return {
+ deleteOneObject,
+ objectNotFoundInMetadata,
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts b/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts
index ea77646c9..dfd92d535 100644
--- a/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts
+++ b/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts
@@ -15,13 +15,17 @@ import { useApolloMetadataClient } from './useApolloMetadataClient';
export const useFindManyMetadataObjects = () => {
const apolloMetadataClient = useApolloMetadataClient();
- const { data, fetchMore: fetchMoreInternal } = useQuery<
- MetadataObjectsQuery,
- MetadataObjectsQueryVariables
- >(FIND_MANY_METADATA_OBJECTS, {
- client: apolloMetadataClient ?? undefined,
- skip: !apolloMetadataClient,
- });
+ const {
+ data,
+ fetchMore: fetchMoreInternal,
+ loading,
+ } = useQuery(
+ FIND_MANY_METADATA_OBJECTS,
+ {
+ client: apolloMetadataClient ?? undefined,
+ skip: !apolloMetadataClient,
+ },
+ );
const hasMore = data?.objects?.pageInfo?.hasNextPage;
@@ -38,23 +42,10 @@ export const useFindManyMetadataObjects = () => {
});
}, [data]);
- const getMetadataObjectsFromCache = () => {
- const queryResult = apolloMetadataClient?.readQuery<
- MetadataObjectsQuery,
- MetadataObjectsQueryVariables
- >({
- query: FIND_MANY_METADATA_OBJECTS,
- });
-
- return formatPagedMetadataObjectsToMetadataObjects({
- pagedMetadataObjects: queryResult ?? undefined,
- });
- };
-
return {
metadataObjects,
hasMore,
fetchMore,
- getMetadataObjectsFromCache,
+ loading,
};
};
diff --git a/front/src/modules/metadata/hooks/useFindManyObjects.ts b/front/src/modules/metadata/hooks/useFindManyObjects.ts
index 3b0678452..e0293d18e 100644
--- a/front/src/modules/metadata/hooks/useFindManyObjects.ts
+++ b/front/src/modules/metadata/hooks/useFindManyObjects.ts
@@ -1,10 +1,9 @@
import { useMemo } from 'react';
-import { gql, useQuery } from '@apollo/client';
+import { useQuery } from '@apollo/client';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
import { PaginatedObjectType } from '../types/PaginatedObjectType';
import { formatPagedObjectsToObjects } from '../utils/formatPagedObjectsToObjects';
-import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
import { useFindOneMetadataObject } from './useFindOneMetadataObject';
@@ -15,23 +14,13 @@ export const useFindManyObjects = <
>({
objectNamePlural,
}: MetadataObjectIdentifier) => {
- const { foundMetadataObject, objectNotFoundInMetadata } =
+ const { foundMetadataObject, objectNotFoundInMetadata, findManyQuery } =
useFindOneMetadataObject({
objectNamePlural,
});
- const generatedQuery = foundMetadataObject
- ? generateFindManyCustomObjectsQuery({
- metadataObject: foundMetadataObject,
- })
- : gql`
- query EmptyQuery {
- empty
- }
- `;
-
const { data, loading, error } = useQuery>(
- generatedQuery,
+ findManyQuery,
{
skip: !foundMetadataObject,
},
diff --git a/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts b/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts
index e4c584c27..2fba062c4 100644
--- a/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts
+++ b/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts
@@ -1,21 +1,80 @@
+import { gql } from '@apollo/client';
+
+import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
+import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
+
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
+import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition';
+import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation';
+import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
export const useFindOneMetadataObject = ({
objectNamePlural,
}: MetadataObjectIdentifier) => {
- const { metadataObjects } = useFindManyMetadataObjects();
+ const { metadataObjects, loading } = useFindManyMetadataObjects();
const foundMetadataObject = metadataObjects.find(
(object) => object.namePlural === objectNamePlural,
);
const objectNotFoundInMetadata =
- metadataObjects.length > 0 && !foundMetadataObject;
+ metadataObjects.length === 0 ||
+ (metadataObjects.length > 0 && !foundMetadataObject);
+
+ const columnDefinitions: ColumnDefinition[] =
+ foundMetadataObject?.fields.map((field, index) =>
+ formatMetadataFieldAsColumnDefinition({
+ index,
+ field,
+ }),
+ ) ?? [];
+
+ // eslint-disable-next-line no-console
+ console.log({
+ foundMetadataObject,
+ columnDefinitions,
+ });
+
+ const findManyQuery = foundMetadataObject
+ ? generateFindManyCustomObjectsQuery({
+ metadataObject: foundMetadataObject,
+ })
+ : gql`
+ query EmptyQuery {
+ empty
+ }
+ `;
+
+ const createOneMutation = foundMetadataObject
+ ? generateCreateOneObjectMutation({
+ metadataObject: foundMetadataObject,
+ })
+ : gql`
+ mutation EmptyMutation {
+ empty
+ }
+ `;
+
+ // TODO: implement backend delete
+ const deleteOneMutation = foundMetadataObject
+ ? generateCreateOneObjectMutation({
+ metadataObject: foundMetadataObject,
+ })
+ : gql`
+ mutation EmptyMutation {
+ empty
+ }
+ `;
return {
foundMetadataObject,
objectNotFoundInMetadata,
+ columnDefinitions,
+ findManyQuery,
+ createOneMutation,
+ deleteOneMutation,
+ loading,
};
};
diff --git a/front/src/modules/metadata/hooks/useMetadataObject.ts b/front/src/modules/metadata/hooks/useMetadataObject.ts
new file mode 100644
index 000000000..75412dae6
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useMetadataObject.ts
@@ -0,0 +1,18 @@
+import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
+
+import { MetadataObjectScopeInternalContext } from '../scopes/scope-internal-context/MetadataObjectScopeInternalContext';
+
+type UseMetadataObjectProps = {
+ metadataObjectNamePlural?: string;
+};
+
+export const useMetadataObject = (props?: UseMetadataObjectProps) => {
+ const scopeId = useAvailableScopeIdOrThrow(
+ MetadataObjectScopeInternalContext,
+ props?.metadataObjectNamePlural,
+ );
+
+ return {
+ scopeId,
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts b/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts
new file mode 100644
index 000000000..83967ae8c
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts
@@ -0,0 +1,27 @@
+import { useContext } from 'react';
+
+import { MetadataObjectScopeInternalContext } from '../scopes/scope-internal-context/MetadataObjectScopeInternalContext';
+
+import { useFindOneMetadataObject } from './useFindOneMetadataObject';
+
+export const useMetadataObjectInContext = () => {
+ const context = useContext(MetadataObjectScopeInternalContext);
+
+ if (!context) {
+ throw new Error(
+ 'Could not find MetadataObjectScopeInternalContext while in useMetadataObjectInContext',
+ );
+ }
+
+ const { foundMetadataObject, loading, columnDefinitions } =
+ useFindOneMetadataObject({
+ objectNamePlural: context.objectNamePlural,
+ });
+
+ return {
+ ...context,
+ foundMetadataObject,
+ loading,
+ columnDefinitions,
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useMetadataTableViews.ts b/front/src/modules/metadata/hooks/useMetadataTableViews.ts
new file mode 100644
index 000000000..fef774933
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useMetadataTableViews.ts
@@ -0,0 +1,81 @@
+import { useSearchParams } from 'react-router-dom';
+
+import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
+import { tableColumnsScopedState } from '@/ui/data/data-table/states/tableColumnsScopedState';
+import { filtersScopedState } from '@/ui/data/view-bar/states/filtersScopedState';
+import { sortsScopedState } from '@/ui/data/view-bar/states/sortsScopedState';
+import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
+import { useTableViewFields } from '@/views/hooks/useTableViewFields';
+import { useViewFilters } from '@/views/hooks/useViewFilters';
+import { useViews } from '@/views/hooks/useViews';
+import { useViewSorts } from '@/views/hooks/useViewSorts';
+import { ViewType } from '~/generated/graphql';
+
+import { useMetadataObjectInContext } from './useMetadataObjectInContext';
+
+export const useMetadataTableViews = () => {
+ const { objectNamePlural, columnDefinitions } = useMetadataObjectInContext();
+
+ const tableColumns = useRecoilScopedValue(
+ tableColumnsScopedState,
+ TableRecoilScopeContext,
+ );
+ const filters = useRecoilScopedValue(
+ filtersScopedState,
+ TableRecoilScopeContext,
+ );
+ const sorts = useRecoilScopedValue(sortsScopedState, TableRecoilScopeContext);
+
+ const [_, setSearchParams] = useSearchParams();
+
+ const handleViewCreate = async (viewId: string) => {
+ await createViewFields(tableColumns, viewId);
+ await createViewFilters(filters, viewId);
+ await createViewSorts(sorts, viewId);
+ setSearchParams({ view: viewId });
+ };
+
+ const objectId = objectNamePlural;
+
+ const { createView, deleteView, isFetchingViews, updateView } = useViews({
+ objectId,
+ onViewCreate: handleViewCreate,
+ type: ViewType.Table,
+ RecoilScopeContext: TableRecoilScopeContext,
+ });
+
+ const { createViewFields, persistColumns } = useTableViewFields({
+ objectId,
+ columnDefinitions,
+ skipFetch: isFetchingViews,
+ });
+
+ const createDefaultViewFields = async () => {
+ await createViewFields(tableColumns);
+ };
+
+ const { createViewFilters, persistFilters } = useViewFilters({
+ RecoilScopeContext: TableRecoilScopeContext,
+ skipFetch: isFetchingViews,
+ });
+
+ const { createViewSorts, persistSorts } = useViewSorts({
+ RecoilScopeContext: TableRecoilScopeContext,
+ skipFetch: isFetchingViews,
+ });
+
+ const submitCurrentView = async () => {
+ await persistFilters();
+ await persistSorts();
+ };
+
+ return {
+ createView,
+ deleteView,
+ persistColumns,
+ submitCurrentView,
+ updateView,
+ createDefaultViewFields,
+ isFetchingViews,
+ };
+};
diff --git a/front/src/modules/metadata/hooks/useSeedCustomObjectsTemp.ts b/front/src/modules/metadata/hooks/useSeedCustomObjectsTemp.ts
index 2a7e28b19..e2fd0f28a 100644
--- a/front/src/modules/metadata/hooks/useSeedCustomObjectsTemp.ts
+++ b/front/src/modules/metadata/hooks/useSeedCustomObjectsTemp.ts
@@ -2,16 +2,11 @@ import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
import { useCreateOneMetadataField } from './useCreateOneMetadataField';
import { useCreateOneMetadataObject } from './useCreateOneMetadataObject';
-import { useUpdateOneMetadataField } from './useUpdateOneMetadataField';
-import { useUpdateOneMetadataObject } from './useUpdateOneMetadataObject';
export const useSeedCustomObjectsTemp = () => {
const { createOneMetadataObject } = useCreateOneMetadataObject();
const { createOneMetadataField } = useCreateOneMetadataField();
- const { updateOneMetadataObject } = useUpdateOneMetadataObject();
- const { updateOneMetadataField } = useUpdateOneMetadataField();
-
return async () => {
const { data: createdMetadataObject, errors } =
await createOneMetadataObject({
@@ -26,7 +21,7 @@ export const useSeedCustomObjectsTemp = () => {
if (!isNonEmptyArray(errors)) {
const supplierObjectId = createdMetadataObject?.createOneObject?.id ?? '';
- const { data: createNameFieldData } = await createOneMetadataField({
+ await createOneMetadataField({
objectId: supplierObjectId,
name: 'name',
type: 'text',
@@ -35,9 +30,7 @@ export const useSeedCustomObjectsTemp = () => {
icon: 'IconBuilding',
});
- const nameFieldId = createNameFieldData?.createOneField.id ?? '';
-
- const { data: createCityFieldData } = await createOneMetadataField({
+ await createOneMetadataField({
objectId: supplierObjectId,
label: 'City',
name: 'city',
@@ -45,31 +38,6 @@ export const useSeedCustomObjectsTemp = () => {
description: 'City',
icon: 'IconMap',
});
-
- const cityFieldId = createCityFieldData?.createOneField.id ?? '';
-
- await updateOneMetadataObject({
- idToUpdate: supplierObjectId,
- updatePayload: {
- labelPlural: 'Suppliers 2',
- },
- });
-
- await updateOneMetadataField({
- objectIdToUpdate: supplierObjectId,
- fieldIdToUpdate: cityFieldId,
- updatePayload: {
- label: 'City 2',
- },
- });
-
- await updateOneMetadataField({
- objectIdToUpdate: supplierObjectId,
- fieldIdToUpdate: nameFieldId,
- updatePayload: {
- label: 'Name 2',
- },
- });
}
};
};
diff --git a/front/src/modules/metadata/hooks/useUpdateOneMetadataField.ts b/front/src/modules/metadata/hooks/useUpdateOneMetadataField.ts
index 1eabc93cb..f10c937c6 100644
--- a/front/src/modules/metadata/hooks/useUpdateOneMetadataField.ts
+++ b/front/src/modules/metadata/hooks/useUpdateOneMetadataField.ts
@@ -10,13 +10,10 @@ import { UPDATE_ONE_METADATA_FIELD } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
-import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
export const useUpdateOneMetadataField = () => {
const apolloMetadataClient = useApolloMetadataClient();
- const { getMetadataObjectsFromCache } = useFindManyMetadataObjects();
-
const [mutate] = useMutation<
UpdateOneMetadataFieldMutation,
UpdateOneMetadataFieldMutationVariables
@@ -24,48 +21,24 @@ export const useUpdateOneMetadataField = () => {
client: apolloMetadataClient ?? undefined,
});
- const updateOneMetadataField = ({
- objectIdToUpdate,
+ const updateOneMetadataField = async ({
fieldIdToUpdate,
updatePayload,
}: {
- objectIdToUpdate: string;
fieldIdToUpdate: UpdateOneMetadataFieldMutationVariables['idToUpdate'];
- updatePayload: Partial<
- Pick<
- UpdateOneMetadataFieldMutationVariables['updatePayload'],
- 'description' | 'icon' | 'isActive' | 'label'
- >
+ updatePayload: Pick<
+ UpdateOneMetadataFieldMutationVariables['updatePayload'],
+ 'description' | 'icon' | 'isActive' | 'label'
>;
}) => {
- const metadataObjects = getMetadataObjectsFromCache();
-
- const foundMetadataObject = metadataObjects.find(
- (metadataObject) => metadataObject.id === objectIdToUpdate,
- );
-
- if (!foundMetadataObject)
- throw new Error(`Metadata object with id ${objectIdToUpdate} not found`);
-
- const foundMetadataField = foundMetadataObject.fields.find(
- (metadataField) => metadataField.id === fieldIdToUpdate,
- );
-
- if (!foundMetadataField)
- throw new Error(`Metadata field with id ${fieldIdToUpdate} not found`);
-
- return mutate({
+ return await mutate({
variables: {
idToUpdate: fieldIdToUpdate,
updatePayload: {
- name: foundMetadataField.name,
- description: foundMetadataField.description,
- icon: foundMetadataField.icon,
- isActive: foundMetadataField.isActive,
- label: foundMetadataField.label,
- ...updatePayload,
+ label: updatePayload.label ?? undefined,
},
},
+ awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
diff --git a/front/src/modules/metadata/hooks/useUpdateOneMetadataObject.ts b/front/src/modules/metadata/hooks/useUpdateOneMetadataObject.ts
index 5198f9afb..10a989f0b 100644
--- a/front/src/modules/metadata/hooks/useUpdateOneMetadataObject.ts
+++ b/front/src/modules/metadata/hooks/useUpdateOneMetadataObject.ts
@@ -10,14 +10,11 @@ import { UPDATE_ONE_METADATA_OBJECT } from '../graphql/mutations';
import { FIND_MANY_METADATA_OBJECTS } from '../graphql/queries';
import { useApolloMetadataClient } from './useApolloMetadataClient';
-import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
// 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 useUpdateOneMetadataObject = () => {
const apolloClientMetadata = useApolloMetadataClient();
- const { getMetadataObjectsFromCache } = useFindManyMetadataObjects();
-
const [mutate] = useMutation<
UpdateOneMetadataObjectMutation,
UpdateOneMetadataObjectMutationVariables
@@ -25,41 +22,22 @@ export const useUpdateOneMetadataObject = () => {
client: apolloClientMetadata ?? undefined,
});
- const updateOneMetadataObject = ({
+ const updateOneMetadataObject = async ({
idToUpdate,
updatePayload,
}: {
idToUpdate: UpdateOneMetadataObjectMutationVariables['idToUpdate'];
- updatePayload: Partial<
- Pick<
- UpdateOneMetadataObjectMutationVariables['updatePayload'],
- 'description' | 'icon' | 'isActive' | 'labelPlural' | 'labelSingular'
- >
+ updatePayload: Pick<
+ UpdateOneMetadataObjectMutationVariables['updatePayload'],
+ 'description' | 'icon' | 'isActive' | 'labelPlural' | 'labelSingular'
>;
}) => {
- const metadataObjects = getMetadataObjectsFromCache();
-
- const foundMetadataObject = metadataObjects.find(
- (metadataObject) => metadataObject.id === idToUpdate,
- );
-
- if (!foundMetadataObject)
- throw new Error(`Metadata object with id ${idToUpdate} not found`);
-
- return mutate({
+ return await mutate({
variables: {
idToUpdate,
- updatePayload: {
- namePlural: foundMetadataObject.namePlural,
- nameSingular: foundMetadataObject.nameSingular,
- description: foundMetadataObject.description,
- icon: foundMetadataObject.icon,
- isActive: foundMetadataObject.isActive,
- labelPlural: foundMetadataObject.labelPlural,
- labelSingular: foundMetadataObject.labelSingular,
- ...updatePayload,
- },
+ updatePayload,
},
+ awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_METADATA_OBJECTS) ?? ''],
});
};
diff --git a/front/src/modules/metadata/scopes/MetadataObjectScope.tsx b/front/src/modules/metadata/scopes/MetadataObjectScope.tsx
new file mode 100644
index 000000000..428179355
--- /dev/null
+++ b/front/src/modules/metadata/scopes/MetadataObjectScope.tsx
@@ -0,0 +1,24 @@
+import { ReactNode } from 'react';
+
+import { MetadataObjectScopeInternalContext } from './scope-internal-context/MetadataObjectScopeInternalContext';
+
+type MetadataObjectScopeProps = {
+ children: ReactNode;
+ metadataObjectNamePlural: string;
+};
+
+export const MetadataObjectScope = ({
+ children,
+ metadataObjectNamePlural,
+}: MetadataObjectScopeProps) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/front/src/modules/metadata/scopes/scope-internal-context/MetadataObjectScopeInternalContext.ts b/front/src/modules/metadata/scopes/scope-internal-context/MetadataObjectScopeInternalContext.ts
new file mode 100644
index 000000000..c36a76cd2
--- /dev/null
+++ b/front/src/modules/metadata/scopes/scope-internal-context/MetadataObjectScopeInternalContext.ts
@@ -0,0 +1,9 @@
+import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey';
+import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
+
+type MetadataObjectScopeInternalContextProps = ScopedStateKey & {
+ objectNamePlural: string;
+};
+
+export const MetadataObjectScopeInternalContext =
+ createScopeInternalContext();
diff --git a/front/src/modules/metadata/states/metadataObjectsState.ts b/front/src/modules/metadata/states/metadataObjectsState.ts
index 1a7304b47..122425b1c 100644
--- a/front/src/modules/metadata/states/metadataObjectsState.ts
+++ b/front/src/modules/metadata/states/metadataObjectsState.ts
@@ -2,6 +2,9 @@ import { atom } from 'recoil';
import { MetadataObject } from '../types/MetadataObject';
+/**
+ * @deprecated Use `useFindManyMetadataObjects` instead.
+ */
export const metadataObjectsState = atom({
key: 'metadataObjectsState',
default: [],
diff --git a/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts b/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts
new file mode 100644
index 000000000..18558d8da
--- /dev/null
+++ b/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts
@@ -0,0 +1,34 @@
+import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
+import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
+import { FieldType } from '@/ui/data/field/types/FieldType';
+import { IconBrandLinkedin } from '@/ui/display/icon';
+
+import { MetadataObject } from '../types/MetadataObject';
+
+const parseFieldType = (fieldType: string): FieldType => {
+ if (fieldType === 'url') {
+ return 'urlV2';
+ }
+
+ return fieldType as FieldType;
+};
+
+export const formatMetadataFieldAsColumnDefinition = ({
+ index,
+ field,
+}: {
+ index: number;
+ field: MetadataObject['fields'][0];
+}): ColumnDefinition => ({
+ index,
+ key: field.name,
+ name: field.label,
+ size: 100,
+ type: parseFieldType(field.type),
+ metadata: {
+ fieldName: field.name,
+ placeHolder: field.label,
+ },
+ Icon: IconBrandLinkedin,
+ isVisible: true,
+});
diff --git a/front/src/modules/metadata/utils/generateDeleteOneObjectMutation.ts b/front/src/modules/metadata/utils/generateDeleteOneObjectMutation.ts
new file mode 100644
index 000000000..23353fecd
--- /dev/null
+++ b/front/src/modules/metadata/utils/generateDeleteOneObjectMutation.ts
@@ -0,0 +1,22 @@
+import { gql } from '@apollo/client';
+
+import { capitalize } from '~/utils/string/capitalize';
+
+import { MetadataObject } from '../types/MetadataObject';
+
+// TODO: implement
+export const generateDeleteOneObjectMutation = ({
+ metadataObject,
+}: {
+ metadataObject: MetadataObject;
+}) => {
+ const capitalizedObjectName = capitalize(metadataObject.nameSingular);
+
+ return gql`
+ mutation DeleteOne${capitalizedObjectName}($input: ${capitalizedObjectName}DeleteInput!) {
+ createOne${capitalizedObjectName}(data: $input) {
+ id
+ }
+ }
+ `;
+};
diff --git a/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts b/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts
index bbb7bdb02..f04e19a7b 100644
--- a/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts
+++ b/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts
@@ -1,5 +1,7 @@
import { gql } from '@apollo/client';
+import { FieldType } from '@/ui/data/field/types/FieldType';
+
import { MetadataObject } from '../types/MetadataObject';
export const generateFindManyCustomObjectsQuery = ({
@@ -15,7 +17,24 @@ export const generateFindManyCustomObjectsQuery = ({
edges {
node {
id
- ${metadataObject.fields.map((field) => field.name).join('\n')}
+ ${metadataObject.fields
+ .map((field) => {
+ // TODO: parse
+ const fieldType = field.type as FieldType;
+
+ if (fieldType === 'text') {
+ return field.name;
+ } else if (fieldType === 'url') {
+ return `
+ ${field.name}
+ {
+ text
+ link
+ }
+ `;
+ }
+ })
+ .join('\n')}
}
cursor
}
diff --git a/front/src/modules/ui/data/field/components/FieldDisplay.tsx b/front/src/modules/ui/data/field/components/FieldDisplay.tsx
index 72d290440..1b610ba51 100644
--- a/front/src/modules/ui/data/field/components/FieldDisplay.tsx
+++ b/front/src/modules/ui/data/field/components/FieldDisplay.tsx
@@ -12,6 +12,7 @@ import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDi
import { RelationFieldDisplay } from '../meta-types/display/components/RelationFieldDisplay';
import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay';
import { URLFieldDisplay } from '../meta-types/display/components/URLFieldDisplay';
+import { URLV2FieldDisplay } from '../meta-types/display/components/URLV2FieldDisplay';
import { isFieldChip } from '../types/guards/isFieldChip';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDoubleText } from '../types/guards/isFieldDoubleText';
@@ -23,6 +24,7 @@ import { isFieldPhone } from '../types/guards/isFieldPhone';
import { isFieldRelation } from '../types/guards/isFieldRelation';
import { isFieldText } from '../types/guards/isFieldText';
import { isFieldURL } from '../types/guards/isFieldURL';
+import { isFieldURLV2 } from '../types/guards/isFieldURLV2';
export const FieldDisplay = () => {
const { fieldDefinition } = useContext(FieldContext);
@@ -43,6 +45,8 @@ export const FieldDisplay = () => {
) : isFieldURL(fieldDefinition) ? (
+ ) : isFieldURLV2(fieldDefinition) ? (
+
) : isFieldPhone(fieldDefinition) ? (
) : isFieldChip(fieldDefinition) ? (
diff --git a/front/src/modules/ui/data/field/components/FieldInput.tsx b/front/src/modules/ui/data/field/components/FieldInput.tsx
index c1ce74807..b50f7c0ff 100644
--- a/front/src/modules/ui/data/field/components/FieldInput.tsx
+++ b/front/src/modules/ui/data/field/components/FieldInput.tsx
@@ -16,6 +16,7 @@ import { ProbabilityFieldInput } from '../meta-types/input/components/Probabilit
import { RelationFieldInput } from '../meta-types/input/components/RelationFieldInput';
import { TextFieldInput } from '../meta-types/input/components/TextFieldInput';
import { URLFieldInput } from '../meta-types/input/components/URLFieldInput';
+import { URLV2FieldInput } from '../meta-types/input/components/URLV2FieldInput';
import { FieldInputEvent } from '../types/FieldInputEvent';
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
import { isFieldChip } from '../types/guards/isFieldChip';
@@ -30,6 +31,7 @@ import { isFieldProbability } from '../types/guards/isFieldProbability';
import { isFieldRelation } from '../types/guards/isFieldRelation';
import { isFieldText } from '../types/guards/isFieldText';
import { isFieldURL } from '../types/guards/isFieldURL';
+import { isFieldURLV2 } from '../types/guards/isFieldURLV2';
type FieldInputProps = {
onSubmit?: FieldInputEvent;
@@ -98,6 +100,14 @@ export const FieldInput = ({
onTab={onTab}
onShiftTab={onShiftTab}
/>
+ ) : isFieldURLV2(fieldDefinition) ? (
+
) : isFieldPhone(fieldDefinition) ? (
{
@@ -66,6 +68,9 @@ export const usePersistField = () => {
const fieldIsURL =
isFieldURL(fieldDefinition) && isFieldURLValue(valueToPersist);
+ const fieldIsURLV2 =
+ isFieldURLV2(fieldDefinition) && isFieldURLV2Value(valueToPersist);
+
const fieldIsBoolean =
isFieldBoolean(fieldDefinition) &&
isFieldBooleanValue(valueToPersist);
@@ -154,7 +159,8 @@ export const usePersistField = () => {
fieldIsNumber ||
fieldIsMoney ||
fieldIsDate ||
- fieldIsPhone
+ fieldIsPhone ||
+ fieldIsURLV2
) {
const fieldName = fieldDefinition.metadata.fieldName;
@@ -173,7 +179,11 @@ export const usePersistField = () => {
});
} else {
throw new Error(
- `Invalid value to persist: ${valueToPersist} for type : ${fieldDefinition.type}, type may not be implemented in usePersistField.`,
+ `Invalid value to persist: ${JSON.stringify(
+ valueToPersist,
+ )} for type : ${
+ fieldDefinition.type
+ }, type may not be implemented in usePersistField.`,
);
}
},
diff --git a/front/src/modules/ui/data/field/meta-types/display/components/URLV2FieldDisplay.tsx b/front/src/modules/ui/data/field/meta-types/display/components/URLV2FieldDisplay.tsx
new file mode 100644
index 000000000..1fefd28d5
--- /dev/null
+++ b/front/src/modules/ui/data/field/meta-types/display/components/URLV2FieldDisplay.tsx
@@ -0,0 +1,8 @@
+import { useURLV2Field } from '../../hooks/useURLV2Field';
+import { URLV2Display } from '../content-display/components/URLDisplayV2';
+
+export const URLV2FieldDisplay = () => {
+ const { fieldValue } = useURLV2Field();
+
+ return ;
+};
diff --git a/front/src/modules/ui/data/field/meta-types/display/content-display/components/URLDisplayV2.tsx b/front/src/modules/ui/data/field/meta-types/display/content-display/components/URLDisplayV2.tsx
new file mode 100644
index 000000000..eaf80e513
--- /dev/null
+++ b/front/src/modules/ui/data/field/meta-types/display/content-display/components/URLDisplayV2.tsx
@@ -0,0 +1,73 @@
+import { MouseEvent } from 'react';
+import styled from '@emotion/styled';
+
+import { FieldURLV2Value } from '@/ui/data/field/types/FieldMetadata';
+import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
+import {
+ LinkType,
+ SocialLink,
+} from '@/ui/navigation/link/components/SocialLink';
+
+import { EllipsisDisplay } from './EllipsisDisplay';
+
+const StyledRawLink = styled(RoundedLink)`
+ overflow: hidden;
+
+ a {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+`;
+
+type URLV2DisplayProps = {
+ value?: FieldURLV2Value;
+};
+
+const checkUrlType = (url: string) => {
+ if (
+ /^(http|https):\/\/(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(
+ url,
+ )
+ ) {
+ return LinkType.LinkedIn;
+ }
+ if (url.match(/^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?/i)) {
+ return LinkType.Twitter;
+ }
+
+ return LinkType.Url;
+};
+
+export const URLV2Display = ({ value }: URLV2DisplayProps) => {
+ const handleClick = (event: MouseEvent) => {
+ event.stopPropagation();
+ };
+
+ const absoluteUrl = value?.link
+ ? value.link.startsWith('http')
+ ? value.link
+ : 'https://' + value.link
+ : '';
+
+ const displayedValue = value?.text ?? '';
+
+ const type = checkUrlType(absoluteUrl);
+
+ if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
+ return (
+
+
+ {displayedValue}
+
+
+ );
+ }
+ return (
+
+
+ {displayedValue}
+
+
+ );
+};
diff --git a/front/src/modules/ui/data/field/meta-types/hooks/useURLV2Field.ts b/front/src/modules/ui/data/field/meta-types/hooks/useURLV2Field.ts
new file mode 100644
index 000000000..821a0d6e1
--- /dev/null
+++ b/front/src/modules/ui/data/field/meta-types/hooks/useURLV2Field.ts
@@ -0,0 +1,43 @@
+import { useContext } from 'react';
+import { useRecoilState } from 'recoil';
+
+import { FieldContext } from '../../contexts/FieldContext';
+import { usePersistField } from '../../hooks/usePersistField';
+import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
+import { FieldURLV2Value } from '../../types/FieldMetadata';
+import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
+import { isFieldURLV2 } from '../../types/guards/isFieldURLV2';
+import { isFieldURLV2Value } from '../../types/guards/isFieldURLV2Value';
+
+export const useURLV2Field = () => {
+ const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
+
+ assertFieldMetadata('urlV2', isFieldURLV2, fieldDefinition);
+
+ const fieldName = fieldDefinition.metadata.fieldName;
+
+ const [fieldValue, setFieldValue] = useRecoilState(
+ entityFieldsFamilySelector({
+ entityId: entityId,
+ fieldName: fieldName,
+ }),
+ );
+
+ const persistField = usePersistField();
+
+ const persistURLField = (newValue: FieldURLV2Value) => {
+ if (!isFieldURLV2Value(newValue)) {
+ return;
+ }
+
+ persistField(newValue);
+ };
+
+ return {
+ fieldDefinition,
+ fieldValue,
+ setFieldValue,
+ hotkeyScope,
+ persistURLField,
+ };
+};
diff --git a/front/src/modules/ui/data/field/meta-types/input/components/URLV2FieldInput.tsx b/front/src/modules/ui/data/field/meta-types/input/components/URLV2FieldInput.tsx
new file mode 100644
index 000000000..271703e79
--- /dev/null
+++ b/front/src/modules/ui/data/field/meta-types/input/components/URLV2FieldInput.tsx
@@ -0,0 +1,89 @@
+import { FieldDoubleText } from '../../../types/FieldDoubleText';
+import { useURLV2Field } from '../../hooks/useURLV2Field';
+
+import { DoubleTextInput } from './internal/DoubleTextInput';
+import { FieldInputOverlay } from './internal/FieldInputOverlay';
+import { FieldInputEvent } from './DateFieldInput';
+
+export type URLV2FieldInputProps = {
+ onClickOutside?: FieldInputEvent;
+ onEnter?: FieldInputEvent;
+ onEscape?: FieldInputEvent;
+ onTab?: FieldInputEvent;
+ onShiftTab?: FieldInputEvent;
+};
+
+export const URLV2FieldInput = ({
+ onEnter,
+ onEscape,
+ onClickOutside,
+ onTab,
+ onShiftTab,
+}: URLV2FieldInputProps) => {
+ const { fieldValue, hotkeyScope, persistURLField } = useURLV2Field();
+
+ const handleEnter = (newURL: FieldDoubleText) => {
+ onEnter?.(() =>
+ persistURLField({
+ link: newURL.firstValue,
+ text: newURL.secondValue,
+ }),
+ );
+ };
+
+ const handleEscape = (newURL: FieldDoubleText) => {
+ onEscape?.(() =>
+ persistURLField({
+ link: newURL.firstValue,
+ text: newURL.secondValue,
+ }),
+ );
+ };
+
+ const handleClickOutside = (
+ event: MouseEvent | TouchEvent,
+ newURL: FieldDoubleText,
+ ) => {
+ onClickOutside?.(() =>
+ persistURLField({
+ link: newURL.firstValue,
+ text: newURL.secondValue,
+ }),
+ );
+ };
+
+ const handleTab = (newURL: FieldDoubleText) => {
+ onTab?.(() =>
+ persistURLField({
+ link: newURL.firstValue,
+ text: newURL.secondValue,
+ }),
+ );
+ };
+
+ const handleShiftTab = (newURL: FieldDoubleText) => {
+ onShiftTab?.(() =>
+ persistURLField({
+ link: newURL.firstValue,
+ text: newURL.secondValue,
+ }),
+ );
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/front/src/modules/ui/data/field/types/FieldMetadata.ts b/front/src/modules/ui/data/field/types/FieldMetadata.ts
index cf64c9d63..762add033 100644
--- a/front/src/modules/ui/data/field/types/FieldMetadata.ts
+++ b/front/src/modules/ui/data/field/types/FieldMetadata.ts
@@ -16,6 +16,11 @@ export type FieldURLMetadata = {
fieldName: string;
};
+export type FieldURLV2Metadata = {
+ placeHolder: string;
+ fieldName: string;
+};
+
export type FieldDateMetadata = {
fieldName: string;
};
@@ -82,6 +87,7 @@ export type FieldMetadata =
| FieldDoubleTextMetadata
| FieldPhoneMetadata
| FieldURLMetadata
+ | FieldURLV2Metadata
| FieldNumberMetadata
| FieldMoneyMetadata
| FieldEmailMetadata
@@ -95,6 +101,7 @@ export type FieldChipValue = string;
export type FieldDateValue = string | null;
export type FieldPhoneValue = string;
export type FieldURLValue = string;
+export type FieldURLV2Value = { link: string; text: string };
export type FieldNumberValue = number | null;
export type FieldMoneyValue = number | null;
export type FieldEmailValue = string;
diff --git a/front/src/modules/ui/data/field/types/FieldType.ts b/front/src/modules/ui/data/field/types/FieldType.ts
index ad4992377..3fe8ff56a 100644
--- a/front/src/modules/ui/data/field/types/FieldType.ts
+++ b/front/src/modules/ui/data/field/types/FieldType.ts
@@ -10,5 +10,6 @@ export type FieldType =
| 'date'
| 'phone'
| 'url'
+ | 'urlV2'
| 'probability'
| 'moneyAmount';
diff --git a/front/src/modules/ui/data/field/types/guards/assertFieldMetadata.ts b/front/src/modules/ui/data/field/types/guards/assertFieldMetadata.ts
index fde2f235f..7da9696c0 100644
--- a/front/src/modules/ui/data/field/types/guards/assertFieldMetadata.ts
+++ b/front/src/modules/ui/data/field/types/guards/assertFieldMetadata.ts
@@ -14,6 +14,7 @@ import {
FieldRelationMetadata,
FieldTextMetadata,
FieldURLMetadata,
+ FieldURLV2Metadata,
} from '../FieldMetadata';
import { FieldType } from '../FieldType';
@@ -41,6 +42,8 @@ type AssertFieldMetadataFunction = <
? FieldPhoneMetadata
: E extends 'url'
? FieldURLMetadata
+ : E extends 'urlV2'
+ ? FieldURLV2Metadata
: E extends 'probability'
? FieldProbabilityMetadata
: E extends 'moneyAmount'
diff --git a/front/src/modules/ui/data/field/types/guards/isFieldURLV2.ts b/front/src/modules/ui/data/field/types/guards/isFieldURLV2.ts
new file mode 100644
index 000000000..f555a2fd9
--- /dev/null
+++ b/front/src/modules/ui/data/field/types/guards/isFieldURLV2.ts
@@ -0,0 +1,6 @@
+import { FieldDefinition } from '../FieldDefinition';
+import { FieldMetadata, FieldURLV2Metadata } from '../FieldMetadata';
+
+export const isFieldURLV2 = (
+ field: FieldDefinition,
+): field is FieldDefinition => field.type === 'urlV2';
diff --git a/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts b/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts
new file mode 100644
index 000000000..2fe1de753
--- /dev/null
+++ b/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+import { FieldURLV2Value } from '../FieldMetadata';
+
+const urlV2Schema = z.object({
+ link: z.string(),
+ text: z.string(),
+});
+
+// TODO: add yup
+export const isFieldURLV2Value = (
+ fieldValue: unknown,
+): fieldValue is FieldURLV2Value => urlV2Schema.safeParse(fieldValue).success;
diff --git a/front/src/modules/views/hooks/useTableViewFields.ts b/front/src/modules/views/hooks/useTableViewFields.ts
index e841a1da8..91ac2e60c 100644
--- a/front/src/modules/views/hooks/useTableViewFields.ts
+++ b/front/src/modules/views/hooks/useTableViewFields.ts
@@ -23,8 +23,8 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { GET_VIEW_FIELDS } from '../graphql/queries/getViewFields';
-const toViewFieldInput = (
- objectId: 'company' | 'person',
+export const toViewFieldInput = (
+ objectId: string,
fieldDefinition: ColumnDefinition,
) => ({
key: fieldDefinition.key,
@@ -40,7 +40,7 @@ export const useTableViewFields = ({
columnDefinitions,
skipFetch,
}: {
- objectId: 'company' | 'person';
+ objectId: string;
columnDefinitions: ColumnDefinition[];
skipFetch?: boolean;
}) => {
@@ -110,7 +110,7 @@ export const useTableViewFields = ({
);
useGetViewFieldsQuery({
- skip: !currentViewId || skipFetch,
+ skip: !currentViewId || skipFetch || columnDefinitions.length === 0,
variables: {
orderBy: { index: SortOrder.Asc },
where: {
diff --git a/front/src/modules/views/hooks/useTableViews.ts b/front/src/modules/views/hooks/useTableViews.ts
index f145a91c6..53823017c 100644
--- a/front/src/modules/views/hooks/useTableViews.ts
+++ b/front/src/modules/views/hooks/useTableViews.ts
@@ -18,7 +18,7 @@ export const useTableViews = ({
objectId,
columnDefinitions,
}: {
- objectId: 'company' | 'person';
+ objectId: string;
columnDefinitions: ColumnDefinition[];
}) => {
const tableColumns = useRecoilScopedValue(
@@ -52,6 +52,10 @@ export const useTableViews = ({
skipFetch: isFetchingViews,
});
+ const createDefaultViewFields = async () => {
+ await createViewFields(tableColumns);
+ };
+
const { createViewFilters, persistFilters } = useViewFilters({
RecoilScopeContext: TableRecoilScopeContext,
skipFetch: isFetchingViews,
@@ -73,5 +77,7 @@ export const useTableViews = ({
persistColumns,
submitCurrentView,
updateView,
+ createDefaultViewFields,
+ isFetchingViews,
};
};
diff --git a/front/src/modules/views/hooks/useViews.ts b/front/src/modules/views/hooks/useViews.ts
index d41240b11..c84f84b2a 100644
--- a/front/src/modules/views/hooks/useViews.ts
+++ b/front/src/modules/views/hooks/useViews.ts
@@ -22,7 +22,7 @@ export const useViews = ({
RecoilScopeContext,
type,
}: {
- objectId: 'company' | 'person';
+ objectId: string;
onViewCreate?: (viewId: string) => Promise;
RecoilScopeContext: RecoilScopeContext;
type: ViewType;
diff --git a/server/src/metadata/field-metadata/dtos/update-field.input.ts b/server/src/metadata/field-metadata/dtos/update-field.input.ts
index 767b5459a..5402999d7 100644
--- a/server/src/metadata/field-metadata/dtos/update-field.input.ts
+++ b/server/src/metadata/field-metadata/dtos/update-field.input.ts
@@ -1,18 +1,13 @@
import { Field, InputType } from '@nestjs/graphql';
-import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
+import { IsBoolean, IsOptional, IsString } from 'class-validator';
@InputType()
export class UpdateFieldInput {
@IsString()
- @IsNotEmpty()
- @Field()
- name: string;
-
- @IsString()
- @IsNotEmpty()
- @Field()
- label: string;
+ @IsOptional()
+ @Field({ nullable: true })
+ label?: string;
@IsString()
@IsOptional()
diff --git a/server/src/metadata/object-metadata/dtos/update-object.input.ts b/server/src/metadata/object-metadata/dtos/update-object.input.ts
index 7693ee6ff..79118195e 100644
--- a/server/src/metadata/object-metadata/dtos/update-object.input.ts
+++ b/server/src/metadata/object-metadata/dtos/update-object.input.ts
@@ -6,23 +6,13 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator';
export class UpdateObjectInput {
@IsString()
@IsOptional()
- @Field()
- nameSingular: string;
+ @Field({ nullable: true })
+ labelSingular?: string;
@IsString()
@IsOptional()
- @Field()
- namePlural: string;
-
- @IsString()
- @IsOptional()
- @Field()
- labelSingular: string;
-
- @IsString()
- @IsOptional()
- @Field()
- labelPlural: string;
+ @Field({ nullable: true })
+ labelPlural?: string;
@IsString()
@IsOptional()