feat: add Fields table to Object Detail page (#1988)

* feat: add Fields table to Object Detail page

Closes #1815

* refactor: add ObjectFieldDataType
This commit is contained in:
Thaïs
2023-10-13 11:51:11 +02:00
committed by GitHub
parent bd9a6c56fe
commit 818efd72d0
14 changed files with 342 additions and 58 deletions

View File

@ -0,0 +1,61 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
IconCheck,
IconLink,
IconNumbers,
IconPlug,
IconSocial,
IconUserCircle,
} from '@/ui/icon';
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { ObjectFieldItem } from '../types/ObjectFieldItem';
type ObjectFieldDataTypeProps = {
value: ObjectFieldItem['dataType'];
};
const StyledDataType = styled.div<{ value: ObjectFieldItem['dataType'] }>`
align-items: center;
border: 1px solid transparent;
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(1)};
height: 20px;
padding: 0 ${({ theme }) => theme.spacing(2)};
${({ theme, value }) =>
value === 'relation'
? css`
border-color: ${theme.color.purple20};
color: ${theme.color.purple};
`
: ''}
`;
const dataTypes: Record<
ObjectFieldItem['dataType'],
{ label: string; Icon: IconComponent }
> = {
boolean: { label: 'True/False', Icon: IconCheck },
number: { label: 'Number', Icon: IconNumbers },
relation: { label: 'Relation', Icon: IconPlug },
social: { label: 'Social', Icon: IconSocial },
teammate: { label: 'Teammate', Icon: IconUserCircle },
text: { label: 'Text', Icon: IconLink },
};
export const ObjectFieldDataType = ({ value }: ObjectFieldDataTypeProps) => {
const theme = useTheme();
const { label, Icon } = dataTypes[value];
return (
<StyledDataType value={value}>
<Icon size={theme.icon.size.sm} />
{label}
</StyledDataType>
);
};

View File

@ -0,0 +1,57 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconDotsVertical } from '@/ui/icon';
import { TableCell } from '@/ui/table/components/TableCell';
import { TableRow } from '@/ui/table/components/TableRow';
import { ObjectFieldItem } from '../types/ObjectFieldItem';
import { ObjectFieldDataType } from './ObjectFieldDataType';
export const StyledObjectFieldTableRow = styled(TableRow)`
grid-template-columns: 180px 148px 148px 36px;
`;
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledIconDotsVertical = styled(IconDotsVertical)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
export const ObjectFieldItemTableRow = ({
fieldItem,
}: {
fieldItem: ObjectFieldItem;
}) => {
const theme = useTheme();
return (
<StyledObjectFieldTableRow>
<StyledNameTableCell>
<fieldItem.Icon size={theme.icon.size.md} />
{fieldItem.name}
</StyledNameTableCell>
<TableCell>
{fieldItem.type === 'standard' ? 'Standard' : 'Custom'}
</TableCell>
<TableCell>
<ObjectFieldDataType value={fieldItem.dataType} />
</TableCell>
<StyledIconTableCell>
<StyledIconDotsVertical
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconTableCell>
</StyledObjectFieldTableRow>
);
};

View File

@ -0,0 +1,125 @@
import { ObjectFieldItem } from '@/settings/objects/types/ObjectFieldItem';
import {
IconBrandLinkedin,
IconBrandTwitter,
IconBuildingSkyscraper,
IconCurrencyDollar,
IconFreeRights,
IconGraph,
IconHeadphones,
IconLink,
IconLuggage,
IconPlane,
IconTarget,
IconUser,
IconUserCircle,
IconUsers,
} from '@/ui/icon';
export const activeObjectItems = [
{
name: 'Companies',
singularName: 'company',
Icon: IconBuildingSkyscraper,
type: 'standard',
fields: 23,
instances: 165,
},
{
name: 'People',
singularName: 'person',
Icon: IconUser,
type: 'standard',
fields: 16,
instances: 462,
},
];
export const disabledObjectItems = [
{
name: 'Travels',
Icon: IconLuggage,
type: 'custom',
fields: 23,
instances: 165,
},
{
name: 'Flights',
Icon: IconPlane,
type: 'custom',
fields: 23,
instances: 165,
},
];
export const activeFieldItems: ObjectFieldItem[] = [
{
name: 'People',
Icon: IconUser,
type: 'standard',
dataType: 'relation',
},
{
name: 'URL',
Icon: IconLink,
type: 'standard',
dataType: 'text',
},
{
name: 'Linkedin',
Icon: IconBrandLinkedin,
type: 'standard',
dataType: 'social',
},
{
name: 'Account Owner',
Icon: IconUserCircle,
type: 'standard',
dataType: 'teammate',
},
{
name: 'Employees',
Icon: IconUsers,
type: 'custom',
dataType: 'number',
},
];
export const disabledFieldItems: ObjectFieldItem[] = [
{
name: 'ICP',
Icon: IconTarget,
type: 'standard',
dataType: 'boolean',
},
{
name: 'Twitter',
Icon: IconBrandTwitter,
type: 'standard',
dataType: 'social',
},
{
name: 'Annual revenue',
Icon: IconCurrencyDollar,
type: 'standard',
dataType: 'number',
},
{
name: 'Is public',
Icon: IconGraph,
type: 'standard',
dataType: 'boolean',
},
{
name: 'Free tier?',
Icon: IconFreeRights,
type: 'custom',
dataType: 'boolean',
},
{
name: 'Priority support',
Icon: IconHeadphones,
type: 'custom',
dataType: 'boolean',
},
];

