diff --git a/apps/data-studio/src/components/LibraryHome/LibraryHome.tsx b/apps/data-studio/src/components/LibraryHome/LibraryHome.tsx index f8eab7bd8..d05ace631 100644 --- a/apps/data-studio/src/components/LibraryHome/LibraryHome.tsx +++ b/apps/data-studio/src/components/LibraryHome/LibraryHome.tsx @@ -148,7 +148,7 @@ const LibraryHome: FunctionComponent = ({library}) => { type: 'library', libraryId: library }} - defaultActionsForItem={['edit', 'deactivate']} + defaultActionsForItem={['edit', 'remove']} defaultPrimaryActions={['create']} itemActions={[ { diff --git a/libs/ui/src/components/Explorer/Explorer.test.tsx b/libs/ui/src/components/Explorer/Explorer.test.tsx index 5c9d44355..81cbbac74 100644 --- a/libs/ui/src/components/Explorer/Explorer.test.tsx +++ b/libs/ui/src/components/Explorer/Explorer.test.tsx @@ -708,6 +708,36 @@ describe('Explorer', () => { expect(mockDeactivateMutation).toHaveBeenCalled(); }); + test('Should be able to delete a linked record with default actions', async () => { + const mockDeleteValueMutation = jest.fn().mockReturnValue({ + data: { + deleteValue: [ + { + id_value: 0, + linkValue: mockRecords[0] + } + ] + } + }); + + jest.spyOn(gqlTypes, 'useDeleteValueMutation').mockImplementation(() => [ + mockDeleteValueMutation, + {loading: false, called: false, client: {} as any, reset: jest.fn()} + ]); + + render(, { + mocks: [ExplorerLinkAttributeQueryMock] + }); + + const [_columnNameRow, firstRecordRow] = await screen.findAllByRole('row'); + await user.click(within(firstRecordRow).getByRole('button', {name: 'explorer.delete-item'})); + + expect(screen.getByText('record_edition.delete_link_confirm')).toBeVisible(); + await user.click(screen.getByText('global.submit')); + + expect(mockDeleteValueMutation).toHaveBeenCalled(); + }); + test('Should be able to edit a record with default actions', async () => { render(); @@ -972,7 +1002,7 @@ describe('Explorer', () => { describe('Entrypoint type link', () => { test('Should display the list of linked records', async () => { const actionCallback = jest.fn(); - const {container} = render( + render( ; + defaultActionsForItem?: Array<'edit' | 'remove'>; defaultPrimaryActions?: Array<'create'>; defaultViewSettings?: DefaultViewSettings; } @@ -63,7 +63,7 @@ export const Explorer: FunctionComponent = ({ primaryActions, title, noPagination, - defaultActionsForItem = ['edit', 'deactivate'], + defaultActionsForItem = ['edit', 'remove'], defaultPrimaryActions = ['create'], defaultViewSettings }) => { @@ -90,9 +90,12 @@ export const Explorer: FunctionComponent = ({ skip: viewSettingsLoading }); // TODO: refresh when go back on page - const {deactivateAction} = useDeactivateAction({ - isEnabled: isNotEmpty(defaultActionsForItem) && defaultActionsForItem.includes('deactivate') - }); + const {removeAction} = useRemoveAction( + { + isEnabled: isNotEmpty(defaultActionsForItem) && defaultActionsForItem.includes('remove') + }, + entrypoint + ); const {editAction, editModal} = useEditAction({ isEnabled: isNotEmpty(defaultActionsForItem) && defaultActionsForItem.includes('edit') @@ -151,7 +154,7 @@ export const Explorer: FunctionComponent = ({ } : undefined } - itemActions={[editAction, deactivateAction, ...(itemActions ?? emptyArray)].filter(Boolean)} + itemActions={[editAction, removeAction, ...(itemActions ?? emptyArray)].filter(Boolean)} /> )} diff --git a/libs/ui/src/components/Explorer/useDeactivateAction.tsx b/libs/ui/src/components/Explorer/useDeactivateAction.tsx deleted file mode 100644 index dd444697d..000000000 --- a/libs/ui/src/components/Explorer/useDeactivateAction.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06 -// This file is released under LGPL V3 -// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt -import {FaTrash} from 'react-icons/fa'; -import {KitModal} from 'aristid-ds'; -import {useDeactivateRecordsMutation} from '_ui/_gqlTypes'; -import {useSharedTranslation} from '_ui/hooks/useSharedTranslation'; -import {ActionHook, IItemAction} from './_types'; -import {useMemo} from 'react'; - -/** - * Hook used to get the action for `` component. - * - * When the mutation for deactivation is done, the Apollo cache will be clean (`Record` and `RecordIdentity`) - * from deactivated record. - * - * @param isEnabled - whether the action is present - */ -export const useDeactivateAction = ({isEnabled}: ActionHook) => { - const {t} = useSharedTranslation(); - - const [deactivateRecordsMutation] = useDeactivateRecordsMutation({ - update(cache, deactivatedRecords) { - deactivatedRecords.data?.deactivateRecords.forEach(record => { - cache.evict({ - id: cache.identify(record) - }); - }); - cache.gc(); - } - }); - - const _deactivateAction: IItemAction = useMemo( - () => ({ - label: t('explorer.deactivate-item'), - icon: , - isDanger: true, - callback: ({itemId, libraryId}) => - KitModal.confirm({ - type: 'confirm', - dangerConfirm: true, - content: t('records_deactivation.confirm_one') ?? undefined, - okText: t('global.submit') ?? undefined, - cancelText: t('global.cancel') ?? undefined, - onOk: () => - deactivateRecordsMutation({ - variables: { - libraryId, - recordsIds: [itemId] - } - }) - }) - }), - [t, deactivateRecordsMutation] - ); - - return { - deactivateAction: isEnabled ? _deactivateAction : null - }; -}; diff --git a/libs/ui/src/components/Explorer/useRemoveAction.tsx b/libs/ui/src/components/Explorer/useRemoveAction.tsx new file mode 100644 index 000000000..aa7570923 --- /dev/null +++ b/libs/ui/src/components/Explorer/useRemoveAction.tsx @@ -0,0 +1,101 @@ +// Copyright LEAV Solutions 2017 until 2023/11/05, Copyright Aristid from 2023/11/06 +// This file is released under LGPL V3 +// License text available at https://www.gnu.org/licenses/lgpl-3.0.txt +import {FaTrash} from 'react-icons/fa'; +import {KitModal} from 'aristid-ds'; +import {useDeactivateRecordsMutation, useDeleteValueMutation} from '_ui/_gqlTypes'; +import {useSharedTranslation} from '_ui/hooks/useSharedTranslation'; +import {ActionHook, Entrypoint, IEntrypointLink, IItemAction} from './_types'; +import {useMemo} from 'react'; +import {useValuesCacheUpdate} from '_ui/hooks'; + +/** + * Hook used to get the action for `` component. + * + * When the mutation for removing is done, the Apollo cache will be clean (`Record` and `RecordIdentity`) + * from removed record. + * + * @param isEnabled - whether the action is present + */ +export const useRemoveAction = ( + {isEnabled}: ActionHook, + entrypoint: Entrypoint +): {removeAction: IItemAction | null} => { + const {t} = useSharedTranslation(); + const updateValuesCache = useValuesCacheUpdate(); + + const [deactivateRecordsMutation] = useDeactivateRecordsMutation({ + update(cache, deactivatedRecords) { + deactivatedRecords.data?.deactivateRecords.forEach(record => { + cache.evict({ + id: cache.identify(record) + }); + }); + cache.gc(); + } + }); + + const [deleteRecordLinkMutation] = useDeleteValueMutation({ + update: (_, deletedRecord) => { + const parentRecord = { + id: (entrypoint as IEntrypointLink).parentRecordId, + library: { + id: (entrypoint as IEntrypointLink).parentLibraryId + } + }; + updateValuesCache(parentRecord, deletedRecord.data?.deleteValue ?? []); + } + }); + + const _removeAction: IItemAction = useMemo( + () => ({ + label: entrypoint.type === 'library' ? t('explorer.deactivate-item') : t('explorer.delete-item'), + icon: , + isDanger: true, + callback: ({itemId, libraryId, id_value}) => { + KitModal.confirm({ + type: 'confirm', + dangerConfirm: true, + content: + entrypoint.type === 'library' + ? t('records_deactivation.confirm_one') + : t('record_edition.delete_link_confirm'), + okText: t('global.submit') ?? undefined, + cancelText: t('global.cancel') ?? undefined, + onOk: () => { + switch (entrypoint.type) { + case 'library': + deactivateRecordsMutation({ + variables: { + libraryId, + recordsIds: [itemId] + } + }); + break; + case 'link': + deleteRecordLinkMutation({ + variables: { + library: entrypoint.parentLibraryId, + attribute: entrypoint.linkAttributeId, + recordId: entrypoint.parentRecordId, + value: { + payload: itemId, + id_value + } + } + }); + break; + default: + return; + } + } + }); + } + }), + [t, deactivateRecordsMutation, deleteRecordLinkMutation, entrypoint.type] + ); + + return { + removeAction: isEnabled ? _removeAction : null + }; +}; diff --git a/libs/ui/src/locales/en/shared.json b/libs/ui/src/locales/en/shared.json index 842d44aed..a785ff3cc 100644 --- a/libs/ui/src/locales/en/shared.json +++ b/libs/ui/src/locales/en/shared.json @@ -405,6 +405,7 @@ "add_value_link": "Add link", "delete_value": "Delete value", "delete_value_confirm": "Do you really want to delete this value?", + "delete_link_confirm": "Do you really want to delete this link?", "whoAmI": "Information", "no_value": "No value", "value_details": "Value details", @@ -626,7 +627,8 @@ "create-one": "Create", "actions": "Actions", "more-actions": "Show more", - "deactivate-item": "Deactivate item", + "deactivate-item": "Deactivate", + "delete-item": "Delete", "edit-item": "Edit item", "settings": "Settings", "back": "Back", diff --git a/libs/ui/src/locales/fr/shared.json b/libs/ui/src/locales/fr/shared.json index ed1d21f07..fad741498 100644 --- a/libs/ui/src/locales/fr/shared.json +++ b/libs/ui/src/locales/fr/shared.json @@ -405,6 +405,7 @@ "add_value_link": "Ajouter une liaison", "delete_value": "Supprimer cette valeur", "delete_value_confirm": "Êtes-vous sûr de vouloir supprimer cette valeur?", + "delete_link_confirm": "Êtes-vous sûr de vouloir supprimer cette liaison?", "whoAmI": "Informations", "no_value": "Pas de valeur", "value_details": "Infos valeur", @@ -626,7 +627,8 @@ "create-one": "Créer", "actions": "Actions", "more-actions": "Voir plus", - "deactivate-item": "Désactiver l’élément", + "deactivate-item": "Désactiver", + "delete-item": "Supprimer", "edit-item": "Éditer l’élément", "settings": "Paramètres", "back": "Retour",