diff --git a/packages/client/src/v2-events/features/events/actions/collect-certificate/Pages.tsx b/packages/client/src/v2-events/features/events/actions/collect-certificate/Pages.tsx new file mode 100644 index 00000000000..caff414c5ac --- /dev/null +++ b/packages/client/src/v2-events/features/events/actions/collect-certificate/Pages.tsx @@ -0,0 +1,116 @@ +/* + * 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 { useNavigate } from 'react-router-dom' +import { + useTypedParams, + useTypedSearchParams +} from 'react-router-typesafe-routes/dom' +import { v4 as uuid } from 'uuid' +import { ActionType, getCurrentEventState } from '@opencrvs/commons/client' +import { useEvents } from '@client/v2-events//features/events/useEvents/useEvents' +import { Pages as PagesComponent } from '@client/v2-events/features/events/components/Pages' +import { useEventConfiguration } from '@client/v2-events/features/events/useEventConfiguration' +import { useEventFormNavigation } from '@client/v2-events/features/events/useEventFormNavigation' +import { ROUTES } from '@client/v2-events/routes' +import { useEventFormData } from '@client/v2-events/features/events/useEventFormData' +import { FormLayout } from '@client/v2-events/layouts/form' + +export function Pages() { + const { eventId, pageId } = useTypedParams( + ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.PAGES + ) + const [searchParams] = useTypedSearchParams( + ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.PAGES + ) + const setFormValues = useEventFormData((state) => state.setFormValues) + const formEventId = useEventFormData((state) => state.eventId) + const form = useEventFormData((state) => state.formValues) + const navigate = useNavigate() + const events = useEvents() + const { modal, goToHome } = useEventFormNavigation() + + const [event] = events.getEvent.useSuspenseQuery(eventId) + const currentState = getCurrentEventState(event) + + useEffect(() => { + if (formEventId !== event.id) { + setFormValues(event.id, currentState.data) + } + }, [currentState.data, event.id, formEventId, setFormValues]) + + const { eventConfiguration: configuration } = useEventConfiguration( + event.type + ) + const formPages = configuration.actions + .find((action) => action.type === ActionType.COLLECT_CERTIFICATE) + ?.forms.find((form) => form.active)?.pages + + if (!formPages) { + throw new Error('Form configuration not found for type: ' + event.type) + } + + const currentPageId = + formPages.find((p) => p.id === pageId)?.id || formPages[0]?.id + + if (!currentPageId) { + throw new Error('Form does not have any pages') + } + + useEffect(() => { + if (pageId !== currentPageId) { + navigate( + ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.PAGES.buildPath({ + eventId, + pageId: currentPageId + }), + { replace: true } + ) + } + }, [pageId, currentPageId, navigate, eventId]) + + return ( + { + events.actions.collectCertificate.mutate({ + eventId: event.id, + data: form, + transactionId: uuid(), + draft: true + }) + goToHome() + }} + > + {modal} + + navigate( + ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.PAGES.buildPath({ + eventId, + pageId: nextPageId + }) + ) + } + onSubmit={() => + navigate( + ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.REVIEW.buildPath({ eventId }) + ) + } + /> + + ) +} diff --git a/packages/client/src/v2-events/features/events/actions/collect-certificate/Review.tsx b/packages/client/src/v2-events/features/events/actions/collect-certificate/Review.tsx new file mode 100644 index 00000000000..682200f13b0 --- /dev/null +++ b/packages/client/src/v2-events/features/events/actions/collect-certificate/Review.tsx @@ -0,0 +1,151 @@ +/* + * 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 { defineMessages } from 'react-intl' +import { useNavigate } from 'react-router-dom' +import { v4 as uuid } from 'uuid' +import { useTypedParams } from 'react-router-typesafe-routes/dom' +import { getCurrentEventState, ActionType } from '@opencrvs/commons/client' +import { ROUTES } from '@client/v2-events/routes' +import { useEvents } from '@client/v2-events/features/events/useEvents/useEvents' +import { Review as ReviewComponent } from '@client/v2-events/features/events/components/Review' +import { useModal } from '@client/v2-events/hooks/useModal' +import { useEventFormNavigation } from '@client/v2-events/features/events/useEventFormNavigation' +import { useEventConfiguration } from '@client/v2-events/features/events/useEventConfiguration' +import { useEventFormData } from '@client/v2-events/features/events/useEventFormData' +import { FormLayout } from '@client/v2-events/layouts/form' + +const messages = defineMessages({ + registerActionTitle: { + id: 'registerAction.title', + defaultMessage: 'Register member', + description: 'The title for register action' + }, + registerActionDescription: { + id: 'registerAction.description', + defaultMessage: + 'By clicking register, you confirm that the information entered is correct and the member can be registered.', + description: 'The description for register action' + }, + registerActionDeclare: { + id: 'registerAction.Declare', + defaultMessage: 'Register', + description: 'The label for declare button of register action' + } +}) + +/** + * + * Preview of event to be registered. + */ +export function Review() { + const { eventId } = useTypedParams(ROUTES.V2.EVENTS.COLLECT_CERTIFICATE) + const events = useEvents() + const [modal, openModal] = useModal() + const navigate = useNavigate() + const { goToHome } = useEventFormNavigation() + const collectCertificateMutation = events.actions.collectCertificate + + const [event] = events.getEvent.useSuspenseQuery(eventId) + + const { eventConfiguration: config } = useEventConfiguration(event.type) + + if (!config) { + throw new Error('Event configuration not found with type: ' + event.type) + } + + const { forms: formConfigs } = config.actions.filter( + (action) => action.type === ActionType.COLLECT_CERTIFICATE + )[0] + + const setFormValues = useEventFormData((state) => state.setFormValues) + const getFormValues = useEventFormData((state) => state.getFormValues) + + useEffect(() => { + setFormValues(eventId, getCurrentEventState(event).data) + }, [event, eventId, setFormValues]) + + const form = getFormValues(eventId) + + async function handleEdit({ + pageId, + fieldId + }: { + pageId: string + fieldId?: string + }) { + const confirmedEdit = await openModal((close) => ( + + )) + + if (confirmedEdit) { + navigate( + ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.PAGES.buildPath( + { pageId, eventId }, + { + from: 'review' + }, + fieldId + ) + ) + } + return + } + + async function handleRegistration() { + const confirmedRegistration = await openModal((close) => ( + + )) + if (confirmedRegistration) { + collectCertificateMutation.mutate({ + eventId: event.id, + data: form, + transactionId: uuid() + }) + + goToHome() + } + } + + return ( + { + events.actions.collectCertificate.mutate({ + eventId: event.id, + data: form, + transactionId: uuid(), + draft: true + }) + goToHome() + }} + > + + + {modal} + + + ) +} diff --git a/packages/client/src/v2-events/features/events/actions/collect-certificate/index.ts b/packages/client/src/v2-events/features/events/actions/collect-certificate/index.ts new file mode 100644 index 00000000000..8d964f211e4 --- /dev/null +++ b/packages/client/src/v2-events/features/events/actions/collect-certificate/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { Review } from './Review' +import { Pages } from './Pages' + +export { Review, Pages } diff --git a/packages/client/src/v2-events/features/events/useEvents/procedures/action.ts b/packages/client/src/v2-events/features/events/useEvents/procedures/action.ts index 33f7d9cea03..92287fd9624 100644 --- a/packages/client/src/v2-events/features/events/useEvents/procedures/action.ts +++ b/packages/client/src/v2-events/features/events/useEvents/procedures/action.ts @@ -51,12 +51,14 @@ type Mutation = | typeof api.event.actions.notify | typeof api.event.actions.register | typeof api.event.actions.validate + | typeof api.event.actions.collectCertificate type Procedure = | typeof utils.event.actions.declare | typeof utils.event.actions.notify | typeof utils.event.actions.register | typeof utils.event.actions.validate + | typeof utils.event.actions.collectCertificate /* * This makes sure that if you are offline and do @@ -164,6 +166,15 @@ utils.event.actions.validate.setMutationDefaults(({ canonicalMutationFn }) => ({ onSuccess: updateLocalEvent })) +utils.event.actions.collectCertificate.setMutationDefaults( + ({ canonicalMutationFn }) => ({ + retry: true, + retryDelay: 10000, + mutationFn: waitUntilEventIsCreated(canonicalMutationFn), + onSuccess: updateLocalEvent + }) +) + export function useEventAction

( procedure: P, mutation: M diff --git a/packages/client/src/v2-events/features/events/useEvents/useEvents.ts b/packages/client/src/v2-events/features/events/useEvents/useEvents.ts index f03e50470ba..2328869e05f 100644 --- a/packages/client/src/v2-events/features/events/useEvents/useEvents.ts +++ b/packages/client/src/v2-events/features/events/useEvents/useEvents.ts @@ -118,6 +118,10 @@ export function useEvents() { register: useEventAction( utils.event.actions.register, api.event.actions.register + ), + collectCertificate: useEventAction( + utils.event.actions.collectCertificate, + api.event.actions.collectCertificate ) } } diff --git a/packages/client/src/v2-events/layouts/form/index.tsx b/packages/client/src/v2-events/layouts/form/index.tsx index 69f61ac5915..f5ac0ff7ba2 100644 --- a/packages/client/src/v2-events/layouts/form/index.tsx +++ b/packages/client/src/v2-events/layouts/form/index.tsx @@ -21,6 +21,7 @@ import { FormHeader } from './FormHeader' type AllowedRoute = | typeof ROUTES.V2.EVENTS.REGISTER | typeof ROUTES.V2.EVENTS.DECLARE + | typeof ROUTES.V2.EVENTS.COLLECT_CERTIFICATE /** * Layout for form and review pages. diff --git a/packages/client/src/v2-events/routes/config.tsx b/packages/client/src/v2-events/routes/config.tsx index 1742bb74634..3c74e3f5c70 100644 --- a/packages/client/src/v2-events/routes/config.tsx +++ b/packages/client/src/v2-events/routes/config.tsx @@ -15,6 +15,7 @@ import { Debug } from '@client/v2-events/features/debug/debug' import * as Declare from '@client/v2-events/features/events/actions/declare' import { DeleteEvent } from '@client/v2-events/features/events/actions/delete' import * as Register from '@client/v2-events/features/events/actions/register' +import * as CollectCertificate from '@client/v2-events/features/events/actions/collect-certificate' import { ValidateEvent } from '@client/v2-events/features/events/actions/validate' import { EventSelection } from '@client/v2-events/features/events/EventSelection' import { EventOverviewIndex } from '@client/v2-events/features/workqueues/EventOverview/EventOverview' @@ -108,6 +109,24 @@ export const routesConfig = { element: } ] + }, + { + path: ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.path, + element: , + children: [ + { + index: true, + element: + }, + { + path: ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.PAGES.path, + element: + }, + { + path: ROUTES.V2.EVENTS.COLLECT_CERTIFICATE.REVIEW.path, + element: + } + ] } ] } diff --git a/packages/client/src/v2-events/routes/routes.ts b/packages/client/src/v2-events/routes/routes.ts index 8d27b5cce30..c4baeca4a4f 100644 --- a/packages/client/src/v2-events/routes/routes.ts +++ b/packages/client/src/v2-events/routes/routes.ts @@ -61,7 +61,23 @@ export const ROUTES = { ), VALIDATE: route('validate/:eventId', { params: { eventId: string().defined() } - }) + }), + COLLECT_CERTIFICATE: route( + 'collect-certificates/:eventId', + { + params: { eventId: string().defined() } + }, + { + REVIEW: route('review'), + PAGES: route('pages/:pageId', { + params: { pageId: string() }, + searchParams: { + from: string() + }, + hash: hashValues() + }) + } + ) } ), WORKQUEUE: route('workqueue', { diff --git a/packages/commons/src/events/ActionConfig.ts b/packages/commons/src/events/ActionConfig.ts index 8d739252dc9..31461f90db0 100644 --- a/packages/commons/src/events/ActionConfig.ts +++ b/packages/commons/src/events/ActionConfig.ts @@ -34,6 +34,7 @@ export const ActionType = { NOTIFY: 'NOTIFY', DECLARE: 'DECLARE', DELETE: 'DELETE', + COLLECT_CERTIFICATE: 'COLLECT_CERTIFICATE', CUSTOM: 'CUSTOM' } as const @@ -67,6 +68,12 @@ const DeleteConfig = ActionConfigBase.merge( }) ) +const CollectCertificateActionConfig = ActionConfigBase.merge( + z.object({ + type: z.literal(ActionType.COLLECT_CERTIFICATE) + }) +) + const CustomConfig = ActionConfigBase.merge( z.object({ type: z.literal(ActionType.CUSTOM) @@ -79,6 +86,7 @@ export const ActionConfig = z.discriminatedUnion('type', [ ValidateConfig, RegisterConfig, DeleteConfig, + CollectCertificateActionConfig, CustomConfig ]) diff --git a/packages/commons/src/events/ActionDocument.ts b/packages/commons/src/events/ActionDocument.ts index 964a0c403d4..cb9db859612 100644 --- a/packages/commons/src/events/ActionDocument.ts +++ b/packages/commons/src/events/ActionDocument.ts @@ -67,6 +67,12 @@ const NotifiedAction = ActionBase.merge( }) ) +const CollectCertificateAction = ActionBase.merge( + z.object({ + type: z.literal(ActionType.COLLECT_CERTIFICATE) + }) +) + const CustomAction = ActionBase.merge( z.object({ type: z.literal(ActionType.CUSTOM) @@ -81,6 +87,7 @@ export const ActionDocument = z.discriminatedUnion('type', [ DeclareAction, AssignedAction, UnassignedAction, + CollectCertificateAction, CustomAction ]) diff --git a/packages/commons/src/events/ActionInput.ts b/packages/commons/src/events/ActionInput.ts index a1a3dd3cbc1..b4b86745866 100644 --- a/packages/commons/src/events/ActionInput.ts +++ b/packages/commons/src/events/ActionInput.ts @@ -56,6 +56,14 @@ export const DeclareActionInput = BaseActionInput.merge( }) ) +export const CollectCertificateActionInput = BaseActionInput.merge( + z.object({ + type: z + .literal(ActionType.COLLECT_CERTIFICATE) + .default(ActionType.COLLECT_CERTIFICATE) + }) +) + export type DeclareActionInput = z.infer const AssignActionInput = BaseActionInput.merge( @@ -85,7 +93,8 @@ export const ActionInput = z.discriminatedUnion('type', [ NotifyActionInput, DeclareActionInput, AssignActionInput, - UnassignActionInput + UnassignActionInput, + CollectCertificateActionInput ]) export type ActionInput = z.input diff --git a/packages/events/src/router/router.ts b/packages/events/src/router/router.ts index 42318ab6251..5bf40925b88 100644 --- a/packages/events/src/router/router.ts +++ b/packages/events/src/router/router.ts @@ -38,7 +38,8 @@ import { EventInput, NotifyActionInput, RegisterActionInput, - ValidateActionInput + ValidateActionInput, + CollectCertificateActionInput } from '@opencrvs/commons/events' const validateEventType = ({ @@ -163,6 +164,16 @@ export const appRouter = router({ token: options.ctx.token } ) + }), + collectCertificate: publicProcedure + .input(CollectCertificateActionInput) + .mutation((options) => { + return addAction(options.input, { + eventId: options.input.eventId, + createdBy: options.ctx.user.id, + createdAtLocation: options.ctx.user.primaryOfficeId, + token: options.ctx.token + }) }) }) }),