From 4ed93eb2fa052a014554b20b38468e80d121885c Mon Sep 17 00:00:00 2001 From: Riku Rouvila Date: Wed, 8 Jan 2025 00:05:55 +0900 Subject: [PATCH] Events v2: implement delete action for action menu, make sure delete works in the form three dot menu (#8297) --- .../features/events/actions/delete/index.tsx | 30 ++++++++++++++++ .../events/useEventFormNavigation.tsx | 1 + .../events/useEvents/procedures/delete.ts | 15 +++++++- .../EventOverview/components/ActionMenu.tsx | 35 +++++++++++-------- .../client/src/v2-events/routes/config.tsx | 5 +++ .../client/src/v2-events/routes/routes.ts | 1 + packages/client/src/v2-events/trpc.tsx | 17 ++++++--- packages/commons/src/events/ActionConfig.ts | 8 +++++ packages/events/src/service/events.ts | 8 +++-- 9 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 packages/client/src/v2-events/features/events/actions/delete/index.tsx diff --git a/packages/client/src/v2-events/features/events/actions/delete/index.tsx b/packages/client/src/v2-events/features/events/actions/delete/index.tsx new file mode 100644 index 0000000000..6a1b5e4af4 --- /dev/null +++ b/packages/client/src/v2-events/features/events/actions/delete/index.tsx @@ -0,0 +1,30 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * OpenCRVS is also distributed under the terms of the Civil Registration + * & Healthcare Disclaimer located at http://opencrvs.org/license. + * + * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. + */ + +import React, { useEffect } from 'react' +import { useTypedParams } from 'react-router-typesafe-routes/dom' +import { useNavigate } from 'react-router-dom' +import { ROUTES } from '@client/v2-events/routes' +import { useEvents } from '@client/v2-events/features/events/useEvents/useEvents' + +export function DeleteEvent() { + const { eventId } = useTypedParams(ROUTES.V2.EVENTS.DELETE) + const navigate = useNavigate() + const events = useEvents() + const deleteEvent = events.deleteEvent + + useEffect(() => { + deleteEvent.mutate({ eventId }) + navigate(ROUTES.V2.path) + }, [deleteEvent, eventId, navigate]) + + return
+} diff --git a/packages/client/src/v2-events/features/events/useEventFormNavigation.tsx b/packages/client/src/v2-events/features/events/useEventFormNavigation.tsx index 3c174ac8d9..b7753e427a 100644 --- a/packages/client/src/v2-events/features/events/useEventFormNavigation.tsx +++ b/packages/client/src/v2-events/features/events/useEventFormNavigation.tsx @@ -149,6 +149,7 @@ export function useEventFormNavigation() { if (deleteConfirm) { deleteEvent.mutate({ eventId }) + goToHome() } } diff --git a/packages/client/src/v2-events/features/events/useEvents/procedures/delete.ts b/packages/client/src/v2-events/features/events/useEvents/procedures/delete.ts index 5be0e0ae4a..0a5ef9042a 100644 --- a/packages/client/src/v2-events/features/events/useEvents/procedures/delete.ts +++ b/packages/client/src/v2-events/features/events/useEvents/procedures/delete.ts @@ -13,10 +13,18 @@ import { useMutation } from '@tanstack/react-query' import { getMutationKey } from '@trpc/react-query' import { api, utils } from '@client/v2-events/trpc' +function isTemporaryId(id: string) { + return id.startsWith('tmp-') +} + function waitUntilEventIsCreated( canonicalMutationFn: (params: { eventId: string }) => Promise ): (params: { eventId: string }) => Promise { return async ({ eventId }) => { + if (!isTemporaryId(eventId)) { + return canonicalMutationFn({ eventId: eventId }) + } + const localVersion = utils.event.get.getData(eventId) if (!localVersion || localVersion.id === localVersion.transactionId) { throw new Error('Event that has not been stored yet cannot be deleted') @@ -27,7 +35,12 @@ function waitUntilEventIsCreated( } utils.event.delete.setMutationDefaults(({ canonicalMutationFn }) => ({ - retry: true, + retry: (_, error) => { + if (error.data?.httpStatus === 404 || error.data?.httpStatus === 400) { + return false + } + return true + }, retryDelay: 10000, onSuccess: ({ id }) => { void utils.events.get.invalidate() diff --git a/packages/client/src/v2-events/features/workqueues/EventOverview/components/ActionMenu.tsx b/packages/client/src/v2-events/features/workqueues/EventOverview/components/ActionMenu.tsx index 3ef06f0f4a..e069787761 100644 --- a/packages/client/src/v2-events/features/workqueues/EventOverview/components/ActionMenu.tsx +++ b/packages/client/src/v2-events/features/workqueues/EventOverview/components/ActionMenu.tsx @@ -13,7 +13,7 @@ import React from 'react' import { useIntl } from 'react-intl' import { useNavigate } from 'react-router-dom' -import { validate } from '@opencrvs/commons/client' +import { validate, ActionType } from '@opencrvs/commons/client' import { type ActionConfig } from '@opencrvs/commons' import { CaretDown } from '@opencrvs/components/lib/Icon/all-icons' import { PrimaryButton } from '@opencrvs/components/lib/buttons' @@ -57,21 +57,26 @@ export function ActionMenu({ eventId }: { eventId: string }) { - {configuration.actions.filter(isActionVisible).map((action) => ( - { - if (action.type === 'CREATE' || action.type === 'CUSTOM') { - alert(`Action ${action.type} is not implemented yet.`) - return - } + {configuration.actions.filter(isActionVisible).map((action) => { + return ( + { + if ( + action.type === ActionType.CREATE || + action.type === ActionType.CUSTOM + ) { + alert(`Action ${action.type} is not implemented yet.`) + return + } - navigate(ROUTES.V2.EVENTS[action.type].buildPath({ eventId })) - }} - > - {intl.formatMessage(action.label)} - - ))} + navigate(ROUTES.V2.EVENTS[action.type].buildPath({ eventId })) + }} + > + {intl.formatMessage(action.label)} + + ) + })} diff --git a/packages/client/src/v2-events/routes/config.tsx b/packages/client/src/v2-events/routes/config.tsx index 95b71b7b44..d80b0bbb94 100644 --- a/packages/client/src/v2-events/routes/config.tsx +++ b/packages/client/src/v2-events/routes/config.tsx @@ -19,6 +19,7 @@ import { Debug } from '@client/v2-events/features/debug/debug' import * as Declare from '@client/v2-events/features/events/actions/declare' import * as Register from '@client/v2-events/features/events/actions/register' import { WorkqueueLayout, FormLayout } from '@client/v2-events/layouts' +import { DeleteEvent } from '@client/v2-events/features/events/actions/delete' import { ROUTES } from './routes' /** @@ -63,6 +64,10 @@ export const routesConfig = { path: ROUTES.V2.EVENTS.CREATE.path, element: }, + { + path: ROUTES.V2.EVENTS.DELETE.path, + element: + }, { path: ROUTES.V2.EVENTS.DECLARE.path, element: ( diff --git a/packages/client/src/v2-events/routes/routes.ts b/packages/client/src/v2-events/routes/routes.ts index b71f625039..8d27b5cce3 100644 --- a/packages/client/src/v2-events/routes/routes.ts +++ b/packages/client/src/v2-events/routes/routes.ts @@ -26,6 +26,7 @@ export const ROUTES = { params: { eventId: string().defined() } }), CREATE: route('create'), + DELETE: route('delete/:eventId'), DECLARE: route( 'declare/:eventId', { diff --git a/packages/client/src/v2-events/trpc.tsx b/packages/client/src/v2-events/trpc.tsx index b89a277b88..f7723960b7 100644 --- a/packages/client/src/v2-events/trpc.tsx +++ b/packages/client/src/v2-events/trpc.tsx @@ -15,12 +15,13 @@ import { type PersistedClient, type Persister } from '@tanstack/react-query-persist-client' -import { httpLink, loggerLink } from '@trpc/client' +import { httpLink, loggerLink, TRPCClientError } from '@trpc/client' import { createTRPCQueryUtils, createTRPCReact } from '@trpc/react-query' import React from 'react' import superjson from 'superjson' -import { getToken } from '@client/utils/authUtils' + import { storage } from '@client/storage' +import { getToken } from '@client/utils/authUtils' export const api = createTRPCReact() @@ -89,8 +90,16 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) { maxAge: undefined, buster: 'persisted-indexed-db', dehydrateOptions: { - shouldDehydrateMutation: (mut) => { - return mut.state.status !== 'success' + shouldDehydrateMutation: (mutation) => { + if (mutation.state.status === 'error') { + const error = mutation.state.error + if (error instanceof TRPCClientError && error.data?.httpStatus) { + return !error.data.httpStatus.toString().startsWith('4') + } + return true + } + + return mutation.state.status !== 'success' } } }} diff --git a/packages/commons/src/events/ActionConfig.ts b/packages/commons/src/events/ActionConfig.ts index 18478779d4..4c0230401a 100644 --- a/packages/commons/src/events/ActionConfig.ts +++ b/packages/commons/src/events/ActionConfig.ts @@ -33,6 +33,7 @@ export const ActionType = { DETECT_DUPLICATE: 'DETECT_DUPLICATE', NOTIFY: 'NOTIFY', DECLARE: 'DECLARE', + DELETE: 'DELETE', CUSTOM: 'CUSTOM' } as const @@ -60,6 +61,12 @@ const RegisterConfig = ActionConfigBase.merge( }) ) +const DeleteConfig = ActionConfigBase.merge( + z.object({ + type: z.literal(ActionType.DELETE) + }) +) + const CustomConfig = ActionConfigBase.merge( z.object({ type: z.literal(ActionType.CUSTOM) @@ -71,6 +78,7 @@ export const ActionConfig = z.discriminatedUnion('type', [ DeclareConfig, ValidateConfig, RegisterConfig, + DeleteConfig, CustomConfig ]) diff --git a/packages/events/src/service/events.ts b/packages/events/src/service/events.ts index 60baff78f9..6b40b19a79 100644 --- a/packages/events/src/service/events.ts +++ b/packages/events/src/service/events.ts @@ -39,12 +39,14 @@ async function getEventByTransactionId(transactionId: string) { return document } -class EventNotFoundError extends Error { +class EventNotFoundError extends TRPCError { constructor(id: string) { - super('Event not found with ID: ' + id) + super({ + code: 'NOT_FOUND', + message: `Event not found with ID: ${id}` + }) } } - export async function getEventById(id: string) { const db = await getClient()