Added ability to search objects and fields (#6775)

Closes #6770 



https://github.com/user-attachments/assets/e3134389-30d2-48c8-bbca-34209d5ae66d
This commit is contained in:
nitin
2024-08-29 17:45:01 +05:30
committed by GitHub
parent 9fafb2cca0
commit ebfdc6cfd2
2 changed files with 126 additions and 56 deletions

View File

@ -4,16 +4,18 @@ import {
StyledObjectFieldTableRow, StyledObjectFieldTableRow,
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow'; } from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
import { TextInput } from '@/ui/input/components/TextInput';
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
import { Table } from '@/ui/layout/table/components/Table'; import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableSection } from '@/ui/layout/table/components/TableSection'; import { TableSection } from '@/ui/layout/table/components/TableSection';
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { TableMetadata } from '@/ui/layout/table/types/TableMetadata'; import { TableMetadata } from '@/ui/layout/table/types/TableMetadata';
import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards'; import { isNonEmptyArray } from '@sniptt/guards';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo } from 'react';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { IconSearch } from 'twenty-ui';
import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem'; import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem';
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem'; import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
@ -75,6 +77,9 @@ const SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM: TableMetadata<SettingsObject
}, },
}; };
const StyledSearchInput = styled(TextInput)`
width: 100%;
`;
export type SettingsObjectFieldTableProps = { export type SettingsObjectFieldTableProps = {
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
mode: 'view' | 'new-field'; mode: 'view' | 'new-field';
@ -85,6 +90,8 @@ export const SettingsObjectFieldTable = ({
objectMetadataItem, objectMetadataItem,
mode, mode,
}: SettingsObjectFieldTableProps) => { }: SettingsObjectFieldTableProps) => {
const [searchTerm, setSearchTerm] = useState('');
const tableMetadata = objectMetadataItem.isCustom const tableMetadata = objectMetadataItem.isCustom
? SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM ? SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM
: SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD; : SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD;
@ -144,7 +151,34 @@ export const SettingsObjectFieldTable = ({
tableMetadata, tableMetadata,
); );
const filteredActiveItems = useMemo(
() =>
sortedActiveObjectSettingsDetailItems.filter(
(item) =>
item.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.dataType.toLowerCase().includes(searchTerm.toLowerCase()),
),
[sortedActiveObjectSettingsDetailItems, searchTerm],
);
const filteredDisabledItems = useMemo(
() =>
sortedDisabledObjectSettingsDetailItems.filter(
(item) =>
item.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.dataType.toLowerCase().includes(searchTerm.toLowerCase()),
),
[sortedDisabledObjectSettingsDetailItems, searchTerm],
);
return ( return (
<>
<StyledSearchInput
LeftIcon={IconSearch}
placeholder="Search a field..."
value={searchTerm}
onChange={setSearchTerm}
/>
<Table> <Table>
<StyledObjectFieldTableRow> <StyledObjectFieldTableRow>
{tableMetadata.fields.map((item) => ( {tableMetadata.fields.map((item) => (
@ -158,37 +192,34 @@ export const SettingsObjectFieldTable = ({
))} ))}
<TableHeader></TableHeader> <TableHeader></TableHeader>
</StyledObjectFieldTableRow> </StyledObjectFieldTableRow>
{isNonEmptyArray(sortedActiveObjectSettingsDetailItems) && ( {isNonEmptyArray(filteredActiveItems) && (
<TableSection title="Active"> <TableSection title="Active">
{sortedActiveObjectSettingsDetailItems.map( {filteredActiveItems.map((objectSettingsDetailItem) => (
(objectSettingsDetailItem) => (
<SettingsObjectFieldItemTableRow <SettingsObjectFieldItemTableRow
key={objectSettingsDetailItem.fieldMetadataItem.id} key={objectSettingsDetailItem.fieldMetadataItem.id}
settingsObjectDetailTableItem={objectSettingsDetailItem} settingsObjectDetailTableItem={objectSettingsDetailItem}
status="active" status="active"
mode={mode} mode={mode}
/> />
), ))}
)}
</TableSection> </TableSection>
)} )}
{isNonEmptyArray(sortedDisabledObjectSettingsDetailItems) && ( {isNonEmptyArray(filteredDisabledItems) && (
<TableSection <TableSection
isInitiallyExpanded={mode === 'new-field' ? true : false} isInitiallyExpanded={mode === 'new-field' ? true : false}
title="Inactive" title="Inactive"
> >
{sortedDisabledObjectSettingsDetailItems.map( {filteredDisabledItems.map((objectSettingsDetailItem) => (
(objectSettingsDetailItem) => (
<SettingsObjectFieldItemTableRow <SettingsObjectFieldItemTableRow
key={objectSettingsDetailItem.fieldMetadataItem.id} key={objectSettingsDetailItem.fieldMetadataItem.id}
settingsObjectDetailTableItem={objectSettingsDetailItem} settingsObjectDetailTableItem={objectSettingsDetailItem}
status="disabled" status="disabled"
mode={mode} mode={mode}
/> />
), ))}
)}
</TableSection> </TableSection>
)} )}
</Table> </Table>
</>
); );
}; };

View File

@ -1,6 +1,12 @@
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { H2Title, IconChevronRight, IconHierarchy2, IconPlus } from 'twenty-ui'; import {
H2Title,
IconChevronRight,
IconHierarchy2,
IconPlus,
IconSearch,
} from 'twenty-ui';
import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem'; import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
@ -18,6 +24,7 @@ import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLab
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
@ -27,17 +34,19 @@ import { TableSection } from '@/ui/layout/table/components/TableSection';
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { isNonEmptyArray } from '@sniptt/guards'; import { isNonEmptyArray } from '@sniptt/guards';
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata'; import { SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata';
import { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem'; import { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
const StyledIconChevronRight = styled(IconChevronRight)` const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ theme }) => theme.font.color.tertiary};
`; `;
const StyledSearchInput = styled(TextInput)`
width: 100%;
`;
export const SettingsObjects = () => { export const SettingsObjects = () => {
const theme = useTheme(); const theme = useTheme();
const [searchTerm, setSearchTerm] = useState('');
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem(); const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem(); const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
@ -102,7 +111,25 @@ export const SettingsObjects = () => {
inactiveObjectSettingsArray, inactiveObjectSettingsArray,
SETTINGS_OBJECT_TABLE_METADATA, SETTINGS_OBJECT_TABLE_METADATA,
); );
const filteredActiveObjectSettingsItems = useMemo(
() =>
sortedActiveObjectSettingsItems.filter(
(item) =>
item.labelPlural.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.objectTypeLabel.toLowerCase().includes(searchTerm.toLowerCase()),
),
[sortedActiveObjectSettingsItems, searchTerm],
);
const filteredInactiveObjectSettingsItems = useMemo(
() =>
sortedInactiveObjectSettingsItems.filter(
(item) =>
item.labelPlural.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.objectTypeLabel.toLowerCase().includes(searchTerm.toLowerCase()),
),
[sortedInactiveObjectSettingsItems, searchTerm],
);
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
@ -123,6 +150,14 @@ export const SettingsObjects = () => {
<SettingsObjectCoverImage /> <SettingsObjectCoverImage />
<Section> <Section>
<H2Title title="Existing objects" /> <H2Title title="Existing objects" />
<StyledSearchInput
LeftIcon={IconSearch}
placeholder="Search an object..."
value={searchTerm}
onChange={setSearchTerm}
/>
<Table> <Table>
<StyledObjectTableRow> <StyledObjectTableRow>
{SETTINGS_OBJECT_TABLE_METADATA.fields.map( {SETTINGS_OBJECT_TABLE_METADATA.fields.map(
@ -141,10 +176,13 @@ export const SettingsObjects = () => {
</StyledObjectTableRow> </StyledObjectTableRow>
{isNonEmptyArray(sortedActiveObjectSettingsItems) && ( {isNonEmptyArray(sortedActiveObjectSettingsItems) && (
<TableSection title="Active"> <TableSection title="Active">
{sortedActiveObjectSettingsItems.map((objectSettingsItem) => ( {filteredActiveObjectSettingsItems.map(
(objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow <SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural} key={objectSettingsItem.objectMetadataItem.namePlural}
objectMetadataItem={objectSettingsItem.objectMetadataItem} objectMetadataItem={
objectSettingsItem.objectMetadataItem
}
totalObjectCount={objectSettingsItem.totalObjectCount} totalObjectCount={objectSettingsItem.totalObjectCount}
action={ action={
<StyledIconChevronRight <StyledIconChevronRight
@ -156,12 +194,13 @@ export const SettingsObjects = () => {
objectSettingsItem.objectMetadataItem, objectSettingsItem.objectMetadataItem,
)}`} )}`}
/> />
))} ),
)}
</TableSection> </TableSection>
)} )}
{isNonEmptyArray(inactiveObjectMetadataItems) && ( {isNonEmptyArray(inactiveObjectMetadataItems) && (
<TableSection title="Inactive"> <TableSection title="Inactive">
{sortedInactiveObjectSettingsItems.map( {filteredInactiveObjectSettingsItems.map(
(objectSettingsItem) => ( (objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow <SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural} key={objectSettingsItem.objectMetadataItem.namePlural}