Files
twenty/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldTable.tsx
T

225 lines
8.3 KiB
TypeScript

import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
SettingsObjectFieldItemTableRow,
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
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 { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { useEffect, useMemo, useState } from 'react';
import { FieldMetadataType } from 'twenty-shared/types';
import { IconArchive, IconSettings } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem';
import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
const StyledSearchContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[2]};
`;
const SETTINGS_OBJECT_FIELD_TABLE_METADATA: TableMetadata<SettingsObjectDetailTableItem> =
{
tableId: 'settingsObjectDetail',
fields: [
{
fieldLabel: msg`Name`,
fieldName: 'label',
fieldType: 'string',
align: 'left',
},
{
fieldLabel: msg`App`,
fieldName: 'fieldType',
fieldType: 'string',
align: 'left',
},
{
fieldLabel: msg`Data type`,
fieldName: 'dataType',
fieldType: 'string',
align: 'left',
},
],
initialSort: {
fieldName: 'label',
orderBy: 'AscNullsLast',
},
};
export type SettingsObjectFieldTableProps = {
objectMetadataItem: EnrichedObjectMetadataItem;
mode: 'view' | 'new-field';
excludeRelations?: boolean;
};
// TODO: find another way than using mode which feels like it could be replaced by another pattern
export const SettingsObjectFieldTable = ({
objectMetadataItem,
mode,
excludeRelations = false,
}: SettingsObjectFieldTableProps) => {
const { t } = useLingui();
const [searchTerm, setSearchTerm] = useState('');
const [showInactive, setShowInactive] = useState(true);
const [showSystemFields, setShowSystemFields] = useState(false);
const isAdvancedModeEnabled = useAtomStateValue(isAdvancedModeEnabledState);
const tableMetadata = SETTINGS_OBJECT_FIELD_TABLE_METADATA;
const { mapFieldMetadataItemToSettingsObjectDetailTableItem } =
useMapFieldMetadataItemToSettingsObjectDetailTableItem(objectMetadataItem);
const settingsObjectFields = useAtomFamilyStateValue(
settingsObjectFieldsFamilyState,
{ objectMetadataItemId: objectMetadataItem.id },
);
const setSettingsObjectFields = useSetAtomFamilyState(
settingsObjectFieldsFamilyState,
{ objectMetadataItemId: objectMetadataItem.id },
);
useEffect(() => {
setSettingsObjectFields(objectMetadataItem.fields);
}, [objectMetadataItem, setSettingsObjectFields]);
const allObjectSettingsDetailItems = useMemo(() => {
const filteredBySystem = showSystemFields
? settingsObjectFields
: settingsObjectFields?.filter(
(fieldMetadataItem) => !isHiddenSystemField(fieldMetadataItem),
);
const fieldsToDisplay = excludeRelations
? filteredBySystem?.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type !== FieldMetadataType.RELATION &&
fieldMetadataItem.type !== FieldMetadataType.MORPH_RELATION,
)
: filteredBySystem;
return (
fieldsToDisplay?.map(
mapFieldMetadataItemToSettingsObjectDetailTableItem,
) ?? []
);
}, [
settingsObjectFields,
mapFieldMetadataItemToSettingsObjectDetailTableItem,
excludeRelations,
showSystemFields,
]);
const sortedAllObjectSettingsDetailItems = useSortedArray(
allObjectSettingsDetailItems,
tableMetadata,
);
const filteredItems = useMemo(() => {
const searchNormalized = normalizeSearchText(searchTerm);
return sortedAllObjectSettingsDetailItems.filter((item) => {
const matchesActiveFilter =
showInactive || item.fieldMetadataItem.isActive;
const matchesSearch =
normalizeSearchText(item.label).includes(searchNormalized) ||
normalizeSearchText(item.dataType).includes(searchNormalized);
return matchesActiveFilter && matchesSearch;
});
}, [sortedAllObjectSettingsDetailItems, searchTerm, showInactive]);
return (
<>
<StyledSearchContainer>
<SearchInput
placeholder={t`Search a field...`}
value={searchTerm}
onChange={setSearchTerm}
filterDropdown={(filterButton) => (
<Dropdown
dropdownId="settings-fields-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={filterButton}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconArchive}
onToggleChange={() => setShowInactive(!showInactive)}
toggled={showInactive}
text={t`Inactive`}
toggleSize="small"
/>
{isAdvancedModeEnabled && (
<MenuItemToggle
LeftIcon={IconSettings}
onToggleChange={() =>
setShowSystemFields(!showSystemFields)
}
toggled={showSystemFields}
text={t`System fields`}
toggleSize="small"
/>
)}
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
)}
/>
</StyledSearchContainer>
<Table>
<TableRow
gridTemplateColumns={OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
{tableMetadata.fields.map((item) => (
<SortableTableHeader
key={item.fieldName}
fieldName={item.fieldName}
label={t(item.fieldLabel)}
tableId={tableMetadata.tableId}
initialSort={tableMetadata.initialSort}
/>
))}
<TableHeader></TableHeader>
</TableRow>
{filteredItems.map((objectSettingsDetailItem) => {
const status = objectSettingsDetailItem.fieldMetadataItem.isActive
? 'active'
: 'disabled';
return (
<SettingsObjectFieldItemTableRow
key={objectSettingsDetailItem.fieldMetadataItem.id}
settingsObjectDetailTableItem={objectSettingsDetailItem}
status={status}
mode={mode}
/>
);
})}
</Table>
</>
);
};