View File

@ -0,0 +1,8 @@
import { IconComponent } from '@/ui/icon/types/IconComponent';
export type ObjectFieldItem = {
name: string;
Icon: IconComponent;
type: 'standard' | 'custom';
dataType: 'boolean' | 'number' | 'relation' | 'social' | 'teammate' | 'text';
};

View File

@ -40,7 +40,10 @@ export {
IconFileImport,
IconFileUpload,
IconForbid,
IconFreeRights,
IconGraph,
IconGripVertical,
IconHeadphones,
IconHeart,
IconHeartOff,
IconHelpCircle,
@ -60,13 +63,16 @@ export {
IconMinus,
IconMoneybag,
IconNotes,
IconNumbers,
IconPencil,
IconPhone,
IconPlane,
IconPlug,
IconPlus,
IconProgressCheck,
IconSearch,
IconSettings,
IconSocial,
IconTag,
IconTarget,
IconTargetArrow,

View File

@ -8,6 +8,7 @@ const StyledPanel = styled.div`
display: flex;
flex-direction: row;
height: 100%;
overflow: auto;
width: 100%;
`;

View File

@ -6,6 +6,7 @@ import { IconChevronDown, IconChevronUp } from '@/ui/icon';
type TableSectionProps = {
children: ReactNode;
isInitiallyExpanded?: boolean;
title: string;
};
@ -38,9 +39,13 @@ const StyledSectionContent = styled.div`
padding: ${({ theme }) => theme.spacing(2)} 0;
`;
export const TableSection = ({ children, title }: TableSectionProps) => {
export const TableSection = ({
children,
isInitiallyExpanded = true,
title,
}: TableSectionProps) => {
const theme = useTheme();
const [isExpanded, setIsExpanded] = useState(true);
const [isExpanded, setIsExpanded] = useState(isInitiallyExpanded);
const handleToggleSection = () =>
setIsExpanded((previousIsExpanded) => !previousIsExpanded);

View File

@ -1,12 +1,12 @@
import styled from '@emotion/styled';
import { objectSettingsWidth } from '@/settings/objects/constants/objectSettings';
import { Breadcrumb } from '@/ui/breadcrumb/components/Breadcrumb';
import { IconSettings } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { objectSettingsWidth } from './constants/objectSettings';
const StyledContainer = styled.div`
height: fit-content;
padding: ${({ theme }) => theme.spacing(8)};
width: ${objectSettingsWidth};
`;

View File

@ -2,19 +2,43 @@ import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import {
ObjectFieldItemTableRow,
StyledObjectFieldTableRow,
} from '@/settings/objects/components/ObjectFieldItemTableRow';
import {
activeFieldItems,
activeObjectItems,
disabledFieldItems,
} from '@/settings/objects/constants/mockObjects';
import { objectSettingsWidth } from '@/settings/objects/constants/objectSettings';
import { AppPath } from '@/types/AppPath';
import { Breadcrumb } from '@/ui/breadcrumb/components/Breadcrumb';
import { IconSettings } from '@/ui/icon';
import { Button } from '@/ui/button/components/Button';
import { IconPlus, IconSettings } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { activeObjectItems } from './constants/mockObjects';
import { objectSettingsWidth } from './constants/objectSettings';
import { Table } from '@/ui/table/components/Table';
import { TableHeader } from '@/ui/table/components/TableHeader';
import { TableSection } from '@/ui/table/components/TableSection';
import { H2Title } from '@/ui/typography/components/H2Title';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: fit-content;
padding: ${({ theme }) => theme.spacing(8)};
width: ${objectSettingsWidth};
`;
const StyledBreadcrumb = styled(Breadcrumb)`
margin-bottom: ${({ theme }) => theme.spacing(8)};
`;
const StyledAddFieldButton = styled(Button)`
align-self: flex-end;
margin-top: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsObjectDetail = () => {
const navigate = useNavigate();
const { pluralObjectName = '' } = useParams();
@ -29,12 +53,48 @@ export const SettingsObjectDetail = () => {
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<StyledContainer>
<Breadcrumb
<StyledBreadcrumb
links={[
{ children: 'Objects', href: '/settings/objects' },
{ children: activeObject?.name ?? '' },
]}
/>
<H2Title
title="Fields"
description={`Customise the fields available in the ${activeObject?.singularName} views and their display order in the ${activeObject?.singularName} detail view and menus.`}
/>
<Table>
<StyledObjectFieldTableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Field type</TableHeader>
<TableHeader>Data type</TableHeader>
<TableHeader></TableHeader>
</StyledObjectFieldTableRow>
<TableSection title="Active">
{activeFieldItems.map((fieldItem) => (
<ObjectFieldItemTableRow
key={fieldItem.name}
fieldItem={fieldItem}
/>
))}
</TableSection>
{!!disabledFieldItems.length && (
<TableSection isInitiallyExpanded={false} title="Disabled">
{disabledFieldItems.map((fieldItem) => (
<ObjectFieldItemTableRow
key={fieldItem.name}
fieldItem={fieldItem}
/>
))}
</TableSection>
)}
</Table>
<StyledAddFieldButton
Icon={IconPlus}
title="Add Field"
size="small"
variant="secondary"
/>
</StyledContainer>
</SubMenuTopBarContainer>
);

View File

@ -2,15 +2,15 @@ import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { activeObjectItems } from '@/settings/objects/constants/mockObjects';
import { objectSettingsWidth } from '@/settings/objects/constants/objectSettings';
import { AppPath } from '@/types/AppPath';
import { Breadcrumb } from '@/ui/breadcrumb/components/Breadcrumb';
import { IconSettings } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { activeObjectItems } from './constants/mockObjects';
import { objectSettingsWidth } from './constants/objectSettings';
const StyledContainer = styled.div`
height: fit-content;
padding: ${({ theme }) => theme.spacing(8)};
width: ${objectSettingsWidth};
`;

View File

@ -2,6 +2,11 @@ import { useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
activeObjectItems,
disabledObjectItems,
} from '@/settings/objects/constants/mockObjects';
import { objectSettingsWidth } from '@/settings/objects/constants/objectSettings';
import { Button } from '@/ui/button/components/Button';
import {
IconChevronRight,
@ -19,13 +24,8 @@ import { Tag } from '@/ui/tag/components/Tag';
import { H1Title } from '@/ui/typography/components/H1Title';
import { H2Title } from '@/ui/typography/components/H2Title';
import {
activeObjectItems,
disabledObjectItems,
} from './constants/mockObjects';
import { objectSettingsWidth } from './constants/objectSettings';
const StyledContainer = styled.div`
height: fit-content;
padding: ${({ theme }) => theme.spacing(8)};
width: ${objectSettingsWidth};
`;

View File

@ -20,6 +20,7 @@ import {
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: fit-content;
padding: ${({ theme }) => theme.spacing(8)};
width: 350px;
`;

View File

@ -1,40 +0,0 @@
import {
IconBuildingSkyscraper,
IconLuggage,
IconPlane,
IconUser,
} from '@/ui/icon';
export const activeObjectItems = [
{
name: 'Companies',
Icon: IconBuildingSkyscraper,
type: 'standard',
fields: 23,
instances: 165,
},
{
name: 'People',
Icon: IconUser,
type: 'standard',
fields: 16,
instances: 462,
},
];
export const disabledObjectItems = [
{
name: 'Travels',
Icon: IconLuggage,
type: 'custom',
fields: 23,
instances: 165,
},
{
name: 'Flights',
Icon: IconPlane,
type: 'custom',
fields: 23,
instances: 165,
},
];