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:
@ -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(() => {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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());
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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' }}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 }),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -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);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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) &&
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
13
packages/twenty-front/src/utils/array/groupArrayItemsBy.ts
Normal file
13
packages/twenty-front/src/utils/array/groupArrayItemsBy.ts
Normal 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],
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export const mapArrayToObject = <ArrayItem>(
|
||||||
|
array: ArrayItem[],
|
||||||
|
computeItemKey: (item: ArrayItem) => string,
|
||||||
|
) => Object.fromEntries(array.map((item) => [computeItemKey(item), item]));
|
||||||
14
packages/twenty-front/src/utils/array/moveArrayItem.ts
Normal file
14
packages/twenty-front/src/utils/array/moveArrayItem.ts
Normal 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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user