Feat/pagination front (#2387)
* Finished renaming and scope * wip * WIP update * Ok * Cleaned * Finished infinite scroll * Clean * Fixed V1 tables * Fix post merge * Removed ScrollWrapper * Put back ScrollWrapper * Put back in the right place
This commit is contained in:
@ -45,6 +45,7 @@
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-hook-form": "^7.45.1",
|
||||
"react-hotkeys-hook": "^4.4.0",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-loading-skeleton": "^3.3.1",
|
||||
"react-phone-number-input": "^3.3.4",
|
||||
"react-responsive": "^9.0.2",
|
||||
|
||||
@ -4,9 +4,12 @@ import {
|
||||
OperationVariables,
|
||||
useApolloClient,
|
||||
} from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies';
|
||||
import { ObjectMetadataItem } from '@/metadata/types/ObjectMetadataItem';
|
||||
import { generateFindManyCustomObjectsQuery } from '@/metadata/utils/generateFindManyCustomObjectsQuery';
|
||||
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
|
||||
import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys';
|
||||
import {
|
||||
@ -16,6 +19,7 @@ import {
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { optimisticEffectState } from '../states/optimisticEffectState';
|
||||
import { OptimisticEffect } from '../types/internal/OptimisticEffect';
|
||||
import { OptimisticEffectDefinition } from '../types/OptimisticEffectDefinition';
|
||||
|
||||
export const useOptimisticEffect = () => {
|
||||
@ -28,7 +32,7 @@ export const useOptimisticEffect = () => {
|
||||
definition,
|
||||
}: {
|
||||
variables: OperationVariables;
|
||||
definition: OptimisticEffectDefinition<T>;
|
||||
definition: OptimisticEffectDefinition;
|
||||
}) => {
|
||||
const optimisticEffects = snapshot
|
||||
.getLoadable(optimisticEffectState)
|
||||
@ -39,12 +43,47 @@ export const useOptimisticEffect = () => {
|
||||
newData,
|
||||
query,
|
||||
variables,
|
||||
isUsingFlexibleBackend,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
newData: unknown[];
|
||||
newData: unknown;
|
||||
variables: OperationVariables;
|
||||
query: DocumentNode;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
}) => {
|
||||
if (isUsingFlexibleBackend && objectMetadataItem) {
|
||||
const generatedQuery = generateFindManyCustomObjectsQuery({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const existingData = cache.readQuery({
|
||||
query: generatedQuery,
|
||||
variables,
|
||||
});
|
||||
|
||||
if (!existingData) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.writeQuery({
|
||||
query: generatedQuery,
|
||||
variables,
|
||||
data: {
|
||||
[objectMetadataItem.namePlural]: definition.resolver({
|
||||
currentData: (existingData as any)?.[
|
||||
objectMetadataItem.namePlural
|
||||
],
|
||||
newData,
|
||||
variables,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const existingData = cache.readQuery({
|
||||
query,
|
||||
variables,
|
||||
@ -82,6 +121,7 @@ export const useOptimisticEffect = () => {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (query === GET_API_KEYS) {
|
||||
cache.writeQuery({
|
||||
query,
|
||||
@ -104,7 +144,9 @@ export const useOptimisticEffect = () => {
|
||||
typename: definition.typename,
|
||||
query: definition.query,
|
||||
writer: optimisticEffectWriter,
|
||||
};
|
||||
objectMetadataItem: definition.objectMetadataItem,
|
||||
isUsingFlexibleBackend: definition.isUsingFlexibleBackend,
|
||||
} satisfies OptimisticEffect<T>;
|
||||
|
||||
set(optimisticEffectState, {
|
||||
...optimisticEffects,
|
||||
@ -115,26 +157,31 @@ export const useOptimisticEffect = () => {
|
||||
|
||||
const triggerOptimisticEffects = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(typename: string, newData: any[]) => {
|
||||
(typename: string, newData: unknown) => {
|
||||
const optimisticEffects = snapshot
|
||||
.getLoadable(optimisticEffectState)
|
||||
.getValue();
|
||||
|
||||
Object.values(optimisticEffects).forEach((optimisticEffect) => {
|
||||
for (const optimisticEffect of Object.values(optimisticEffects)) {
|
||||
// We need to update the typename when createObject type differs from listObject types
|
||||
// It is the case for apiKey, where the creation route returns an ApiKeyToken type
|
||||
const formattedNewData = newData.map((data) => {
|
||||
return { ...data, __typename: typename };
|
||||
});
|
||||
const formattedNewData = isNonEmptyArray(newData)
|
||||
? newData.map((data: any) => {
|
||||
return { ...data, __typename: typename };
|
||||
})
|
||||
: newData;
|
||||
|
||||
if (optimisticEffect.typename === typename) {
|
||||
optimisticEffect.writer({
|
||||
cache: apolloClient.cache,
|
||||
query: optimisticEffect.query,
|
||||
query: optimisticEffect.query ?? ({} as DocumentNode),
|
||||
newData: formattedNewData,
|
||||
variables: optimisticEffect.variables,
|
||||
isUsingFlexibleBackend: optimisticEffect.isUsingFlexibleBackend,
|
||||
objectMetadataItem: optimisticEffect.objectMetadataItem,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { DocumentNode } from 'graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '@/metadata/types/ObjectMetadataItem';
|
||||
|
||||
import { OptimisticEffectResolver } from './OptimisticEffectResolver';
|
||||
|
||||
export type OptimisticEffectDefinition<T> = {
|
||||
export type OptimisticEffectDefinition = {
|
||||
key: string;
|
||||
query: DocumentNode;
|
||||
query?: DocumentNode;
|
||||
typename: string;
|
||||
resolver: OptimisticEffectResolver<T>;
|
||||
resolver: OptimisticEffectResolver;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { OperationVariables } from '@apollo/client';
|
||||
|
||||
export type OptimisticEffectResolver<T> = ({
|
||||
export type OptimisticEffectResolver = ({
|
||||
currentData,
|
||||
newData,
|
||||
variables,
|
||||
}: {
|
||||
currentData: T[];
|
||||
newData: T[];
|
||||
currentData: any; //TODO: Change when decommissioning v1
|
||||
newData: any; //TODO: Change when decommissioning v1
|
||||
variables: OperationVariables;
|
||||
}) => void;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { ApolloCache, DocumentNode, OperationVariables } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/metadata/types/ObjectMetadataItem';
|
||||
|
||||
type OptimisticEffectWriter<T> = ({
|
||||
cache,
|
||||
newData,
|
||||
@ -8,14 +10,18 @@ type OptimisticEffectWriter<T> = ({
|
||||
}: {
|
||||
cache: ApolloCache<T>;
|
||||
query: DocumentNode;
|
||||
newData: T[];
|
||||
newData: T;
|
||||
variables: OperationVariables;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
}) => void;
|
||||
|
||||
export type OptimisticEffect<T> = {
|
||||
key: string;
|
||||
query: DocumentNode;
|
||||
query?: DocumentNode;
|
||||
typename: string;
|
||||
variables: OperationVariables;
|
||||
writer: OptimisticEffectWriter<T>;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
isUsingFlexibleBackend?: boolean;
|
||||
};
|
||||
|
||||
@ -5,8 +5,8 @@ import { companiesAvailableFieldDefinitions } from '@/companies/constants/compan
|
||||
import { getCompaniesOptimisticEffectDefinition } from '@/companies/graphql/optimistic-effect-definitions/getCompaniesOptimisticEffectDefinition';
|
||||
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
|
||||
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
||||
import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
|
||||
import { RecordTableEffect } from '@/ui/object/record-table/components/RecordTableEffect';
|
||||
import { RecordTableV1 } from '@/ui/object/record-table/components/RecordTableV1';
|
||||
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { TableOptionsDropdown } from '@/ui/object/record-table/options/components/TableOptionsDropdown';
|
||||
@ -135,7 +135,7 @@ export const CompanyTable = () => {
|
||||
setContextMenuEntries={setContextMenuEntries}
|
||||
setActionBarEntries={setActionBarEntries}
|
||||
/>
|
||||
<RecordTable
|
||||
<RecordTableV1
|
||||
updateEntityMutation={({
|
||||
variables,
|
||||
}: {
|
||||
|
||||
@ -32,12 +32,14 @@ export const RecordTableContainer = ({
|
||||
}: {
|
||||
objectNamePlural: string;
|
||||
}) => {
|
||||
const { columnDefinitions } = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
const { columnDefinitions, foundObjectMetadataItem } =
|
||||
useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { updateOneObject } = useUpdateOneObject({
|
||||
objectNamePlural,
|
||||
objectNameSingular: foundObjectMetadataItem?.nameSingular,
|
||||
});
|
||||
|
||||
const tableScopeId = objectNamePlural ?? '';
|
||||
|
||||
@ -1,27 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { turnFiltersIntoWhereClauseV2 } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClauseV2';
|
||||
import { turnSortsIntoOrderByV2 } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2';
|
||||
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { useView } from '@/views/hooks/useView';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
import { useRecordTable } from '../../ui/object/record-table/hooks/useRecordTable';
|
||||
import { useFindManyObjects } from '../hooks/useFindManyObjects';
|
||||
import { useFindOneObjectMetadataItem } from '../hooks/useFindOneObjectMetadataItem';
|
||||
import { useTableObjects } from '../hooks/useTableObjects';
|
||||
|
||||
export const RecordTableEffect = () => {
|
||||
const { scopeId } = useRecordTable();
|
||||
const { scopeId: objectNamePlural, setAvailableTableColumns } =
|
||||
useRecordTable();
|
||||
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
columnDefinitions,
|
||||
filterDefinitions,
|
||||
sortDefinitions,
|
||||
foundObjectMetadataItem,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural: scopeId,
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const {
|
||||
setAvailableSortDefinitions,
|
||||
setAvailableFilterDefinitions,
|
||||
@ -30,32 +28,7 @@ export const RecordTableEffect = () => {
|
||||
setViewObjectId,
|
||||
} = useView();
|
||||
|
||||
const { setRecordTableData, setAvailableTableColumns } = useRecordTable();
|
||||
|
||||
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
|
||||
|
||||
const tableFilters = useRecoilValue(tableFiltersState);
|
||||
const tableSorts = useRecoilValue(tableSortsState);
|
||||
|
||||
const { objects, loading } = useFindManyObjects({
|
||||
objectNamePlural: scopeId,
|
||||
filter: turnFiltersIntoWhereClauseV2(
|
||||
tableFilters,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
),
|
||||
orderBy: turnSortsIntoOrderByV2(
|
||||
tableSorts,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
const entities = objects ?? [];
|
||||
|
||||
setRecordTableData(entities);
|
||||
}
|
||||
}, [objects, setRecordTableData, loading]);
|
||||
useTableObjects();
|
||||
|
||||
useEffect(() => {
|
||||
if (!foundObjectMetadataItem) {
|
||||
@ -70,7 +43,6 @@ export const RecordTableEffect = () => {
|
||||
|
||||
setAvailableTableColumns(columnDefinitions);
|
||||
}, [
|
||||
setAvailableTableColumns,
|
||||
setViewObjectId,
|
||||
setViewType,
|
||||
columnDefinitions,
|
||||
@ -80,6 +52,7 @@ export const RecordTableEffect = () => {
|
||||
foundObjectMetadataItem,
|
||||
sortDefinitions,
|
||||
filterDefinitions,
|
||||
setAvailableTableColumns,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { OptimisticEffectDefinition } from '@/apollo/optimistic-effect/types/OptimisticEffectDefinition';
|
||||
import { ObjectMetadataItem } from '@/metadata/types/ObjectMetadataItem';
|
||||
import { PaginatedObjectTypeResults } from '@/metadata/types/PaginatedObjectTypeResults';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getRecordOptimisticEffectDefinition = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) =>
|
||||
({
|
||||
key: `record-create-optimistic-effect-definition-${objectMetadataItem.nameSingular}`,
|
||||
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
resolver: ({
|
||||
currentData,
|
||||
newData,
|
||||
}: {
|
||||
currentData: unknown;
|
||||
newData: unknown;
|
||||
}) => {
|
||||
const newRecordPaginatedCacheField = produce<
|
||||
PaginatedObjectTypeResults<any>
|
||||
>(currentData as PaginatedObjectTypeResults<any>, (draft) => {
|
||||
draft.edges.unshift({ node: newData, cursor: '' });
|
||||
});
|
||||
|
||||
return newRecordPaginatedCacheField;
|
||||
},
|
||||
isUsingFlexibleBackend: true,
|
||||
objectMetadataItem,
|
||||
} satisfies OptimisticEffectDefinition);
|
||||
@ -1,7 +1,8 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { Currency, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
|
||||
|
||||
@ -23,10 +24,11 @@ const defaultFieldValues: Record<FieldMetadataType, unknown> = {
|
||||
export const useCreateOneObject = ({
|
||||
objectNamePlural,
|
||||
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'>) => {
|
||||
const { triggerOptimisticEffects } = useOptimisticEffect();
|
||||
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
objectNotFoundInMetadata,
|
||||
findManyQuery,
|
||||
createOneMutation,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
@ -36,8 +38,8 @@ export const useCreateOneObject = ({
|
||||
const [mutate] = useMutation(createOneMutation);
|
||||
|
||||
const createOneObject = foundObjectMetadataItem
|
||||
? (input: Record<string, unknown> = {}) => {
|
||||
return mutate({
|
||||
? async (input: Record<string, any>) => {
|
||||
const createdObject = await mutate({
|
||||
variables: {
|
||||
input: {
|
||||
...foundObjectMetadataItem.fields.reduce(
|
||||
@ -50,8 +52,14 @@ export const useCreateOneObject = ({
|
||||
...input,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||
});
|
||||
|
||||
triggerOptimisticEffects(
|
||||
`${capitalize(foundObjectMetadataItem.nameSingular)}Edge`,
|
||||
createdObject.data[
|
||||
`create${capitalize(foundObjectMetadataItem.nameSingular)}`
|
||||
],
|
||||
);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
|
||||
import { logError } from '~/utils/logError';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { cursorFamilyState } from '../states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
|
||||
import { isFetchingMoreObjectsFamilyState } from '../states/isFetchingMoreObjectsFamilyState';
|
||||
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
|
||||
import { PaginatedObjectType } from '../types/PaginatedObjectType';
|
||||
import { PaginatedObjectTypeResults } from '../types/PaginatedObjectTypeResults';
|
||||
import {
|
||||
PaginatedObjectTypeEdge,
|
||||
PaginatedObjectTypeResults,
|
||||
} from '../types/PaginatedObjectTypeResults';
|
||||
import { formatPagedObjectsToObjects } from '../utils/formatPagedObjectsToObjects';
|
||||
|
||||
import { useFindOneObjectMetadataItem } from './useFindOneObjectMetadataItem';
|
||||
@ -27,6 +37,18 @@ export const useFindManyObjects = <
|
||||
onCompleted?: (data: PaginatedObjectTypeResults<ObjectType>) => void;
|
||||
skip?: boolean;
|
||||
}) => {
|
||||
const [lastCursor, setLastCursor] = useRecoilState(
|
||||
cursorFamilyState(objectNamePlural),
|
||||
);
|
||||
|
||||
const [hasNextPage, setHasNextPage] = useRecoilState(
|
||||
hasNextPageFamilyState(objectNamePlural),
|
||||
);
|
||||
|
||||
const [, setIsFetchingMoreObjects] = useRecoilState(
|
||||
isFetchingMoreObjectsFamilyState(objectNamePlural),
|
||||
);
|
||||
|
||||
const { foundObjectMetadataItem, objectNotFoundInMetadata, findManyQuery } =
|
||||
useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
@ -35,29 +57,107 @@ export const useFindManyObjects = <
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const { data, loading, error } = useQuery<PaginatedObjectType<ObjectType>>(
|
||||
findManyQuery,
|
||||
{
|
||||
skip: skip || !foundObjectMetadataItem,
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
},
|
||||
onCompleted: (data) =>
|
||||
objectNamePlural && onCompleted?.(data[objectNamePlural]),
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useFindManyObjects for "${objectNamePlural}" error : ` + error,
|
||||
);
|
||||
const { data, loading, error, fetchMore } = useQuery<
|
||||
PaginatedObjectType<ObjectType>
|
||||
>(findManyQuery, {
|
||||
skip: skip || !foundObjectMetadataItem || !objectNamePlural,
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (objectNamePlural) {
|
||||
onCompleted?.(data[objectNamePlural]);
|
||||
|
||||
if (objectNamePlural && data?.[objectNamePlural]) {
|
||||
setLastCursor(data?.[objectNamePlural]?.pageInfo.endCursor);
|
||||
setHasNextPage(data?.[objectNamePlural]?.pageInfo.hasNextPage);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
logError(`useFindManyObjects for "${objectNamePlural}" error : ` + error);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyObjects for "${objectNamePlural}", ${error.message}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const fetchMoreObjects = useCallback(async () => {
|
||||
if (objectNamePlural && hasNextPage) {
|
||||
setIsFetchingMoreObjects(true);
|
||||
|
||||
try {
|
||||
await fetchMore({
|
||||
variables: {
|
||||
filter: filter ?? {},
|
||||
orderBy: orderBy ?? {},
|
||||
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
|
||||
},
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
const uniqueByCursor = (
|
||||
a: PaginatedObjectTypeEdge<ObjectType>[],
|
||||
) => {
|
||||
const seenCursors = new Set();
|
||||
|
||||
return a.filter((item) => {
|
||||
const currentCursor = item.cursor;
|
||||
|
||||
return seenCursors.has(currentCursor)
|
||||
? false
|
||||
: seenCursors.add(currentCursor);
|
||||
});
|
||||
};
|
||||
|
||||
const previousEdges = prev?.[objectNamePlural]?.edges;
|
||||
const nextEdges = fetchMoreResult?.[objectNamePlural]?.edges;
|
||||
|
||||
let newEdges: any[] = [];
|
||||
|
||||
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
|
||||
newEdges = uniqueByCursor([
|
||||
...prev?.[objectNamePlural]?.edges,
|
||||
...fetchMoreResult?.[objectNamePlural]?.edges,
|
||||
]);
|
||||
}
|
||||
|
||||
return Object.assign({}, prev, {
|
||||
[objectNamePlural]: {
|
||||
__typename: `${capitalize(
|
||||
foundObjectMetadataItem?.nameSingular ?? '',
|
||||
)}Connection`,
|
||||
edges: newEdges,
|
||||
pageInfo: fetchMoreResult?.[objectNamePlural].pageInfo,
|
||||
},
|
||||
} as PaginatedObjectType<ObjectType>);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logError(`fetchMoreObjects for "${objectNamePlural}" error : ` + error);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyObjects for "${objectNamePlural}", ${error.message}`,
|
||||
`Error during fetchMoreObjects for "${objectNamePlural}", ${error}`,
|
||||
{
|
||||
variant: 'error',
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
setIsFetchingMoreObjects(false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
objectNamePlural,
|
||||
lastCursor,
|
||||
fetchMore,
|
||||
filter,
|
||||
orderBy,
|
||||
foundObjectMetadataItem,
|
||||
hasNextPage,
|
||||
setIsFetchingMoreObjects,
|
||||
enqueueSnackBar,
|
||||
]);
|
||||
|
||||
const objects = useMemo(
|
||||
() =>
|
||||
@ -75,5 +175,6 @@ export const useFindManyObjects = <
|
||||
loading,
|
||||
error,
|
||||
objectNotFoundInMetadata,
|
||||
fetchMoreObjects,
|
||||
};
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ export const useSetRecordTableData = () => {
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
<T extends { id: string }>(newEntityArray: T[]) => {
|
||||
<T extends { id: string } & Record<string, any>>(newEntityArray: T[]) => {
|
||||
for (const entity of newEntityArray) {
|
||||
const currentEntity = snapshot
|
||||
.getLoadable(entityFieldsFamilyState(entity.id))
|
||||
|
||||
65
front/src/modules/metadata/hooks/useTableObjects.ts
Normal file
65
front/src/modules/metadata/hooks/useTableObjects.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { turnFiltersIntoWhereClauseV2 } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClauseV2';
|
||||
import { turnSortsIntoOrderByV2 } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2';
|
||||
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
|
||||
import { getRecordOptimisticEffectDefinition } from '../graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
|
||||
|
||||
import { useFindManyObjects } from './useFindManyObjects';
|
||||
import { useFindOneObjectMetadataItem } from './useFindOneObjectMetadataItem';
|
||||
|
||||
export const useTableObjects = () => {
|
||||
const { scopeId: objectNamePlural } = useRecordTable();
|
||||
|
||||
const { registerOptimisticEffect } = useOptimisticEffect();
|
||||
|
||||
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { setRecordTableData } = useRecordTable();
|
||||
|
||||
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
|
||||
|
||||
const tableFilters = useRecoilValue(tableFiltersState);
|
||||
const tableSorts = useRecoilValue(tableSortsState);
|
||||
|
||||
const filter = turnFiltersIntoWhereClauseV2(
|
||||
tableFilters,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
const orderBy = turnSortsIntoOrderByV2(
|
||||
tableSorts,
|
||||
foundObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
const { objects, loading, fetchMoreObjects } = useFindManyObjects({
|
||||
objectNamePlural,
|
||||
filter,
|
||||
orderBy,
|
||||
onCompleted: (data) => {
|
||||
const entities = data.edges.map((edge) => edge.node) ?? [];
|
||||
|
||||
setRecordTableData(entities);
|
||||
|
||||
if (foundObjectMetadataItem) {
|
||||
registerOptimisticEffect({
|
||||
variables: { orderBy, filter },
|
||||
definition: getRecordOptimisticEffectDefinition({
|
||||
objectMetadataItem: foundObjectMetadataItem,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
objects,
|
||||
loading,
|
||||
fetchMoreObjects,
|
||||
};
|
||||
};
|
||||
@ -1,5 +1,4 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
|
||||
|
||||
@ -12,7 +11,6 @@ export const useUpdateOneObject = ({
|
||||
const {
|
||||
foundObjectMetadataItem,
|
||||
objectNotFoundInMetadata,
|
||||
findManyQuery,
|
||||
updateOneMutation,
|
||||
} = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
@ -20,9 +18,7 @@ export const useUpdateOneObject = ({
|
||||
});
|
||||
|
||||
// TODO: type this with a minimal type at least with Record<string, any>
|
||||
const [mutate] = useMutation(updateOneMutation, {
|
||||
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||
});
|
||||
const [mutate] = useMutation(updateOneMutation);
|
||||
|
||||
const updateOneObject = foundObjectMetadataItem
|
||||
? ({
|
||||
|
||||
6
front/src/modules/metadata/states/cursorFamilyState.ts
Normal file
6
front/src/modules/metadata/states/cursorFamilyState.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const cursorFamilyState = atomFamily<string, string | undefined>({
|
||||
key: 'cursorFamilyState',
|
||||
default: '',
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const fetchMoreObjectsFamilyState = atomFamily<
|
||||
{ fetchMore: () => void },
|
||||
string
|
||||
>({
|
||||
key: 'fetchMoreObjectsFamilyState',
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const hasNextPageFamilyState = atomFamily<boolean, string | undefined>({
|
||||
key: 'hasNextPageFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const isFetchingMoreObjectsFamilyState = atomFamily<
|
||||
boolean,
|
||||
string | undefined
|
||||
>({
|
||||
key: 'isFetchingMoreObjectsFamilyState',
|
||||
default: false,
|
||||
});
|
||||
@ -1,6 +1,14 @@
|
||||
export type PaginatedObjectTypeResults<ObjectType extends { id: string }> = {
|
||||
edges: {
|
||||
node: ObjectType;
|
||||
cursor: string;
|
||||
}[];
|
||||
export type PaginatedObjectTypeEdge<ObjectType extends { id: string }> = {
|
||||
node: ObjectType;
|
||||
cursor: string;
|
||||
};
|
||||
|
||||
export type PaginatedObjectTypeResults<ObjectType extends { id: string }> = {
|
||||
__typename?: string;
|
||||
edges: PaginatedObjectTypeEdge<ObjectType>[];
|
||||
pageInfo: {
|
||||
hasNextPage: boolean;
|
||||
startCursor: string;
|
||||
endCursor: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -4,6 +4,8 @@ import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
import { mapFieldMetadataToGraphQLQuery } from './mapFieldMetadataToGraphQLQuery';
|
||||
|
||||
export const generateCreateOneObjectMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
@ -15,6 +17,9 @@ export const generateCreateOneObjectMutation = ({
|
||||
mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) {
|
||||
create${capitalizedObjectName}(data: $input) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map(mapFieldMetadataToGraphQLQuery)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -8,18 +8,20 @@ import { mapFieldMetadataToGraphQLQuery } from './mapFieldMetadataToGraphQLQuery
|
||||
|
||||
export const generateFindManyCustomObjectsQuery = ({
|
||||
objectMetadataItem,
|
||||
_fromCursor,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
_fromCursor?: string;
|
||||
}) => {
|
||||
return gql`
|
||||
query FindMany${objectMetadataItem.namePlural}($filter: ${capitalize(
|
||||
query FindMany${capitalize(
|
||||
objectMetadataItem.namePlural,
|
||||
)}($filter: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput, $orderBy: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}OrderByInput) {
|
||||
${objectMetadataItem.namePlural}(filter: $filter, orderBy: $orderBy){
|
||||
)}OrderByInput, $lastCursor: String) {
|
||||
${
|
||||
objectMetadataItem.namePlural
|
||||
}(filter: $filter, orderBy: $orderBy, first: 30, after: $lastCursor){
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
@ -29,6 +31,11 @@ export const generateFindManyCustomObjectsQuery = ({
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -4,6 +4,16 @@ import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
import { mapFieldMetadataToGraphQLQuery } from './mapFieldMetadataToGraphQLQuery';
|
||||
|
||||
export const getUpdateOneObjectMutationGraphQLField = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `update${capitalize(objectNameSingular)}`;
|
||||
};
|
||||
|
||||
export const generateUpdateOneObjectMutation = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
@ -11,10 +21,18 @@ export const generateUpdateOneObjectMutation = ({
|
||||
}) => {
|
||||
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
|
||||
|
||||
const graphQLFieldForUpdateOneObjectMutation =
|
||||
getUpdateOneObjectMutationGraphQLField({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
return gql`
|
||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||
update${capitalizedObjectName}(id: $idToUpdate, data: $input) {
|
||||
${graphQLFieldForUpdateOneObjectMutation}(id: $idToUpdate, data: $input) {
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map(mapFieldMetadataToGraphQLQuery)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -5,8 +5,8 @@ import { getPeopleOptimisticEffectDefinition } from '@/people/graphql/optimistic
|
||||
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePersonTableContextMenuEntries';
|
||||
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
||||
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||
import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
|
||||
import { RecordTableEffect } from '@/ui/object/record-table/components/RecordTableEffect';
|
||||
import { RecordTableV1 } from '@/ui/object/record-table/components/RecordTableV1';
|
||||
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
|
||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||
import { TableOptionsDropdown } from '@/ui/object/record-table/options/components/TableOptionsDropdown';
|
||||
@ -119,7 +119,7 @@ export const PersonTable = () => {
|
||||
setContextMenuEntries={setContextMenuEntries}
|
||||
setActionBarEntries={setActionBarEntries}
|
||||
/>
|
||||
<RecordTable
|
||||
<RecordTableV1
|
||||
updateEntityMutation={({
|
||||
variables,
|
||||
}: {
|
||||
|
||||
@ -1,78 +1,72 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useVirtual } from '@tanstack/react-virtual';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useEffect } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
|
||||
import { useFindOneObjectMetadataItem } from '@/metadata/hooks/useFindOneObjectMetadataItem';
|
||||
import { useTableObjects } from '@/metadata/hooks/useTableObjects';
|
||||
import { isFetchingMoreObjectsFamilyState } from '@/metadata/states/isFetchingMoreObjectsFamilyState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { RowIdContext } from '../contexts/RowIdContext';
|
||||
import { RowIndexContext } from '../contexts/RowIndexContext';
|
||||
import { useRecordTable } from '../hooks/useRecordTable';
|
||||
import { isFetchingRecordTableDataState } from '../states/isFetchingRecordTableDataState';
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
import { RecordTableRow } from './RecordTableRow';
|
||||
|
||||
type SpaceProps = {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
};
|
||||
|
||||
const StyledSpace = styled.td<SpaceProps>`
|
||||
${({ top }) => top && `padding-top: ${top}px;`}
|
||||
${({ bottom }) => bottom && `padding-bottom: ${bottom}px;`}
|
||||
`;
|
||||
import { RecordTableRow, StyledRow } from './RecordTableRow';
|
||||
|
||||
export const RecordTableBody = () => {
|
||||
const scrollWrapperRef = useScrollWrapperScopedRef();
|
||||
const { ref: lastTableRowRef, inView: lastTableRowIsVisible } = useInView();
|
||||
|
||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||
|
||||
const { scopeId: objectNamePlural } = useRecordTable();
|
||||
|
||||
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const [isFetchingMoreObjects] = useRecoilState(
|
||||
isFetchingMoreObjectsFamilyState(foundObjectMetadataItem?.namePlural),
|
||||
);
|
||||
|
||||
const isFetchingRecordTableData = useRecoilValue(
|
||||
isFetchingRecordTableDataState,
|
||||
);
|
||||
|
||||
const rowVirtualizer = useVirtual({
|
||||
size: tableRowIds.length,
|
||||
parentRef: scrollWrapperRef,
|
||||
overscan: 50,
|
||||
});
|
||||
const { fetchMoreObjects } = useTableObjects();
|
||||
|
||||
const items = rowVirtualizer.virtualItems;
|
||||
const paddingTop = items.length > 0 ? items[0].start : 0;
|
||||
const paddingBottom =
|
||||
items.length > 0
|
||||
? rowVirtualizer.totalSize - items[items.length - 1].end
|
||||
: 0;
|
||||
useEffect(() => {
|
||||
if (lastTableRowIsVisible && isDefined(fetchMoreObjects)) {
|
||||
fetchMoreObjects();
|
||||
}
|
||||
}, [lastTableRowIsVisible, fetchMoreObjects]);
|
||||
|
||||
const lastRowId = tableRowIds[tableRowIds.length - 1];
|
||||
|
||||
if (isFetchingRecordTableData) {
|
||||
return null;
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{paddingTop > 0 && (
|
||||
<tr>
|
||||
<StyledSpace top={paddingTop} />
|
||||
</tr>
|
||||
)}
|
||||
{items.map((virtualItem) => {
|
||||
const rowId = tableRowIds[virtualItem.index];
|
||||
|
||||
return (
|
||||
<RowIdContext.Provider value={rowId} key={rowId}>
|
||||
<RowIndexContext.Provider value={virtualItem.index}>
|
||||
<RecordTableRow
|
||||
key={virtualItem.index}
|
||||
ref={virtualItem.measureRef}
|
||||
rowId={rowId}
|
||||
/>
|
||||
</RowIndexContext.Provider>
|
||||
</RowIdContext.Provider>
|
||||
);
|
||||
})}
|
||||
{paddingBottom > 0 && (
|
||||
<tr>
|
||||
<StyledSpace bottom={paddingBottom} />
|
||||
</tr>
|
||||
{tableRowIds.map((rowId, rowIndex) => (
|
||||
<RowIdContext.Provider value={rowId} key={rowId}>
|
||||
<RowIndexContext.Provider value={rowIndex}>
|
||||
<RecordTableRow
|
||||
key={rowId}
|
||||
ref={rowId === lastRowId ? lastTableRowRef : undefined}
|
||||
rowId={rowId}
|
||||
/>
|
||||
</RowIndexContext.Provider>
|
||||
</RowIdContext.Provider>
|
||||
))}
|
||||
{isFetchingMoreObjects && (
|
||||
<StyledRow selected={false}>
|
||||
<td style={{ height: 50 }} colSpan={1000}>
|
||||
Fetching more...
|
||||
</td>
|
||||
</StyledRow>
|
||||
)}
|
||||
</tbody>
|
||||
);
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { RowIdContext } from '../contexts/RowIdContext';
|
||||
import { RowIndexContext } from '../contexts/RowIndexContext';
|
||||
import { isFetchingRecordTableDataState } from '../states/isFetchingRecordTableDataState';
|
||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||
|
||||
import { RecordTableRow } from './RecordTableRow';
|
||||
|
||||
export const RecordTableBodyV1 = () => {
|
||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||
|
||||
const isFetchingRecordTableData = useRecoilValue(
|
||||
isFetchingRecordTableDataState,
|
||||
);
|
||||
|
||||
if (isFetchingRecordTableData) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{tableRowIds.map((rowId, rowIndex) => (
|
||||
<RowIdContext.Provider value={rowId} key={rowId}>
|
||||
<RowIndexContext.Provider value={rowIndex}>
|
||||
<RecordTableRow key={rowId} rowId={rowId} />
|
||||
</RowIndexContext.Provider>
|
||||
</RowIdContext.Provider>
|
||||
))}
|
||||
</tbody>
|
||||
);
|
||||
};
|
||||
@ -26,8 +26,7 @@ export const RecordTableEffect = ({
|
||||
}: {
|
||||
useGetRequest: typeof useGetCompaniesQuery | typeof useGetPeopleQuery;
|
||||
getRequestResultKey: string;
|
||||
getRequestOptimisticEffectDefinition: OptimisticEffectDefinition<any>;
|
||||
|
||||
getRequestOptimisticEffectDefinition: OptimisticEffectDefinition;
|
||||
filterDefinitionArray: FilterDefinition[];
|
||||
sortDefinitionArray: SortDefinition[];
|
||||
setActionBarEntries?: () => void;
|
||||
|
||||
@ -9,7 +9,7 @@ import { useCurrentRowSelected } from '../record-table-row/hooks/useCurrentRowSe
|
||||
import { CheckboxCell } from './CheckboxCell';
|
||||
import { RecordTableCell } from './RecordTableCell';
|
||||
|
||||
const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
export const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
background: ${(props) =>
|
||||
props.selected ? props.theme.accent.quaternary : 'none'};
|
||||
`;
|
||||
|
||||
@ -0,0 +1,139 @@
|
||||
import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import {
|
||||
useListenClickOutside,
|
||||
useListenClickOutsideByClassName,
|
||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { useRecordTable } from '../hooks/useRecordTable';
|
||||
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
||||
|
||||
import { RecordTableBodyV1 } from './RecordTableBodyV1';
|
||||
import { RecordTableHeader } from './RecordTableHeader';
|
||||
|
||||
const StyledTable = styled.table`
|
||||
border-collapse: collapse;
|
||||
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-spacing: 0;
|
||||
margin-left: ${({ theme }) => theme.table.horizontalCellMargin};
|
||||
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
|
||||
table-layout: fixed;
|
||||
|
||||
width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2);
|
||||
|
||||
th {
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-collapse: collapse;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
:last-child {
|
||||
border-right-color: transparent;
|
||||
}
|
||||
:first-of-type {
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
:last-of-type {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-collapse: collapse;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
padding: 0;
|
||||
|
||||
text-align: left;
|
||||
|
||||
:last-child {
|
||||
border-right-color: transparent;
|
||||
}
|
||||
:first-of-type {
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableWithHeader = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type RecordTableV1Props = {
|
||||
updateEntityMutation: (params: any) => void;
|
||||
};
|
||||
|
||||
export const RecordTableV1 = ({ updateEntityMutation }: RecordTableV1Props) => {
|
||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
leaveTableFocus,
|
||||
setRowSelectedState,
|
||||
resetTableRowSelection,
|
||||
useMapKeyboardToSoftFocus,
|
||||
} = useRecordTable();
|
||||
|
||||
useMapKeyboardToSoftFocus();
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [tableBodyRef],
|
||||
callback: () => {
|
||||
leaveTableFocus();
|
||||
},
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
'escape',
|
||||
() => {
|
||||
resetTableRowSelection();
|
||||
},
|
||||
TableHotkeyScope.Table,
|
||||
);
|
||||
|
||||
useListenClickOutsideByClassName({
|
||||
classNames: ['entity-table-cell'],
|
||||
excludeClassNames: ['action-bar', 'context-menu'],
|
||||
callback: () => {
|
||||
resetTableRowSelection();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<EntityUpdateMutationContext.Provider value={updateEntityMutation}>
|
||||
<StyledTableWithHeader>
|
||||
<StyledTableContainer>
|
||||
<div ref={tableBodyRef}>
|
||||
<StyledTable className="entity-table-cell">
|
||||
<RecordTableHeader />
|
||||
<RecordTableBodyV1 />
|
||||
</StyledTable>
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
onDragSelectionStart={resetTableRowSelection}
|
||||
onDragSelectionChange={setRowSelectedState}
|
||||
/>
|
||||
</div>
|
||||
</StyledTableContainer>
|
||||
</StyledTableWithHeader>
|
||||
</EntityUpdateMutationContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -16416,6 +16416,11 @@ react-inspector@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d"
|
||||
integrity sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==
|
||||
|
||||
react-intersection-observer@^9.5.2:
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz#f68363a1ff292323c0808201b58134307a1626d0"
|
||||
integrity sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==
|
||||
|
||||
react-is@18.1.0:
|
||||
version "18.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67"
|
||||
|
||||
Reference in New Issue
Block a user