7665 handle the select all case inside the action menu (#7742)
Closes #7665 - Handle select all - Handle Filters --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,51 +1,91 @@
|
|||||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
|
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
|
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
|
||||||
import { useDeleteTableData } from '@/object-record/record-index/options/hooks/useDeleteTableData';
|
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||||
|
import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds';
|
||||||
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconTrash } from 'twenty-ui';
|
import { IconTrash, isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
export const DeleteRecordsActionEffect = ({
|
export const DeleteRecordsActionEffect = ({
|
||||||
position,
|
position,
|
||||||
|
objectMetadataItem,
|
||||||
}: {
|
}: {
|
||||||
position: number;
|
position: number;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
}) => {
|
}) => {
|
||||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||||
|
|
||||||
const contextStoreTargetedRecordIds = useRecoilValue(
|
|
||||||
contextStoreTargetedRecordIdsState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilValue(
|
|
||||||
contextStoreCurrentObjectMetadataIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
|
||||||
objectId: contextStoreCurrentObjectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
const { deleteTableData } = useDeleteTableData({
|
const { resetTableRowSelection } = useRecordTable({
|
||||||
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
|
recordTableId: objectMetadataItem.namePlural,
|
||||||
recordIndexId: objectMetadataItem?.namePlural ?? '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDeleteClick = useCallback(() => {
|
const { deleteManyRecords } = useDeleteManyRecords({
|
||||||
deleteTableData(contextStoreTargetedRecordIds);
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
}, [deleteTableData, contextStoreTargetedRecordIds]);
|
});
|
||||||
|
|
||||||
const isRemoteObject = objectMetadataItem?.isRemote ?? false;
|
const { favorites, deleteFavorite } = useFavorites();
|
||||||
|
|
||||||
const numberOfSelectedRecords = contextStoreTargetedRecordIds.length;
|
const contextStoreNumberOfSelectedRecords = useRecoilValue(
|
||||||
|
contextStoreNumberOfSelectedRecordsState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const contextStoreTargetedRecordsRule = useRecoilValue(
|
||||||
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const graphqlFilter = computeContextStoreFilters(
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { fetchAllRecordIds } = useFetchAllRecordIds({
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
filter: graphqlFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(async () => {
|
||||||
|
const recordIdsToDelete = await fetchAllRecordIds();
|
||||||
|
|
||||||
|
resetTableRowSelection();
|
||||||
|
|
||||||
|
for (const recordIdToDelete of recordIdsToDelete) {
|
||||||
|
const foundFavorite = favorites?.find(
|
||||||
|
(favorite) => favorite.recordId === recordIdToDelete,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (foundFavorite !== undefined) {
|
||||||
|
deleteFavorite(foundFavorite.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteManyRecords(recordIdsToDelete, {
|
||||||
|
delayInMsBetweenRequests: 50,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
deleteFavorite,
|
||||||
|
deleteManyRecords,
|
||||||
|
favorites,
|
||||||
|
fetchAllRecordIds,
|
||||||
|
resetTableRowSelection,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isRemoteObject = objectMetadataItem.isRemote;
|
||||||
|
|
||||||
const canDelete =
|
const canDelete =
|
||||||
!isRemoteObject && numberOfSelectedRecords < DELETE_MAX_COUNT;
|
!isRemoteObject &&
|
||||||
|
isDefined(contextStoreNumberOfSelectedRecords) &&
|
||||||
|
contextStoreNumberOfSelectedRecords < DELETE_MAX_COUNT &&
|
||||||
|
contextStoreNumberOfSelectedRecords > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
@ -62,17 +102,19 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
isOpen={isDeleteRecordsModalOpen}
|
isOpen={isDeleteRecordsModalOpen}
|
||||||
setIsOpen={setIsDeleteRecordsModalOpen}
|
setIsOpen={setIsDeleteRecordsModalOpen}
|
||||||
title={`Delete ${numberOfSelectedRecords} ${
|
title={`Delete ${contextStoreNumberOfSelectedRecords} ${
|
||||||
numberOfSelectedRecords === 1 ? `record` : 'records'
|
contextStoreNumberOfSelectedRecords === 1 ? `record` : 'records'
|
||||||
}`}
|
}`}
|
||||||
subtitle={`Are you sure you want to delete ${
|
subtitle={`Are you sure you want to delete ${
|
||||||
numberOfSelectedRecords === 1 ? 'this record' : 'these records'
|
contextStoreNumberOfSelectedRecords === 1
|
||||||
|
? 'this record'
|
||||||
|
: 'these records'
|
||||||
}? ${
|
}? ${
|
||||||
numberOfSelectedRecords === 1 ? 'It' : 'They'
|
contextStoreNumberOfSelectedRecords === 1 ? 'It' : 'They'
|
||||||
} can be recovered from the Options menu.`}
|
} can be recovered from the Options menu.`}
|
||||||
onConfirmClick={() => handleDeleteClick()}
|
onConfirmClick={() => handleDeleteClick()}
|
||||||
deleteButtonText={`Delete ${
|
deleteButtonText={`Delete ${
|
||||||
numberOfSelectedRecords > 1 ? 'Records' : 'Record'
|
contextStoreNumberOfSelectedRecords > 1 ? 'Records' : 'Record'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -80,14 +122,18 @@ export const DeleteRecordsActionEffect = ({
|
|||||||
} else {
|
} else {
|
||||||
removeActionMenuEntry('delete');
|
removeActionMenuEntry('delete');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeActionMenuEntry('delete');
|
||||||
|
};
|
||||||
}, [
|
}, [
|
||||||
canDelete,
|
|
||||||
addActionMenuEntry,
|
addActionMenuEntry,
|
||||||
removeActionMenuEntry,
|
canDelete,
|
||||||
isDeleteRecordsModalOpen,
|
contextStoreNumberOfSelectedRecords,
|
||||||
numberOfSelectedRecords,
|
|
||||||
handleDeleteClick,
|
handleDeleteClick,
|
||||||
|
isDeleteRecordsModalOpen,
|
||||||
position,
|
position,
|
||||||
|
removeActionMenuEntry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,38 +1,27 @@
|
|||||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
|
||||||
import {
|
import {
|
||||||
displayedExportProgress,
|
displayedExportProgress,
|
||||||
useExportTableData,
|
useExportRecordData,
|
||||||
} from '@/object-record/record-index/options/hooks/useExportTableData';
|
} from '@/action-menu/hooks/useExportRecordData';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { IconFileExport } from 'twenty-ui';
|
import { IconFileExport } from 'twenty-ui';
|
||||||
|
|
||||||
export const ExportRecordsActionEffect = ({
|
export const ExportRecordsActionEffect = ({
|
||||||
position,
|
position,
|
||||||
|
objectMetadataItem,
|
||||||
}: {
|
}: {
|
||||||
position: number;
|
position: number;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
}) => {
|
}) => {
|
||||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||||
|
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilValue(
|
const { progress, download } = useExportRecordData({
|
||||||
contextStoreCurrentObjectMetadataIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
|
||||||
objectId: contextStoreCurrentObjectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseTableDataParams = {
|
|
||||||
delayMs: 100,
|
delayMs: 100,
|
||||||
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
|
objectMetadataItem,
|
||||||
recordIndexId: objectMetadataItem?.namePlural ?? '',
|
recordIndexId: objectMetadataItem.namePlural,
|
||||||
};
|
filename: `${objectMetadataItem.nameSingular}.csv`,
|
||||||
|
|
||||||
const { progress, download } = useExportTableData({
|
|
||||||
...baseTableDataParams,
|
|
||||||
filename: `${objectMetadataItem?.nameSingular}.csv`,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
|
||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
|
||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@ -10,30 +9,28 @@ import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui';
|
|||||||
|
|
||||||
export const ManageFavoritesActionEffect = ({
|
export const ManageFavoritesActionEffect = ({
|
||||||
position,
|
position,
|
||||||
|
objectMetadataItem,
|
||||||
}: {
|
}: {
|
||||||
position: number;
|
position: number;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
}) => {
|
}) => {
|
||||||
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
|
||||||
|
|
||||||
const contextStoreTargetedRecordIds = useRecoilValue(
|
const contextStoreTargetedRecordsRule = useRecoilValue(
|
||||||
contextStoreTargetedRecordIdsState,
|
contextStoreTargetedRecordsRuleState,
|
||||||
);
|
|
||||||
const contextStoreCurrentObjectMetadataId = useRecoilValue(
|
|
||||||
contextStoreCurrentObjectMetadataIdState,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
||||||
|
|
||||||
const selectedRecordId = contextStoreTargetedRecordIds[0];
|
const selectedRecordId =
|
||||||
|
contextStoreTargetedRecordsRule.mode === 'selection'
|
||||||
|
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const selectedRecord = useRecoilValue(
|
const selectedRecord = useRecoilValue(
|
||||||
recordStoreFamilyState(selectedRecordId),
|
recordStoreFamilyState(selectedRecordId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
|
||||||
objectId: contextStoreCurrentObjectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const foundFavorite = favorites?.find(
|
const foundFavorite = favorites?.find(
|
||||||
(favorite) => favorite.recordId === selectedRecordId,
|
(favorite) => favorite.recordId === selectedRecordId,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
||||||
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
const actionEffects = [ExportRecordsActionEffect, DeleteRecordsActionEffect];
|
const actionEffects = [ExportRecordsActionEffect, DeleteRecordsActionEffect];
|
||||||
|
|
||||||
export const MultipleRecordsActionMenuEntriesSetter = () => {
|
export const MultipleRecordsActionMenuEntriesSetter = ({
|
||||||
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{actionEffects.map((ActionEffect, index) => (
|
{actionEffects.map((ActionEffect, index) => (
|
||||||
<ActionEffect key={index} position={index} />
|
<ActionEffect
|
||||||
|
key={index}
|
||||||
|
position={index}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,20 +1,44 @@
|
|||||||
import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter';
|
import { MultipleRecordsActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/MultipleRecordsActionMenuEntriesSetter';
|
||||||
import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter';
|
import { SingleRecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/SingleRecordActionMenuEntriesSetter';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
||||||
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
export const RecordActionMenuEntriesSetter = () => {
|
export const RecordActionMenuEntriesSetter = () => {
|
||||||
const contextStoreTargetedRecordIds = useRecoilValue(
|
const contextStoreNumberOfSelectedRecords = useRecoilValue(
|
||||||
contextStoreTargetedRecordIdsState,
|
contextStoreNumberOfSelectedRecordsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (contextStoreTargetedRecordIds.length === 0) {
|
const contextStoreCurrentObjectMetadataId = useRecoilValue(
|
||||||
|
contextStoreCurrentObjectMetadataIdState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||||
|
objectId: contextStoreCurrentObjectMetadataId ?? '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!objectMetadataItem) {
|
||||||
|
throw new Error(
|
||||||
|
`Object metadata item not found for id ${contextStoreCurrentObjectMetadataId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contextStoreNumberOfSelectedRecords) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextStoreTargetedRecordIds.length === 1) {
|
if (contextStoreNumberOfSelectedRecords === 1) {
|
||||||
return <SingleRecordActionMenuEntriesSetter />;
|
return (
|
||||||
|
<SingleRecordActionMenuEntriesSetter
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <MultipleRecordsActionMenuEntriesSetter />;
|
return (
|
||||||
|
<MultipleRecordsActionMenuEntriesSetter
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
import { DeleteRecordsActionEffect } from '@/action-menu/actions/record-actions/components/DeleteRecordsActionEffect';
|
||||||
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
import { ExportRecordsActionEffect } from '@/action-menu/actions/record-actions/components/ExportRecordsActionEffect';
|
||||||
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';
|
import { ManageFavoritesActionEffect } from '@/action-menu/actions/record-actions/components/ManageFavoritesActionEffect';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
export const SingleRecordActionMenuEntriesSetter = () => {
|
export const SingleRecordActionMenuEntriesSetter = ({
|
||||||
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
const actionEffects = [
|
const actionEffects = [
|
||||||
ManageFavoritesActionEffect,
|
ManageFavoritesActionEffect,
|
||||||
ExportRecordsActionEffect,
|
ExportRecordsActionEffect,
|
||||||
@ -11,7 +16,11 @@ export const SingleRecordActionMenuEntriesSetter = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{actionEffects.map((ActionEffect, index) => (
|
{actionEffects.map((ActionEffect, index) => (
|
||||||
<ActionEffect key={index} position={index} />
|
<ActionEffect
|
||||||
|
key={index}
|
||||||
|
position={index}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
||||||
|
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
|
||||||
|
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||||
|
import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown';
|
||||||
|
import { ActionMenuEffect } from '@/action-menu/components/ActionMenuEffect';
|
||||||
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
|
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
export const ActionMenu = ({ actionMenuId }: { actionMenuId: string }) => {
|
||||||
|
const contextStoreCurrentObjectMetadataId = useRecoilValue(
|
||||||
|
contextStoreCurrentObjectMetadataIdState,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{contextStoreCurrentObjectMetadataId && (
|
||||||
|
<ActionMenuComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: actionMenuId }}
|
||||||
|
>
|
||||||
|
<ActionMenuBar />
|
||||||
|
<ActionMenuDropdown />
|
||||||
|
<ActionMenuConfirmationModals />
|
||||||
|
<ActionMenuEffect />
|
||||||
|
<RecordActionMenuEntriesSetter />
|
||||||
|
</ActionMenuComponentInstanceContext.Provider>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -4,7 +4,7 @@ import { ActionMenuBarEntry } from '@/action-menu/components/ActionMenuBarEntry'
|
|||||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
||||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
@ -19,8 +19,8 @@ const StyledLabel = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionMenuBar = () => {
|
export const ActionMenuBar = () => {
|
||||||
const contextStoreTargetedRecordIds = useRecoilValue(
|
const contextStoreNumberOfSelectedRecords = useRecoilValue(
|
||||||
contextStoreTargetedRecordIdsState,
|
contextStoreNumberOfSelectedRecordsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||||
@ -42,9 +42,7 @@ export const ActionMenuBar = () => {
|
|||||||
scope: ActionBarHotkeyScope.ActionBar,
|
scope: ActionBarHotkeyScope.ActionBar,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledLabel>
|
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
|
||||||
{contextStoreTargetedRecordIds.length} selected:
|
|
||||||
</StyledLabel>
|
|
||||||
{actionMenuEntries.map((entry, index) => (
|
{actionMenuEntries.map((entry, index) => (
|
||||||
<ActionMenuBarEntry key={index} entry={entry} />
|
<ActionMenuBarEntry key={index} entry={entry} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export const ActionMenuDropdown = () => {
|
|||||||
return (
|
return (
|
||||||
<StyledContainerActionMenuDropdown
|
<StyledContainerActionMenuDropdown
|
||||||
position={actionMenuDropdownPosition}
|
position={actionMenuDropdownPosition}
|
||||||
className="context-menu"
|
className="action-menu-dropdown"
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={`action-menu-dropdown-${actionMenuId}`}
|
dropdownId={`action-menu-dropdown-${actionMenuId}`}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
@ -8,8 +8,8 @@ import { useEffect } from 'react';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
export const ActionMenuEffect = () => {
|
export const ActionMenuEffect = () => {
|
||||||
const contextStoreTargetedRecordIds = useRecoilValue(
|
const contextStoreNumberOfSelectedRecords = useRecoilValue(
|
||||||
contextStoreTargetedRecordIdsState,
|
contextStoreNumberOfSelectedRecordsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||||
@ -26,17 +26,17 @@ export const ActionMenuEffect = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contextStoreTargetedRecordIds.length > 0 && !isDropdownOpen) {
|
if (contextStoreNumberOfSelectedRecords > 0 && !isDropdownOpen) {
|
||||||
// We only handle opening the ActionMenuBar here, not the Dropdown.
|
// We only handle opening the ActionMenuBar here, not the Dropdown.
|
||||||
// The Dropdown is already managed by sync handlers for events like
|
// The Dropdown is already managed by sync handlers for events like
|
||||||
// right-click to open and click outside to close.
|
// right-click to open and click outside to close.
|
||||||
openActionBar();
|
openActionBar();
|
||||||
}
|
}
|
||||||
if (contextStoreTargetedRecordIds.length === 0) {
|
if (contextStoreNumberOfSelectedRecords === 0 && isDropdownOpen) {
|
||||||
closeActionBar();
|
closeActionBar();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
contextStoreTargetedRecordIds,
|
contextStoreNumberOfSelectedRecords,
|
||||||
openActionBar,
|
openActionBar,
|
||||||
closeActionBar,
|
closeActionBar,
|
||||||
isDropdownOpen,
|
isDropdownOpen,
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import { RecoilRoot } from 'recoil';
|
|||||||
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
|
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
|
||||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||||
import { userEvent, waitFor, within } from '@storybook/test';
|
import { userEvent, waitFor, within } from '@storybook/test';
|
||||||
import { IconCheckbox, IconTrash } from 'twenty-ui';
|
import { IconCheckbox, IconTrash } from 'twenty-ui';
|
||||||
@ -20,7 +21,11 @@ const meta: Meta<typeof ActionMenuBar> = {
|
|||||||
(Story) => (
|
(Story) => (
|
||||||
<RecoilRoot
|
<RecoilRoot
|
||||||
initializeState={({ set }) => {
|
initializeState={({ set }) => {
|
||||||
set(contextStoreTargetedRecordIdsState, ['1', '2', '3']);
|
set(contextStoreTargetedRecordsRuleState, {
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: ['1', '2', '3'],
|
||||||
|
});
|
||||||
|
set(contextStoreNumberOfSelectedRecordsState, 3);
|
||||||
set(
|
set(
|
||||||
actionMenuEntriesComponentState.atomFamily({
|
actionMenuEntriesComponentState.atomFamily({
|
||||||
instanceId: 'story-action-menu',
|
instanceId: 'story-action-menu',
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
displayedExportProgress,
|
displayedExportProgress,
|
||||||
download,
|
download,
|
||||||
generateCsv,
|
generateCsv,
|
||||||
} from '../useExportTableData';
|
} from '../useExportRecordData';
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
@ -4,10 +4,11 @@ import { useMemo } from 'react';
|
|||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize';
|
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize';
|
||||||
import { useProcessRecordsForCSVExport } from '@/object-record/record-index/options/hooks/useProcessRecordsForCSVExport';
|
import { useProcessRecordsForCSVExport } from '@/object-record/record-index/options/hooks/useProcessRecordsForCSVExport';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useTableData,
|
UseRecordDataOptions,
|
||||||
UseTableDataOptions,
|
useRecordData,
|
||||||
} from '@/object-record/record-index/options/hooks/useTableData';
|
} from '@/object-record/record-index/options/hooks/useRecordData';
|
||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||||
@ -134,21 +135,22 @@ const downloader = (mimeType: string, generator: GenerateExport) => {
|
|||||||
|
|
||||||
export const csvDownloader = downloader('text/csv', generateCsv);
|
export const csvDownloader = downloader('text/csv', generateCsv);
|
||||||
|
|
||||||
type UseExportTableDataOptions = Omit<UseTableDataOptions, 'callback'> & {
|
type UseExportTableDataOptions = Omit<UseRecordDataOptions, 'callback'> & {
|
||||||
filename: string;
|
filename: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useExportTableData = ({
|
export const useExportRecordData = ({
|
||||||
delayMs,
|
delayMs,
|
||||||
filename,
|
filename,
|
||||||
maximumRequests = 100,
|
maximumRequests = 100,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE,
|
pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
viewType,
|
viewType,
|
||||||
}: UseExportTableDataOptions) => {
|
}: UseExportTableDataOptions) => {
|
||||||
const { processRecordsForCSVExport } =
|
const { processRecordsForCSVExport } = useProcessRecordsForCSVExport(
|
||||||
useProcessRecordsForCSVExport(objectNameSingular);
|
objectMetadataItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
const downloadCsv = useMemo(
|
const downloadCsv = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -160,10 +162,10 @@ export const useExportTableData = ({
|
|||||||
[filename, processRecordsForCSVExport],
|
[filename, processRecordsForCSVExport],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getTableData: download, progress } = useTableData({
|
const { getTableData: download, progress } = useRecordData({
|
||||||
delayMs,
|
delayMs,
|
||||||
maximumRequests,
|
maximumRequests,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
pageSize,
|
pageSize,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
callback: downloadCsv,
|
callback: downloadCsv,
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const contextStoreNumberOfSelectedRecordsState = createState<number>({
|
||||||
|
key: 'contextStoreNumberOfSelectedRecordsState',
|
||||||
|
defaultValue: 0,
|
||||||
|
});
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const contextStoreTargetedRecordIdsState = createState<string[]>({
|
|
||||||
key: 'contextStoreTargetedRecordIdsState',
|
|
||||||
defaultValue: [],
|
|
||||||
});
|
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
type ContextStoreTargetedRecordsRuleSelectionMode = {
|
||||||
|
mode: 'selection';
|
||||||
|
selectedRecordIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContextStoreTargetedRecordsRuleExclusionMode = {
|
||||||
|
mode: 'exclusion';
|
||||||
|
excludedRecordIds: string[];
|
||||||
|
filters: Filter[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ContextStoreTargetedRecordsRule =
|
||||||
|
| ContextStoreTargetedRecordsRuleSelectionMode
|
||||||
|
| ContextStoreTargetedRecordsRuleExclusionMode;
|
||||||
|
|
||||||
|
export const contextStoreTargetedRecordsRuleState =
|
||||||
|
createState<ContextStoreTargetedRecordsRule>({
|
||||||
|
key: 'contextStoreTargetedRecordsRuleState',
|
||||||
|
defaultValue: {
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
|
describe('computeContextStoreFilters', () => {
|
||||||
|
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||||
|
(item) => item.nameSingular === 'person',
|
||||||
|
)!;
|
||||||
|
|
||||||
|
it('should work for selection mode', () => {
|
||||||
|
const contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule = {
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: ['1', '2', '3'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const filters = computeContextStoreFilters(
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
personObjectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filters).toEqual({
|
||||||
|
id: {
|
||||||
|
in: ['1', '2', '3'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for exclusion mode', () => {
|
||||||
|
const contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule = {
|
||||||
|
mode: 'exclusion',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: 'name-filter',
|
||||||
|
variant: 'default',
|
||||||
|
fieldMetadataId: personObjectMetadataItem.fields.find(
|
||||||
|
(field) => field.name === 'name',
|
||||||
|
)!.id,
|
||||||
|
value: 'John',
|
||||||
|
displayValue: 'John',
|
||||||
|
displayAvatarUrl: undefined,
|
||||||
|
operand: ViewFilterOperand.Contains,
|
||||||
|
definition: {
|
||||||
|
fieldMetadataId: personObjectMetadataItem.fields.find(
|
||||||
|
(field) => field.name === 'name',
|
||||||
|
)!.id,
|
||||||
|
label: 'Name',
|
||||||
|
iconName: 'person',
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
excludedRecordIds: ['1', '2', '3'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const filters = computeContextStoreFilters(
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
personObjectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filters).toEqual({
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
ilike: '%John%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
id: {
|
||||||
|
in: ['1', '2', '3'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||||
|
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||||
|
|
||||||
|
export const computeContextStoreFilters = (
|
||||||
|
contextStoreTargetedRecordsRule: ContextStoreTargetedRecordsRule,
|
||||||
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
|
) => {
|
||||||
|
let queryFilter: RecordGqlOperationFilter | undefined;
|
||||||
|
|
||||||
|
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
||||||
|
queryFilter = makeAndFilterVariables([
|
||||||
|
turnFiltersIntoQueryFilter(
|
||||||
|
contextStoreTargetedRecordsRule.filters,
|
||||||
|
objectMetadataItem?.fields ?? [],
|
||||||
|
),
|
||||||
|
contextStoreTargetedRecordsRule.excludedRecordIds.length > 0
|
||||||
|
? {
|
||||||
|
not: {
|
||||||
|
id: {
|
||||||
|
in: contextStoreTargetedRecordsRule.excludedRecordIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (contextStoreTargetedRecordsRule.mode === 'selection') {
|
||||||
|
queryFilter =
|
||||||
|
contextStoreTargetedRecordsRule.selectedRecordIds.length > 0
|
||||||
|
? {
|
||||||
|
id: {
|
||||||
|
in: contextStoreTargetedRecordsRule.selectedRecordIds,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryFilter;
|
||||||
|
};
|
||||||
@ -33,16 +33,11 @@ describe('useObjectMetadataItemById', () => {
|
|||||||
expect(objectMetadataItem?.id).toBe(opportunityObjectMetadata.id);
|
expect(objectMetadataItem?.id).toBe(opportunityObjectMetadata.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null when invalid ID is provided', async () => {
|
it('should throw an error when invalid ID is provided', async () => {
|
||||||
const { result } = renderHook(
|
expect(() =>
|
||||||
() => useObjectMetadataItemById({ objectId: 'invalid-id' }),
|
renderHook(() => useObjectMetadataItemById({ objectId: 'invalid-id' }), {
|
||||||
{
|
|
||||||
wrapper: Wrapper,
|
wrapper: Wrapper,
|
||||||
},
|
}),
|
||||||
);
|
).toThrow(`Object metadata item not found for id invalid-id`);
|
||||||
|
|
||||||
const { objectMetadataItem } = result.current;
|
|
||||||
|
|
||||||
expect(objectMetadataItem).toBeNull();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { isDefined } from '~/utils/isDefined';
|
|||||||
export const useObjectMetadataItemById = ({
|
export const useObjectMetadataItemById = ({
|
||||||
objectId,
|
objectId,
|
||||||
}: {
|
}: {
|
||||||
objectId: string | null;
|
objectId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
@ -15,9 +15,7 @@ export const useObjectMetadataItemById = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isDefined(objectMetadataItem)) {
|
if (!isDefined(objectMetadataItem)) {
|
||||||
return {
|
throw new Error(`Object metadata item not found for id ${objectId}`);
|
||||||
objectMetadataItem: null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export const RecordBoard = () => {
|
|||||||
|
|
||||||
useListenClickOutsideByClassName({
|
useListenClickOutsideByClassName({
|
||||||
classNames: ['record-board-card'],
|
classNames: ['record-board-card'],
|
||||||
excludeClassNames: ['bottom-bar', 'context-menu'],
|
excludeClassNames: ['bottom-bar', 'action-menu-dropdown'],
|
||||||
callback: resetRecordSelection,
|
callback: resetRecordSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
@ -16,7 +16,7 @@ const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
|||||||
|
|
||||||
jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));
|
jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));
|
||||||
|
|
||||||
describe('turnObjectDropdownFilterIntoQueryFilter', () => {
|
describe('turnFiltersIntoQueryFilter', () => {
|
||||||
it('should work as expected for single filter', () => {
|
it('should work as expected for single filter', () => {
|
||||||
const companyMockNameFieldMetadataId =
|
const companyMockNameFieldMetadataId =
|
||||||
companyMockObjectMetadataItem.fields.find(
|
companyMockObjectMetadataItem.fields.find(
|
||||||
@ -37,7 +37,7 @@ describe('turnObjectDropdownFilterIntoQueryFilter', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[nameFilter],
|
[nameFilter],
|
||||||
companyMockObjectMetadataItem.fields,
|
companyMockObjectMetadataItem.fields,
|
||||||
);
|
);
|
||||||
@ -88,7 +88,7 @@ describe('turnObjectDropdownFilterIntoQueryFilter', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[nameFilter, employeesFilter],
|
[nameFilter, employeesFilter],
|
||||||
companyMockObjectMetadataItem.fields,
|
companyMockObjectMetadataItem.fields,
|
||||||
);
|
);
|
||||||
@ -173,7 +173,7 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[
|
[
|
||||||
addressFilterContains,
|
addressFilterContains,
|
||||||
addressFilterDoesNotContain,
|
addressFilterDoesNotContain,
|
||||||
@ -554,7 +554,7 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[
|
[
|
||||||
phonesFilterContains,
|
phonesFilterContains,
|
||||||
phonesFilterDoesNotContain,
|
phonesFilterDoesNotContain,
|
||||||
@ -754,7 +754,7 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[
|
[
|
||||||
emailsFilterContains,
|
emailsFilterContains,
|
||||||
emailsFilterDoesNotContain,
|
emailsFilterDoesNotContain,
|
||||||
@ -908,7 +908,7 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[
|
[
|
||||||
dateFilterIsAfter,
|
dateFilterIsAfter,
|
||||||
dateFilterIsBefore,
|
dateFilterIsBefore,
|
||||||
@ -1023,7 +1023,7 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = turnObjectDropdownFilterIntoQueryFilter(
|
const result = turnFiltersIntoQueryFilter(
|
||||||
[
|
[
|
||||||
employeesFilterIsGreaterThan,
|
employeesFilterIsGreaterThan,
|
||||||
employeesFilterIsLessThan,
|
employeesFilterIsLessThan,
|
||||||
@ -31,7 +31,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
// TODO: break this down into smaller functions and make the whole thing immutable
|
// TODO: break this down into smaller functions and make the whole thing immutable
|
||||||
// Especially applyEmptyFilters
|
// Especially applyEmptyFilters
|
||||||
export const turnObjectDropdownFilterIntoQueryFilter = (
|
export const turnFiltersIntoQueryFilter = (
|
||||||
rawUIFilters: Filter[],
|
rawUIFilters: Filter[],
|
||||||
fields: Pick<Field, 'id' | 'name'>[],
|
fields: Pick<Field, 'id' | 'name'>[],
|
||||||
): RecordGqlOperationFilter | undefined => {
|
): RecordGqlOperationFilter | undefined => {
|
||||||
@ -2,8 +2,7 @@ import { useCallback, useEffect } from 'react';
|
|||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
||||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||||
@ -121,32 +120,23 @@ export const RecordIndexBoardDataLoaderEffect = ({
|
|||||||
|
|
||||||
const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector());
|
const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector());
|
||||||
|
|
||||||
const setContextStoreTargetedRecordIds = useSetRecoilState(
|
const setContextStoreTargetedRecords = useSetRecoilState(
|
||||||
contextStoreTargetedRecordIdsState,
|
contextStoreTargetedRecordsRuleState,
|
||||||
);
|
|
||||||
|
|
||||||
const setContextStoreCurrentObjectMetadataItem = useSetRecoilState(
|
|
||||||
contextStoreCurrentObjectMetadataIdState,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContextStoreTargetedRecordIds(selectedRecordIds);
|
setContextStoreTargetedRecords({
|
||||||
}, [selectedRecordIds, setContextStoreTargetedRecordIds]);
|
mode: 'selection',
|
||||||
|
selectedRecordIds: selectedRecordIds,
|
||||||
useEffect(() => {
|
});
|
||||||
setContextStoreTargetedRecordIds(selectedRecordIds);
|
|
||||||
setContextStoreCurrentObjectMetadataItem(objectMetadataItem?.id);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setContextStoreTargetedRecordIds([]);
|
setContextStoreTargetedRecords({
|
||||||
setContextStoreCurrentObjectMetadataItem(null);
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [
|
}, [selectedRecordIds, setContextStoreTargetedRecords]);
|
||||||
objectMetadataItem?.id,
|
|
||||||
selectedRecordIds,
|
|
||||||
setContextStoreCurrentObjectMetadataItem,
|
|
||||||
setContextStoreTargetedRecordIds,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,12 +22,8 @@ import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-
|
|||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||||
|
|
||||||
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
|
import { ActionMenu } from '@/action-menu/components/ActionMenu';
|
||||||
import { ActionMenuBar } from '@/action-menu/components/ActionMenuBar';
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
|
||||||
import { ActionMenuDropdown } from '@/action-menu/components/ActionMenuDropdown';
|
|
||||||
import { ActionMenuEffect } from '@/action-menu/components/ActionMenuEffect';
|
|
||||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
|
||||||
import { ViewBar } from '@/views/components/ViewBar';
|
import { ViewBar } from '@/views/components/ViewBar';
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
import { ViewField } from '@/views/types/ViewField';
|
import { ViewField } from '@/views/types/ViewField';
|
||||||
@ -106,6 +102,10 @@ export const RecordIndexContainer = () => {
|
|||||||
[columnDefinitions, setTableColumns],
|
[columnDefinitions, setTableColumns],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setContextStoreTargetedRecordsRule = useSetRecoilState(
|
||||||
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<InformationBannerWrapper />
|
<InformationBannerWrapper />
|
||||||
@ -119,7 +119,7 @@ export const RecordIndexContainer = () => {
|
|||||||
optionsDropdownButton={
|
optionsDropdownButton={
|
||||||
<RecordIndexOptionsDropdown
|
<RecordIndexOptionsDropdown
|
||||||
recordIndexId={recordIndexId}
|
recordIndexId={recordIndexId}
|
||||||
objectNameSingular={objectNameSingular}
|
objectMetadataItem={objectMetadataItem}
|
||||||
viewType={recordIndexViewType ?? ViewType.Table}
|
viewType={recordIndexViewType ?? ViewType.Table}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -135,6 +135,13 @@ export const RecordIndexContainer = () => {
|
|||||||
setRecordIndexFilters(
|
setRecordIndexFilters(
|
||||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||||
);
|
);
|
||||||
|
setContextStoreTargetedRecordsRule((prev) => ({
|
||||||
|
...prev,
|
||||||
|
filters: mapViewFiltersToFilters(
|
||||||
|
view.viewFilters,
|
||||||
|
filterDefinitions,
|
||||||
|
),
|
||||||
|
}));
|
||||||
setTableSorts(
|
setTableSorts(
|
||||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||||
);
|
);
|
||||||
@ -179,15 +186,7 @@ export const RecordIndexContainer = () => {
|
|||||||
/>
|
/>
|
||||||
</StyledContainerWithPadding>
|
</StyledContainerWithPadding>
|
||||||
)}
|
)}
|
||||||
<ActionMenuComponentInstanceContext.Provider
|
<ActionMenu actionMenuId={recordIndexId} />
|
||||||
value={{ instanceId: recordIndexId }}
|
|
||||||
>
|
|
||||||
<ActionMenuEffect />
|
|
||||||
<RecordActionMenuEntriesSetter />
|
|
||||||
<ActionMenuBar />
|
|
||||||
<ActionMenuDropdown />
|
|
||||||
<ActionMenuConfirmationModals />
|
|
||||||
</ActionMenuComponentInstanceContext.Provider>
|
|
||||||
</RecordFieldValueSelectorContextProvider>
|
</RecordFieldValueSelectorContextProvider>
|
||||||
</ViewComponentInstanceContext.Provider>
|
</ViewComponentInstanceContext.Provider>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@ -0,0 +1,66 @@
|
|||||||
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
|
import { useFindManyParams } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
|
||||||
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||||
|
() => {
|
||||||
|
const setContextStoreNumberOfSelectedRecords = useSetRecoilState(
|
||||||
|
contextStoreNumberOfSelectedRecordsState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const contextStoreTargetedRecordsRule = useRecoilValue(
|
||||||
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { objectNamePlural } = useContext(RecordIndexRootPropsContext);
|
||||||
|
|
||||||
|
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||||
|
objectNamePlural,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const findManyRecordsParams = useFindManyParams(
|
||||||
|
objectMetadataItem?.nameSingular ?? '',
|
||||||
|
objectMetadataItem?.namePlural ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { totalCount } = useFindManyRecords({
|
||||||
|
...findManyRecordsParams,
|
||||||
|
recordGqlFields: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
filter: computeContextStoreFilters(
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
objectMetadataItem,
|
||||||
|
),
|
||||||
|
limit: 1,
|
||||||
|
skip: contextStoreTargetedRecordsRule.mode === 'selection',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (contextStoreTargetedRecordsRule.mode === 'selection') {
|
||||||
|
setContextStoreNumberOfSelectedRecords(
|
||||||
|
contextStoreTargetedRecordsRule.selectedRecordIds.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (contextStoreTargetedRecordsRule.mode === 'exclusion') {
|
||||||
|
setContextStoreNumberOfSelectedRecords(totalCount ?? 0);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
setContextStoreNumberOfSelectedRecords,
|
||||||
|
totalCount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
export const RecordIndexContainerContextStoreObjectMetadataEffect = () => {
|
||||||
|
const setContextStoreCurrentObjectMetadataItem = useSetRecoilState(
|
||||||
|
contextStoreCurrentObjectMetadataIdState,
|
||||||
|
);
|
||||||
|
const { objectNamePlural } = useContext(RecordIndexRootPropsContext);
|
||||||
|
|
||||||
|
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||||
|
objectNamePlural,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setContextStoreCurrentObjectMetadataItem(objectMetadataItem.id);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setContextStoreCurrentObjectMetadataItem(null);
|
||||||
|
};
|
||||||
|
}, [objectMetadataItem.id, setContextStoreCurrentObjectMetadataItem]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
|
||||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
@ -24,18 +23,12 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
selectedRowIdsSelector,
|
selectedRowIdsSelector,
|
||||||
setOnToggleColumnFilter,
|
setOnToggleColumnFilter,
|
||||||
setOnToggleColumnSort,
|
setOnToggleColumnSort,
|
||||||
|
hasUserSelectedAllRowsState,
|
||||||
|
unselectedRowIdsSelector,
|
||||||
} = useRecordTable({
|
} = useRecordTable({
|
||||||
recordTableId: recordIndexId,
|
recordTableId: recordIndexId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setContextStoreTargetedRecordIds = useSetRecoilState(
|
|
||||||
contextStoreTargetedRecordIdsState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setContextStoreCurrentObjectMetadataItem = useSetRecoilState(
|
|
||||||
contextStoreCurrentObjectMetadataIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
@ -50,8 +43,6 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
setAvailableTableColumns(columnDefinitions);
|
setAvailableTableColumns(columnDefinitions);
|
||||||
}, [columnDefinitions, setAvailableTableColumns]);
|
}, [columnDefinitions, setAvailableTableColumns]);
|
||||||
|
|
||||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
|
||||||
|
|
||||||
const handleToggleColumnFilter = useHandleToggleColumnFilter({
|
const handleToggleColumnFilter = useHandleToggleColumnFilter({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
viewBarId,
|
viewBarId,
|
||||||
@ -82,19 +73,38 @@ export const RecordIndexTableContainerEffect = () => {
|
|||||||
);
|
);
|
||||||
}, [setRecordCountInCurrentView, setOnEntityCountChange]);
|
}, [setRecordCountInCurrentView, setOnEntityCountChange]);
|
||||||
|
|
||||||
|
const setContextStoreTargetedRecords = useSetRecoilState(
|
||||||
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
);
|
||||||
|
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
|
||||||
|
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
||||||
|
const unselectedRowIds = useRecoilValue(unselectedRowIdsSelector());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContextStoreTargetedRecordIds(selectedRowIds);
|
if (hasUserSelectedAllRows) {
|
||||||
setContextStoreCurrentObjectMetadataItem(objectMetadataItem?.id);
|
setContextStoreTargetedRecords({
|
||||||
|
mode: 'exclusion',
|
||||||
|
excludedRecordIds: unselectedRowIds,
|
||||||
|
filters: [],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setContextStoreTargetedRecords({
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: selectedRowIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setContextStoreTargetedRecordIds([]);
|
setContextStoreTargetedRecords({
|
||||||
setContextStoreCurrentObjectMetadataItem(null);
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
objectMetadataItem?.id,
|
hasUserSelectedAllRows,
|
||||||
selectedRowIds,
|
selectedRowIds,
|
||||||
setContextStoreCurrentObjectMetadataItem,
|
setContextStoreTargetedRecords,
|
||||||
setContextStoreTargetedRecordIds,
|
unselectedRowIds,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||||
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
||||||
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
|
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
|
||||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||||
@ -44,7 +44,7 @@ export const useLoadRecordIndexBoard = ({
|
|||||||
|
|
||||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||||
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
|
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
|
||||||
const requestFilters = turnObjectDropdownFilterIntoQueryFilter(
|
const requestFilters = turnFiltersIntoQueryFilter(
|
||||||
recordIndexFilters,
|
recordIndexFilters,
|
||||||
objectMetadataItem?.fields ?? [],
|
objectMetadataItem?.fields ?? [],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||||
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
||||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||||
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
|
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
|
||||||
@ -35,7 +35,7 @@ export const useLoadRecordIndexBoardColumn = ({
|
|||||||
|
|
||||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||||
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
|
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
|
||||||
const requestFilters = turnObjectDropdownFilterIntoQueryFilter(
|
const requestFilters = turnFiltersIntoQueryFilter(
|
||||||
recordIndexFilters,
|
recordIndexFilters,
|
||||||
objectMetadataItem?.fields ?? [],
|
objectMetadataItem?.fields ?? [],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||||
import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hooks/useRecordTableRecordGqlFields';
|
import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hooks/useRecordTableRecordGqlFields';
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
@ -27,7 +27,7 @@ export const useFindManyParams = (
|
|||||||
const tableFilters = useRecoilValue(tableFiltersState);
|
const tableFilters = useRecoilValue(tableFiltersState);
|
||||||
const tableSorts = useRecoilValue(tableSortsState);
|
const tableSorts = useRecoilValue(tableSortsState);
|
||||||
|
|
||||||
const filter = turnObjectDropdownFilterIntoQueryFilter(
|
const filter = turnFiltersIntoQueryFilter(
|
||||||
tableFilters,
|
tableFilters,
|
||||||
objectMetadataItem?.fields ?? [],
|
objectMetadataItem?.fields ?? [],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { RecordIndexOptionsDropdownButton } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdownButton';
|
import { RecordIndexOptionsDropdownButton } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdownButton';
|
||||||
import { RecordIndexOptionsDropdownContent } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdownContent';
|
import { RecordIndexOptionsDropdownContent } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdownContent';
|
||||||
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
|
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
|
||||||
@ -7,13 +8,13 @@ import { ViewType } from '@/views/types/ViewType';
|
|||||||
|
|
||||||
type RecordIndexOptionsDropdownProps = {
|
type RecordIndexOptionsDropdownProps = {
|
||||||
viewType: ViewType;
|
viewType: ViewType;
|
||||||
objectNameSingular: string;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
recordIndexId: string;
|
recordIndexId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordIndexOptionsDropdown = ({
|
export const RecordIndexOptionsDropdown = ({
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
viewType,
|
viewType,
|
||||||
}: RecordIndexOptionsDropdownProps) => {
|
}: RecordIndexOptionsDropdownProps) => {
|
||||||
return (
|
return (
|
||||||
@ -26,7 +27,7 @@ export const RecordIndexOptionsDropdown = ({
|
|||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<RecordIndexOptionsDropdownContent
|
<RecordIndexOptionsDropdownContent
|
||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
objectNameSingular={objectNameSingular}
|
objectMetadataItem={objectMetadataItem}
|
||||||
recordIndexId={recordIndexId}
|
recordIndexId={recordIndexId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,12 @@ import {
|
|||||||
import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
|
import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
|
||||||
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
|
||||||
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
|
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
displayedExportProgress,
|
displayedExportProgress,
|
||||||
useExportTableData,
|
useExportRecordData,
|
||||||
} from '@/object-record/record-index/options/hooks/useExportTableData';
|
} from '@/action-menu/hooks/useExportRecordData';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
|
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
|
||||||
import { useRecordIndexOptionsForTable } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForTable';
|
import { useRecordIndexOptionsForTable } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForTable';
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||||
@ -44,14 +46,14 @@ type RecordIndexOptionsMenu = 'fields' | 'hiddenFields';
|
|||||||
|
|
||||||
type RecordIndexOptionsDropdownContentProps = {
|
type RecordIndexOptionsDropdownContentProps = {
|
||||||
recordIndexId: string;
|
recordIndexId: string;
|
||||||
objectNameSingular: string;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
viewType: ViewType;
|
viewType: ViewType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordIndexOptionsDropdownContent = ({
|
export const RecordIndexOptionsDropdownContent = ({
|
||||||
viewType,
|
viewType,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
}: RecordIndexOptionsDropdownContentProps) => {
|
}: RecordIndexOptionsDropdownContentProps) => {
|
||||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||||
|
|
||||||
@ -68,7 +70,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { objectNamePlural } = useObjectNamePluralFromSingular({
|
const { objectNamePlural } = useObjectNamePluralFromSingular({
|
||||||
objectNameSingular: objectNameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsUrl = getSettingsPagePath(SettingsPath.ObjectDetail, {
|
const settingsUrl = getSettingsPagePath(SettingsPath.ObjectDetail, {
|
||||||
@ -92,7 +94,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
|
|
||||||
const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } =
|
const { handleToggleTrashColumnFilter, toggleSoftDeleteFilterState } =
|
||||||
useHandleToggleTrashColumnFilter({
|
useHandleToggleTrashColumnFilter({
|
||||||
objectNameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -104,7 +106,7 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
isCompactModeActive,
|
isCompactModeActive,
|
||||||
setAndPersistIsCompactModeActive,
|
setAndPersistIsCompactModeActive,
|
||||||
} = useRecordIndexOptionsForBoard({
|
} = useRecordIndexOptionsForBoard({
|
||||||
objectNameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordBoardId: recordIndexId,
|
recordBoardId: recordIndexId,
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
});
|
});
|
||||||
@ -126,12 +128,12 @@ export const RecordIndexOptionsDropdownContent = ({
|
|||||||
: handleColumnVisibilityChange;
|
: handleColumnVisibilityChange;
|
||||||
|
|
||||||
const { openObjectRecordsSpreasheetImportDialog } =
|
const { openObjectRecordsSpreasheetImportDialog } =
|
||||||
useOpenObjectRecordsSpreasheetImportDialog(objectNameSingular);
|
useOpenObjectRecordsSpreasheetImportDialog(objectMetadataItem.nameSingular);
|
||||||
|
|
||||||
const { progress, download } = useExportTableData({
|
const { progress, download } = useExportRecordData({
|
||||||
delayMs: 100,
|
delayMs: 100,
|
||||||
filename: `${objectNameSingular}.csv`,
|
filename: `${objectMetadataItem.nameSingular}.csv`,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
viewType,
|
viewType,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { renderHook, waitFor } from '@testing-library/react';
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
import { act } from 'react';
|
import { act } from 'react';
|
||||||
import { percentage, sleep, useTableData } from '../useTableData';
|
import { percentage, sleep, useRecordData } from '../useRecordData';
|
||||||
|
|
||||||
import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments';
|
import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments';
|
||||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||||
@ -11,7 +11,7 @@ import { ViewType } from '@/views/types/ViewType';
|
|||||||
import { MockedResponse } from '@apollo/client/testing';
|
import { MockedResponse } from '@apollo/client/testing';
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
import { getJestMetadataAndApolloMocksAndContextStoreWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndContextStoreWrapper';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
|
|
||||||
const defaultResponseData = {
|
const defaultResponseData = {
|
||||||
@ -127,9 +127,16 @@ const mocks: MockedResponse[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const WrapperWithResponse = getJestMetadataAndApolloMocksWrapper({
|
const WrapperWithResponse = getJestMetadataAndApolloMocksAndContextStoreWrapper(
|
||||||
apolloMocks: mocks,
|
{
|
||||||
});
|
apolloMocks: mocks,
|
||||||
|
contextStoreTargetedRecordsRule: {
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
},
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular: 'person',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const graphqlEmptyResponse = [
|
const graphqlEmptyResponse = [
|
||||||
{
|
{
|
||||||
@ -145,28 +152,41 @@ const graphqlEmptyResponse = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const WrapperWithEmptyResponse = getJestMetadataAndApolloMocksWrapper({
|
const WrapperWithEmptyResponse =
|
||||||
apolloMocks: graphqlEmptyResponse,
|
getJestMetadataAndApolloMocksAndContextStoreWrapper({
|
||||||
});
|
apolloMocks: graphqlEmptyResponse,
|
||||||
|
contextStoreTargetedRecordsRule: {
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
},
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular: 'person',
|
||||||
|
});
|
||||||
|
|
||||||
describe('useTableData', () => {
|
describe('useRecordData', () => {
|
||||||
const recordIndexId = 'people';
|
const recordIndexId = 'people';
|
||||||
const objectNameSingular = 'person';
|
const objectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||||
|
(item) => item.nameSingular === 'person',
|
||||||
|
);
|
||||||
|
if (!objectMetadataItem) {
|
||||||
|
throw new Error('Object metadata item not found');
|
||||||
|
}
|
||||||
describe('data fetching', () => {
|
describe('data fetching', () => {
|
||||||
it('should handle no records', async () => {
|
it('should handle no records', async () => {
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
|
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useTableData({
|
useRecordData({
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
callback,
|
callback,
|
||||||
delayMs: 0,
|
delayMs: 0,
|
||||||
viewType: ViewType.Kanban,
|
viewType: ViewType.Kanban,
|
||||||
}),
|
}),
|
||||||
{ wrapper: WrapperWithEmptyResponse },
|
{
|
||||||
|
wrapper: WrapperWithEmptyResponse,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
@ -182,9 +202,9 @@ describe('useTableData', () => {
|
|||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
() =>
|
() =>
|
||||||
useTableData({
|
useRecordData({
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
callback,
|
callback,
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
|
|
||||||
@ -211,9 +231,9 @@ describe('useTableData', () => {
|
|||||||
recordIndexId,
|
recordIndexId,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
tableData: useTableData({
|
tableData: useRecordData({
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
callback,
|
callback,
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
maximumRequests: 100,
|
maximumRequests: 100,
|
||||||
@ -223,7 +243,7 @@ describe('useTableData', () => {
|
|||||||
useRecordBoardHook: useRecordBoard(recordIndexId),
|
useRecordBoardHook: useRecordBoard(recordIndexId),
|
||||||
kanbanFieldName: useRecoilValue(kanbanFieldNameState),
|
kanbanFieldName: useRecoilValue(kanbanFieldNameState),
|
||||||
kanbanData: useRecordIndexOptionsForBoard({
|
kanbanData: useRecordIndexOptionsForBoard({
|
||||||
objectNameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordBoardId: recordIndexId,
|
recordBoardId: recordIndexId,
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
}),
|
}),
|
||||||
@ -304,9 +324,9 @@ describe('useTableData', () => {
|
|||||||
recordIndexId,
|
recordIndexId,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
tableData: useTableData({
|
tableData: useRecordData({
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNameSingular,
|
objectMetadataItem,
|
||||||
callback,
|
callback,
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
maximumRequests: 100,
|
maximumRequests: 100,
|
||||||
@ -316,7 +336,7 @@ describe('useTableData', () => {
|
|||||||
setKanbanFieldName: useRecordBoard(recordIndexId),
|
setKanbanFieldName: useRecordBoard(recordIndexId),
|
||||||
kanbanFieldName: useRecoilValue(kanbanFieldNameState),
|
kanbanFieldName: useRecoilValue(kanbanFieldNameState),
|
||||||
kanbanData: useRecordIndexOptionsForBoard({
|
kanbanData: useRecordIndexOptionsForBoard({
|
||||||
objectNameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordBoardId: recordIndexId,
|
recordBoardId: recordIndexId,
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
}),
|
}),
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|
||||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
|
||||||
import { UseTableDataOptions } from '@/object-record/record-index/options/hooks/useTableData';
|
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
|
||||||
|
|
||||||
type UseDeleteTableDataOptions = Pick<
|
|
||||||
UseTableDataOptions,
|
|
||||||
'objectNameSingular' | 'recordIndexId'
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const useDeleteTableData = ({
|
|
||||||
objectNameSingular,
|
|
||||||
recordIndexId,
|
|
||||||
}: UseDeleteTableDataOptions) => {
|
|
||||||
const { resetTableRowSelection } = useRecordTable({
|
|
||||||
recordTableId: recordIndexId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { deleteManyRecords } = useDeleteManyRecords({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
const { favorites, deleteFavorite } = useFavorites();
|
|
||||||
|
|
||||||
const deleteRecords = async (recordIdsToDelete: string[]) => {
|
|
||||||
resetTableRowSelection();
|
|
||||||
|
|
||||||
for (const recordIdToDelete of recordIdsToDelete) {
|
|
||||||
const foundFavorite = favorites?.find(
|
|
||||||
(favorite) => favorite.recordId === recordIdToDelete,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (foundFavorite !== undefined) {
|
|
||||||
deleteFavorite(foundFavorite.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteManyRecords(recordIdsToDelete, {
|
|
||||||
delayInMsBetweenRequests: 50,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return { deleteTableData: deleteRecords };
|
|
||||||
};
|
|
||||||
@ -1,18 +1,21 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
|
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
|
||||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
|
import { useFindManyParams } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
|
||||||
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize';
|
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/record-index/options/constants/ExportTableDataDefaultPageSize';
|
||||||
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
|
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useFindManyParams } from '../../hooks/useLoadRecordIndexTable';
|
|
||||||
|
|
||||||
export const sleep = (ms: number) =>
|
export const sleep = (ms: number) =>
|
||||||
new Promise((resolve) => setTimeout(resolve, ms));
|
new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
@ -21,10 +24,10 @@ export const percentage = (part: number, whole: number): number => {
|
|||||||
return Math.round((part / whole) * 100);
|
return Math.round((part / whole) * 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseTableDataOptions = {
|
export type UseRecordDataOptions = {
|
||||||
delayMs: number;
|
delayMs: number;
|
||||||
maximumRequests?: number;
|
maximumRequests?: number;
|
||||||
objectNameSingular: string;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
recordIndexId: string;
|
recordIndexId: string;
|
||||||
callback: (
|
callback: (
|
||||||
@ -40,15 +43,15 @@ type ExportProgress = {
|
|||||||
displayType: 'percentage' | 'number';
|
displayType: 'percentage' | 'number';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTableData = ({
|
export const useRecordData = ({
|
||||||
|
objectMetadataItem,
|
||||||
delayMs,
|
delayMs,
|
||||||
maximumRequests = 100,
|
maximumRequests = 100,
|
||||||
objectNameSingular,
|
|
||||||
pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE,
|
pageSize = EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
callback,
|
callback,
|
||||||
viewType = ViewType.Table,
|
viewType = ViewType.Table,
|
||||||
}: UseTableDataOptions) => {
|
}: UseRecordDataOptions) => {
|
||||||
const [isDownloading, setIsDownloading] = useState(false);
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
const [inflight, setInflight] = useState(false);
|
const [inflight, setInflight] = useState(false);
|
||||||
const [pageCount, setPageCount] = useState(0);
|
const [pageCount, setPageCount] = useState(0);
|
||||||
@ -57,15 +60,10 @@ export const useTableData = ({
|
|||||||
});
|
});
|
||||||
const [previousRecordCount, setPreviousRecordCount] = useState(0);
|
const [previousRecordCount, setPreviousRecordCount] = useState(0);
|
||||||
|
|
||||||
const {
|
const { visibleTableColumnsSelector } = useRecordTableStates(recordIndexId);
|
||||||
visibleTableColumnsSelector,
|
|
||||||
selectedRowIdsSelector,
|
|
||||||
tableRowIdsState,
|
|
||||||
hasUserSelectedAllRowsState,
|
|
||||||
} = useRecordTableStates(recordIndexId);
|
|
||||||
|
|
||||||
const { hiddenBoardFields } = useRecordIndexOptionsForBoard({
|
const { hiddenBoardFields } = useRecordIndexOptionsForBoard({
|
||||||
objectNameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordBoardId: recordIndexId,
|
recordBoardId: recordIndexId,
|
||||||
viewBarId: recordIndexId,
|
viewBarId: recordIndexId,
|
||||||
});
|
});
|
||||||
@ -76,61 +74,21 @@ export const useTableData = ({
|
|||||||
(column) => column.metadata.fieldName === kanbanFieldMetadataName,
|
(column) => column.metadata.fieldName === kanbanFieldMetadataName,
|
||||||
);
|
);
|
||||||
const columns = useRecoilValue(visibleTableColumnsSelector());
|
const columns = useRecoilValue(visibleTableColumnsSelector());
|
||||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
|
||||||
|
|
||||||
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
|
const contextStoreTargetedRecordsRule = useRecoilValue(
|
||||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
);
|
||||||
|
|
||||||
// user has checked select all and then unselected some rows
|
const queryFilter = computeContextStoreFilters(
|
||||||
const userHasUnselectedSomeRows =
|
contextStoreTargetedRecordsRule,
|
||||||
hasUserSelectedAllRows && selectedRowIds.length < tableRowIds.length;
|
objectMetadataItem,
|
||||||
|
|
||||||
const hasSelectedRows =
|
|
||||||
selectedRowIds.length > 0 &&
|
|
||||||
!(hasUserSelectedAllRows && selectedRowIds.length === tableRowIds.length);
|
|
||||||
|
|
||||||
const unselectedRowIds = useMemo(
|
|
||||||
() =>
|
|
||||||
userHasUnselectedSomeRows
|
|
||||||
? tableRowIds.filter((id) => !selectedRowIds.includes(id))
|
|
||||||
: [],
|
|
||||||
[userHasUnselectedSomeRows, tableRowIds, selectedRowIds],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const findManyRecordsParams = useFindManyParams(
|
const findManyRecordsParams = useFindManyParams(
|
||||||
objectNameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedFindManyParams = {
|
|
||||||
...findManyRecordsParams,
|
|
||||||
filter: {
|
|
||||||
...findManyRecordsParams.filter,
|
|
||||||
id: {
|
|
||||||
in: selectedRowIds,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const unselectedFindManyParams = {
|
|
||||||
...findManyRecordsParams,
|
|
||||||
filter: {
|
|
||||||
...findManyRecordsParams.filter,
|
|
||||||
not: {
|
|
||||||
id: {
|
|
||||||
in: unselectedRowIds,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const usedFindManyParams =
|
|
||||||
hasSelectedRows && !userHasUnselectedSomeRows
|
|
||||||
? selectedFindManyParams
|
|
||||||
: userHasUnselectedSomeRows
|
|
||||||
? unselectedFindManyParams
|
|
||||||
: findManyRecordsParams;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
findManyRecords,
|
findManyRecords,
|
||||||
totalCount,
|
totalCount,
|
||||||
@ -138,7 +96,8 @@ export const useTableData = ({
|
|||||||
fetchMoreRecordsWithPagination,
|
fetchMoreRecordsWithPagination,
|
||||||
loading,
|
loading,
|
||||||
} = useLazyFindManyRecords({
|
} = useLazyFindManyRecords({
|
||||||
...usedFindManyParams,
|
...findManyRecordsParams,
|
||||||
|
filter: queryFilter,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1,41 +1,22 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
|
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import {
|
|
||||||
ClickOutsideMode,
|
|
||||||
useListenClickOutsideByClassName,
|
|
||||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
|
||||||
|
|
||||||
type RecordTableInternalEffectProps = {
|
type RecordTableInternalEffectProps = {
|
||||||
recordTableId: string;
|
recordTableId: string;
|
||||||
tableBodyRef: React.RefObject<HTMLDivElement>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableInternalEffect = ({
|
export const RecordTableInternalEffect = ({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
tableBodyRef,
|
|
||||||
}: RecordTableInternalEffectProps) => {
|
}: RecordTableInternalEffectProps) => {
|
||||||
const { leaveTableFocus, resetTableRowSelection, useMapKeyboardToSoftFocus } =
|
const { leaveTableFocus, resetTableRowSelection, useMapKeyboardToSoftFocus } =
|
||||||
useRecordTable({ recordTableId });
|
useRecordTable({ recordTableId });
|
||||||
|
|
||||||
useMapKeyboardToSoftFocus();
|
useMapKeyboardToSoftFocus();
|
||||||
|
|
||||||
const { useListenClickOutside } = useClickOutsideListener(
|
|
||||||
SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID,
|
|
||||||
);
|
|
||||||
|
|
||||||
useListenClickOutside({
|
|
||||||
refs: [tableBodyRef],
|
|
||||||
callback: () => {
|
|
||||||
leaveTableFocus();
|
|
||||||
},
|
|
||||||
mode: ClickOutsideMode.compareHTMLRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
[Key.Escape],
|
[Key.Escape],
|
||||||
() => {
|
() => {
|
||||||
@ -46,9 +27,9 @@ export const RecordTableInternalEffect = ({
|
|||||||
|
|
||||||
useListenClickOutsideByClassName({
|
useListenClickOutsideByClassName({
|
||||||
classNames: ['entity-table-cell'],
|
classNames: ['entity-table-cell'],
|
||||||
excludeClassNames: ['bottom-bar', 'context-menu'],
|
excludeClassNames: ['bottom-bar', 'action-menu-dropdown'],
|
||||||
callback: () => {
|
callback: () => {
|
||||||
resetTableRowSelection();
|
leaveTableFocus();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -87,10 +87,7 @@ export const RecordTableWithWrappers = ({
|
|||||||
onDragSelectionChange={setRowSelected}
|
onDragSelectionChange={setRowSelected}
|
||||||
/>
|
/>
|
||||||
</StyledTableInternalContainer>
|
</StyledTableInternalContainer>
|
||||||
<RecordTableInternalEffect
|
<RecordTableInternalEffect recordTableId={recordTableId} />
|
||||||
recordTableId={recordTableId}
|
|
||||||
tableBodyRef={tableBodyRef}
|
|
||||||
/>
|
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
</StyledTableWithHeader>
|
</StyledTableWithHeader>
|
||||||
</RecordUpdateContext.Provider>
|
</RecordUpdateContext.Provider>
|
||||||
|
|||||||
@ -6,21 +6,19 @@ import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotV
|
|||||||
|
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
|
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||||
import { useCloseCurrentTableCellInEditMode } from './useCloseCurrentTableCellInEditMode';
|
import { useCloseCurrentTableCellInEditMode } from './useCloseCurrentTableCellInEditMode';
|
||||||
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
import { useDisableSoftFocus } from './useDisableSoftFocus';
|
||||||
import { useSetHasUserSelectedAllRows } from './useSetAllRowSelectedState';
|
|
||||||
|
|
||||||
export const useLeaveTableFocus = (recordTableId?: string) => {
|
export const useLeaveTableFocus = (recordTableId?: string) => {
|
||||||
const disableSoftFocus = useDisableSoftFocus(recordTableId);
|
const disableSoftFocus = useDisableSoftFocus(recordTableId);
|
||||||
const closeCurrentCellInEditMode =
|
const closeCurrentCellInEditMode =
|
||||||
useCloseCurrentTableCellInEditMode(recordTableId);
|
useCloseCurrentTableCellInEditMode(recordTableId);
|
||||||
|
|
||||||
const setHasUserSelectedAllRows = useSetHasUserSelectedAllRows(recordTableId);
|
|
||||||
|
|
||||||
const selectAllRows = useSetHasUserSelectedAllRows(recordTableId);
|
|
||||||
|
|
||||||
const { isSoftFocusActiveState } = useRecordTableStates(recordTableId);
|
const { isSoftFocusActiveState } = useRecordTableStates(recordTableId);
|
||||||
|
|
||||||
|
const resetTableRowSelection = useResetTableRowSelection(recordTableId);
|
||||||
|
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
() => {
|
() => {
|
||||||
@ -33,6 +31,8 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
|
|||||||
.getLoadable(currentHotkeyScopeState)
|
.getLoadable(currentHotkeyScopeState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
|
resetTableRowSelection();
|
||||||
|
|
||||||
if (!isSoftFocusActive) {
|
if (!isSoftFocusActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,15 +43,12 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
|
|||||||
|
|
||||||
closeCurrentCellInEditMode();
|
closeCurrentCellInEditMode();
|
||||||
disableSoftFocus();
|
disableSoftFocus();
|
||||||
setHasUserSelectedAllRows(false);
|
|
||||||
selectAllRows(false);
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
closeCurrentCellInEditMode,
|
closeCurrentCellInEditMode,
|
||||||
disableSoftFocus,
|
disableSoftFocus,
|
||||||
isSoftFocusActiveState,
|
isSoftFocusActiveState,
|
||||||
selectAllRows,
|
resetTableRowSelection,
|
||||||
setHasUserSelectedAllRows,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-t
|
|||||||
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
||||||
import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
|
import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
|
||||||
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
|
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
|
||||||
|
import { unselectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
import { softFocusPositionComponentState } from '@/object-record/record-table/states/softFocusPositionComponentState';
|
import { softFocusPositionComponentState } from '@/object-record/record-table/states/softFocusPositionComponentState';
|
||||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||||
@ -134,6 +135,10 @@ export const useRecordTableStates = (recordTableId?: string) => {
|
|||||||
selectedRowIdsComponentSelector,
|
selectedRowIdsComponentSelector,
|
||||||
scopeId,
|
scopeId,
|
||||||
),
|
),
|
||||||
|
unselectedRowIdsSelector: extractComponentReadOnlySelector(
|
||||||
|
unselectedRowIdsComponentSelector,
|
||||||
|
scopeId,
|
||||||
|
),
|
||||||
visibleTableColumnsSelector: extractComponentReadOnlySelector(
|
visibleTableColumnsSelector: extractComponentReadOnlySelector(
|
||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
scopeId,
|
scopeId,
|
||||||
|
|||||||
@ -46,17 +46,16 @@ export const useSetRecordTableData = ({
|
|||||||
const recordIds = newRecords.map((record) => record.id);
|
const recordIds = newRecords.map((record) => record.id);
|
||||||
|
|
||||||
if (!isDeeplyEqual(currentRowIds, recordIds)) {
|
if (!isDeeplyEqual(currentRowIds, recordIds)) {
|
||||||
set(tableRowIdsState, recordIds);
|
if (hasUserSelectedAllRows) {
|
||||||
}
|
for (const rowId of recordIds) {
|
||||||
|
set(isRowSelectedFamilyState(rowId), true);
|
||||||
if (hasUserSelectedAllRows) {
|
}
|
||||||
for (const rowId of recordIds) {
|
|
||||||
set(isRowSelectedFamilyState(rowId), true);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
set(numberOfTableRowsState, totalCount ?? 0);
|
set(tableRowIdsState, recordIds);
|
||||||
onEntityCountChange(totalCount);
|
set(numberOfTableRowsState, totalCount ?? 0);
|
||||||
|
onEntityCountChange(totalCount);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
numberOfTableRowsState,
|
numberOfTableRowsState,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
isRecordTableInitialLoadingState,
|
isRecordTableInitialLoadingState,
|
||||||
tableLastRowVisibleState,
|
tableLastRowVisibleState,
|
||||||
selectedRowIdsSelector,
|
selectedRowIdsSelector,
|
||||||
|
unselectedRowIdsSelector,
|
||||||
onToggleColumnFilterState,
|
onToggleColumnFilterState,
|
||||||
onToggleColumnSortState,
|
onToggleColumnSortState,
|
||||||
pendingRecordIdState,
|
pendingRecordIdState,
|
||||||
@ -223,6 +224,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
setSoftFocusPosition,
|
setSoftFocusPosition,
|
||||||
isSomeCellInEditModeState,
|
isSomeCellInEditModeState,
|
||||||
selectedRowIdsSelector,
|
selectedRowIdsSelector,
|
||||||
|
unselectedRowIdsSelector,
|
||||||
setHasUserSelectedAllRows,
|
setHasUserSelectedAllRows,
|
||||||
setOnToggleColumnFilter,
|
setOnToggleColumnFilter,
|
||||||
setOnToggleColumnSort,
|
setOnToggleColumnSort,
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
|
||||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||||
import { Checkbox } from '@/ui/input/components/Checkbox';
|
import { Checkbox } from '@/ui/input/components/Checkbox';
|
||||||
@ -21,19 +19,16 @@ const StyledContainer = styled.div`
|
|||||||
export const RecordTableCellCheckbox = () => {
|
export const RecordTableCellCheckbox = () => {
|
||||||
const { isSelected } = useContext(RecordTableRowContext);
|
const { isSelected } = useContext(RecordTableRowContext);
|
||||||
|
|
||||||
const { recordId } = useContext(RecordTableRowContext);
|
|
||||||
const { isRowSelectedFamilyState } = useRecordTableStates();
|
|
||||||
const { setCurrentRowSelected } = useSetCurrentRowSelected();
|
const { setCurrentRowSelected } = useSetCurrentRowSelected();
|
||||||
const currentRowSelected = useRecoilValue(isRowSelectedFamilyState(recordId));
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
setCurrentRowSelected(!currentRowSelected);
|
setCurrentRowSelected(!isSelected);
|
||||||
}, [currentRowSelected, setCurrentRowSelected]);
|
}, [isSelected, setCurrentRowSelected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordTableTd isSelected={isSelected} hasRightBorder={false}>
|
<RecordTableTd isSelected={isSelected} hasRightBorder={false}>
|
||||||
<StyledContainer onClick={handleClick}>
|
<StyledContainer onClick={handleClick}>
|
||||||
<Checkbox hoverable checked={currentRowSelected} />
|
<Checkbox hoverable checked={isSelected} />
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</RecordTableTd>
|
</RecordTableTd>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -37,7 +37,6 @@ export const RecordTableHeaderCheckboxColumn = () => {
|
|||||||
setHasUserSelectedAllRows(true);
|
setHasUserSelectedAllRows(true);
|
||||||
selectAllRows();
|
selectAllRows();
|
||||||
} else {
|
} else {
|
||||||
setHasUserSelectedAllRows(false);
|
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||||
|
import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState';
|
||||||
|
import { createComponentReadOnlySelector } from '@/ui/utilities/state/component-state/utils/createComponentReadOnlySelector';
|
||||||
|
|
||||||
|
export const unselectedRowIdsComponentSelector =
|
||||||
|
createComponentReadOnlySelector<string[]>({
|
||||||
|
key: 'unselectedRowIdsComponentSelector',
|
||||||
|
get:
|
||||||
|
({ scopeId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const rowIds = get(tableRowIdsComponentState({ scopeId }));
|
||||||
|
|
||||||
|
return rowIds.filter(
|
||||||
|
(rowId) =>
|
||||||
|
get(
|
||||||
|
isRowSelectedComponentFamilyState({
|
||||||
|
scopeId,
|
||||||
|
familyKey: rowId,
|
||||||
|
}),
|
||||||
|
) === false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,11 +1,9 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdown';
|
|
||||||
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||||
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
|
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
|
||||||
import { ViewBar } from '@/views/components/ViewBar';
|
import { ViewBar } from '@/views/components/ViewBar';
|
||||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -26,13 +24,7 @@ export const SignInBackgroundMockContainer = () => {
|
|||||||
<ViewBar
|
<ViewBar
|
||||||
viewBarId={viewBarId}
|
viewBarId={viewBarId}
|
||||||
onCurrentViewChange={async () => {}}
|
onCurrentViewChange={async () => {}}
|
||||||
optionsDropdownButton={
|
optionsDropdownButton={<></>}
|
||||||
<RecordIndexOptionsDropdown
|
|
||||||
recordIndexId={recordIndexId}
|
|
||||||
objectNameSingular={objectNameSingular}
|
|
||||||
viewType={ViewType.Table}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<SignInBackgroundMockContainerEffect
|
<SignInBackgroundMockContainerEffect
|
||||||
objectNamePlural={objectNamePlural}
|
objectNamePlural={objectNamePlural}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|||||||
import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
import { formatFieldMetadataItemsAsFilterDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
|
import { formatFieldMetadataItemsAsSortDefinitions } from '@/object-metadata/utils/formatFieldMetadataItemsAsSortDefinitions';
|
||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
import { turnFiltersIntoQueryFilter } from '@/object-record/record-filter/utils/turnFiltersIntoQueryFilter';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||||
@ -35,7 +35,7 @@ export const getQueryVariablesFromView = ({
|
|||||||
fields: fieldMetadataItems,
|
fields: fieldMetadataItems,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filter = turnObjectDropdownFilterIntoQueryFilter(
|
const filter = turnFiltersIntoQueryFilter(
|
||||||
mapViewFiltersToFilters(viewFilters, filterDefinitions),
|
mapViewFiltersToFilters(viewFilters, filterDefinitions),
|
||||||
objectMetadataItem?.fields ?? [],
|
objectMetadataItem?.fields ?? [],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
|
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
|
||||||
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
|
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
|
||||||
|
import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect';
|
||||||
|
import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect';
|
||||||
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
|
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
|
||||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||||
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
|
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
|
||||||
@ -71,6 +73,8 @@ export const RecordIndexPage = () => {
|
|||||||
<RecordIndexPageHeader />
|
<RecordIndexPageHeader />
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<StyledIndexContainer>
|
<StyledIndexContainer>
|
||||||
|
<RecordIndexContainerContextStoreObjectMetadataEffect />
|
||||||
|
<RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect />
|
||||||
<RecordIndexContainer />
|
<RecordIndexContainer />
|
||||||
</StyledIndexContainer>
|
</StyledIndexContainer>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
import { contextStoreNumberOfSelectedRecordsState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsState';
|
||||||
|
import { contextStoreTargetedRecordsRuleState } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@ -10,8 +11,8 @@ export const RecordShowPageContextStoreEffect = ({
|
|||||||
}: {
|
}: {
|
||||||
recordId: string;
|
recordId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const setContextStoreTargetedRecordIds = useSetRecoilState(
|
const setContextStoreTargetedRecordsRule = useSetRecoilState(
|
||||||
contextStoreTargetedRecordIdsState,
|
contextStoreTargetedRecordsRuleState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const setContextStoreCurrentObjectMetadataId = useSetRecoilState(
|
const setContextStoreCurrentObjectMetadataId = useSetRecoilState(
|
||||||
@ -24,19 +25,32 @@ export const RecordShowPageContextStoreEffect = ({
|
|||||||
objectNameSingular: objectNameSingular ?? '',
|
objectNameSingular: objectNameSingular ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setContextStoreNumberOfSelectedRecords = useSetRecoilState(
|
||||||
|
contextStoreNumberOfSelectedRecordsState,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContextStoreTargetedRecordIds([recordId]);
|
setContextStoreTargetedRecordsRule({
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [recordId],
|
||||||
|
});
|
||||||
setContextStoreCurrentObjectMetadataId(objectMetadataItem?.id);
|
setContextStoreCurrentObjectMetadataId(objectMetadataItem?.id);
|
||||||
|
setContextStoreNumberOfSelectedRecords(1);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setContextStoreTargetedRecordIds([]);
|
setContextStoreTargetedRecordsRule({
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
});
|
||||||
setContextStoreCurrentObjectMetadataId(null);
|
setContextStoreCurrentObjectMetadataId(null);
|
||||||
|
setContextStoreNumberOfSelectedRecords(0);
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
recordId,
|
recordId,
|
||||||
setContextStoreTargetedRecordIds,
|
setContextStoreTargetedRecordsRule,
|
||||||
setContextStoreCurrentObjectMetadataId,
|
setContextStoreCurrentObjectMetadataId,
|
||||||
objectMetadataItem?.id,
|
objectMetadataItem?.id,
|
||||||
|
setContextStoreNumberOfSelectedRecords,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
export const RecordShowPageEffect = ({ recordId }: { recordId: string }) => {
|
|
||||||
const setContextStoreTargetedRecordIds = useSetRecoilState(
|
|
||||||
contextStoreTargetedRecordIdsState,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setContextStoreTargetedRecordIds([recordId]);
|
|
||||||
}, [recordId, setContextStoreTargetedRecordIds]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
||||||
|
import {
|
||||||
|
ContextStoreTargetedRecordsRule,
|
||||||
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
} from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
|
||||||
|
export const JestContextStoreSetter = ({
|
||||||
|
contextStoreTargetedRecordsRule = {
|
||||||
|
mode: 'selection',
|
||||||
|
selectedRecordIds: [],
|
||||||
|
},
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular = '',
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule;
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular?: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}) => {
|
||||||
|
const setContextStoreTargetedRecordsRule = useSetRecoilState(
|
||||||
|
contextStoreTargetedRecordsRuleState,
|
||||||
|
);
|
||||||
|
const setContextStoreCurrentObjectMetadataId = useSetRecoilState(
|
||||||
|
contextStoreCurrentObjectMetadataIdState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular: contextStoreCurrentObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const contextStoreCurrentObjectMetadataId = objectMetadataItem.id;
|
||||||
|
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
setContextStoreTargetedRecordsRule(contextStoreTargetedRecordsRule);
|
||||||
|
setContextStoreCurrentObjectMetadataId(contextStoreCurrentObjectMetadataId);
|
||||||
|
setIsLoaded(true);
|
||||||
|
}, [
|
||||||
|
setContextStoreTargetedRecordsRule,
|
||||||
|
setContextStoreCurrentObjectMetadataId,
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
contextStoreCurrentObjectMetadataId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return isLoaded ? <>{children}</> : null;
|
||||||
|
};
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextStoreTargetedRecordsRuleState';
|
||||||
|
import { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { MutableSnapshot } from 'recoil';
|
||||||
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
|
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||||
|
|
||||||
|
export const getJestMetadataAndApolloMocksAndContextStoreWrapper = ({
|
||||||
|
apolloMocks,
|
||||||
|
onInitializeRecoilSnapshot,
|
||||||
|
contextStoreTargetedRecordsRule,
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular,
|
||||||
|
}: {
|
||||||
|
apolloMocks:
|
||||||
|
| readonly MockedResponse<Record<string, any>, Record<string, any>>[]
|
||||||
|
| undefined;
|
||||||
|
onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void;
|
||||||
|
contextStoreTargetedRecordsRule?: ContextStoreTargetedRecordsRule;
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular?: string;
|
||||||
|
}) => {
|
||||||
|
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||||
|
apolloMocks,
|
||||||
|
onInitializeRecoilSnapshot,
|
||||||
|
});
|
||||||
|
return ({ children }: { children: ReactNode }) => (
|
||||||
|
<Wrapper>
|
||||||
|
<JestContextStoreSetter
|
||||||
|
contextStoreTargetedRecordsRule={contextStoreTargetedRecordsRule}
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular={
|
||||||
|
contextStoreCurrentObjectMetadataNameSingular
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</JestContextStoreSetter>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user