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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import {
FieldMetadataItemAsFieldDefinitionProps, FieldMetadataItemAsFieldDefinitionProps,
formatFieldMetadataItemAsFieldDefinition, formatFieldMetadataItemAsFieldDefinition,
} from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition'; } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@ -15,14 +16,22 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
objectMetadataItem, objectMetadataItem,
showLabel, showLabel,
labelWidth, labelWidth,
}: FieldMetadataItemAsColumnDefinitionProps): ColumnDefinition<FieldMetadata> => ({ }: FieldMetadataItemAsColumnDefinitionProps): ColumnDefinition<FieldMetadata> => {
...formatFieldMetadataItemAsFieldDefinition({ const isLabelIdentifier = isLabelIdentifierField({
field, fieldMetadataItem: field,
objectMetadataItem, objectMetadataItem,
showLabel, });
labelWidth,
}), return {
position, ...formatFieldMetadataItemAsFieldDefinition({
size: 100, field,
isVisible: true, 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'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
export const getLabelIdentifierFieldMetadataItem = ( export const getLabelIdentifierFieldMetadataItem = (
objectMetadataItem: ObjectMetadataItem, objectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'labelIdentifierFieldMetadataId'
>,
): FieldMetadataItem | undefined => ): FieldMetadataItem | undefined =>
objectMetadataItem.fields.find((fieldMetadataItem) => objectMetadataItem.fields.find((fieldMetadataItem) =>
isLabelIdentifierField({ isLabelIdentifierField({

View File

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

View File

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

View File

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

View File

@ -159,10 +159,9 @@ export const RecordIndexOptionsDropdownContent = ({
<ViewFieldsVisibilityDropdownSection <ViewFieldsVisibilityDropdownSection
title="Visible" title="Visible"
fields={visibleRecordFields} fields={visibleRecordFields}
isVisible={true} isDraggable
onVisibilityChange={handleChangeFieldVisibility}
isDraggable={true}
onDragEnd={handleReorderFields} onDragEnd={handleReorderFields}
onVisibilityChange={handleChangeFieldVisibility}
/> />
{hiddenRecordFields.length > 0 && ( {hiddenRecordFields.length > 0 && (
<> <>
@ -170,9 +169,8 @@ export const RecordIndexOptionsDropdownContent = ({
<ViewFieldsVisibilityDropdownSection <ViewFieldsVisibilityDropdownSection
title="Hidden" title="Hidden"
fields={hiddenRecordFields} fields={hiddenRecordFields}
isVisible={false}
onVisibilityChange={handleChangeFieldVisibility}
isDraggable={false} 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 { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState'; import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useViewFields } from '@/views/hooks/internal/useViewFields'; import { useViewFields } from '@/views/hooks/internal/useViewFields';
import { useViews } from '@/views/hooks/internal/useViews'; import { useViews } from '@/views/hooks/internal/useViews';
import { GraphQLView } from '@/views/types/GraphQLView'; 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 = { type useRecordIndexOptionsForBoardParams = {
objectNameSingular: string; objectNameSingular: string;
@ -40,74 +42,82 @@ export const useRecordIndexOptionsForBoard = ({
objectNameSingular, objectNameSingular,
}); });
const { columnDefinitions: availableColumnDefinitions } = const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem); useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
// Todo replace this with label identifier logic const availableColumnDefinitions = useMemo(
const columnDefinitions = availableColumnDefinitions () =>
.filter( columnDefinitions.filter(({ isLabelIdentifier }) => !isLabelIdentifier),
(columnDefinition) => columnDefinition.metadata.fieldName !== 'name', [columnDefinitions],
) );
.filter(filterAvailableTableColumns);
const recordIndexFieldDefinitionsByKey = useMemo(
() =>
mapArrayToObject(
recordIndexFieldDefinitions,
({ fieldMetadataId }) => fieldMetadataId,
),
[recordIndexFieldDefinitions],
);
const visibleBoardFields = useMemo( const visibleBoardFields = useMemo(
() => () =>
columnDefinitions.filter((columnDefinition) => { recordIndexFieldDefinitions
return recordIndexFieldDefinitions.some( .filter((boardField) => boardField.isVisible)
(existingRecordFieldDefinition) => { .sort(
return ( (boardFieldA, boardFieldB) =>
columnDefinition.fieldMetadataId === boardFieldA.position - boardFieldB.position,
existingRecordFieldDefinition.fieldMetadataId && ),
existingRecordFieldDefinition.isVisible [recordIndexFieldDefinitions],
);
},
);
}),
[columnDefinitions, recordIndexFieldDefinitions],
); );
const hiddenBoardFields = useMemo( const hiddenBoardFields = useMemo(
() => () =>
columnDefinitions.filter((columnDefinition) => { availableColumnDefinitions
return !recordIndexFieldDefinitions.some( .filter(
(existingRecordFieldDefinition) => { ({ fieldMetadataId }) =>
return ( !recordIndexFieldDefinitionsByKey[fieldMetadataId]?.isVisible,
columnDefinition.fieldMetadataId === )
existingRecordFieldDefinition.fieldMetadataId && .map((availableColumnDefinition) => {
existingRecordFieldDefinition.isVisible const { fieldMetadataId } = availableColumnDefinition;
); const existingBoardField =
}, recordIndexFieldDefinitionsByKey[fieldMetadataId];
);
}), return {
[columnDefinitions, recordIndexFieldDefinitions], ...(existingBoardField || availableColumnDefinition),
isVisible: false,
};
}),
[availableColumnDefinitions, recordIndexFieldDefinitionsByKey],
); );
const handleReorderBoardFields: OnDragEndResponder = useCallback( const handleReorderBoardFields: OnDragEndResponder = useCallback(
(result) => { (result) => {
if ( if (!result.destination) {
!result.destination ||
result.destination.index === 1 ||
result.source.index === 1
) {
return; return;
} }
const reorderFields = [...recordIndexFieldDefinitions]; const reorderedVisibleBoardFields = moveArrayItem(visibleBoardFields, {
const [removed] = reorderFields.splice(result.source.index - 1, 1); fromIndex: result.source.index - 1,
reorderFields.splice(result.destination.index - 1, 0, removed); toIndex: result.destination.index - 1,
});
const updatedFields = reorderFields.map((field, index) => ({ if (isDeeplyEqual(visibleBoardFields, reorderedVisibleBoardFields))
...field, return;
position: index,
})); const updatedFields = [
...reorderedVisibleBoardFields,
...hiddenBoardFields,
].map((field, index) => ({ ...field, position: index }));
setRecordIndexFieldDefinitions(updatedFields); setRecordIndexFieldDefinitions(updatedFields);
persistViewFields(mapBoardFieldDefinitionsToViewFields(updatedFields)); persistViewFields(mapBoardFieldDefinitionsToViewFields(updatedFields));
}, },
[ [
hiddenBoardFields,
persistViewFields, persistViewFields,
recordIndexFieldDefinitions,
setRecordIndexFieldDefinitions, setRecordIndexFieldDefinitions,
visibleBoardFields,
], ],
); );
@ -120,36 +130,43 @@ export const useRecordIndexOptionsForBoard = ({
'size' | 'position' 'size' | 'position'
>, >,
) => { ) => {
const isNewViewField = !recordIndexFieldDefinitions.some( const isNewViewField = !(
(fieldDefinition) => updatedFieldDefinition.fieldMetadataId in
fieldDefinition.fieldMetadataId === recordIndexFieldDefinitionsByKey
updatedFieldDefinition.fieldMetadataId,
); );
let updatedFieldsDefinitions: ColumnDefinition<FieldMetadata>[]; let updatedFieldsDefinitions: ColumnDefinition<FieldMetadata>[];
if (isNewViewField) { if (isNewViewField) {
const correspondingFieldDefinition = columnDefinitions.find( const correspondingFieldDefinition = availableColumnDefinitions.find(
(availableTableColumn) => (availableColumnDefinition) =>
availableTableColumn.fieldMetadataId === availableColumnDefinition.fieldMetadataId ===
updatedFieldDefinition.fieldMetadataId, updatedFieldDefinition.fieldMetadataId,
); );
if (!correspondingFieldDefinition) return; if (!correspondingFieldDefinition) return;
const lastVisibleBoardField =
visibleBoardFields[visibleBoardFields.length - 1];
updatedFieldsDefinitions = [ updatedFieldsDefinitions = [
...recordIndexFieldDefinitions, ...recordIndexFieldDefinitions,
{ ...correspondingFieldDefinition, isVisible: true }, {
...correspondingFieldDefinition,
position: lastVisibleBoardField.position + 1,
isVisible: true,
},
]; ];
} else { } else {
updatedFieldsDefinitions = recordIndexFieldDefinitions.map( updatedFieldsDefinitions = recordIndexFieldDefinitions.map(
(exitingFieldDefinition) => (existingFieldDefinition) =>
exitingFieldDefinition.fieldMetadataId === existingFieldDefinition.fieldMetadataId ===
updatedFieldDefinition.fieldMetadataId updatedFieldDefinition.fieldMetadataId
? { ? {
...exitingFieldDefinition, ...existingFieldDefinition,
isVisible: !exitingFieldDefinition.isVisible, isVisible: !existingFieldDefinition.isVisible,
} }
: exitingFieldDefinition, : existingFieldDefinition,
); );
} }
@ -160,10 +177,12 @@ export const useRecordIndexOptionsForBoard = ({
); );
}, },
[ [
recordIndexFieldDefinitions, recordIndexFieldDefinitionsByKey,
setRecordIndexFieldDefinitions, setRecordIndexFieldDefinitions,
persistViewFields, 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 { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns'; import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
export const useRecordIndexOptionsForTable = (recordTableId: string) => { export const useRecordIndexOptionsForTable = (recordTableId: string) => {
const { getHiddenTableColumnsSelector, getVisibleTableColumnsSelector } = const { getHiddenTableColumnsSelector, getVisibleTableColumnsSelector } =
@ -26,11 +27,12 @@ export const useRecordIndexOptionsForTable = (recordTableId: string) => {
return; return;
} }
const reorderFields = [...visibleTableColumns]; const reorderedFields = moveArrayItem(visibleTableColumns, {
const [removed] = reorderFields.splice(result.source.index - 1, 1); fromIndex: result.source.index - 1,
reorderFields.splice(result.destination.index - 1, 0, removed); toIndex: result.destination.index - 1,
});
handleColumnReorder(reorderFields); handleColumnReorder(reorderedFields);
}, },
[visibleTableColumns, handleColumnReorder], [visibleTableColumns, handleColumnReorder],
); );

View File

@ -9,9 +9,6 @@ import { RecordTableColumnDropdownMenu } from './RecordTableColumnDropdownMenu';
type ColumnHeadWithDropdownProps = { type ColumnHeadWithDropdownProps = {
column: ColumnDefinition<FieldMetadata>; column: ColumnDefinition<FieldMetadata>;
isFirstColumn: boolean;
isLastColumn: boolean;
primaryColumnKey: string;
}; };
const StyledDropdown = styled(Dropdown)` const StyledDropdown = styled(Dropdown)`
@ -21,22 +18,12 @@ const StyledDropdown = styled(Dropdown)`
export const ColumnHeadWithDropdown = ({ export const ColumnHeadWithDropdown = ({
column, column,
isFirstColumn,
isLastColumn,
primaryColumnKey,
}: ColumnHeadWithDropdownProps) => { }: ColumnHeadWithDropdownProps) => {
return ( return (
<StyledDropdown <StyledDropdown
dropdownId={column.fieldMetadataId + '-header'} dropdownId={column.fieldMetadataId + '-header'}
clickableComponent={<ColumnHead column={column} />} clickableComponent={<ColumnHead column={column} />}
dropdownComponents={ dropdownComponents={<RecordTableColumnDropdownMenu column={column} />}
<RecordTableColumnDropdownMenu
column={column}
isFirstColumn={isFirstColumn}
isLastColumn={isLastColumn}
primaryColumnKey={primaryColumnKey}
/>
}
dropdownOffset={{ x: -1 }} dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }} 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 { 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 { IconArrowLeft, IconArrowRight, IconEyeOff } from '@/ui/display/icon';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -9,17 +12,23 @@ import { ColumnDefinition } from '../types/ColumnDefinition';
export type RecordTableColumnDropdownMenuProps = { export type RecordTableColumnDropdownMenuProps = {
column: ColumnDefinition<FieldMetadata>; column: ColumnDefinition<FieldMetadata>;
isFirstColumn: boolean;
isLastColumn: boolean;
primaryColumnKey: string;
}; };
export const RecordTableColumnDropdownMenu = ({ export const RecordTableColumnDropdownMenu = ({
column, column,
isFirstColumn,
isLastColumn,
primaryColumnKey,
}: RecordTableColumnDropdownMenuProps) => { }: 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 } = const { handleColumnVisibilityChange, handleMoveTableColumn } =
useTableColumns(); useTableColumns();
@ -27,17 +36,17 @@ export const RecordTableColumnDropdownMenu = ({
const handleColumnMoveLeft = () => { const handleColumnMoveLeft = () => {
closeDropdown(); closeDropdown();
if (isFirstColumn) {
return; if (!canMoveLeft) return;
}
handleMoveTableColumn('left', column); handleMoveTableColumn('left', column);
}; };
const handleColumnMoveRight = () => { const handleColumnMoveRight = () => {
closeDropdown(); closeDropdown();
if (isLastColumn) {
return; if (!canMoveRight) return;
}
handleMoveTableColumn('right', column); handleMoveTableColumn('right', column);
}; };
@ -46,18 +55,16 @@ export const RecordTableColumnDropdownMenu = ({
handleColumnVisibilityChange(column); handleColumnVisibilityChange(column);
}; };
return column.fieldMetadataId === primaryColumnKey ? ( return (
<></>
) : (
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
{!isFirstColumn && ( {canMoveLeft && (
<MenuItem <MenuItem
LeftIcon={IconArrowLeft} LeftIcon={IconArrowLeft}
onClick={handleColumnMoveLeft} onClick={handleColumnMoveLeft}
text="Move left" text="Move left"
/> />
)} )}
{!isLastColumn && ( {canMoveRight && (
<MenuItem <MenuItem
LeftIcon={IconArrowRight} LeftIcon={IconArrowRight}
onClick={handleColumnMoveRight} 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 styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; 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 { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns'; import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; 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 { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown'; import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown';
@ -80,20 +82,19 @@ export const RecordTableHeaderCell = ({
column: ColumnDefinition<FieldMetadata>; column: ColumnDefinition<FieldMetadata>;
createRecord: () => void; createRecord: () => void;
}) => { }) => {
const { const { getResizeFieldOffsetState, getTableColumnsState } =
getResizeFieldOffsetState, useRecordTableStates();
getTableColumnsState,
getTableColumnsByKeySelector,
getVisibleTableColumnsSelector,
} = useRecordTableStates();
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState( const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
getResizeFieldOffsetState(), getResizeFieldOffsetState(),
); );
const tableColumns = useRecoilValue(getTableColumnsState()); const tableColumns = useRecoilValue(getTableColumnsState());
const tableColumnsByKey = useRecoilValue(getTableColumnsByKeySelector()); const tableColumnsByKey = useMemo(
const visibleTableColumns = useRecoilValue(getVisibleTableColumnsSelector()); () =>
mapArrayToObject(tableColumns, ({ fieldMetadataId }) => fieldMetadataId),
[tableColumns],
);
const [initialPointerPositionX, setInitialPointerPositionX] = useState< const [initialPointerPositionX, setInitialPointerPositionX] = useState<
number | null number | null
@ -108,10 +109,6 @@ export const RecordTableHeaderCell = ({
const [iconVisibility, setIconVisibility] = useState(false); const [iconVisibility, setIconVisibility] = useState(false);
const primaryColumn = visibleTableColumns.find(
(column) => column.position === 0,
);
const handleResizeHandlerMove = useCallback( const handleResizeHandlerMove = useCallback(
(positionX: number) => { (positionX: number) => {
if (!initialPointerPositionX) return; if (!initialPointerPositionX) return;
@ -182,13 +179,12 @@ export const RecordTableHeaderCell = ({
onMouseEnter={() => setIconVisibility(true)} onMouseEnter={() => setIconVisibility(true)}
onMouseLeave={() => setIconVisibility(false)} onMouseLeave={() => setIconVisibility(false)}
> >
<ColumnHeadWithDropdown {column.isLabelIdentifier ? (
column={column} <ColumnHead column={column} />
isFirstColumn={column.position === 1} ) : (
isLastColumn={column.position === visibleTableColumns.length - 1} <ColumnHeadWithDropdown column={column} />
primaryColumnKey={primaryColumn?.fieldMetadataId || ''} )}
/> {iconVisibility && !!column.isLabelIdentifier && (
{iconVisibility && column.position === 0 && (
<StyledHeaderIcon> <StyledHeaderIcon>
<LightIconButton <LightIconButton
Icon={IconPlus} Icon={IconPlus}

View File

@ -55,23 +55,21 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
<StyledTd> <StyledTd>
<CheckboxCell /> <CheckboxCell />
</StyledTd> </StyledTd>
{[...visibleTableColumns] {visibleTableColumns.map((column, columnIndex) =>
.sort((columnA, columnB) => columnA.position - columnB.position) inView ? (
.map((column, columnIndex) => { <RecordTableCellContext.Provider
return inView ? ( value={{
<RecordTableCellContext.Provider columnDefinition: column,
value={{ columnIndex,
columnDefinition: column, }}
columnIndex, key={column.fieldMetadataId}
}} >
key={column.fieldMetadataId} <RecordTableCellContainer />
> </RecordTableCellContext.Provider>
<RecordTableCellContainer /> ) : (
</RecordTableCellContext.Provider> <td key={column.fieldMetadataId}></td>
) : ( ),
<td key={column.fieldMetadataId}></td> )}
);
})}
<td></td> <td></td>
</tr> </tr>
</RecordTableRowContext.Provider> </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 { hiddenTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/hiddenTableColumnsSelectorScopeMap';
import { numberOfTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/numberOfTableColumnsSelectorScopeMap'; import { numberOfTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/numberOfTableColumnsSelectorScopeMap';
import { selectedRowIdsSelectorScopeMap } from '@/object-record/record-table/states/selectors/selectedRowIdsSelectorScopeMap'; 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 { visibleTableColumnsSelectorScopeMap } from '@/object-record/record-table/states/selectors/visibleTableColumnsSelectorScopeMap';
import { softFocusPositionStateScopeMap } from '@/object-record/record-table/states/softFocusPositionStateScopeMap'; import { softFocusPositionStateScopeMap } from '@/object-record/record-table/states/softFocusPositionStateScopeMap';
import { tableColumnsStateScopeMap } from '@/object-record/record-table/states/tableColumnsStateScopeMap'; import { tableColumnsStateScopeMap } from '@/object-record/record-table/states/tableColumnsStateScopeMap';
@ -106,10 +105,6 @@ export const useRecordTableStates = (recordTableId?: string) => {
selectedRowIdsSelectorScopeMap, selectedRowIdsSelectorScopeMap,
scopeId, scopeId,
), ),
getTableColumnsByKeySelector: getSelectorReadOnly(
tableColumnsByKeySelectorScopeMap,
scopeId,
),
getVisibleTableColumnsSelector: getSelectorReadOnly( getVisibleTableColumnsSelector: getSelectorReadOnly(
visibleTableColumnsSelectorScopeMap, visibleTableColumnsSelectorScopeMap,
scopeId, 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 { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
import { availableTableColumnsStateScopeMap } from '../availableTableColumnsStateScopeMap';
import { tableColumnsStateScopeMap } from '../tableColumnsStateScopeMap';
export const hiddenTableColumnsSelectorScopeMap = export const hiddenTableColumnsSelectorScopeMap =
createSelectorReadOnlyScopeMap({ createSelectorReadOnlyScopeMap({
@ -9,19 +9,30 @@ export const hiddenTableColumnsSelectorScopeMap =
get: get:
({ scopeId }) => ({ scopeId }) =>
({ get }) => { ({ get }) => {
const columns = get(tableColumnsStateScopeMap({ scopeId })); const tableColumns = get(tableColumnsStateScopeMap({ scopeId }));
const columnKeys = columns.map( const availableColumns = get(
availableTableColumnsStateScopeMap({ scopeId }),
);
const tableColumnsByKey = mapArrayToObject(
tableColumns,
({ fieldMetadataId }) => fieldMetadataId, ({ fieldMetadataId }) => fieldMetadataId,
); );
const otherAvailableColumns = get(
availableTableColumnsStateScopeMap({ scopeId }),
).filter(
({ fieldMetadataId }) => !columnKeys.includes(fieldMetadataId),
);
return [ const hiddenColumns = availableColumns
...columns.filter((column) => !column.isVisible), .filter(
...otherAvailableColumns, ({ 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 { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
import { tableColumnsStateScopeMap } from '../tableColumnsStateScopeMap';
export const visibleTableColumnsSelectorScopeMap = export const visibleTableColumnsSelectorScopeMap =
createSelectorReadOnlyScopeMap({ createSelectorReadOnlyScopeMap({
key: 'visibleTableColumnsSelectorScopeMap', key: 'visibleTableColumnsSelectorScopeMap',
@ -10,16 +8,8 @@ export const visibleTableColumnsSelectorScopeMap =
({ scopeId }) => ({ scopeId }) =>
({ get }) => { ({ get }) => {
const columns = get(tableColumnsStateScopeMap({ scopeId })); const columns = get(tableColumnsStateScopeMap({ scopeId }));
const availableColumnKeys = get( return columns
availableTableColumnsStateScopeMap({ scopeId }), .filter((column) => column.isVisible)
).map(({ fieldMetadataId }) => fieldMetadataId); .sort((columnA, columnB) => columnA.position - columnB.position);
return [...columns]
.filter(
(column) =>
column.isVisible &&
availableColumnKeys.includes(column.fieldMetadataId),
)
.sort((a, b) => a.position - b.position);
}, },
}); });

View File

@ -4,6 +4,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & { export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
size: number; size: number;
position: number; position: number;
isLabelIdentifier?: boolean;
isVisible?: boolean; isVisible?: boolean;
viewFieldId?: string; 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 { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList'; import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors'; import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption'; import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
@ -61,10 +62,10 @@ export const SettingsObjectFieldSelectForm = ({
const handleDragEnd = (result: DropResult) => { const handleDragEnd = (result: DropResult) => {
if (!result.destination) return; if (!result.destination) return;
const nextOptions = [...values]; const nextOptions = moveArrayItem(values, {
const [movedOption] = nextOptions.splice(result.source.index, 1); fromIndex: result.source.index,
toIndex: result.destination.index,
nextOptions.splice(result.destination.index, 0, movedOption); });
onChange(nextOptions); onChange(nextOptions);
}; };

View File

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

View File

@ -1,255 +1,259 @@
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition'; 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 { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
export const signInBackgroundMockColumnDefinitions = [ export const signInBackgroundMockColumnDefinitions = (
{ [
position: 0, {
fieldMetadataId: '20202020-5e4e-4007-a630-8a2617914889', position: 0,
label: 'Domain Name', fieldMetadataId: '20202020-5e4e-4007-a630-8a2617914889',
size: 100, label: 'Domain Name',
type: 'TEXT', size: 100,
metadata: { type: 'TEXT',
fieldName: 'domainName', metadata: {
placeHolder: 'Domain Name', fieldName: 'domainName',
relationObjectMetadataNameSingular: '', placeHolder: 'Domain Name',
relationObjectMetadataNamePlural: '', relationObjectMetadataNameSingular: '',
objectMetadataNameSingular: 'company', relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconLink',
isVisible: true,
}, },
iconName: 'IconLink', {
isVisible: true, position: 1,
}, fieldMetadataId: '20202020-7fbd-41ad-b64d-25a15ff62f04',
{ label: 'Employees',
position: 1, size: 100,
fieldMetadataId: '20202020-7fbd-41ad-b64d-25a15ff62f04', type: 'NUMBER',
label: 'Employees', metadata: {
size: 100, fieldName: 'employees',
type: 'NUMBER', placeHolder: 'Employees',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'employees', relationObjectMetadataNamePlural: '',
placeHolder: 'Employees', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconUsers',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconUsers', {
isVisible: true, position: 2,
}, fieldMetadataId: '20202020-6d30-4111-9f40-b4301906fd3c',
{ label: 'Name',
position: 2, size: 100,
fieldMetadataId: '20202020-6d30-4111-9f40-b4301906fd3c', type: 'TEXT',
label: 'Name', metadata: {
size: 100, fieldName: 'name',
type: 'TEXT', placeHolder: 'Name',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'name', relationObjectMetadataNamePlural: '',
placeHolder: 'Name', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconBuildingSkyscraper',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconBuildingSkyscraper', {
isVisible: true, position: 3,
}, fieldMetadataId: '20202020-e7c8-4771-8cc4-ce0e8c36a3c0',
{ label: 'Favorites',
position: 3, size: 100,
fieldMetadataId: '20202020-e7c8-4771-8cc4-ce0e8c36a3c0', type: 'RELATION',
label: 'Favorites', metadata: {
size: 100, fieldName: 'favorites',
type: 'RELATION', placeHolder: 'Favorites',
metadata: { relationType: 'FROM_MANY_OBJECTS',
fieldName: 'favorites', relationObjectMetadataNameSingular: '',
placeHolder: 'Favorites', relationObjectMetadataNamePlural: '',
relationType: 'FROM_MANY_OBJECTS', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconHeart',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconHeart', {
isVisible: true, position: 4,
}, fieldMetadataId: '20202020-ad10-4117-a039-3f04b7a5f939',
{ label: 'Address',
position: 4, size: 100,
fieldMetadataId: '20202020-ad10-4117-a039-3f04b7a5f939', type: 'TEXT',
label: 'Address', metadata: {
size: 100, fieldName: 'address',
type: 'TEXT', placeHolder: 'Address',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'address', relationObjectMetadataNamePlural: '',
placeHolder: 'Address', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconMap',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconMap', {
isVisible: true, position: 5,
}, fieldMetadataId: '20202020-0739-495d-8e70-c0807f6b2268',
{ label: 'Account Owner',
position: 5, size: 100,
fieldMetadataId: '20202020-0739-495d-8e70-c0807f6b2268', type: 'RELATION',
label: 'Account Owner', metadata: {
size: 100, fieldName: 'accountOwner',
type: 'RELATION', placeHolder: 'Account Owner',
metadata: { relationType: 'TO_ONE_OBJECT',
fieldName: 'accountOwner', relationObjectMetadataNameSingular: 'workspaceMember',
placeHolder: 'Account Owner', relationObjectMetadataNamePlural: 'workspaceMembers',
relationType: 'TO_ONE_OBJECT', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: 'workspaceMember', },
relationObjectMetadataNamePlural: 'workspaceMembers', iconName: 'IconUserCircle',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconUserCircle', {
isVisible: true, position: 6,
}, fieldMetadataId: '20202020-68b4-4c8e-af19-738eba2a42a5',
{ label: 'People',
position: 6, size: 100,
fieldMetadataId: '20202020-68b4-4c8e-af19-738eba2a42a5', type: 'RELATION',
label: 'People', metadata: {
size: 100, fieldName: 'people',
type: 'RELATION', placeHolder: 'People',
metadata: { relationType: 'FROM_MANY_OBJECTS',
fieldName: 'people', relationObjectMetadataNameSingular: '',
placeHolder: 'People', relationObjectMetadataNamePlural: '',
relationType: 'FROM_MANY_OBJECTS', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconUsers',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconUsers', {
isVisible: true, position: 7,
}, fieldMetadataId: '20202020-61af-4ffd-b79b-baed6db8ad11',
{ label: 'Attachments',
position: 7, size: 100,
fieldMetadataId: '20202020-61af-4ffd-b79b-baed6db8ad11', type: 'RELATION',
label: 'Attachments', metadata: {
size: 100, fieldName: 'attachments',
type: 'RELATION', placeHolder: 'Attachments',
metadata: { relationType: 'FROM_MANY_OBJECTS',
fieldName: 'attachments', relationObjectMetadataNameSingular: '',
placeHolder: 'Attachments', relationObjectMetadataNamePlural: '',
relationType: 'FROM_MANY_OBJECTS', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconFileImport',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconFileImport', {
isVisible: true, position: 8,
}, fieldMetadataId: '20202020-4dc2-47c9-bb15-6e6f19ba9e46',
{ label: 'Creation date',
position: 8, size: 100,
fieldMetadataId: '20202020-4dc2-47c9-bb15-6e6f19ba9e46', type: 'DATE_TIME',
label: 'Creation date', metadata: {
size: 100, fieldName: 'createdAt',
type: 'DATE_TIME', placeHolder: 'Creation date',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'createdAt', relationObjectMetadataNamePlural: '',
placeHolder: 'Creation date', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconCalendar',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconCalendar', {
isVisible: true, position: 9,
}, fieldMetadataId: '20202020-9e9f-4235-98b2-c76f3e2d281e',
{ label: 'ICP',
position: 9, size: 100,
fieldMetadataId: '20202020-9e9f-4235-98b2-c76f3e2d281e', type: 'BOOLEAN',
label: 'ICP', metadata: {
size: 100, fieldName: 'idealCustomerProfile',
type: 'BOOLEAN', placeHolder: 'ICP',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'idealCustomerProfile', relationObjectMetadataNamePlural: '',
placeHolder: 'ICP', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconTarget',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconTarget', {
isVisible: true, position: 10,
}, fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1',
{ label: 'Linkedin',
position: 10, size: 100,
fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1', type: 'LINK',
label: 'Linkedin', metadata: {
size: 100, fieldName: 'linkedinLink',
type: 'LINK', placeHolder: 'Linkedin',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'linkedinLink', relationObjectMetadataNamePlural: '',
placeHolder: 'Linkedin', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconBrandLinkedin',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconBrandLinkedin', {
isVisible: true, position: 11,
}, fieldMetadataId: '20202020-e3fc-46ff-b552-3e757843f06e',
{ label: 'Opportunities',
position: 11, size: 100,
fieldMetadataId: '20202020-e3fc-46ff-b552-3e757843f06e', type: 'RELATION',
label: 'Opportunities', metadata: {
size: 100, fieldName: 'opportunities',
type: 'RELATION', placeHolder: 'Opportunities',
metadata: { relationType: 'FROM_MANY_OBJECTS',
fieldName: 'opportunities', relationObjectMetadataNameSingular: '',
placeHolder: 'Opportunities', relationObjectMetadataNamePlural: '',
relationType: 'FROM_MANY_OBJECTS', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconTargetArrow',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconTargetArrow', {
isVisible: true, position: 12,
}, fieldMetadataId: '20202020-46e3-479a-b8f4-77137c74daa6',
{ label: 'X',
position: 12, size: 100,
fieldMetadataId: '20202020-46e3-479a-b8f4-77137c74daa6', type: 'LINK',
label: 'X', metadata: {
size: 100, fieldName: 'xLink',
type: 'LINK', placeHolder: 'X',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'xLink', relationObjectMetadataNamePlural: '',
placeHolder: 'X', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconBrandX',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconBrandX', {
isVisible: true, position: 13,
}, fieldMetadataId: '20202020-4a2e-4b41-8562-279963e8947e',
{ label: 'Activities',
position: 13, size: 100,
fieldMetadataId: '20202020-4a2e-4b41-8562-279963e8947e', type: 'RELATION',
label: 'Activities', metadata: {
size: 100, fieldName: 'activityTargets',
type: 'RELATION', placeHolder: 'Activities',
metadata: { relationType: 'FROM_MANY_OBJECTS',
fieldName: 'activityTargets', relationObjectMetadataNameSingular: '',
placeHolder: 'Activities', relationObjectMetadataNamePlural: '',
relationType: 'FROM_MANY_OBJECTS', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconCheckbox',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconCheckbox', {
isVisible: true, position: 14,
}, fieldMetadataId: '20202020-4a5a-466f-92d9-c3870d9502a9',
{ label: 'ARR',
position: 14, size: 100,
fieldMetadataId: '20202020-4a5a-466f-92d9-c3870d9502a9', type: 'CURRENCY',
label: 'ARR', metadata: {
size: 100, fieldName: 'annualRecurringRevenue',
type: 'CURRENCY', placeHolder: 'ARR',
metadata: { relationObjectMetadataNameSingular: '',
fieldName: 'annualRecurringRevenue', relationObjectMetadataNamePlural: '',
placeHolder: 'ARR', objectMetadataNameSingular: 'company',
relationObjectMetadataNameSingular: '', },
relationObjectMetadataNamePlural: '', iconName: 'IconMoneybag',
objectMetadataNameSingular: 'company', isVisible: true,
}, },
iconName: 'IconMoneybag', ] satisfies ColumnDefinition<FieldMetadata>[]
isVisible: true, ).filter(filterAvailableTableColumns);
},
] as ColumnDefinition<any>[];
export const signInBackgroundMockFilterDefinitions = [ 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 { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemDraggable } from '@/ui/navigation/menu-item/components/MenuItemDraggable'; import { MenuItemDraggable } from '@/ui/navigation/menu-item/components/MenuItemDraggable';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
type ViewFieldsVisibilityDropdownSectionProps = { type ViewFieldsVisibilityDropdownSectionProps = {
fields: Omit<ColumnDefinition<FieldMetadata>, 'size'>[]; fields: Omit<ColumnDefinition<FieldMetadata>, 'size'>[];
isDraggable: boolean;
onDragEnd?: OnDragEndResponder;
onVisibilityChange: ( onVisibilityChange: (
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>, field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
) => void; ) => void;
title: string; title: string;
isVisible: boolean;
isDraggable: boolean;
onDragEnd?: OnDragEndResponder;
}; };
export const ViewFieldsVisibilityDropdownSection = ({ export const ViewFieldsVisibilityDropdownSection = ({
fields, fields,
onVisibilityChange,
title,
isVisible,
isDraggable, isDraggable,
onDragEnd, onDragEnd,
onVisibilityChange,
title,
}: ViewFieldsVisibilityDropdownSectionProps) => { }: ViewFieldsVisibilityDropdownSectionProps) => {
const handleOnDrag = (result: DropResult, provided: ResponderProvided) => { const handleOnDrag = (result: DropResult, provided: ResponderProvided) => {
onDragEnd?.(result, provided); onDragEnd?.(result, provided);
@ -47,8 +46,7 @@ export const ViewFieldsVisibilityDropdownSection = ({
const [openToolTipIndex, setOpenToolTipIndex] = useState<number>(); const [openToolTipIndex, setOpenToolTipIndex] = useState<number>();
const handleInfoButtonClick = (index: number) => { const handleInfoButtonClick = (index: number) => {
if (index === openToolTipIndex) setOpenToolTipIndex(undefined); setOpenToolTipIndex(index === openToolTipIndex ? undefined : index);
else setOpenToolTipIndex(index);
}; };
const { getIcon } = useIcons(); const { getIcon } = useIcons();
@ -57,27 +55,23 @@ export const ViewFieldsVisibilityDropdownSection = ({
index: number, index: number,
field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>, field: Omit<ColumnDefinition<FieldMetadata>, 'size' | 'position'>,
) => { ) => {
if (field.infoTooltipContent) { const iconButtons = [
return [ field.infoTooltipContent
{ ? {
Icon: IconInfoCircle, Icon: IconInfoCircle,
onClick: () => handleInfoButtonClick(index), onClick: () => handleInfoButtonClick(index),
isActive: openToolTipIndex === index, isActive: openToolTipIndex === index,
}, }
{ : null,
Icon: field.isVisible ? IconMinus : IconPlus, field.isLabelIdentifier
onClick: () => onVisibilityChange(field), ? null
}, : {
]; Icon: field.isVisible ? IconMinus : IconPlus,
} onClick: () => onVisibilityChange(field),
if (!field.infoTooltipContent) { },
return [ ].filter(isDefined);
{
Icon: isVisible ? IconMinus : IconPlus, return iconButtons.length ? iconButtons : undefined;
onClick: () => onVisibilityChange(field),
},
];
}
}; };
const ref = useRef<HTMLDivElement>(null); 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 ( return (
<div ref={ref}> <div ref={ref}>
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader> <StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
<DropdownMenuItemsContainer> <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 <DraggableList
onDragEnd={handleOnDrag} onDragEnd={handleOnDrag}
draggableItems={ draggableItems={
<> <>
{[...fields] {draggableItems.map((field, index) => {
.sort((a, b) => a.position - b.position) const fieldIndex = index + nonDraggableItems.length;
.map((field, index) => (
return (
<DraggableItem <DraggableItem
key={field.fieldMetadataId} key={field.fieldMetadataId}
draggableId={field.fieldMetadataId} draggableId={field.fieldMetadataId}
index={index + 1} index={fieldIndex + 1}
itemComponent={ itemComponent={
<MenuItemDraggable <MenuItemDraggable
key={field.fieldMetadataId} key={field.fieldMetadataId}
LeftIcon={getIcon(field.iconName)} LeftIcon={getIcon(field.iconName)}
iconButtons={getIconButtons(index + 1, field)} iconButtons={getIconButtons(fieldIndex, field)}
isTooltipOpen={openToolTipIndex === index + 1} isTooltipOpen={openToolTipIndex === fieldIndex}
text={field.label} text={field.label}
className={`${title}-draggable-item-tooltip-anchor-${ className={`${title}-draggable-item-tooltip-anchor-${fieldIndex}`}
index + 1
}`}
/> />
} }
/> />
))} );
})}
</> </>
} }
/> />
) : (
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> </DropdownMenuItemsContainer>
{isDefined(openToolTipIndex) && {isDefined(openToolTipIndex) &&

View File

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

View File

@ -1,33 +1,66 @@
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; 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 { assertNotNull } from '~/utils/assert';
import { ViewField } from '../types/ViewField'; import { ViewField } from '../types/ViewField';
export const mapViewFieldsToColumnDefinitions = ( export const mapViewFieldsToColumnDefinitions = ({
viewFields: ViewField[], columnDefinitions,
fieldsMetadata: ColumnDefinition<FieldMetadata>[], viewFields,
): ColumnDefinition<FieldMetadata>[] => { }: {
return viewFields columnDefinitions: ColumnDefinition<FieldMetadata>[];
.map((viewField) => { viewFields: ViewField[];
const correspondingFieldMetadata = fieldsMetadata.find( }): ColumnDefinition<FieldMetadata>[] => {
({ fieldMetadataId }) => viewField.fieldMetadataId === fieldMetadataId, let labelIdentifierFieldMetadataId = '';
);
return correspondingFieldMetadata const columnDefinitionsByFieldMetadataId = mapArrayToObject(
? { columnDefinitions,
fieldMetadataId: viewField.fieldMetadataId, ({ fieldMetadataId }) => fieldMetadataId,
label: correspondingFieldMetadata.label, );
metadata: correspondingFieldMetadata.metadata,
infoTooltipContent: correspondingFieldMetadata.infoTooltipContent, const columnDefinitionsFromViewFields = viewFields
iconName: correspondingFieldMetadata.iconName, .map((viewField) => {
type: correspondingFieldMetadata.type, const correspondingColumnDefinition =
position: viewField.position, columnDefinitionsByFieldMetadataId[viewField.fieldMetadataId];
size: viewField.size ?? correspondingFieldMetadata.size,
isVisible: viewField.isVisible, if (!correspondingColumnDefinition) return null;
viewFieldId: viewField.id,
} const { isLabelIdentifier } = correspondingColumnDefinition;
: null;
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); .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 { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { IconBuildingSkyscraper } from '@/ui/display/icon'; 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 { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton'; import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { useFindOneRecord } from '../../modules/object-record/hooks/useFindOneRecord';
export const RecordShowPage = () => { export const RecordShowPage = () => {
const { objectNameSingular, objectRecordId } = useParams<{ const { objectNameSingular, objectRecordId } = useParams<{
objectNameSingular: string; objectNameSingular: string;
@ -32,9 +32,8 @@ export const RecordShowPage = () => {
throw new Error(`Record id is not defined`); throw new Error(`Record id is not defined`);
} }
const { objectMetadataItem } = useObjectMetadataItem({ const { labelIdentifierFieldMetadata, objectMetadataItem } =
objectNameSingular, useObjectMetadataItem({ objectNameSingular });
});
const { favorites, createFavorite, deleteFavorite } = useFavorites(); const { favorites, createFavorite, deleteFavorite } = useFavorites();
@ -68,10 +67,15 @@ export const RecordShowPage = () => {
} }
}; };
const labelIdentifierFieldValue =
record?.[labelIdentifierFieldMetadata?.name ?? ''];
const pageName = const pageName =
objectNameSingular === 'person' labelIdentifierFieldMetadata?.type === FieldMetadataType.FullName
? record?.name.firstName + ' ' + record?.name.lastName ? [
: record?.name; labelIdentifierFieldValue?.firstName,
labelIdentifierFieldValue?.lastName,
].join(' ')
: labelIdentifierFieldValue;
return ( return (
<PageContainer> <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;
};