Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to search objects and fields #6775

Merged
merged 4 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconSearch } from 'twenty-ui';

const StyledSearchInputContainer = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(2)};
position: relative;
width: 100%;
`;

const StyledSearchInput = styled.input`
width: 100%;
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
background-color: transparent;
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
outline: none;
height: 32px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using theme for height to maintain consistency

padding: 0 ${({ theme }) => theme.spacing(2)} 0
${({ theme }) => theme.spacing(7)};
box-sizing: border-box;
&::placeholder {
color: ${({ theme }) => theme.font.color.light};
font-weight: ${({ theme }) => theme.font.weight.medium};
}
`;

const StyledSearchIcon = styled(IconSearch)`
position: absolute;
left: ${({ theme }) => theme.spacing(2)};
top: 50%;
transform: translateY(-50%);
color: ${({ theme }) => theme.font.color.light};
`;

interface SearchInputProps {
placeholder: string;
value: string;
onChange: (value: string) => void;
}

export const SearchInput = ({
placeholder,
value,
onChange,
}: SearchInputProps) => {
const theme = useTheme();
return (
<StyledSearchInputContainer>
<StyledSearchIcon size={theme.icon.size.md} />
<StyledSearchInput
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</StyledSearchInputContainer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
StyledObjectFieldTableRow,
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
import { SearchInput } from '@/ui/input/components/SearchInput';
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
Expand All @@ -12,7 +13,7 @@ import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { TableMetadata } from '@/ui/layout/table/types/TableMetadata';
import { isNonEmptyArray } from '@sniptt/guards';

import { useEffect, useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useRecoilState } from 'recoil';
import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem';
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
Expand Down Expand Up @@ -85,6 +86,8 @@ export const SettingsObjectFieldTable = ({
objectMetadataItem,
mode,
}: SettingsObjectFieldTableProps) => {
const [searchTerm, setSearchTerm] = useState('');

const tableMetadata = objectMetadataItem.isCustom
? SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM
: SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD;
Expand Down Expand Up @@ -144,51 +147,74 @@ export const SettingsObjectFieldTable = ({
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 (
<Table>
<StyledObjectFieldTableRow>
{tableMetadata.fields.map((item) => (
<SortableTableHeader
key={item.fieldName}
fieldName={item.fieldName}
label={item.fieldLabel}
tableId={tableMetadata.tableId}
initialSort={tableMetadata.initialSort}
/>
))}
<TableHeader></TableHeader>
</StyledObjectFieldTableRow>
{isNonEmptyArray(sortedActiveObjectSettingsDetailItems) && (
<TableSection title="Active">
{sortedActiveObjectSettingsDetailItems.map(
(objectSettingsDetailItem) => (
<>
<SearchInput
placeholder="Search a field..."
value={searchTerm}
onChange={setSearchTerm}
/>
<Table>
<StyledObjectFieldTableRow>
{tableMetadata.fields.map((item) => (
<SortableTableHeader
key={item.fieldName}
fieldName={item.fieldName}
label={item.fieldLabel}
tableId={tableMetadata.tableId}
initialSort={tableMetadata.initialSort}
/>
))}
<TableHeader></TableHeader>
</StyledObjectFieldTableRow>
{isNonEmptyArray(filteredActiveItems) && (
<TableSection title="Active">
{filteredActiveItems.map((objectSettingsDetailItem) => (
<SettingsObjectFieldItemTableRow
key={objectSettingsDetailItem.fieldMetadataItem.id}
settingsObjectDetailTableItem={objectSettingsDetailItem}
status="active"
mode={mode}
/>
),
)}
</TableSection>
)}
{isNonEmptyArray(sortedDisabledObjectSettingsDetailItems) && (
<TableSection
isInitiallyExpanded={mode === 'new-field' ? true : false}
title="Inactive"
>
{sortedDisabledObjectSettingsDetailItems.map(
(objectSettingsDetailItem) => (
))}
</TableSection>
)}
{isNonEmptyArray(filteredDisabledItems) && (
<TableSection
isInitiallyExpanded={mode === 'new-field' ? true : false}
title="Inactive"
>
{filteredDisabledItems.map((objectSettingsDetailItem) => (
<SettingsObjectFieldItemTableRow
key={objectSettingsDetailItem.fieldMetadataItem.id}
settingsObjectDetailTableItem={objectSettingsDetailItem}
status="disabled"
mode={mode}
/>
),
)}
</TableSection>
)}
</Table>
))}
</TableSection>
)}
</Table>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLab
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button';
import { SearchInput } from '@/ui/input/components/SearchInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
Expand All @@ -27,7 +28,7 @@ import { TableSection } from '@/ui/layout/table/components/TableSection';
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
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 { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';

Expand All @@ -37,7 +38,7 @@ const StyledIconChevronRight = styled(IconChevronRight)`

export const SettingsObjects = () => {
const theme = useTheme();

const [searchTerm, setSearchTerm] = useState('');
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();

Expand Down Expand Up @@ -102,7 +103,25 @@ export const SettingsObjects = () => {
inactiveObjectSettingsArray,
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 (
<SubMenuTopBarContainer
Icon={IconHierarchy2}
Expand All @@ -123,6 +142,13 @@ export const SettingsObjects = () => {
<SettingsObjectCoverImage />
<Section>
<H2Title title="Existing objects" />

<SearchInput
placeholder="Search an object..."
value={searchTerm}
onChange={setSearchTerm}
/>

<Table>
<StyledObjectTableRow>
{SETTINGS_OBJECT_TABLE_METADATA.fields.map(
Expand All @@ -141,27 +167,31 @@ export const SettingsObjects = () => {
</StyledObjectTableRow>
{isNonEmptyArray(sortedActiveObjectSettingsItems) && (
<TableSection title="Active">
{sortedActiveObjectSettingsItems.map((objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural}
objectMetadataItem={objectSettingsItem.objectMetadataItem}
totalObjectCount={objectSettingsItem.totalObjectCount}
action={
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
}
link={`/settings/objects/${getObjectSlug(
objectSettingsItem.objectMetadataItem,
)}`}
/>
))}
{filteredActiveObjectSettingsItems.map(
(objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural}
objectMetadataItem={
objectSettingsItem.objectMetadataItem
}
totalObjectCount={objectSettingsItem.totalObjectCount}
action={
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
}
link={`/settings/objects/${getObjectSlug(
objectSettingsItem.objectMetadataItem,
)}`}
/>
),
)}
</TableSection>
)}
{isNonEmptyArray(inactiveObjectMetadataItems) && (
<TableSection title="Inactive">
{sortedInactiveObjectSettingsItems.map(
{filteredInactiveObjectSettingsItems.map(
(objectSettingsItem) => (
<SettingsObjectMetadataItemTableRow
key={objectSettingsItem.objectMetadataItem.namePlural}
Expand Down
Loading