27da9d60eb
## Summary
Three fixes in this PR:
### 1. Migration commands now set `isActive = true` and use generic
'Target' label
When converting relation fields to `MORPH_RELATION` type, the migration
commands now:
- Set `isActive = true` to prevent inactive fields from being selected
as the representative morph field
- Update all field labels to generic **'Target'** instead of keeping
individual labels like 'Company', 'Person'
This ensures the UI shows a coherent label ('Target') alongside 'X
Objects' for the type.
**Files changed:**
- `1-17-migrate-note-target-to-morph-relations.command.ts`
- `1-17-migrate-task-target-to-morph-relations.command.ts`
### 2. Added system fields/relations toggles in Settings
The fields and relations tables in Settings > Data Model were filtering
out system fields with no way to view them. Added a "System fields" /
"System relations" toggle (visible in advanced mode) to allow viewing
these fields.
**Files changed:**
- `SettingsObjectFieldTable.tsx`
- `SettingsObjectRelationsTable.tsx`
This matches the existing behavior on the Objects table which already
has a "System objects" toggle.
189 lines
6.2 KiB
TypeScript
189 lines
6.2 KiB
TypeScript
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
|
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
|
import { Table } from '@/ui/layout/table/components/Table';
|
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
|
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
|
import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
|
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
|
import styled from '@emotion/styled';
|
|
import { msg } from '@lingui/core/macro';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { useMemo, useState } from 'react';
|
|
import { useRecoilValue } from 'recoil';
|
|
import { FieldMetadataType } from 'twenty-shared/types';
|
|
import {
|
|
IconArchive,
|
|
IconFilter,
|
|
IconSearch,
|
|
IconSettings,
|
|
} from 'twenty-ui/display';
|
|
import { Button } from 'twenty-ui/input';
|
|
import { MenuItemToggle } from 'twenty-ui/navigation';
|
|
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
|
import {
|
|
SettingsObjectRelationItemTableRow,
|
|
StyledObjectRelationTableRow,
|
|
} from './SettingsObjectRelationItemTableRow';
|
|
|
|
const StyledSearchAndFilterContainer = styled.div`
|
|
display: flex;
|
|
gap: ${({ theme }) => theme.spacing(2)};
|
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
|
width: 100%;
|
|
`;
|
|
|
|
const StyledSearchInput = styled(SettingsTextInput)`
|
|
flex: 1;
|
|
`;
|
|
|
|
const SETTINGS_OBJECT_RELATION_TABLE_METADATA: TableMetadata<FieldMetadataItem> =
|
|
{
|
|
tableId: 'settingsObjectRelations',
|
|
fields: [
|
|
{
|
|
fieldLabel: msg`Name`,
|
|
fieldName: 'label',
|
|
fieldType: 'string',
|
|
align: 'left',
|
|
},
|
|
{
|
|
fieldLabel: msg`App`,
|
|
fieldName: 'isCustom',
|
|
fieldType: 'string',
|
|
align: 'left',
|
|
},
|
|
{
|
|
fieldLabel: msg`Type`,
|
|
fieldName: 'type',
|
|
fieldType: 'string',
|
|
align: 'left',
|
|
},
|
|
],
|
|
initialSort: {
|
|
fieldName: 'label',
|
|
orderBy: 'AscNullsLast',
|
|
},
|
|
};
|
|
|
|
type SettingsObjectRelationsTableProps = {
|
|
objectMetadataItem: ObjectMetadataItem;
|
|
};
|
|
|
|
export const SettingsObjectRelationsTable = ({
|
|
objectMetadataItem,
|
|
}: SettingsObjectRelationsTableProps) => {
|
|
const { t } = useLingui();
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [showInactive, setShowInactive] = useState(true);
|
|
const [showSystemRelations, setShowSystemRelations] = useState(false);
|
|
|
|
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
|
|
|
|
const tableMetadata = SETTINGS_OBJECT_RELATION_TABLE_METADATA;
|
|
|
|
const relationFields = useMemo(() => {
|
|
return objectMetadataItem.fields.filter(
|
|
(field) =>
|
|
(showSystemRelations || !field.isSystem) &&
|
|
(field.type === FieldMetadataType.RELATION ||
|
|
field.type === FieldMetadataType.MORPH_RELATION),
|
|
);
|
|
}, [objectMetadataItem.fields, showSystemRelations]);
|
|
|
|
const sortedRelationFields = useSortedArray(relationFields, tableMetadata);
|
|
|
|
const filteredRelationFields = useMemo(() => {
|
|
const searchNormalized = normalizeSearchText(searchTerm);
|
|
|
|
return sortedRelationFields.filter((field) => {
|
|
const matchesActiveFilter = showInactive || field.isActive;
|
|
const matchesSearch = normalizeSearchText(field.label).includes(
|
|
searchNormalized,
|
|
);
|
|
return matchesActiveFilter && matchesSearch;
|
|
});
|
|
}, [sortedRelationFields, searchTerm, showInactive]);
|
|
|
|
if (relationFields.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<StyledSearchAndFilterContainer>
|
|
<StyledSearchInput
|
|
instanceId="object-relation-table-search"
|
|
LeftIcon={IconSearch}
|
|
placeholder={t`Search a field...`}
|
|
value={searchTerm}
|
|
onChange={setSearchTerm}
|
|
/>
|
|
<Dropdown
|
|
dropdownId="settings-relations-filter-dropdown"
|
|
dropdownPlacement="bottom-end"
|
|
dropdownOffset={{ x: 0, y: 8 }}
|
|
clickableComponent={
|
|
<Button
|
|
Icon={IconFilter}
|
|
size="medium"
|
|
variant="secondary"
|
|
accent="default"
|
|
ariaLabel={t`Filter`}
|
|
/>
|
|
}
|
|
dropdownComponents={
|
|
<DropdownContent>
|
|
<DropdownMenuItemsContainer>
|
|
<MenuItemToggle
|
|
LeftIcon={IconArchive}
|
|
onToggleChange={() => setShowInactive(!showInactive)}
|
|
toggled={showInactive}
|
|
text={t`Inactive`}
|
|
toggleSize="small"
|
|
/>
|
|
{isAdvancedModeEnabled && (
|
|
<MenuItemToggle
|
|
LeftIcon={IconSettings}
|
|
onToggleChange={() =>
|
|
setShowSystemRelations(!showSystemRelations)
|
|
}
|
|
toggled={showSystemRelations}
|
|
text={t`System relations`}
|
|
toggleSize="small"
|
|
/>
|
|
)}
|
|
</DropdownMenuItemsContainer>
|
|
</DropdownContent>
|
|
}
|
|
/>
|
|
</StyledSearchAndFilterContainer>
|
|
<Table>
|
|
<StyledObjectRelationTableRow>
|
|
{tableMetadata.fields.map((item) => (
|
|
<SortableTableHeader
|
|
key={item.fieldName}
|
|
fieldName={item.fieldName}
|
|
label={t(item.fieldLabel)}
|
|
tableId={tableMetadata.tableId}
|
|
initialSort={tableMetadata.initialSort}
|
|
/>
|
|
))}
|
|
<TableHeader></TableHeader>
|
|
</StyledObjectRelationTableRow>
|
|
{filteredRelationFields.map((fieldMetadataItem) => (
|
|
<SettingsObjectRelationItemTableRow
|
|
key={fieldMetadataItem.id}
|
|
fieldMetadataItem={fieldMetadataItem}
|
|
objectMetadataItem={objectMetadataItem}
|
|
/>
|
|
))}
|
|
</Table>
|
|
</>
|
|
);
|
|
};
|