Fix post merge revamp navigation bar (#6297)

Closes #6285 

@charlesBochet Also added some more utils for our component state v2.
This commit is contained in:
Lucas Bordeau 2024-07-19 14:24:47 +02:00 committed by GitHub
parent 67e2d5c73a
commit 1b0759ef2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1197 additions and 230 deletions

View File

@ -7,6 +7,7 @@ import { scrollPositionState } from '@/ui/utilities/scroll/states/scrollPosition
import { isDefined } from '~/utils/isDefined';
/**
* @deprecated We should now use useScrollToPosition instead
* Note that `location.key` is used in the cache key, not `location.pathname`,
* so the same path navigated to at different points in the history stack will
* not share the same scroll position.

View File

@ -0,0 +1,20 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useMemo } from 'react';
export const useActiveFieldMetadataItems = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const activeFieldMetadataItems = useMemo(
() =>
objectMetadataItem
? objectMetadataItem.fields.filter(
({ isActive, isSystem }) => isActive && !isSystem,
)
: [],
[objectMetadataItem],
);
return { activeFieldMetadataItems };
};

View File

@ -209,15 +209,16 @@ export const useFetchMoreRecordsWithPagination = <
const totalCount = data?.[objectMetadataItem.namePlural]?.totalCount;
const recordConnection = data?.[objectMetadataItem.namePlural];
const records = useMemo(
() =>
data?.[objectMetadataItem.namePlural]
isDefined(recordConnection)
? getRecordsFromRecordConnection<T>({
recordConnection: data?.[objectMetadataItem.namePlural],
recordConnection,
})
: ([] as T[]),
[data, objectMetadataItem.namePlural],
: [],
[recordConnection],
);
return {

View File

@ -13,7 +13,7 @@ import {
RecordUpdateHookParams,
} from '@/object-record/record-field/contexts/FieldContext';
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
import { RecordIndexRecordChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
@ -222,7 +222,7 @@ export const RecordBoardCard = () => {
}}
>
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
<RecordIndexRecordChip
<RecordIdentifierChip
objectNameSingular={objectMetadataItem.nameSingular}
record={record}
variant={AvatarChipVariant.Transparent}

View File

@ -4,7 +4,8 @@ import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/dis
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
import { RelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay';
import { isFieldChipDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldChipDisplay';
import { isFieldIdentifierDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldIdentifierDisplay';
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
@ -46,7 +47,10 @@ import { isFieldUuid } from '../types/guards/isFieldUuid';
export const FieldDisplay = () => {
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
const isChipDisplay = isFieldChipDisplay(fieldDefinition, isLabelIdentifier);
const isChipDisplay = isFieldIdentifierDisplay(
fieldDefinition,
isLabelIdentifier,
);
return isChipDisplay ? (
<ChipFieldDisplay />

View File

@ -1,6 +1,6 @@
import { RecordChip } from '@/object-record/components/RecordChip';
import { useChipFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useChipFieldDisplay';
import { RecordIndexRecordChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
export const ChipFieldDisplay = () => {
const { recordValue, objectNameSingular, isLabelIdentifier } =
@ -11,7 +11,7 @@ export const ChipFieldDisplay = () => {
}
return isLabelIdentifier ? (
<RecordIndexRecordChip
<RecordIdentifierChip
objectNameSingular={objectNameSingular}
record={recordValue}
/>

View File

@ -3,7 +3,7 @@ import { isFieldFullName } from '@/object-record/record-field/types/guards/isFie
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
export const isFieldChipDisplay = (
export const isFieldIdentifierDisplay = (
field: Pick<FieldMetadataItem, 'type'>,
isLabelIdentifier: boolean,
) =>

View File

@ -1,17 +1,10 @@
import styled from '@emotion/styled';
import {
useRecoilCallback,
useRecoilState,
useRecoilValue,
useSetRecoilState,
} from 'recoil';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer';
import { RecordIndexBoardDataLoader } from '@/object-record/record-index/components/RecordIndexBoardDataLoader';
import { RecordIndexBoardDataLoaderEffect } from '@/object-record/record-index/components/RecordIndexBoardDataLoaderEffect';
@ -26,22 +19,17 @@ import { recordIndexIsCompactModeActiveState } from '@/object-record/record-inde
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { useFindRecordCursorFromFindManyCacheRootQuery } from '@/object-record/record-show/hooks/useFindRecordCursorFromFindManyCacheRootQuery';
import { findView } from '@/object-record/record-show/hooks/useRecordShowPagePagination';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { ViewBar } from '@/views/components/ViewBar';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { View } from '@/views/types/View';
import { ViewField } from '@/views/types/ViewField';
import { ViewType } from '@/views/types/ViewType';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { useNavigate } from 'react-router-dom';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
const StyledContainer = styled.div`
@ -124,54 +112,11 @@ export const RecordIndexContainer = ({
[columnDefinitions, setTableColumns],
);
const navigate = useNavigate();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const currentViewId = useRecoilValue(
currentViewIdComponentState({
scopeId: recordIndexId,
}),
);
const view = findView({
objectMetadataItemId: objectMetadataItem?.id ?? '',
viewId: currentViewId ?? null,
views,
});
const filter = turnObjectDropdownFilterIntoQueryFilter(
mapViewFiltersToFilters(view?.viewFilters ?? [], filterDefinitions),
objectMetadataItem?.fields ?? [],
);
const orderBy = turnSortsIntoOrderBy(
const { handleIndexIdentifierClick } = useHandleIndexIdentifierClick({
objectMetadataItem,
mapViewSortsToSorts(view?.viewSorts ?? [], sortDefinitions),
);
const { findCursorInCache } = useFindRecordCursorFromFindManyCacheRootQuery({
fieldVariables: {
filter,
orderBy,
},
objectNamePlural: objectNamePlural,
recordIndexId,
});
const handleIndexIdentifierClick = (recordId: string) => {
const cursor = findCursorInCache(recordId);
// TODO: use URL builder
navigate(
`/object/${objectNameSingular}/${recordId}?view=${currentViewId}`,
{
state: {
cursor,
},
},
);
};
const handleIndexRecordsLoaded = useRecoilCallback(
({ set }) =>
() => {

View File

@ -5,17 +5,17 @@ import { RecordIndexEventContext } from '@/object-record/record-index/contexts/R
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useContext } from 'react';
export type RecordIndexRecordChipProps = {
export type RecordIdentifierChipProps = {
objectNameSingular: string;
record: ObjectRecord;
variant?: AvatarChipVariant;
};
export const RecordIndexRecordChip = ({
export const RecordIdentifierChip = ({
objectNameSingular,
record,
variant,
}: RecordIndexRecordChipProps) => {
}: RecordIdentifierChipProps) => {
const { onIndexIdentifierClick } = useContext(RecordIndexEventContext);
const { recordChipData } = useRecordChipData({

View File

@ -0,0 +1,33 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
export const useHandleIndexIdentifierClick = ({
objectMetadataItem,
recordIndexId,
}: {
recordIndexId: string;
objectMetadataItem: ObjectMetadataItem;
}) => {
const navigate = useNavigate();
const currentViewId = useRecoilValue(
currentViewIdComponentState({
scopeId: recordIndexId,
}),
);
const handleIndexIdentifierClick = (recordId: string) => {
const showPageURL = buildShowPageURL(
objectMetadataItem.nameSingular,
recordId,
currentViewId,
);
navigate(showPageURL);
};
return { handleIndexIdentifierClick };
};

View File

@ -1,11 +1,10 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
export const findOneRecordForShowPageOperationSignatureFactory: RecordGqlOperationSignatureFactory =
export const buildFindOneRecordForShowPageOperationSignature: RecordGqlOperationSignatureFactory =
({ objectMetadataItem }: { objectMetadataItem: ObjectMetadataItem }) => ({
objectNameSingular: CoreObjectNameSingular.Activity,
objectNameSingular: objectMetadataItem.nameSingular,
variables: {},
fields: generateDepthOneRecordGqlFields({ objectMetadataItem }),
});

View File

@ -7,7 +7,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { findOneRecordForShowPageOperationSignatureFactory } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
@ -39,7 +39,8 @@ export const useRecordShowPage = (
const { getIcon } = useIcons();
const headerIcon = getIcon(objectMetadataItem?.icon);
const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE =
findOneRecordForShowPageOperationSignatureFactory({ objectMetadataItem });
buildFindOneRecordForShowPageOperationSignature({ objectMetadataItem });
const { record, loading } = useFindOneRecord({
objectRecordId,
objectNameSingular,

View File

@ -1,52 +1,19 @@
/* eslint-disable @nx/workspace-no-navigate-prefer-link */
import { useMemo, useState } from 'react';
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-show/hooks/useRecordIdsFromFindManyCacheRootQuery';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
import { buildIndexTablePageURL } from '@/object-record/record-table/utils/buildIndexTableURL';
import { useQueryVariablesFromActiveFieldsOfViewOrDefaultView } from '@/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView';
import { isNonEmptyString } from '@sniptt/guards';
import { getRelayCursorFromRecordId } from '~/utils/getRelayCursorFromRecordId';
import { capitalize } from '~/utils/string/capitalize';
export const findView = ({
viewId,
objectMetadataItemId,
views,
}: {
viewId: string | null;
objectMetadataItemId: string;
views: View[];
}) => {
if (!viewId) {
return views.find(
(view: any) =>
view.key === 'INDEX' && view?.objectMetadataId === objectMetadataItemId,
) as View;
} else {
return views.find(
(view: any) =>
view?.id === viewId && view?.objectMetadataId === objectMetadataItemId,
) as View;
}
};
export const useRecordShowPagePagination = (
propsObjectNameSingular: string,
propsObjectRecordId: string,
@ -61,8 +28,6 @@ export const useRecordShowPagePagination = (
const setLastShowPageRecordId = useSetRecoilState(lastShowPageRecordIdState);
const [isLoadedRecords] = useState(false);
const objectNameSingular = propsObjectNameSingular || paramObjectNameSingular;
const objectRecordId = propsObjectRecordId || paramObjectRecordId;
@ -71,77 +36,26 @@ export const useRecordShowPagePagination = (
}
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const view = useMemo(() => {
return findView({
objectMetadataItemId: objectMetadataItem?.id ?? '',
viewId: viewIdQueryParam,
views,
});
}, [viewIdQueryParam, objectMetadataItem, views]);
const activeFieldMetadataItems = useMemo(
() =>
objectMetadataItem
? objectMetadataItem.fields.filter(
({ isActive, isSystem }) => isActive && !isSystem,
)
: [],
[objectMetadataItem],
);
const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({
fields: activeFieldMetadataItems,
});
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
fields: activeFieldMetadataItems,
});
const filter = turnObjectDropdownFilterIntoQueryFilter(
mapViewFiltersToFilters(view?.viewFilters ?? [], filterDefinitions),
objectMetadataItem?.fields ?? [],
);
const orderBy = turnSortsIntoOrderBy(
objectMetadataItem,
mapViewSortsToSorts(view?.viewSorts ?? [], sortDefinitions),
);
const recordGqlFields = generateDepthOneRecordGqlFields({
objectMetadataItem,
});
const { state } = useLocation();
const cursorFromIndexPage = state?.cursor;
const { loading: loadingCurrentRecord, pageInfo: currentRecordsPageInfo } =
useFindManyRecords({
filter: {
id: { eq: objectRecordId },
},
orderBy,
skip: isLoadedRecords,
limit: 1,
objectNameSingular,
recordGqlFields,
const { filter, orderBy } =
useQueryVariablesFromActiveFieldsOfViewOrDefaultView({
objectMetadataItem,
viewId: viewIdQueryParam,
});
const currentRecordCursor = currentRecordsPageInfo?.endCursor;
const cursor = cursorFromIndexPage ?? currentRecordCursor;
const cursor = getRelayCursorFromRecordId(objectRecordId);
const {
loading: loadingRecordBefore,
records: recordsBefore,
pageInfo: pageInfoBefore,
totalCount: totalCountBefore,
} = useFindManyRecords({
filter,
orderBy,
skip: isLoadedRecords,
cursorFilter: isNonEmptyString(cursor)
? {
cursorDirection: 'before',
@ -156,12 +70,10 @@ export const useRecordShowPagePagination = (
const {
loading: loadingRecordAfter,
records: recordsAfter,
pageInfo: pageInfoAfter,
totalCount: totalCountAfter,
} = useFindManyRecords({
filter,
orderBy,
skip: isLoadedRecords,
cursorFilter: cursor
? {
cursorDirection: 'after',
@ -175,8 +87,7 @@ export const useRecordShowPagePagination = (
const totalCount = Math.max(totalCountBefore ?? 0, totalCountAfter ?? 0);
const loading =
loadingRecordAfter || loadingRecordBefore || loadingCurrentRecord;
const loading = loadingRecordAfter || loadingRecordBefore;
const isThereARecordBefore = recordsBefore.length > 0;
const isThereARecordAfter = recordsAfter.length > 0;
@ -184,43 +95,27 @@ export const useRecordShowPagePagination = (
const recordBefore = recordsBefore[0];
const recordAfter = recordsAfter[0];
const recordBeforeCursor = pageInfoBefore?.endCursor;
const recordAfterCursor = pageInfoAfter?.endCursor;
const navigateToPreviousRecord = () => {
navigate(
`/object/${objectNameSingular}/${recordBefore.id}${
viewIdQueryParam ? `?view=${viewIdQueryParam}` : ''
}`,
{
state: {
cursor: recordBeforeCursor,
},
},
buildShowPageURL(objectNameSingular, recordBefore.id, viewIdQueryParam),
);
};
const navigateToNextRecord = () => {
navigate(
`/object/${objectNameSingular}/${recordAfter.id}${
viewIdQueryParam ? `?view=${viewIdQueryParam}` : ''
}`,
{
state: {
cursor: recordAfterCursor,
},
},
buildShowPageURL(objectNameSingular, recordAfter.id, viewIdQueryParam),
);
};
const navigateToIndexView = () => {
const indexPath = `/objects/${objectMetadataItem.namePlural}${
viewIdQueryParam ? `?view=${viewIdQueryParam}` : ''
}`;
const indexTableURL = buildIndexTablePageURL(
objectMetadataItem.namePlural,
viewIdQueryParam,
);
setLastShowPageRecordId(objectRecordId);
navigate(indexPath);
navigate(indexTableURL);
};
const { recordIdsInCache } = useRecordIdsFromFindManyCacheRootQuery({

View File

@ -0,0 +1,9 @@
export const buildShowPageURL = (
objectNameSingular: string,
recordId: string,
viewId?: string | null | undefined,
) => {
return `/object/${objectNameSingular}/${recordId}${
viewId ? `?view=${viewId}` : ''
}`;
};

View File

@ -0,0 +1 @@
export const ROW_HEIGHT = 32;

View File

@ -24,17 +24,18 @@ export const useSetRecordTableData = ({
return useRecoilCallback(
({ set, snapshot }) =>
<T extends ObjectRecord>(newEntityArray: T[], totalCount?: number) => {
for (const entity of newEntityArray) {
<T extends ObjectRecord>(newRecords: T[], totalCount?: number) => {
for (const record of newRecords) {
// TODO: refactor with scoped state later
const currentEntity = snapshot
.getLoadable(recordStoreFamilyState(entity.id))
const currentRecord = snapshot
.getLoadable(recordStoreFamilyState(record.id))
.getValue();
if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
set(recordStoreFamilyState(entity.id), entity);
if (JSON.stringify(currentRecord) !== JSON.stringify(record)) {
set(recordStoreFamilyState(record.id), record);
}
}
const currentRowIds = getSnapshotValue(snapshot, tableRowIdsState);
const hasUserSelectedAllRows = getSnapshotValue(
@ -42,7 +43,7 @@ export const useSetRecordTableData = ({
hasUserSelectedAllRowsState,
);
const entityIds = newEntityArray.map((entity) => entity.id);
const entityIds = newRecords.map((entity) => entity.id);
if (!isDeeplyEqual(currentRowIds, entityIds)) {
set(tableRowIdsState, entityIds);

View File

@ -4,6 +4,7 @@ import { useDebouncedCallback } from 'use-debounce';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
@ -13,11 +14,8 @@ import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
import { scrollTopState } from '@/ui/utilities/scroll/states/scrollTopState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { isNonEmptyString } from '@sniptt/guards';
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
export const ROW_HEIGHT = 32;
export const RecordTableBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext);
@ -30,6 +28,7 @@ export const RecordTableBodyEffect = () => {
setRecordTableData,
loading,
queryStateIdentifier,
cursorsByRecordId,
} = useLoadRecordIndexTable(objectNameSingular);
const isFetchingMoreObjects = useRecoilValue(
@ -45,6 +44,7 @@ export const RecordTableBodyEffect = () => {
isRecordTableScrolledTopComponentState,
);
// TODO: move this outside because it might cause way too many re-renders for other hooks
useEffect(() => {
setIsRecordTableScrolledTop(scrollTop === 0);
if (scrollTop > 0) {
@ -83,9 +83,6 @@ export const RecordTableBodyEffect = () => {
}
}, [scrollLeft, setIsRecordTableScrolledLeft]);
const rowHeight = ROW_HEIGHT;
const viewportHeight = records.length * rowHeight;
const [lastShowPageRecordId, setLastShowPageRecordId] = useRecoilState(
lastShowPageRecordIdState,
);
@ -121,13 +118,11 @@ export const RecordTableBodyEffect = () => {
setLastShowPageRecordId,
]);
useScrollRestoration(viewportHeight);
useEffect(() => {
if (!loading) {
setRecordTableData(records, totalCount);
}
}, [records, totalCount, setRecordTableData, loading]);
}, [records, totalCount, setRecordTableData, loading, cursorsByRecordId]);
const fetchMoreDebouncedIfRequested = useDebouncedCallback(async () => {
// We are debouncing here to give the user some room to scroll if they want to within this throttle window

View File

@ -4,6 +4,7 @@ import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/Co
import { ColumnDefinition } from '../../types/ColumnDefinition';
// TODO: separate scope contexts from event contexts
type RecordTableScopeInternalContextProps = ComponentStateKey & {
onColumnsChange: (columns: ColumnDefinition<FieldMetadata>[]) => void;
};

View File

@ -0,0 +1,6 @@
export const buildIndexTablePageURL = (
objectNamePlural: string,
viewId?: string | null | undefined,
) => {
return `/objects/${objectNamePlural}${viewId ? `?view=${viewId}` : ''}`;
};

View File

@ -8,7 +8,7 @@ import { getAvatarUrl } from '@/object-metadata/utils/getAvatarUrl';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { getLabelIdentifierFieldValue } from '@/object-metadata/utils/getLabelIdentifierFieldValue';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { isFieldChipDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldChipDisplay';
import { isFieldIdentifierDisplay } from '@/object-record/record-field/meta-types/display/utils/isFieldIdentifierDisplay';
import { RecordChipData } from '@/object-record/record-field/types/RecordChipData';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@ -34,7 +34,7 @@ export const getRecordChipGenerators = (
(fieldMetadataItem) =>
labelIdentifierFieldMetadataItem?.id === fieldMetadataItem.id ||
fieldMetadataItem.type === FieldMetadataType.Relation ||
isFieldChipDisplay(
isFieldIdentifierDisplay(
fieldMetadataItem,
isLabelIdentifierField({
fieldMetadataItem: fieldMetadataItem,

View File

@ -0,0 +1,35 @@
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { ComponentFamilyState } from '@/ui/utilities/state/component-state/types/ComponentFamilyState';
import { SerializableParam } from 'recoil';
export const useExtractedComponentFamilyStateV2 = <
FamilyKey extends SerializableParam,
Value,
>(
componentFamilyState: ComponentFamilyState<Value, FamilyKey>,
componentId?: string,
) => {
const componentContext = (window as any).componentContextStateMap?.get(
componentFamilyState.key,
);
if (!componentContext) {
throw new Error(
`Component context for key "${componentFamilyState.key}" is not defined`,
);
}
const internalScopeId = useAvailableScopeIdOrThrow(
componentContext,
getScopeIdOrUndefinedFromComponentId(componentId),
);
const extractedComponentFamilyState = (familyKey: FamilyKey) =>
componentFamilyState.atomFamily({
scopeId: internalScopeId,
familyKey,
});
return extractedComponentFamilyState;
};

View File

@ -0,0 +1,36 @@
import { SerializableParam, useRecoilValue } from 'recoil';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
import { ComponentFamilyState } from '@/ui/utilities/state/component-state/types/ComponentFamilyState';
export const useRecoilComponentFamilyValue = <
StateType,
FamilyKey extends SerializableParam,
>(
componentFamilyState: ComponentFamilyState<StateType, FamilyKey>,
familyKey: FamilyKey,
componentId?: string,
) => {
const componentContext = (window as any).componentContextStateMap?.get(
componentFamilyState.key,
);
if (!componentContext) {
throw new Error(
`Component context for key "${componentFamilyState.key}" is not defined`,
);
}
const internalComponentId = useAvailableScopeIdOrThrow(
componentContext,
getScopeIdOrUndefinedFromComponentId(componentId),
);
return useRecoilValue(
componentFamilyState.atomFamily({
scopeId: internalComponentId,
familyKey,
}),
);
};

View File

@ -0,0 +1,23 @@
import { useScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopeInternalContext';
import { ComponentFamilyState } from '@/ui/utilities/state/component-state/types/ComponentFamilyState';
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
export const useScopeIdFromStateContext = (
componentState: ComponentState<any> | ComponentFamilyState<any, any>,
) => {
const componentContext = (window as any).componentContextStateMap?.get(
componentState.key,
);
if (!componentContext) {
throw new Error(
`Component context for key "${componentState.key}" is not defined`,
);
}
const scopeInternalContext = useScopeInternalContext(componentContext);
const scopeIdFromContext = scopeInternalContext?.scopeId;
return { scopeId: scopeIdFromContext };
};

View File

@ -0,0 +1,13 @@
import { RecoilState, SerializableParam } from 'recoil';
import { ComponentFamilyStateKey } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKey';
export type ComponentFamilyState<
StateType,
FamilyKey extends SerializableParam,
> = {
key: string;
atomFamily: (
componentStateKey: ComponentFamilyStateKey<FamilyKey>,
) => RecoilState<StateType>;
};

View File

@ -0,0 +1,39 @@
import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
import { ScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopeInternalContext';
import { ComponentFamilyStateKey } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKey';
import { isDefined } from 'twenty-ui';
type CreateComponentFamilyStateV2Type<ValueType> = {
key: string;
defaultValue: ValueType;
componentContext: ScopeInternalContext<any> | null;
effects?: AtomEffect<ValueType>[];
};
export const createComponentFamilyStateV2 = <
ValueType,
FamilyKey extends SerializableParam,
>({
key,
effects,
defaultValue,
componentContext,
}: CreateComponentFamilyStateV2Type<ValueType>) => {
if (isDefined(componentContext)) {
if (!isDefined((window as any).componentContextStateMap)) {
(window as any).componentContextStateMap = new Map();
}
(window as any).componentContextStateMap.set(key, componentContext);
}
return {
key,
atomFamily: atomFamily<ValueType, ComponentFamilyStateKey<FamilyKey>>({
key,
default: defaultValue,
effects,
}),
};
};

View File

@ -0,0 +1,32 @@
import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews';
import { getQueryVariablesFromView } from '@/views/utils/getQueryVariablesFromView';
export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
objectMetadataItem,
viewId,
}: {
objectMetadataItem: ObjectMetadataItem;
viewId: string | null | undefined;
}) => {
const { view } = useViewOrDefaultViewFromPrefetchedViews({
objectMetadataItemId: objectMetadataItem.id,
viewId,
});
const { activeFieldMetadataItems } = useActiveFieldMetadataItems({
objectMetadataItem,
});
const { filter, orderBy } = getQueryVariablesFromView({
fieldMetadataItems: activeFieldMetadataItems,
objectMetadataItem,
view,
});
return {
filter,
orderBy,
};
};

View File

@ -0,0 +1,24 @@
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useMemo } from 'react';
export const useViewOrDefaultViewFromPrefetchedViews = ({
objectMetadataItemId,
viewId,
}: {
objectMetadataItemId: string;
viewId: string | null | undefined;
}) => {
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const view = useMemo(() => {
return views.find(
(view: View) =>
(view.key === 'INDEX' || view?.id === viewId) &&
view?.objectMetadataId === objectMetadataItemId,
);
}, [viewId, views, objectMetadataItemId]);
return { view };
};

View File

@ -0,0 +1,52 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
import { View } from '@/views/types/View';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { isDefined } from '~/utils/isDefined';
export const getQueryVariablesFromView = ({
view,
fieldMetadataItems,
objectMetadataItem,
}: {
view: View | null | undefined;
fieldMetadataItems: FieldMetadataItem[];
objectMetadataItem: ObjectMetadataItem;
}) => {
if (!isDefined(view)) {
return {
filter: {},
orderBy: [],
};
}
const { viewFilters, viewSorts } = view;
const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({
fields: fieldMetadataItems,
});
const sortDefinitions = formatFieldMetadataItemsAsSortDefinitions({
fields: fieldMetadataItems,
});
const filter = turnObjectDropdownFilterIntoQueryFilter(
mapViewFiltersToFilters(viewFilters, filterDefinitions),
objectMetadataItem?.fields ?? [],
);
const orderBy = turnSortsIntoOrderBy(
objectMetadataItem,
mapViewSortsToSorts(viewSorts, sortDefinitions),
);
return {
filter,
orderBy,
};
};

View File

@ -11,6 +11,7 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
import { getPeopleMock, peopleQueryResult } from '~/testing/mock-data/people';
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
import { viewQueryResultMock } from '~/testing/mock-data/views';
import { RecordShowPage } from '../RecordShowPage';
const peopleMock = getPeopleMock();
@ -65,15 +66,7 @@ const meta: Meta<PageDecoratorArgs> = {
graphql.query('FindManyViews', () => {
return HttpResponse.json({
data: {
views: {
edges: [],
pageInfo: {
hasNextPage: false,
startCursor: peopleMock[0].id,
endCursor: peopleMock[0].id,
},
totalCount: 0,
},
views: viewQueryResultMock,
},
});
}),

View File

@ -1,3 +1,808 @@
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { ViewType } from '@/views/types/ViewType';
export const viewQueryResultMock: RecordGqlConnection = {
__typename: 'ViewConnection',
totalCount: 6,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
hasPreviousPage: false,
startCursor: 'WyIyY2M5MGJjZC0wNzkzLTRkMzctYWZlOS1kZTVlY2NmYmFlNzEiXQ==',
endCursor: 'WyJmZjhlZGQyMi02NjVhLTQ5NWYtODljYy03MGFiOGZkNWMxYTYiXQ==',
},
edges: [
{
__typename: 'ViewEdge',
cursor: 'WyIyY2M5MGJjZC0wNzkzLTRkMzctYWZlOS1kZTVlY2NmYmFlNzEiXQ==',
node: {
__typename: 'View',
position: 1,
updatedAt: '2024-07-11T10:21:33.304Z',
key: null,
id: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
objectMetadataId: '9c293c05-f461-456a-b5a2-2710b5b30447',
createdAt: '2024-07-11T10:21:33.304Z',
icon: 'IconLayoutKanban',
isCompact: false,
name: 'By Stage',
type: 'kanban' as ViewType,
kanbanFieldMetadataId: 'f74de381-4392-4662-a890-5ed3b5bd847d',
viewSorts: {
__typename: 'ViewSortConnection',
edges: [],
},
viewFilters: {
__typename: 'ViewFilterConnection',
edges: [],
},
viewFields: {
__typename: 'ViewFieldConnection',
edges: [
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 2,
id: '05ffd5e0-69b0-4774-843a-fbae12231e7d',
viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: 'c4e1b90f-bf9a-4a04-b67a-0f88263d8706',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 4,
id: '573ae123-1eed-4671-8fff-d9ac9455b1b4',
viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '6e073ac2-034c-43ab-b0c6-206b1dd1174b',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 0,
id: 'ae4f318f-5059-41ba-b365-22daa0b3cb0e',
viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '4ee7183a-f1f6-42a6-94e5-79f741357760',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 1,
id: 'b5ac37dc-9f64-412f-a598-611bdb5d27f8',
viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '37593700-f3ac-43a2-9ce2-1b811fa3fbfc',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 5,
id: 'bda12277-2962-4b35-a549-665cbbe53483',
viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '031bc747-1787-4e46-9320-562a8b75f3ff',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 3,
id: 'f43e660f-bbf8-4a2f-aeb1-54890ac40f4b',
viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '2cc0fa2b-dbea-4fd0-b7f5-11fa54cd0242',
},
},
],
},
},
},
{
__typename: 'ViewEdge',
cursor: 'WyI1N2FkYTUyMy0zZDgzLTQzOTEtYThiOS0wZTkxOGUyNGE1MTkiXQ==',
node: {
__typename: 'View',
position: null,
updatedAt: '2024-07-12T09:52:15.595Z',
key: 'INDEX',
id: '57ada523-3d83-4391-a8b9-0e918e24a519',
objectMetadataId: '3561dbe5-39a2-40fa-a111-4af924e39908',
createdAt: '2024-07-12T09:52:15.595Z',
icon: 'IconListNumbers',
isCompact: false,
name: 'All Tests',
type: 'table',
kanbanFieldMetadataId: '',
viewSorts: {
__typename: 'ViewSortConnection',
edges: [],
},
viewFilters: {
__typename: 'ViewFilterConnection',
edges: [],
},
viewFields: {
__typename: 'ViewFieldConnection',
edges: [
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-16T12:58:56.823Z',
position: 2,
id: '39a026d9-8362-4a4c-9b35-3d23218122a7',
viewId: '57ada523-3d83-4391-a8b9-0e918e24a519',
createdAt: '2024-07-16T12:58:56.823Z',
isVisible: true,
size: 100,
fieldMetadataId: '9918f304-99d9-4d5b-8351-c6b6f7cc38bb',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-12T09:52:15.598Z',
position: 0,
id: '3ab70930-e60a-4bfd-830a-57355121d889',
viewId: '57ada523-3d83-4391-a8b9-0e918e24a519',
createdAt: '2024-07-12T09:52:15.598Z',
isVisible: true,
size: 180,
fieldMetadataId: 'f7f485fc-0c14-4b70-a180-0508699a5c14',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-12T09:52:15.604Z',
position: 1,
id: '43ec0b2c-d94f-4eaf-a4bc-f00d409661b5',
viewId: '57ada523-3d83-4391-a8b9-0e918e24a519',
createdAt: '2024-07-12T09:52:15.604Z',
isVisible: true,
size: 180,
fieldMetadataId: '66645848-4100-4649-bc0e-d50281df2fd6',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-12T09:52:15.604Z',
position: 2,
id: '53f01c19-7042-4551-97d8-d36b6ae28602',
viewId: '57ada523-3d83-4391-a8b9-0e918e24a519',
createdAt: '2024-07-12T09:52:15.604Z',
isVisible: true,
size: 180,
fieldMetadataId: '1b3caa7a-343a-4b4b-8c2e-3371cd1dd237',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-16T12:59:52.966Z',
position: 3,
id: 'ecbc275e-f937-4d00-b035-6225e6f87c90',
viewId: '57ada523-3d83-4391-a8b9-0e918e24a519',
createdAt: '2024-07-16T12:59:46.864Z',
isVisible: true,
size: 209,
fieldMetadataId: '9e3e6ed9-7889-4979-bc15-c7803bf437f1',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-12T09:52:15.605Z',
position: 3,
id: 'ed9f32e7-cd08-4bcf-b78f-838371cd282a',
viewId: '57ada523-3d83-4391-a8b9-0e918e24a519',
createdAt: '2024-07-12T09:52:15.605Z',
isVisible: true,
size: 180,
fieldMetadataId: 'ffa953c4-d8e0-49af-b2ef-f16e238f4687',
},
},
],
},
},
},
{
__typename: 'ViewEdge',
cursor: 'WyI1ODJmMjI0Yy0zYzNmLTQxMjctYjFlZC0yOTcxZDI3ZTU0YTQiXQ==',
node: {
__typename: 'View',
position: 0,
updatedAt: '2024-07-11T10:21:33.304Z',
key: 'INDEX',
id: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
objectMetadataId: '9c293c05-f461-456a-b5a2-2710b5b30447',
createdAt: '2024-07-11T10:21:33.304Z',
icon: 'IconTargetArrow',
isCompact: false,
name: 'All Opportunities',
type: 'table',
kanbanFieldMetadataId: '',
viewSorts: {
__typename: 'ViewSortConnection',
edges: [],
},
viewFilters: {
__typename: 'ViewFilterConnection',
edges: [],
},
viewFields: {
__typename: 'ViewFieldConnection',
edges: [
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 2,
id: '0f0cacad-7f1d-4667-a0e8-466cddad3e65',
viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: 'c4e1b90f-bf9a-4a04-b67a-0f88263d8706',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 1,
id: '19d0d674-9825-492d-bbd0-c1de494201dc',
viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '37593700-f3ac-43a2-9ce2-1b811fa3fbfc',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 5,
id: '77d6a102-7b8e-40c0-9d53-33e9a8d0df0f',
viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '031bc747-1787-4e46-9320-562a8b75f3ff',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 4,
id: '8d1da76d-4056-4675-b2e5-907021c1b482',
viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '6e073ac2-034c-43ab-b0c6-206b1dd1174b',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 3,
id: 'a5556adc-e4a0-4f71-aee3-2ff2a4e53b31',
viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '2cc0fa2b-dbea-4fd0-b7f5-11fa54cd0242',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 0,
id: 'fa8cdb32-24f0-483d-a9f6-bc92f2704452',
viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '4ee7183a-f1f6-42a6-94e5-79f741357760',
},
},
],
},
},
},
{
__typename: 'ViewEdge',
cursor: 'WyI2NTM3M2UxZS0xNmU1LTRlNWYtOWJjMS1jMDlkOTAxNTZmMjciXQ==',
node: {
__typename: 'View',
position: null,
updatedAt: '2024-07-11T15:41:08.076Z',
key: null,
id: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
objectMetadataId: 'b8115dc1-5304-4d22-b300-0b4efda42ebc',
createdAt: '2024-07-11T15:41:08.076Z',
icon: 'IconBuildingSkyscraper',
isCompact: false,
name: 'All Companies L',
type: 'kanban',
kanbanFieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a',
viewSorts: {
__typename: 'ViewSortConnection',
edges: [],
},
viewFilters: {
__typename: 'ViewFilterConnection',
edges: [],
},
viewFields: {
__typename: 'ViewFieldConnection',
edges: [
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.141Z',
position: 5,
id: '00bf00d5-f257-4ed0-9a80-ce6d7fa2eace',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.141Z',
isVisible: true,
size: 170,
fieldMetadataId: 'f50611a0-d4b2-49a3-8110-1ca1282ad9c2',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.082Z',
position: 4,
id: '08ccb08d-d279-4738-bdc0-32a0f9b01390',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.082Z',
isVisible: true,
size: 150,
fieldMetadataId: '2334adb8-a0c5-408e-a449-6730f010aff1',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.115Z',
position: 2,
id: '478c93ae-1dcc-4d79-b821-b53431348abe',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.115Z',
isVisible: true,
size: 150,
fieldMetadataId: 'be572271-de80-4d55-ae25-6141ec48e1a7',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.083Z',
position: 7,
id: '4b28e7c9-f97b-4e86-80bf-ca7a1cc49f64',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.083Z',
isVisible: true,
size: 180,
fieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.076Z',
position: 3,
id: '6402a5db-dc6f-433c-9de3-af19a6d71a28',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.076Z',
isVisible: true,
size: 150,
fieldMetadataId: '04f98129-3433-43f6-a5fa-5ede5314fafd',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.141Z',
position: 1,
id: '97938fb7-d3a2-42a1-8c04-7ff59d18e41c',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.141Z',
isVisible: true,
size: 100,
fieldMetadataId: '7b76bf52-04ff-4624-9dd5-26ef59be0d88',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.129Z',
position: 0,
id: 'c7429772-5214-49ee-9d96-c4c9ea929888',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.129Z',
isVisible: true,
size: 180,
fieldMetadataId: '716b202a-7f2f-4d7a-a78a-666db003d94f',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:09.083Z',
position: 6,
id: 'ec211171-6676-40d2-acc2-5fa13f11ed00',
viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27',
createdAt: '2024-07-11T15:41:09.083Z',
isVisible: true,
size: 170,
fieldMetadataId: '479e7d9f-cd8a-4064-b009-65cb89a16c36',
},
},
],
},
},
},
{
__typename: 'ViewEdge',
cursor: 'WyJiZWU2NWJjNC05YmNiLTQ5YTgtOGVhNS0xYmQ5MjQxYjA5YzMiXQ==',
node: {
__typename: 'View',
position: 0,
updatedAt: '2024-07-11T10:21:33.304Z',
key: 'INDEX',
id: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
objectMetadataId: '48824ee2-367d-481f-b80b-ca1eeb85c4ab',
createdAt: '2024-07-11T10:21:33.304Z',
icon: 'IconUser',
isCompact: false,
name: 'All People',
type: 'table',
kanbanFieldMetadataId: '',
viewSorts: {
__typename: 'ViewSortConnection',
edges: [],
},
viewFilters: {
__typename: 'ViewFilterConnection',
edges: [],
},
viewFields: {
__typename: 'ViewFieldConnection',
edges: [
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 3,
id: '2dc48490-3ee8-4ade-a979-d5326da33d43',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '34ed07ad-067a-4f5f-bdee-21a37616f96b',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 0,
id: '67af4225-56e0-4ef9-bcfc-4a551d676c2b',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 210,
fieldMetadataId: 'c485ed46-3f8a-4ee6-af70-628b9f18ad47',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 5,
id: '796bdd63-cc83-4f8c-b538-9f8e9dfb1937',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '4aadffed-1df4-4732-bb99-559f31a464af',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 7,
id: '80cb1229-5e05-45d4-89da-b2ec850ffb2f',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: 'a8298361-b7c8-4b6c-be6c-d33885e00237',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 8,
id: '835b104c-b9fc-4c9f-b659-3dc4bb54d9ef',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '125cbc00-7efb-473d-b0a6-581d3cf868dd',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 6,
id: '8f494c43-6b63-4033-b303-0110698cf19c',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '22566388-8ece-43dc-8205-371e662716d4',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 4,
id: 'b2d72e77-a323-4e2e-acef-598b6da04712',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: 'b328512c-ff13-431b-9c94-1018ef0bd53c',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 1,
id: 'c7e1a253-9af8-498a-b579-adab742acf2d',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '2eb50615-376c-45e8-b99b-440a92a912d3',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 2,
id: 'cceae812-2687-49b5-a0c8-eb59956865e8',
viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: 'f34c04f8-ce2f-4c92-8dbc-9166c6e0d49f',
},
},
],
},
},
},
{
__typename: 'ViewEdge',
cursor: 'WyJmZjhlZGQyMi02NjVhLTQ5NWYtODljYy03MGFiOGZkNWMxYTYiXQ==',
node: {
__typename: 'View',
position: 0,
updatedAt: '2024-07-11T10:21:33.304Z',
key: 'INDEX',
id: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
objectMetadataId: 'b8115dc1-5304-4d22-b300-0b4efda42ebc',
createdAt: '2024-07-11T10:21:33.304Z',
icon: 'IconBuildingSkyscraper',
isCompact: false,
name: 'All Companies',
type: 'table',
kanbanFieldMetadataId: '',
viewSorts: {
__typename: 'ViewSortConnection',
edges: [],
},
viewFilters: {
__typename: 'ViewFilterConnection',
edges: [],
},
viewFields: {
__typename: 'ViewFieldConnection',
edges: [
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 3,
id: '1cb6aeed-8011-495f-9371-20bace45814a',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '04f98129-3433-43f6-a5fa-5ede5314fafd',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 6,
id: '3beb2130-bdc5-48d1-8cd0-22c5d0010ad2',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 170,
fieldMetadataId: '479e7d9f-cd8a-4064-b009-65cb89a16c36',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 4,
id: '5f73729b-9592-473a-8742-8e52b693c780',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: '2334adb8-a0c5-408e-a449-6730f010aff1',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T15:41:03.814Z',
position: 7,
id: '788627cb-0d3a-4659-ab4b-69deabf02f27',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T15:41:03.814Z',
isVisible: true,
size: 180,
fieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 2,
id: 'ac5797a1-2d29-42d2-b9fb-d679a945eec5',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 150,
fieldMetadataId: 'be572271-de80-4d55-ae25-6141ec48e1a7',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 0,
id: 'ae98037e-38f7-4fbf-8ae1-c0b6754c6311',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 180,
fieldMetadataId: '716b202a-7f2f-4d7a-a78a-666db003d94f',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 5,
id: 'b4d9f94e-0c4b-4422-839a-f2ceb293fde1',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 170,
fieldMetadataId: 'f50611a0-d4b2-49a3-8110-1ca1282ad9c2',
},
},
{
__typename: 'ViewFieldEdge',
node: {
__typename: 'ViewField',
updatedAt: '2024-07-11T10:21:33.304Z',
position: 1,
id: 'd45b1412-ff6b-41e5-86df-0fb778033bb3',
viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6',
createdAt: '2024-07-11T10:21:33.304Z',
isVisible: true,
size: 100,
fieldMetadataId: '7b76bf52-04ff-4624-9dd5-26ef59be0d88',
},
},
],
},
},
},
],
};
export const mockedViewsData = [
{
id: '37a8a866-eb17-4e76-9382-03143a2f6a80',

View File

@ -0,0 +1,3 @@
export const getRelayCursorFromRecordId = (recordId: string) => {
return btoa(`[1, "${recordId}"]`);
};