From f35ea19f4dffbd0f5f0a57af52d9a617c44b2c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Thu, 19 Oct 2023 16:58:18 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20get=20object=20metadata=20from=20backen?= =?UTF-8?q?d=20in=20Object=20Detail=20and=20New=20Field=E2=80=A6=20(#2122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: get object metadata from backend in Object Detail and New Field - Step 1 Closes #2008 * refactor: add useLazyLoadIcon hook --- .../components/SettingsObjectAboutSection.tsx | 22 ++++---- .../SettingsObjectFieldDataType.tsx | 8 +-- .../SettingsObjectFieldItemTableRow.tsx | 19 ++++--- .../components/SettingsObjectItemTableRow.tsx | 6 +-- .../data-model/types/ObjectFieldDataType.ts | 7 +++ .../data-model/types/ObjectFieldItem.ts | 4 +- .../ui/input/components/IconPicker.tsx | 12 ++--- .../modules/ui/input/hooks/useLazyLoadIcon.ts | 21 ++++++++ .../ui/input/hooks/useLazyLoadIcons.ts | 17 +++++++ .../data-model/SettingsObjectDetail.tsx | 50 +++++++++++-------- .../SettingsObjectNewFieldStep1.tsx | 41 ++++++++------- .../settings/data-model/SettingsObjects.tsx | 12 ----- 12 files changed, 133 insertions(+), 86 deletions(-) create mode 100644 front/src/modules/settings/data-model/types/ObjectFieldDataType.ts create mode 100644 front/src/modules/ui/input/hooks/useLazyLoadIcon.ts create mode 100644 front/src/modules/ui/input/hooks/useLazyLoadIcons.ts diff --git a/front/src/modules/settings/data-model/object-details/components/SettingsObjectAboutSection.tsx b/front/src/modules/settings/data-model/object-details/components/SettingsObjectAboutSection.tsx index 89336cacb..442705acd 100644 --- a/front/src/modules/settings/data-model/object-details/components/SettingsObjectAboutSection.tsx +++ b/front/src/modules/settings/data-model/object-details/components/SettingsObjectAboutSection.tsx @@ -2,17 +2,17 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { IconDotsVertical } from '@/ui/display/icon'; -import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { Tag } from '@/ui/display/tag/components/Tag'; import { H2Title } from '@/ui/display/typography/components/H2Title'; +import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon'; import { Section } from '@/ui/layout/section/components/Section'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; type SettingsAboutSectionProps = { - Icon: IconComponent; + iconKey?: string; + isCustom: boolean; name: string; - type: string; }; const StyledIconTableCell = styled(TableCell)` @@ -45,25 +45,27 @@ const StyledFlexContainer = styled.div` `; export const SettingsAboutSection = ({ - Icon, + iconKey = '', + isCustom, name, - type, }: SettingsAboutSectionProps) => { const theme = useTheme(); + const { Icon } = useLazyLoadIcon(iconKey); + return (
- + - + {!!Icon && } {name} - {type === 'standard' ? ( - - ) : ( + {isCustom ? ( + ) : ( + )} diff --git a/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx b/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx index 618e03e62..75ba783b1 100644 --- a/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx +++ b/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldDataType.tsx @@ -11,9 +11,9 @@ import { } from '@/ui/display/icon'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; -import { ObjectFieldItem } from '../../types/ObjectFieldItem'; +import { ObjectFieldDataType } from '../../types/ObjectFieldDataType'; -const StyledDataType = styled.div<{ value: ObjectFieldItem['dataType'] }>` +const StyledDataType = styled.div<{ value: ObjectFieldDataType }>` align-items: center; border: 1px solid transparent; border-radius: ${({ theme }) => theme.border.radius.sm}; @@ -33,7 +33,7 @@ const StyledDataType = styled.div<{ value: ObjectFieldItem['dataType'] }>` `; const dataTypes: Record< - ObjectFieldItem['dataType'], + ObjectFieldDataType, { label: string; Icon: IconComponent } > = { boolean: { label: 'True/False', Icon: IconCheck }, @@ -45,7 +45,7 @@ const dataTypes: Record< }; type SettingsObjectFieldDataTypeProps = { - value: ObjectFieldItem['dataType']; + value: ObjectFieldDataType; }; export const SettingsObjectFieldDataType = ({ diff --git a/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx b/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx index a63a7275f..f427c4b4c 100644 --- a/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx +++ b/front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx @@ -3,16 +3,18 @@ import styled from '@emotion/styled'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; +import { Field } from '~/generated-metadata/graphql'; -import { ObjectFieldItem } from '../../types/ObjectFieldItem'; +import { ObjectFieldDataType } from '../../types/ObjectFieldDataType'; import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType'; type SettingsObjectFieldItemTableRowProps = { ActionIcon: IconComponent; - fieldItem: ObjectFieldItem; + fieldItem: Field; }; export const StyledObjectFieldTableRow = styled(TableRow)` @@ -34,18 +36,19 @@ export const SettingsObjectFieldItemTableRow = ({ fieldItem, }: SettingsObjectFieldItemTableRowProps) => { const theme = useTheme(); + const { Icon } = useLazyLoadIcon(fieldItem.icon ?? ''); return ( - - {fieldItem.name} + {!!Icon && } + {fieldItem.label} + {fieldItem.isCustom ? 'Custom' : 'Standard'} - {fieldItem.type === 'standard' ? 'Standard' : 'Custom'} - - - + diff --git a/front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx b/front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx index 41f5850c9..9ef64ae51 100644 --- a/front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx +++ b/front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx @@ -4,14 +4,13 @@ import styled from '@emotion/styled'; import { useFindManyObjects } from '@/metadata/hooks/useFindManyObjects'; import { MetadataObject } from '@/metadata/types/MetadataObject'; -import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { Tag } from '@/ui/display/tag/components/Tag'; +import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; type SettingsObjectItemTableRowProps = { action: ReactNode; - Icon?: IconComponent; objectItem: MetadataObject; onClick?: () => void; }; @@ -37,7 +36,6 @@ const StyledActionTableCell = styled(TableCell)` export const SettingsObjectItemTableRow = ({ action, - Icon, objectItem, onClick, }: SettingsObjectItemTableRowProps) => { @@ -47,6 +45,8 @@ export const SettingsObjectItemTableRow = ({ objectNamePlural: objectItem.namePlural, }); + const { Icon } = useLazyLoadIcon(objectItem.icon ?? ''); + return ( diff --git a/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts b/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts new file mode 100644 index 000000000..11f350f01 --- /dev/null +++ b/front/src/modules/settings/data-model/types/ObjectFieldDataType.ts @@ -0,0 +1,7 @@ +export type ObjectFieldDataType = + | 'boolean' + | 'number' + | 'relation' + | 'social' + | 'teammate' + | 'text'; diff --git a/front/src/modules/settings/data-model/types/ObjectFieldItem.ts b/front/src/modules/settings/data-model/types/ObjectFieldItem.ts index 77edcb1b6..3e3b3db3f 100644 --- a/front/src/modules/settings/data-model/types/ObjectFieldItem.ts +++ b/front/src/modules/settings/data-model/types/ObjectFieldItem.ts @@ -1,8 +1,10 @@ import { IconComponent } from '@/ui/display/icon/types/IconComponent'; +import { ObjectFieldDataType } from './ObjectFieldDataType'; + export type ObjectFieldItem = { name: string; Icon: IconComponent; type: 'standard' | 'custom'; - dataType: 'boolean' | 'number' | 'relation' | 'social' | 'teammate' | 'text'; + dataType: ObjectFieldDataType; }; diff --git a/front/src/modules/ui/input/components/IconPicker.tsx b/front/src/modules/ui/input/components/IconPicker.tsx index b29b00e7a..3bb8fcb63 100644 --- a/front/src/modules/ui/input/components/IconPicker.tsx +++ b/front/src/modules/ui/input/components/IconPicker.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import styled from '@emotion/styled'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; @@ -13,6 +13,7 @@ import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { IconButton } from '../button/components/IconButton'; import { LightIconButton } from '../button/components/LightIconButton'; import { IconApps } from '../constants/icons'; +import { useLazyLoadIcons } from '../hooks/useLazyLoadIcons'; import { DropdownMenuSkeletonItem } from '../relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope'; @@ -48,17 +49,10 @@ export const IconPicker = ({ onOpen, }: IconPickerProps) => { const [searchString, setSearchString] = useState(''); - const [isLoading, setIsLoading] = useState(true); - const [icons, setIcons] = useState>({}); const { closeDropdown } = useDropdown({ dropdownScopeId: 'icon-picker' }); - useEffect(() => { - import('../constants/icons').then((lazyLoadedIcons) => { - setIcons(lazyLoadedIcons); - setIsLoading(false); - }); - }, []); + const { icons, isLoadingIcons: isLoading } = useLazyLoadIcons(); const iconKeys = useMemo(() => { const filteredIconKeys = Object.keys(icons).filter( diff --git a/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts b/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts new file mode 100644 index 000000000..79de15fef --- /dev/null +++ b/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +import { IconComponent } from '@/ui/display/icon/types/IconComponent'; + +export const useLazyLoadIcon = (iconKey: string) => { + const [Icon, setIcon] = useState(); + const [isLoadingIcon, setIsLoadingIcon] = useState(true); + + useEffect(() => { + if (!iconKey) return; + + import(`@tabler/icons-react/dist/esm/icons/${iconKey}.js`).then( + (lazyLoadedIcon) => { + setIcon(lazyLoadedIcon.default); + setIsLoadingIcon(false); + }, + ); + }, [iconKey]); + + return { Icon, isLoadingIcon }; +}; diff --git a/front/src/modules/ui/input/hooks/useLazyLoadIcons.ts b/front/src/modules/ui/input/hooks/useLazyLoadIcons.ts new file mode 100644 index 000000000..486bfe106 --- /dev/null +++ b/front/src/modules/ui/input/hooks/useLazyLoadIcons.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react'; + +import { IconComponent } from '@/ui/display/icon/types/IconComponent'; + +export const useLazyLoadIcons = () => { + const [icons, setIcons] = useState>({}); + const [isLoadingIcons, setIsLoadingIcons] = useState(true); + + useEffect(() => { + import('../constants/icons').then((lazyLoadedIcons) => { + setIcons(lazyLoadedIcons); + setIsLoadingIcons(false); + }); + }, []); + + return { icons, isLoadingIcons }; +}; diff --git a/front/src/pages/settings/data-model/SettingsObjectDetail.tsx b/front/src/pages/settings/data-model/SettingsObjectDetail.tsx index 3cc5001a8..cdca7fbd5 100644 --- a/front/src/pages/settings/data-model/SettingsObjectDetail.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectDetail.tsx @@ -2,12 +2,8 @@ import { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import styled from '@emotion/styled'; +import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { - activeFieldItems, - activeObjectItems, - disabledFieldItems, -} from '@/settings/data-model/constants/mockObjects'; import { SettingsAboutSection } from '@/settings/data-model/object-details/components/SettingsObjectAboutSection'; import { SettingsObjectFieldItemTableRow, @@ -34,13 +30,23 @@ export const SettingsObjectDetail = () => { const navigate = useNavigate(); const { pluralObjectName = '' } = useParams(); - const activeObject = activeObjectItems.find( - (activeObject) => activeObject.name.toLowerCase() === pluralObjectName, + const { activeObjects } = useObjectMetadata(); + const activeObject = activeObjects.find( + (activeObject) => activeObject.namePlural === pluralObjectName, ); useEffect(() => { - if (!activeObject) navigate(AppPath.NotFound); - }, [activeObject, navigate]); + if (activeObjects.length && !activeObject) { + navigate(AppPath.NotFound); + } + }, [activeObject, activeObjects.length, navigate]); + + const activeFields = activeObject?.fields.filter( + (fieldItem) => fieldItem.isActive, + ); + const disabledFields = activeObject?.fields.filter( + (fieldItem) => !fieldItem.isActive, + ); return ( @@ -48,20 +54,20 @@ export const SettingsObjectDetail = () => { {activeObject && ( )}
@@ -71,21 +77,21 @@ export const SettingsObjectDetail = () => { - {activeFieldItems.map((fieldItem) => ( + {activeFields?.map((fieldItem) => ( ))} - {!!disabledFieldItems.length && ( + {!!disabledFields?.length && ( - {disabledFieldItems.map((fieldItem) => ( + {disabledFields.map((fieldItem) => ( ))} @@ -99,7 +105,7 @@ export const SettingsObjectDetail = () => { variant="secondary" onClick={() => navigate( - disabledFieldItems.length + disabledFields?.length ? './new-field/step-1' : './new-field/step-2', ) diff --git a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx index 62dcc1d52..ec4b4785e 100644 --- a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1.tsx @@ -2,14 +2,10 @@ import { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import styled from '@emotion/styled'; +import { useObjectMetadata } from '@/metadata/hooks/useObjectMetadata'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { - activeFieldItems, - activeObjectItems, - disabledFieldItems, -} from '@/settings/data-model/constants/mockObjects'; import { SettingsObjectFieldItemTableRow, StyledObjectFieldTableRow, @@ -37,14 +33,25 @@ const StyledAddCustomFieldButton = styled(Button)` export const SettingsObjectNewFieldStep1 = () => { const navigate = useNavigate(); + const { pluralObjectName = '' } = useParams(); - const activeObject = activeObjectItems.find( - (activeObject) => activeObject.name.toLowerCase() === pluralObjectName, + const { activeObjects } = useObjectMetadata(); + const activeObject = activeObjects.find( + (activeObject) => activeObject.namePlural === pluralObjectName, ); useEffect(() => { - if (!activeObject) navigate(AppPath.NotFound); - }, [activeObject, navigate]); + if (activeObjects.length && !activeObject) { + navigate(AppPath.NotFound); + } + }, [activeObject, activeObjects.length, navigate]); + + const activeFields = activeObject?.fields.filter( + (fieldItem) => fieldItem.isActive, + ); + const disabledFields = activeObject?.fields.filter( + (fieldItem) => !fieldItem.isActive, + ); return ( @@ -54,7 +61,7 @@ export const SettingsObjectNewFieldStep1 = () => { links={[ { children: 'Objects', href: '/settings/objects' }, { - children: activeObject?.name ?? '', + children: activeObject?.labelPlural ?? '', href: `/settings/objects/${pluralObjectName}`, }, { children: 'New Field' }, @@ -65,7 +72,7 @@ export const SettingsObjectNewFieldStep1 = () => { onCancel={() => { navigate(`/settings/objects/${pluralObjectName}`); }} - onSave={() => {}} + onSave={() => undefined} /> @@ -81,21 +88,21 @@ export const SettingsObjectNewFieldStep1 = () => { - {activeFieldItems.map((fieldItem) => ( + {activeFields?.map((fieldItem) => ( ))} - {!!disabledFieldItems.length && ( + {!!disabledFields?.length && ( - {disabledFieldItems.map((fieldItem) => ( + {disabledFields.map((fieldItem) => ( ))} diff --git a/front/src/pages/settings/data-model/SettingsObjects.tsx b/front/src/pages/settings/data-model/SettingsObjects.tsx index ecf6978aa..2f79fd4fc 100644 --- a/front/src/pages/settings/data-model/SettingsObjects.tsx +++ b/front/src/pages/settings/data-model/SettingsObjects.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; @@ -13,7 +12,6 @@ import { import { SettingsObjectCoverImage } from '@/settings/data-model/objects/SettingsObjectCoverImage'; import { SettingsObjectDisabledMenuDropDown } from '@/settings/data-model/objects/SettingsObjectDisabledMenuDropDown'; import { IconChevronRight, IconPlus, IconSettings } from '@/ui/display/icon'; -import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { H1Title } from '@/ui/display/typography/components/H1Title'; import { H2Title } from '@/ui/display/typography/components/H2Title'; import { Button } from '@/ui/input/button/components/Button'; @@ -37,14 +35,6 @@ export const SettingsObjects = () => { const { activeObjects, disabledObjects } = useObjectMetadata(); - const [icons, setIcons] = useState>({}); - - useEffect(() => { - import('@/ui/input/constants/icons').then((lazyLoadedIcons) => { - setIcons(lazyLoadedIcons); - }); - }, []); - return ( @@ -76,7 +66,6 @@ export const SettingsObjects = () => { {activeObjects.map((objectItem) => ( { {disabledObjects.map((objectItem) => (