diff --git a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx index aecd55dd7..726cc0cb0 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx @@ -1,5 +1,5 @@ import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips'; -import { IconArrowUpRight } from '@/ui/icon'; +import { IconArrowUpRight, IconPencil } from '@/ui/icon'; import { InlineCellContainer } from '@/ui/inline-cell/components/InlineCellContainer'; import { FieldRecoilScopeContext } from '@/ui/inline-cell/states/recoil-scope-contexts/FieldRecoilScopeContext'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; @@ -24,7 +24,7 @@ export const ActivityRelationEditableField = ({ activity }: OwnProps) => { { Icon: viewField.Icon, type: viewField.type, metadata: viewField.metadata, - useEditButton: viewField.useEditButton, + buttonIcon: viewField.buttonIcon, }, useUpdateEntityMutation: useUpdateOnePipelineProgressMutation, hotkeyScope: InlineCellHotkeyScope.InlineCell, diff --git a/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx b/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx index c4b9077a1..7e9a583ac 100644 --- a/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx +++ b/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx @@ -10,6 +10,7 @@ import { FieldURLMetadata, } from '@/ui/field/types/FieldMetadata'; import { + IconArrowUpRight, IconBrandLinkedin, IconBrandX, IconBuildingSkyscraper, @@ -17,6 +18,7 @@ import { IconLink, IconMap, IconMoneybag, + IconPencil, IconTarget, IconUserCircle, IconUsers, @@ -40,6 +42,8 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, { key: 'domainName', @@ -53,7 +57,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, { key: 'accountOwner', @@ -106,7 +110,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, { key: 'address', @@ -157,6 +161,6 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, ]; diff --git a/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx b/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx index d72c6a9a7..af98d628a 100644 --- a/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx +++ b/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx @@ -9,6 +9,7 @@ import { FieldURLMetadata, } from '@/ui/field/types/FieldMetadata'; import { + IconArrowUpRight, IconBrandLinkedin, IconBrandX, IconBriefcase, @@ -16,6 +17,7 @@ import { IconCalendarEvent, IconMail, IconMap, + IconPencil, IconPhone, IconUser, } from '@/ui/icon/index'; @@ -39,6 +41,8 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition[] avatarUrlFieldName: 'avatarUrl', entityType: Entity.Person, }, + buttonIcon: IconArrowUpRight, + basePathToShowPage: '/person/', } satisfies ColumnDefinition, { key: 'email', @@ -51,7 +55,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition[] fieldName: 'email', placeHolder: 'Ema​il', // Hack: Fake character to prevent password-manager from filling the field }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies ColumnDefinition, { key: 'company', @@ -76,7 +80,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition[] fieldName: 'phone', placeHolder: 'Phon​e', // Hack: Fake character to prevent password-manager from filling the field }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies ColumnDefinition, { key: 'createdAt', @@ -124,7 +128,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition[] fieldName: 'linkedinUrl', placeHolder: 'LinkedIn', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies ColumnDefinition, { key: 'x', @@ -137,6 +141,6 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition[] fieldName: 'xUrl', placeHolder: 'X', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies ColumnDefinition, ]; diff --git a/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx b/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx index 0ef1f1aa4..cce4c5fd3 100644 --- a/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx +++ b/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx @@ -9,6 +9,7 @@ import { import { IconCalendarEvent, IconCurrencyDollar, + IconPencil, IconProgressCheck, IconUser, } from '@/ui/icon'; @@ -62,6 +63,6 @@ export const pipelineAvailableFieldDefinitions: BoardFieldDefinition, ]; diff --git a/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts b/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts index d89f04b1a..6975bd6d8 100644 --- a/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts +++ b/front/src/modules/ui/field/states/selectors/isEntityFieldEmptyFamilySelector.ts @@ -2,7 +2,9 @@ import { selectorFamily } from 'recoil'; import { FieldDefinition } from '../../types/FieldDefinition'; import { FieldMetadata } from '../../types/FieldMetadata'; +import { isFieldChip } from '../../types/guards/isFieldChip'; import { isFieldDate } from '../../types/guards/isFieldDate'; +import { isFieldDoubleTextChip } from '../../types/guards/isFieldDoubleTextChip'; import { isFieldEmail } from '../../types/guards/isFieldEmail'; import { isFieldMoney } from '../../types/guards/isFieldMoney'; import { isFieldNumber } from '../../types/guards/isFieldNumber'; @@ -51,6 +53,41 @@ export const isEntityFieldEmptyFamilySelector = selectorFamily({ if (isFieldRelationValue(fieldValue)) { return fieldValue === null || fieldValue === undefined; } + } else if (isFieldChip(fieldDefinition)) { + const contentFieldName = fieldDefinition.metadata.contentFieldName; + + const contentFieldValue = get(entityFieldsFamilyState(entityId))?.[ + contentFieldName + ] as string | null; + + return ( + contentFieldValue === null || + contentFieldValue === undefined || + contentFieldValue === '' + ); + } else if (isFieldDoubleTextChip(fieldDefinition)) { + const firstValueFieldName = + fieldDefinition.metadata.firstValueFieldName; + + const secondValueFieldName = + fieldDefinition.metadata.secondValueFieldName; + + const contentFieldFirstValue = get(entityFieldsFamilyState(entityId))?.[ + firstValueFieldName + ] as string | null; + + const contentFieldSecondValue = get( + entityFieldsFamilyState(entityId), + )?.[secondValueFieldName] as string | null; + + return ( + (contentFieldFirstValue === null || + contentFieldFirstValue === undefined || + contentFieldFirstValue === '') && + (contentFieldSecondValue === null || + contentFieldSecondValue === undefined || + contentFieldSecondValue === '') + ); } return false; diff --git a/front/src/modules/ui/field/types/FieldDefinition.ts b/front/src/modules/ui/field/types/FieldDefinition.ts index e42b7654e..7598a634f 100644 --- a/front/src/modules/ui/field/types/FieldDefinition.ts +++ b/front/src/modules/ui/field/types/FieldDefinition.ts @@ -9,5 +9,6 @@ export type FieldDefinition = { Icon?: IconComponent; type: FieldType; metadata: T; - useEditButton?: boolean; + buttonIcon?: IconComponent; + basePathToShowPage?: string; }; diff --git a/front/src/modules/ui/inline-cell/components/InlineCell.tsx b/front/src/modules/ui/inline-cell/components/InlineCell.tsx index a41f1f598..fbccca4b5 100644 --- a/front/src/modules/ui/inline-cell/components/InlineCell.tsx +++ b/front/src/modules/ui/inline-cell/components/InlineCell.tsx @@ -57,7 +57,7 @@ export const InlineCell = () => { return ( - + )} diff --git a/front/src/modules/ui/inline-cell/components/InlineCellEditButton.tsx b/front/src/modules/ui/inline-cell/components/InlineCellEditButton.tsx index b547a54ad..66c00f3ac 100644 --- a/front/src/modules/ui/inline-cell/components/InlineCellEditButton.tsx +++ b/front/src/modules/ui/inline-cell/components/InlineCellEditButton.tsx @@ -1,9 +1,9 @@ import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; -import { IconPencil } from '@/ui/icon'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; import { useInlineCell } from '../hooks/useInlineCell'; -export const InlineCellEditButton = () => { +export const InlineCellButton = ({ Icon }: { Icon: IconComponent }) => { const { openInlineCell } = useInlineCell(); const handleClick = () => { @@ -14,7 +14,7 @@ export const InlineCellEditButton = () => { ); diff --git a/front/src/modules/ui/table/table-cell/components/TableCell.tsx b/front/src/modules/ui/table/table-cell/components/TableCell.tsx index 580f8b034..8bd9f7595 100644 --- a/front/src/modules/ui/table/table-cell/components/TableCell.tsx +++ b/front/src/modules/ui/table/table-cell/components/TableCell.tsx @@ -68,7 +68,7 @@ export const TableCell = ({ /> } nonEditModeContent={} - useEditButton={fieldDefinition.useEditButton} + buttonIcon={fieldDefinition.buttonIcon} > ); }; diff --git a/front/src/modules/ui/table/table-cell/components/TableCellButton.tsx b/front/src/modules/ui/table/table-cell/components/TableCellButton.tsx new file mode 100644 index 000000000..67783fae1 --- /dev/null +++ b/front/src/modules/ui/table/table-cell/components/TableCellButton.tsx @@ -0,0 +1,26 @@ +import styled from '@emotion/styled'; +import { motion } from 'framer-motion'; + +import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; + +const StyledEditButtonContainer = styled(motion.div)` + position: absolute; + right: 5px; +`; + +type TableCellButtonProps = { + onClick?: () => void; + Icon: IconComponent; +}; + +export const TableCellButton = ({ onClick, Icon }: TableCellButtonProps) => ( + + + +); diff --git a/front/src/modules/ui/table/table-cell/components/TableCellContainer.tsx b/front/src/modules/ui/table/table-cell/components/TableCellContainer.tsx index 2a746ab09..fa7fb1bf7 100644 --- a/front/src/modules/ui/table/table-cell/components/TableCellContainer.tsx +++ b/front/src/modules/ui/table/table-cell/components/TableCellContainer.tsx @@ -1,18 +1,21 @@ -import { ReactElement, useState } from 'react'; +import { ReactElement, useContext, useState } from 'react'; import styled from '@emotion/styled'; +import { useIsFieldEmpty } from '@/ui/field/hooks/useIsFieldEmpty'; import { useIsFieldInputOnly } from '@/ui/field/hooks/useIsFieldInputOnly'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; +import { ColumnIndexContext } from '../../contexts/ColumnIndexContext'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { useCurrentTableCellEditMode } from '../hooks/useCurrentTableCellEditMode'; import { useIsSoftFocusOnCurrentTableCell } from '../hooks/useIsSoftFocusOnCurrentTableCell'; import { useSetSoftFocusOnCurrentTableCell } from '../hooks/useSetSoftFocusOnCurrentTableCell'; import { useTableCell } from '../hooks/useTableCell'; +import { TableCellButton } from './TableCellButton'; import { TableCellDisplayMode } from './TableCellDisplayMode'; -import { TableCellEditButton } from './TableCellEditButton'; import { TableCellEditMode } from './TableCellEditMode'; import { TableCellSoftFocusMode } from './TableCellSoftFocusMode'; @@ -34,7 +37,7 @@ export type EditableCellProps = { editHotkeyScope?: HotkeyScope; transparent?: boolean; maxContentWidth?: number; - useEditButton?: boolean; + buttonIcon?: IconComponent; onSubmit?: () => void; onCancel?: () => void; }; @@ -51,16 +54,17 @@ export const TableCellContainer = ({ editHotkeyScope, transparent = false, maxContentWidth, - useEditButton, + buttonIcon, }: EditableCellProps) => { const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode(); + const [isHovered, setIsHovered] = useState(false); const setSoftFocusOnCurrentTableCell = useSetSoftFocusOnCurrentTableCell(); const { openTableCell } = useTableCell(); - const handlePenClick = () => { + const handleButtonClick = () => { setSoftFocusOnCurrentTableCell(); openTableCell(); }; @@ -75,11 +79,16 @@ export const TableCellContainer = ({ const editModeContentOnly = useIsFieldInputOnly(); - const showEditButton = - useEditButton && + const isFirstColumnCell = useContext(ColumnIndexContext) === 0; + + const isEmpty = useIsFieldEmpty(); + + const showButton = + buttonIcon && isHovered && !isCurrentTableCellInEditMode && - !editModeContentOnly; + !editModeContentOnly && + (!isFirstColumnCell || !isEmpty); const hasSoftFocus = useIsSoftFocusOnCurrentTableCell(); @@ -102,14 +111,18 @@ export const TableCellContainer = ({ ) : hasSoftFocus ? ( <> - {showEditButton && } + {showButton && ( + + )} {editModeContentOnly ? editModeContent : nonEditModeContent} ) : ( <> - {showEditButton && } + {showButton && ( + + )} {editModeContentOnly ? editModeContent : nonEditModeContent} diff --git a/front/src/modules/ui/table/table-cell/components/TableCellEditButton.tsx b/front/src/modules/ui/table/table-cell/components/TableCellEditButton.tsx index 131dc6eb6..67783fae1 100644 --- a/front/src/modules/ui/table/table-cell/components/TableCellEditButton.tsx +++ b/front/src/modules/ui/table/table-cell/components/TableCellEditButton.tsx @@ -2,26 +2,25 @@ import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; -import { IconPencil } from '@/ui/icon'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; const StyledEditButtonContainer = styled(motion.div)` position: absolute; right: 5px; `; -type EditableCellEditButtonProps = { +type TableCellButtonProps = { onClick?: () => void; + Icon: IconComponent; }; -export const TableCellEditButton = ({ - onClick, -}: EditableCellEditButtonProps) => ( +export const TableCellButton = ({ onClick, Icon }: TableCellButtonProps) => ( - + ); diff --git a/front/src/modules/ui/table/table-cell/hooks/useTableCell.ts b/front/src/modules/ui/table/table-cell/hooks/useTableCell.ts index bd5ba081b..ff967106d 100644 --- a/front/src/modules/ui/table/table-cell/hooks/useTableCell.ts +++ b/front/src/modules/ui/table/table-cell/hooks/useTableCell.ts @@ -1,10 +1,14 @@ import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { FieldContext } from '@/ui/field/contexts/FieldContext'; +import { useIsFieldEmpty } from '@/ui/field/hooks/useIsFieldEmpty'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext'; +import { ColumnIndexContext } from '../../contexts/ColumnIndexContext'; import { useCloseCurrentTableCellInEditMode } from '../../hooks/useCloseCurrentTableCellInEditMode'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; @@ -30,7 +34,20 @@ export const useTableCell = () => { setHotkeyScope(TableHotkeyScope.TableSoftFocus); }; + const navigate = useNavigate(); + + const isFirstColumnCell = useContext(ColumnIndexContext) === 0; + + const isEmpty = useIsFieldEmpty(); + + const { entityId, fieldDefinition } = useContext(FieldContext); + const openTableCell = () => { + if (isFirstColumnCell && !isEmpty && fieldDefinition.basePathToShowPage) { + navigate(`${fieldDefinition.basePathToShowPage}${entityId}`); + return; + } + setDragSelectionStartEnabled(false); setCurrentTableCellInEditMode(); diff --git a/front/src/pages/companies/constants/companyShowFieldDefinition.tsx b/front/src/pages/companies/constants/companyShowFieldDefinition.tsx index 52dfd2576..816f79a3a 100644 --- a/front/src/pages/companies/constants/companyShowFieldDefinition.tsx +++ b/front/src/pages/companies/constants/companyShowFieldDefinition.tsx @@ -13,6 +13,7 @@ import { IconCalendar, IconLink, IconMap, + IconPencil, IconTarget, IconUserCircle, IconUsers, @@ -29,7 +30,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ fieldName: 'domainName', placeHolder: 'URL', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies FieldDefinition, { key: 'accountOwner', @@ -79,7 +80,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ fieldName: 'xUrl', placeHolder: 'X', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies FieldDefinition, { key: 'createdAt', diff --git a/front/src/pages/people/constants/personShowFieldDefinition.tsx b/front/src/pages/people/constants/personShowFieldDefinition.tsx index 52b42ce3a..c2bdcb568 100644 --- a/front/src/pages/people/constants/personShowFieldDefinition.tsx +++ b/front/src/pages/people/constants/personShowFieldDefinition.tsx @@ -15,6 +15,7 @@ import { IconCalendar, IconMail, IconMap, + IconPencil, IconPhone, } from '@/ui/icon'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; @@ -35,7 +36,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ name: 'Company', Icon: IconBuildingSkyscraper, type: 'relation', - useEditButton: true, + buttonIcon: IconPencil, metadata: { fieldName: 'company', relationType: Entity.Company, @@ -50,7 +51,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ fieldName: 'phone', placeHolder: 'Phone', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies FieldDefinition, { key: 'jobTitle', @@ -81,7 +82,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ fieldName: 'linkedinUrl', placeHolder: 'Linkedin URL', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies FieldDefinition, { key: 'xUrl', @@ -92,7 +93,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ fieldName: 'xUrl', placeHolder: 'X URL', }, - useEditButton: true, + buttonIcon: IconPencil, } satisfies FieldDefinition, { key: 'createdAt',