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,51 +151,75 @@ 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 (
<Table> <>
<StyledObjectFieldTableRow> <StyledSearchInput
{tableMetadata.fields.map((item) => ( LeftIcon={IconSearch}
<SortableTableHeader placeholder="Search a field..."
key={item.fieldName} value={searchTerm}
fieldName={item.fieldName} onChange={setSearchTerm}
label={item.fieldLabel} />
tableId={tableMetadata.tableId} <Table>
initialSort={tableMetadata.initialSort} <StyledObjectFieldTableRow>
/> {tableMetadata.fields.map((item) => (
))} <SortableTableHeader
<TableHeader></TableHeader> key={item.fieldName}
</StyledObjectFieldTableRow> fieldName={item.fieldName}
{isNonEmptyArray(sortedActiveObjectSettingsDetailItems) && ( label={item.fieldLabel}
<TableSection title="Active"> tableId={tableMetadata.tableId}
{sortedActiveObjectSettingsDetailItems.map( initialSort={tableMetadata.initialSort}
(objectSettingsDetailItem) => ( />
))}
<TableHeader></TableHeader>
</StyledObjectFieldTableRow>
{isNonEmptyArray(filteredActiveItems) && (
<TableSection title="Active">
{filteredActiveItems.map((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(filteredDisabledItems) && (
{isNonEmptyArray(sortedDisabledObjectSettingsDetailItems) && ( <TableSection
<TableSection isInitiallyExpanded={mode === 'new-field' ? true : false}
isInitiallyExpanded={mode === 'new-field' ? true : false} title="Inactive"
title="Inactive" >
> {filteredDisabledItems.map((objectSettingsDetailItem) => (
{sortedDisabledObjectSettingsDetailItems.map(
(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,27 +176,31 @@ export const SettingsObjects = () => {
</StyledObjectTableRow> </StyledObjectTableRow>
{isNonEmptyArray(sortedActiveObjectSettingsItems) && ( {isNonEmptyArray(sortedActiveObjectSettingsItems) && (
<TableSection title="Active"> <TableSection title="Active">
{sortedActiveObjectSettingsItems.map((objectSettingsItem) => ( {filteredActiveObjectSettingsItems.map(
<SettingsObjectMetadataItemTableRow (objectSettingsItem) => (
key={objectSettingsItem.objectMetadataItem.namePlural} <SettingsObjectMetadataItemTableRow
objectMetadataItem={objectSettingsItem.objectMetadataItem} key={objectSettingsItem.objectMetadataItem.namePlural}
totalObjectCount={objectSettingsItem.totalObjectCount} objectMetadataItem={
action={ objectSettingsItem.objectMetadataItem
<StyledIconChevronRight }
size={theme.icon.size.md} totalObjectCount={objectSettingsItem.totalObjectCount}
stroke={theme.icon.stroke.sm} action={
/> <StyledIconChevronRight
} size={theme.icon.size.md}
link={`/settings/objects/${getObjectSlug( stroke={theme.icon.stroke.sm}
objectSettingsItem.objectMetadataItem, />
)}`} }
/> link={`/settings/objects/${getObjectSlug(
))} 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}