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:
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user