feat: display record identifier field as first column in table (#3788)

* feat: display record identifier field as first column in table

& forbid hiding and moving record identifier column

Closes #3303

* refactor: add availableTableColumnKeysSelectorScopeMap

* feat: show plus icon button for label identifier column and dropdown menu for other columns

* fix: use label identifier field value in RecordShowPage title

* refactor: remove availableColumnKeys selector

* refactor: review - compute label identifier logic in mapViewFieldsToColumnDefinitions + remove selectors

* fix: several fixes

* fix: fix board fields isVisible

* fix: fix board fields reordering

* fix: more board fields fixes

* fix: fix hiddenTableColumnsSelectorScopeMap
This commit is contained in:
Thaïs 2024-02-09 08:36:08 -03:00 committed by GitHub
parent 9299ad1432
commit 201a2c8acc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 636 additions and 580 deletions

View File

@ -9,7 +9,6 @@ import { availableRecordBoardDeprecatedCardFieldsScopedState } from '@/object-re
import { recordBoardCardFieldsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedCardFieldsScopedState';
import { recordBoardFiltersScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedFiltersScopedState';
import { recordBoardSortsScopedState } from '@/object-record/record-board-deprecated/states/recordBoardDeprecatedSortsScopedState';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useSetRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useSetRecoilScopedStateV2';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
@ -61,11 +60,7 @@ export const HooksCompanyBoardEffect = ({
]);
useEffect(() => {
const availableTableColumns = columnDefinitions.filter(
filterAvailableTableColumns,
);
setAvailableBoardCardFields(availableTableColumns);
setAvailableBoardCardFields(columnDefinitions);
}, [columnDefinitions, setAvailableBoardCardFields]);
useEffect(() => {

View File

@ -1,3 +1,5 @@
import { v4 } from 'uuid';
import { BoardFieldDefinition } from '@/object-record/record-board-deprecated/types/BoardFieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ViewField } from '@/views/types/ViewField';
@ -7,7 +9,7 @@ export const mapBoardFieldDefinitionsToViewFields = (
): ViewField[] => {
return fieldsDefinitions.map(
(fieldDefinition): ViewField => ({
id: fieldDefinition.viewFieldId || '',
id: fieldDefinition.viewFieldId || v4(),
fieldMetadataId: fieldDefinition.fieldMetadataId,
size: 0,
position: fieldDefinition.position,

View File

@ -3,6 +3,7 @@ import { useMemo } from 'react';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { Nullable } from '~/types/Nullable';
import { formatFieldMetadataItemAsColumnDefinition } from '../utils/formatFieldMetadataItemAsColumnDefinition';
@ -25,13 +26,15 @@ export const useColumnDefinitionsFromFieldMetadata = (
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = useMemo(
() =>
objectMetadataItem
? activeFieldMetadataItems.map((field, index) =>
formatFieldMetadataItemAsColumnDefinition({
position: index,
field,
objectMetadataItem,
}),
)
? activeFieldMetadataItems
.map((field, index) =>
formatFieldMetadataItemAsColumnDefinition({
position: index,
field,
objectMetadataItem,
}),
)
.filter(filterAvailableTableColumns)
: [],
[activeFieldMetadataItems, objectMetadataItem],
);

View File

@ -2,6 +2,7 @@ import {
FieldMetadataItemAsFieldDefinitionProps,
formatFieldMetadataItemAsFieldDefinition,
} from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@ -15,14 +16,22 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
objectMetadataItem,
showLabel,
labelWidth,
}: FieldMetadataItemAsColumnDefinitionProps): ColumnDefinition<FieldMetadata> => ({
...formatFieldMetadataItemAsFieldDefinition({
field,
}: FieldMetadataItemAsColumnDefinitionProps): ColumnDefinition<FieldMetadata> => {
const isLabelIdentifier = isLabelIdentifierField({
fieldMetadataItem: field,
objectMetadataItem,
showLabel,
labelWidth,
}),
position,
size: 100,
isVisible: true,
});
});
return {
...formatFieldMetadataItemAsFieldDefinition({
field,
objectMetadataItem,
showLabel,
labelWidth,
}),
position: isLabelIdentifier ? 0 : position,
size: 100,
isLabelIdentifier,
isVisible: true,
};
};

View File

@ -3,7 +3,10 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
export const getLabelIdentifierFieldMetadataItem = (
objectMetadataItem: ObjectMetadataItem,
objectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'labelIdentifierFieldMetadataId'
>,
): FieldMetadataItem | undefined =>
objectMetadataItem.fields.find((fieldMetadataItem) =>
isLabelIdentifierField({

View File

@ -26,6 +26,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { useViewBar } from '@/views/hooks/useViewBar';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { useRecordBoardDeprecatedCardFieldsInternal } from '../../hooks/internal/useRecordBoardDeprecatedCardFieldsInternal';
import { BoardColumnDefinition } from '../../types/BoardColumnDefinition';
@ -113,11 +114,12 @@ export const RecordBoardDeprecatedOptionsDropdownContent = ({
return;
}
const reorderFields = [...visibleBoardCardFields];
const [removed] = reorderFields.splice(result.source.index - 1, 1);
reorderFields.splice(result.destination.index - 1, 0, removed);
const reorderedFields = moveArrayItem(visibleBoardCardFields, {
fromIndex: result.source.index - 1,
toIndex: result.destination.index - 1,
});
handleFieldsReorder(reorderFields);
handleFieldsReorder(reorderedFields);
},
[handleFieldsReorder, visibleBoardCardFields],
);
@ -217,10 +219,9 @@ export const RecordBoardDeprecatedOptionsDropdownContent = ({
<ViewFieldsVisibilityDropdownSection
title="Visible"
fields={visibleBoardCardFields}
isVisible={true}
onVisibilityChange={handleFieldVisibilityChange}
isDraggable={true}
isDraggable
onDragEnd={handleReorderField}
onVisibilityChange={handleFieldVisibilityChange}
/>
)}
{hasVisibleFields && hasHiddenFields && <DropdownMenuSeparator />}
@ -228,9 +229,8 @@ export const RecordBoardDeprecatedOptionsDropdownContent = ({
<ViewFieldsVisibilityDropdownSection
title="Hidden"
fields={hiddenBoardCardFields}
isVisible={false}
onVisibilityChange={handleFieldVisibilityChange}
isDraggable={false}
onVisibilityChange={handleFieldVisibilityChange}
/>
)}
</>

View File

@ -74,25 +74,29 @@ export const RecordIndexContainer = ({
const onViewFieldsChange = useRecoilCallback(
({ set, snapshot }) =>
(viewFields: ViewField[]) => {
setTableColumns(
mapViewFieldsToColumnDefinitions(viewFields, columnDefinitions),
const newFieldDefinitions = mapViewFieldsToColumnDefinitions({
viewFields,
columnDefinitions,
});
setTableColumns(newFieldDefinitions);
const newRecordIndexFieldDefinitions = newFieldDefinitions.filter(
(boardField) => !boardField.isLabelIdentifier,
);
const existingRecordIndexFieldDefinitions = snapshot
.getLoadable(recordIndexFieldDefinitionsState)
.getValue();
const newFieldDefinitions = mapViewFieldsToColumnDefinitions(
viewFields,
columnDefinitions,
);
if (
!isDeeplyEqual(
existingRecordIndexFieldDefinitions,
newFieldDefinitions,
newRecordIndexFieldDefinitions,
)
)
set(recordIndexFieldDefinitionsState, newFieldDefinitions);
) {
set(recordIndexFieldDefinitionsState, newRecordIndexFieldDefinitions);
}
},
[columnDefinitions, setTableColumns],
);

View File

@ -5,7 +5,6 @@ import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/u
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useViewBar } from '@/views/hooks/useViewBar';
type RecordIndexTableContainerEffectProps = {
@ -32,7 +31,7 @@ export const RecordIndexTableContainerEffect = ({
objectNameSingular,
});
const { columnDefinitions, filterDefinitions, sortDefinitions } =
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { setEntityCountInCurrentView } = useViewBar({
@ -40,18 +39,8 @@ export const RecordIndexTableContainerEffect = ({
});
useEffect(() => {
const availableTableColumns = columnDefinitions.filter(
filterAvailableTableColumns,
);
setAvailableTableColumns(availableTableColumns);
}, [
columnDefinitions,
objectMetadataItem,
sortDefinitions,
filterDefinitions,
setAvailableTableColumns,
]);
setAvailableTableColumns(columnDefinitions);
}, [columnDefinitions, setAvailableTableColumns]);
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());

View File

@ -159,10 +159,9 @@ export const RecordIndexOptionsDropdownContent = ({
<ViewFieldsVisibilityDropdownSection
title="Visible"
fields={visibleRecordFields}
isVisible={true}
onVisibilityChange={handleChangeFieldVisibility}
isDraggable={true}
isDraggable
onDragEnd={handleReorderFields}
onVisibilityChange={handleChangeFieldVisibility}
/>
{hiddenRecordFields.length > 0 && (
<>
@ -170,9 +169,8 @@ export const RecordIndexOptionsDropdownContent = ({
<ViewFieldsVisibilityDropdownSection
title="Hidden"
fields={hiddenRecordFields}
isVisible={false}
onVisibilityChange={handleChangeFieldVisibility}
isDraggable={false}
onVisibilityChange={handleChangeFieldVisibility}
/>
</>
)}

View File

@ -9,10 +9,12 @@ import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoar
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useViewFields } from '@/views/hooks/internal/useViewFields';
import { useViews } from '@/views/hooks/internal/useViews';
import { GraphQLView } from '@/views/types/GraphQLView';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
type useRecordIndexOptionsForBoardParams = {
objectNameSingular: string;
@ -40,74 +42,82 @@ export const useRecordIndexOptionsForBoard = ({
objectNameSingular,
});
const { columnDefinitions: availableColumnDefinitions } =
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
// Todo replace this with label identifier logic
const columnDefinitions = availableColumnDefinitions
.filter(
(columnDefinition) => columnDefinition.metadata.fieldName !== 'name',
)
.filter(filterAvailableTableColumns);
const availableColumnDefinitions = useMemo(
() =>
columnDefinitions.filter(({ isLabelIdentifier }) => !isLabelIdentifier),
[columnDefinitions],
);
const recordIndexFieldDefinitionsByKey = useMemo(
() =>
mapArrayToObject(
recordIndexFieldDefinitions,
({ fieldMetadataId }) => fieldMetadataId,
),
[recordIndexFieldDefinitions],
);
const visibleBoardFields = useMemo(
() =>
columnDefinitions.filter((columnDefinition) => {
return recordIndexFieldDefinitions.some(
(existingRecordFieldDefinition) => {
return (
columnDefinition.fieldMetadataId ===
existingRecordFieldDefinition.fieldMetadataId &&
existingRecordFieldDefinition.isVisible
);
},
);
}),
[columnDefinitions, recordIndexFieldDefinitions],
recordIndexFieldDefinitions
.filter((boardField) => boardField.isVisible)
.sort(
(boardFieldA, boardFieldB) =>
boardFieldA.position - boardFieldB.position,
),
[recordIndexFieldDefinitions],
);
const hiddenBoardFields = useMemo(
() =>
columnDefinitions.filter((columnDefinition) => {
return !recordIndexFieldDefinitions.some(
(existingRecordFieldDefinition) => {
return (
columnDefinition.fieldMetadataId ===
existingRecordFieldDefinition.fieldMetadataId &&
existingRecordFieldDefinition.isVisible
);
},
);
}),
[columnDefinitions, recordIndexFieldDefinitions],
availableColumnDefinitions
.filter(
({ fieldMetadataId }) =>
!recordIndexFieldDefinitionsByKey[fieldMetadataId]?.isVisible,
)
.map((availableColumnDefinition) => {
const { fieldMetadataId } = availableColumnDefinition;
const existingBoardField =
recordIndexFieldDefinitionsByKey[fieldMetadataId];
return {
...(existingBoardField || availableColumnDefinition),
isVisible: false,
};
}),
[availableColumnDefinitions, recordIndexFieldDefinitionsByKey],
);
const handleReorderBoardFields: OnDragEndResponder = useCallback(
(result) => {
if (
!result.destination ||
result.destination.index === 1 ||
result.source.index === 1
) {
if (!result.destination) {
return;
}
const reorderFields = [...recordIndexFieldDefinitions];
const [removed] = reorderFields.splice(result.source.index - 1, 1);
reorderFields.splice(result.destination.index - 1, 0, removed);
const reorderedVisibleBoardFields = moveArrayItem(visibleBoardFields, {
fromIndex: result.source.index - 1,
toIndex: result.destination.index - 1,
});
const updatedFields = reorderFields.map((field, index) => ({
...field,
position: index,
}));
if (isDeeplyEqual(visibleBoardFields, reorderedVisibleBoardFields))
return;
const updatedFields = [
...reorderedVisibleBoardFields,
...hiddenBoardFields,
].map((field, index) => ({ ...field, position: index }));
setRecordIndexFieldDefinitions(updatedFields);
persistViewFields(mapBoardFieldDefinitionsToViewFields(updatedFields));
},
[
hiddenBoardFields,
persistViewFields,
recordIndexFieldDefinitions,
setRecordIndexFieldDefinitions,
visibleBoardFields,
],
);
@ -120,36 +130,43 @@ export const useRecordIndexOptionsForBoard = ({
'size' | 'position'
>,
) => {
const isNewViewField = !recordIndexFieldDefinitions.some(
(fieldDefinition) =>
fieldDefinition.fieldMetadataId ===
updatedFieldDefinition.fieldMetadataId,
const isNewViewField = !(
updatedFieldDefinition.fieldMetadataId in
recordIndexFieldDefinitionsByKey
);
let updatedFieldsDefinitions: ColumnDefinition<FieldMetadata>[];
if (isNewViewField) {
const correspondingFieldDefinition = columnDefinitions.find(
(availableTableColumn) =>
availableTableColumn.fieldMetadataId ===
const correspondingFieldDefinition = availableColumnDefinitions.find(
(availableColumnDefinition) =>
availableColumnDefinition.fieldMetadataId ===
updatedFieldDefinition.fieldMetadataId,
);
if (!correspondingFieldDefinition) return;
const lastVisibleBoardField =
visibleBoardFields[visibleBoardFields.length - 1];
updatedFieldsDefinitions = [
...recordIndexFieldDefinitions,
{ ...correspondingFieldDefinition, isVisible: true },
{
...correspondingFieldDefinition,
position: lastVisibleBoardField.position + 1,
isVisible: true,
},
];
} else {
updatedFieldsDefinitions = recordIndexFieldDefinitions.map(
(exitingFieldDefinition) =>
exitingFieldDefinition.fieldMetadataId ===
(existingFieldDefinition) =>
existingFieldDefinition.fieldMetadataId ===
updatedFieldDefinition.fieldMetadataId
? {
...exitingFieldDefinition,
isVisible: !exitingFieldDefinition.isVisible,
...existingFieldDefinition,
isVisible: !existingFieldDefinition.isVisible,
}
: exitingFieldDefinition,
: existingFieldDefinition,
);
}
@ -160,10 +177,12 @@ export const useRecordIndexOptionsForBoard = ({
);
},
[
recordIndexFieldDefinitions,
recordIndexFieldDefinitionsByKey,
setRecordIndexFieldDefinitions,
persistViewFields,
columnDefinitions,
availableColumnDefinitions,
visibleBoardFields,
recordIndexFieldDefinitions,
],
);

View File

@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
export const useRecordIndexOptionsForTable = (recordTableId: string) => {
const { getHiddenTableColumnsSelector, getVisibleTableColumnsSelector } =
@ -26,11 +27,12 @@ export const useRecordIndexOptionsForTable = (recordTableId: string) => {
return;
}
const reorderFields = [...visibleTableColumns];
const [removed] = reorderFields.splice(result.source.index - 1, 1);
reorderFields.splice(result.destination.index - 1, 0, removed);
const reorderedFields = moveArrayItem(visibleTableColumns, {
fromIndex: result.source.index - 1,
toIndex: result.destination.index - 1,
});
handleColumnReorder(reorderFields);
handleColumnReorder(reorderedFields);
},
[visibleTableColumns, handleColumnReorder],
);

View File

@ -9,9 +9,6 @@ import { RecordTableColumnDropdownMenu } from './RecordTableColumnDropdownMenu';
type ColumnHeadWithDropdownProps = {
column: ColumnDefinition<FieldMetadata>;
isFirstColumn: boolean;
isLastColumn: boolean;
primaryColumnKey: string;
};
const StyledDropdown = styled(Dropdown)`
@ -21,22 +18,12 @@ const StyledDropdown = styled(Dropdown)`
export const ColumnHeadWithDropdown = ({
column,
isFirstColumn,
isLastColumn,
primaryColumnKey,
}: ColumnHeadWithDropdownProps) => {
return (
<StyledDropdown
dropdownId={column.fieldMetadataId + '-header'}
clickableComponent={<ColumnHead column={column} />}
dropdownComponents={
<RecordTableColumnDropdownMenu
column={column}
isFirstColumn={isFirstColumn}
isLastColumn={isLastColumn}
primaryColumnKey={primaryColumnKey}
/>
}
dropdownComponents={<RecordTableColumnDropdownMenu column={column} />}
dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }}

View File

@ -1,4 +1,7 @@
import { useRecoilValue } from 'recoil';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { IconArrowLeft, IconArrowRight, IconEyeOff } from '@/ui/display/icon';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -9,17 +12,23 @@ import { ColumnDefinition } from '../types/ColumnDefinition';
export type RecordTableColumnDropdownMenuProps = {
column: ColumnDefinition<FieldMetadata>;
isFirstColumn: boolean;
isLastColumn: boolean;
primaryColumnKey: string;
};
export const RecordTableColumnDropdownMenu = ({
column,
isFirstColumn,
isLastColumn,
primaryColumnKey,
}: RecordTableColumnDropdownMenuProps) => {
const { getVisibleTableColumnsSelector } = useRecordTableStates();
const visibleTableColumns = useRecoilValue(getVisibleTableColumnsSelector());
const secondVisibleColumn = visibleTableColumns[1];
const canMoveLeft =
column.fieldMetadataId !== secondVisibleColumn?.fieldMetadataId;
const lastVisibleColumn = visibleTableColumns[visibleTableColumns.length - 1];
const canMoveRight =
column.fieldMetadataId !== lastVisibleColumn?.fieldMetadataId;
const { handleColumnVisibilityChange, handleMoveTableColumn } =
useTableColumns();
@ -27,17 +36,17 @@ export const RecordTableColumnDropdownMenu = ({
const handleColumnMoveLeft = () => {
closeDropdown();
if (isFirstColumn) {
return;
}
if (!canMoveLeft) return;
handleMoveTableColumn('left', column);
};
const handleColumnMoveRight = () => {
closeDropdown();
if (isLastColumn) {
return;
}
if (!canMoveRight) return;
handleMoveTableColumn('right', column);
};
@ -46,18 +55,16 @@ export const RecordTableColumnDropdownMenu = ({
handleColumnVisibilityChange(column);
};
return column.fieldMetadataId === primaryColumnKey ? (
<></>
) : (
return (
<DropdownMenuItemsContainer>
{!isFirstColumn && (
{canMoveLeft && (
<MenuItem
LeftIcon={IconArrowLeft}
onClick={handleColumnMoveLeft}
text="Move left"
/>
)}
{!isLastColumn && (
{canMoveRight && (
<MenuItem
LeftIcon={IconArrowRight}
onClick={handleColumnMoveRight}

View File

@ -1,8 +1,9 @@
import { useCallback, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnHead } from '@/object-record/record-table/components/ColumnHead';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@ -10,6 +11,7 @@ import { IconPlus } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown';
@ -80,20 +82,19 @@ export const RecordTableHeaderCell = ({
column: ColumnDefinition<FieldMetadata>;
createRecord: () => void;
}) => {
const {
getResizeFieldOffsetState,
getTableColumnsState,
getTableColumnsByKeySelector,
getVisibleTableColumnsSelector,
} = useRecordTableStates();
const { getResizeFieldOffsetState, getTableColumnsState } =
useRecordTableStates();
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
getResizeFieldOffsetState(),
);
const tableColumns = useRecoilValue(getTableColumnsState());
const tableColumnsByKey = useRecoilValue(getTableColumnsByKeySelector());
const visibleTableColumns = useRecoilValue(getVisibleTableColumnsSelector());
const tableColumnsByKey = useMemo(
() =>
mapArrayToObject(tableColumns, ({ fieldMetadataId }) => fieldMetadataId),
[tableColumns],
);
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
number | null
@ -108,10 +109,6 @@ export const RecordTableHeaderCell = ({
const [iconVisibility, setIconVisibility] = useState(false);
const primaryColumn = visibleTableColumns.find(
(column) => column.position === 0,
);
const handleResizeHandlerMove = useCallback(
(positionX: number) => {
if (!initialPointerPositionX) return;
@ -182,13 +179,12 @@ export const RecordTableHeaderCell = ({
onMouseEnter={() => setIconVisibility(true)}
onMouseLeave={() => setIconVisibility(false)}
>
<ColumnHeadWithDropdown
column={column}
isFirstColumn={column.position === 1}
isLastColumn={column.position === visibleTableColumns.length - 1}
primaryColumnKey={primaryColumn?.fieldMetadataId || ''}
/>
{iconVisibility && column.position === 0 && (
{column.isLabelIdentifier ? (
<ColumnHead column={column} />
) : (
<ColumnHeadWithDropdown column={column} />
)}
{iconVisibility && !!column.isLabelIdentifier && (
<StyledHeaderIcon>
<LightIconButton
Icon={IconPlus}

View File

@ -55,23 +55,21 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
<StyledTd>
<CheckboxCell />
</StyledTd>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column, columnIndex) => {
return inView ? (
<RecordTableCellContext.Provider
value={{
columnDefinition: column,
columnIndex,
}}
key={column.fieldMetadataId}
>
<RecordTableCellContainer />
</RecordTableCellContext.Provider>
) : (
<td key={column.fieldMetadataId}></td>
);
})}
{visibleTableColumns.map((column, columnIndex) =>
inView ? (
<RecordTableCellContext.Provider
value={{
columnDefinition: column,
columnIndex,
}}
key={column.fieldMetadataId}
>
<RecordTableCellContainer />
</RecordTableCellContext.Provider>
) : (
<td key={column.fieldMetadataId}></td>
),
)}
<td></td>
</tr>
</RecordTableRowContext.Provider>

View File

@ -14,7 +14,6 @@ import { allRowsSelectedStatusSelectorScopeMap } from '@/object-record/record-ta
import { hiddenTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/hiddenTableColumnsSelectorScopeMap';
import { numberOfTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/numberOfTableColumnsSelectorScopeMap';
import { selectedRowIdsSelectorScopeMap } from '@/object-record/record-table/states/selectors/selectedRowIdsSelectorScopeMap';
import { tableColumnsByKeySelectorScopeMap } from '@/object-record/record-table/states/selectors/tableColumnsByKeySelectorScopeMap';
import { visibleTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/visibleTableColumnsSelectorScopeMap';
import { softFocusPositionStateScopeMap } from '@/object-record/record-table/states/softFocusPositionStateScopeMap';
import { tableColumnsStateScopeMap } from '@/object-record/record-table/states/tableColumnsStateScopeMap';
@ -106,10 +105,6 @@ export const useRecordTableStates = (recordTableId?: string) => {
selectedRowIdsSelectorScopeMap,
scopeId,
),
getTableColumnsByKeySelector: getSelectorReadOnly(
tableColumnsByKeySelectorScopeMap,
scopeId,
),
getVisibleTableColumnsSelector: getSelectorReadOnly(
visibleTableColumnsSelectorScopeMap,
scopeId,

View File

@ -1,7 +1,7 @@
import { availableTableColumnsStateScopeMap } from '@/object-record/record-table/states/availableTableColumnsStateScopeMap';
import { tableColumnsStateScopeMap } from '@/object-record/record-table/states/tableColumnsStateScopeMap';
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
import { availableTableColumnsStateScopeMap } from '../availableTableColumnsStateScopeMap';
import { tableColumnsStateScopeMap } from '../tableColumnsStateScopeMap';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
export const hiddenTableColumnsSelectorScopeMap =
createSelectorReadOnlyScopeMap({
@ -9,19 +9,30 @@ export const hiddenTableColumnsSelectorScopeMap =
get:
({ scopeId }) =>
({ get }) => {
const columns = get(tableColumnsStateScopeMap({ scopeId }));
const columnKeys = columns.map(
const tableColumns = get(tableColumnsStateScopeMap({ scopeId }));
const availableColumns = get(
availableTableColumnsStateScopeMap({ scopeId }),
);
const tableColumnsByKey = mapArrayToObject(
tableColumns,
({ fieldMetadataId }) => fieldMetadataId,
);
const otherAvailableColumns = get(
availableTableColumnsStateScopeMap({ scopeId }),
).filter(
({ fieldMetadataId }) => !columnKeys.includes(fieldMetadataId),
);
return [
...columns.filter((column) => !column.isVisible),
...otherAvailableColumns,
];
const hiddenColumns = availableColumns
.filter(
({ fieldMetadataId }) =>
!tableColumnsByKey[fieldMetadataId]?.isVisible,
)
.map((availableColumn) => {
const { fieldMetadataId } = availableColumn;
const existingTableColumn = tableColumnsByKey[fieldMetadataId];
return {
...(existingTableColumn || availableColumn),
isVisible: false,
};
});
return hiddenColumns;
},
});

View File

@ -1,20 +0,0 @@
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
import { tableColumnsStateScopeMap } from '../tableColumnsStateScopeMap';
export const tableColumnsByKeySelectorScopeMap = createSelectorReadOnlyScopeMap(
{
key: 'tableColumnsByKeySelectorScopeMap',
get:
({ scopeId }) =>
({ get }) =>
get(tableColumnsStateScopeMap({ scopeId })).reduce<
Record<string, ColumnDefinition<FieldMetadata>>
>(
(result, column) => ({ ...result, [column.fieldMetadataId]: column }),
{},
),
},
);

View File

@ -1,8 +1,6 @@
import { availableTableColumnsStateScopeMap } from '@/object-record/record-table/states/availableTableColumnsStateScopeMap';
import { tableColumnsStateScopeMap } from '@/object-record/record-table/states/tableColumnsStateScopeMap';
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
import { tableColumnsStateScopeMap } from '../tableColumnsStateScopeMap';
export const visibleTableColumnsSelectorScopeMap =
createSelectorReadOnlyScopeMap({
key: 'visibleTableColumnsSelectorScopeMap',
@ -10,16 +8,8 @@ export const visibleTableColumnsSelectorScopeMap =
({ scopeId }) =>
({ get }) => {
const columns = get(tableColumnsStateScopeMap({ scopeId }));
const availableColumnKeys = get(
availableTableColumnsStateScopeMap({ scopeId }),
).map(({ fieldMetadataId }) => fieldMetadataId);
return [...columns]
.filter(
(column) =>
column.isVisible &&
availableColumnKeys.includes(column.fieldMetadataId),
)
.sort((a, b) => a.position - b.position);
return columns
.filter((column) => column.isVisible)
.sort((columnA, columnB) => columnA.position - columnB.position);
},
});

View File

@ -4,6 +4,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
size: number;
position: number;
isLabelIdentifier?: boolean;
isVisible?: boolean;
viewFieldId?: string;
};

View File

@ -9,6 +9,7 @@ import { CardFooter } from '@/ui/layout/card/components/CardFooter';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
@ -61,10 +62,10 @@ export const SettingsObjectFieldSelectForm = ({
const handleDragEnd = (result: DropResult) => {
if (!result.destination) return;
const nextOptions = [...values];
const [movedOption] = nextOptions.splice(result.source.index, 1);
nextOptions.splice(result.destination.index, 0, movedOption);
const nextOptions = moveArrayItem(values, {
fromIndex: result.source.index,
toIndex: result.destination.index,
});
onChange(nextOptions);
};

View File

@ -4,7 +4,6 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import {
signInBackgroundMockColumnDefinitions,
signInBackgroundMockFilterDefinitions,
@ -57,17 +56,13 @@ export const SignInBackgroundMockContainerEffect = ({
setAvailableFilterDefinitions?.(signInBackgroundMockFilterDefinitions);
setAvailableFieldDefinitions?.(signInBackgroundMockColumnDefinitions);
const availableTableColumns = signInBackgroundMockColumnDefinitions.filter(
filterAvailableTableColumns,
);
setAvailableTableColumns(availableTableColumns);
setAvailableTableColumns(signInBackgroundMockColumnDefinitions);
setTableColumns(
mapViewFieldsToColumnDefinitions(
signInBackgroundMockViewFields,
signInBackgroundMockColumnDefinitions,
),
mapViewFieldsToColumnDefinitions({
viewFields: signInBackgroundMockViewFields,
columnDefinitions: signInBackgroundMockColumnDefinitions,
}),
);
}, [
setViewObjectMetadataId,

View File

@ -1,255 +1,259 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
export const signInBackgroundMockColumnDefinitions = [
{
position: 0,
fieldMetadataId: '20202020-5e4e-4007-a630-8a2617914889',
label: 'Domain Name',
size: 100,
type: 'TEXT',
metadata: {
fieldName: 'domainName',
placeHolder: 'Domain Name',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
export const signInBackgroundMockColumnDefinitions = (
[
{
position: 0,
fieldMetadataId: '20202020-5e4e-4007-a630-8a2617914889',
label: 'Domain Name',
size: 100,
type: 'TEXT',
metadata: {
fieldName: 'domainName',
placeHolder: 'Domain Name',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconLink',
isVisible: true,
},
iconName: 'IconLink',
isVisible: true,
},
{
position: 1,
fieldMetadataId: '20202020-7fbd-41ad-b64d-25a15ff62f04',
label: 'Employees',
size: 100,
type: 'NUMBER',
metadata: {
fieldName: 'employees',
placeHolder: 'Employees',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 1,
fieldMetadataId: '20202020-7fbd-41ad-b64d-25a15ff62f04',
label: 'Employees',
size: 100,
type: 'NUMBER',
metadata: {
fieldName: 'employees',
placeHolder: 'Employees',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconUsers',
isVisible: true,
},
iconName: 'IconUsers',
isVisible: true,
},
{
position: 2,
fieldMetadataId: '20202020-6d30-4111-9f40-b4301906fd3c',
label: 'Name',
size: 100,
type: 'TEXT',
metadata: {
fieldName: 'name',
placeHolder: 'Name',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 2,
fieldMetadataId: '20202020-6d30-4111-9f40-b4301906fd3c',
label: 'Name',
size: 100,
type: 'TEXT',
metadata: {
fieldName: 'name',
placeHolder: 'Name',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconBuildingSkyscraper',
isVisible: true,
},
iconName: 'IconBuildingSkyscraper',
isVisible: true,
},
{
position: 3,
fieldMetadataId: '20202020-e7c8-4771-8cc4-ce0e8c36a3c0',
label: 'Favorites',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'favorites',
placeHolder: 'Favorites',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 3,
fieldMetadataId: '20202020-e7c8-4771-8cc4-ce0e8c36a3c0',
label: 'Favorites',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'favorites',
placeHolder: 'Favorites',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconHeart',
isVisible: true,
},
iconName: 'IconHeart',
isVisible: true,
},
{
position: 4,
fieldMetadataId: '20202020-ad10-4117-a039-3f04b7a5f939',
label: 'Address',
size: 100,
type: 'TEXT',
metadata: {
fieldName: 'address',
placeHolder: 'Address',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 4,
fieldMetadataId: '20202020-ad10-4117-a039-3f04b7a5f939',
label: 'Address',
size: 100,
type: 'TEXT',
metadata: {
fieldName: 'address',
placeHolder: 'Address',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconMap',
isVisible: true,
},
iconName: 'IconMap',
isVisible: true,
},
{
position: 5,
fieldMetadataId: '20202020-0739-495d-8e70-c0807f6b2268',
label: 'Account Owner',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'accountOwner',
placeHolder: 'Account Owner',
relationType: 'TO_ONE_OBJECT',
relationObjectMetadataNameSingular: 'workspaceMember',
relationObjectMetadataNamePlural: 'workspaceMembers',
objectMetadataNameSingular: 'company',
{
position: 5,
fieldMetadataId: '20202020-0739-495d-8e70-c0807f6b2268',
label: 'Account Owner',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'accountOwner',
placeHolder: 'Account Owner',
relationType: 'TO_ONE_OBJECT',
relationObjectMetadataNameSingular: 'workspaceMember',
relationObjectMetadataNamePlural: 'workspaceMembers',
objectMetadataNameSingular: 'company',
},
iconName: 'IconUserCircle',
isVisible: true,
},
iconName: 'IconUserCircle',
isVisible: true,
},
{
position: 6,
fieldMetadataId: '20202020-68b4-4c8e-af19-738eba2a42a5',
label: 'People',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'people',
placeHolder: 'People',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 6,
fieldMetadataId: '20202020-68b4-4c8e-af19-738eba2a42a5',
label: 'People',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'people',
placeHolder: 'People',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconUsers',
isVisible: true,
},
iconName: 'IconUsers',
isVisible: true,
},
{
position: 7,
fieldMetadataId: '20202020-61af-4ffd-b79b-baed6db8ad11',
label: 'Attachments',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'attachments',
placeHolder: 'Attachments',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 7,
fieldMetadataId: '20202020-61af-4ffd-b79b-baed6db8ad11',
label: 'Attachments',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'attachments',
placeHolder: 'Attachments',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconFileImport',
isVisible: true,
},
iconName: 'IconFileImport',
isVisible: true,
},
{
position: 8,
fieldMetadataId: '20202020-4dc2-47c9-bb15-6e6f19ba9e46',
label: 'Creation date',
size: 100,
type: 'DATE_TIME',
metadata: {
fieldName: 'createdAt',
placeHolder: 'Creation date',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 8,
fieldMetadataId: '20202020-4dc2-47c9-bb15-6e6f19ba9e46',
label: 'Creation date',
size: 100,
type: 'DATE_TIME',
metadata: {
fieldName: 'createdAt',
placeHolder: 'Creation date',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconCalendar',
isVisible: true,
},
iconName: 'IconCalendar',
isVisible: true,
},
{
position: 9,
fieldMetadataId: '20202020-9e9f-4235-98b2-c76f3e2d281e',
label: 'ICP',
size: 100,
type: 'BOOLEAN',
metadata: {
fieldName: 'idealCustomerProfile',
placeHolder: 'ICP',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 9,
fieldMetadataId: '20202020-9e9f-4235-98b2-c76f3e2d281e',
label: 'ICP',
size: 100,
type: 'BOOLEAN',
metadata: {
fieldName: 'idealCustomerProfile',
placeHolder: 'ICP',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconTarget',
isVisible: true,
},
iconName: 'IconTarget',
isVisible: true,
},
{
position: 10,
fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1',
label: 'Linkedin',
size: 100,
type: 'LINK',
metadata: {
fieldName: 'linkedinLink',
placeHolder: 'Linkedin',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 10,
fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1',
label: 'Linkedin',
size: 100,
type: 'LINK',
metadata: {
fieldName: 'linkedinLink',
placeHolder: 'Linkedin',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconBrandLinkedin',
isVisible: true,
},
iconName: 'IconBrandLinkedin',
isVisible: true,
},
{
position: 11,
fieldMetadataId: '20202020-e3fc-46ff-b552-3e757843f06e',
label: 'Opportunities',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'opportunities',
placeHolder: 'Opportunities',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 11,
fieldMetadataId: '20202020-e3fc-46ff-b552-3e757843f06e',
label: 'Opportunities',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'opportunities',
placeHolder: 'Opportunities',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconTargetArrow',
isVisible: true,
},
iconName: 'IconTargetArrow',
isVisible: true,
},
{
position: 12,
fieldMetadataId: '20202020-46e3-479a-b8f4-77137c74daa6',
label: 'X',
size: 100,
type: 'LINK',
metadata: {
fieldName: 'xLink',
placeHolder: 'X',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 12,
fieldMetadataId: '20202020-46e3-479a-b8f4-77137c74daa6',
label: 'X',
size: 100,
type: 'LINK',
metadata: {
fieldName: 'xLink',
placeHolder: 'X',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconBrandX',
isVisible: true,
},
iconName: 'IconBrandX',
isVisible: true,
},
{
position: 13,
fieldMetadataId: '20202020-4a2e-4b41-8562-279963e8947e',
label: 'Activities',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'activityTargets',
placeHolder: 'Activities',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 13,
fieldMetadataId: '20202020-4a2e-4b41-8562-279963e8947e',
label: 'Activities',
size: 100,
type: 'RELATION',
metadata: {
fieldName: 'activityTargets',
placeHolder: 'Activities',
relationType: 'FROM_MANY_OBJECTS',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconCheckbox',
isVisible: true,
},
iconName: 'IconCheckbox',
isVisible: true,
},
{
position: 14,
fieldMetadataId: '20202020-4a5a-466f-92d9-c3870d9502a9',
label: 'ARR',
size: 100,
type: 'CURRENCY',
metadata: {
fieldName: 'annualRecurringRevenue',
placeHolder: 'ARR',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
{
position: 14,
fieldMetadataId: '20202020-4a5a-466f-92d9-c3870d9502a9',
label: 'ARR',
size: 100,
type: 'CURRENCY',
metadata: {
fieldName: 'annualRecurringRevenue',
placeHolder: 'ARR',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconMoneybag',
isVisible: true,
},
iconName: 'IconMoneybag',
isVisible: true,
},
] as ColumnDefinition<any>[];
] satisfies ColumnDefinition<FieldMetadata>[]
).filter(filterAvailableTableColumns);
export const signInBackgroundMockFilterDefinitions = [
{

View File

@ -19,26 +19,25 @@ import { StyledDropdownMenuSubheader } from '@/ui/layout/dropdown/components/Sty
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemDraggable } from '@/ui/navigation/menu-item/components/MenuItemDraggable';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
import { isDefined } from '~/utils/isDefined';
type ViewFieldsVisibilityDropdownSectionProps = {
fields: Omit<ColumnDefinition<FieldMetadata>, 'size'>[];
isDraggable: boolean;
onDragEnd?: OnDragEndResponder;
onVisibilityChange: (
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
) => void;
title: string;
isVisible: boolean;
isDraggable: boolean;
onDragEnd?: OnDragEndResponder;
};
export const ViewFieldsVisibilityDropdownSection = ({
fields,
onVisibilityChange,
title,
isVisible,
isDraggable,
onDragEnd,
onVisibilityChange,
title,
}: ViewFieldsVisibilityDropdownSectionProps) => {
const handleOnDrag = (result: DropResult, provided: ResponderProvided) => {
onDragEnd?.(result, provided);
@ -47,8 +46,7 @@ export const ViewFieldsVisibilityDropdownSection = ({
const [openToolTipIndex, setOpenToolTipIndex] = useState<number>();
const handleInfoButtonClick = (index: number) => {
if (index === openToolTipIndex) setOpenToolTipIndex(undefined);
else setOpenToolTipIndex(index);
setOpenToolTipIndex(index === openToolTipIndex ? undefined : index);
};
const { getIcon } = useIcons();
@ -57,27 +55,23 @@ export const ViewFieldsVisibilityDropdownSection = ({
index: number,
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
) => {
if (field.infoTooltipContent) {
return [
{
Icon: IconInfoCircle,
onClick: () => handleInfoButtonClick(index),
isActive: openToolTipIndex === index,
},
{
Icon: field.isVisible ? IconMinus : IconPlus,
onClick: () => onVisibilityChange(field),
},
];
}
if (!field.infoTooltipContent) {
return [
{
Icon: isVisible ? IconMinus : IconPlus,
onClick: () => onVisibilityChange(field),
},
];
}
const iconButtons = [
field.infoTooltipContent
? {
Icon: IconInfoCircle,
onClick: () => handleInfoButtonClick(index),
isActive: openToolTipIndex === index,
}
: null,
field.isLabelIdentifier
? null
: {
Icon: field.isVisible ? IconMinus : IconPlus,
onClick: () => onVisibilityChange(field),
},
].filter(isDefined);
return iconButtons.length ? iconButtons : undefined;
};
const ref = useRef<HTMLDivElement>(null);
@ -89,50 +83,55 @@ export const ViewFieldsVisibilityDropdownSection = ({
},
});
const { nonDraggableItems = [], draggableItems = [] } = isDraggable
? groupArrayItemsBy(fields, ({ isLabelIdentifier }) =>
isLabelIdentifier ? 'nonDraggableItems' : 'draggableItems',
)
: { nonDraggableItems: fields, draggableItems: [] };
return (
<div ref={ref}>
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
<DropdownMenuItemsContainer>
{isDraggable ? (
{nonDraggableItems.map((field, fieldIndex) => (
<MenuItem
key={field.fieldMetadataId}
LeftIcon={getIcon(field.iconName)}
iconButtons={getIconButtons(fieldIndex, field)}
isTooltipOpen={openToolTipIndex === fieldIndex}
text={field.label}
className={`${title}-fixed-item-tooltip-anchor-${fieldIndex}`}
/>
))}
{!!draggableItems.length && (
<DraggableList
onDragEnd={handleOnDrag}
draggableItems={
<>
{[...fields]
.sort((a, b) => a.position - b.position)
.map((field, index) => (
{draggableItems.map((field, index) => {
const fieldIndex = index + nonDraggableItems.length;
return (
<DraggableItem
key={field.fieldMetadataId}
draggableId={field.fieldMetadataId}
index={index + 1}
index={fieldIndex + 1}
itemComponent={
<MenuItemDraggable
key={field.fieldMetadataId}
LeftIcon={getIcon(field.iconName)}
iconButtons={getIconButtons(index + 1, field)}
isTooltipOpen={openToolTipIndex === index + 1}
iconButtons={getIconButtons(fieldIndex, field)}
isTooltipOpen={openToolTipIndex === fieldIndex}
text={field.label}
className={`${title}-draggable-item-tooltip-anchor-${
index + 1
}`}
className={`${title}-draggable-item-tooltip-anchor-${fieldIndex}`}
/>
}
/>
))}
);
})}
</>
}
/>
) : (
fields.map((field, index) => (
<MenuItem
key={field.fieldMetadataId}
LeftIcon={getIcon(field.iconName)}
iconButtons={getIconButtons(index, field)}
isTooltipOpen={openToolTipIndex === index}
text={field.label}
className={`${title}-fixed-item-tooltip-anchor-${index}`}
/>
))
)}
</DropdownMenuItemsContainer>
{isDefined(openToolTipIndex) &&

View File

@ -133,7 +133,7 @@ describe('mapViewFieldsToColumnDefinitions', () => {
},
];
const fieldsMetadata: ColumnDefinition<FieldMetadata>[] = [
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = [
{
fieldMetadataId: '1',
label: 'label 1',
@ -183,10 +183,10 @@ describe('mapViewFieldsToColumnDefinitions', () => {
},
];
const actualColumnDefinitions = mapViewFieldsToColumnDefinitions(
const actualColumnDefinitions = mapViewFieldsToColumnDefinitions({
columnDefinitions,
viewFields,
fieldsMetadata,
);
});
expect(actualColumnDefinitions).toEqual(expectedColumnDefinitions);
});

View File

@ -1,33 +1,66 @@
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { assertNotNull } from '~/utils/assert';
import { ViewField } from '../types/ViewField';
export const mapViewFieldsToColumnDefinitions = (
viewFields: ViewField[],
fieldsMetadata: ColumnDefinition<FieldMetadata>[],
): ColumnDefinition<FieldMetadata>[] => {
return viewFields
.map((viewField) => {
const correspondingFieldMetadata = fieldsMetadata.find(
({ fieldMetadataId }) => viewField.fieldMetadataId === fieldMetadataId,
);
export const mapViewFieldsToColumnDefinitions = ({
columnDefinitions,
viewFields,
}: {
columnDefinitions: ColumnDefinition<FieldMetadata>[];
viewFields: ViewField[];
}): ColumnDefinition<FieldMetadata>[] => {
let labelIdentifierFieldMetadataId = '';
return correspondingFieldMetadata
? {
fieldMetadataId: viewField.fieldMetadataId,
label: correspondingFieldMetadata.label,
metadata: correspondingFieldMetadata.metadata,
infoTooltipContent: correspondingFieldMetadata.infoTooltipContent,
iconName: correspondingFieldMetadata.iconName,
type: correspondingFieldMetadata.type,
position: viewField.position,
size: viewField.size ?? correspondingFieldMetadata.size,
isVisible: viewField.isVisible,
viewFieldId: viewField.id,
}
: null;
const columnDefinitionsByFieldMetadataId = mapArrayToObject(
columnDefinitions,
({ fieldMetadataId }) => fieldMetadataId,
);
const columnDefinitionsFromViewFields = viewFields
.map((viewField) => {
const correspondingColumnDefinition =
columnDefinitionsByFieldMetadataId[viewField.fieldMetadataId];
if (!correspondingColumnDefinition) return null;
const { isLabelIdentifier } = correspondingColumnDefinition;
if (isLabelIdentifier) {
labelIdentifierFieldMetadataId =
correspondingColumnDefinition.fieldMetadataId;
}
return {
fieldMetadataId: viewField.fieldMetadataId,
label: correspondingColumnDefinition.label,
metadata: correspondingColumnDefinition.metadata,
infoTooltipContent: correspondingColumnDefinition.infoTooltipContent,
iconName: correspondingColumnDefinition.iconName,
type: correspondingColumnDefinition.type,
position: isLabelIdentifier ? 0 : viewField.position,
size: viewField.size ?? correspondingColumnDefinition.size,
isLabelIdentifier,
isVisible: isLabelIdentifier || viewField.isVisible,
viewFieldId: viewField.id,
};
})
.filter(assertNotNull);
// No label identifier set for this object
if (!labelIdentifierFieldMetadataId) return columnDefinitionsFromViewFields;
const labelIdentifierIndex = columnDefinitionsFromViewFields.findIndex(
({ fieldMetadataId }) => fieldMetadataId === labelIdentifierFieldMetadataId,
);
// Label identifier field found in view fields
// => move it to the start of the list
return moveArrayItem(columnDefinitionsFromViewFields, {
fromIndex: labelIdentifierIndex,
toIndex: 0,
});
};

View File

@ -4,6 +4,7 @@ import { useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { IconBuildingSkyscraper } from '@/ui/display/icon';
@ -14,10 +15,9 @@ import { PageHeader } from '@/ui/layout/page/PageHeader';
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { useFindOneRecord } from '../../modules/object-record/hooks/useFindOneRecord';
export const RecordShowPage = () => {
const { objectNameSingular, objectRecordId } = useParams<{
objectNameSingular: string;
@ -32,9 +32,8 @@ export const RecordShowPage = () => {
throw new Error(`Record id is not defined`);
}
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { labelIdentifierFieldMetadata, objectMetadataItem } =
useObjectMetadataItem({ objectNameSingular });
const { favorites, createFavorite, deleteFavorite } = useFavorites();
@ -68,10 +67,15 @@ export const RecordShowPage = () => {
}
};
const labelIdentifierFieldValue =
record?.[labelIdentifierFieldMetadata?.name ?? ''];
const pageName =
objectNameSingular === 'person'
? record?.name.firstName + ' ' + record?.name.lastName
: record?.name;
labelIdentifierFieldMetadata?.type === FieldMetadataType.FullName
? [
labelIdentifierFieldValue?.firstName,
labelIdentifierFieldValue?.lastName,
].join(' ')
: labelIdentifierFieldValue;
return (
<PageContainer>

View File

@ -0,0 +1,13 @@
export const groupArrayItemsBy = <Item, Key extends string>(
array: Item[],
computeGroupKey: (item: Item) => Key,
) =>
array.reduce<Partial<Record<Key, Item[]>>>((result, item) => {
const groupKey = computeGroupKey(item);
const previousGroup = result[groupKey] || [];
return {
...result,
[groupKey]: [...previousGroup, item],
};
}, {});

View File

@ -0,0 +1,4 @@
export const mapArrayToObject = <ArrayItem>(
array: ArrayItem[],
computeItemKey: (item: ArrayItem) => string,
) => Object.fromEntries(array.map((item) => [computeItemKey(item), item]));

View File

@ -0,0 +1,14 @@
export const moveArrayItem = <Item>(
array: Item[],
{ fromIndex, toIndex }: { fromIndex: number; toIndex: number },
) => {
if (!(fromIndex in array) || !(toIndex in array) || fromIndex === toIndex) {
return array;
}
const arrayWithMovedItem = [...array];
const [itemToMove] = arrayWithMovedItem.splice(fromIndex, 1);
arrayWithMovedItem.splice(toIndex, 0, itemToMove);
return arrayWithMovedItem;
};