feat: activate standard objects in New Object page (#2232)

* feat: activate standard objects in New Object page

Closes #2010, Closes #2173

* Pagination limit = 1000

* Various fixes

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Thaïs
2023-10-27 15:46:29 +02:00
committed by GitHub
parent ec3327ca81
commit 3c6ce75606
29 changed files with 470 additions and 343 deletions
@@ -2,7 +2,7 @@ import { gql } from '@apollo/client';
export const FIND_MANY_METADATA_OBJECTS = gql`
query MetadataObjects {
objects(paging: { first: 100 }) {
objects(paging: { first: 1000 }) {
edges {
node {
id
@@ -17,7 +17,7 @@ export const FIND_MANY_METADATA_OBJECTS = gql`
isActive
createdAt
updatedAt
fields(paging: { first: 100 }) {
fields(paging: { first: 1000 }) {
edges {
node {
id
@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { useQuery } from '@apollo/client';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
import {
MetadataObjectsQuery,
MetadataObjectsQueryVariables,
@@ -15,15 +16,28 @@ import { useApolloMetadataClient } from './useApolloMetadataClient';
export const useFindManyMetadataObjects = () => {
const apolloMetadataClient = useApolloMetadataClient();
const { enqueueSnackBar } = useSnackBar();
const {
data,
fetchMore: fetchMoreInternal,
loading,
error,
} = useQuery<MetadataObjectsQuery, MetadataObjectsQueryVariables>(
FIND_MANY_METADATA_OBJECTS,
{
client: apolloMetadataClient ?? undefined,
skip: !apolloMetadataClient,
onError: (error) => {
// eslint-disable-next-line no-console
console.error('useFindManyMetadataObjects error : ', error);
enqueueSnackBar(
`Error during useFindManyMetadataObjects, ${error.message}`,
{
variant: 'error',
},
);
},
},
);
@@ -47,5 +61,6 @@ export const useFindManyMetadataObjects = () => {
hasMore,
fetchMore,
loading,
error,
};
};
@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import { useQuery } from '@apollo/client';
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
import { PaginatedObjectType } from '../types/PaginatedObjectType';
import { formatPagedObjectsToObjects } from '../utils/formatPagedObjectsToObjects';
@@ -19,10 +21,25 @@ export const useFindManyObjects = <
objectNamePlural,
});
const { enqueueSnackBar } = useSnackBar();
const { data, loading, error } = useQuery<PaginatedObjectType<ObjectType>>(
findManyQuery,
{
skip: !foundMetadataObject,
onError: (error) => {
// eslint-disable-next-line no-console
console.error(
`useFindManyObjects for "${objectNamePlural}" error : `,
error,
);
enqueueSnackBar(
`Error during useFindManyObjects for "${objectNamePlural}", ${error.message}`,
{
variant: 'error',
},
);
},
},
);
@@ -1,4 +1,4 @@
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { MetadataFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { Field } from '~/generated/graphql';
import { formatMetadataFieldInput } from '../utils/formatMetadataFieldInput';
@@ -7,15 +7,15 @@ import { useCreateOneMetadataField } from './useCreateOneMetadataField';
import { useDeleteOneMetadataField } from './useDeleteOneMetadataField';
import { useUpdateOneMetadataField } from './useUpdateOneMetadataField';
export const useFieldMetadata = () => {
export const useMetadataField = () => {
const { createOneMetadataField } = useCreateOneMetadataField();
const { updateOneMetadataField } = useUpdateOneMetadataField();
const { deleteOneMetadataField } = useDeleteOneMetadataField();
const createField = (
const createMetadataField = (
input: Pick<Field, 'label' | 'icon' | 'description'> & {
objectId: string;
type: ObjectFieldDataType;
type: MetadataFieldDataType;
},
) =>
createOneMetadataField({
@@ -23,25 +23,25 @@ export const useFieldMetadata = () => {
objectId: input.objectId,
});
const activateField = (metadataField: Field) =>
const activateMetadataField = (metadataField: Field) =>
updateOneMetadataField({
fieldIdToUpdate: metadataField.id,
updatePayload: { isActive: true },
});
const disableField = (metadataField: Field) =>
const disableMetadataField = (metadataField: Field) =>
updateOneMetadataField({
fieldIdToUpdate: metadataField.id,
updatePayload: { isActive: false },
});
const eraseField = (metadataField: Field) =>
const eraseMetadataField = (metadataField: Field) =>
deleteOneMetadataField(metadataField.id);
return {
activateField,
createField,
disableField,
eraseField,
activateMetadataField,
createMetadataField,
disableMetadataField,
eraseMetadataField,
};
};
@@ -7,7 +7,7 @@ import { useDeleteOneMetadataObject } from './useDeleteOneMetadataObject';
import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
import { useUpdateOneMetadataObject } from './useUpdateOneMetadataObject';
export const useObjectMetadata = () => {
export const useMetadataObjectForSettings = () => {
const { metadataObjects, loading } = useFindManyMetadataObjects();
const activeMetadataObjects = metadataObjects.filter(
@@ -17,23 +17,23 @@ export const useObjectMetadata = () => {
({ isActive }) => !isActive,
);
const findActiveObjectBySlug = (slug: string) =>
const findActiveMetadataObjectBySlug = (slug: string) =>
activeMetadataObjects.find(
(activeObject) => getObjectSlug(activeObject) === slug,
(activeMetadataObject) => getObjectSlug(activeMetadataObject) === slug,
);
const { createOneMetadataObject } = useCreateOneMetadataObject();
const { updateOneMetadataObject } = useUpdateOneMetadataObject();
const { deleteOneMetadataObject } = useDeleteOneMetadataObject();
const createObject = (
const createMetadataObject = (
input: Pick<
MetadataObject,
'labelPlural' | 'labelSingular' | 'icon' | 'description'
>,
) => createOneMetadataObject(formatMetadataObjectInput(input));
const editObject = (
const editMetadataObject = (
input: Pick<
MetadataObject,
'id' | 'labelPlural' | 'labelSingular' | 'icon' | 'description'
@@ -44,30 +44,30 @@ export const useObjectMetadata = () => {
updatePayload: formatMetadataObjectInput(input),
});
const activateObject = (metadataObject: MetadataObject) =>
const activateMetadataObject = (metadataObject: Pick<MetadataObject, 'id'>) =>
updateOneMetadataObject({
idToUpdate: metadataObject.id,
updatePayload: { isActive: true },
});
const disableObject = (metadataObject: MetadataObject) =>
const disableMetadataObject = (metadataObject: Pick<MetadataObject, 'id'>) =>
updateOneMetadataObject({
idToUpdate: metadataObject.id,
updatePayload: { isActive: false },
});
const eraseObject = (metadataObject: Pick<MetadataObject, 'id'>) =>
const eraseMetadataObject = (metadataObject: Pick<MetadataObject, 'id'>) =>
deleteOneMetadataObject(metadataObject.id);
return {
activateObject,
activeObjects: activeMetadataObjects,
createObject,
disabledObjects: disabledMetadataObjects,
disableObject,
editObject,
eraseObject,
findActiveObjectBySlug,
activateMetadataObject,
activeMetadataObjects,
createMetadataObject,
disabledMetadataObjects,
disableMetadataObject,
editMetadataObject,
eraseMetadataObject,
findActiveMetadataObjectBySlug,
loading,
};
};
@@ -1,11 +1,11 @@
import toCamelCase from 'lodash.camelcase';
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { MetadataFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { Field } from '~/generated-metadata/graphql';
export const formatMetadataFieldInput = (
input: Pick<Field, 'label' | 'icon' | 'description'> & {
type: ObjectFieldDataType;
type: MetadataFieldDataType;
},
) => ({
description: input.description?.trim() ?? null,
@@ -3,12 +3,12 @@ import { Select } from '@/ui/input/components/Select';
import { Section } from '@/ui/layout/section/components/Section';
import { dataTypes } from '../constants/dataTypes';
import { ObjectFieldDataType } from '../types/ObjectFieldDataType';
import { MetadataFieldDataType } from '../types/ObjectFieldDataType';
type SettingsObjectFieldTypeSelectSectionProps = {
disabled?: boolean;
onChange?: (value: ObjectFieldDataType) => void;
type: ObjectFieldDataType;
onChange?: (value: MetadataFieldDataType) => void;
type: MetadataFieldDataType;
};
// TODO: remove "relation" type for now, add it back when the backend is ready.
@@ -31,7 +31,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
onChange={onChange}
options={Object.entries(dataTypesWithoutRelation).map(
([key, dataType]) => ({
value: key as ObjectFieldDataType,
value: key as MetadataFieldDataType,
...dataType,
}),
)}
@@ -7,10 +7,10 @@ import {
} from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { ObjectFieldDataType } from '../types/ObjectFieldDataType';
import { MetadataFieldDataType } from '../types/ObjectFieldDataType';
export const dataTypes: Record<
ObjectFieldDataType,
MetadataFieldDataType,
{ label: string; Icon: IconComponent }
> = {
number: { label: 'Number', Icon: IconNumbers },
@@ -0,0 +1,65 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { MetadataObject } from '@/metadata/types/MetadataObject';
import { Checkbox } from '@/ui/input/components/Checkbox';
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
type SettingsAvailableStandardObjectItemTableRowProps = {
isSelected?: boolean;
objectItem: MetadataObject;
onClick?: () => void;
};
export const StyledAvailableStandardObjectTableRow = styled(TableRow)`
grid-template-columns: 28px 148px 256px 80px;
`;
const StyledCheckboxTableCell = styled(TableCell)`
justify-content: center;
padding: 0;
padding-left: ${({ theme }) => theme.spacing(1)};
`;
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledDescription = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const SettingsAvailableStandardObjectItemTableRow = ({
isSelected,
objectItem,
onClick,
}: SettingsAvailableStandardObjectItemTableRowProps) => {
const theme = useTheme();
const { Icon } = useLazyLoadIcon(objectItem.icon ?? '');
return (
<StyledAvailableStandardObjectTableRow
key={objectItem.namePlural}
isSelected={isSelected}
onClick={onClick}
>
<StyledCheckboxTableCell>
<Checkbox checked={!!isSelected} />
</StyledCheckboxTableCell>
<StyledNameTableCell>
{!!Icon && <Icon size={theme.icon.size.md} />}
{objectItem.labelPlural}
</StyledNameTableCell>
<TableCell>
<StyledDescription>{objectItem.description}</StyledDescription>
</TableCell>
<TableCell align="right">{objectItem.fields.length}</TableCell>
</StyledAvailableStandardObjectTableRow>
);
};
@@ -0,0 +1,53 @@
import { MetadataObject } from '@/metadata/types/MetadataObject';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Section } from '@/ui/layout/section/components/Section';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import {
SettingsAvailableStandardObjectItemTableRow,
StyledAvailableStandardObjectTableRow,
} from './SettingsAvailableStandardObjectItemTableRow';
type SettingsAvailableStandardObjectsSectionProps = {
objectItems: MetadataObject[];
onChange: (selectedIds: Record<string, boolean>) => void;
selectedIds: Record<string, boolean>;
};
export const SettingsAvailableStandardObjectsSection = ({
objectItems,
onChange,
selectedIds,
}: SettingsAvailableStandardObjectsSectionProps) => (
<Section>
<H2Title
title="Available"
description="Select one or several standard objects to activate below"
/>
<Table>
<StyledAvailableStandardObjectTableRow>
<TableHeader></TableHeader>
<TableHeader>Name</TableHeader>
<TableHeader>Description</TableHeader>
<TableHeader align="right">Fields</TableHeader>
</StyledAvailableStandardObjectTableRow>
<TableBody>
{objectItems.map((objectItem) => (
<SettingsAvailableStandardObjectItemTableRow
key={objectItem.id}
isSelected={selectedIds[objectItem.id]}
objectItem={objectItem}
onClick={() =>
onChange({
...selectedIds,
[objectItem.id]: !selectedIds[objectItem.id],
})
}
/>
))}
</TableBody>
</Table>
</Section>
);
@@ -4,7 +4,6 @@ import styled from '@emotion/styled';
import { IconBox, IconDatabase, IconFileCheck } from '@/ui/display/icon';
import { SettingsObjectTypeCard } from './SettingsObjectTypeCard';
import { SettingsStandardObjects } from './SettingsStandardObjects';
export type NewObjectType = 'Standard' | 'Custom' | 'Remote';
@@ -17,8 +16,6 @@ const StyledContainer = styled.div`
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(8)};
width: 100%;
`;
export const SettingsNewObjectType = ({
@@ -27,50 +24,47 @@ export const SettingsNewObjectType = ({
}: SettingsNewObjectTypeProps) => {
const theme = useTheme();
return (
<>
<StyledContainer>
<SettingsObjectTypeCard
title={'Standard'}
color="blue"
selected={selectedType === 'Standard'}
prefixIcon={
<IconFileCheck
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
onClick={() => onTypeSelect?.('Standard')}
/>
<SettingsObjectTypeCard
title="Custom"
color="orange"
selected={selectedType === 'Custom'}
prefixIcon={
<IconBox
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
onClick={() => onTypeSelect?.('Custom')}
/>
<SettingsObjectTypeCard
title="Remote"
soon
disabled
color="green"
selected={selectedType === 'Remote'}
prefixIcon={
<IconDatabase
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
/>
</StyledContainer>
{selectedType === 'Standard' && <SettingsStandardObjects />}
</>
<StyledContainer>
<SettingsObjectTypeCard
title={'Standard'}
color="blue"
selected={selectedType === 'Standard'}
prefixIcon={
<IconFileCheck
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
onClick={() => onTypeSelect?.('Standard')}
/>
<SettingsObjectTypeCard
title="Custom"
color="orange"
selected={selectedType === 'Custom'}
prefixIcon={
<IconBox
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
onClick={() => onTypeSelect?.('Custom')}
/>
<SettingsObjectTypeCard
title="Remote"
soon
disabled
color="green"
selected={selectedType === 'Remote'}
prefixIcon={
<IconDatabase
size={theme.icon.size.lg}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
/>
</StyledContainer>
);
};
@@ -1,107 +0,0 @@
import { useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { standardObjects } from '../../constants/mockObjects';
const StyledTableRow = styled(TableRow)<{
selectedRows?: number[];
rowNumber?: number;
}>`
align-items: center;
background: ${({ selectedRows, rowNumber, theme }) =>
selectedRows?.includes(rowNumber!)
? theme.accent.quaternary
: theme.background.primary};
grid-template-columns: 36px 132px 240px 98.7px;
`;
const StyledCheckboxCell = styled(TableCell)`
padding: 0 ${({ theme }) => theme.spacing(2)} 0
${({ theme }) => theme.spacing(1)};
`;
const StyledNameTableCell = styled(TableCell)`
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledDescriptionCell = styled.div<{
align?: 'left' | 'center' | 'right';
}>`
color: ${({ theme }) => theme.font.color.secondary};
justify-content: ${({ align }) =>
align === 'right'
? 'flex-end'
: align === 'center'
? 'center'
: 'flex-start'};
overflow: hidden;
padding: 0 ${({ theme }) => theme.spacing(2)};
padding: 0 ${({ theme }) => theme.spacing(2)};
text-align: ${({ align }) => align ?? 'left'};
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledTable = styled(Table)`
display: grid;
gap: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsStandardObjects = () => {
const theme = useTheme();
const [selectedRows, setSelectedRows] = useState<number[]>([]);
return (
<>
<H2Title
title="Available"
description="Select one or several standard objects to activate below"
/>
<StyledTable>
<StyledTableRow>
<TableHeader></TableHeader>
<TableHeader>Name</TableHeader>
<TableHeader>Description</TableHeader>
<TableHeader align="right">Fields</TableHeader>
</StyledTableRow>
{standardObjects.map((object, rowNumber) => (
<StyledTableRow
selectedRows={selectedRows}
rowNumber={rowNumber}
onClick={() => {
const indexOfRowClicked = selectedRows.indexOf(rowNumber);
if (indexOfRowClicked === -1) {
setSelectedRows([...selectedRows, rowNumber]);
} else {
const newSelectedRows = [...selectedRows];
newSelectedRows.splice(indexOfRowClicked, 1);
setSelectedRows(newSelectedRows);
}
}}
key={object.name}
>
<StyledCheckboxCell>
<input
type="checkbox"
checked={selectedRows.includes(rowNumber)}
/>
</StyledCheckboxCell>
<StyledNameTableCell>
<object.Icon size={theme.icon.size.md} />
{object.name}
</StyledNameTableCell>
<StyledDescriptionCell>{object.description}</StyledDescriptionCell>
<TableCell align="right">{object.fields}</TableCell>
</StyledTableRow>
))}
</StyledTable>
</>
);
};
@@ -2,9 +2,9 @@ import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { dataTypes } from '../../constants/dataTypes';
import { ObjectFieldDataType } from '../../types/ObjectFieldDataType';
import { MetadataFieldDataType } from '../../types/ObjectFieldDataType';
const StyledDataType = styled.div<{ value: ObjectFieldDataType }>`
const StyledDataType = styled.div<{ value: MetadataFieldDataType }>`
align-items: center;
border: 1px solid transparent;
border-radius: ${({ theme }) => theme.border.radius.sm};
@@ -24,7 +24,7 @@ const StyledDataType = styled.div<{ value: ObjectFieldDataType }>`
`;
type SettingsObjectFieldDataTypeProps = {
value: ObjectFieldDataType;
value: MetadataFieldDataType;
};
export const SettingsObjectFieldDataType = ({
@@ -7,7 +7,7 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { Field } from '~/generated-metadata/graphql';
import { ObjectFieldDataType } from '../../types/ObjectFieldDataType';
import { MetadataFieldDataType } from '../../types/ObjectFieldDataType';
import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
@@ -58,7 +58,7 @@ export const SettingsObjectFieldItemTableRow = ({
<TableCell>{fieldItem.isCustom ? 'Custom' : 'Standard'}</TableCell>
<TableCell>
<SettingsObjectFieldDataType
value={fieldItem.type as ObjectFieldDataType}
value={fieldItem.type as MetadataFieldDataType}
/>
</TableCell>
<StyledIconTableCell>{ActionIcon}</StyledIconTableCell>
@@ -1,4 +1,4 @@
export type ObjectFieldDataType =
export type MetadataFieldDataType =
| 'boolean'
| 'number'
| 'relation'
@@ -0,0 +1,10 @@
import styled from '@emotion/styled';
const StyledTableBody = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
padding: ${({ theme }) => theme.spacing(2)} 0;
`;
export { StyledTableBody as TableBody };
@@ -1,6 +1,11 @@
import styled from '@emotion/styled';
const StyledTableRow = styled.div<{ onClick?: () => void }>`
const StyledTableRow = styled.div<{
isSelected?: boolean;
onClick?: () => void;
}>`
background-color: ${({ isSelected, theme }) =>
isSelected ? theme.accent.quaternary : 'transparent'};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: grid;
grid-auto-columns: 1fr;
@@ -4,6 +4,8 @@ import styled from '@emotion/styled';
import { IconChevronDown, IconChevronUp } from '@/ui/display/icon';
import { TableBody } from './TableBody';
type TableSectionProps = {
children: ReactNode;
isInitiallyExpanded?: boolean;
@@ -34,9 +36,8 @@ const StyledSection = styled.div<{ isExpanded: boolean }>`
opacity ${({ theme }) => theme.animation.duration.normal}s;
`;
const StyledSectionContent = styled.div`
const StyledSectionContent = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
padding: ${({ theme }) => theme.spacing(2)} 0;
`;
export const TableSection = ({
@@ -1,11 +1,12 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectFormSection } from '@/settings/data-model/components/SettingsObjectFormSection';
import { SettingsAvailableStandardObjectsSection } from '@/settings/data-model/new-object/components/SettingsAvailableStandardObjectsSection';
import {
NewObjectType,
SettingsNewObjectType,
@@ -22,7 +23,15 @@ export const SettingsNewObject = () => {
const [selectedObjectType, setSelectedObjectType] =
useState<NewObjectType>('Standard');
const { createObject } = useObjectMetadata();
const {
activateMetadataObject: activateObject,
createMetadataObject: createObject,
disabledMetadataObjects: disabledObjects,
} = useMetadataObjectForSettings();
const [selectedStandardObjectIds, setSelectedStandardObjectIds] = useState<
Record<string, boolean>
>({});
const [customFormValues, setCustomFormValues] = useState<{
description?: string;
@@ -32,11 +41,24 @@ export const SettingsNewObject = () => {
}>({ icon: 'IconPigMoney', labelPlural: '', labelSingular: '' });
const canSave =
selectedObjectType === 'Custom' &&
!!customFormValues.labelPlural &&
!!customFormValues.labelSingular;
(selectedObjectType === 'Standard' &&
Object.values(selectedStandardObjectIds).some(
(isSelected) => isSelected,
)) ||
(selectedObjectType === 'Custom' &&
!!customFormValues.labelPlural &&
!!customFormValues.labelSingular);
const handleSave = async () => {
if (selectedObjectType === 'Standard') {
await Promise.all(
Object.entries(selectedStandardObjectIds).map(
([standardObjectId, isSelected]) =>
isSelected ? activateObject({ id: standardObjectId }) : undefined,
),
);
}
if (selectedObjectType === 'Custom') {
await createObject({
labelPlural: customFormValues.labelPlural,
@@ -69,7 +91,7 @@ export const SettingsNewObject = () => {
</SettingsHeaderContainer>
<Section>
<H2Title
title="Object Type"
title="Object type"
description="The type of object you want to add"
/>
<SettingsNewObjectType
@@ -77,6 +99,18 @@ export const SettingsNewObject = () => {
onTypeSelect={setSelectedObjectType}
/>
</Section>
{selectedObjectType === 'Standard' && (
<SettingsAvailableStandardObjectsSection
objectItems={disabledObjects.filter(({ isCustom }) => !isCustom)}
onChange={(selectedIds) =>
setSelectedStandardObjectIds((previousSelectedIds) => ({
...previousSelectedIds,
...selectedIds,
}))
}
selectedIds={selectedStandardObjectIds}
/>
)}
{selectedObjectType === 'Custom' && (
<>
<SettingsObjectIconSection
@@ -2,8 +2,8 @@ import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { useFieldMetadata } from '@/metadata/hooks/useFieldMetadata';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataField } from '@/metadata/hooks/useMetadataField';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { getFieldSlug } from '@/metadata/utils/getFieldSlug';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsAboutSection } from '@/settings/data-model/object-details/components/SettingsObjectAboutSection';
@@ -34,28 +34,30 @@ export const SettingsObjectDetail = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { disableObject, findActiveObjectBySlug, loading } =
useObjectMetadata();
const activeObject = findActiveObjectBySlug(objectSlug);
const { disableMetadataObject, findActiveMetadataObjectBySlug, loading } =
useMetadataObjectForSettings();
const activeMetadataObject = findActiveMetadataObjectBySlug(objectSlug);
useEffect(() => {
if (loading) return;
if (!activeObject) navigate(AppPath.NotFound);
}, [activeObject, loading, navigate]);
if (!activeMetadataObject) navigate(AppPath.NotFound);
}, [activeMetadataObject, loading, navigate]);
const { activateField, disableField, eraseField } = useFieldMetadata();
const { activateMetadataField, disableMetadataField, eraseMetadataField } =
useMetadataField();
if (!activeObject) return null;
if (!activeMetadataObject) return null;
const activeFields = activeObject.fields.filter(
(fieldItem) => fieldItem.isActive,
const activeMetadataFields = activeMetadataObject.fields.filter(
(metadataField) => metadataField.isActive,
);
const disabledFields = activeObject.fields.filter(
(fieldItem) => !fieldItem.isActive,
const disabledMetadataFields = activeMetadataObject.fields.filter(
(metadataField) => !metadataField.isActive,
);
const handleDisable = async () => {
await disableObject(activeObject);
await disableMetadataObject(activeMetadataObject);
navigate('/settings/objects');
};
@@ -65,20 +67,20 @@ export const SettingsObjectDetail = () => {
<Breadcrumb
links={[
{ children: 'Objects', href: '/settings/objects' },
{ children: activeObject.labelPlural },
{ children: activeMetadataObject.labelPlural },
]}
/>
<SettingsAboutSection
iconKey={activeObject.icon ?? undefined}
name={activeObject.labelPlural || ''}
isCustom={activeObject.isCustom}
iconKey={activeMetadataObject.icon ?? undefined}
name={activeMetadataObject.labelPlural || ''}
isCustom={activeMetadataObject.isCustom}
onDisable={handleDisable}
onEdit={() => navigate('./edit')}
/>
<Section>
<H2Title
title="Fields"
description={`Customise the fields available in the ${activeObject.labelSingular} views and their display order in the ${activeObject.labelSingular} detail view and menus.`}
description={`Customise the fields available in the ${activeMetadataObject.labelSingular} views and their display order in the ${activeMetadataObject.labelSingular} detail view and menus.`}
/>
<Table>
<StyledObjectFieldTableRow>
@@ -87,36 +89,44 @@ export const SettingsObjectDetail = () => {
<TableHeader>Data type</TableHeader>
<TableHeader></TableHeader>
</StyledObjectFieldTableRow>
{!!activeFields.length && (
{!!activeMetadataFields.length && (
<TableSection title="Active">
{activeFields.map((fieldItem) => (
{activeMetadataFields.map((activeMetadataField) => (
<SettingsObjectFieldItemTableRow
key={fieldItem.id}
fieldItem={fieldItem}
key={activeMetadataField.id}
fieldItem={activeMetadataField}
ActionIcon={
<SettingsObjectFieldActiveActionDropdown
isCustomField={fieldItem.isCustom}
scopeKey={fieldItem.id}
onEdit={() => navigate(`./${getFieldSlug(fieldItem)}`)}
onDisable={() => disableField(fieldItem)}
isCustomField={activeMetadataField.isCustom}
scopeKey={activeMetadataField.id}
onEdit={() =>
navigate(`./${getFieldSlug(activeMetadataField)}`)
}
onDisable={() =>
disableMetadataField(activeMetadataField)
}
/>
}
/>
))}
</TableSection>
)}
{!!disabledFields.length && (
{!!disabledMetadataFields.length && (
<TableSection isInitiallyExpanded={false} title="Disabled">
{disabledFields.map((fieldItem) => (
{disabledMetadataFields.map((disabledMetadataField) => (
<SettingsObjectFieldItemTableRow
key={fieldItem.id}
fieldItem={fieldItem}
key={disabledMetadataField.id}
fieldItem={disabledMetadataField}
ActionIcon={
<SettingsObjectFieldDisabledActionDropdown
isCustomField={fieldItem.isCustom}
scopeKey={fieldItem.id}
onActivate={() => activateField(fieldItem)}
onErase={() => eraseField(fieldItem)}
isCustomField={disabledMetadataField.isCustom}
scopeKey={disabledMetadataField.id}
onActivate={() =>
activateMetadataField(disabledMetadataField)
}
onErase={() =>
eraseMetadataField(disabledMetadataField)
}
/>
}
/>
@@ -132,7 +142,7 @@ export const SettingsObjectDetail = () => {
variant="secondary"
onClick={() =>
navigate(
disabledFields.length
disabledMetadataFields.length
? './new-field/step-1'
: './new-field/step-2',
)
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { getObjectSlug } from '@/metadata/utils/getObjectSlug';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
@@ -20,9 +20,14 @@ export const SettingsObjectEdit = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { disableObject, editObject, findActiveObjectBySlug, loading } =
useObjectMetadata();
const activeObject = findActiveObjectBySlug(objectSlug);
const {
disableMetadataObject,
editMetadataObject,
findActiveMetadataObjectBySlug,
loading,
} = useMetadataObjectForSettings();
const activeMetadataObject = findActiveMetadataObjectBySlug(objectSlug);
const [formValues, setFormValues] = useState<
Partial<{
@@ -36,44 +41,44 @@ export const SettingsObjectEdit = () => {
useEffect(() => {
if (loading) return;
if (!activeObject) {
if (!activeMetadataObject) {
navigate(AppPath.NotFound);
return;
}
if (!Object.keys(formValues).length) {
setFormValues({
icon: activeObject.icon ?? undefined,
labelSingular: activeObject.labelSingular,
labelPlural: activeObject.labelPlural,
description: activeObject.description ?? undefined,
icon: activeMetadataObject.icon ?? undefined,
labelSingular: activeMetadataObject.labelSingular,
labelPlural: activeMetadataObject.labelPlural,
description: activeMetadataObject.description ?? undefined,
});
}
}, [activeObject, formValues, loading, navigate]);
}, [activeMetadataObject, formValues, loading, navigate]);
if (!activeObject) return null;
if (!activeMetadataObject) return null;
const areRequiredFieldsFilled =
!!formValues.labelSingular && !!formValues.labelPlural;
const hasChanges =
formValues.description !== activeObject.description ||
formValues.icon !== activeObject.icon ||
formValues.labelPlural !== activeObject.labelPlural ||
formValues.labelSingular !== activeObject.labelSingular;
formValues.description !== activeMetadataObject.description ||
formValues.icon !== activeMetadataObject.icon ||
formValues.labelPlural !== activeMetadataObject.labelPlural ||
formValues.labelSingular !== activeMetadataObject.labelSingular;
const canSave = areRequiredFieldsFilled && hasChanges;
const handleSave = async () => {
const editedObject = { ...activeObject, ...formValues };
const editedMetadataObject = { ...activeMetadataObject, ...formValues };
await editObject(editedObject);
await editMetadataObject(editedMetadataObject);
navigate(`/settings/objects/${getObjectSlug(editedObject)}`);
navigate(`/settings/objects/${getObjectSlug(editedMetadataObject)}`);
};
const handleDisable = async () => {
await disableObject(activeObject);
await disableMetadataObject(activeMetadataObject);
navigate('/settings/objects');
};
@@ -85,13 +90,13 @@ export const SettingsObjectEdit = () => {
links={[
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObject.labelPlural,
children: activeMetadataObject.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: 'Edit' },
]}
/>
{!!activeObject.isCustom && (
{!!activeMetadataObject.isCustom && (
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() => navigate(`/settings/objects/${objectSlug}`)}
@@ -100,7 +105,7 @@ export const SettingsObjectEdit = () => {
)}
</SettingsHeaderContainer>
<SettingsObjectIconSection
disabled={!activeObject.isCustom}
disabled={!activeMetadataObject.isCustom}
iconKey={formValues.icon}
label={formValues.labelPlural}
onChange={({ iconKey }) =>
@@ -111,7 +116,7 @@ export const SettingsObjectEdit = () => {
}
/>
<SettingsObjectFormSection
disabled={!activeObject.isCustom}
disabled={!activeMetadataObject.isCustom}
singularName={formValues.labelSingular}
pluralName={formValues.labelPlural}
description={formValues.description}
@@ -1,14 +1,14 @@
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useFieldMetadata } from '@/metadata/hooks/useFieldMetadata';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataField } from '@/metadata/hooks/useMetadataField';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { getFieldSlug } from '@/metadata/utils/getFieldSlug';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectFieldFormSection } from '@/settings/data-model/components/SettingsObjectFieldFormSection';
import { SettingsObjectFieldTypeSelectSection } from '@/settings/data-model/components/SettingsObjectFieldTypeSelectSection';
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { MetadataFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { AppPath } from '@/types/AppPath';
import { IconArchive, IconSettings } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
@@ -21,23 +21,27 @@ export const SettingsObjectFieldEdit = () => {
const navigate = useNavigate();
const { objectSlug = '', fieldSlug = '' } = useParams();
const { findActiveObjectBySlug, loading } = useObjectMetadata();
const activeObject = findActiveObjectBySlug(objectSlug);
const { findActiveMetadataObjectBySlug, loading } =
useMetadataObjectForSettings();
const { disableField } = useFieldMetadata();
const activeField = activeObject?.fields.find(
(field) => field.isActive && getFieldSlug(field) === fieldSlug,
const activeMetadataObject = findActiveMetadataObjectBySlug(objectSlug);
const { disableMetadataField: disableField } = useMetadataField();
const activeMetadataField = activeMetadataObject?.fields.find(
(metadataField) =>
metadataField.isActive && getFieldSlug(metadataField) === fieldSlug,
);
useEffect(() => {
if (loading) return;
if (!activeObject || !activeField) navigate(AppPath.NotFound);
}, [activeField, activeObject, loading, navigate]);
if (!activeMetadataObject || !activeMetadataField)
navigate(AppPath.NotFound);
}, [activeMetadataField, activeMetadataObject, loading, navigate]);
if (!activeObject || !activeField) return null;
if (!activeMetadataObject || !activeMetadataField) return null;
const handleDisable = async () => {
await disableField(activeField);
await disableField(activeMetadataField);
navigate(`/settings/objects/${objectSlug}`);
};
@@ -49,23 +53,23 @@ export const SettingsObjectFieldEdit = () => {
links={[
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObject.labelPlural,
children: activeMetadataObject.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: activeField.label },
{ children: activeMetadataField.label },
]}
/>
</SettingsHeaderContainer>
<SettingsObjectFieldFormSection
disabled={!activeField.isCustom}
name={activeField.label}
description={activeField.description ?? undefined}
iconKey={activeField.icon ?? undefined}
disabled={!activeMetadataField.isCustom}
name={activeMetadataField.label}
description={activeMetadataField.description ?? undefined}
iconKey={activeMetadataField.icon ?? undefined}
onChange={() => undefined}
/>
<SettingsObjectFieldTypeSelectSection
disabled
type={activeField.type as ObjectFieldDataType}
type={activeMetadataField.type as MetadataFieldDataType}
/>
<Section>
<H2Title title="Danger zone" description="Disable this field" />
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
@@ -36,21 +36,23 @@ export const SettingsObjectNewFieldStep1 = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { findActiveObjectBySlug, loading } = useObjectMetadata();
const activeObject = findActiveObjectBySlug(objectSlug);
const { findActiveMetadataObjectBySlug, loading } =
useMetadataObjectForSettings();
const activeMetadataObject = findActiveMetadataObjectBySlug(objectSlug);
useEffect(() => {
if (loading) return;
if (!activeObject) navigate(AppPath.NotFound);
}, [activeObject, loading, navigate]);
if (!activeMetadataObject) navigate(AppPath.NotFound);
}, [activeMetadataObject, loading, navigate]);
if (!activeObject) return null;
if (!activeMetadataObject) return null;
const activeFields = activeObject.fields.filter(
(fieldItem) => fieldItem.isActive,
const activeMetadataFields = activeMetadataObject.fields.filter(
(metadataField) => metadataField.isActive,
);
const disabledFields = activeObject.fields.filter(
(fieldItem) => !fieldItem.isActive,
const disabledMetadataFields = activeMetadataObject.fields.filter(
(metadataField) => !metadataField.isActive,
);
return (
@@ -61,7 +63,7 @@ export const SettingsObjectNewFieldStep1 = () => {
links={[
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObject.labelPlural,
children: activeMetadataObject.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: 'New Field' },
@@ -85,12 +87,12 @@ export const SettingsObjectNewFieldStep1 = () => {
<TableHeader>Data type</TableHeader>
<TableHeader></TableHeader>
</StyledObjectFieldTableRow>
{!!activeFields.length && (
{!!activeMetadataFields.length && (
<TableSection isInitiallyExpanded={false} title="Active">
{activeFields.map((fieldItem) => (
{activeMetadataFields.map((activeMetadataField) => (
<SettingsObjectFieldItemTableRow
key={fieldItem.id}
fieldItem={fieldItem}
key={activeMetadataField.id}
fieldItem={activeMetadataField}
ActionIcon={
<LightIconButton Icon={IconMinus} accent="tertiary" />
}
@@ -98,12 +100,12 @@ export const SettingsObjectNewFieldStep1 = () => {
))}
</TableSection>
)}
{!!disabledFields.length && (
{!!disabledMetadataFields.length && (
<TableSection title="Disabled">
{disabledFields.map((fieldItem) => (
{disabledMetadataFields.map((disabledMetadataField) => (
<SettingsObjectFieldItemTableRow
key={fieldItem.name}
fieldItem={fieldItem}
key={disabledMetadataField.name}
fieldItem={disabledMetadataField}
ActionIcon={
<LightIconButton Icon={IconPlus} accent="tertiary" />
}
@@ -1,14 +1,14 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useFieldMetadata } from '@/metadata/hooks/useFieldMetadata';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataField } from '@/metadata/hooks/useMetadataField';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectFieldFormSection } from '@/settings/data-model/components/SettingsObjectFieldFormSection';
import { SettingsObjectFieldTypeSelectSection } from '@/settings/data-model/components/SettingsObjectFieldTypeSelectSection';
import { ObjectFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { MetadataFieldDataType } from '@/settings/data-model/types/ObjectFieldDataType';
import { AppPath } from '@/types/AppPath';
import { IconSettings } from '@/ui/display/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
@@ -17,28 +17,34 @@ import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
export const SettingsObjectNewFieldStep2 = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { findActiveObjectBySlug, loading } = useObjectMetadata();
const activeObject = findActiveObjectBySlug(objectSlug);
const { createField } = useFieldMetadata();
const { findActiveMetadataObjectBySlug, loading } =
useMetadataObjectForSettings();
const activeMetadataObject = findActiveMetadataObjectBySlug(objectSlug);
const { createMetadataField } = useMetadataField();
useEffect(() => {
if (loading) return;
if (!activeObject) navigate(AppPath.NotFound);
}, [activeObject, loading, navigate]);
if (!activeMetadataObject) navigate(AppPath.NotFound);
}, [activeMetadataObject, loading, navigate]);
const [formValues, setFormValues] = useState<{
description?: string;
icon: string;
label: string;
type: ObjectFieldDataType;
type: MetadataFieldDataType;
}>({ icon: 'IconUsers', label: '', type: 'number' });
if (!activeObject) return null;
if (!activeMetadataObject) return null;
const canSave = !!formValues.label;
const handleSave = async () => {
await createField({ ...formValues, objectId: activeObject.id });
await createMetadataField({
...formValues,
objectId: activeMetadataObject.id,
});
navigate(`/settings/objects/${objectSlug}`);
};
@@ -50,7 +56,7 @@ export const SettingsObjectNewFieldStep2 = () => {
links={[
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObject.labelPlural,
children: activeMetadataObject.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: 'New Field' },
@@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { getObjectSlug } from '@/metadata/utils/getObjectSlug';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
@@ -34,8 +34,12 @@ export const SettingsObjects = () => {
const theme = useTheme();
const navigate = useNavigate();
const { activateObject, activeObjects, disabledObjects, eraseObject } =
useObjectMetadata();
const {
activateMetadataObject,
activeMetadataObjects,
disabledMetadataObjects,
eraseMetadataObject,
} = useMetadataObjectForSettings();
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
@@ -62,12 +66,12 @@ export const SettingsObjects = () => {
<TableHeader align="right">Instances</TableHeader>
<TableHeader></TableHeader>
</StyledObjectTableRow>
{!!activeObjects.length && (
{!!activeMetadataObjects.length && (
<TableSection title="Active">
{activeObjects.map((objectItem) => (
{activeMetadataObjects.map((activeMetadataObject) => (
<SettingsObjectItemTableRow
key={objectItem.namePlural}
objectItem={objectItem}
key={activeMetadataObject.namePlural}
objectItem={activeMetadataObject}
action={
<StyledIconChevronRight
size={theme.icon.size.md}
@@ -76,25 +80,31 @@ export const SettingsObjects = () => {
}
onClick={() =>
navigate(
`/settings/objects/${getObjectSlug(objectItem)}`,
`/settings/objects/${getObjectSlug(
activeMetadataObject,
)}`,
)
}
/>
))}
</TableSection>
)}
{!!disabledObjects.length && (
{!!disabledMetadataObjects.length && (
<TableSection title="Disabled">
{disabledObjects.map((objectItem) => (
{disabledMetadataObjects.map((disabledMetadataObject) => (
<SettingsObjectItemTableRow
key={objectItem.namePlural}
objectItem={objectItem}
key={disabledMetadataObject.namePlural}
objectItem={disabledMetadataObject}
action={
<SettingsObjectDisabledMenuDropDown
isCustomObject={objectItem.isCustom}
scopeKey={objectItem.namePlural}
onActivate={() => activateObject(objectItem)}
onErase={() => eraseObject(objectItem)}
isCustomObject={disabledMetadataObject.isCustom}
scopeKey={disabledMetadataObject.namePlural}
onActivate={() =>
activateMetadataObject(disabledMetadataObject)
}
onErase={() =>
eraseMetadataObject(disabledMetadataObject)
}
/>
}
/>
+1
View File
@@ -10,6 +10,7 @@ provision-postgres:
@docker rm twenty_postgres || true
@docker volume rm twenty_db_data || true
@docker compose up --build postgres -d
@cd ../../server && yarn database:setup && yarn database:reset
up:
@docker compose up -d
+11 -11
View File
@@ -15,18 +15,18 @@ export const seedMetadata = async (prisma: PrismaClient) => {
await prisma.$queryRawUnsafe(`CREATE TABLE IF NOT EXISTS
workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.company(
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
"id" TEXT PRIMARY KEY,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
"deletedAt" TIMESTAMP WITH TIME ZONE,
domainName TEXT NOT NULL,
address TEXT NOT NULL,
employees INTEGER NOT NULL
"domainName" TEXT NOT NULL,
"address" TEXT NOT NULL,
"employees" INTEGER NOT NULL
);
`);
await prisma.$queryRawUnsafe(`INSERT INTO workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.company(
id, name, domainName, address, employees
"id", "name", "domainName", "address", "employees"
)
VALUES (
'89bb825c-171e-4bcc-9cf7-43448d6fb278', 'Airbnb', 'airbnb.com', 'San Francisco', 5000
@@ -44,19 +44,19 @@ export const seedMetadata = async (prisma: PrismaClient) => {
id, name_singular, name_plural, label_singular, label_plural, description, icon, target_table_name, is_custom, is_active, workspace_id, data_source_id
)
VALUES (
'ba391617-ee08-432f-9438-2e17df5ac279', 'companyV2', 'companiesV2', 'Company', 'Companies', 'Companies', 'company', 'company', false, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419', 'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1'
'ba391617-ee08-432f-9438-2e17df5ac279', 'companyV2', 'companiesV2', 'Company', 'Companies', 'Companies', 'IconBuilding', 'company', false, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419', 'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1'
) ON CONFLICT DO NOTHING`);
await prisma.$queryRawUnsafe(`INSERT INTO metadata.field_metadata(
id, object_id, type, name, label, target_column_map, description, icon, enums, is_custom, is_active, is_nullable, workspace_id
)
VALUES (
'22f5906d-153f-448c-b254-28adce721dcd', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'name', 'Name', '{"value": "name"}', 'Name', 'user', NULL, false, true, false, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
'22f5906d-153f-448c-b254-28adce721dcd', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'name', 'Name', '{"value": "name"}', 'Name', 'IconUser', NULL, false, true, false, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
), (
'19bfab29-1cbb-4ce2-9117-8540ac45a0f1', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'domainName', 'Domain Name', '{"value": "domainName"}', 'Domain Name', 'link', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
'19bfab29-1cbb-4ce2-9117-8540ac45a0f1', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'domainName', 'Domain Name', '{"value": "domainName"}', 'Domain Name', 'IconExternalLink', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
), (
'70130f27-9497-4b44-a04c-1a0fb9a4829c', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'address', 'Address', '{"value": "address"}', 'Address', 'location', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
'70130f27-9497-4b44-a04c-1a0fb9a4829c', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'address', 'Address', '{"value": "address"}', 'Address', 'IconMap', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
), (
'2a63c30e-8e80-475b-b5d7-9dda17adc537', 'ba391617-ee08-432f-9438-2e17df5ac279', 'number', 'employees', 'Employees', '{"value": "employees"}', 'Employees', 'people', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
'2a63c30e-8e80-475b-b5d7-9dda17adc537', 'ba391617-ee08-432f-9438-2e17df5ac279', 'number', 'employees', 'Employees', '{"value": "employees"}', 'Employees', 'IconUsers', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
) ON CONFLICT DO NOTHING`);
};
@@ -36,6 +36,7 @@ export type FieldMetadataTargetColumnMap = {
defaultResultSize: 10,
disableFilter: true,
disableSort: true,
maxResultsSize: 1000,
})
@Unique('IndexOnNameObjectIdAndWorkspaceIdUnique', [
'name',
@@ -33,6 +33,7 @@ import { BeforeCreateOneObject } from './hooks/before-create-one-object.hook';
defaultResultSize: 10,
disableFilter: true,
disableSort: true,
maxResultsSize: 1000,
})
@CursorConnection('fields', () => FieldMetadata)
@Unique('IndexOnNameSingularAndWorkspaceIdUnique', [