From 0084224cbf599387e342b2dfad516a3240423b07 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 7 Oct 2024 11:58:21 +0600 Subject: [PATCH 01/58] update config package to get certificates config from country config --- .../application/applicationConfigHandler.ts | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/packages/config/src/handlers/application/applicationConfigHandler.ts b/packages/config/src/handlers/application/applicationConfigHandler.ts index 798fc5187fb..20b57ba8b75 100644 --- a/packages/config/src/handlers/application/applicationConfigHandler.ts +++ b/packages/config/src/handlers/application/applicationConfigHandler.ts @@ -35,7 +35,7 @@ export default async function configHandler( ) { try { const [certificates, config, systems] = await Promise.all([ - getCertificates(request, h), + getCertificatesConfig(request, h), getApplicationConfig(request, h), getSystems(request, h) ]) @@ -53,7 +53,10 @@ export default async function configHandler( } } -async function getCertificates(request: Hapi.Request, h: Hapi.ResponseToolkit) { +async function getCertificatesConfig( + request: Hapi.Request, + h: Hapi.ResponseToolkit +) { const authToken = getToken(request) const decodedOrError = pipe(authToken, verifyToken) if (decodedOrError._tag === 'Left') { @@ -67,12 +70,18 @@ async function getCertificates(request: Hapi.Request, h: Hapi.ResponseToolkit) { scope.includes(RouteScope.VALIDATE) || scope.includes(RouteScope.NATLSYSADMIN)) ) { - return Promise.all( - (['birth', 'death', 'marriage'] as const).map(async (event) => { - const response = await getEventCertificate(event, getToken(request)) - return response - }) - ) + const url = new URL(`/certificates`, COUNTRY_CONFIG_URL).toString() + + const res = await fetch(url, { + headers: { Authorization: `Bearer ${authToken}` } + }) + + if (!res.ok) { + throw new Error( + `Failed to fetch certificates configuration: ${res.statusText} ${url}` + ) + } + return res.json() } return [] } @@ -86,27 +95,6 @@ async function getConfigFromCountry(authToken?: string) { return res.json() } -async function getEventCertificate( - event: 'birth' | 'death' | 'marriage', - authToken: string -) { - const url = new URL( - `/certificates/${event}.svg`, - env.COUNTRY_CONFIG_URL - ).toString() - - const res = await fetch(url, { - headers: { Authorization: `Bearer ${authToken}` } - }) - - if (!res.ok) { - throw new Error(`Failed to fetch ${event} certificate: ${res.statusText}`) - } - const responseText = await res.text() - - return { svgCode: responseText, event } -} - async function getApplicationConfig( request?: Hapi.Request, h?: Hapi.ResponseToolkit From 306e56eb5fe2d731dc1226ae669a18635c26861f Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 4 Oct 2024 10:00:47 +0600 Subject: [PATCH 02/58] getting certificates config data from config package in client instead of svgcode --- packages/client/src/utils/referenceApi.ts | 52 +++++++++-------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index 341257463b4..d29a2a471b2 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -18,7 +18,7 @@ import { ILocation } from '@client/offline/reducer' import { getToken } from '@client/utils/authUtils' -import { Event, System } from '@client/utils/gateway' +import { System } from '@client/utils/gateway' import { Validator } from '@client/forms/validators' import { IntlShape } from 'react-intl' @@ -77,9 +77,23 @@ interface ILoginBackground { backgroundImage?: string imageFit?: string } -export interface ICertificateTemplateData { - event: Event - svgCode: string +export interface ICertificateConfigData { + id: string + event: string + label: { + id: string + defaultMessage: string + description: string + } + svgUrl: string + fonts: { + [fontName: string]: { + normal: string + bold: string + italics: string + bolditalics: string + } + } } export interface ICurrency { isoCode: string @@ -143,7 +157,7 @@ export interface IApplicationConfig { } export interface IApplicationConfigResponse { config: IApplicationConfig - certificates: ICertificateTemplateData[] + certificates: ICertificateConfigData[] systems: System[] } @@ -250,33 +264,6 @@ async function importHandlebarHelpers(): Promise { return {} } } -async function loadCertificateConfiguration(): Promise { - const url = `${window.config.COUNTRY_CONFIG_URL}/certificate-configuration` - - const res = await fetch(url, { - method: 'GET' - }) - - // for backward compatibility, if the endpoint is unimplemented - if (res.status === 404) { - return { - fonts: { - notosans: { - normal: 'NotoSans-Light.ttf', - bold: 'NotoSans-Regular.ttf', - italics: 'NotoSans-Light.ttf', - bolditalics: 'NotoSans-Regular.ttf' - } - } - } - } - - if (!res.ok) { - throw Error(res.statusText) - } - - return res.json() -} async function loadContent(): Promise { const url = `${window.config.COUNTRY_CONFIG_URL}/content/client` @@ -415,7 +402,6 @@ async function loadFacilities(): Promise { export const referenceApi = { loadLocations, loadFacilities, - loadCertificateConfiguration, loadContent, loadConfig, loadForms, From 6f2ecdf9dc328607022da317a47d18ad6a2f1fe2 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 1 Nov 2024 17:20:35 +0600 Subject: [PATCH 03/58] changing actions and reducers to accomodate new certificate config changes --- packages/client/src/offline/actions.ts | 33 +-------- packages/client/src/offline/reducer.ts | 94 +++----------------------- 2 files changed, 11 insertions(+), 116 deletions(-) diff --git a/packages/client/src/offline/actions.ts b/packages/client/src/offline/actions.ts index 14b5e8b9e0b..5a3733ffd1d 100644 --- a/packages/client/src/offline/actions.ts +++ b/packages/client/src/offline/actions.ts @@ -26,8 +26,7 @@ import { LoadFormsResponse, LoadValidatorsResponse, LoadConditionalsResponse, - LoadHandlebarHelpersResponse, - CertificateConfiguration + LoadHandlebarHelpersResponse } from '@client/utils/referenceApi' import { System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' @@ -116,20 +115,6 @@ type CertificateLoadFailedAction = { payload: Error } -export const CERTIFICATE_CONFIGURATION_LOADED = - 'OFFLINE/CERTIFICATE_CONFIGURATION_LOADED' -type CertificateConfigurationLoadedAction = { - type: typeof CERTIFICATE_CONFIGURATION_LOADED - payload: CertificateConfiguration -} - -export const CERTIFICATE_CONFIGURATION_LOAD_FAILED = - 'OFFLINE/CERTIFICATE_CONFIGURATION_LOAD_FAILED' -type CertificateConfigurationLoadFailedAction = { - type: typeof CERTIFICATE_CONFIGURATION_LOAD_FAILED - payload: Error -} - export const UPDATE_OFFLINE_CONFIG = 'OFFLINE/UPDATE_OFFLINE_CONFIG' as const type ApplicationConfigUpdatedAction = { type: typeof UPDATE_OFFLINE_CONFIG @@ -267,20 +252,6 @@ export const configLoaded = ( payload: payload }) -export const certificateConfigurationLoaded = ( - payload: CertificateConfiguration -): CertificateConfigurationLoadedAction => ({ - type: CERTIFICATE_CONFIGURATION_LOADED, - payload -}) - -export const certificateConfigurationLoadFailed = ( - payload: CertificateConfigurationLoadFailedAction['payload'] -): CertificateConfigurationLoadFailedAction => ({ - type: CERTIFICATE_CONFIGURATION_LOAD_FAILED, - payload -}) - export const configFailed = (error: Error): ApplicationConfigFailedAction => ({ type: APPLICATION_CONFIG_FAILED, payload: error @@ -349,8 +320,6 @@ export type Action = | ApplicationConfigFailedAction | ApplicationConfigUpdatedAction | CertificateLoadFailedAction - | CertificateConfigurationLoadedAction - | CertificateConfigurationLoadFailedAction | UpdateOfflineSystemsAction | IFilterLocationsAction | ReturnType diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index af5d8ca3ccc..aaf26b85e95 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -27,14 +27,14 @@ import { referenceApi, CertificateConfiguration, IFacilitiesDataResponse, - IOfficesDataResponse + IOfficesDataResponse, + ICertificateConfigData } from '@client/utils/referenceApi' import { ILanguage } from '@client/i18n/reducer' import { filterLocations } from '@client/utils/locationUtils' import { Event, System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' import { isOfflineDataLoaded } from './selectors' -import { ISVGTemplate } from '@client/pdfRenderer' import { merge } from 'lodash' import { isNavigatorOnline } from '@client/utils' import { ISerializedForm } from '@client/forms' @@ -105,11 +105,7 @@ export interface IOfflineData { fonts?: CertificateConfiguration['fonts'] // Certificates might not be defined in the case of // a field agent using the app. - certificates?: { - birth: ISVGTemplate - death: ISVGTemplate - marriage: ISVGTemplate - } + certificates?: ICertificateConfigData[] } assets: { logo: string @@ -200,14 +196,6 @@ const CONFIG_CMD = Cmd.run(() => referenceApi.loadConfig(), { failActionCreator: actions.configFailed }) -const CERTIFICATE_CONFIG_CMD = Cmd.run( - () => referenceApi.loadCertificateConfiguration(), - { - successActionCreator: actions.certificateConfigurationLoaded, - failActionCreator: actions.certificateConfigurationLoadFailed - } -) - const CONTENT_CMD = Cmd.run(() => referenceApi.loadContent(), { successActionCreator: actions.contentLoaded, failActionCreator: actions.contentFailed @@ -242,7 +230,6 @@ function getDataLoadingCommands() { FACILITIES_CMD, LOCATIONS_CMD, CONFIG_CMD, - CERTIFICATE_CONFIG_CMD, CONDITIONALS_CMD, VALIDATORS_CMD, HANDLEBARS_CMD, @@ -368,58 +355,14 @@ function reducer( case actions.APPLICATION_CONFIG_LOADED: { const { certificates, config, systems } = action.payload merge(window.config, config) - const birthCertificateTemplate = certificates.find( - ({ event }) => event === Event.Birth - ) - - const deathCertificateTemplate = certificates.find( - ({ event }) => event === Event.Death - ) - - const marriageCertificateTemplate = certificates.find( - ({ event }) => event === Event.Marriage - ) - - let newOfflineData: Partial - if ( - birthCertificateTemplate && - deathCertificateTemplate && - marriageCertificateTemplate - ) { - const certificatesTemplates = { - birth: { - definition: birthCertificateTemplate.svgCode - }, - death: { - definition: deathCertificateTemplate.svgCode - }, - marriage: { - definition: marriageCertificateTemplate.svgCode - } - } - - newOfflineData = { - ...state.offlineData, - config, - systems, - templates: { - ...state.offlineData.templates, - certificates: certificatesTemplates - } - } - } else { - newOfflineData = { - ...state.offlineData, - config, - systems, - - // Field agents do not get certificate templates from the config service. - // Our loading logic depends on certificates being present and the app would load infinitely - // without a value here. - // This is a quickfix for the issue. If done properly, we should amend the "is loading" check - // to not expect certificate templates when a field agent is logged in. - templates: {} + const newOfflineData = { + ...state.offlineData, + config, + systems, + templates: { + ...state.offlineData.templates, + certificates } } @@ -477,23 +420,6 @@ function reducer( ) } - case actions.CERTIFICATE_CONFIGURATION_LOADED: { - return { - ...state, - offlineData: { - ...state.offlineData, - templates: { - ...state.offlineData.templates, - fonts: action.payload.fonts - } - } - } - } - - case actions.CERTIFICATE_CONFIGURATION_LOAD_FAILED: { - return loop(state, delay(CERTIFICATE_CONFIG_CMD, RETRY_TIMEOUT)) - } - /* * Locations */ From be4d5c6aa5520b100f16525ca1b6676a86a140b0 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 4 Oct 2024 10:18:12 +0600 Subject: [PATCH 04/58] certificate template selection is required for collectors to proceed to ReviewCertificate before print in advance --- .../fieldDefinitions/collectorSection.ts | 21 +++++- packages/client/src/navigation/index.ts | 9 ++- packages/client/src/navigation/routes.ts | 2 +- .../src/views/PrintCertificate/PDFUtils.ts | 23 +++--- .../ReviewCertificateAction.tsx | 12 ++-- .../collectorForm/CollectorForm.tsx | 10 ++- .../usePrintableCertificate.ts | 70 ++++++++++++------- 7 files changed, 93 insertions(+), 54 deletions(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index d306f3df5be..406e4c23313 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -36,6 +36,7 @@ import { identityHelperTextMapper, identityNameMapper } from './messages' import { Event } from '@client/utils/gateway' import { IDeclaration } from '@client/declarations' import { issueMessages } from '@client/i18n/messages/issueCertificate' +import { ICertificateConfigData } from '@client/utils/referenceApi' interface INameField { firstNamesField: string @@ -1012,7 +1013,8 @@ const marriageIssueCollectorFormOptions = [ ] function getCertCollectorGroupForEvent( - declaration: IDeclaration + declaration: IDeclaration, + certificates?: ICertificateConfigData[] ): IFormSectionGroup { const informant = (declaration.data.informant.otherInformantType || declaration.data.informant.informantType) as string @@ -1055,13 +1057,26 @@ function getCertCollectorGroupForEvent( initialValue: '', validator: [], options: finalOptions + }, + { + name: 'certTemplateId', + type: 'SELECT_WITH_OPTIONS', + label: certificateMessages.certificateConfirmationTxt, + required: true, + initialValue: '', + validator: [], + options: + certificates + ?.filter((x) => x.event === declaration.event) + .map((x) => ({ label: x.label, value: x.id })) || [] } ] } } export function getCertificateCollectorFormSection( - declaration: IDeclaration + declaration: IDeclaration, + certificates?: ICertificateConfigData[] ): IFormSection { return { id: CertificateSection.Collector, @@ -1069,7 +1084,7 @@ export function getCertificateCollectorFormSection( name: certificateMessages.printCertificate, title: certificateMessages.certificateCollectionTitle, groups: [ - getCertCollectorGroupForEvent(declaration), + getCertCollectorGroupForEvent(declaration, certificates), otherCertCollectorFormGroup(declaration.event), affidavitCertCollectorGroup ] diff --git a/packages/client/src/navigation/index.ts b/packages/client/src/navigation/index.ts index 4dc272f1a92..bbceb637671 100644 --- a/packages/client/src/navigation/index.ts +++ b/packages/client/src/navigation/index.ts @@ -360,11 +360,14 @@ export function goToVerifyCorrector(declarationId: string, corrector: string) { ) } -export function goToReviewCertificate(registrationId: string, event: Event) { +export function goToReviewCertificate( + registrationId: string, + certTemplateId: string +) { return push( formatUrl(REVIEW_CERTIFICATE, { - registrationId: registrationId.toString(), - eventType: event + registrationId, + certTemplateId }), { isNavigatedInsideApp: true } ) diff --git a/packages/client/src/navigation/routes.ts b/packages/client/src/navigation/routes.ts index 35395667abe..9490b3c4dcb 100644 --- a/packages/client/src/navigation/routes.ts +++ b/packages/client/src/navigation/routes.ts @@ -48,7 +48,7 @@ export const ISSUE_COLLECTOR = '/issue/:registrationId/:pageId' export const ISSUE_VERIFY_COLLECTOR = '/issue/check/:registrationId/:eventType/:collector' export const VERIFY_COLLECTOR = - '/print/check/:registrationId/:eventType/:collector' + '/print/check/:registrationId/:certTemplateId/:collector' export const REVIEW_CERTIFICATE = '/review/:registrationId/:eventType' export const PRINT_CERTIFICATE_PAYMENT = diff --git a/packages/client/src/views/PrintCertificate/PDFUtils.ts b/packages/client/src/views/PrintCertificate/PDFUtils.ts index d8d72568334..c9c73983ac9 100644 --- a/packages/client/src/views/PrintCertificate/PDFUtils.ts +++ b/packages/client/src/views/PrintCertificate/PDFUtils.ts @@ -14,11 +14,7 @@ import { createIntl, createIntlCache } from 'react-intl' -import { - AdminStructure, - ILocation, - IOfflineData -} from '@client/offline/reducer' +import { AdminStructure, ILocation } from '@client/offline/reducer' import { IPDFTemplate } from '@client/pdfRenderer' import { certificateBaseTemplate } from '@client/templates/register' import * as Handlebars from 'handlebars' @@ -27,7 +23,10 @@ import { getOfflineData } from '@client/offline/selectors' import isValid from 'date-fns/isValid' import format from 'date-fns/format' import { getHandlebarHelpers } from '@client/forms/handlebarHelpers' -import { FontFamilyTypes } from '@client/utils/referenceApi' +import { + CertificateConfiguration, + FontFamilyTypes +} from '@client/utils/referenceApi' import htmlToPdfmake from 'html-to-pdfmake' import { Content } from 'pdfmake/interfaces' @@ -225,23 +224,23 @@ src: url("${url}") format("truetype"); const serializer = new XMLSerializer() return serializer.serializeToString(svg) } -export function svgToPdfTemplate(svg: string, offlineResource: IOfflineData) { - const initialDefaultFont = offlineResource.templates.fonts - ? Object.keys(offlineResource.templates.fonts)[0] - : null +export function svgToPdfTemplate( + svg: string, + certificateFonts: CertificateConfiguration +) { const pdfTemplate: IPDFTemplate = { ...certificateBaseTemplate, definition: { ...certificateBaseTemplate.definition, defaultStyle: { font: - initialDefaultFont || + Object.keys(certificateFonts)[0] || certificateBaseTemplate.definition.defaultStyle.font } }, fonts: { ...certificateBaseTemplate.fonts, - ...offlineResource.templates.fonts + ...certificateFonts } } diff --git a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx index ac5ee08d4b1..7a36f05e120 100644 --- a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx +++ b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx @@ -21,7 +21,7 @@ import { } from '@opencrvs/components' import React from 'react' import { useDispatch } from 'react-redux' -import { useLocation, useParams } from 'react-router' +import { useLocation } from 'react-router' import { messages as certificateMessages } from '@client/i18n/messages/views/certificate' import { useIntl } from 'react-intl' import { buttonMessages } from '@client/i18n/messages/buttons' @@ -110,19 +110,17 @@ const ReviewCertificateFrame = ({ } export const ReviewCertificate = () => { - const { registrationId } = useParams<{ registrationId: string }>() - const { - svg, + svgCode, handleCertify, isPrintInAdvance, canUserEditRecord, handleEdit - } = usePrintableCertificate(registrationId) + } = usePrintableCertificate() const intl = useIntl() const [modal, openModal] = useModal() - if (!svg) { + if (!svgCode) { return ( @@ -183,7 +181,7 @@ export const ReviewCertificate = () => { { this.props.writeDeclaration(draft) if (isCertificateForPrintInAdvance(draft)) { - this.props.goToReviewCertificate(declarationId, event) + this.props.goToReviewCertificate( + declarationId, + collector.certTemplateId as string + ) } else { this.props.goToVerifyCollector( declarationId, @@ -507,7 +510,10 @@ const mapStateToProps = ( const userOfficeId = userDetails?.primaryOffice?.id const registeringOfficeId = getRegisteringOfficeId(declaration) - const certFormSection = getCertificateCollectorFormSection(declaration) + const certFormSection = getCertificateCollectorFormSection( + declaration, + state.offline.offlineData.templates?.certificates + ) const isAllowPrintInAdvance = event === Event.Birth diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index e69966f3389..27787c0b356 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -44,6 +44,8 @@ import { formatLongDate } from '@client/utils/date-formatting' import { AdminStructure, IOfflineData } from '@client/offline/reducer' import { getLocationHierarchy } from '@client/utils/locationUtils' import { printPDF } from '@client/pdfRenderer' +import { useEffect, useState } from 'react' +import { useParams } from 'react-router' const withEnhancedTemplateVariables = ( declaration: IPrintableDeclaration | undefined, @@ -100,10 +102,14 @@ const withEnhancedTemplateVariables = ( } } -export const usePrintableCertificate = (declarationId: string) => { +export const usePrintableCertificate = () => { + const { registrationId, certTemplateId } = useParams<{ + registrationId: string + certTemplateId: string + }>() const declarationWithoutAllTemplateVariables = useDeclaration< IPrintableDeclaration | undefined - >(declarationId) + >(registrationId) const userDetails = useSelector(getUserDetails) const offlineData = useSelector(getOfflineData) const declaration = withEnhancedTemplateVariables( @@ -120,28 +126,37 @@ export const usePrintableCertificate = (declarationId: string) => { declaration?.event !== Event.Marriage && (hasRegisterScope(scope) || hasRegistrationClerkScope(scope)) - let svg = undefined - const certificateTemplate = - declaration && - offlineData.templates.certificates?.[declaration.event].definition - if (certificateTemplate) { - const svgWithoutFonts = compileSvg( - certificateTemplate, - { ...declaration.data.template, preview: true }, - state - ) - const svgWithFonts = addFontsToSvg( - svgWithoutFonts, - offlineData.templates.fonts ?? {} - ) - svg = svgWithFonts - } + const [svgCode, setSvgCode] = useState() + const certificateConfig = offlineData.templates.certificates?.find( + (x) => x.id === certTemplateId + ) + const certificateFonts = certificateConfig?.fonts ?? {} + + useEffect(() => { + const certificateUrl = + declaration && + offlineData.templates.certificates?.find((x) => x.id === certTemplateId) + ?.svgUrl + + if (certificateUrl) { + fetch(certificateUrl) + .then((res) => res.text()) + .then((certificateTemplate) => { + if (!certificateTemplate) return + const svgWithoutFonts = compileSvg( + certificateTemplate, + { ...declaration.data.template, preview: true }, + state + ) + const svgWithFonts = addFontsToSvg(svgWithoutFonts, certificateFonts) + setSvgCode(svgWithFonts) + }) + } + // eslint-disable-next-line + }, []) const handleCertify = async () => { - if ( - !declaration || - !offlineData.templates.certificates?.[declaration.event].definition - ) { + if (!declaration || !certificateConfig) { return } const draft = cloneDeep(declaration) @@ -169,8 +184,11 @@ export const usePrintableCertificate = (declarationId: string) => { } } + const svgTemplate = await fetch(certificateConfig.svgUrl).then((res) => + res.text() + ) const svg = await compileSvg( - offlineData.templates.certificates[draft.event].definition, + svgTemplate, { ...draft.data.template, preview: false }, state ) @@ -185,7 +203,7 @@ export const usePrintableCertificate = (declarationId: string) => { ] } - const pdfTemplate = svgToPdfTemplate(svg, offlineData) + const pdfTemplate = svgToPdfTemplate(svg, certificateFonts) printPDF(pdfTemplate, draft.id) @@ -214,12 +232,12 @@ export const usePrintableCertificate = (declarationId: string) => { } dispatch( - goToCertificateCorrection(declarationId, CorrectionSection.Corrector) + goToCertificateCorrection(registrationId, CorrectionSection.Corrector) ) } return { - svg, + svgCode, handleCertify, isPrintInAdvance, canUserEditRecord, From bcacdc0059b7b3fceceee2d73933107ef4e186bc Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 4 Oct 2024 10:18:59 +0600 Subject: [PATCH 05/58] certificate collection removed via migration script --- ...002232752-remove-certificate-collection.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts diff --git a/packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts b/packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts new file mode 100644 index 00000000000..5bbe022ece3 --- /dev/null +++ b/packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts @@ -0,0 +1,66 @@ +/* + * 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 { Db, MongoClient } from 'mongodb' + +export const up = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const collectionExists = await db + .listCollections({ name: 'certificates' }) + .hasNext() + + if (collectionExists) { + await db.collection('certificates').drop() + console.log('Certificates collection removed successfully') + } else { + console.log('Certificates collection does not exist, skipping removal') + } + }) + } catch (error) { + console.error( + 'Error occurred while removing certificates collection:', + error + ) + throw error + } finally { + session.endSession() + } +} + +export const down = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const collectionExists = await db + .listCollections({ name: 'certificates' }) + .hasNext() + + if (!collectionExists) { + await db.createCollection('certificates') + console.log('Certificates collection recreated successfully') + } else { + console.log( + 'Certificates collection already exists, skipping recreation' + ) + } + }) + } catch (error) { + console.error( + 'Error occurred while recreating certificates collection:', + error + ) + throw error + } finally { + session.endSession() + } +} From 6ba8f28fade934c988ca26a24ca5091af8cfd9f4 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 4 Oct 2024 11:36:22 +0600 Subject: [PATCH 06/58] select certificate template label added --- .../forms/certificate/fieldDefinitions/collectorSection.ts | 2 +- packages/client/src/i18n/messages/views/certificate.ts | 6 ++++++ packages/client/src/tests/languages.json | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index 406e4c23313..48bfe44104b 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -1061,7 +1061,7 @@ function getCertCollectorGroupForEvent( { name: 'certTemplateId', type: 'SELECT_WITH_OPTIONS', - label: certificateMessages.certificateConfirmationTxt, + label: certificateMessages.certificateTemplateSelectLabel, required: true, initialValue: '', validator: [], diff --git a/packages/client/src/i18n/messages/views/certificate.ts b/packages/client/src/i18n/messages/views/certificate.ts index 3c3601da743..062d679af8f 100644 --- a/packages/client/src/i18n/messages/views/certificate.ts +++ b/packages/client/src/i18n/messages/views/certificate.ts @@ -14,6 +14,7 @@ interface ICertificateMessages extends Record { certificateCollectionTitle: MessageDescriptor addAnotherSignature: MessageDescriptor + certificateTemplateSelectLabel: MessageDescriptor certificateConfirmationTxt: MessageDescriptor certificateIsCorrect: MessageDescriptor certificateReceiptHeader: MessageDescriptor @@ -95,6 +96,11 @@ const messagesToDefine: ICertificateMessages = { description: 'The title of print certificate action', id: 'print.certificate.section.title' }, + certificateTemplateSelectLabel: { + defaultMessage: 'Select certificate template', + description: 'The title of select certificate template action', + id: 'certificate.selectTemplate' + }, certificateConfirmationTxt: { defaultMessage: 'Edit', description: 'Edit', diff --git a/packages/client/src/tests/languages.json b/packages/client/src/tests/languages.json index e3329737a31..57c24fea6df 100644 --- a/packages/client/src/tests/languages.json +++ b/packages/client/src/tests/languages.json @@ -52,6 +52,7 @@ "buttons.upload": "Upload", "buttons.yes": "Yes", "certificate.confirmCorrect": "Please confirm that the informant has reviewed that the information on the certificate is correct and that you are ready to print.", + "certificate.selectTemplate": "Select certificate template", "certificate.isCertificateCorrect": "Is the {event} certificate correct?", "certificate.label.birth": "Birth", "certificate.label.death": "Death", @@ -1200,6 +1201,7 @@ "buttons.upload": "আপলোড", "buttons.yes": "হ্যাঁ", "certificate.confirmCorrect": "অনুগ্রহ করে নিশ্চিত করুন যে নিবন্ধনটি পর্যালোচনা হয়েছে তার তথ্য সঠিক এবং আপনি মুদ্রণ করতে প্রস্তুত", + "certificate.selectTemplate": "নিবন্ধন টেমপ্লেট নির্বাচন করুন", "certificate.isCertificateCorrect": "জন্ম নিবন্ধনটি কি সঠিক?", "certificate.label.birth": "জন্ম", "certificate.label.death": "মৃত্যু", From 08137c78f2eb70d856751d566cd16c41bfefb9ef Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 7 Oct 2024 11:23:19 +0600 Subject: [PATCH 07/58] fixed different routes to accomodate certTemplateId --- .../fieldDefinitions/collectorSection.ts | 16 ++++++---- packages/client/src/navigation/index.ts | 22 +++++++------- packages/client/src/navigation/routes.ts | 8 ++--- .../src/views/PrintCertificate/Payment.tsx | 29 +++++++++---------- .../PrintCertificate/VerifyCollector.tsx | 10 +++---- .../collectorForm/CollectorForm.tsx | 23 +++++++++++---- 6 files changed, 61 insertions(+), 47 deletions(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index 48bfe44104b..f6940a3b0bd 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -1041,7 +1041,14 @@ function getCertCollectorGroupForEvent( birthCertCollectorOptions, marriageCertCollectorOptions ) - + const certificateTemplateOptions = + certificates + ?.filter((x) => x.event === declaration.event) + .map((x) => ({ label: x.label, value: x.id })) || [] + const certTemplateDefaultValue = + certificateTemplateOptions.length > 0 + ? certificateTemplateOptions[0].value + : '' return { id: 'certCollector', title: certificateMessages.whoToCollect, @@ -1063,12 +1070,9 @@ function getCertCollectorGroupForEvent( type: 'SELECT_WITH_OPTIONS', label: certificateMessages.certificateTemplateSelectLabel, required: true, - initialValue: '', + initialValue: certTemplateDefaultValue, validator: [], - options: - certificates - ?.filter((x) => x.event === declaration.event) - .map((x) => ({ label: x.label, value: x.id })) || [] + options: certificateTemplateOptions } ] } diff --git a/packages/client/src/navigation/index.ts b/packages/client/src/navigation/index.ts index bbceb637671..4ff678536b4 100644 --- a/packages/client/src/navigation/index.ts +++ b/packages/client/src/navigation/index.ts @@ -293,13 +293,13 @@ export function goToBirthRegistrationAsParent(declarationId: string) { export function goToPrintCertificate( registrationId: string, - event: string, + certTemplateId: string, groupId?: string ) { return push( formatUrl(CERTIFICATE_COLLECTOR, { - registrationId: registrationId.toString(), - eventType: event.toLowerCase().toString(), + registrationId, + certTemplateId, groupId: groupId || 'certCollector' }) ) @@ -375,13 +375,13 @@ export function goToReviewCertificate( export function goToVerifyCollector( registrationId: string, - event: string, + certTemplateId: string, collector: string ) { return push( formatUrl(VERIFY_COLLECTOR, { registrationId: registrationId.toString(), - eventType: event.toLowerCase().toString(), + certTemplateId: certTemplateId.toLowerCase().toString(), collector: collector.toLowerCase().toString() }) ) @@ -389,24 +389,24 @@ export function goToVerifyCollector( export function goToPrintCertificatePayment( registrationId: string, - event: Event + certTemplateId: string ) { return push( formatUrl(PRINT_CERTIFICATE_PAYMENT, { - registrationId: registrationId.toString(), - eventType: event + registrationId, + certTemplateId }) ) } export function goToIssueCertificatePayment( registrationId: string, - event: Event + certTemplateId: string ) { return push( formatUrl(ISSUE_CERTIFICATE_PAYMENT, { - registrationId: registrationId.toString(), - eventType: event + registrationId, + certTemplateId }) ) } diff --git a/packages/client/src/navigation/routes.ts b/packages/client/src/navigation/routes.ts index 9490b3c4dcb..c9618765065 100644 --- a/packages/client/src/navigation/routes.ts +++ b/packages/client/src/navigation/routes.ts @@ -43,18 +43,18 @@ export const VERIFY_CORRECTOR = '/correction/:declarationId/verify/:corrector' export const SEARCH = '/search' export const SEARCH_RESULT = '/search-result' export const CERTIFICATE_COLLECTOR = - '/cert/collector/:registrationId/:eventType/:groupId' + '/cert/collector/:registrationId/:certTemplateId/:groupId' export const ISSUE_COLLECTOR = '/issue/:registrationId/:pageId' export const ISSUE_VERIFY_COLLECTOR = '/issue/check/:registrationId/:eventType/:collector' export const VERIFY_COLLECTOR = '/print/check/:registrationId/:certTemplateId/:collector' -export const REVIEW_CERTIFICATE = '/review/:registrationId/:eventType' +export const REVIEW_CERTIFICATE = '/review/:registrationId/:certTemplateId' export const PRINT_CERTIFICATE_PAYMENT = - '/print/payment/:registrationId/:eventType' + '/print/payment/:registrationId/:certTemplateId' export const ISSUE_CERTIFICATE_PAYMENT = - '/issue/payment/:registrationId/:eventType' + '/issue/payment/:registrationId/:certTemplateId' export const REGISTRAR_HOME = '/registration-home' export const REGISTRAR_HOME_TAB = '/registration-home/:tabId/:selectorId?' diff --git a/packages/client/src/views/PrintCertificate/Payment.tsx b/packages/client/src/views/PrintCertificate/Payment.tsx index 296af04acc1..ba98cd727c6 100644 --- a/packages/client/src/views/PrintCertificate/Payment.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.tsx @@ -46,6 +46,7 @@ import { UserDetails } from '@client/utils/userUtils' interface IProps { event: Event registrationId: string + certTemplateId: string language: string declaration: IPrintableDeclaration theme: ITheme @@ -63,11 +64,12 @@ const PaymentComponent = ({ declaration, intl, event, + registrationId, + certTemplateId, goBack, offlineCountryConfig, modifyDeclaration, - goToReviewCertificate, - registrationId + goToReviewCertificate }: IFullProps) => { const handleContinue = (paymentAmount: string) => { const certificates = @@ -96,7 +98,7 @@ const PaymentComponent = ({ } }) - goToReviewCertificate(registrationId, event) + goToReviewCertificate(registrationId, certTemplateId) } if (!declaration) { @@ -180,30 +182,25 @@ const PaymentComponent = ({ ) } -const getEvent = (eventType: string | undefined) => { - switch (eventType && eventType.toLowerCase()) { - case 'birth': - default: - return Event.Birth - case 'death': - return Event.Death - case 'marriage': - return Event.Marriage - } +const getEvent = (state: IStoreState, certTemplateId: string | undefined) => { + return state.offline.offlineData.templates?.certificates?.find( + (x) => x.id === certTemplateId + )?.event } function mapStatetoProps( state: IStoreState, - props: RouteComponentProps<{ registrationId: string; eventType: string }> + props: RouteComponentProps<{ registrationId: string; certTemplateId: string }> ) { - const { registrationId, eventType } = props.match.params - const event = getEvent(eventType) + const { registrationId, certTemplateId } = props.match.params + const event = getEvent(state, certTemplateId) as Event const declaration = state.declarationsState.declarations.find( (app) => app.id === registrationId && app.event === event ) as IPrintableDeclaration return { event, + certTemplateId, registrationId, language: state.i18n.language, declaration, diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx index 5ae1f8aff0a..3af807adcb9 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx @@ -51,7 +51,7 @@ import { getUserDetails } from '@client/profile/profileSelectors' interface IMatchParams { registrationId: string - eventType: Event + certTemplateId: Event collector: string } @@ -104,24 +104,24 @@ class VerifyCollectorComponent extends React.Component { if (!isIssueUrl) { this.props.goToReviewCertificate( this.props.match.params.registrationId, - event + this.props.match.params.certTemplateId ) } else { this.props.goToIssueCertificatePayment( this.props.match.params.registrationId, - event + this.props.match.params.certTemplateId ) } } else { if (!isIssueUrl) { this.props.goToPrintCertificatePayment( this.props.match.params.registrationId, - event + this.props.match.params.certTemplateId ) } else { this.props.goToIssueCertificatePayment( this.props.match.params.registrationId, - event + this.props.match.params.certTemplateId ) } } diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index ff873fd8a3b..b680f0cba9b 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -184,6 +184,7 @@ interface IState { showError: boolean showModalForNoSignedAffidavit: boolean isFileUploading: boolean + certTemplateId: string } class CollectorFormComponent extends React.Component { @@ -192,7 +193,8 @@ class CollectorFormComponent extends React.Component { this.state = { showError: false, showModalForNoSignedAffidavit: false, - isFileUploading: false + isFileUploading: false, + certTemplateId: '' } } @@ -257,6 +259,10 @@ class CollectorFormComponent extends React.Component { sectionId as keyof typeof certificate ] as IFormSectionData + this.setState({ + ...this.state, + certTemplateId: collector.certTemplateId as string + }) if (errLength > 0) { this.setState({ showError: true @@ -309,12 +315,16 @@ class CollectorFormComponent extends React.Component { } else { this.props.goToVerifyCollector( declarationId, - event, + collector.certTemplateId as string, collector.type as string ) } } else { - this.props.goToPrintCertificate(declarationId, event, nextGroup) + this.props.goToPrintCertificate( + declarationId, + collector.certTemplateId as string, + nextGroup + ) } } @@ -333,9 +343,12 @@ class CollectorFormComponent extends React.Component { offlineCountryConfiguration ) ) { - this.props.goToReviewCertificate(declarationId, event) + this.props.goToReviewCertificate(declarationId, this.state.certTemplateId) } else { - this.props.goToPrintCertificatePayment(declarationId, event) + this.props.goToPrintCertificatePayment( + declarationId, + this.state.certTemplateId + ) } } From 46773ad51b6cfcc4354963867aca8373fda6c735 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 7 Oct 2024 12:23:41 +0600 Subject: [PATCH 08/58] fixing test --- packages/client/src/setupTests.ts | 1 - packages/client/src/tests/util.tsx | 51 +++++++++++++++++++----------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/client/src/setupTests.ts b/packages/client/src/setupTests.ts index bc635072148..c9ad41872e8 100644 --- a/packages/client/src/setupTests.ts +++ b/packages/client/src/setupTests.ts @@ -201,7 +201,6 @@ vi.doMock( languages: mockOfflineData.languages }), loadConfig: () => Promise.resolve(mockConfigResponse), - loadCertificateConfiguration: () => Promise.resolve({}), loadConfigAnonymousUser: () => Promise.resolve(mockConfigResponse), loadForms: () => Promise.resolve(mockOfflineData.forms.forms), importConditionals: () => Promise.resolve({}), diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 916ee33f292..02061a68af7 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -802,26 +802,41 @@ export const mockDeathRegistrationSectionData = { const mockFetchCertificatesTemplatesDefinition = [ { - id: '12313546', - event: Event.Birth, - status: 'ACTIVE', - svgCode: - '\n\n\n\n{registrarName}
({role}) \n \n \nThis event was registered at {registrationLocation} \n{eventDate} \nDied on \nPlace of death \n \n{placeOfDeath} \n{informantName} \nThis is to certify that \n{registrationNumber} \nDeath Registration No \nDate of issuance of certificate: {certificateDate}\n\n\n\n\n\n\n\n\n\n\n', - svgDateCreated: '1640696680593', - svgDateUpdated: '1644326332088', - svgFilename: 'oCRVS_DefaultZambia_Death_v1.svg', - user: '61d42359f1a2c25ea01beb4b' + id: 'birth.certificate', + event: 'birth', + label: { + id: 'certificates.birth.certificate', + defaultMessage: 'Birth Certificate', + description: 'The label for a birth certificate' + }, + svgUrl: '/api/countryconfig/certificates/birth-certificate.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } }, { - id: '25313546', - event: Event.Death, - status: 'ACTIVE', - svgCode: - '\n\n\n\n{registrarName}
({role}) \n \n \nThis event was registered at {registrationLocation} \n{eventDate} \nWas born on \nPlace of birth \n \n{placeOfBirth} \n{informantName} \nThis is to certify that \n{registrationNumber} \nBirth Registration No \nDate of issuance of certificate: {certificateDate}\n\n\n\n\n\n\n\n\n\n\n', - svgDateCreated: '1640696804785', - svgDateUpdated: '1643885502999', - svgFilename: 'oCRVS_DefaultZambia_Birth_v1.svg', - user: '61d42359f1a2c25ea01beb4b' + id: 'birth.certificate.copy', + event: 'birth', + label: { + id: 'certificates.birth.certificate.copy', + defaultMessage: 'Birth Certificate certified copy', + description: 'The label for a birth certificate' + }, + svgUrl: + '/api/countryconfig/certificates/birth-certificate-certified-copy.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } } ] From 02124852b362347dd2cf044d76caf1ba438121a6 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 7 Oct 2024 15:35:20 +0600 Subject: [PATCH 09/58] added missing variable --- .../config/src/handlers/application/applicationConfigHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/src/handlers/application/applicationConfigHandler.ts b/packages/config/src/handlers/application/applicationConfigHandler.ts index 20b57ba8b75..19f7c4eaf98 100644 --- a/packages/config/src/handlers/application/applicationConfigHandler.ts +++ b/packages/config/src/handlers/application/applicationConfigHandler.ts @@ -70,7 +70,7 @@ async function getCertificatesConfig( scope.includes(RouteScope.VALIDATE) || scope.includes(RouteScope.NATLSYSADMIN)) ) { - const url = new URL(`/certificates`, COUNTRY_CONFIG_URL).toString() + const url = new URL(`/certificates`, env.COUNTRY_CONFIG_URL).toString() const res = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } From f8d2c5e4c76acb403601a79b91d29a2546fa28f5 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 7 Oct 2024 18:48:18 +0600 Subject: [PATCH 10/58] payment and verify-collector test fixed --- packages/client/src/tests/templates.json | 65 +++++++++++++++---- packages/client/src/tests/util.tsx | 18 +++++ .../views/PrintCertificate/Payment.test.tsx | 36 ++-------- .../src/views/PrintCertificate/Payment.tsx | 8 ++- .../PrintCertificate/VerifyCollector.test.tsx | 10 +-- .../PrintCertificate/VerifyCollector.tsx | 2 +- 6 files changed, 86 insertions(+), 53 deletions(-) diff --git a/packages/client/src/tests/templates.json b/packages/client/src/tests/templates.json index bc7f832fae7..32e4e6f23a6 100644 --- a/packages/client/src/tests/templates.json +++ b/packages/client/src/tests/templates.json @@ -7,19 +7,60 @@ "bolditalics": "NotoSans-Regular.ttf" } }, - "certificates": { - "birth": { - "definition": " {eventDate} Was born on Place of birth {placeOfBirth} {informantName} This is to certify that {registrationNumber} Birth Registration No Date of issuance of certificate:{certificateDate} {registrarName} ({role}) This event was registered at{registrationLocation} ", - "fileName": "farajaland-birth-certificate-v3.svg" + "certificates": [ + { + "id": "birth.certificate", + "event": "birth", + "label": { + "id": "certificates.birth.certificate", + "defaultMessage": "Birth Certificate", + "description": "The label for a birth certificate" + }, + "svgUrl": "/api/countryconfig/certificates/birth-certificate.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } }, - "death": { - "definition": " {eventDate} Died on Place of death {placeOfDeath} {informantName} This is to certify that {registrationNumber} Death Registration No Date of issuance of certificate:{certificateDate} {registrarName} ({role}) This event was registered at{registrationLocation} ", - "fileName": "farajaland-death-certificate-v3.svg" + { + "id": "birth.certificate.copy", + "event": "birth", + "label": { + "id": "certificates.birth.certificate.copy", + "defaultMessage": "Birth Certificate certified copy", + "description": "The label for a birth certificate" + }, + "svgUrl": "/api/countryconfig/certificates/birth-certificate-certified-copy.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } }, - "marriage": { - "definition": " {eventDate} Died on Place of death {placeOfDeath} {informantName} This is to certify that {registrationNumber} Death Registration No Date of issuance of certificate:{certificateDate} {registrarName} ({role}) This event was registered at{registrationLocation} ", - "fileName": "farajaland-marriage-certificate-v1.svg", - "lastModifiedDate": "1678106545001" + { + "id": "death-certificate", + "event": "death", + "label": { + "id": "certificates.death.certificate", + "defaultMessage": "Death Certificate", + "description": "The label for a death certificate" + }, + "svgUrl": "/api/countryconfig/certificates/death-certificate.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } } - } + ] } diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 02061a68af7..d41dd8b12aa 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -837,6 +837,24 @@ const mockFetchCertificatesTemplatesDefinition = [ bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' } } + }, + { + id: 'death-certificate', + event: 'death', + label: { + id: 'certificates.death.certificate', + defaultMessage: 'Death Certificate', + description: 'The label for a death certificate' + }, + svgUrl: '/api/countryconfig/certificates/death-certificate.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } } ] diff --git a/packages/client/src/views/PrintCertificate/Payment.test.tsx b/packages/client/src/views/PrintCertificate/Payment.test.tsx index 9896a006a72..6397056511a 100644 --- a/packages/client/src/views/PrintCertificate/Payment.test.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.test.tsx @@ -82,7 +82,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - eventType: Event.Birth + certTemplateId: 'birth.certificate.copy' }, isExact: true, path: '', @@ -103,43 +103,15 @@ describe('verify collector tests', () => { testComponent.find('#Continue').hostNodes().simulate('click') }) - /* - - // Commenting out this test because receipt templates are not currently configurable - - it('print payment receipt', async () => { - const printMoneyReceiptSpy = vi.spyOn(PDFUtils, 'printMoneyReceipt') - const testComponent = await createTestComponent( - , - { store, history } - ) - - testComponent.find('#print-receipt').hostNodes().simulate('click') - - expect(printMoneyReceiptSpy).toBeCalled() - })*/ - it('invalid declaration id', async () => { - await createTestComponent( + const payments = await createTestComponent( { match={{ params: { registrationId: 'mockDeath1234', - eventType: Event.Death + certTemplateId: 'death-certificate' }, isExact: true, path: '', diff --git a/packages/client/src/views/PrintCertificate/Payment.tsx b/packages/client/src/views/PrintCertificate/Payment.tsx index ba98cd727c6..679d61f945f 100644 --- a/packages/client/src/views/PrintCertificate/Payment.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.tsx @@ -183,9 +183,11 @@ const PaymentComponent = ({ } const getEvent = (state: IStoreState, certTemplateId: string | undefined) => { - return state.offline.offlineData.templates?.certificates?.find( - (x) => x.id === certTemplateId - )?.event + return ( + state.offline.offlineData.templates?.certificates?.find( + (x) => x.id === certTemplateId + )?.event || '' + ) } function mapStatetoProps( diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx index 30d7de84a8b..255ba4ec4f8 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx @@ -67,7 +67,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - eventType: Event.Birth, + certTemplateId: 'birth.certificate.copy', collector: 'mother' }, isExact: true, @@ -89,7 +89,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - eventType: Event.Birth, + certTemplateId: 'birth.certificate.copy', collector: 'mother' }, isExact: true, @@ -128,7 +128,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockDeath1234', - eventType: Event.Death, + certTemplateId: 'death.certificate', collector: 'informant' }, isExact: true, @@ -168,7 +168,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - eventType: Event.Death, + certTemplateId: 'birth.certificate.copy', collector: 'father' }, isExact: true, @@ -251,7 +251,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockDeath1234', - eventType: Event.Death, + certTemplateId: 'death.certificate', collector: 'informant' }, isExact: true, diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx index 3af807adcb9..60a87577da9 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx @@ -51,7 +51,7 @@ import { getUserDetails } from '@client/profile/profileSelectors' interface IMatchParams { registrationId: string - certTemplateId: Event + certTemplateId: string collector: string } From c09eda937575059cfff0675fc75d991666bf0042 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 8 Oct 2024 15:05:33 +0600 Subject: [PATCH 11/58] VerifyCollector, ReviewCertificate and Payment test cases adjusted for certified copies changes --- packages/client/src/declarations/selectors.ts | 11 +- packages/client/src/tests/templates.json | 22 ++- packages/client/src/tests/util.tsx | 24 ++- .../views/PrintCertificate/Payment.test.tsx | 4 +- .../ReviewCertificateAction.test.tsx | 154 +++++++++++------- .../PrintCertificate/VerifyCollector.test.tsx | 10 +- .../usePrintableCertificate.ts | 11 +- 7 files changed, 157 insertions(+), 79 deletions(-) diff --git a/packages/client/src/declarations/selectors.ts b/packages/client/src/declarations/selectors.ts index ff047a3660f..452bb324002 100644 --- a/packages/client/src/declarations/selectors.ts +++ b/packages/client/src/declarations/selectors.ts @@ -29,11 +29,16 @@ export const getInitialDeclarationsLoaded = ( const selectDeclaration = (declarationId: string) => - (store: IStoreState) => - getKey(store, 'declarations').find(({ id }) => declarationId === id) as T + (store: IStoreState) => { + const bar = getKey(store, 'declarations').find( + ({ id }) => declarationId === id + ) as T + return bar + } export const useDeclaration = ( declarationId: string ) => { - return useSelector(selectDeclaration(declarationId)) + const foo = useSelector(selectDeclaration(declarationId)) + return foo } diff --git a/packages/client/src/tests/templates.json b/packages/client/src/tests/templates.json index 32e4e6f23a6..6f9b93b0e96 100644 --- a/packages/client/src/tests/templates.json +++ b/packages/client/src/tests/templates.json @@ -9,7 +9,7 @@ }, "certificates": [ { - "id": "birth.certificate", + "id": "birth-certificate", "event": "birth", "label": { "id": "certificates.birth.certificate", @@ -27,7 +27,7 @@ } }, { - "id": "birth.certificate.copy", + "id": "birth-certificate-copy", "event": "birth", "label": { "id": "certificates.birth.certificate.copy", @@ -61,6 +61,24 @@ "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" } } + }, + { + "id": "marriage-certificate", + "event": "marriage", + "label": { + "id": "certificates.marriage.certificate", + "defaultMessage": "Marriage Certificate", + "description": "The label for a marriage certificate" + }, + "svgUrl": "/api/countryconfig/certificates/marriage-certificate.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } } ] } diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index d41dd8b12aa..2879021a571 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -802,7 +802,7 @@ export const mockDeathRegistrationSectionData = { const mockFetchCertificatesTemplatesDefinition = [ { - id: 'birth.certificate', + id: 'birth-certificate', event: 'birth', label: { id: 'certificates.birth.certificate', @@ -820,10 +820,10 @@ const mockFetchCertificatesTemplatesDefinition = [ } }, { - id: 'birth.certificate.copy', + id: 'birth-certificate-copy', event: 'birth', label: { - id: 'certificates.birth.certificate.copy', + id: 'certificates.birth-certificate-copy', defaultMessage: 'Birth Certificate certified copy', description: 'The label for a birth certificate' }, @@ -855,6 +855,24 @@ const mockFetchCertificatesTemplatesDefinition = [ bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' } } + }, + { + id: 'marriage-certificate', + event: 'marriage', + label: { + id: 'certificates.marriage.certificate', + defaultMessage: 'Marriage Certificate', + description: 'The label for a marriage certificate' + }, + svgUrl: '/api/countryconfig/certificates/marriage-certificate.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } } ] diff --git a/packages/client/src/views/PrintCertificate/Payment.test.tsx b/packages/client/src/views/PrintCertificate/Payment.test.tsx index 6397056511a..a4d2818636b 100644 --- a/packages/client/src/views/PrintCertificate/Payment.test.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.test.tsx @@ -82,7 +82,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - certTemplateId: 'birth.certificate.copy' + certTemplateId: 'birth-certificate-copy' }, isExact: true, path: '', @@ -111,7 +111,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth', - certTemplateId: 'birth.certificate.copy' + certTemplateId: 'birth-certificate-copy' }, isExact: true, path: '', diff --git a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx index dd723b7643d..131e26f08dc 100644 --- a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx +++ b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx @@ -8,7 +8,7 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { Mock } from 'vitest' +import { vi, Mock } from 'vitest' import * as React from 'react' import { createStore } from '@client/store' import { storeDeclaration, IDeclaration } from '@client/declarations' @@ -45,35 +45,74 @@ const deathDeclaration = { event: Event.Death } +const mockBirthDeclaration = { + id: 'mockBirth1234', + data: { + ...mockDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] as unknown as IFormSectionData + }, + event: Event.Birth +} +const mockMarriageDeclaration = { + id: 'mockMarriage1234', + data: { + ...mockMarriageDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] + }, + event: Event.Marriage +} + describe('when user wants to review death certificate', () => { - it('displays the "Confirm & Print" button', async () => { + let component: ReactWrapper<{}, {}> + beforeEach(async () => { + const mockSvgTemplate = 'Sample Certificate' + global.fetch = vi.fn().mockImplementation((url) => { + return Promise.resolve({ + text: vi.fn().mockResolvedValue(mockSvgTemplate) + }) + }) const { history, match } = createRouterProps( '/', { isNavigatedInsideApp: false }, { matchParams: { registrationId: 'mockDeath1234', - eventType: Event.Death + certTemplateId: 'death-certificate' } } ) ;(useParams as Mock).mockImplementation(() => match.params) - - const { store } = createStore() - + const { store } = createStore(history) loginAsFieldAgent(store) + const clonedMockDeathDeclarationData = cloneDeep(deathDeclaration) + await flushPromises() + // @ts-ignore + store.dispatch(storeDeclaration(clonedMockDeathDeclarationData)) - const component = await createTestComponent(, { + component = await createTestComponent(, { store, history }) - - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) + await flushPromises() component.update() + }) - const confirmBtn = component.find('#confirm-print') - const confirmBtnExist = !!confirmBtn.hostNodes().length + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length expect(confirmBtnExist).toBe(true) }) }) @@ -118,7 +157,7 @@ describe('back button behavior tests of review certificate action', () => { const birthDeclaration = { id: 'asdhdqe2472487jsdfsdf', data: mockBirthDeclarationData, - event: Event.Birth + certTemplateId: 'birth-certificate-copy' } store.dispatch( // @ts-ignore @@ -153,7 +192,7 @@ describe('back button behavior tests of review certificate action', () => { storeDeclaration({ id: 'asdhdqe2472487jsdfsdf', data: mockBirthDeclarationData, - event: Event.Birth + certTemplateId: 'birth-certificate-copy' } as IDeclaration) ) component = await createTestComponent(, { @@ -169,45 +208,31 @@ describe('back button behavior tests of review certificate action', () => { describe('when user wants to review birth certificate', () => { let component: ReactWrapper<{}, {}> - beforeEach(async () => { + const mockSvgTemplate = 'Sample Certificate' + global.fetch = vi.fn().mockImplementation((url) => { + return Promise.resolve({ + text: vi.fn().mockResolvedValue(mockSvgTemplate) + }) + }) const { history, match } = createRouterProps( '/', { isNavigatedInsideApp: false }, { matchParams: { - registrationId: 'asdhdqe2472487jsdfsdf', - eventType: Event.Birth + registrationId: 'mockBirth1234', + certTemplateId: 'birth-certificate' } } ) ;(useParams as Mock).mockImplementation(() => match.params) const { store } = createStore(history) - - const mockBirthDeclarationData = cloneDeep(mockDeclarationData) - mockBirthDeclarationData.registration.certificates[0] = { - collector: { - type: 'PRINT_IN_ADVANCE' - } - } loginAsFieldAgent(store) await flushPromises() - store.dispatch( - storeDeclaration({ - id: 'asdhdqe2472487jsdfsdf', - data: { - ...mockBirthDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] as unknown as IFormSectionData - }, - event: Event.Birth - }) - ) + + const clonedMockBirthDeclaration = cloneDeep(mockBirthDeclaration) + // @ts-ignore + store.dispatch(storeDeclaration(clonedMockBirthDeclaration)) component = await createTestComponent(, { store, @@ -216,20 +241,18 @@ describe('when user wants to review birth certificate', () => { await flushPromises() component.update() }) - - it('displays have the Continue and print Button', () => { - const confirmBtnExist = !!component.find('#confirm-print').hostNodes() - .length + it('displays have the Continue and print Button', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + const confirmBtnExist = !!confirmBtn.hostNodes().length expect(confirmBtnExist).toBe(true) }) - it('shows the Confirm Print Modal', () => { - const confirmBtn = component.find('#confirm-print').hostNodes() - confirmBtn.simulate('click') + it('shows the Confirm Print Modal', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') component.update() - const modalIsDisplayed = !!component - .find('#confirm-print-modal') - .hostNodes().length + const modal = await waitForElement(component, '#confirm-print-modal') + const modalIsDisplayed = !!modal.hostNodes().length expect(modalIsDisplayed).toBe(true) }) @@ -245,19 +268,29 @@ describe('when user wants to review birth certificate', () => { expect(modalIsClosed).toBe(false) }) + + afterAll(() => { + flushPromises() + }) }) describe('when user wants to review marriage certificate', () => { let component: ReactWrapper<{}, {}> beforeEach(async () => { + const mockSvgTemplate = 'Sample Certificate' + global.fetch = vi.fn().mockImplementation((url) => { + return Promise.resolve({ + text: vi.fn().mockResolvedValue(mockSvgTemplate) + }) + }) const { history, match } = createRouterProps( '/', { isNavigatedInsideApp: false }, { matchParams: { registrationId: '1234896128934719', - eventType: Event.Birth + certTemplateId: 'marriage-certificate' } } ) @@ -293,19 +326,22 @@ describe('when user wants to review marriage certificate', () => { component.update() }) - it('displays have the Continue and print Button', () => { - const confirmBtnExist = !!component.find('#confirm-print').hostNodes() - .length + it('displays have the Continue and print Button toot', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length expect(confirmBtnExist).toBe(true) }) - it('shows the Confirm Print Modal', () => { - const confirmBtn = component.find('#confirm-print').hostNodes() + it('shows the Confirm Print Modal', async () => { + const confirmBtn = ( + await waitForElement(component, '#confirm-print') + ).hostNodes() confirmBtn.simulate('click') component.update() - const modalIsDisplayed = !!component - .find('#confirm-print-modal') - .hostNodes().length + const modalIsDisplayed = !!( + await waitForElement(component, '#confirm-print-modal') + ).hostNodes().length expect(modalIsDisplayed).toBe(true) }) diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx index 255ba4ec4f8..e5ccd9b186b 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx @@ -67,7 +67,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - certTemplateId: 'birth.certificate.copy', + certTemplateId: 'birth-certificate-copy', collector: 'mother' }, isExact: true, @@ -89,7 +89,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - certTemplateId: 'birth.certificate.copy', + certTemplateId: 'birth-certificate-copy', collector: 'mother' }, isExact: true, @@ -128,7 +128,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockDeath1234', - certTemplateId: 'death.certificate', + certTemplateId: 'death-certificate', collector: 'informant' }, isExact: true, @@ -168,7 +168,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth1234', - certTemplateId: 'birth.certificate.copy', + certTemplateId: 'birth-certificate-copy', collector: 'father' }, isExact: true, @@ -251,7 +251,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockDeath1234', - certTemplateId: 'death.certificate', + certTemplateId: 'death-certificate', collector: 'informant' }, isExact: true, diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index 27787c0b356..1e238c0fa3e 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -134,11 +134,12 @@ export const usePrintableCertificate = () => { useEffect(() => { const certificateUrl = - declaration && - offlineData.templates.certificates?.find((x) => x.id === certTemplateId) - ?.svgUrl + (declaration && + offlineData.templates.certificates?.find((x) => x.id === certTemplateId) + ?.svgUrl) || + '' - if (certificateUrl) { + if (certificateUrl && declaration) { fetch(certificateUrl) .then((res) => res.text()) .then((certificateTemplate) => { @@ -153,7 +154,7 @@ export const usePrintableCertificate = () => { }) } // eslint-disable-next-line - }, []) + }, [declaration]) const handleCertify = async () => { if (!declaration || !certificateConfig) { From 98eb59721834cbce528970c3a991c513f0f92fba Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 9 Oct 2024 06:49:42 +0600 Subject: [PATCH 12/58] CollectorForm test fixed for certified copies --- .../collectorForm/CollectorForm.test.tsx | 14 +++++++------- .../collectorForm/CollectorForm.tsx | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx index 32b2dbf0184..c2325c3583a 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx @@ -229,7 +229,7 @@ describe('Certificate collector test for a birth registration without father det }) component.update() expect(history.location.pathname).toBe( - '/print/check/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth/father' + '/print/check/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate/father' ) }) @@ -270,7 +270,7 @@ describe('Certificate collector test for a birth registration without father det }) component.update() expect(history.location.pathname).toBe( - '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth/otherCertCollector' + '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate/otherCertCollector' ) }) }) @@ -387,7 +387,7 @@ describe('Certificate collector test for a birth registration without father det }) it('takes the user to affedavit view', async () => { expect(history.location.pathname).toBe( - '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth/affidavit' + '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate/affidavit' ) }) @@ -439,7 +439,7 @@ describe('Certificate collector test for a birth registration without father det component.find('#submit_confirm').hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/print/payment/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth' + '/print/payment/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate' ) }) @@ -464,7 +464,7 @@ describe('Certificate collector test for a birth registration without father det ).toHaveLength(1) component.find('#submit_confirm').hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/review/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth' + '/review/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate' ) }) @@ -575,7 +575,7 @@ describe('Certificate collector test for a death registration', () => { $confirm.hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/review/16ff35e1-3f92-4db3-b812-c402e609fb00/death' + '/review/16ff35e1-3f92-4db3-b812-c402e609fb00/death-certificate' ) }) }) @@ -632,7 +632,7 @@ describe('Certificate collector test for a marriage registration', () => { $confirm.hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/review/18ff35e1-3d92-4db3-b815-c4d2e609fb23/marriage' + '/review/18ff35e1-3d92-4db3-b815-c4d2e609fb23/marriage-certificate' ) }) }) diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index b680f0cba9b..3b097900613 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -261,7 +261,7 @@ class CollectorFormComponent extends React.Component { this.setState({ ...this.state, - certTemplateId: collector.certTemplateId as string + certTemplateId: collector?.certTemplateId as string }) if (errLength > 0) { this.setState({ @@ -310,19 +310,19 @@ class CollectorFormComponent extends React.Component { if (isCertificateForPrintInAdvance(draft)) { this.props.goToReviewCertificate( declarationId, - collector.certTemplateId as string + collector?.certTemplateId as string ) } else { this.props.goToVerifyCollector( declarationId, - collector.certTemplateId as string, + collector?.certTemplateId as string, collector.type as string ) } } else { this.props.goToPrintCertificate( declarationId, - collector.certTemplateId as string, + collector?.certTemplateId as string, nextGroup ) } From c68e2872e0c2e814e5b5e2331ac152c90830b97d Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 9 Oct 2024 10:29:28 +0600 Subject: [PATCH 13/58] refactor test code and lint issues --- .../views/PrintCertificate/Payment.test.tsx | 164 ++++---- .../ReviewCertificateAction.test.tsx | 390 +++++++----------- .../PrintCertificate/VerifyCollector.test.tsx | 373 ++++++++--------- .../PrintCertificate/VerifyCollector.tsx | 1 - 4 files changed, 419 insertions(+), 509 deletions(-) diff --git a/packages/client/src/views/PrintCertificate/Payment.test.tsx b/packages/client/src/views/PrintCertificate/Payment.test.tsx index a4d2818636b..e404ed275dc 100644 --- a/packages/client/src/views/PrintCertificate/Payment.test.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.test.tsx @@ -27,75 +27,107 @@ import { vi, Mock } from 'vitest' import { WORKQUEUE_TABS } from '@client/components/interface/Navigation' import { REGISTRAR_HOME_TAB } from '@client/navigation/routes' import { formatUrl } from '@client/navigation' +import { IFormSectionData } from '@client/forms' +// Mock setup const getItem = window.localStorage.getItem as Mock ;(queries.fetchUserDetails as Mock).mockReturnValue(mockUserResponse) +// Common mock data +const birthDeclaration = { + id: 'mockBirth1234', + data: { + ...mockDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] as unknown as IFormSectionData + }, + event: Event.Birth +} + +const deathDeclaration = { + id: 'mockDeath1234', + data: { + ...mockDeathDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] as unknown as IFormSectionData + }, + event: Event.Death +} + +// Helper function to set up test +async function setupPaymentTest({ + registrationId, + certTemplateId, + declaration, + store, + history, + mockLocation +}: { + registrationId: string + certTemplateId: string + declaration: any + store: any + history: any + mockLocation: any +}) { + store.dispatch(storeDeclaration(declaration)) + await flushPromises() + + const testComponent = await createTestComponent( + , + { store, history } + ) + + return testComponent +} + describe('verify collector tests', () => { const { store, history } = createStore() const mockLocation: any = vi.fn() - const birthDeclaration = { - id: 'mockBirth1234', - data: { - ...mockDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Birth - } - - const deathDeclaration = { - id: 'mockDeath1234', - data: { - ...mockDeathDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Death - } - describe('in case of birth declaration', () => { beforeAll(async () => { getItem.mockReturnValue(validToken) await store.dispatch(checkAuth()) await flushPromises() - // @ts-ignore store.dispatch(storeDeclaration(birthDeclaration)) }) - it('when mother is collector renders Payment component', async () => { - const testComponent = await createTestComponent( - , - { store, history } - ) + it('renders Payment component when mother is collector', async () => { + const testComponent = await setupPaymentTest({ + registrationId: 'mockBirth1234', + certTemplateId: 'birth-certificate-copy', + declaration: birthDeclaration, + store, + history, + mockLocation + }) expect(testComponent.find('#service').hostNodes().text()).toContain( 'Birth' ) - expect(testComponent.find('#amountDue').hostNodes().text()).toContain( '20' ) @@ -103,8 +135,8 @@ describe('verify collector tests', () => { testComponent.find('#Continue').hostNodes().simulate('click') }) - it('invalid declaration id', async () => { - const payments = await createTestComponent( + it('redirects to home on invalid declaration id', async () => { + await createTestComponent( { />, { store, history } ) + expect(history.location.pathname).toEqual( formatUrl(REGISTRAR_HOME_TAB, { tabId: WORKQUEUE_TABS.readyToPrint, @@ -129,29 +162,20 @@ describe('verify collector tests', () => { }) }) - describe('in case of death declaration renders payment component', () => { + describe('in case of death declaration renders Payment component', () => { beforeAll(() => { - // @ts-ignore store.dispatch(storeDeclaration(deathDeclaration)) }) - it('when informant is collector', async () => { - const testComponent = await createTestComponent( - , - { store, history } - ) + it('renders Payment component when informant is collector', async () => { + const testComponent = await setupPaymentTest({ + registrationId: 'mockDeath1234', + certTemplateId: 'death-certificate', + declaration: deathDeclaration, + store, + history, + mockLocation + }) expect(testComponent.find('#service').hostNodes().text()).toContain( 'Death' diff --git a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx index 131e26f08dc..9be3eb7852e 100644 --- a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx +++ b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx @@ -11,7 +11,7 @@ import { vi, Mock } from 'vitest' import * as React from 'react' import { createStore } from '@client/store' -import { storeDeclaration, IDeclaration } from '@client/declarations' +import { storeDeclaration } from '@client/declarations' import { createTestComponent, mockDeclarationData, @@ -30,25 +30,26 @@ import { waitForElement } from '@client/tests/wait-for-element' import { push } from 'connected-react-router' import { useParams } from 'react-router' -const deathDeclaration = { - id: 'mockDeath1234', +const mockSvgTemplate = 'Sample Certificate' +const birthDeclaration = { + id: 'mockBirth1234', data: { - ...mockDeathDeclarationData, + ...mockDeclarationData, history: [ { date: '2022-03-21T08:16:24.467+00:00', regStatus: 'REGISTERED', reinstated: false } - ] + ] as unknown as IFormSectionData }, - event: Event.Death + event: Event.Birth } -const mockBirthDeclaration = { - id: 'mockBirth1234', +const deathDeclaration = { + id: 'mockDeath1234', data: { - ...mockDeclarationData, + ...mockDeathDeclarationData, history: [ { date: '2022-03-21T08:16:24.467+00:00', @@ -57,9 +58,10 @@ const mockBirthDeclaration = { } ] as unknown as IFormSectionData }, - event: Event.Birth + event: Event.Death } -const mockMarriageDeclaration = { + +const marriageDeclaration = { id: 'mockMarriage1234', data: { ...mockMarriageDeclarationData, @@ -69,73 +71,158 @@ const mockMarriageDeclaration = { regStatus: 'REGISTERED', reinstated: false } - ] + ] as unknown as IFormSectionData }, event: Event.Marriage } -describe('when user wants to review death certificate', () => { +async function setupTest({ + declaration, + certTemplateId, + registrationId +}: { + declaration: any + certTemplateId: string + registrationId: string +}) { + global.fetch = vi.fn().mockImplementation(() => + Promise.resolve({ + text: vi.fn().mockResolvedValue(mockSvgTemplate) + }) + ) + + const { history, match } = createRouterProps( + '/', + { isNavigatedInsideApp: false }, + { + matchParams: { + registrationId, + certTemplateId + } + } + ) + + ;(useParams as Mock).mockImplementation(() => match.params) + + const { store } = createStore(history) + loginAsFieldAgent(store) + const clonedDeclaration = cloneDeep(declaration) + + await flushPromises() + store.dispatch(storeDeclaration(clonedDeclaration)) + + const component = await createTestComponent(, { + store, + history + }) + + await flushPromises() + component.update() + + return component +} + +describe('Review Certificate Tests', () => { let component: ReactWrapper<{}, {}> - beforeEach(async () => { - const mockSvgTemplate = 'Sample Certificate' - global.fetch = vi.fn().mockImplementation((url) => { - return Promise.resolve({ - text: vi.fn().mockResolvedValue(mockSvgTemplate) + + describe('when user wants to review death certificate', () => { + beforeEach(async () => { + component = await setupTest({ + declaration: deathDeclaration, + certTemplateId: 'death-certificate', + registrationId: 'mockDeath1234' }) }) - const { history, match } = createRouterProps( - '/', - { isNavigatedInsideApp: false }, - { - matchParams: { - registrationId: 'mockDeath1234', - certTemplateId: 'death-certificate' - } - } - ) - ;(useParams as Mock).mockImplementation(() => match.params) - const { store } = createStore(history) - loginAsFieldAgent(store) - const clonedMockDeathDeclarationData = cloneDeep(deathDeclaration) - await flushPromises() - // @ts-ignore - store.dispatch(storeDeclaration(clonedMockDeathDeclarationData)) - component = await createTestComponent(, { - store, - history + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length + expect(confirmBtnExist).toBe(true) + }) + }) + + describe('when user wants to review birth certificate', () => { + beforeEach(async () => { + component = await setupTest({ + declaration: birthDeclaration, + certTemplateId: 'birth-certificate', + registrationId: 'mockBirth1234' + }) + }) + + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length + expect(confirmBtnExist).toBe(true) + }) + + it('shows the Confirm Print Modal', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + const modal = await waitForElement(component, '#confirm-print-modal') + const modalIsDisplayed = !!modal.hostNodes().length + expect(modalIsDisplayed).toBe(true) + }) + + it('closes the modal on clicking the print the button', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + component.find('#print-certificate').hostNodes().simulate('click') + component.update() + + const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() + .length + expect(modalIsClosed).toBe(false) }) - await flushPromises() - component.update() }) - it('displays the "Confirm & Print" button', async () => { - const confirmBtnExist = !!( - await waitForElement(component, '#confirm-print') - ).hostNodes().length - expect(confirmBtnExist).toBe(true) + describe('when user wants to review marriage certificate', () => { + beforeEach(async () => { + component = await setupTest({ + declaration: marriageDeclaration, + certTemplateId: 'marriage-certificate', + registrationId: 'mockMarriage1234' + }) + }) + + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length + expect(confirmBtnExist).toBe(true) + }) + + it('shows the Confirm Print Modal', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + const modalIsDisplayed = !!( + await waitForElement(component, '#confirm-print-modal') + ).hostNodes().length + expect(modalIsDisplayed).toBe(true) + }) + + it('closes the modal on clicking the print the button', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + component.find('#print-certificate').hostNodes().simulate('click') + component.update() + + const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() + .length + expect(modalIsClosed).toBe(false) + }) }) }) -describe('back button behavior tests of review certificate action', () => { +describe('Back button behavior tests of review certificate action', () => { let component: ReactWrapper - const mockBirthDeclarationData = { - ...cloneDeep(mockDeclarationData), - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - } - mockBirthDeclarationData.registration.certificates[0] = { - collector: { - type: 'PRINT_IN_ADVANCE' - } - } - it('takes user history back when navigated from inside app', async () => { const { history, match } = createRouterProps( '/previous-route', @@ -152,17 +239,10 @@ describe('back button behavior tests of review certificate action', () => { const { store } = createStore(history) store.dispatch(push('/new-route', { isNavigatedInsideApp: true })) - loginAsFieldAgent(store) - const birthDeclaration = { - id: 'asdhdqe2472487jsdfsdf', - data: mockBirthDeclarationData, - certTemplateId: 'birth-certificate-copy' - } - store.dispatch( - // @ts-ignore - storeDeclaration(birthDeclaration) - ) + + store.dispatch(storeDeclaration(birthDeclaration)) + component = await createTestComponent(, { store, history @@ -178,7 +258,7 @@ describe('back button behavior tests of review certificate action', () => { { isNavigatedInsideApp: false }, { matchParams: { - registrationId: 'asdhdqe2472487jsdfsdf', + registrationId: 'mockBirth1234', eventType: Event.Birth } } @@ -187,174 +267,16 @@ describe('back button behavior tests of review certificate action', () => { const { store } = createStore(history) loginAsFieldAgent(store) - store.dispatch( - // @ts-ignore - storeDeclaration({ - id: 'asdhdqe2472487jsdfsdf', - data: mockBirthDeclarationData, - certTemplateId: 'birth-certificate-copy' - } as IDeclaration) - ) - component = await createTestComponent(, { - store, - history - }) - component.find('#action_page_back_button').hostNodes().simulate('click') - await flushPromises() - expect(history.location.pathname).toContain('/registration-home/print/') - }) -}) - -describe('when user wants to review birth certificate', () => { - let component: ReactWrapper<{}, {}> - beforeEach(async () => { - const mockSvgTemplate = 'Sample Certificate' - global.fetch = vi.fn().mockImplementation((url) => { - return Promise.resolve({ - text: vi.fn().mockResolvedValue(mockSvgTemplate) - }) - }) - const { history, match } = createRouterProps( - '/', - { isNavigatedInsideApp: false }, - { - matchParams: { - registrationId: 'mockBirth1234', - certTemplateId: 'birth-certificate' - } - } - ) - ;(useParams as Mock).mockImplementation(() => match.params) - const { store } = createStore(history) - loginAsFieldAgent(store) - await flushPromises() - - const clonedMockBirthDeclaration = cloneDeep(mockBirthDeclaration) - // @ts-ignore - store.dispatch(storeDeclaration(clonedMockBirthDeclaration)) + store.dispatch(storeDeclaration(birthDeclaration)) component = await createTestComponent(, { store, history }) - await flushPromises() - component.update() - }) - it('displays have the Continue and print Button', async () => { - const confirmBtn = await waitForElement(component, '#confirm-print') - const confirmBtnExist = !!confirmBtn.hostNodes().length - expect(confirmBtnExist).toBe(true) - }) - it('shows the Confirm Print Modal', async () => { - const confirmBtn = await waitForElement(component, '#confirm-print') - confirmBtn.hostNodes().simulate('click') - component.update() - const modal = await waitForElement(component, '#confirm-print-modal') - const modalIsDisplayed = !!modal.hostNodes().length - expect(modalIsDisplayed).toBe(true) - }) - - it('closes the modal on clicking the print the button', async () => { - const confirmBtn = await waitForElement(component, '#confirm-print') - confirmBtn.hostNodes().simulate('click') - component.update() - component.find('#print-certificate').hostNodes().simulate('click') - component.update() - - const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() - .length - - expect(modalIsClosed).toBe(false) - }) - - afterAll(() => { - flushPromises() - }) -}) - -describe('when user wants to review marriage certificate', () => { - let component: ReactWrapper<{}, {}> - - beforeEach(async () => { - const mockSvgTemplate = 'Sample Certificate' - global.fetch = vi.fn().mockImplementation((url) => { - return Promise.resolve({ - text: vi.fn().mockResolvedValue(mockSvgTemplate) - }) - }) - const { history, match } = createRouterProps( - '/', - { isNavigatedInsideApp: false }, - { - matchParams: { - registrationId: '1234896128934719', - certTemplateId: 'marriage-certificate' - } - } - ) - ;(useParams as Mock).mockImplementation(() => match.params) - const { store } = createStore(history) - - const mockMarriageData = cloneDeep(mockMarriageDeclarationData) - - loginAsFieldAgent(store) - await flushPromises() - store.dispatch( - storeDeclaration({ - id: '1234896128934719', - data: { - ...mockMarriageData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] as unknown as IFormSectionData - }, - event: Event.Marriage - }) - ) - - component = await createTestComponent(, { - store, - history - }) - await flushPromises() - component.update() - }) - - it('displays have the Continue and print Button toot', async () => { - const confirmBtnExist = !!( - await waitForElement(component, '#confirm-print') - ).hostNodes().length - expect(confirmBtnExist).toBe(true) - }) - - it('shows the Confirm Print Modal', async () => { - const confirmBtn = ( - await waitForElement(component, '#confirm-print') - ).hostNodes() - confirmBtn.simulate('click') - component.update() - const modalIsDisplayed = !!( - await waitForElement(component, '#confirm-print-modal') - ).hostNodes().length - expect(modalIsDisplayed).toBe(true) - }) - - it('closes the modal on clicking the print the button', async () => { - const confirmBtn = await waitForElement(component, '#confirm-print') - confirmBtn.hostNodes().simulate('click') - component.update() - component.find('#print-certificate').hostNodes().simulate('click') - component.update() - - const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() - .length + component.find('#action_page_back_button').hostNodes().simulate('click') - expect(modalIsClosed).toBe(false) + expect(history.location.pathname).toContain('/registration-home/print/') }) }) diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx index e5ccd9b186b..1a5c134813a 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx @@ -13,255 +13,220 @@ import { createStore } from '@client/store' import { createTestComponent, mockDeclarationData, - mockDeathDeclarationData + mockDeathDeclarationData, + flushPromises } from '@client/tests/util' import { VerifyCollector } from './VerifyCollector' import { storeDeclaration } from '@client/declarations' import { Event } from '@client/utils/gateway' import { ReactWrapper } from 'enzyme' +// Common mock data +const birthDeclaration = { + id: 'mockBirth1234', + data: { + ...mockDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] + }, + event: Event.Birth +} + +const deathDeclaration = { + id: 'mockDeath1234', + data: { + ...mockDeathDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] + }, + event: Event.Death +} + +// Helper function for setting up tests +async function setupTest({ + registrationId, + certTemplateId, + collector, + declaration, + store, + history +}: { + registrationId: string + certTemplateId: string + collector: string + declaration: any + store: any + history: any +}) { + store.dispatch(storeDeclaration(declaration)) + await flushPromises() + + const testComponent = await createTestComponent( + // @ts-ignore + , + { store, history } + ) + + return testComponent +} + describe('verify collector tests', () => { const { store, history } = createStore() - const birthDeclaration = { - id: 'mockBirth1234', - data: { - ...mockDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Birth - } - - const deathDeclaration = { - id: 'mockDeath1234', - data: { - ...mockDeathDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Death - } - describe('in case of birth declaration', () => { + let testComponent: ReactWrapper + beforeAll(async () => { - // @ts-ignore - store.dispatch(storeDeclaration(birthDeclaration)) + testComponent = await setupTest({ + registrationId: 'mockBirth1234', + certTemplateId: 'birth-certificate-copy', + collector: 'mother', + declaration: birthDeclaration, + store, + history + }) }) - it('when mother is collector renders idVerifier component', async () => { - const testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - + it('renders idVerifier component when mother is collector', () => { expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) }) - it('should takes user go back', async () => { - const testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - + it('takes user back on clicking back button', async () => { testComponent .find('#action_page_back_button') .hostNodes() .simulate('click') - await new Promise((resolve) => { - setTimeout(resolve, 500) - }) - + await flushPromises() testComponent.update() expect(history.location.pathname).toBe('/') }) + }) - describe('when informant is collector', () => { - let testComponent: ReactWrapper - beforeAll(() => { - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) - }) - beforeEach(async () => { - testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - }) - - it('renders idVerifier compomnent', () => { - expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) - }) - - it('clicking on yes button takes user to review certificate if there is no fee', () => { - testComponent - .find('#idVerifier') - .find('#verifyPositive') - .hostNodes() - .simulate('click') + describe('when informant is collector for death declaration', () => { + let testComponent: ReactWrapper - expect(history.location.pathname).toContain('review') + beforeAll(async () => { + testComponent = await setupTest({ + registrationId: 'mockDeath1234', + certTemplateId: 'death-certificate', + collector: 'informant', + declaration: deathDeclaration, + store, + history }) + }) - describe('when father is collector', () => { - let testComponent: ReactWrapper - beforeAll(() => { - // @ts-ignore - store.dispatch(storeDeclaration(birthDeclaration)) - }) - beforeEach(async () => { - testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - }) + it('renders idVerifier component', () => { + expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) + }) - it('clicking on send button on modal takes user to payment if there is fee', () => { - testComponent - .find('#idVerifier') - .find('#verifyNegative') - .hostNodes() - .simulate('click') + it('redirects to review page on clicking yes button with no fee', () => { + testComponent + .find('#idVerifier #verifyPositive') + .hostNodes() + .simulate('click') - testComponent.update() + expect(history.location.pathname).toContain('review') + }) + it('clicking on cancel button hides the modal', () => { + testComponent + .find('#idVerifier #verifyNegative') + .hostNodes() + .simulate('click') - testComponent - .find('#withoutVerificationPrompt') - .find('#send') - .hostNodes() - .simulate('click') + testComponent.update() + testComponent + .find('#withoutVerificationPrompt #cancel') + .hostNodes() + .simulate('click') - expect(history.location.pathname).toContain('payment') - }) - }) + testComponent.update() + expect( + testComponent.find('#withoutVerificationPrompt').hostNodes() + ).toHaveLength(0) + }) + it('shows modal on clicking no button', () => { + testComponent + .find('#idVerifier #verifyNegative') + .hostNodes() + .simulate('click') - it('clicking on no button shows up modal', () => { - testComponent - .find('#idVerifier') - .find('#verifyNegative') - .hostNodes() - .simulate('click') + testComponent.update() + expect( + testComponent.find('#withoutVerificationPrompt').hostNodes() + ).toHaveLength(1) + }) + }) - testComponent.update() + describe('when father is collector for birth declaration', () => { + let testComponent: ReactWrapper - expect( - testComponent.find('#withoutVerificationPrompt').hostNodes() - ).toHaveLength(1) + beforeAll(async () => { + testComponent = await setupTest({ + registrationId: 'mockBirth1234', + certTemplateId: 'birth-certificate-copy', + collector: 'father', + declaration: birthDeclaration, + store, + history }) + }) - it('clicking on cancel button hides the modal', () => { - testComponent - .find('#idVerifier') - .find('#verifyNegative') - .hostNodes() - .simulate('click') - - testComponent.update() - - testComponent - .find('#withoutVerificationPrompt') - .find('#cancel') - .hostNodes() - .simulate('click') + it('takes user to payment page on clicking send button when there is fee', () => { + testComponent + .find('#idVerifier #verifyNegative') + .hostNodes() + .simulate('click') - testComponent.update() + testComponent.update() + testComponent + .find('#withoutVerificationPrompt #send') + .hostNodes() + .simulate('click') - expect( - testComponent.find('#withoutVerificationPrompt').hostNodes() - ).toHaveLength(0) - }) + expect(history.location.pathname).toContain('payment') }) }) - describe('in case of death declaration renders idVerifier component', () => { - beforeAll(() => { - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) - }) + describe('in case of death declaration with informant as collector', () => { + let testComponent: ReactWrapper - it('when informant is collector', async () => { - const testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) + beforeAll(async () => { + testComponent = await setupTest({ + registrationId: 'mockDeath1234', + certTemplateId: 'death-certificate', + collector: 'informant', + declaration: deathDeclaration, + store, + history + }) + }) + it('renders idVerifier component', () => { expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) }) }) diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx index 60a87577da9..9b035174c6f 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx @@ -14,7 +14,6 @@ import { modifyDeclaration, writeDeclaration } from '@client/declarations' -import { Event } from '@client/utils/gateway' import { messages } from '@client/i18n/messages/views/certificate' import { formatUrl, From 2f87522d219e22040c52c0bbfbc17891bf340fab Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:14:40 +0600 Subject: [PATCH 14/58] certTemplateId added in bundle in workflow --- packages/workflow/src/documents.ts | 7 +------ packages/workflow/src/records/fhir.ts | 21 +++++++++++++++++-- .../workflow/src/records/state-transitions.ts | 8 +++++-- packages/workflow/src/records/validations.ts | 8 +++---- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/workflow/src/documents.ts b/packages/workflow/src/documents.ts index 671c4b444c2..faf3c014dbb 100644 --- a/packages/workflow/src/documents.ts +++ b/packages/workflow/src/documents.ts @@ -61,12 +61,7 @@ export async function uploadCertificateAttachmentsToDocumentsStore< affidavit.data = await uploadBase64ToMinio(affidavit.data, authHeader) } } - if ('data' in certificateDetails) { - certificateDetails.data = await uploadBase64ToMinio( - certificateDetails.data, - authHeader - ) - } + return certificateDetails } diff --git a/packages/workflow/src/records/fhir.ts b/packages/workflow/src/records/fhir.ts index 78d714d1b95..090fec99a5b 100644 --- a/packages/workflow/src/records/fhir.ts +++ b/packages/workflow/src/records/fhir.ts @@ -218,6 +218,7 @@ export function createDocumentReferenceEntryForCertificate( temporaryRelatedPersonId: UUID, eventType: EVENT_TYPE, hasShowedVerifiedDocument: boolean, + certTemplateId?: string, attachmentUrl?: string, paymentUrl?: URNReference | ResourceIdentifier ): BundleEntry { @@ -240,6 +241,10 @@ export function createDocumentReferenceEntryForCertificate( url: 'http://opencrvs.org/specs/extension/hasShowedVerifiedDocument', valueBoolean: hasShowedVerifiedDocument }, + { + url: 'http://opencrvs.org/specs/extension/certTemplateId', + valueString: certTemplateId + }, ...(paymentUrl ? [ { @@ -912,8 +917,20 @@ export async function createUnassignedTask( return unassignedTask } -export function createCertifiedTask(previousTask: SavedTask): SavedTask { - return createNewTaskResource(previousTask, [], 'CERTIFIED') +export function createCertifiedTask( + previousTask: SavedTask, + certTemplateId: string +): SavedTask { + return createNewTaskResource( + previousTask, + [ + { + url: 'http://opencrvs.org/specs/extension/certTemplateId', + valueString: certTemplateId + } + ], + 'CERTIFIED' + ) } export function createIssuedTask(previousTask: SavedTask): SavedTask { diff --git a/packages/workflow/src/records/state-transitions.ts b/packages/workflow/src/records/state-transitions.ts index ec82f448f23..a83a2a24ced 100644 --- a/packages/workflow/src/records/state-transitions.ts +++ b/packages/workflow/src/records/state-transitions.ts @@ -941,7 +941,10 @@ export async function toCertified( certificateDetails: CertifyInput ): Promise { const previousTask = getTaskFromSavedBundle(record) - const taskWithoutPractitionerExtensions = createCertifiedTask(previousTask) + const taskWithoutPractitionerExtensions = createCertifiedTask( + previousTask, + certificateDetails.certTemplateId + ) const [certifiedTask, practitionerResourcesBundle] = await withPractitionerDetails(taskWithoutPractitionerExtensions, token) @@ -960,7 +963,7 @@ export async function toCertified( temporaryRelatedPersonId, eventType, certificateDetails.hasShowedVerifiedDocument, - certificateDetails.data + certificateDetails.certTemplateId ) const certificateSection: CompositionSection = { @@ -1042,6 +1045,7 @@ export async function toIssued( temporaryRelatedPersonId, eventType, certificateDetails.hasShowedVerifiedDocument, + certificateDetails.certTemplateId, undefined, paymentEntry.fullUrl ) diff --git a/packages/workflow/src/records/validations.ts b/packages/workflow/src/records/validations.ts index fb81ff2b635..99e82a47bb3 100644 --- a/packages/workflow/src/records/validations.ts +++ b/packages/workflow/src/records/validations.ts @@ -18,7 +18,7 @@ export const CertifyRequestSchema = z.object({ event: z.custom(), certificate: z.object({ hasShowedVerifiedDocument: z.boolean(), - data: z.string(), + certTemplateId: z.string(), collector: z .object({ relationship: z.string(), @@ -63,9 +63,9 @@ const PaymentSchema = z.object({ export const IssueRequestSchema = z.object({ event: z.custom(), - certificate: CertifyRequestSchema.shape.certificate - .omit({ data: true }) - .and(z.object({ payment: PaymentSchema })) + certificate: CertifyRequestSchema.shape.certificate.and( + z.object({ payment: PaymentSchema }) + ) }) export const ChangedValuesInput = z.array( From 6b1f8553d65a9770a5f8aebfdd435399aa348bb5 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:19:08 +0600 Subject: [PATCH 15/58] certTemplateId adjusted in gateway --- packages/commons/src/fhir/extension.ts | 4 ++++ .../commons/src/fhir/transformers/input.ts | 2 +- .../src/features/registration/schema.graphql | 5 +++-- .../features/registration/type-resolvers.ts | 16 ++++++++++++-- packages/gateway/src/graphql/schema.d.ts | 22 +++++++++++++++---- packages/gateway/src/graphql/schema.graphql | 5 +++-- 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/packages/commons/src/fhir/extension.ts b/packages/commons/src/fhir/extension.ts index 002c5bc2897..a7d4c1b1d5a 100644 --- a/packages/commons/src/fhir/extension.ts +++ b/packages/commons/src/fhir/extension.ts @@ -128,6 +128,10 @@ export type StringExtensionType = { valueString?: string valueBoolean: boolean } + 'http://opencrvs.org/specs/extension/certTemplateId': { + url: 'http://opencrvs.org/specs/extension/certTemplateId' + valueString?: string + } 'http://opencrvs.org/specs/extension/regLastOffice': { url: 'http://opencrvs.org/specs/extension/regLastOffice' valueReference: { reference: ResourceIdentifier } diff --git a/packages/commons/src/fhir/transformers/input.ts b/packages/commons/src/fhir/transformers/input.ts index 43890b41993..73c5f3cad03 100644 --- a/packages/commons/src/fhir/transformers/input.ts +++ b/packages/commons/src/fhir/transformers/input.ts @@ -207,7 +207,7 @@ interface Certificate { collector?: RelatedPerson hasShowedVerifiedDocument?: boolean payments?: Array - data?: string + certTemplateId?: string } interface Deceased { deceased?: boolean diff --git a/packages/gateway/src/features/registration/schema.graphql b/packages/gateway/src/features/registration/schema.graphql index cc30d8580f1..a825f4bfbce 100644 --- a/packages/gateway/src/features/registration/schema.graphql +++ b/packages/gateway/src/features/registration/schema.graphql @@ -159,6 +159,7 @@ type History { requester: String requesterOther: String hasShowedVerifiedDocument: Boolean + certTemplateId: String noSupportingDocumentationRequired: Boolean otherReason: String #This doesn't resolve to the System model properly rather @@ -418,14 +419,14 @@ input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - data: String + certTemplateId: String } type Certificate { # -> Document Reference collector: RelatedPerson # -> .extension hasShowedVerifiedDocument: Boolean # -> .extension payments: [Payment] # -> .extension - data: String # -> .content.attachment.data base64 + certTemplateId: String } input QuestionnaireQuestionInput { diff --git a/packages/gateway/src/features/registration/type-resolvers.ts b/packages/gateway/src/features/registration/type-resolvers.ts index adea50cede8..00d86167864 100644 --- a/packages/gateway/src/features/registration/type-resolvers.ts +++ b/packages/gateway/src/features/registration/type-resolvers.ts @@ -1222,6 +1222,13 @@ export const typeResolvers: GQLResolver = { } return false + }, + certTemplateId(docRef: DocumentReference, _) { + const certTemplateId = findExtension( + `${OPENCRVS_SPECIFICATION_URL}extension/certTemplateId`, + docRef.extension as Extension[] + ) + return certTemplateId?.valueString } }, Identifier: { @@ -1387,7 +1394,6 @@ export const typeResolvers: GQLResolver = { `${OPENCRVS_SPECIFICATION_URL}extension/hasShowedVerifiedDocument`, task.extension as Extension[] ) - if (hasShowedDocument?.valueString) { return Boolean(hasShowedDocument?.valueString) } @@ -1398,7 +1404,13 @@ export const typeResolvers: GQLResolver = { return false }, - + certTemplateId: (task: Task) => { + const certTemplateId = findExtension( + `${OPENCRVS_SPECIFICATION_URL}extension/certTemplateId`, + task.extension as Extension[] + ) + return certTemplateId?.valueString + }, noSupportingDocumentationRequired: (task: Task) => { const hasShowedDocument = findExtension( NO_SUPPORTING_DOCUMENTATION_REQUIRED, diff --git a/packages/gateway/src/graphql/schema.d.ts b/packages/gateway/src/graphql/schema.d.ts index 36e768d82eb..81ad6cab0c1 100644 --- a/packages/gateway/src/graphql/schema.d.ts +++ b/packages/gateway/src/graphql/schema.d.ts @@ -757,6 +757,7 @@ export interface GQLHistory { requester?: string requesterOther?: string hasShowedVerifiedDocument?: boolean + certTemplateId?: string noSupportingDocumentationRequired?: boolean otherReason?: string system?: GQLIntegratedSystem @@ -1248,7 +1249,7 @@ export interface GQLCertificate { collector?: GQLRelatedPerson hasShowedVerifiedDocument?: boolean payments?: Array - data?: string + certTemplateId?: string } export interface GQLDuplicatesInfo { @@ -1570,7 +1571,7 @@ export interface GQLCertificateInput { collector?: GQLRelatedPersonInput hasShowedVerifiedDocument?: boolean payments?: Array - data?: string + certTemplateId?: string } export interface GQLIdentityInput { @@ -6251,6 +6252,7 @@ export interface GQLHistoryTypeResolver { requester?: HistoryToRequesterResolver requesterOther?: HistoryToRequesterOtherResolver hasShowedVerifiedDocument?: HistoryToHasShowedVerifiedDocumentResolver + certTemplateId?: HistoryToCertTemplateIdResolver noSupportingDocumentationRequired?: HistoryToNoSupportingDocumentationRequiredResolver otherReason?: HistoryToOtherReasonResolver system?: HistoryToSystemResolver @@ -6370,6 +6372,15 @@ export interface HistoryToHasShowedVerifiedDocumentResolver< ): TResult } +export interface HistoryToCertTemplateIdResolver { + ( + parent: TParent, + args: {}, + context: Context, + info: GraphQLResolveInfo + ): TResult +} + export interface HistoryToNoSupportingDocumentationRequiredResolver< TParent = any, TResult = any @@ -7948,7 +7959,7 @@ export interface GQLCertificateTypeResolver { collector?: CertificateToCollectorResolver hasShowedVerifiedDocument?: CertificateToHasShowedVerifiedDocumentResolver payments?: CertificateToPaymentsResolver - data?: CertificateToDataResolver + certTemplateId?: CertificateToCertTemplateIdResolver } export interface CertificateToCollectorResolver { @@ -7981,7 +7992,10 @@ export interface CertificateToPaymentsResolver { ): TResult } -export interface CertificateToDataResolver { +export interface CertificateToCertTemplateIdResolver< + TParent = any, + TResult = any +> { ( parent: TParent, args: {}, diff --git a/packages/gateway/src/graphql/schema.graphql b/packages/gateway/src/graphql/schema.graphql index 6aaf55c669e..0212dcaf4cc 100644 --- a/packages/gateway/src/graphql/schema.graphql +++ b/packages/gateway/src/graphql/schema.graphql @@ -878,6 +878,7 @@ type History { requester: String requesterOther: String hasShowedVerifiedDocument: Boolean + certTemplateId: String noSupportingDocumentationRequired: Boolean otherReason: String system: IntegratedSystem @@ -1345,7 +1346,7 @@ type Certificate { collector: RelatedPerson hasShowedVerifiedDocument: Boolean payments: [Payment] - data: String + certTemplateId: String } type DuplicatesInfo { @@ -1666,7 +1667,7 @@ input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - data: String + certTemplateId: String } input IdentityInput { From a1c7b25611e8f6358358a3256a8fd73520a6910f Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:23:53 +0600 Subject: [PATCH 16/58] registration-mappings and gql queries updated to facilitate certTemplateId --- .../birth/mutation/registration-mappings.ts | 16 ++++-- .../birth/query/registration-mappings.ts | 25 ++++++++- .../death/mutation/registration-mappings.ts | 16 ++++-- .../death/query/registration-mappings.ts | 53 ++++++++++++------- .../mutation/registration-mappings.ts | 24 +++++++-- .../marriage/query/registration-mappings.ts | 24 ++++++++- .../forms/register/mappings/mutation/utils.ts | 38 ++++++++++--- .../src/views/DataProvider/birth/queries.ts | 21 ++++++++ .../src/views/DataProvider/death/queries.ts | 21 ++++++++ .../views/DataProvider/marriage/queries.ts | 19 +++++++ 10 files changed, 212 insertions(+), 45 deletions(-) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts index 4328580a695..a0d1acc08ab 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts @@ -44,11 +44,17 @@ export function setBirthRegistrationSectionTransformer( }) } - if (draftData[sectionId].certificates) { - transformCertificateData( - transformedData, - (draftData[sectionId].certificates as ICertificate[])[0], - sectionId + if ( + Array.isArray(draftData[sectionId].certificates) && + draftData[sectionId].certificates.length + ) { + const updatedCertificates = transformCertificateData( + (draftData[sectionId].certificates as ICertificate[]).slice(-1) ) + transformedData[sectionId].certificates = + updatedCertificates.length > 0 && + Object.keys(updatedCertificates[0]).length > 0 // making sure we are not sending empty object as certificate + ? updatedCertificates + : [] } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts index 31f290ae004..ac7a8babf6f 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts @@ -8,14 +8,18 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { IFormData } from '@client/forms' +import { IFormData, IFormField } from '@client/forms' import { Event } from '@client/utils/gateway' import { transformStatusData } from '@client/forms/register/mappings/query/utils' +import { IOfflineData } from '@client/offline/reducer' export function getBirthRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - sectionId: string + sectionId: string, + fieldDef: IFormField, + nestedFormField?: IFormField, + offlineData?: IOfflineData ) { if (queryData[sectionId].trackingId) { transformedData[sectionId].trackingId = queryData[sectionId].trackingId @@ -33,4 +37,21 @@ export function getBirthRegistrationSectionTransformer( if (queryData[sectionId].status) { transformStatusData(transformedData, queryData[sectionId].status, sectionId) } + + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + const certificate = queryData[sectionId].certificates.slice(-1)[0] + // since we shall need this certificate only for ready to issue tab, to calculate certificate fee + transformedData[sectionId].certificates = certificate?.certTemplateId + ? [ + { + templateConfig: offlineData?.templates.certificates?.find( + (x) => x.id === certificate.certTemplateId + ) + } + ] + : [] + } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts index 2741b81e4c8..664432eaddf 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts @@ -51,12 +51,18 @@ export function setDeathRegistrationSectionTransformer( }) } - if (draftData.registration.certificates) { - transformCertificateData( - transformedData, - (draftData.registration.certificates as ICertificate[])[0], - 'registration' + if ( + Array.isArray(draftData[sectionId].certificates) && + draftData[sectionId].certificates.length + ) { + const updatedCertificates = transformCertificateData( + (draftData[sectionId].certificates as ICertificate[]).slice(-1) ) + transformedData[sectionId].certificates = + updatedCertificates.length > 0 && + Object.keys(updatedCertificates[0]).length > 0 // making sure we are not sending empty object as certificate + ? updatedCertificates + : [] } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts index acd0fb058fd..02bd8068d10 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts @@ -8,45 +8,62 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { IFormData } from '@client/forms' +import { IFormData, IFormField } from '@client/forms' import { Event } from '@client/utils/gateway' import type { GQLRegWorkflow } from '@client/utils/gateway-deprecated-do-not-use' import { transformStatusData } from '@client/forms/register/mappings/query/utils' +import { IOfflineData } from '@client/offline/reducer' export function getDeathRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - _sectionId: string + sectionId: string, + fieldDef: IFormField, + nestedFormField?: IFormField, + offlineData?: IOfflineData ) { - if (!transformedData['registration']) { - transformedData['registration'] = {} + if (!transformedData[sectionId]) { + transformedData[sectionId] = {} } - if (queryData['registration'].id) { - transformedData['registration']._fhirID = queryData['registration'].id + if (queryData[sectionId].id) { + transformedData[sectionId]._fhirID = queryData[sectionId].id } - if (queryData['registration'].trackingId) { - transformedData['registration'].trackingId = - queryData['registration'].trackingId + if (queryData[sectionId].trackingId) { + transformedData[sectionId].trackingId = queryData[sectionId].trackingId } - if (queryData['registration'].registrationNumber) { - transformedData['registration'].registrationNumber = - queryData['registration'].registrationNumber + if (queryData[sectionId].registrationNumber) { + transformedData[sectionId].registrationNumber = + queryData[sectionId].registrationNumber + } + + if (queryData[sectionId].type && queryData[sectionId].type === 'DEATH') { + transformedData[sectionId].type = Event.Death } if ( - queryData['registration'].type && - queryData['registration'].type === 'DEATH' + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 ) { - transformedData['registration'].type = Event.Death + const certificate = queryData[sectionId].certificates.slice(-1)[0] + // since we shall need this certificate only for ready to issue tab, to calculate certificate fee + transformedData[sectionId].certificates = certificate?.certTemplateId + ? [ + { + templateConfig: offlineData?.templates.certificates?.find( + (x) => x.id === certificate.certTemplateId + ) + } + ] + : [] } - if (queryData['registration'].status) { + if (queryData[sectionId].status) { transformStatusData( transformedData, - queryData['registration'].status as GQLRegWorkflow[], - 'registration' + queryData[sectionId].status as GQLRegWorkflow[], + sectionId ) } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts index fb9c8a5cbc5..046ea7569c7 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts @@ -50,12 +50,26 @@ export function setMarriageRegistrationSectionTransformer( }) } - if (draftData[sectionId].certificates) { - transformCertificateData( - transformedData, - (draftData[sectionId].certificates as ICertificate[])[0], - sectionId + if ( + Array.isArray(draftData[sectionId].certificates) && + draftData[sectionId].certificates.length > 0 + ) { + transformedData[sectionId].certificates = + draftData[sectionId].certificates.slice(-1) + } + + if ( + Array.isArray(draftData[sectionId].certificates) && + draftData[sectionId].certificates.length + ) { + const updatedCertificates = transformCertificateData( + (draftData[sectionId].certificates as ICertificate[]).slice(-1) ) + transformedData[sectionId].certificates = + updatedCertificates.length > 0 && + Object.keys(updatedCertificates[0]).length > 0 // making sure we are not sending empty object as certificate + ? updatedCertificates + : [] } } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts index 0995b1b5a61..92c68ed9505 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts @@ -8,15 +8,19 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { IFormData } from '@client/forms' +import { IFormData, IFormField } from '@client/forms' import { transformStatusData } from '@client/forms/register/mappings/query/utils' +import { IOfflineData } from '@client/offline/reducer' import { Event } from '@client/utils/gateway' import type { GQLRegWorkflow } from '@client/utils/gateway-deprecated-do-not-use' export function getMarriageRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - sectionId: string + sectionId: string, + fieldDef: IFormField, + nestedFormField?: IFormField, + offlineData?: IOfflineData ) { if (queryData[sectionId].trackingId) { transformedData[sectionId].trackingId = queryData[sectionId].trackingId @@ -35,6 +39,22 @@ export function getMarriageRegistrationSectionTransformer( transformedData[sectionId].type = Event.Marriage } + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + const certificate = queryData[sectionId].certificates.slice(-1)[0] + // since we shall need this certificate only for ready to issue tab, to calculate certificate fee + transformedData[sectionId].certificates = certificate?.certTemplateId + ? [ + { + templateConfig: offlineData?.templates.certificates?.find( + (x) => x.id === certificate.certTemplateId + ) + } + ] + : [] + } if (queryData[sectionId].status) { transformStatusData( transformedData, diff --git a/packages/client/src/forms/register/mappings/mutation/utils.ts b/packages/client/src/forms/register/mappings/mutation/utils.ts index 802ca2fd848..d49f0bdd158 100644 --- a/packages/client/src/forms/register/mappings/mutation/utils.ts +++ b/packages/client/src/forms/register/mappings/mutation/utils.ts @@ -13,16 +13,30 @@ import type { GQLRelatedPersonInput } from '@client/utils/gateway-deprecated-do- import { ICertificate, IFileValue, TransformedData } from '@client/forms' import { omit } from 'lodash' -export function transformCertificateData( - transformedData: TransformedData, - certificateData: ICertificate, - sectionId: string -) { - transformedData[sectionId].certificates = [ +export function stripTypename(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(stripTypename) + } else if (obj !== null && typeof obj === 'object') { + const newObj: any = {} + for (const key in obj) { + if (key !== '__typename' && Object.hasOwn(obj, key)) { + newObj[key] = stripTypename(obj[key]) + } + } + return newObj + } + return obj +} +export function transformCertificateData(certificates: ICertificate[]) { + const certificateData = certificates[0] + + // Prepare the base certificate data + const updatedCertificates: ICertificate[] = [ { - ...omit(certificateData, 'collector') + ...omit(certificateData, 'collector', 'templateConfig') } ] + // for collector mapping if (certificateData && certificateData.collector) { let collector: GQLRelatedPersonInput = {} @@ -58,6 +72,14 @@ export function transformCertificateData( } ] } - transformedData[sectionId].certificates[0].collector = collector + updatedCertificates[0].collector = collector as any + } + + // for templateConfig mapping + if (certificateData && certificateData.templateConfig) { + updatedCertificates[0].certTemplateId = certificateData.templateConfig.id } + + // Return the processed certificates array + return updatedCertificates } diff --git a/packages/client/src/views/DataProvider/birth/queries.ts b/packages/client/src/views/DataProvider/birth/queries.ts index 441ac15bc78..a19dff59f36 100644 --- a/packages/client/src/views/DataProvider/birth/queries.ts +++ b/packages/client/src/views/DataProvider/birth/queries.ts @@ -149,6 +149,24 @@ const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` contactRelationship contactPhoneNumber contactEmail + certificates { + hasShowedVerifiedDocument + certTemplateId + collector { + relationship + otherRelationship + name { + use + firstNames + familyName + } + telecom { + system + value + use + } + } + } duplicates { compositionId trackingId @@ -206,6 +224,7 @@ const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` requesterOther noSupportingDocumentationRequired hasShowedVerifiedDocument + certTemplateId date action regStatus @@ -294,6 +313,7 @@ const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` } certificates { hasShowedVerifiedDocument + certTemplateId collector { relationship otherRelationship @@ -569,6 +589,7 @@ export const GET_BIRTH_REGISTRATION_FOR_CERTIFICATE = gql` } certificates { hasShowedVerifiedDocument + certTemplateId collector { relationship otherRelationship diff --git a/packages/client/src/views/DataProvider/death/queries.ts b/packages/client/src/views/DataProvider/death/queries.ts index 86aecd6e7ce..3c09cb067c2 100644 --- a/packages/client/src/views/DataProvider/death/queries.ts +++ b/packages/client/src/views/DataProvider/death/queries.ts @@ -213,6 +213,24 @@ const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` contactRelationship contactPhoneNumber contactEmail + certificates { + hasShowedVerifiedDocument + certTemplateId + collector { + relationship + otherRelationship + name { + use + firstNames + familyName + } + telecom { + system + value + use + } + } + } duplicates { compositionId trackingId @@ -288,6 +306,7 @@ const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` requester requesterOther hasShowedVerifiedDocument + certTemplateId noSupportingDocumentationRequired date action @@ -363,6 +382,7 @@ const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` } certificates { hasShowedVerifiedDocument + certTemplateId collector { relationship otherRelationship @@ -628,6 +648,7 @@ export const GET_DEATH_REGISTRATION_FOR_CERTIFICATION = gql` } certificates { hasShowedVerifiedDocument + certTemplateId collector { relationship otherRelationship diff --git a/packages/client/src/views/DataProvider/marriage/queries.ts b/packages/client/src/views/DataProvider/marriage/queries.ts index 2eb8d12cb2a..24a10eb0a9d 100644 --- a/packages/client/src/views/DataProvider/marriage/queries.ts +++ b/packages/client/src/views/DataProvider/marriage/queries.ts @@ -158,6 +158,24 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` contactRelationship contactPhoneNumber contactEmail + certificates { + hasShowedVerifiedDocument + certTemplateId + collector { + relationship + otherRelationship + name { + use + firstNames + familyName + } + telecom { + system + value + use + } + } + } groomSignature brideSignature witnessOneSignature @@ -227,6 +245,7 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` otherReason requester hasShowedVerifiedDocument + certTemplateId noSupportingDocumentationRequired date action From b1e2928c491200f0446381ad8103654e31ee907e Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:25:34 +0600 Subject: [PATCH 17/58] certTemplateId related definitions --- packages/client/graphql.schema.json | 566 +++++++++++++++++- packages/client/src/declarations/index.ts | 5 +- .../src/declarations/submissionMiddleware.ts | 6 - packages/client/src/forms/index.ts | 4 +- packages/client/src/transformer/index.ts | 9 +- .../utils/gateway-deprecated-do-not-use.d.ts | 7 +- packages/client/src/utils/gateway.ts | 41 +- packages/client/src/utils/referenceApi.ts | 21 +- 8 files changed, 615 insertions(+), 44 deletions(-) diff --git a/packages/client/graphql.schema.json b/packages/client/graphql.schema.json index 4eaf4b8853f..ef106b81738 100644 --- a/packages/client/graphql.schema.json +++ b/packages/client/graphql.schema.json @@ -3582,39 +3582,376 @@ "deprecationReason": null }, { - "name": "data", + "name": "hasShowedVerifiedDocument", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "payments", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Payment", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "templateConfig", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "CertificateConfigData", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CertificateConfigData", + "description": null, + "fields": [ + { + "name": "event", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fee", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CertificateFee", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CertificateLabel", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "lateRegistrationTarget", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "printInAdvance", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "registrationTarget", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "svgUrl", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CertificateConfigDataInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "event", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fee", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CertificateFeeInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CertificateLabelInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "lateRegistrationTarget", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "printInAdvance", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "registrationTarget", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "svgUrl", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CertificateFee", + "description": null, + "fields": [ + { + "name": "delayed", "description": null, "args": [], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "hasShowedVerifiedDocument", + "name": "late", "description": null, "args": [], "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "payments", + "name": "onTime", "description": null, "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "Payment", + "kind": "SCALAR", + "name": "Float", "ofType": null } }, @@ -3629,28 +3966,75 @@ }, { "kind": "INPUT_OBJECT", - "name": "CertificateInput", + "name": "CertificateFeeInput", "description": null, "fields": null, "inputFields": [ { - "name": "collector", + "name": "delayed", "description": null, "type": { - "kind": "INPUT_OBJECT", - "name": "RelatedPersonInput", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "data", + "name": "late", "description": null, "type": { - "kind": "SCALAR", - "name": "String", + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onTime", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CertificateInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "collector", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "RelatedPersonInput", "ofType": null }, "defaultValue": null, @@ -3684,6 +4068,136 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "templateConfig", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "CertificateConfigDataInput", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CertificateLabel", + "description": null, + "fields": [ + { + "name": "defaultMessage", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CertificateLabelInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "defaultMessage", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "interfaces": null, @@ -6829,6 +7343,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "templateConfig", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "CertificateConfigData", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "user", "description": null, diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index 9b8186070d0..bb6403b7f33 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -91,6 +91,7 @@ import { getReviewForm } from '@client/forms/register/review-selectors' import { getBirthQueryMappings } from '@client/views/DataProvider/birth/queries' import { getDeathQueryMappings } from '@client/views/DataProvider/death/queries' import { getMarriageQueryMappings } from '@client/views/DataProvider/marriage/queries' +import { ICertificateConfigData } from '@client/utils/referenceApi' const ARCHIVE_DECLARATION = 'DECLARATION/ARCHIVE' const SET_INITIAL_DECLARATION = 'DECLARATION/SET_INITIAL_DECLARATION' @@ -266,11 +267,11 @@ type RelationForCertificateCorrection = | 'CHILD' export type ICertificate = { - collector?: Partial<{ type: Relation | string }> + collector?: Partial<{ type: Relation | string; certTemplateId?: string }> corrector?: Partial<{ type: RelationForCertificateCorrection | string }> hasShowedVerifiedDocument?: boolean payments?: Payment - data?: string + templateConfig?: ICertificateConfigData } /* diff --git a/packages/client/src/declarations/submissionMiddleware.ts b/packages/client/src/declarations/submissionMiddleware.ts index 9322d0cda31..ded98060b82 100644 --- a/packages/client/src/declarations/submissionMiddleware.ts +++ b/packages/client/src/declarations/submissionMiddleware.ts @@ -305,12 +305,6 @@ export const submissionMiddleware: Middleware<{}, IStoreState> = details: graphqlPayload } }) - //delete data from certificates to identify event in workflow for markEventAsIssued - if (declaration.data.registration.certificates) { - delete ( - declaration.data.registration.certificates as ICertificate[] - )?.[0].data - } updateDeclaration(dispatch, { ...declaration, registrationStatus: RegStatus.Certified, diff --git a/packages/client/src/forms/index.ts b/packages/client/src/forms/index.ts index cbe88e38f56..9c3afbff3e3 100644 --- a/packages/client/src/forms/index.ts +++ b/packages/client/src/forms/index.ts @@ -38,6 +38,7 @@ import { NATIONAL_ID } from '@client/utils/constants' import { IconProps } from '@opencrvs/components/lib' +import { ICertificateConfigData } from '@client/utils/referenceApi' export const TEXT = 'TEXT' export const TEL = 'TEL' @@ -1296,5 +1297,6 @@ export interface ICertificate { collector?: IFormSectionData hasShowedVerifiedDocument?: boolean payments?: Payment[] - data?: string + templateConfig?: ICertificateConfigData + certTemplateId?: string } diff --git a/packages/client/src/transformer/index.ts b/packages/client/src/transformer/index.ts index f801defa8f4..9a1bb2d62d0 100644 --- a/packages/client/src/transformer/index.ts +++ b/packages/client/src/transformer/index.ts @@ -433,7 +433,14 @@ export const gqlToDraftTransformer = ( transformedData[section.id]._fhirID = queryData[section.id].id } if (section.mapping && section.mapping.query) { - section.mapping.query(transformedData, queryData, section.id) + section.mapping.query( + transformedData, + queryData, + section.id, + undefined, + undefined, + offlineData + ) } if (section.mapping?.template) { if (!transformedData.template) { diff --git a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts index 65ba841ca1e..9308b2db0e4 100644 --- a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts +++ b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts @@ -6951,7 +6951,7 @@ export interface GQLCertificateTypeResolver { collector?: CertificateToCollectorResolver hasShowedVerifiedDocument?: CertificateToHasShowedVerifiedDocumentResolver payments?: CertificateToPaymentsResolver - data?: CertificateToDataResolver + templateConfig?: CertificateToTemplateConfigResolver } export interface CertificateToCollectorResolver { @@ -6969,7 +6969,10 @@ export interface CertificateToPaymentsResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } -export interface CertificateToDataResolver { +export interface CertificateToTemplateConfigResolver< + TParent = any, + TResult = any +> { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } diff --git a/packages/client/src/utils/gateway.ts b/packages/client/src/utils/gateway.ts index e7c9d89484c..18555994387 100644 --- a/packages/client/src/utils/gateway.ts +++ b/packages/client/src/utils/gateway.ts @@ -383,16 +383,29 @@ export type BookmarkedSeachItem = { export type Certificate = { __typename?: 'Certificate' collector?: Maybe - data?: Maybe hasShowedVerifiedDocument?: Maybe payments?: Maybe>> + certTemplateId?: Maybe } export type CertificateInput = { collector?: InputMaybe - data?: InputMaybe hasShowedVerifiedDocument?: InputMaybe payments?: InputMaybe>> + certTemplateId?: InputMaybe +} + +export type CertificateLabel = { + __typename?: 'CertificateLabel' + defaultMessage: Scalars['String'] + description: Scalars['String'] + id: Scalars['String'] +} + +export type CertificateLabelInput = { + defaultMessage: Scalars['String'] + description: Scalars['String'] + id: Scalars['String'] } export type CertificationMetric = { @@ -718,6 +731,7 @@ export type History = { signature?: Maybe statusReason?: Maybe system?: Maybe + certTemplateId?: Maybe user?: Maybe } @@ -3223,6 +3237,28 @@ export type FetchBirthRegistrationForReviewQuery = { type?: RegistrationType | null trackingId?: string | null registrationNumber?: string | null + certificates?: Array<{ + __typename?: 'Certificate' + hasShowedVerifiedDocument?: boolean | null + certTemplateId?: string | null + collector?: { + __typename?: 'RelatedPerson' + relationship?: string | null + otherRelationship?: string | null + name?: Array<{ + __typename?: 'HumanName' + use?: string | null + firstNames?: string | null + familyName?: string | null + } | null> | null + telecom?: Array<{ + __typename?: 'ContactPoint' + system?: string | null + value?: string | null + use?: string | null + } | null> | null + } | null + } | null> | null duplicates?: Array<{ __typename?: 'DuplicatesInfo' compositionId?: string | null @@ -3291,6 +3327,7 @@ export type FetchBirthRegistrationForReviewQuery = { reason?: string | null duplicateOf?: string | null potentialDuplicates?: Array | null + certTemplateId?: string | null documents: Array<{ __typename?: 'Attachment' id: string diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index d29a2a471b2..0f4aa7549aa 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -18,7 +18,7 @@ import { ILocation } from '@client/offline/reducer' import { getToken } from '@client/utils/authUtils' -import { System } from '@client/utils/gateway' +import { Event, System } from '@client/utils/gateway' import { Validator } from '@client/forms/validators' import { IntlShape } from 'react-intl' @@ -79,21 +79,22 @@ interface ILoginBackground { } export interface ICertificateConfigData { id: string - event: string + event: Event label: { id: string defaultMessage: string description: string } - svgUrl: string - fonts: { - [fontName: string]: { - normal: string - bold: string - italics: string - bolditalics: string - } + registrationTarget: number + lateRegistrationTarget: number + printInAdvance: boolean + fee: { + onTime: number + late: number + delayed: number } + svgUrl: string + fonts?: Record } export interface ICurrency { isoCode: string From 974777dcd23c5f2619eacb2ba1824995b985d995 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:27:00 +0600 Subject: [PATCH 18/58] certTemplateId related routing updated --- packages/client/src/navigation/index.ts | 24 ++++++++++++------------ packages/client/src/navigation/routes.ts | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/client/src/navigation/index.ts b/packages/client/src/navigation/index.ts index 4ff678536b4..0d9c8fdcc2c 100644 --- a/packages/client/src/navigation/index.ts +++ b/packages/client/src/navigation/index.ts @@ -293,13 +293,13 @@ export function goToBirthRegistrationAsParent(declarationId: string) { export function goToPrintCertificate( registrationId: string, - certTemplateId: string, + eventType: string, groupId?: string ) { return push( formatUrl(CERTIFICATE_COLLECTOR, { registrationId, - certTemplateId, + eventType, groupId: groupId || 'certCollector' }) ) @@ -319,13 +319,13 @@ export function goToIssueCertificate( export function goToVerifyIssueCollector( registrationId: string, - event: string, + eventType: string, collector: string ) { return push( formatUrl(ISSUE_VERIFY_COLLECTOR, { registrationId: registrationId.toString(), - eventType: event.toLowerCase().toString(), + eventType, collector: collector.toLowerCase().toString() }) ) @@ -362,12 +362,12 @@ export function goToVerifyCorrector(declarationId: string, corrector: string) { export function goToReviewCertificate( registrationId: string, - certTemplateId: string + eventType: string ) { return push( formatUrl(REVIEW_CERTIFICATE, { registrationId, - certTemplateId + eventType }), { isNavigatedInsideApp: true } ) @@ -375,13 +375,13 @@ export function goToReviewCertificate( export function goToVerifyCollector( registrationId: string, - certTemplateId: string, + eventType: string, collector: string ) { return push( formatUrl(VERIFY_COLLECTOR, { registrationId: registrationId.toString(), - certTemplateId: certTemplateId.toLowerCase().toString(), + eventType: eventType.toLowerCase().toString(), collector: collector.toLowerCase().toString() }) ) @@ -389,24 +389,24 @@ export function goToVerifyCollector( export function goToPrintCertificatePayment( registrationId: string, - certTemplateId: string + eventType: string ) { return push( formatUrl(PRINT_CERTIFICATE_PAYMENT, { registrationId, - certTemplateId + eventType }) ) } export function goToIssueCertificatePayment( registrationId: string, - certTemplateId: string + eventType: string ) { return push( formatUrl(ISSUE_CERTIFICATE_PAYMENT, { registrationId, - certTemplateId + eventType }) ) } diff --git a/packages/client/src/navigation/routes.ts b/packages/client/src/navigation/routes.ts index c9618765065..35395667abe 100644 --- a/packages/client/src/navigation/routes.ts +++ b/packages/client/src/navigation/routes.ts @@ -43,18 +43,18 @@ export const VERIFY_CORRECTOR = '/correction/:declarationId/verify/:corrector' export const SEARCH = '/search' export const SEARCH_RESULT = '/search-result' export const CERTIFICATE_COLLECTOR = - '/cert/collector/:registrationId/:certTemplateId/:groupId' + '/cert/collector/:registrationId/:eventType/:groupId' export const ISSUE_COLLECTOR = '/issue/:registrationId/:pageId' export const ISSUE_VERIFY_COLLECTOR = '/issue/check/:registrationId/:eventType/:collector' export const VERIFY_COLLECTOR = - '/print/check/:registrationId/:certTemplateId/:collector' -export const REVIEW_CERTIFICATE = '/review/:registrationId/:certTemplateId' + '/print/check/:registrationId/:eventType/:collector' +export const REVIEW_CERTIFICATE = '/review/:registrationId/:eventType' export const PRINT_CERTIFICATE_PAYMENT = - '/print/payment/:registrationId/:certTemplateId' + '/print/payment/:registrationId/:eventType' export const ISSUE_CERTIFICATE_PAYMENT = - '/issue/payment/:registrationId/:certTemplateId' + '/issue/payment/:registrationId/:eventType' export const REGISTRAR_HOME = '/registration-home' export const REGISTRAR_HOME_TAB = '/registration-home/:tabId/:selectorId?' From a7f80c7f62a3dd7520d57147b4104b3c19e26b0f Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:28:07 +0600 Subject: [PATCH 19/58] certTemplateId related workflow in client --- .../IssueCollectorForm/IssueCollectorForm.tsx | 11 +- .../IssueCollectorForm/IssueFormForOthers.tsx | 6 +- .../IssueCollectorForm/IssuePayment.tsx | 7 +- .../src/views/PrintCertificate/Payment.tsx | 34 ++--- .../PrintCertificate/VerifyCollector.tsx | 13 +- .../collectorForm/CollectorForm.tsx | 40 +++--- .../usePrintableCertificate.ts | 28 +++-- .../src/views/PrintCertificate/utils.ts | 116 +++++++++--------- 8 files changed, 132 insertions(+), 123 deletions(-) diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx index 90492339a88..9a0774042e0 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx @@ -63,7 +63,8 @@ export function IssueCollectorForm({ certificates: [ { collector: collector, - hasShowedVerifiedDocument: false + hasShowedVerifiedDocument: false, + templateConfig: certificate.templateConfig } ] } @@ -81,7 +82,13 @@ export function IssueCollectorForm({ if (relationship === 'OTHER') { dispatch(goToIssueCertificate(declaration.id, 'otherCollector')) } else { - dispatch(goToVerifyIssueCollector(declaration.id, event, relationship)) + dispatch( + goToVerifyIssueCollector( + declaration.id, + declaration.event, + relationship + ) + ) } } diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx index bf8a07d5f38..1f3cf046c63 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx @@ -79,7 +79,8 @@ export const IssueCollectorFormForOthers = ({ certificates: [ { collector: collector, - hasShowedVerifiedDocument: false + hasShowedVerifiedDocument: false, + templateConfig: certificate.templateConfig } ] } @@ -100,8 +101,7 @@ export const IssueCollectorFormForOthers = ({ } function continueButtonHandler() { - const event = declaration.event - dispatch(goToIssueCertificatePayment(declaration.id, event)) + dispatch(goToIssueCertificatePayment(declaration.id, declaration.event)) } return ( diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx index ffd2b09c28f..f966bb3ec72 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx @@ -10,6 +10,7 @@ */ import { + ICertificate, IDeclaration, IPrintableDeclaration, modifyDeclaration, @@ -81,7 +82,8 @@ export const IssuePayment = () => { event, eventDate, registeredDate, - offlineCountryConfig + offlineCountryConfig, + certificate as ICertificate ) certificate.payments = { type: 'MANUAL' as const, @@ -117,7 +119,8 @@ export const IssuePayment = () => { event, eventDate, registeredDate, - offlineCountryConfig + offlineCountryConfig, + declaration.data.registration.certificates[0] ) const serviceMessage = getServiceMessage( diff --git a/packages/client/src/views/PrintCertificate/Payment.tsx b/packages/client/src/views/PrintCertificate/Payment.tsx index 679d61f945f..77c08f82071 100644 --- a/packages/client/src/views/PrintCertificate/Payment.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.tsx @@ -46,7 +46,6 @@ import { UserDetails } from '@client/utils/userUtils' interface IProps { event: Event registrationId: string - certTemplateId: string language: string declaration: IPrintableDeclaration theme: ITheme @@ -64,12 +63,11 @@ const PaymentComponent = ({ declaration, intl, event, - registrationId, - certTemplateId, goBack, offlineCountryConfig, modifyDeclaration, - goToReviewCertificate + goToReviewCertificate, + registrationId }: IFullProps) => { const handleContinue = (paymentAmount: string) => { const certificates = @@ -98,7 +96,7 @@ const PaymentComponent = ({ } }) - goToReviewCertificate(registrationId, certTemplateId) + goToReviewCertificate(registrationId, event) } if (!declaration) { @@ -120,7 +118,8 @@ const PaymentComponent = ({ event, eventDate, registeredDate, - offlineCountryConfig + offlineCountryConfig, + declaration.data.registration.certificates[0] ) const serviceMessage = getServiceMessage( @@ -182,27 +181,30 @@ const PaymentComponent = ({ ) } -const getEvent = (state: IStoreState, certTemplateId: string | undefined) => { - return ( - state.offline.offlineData.templates?.certificates?.find( - (x) => x.id === certTemplateId - )?.event || '' - ) +const getEvent = (eventType: string | undefined) => { + switch (eventType && eventType.toLowerCase()) { + case 'birth': + default: + return Event.Birth + case 'death': + return Event.Death + case 'marriage': + return Event.Marriage + } } function mapStatetoProps( state: IStoreState, - props: RouteComponentProps<{ registrationId: string; certTemplateId: string }> + props: RouteComponentProps<{ registrationId: string; eventType: string }> ) { - const { registrationId, certTemplateId } = props.match.params - const event = getEvent(state, certTemplateId) as Event + const { registrationId, eventType } = props.match.params + const event = getEvent(eventType) as Event const declaration = state.declarationsState.declarations.find( (app) => app.id === registrationId && app.event === event ) as IPrintableDeclaration return { event, - certTemplateId, registrationId, language: state.i18n.language, declaration, diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx index 9b035174c6f..4765491b787 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx @@ -47,10 +47,11 @@ import { IForm } from '@client/forms' import { getEventRegisterForm } from '@client/forms/register/declaration-selectors' import { UserDetails } from '@client/utils/userUtils' import { getUserDetails } from '@client/profile/profileSelectors' +import { Event } from '@client/utils/gateway' interface IMatchParams { registrationId: string - certTemplateId: string + eventType: Event collector: string } @@ -94,7 +95,7 @@ class VerifyCollectorComponent extends React.Component { if ( isFreeOfCost( - event, + declaration.data.registration.certificates[0], eventDate, registeredDate, offlineCountryConfiguration @@ -103,24 +104,24 @@ class VerifyCollectorComponent extends React.Component { if (!isIssueUrl) { this.props.goToReviewCertificate( this.props.match.params.registrationId, - this.props.match.params.certTemplateId + event ) } else { this.props.goToIssueCertificatePayment( this.props.match.params.registrationId, - this.props.match.params.certTemplateId + event ) } } else { if (!isIssueUrl) { this.props.goToPrintCertificatePayment( this.props.match.params.registrationId, - this.props.match.params.certTemplateId + event ) } else { this.props.goToIssueCertificatePayment( this.props.match.params.registrationId, - this.props.match.params.certTemplateId + event ) } } diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index 3b097900613..4a4d44b6186 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -184,7 +184,6 @@ interface IState { showError: boolean showModalForNoSignedAffidavit: boolean isFileUploading: boolean - certTemplateId: string } class CollectorFormComponent extends React.Component { @@ -193,8 +192,7 @@ class CollectorFormComponent extends React.Component { this.state = { showError: false, showModalForNoSignedAffidavit: false, - isFileUploading: false, - certTemplateId: '' + isFileUploading: false } } @@ -212,7 +210,10 @@ class CollectorFormComponent extends React.Component { const certificates = declaration.data.registration.certificates const certificate = (certificates && certificates[0]) || {} const collector = { ...(certificate.collector || {}), ...sectionData } - + const selectedTemplatedConfig = + this.props.offlineCountryConfiguration.templates.certificates?.find( + (x) => x.id === (collector.certTemplateId as string) + ) this.props.modifyDeclaration({ ...declaration, data: { @@ -222,7 +223,8 @@ class CollectorFormComponent extends React.Component { certificates: [ { collector: collector, - hasShowedVerifiedDocument: false + hasShowedVerifiedDocument: false, + templateConfig: selectedTemplatedConfig } ] } @@ -259,10 +261,6 @@ class CollectorFormComponent extends React.Component { sectionId as keyof typeof certificate ] as IFormSectionData - this.setState({ - ...this.state, - certTemplateId: collector?.certTemplateId as string - }) if (errLength > 0) { this.setState({ showError: true @@ -308,23 +306,16 @@ class CollectorFormComponent extends React.Component { this.props.writeDeclaration(draft) if (isCertificateForPrintInAdvance(draft)) { - this.props.goToReviewCertificate( - declarationId, - collector?.certTemplateId as string - ) + this.props.goToReviewCertificate(declarationId, event) } else { this.props.goToVerifyCollector( declarationId, - collector?.certTemplateId as string, + event, collector.type as string ) } } else { - this.props.goToPrintCertificate( - declarationId, - collector?.certTemplateId as string, - nextGroup - ) + this.props.goToPrintCertificate(declarationId, event, nextGroup) } } @@ -337,18 +328,15 @@ class CollectorFormComponent extends React.Component { .props as PropsWhenDeclarationIsFound if ( isFreeOfCost( - event, + declaration.data.registration.certificates[0], getEventDate(declaration.data, event), getRegisteredDate(declaration.data), offlineCountryConfiguration ) ) { - this.props.goToReviewCertificate(declarationId, this.state.certTemplateId) + this.props.goToReviewCertificate(declarationId, event) } else { - this.props.goToPrintCertificatePayment( - declarationId, - this.state.certTemplateId - ) + this.props.goToPrintCertificatePayment(declarationId, event) } } @@ -564,7 +552,7 @@ const mapStateToProps = ( declaration.data.registration.certificates && declaration.data.registration.certificates[ declaration.data.registration.certificates.length - 1 - ].collector) || + ]?.collector) || {}, declaration && declaration.data, offlineCountryConfiguration, diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index 1e238c0fa3e..bcbfbf8dbd8 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -46,6 +46,7 @@ import { getLocationHierarchy } from '@client/utils/locationUtils' import { printPDF } from '@client/pdfRenderer' import { useEffect, useState } from 'react' import { useParams } from 'react-router' +import { ICertificateConfigData } from '@client/utils/referenceApi' const withEnhancedTemplateVariables = ( declaration: IPrintableDeclaration | undefined, @@ -62,7 +63,8 @@ const withEnhancedTemplateVariables = ( declaration.event, eventDate, registeredDate, - offlineData + offlineData, + declaration.data.registration.certificates[0] ) const locationKey = userDetails?.primaryOffice?.id @@ -103,9 +105,8 @@ const withEnhancedTemplateVariables = ( } export const usePrintableCertificate = () => { - const { registrationId, certTemplateId } = useParams<{ + const { registrationId } = useParams<{ registrationId: string - certTemplateId: string }>() const declarationWithoutAllTemplateVariables = useDeclaration< IPrintableDeclaration | undefined @@ -127,15 +128,14 @@ export const usePrintableCertificate = () => { (hasRegisterScope(scope) || hasRegistrationClerkScope(scope)) const [svgCode, setSvgCode] = useState() - const certificateConfig = offlineData.templates.certificates?.find( - (x) => x.id === certTemplateId - ) - const certificateFonts = certificateConfig?.fonts ?? {} + const certificateTemplateConfig: ICertificateConfigData | undefined = + declaration?.data.registration.certificates.slice(-1)[0].templateConfig + const certificateFonts = certificateTemplateConfig?.fonts ?? {} useEffect(() => { const certificateUrl = (declaration && - offlineData.templates.certificates?.find((x) => x.id === certTemplateId) + declaration?.data.registration.certificates.slice(-1)[0].templateConfig ?.svgUrl) || '' @@ -157,7 +157,7 @@ export const usePrintableCertificate = () => { }, [declaration]) const handleCertify = async () => { - if (!declaration || !certificateConfig) { + if (!declaration || !certificateTemplateConfig) { return } const draft = cloneDeep(declaration) @@ -175,7 +175,8 @@ export const usePrintableCertificate = () => { draft.event, eventDate, registeredDate, - offlineData + offlineData, + declaration.data.registration.certificates[0] ) certificate.payments = { type: 'MANUAL' as const, @@ -185,8 +186,8 @@ export const usePrintableCertificate = () => { } } - const svgTemplate = await fetch(certificateConfig.svgUrl).then((res) => - res.text() + const svgTemplate = await fetch(certificateTemplateConfig.svgUrl).then( + (res) => res.text() ) const svg = await compileSvg( svgTemplate, @@ -194,12 +195,13 @@ export const usePrintableCertificate = () => { state ) + const { fonts, ...templateConfig } = certificateTemplateConfig draft.data.registration = { ...draft.data.registration, certificates: [ { ...certificate, - data: svg || '' + templateConfig } ] } diff --git a/packages/client/src/views/PrintCertificate/utils.ts b/packages/client/src/views/PrintCertificate/utils.ts index d40371b1c3c..722bddfb2a5 100644 --- a/packages/client/src/views/PrintCertificate/utils.ts +++ b/packages/client/src/views/PrintCertificate/utils.ts @@ -13,7 +13,7 @@ import { Event } from '@client/utils/gateway' import { dynamicMessages } from '@client/i18n/messages/views/certificate' import { getAvailableLanguages } from '@client/i18n/utils' import { ILanguageState } from '@client/i18n/reducer' -import { IPrintableDeclaration } from '@client/declarations' +import { ICertificate, IPrintableDeclaration } from '@client/declarations' import { IntlShape } from 'react-intl' import { IOfflineData } from '@client/offline/reducer' import differenceInDays from 'date-fns/differenceInDays' @@ -57,72 +57,76 @@ export function getCountryTranslations( return certificateCountries } -interface IDayRange { - rangeData: { [key in Event]?: IRange[] } -} - -function getDayRanges(offlineData: IOfflineData): IDayRange { - const BIRTH_REGISTRATION_TARGET = offlineData.config.BIRTH.REGISTRATION_TARGET - const BIRTH_LATE_REGISTRATION_TARGET = - offlineData.config.BIRTH.LATE_REGISTRATION_TARGET - const BIRTH_ON_TIME_FEE = offlineData.config.BIRTH.FEE.ON_TIME - const BIRTH_LATE_FEE = offlineData.config.BIRTH.FEE.LATE - const BIRTH_DELAYED_FEE = offlineData.config.BIRTH.FEE.DELAYED - - const DEATH_REGISTRATION_TARGET = offlineData.config.DEATH.REGISTRATION_TARGET - const DEATH_ON_TIME_FEE = offlineData.config.DEATH.FEE.ON_TIME - const DEATH_DELAYED_FEE = offlineData.config.DEATH.FEE.DELAYED - - const MARRIAGE_REGISTRATION_TARGET = - offlineData.config.MARRIAGE.REGISTRATION_TARGET - const MARRIAGE_ON_TIME_FEE = offlineData.config.MARRIAGE.FEE.ON_TIME - const MARRIAGE_DELAYED_FEE = offlineData.config.MARRIAGE.FEE.DELAYED - - const birthRanges = [ - { start: 0, end: BIRTH_REGISTRATION_TARGET, value: BIRTH_ON_TIME_FEE }, - { - start: BIRTH_REGISTRATION_TARGET + 1, - end: BIRTH_LATE_REGISTRATION_TARGET, - value: BIRTH_LATE_FEE - }, - { start: BIRTH_LATE_REGISTRATION_TARGET + 1, value: BIRTH_DELAYED_FEE } - ] +function getDayRanges( + offlineData: IOfflineData, + certificate: ICertificate +): IRange[] { + switch (certificate.templateConfig?.event) { + case Event.Birth: { + const BIRTH_REGISTRATION_TARGET = + certificate.templateConfig?.registrationTarget + const BIRTH_LATE_REGISTRATION_TARGET = + certificate.templateConfig?.lateRegistrationTarget + const BIRTH_ON_TIME_FEE = certificate.templateConfig?.fee.onTime + const BIRTH_LATE_FEE = certificate.templateConfig?.fee.late + const BIRTH_DELAYED_FEE = certificate.templateConfig?.fee.delayed + const birthRanges = [ + { start: 0, end: BIRTH_REGISTRATION_TARGET, value: BIRTH_ON_TIME_FEE }, + { + start: BIRTH_REGISTRATION_TARGET + 1, + end: BIRTH_LATE_REGISTRATION_TARGET, + value: BIRTH_LATE_FEE + }, + { start: BIRTH_LATE_REGISTRATION_TARGET + 1, value: BIRTH_DELAYED_FEE } + ] + return birthRanges + } - const deathRanges = [ - { start: 0, end: DEATH_REGISTRATION_TARGET, value: DEATH_ON_TIME_FEE }, - { start: DEATH_REGISTRATION_TARGET + 1, value: DEATH_DELAYED_FEE } - ] + case Event.Death: { + const DEATH_REGISTRATION_TARGET = + certificate.templateConfig?.registrationTarget + const DEATH_ON_TIME_FEE = certificate.templateConfig?.fee.onTime + const DEATH_DELAYED_FEE = certificate.templateConfig?.fee.delayed - const marriageRanges = [ - { - start: 0, - end: MARRIAGE_REGISTRATION_TARGET, - value: MARRIAGE_ON_TIME_FEE - }, - { start: MARRIAGE_REGISTRATION_TARGET + 1, value: MARRIAGE_DELAYED_FEE } - ] + const deathRanges = [ + { start: 0, end: DEATH_REGISTRATION_TARGET, value: DEATH_ON_TIME_FEE }, + { start: DEATH_REGISTRATION_TARGET + 1, value: DEATH_DELAYED_FEE } + ] + return deathRanges + } + case Event.Marriage: { + const MARRIAGE_REGISTRATION_TARGET = + certificate.templateConfig?.registrationTarget + const MARRIAGE_ON_TIME_FEE = certificate.templateConfig?.fee.onTime + const MARRIAGE_DELAYED_FEE = certificate.templateConfig?.fee.delayed + const marriageRanges = [ + { + start: 0, + end: MARRIAGE_REGISTRATION_TARGET, + value: MARRIAGE_ON_TIME_FEE + }, + { start: MARRIAGE_REGISTRATION_TARGET + 1, value: MARRIAGE_DELAYED_FEE } + ] - return { - rangeData: { - [Event.Birth]: birthRanges, - [Event.Death]: deathRanges, - [Event.Marriage]: marriageRanges + return marriageRanges } + default: + return [] } } function getValue( offlineData: IOfflineData, - event: Event, + certificate: ICertificate, check: number ): IRange['value'] { - const rangeByEvent = getDayRanges(offlineData).rangeData[event] as IRange[] + const rangeByEvent = getDayRanges(offlineData, certificate) as IRange[] const foundRange = rangeByEvent.find((range) => range.end ? check >= range.start && check <= range.end : check >= range.start ) - return foundRange ? foundRange.value : rangeByEvent[0].value + return foundRange ? foundRange.value : rangeByEvent[0]?.value || 0 } export function calculateDaysFromToday(doE: string) { @@ -162,10 +166,12 @@ export function calculatePrice( event: Event, eventDate: string, registeredDate: string, - offlineData: IOfflineData + offlineData: IOfflineData, + certificate: ICertificate ) { + if (!certificate) return 0 const days = calculateDays(eventDate, registeredDate) - const result = getValue(offlineData, event, days) + const result = getValue(offlineData, certificate, days) return result } @@ -220,13 +226,13 @@ export function getServiceMessage( } export function isFreeOfCost( - event: Event, + certificate: ICertificate, eventDate: string, registeredDate: string, offlineData: IOfflineData ): boolean { const days = calculateDays(eventDate, registeredDate) - const result = getValue(offlineData, event, days) + const result = getValue(offlineData, certificate, days) return result === 0 } From 1a3bb1b505761486ee4ae64a5d257f1065204774 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 15:28:29 +0600 Subject: [PATCH 20/58] certTemplateId related tests updated --- packages/client/src/tests/schema.graphql | 50 +++++++++- packages/client/src/tests/templates.json | 32 ++++++ packages/client/src/tests/util.tsx | 98 ++++++++++++++++++- .../views/PrintCertificate/Payment.test.tsx | 14 +-- .../ReviewCertificateAction.test.tsx | 8 +- .../PrintCertificate/VerifyCollector.test.tsx | 14 +-- 6 files changed, 190 insertions(+), 26 deletions(-) diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index 67a49e5ff27..f2450cb23db 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -403,14 +403,14 @@ type Certificate { collector: RelatedPerson hasShowedVerifiedDocument: Boolean payments: [Payment] - data: String + certTemplateId: String } input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - data: String + certTemplateId: String } enum CertificateStatus { @@ -1944,3 +1944,49 @@ type WebhookPermission { event: String! permissions: [String!]! } + +type CertificateConfigData { + id: String! + event: String! + label: CertificateLabel! + registrationTarget: Int! + lateRegistrationTarget: Int! + printInAdvance: Boolean! + fee: CertificateFee! + svgUrl: String! +} + +type CertificateLabel { + id: String! + defaultMessage: String! + description: String! +} + +type CertificateFee { + onTime: Float! + late: Float! + delayed: Float! +} + +input CertificateConfigDataInput { + id: String! + event: String! + label: CertificateLabelInput! + registrationTarget: Int! + lateRegistrationTarget: Int! + printInAdvance: Boolean! + fee: CertificateFeeInput! + svgUrl: String! +} + +input CertificateLabelInput { + id: String! + defaultMessage: String! + description: String! +} + +input CertificateFeeInput { + onTime: Float! + late: Float! + delayed: Float! +} diff --git a/packages/client/src/tests/templates.json b/packages/client/src/tests/templates.json index 6f9b93b0e96..5abc85517c9 100644 --- a/packages/client/src/tests/templates.json +++ b/packages/client/src/tests/templates.json @@ -16,6 +16,14 @@ "defaultMessage": "Birth Certificate", "description": "The label for a birth certificate" }, + "registrationTarget": 30, + "lateRegistrationTarget": 365, + "printInAdvance": false, + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, "svgUrl": "/api/countryconfig/certificates/birth-certificate.svg", "fonts": { "Noto Sans": { @@ -34,6 +42,14 @@ "defaultMessage": "Birth Certificate certified copy", "description": "The label for a birth certificate" }, + "registrationTarget": 30, + "lateRegistrationTarget": 365, + "printInAdvance": false, + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, "svgUrl": "/api/countryconfig/certificates/birth-certificate-certified-copy.svg", "fonts": { "Noto Sans": { @@ -52,6 +68,14 @@ "defaultMessage": "Death Certificate", "description": "The label for a death certificate" }, + "registrationTarget": 30, + "lateRegistrationTarget": 365, + "printInAdvance": false, + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, "svgUrl": "/api/countryconfig/certificates/death-certificate.svg", "fonts": { "Noto Sans": { @@ -70,6 +94,14 @@ "defaultMessage": "Marriage Certificate", "description": "The label for a marriage certificate" }, + "registrationTarget": 30, + "lateRegistrationTarget": 365, + "printInAdvance": false, + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, "svgUrl": "/api/countryconfig/certificates/marriage-certificate.svg", "fonts": { "Noto Sans": { diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 2879021a571..49285511dec 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -547,7 +547,29 @@ export const mockDeclarationData = { officeAddressLevel3: 'Gazipur', officeAddressLevel4: 'Dhaka' }, - certificates: [{}] + certificates: [ + { + templateConfig: { + id: 'certified-birth-certificate', + event: 'birth', + label: { + id: 'certificates.birth.certificate.copy', + defaultMessage: 'Birth Certificate certified copy', + description: 'The label for a birth certificate' + }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: true, + fee: { + onTime: 10, + late: 500, + delayed: 150 + }, + svgUrl: + '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' + } + } + ] }, documents: {} } @@ -647,7 +669,26 @@ export const mockDeathDeclarationData = { collector: { type: 'MOTHER' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + templateConfig: { + id: 'certified-death-certificate', + event: 'death', + label: { + id: 'certificates.death.certificate.copy', + defaultMessage: 'Death Certificate certified copy', + description: 'The label for a death certificate' + }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: true, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + svgUrl: + '/api/countryconfig/certificates/death-certificate-certified-copy.svg' + } } ] } @@ -676,7 +717,26 @@ export const mockMarriageDeclarationData = { collector: { type: 'BRIDE' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + templateConfig: { + id: 'certified-marriage-certificate', + event: 'marriage', + label: { + id: 'certificates.marriage.certificate.copy', + defaultMessage: 'Marriage Certificate certified copy', + description: 'The label for a marriage certificate' + }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: true, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + svgUrl: + '/api/countryconfig/certificates/marriage-certificate-certified-copy.svg' + } } ] }, @@ -809,6 +869,14 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Birth Certificate', description: 'The label for a birth certificate' }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: false, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, svgUrl: '/api/countryconfig/certificates/birth-certificate.svg', fonts: { 'Noto Sans': { @@ -827,6 +895,14 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Birth Certificate certified copy', description: 'The label for a birth certificate' }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: false, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, svgUrl: '/api/countryconfig/certificates/birth-certificate-certified-copy.svg', fonts: { @@ -846,6 +922,14 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Death Certificate', description: 'The label for a death certificate' }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: false, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, svgUrl: '/api/countryconfig/certificates/death-certificate.svg', fonts: { 'Noto Sans': { @@ -864,6 +948,14 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Marriage Certificate', description: 'The label for a marriage certificate' }, + registrationTarget: 30, + lateRegistrationTarget: 365, + printInAdvance: false, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, svgUrl: '/api/countryconfig/certificates/marriage-certificate.svg', fonts: { 'Noto Sans': { diff --git a/packages/client/src/views/PrintCertificate/Payment.test.tsx b/packages/client/src/views/PrintCertificate/Payment.test.tsx index e404ed275dc..e78a70a4c6e 100644 --- a/packages/client/src/views/PrintCertificate/Payment.test.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.test.tsx @@ -67,14 +67,14 @@ const deathDeclaration = { // Helper function to set up test async function setupPaymentTest({ registrationId, - certTemplateId, + eventType, declaration, store, history, mockLocation }: { registrationId: string - certTemplateId: string + eventType: string declaration: any store: any history: any @@ -90,7 +90,7 @@ async function setupPaymentTest({ match={{ params: { registrationId, - certTemplateId + eventType }, isExact: true, path: '', @@ -118,7 +118,7 @@ describe('verify collector tests', () => { it('renders Payment component when mother is collector', async () => { const testComponent = await setupPaymentTest({ registrationId: 'mockBirth1234', - certTemplateId: 'birth-certificate-copy', + eventType: 'birth', declaration: birthDeclaration, store, history, @@ -129,7 +129,7 @@ describe('verify collector tests', () => { 'Birth' ) expect(testComponent.find('#amountDue').hostNodes().text()).toContain( - '20' + '150' ) testComponent.find('#Continue').hostNodes().simulate('click') @@ -143,7 +143,7 @@ describe('verify collector tests', () => { match={{ params: { registrationId: 'mockBirth', - certTemplateId: 'birth-certificate-copy' + eventType: 'birth' }, isExact: true, path: '', @@ -170,7 +170,7 @@ describe('verify collector tests', () => { it('renders Payment component when informant is collector', async () => { const testComponent = await setupPaymentTest({ registrationId: 'mockDeath1234', - certTemplateId: 'death-certificate', + eventType: 'death', declaration: deathDeclaration, store, history, diff --git a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx index 9be3eb7852e..78b3a1d253f 100644 --- a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx +++ b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx @@ -78,11 +78,9 @@ const marriageDeclaration = { async function setupTest({ declaration, - certTemplateId, registrationId }: { declaration: any - certTemplateId: string registrationId: string }) { global.fetch = vi.fn().mockImplementation(() => @@ -96,8 +94,7 @@ async function setupTest({ { isNavigatedInsideApp: false }, { matchParams: { - registrationId, - certTemplateId + registrationId } } ) @@ -129,7 +126,6 @@ describe('Review Certificate Tests', () => { beforeEach(async () => { component = await setupTest({ declaration: deathDeclaration, - certTemplateId: 'death-certificate', registrationId: 'mockDeath1234' }) }) @@ -146,7 +142,6 @@ describe('Review Certificate Tests', () => { beforeEach(async () => { component = await setupTest({ declaration: birthDeclaration, - certTemplateId: 'birth-certificate', registrationId: 'mockBirth1234' }) }) @@ -184,7 +179,6 @@ describe('Review Certificate Tests', () => { beforeEach(async () => { component = await setupTest({ declaration: marriageDeclaration, - certTemplateId: 'marriage-certificate', registrationId: 'mockMarriage1234' }) }) diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx index 1a5c134813a..5ddf04e9454 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx @@ -55,14 +55,14 @@ const deathDeclaration = { // Helper function for setting up tests async function setupTest({ registrationId, - certTemplateId, + event, collector, declaration, store, history }: { registrationId: string - certTemplateId: string + event: string collector: string declaration: any store: any @@ -78,7 +78,7 @@ async function setupTest({ match={{ params: { registrationId, - certTemplateId, + eventType: event as Event, collector }, isExact: true, @@ -101,7 +101,7 @@ describe('verify collector tests', () => { beforeAll(async () => { testComponent = await setupTest({ registrationId: 'mockBirth1234', - certTemplateId: 'birth-certificate-copy', + event: 'birth', collector: 'mother', declaration: birthDeclaration, store, @@ -132,7 +132,7 @@ describe('verify collector tests', () => { beforeAll(async () => { testComponent = await setupTest({ registrationId: 'mockDeath1234', - certTemplateId: 'death-certificate', + event: 'death', collector: 'informant', declaration: deathDeclaration, store, @@ -188,7 +188,7 @@ describe('verify collector tests', () => { beforeAll(async () => { testComponent = await setupTest({ registrationId: 'mockBirth1234', - certTemplateId: 'birth-certificate-copy', + event: 'birth', collector: 'father', declaration: birthDeclaration, store, @@ -218,7 +218,7 @@ describe('verify collector tests', () => { beforeAll(async () => { testComponent = await setupTest({ registrationId: 'mockDeath1234', - certTemplateId: 'death-certificate', + event: 'death', collector: 'informant', declaration: deathDeclaration, store, From 48dba914e6052f57a9c98d69ad23a6fa98ce154c Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 22 Oct 2024 18:42:36 +0600 Subject: [PATCH 21/58] Adds Action to history table based on template printed --- .../src/i18n/messages/views/certificate.ts | 6 ++++ packages/client/src/tests/languages.json | 2 ++ .../views/RecordAudit/ActionDetailsModal.tsx | 32 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/packages/client/src/i18n/messages/views/certificate.ts b/packages/client/src/i18n/messages/views/certificate.ts index 062d679af8f..a6261039957 100644 --- a/packages/client/src/i18n/messages/views/certificate.ts +++ b/packages/client/src/i18n/messages/views/certificate.ts @@ -59,6 +59,7 @@ interface ICertificateMessages receiptPaidAmount: MessageDescriptor receiptService: MessageDescriptor selectSignature: MessageDescriptor + selectedCertificateTemplateLabel: MessageDescriptor service: MessageDescriptor amountDue: MessageDescriptor typeOfID: MessageDescriptor @@ -336,6 +337,11 @@ const messagesToDefine: ICertificateMessages = { description: 'The label for choose signature select', id: 'print.certificate.selectSignature' }, + selectedCertificateTemplateLabel: { + defaultMessage: 'Selected certificate template', + description: 'The title of selected certificate template label', + id: 'certificate.selectedTemplate' + }, service: { defaultMessage: 'Service: Birth registration after {service, plural, =0 {0 month} one {1 month} other{{service} months}} of D.o.B.
Amount Due:', diff --git a/packages/client/src/tests/languages.json b/packages/client/src/tests/languages.json index 57c24fea6df..ab5d4e0bf42 100644 --- a/packages/client/src/tests/languages.json +++ b/packages/client/src/tests/languages.json @@ -53,6 +53,7 @@ "buttons.yes": "Yes", "certificate.confirmCorrect": "Please confirm that the informant has reviewed that the information on the certificate is correct and that you are ready to print.", "certificate.selectTemplate": "Select certificate template", + "certificate.selectedTemplate": "Selected certificate template", "certificate.isCertificateCorrect": "Is the {event} certificate correct?", "certificate.label.birth": "Birth", "certificate.label.death": "Death", @@ -1202,6 +1203,7 @@ "buttons.yes": "হ্যাঁ", "certificate.confirmCorrect": "অনুগ্রহ করে নিশ্চিত করুন যে নিবন্ধনটি পর্যালোচনা হয়েছে তার তথ্য সঠিক এবং আপনি মুদ্রণ করতে প্রস্তুত", "certificate.selectTemplate": "নিবন্ধন টেমপ্লেট নির্বাচন করুন", + "certificate.selectedTemplate": "নির্বাচিত নিবন্ধন টেমপ্লেট", "certificate.isCertificateCorrect": "জন্ম নিবন্ধনটি কি সঠিক?", "certificate.label.birth": "জন্ম", "certificate.label.death": "মৃত্যু", diff --git a/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx b/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx index 0ea39cd6490..0dedc1e498e 100644 --- a/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx +++ b/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx @@ -452,6 +452,27 @@ const ActionDetailsModalListTable = ({ width: 100 } ] + + const selectedCertificateTemplate = [ + { + key: 'certTemplate', + label: intl.formatMessage( + certificateMessages.selectedCertificateTemplateLabel + ), + width: 200 + } + ] + + const certificateTemplateMessageDescriptor = + offlineData.templates?.certificates?.find( + (x) => x.id === actionDetailsData.certTemplateId + )?.label + + const selectedCertificateTemplateName = { + certTemplate: certificateTemplateMessageDescriptor + ? intl.formatMessage(certificateTemplateMessageDescriptor) + : '' + } const pageChangeHandler = (cp: number) => setCurrentPage(cp) const content = prepareComments(actionDetailsData, draft) const requesterLabel = requesterLabelMapper( @@ -609,6 +630,17 @@ const ActionDetailsModalListTable = ({ onPageChange={pageChangeHandler} /> )} + {!isEmpty(collectorData) && !!actionDetailsData.certTemplateId && ( + + )} {/* Matched to */} {actionDetailsData.potentialDuplicates && From cf3e78420a6a9ff67149807499d9a9d451f4e2ae Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 23 Oct 2024 16:06:41 +0600 Subject: [PATCH 22/58] removed targetDates and printInAdvance from certificate config --- packages/client/src/tests/schema.graphql | 8 ++---- packages/client/src/tests/templates.json | 16 +++-------- packages/client/src/tests/util.tsx | 28 +++++-------------- packages/client/src/utils/referenceApi.ts | 4 +-- .../src/views/PrintCertificate/utils.ts | 8 +++--- 5 files changed, 18 insertions(+), 46 deletions(-) diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index f2450cb23db..03c0e2749db 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -1949,9 +1949,7 @@ type CertificateConfigData { id: String! event: String! label: CertificateLabel! - registrationTarget: Int! - lateRegistrationTarget: Int! - printInAdvance: Boolean! + fee: CertificateFee! svgUrl: String! } @@ -1972,9 +1970,7 @@ input CertificateConfigDataInput { id: String! event: String! label: CertificateLabelInput! - registrationTarget: Int! - lateRegistrationTarget: Int! - printInAdvance: Boolean! + fee: CertificateFeeInput! svgUrl: String! } diff --git a/packages/client/src/tests/templates.json b/packages/client/src/tests/templates.json index 5abc85517c9..e40f9241690 100644 --- a/packages/client/src/tests/templates.json +++ b/packages/client/src/tests/templates.json @@ -16,9 +16,7 @@ "defaultMessage": "Birth Certificate", "description": "The label for a birth certificate" }, - "registrationTarget": 30, - "lateRegistrationTarget": 365, - "printInAdvance": false, + "fee": { "onTime": 0, "late": 5.5, @@ -42,9 +40,7 @@ "defaultMessage": "Birth Certificate certified copy", "description": "The label for a birth certificate" }, - "registrationTarget": 30, - "lateRegistrationTarget": 365, - "printInAdvance": false, + "fee": { "onTime": 0, "late": 5.5, @@ -68,9 +64,7 @@ "defaultMessage": "Death Certificate", "description": "The label for a death certificate" }, - "registrationTarget": 30, - "lateRegistrationTarget": 365, - "printInAdvance": false, + "fee": { "onTime": 0, "late": 5.5, @@ -94,9 +88,7 @@ "defaultMessage": "Marriage Certificate", "description": "The label for a marriage certificate" }, - "registrationTarget": 30, - "lateRegistrationTarget": 365, - "printInAdvance": false, + "fee": { "onTime": 0, "late": 5.5, diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 49285511dec..ac689fadc02 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -557,9 +557,7 @@ export const mockDeclarationData = { defaultMessage: 'Birth Certificate certified copy', description: 'The label for a birth certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: true, + fee: { onTime: 10, late: 500, @@ -678,9 +676,7 @@ export const mockDeathDeclarationData = { defaultMessage: 'Death Certificate certified copy', description: 'The label for a death certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: true, + fee: { onTime: 0, late: 5.5, @@ -726,9 +722,7 @@ export const mockMarriageDeclarationData = { defaultMessage: 'Marriage Certificate certified copy', description: 'The label for a marriage certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: true, + fee: { onTime: 0, late: 5.5, @@ -869,9 +863,7 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Birth Certificate', description: 'The label for a birth certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: false, + fee: { onTime: 0, late: 5.5, @@ -895,9 +887,7 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Birth Certificate certified copy', description: 'The label for a birth certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: false, + fee: { onTime: 0, late: 5.5, @@ -922,9 +912,7 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Death Certificate', description: 'The label for a death certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: false, + fee: { onTime: 0, late: 5.5, @@ -948,9 +936,7 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Marriage Certificate', description: 'The label for a marriage certificate' }, - registrationTarget: 30, - lateRegistrationTarget: 365, - printInAdvance: false, + fee: { onTime: 0, late: 5.5, diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index 0f4aa7549aa..b39f7827866 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -85,9 +85,7 @@ export interface ICertificateConfigData { defaultMessage: string description: string } - registrationTarget: number - lateRegistrationTarget: number - printInAdvance: boolean + fee: { onTime: number late: number diff --git a/packages/client/src/views/PrintCertificate/utils.ts b/packages/client/src/views/PrintCertificate/utils.ts index 722bddfb2a5..213f7d2076a 100644 --- a/packages/client/src/views/PrintCertificate/utils.ts +++ b/packages/client/src/views/PrintCertificate/utils.ts @@ -64,9 +64,9 @@ function getDayRanges( switch (certificate.templateConfig?.event) { case Event.Birth: { const BIRTH_REGISTRATION_TARGET = - certificate.templateConfig?.registrationTarget + offlineData.config.BIRTH.REGISTRATION_TARGET const BIRTH_LATE_REGISTRATION_TARGET = - certificate.templateConfig?.lateRegistrationTarget + offlineData.config.BIRTH.LATE_REGISTRATION_TARGET const BIRTH_ON_TIME_FEE = certificate.templateConfig?.fee.onTime const BIRTH_LATE_FEE = certificate.templateConfig?.fee.late const BIRTH_DELAYED_FEE = certificate.templateConfig?.fee.delayed @@ -84,7 +84,7 @@ function getDayRanges( case Event.Death: { const DEATH_REGISTRATION_TARGET = - certificate.templateConfig?.registrationTarget + offlineData.config.DEATH.REGISTRATION_TARGET const DEATH_ON_TIME_FEE = certificate.templateConfig?.fee.onTime const DEATH_DELAYED_FEE = certificate.templateConfig?.fee.delayed @@ -96,7 +96,7 @@ function getDayRanges( } case Event.Marriage: { const MARRIAGE_REGISTRATION_TARGET = - certificate.templateConfig?.registrationTarget + offlineData.config.MARRIAGE.REGISTRATION_TARGET const MARRIAGE_ON_TIME_FEE = certificate.templateConfig?.fee.onTime const MARRIAGE_DELAYED_FEE = certificate.templateConfig?.fee.delayed const marriageRanges = [ From c117a06b945e346c0dbac0e1e654676f88d66ef1 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 23 Oct 2024 17:21:37 +0600 Subject: [PATCH 23/58] removed FEE global variable references --- packages/client/src/setupTests.ts | 11 +- .../client/src/tests/mock-offline-data.ts | 13 --- packages/client/src/tests/schema.graphql | 40 ------- .../utils/gateway-deprecated-do-not-use.d.ts | 100 ------------------ packages/client/src/utils/referenceApi.ts | 13 --- packages/client/typings/window.d.ts | 13 --- .../applicationConfigHandler.test.ts | 13 --- .../application/applicationConfigHandler.ts | 19 ---- packages/config/src/models/config.ts | 26 ----- packages/metrics/src/configApi.ts | 13 --- 10 files changed, 2 insertions(+), 259 deletions(-) diff --git a/packages/client/src/setupTests.ts b/packages/client/src/setupTests.ts index c9ad41872e8..8d844ab9a87 100644 --- a/packages/client/src/setupTests.ts +++ b/packages/client/src/setupTests.ts @@ -40,11 +40,7 @@ const config = { BIRTH: { REGISTRATION_TARGET: 45, LATE_REGISTRATION_TARGET: 365, - FEE: { - ON_TIME: 0, - LATE: 0, - DELAYED: 0 - } + PRINT_IN_ADVANCE: true }, COUNTRY: 'BGD', CURRENCY: { @@ -53,10 +49,7 @@ const config = { }, DEATH: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - } + PRINT_IN_ADVANCE: true }, FEATURES: { DEATH_REGISTRATION: true, diff --git a/packages/client/src/tests/mock-offline-data.ts b/packages/client/src/tests/mock-offline-data.ts index 24a34b559de..1915e21a6e6 100644 --- a/packages/client/src/tests/mock-offline-data.ts +++ b/packages/client/src/tests/mock-offline-data.ts @@ -426,27 +426,14 @@ export const mockOfflineData = { BIRTH: { REGISTRATION_TARGET: 45, LATE_REGISTRATION_TARGET: 365, - FEE: { - ON_TIME: 0, - LATE: 15, - DELAYED: 20 - }, PRINT_IN_ADVANCE: true }, DEATH: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, MARRIAGE: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, FEATURES: { diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index 03c0e2749db..c706f0df853 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -293,7 +293,6 @@ input AvatarInput { type Birth { REGISTRATION_TARGET: Int LATE_REGISTRATION_TARGET: Int - FEE: BirthFee PRINT_IN_ADVANCE: Boolean } @@ -317,22 +316,9 @@ type BirthEventSearchSet implements EventSearchSet { fatherIdentifier: String } -type BirthFee { - ON_TIME: Float - LATE: Float - DELAYED: Float -} - -input BirthFeeInput { - ON_TIME: Float - LATE: Float - DELAYED: Float -} - input BirthInput { REGISTRATION_TARGET: Int LATE_REGISTRATION_TARGET: Int - FEE: BirthFeeInput PRINT_IN_ADVANCE: Boolean } @@ -572,7 +558,6 @@ scalar FieldValue type Death { REGISTRATION_TARGET: Int - FEE: DeathFee PRINT_IN_ADVANCE: Boolean } @@ -586,19 +571,8 @@ type DeathEventSearchSet implements EventSearchSet { operationHistories: [OperationHistorySearchSet] } -type DeathFee { - ON_TIME: Float - DELAYED: Float -} - -input DeathFeeInput { - ON_TIME: Float - DELAYED: Float -} - input DeathInput { REGISTRATION_TARGET: Int - FEE: DeathFeeInput PRINT_IN_ADVANCE: Boolean } @@ -934,7 +908,6 @@ scalar Map type Marriage { REGISTRATION_TARGET: Int - FEE: MarriageFee PRINT_IN_ADVANCE: Boolean } @@ -950,19 +923,8 @@ type MarriageEventSearchSet implements EventSearchSet { operationHistories: [OperationHistorySearchSet] } -type MarriageFee { - ON_TIME: Float - DELAYED: Float -} - -input MarriageFeeInput { - ON_TIME: Float - DELAYED: Float -} - input MarriageInput { REGISTRATION_TARGET: Int - FEE: MarriageFeeInput PRINT_IN_ADVANCE: Boolean } @@ -1949,7 +1911,6 @@ type CertificateConfigData { id: String! event: String! label: CertificateLabel! - fee: CertificateFee! svgUrl: String! } @@ -1970,7 +1931,6 @@ input CertificateConfigDataInput { id: String! event: String! label: CertificateLabelInput! - fee: CertificateFeeInput! svgUrl: String! } diff --git a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts index 9308b2db0e4..af6d2ad41ba 100644 --- a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts +++ b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts @@ -1325,7 +1325,6 @@ export interface GQLRoleInput { export interface GQLBirth { REGISTRATION_TARGET?: number LATE_REGISTRATION_TARGET?: number - FEE?: GQLBirthFee PRINT_IN_ADVANCE?: boolean } @@ -1341,13 +1340,11 @@ export interface GQLCurrency { export interface GQLDeath { REGISTRATION_TARGET?: number - FEE?: GQLDeathFee PRINT_IN_ADVANCE?: boolean } export interface GQLMarriage { REGISTRATION_TARGET?: number - FEE?: GQLMarriageFee PRINT_IN_ADVANCE?: boolean } @@ -1360,7 +1357,6 @@ export interface GQLLoginBackground { export interface GQLBirthInput { REGISTRATION_TARGET?: number LATE_REGISTRATION_TARGET?: number - FEE?: GQLBirthFeeInput PRINT_IN_ADVANCE?: boolean } @@ -1376,13 +1372,11 @@ export interface GQLCurrencyInput { export interface GQLDeathInput { REGISTRATION_TARGET?: number - FEE?: GQLDeathFeeInput PRINT_IN_ADVANCE?: boolean } export interface GQLMarriageInput { REGISTRATION_TARGET?: number - FEE?: GQLMarriageFeeInput PRINT_IN_ADVANCE?: boolean } @@ -1786,43 +1780,11 @@ export interface GQLLabelInput { label: string } -export interface GQLBirthFee { - ON_TIME?: number - LATE?: number - DELAYED?: number -} - -export interface GQLDeathFee { - ON_TIME?: number - DELAYED?: number -} - -export interface GQLMarriageFee { - ON_TIME?: number - DELAYED?: number -} - export const enum GQLImageFit { FILL = 'FILL', TILE = 'TILE' } -export interface GQLBirthFeeInput { - ON_TIME?: number - LATE?: number - DELAYED?: number -} - -export interface GQLDeathFeeInput { - ON_TIME?: number - DELAYED?: number -} - -export interface GQLMarriageFeeInput { - ON_TIME?: number - DELAYED?: number -} - export interface GQLAuditLogItemBase { time: string ipAddress: string @@ -2025,9 +1987,6 @@ export interface GQLResolver { EventProgressData?: GQLEventProgressDataTypeResolver WebhookPermission?: GQLWebhookPermissionTypeResolver OIDPUserAddress?: GQLOIDPUserAddressTypeResolver - BirthFee?: GQLBirthFeeTypeResolver - DeathFee?: GQLDeathFeeTypeResolver - MarriageFee?: GQLMarriageFeeTypeResolver AuditLogItemBase?: { __resolveType: GQLAuditLogItemBaseTypeResolver } @@ -6732,7 +6691,6 @@ export interface OIDPUserInfoToUpdated_atResolver< export interface GQLBirthTypeResolver { REGISTRATION_TARGET?: BirthToREGISTRATION_TARGETResolver LATE_REGISTRATION_TARGET?: BirthToLATE_REGISTRATION_TARGETResolver - FEE?: BirthToFEEResolver PRINT_IN_ADVANCE?: BirthToPRINT_IN_ADVANCEResolver } @@ -6750,10 +6708,6 @@ export interface BirthToLATE_REGISTRATION_TARGETResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } -export interface BirthToFEEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - export interface BirthToPRINT_IN_ADVANCEResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } @@ -6789,7 +6743,6 @@ export interface CurrencyToLanguagesAndCountryResolver< export interface GQLDeathTypeResolver { REGISTRATION_TARGET?: DeathToREGISTRATION_TARGETResolver - FEE?: DeathToFEEResolver PRINT_IN_ADVANCE?: DeathToPRINT_IN_ADVANCEResolver } @@ -6800,17 +6753,12 @@ export interface DeathToREGISTRATION_TARGETResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } -export interface DeathToFEEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - export interface DeathToPRINT_IN_ADVANCEResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } export interface GQLMarriageTypeResolver { REGISTRATION_TARGET?: MarriageToREGISTRATION_TARGETResolver - FEE?: MarriageToFEEResolver PRINT_IN_ADVANCE?: MarriageToPRINT_IN_ADVANCEResolver } @@ -6821,10 +6769,6 @@ export interface MarriageToREGISTRATION_TARGETResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } -export interface MarriageToFEEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - export interface MarriageToPRINT_IN_ADVANCEResolver< TParent = any, TResult = any @@ -8381,50 +8325,6 @@ export interface OIDPUserAddressToCountryResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } -export interface GQLBirthFeeTypeResolver { - ON_TIME?: BirthFeeToON_TIMEResolver - LATE?: BirthFeeToLATEResolver - DELAYED?: BirthFeeToDELAYEDResolver -} - -export interface BirthFeeToON_TIMEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - -export interface BirthFeeToLATEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - -export interface BirthFeeToDELAYEDResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - -export interface GQLDeathFeeTypeResolver { - ON_TIME?: DeathFeeToON_TIMEResolver - DELAYED?: DeathFeeToDELAYEDResolver -} - -export interface DeathFeeToON_TIMEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - -export interface DeathFeeToDELAYEDResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - -export interface GQLMarriageFeeTypeResolver { - ON_TIME?: MarriageFeeToON_TIMEResolver - DELAYED?: MarriageFeeToDELAYEDResolver -} - -export interface MarriageFeeToON_TIMEResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - -export interface MarriageFeeToDELAYEDResolver { - (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult -} - export interface GQLAuditLogItemBaseTypeResolver { (parent: TParent, context: any, info: GraphQLResolveInfo): | 'UserAuditLogItemWithComposition' diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index b39f7827866..61795667078 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -111,29 +111,16 @@ export interface IApplicationConfig { BIRTH: { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } COUNTRY_LOGO: ICountryLogo CURRENCY: ICurrency DEATH: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } MARRIAGE: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } FEATURES: { diff --git a/packages/client/typings/window.d.ts b/packages/client/typings/window.d.ts index c67120df95b..fa123a9b073 100644 --- a/packages/client/typings/window.d.ts +++ b/packages/client/typings/window.d.ts @@ -16,11 +16,6 @@ interface Window { BIRTH: { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } CONFIG_API_URL: string @@ -35,18 +30,10 @@ interface Window { } DEATH: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } MARRIAGE: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } FEATURES: { diff --git a/packages/config/src/handlers/application/applicationConfigHandler.test.ts b/packages/config/src/handlers/application/applicationConfigHandler.test.ts index ce69652eb3e..8fa569a6aa1 100644 --- a/packages/config/src/handlers/application/applicationConfigHandler.test.ts +++ b/packages/config/src/handlers/application/applicationConfigHandler.test.ts @@ -37,11 +37,6 @@ const mockConfig = { BIRTH: { REGISTRATION_TARGET: 45, LATE_REGISTRATION_TARGET: 365, - FEE: { - ON_TIME: 0, - LATE: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, COUNTRY_LOGO: { @@ -54,18 +49,10 @@ const mockConfig = { }, DEATH: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, MARRIAGE: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, PHONE_NUMBER_PATTERN: '^0(7|9)[0-9]{8}$', diff --git a/packages/config/src/handlers/application/applicationConfigHandler.ts b/packages/config/src/handlers/application/applicationConfigHandler.ts index 19f7c4eaf98..6c3f0991a36 100644 --- a/packages/config/src/handlers/application/applicationConfigHandler.ts +++ b/packages/config/src/handlers/application/applicationConfigHandler.ts @@ -160,37 +160,18 @@ const applicationConfigResponseValidation = Joi.object({ .keys({ REGISTRATION_TARGET: Joi.number().required(), LATE_REGISTRATION_TARGET: Joi.number().required(), - FEE: Joi.object() - .keys({ - ON_TIME: Joi.number().required(), - LATE: Joi.number().required(), - DELAYED: Joi.number().required() - }) - .required(), PRINT_IN_ADVANCE: Joi.boolean().required() }) .required(), DEATH: Joi.object() .keys({ REGISTRATION_TARGET: Joi.number().required(), - FEE: Joi.object() - .keys({ - ON_TIME: Joi.number().required(), - DELAYED: Joi.number().required() - }) - .required(), PRINT_IN_ADVANCE: Joi.boolean().required() }) .required(), MARRIAGE: Joi.object() .keys({ REGISTRATION_TARGET: Joi.number().required(), - FEE: Joi.object() - .keys({ - ON_TIME: Joi.number().required(), - DELAYED: Joi.number().required() - }) - .required(), PRINT_IN_ADVANCE: Joi.boolean().required() }) .required(), diff --git a/packages/config/src/models/config.ts b/packages/config/src/models/config.ts index ff78df0bb2c..740818f885e 100644 --- a/packages/config/src/models/config.ts +++ b/packages/config/src/models/config.ts @@ -12,27 +12,14 @@ import { model, Schema, Document } from 'mongoose' interface IBirth { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IDeath { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IMarriage { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface ICurrency { @@ -66,29 +53,16 @@ export interface IApplicationConfigurationModel extends Document { const birthSchema = new Schema({ REGISTRATION_TARGET: { type: Number, default: 45 }, LATE_REGISTRATION_TARGET: { type: Number, default: 365 }, - FEE: { - ON_TIME: Number, - LATE: Number, - DELAYED: Number - }, PRINT_IN_ADVANCE: { type: Boolean, default: true } }) const deathSchema = new Schema({ REGISTRATION_TARGET: { type: Number, default: 45 }, - FEE: { - ON_TIME: Number, - DELAYED: Number - }, PRINT_IN_ADVANCE: { type: Boolean, default: true } }) const marriageSchema = new Schema({ REGISTRATION_TARGET: { type: Number, default: 45 }, - FEE: { - ON_TIME: { type: Number, default: 10 }, - DELAYED: { type: Number, default: 45 } - }, PRINT_IN_ADVANCE: { type: Boolean, default: true } }) diff --git a/packages/metrics/src/configApi.ts b/packages/metrics/src/configApi.ts index 2a56205689f..caca8aa1238 100644 --- a/packages/metrics/src/configApi.ts +++ b/packages/metrics/src/configApi.ts @@ -21,28 +21,15 @@ import { interface IBirth { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IDeath { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IMarriage { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } export interface ICountryLogo { From 28a728f49a535efbaadec9a5c40ca067eeb85f35 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 23 Oct 2024 20:44:23 +0600 Subject: [PATCH 24/58] test code related to certTemplateId fixed in gateway and workflow --- packages/client/src/forms/register/mappings/mutation/utils.ts | 2 +- .../IssueCollectorForm/IssueCollectorForm.tsx | 1 - .../registration/__snapshots__/type-resolvers.test.ts.snap | 3 +++ packages/workflow/src/records/handler/certify.test.ts | 4 ++-- packages/workflow/src/records/handler/issue.test.ts | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/client/src/forms/register/mappings/mutation/utils.ts b/packages/client/src/forms/register/mappings/mutation/utils.ts index d49f0bdd158..edd391cf674 100644 --- a/packages/client/src/forms/register/mappings/mutation/utils.ts +++ b/packages/client/src/forms/register/mappings/mutation/utils.ts @@ -10,7 +10,7 @@ */ import type { GQLRelatedPersonInput } from '@client/utils/gateway-deprecated-do-not-use' -import { ICertificate, IFileValue, TransformedData } from '@client/forms' +import { ICertificate, IFileValue } from '@client/forms' import { omit } from 'lodash' export function stripTypename(obj: any): any { diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx index 9a0774042e0..81636b34397 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx @@ -77,7 +77,6 @@ export function IssueCollectorForm({ function continueButtonHandler() { const relationship = declaration.data.registration.certificates[0].collector?.type - const event = declaration.event if (!relationship) return if (relationship === 'OTHER') { dispatch(goToIssueCertificate(declaration.id, 'otherCollector')) diff --git a/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap b/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap index 5db255f469c..1ad57510fc3 100644 --- a/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap +++ b/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap @@ -170,6 +170,7 @@ Object { "foetalDeathsToMother": null, "history": Array [ Object { + "certTemplateId": null, "certificates": Array [ null, ], @@ -654,6 +655,7 @@ Object { "femaleDependentsOfDeceased": 4, "history": Array [ Object { + "certTemplateId": null, "certificates": Array [], "comments": Array [], "date": "2023-09-22T11:52:48.611+00:00", @@ -1201,6 +1203,7 @@ Object { }, "history": Array [ Object { + "certTemplateId": null, "certificates": Array [], "comments": Array [], "date": "2023-09-22T08:54:57.825+00:00", diff --git a/packages/workflow/src/records/handler/certify.test.ts b/packages/workflow/src/records/handler/certify.test.ts index 56a9af5e170..749f20a54c6 100644 --- a/packages/workflow/src/records/handler/certify.test.ts +++ b/packages/workflow/src/records/handler/certify.test.ts @@ -111,7 +111,7 @@ describe('Certify record endpoint', () => { event: 'BIRTH', certificate: { hasShowedVerifiedDocument: true, - data: 'data:application/pdf;base64,AXDWYZ', + certTemplateId: 'birth-certificate', collector: { relationship: 'INFORMANT' } @@ -215,7 +215,7 @@ describe('Certify record endpoint', () => { event: 'BIRTH', certificate: { hasShowedVerifiedDocument: true, - data: 'data:application/pdf;base64,AXDWYZ', + certTemplateId: 'birth-certificate', collector: { relationship: 'Other', otherRelationship: 'Uncle', diff --git a/packages/workflow/src/records/handler/issue.test.ts b/packages/workflow/src/records/handler/issue.test.ts index b7804701913..b14ccae8d74 100644 --- a/packages/workflow/src/records/handler/issue.test.ts +++ b/packages/workflow/src/records/handler/issue.test.ts @@ -112,6 +112,7 @@ describe('Issue record endpoint', () => { collector: { relationship: 'INFORMANT' }, + certTemplateId: 'birth-certificate', payment: { type: 'MANUAL', amount: 100, From 9a2b61e375f12d4738bc23a7ae72a705f6825e10 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 23 Oct 2024 20:51:15 +0600 Subject: [PATCH 25/58] changelog updated --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c196eef88..a40f8212c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fetch child identifier in view record - Auth token, ip address, remote address redacted from server log - **Align Patient data model with FHIR**: Previously we were using `string[]` for `Patient.name.family` field instead of `string` as mentioned in the FHIR standard. We've now aligned the field with the standard. +- **Certificate Fetching**: Removed certificates from the database, allowing them to be fetched directly from the country configuration via a simplified API endpoint. ### New features @@ -27,6 +28,10 @@ - Record audit action buttons are moved into action menu [#7390](https://github.com/opencrvs/opencrvs-core/issues/7390) - Reoder the sytem user add/edit field for surname to be first, also change labels from `Last name` to `User's surname` and lastly remove the NID question from the form [#6830](https://github.com/opencrvs/opencrvs-core/issues/6830) - Auth now allows registrar's token to be exchanged for a new token that strictly allows confirming or rejecting a specific record. Core now passes this token to country configuration instead of the registrar's token [#7728](https://github.com/opencrvs/opencrvs-core/issues/7728) [#7849](https://github.com/opencrvs/opencrvs-core/issues/7849) +- **Template Selection for Certified Copies**: Added support for multiple certificate templates for each event (birth, death, marriage). Users can now select a template during the certificate issuance process. +- **Template-based Payment Configuration**: Implemented payment differentiation based on the selected certificate template, ensuring the correct amount is charged. +- **Template Action Tracking**: Each template printed is tracked in the history table, showing which specific template was used. +- **Template Selection Dropdown**: Updated print workflow to include a dropdown menu for template selection when issuing a certificate. ## Bug fixes From 6bd15a5bd275de938c813952960031d6c62bda65 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 23 Oct 2024 21:09:17 +0600 Subject: [PATCH 26/58] certTemplateId renamed to certificateTemplateId --- packages/client/src/declarations/index.ts | 5 ++++- .../fieldDefinitions/collectorSection.ts | 2 +- packages/client/src/forms/index.ts | 2 +- .../birth/query/registration-mappings.ts | 4 ++-- .../death/query/registration-mappings.ts | 4 ++-- .../marriage/query/registration-mappings.ts | 4 ++-- .../forms/register/mappings/mutation/utils.ts | 3 ++- packages/client/src/tests/schema.graphql | 4 ++-- packages/client/src/utils/gateway.ts | 10 +++++----- .../src/views/DataProvider/birth/queries.ts | 8 ++++---- .../src/views/DataProvider/death/queries.ts | 8 ++++---- .../src/views/DataProvider/marriage/queries.ts | 4 ++-- .../collectorForm/CollectorForm.tsx | 2 +- .../views/RecordAudit/ActionDetailsModal.tsx | 4 ++-- packages/commons/src/fhir/extension.ts | 4 ++-- packages/commons/src/fhir/transformers/input.ts | 2 +- .../__snapshots__/type-resolvers.test.ts.snap | 6 +++--- .../src/features/registration/schema.graphql | 6 +++--- .../src/features/registration/type-resolvers.ts | 16 ++++++++-------- packages/gateway/src/graphql/schema.d.ts | 17 ++++++++++------- packages/gateway/src/graphql/schema.graphql | 6 +++--- packages/workflow/src/records/fhir.ts | 12 ++++++------ .../src/records/handler/certify.test.ts | 4 ++-- .../workflow/src/records/handler/issue.test.ts | 2 +- .../workflow/src/records/state-transitions.ts | 6 +++--- packages/workflow/src/records/validations.ts | 2 +- 26 files changed, 77 insertions(+), 70 deletions(-) diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index bb6403b7f33..8d9c22b0285 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -267,7 +267,10 @@ type RelationForCertificateCorrection = | 'CHILD' export type ICertificate = { - collector?: Partial<{ type: Relation | string; certTemplateId?: string }> + collector?: Partial<{ + type: Relation | string + certificateTemplateId?: string + }> corrector?: Partial<{ type: RelationForCertificateCorrection | string }> hasShowedVerifiedDocument?: boolean payments?: Payment diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index f6940a3b0bd..ff7433120c9 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -1066,7 +1066,7 @@ function getCertCollectorGroupForEvent( options: finalOptions }, { - name: 'certTemplateId', + name: 'certificateTemplateId', type: 'SELECT_WITH_OPTIONS', label: certificateMessages.certificateTemplateSelectLabel, required: true, diff --git a/packages/client/src/forms/index.ts b/packages/client/src/forms/index.ts index 9c3afbff3e3..799be95b86a 100644 --- a/packages/client/src/forms/index.ts +++ b/packages/client/src/forms/index.ts @@ -1298,5 +1298,5 @@ export interface ICertificate { hasShowedVerifiedDocument?: boolean payments?: Payment[] templateConfig?: ICertificateConfigData - certTemplateId?: string + certificateTemplateId?: string } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts index ac7a8babf6f..7030c305bd6 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts @@ -44,11 +44,11 @@ export function getBirthRegistrationSectionTransformer( ) { const certificate = queryData[sectionId].certificates.slice(-1)[0] // since we shall need this certificate only for ready to issue tab, to calculate certificate fee - transformedData[sectionId].certificates = certificate?.certTemplateId + transformedData[sectionId].certificates = certificate?.certificateTemplateId ? [ { templateConfig: offlineData?.templates.certificates?.find( - (x) => x.id === certificate.certTemplateId + (x) => x.id === certificate.certificateTemplateId ) } ] diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts index 02bd8068d10..5db2e0d48f4 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts @@ -48,11 +48,11 @@ export function getDeathRegistrationSectionTransformer( ) { const certificate = queryData[sectionId].certificates.slice(-1)[0] // since we shall need this certificate only for ready to issue tab, to calculate certificate fee - transformedData[sectionId].certificates = certificate?.certTemplateId + transformedData[sectionId].certificates = certificate?.certificateTemplateId ? [ { templateConfig: offlineData?.templates.certificates?.find( - (x) => x.id === certificate.certTemplateId + (x) => x.id === certificate.certificateTemplateId ) } ] diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts index 92c68ed9505..d4f7785c60d 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts @@ -45,11 +45,11 @@ export function getMarriageRegistrationSectionTransformer( ) { const certificate = queryData[sectionId].certificates.slice(-1)[0] // since we shall need this certificate only for ready to issue tab, to calculate certificate fee - transformedData[sectionId].certificates = certificate?.certTemplateId + transformedData[sectionId].certificates = certificate?.certificateTemplateId ? [ { templateConfig: offlineData?.templates.certificates?.find( - (x) => x.id === certificate.certTemplateId + (x) => x.id === certificate.certificateTemplateId ) } ] diff --git a/packages/client/src/forms/register/mappings/mutation/utils.ts b/packages/client/src/forms/register/mappings/mutation/utils.ts index edd391cf674..9d86ae85d36 100644 --- a/packages/client/src/forms/register/mappings/mutation/utils.ts +++ b/packages/client/src/forms/register/mappings/mutation/utils.ts @@ -77,7 +77,8 @@ export function transformCertificateData(certificates: ICertificate[]) { // for templateConfig mapping if (certificateData && certificateData.templateConfig) { - updatedCertificates[0].certTemplateId = certificateData.templateConfig.id + updatedCertificates[0].certificateTemplateId = + certificateData.templateConfig.id } // Return the processed certificates array diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index c706f0df853..dfe25af396f 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -389,14 +389,14 @@ type Certificate { collector: RelatedPerson hasShowedVerifiedDocument: Boolean payments: [Payment] - certTemplateId: String + certificateTemplateId: String } input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - certTemplateId: String + certificateTemplateId: String } enum CertificateStatus { diff --git a/packages/client/src/utils/gateway.ts b/packages/client/src/utils/gateway.ts index 18555994387..7d5767c6cc6 100644 --- a/packages/client/src/utils/gateway.ts +++ b/packages/client/src/utils/gateway.ts @@ -385,14 +385,14 @@ export type Certificate = { collector?: Maybe hasShowedVerifiedDocument?: Maybe payments?: Maybe>> - certTemplateId?: Maybe + certificateTemplateId?: Maybe } export type CertificateInput = { collector?: InputMaybe hasShowedVerifiedDocument?: InputMaybe payments?: InputMaybe>> - certTemplateId?: InputMaybe + certificateTemplateId?: InputMaybe } export type CertificateLabel = { @@ -731,7 +731,7 @@ export type History = { signature?: Maybe statusReason?: Maybe system?: Maybe - certTemplateId?: Maybe + certificateTemplateId?: Maybe user?: Maybe } @@ -3240,7 +3240,7 @@ export type FetchBirthRegistrationForReviewQuery = { certificates?: Array<{ __typename?: 'Certificate' hasShowedVerifiedDocument?: boolean | null - certTemplateId?: string | null + certificateTemplateId?: string | null collector?: { __typename?: 'RelatedPerson' relationship?: string | null @@ -3327,7 +3327,7 @@ export type FetchBirthRegistrationForReviewQuery = { reason?: string | null duplicateOf?: string | null potentialDuplicates?: Array | null - certTemplateId?: string | null + certificateTemplateId?: string | null documents: Array<{ __typename?: 'Attachment' id: string diff --git a/packages/client/src/views/DataProvider/birth/queries.ts b/packages/client/src/views/DataProvider/birth/queries.ts index a19dff59f36..205b59422a1 100644 --- a/packages/client/src/views/DataProvider/birth/queries.ts +++ b/packages/client/src/views/DataProvider/birth/queries.ts @@ -151,7 +151,7 @@ const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` contactEmail certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship @@ -224,7 +224,7 @@ const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` requesterOther noSupportingDocumentationRequired hasShowedVerifiedDocument - certTemplateId + certificateTemplateId date action regStatus @@ -313,7 +313,7 @@ const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` } certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship @@ -589,7 +589,7 @@ export const GET_BIRTH_REGISTRATION_FOR_CERTIFICATE = gql` } certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship diff --git a/packages/client/src/views/DataProvider/death/queries.ts b/packages/client/src/views/DataProvider/death/queries.ts index 3c09cb067c2..42bcbcdd5c1 100644 --- a/packages/client/src/views/DataProvider/death/queries.ts +++ b/packages/client/src/views/DataProvider/death/queries.ts @@ -215,7 +215,7 @@ const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` contactEmail certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship @@ -306,7 +306,7 @@ const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` requester requesterOther hasShowedVerifiedDocument - certTemplateId + certificateTemplateId noSupportingDocumentationRequired date action @@ -382,7 +382,7 @@ const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` } certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship @@ -648,7 +648,7 @@ export const GET_DEATH_REGISTRATION_FOR_CERTIFICATION = gql` } certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship diff --git a/packages/client/src/views/DataProvider/marriage/queries.ts b/packages/client/src/views/DataProvider/marriage/queries.ts index 24a10eb0a9d..ba215418f0d 100644 --- a/packages/client/src/views/DataProvider/marriage/queries.ts +++ b/packages/client/src/views/DataProvider/marriage/queries.ts @@ -160,7 +160,7 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` contactEmail certificates { hasShowedVerifiedDocument - certTemplateId + certificateTemplateId collector { relationship otherRelationship @@ -245,7 +245,7 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` otherReason requester hasShowedVerifiedDocument - certTemplateId + certificateTemplateId noSupportingDocumentationRequired date action diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index 4a4d44b6186..ea63271402e 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -212,7 +212,7 @@ class CollectorFormComponent extends React.Component { const collector = { ...(certificate.collector || {}), ...sectionData } const selectedTemplatedConfig = this.props.offlineCountryConfiguration.templates.certificates?.find( - (x) => x.id === (collector.certTemplateId as string) + (x) => x.id === (collector.certificateTemplateId as string) ) this.props.modifyDeclaration({ ...declaration, diff --git a/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx b/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx index 0dedc1e498e..8f9c8919221 100644 --- a/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx +++ b/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx @@ -465,7 +465,7 @@ const ActionDetailsModalListTable = ({ const certificateTemplateMessageDescriptor = offlineData.templates?.certificates?.find( - (x) => x.id === actionDetailsData.certTemplateId + (x) => x.id === actionDetailsData.certificateTemplateId )?.label const selectedCertificateTemplateName = { @@ -630,7 +630,7 @@ const ActionDetailsModalListTable = ({ onPageChange={pageChangeHandler} /> )} - {!isEmpty(collectorData) && !!actionDetailsData.certTemplateId && ( + {!isEmpty(collectorData) && !!actionDetailsData.certificateTemplateId && (
- certTemplateId?: string + certificateTemplateId?: string } interface Deceased { deceased?: boolean diff --git a/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap b/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap index 1ad57510fc3..094c2c8b854 100644 --- a/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap +++ b/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap @@ -170,7 +170,7 @@ Object { "foetalDeathsToMother": null, "history": Array [ Object { - "certTemplateId": null, + "certificateTemplateId": null, "certificates": Array [ null, ], @@ -655,7 +655,7 @@ Object { "femaleDependentsOfDeceased": 4, "history": Array [ Object { - "certTemplateId": null, + "certificateTemplateId": null, "certificates": Array [], "comments": Array [], "date": "2023-09-22T11:52:48.611+00:00", @@ -1203,7 +1203,7 @@ Object { }, "history": Array [ Object { - "certTemplateId": null, + "certificateTemplateId": null, "certificates": Array [], "comments": Array [], "date": "2023-09-22T08:54:57.825+00:00", diff --git a/packages/gateway/src/features/registration/schema.graphql b/packages/gateway/src/features/registration/schema.graphql index a825f4bfbce..8f0f36859b9 100644 --- a/packages/gateway/src/features/registration/schema.graphql +++ b/packages/gateway/src/features/registration/schema.graphql @@ -159,7 +159,7 @@ type History { requester: String requesterOther: String hasShowedVerifiedDocument: Boolean - certTemplateId: String + certificateTemplateId: String noSupportingDocumentationRequired: Boolean otherReason: String #This doesn't resolve to the System model properly rather @@ -419,14 +419,14 @@ input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - certTemplateId: String + certificateTemplateId: String } type Certificate { # -> Document Reference collector: RelatedPerson # -> .extension hasShowedVerifiedDocument: Boolean # -> .extension payments: [Payment] # -> .extension - certTemplateId: String + certificateTemplateId: String } input QuestionnaireQuestionInput { diff --git a/packages/gateway/src/features/registration/type-resolvers.ts b/packages/gateway/src/features/registration/type-resolvers.ts index 00d86167864..966cbb7a8c1 100644 --- a/packages/gateway/src/features/registration/type-resolvers.ts +++ b/packages/gateway/src/features/registration/type-resolvers.ts @@ -1223,12 +1223,12 @@ export const typeResolvers: GQLResolver = { return false }, - certTemplateId(docRef: DocumentReference, _) { - const certTemplateId = findExtension( - `${OPENCRVS_SPECIFICATION_URL}extension/certTemplateId`, + certificateTemplateId(docRef: DocumentReference, _) { + const certificateTemplateId = findExtension( + `${OPENCRVS_SPECIFICATION_URL}extension/certificateTemplateId`, docRef.extension as Extension[] ) - return certTemplateId?.valueString + return certificateTemplateId?.valueString } }, Identifier: { @@ -1404,12 +1404,12 @@ export const typeResolvers: GQLResolver = { return false }, - certTemplateId: (task: Task) => { - const certTemplateId = findExtension( - `${OPENCRVS_SPECIFICATION_URL}extension/certTemplateId`, + certificateTemplateId: (task: Task) => { + const certificateTemplateId = findExtension( + `${OPENCRVS_SPECIFICATION_URL}extension/certificateTemplateId`, task.extension as Extension[] ) - return certTemplateId?.valueString + return certificateTemplateId?.valueString }, noSupportingDocumentationRequired: (task: Task) => { const hasShowedDocument = findExtension( diff --git a/packages/gateway/src/graphql/schema.d.ts b/packages/gateway/src/graphql/schema.d.ts index 81ad6cab0c1..349bfaa0a6d 100644 --- a/packages/gateway/src/graphql/schema.d.ts +++ b/packages/gateway/src/graphql/schema.d.ts @@ -757,7 +757,7 @@ export interface GQLHistory { requester?: string requesterOther?: string hasShowedVerifiedDocument?: boolean - certTemplateId?: string + certificateTemplateId?: string noSupportingDocumentationRequired?: boolean otherReason?: string system?: GQLIntegratedSystem @@ -1249,7 +1249,7 @@ export interface GQLCertificate { collector?: GQLRelatedPerson hasShowedVerifiedDocument?: boolean payments?: Array - certTemplateId?: string + certificateTemplateId?: string } export interface GQLDuplicatesInfo { @@ -1571,7 +1571,7 @@ export interface GQLCertificateInput { collector?: GQLRelatedPersonInput hasShowedVerifiedDocument?: boolean payments?: Array - certTemplateId?: string + certificateTemplateId?: string } export interface GQLIdentityInput { @@ -6252,7 +6252,7 @@ export interface GQLHistoryTypeResolver { requester?: HistoryToRequesterResolver requesterOther?: HistoryToRequesterOtherResolver hasShowedVerifiedDocument?: HistoryToHasShowedVerifiedDocumentResolver - certTemplateId?: HistoryToCertTemplateIdResolver + certificateTemplateId?: HistoryToCertificateTemplateIdResolver noSupportingDocumentationRequired?: HistoryToNoSupportingDocumentationRequiredResolver otherReason?: HistoryToOtherReasonResolver system?: HistoryToSystemResolver @@ -6372,7 +6372,10 @@ export interface HistoryToHasShowedVerifiedDocumentResolver< ): TResult } -export interface HistoryToCertTemplateIdResolver { +export interface HistoryToCertificateTemplateIdResolver< + TParent = any, + TResult = any +> { ( parent: TParent, args: {}, @@ -7959,7 +7962,7 @@ export interface GQLCertificateTypeResolver { collector?: CertificateToCollectorResolver hasShowedVerifiedDocument?: CertificateToHasShowedVerifiedDocumentResolver payments?: CertificateToPaymentsResolver - certTemplateId?: CertificateToCertTemplateIdResolver + certificateTemplateId?: CertificateToCertificateTemplateIdResolver } export interface CertificateToCollectorResolver { @@ -7992,7 +7995,7 @@ export interface CertificateToPaymentsResolver { ): TResult } -export interface CertificateToCertTemplateIdResolver< +export interface CertificateToCertificateTemplateIdResolver< TParent = any, TResult = any > { diff --git a/packages/gateway/src/graphql/schema.graphql b/packages/gateway/src/graphql/schema.graphql index 0212dcaf4cc..8fa4495c948 100644 --- a/packages/gateway/src/graphql/schema.graphql +++ b/packages/gateway/src/graphql/schema.graphql @@ -878,7 +878,7 @@ type History { requester: String requesterOther: String hasShowedVerifiedDocument: Boolean - certTemplateId: String + certificateTemplateId: String noSupportingDocumentationRequired: Boolean otherReason: String system: IntegratedSystem @@ -1346,7 +1346,7 @@ type Certificate { collector: RelatedPerson hasShowedVerifiedDocument: Boolean payments: [Payment] - certTemplateId: String + certificateTemplateId: String } type DuplicatesInfo { @@ -1667,7 +1667,7 @@ input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - certTemplateId: String + certificateTemplateId: String } input IdentityInput { diff --git a/packages/workflow/src/records/fhir.ts b/packages/workflow/src/records/fhir.ts index 090fec99a5b..8078f8d7228 100644 --- a/packages/workflow/src/records/fhir.ts +++ b/packages/workflow/src/records/fhir.ts @@ -218,7 +218,7 @@ export function createDocumentReferenceEntryForCertificate( temporaryRelatedPersonId: UUID, eventType: EVENT_TYPE, hasShowedVerifiedDocument: boolean, - certTemplateId?: string, + certificateTemplateId?: string, attachmentUrl?: string, paymentUrl?: URNReference | ResourceIdentifier ): BundleEntry { @@ -242,8 +242,8 @@ export function createDocumentReferenceEntryForCertificate( valueBoolean: hasShowedVerifiedDocument }, { - url: 'http://opencrvs.org/specs/extension/certTemplateId', - valueString: certTemplateId + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: certificateTemplateId }, ...(paymentUrl ? [ @@ -919,14 +919,14 @@ export async function createUnassignedTask( export function createCertifiedTask( previousTask: SavedTask, - certTemplateId: string + certificateTemplateId: string ): SavedTask { return createNewTaskResource( previousTask, [ { - url: 'http://opencrvs.org/specs/extension/certTemplateId', - valueString: certTemplateId + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: certificateTemplateId } ], 'CERTIFIED' diff --git a/packages/workflow/src/records/handler/certify.test.ts b/packages/workflow/src/records/handler/certify.test.ts index 749f20a54c6..ccc6c437d27 100644 --- a/packages/workflow/src/records/handler/certify.test.ts +++ b/packages/workflow/src/records/handler/certify.test.ts @@ -111,7 +111,7 @@ describe('Certify record endpoint', () => { event: 'BIRTH', certificate: { hasShowedVerifiedDocument: true, - certTemplateId: 'birth-certificate', + certificateTemplateId: 'birth-certificate', collector: { relationship: 'INFORMANT' } @@ -215,7 +215,7 @@ describe('Certify record endpoint', () => { event: 'BIRTH', certificate: { hasShowedVerifiedDocument: true, - certTemplateId: 'birth-certificate', + certificateTemplateId: 'birth-certificate', collector: { relationship: 'Other', otherRelationship: 'Uncle', diff --git a/packages/workflow/src/records/handler/issue.test.ts b/packages/workflow/src/records/handler/issue.test.ts index b14ccae8d74..7a7ad897b59 100644 --- a/packages/workflow/src/records/handler/issue.test.ts +++ b/packages/workflow/src/records/handler/issue.test.ts @@ -112,7 +112,7 @@ describe('Issue record endpoint', () => { collector: { relationship: 'INFORMANT' }, - certTemplateId: 'birth-certificate', + certificateTemplateId: 'birth-certificate', payment: { type: 'MANUAL', amount: 100, diff --git a/packages/workflow/src/records/state-transitions.ts b/packages/workflow/src/records/state-transitions.ts index a83a2a24ced..b49d51ea457 100644 --- a/packages/workflow/src/records/state-transitions.ts +++ b/packages/workflow/src/records/state-transitions.ts @@ -943,7 +943,7 @@ export async function toCertified( const previousTask = getTaskFromSavedBundle(record) const taskWithoutPractitionerExtensions = createCertifiedTask( previousTask, - certificateDetails.certTemplateId + certificateDetails.certificateTemplateId ) const [certifiedTask, practitionerResourcesBundle] = @@ -963,7 +963,7 @@ export async function toCertified( temporaryRelatedPersonId, eventType, certificateDetails.hasShowedVerifiedDocument, - certificateDetails.certTemplateId + certificateDetails.certificateTemplateId ) const certificateSection: CompositionSection = { @@ -1045,7 +1045,7 @@ export async function toIssued( temporaryRelatedPersonId, eventType, certificateDetails.hasShowedVerifiedDocument, - certificateDetails.certTemplateId, + certificateDetails.certificateTemplateId, undefined, paymentEntry.fullUrl ) diff --git a/packages/workflow/src/records/validations.ts b/packages/workflow/src/records/validations.ts index 99e82a47bb3..477bc3106f7 100644 --- a/packages/workflow/src/records/validations.ts +++ b/packages/workflow/src/records/validations.ts @@ -18,7 +18,7 @@ export const CertifyRequestSchema = z.object({ event: z.custom(), certificate: z.object({ hasShowedVerifiedDocument: z.boolean(), - certTemplateId: z.string(), + certificateTemplateId: z.string(), collector: z .object({ relationship: z.string(), From f46704318089d10274ef4a0426b2f16218cbc6ca Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 25 Oct 2024 20:12:18 +0600 Subject: [PATCH 27/58] fixed test --- packages/client/src/declarations/index.ts | 1 + .../declarations/submissionMiddleware.test.ts | 35 +++-- .../mutation/registration-mappings.test.ts | 52 +++---- .../birth/mutation/registration-mappings.ts | 15 +- .../death/mutation/deceased-mappings.test.ts | 27 ++-- .../death/mutation/registration-mappings.ts | 13 +- .../mutation/registration-mappings.ts | 21 +-- packages/client/src/tests/util.tsx | 96 +++++++++--- packages/client/src/utils/gateway.ts | 140 +++++++++--------- .../IssueCollectorForm/IssuePayment.test.tsx | 4 +- .../views/PrintCertificate/Payment.test.tsx | 2 +- .../PrintCertificate/VerifyCollector.test.tsx | 8 +- .../collectorForm/CollectorForm.test.tsx | 56 +++++-- 13 files changed, 275 insertions(+), 195 deletions(-) diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index 8d9c22b0285..5401901839c 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -274,6 +274,7 @@ export type ICertificate = { corrector?: Partial<{ type: RelationForCertificateCorrection | string }> hasShowedVerifiedDocument?: boolean payments?: Payment + certificateTemplateId?: string templateConfig?: ICertificateConfigData } diff --git a/packages/client/src/declarations/submissionMiddleware.test.ts b/packages/client/src/declarations/submissionMiddleware.test.ts index a74b063b0a9..6d55d52f3a4 100644 --- a/packages/client/src/declarations/submissionMiddleware.test.ts +++ b/packages/client/src/declarations/submissionMiddleware.test.ts @@ -143,26 +143,31 @@ describe('Submission middleware', () => { mockDeclarationData.registration.certificates[0] = { collector: { relationship: 'OTHER', - affidavit: { + affidavitFile: { contentType: 'image/jpg', data: 'data:image/png;base64,2324256' }, - individual: { - name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], - identifier: [{ id: '123456', type: 'PASSPORT' }] - } + name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], + identifier: [{ id: '123456', type: 'PASSPORT' }] }, hasShowedVerifiedDocument: true, - payments: [ - { - paymentId: '1234', - type: 'MANUAL', - amount: 50, - outcome: 'COMPLETED', - date: '2018-10-22' - } - ], - data: 'data:image/png;base64,2324256' + certificateTemplateId: 'certified-birth-certificate', + templateConfig: { + id: 'certified-birth-certificate', + event: 'birth', + label: { + id: 'certificates.birth.certificate.copy', + defaultMessage: 'birth Certificate certified copy', + description: 'The label for a birth certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + svgUrl: + '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' + } } const action = declarationReadyForStatusChange({ id: 'mockDeclaration', diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts index fcedb64513c..568f3eb5177 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts @@ -32,46 +32,46 @@ describe('Birth registration mutation mapping related tests', () => { expect(transformedData.registration.trackingId).toEqual('BDSS0SE') expect(transformedData.registration.certificates).toEqual([ { + hasShowedVerifiedDocument: true, + certificateTemplateId: 'certified-birth-certificate', + payments: [ + { + paymentId: '1234', + type: 'MANUAL', + amount: 50, + outcome: 'COMPLETED', + date: '2018-10-22' + } + ], collector: { - relationship: 'OTHER', - otherRelationship: 'Uncle', - name: [ - { - use: 'en', - firstNames: 'Mushraful', - familyName: 'Hoque' - } - ], - identifier: [ - { - id: '123456789', - type: 'PASSPORT' - } - ], - affidavit: [ - { - contentType: 'abc', - data: 'BASE64 data' - } - ] - }, - hasShowedVerifiedDocument: true + otherRelationship: 'OTHER', + name: [{ use: 'en' }], + identifier: [{}], + affidavit: [{ data: 'data:image/png;base64,2324256' }] + } } ]) }) - it('Test certificate mapping without any data', () => { + it('Test certificate mapping template config data', () => { const transformedData: TransformedData = { registration: {} } + const mockBirthDeclaration = cloneDeep({ + ...mockDeclarationData, + registration: { + ...mockDeclarationData.registration, + certificates: [{}] + } + }) setBirthRegistrationSectionTransformer( transformedData, - mockDeclarationData, + mockBirthDeclaration, 'registration' ) expect(transformedData.registration).toBeDefined() expect(transformedData.registration.registrationNumber).toEqual( '201908122365BDSS0SE1' ) - expect(transformedData.registration.certificates).toEqual([{}]) + expect(transformedData.registration.certificates).toEqual([]) }) }) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts index a0d1acc08ab..0f76f464b65 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts @@ -44,17 +44,16 @@ export function setBirthRegistrationSectionTransformer( }) } - if ( - Array.isArray(draftData[sectionId].certificates) && - draftData[sectionId].certificates.length - ) { - const updatedCertificates = transformCertificateData( - (draftData[sectionId].certificates as ICertificate[]).slice(-1) - ) + const certificates: ICertificate[] = draftData[sectionId] + .certificates as ICertificate[] + if (Array.isArray(certificates) && certificates.length) { + const updatedCertificates = transformCertificateData(certificates.slice(-1)) transformedData[sectionId].certificates = updatedCertificates.length > 0 && - Object.keys(updatedCertificates[0]).length > 0 // making sure we are not sending empty object as certificate + Object.keys(updatedCertificates[0]).length > 0 && + updatedCertificates[0].collector // making sure we are not sending empty object as certificate ? updatedCertificates : [] } + return transformedData } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts index 26fbe52da1b..2f6b31ba61b 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts @@ -32,10 +32,13 @@ describe('Death registration mutation mapping related tests', () => { ) expect(transformedData.registration.certificates).toEqual([ { + hasShowedVerifiedDocument: true, collector: { - relationship: 'MOTHER' + otherRelationship: 'MOTHER', + name: [{ use: 'en' }], + identifier: [{}] }, - hasShowedVerifiedDocument: true + certificateTemplateId: 'certified-death-certificate' } ]) }) @@ -54,24 +57,14 @@ describe('Death registration mutation mapping related tests', () => { expect(transformedData.registration.trackingId).toEqual('DDSS0SE') expect(transformedData.registration.certificates).toEqual([ { + hasShowedVerifiedDocument: true, + certificateTemplateId: 'certified-death-certificate', collector: { relationship: 'OTHER', otherRelationship: 'Uncle', - name: [ - { - use: 'en', - firstNames: 'Mushraful', - familyName: 'Hoque' - } - ], - identifier: [ - { - id: '123456789', - type: 'PASSPORT' - } - ] - }, - hasShowedVerifiedDocument: true + name: [{ use: 'en', firstNames: 'Mushraful', familyName: 'Hoque' }], + identifier: [{ id: '123456789', type: 'PASSPORT' }] + } } ]) }) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts index 664432eaddf..f005c340354 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts @@ -51,20 +51,19 @@ export function setDeathRegistrationSectionTransformer( }) } - if ( - Array.isArray(draftData[sectionId].certificates) && - draftData[sectionId].certificates.length - ) { + const certificates: ICertificate[] = draftData[sectionId] + .certificates as ICertificate[] + if (Array.isArray(certificates) && certificates.length) { const updatedCertificates = transformCertificateData( - (draftData[sectionId].certificates as ICertificate[]).slice(-1) + certificates.slice(-1) ) transformedData[sectionId].certificates = updatedCertificates.length > 0 && - Object.keys(updatedCertificates[0]).length > 0 // making sure we are not sending empty object as certificate + Object.keys(updatedCertificates[0]).length > 0 && + updatedCertificates[0].collector // making sure we are not sending empty object as certificate ? updatedCertificates : [] } } - return transformedData } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts index 046ea7569c7..46e2aed2251 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts @@ -50,26 +50,19 @@ export function setMarriageRegistrationSectionTransformer( }) } - if ( - Array.isArray(draftData[sectionId].certificates) && - draftData[sectionId].certificates.length > 0 - ) { - transformedData[sectionId].certificates = - draftData[sectionId].certificates.slice(-1) - } - - if ( - Array.isArray(draftData[sectionId].certificates) && - draftData[sectionId].certificates.length - ) { + const certificates: ICertificate[] = draftData[sectionId] + .certificates as ICertificate[] + if (Array.isArray(certificates) && certificates.length) { const updatedCertificates = transformCertificateData( - (draftData[sectionId].certificates as ICertificate[]).slice(-1) + certificates.slice(-1) ) transformedData[sectionId].certificates = updatedCertificates.length > 0 && - Object.keys(updatedCertificates[0]).length > 0 // making sure we are not sending empty object as certificate + Object.keys(updatedCertificates[0]).length > 0 && + updatedCertificates[0].collector // making sure we are not sending empty object as certificate ? updatedCertificates : [] } } + return transformedData } diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index ac689fadc02..420688f3955 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -538,7 +538,6 @@ export const mockDeclarationData = { }, registration: { informantsSignature: 'data:image/png;base64,abcd', - registrationNumber: '201908122365BDSS0SE1', regStatus: { type: 'REGISTERED', @@ -549,19 +548,29 @@ export const mockDeclarationData = { }, certificates: [ { + collector: { + relationship: 'OTHER', + affidavitFile: { + contentType: 'image/jpg', + data: 'data:image/png;base64,2324256' + }, + name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], + identifier: [{ id: '123456', type: 'PASSPORT' }] + }, + certificateTemplateId: 'certified-birth-certificate', + hasShowedVerifiedDocument: true, templateConfig: { id: 'certified-birth-certificate', event: 'birth', label: { id: 'certificates.birth.certificate.copy', - defaultMessage: 'Birth Certificate certified copy', + defaultMessage: 'birth Certificate certified copy', description: 'The label for a birth certificate' }, - fee: { - onTime: 10, - late: 500, - delayed: 150 + onTime: 0, + late: 5.5, + delayed: 15 }, svgUrl: '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' @@ -665,7 +674,7 @@ export const mockDeathDeclarationData = { certificates: [ { collector: { - type: 'MOTHER' + relationship: 'MOTHER' }, hasShowedVerifiedDocument: true, templateConfig: { @@ -676,7 +685,6 @@ export const mockDeathDeclarationData = { defaultMessage: 'Death Certificate certified copy', description: 'The label for a death certificate' }, - fee: { onTime: 0, late: 5.5, @@ -722,7 +730,6 @@ export const mockMarriageDeclarationData = { defaultMessage: 'Marriage Certificate certified copy', description: 'The label for a marriage certificate' }, - fee: { onTime: 0, late: 5.5, @@ -811,18 +818,42 @@ export const mockBirthRegistrationSectionData = { certificates: [ { collector: { - type: 'OTHER', - relationship: 'Uncle', - firstName: 'Mushraful', - lastName: 'Hoque', - iDType: 'PASSPORT', - iD: '123456789', + relationship: 'OTHER', affidavitFile: { - type: 'abc', - data: 'BASE64 data' - } + contentType: 'image/jpg', + data: 'data:image/png;base64,2324256' + }, + + name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], + identifier: [{ id: '123456', type: 'PASSPORT' }] }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'certified-birth-certificate', + payments: [ + { + paymentId: '1234', + type: 'MANUAL', + amount: 50, + outcome: 'COMPLETED', + date: '2018-10-22' + } + ], + templateConfig: { + id: 'certified-birth-certificate', + event: 'birth', + label: { + id: 'certificates.birth.certificate.copy', + defaultMessage: 'birth Certificate certified copy', + description: 'The label for a birth certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + svgUrl: + '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' + } } ] } @@ -849,7 +880,24 @@ export const mockDeathRegistrationSectionData = { iDType: 'PASSPORT', iD: '123456789' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'certified-death-certificate', + templateConfig: { + id: 'certified-death-certificate', + event: 'death', + label: { + id: 'certificates.death.certificate.copy', + defaultMessage: 'Death Certificate certified copy', + description: 'The label for a death certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + svgUrl: + '/api/countryconfig/certificates/death-certificate-certified-copy.svg' + } } ] } @@ -857,7 +905,7 @@ export const mockDeathRegistrationSectionData = { const mockFetchCertificatesTemplatesDefinition = [ { id: 'birth-certificate', - event: 'birth', + event: 'birth' as Event, label: { id: 'certificates.birth.certificate', defaultMessage: 'Birth Certificate', @@ -881,7 +929,7 @@ const mockFetchCertificatesTemplatesDefinition = [ }, { id: 'birth-certificate-copy', - event: 'birth', + event: 'birth' as Event, label: { id: 'certificates.birth-certificate-copy', defaultMessage: 'Birth Certificate certified copy', @@ -906,7 +954,7 @@ const mockFetchCertificatesTemplatesDefinition = [ }, { id: 'death-certificate', - event: 'death', + event: 'death' as Event, label: { id: 'certificates.death.certificate', defaultMessage: 'Death Certificate', @@ -930,7 +978,7 @@ const mockFetchCertificatesTemplatesDefinition = [ }, { id: 'marriage-certificate', - event: 'marriage', + event: 'marriage' as Event, label: { id: 'certificates.marriage.certificate', defaultMessage: 'Marriage Certificate', diff --git a/packages/client/src/utils/gateway.ts b/packages/client/src/utils/gateway.ts index 7d5767c6cc6..9f8f61a8378 100644 --- a/packages/client/src/utils/gateway.ts +++ b/packages/client/src/utils/gateway.ts @@ -7372,76 +7372,82 @@ export type GetRegistrationsListByFilterQueryVariables = Exact<{ size: Scalars['Int'] }> +export type RegistrationsListByLocationFilter = { + __typename: 'TotalMetricsByLocation' + total?: number | null + results: Array<{ + __typename?: 'EventMetricsByLocation' + total: number + late: number + delayed: number + home: number + healthFacility: number + location: { __typename?: 'Location'; name?: string | null } + }> +} + +export type RegistrationsListByRegistrarFilter = { + __typename: 'TotalMetricsByRegistrar' + total?: number | null + results: Array<{ + __typename?: 'EventMetricsByRegistrar' + total: number + late: number + delayed: number + registrarPractitioner?: { + __typename?: 'User' + id: string + systemRole: SystemRoleType + role: { + __typename?: 'Role' + _id: string + labels: Array<{ + __typename?: 'RoleLabel' + lang: string + label: string + }> + } + primaryOffice?: { + __typename?: 'Location' + name?: string | null + id: string + } | null + name: Array<{ + __typename?: 'HumanName' + firstNames?: string | null + familyName?: string | null + use?: string | null + }> + avatar?: { + __typename?: 'Avatar' + type: string + data: string + } | null + } | null + }> +} + +export type RegistrationsListByTimeFilter = { + __typename: 'TotalMetricsByTime' + total?: number | null + results: Array<{ + __typename?: 'EventMetricsByTime' + total: number + delayed: number + late: number + home: number + healthFacility: number + month: string + time: string + }> +} + export type GetRegistrationsListByFilterQuery = { __typename?: 'Query' getRegistrationsListByFilter?: - | { - __typename: 'TotalMetricsByLocation' - total?: number | null - results: Array<{ - __typename?: 'EventMetricsByLocation' - total: number - late: number - delayed: number - home: number - healthFacility: number - location: { __typename?: 'Location'; name?: string | null } - }> - } - | { - __typename: 'TotalMetricsByRegistrar' - total?: number | null - results: Array<{ - __typename?: 'EventMetricsByRegistrar' - total: number - late: number - delayed: number - registrarPractitioner?: { - __typename?: 'User' - id: string - systemRole: SystemRoleType - role: { - __typename?: 'Role' - _id: string - labels: Array<{ - __typename?: 'RoleLabel' - lang: string - label: string - }> - } - primaryOffice?: { - __typename?: 'Location' - name?: string | null - id: string - } | null - name: Array<{ - __typename?: 'HumanName' - firstNames?: string | null - familyName?: string | null - use?: string | null - }> - avatar?: { - __typename?: 'Avatar' - type: string - data: string - } | null - } | null - }> - } - | { - __typename: 'TotalMetricsByTime' - total?: number | null - results: Array<{ - __typename?: 'EventMetricsByTime' - total: number - delayed: number - late: number - home: number - healthFacility: number - month: string - time: string - }> - } + | RegistrationsListByLocationFilter + | RegistrationsListByRegistrarFilter + | RegistrationsListByTimeFilter | null } diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx index 1a8c4a62b94..4affe015586 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx @@ -88,7 +88,7 @@ describe('verify collector tests for issuance', () => { history }) expect(testComponent.find('#service').hostNodes().text()).toContain('Birth') - expect(testComponent.find('#amountDue').hostNodes().text()).toContain('20') + expect(testComponent.find('#amountDue').hostNodes().text()).toContain('15') testComponent.find('#Continue').hostNodes().simulate('click') }) @@ -140,7 +140,7 @@ describe('in case of death declaration renders issue payment component', () => { history }) expect(testComponent.find('#service').hostNodes().text()).toContain('Death') - expect(testComponent.find('#amountDue').hostNodes().text()).toContain('0.0') + expect(testComponent.find('#amountDue').hostNodes().text()).toContain('15') testComponent.find('#Continue').hostNodes().simulate('click') }) }) diff --git a/packages/client/src/views/PrintCertificate/Payment.test.tsx b/packages/client/src/views/PrintCertificate/Payment.test.tsx index e78a70a4c6e..9e9772f8973 100644 --- a/packages/client/src/views/PrintCertificate/Payment.test.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.test.tsx @@ -129,7 +129,7 @@ describe('verify collector tests', () => { 'Birth' ) expect(testComponent.find('#amountDue').hostNodes().text()).toContain( - '150' + '15' ) testComponent.find('#Continue').hostNodes().simulate('click') diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx index 5ddf04e9454..2106a1407f1 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.test.tsx @@ -40,7 +40,13 @@ const birthDeclaration = { const deathDeclaration = { id: 'mockDeath1234', data: { - ...mockDeathDeclarationData, + ...{ + ...mockDeathDeclarationData, + deathEvent: { + ...mockDeathDeclarationData.deathEvent, + deathDate: new Date().toISOString().slice(0, 10) + } + }, history: [ { date: '2022-03-21T08:16:24.467+00:00', diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx index c2325c3583a..e13fe88ee14 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx @@ -17,17 +17,20 @@ import { inValidImageB64String, mockDeclarationData, mockDeathDeclarationData, - mockMarriageDeclarationData + mockMarriageDeclarationData, + mockOfflineData, + flushPromises } from '@client/tests/util' import { ReactWrapper } from 'enzyme' import * as React from 'react' import { CollectorForm } from './CollectorForm' import { waitFor, waitForElement } from '@client/tests/wait-for-element' import { createLocation, History } from 'history' -import { merge } from 'lodash' +import { cloneDeep, merge } from 'lodash' import { Event } from '@client/utils/gateway' -import { storeDeclaration } from '@client/declarations' +import { modifyDeclaration, storeDeclaration } from '@client/declarations' import { vi } from 'vitest' +import { getOfflineDataSuccess } from '@client/offline/actions' let store: AppStore let history: History @@ -229,7 +232,7 @@ describe('Certificate collector test for a birth registration without father det }) component.update() expect(history.location.pathname).toBe( - '/print/check/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate/father' + '/print/check/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth/father' ) }) @@ -270,7 +273,7 @@ describe('Certificate collector test for a birth registration without father det }) component.update() expect(history.location.pathname).toBe( - '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate/otherCertCollector' + '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth/otherCertCollector' ) }) }) @@ -282,7 +285,11 @@ describe('Certificate collector test for a birth registration without father det /* * Who is collecting the certificate? */ - store.dispatch(storeDeclaration(birthDeclaration)) + const declaration = cloneDeep(birthDeclaration) + delete (declaration.data.registration.certificates[0].collector as any) + .affidavitFile + + store.dispatch(storeDeclaration(declaration)) component = await createTestComponent( { expect(history.location.pathname).toBe( - '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate/affidavit' + '/cert/collector/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth/affidavit' ) }) @@ -439,13 +446,36 @@ describe('Certificate collector test for a birth registration without father det component.find('#submit_confirm').hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/print/payment/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate' + '/print/payment/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth' ) }) it('continue to review section when the mandatory fields are filled and birth event is before target days', async () => { - birthDeclaration.data.child.childBirthDate = '2022-09-20' - store.dispatch(storeDeclaration(birthDeclaration)) + // setting date of birth today + const clonedBirthDeclaration = cloneDeep(birthDeclaration) + clonedBirthDeclaration.data.child.childBirthDate = new Date() + .toISOString() + .slice(0, 10) + store.dispatch(modifyDeclaration(clonedBirthDeclaration)) + + // setting on time birth certificate fee amount to 0 + const offlineDataResponse = JSON.stringify({ + ...mockOfflineData, + templates: { + ...mockOfflineData.templates, + certificates: mockOfflineData.templates.certificates.map( + (x: any) => { + if (x.event === 'birth') { + x.fee.onTime = 0 + } + return x + } + ) + } + }) + store.dispatch(getOfflineDataSuccess(offlineDataResponse)) + await flushPromises() + const comp = await waitForElement( component, '#noAffidavitAgreementAFFIDAVIT' @@ -464,7 +494,7 @@ describe('Certificate collector test for a birth registration without father det ).toHaveLength(1) component.find('#submit_confirm').hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/review/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth-certificate' + '/review/6a5fd35d-01ec-4c37-976e-e055107a74a1/birth' ) }) @@ -575,7 +605,7 @@ describe('Certificate collector test for a death registration', () => { $confirm.hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/review/16ff35e1-3f92-4db3-b812-c402e609fb00/death-certificate' + '/review/16ff35e1-3f92-4db3-b812-c402e609fb00/death' ) }) }) @@ -632,7 +662,7 @@ describe('Certificate collector test for a marriage registration', () => { $confirm.hostNodes().simulate('click') expect(history.location.pathname).toBe( - '/review/18ff35e1-3d92-4db3-b815-c4d2e609fb23/marriage-certificate' + '/review/18ff35e1-3d92-4db3-b815-c4d2e609fb23/marriage' ) }) }) From ab6e0795d26efd7854a239e50dea803d683c76ca Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 28 Oct 2024 12:29:46 +0600 Subject: [PATCH 28/58] templateConfig removed --- packages/client/src/declarations/index.ts | 1 - .../declarations/submissionMiddleware.test.ts | 18 +--- packages/client/src/forms/index.ts | 1 - .../birth/query/registration-mappings.ts | 24 ++--- .../death/query/registration-mappings.ts | 36 +++----- .../marriage/query/registration-mappings.ts | 35 +++----- .../forms/register/mappings/mutation/utils.ts | 8 +- packages/client/src/offline/reducer.ts | 2 +- packages/client/src/tests/util.tsx | 90 ++----------------- .../utils/gateway-deprecated-do-not-use.d.ts | 1 - .../IssueCollectorForm/IssueCollectorForm.tsx | 2 +- .../IssueCollectorForm/IssueFormForOthers.tsx | 15 ++-- .../collectorForm/CollectorForm.tsx | 6 +- .../usePrintableCertificate.ts | 16 ++-- .../src/views/PrintCertificate/utils.ts | 19 ++-- 15 files changed, 68 insertions(+), 206 deletions(-) diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index 5401901839c..88eca59c33d 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -275,7 +275,6 @@ export type ICertificate = { hasShowedVerifiedDocument?: boolean payments?: Payment certificateTemplateId?: string - templateConfig?: ICertificateConfigData } /* diff --git a/packages/client/src/declarations/submissionMiddleware.test.ts b/packages/client/src/declarations/submissionMiddleware.test.ts index 6d55d52f3a4..84797b7126e 100644 --- a/packages/client/src/declarations/submissionMiddleware.test.ts +++ b/packages/client/src/declarations/submissionMiddleware.test.ts @@ -151,23 +151,7 @@ describe('Submission middleware', () => { identifier: [{ id: '123456', type: 'PASSPORT' }] }, hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-birth-certificate', - templateConfig: { - id: 'certified-birth-certificate', - event: 'birth', - label: { - id: 'certificates.birth.certificate.copy', - defaultMessage: 'birth Certificate certified copy', - description: 'The label for a birth certificate' - }, - fee: { - onTime: 0, - late: 5.5, - delayed: 15 - }, - svgUrl: - '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' - } + certificateTemplateId: 'certified-birth-certificate' } const action = declarationReadyForStatusChange({ id: 'mockDeclaration', diff --git a/packages/client/src/forms/index.ts b/packages/client/src/forms/index.ts index 799be95b86a..4820a067a58 100644 --- a/packages/client/src/forms/index.ts +++ b/packages/client/src/forms/index.ts @@ -1297,6 +1297,5 @@ export interface ICertificate { collector?: IFormSectionData hasShowedVerifiedDocument?: boolean payments?: Payment[] - templateConfig?: ICertificateConfigData certificateTemplateId?: string } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts index 7030c305bd6..64465352b31 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts @@ -8,18 +8,14 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { IFormData, IFormField } from '@client/forms' +import { IFormData } from '@client/forms' import { Event } from '@client/utils/gateway' import { transformStatusData } from '@client/forms/register/mappings/query/utils' -import { IOfflineData } from '@client/offline/reducer' export function getBirthRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - sectionId: string, - fieldDef: IFormField, - nestedFormField?: IFormField, - offlineData?: IOfflineData + sectionId: string ) { if (queryData[sectionId].trackingId) { transformedData[sectionId].trackingId = queryData[sectionId].trackingId @@ -42,16 +38,10 @@ export function getBirthRegistrationSectionTransformer( Array.isArray(queryData[sectionId].certificates) && queryData[sectionId].certificates.length > 0 ) { - const certificate = queryData[sectionId].certificates.slice(-1)[0] - // since we shall need this certificate only for ready to issue tab, to calculate certificate fee - transformedData[sectionId].certificates = certificate?.certificateTemplateId - ? [ - { - templateConfig: offlineData?.templates.certificates?.find( - (x) => x.id === certificate.certificateTemplateId - ) - } - ] - : [] + transformedData[sectionId].certificates = [ + queryData[sectionId].certificates[ + queryData[sectionId].certificates.length - 1 + ] + ] } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts index 5db2e0d48f4..276c9263c66 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts @@ -8,19 +8,15 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { IFormData, IFormField } from '@client/forms' +import { IFormData } from '@client/forms' import { Event } from '@client/utils/gateway' import type { GQLRegWorkflow } from '@client/utils/gateway-deprecated-do-not-use' import { transformStatusData } from '@client/forms/register/mappings/query/utils' -import { IOfflineData } from '@client/offline/reducer' export function getDeathRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - sectionId: string, - fieldDef: IFormField, - nestedFormField?: IFormField, - offlineData?: IOfflineData + sectionId: string ) { if (!transformedData[sectionId]) { transformedData[sectionId] = {} @@ -42,23 +38,6 @@ export function getDeathRegistrationSectionTransformer( transformedData[sectionId].type = Event.Death } - if ( - Array.isArray(queryData[sectionId].certificates) && - queryData[sectionId].certificates.length > 0 - ) { - const certificate = queryData[sectionId].certificates.slice(-1)[0] - // since we shall need this certificate only for ready to issue tab, to calculate certificate fee - transformedData[sectionId].certificates = certificate?.certificateTemplateId - ? [ - { - templateConfig: offlineData?.templates.certificates?.find( - (x) => x.id === certificate.certificateTemplateId - ) - } - ] - : [] - } - if (queryData[sectionId].status) { transformStatusData( transformedData, @@ -66,4 +45,15 @@ export function getDeathRegistrationSectionTransformer( sectionId ) } + + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + transformedData[sectionId].certificates = [ + queryData[sectionId].certificates[ + queryData[sectionId].certificates.length - 1 + ] + ] + } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts index d4f7785c60d..9c398b96eda 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts @@ -8,19 +8,15 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { IFormData, IFormField } from '@client/forms' +import { IFormData } from '@client/forms' import { transformStatusData } from '@client/forms/register/mappings/query/utils' -import { IOfflineData } from '@client/offline/reducer' import { Event } from '@client/utils/gateway' import type { GQLRegWorkflow } from '@client/utils/gateway-deprecated-do-not-use' export function getMarriageRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - sectionId: string, - fieldDef: IFormField, - nestedFormField?: IFormField, - offlineData?: IOfflineData + sectionId: string ) { if (queryData[sectionId].trackingId) { transformedData[sectionId].trackingId = queryData[sectionId].trackingId @@ -39,22 +35,6 @@ export function getMarriageRegistrationSectionTransformer( transformedData[sectionId].type = Event.Marriage } - if ( - Array.isArray(queryData[sectionId].certificates) && - queryData[sectionId].certificates.length > 0 - ) { - const certificate = queryData[sectionId].certificates.slice(-1)[0] - // since we shall need this certificate only for ready to issue tab, to calculate certificate fee - transformedData[sectionId].certificates = certificate?.certificateTemplateId - ? [ - { - templateConfig: offlineData?.templates.certificates?.find( - (x) => x.id === certificate.certificateTemplateId - ) - } - ] - : [] - } if (queryData[sectionId].status) { transformStatusData( transformedData, @@ -62,6 +42,17 @@ export function getMarriageRegistrationSectionTransformer( sectionId ) } + + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + transformedData[sectionId].certificates = [ + queryData[sectionId].certificates[ + queryData[sectionId].certificates.length - 1 + ] + ] + } } export function groomSignatureTransformer( diff --git a/packages/client/src/forms/register/mappings/mutation/utils.ts b/packages/client/src/forms/register/mappings/mutation/utils.ts index 9d86ae85d36..ba12b7481ec 100644 --- a/packages/client/src/forms/register/mappings/mutation/utils.ts +++ b/packages/client/src/forms/register/mappings/mutation/utils.ts @@ -33,7 +33,7 @@ export function transformCertificateData(certificates: ICertificate[]) { // Prepare the base certificate data const updatedCertificates: ICertificate[] = [ { - ...omit(certificateData, 'collector', 'templateConfig') + ...omit(certificateData, 'collector') } ] @@ -75,12 +75,6 @@ export function transformCertificateData(certificates: ICertificate[]) { updatedCertificates[0].collector = collector as any } - // for templateConfig mapping - if (certificateData && certificateData.templateConfig) { - updatedCertificates[0].certificateTemplateId = - certificateData.templateConfig.id - } - // Return the processed certificates array return updatedCertificates } diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index aaf26b85e95..fb6b8805930 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -105,7 +105,7 @@ export interface IOfflineData { fonts?: CertificateConfiguration['fonts'] // Certificates might not be defined in the case of // a field agent using the app. - certificates?: ICertificateConfigData[] + certificates: ICertificateConfigData[] } assets: { logo: string diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 420688f3955..21dad7b3707 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -558,23 +558,7 @@ export const mockDeclarationData = { identifier: [{ id: '123456', type: 'PASSPORT' }] }, certificateTemplateId: 'certified-birth-certificate', - hasShowedVerifiedDocument: true, - templateConfig: { - id: 'certified-birth-certificate', - event: 'birth', - label: { - id: 'certificates.birth.certificate.copy', - defaultMessage: 'birth Certificate certified copy', - description: 'The label for a birth certificate' - }, - fee: { - onTime: 0, - late: 5.5, - delayed: 15 - }, - svgUrl: - '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' - } + hasShowedVerifiedDocument: true } ] }, @@ -676,23 +660,7 @@ export const mockDeathDeclarationData = { collector: { relationship: 'MOTHER' }, - hasShowedVerifiedDocument: true, - templateConfig: { - id: 'certified-death-certificate', - event: 'death', - label: { - id: 'certificates.death.certificate.copy', - defaultMessage: 'Death Certificate certified copy', - description: 'The label for a death certificate' - }, - fee: { - onTime: 0, - late: 5.5, - delayed: 15 - }, - svgUrl: - '/api/countryconfig/certificates/death-certificate-certified-copy.svg' - } + hasShowedVerifiedDocument: true } ] } @@ -721,23 +689,7 @@ export const mockMarriageDeclarationData = { collector: { type: 'BRIDE' }, - hasShowedVerifiedDocument: true, - templateConfig: { - id: 'certified-marriage-certificate', - event: 'marriage', - label: { - id: 'certificates.marriage.certificate.copy', - defaultMessage: 'Marriage Certificate certified copy', - description: 'The label for a marriage certificate' - }, - fee: { - onTime: 0, - late: 5.5, - delayed: 15 - }, - svgUrl: - '/api/countryconfig/certificates/marriage-certificate-certified-copy.svg' - } + hasShowedVerifiedDocument: true } ] }, @@ -837,23 +789,7 @@ export const mockBirthRegistrationSectionData = { outcome: 'COMPLETED', date: '2018-10-22' } - ], - templateConfig: { - id: 'certified-birth-certificate', - event: 'birth', - label: { - id: 'certificates.birth.certificate.copy', - defaultMessage: 'birth Certificate certified copy', - description: 'The label for a birth certificate' - }, - fee: { - onTime: 0, - late: 5.5, - delayed: 15 - }, - svgUrl: - '/api/countryconfig/certificates/birth-certificate-certified-copy.svg' - } + ] } ] } @@ -881,23 +817,7 @@ export const mockDeathRegistrationSectionData = { iD: '123456789' }, hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-death-certificate', - templateConfig: { - id: 'certified-death-certificate', - event: 'death', - label: { - id: 'certificates.death.certificate.copy', - defaultMessage: 'Death Certificate certified copy', - description: 'The label for a death certificate' - }, - fee: { - onTime: 0, - late: 5.5, - delayed: 15 - }, - svgUrl: - '/api/countryconfig/certificates/death-certificate-certified-copy.svg' - } + certificateTemplateId: 'certified-death-certificate' } ] } diff --git a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts index af6d2ad41ba..f2f2f3189db 100644 --- a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts +++ b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts @@ -6895,7 +6895,6 @@ export interface GQLCertificateTypeResolver { collector?: CertificateToCollectorResolver hasShowedVerifiedDocument?: CertificateToHasShowedVerifiedDocumentResolver payments?: CertificateToPaymentsResolver - templateConfig?: CertificateToTemplateConfigResolver } export interface CertificateToCollectorResolver { diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx index 81636b34397..2b21513c3ff 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx @@ -64,7 +64,7 @@ export function IssueCollectorForm({ { collector: collector, hasShowedVerifiedDocument: false, - templateConfig: certificate.templateConfig + certificateTemplateId: certificate.certificateTemplateId } ] } diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx index 1f3cf046c63..d93ddd9ef28 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx @@ -59,7 +59,11 @@ export const IssueCollectorFormForOthers = ({ const dispatch = useDispatch() const config = useSelector(getOfflineData) const user = useSelector(getUserDetails) - + const { relationship, ...collectorForm }: { [key: string]: any } = + (declaration && + declaration.data.registration.certificates && + declaration.data.registration.certificates[0].collector) || + {} const fields: IFormField[] = collectorFormFieldsForOthers(declaration.event) const handleChange = ( sectionData: ICertificate['collector'], @@ -80,7 +84,7 @@ export const IssueCollectorFormForOthers = ({ { collector: collector, hasShowedVerifiedDocument: false, - templateConfig: certificate.templateConfig + certificateTemplateId: certificate.certificateTemplateId } ] } @@ -138,12 +142,7 @@ export const IssueCollectorFormForOthers = ({ setAllFieldsDirty={false} fields={replaceInitialValues( fields, - (declaration && - declaration.data.registration.certificates && - declaration.data.registration.certificates[ - declaration.data.registration.certificates.length - 1 - ].collector) || - {}, + collectorForm, declaration && declaration.data, config, user diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index ea63271402e..696aedec610 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -210,10 +210,6 @@ class CollectorFormComponent extends React.Component { const certificates = declaration.data.registration.certificates const certificate = (certificates && certificates[0]) || {} const collector = { ...(certificate.collector || {}), ...sectionData } - const selectedTemplatedConfig = - this.props.offlineCountryConfiguration.templates.certificates?.find( - (x) => x.id === (collector.certificateTemplateId as string) - ) this.props.modifyDeclaration({ ...declaration, data: { @@ -224,7 +220,7 @@ class CollectorFormComponent extends React.Component { { collector: collector, hasShowedVerifiedDocument: false, - templateConfig: selectedTemplatedConfig + certificateTemplateId: certificate.certificateTemplateId } ] } diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index bcbfbf8dbd8..2d9ec6e9a88 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -129,15 +129,16 @@ export const usePrintableCertificate = () => { const [svgCode, setSvgCode] = useState() const certificateTemplateConfig: ICertificateConfigData | undefined = - declaration?.data.registration.certificates.slice(-1)[0].templateConfig + offlineData.templates.certificates.find( + (x) => + x.id === + declaration?.data.registration.certificates[0].certificateTemplateId + ) const certificateFonts = certificateTemplateConfig?.fonts ?? {} useEffect(() => { const certificateUrl = - (declaration && - declaration?.data.registration.certificates.slice(-1)[0].templateConfig - ?.svgUrl) || - '' + (declaration && certificateTemplateConfig?.svgUrl) || '' if (certificateUrl && declaration) { fetch(certificateUrl) @@ -194,14 +195,11 @@ export const usePrintableCertificate = () => { { ...draft.data.template, preview: false }, state ) - - const { fonts, ...templateConfig } = certificateTemplateConfig draft.data.registration = { ...draft.data.registration, certificates: [ { - ...certificate, - templateConfig + ...certificate } ] } diff --git a/packages/client/src/views/PrintCertificate/utils.ts b/packages/client/src/views/PrintCertificate/utils.ts index 213f7d2076a..ea9479493ed 100644 --- a/packages/client/src/views/PrintCertificate/utils.ts +++ b/packages/client/src/views/PrintCertificate/utils.ts @@ -61,15 +61,18 @@ function getDayRanges( offlineData: IOfflineData, certificate: ICertificate ): IRange[] { - switch (certificate.templateConfig?.event) { + const templateConfig = offlineData.templates.certificates.find( + (x) => x.id === certificate.certificateTemplateId + ) + switch (templateConfig?.event) { case Event.Birth: { const BIRTH_REGISTRATION_TARGET = offlineData.config.BIRTH.REGISTRATION_TARGET const BIRTH_LATE_REGISTRATION_TARGET = offlineData.config.BIRTH.LATE_REGISTRATION_TARGET - const BIRTH_ON_TIME_FEE = certificate.templateConfig?.fee.onTime - const BIRTH_LATE_FEE = certificate.templateConfig?.fee.late - const BIRTH_DELAYED_FEE = certificate.templateConfig?.fee.delayed + const BIRTH_ON_TIME_FEE = templateConfig?.fee.onTime + const BIRTH_LATE_FEE = templateConfig?.fee.late + const BIRTH_DELAYED_FEE = templateConfig?.fee.delayed const birthRanges = [ { start: 0, end: BIRTH_REGISTRATION_TARGET, value: BIRTH_ON_TIME_FEE }, { @@ -85,8 +88,8 @@ function getDayRanges( case Event.Death: { const DEATH_REGISTRATION_TARGET = offlineData.config.DEATH.REGISTRATION_TARGET - const DEATH_ON_TIME_FEE = certificate.templateConfig?.fee.onTime - const DEATH_DELAYED_FEE = certificate.templateConfig?.fee.delayed + const DEATH_ON_TIME_FEE = templateConfig?.fee.onTime + const DEATH_DELAYED_FEE = templateConfig?.fee.delayed const deathRanges = [ { start: 0, end: DEATH_REGISTRATION_TARGET, value: DEATH_ON_TIME_FEE }, @@ -97,8 +100,8 @@ function getDayRanges( case Event.Marriage: { const MARRIAGE_REGISTRATION_TARGET = offlineData.config.MARRIAGE.REGISTRATION_TARGET - const MARRIAGE_ON_TIME_FEE = certificate.templateConfig?.fee.onTime - const MARRIAGE_DELAYED_FEE = certificate.templateConfig?.fee.delayed + const MARRIAGE_ON_TIME_FEE = templateConfig?.fee.onTime + const MARRIAGE_DELAYED_FEE = templateConfig?.fee.delayed const marriageRanges = [ { start: 0, From 437fbb091c922a1b0e587998d1bf6f6adbe06519 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 28 Oct 2024 19:49:18 +0600 Subject: [PATCH 29/58] fixed test --- .../death/mutation/deceased-mappings.test.ts | 4 ++-- packages/client/src/tests/util.tsx | 15 ++++++++------- .../collectorForm/CollectorForm.tsx | 2 +- .../PrintCertificate/usePrintableCertificate.ts | 5 +++++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts index 2f6b31ba61b..b759336e70f 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts @@ -38,7 +38,7 @@ describe('Death registration mutation mapping related tests', () => { name: [{ use: 'en' }], identifier: [{}] }, - certificateTemplateId: 'certified-death-certificate' + certificateTemplateId: 'death-certificate' } ]) }) @@ -58,7 +58,7 @@ describe('Death registration mutation mapping related tests', () => { expect(transformedData.registration.certificates).toEqual([ { hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-death-certificate', + certificateTemplateId: 'death-certificate', collector: { relationship: 'OTHER', otherRelationship: 'Uncle', diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 21dad7b3707..869b88c0480 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -557,7 +557,7 @@ export const mockDeclarationData = { name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], identifier: [{ id: '123456', type: 'PASSPORT' }] }, - certificateTemplateId: 'certified-birth-certificate', + certificateTemplateId: 'birth-certificate', hasShowedVerifiedDocument: true } ] @@ -660,7 +660,8 @@ export const mockDeathDeclarationData = { collector: { relationship: 'MOTHER' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'death-certificate' } ] } @@ -689,7 +690,8 @@ export const mockMarriageDeclarationData = { collector: { type: 'BRIDE' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'marriage-certificate' } ] }, @@ -780,7 +782,7 @@ export const mockBirthRegistrationSectionData = { identifier: [{ id: '123456', type: 'PASSPORT' }] }, hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-birth-certificate', + certificateTemplateId: 'birth-certificate', payments: [ { paymentId: '1234', @@ -817,7 +819,7 @@ export const mockDeathRegistrationSectionData = { iD: '123456789' }, hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-death-certificate' + certificateTemplateId: 'death-certificate' } ] } @@ -861,8 +863,7 @@ const mockFetchCertificatesTemplatesDefinition = [ late: 5.5, delayed: 15 }, - svgUrl: - '/api/countryconfig/certificates/birth-certificate-certified-copy.svg', + svgUrl: '/api/countryconfig/certificates/birth-certificate-copy.svg', fonts: { 'Noto Sans': { normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index 696aedec610..bbae3d37700 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -220,7 +220,7 @@ class CollectorFormComponent extends React.Component { { collector: collector, hasShowedVerifiedDocument: false, - certificateTemplateId: certificate.certificateTemplateId + certificateTemplateId: collector.certificateTemplateId } ] } diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index 2d9ec6e9a88..f5c24f737ee 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -140,6 +140,11 @@ export const usePrintableCertificate = () => { const certificateUrl = (declaration && certificateTemplateConfig?.svgUrl) || '' + console.log( + 'declaration?.id, certificateTemplateConfig?.svgUrl ', + declaration?.id, + certificateTemplateConfig?.svgUrl + ) if (certificateUrl && declaration) { fetch(certificateUrl) .then((res) => res.text()) From f50615c95c6b7e30f0200d3f487ceead1b50c389 Mon Sep 17 00:00:00 2001 From: Pyry Rouvila Date: Fri, 25 Oct 2024 15:30:39 +0300 Subject: [PATCH 30/58] feat(auth): allow issuing single record specific tokens (#7728) * refactor: move metrics out of authenticate * feat: initial token exchange endpoint * chore: update comment to be more specific * fix: issue with calling metrics environment * chore: amend changelog * refactor: improve clarity * fix: unused import * chore: add missing schema inputs * revert: revert mosip code removal to keep PR contained * chore: add comment about more fine-grained control * chore: fix test on gateway * feat: actually check if the record id matches to confirm record * revert: the confirm registration changes - lets do inanother pr * refactor: update error messages * fix: make the no-op simpler * fix: the query params not passing properly via gateway * refactor: remove unnecessary gql inputs * fix: update audiences to include minimum * fix: update the todo comment --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a40f8212c83..8882676c5b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - **Template-based Payment Configuration**: Implemented payment differentiation based on the selected certificate template, ensuring the correct amount is charged. - **Template Action Tracking**: Each template printed is tracked in the history table, showing which specific template was used. - **Template Selection Dropdown**: Updated print workflow to include a dropdown menu for template selection when issuing a certificate. +- Auth now allows exchanging user's token for a new record-specific token [#7728](https://github.com/opencrvs/opencrvs-core/issues/7728) ## Bug fixes From 0960666cadde7f8edd8126f83078045c8088b472 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 29 Oct 2024 06:14:09 +0600 Subject: [PATCH 31/58] type fixed --- packages/client/src/declarations/index.ts | 1 - packages/client/src/declarations/selectors.ts | 3 +-- packages/client/src/forms/index.ts | 1 - .../src/views/PrintCertificate/usePrintableCertificate.ts | 5 ----- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index 88eca59c33d..1c29df7cb5e 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -91,7 +91,6 @@ import { getReviewForm } from '@client/forms/register/review-selectors' import { getBirthQueryMappings } from '@client/views/DataProvider/birth/queries' import { getDeathQueryMappings } from '@client/views/DataProvider/death/queries' import { getMarriageQueryMappings } from '@client/views/DataProvider/marriage/queries' -import { ICertificateConfigData } from '@client/utils/referenceApi' const ARCHIVE_DECLARATION = 'DECLARATION/ARCHIVE' const SET_INITIAL_DECLARATION = 'DECLARATION/SET_INITIAL_DECLARATION' diff --git a/packages/client/src/declarations/selectors.ts b/packages/client/src/declarations/selectors.ts index 452bb324002..2fe785d6078 100644 --- a/packages/client/src/declarations/selectors.ts +++ b/packages/client/src/declarations/selectors.ts @@ -39,6 +39,5 @@ const selectDeclaration = export const useDeclaration = ( declarationId: string ) => { - const foo = useSelector(selectDeclaration(declarationId)) - return foo + return useSelector(selectDeclaration(declarationId)) } diff --git a/packages/client/src/forms/index.ts b/packages/client/src/forms/index.ts index 4820a067a58..cc8ddc43d8f 100644 --- a/packages/client/src/forms/index.ts +++ b/packages/client/src/forms/index.ts @@ -38,7 +38,6 @@ import { NATIONAL_ID } from '@client/utils/constants' import { IconProps } from '@opencrvs/components/lib' -import { ICertificateConfigData } from '@client/utils/referenceApi' export const TEXT = 'TEXT' export const TEL = 'TEL' diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index f5c24f737ee..2d9ec6e9a88 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -140,11 +140,6 @@ export const usePrintableCertificate = () => { const certificateUrl = (declaration && certificateTemplateConfig?.svgUrl) || '' - console.log( - 'declaration?.id, certificateTemplateConfig?.svgUrl ', - declaration?.id, - certificateTemplateConfig?.svgUrl - ) if (certificateUrl && declaration) { fetch(certificateUrl) .then((res) => res.text()) From 64460a5b27aedad791ca00ce0589d9b54c134dc0 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 29 Oct 2024 16:22:21 +0600 Subject: [PATCH 32/58] added isDefault property in certificate config --- .../declarations/submissionMiddleware.test.ts | 15 +--- .../fieldDefinitions/collectorSection.ts | 5 +- .../mutation/registration-mappings.test.ts | 13 ++-- packages/client/src/tests/schema.graphql | 69 ------------------- packages/client/src/tests/templates.json | 4 ++ packages/client/src/tests/util.tsx | 38 ++++++---- packages/client/src/utils/referenceApi.ts | 2 +- .../collectorForm/CollectorForm.test.tsx | 4 +- 8 files changed, 43 insertions(+), 107 deletions(-) diff --git a/packages/client/src/declarations/submissionMiddleware.test.ts b/packages/client/src/declarations/submissionMiddleware.test.ts index 84797b7126e..d306085da2d 100644 --- a/packages/client/src/declarations/submissionMiddleware.test.ts +++ b/packages/client/src/declarations/submissionMiddleware.test.ts @@ -12,6 +12,7 @@ import { ApolloError } from '@apollo/client' import { SubmissionAction } from '@client/forms' import { ACTION_STATUS_MAP, + mockBirthRegistrationSectionData, mockDeclarationData, mockOfflineDataDispatch } from '@client/tests/util' @@ -140,19 +141,7 @@ describe('Submission middleware', () => { } it(`should handle ${ACTION_STATUS_MAP[submissionAction]} ${event} declarations`, async () => { - mockDeclarationData.registration.certificates[0] = { - collector: { - relationship: 'OTHER', - affidavitFile: { - contentType: 'image/jpg', - data: 'data:image/png;base64,2324256' - }, - name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], - identifier: [{ id: '123456', type: 'PASSPORT' }] - }, - hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-birth-certificate' - } + mockDeclarationData.registration = mockBirthRegistrationSectionData const action = declarationReadyForStatusChange({ id: 'mockDeclaration', data: mockDeclarationData, diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index ff7433120c9..1f91af0b8db 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -1045,10 +1045,7 @@ function getCertCollectorGroupForEvent( certificates ?.filter((x) => x.event === declaration.event) .map((x) => ({ label: x.label, value: x.id })) || [] - const certTemplateDefaultValue = - certificateTemplateOptions.length > 0 - ? certificateTemplateOptions[0].value - : '' + const certTemplateDefaultValue = certificates?.find((x) => x.isDefault)?.id return { id: 'certCollector', title: certificateMessages.whoToCollect, diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts index 568f3eb5177..b53a3ed6ed1 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts @@ -33,7 +33,7 @@ describe('Birth registration mutation mapping related tests', () => { expect(transformedData.registration.certificates).toEqual([ { hasShowedVerifiedDocument: true, - certificateTemplateId: 'certified-birth-certificate', + certificateTemplateId: 'birth-certificate', payments: [ { paymentId: '1234', @@ -45,9 +45,14 @@ describe('Birth registration mutation mapping related tests', () => { ], collector: { otherRelationship: 'OTHER', - name: [{ use: 'en' }], - identifier: [{}], - affidavit: [{ data: 'data:image/png;base64,2324256' }] + name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], + identifier: [{ id: '123456', type: 'PASSPORT' }], + affidavit: [ + { + contentType: 'image/jpg', + data: 'data:image/png;base64,2324256' + } + ] } } ]) diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index dfe25af396f..e37414c4303 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -399,33 +399,6 @@ input CertificateInput { certificateTemplateId: String } -enum CertificateStatus { - ACTIVE - INACTIVE -} - -type CertificateSVG { - id: ID! - svgCode: String! - svgFilename: String! - svgDateUpdated: String! - svgDateCreated: String! - user: String! - event: Event! - status: CertificateStatus! -} - -input CertificateSVGInput { - id: ID - svgCode: String! - svgFilename: String! - svgDateUpdated: Int - svgDateCreated: Int - user: String! - event: Event! - status: CertificateStatus! -} - type CertificationMetric { total: Float! eventType: String! @@ -1379,8 +1352,6 @@ type Query { sortBy: String sortOrder: String ): [SystemRole!] - getCertificateSVG(status: CertificateStatus!, event: Event!): CertificateSVG - getActiveCertificatesSVG: [CertificateSVG!] fetchSystem(clientId: ID!): System informantSMSNotifications: [SMSNotification!] } @@ -1906,43 +1877,3 @@ type WebhookPermission { event: String! permissions: [String!]! } - -type CertificateConfigData { - id: String! - event: String! - label: CertificateLabel! - fee: CertificateFee! - svgUrl: String! -} - -type CertificateLabel { - id: String! - defaultMessage: String! - description: String! -} - -type CertificateFee { - onTime: Float! - late: Float! - delayed: Float! -} - -input CertificateConfigDataInput { - id: String! - event: String! - label: CertificateLabelInput! - fee: CertificateFeeInput! - svgUrl: String! -} - -input CertificateLabelInput { - id: String! - defaultMessage: String! - description: String! -} - -input CertificateFeeInput { - onTime: Float! - late: Float! - delayed: Float! -} diff --git a/packages/client/src/tests/templates.json b/packages/client/src/tests/templates.json index e40f9241690..5d844ca3d50 100644 --- a/packages/client/src/tests/templates.json +++ b/packages/client/src/tests/templates.json @@ -22,6 +22,7 @@ "late": 5.5, "delayed": 15 }, + "isDefault": true, "svgUrl": "/api/countryconfig/certificates/birth-certificate.svg", "fonts": { "Noto Sans": { @@ -46,6 +47,7 @@ "late": 5.5, "delayed": 15 }, + "isDefault": false, "svgUrl": "/api/countryconfig/certificates/birth-certificate-certified-copy.svg", "fonts": { "Noto Sans": { @@ -70,6 +72,7 @@ "late": 5.5, "delayed": 15 }, + "isDefault": true, "svgUrl": "/api/countryconfig/certificates/death-certificate.svg", "fonts": { "Noto Sans": { @@ -94,6 +97,7 @@ "late": 5.5, "delayed": 15 }, + "isDefault": true, "svgUrl": "/api/countryconfig/certificates/marriage-certificate.svg", "fonts": { "Noto Sans": { diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 869b88c0480..c1cc29e86db 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -551,14 +551,25 @@ export const mockDeclarationData = { collector: { relationship: 'OTHER', affidavitFile: { - contentType: 'image/jpg', + type: 'image/jpg', data: 'data:image/png;base64,2324256' }, - name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], - identifier: [{ id: '123456', type: 'PASSPORT' }] + firstName: 'Doe', + lastName: 'Jane', + iD: '123456', + iDType: 'PASSPORT' }, certificateTemplateId: 'birth-certificate', - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + payments: [ + { + paymentId: '1234', + type: 'MANUAL', + amount: 50, + outcome: 'COMPLETED', + date: '2018-10-22' + } + ] } ] }, @@ -774,15 +785,16 @@ export const mockBirthRegistrationSectionData = { collector: { relationship: 'OTHER', affidavitFile: { - contentType: 'image/jpg', + type: 'image/jpg', data: 'data:image/png;base64,2324256' }, - - name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], - identifier: [{ id: '123456', type: 'PASSPORT' }] + firstName: 'Doe', + lastName: 'Jane', + iD: '123456', + iDType: 'PASSPORT' }, - hasShowedVerifiedDocument: true, certificateTemplateId: 'birth-certificate', + hasShowedVerifiedDocument: true, payments: [ { paymentId: '1234', @@ -833,12 +845,12 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Birth Certificate', description: 'The label for a birth certificate' }, - fee: { onTime: 0, late: 5.5, delayed: 15 }, + isDefault: true, svgUrl: '/api/countryconfig/certificates/birth-certificate.svg', fonts: { 'Noto Sans': { @@ -857,12 +869,12 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Birth Certificate certified copy', description: 'The label for a birth certificate' }, - fee: { onTime: 0, late: 5.5, delayed: 15 }, + isDefault: false, svgUrl: '/api/countryconfig/certificates/birth-certificate-copy.svg', fonts: { 'Noto Sans': { @@ -881,12 +893,12 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Death Certificate', description: 'The label for a death certificate' }, - fee: { onTime: 0, late: 5.5, delayed: 15 }, + isDefault: true, svgUrl: '/api/countryconfig/certificates/death-certificate.svg', fonts: { 'Noto Sans': { @@ -905,12 +917,12 @@ const mockFetchCertificatesTemplatesDefinition = [ defaultMessage: 'Marriage Certificate', description: 'The label for a marriage certificate' }, - fee: { onTime: 0, late: 5.5, delayed: 15 }, + isDefault: true, svgUrl: '/api/countryconfig/certificates/marriage-certificate.svg', fonts: { 'Noto Sans': { diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index 61795667078..5cfec26a14c 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -85,7 +85,7 @@ export interface ICertificateConfigData { defaultMessage: string description: string } - + isDefault: boolean fee: { onTime: number late: number diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx index e13fe88ee14..fcc39216393 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx @@ -286,9 +286,7 @@ describe('Certificate collector test for a birth registration without father det * Who is collecting the certificate? */ const declaration = cloneDeep(birthDeclaration) - delete (declaration.data.registration.certificates[0].collector as any) - .affidavitFile - + declaration.data.registration.certificates = [] store.dispatch(storeDeclaration(declaration)) component = await createTestComponent( Date: Tue, 29 Oct 2024 19:32:23 +0600 Subject: [PATCH 33/58] migration script for adding certificateTemplateId extension on missing Documentrefences --- .../utils/gateway-deprecated-do-not-use.d.ts | 106 +++++++++++++++- ...d-certificateTemplateId-to-missing-docs.ts | 116 ++++++++++++++++++ 2 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts diff --git a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts index f2f2f3189db..65ba841ca1e 100644 --- a/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts +++ b/packages/client/src/utils/gateway-deprecated-do-not-use.d.ts @@ -1325,6 +1325,7 @@ export interface GQLRoleInput { export interface GQLBirth { REGISTRATION_TARGET?: number LATE_REGISTRATION_TARGET?: number + FEE?: GQLBirthFee PRINT_IN_ADVANCE?: boolean } @@ -1340,11 +1341,13 @@ export interface GQLCurrency { export interface GQLDeath { REGISTRATION_TARGET?: number + FEE?: GQLDeathFee PRINT_IN_ADVANCE?: boolean } export interface GQLMarriage { REGISTRATION_TARGET?: number + FEE?: GQLMarriageFee PRINT_IN_ADVANCE?: boolean } @@ -1357,6 +1360,7 @@ export interface GQLLoginBackground { export interface GQLBirthInput { REGISTRATION_TARGET?: number LATE_REGISTRATION_TARGET?: number + FEE?: GQLBirthFeeInput PRINT_IN_ADVANCE?: boolean } @@ -1372,11 +1376,13 @@ export interface GQLCurrencyInput { export interface GQLDeathInput { REGISTRATION_TARGET?: number + FEE?: GQLDeathFeeInput PRINT_IN_ADVANCE?: boolean } export interface GQLMarriageInput { REGISTRATION_TARGET?: number + FEE?: GQLMarriageFeeInput PRINT_IN_ADVANCE?: boolean } @@ -1780,11 +1786,43 @@ export interface GQLLabelInput { label: string } +export interface GQLBirthFee { + ON_TIME?: number + LATE?: number + DELAYED?: number +} + +export interface GQLDeathFee { + ON_TIME?: number + DELAYED?: number +} + +export interface GQLMarriageFee { + ON_TIME?: number + DELAYED?: number +} + export const enum GQLImageFit { FILL = 'FILL', TILE = 'TILE' } +export interface GQLBirthFeeInput { + ON_TIME?: number + LATE?: number + DELAYED?: number +} + +export interface GQLDeathFeeInput { + ON_TIME?: number + DELAYED?: number +} + +export interface GQLMarriageFeeInput { + ON_TIME?: number + DELAYED?: number +} + export interface GQLAuditLogItemBase { time: string ipAddress: string @@ -1987,6 +2025,9 @@ export interface GQLResolver { EventProgressData?: GQLEventProgressDataTypeResolver WebhookPermission?: GQLWebhookPermissionTypeResolver OIDPUserAddress?: GQLOIDPUserAddressTypeResolver + BirthFee?: GQLBirthFeeTypeResolver + DeathFee?: GQLDeathFeeTypeResolver + MarriageFee?: GQLMarriageFeeTypeResolver AuditLogItemBase?: { __resolveType: GQLAuditLogItemBaseTypeResolver } @@ -6691,6 +6732,7 @@ export interface OIDPUserInfoToUpdated_atResolver< export interface GQLBirthTypeResolver { REGISTRATION_TARGET?: BirthToREGISTRATION_TARGETResolver LATE_REGISTRATION_TARGET?: BirthToLATE_REGISTRATION_TARGETResolver + FEE?: BirthToFEEResolver PRINT_IN_ADVANCE?: BirthToPRINT_IN_ADVANCEResolver } @@ -6708,6 +6750,10 @@ export interface BirthToLATE_REGISTRATION_TARGETResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } +export interface BirthToFEEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + export interface BirthToPRINT_IN_ADVANCEResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } @@ -6743,6 +6789,7 @@ export interface CurrencyToLanguagesAndCountryResolver< export interface GQLDeathTypeResolver { REGISTRATION_TARGET?: DeathToREGISTRATION_TARGETResolver + FEE?: DeathToFEEResolver PRINT_IN_ADVANCE?: DeathToPRINT_IN_ADVANCEResolver } @@ -6753,12 +6800,17 @@ export interface DeathToREGISTRATION_TARGETResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } +export interface DeathToFEEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + export interface DeathToPRINT_IN_ADVANCEResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } export interface GQLMarriageTypeResolver { REGISTRATION_TARGET?: MarriageToREGISTRATION_TARGETResolver + FEE?: MarriageToFEEResolver PRINT_IN_ADVANCE?: MarriageToPRINT_IN_ADVANCEResolver } @@ -6769,6 +6821,10 @@ export interface MarriageToREGISTRATION_TARGETResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } +export interface MarriageToFEEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + export interface MarriageToPRINT_IN_ADVANCEResolver< TParent = any, TResult = any @@ -6895,6 +6951,7 @@ export interface GQLCertificateTypeResolver { collector?: CertificateToCollectorResolver hasShowedVerifiedDocument?: CertificateToHasShowedVerifiedDocumentResolver payments?: CertificateToPaymentsResolver + data?: CertificateToDataResolver } export interface CertificateToCollectorResolver { @@ -6912,10 +6969,7 @@ export interface CertificateToPaymentsResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } -export interface CertificateToTemplateConfigResolver< - TParent = any, - TResult = any -> { +export interface CertificateToDataResolver { (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } @@ -8324,6 +8378,50 @@ export interface OIDPUserAddressToCountryResolver< (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult } +export interface GQLBirthFeeTypeResolver { + ON_TIME?: BirthFeeToON_TIMEResolver + LATE?: BirthFeeToLATEResolver + DELAYED?: BirthFeeToDELAYEDResolver +} + +export interface BirthFeeToON_TIMEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + +export interface BirthFeeToLATEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + +export interface BirthFeeToDELAYEDResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + +export interface GQLDeathFeeTypeResolver { + ON_TIME?: DeathFeeToON_TIMEResolver + DELAYED?: DeathFeeToDELAYEDResolver +} + +export interface DeathFeeToON_TIMEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + +export interface DeathFeeToDELAYEDResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + +export interface GQLMarriageFeeTypeResolver { + ON_TIME?: MarriageFeeToON_TIMEResolver + DELAYED?: MarriageFeeToDELAYEDResolver +} + +export interface MarriageFeeToON_TIMEResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + +export interface MarriageFeeToDELAYEDResolver { + (parent: TParent, args: {}, context: any, info: GraphQLResolveInfo): TResult +} + export interface GQLAuditLogItemBaseTypeResolver { (parent: TParent, context: any, info: GraphQLResolveInfo): | 'UserAuditLogItemWithComposition' diff --git a/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts b/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts new file mode 100644 index 00000000000..e6afe82568f --- /dev/null +++ b/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts @@ -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 { Db, MongoClient } from 'mongodb' + +// Define an interface for your DocumentReference +interface DocumentReference { + _id: string + extension: Array<{ url: string; valueString?: string }> + type?: { + coding?: Array<{ system: string; code: string }> + } +} + +export const up = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const collection = db.collection('DocumentReference') + const documents = await collection.find({}).toArray() + + for (const doc of documents) { + // Check if certificateTemplateId extension already exists + const hasCertificateTemplateId = doc.extension.some( + (ext) => + ext.url === + 'http://opencrvs.org/specs/extension/certificateTemplateId' + ) + + if (!hasCertificateTemplateId) { + // Determine the certificate type based on `code` + const certType = doc.type?.coding?.find((x) => + x.system.includes('certificate-type') + )?.code + + let certificateTemplateId: string | null = null + + switch (certType) { + case 'BIRTH': + certificateTemplateId = 'birth-certificate' + break + case 'DEATH': + certificateTemplateId = 'death-certificate' + break + case 'MARRIAGE': + certificateTemplateId = 'marriage-certificate' + break + } + + if (certificateTemplateId) { + // Add the missing certificateTemplateId extension + await collection.updateOne( + { _id: doc._id }, + { + $push: { + extension: { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: certificateTemplateId + } + } + } + ) + console.log( + `Updated DocumentReference document with _id: ${doc._id} for missing certificateTemplateId with the extension value ${certificateTemplateId}` + ) + } + } + } + }) + } catch (error) { + console.error('Error occurred while updating document references:', error) + throw error + } finally { + session.endSession() + } +} + +export const down = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const collection = db.collection('DocumentReference') + + // Remove the certificateTemplateId extension for each certificate type + await collection.updateMany( + { + extension: { + $elemMatch: { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + } + } + }, + { + $pull: { + extension: { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + } + } + } + ) + console.log('Reverted certificateTemplateId extension from all documents') + }) + } catch (error) { + console.error('Error occurred while reverting document references:', error) + throw error + } finally { + session.endSession() + } +} From 5eaeb3c0fae2e09d9ec8f8e79e9a531f76430b65 Mon Sep 17 00:00:00 2001 From: tahmidrahman-dsi Date: Tue, 29 Oct 2024 14:36:21 +0600 Subject: [PATCH 34/58] chore: test trigger From 7678d2979ed89bc46691c9408cfefbcceb833bd3 Mon Sep 17 00:00:00 2001 From: Muhammed Tareq Aziz Date: Thu, 31 Oct 2024 14:42:52 +0600 Subject: [PATCH 35/58] refactor typo Co-authored-by: Riku Rouvila --- packages/client/src/declarations/selectors.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client/src/declarations/selectors.ts b/packages/client/src/declarations/selectors.ts index 2fe785d6078..ff0913ba840 100644 --- a/packages/client/src/declarations/selectors.ts +++ b/packages/client/src/declarations/selectors.ts @@ -30,10 +30,9 @@ export const getInitialDeclarationsLoaded = ( const selectDeclaration = (declarationId: string) => (store: IStoreState) => { - const bar = getKey(store, 'declarations').find( + return getKey(store, 'declarations').find( ({ id }) => declarationId === id ) as T - return bar } export const useDeclaration = ( From 109c758a6a966da302c8aff51fd4c57e600da151 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 31 Oct 2024 17:01:41 +0600 Subject: [PATCH 36/58] refactored collector form as per design --- .../fieldDefinitions/collectorSection.ts | 26 +++++++++---------- .../src/i18n/messages/views/certificate.ts | 2 +- .../collectorForm/CollectorForm.tsx | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index 1f91af0b8db..1c7bffa7a50 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -1014,7 +1014,7 @@ const marriageIssueCollectorFormOptions = [ function getCertCollectorGroupForEvent( declaration: IDeclaration, - certificates?: ICertificateConfigData[] + certificates: ICertificateConfigData[] ): IFormSectionGroup { const informant = (declaration.data.informant.otherInformantType || declaration.data.informant.informantType) as string @@ -1043,14 +1043,23 @@ function getCertCollectorGroupForEvent( ) const certificateTemplateOptions = certificates - ?.filter((x) => x.event === declaration.event) + .filter((x) => x.event === declaration.event) .map((x) => ({ label: x.label, value: x.id })) || [] - const certTemplateDefaultValue = certificates?.find((x) => x.isDefault)?.id + const certTemplateDefaultValue = certificates.find((x) => x.isDefault)?.id return { id: 'certCollector', title: certificateMessages.whoToCollect, error: certificateMessages.certificateCollectorError, fields: [ + { + name: 'certificateTemplateId', + type: 'SELECT_WITH_OPTIONS', + label: certificateMessages.certificateTemplateSelectLabel, + required: true, + initialValue: certTemplateDefaultValue, + validator: [], + options: certificateTemplateOptions + }, { name: 'type', type: RADIO_GROUP, @@ -1061,15 +1070,6 @@ function getCertCollectorGroupForEvent( initialValue: '', validator: [], options: finalOptions - }, - { - name: 'certificateTemplateId', - type: 'SELECT_WITH_OPTIONS', - label: certificateMessages.certificateTemplateSelectLabel, - required: true, - initialValue: certTemplateDefaultValue, - validator: [], - options: certificateTemplateOptions } ] } @@ -1077,7 +1077,7 @@ function getCertCollectorGroupForEvent( export function getCertificateCollectorFormSection( declaration: IDeclaration, - certificates?: ICertificateConfigData[] + certificates: ICertificateConfigData[] ): IFormSection { return { id: CertificateSection.Collector, diff --git a/packages/client/src/i18n/messages/views/certificate.ts b/packages/client/src/i18n/messages/views/certificate.ts index a6261039957..fcd61124444 100644 --- a/packages/client/src/i18n/messages/views/certificate.ts +++ b/packages/client/src/i18n/messages/views/certificate.ts @@ -98,7 +98,7 @@ const messagesToDefine: ICertificateMessages = { id: 'print.certificate.section.title' }, certificateTemplateSelectLabel: { - defaultMessage: 'Select certificate template', + defaultMessage: 'Type', description: 'The title of select certificate template action', id: 'certificate.selectTemplate' }, diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index bbae3d37700..394e6c94169 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -509,7 +509,7 @@ const mapStateToProps = ( const registeringOfficeId = getRegisteringOfficeId(declaration) const certFormSection = getCertificateCollectorFormSection( declaration, - state.offline.offlineData.templates?.certificates + state.offline.offlineData.templates?.certificates || [] ) const isAllowPrintInAdvance = From 7e00c9c896e1f9e2944c9d233aa8f2a35c65ab2c Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 31 Oct 2024 18:23:54 +0600 Subject: [PATCH 37/58] refactored certificateTemplateId migration from code to aggregation --- ...d-certificateTemplateId-to-missing-docs.ts | 169 +++++++++++------- 1 file changed, 105 insertions(+), 64 deletions(-) diff --git a/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts b/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts index e6afe82568f..9b3f6fbd842 100644 --- a/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts +++ b/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts @@ -10,69 +10,94 @@ */ import { Db, MongoClient } from 'mongodb' -// Define an interface for your DocumentReference -interface DocumentReference { - _id: string - extension: Array<{ url: string; valueString?: string }> - type?: { - coding?: Array<{ system: string; code: string }> - } -} - export const up = async (db: Db, client: MongoClient) => { const session = client.startSession() try { await session.withTransaction(async () => { - const collection = db.collection('DocumentReference') - const documents = await collection.find({}).toArray() - - for (const doc of documents) { - // Check if certificateTemplateId extension already exists - const hasCertificateTemplateId = doc.extension.some( - (ext) => - ext.url === - 'http://opencrvs.org/specs/extension/certificateTemplateId' - ) - - if (!hasCertificateTemplateId) { - // Determine the certificate type based on `code` - const certType = doc.type?.coding?.find((x) => - x.system.includes('certificate-type') - )?.code - - let certificateTemplateId: string | null = null - - switch (certType) { - case 'BIRTH': - certificateTemplateId = 'birth-certificate' - break - case 'DEATH': - certificateTemplateId = 'death-certificate' - break - case 'MARRIAGE': - certificateTemplateId = 'marriage-certificate' - break + const bulkOps = [ + { + $match: { + 'extension.url': { + $ne: 'http://opencrvs.org/specs/extension/certificateTemplateId' + } } - - if (certificateTemplateId) { - // Add the missing certificateTemplateId extension - await collection.updateOne( - { _id: doc._id }, - { - $push: { - extension: { - url: 'http://opencrvs.org/specs/extension/certificateTemplateId', - valueString: certificateTemplateId + }, + { + $set: { + certType: { + $arrayElemAt: [ + { + $filter: { + input: '$type.coding', + as: 'coding', + cond: { + $regexMatch: { + input: '$$coding.system', + regex: /certificate-type/ + } + } } - } + }, + 0 + ] + } + } + }, + { + $set: { + certificateTemplateId: { + $switch: { + branches: [ + { + case: { $eq: ['$certType.code', 'BIRTH'] }, + then: 'birth-certificate' + }, + { + case: { $eq: ['$certType.code', 'DEATH'] }, + then: 'death-certificate' + }, + { + case: { $eq: ['$certType.code', 'MARRIAGE'] }, + then: 'marriage-certificate' + } + ], + default: null } - ) - console.log( - `Updated DocumentReference document with _id: ${doc._id} for missing certificateTemplateId with the extension value ${certificateTemplateId}` - ) + } + } + }, + { + $match: { + certificateTemplateId: { $ne: null } + } + }, + { + $set: { + extension: { + $concatArrays: [ + '$extension', + [ + { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: '$certificateTemplateId' + } + ] + ] + } + } + }, + { + $unset: ['certType', 'certificateTemplateId'] + }, + { + $merge: { + into: 'DocumentReference', + whenMatched: 'replace' } } - } + ] + + await db.collection('DocumentReference').aggregate(bulkOps).toArray() }) } catch (error) { console.error('Error occurred while updating document references:', error) @@ -86,25 +111,41 @@ export const down = async (db: Db, client: MongoClient) => { const session = client.startSession() try { await session.withTransaction(async () => { - const collection = db.collection('DocumentReference') - - // Remove the certificateTemplateId extension for each certificate type - await collection.updateMany( + const bulkOps = [ { - extension: { - $elemMatch: { - url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + $match: { + extension: { + $elemMatch: { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + } } } }, { - $pull: { + $set: { extension: { - url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + $filter: { + input: '$extension', + as: 'ext', + cond: { + $ne: [ + '$$ext.url', + 'http://opencrvs.org/specs/extension/certificateTemplateId' + ] + } + } } } + }, + { + $merge: { + into: 'DocumentReference', + whenMatched: 'merge', + whenNotMatched: 'discard' + } } - ) + ] + await db.collection('DocumentReference').aggregate(bulkOps).toArray() console.log('Reverted certificateTemplateId extension from all documents') }) } catch (error) { From b03d7d6a3011f6e14272832e4ade680067c93e5f Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 1 Nov 2024 16:06:20 +0600 Subject: [PATCH 38/58] template config fonts baseUrl is updated with client's window.location.origin --- packages/client/src/offline/reducer.ts | 18 ++++++++- .../usePrintableCertificate.ts | 39 ++++++++++++------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index fb6b8805930..c4133751c04 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -362,7 +362,23 @@ function reducer( systems, templates: { ...state.offlineData.templates, - certificates + certificates: (certificates as ICertificateConfigData[]).map((x) => { + const baseUrl = window.location.origin + if (x.fonts) { + x.fonts = Object.fromEntries( + Object.entries(x.fonts).map(([fontFamily, fontStyles]) => [ + fontFamily, + { + normal: `${baseUrl}${fontStyles.normal}`, + bold: `${baseUrl}${fontStyles.bold}`, + italics: `${baseUrl}${fontStyles.italics}`, + bolditalics: `${baseUrl}${fontStyles.bolditalics}` + } + ]) + ) + } + return x + }) } } diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index 2d9ec6e9a88..a3345e2ab5c 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -26,6 +26,7 @@ import { getOfflineData } from '@client/offline/selectors' import { getScope, getUserDetails } from '@client/profile/profileSelectors' import { IStoreState } from '@client/store' import { + getToken, hasRegisterScope, hasRegistrationClerkScope } from '@client/utils/authUtils' @@ -136,23 +137,32 @@ export const usePrintableCertificate = () => { ) const certificateFonts = certificateTemplateConfig?.fonts ?? {} + const downloadCertificateSvg = async (certificateUrl: string) => { + const response = await fetch(certificateUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${getToken()}` + } + }) + const svg = await response.text() + return svg + } + useEffect(() => { const certificateUrl = (declaration && certificateTemplateConfig?.svgUrl) || '' if (certificateUrl && declaration) { - fetch(certificateUrl) - .then((res) => res.text()) - .then((certificateTemplate) => { - if (!certificateTemplate) return - const svgWithoutFonts = compileSvg( - certificateTemplate, - { ...declaration.data.template, preview: true }, - state - ) - const svgWithFonts = addFontsToSvg(svgWithoutFonts, certificateFonts) - setSvgCode(svgWithFonts) - }) + downloadCertificateSvg(certificateUrl).then((certificateTemplate) => { + if (!certificateTemplate) return + const svgWithoutFonts = compileSvg( + certificateTemplate, + { ...declaration.data.template, preview: true }, + state + ) + const svgWithFonts = addFontsToSvg(svgWithoutFonts, certificateFonts) + setSvgCode(svgWithFonts) + }) } // eslint-disable-next-line }, [declaration]) @@ -187,9 +197,10 @@ export const usePrintableCertificate = () => { } } - const svgTemplate = await fetch(certificateTemplateConfig.svgUrl).then( - (res) => res.text() + const svgTemplate = await downloadCertificateSvg( + certificateTemplateConfig.svgUrl ) + const svg = await compileSvg( svgTemplate, { ...draft.data.template, preview: false }, From 93ae093250fdbda8b803520172acf8f7ca2045d4 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 5 Nov 2024 14:01:47 +0600 Subject: [PATCH 39/58] removed unused import --- packages/client/src/offline/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index c4133751c04..20af3927526 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -32,7 +32,7 @@ import { } from '@client/utils/referenceApi' import { ILanguage } from '@client/i18n/reducer' import { filterLocations } from '@client/utils/locationUtils' -import { Event, System } from '@client/utils/gateway' +import { System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' import { isOfflineDataLoaded } from './selectors' import { merge } from 'lodash' From 6da6fb8e4eaa3c86888de8f10cdb92c18bb7f127 Mon Sep 17 00:00:00 2001 From: Muhammed Tareq Aziz Date: Tue, 5 Nov 2024 14:18:17 +0600 Subject: [PATCH 40/58] made certificateTemplateId mandatory in ICertificate Co-authored-by: Riku Rouvila --- packages/commons/src/fhir/transformers/input.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/commons/src/fhir/transformers/input.ts b/packages/commons/src/fhir/transformers/input.ts index 95201dd454b..d55e67ab300 100644 --- a/packages/commons/src/fhir/transformers/input.ts +++ b/packages/commons/src/fhir/transformers/input.ts @@ -207,7 +207,7 @@ interface Certificate { collector?: RelatedPerson hasShowedVerifiedDocument?: boolean payments?: Array - certificateTemplateId?: string + certificateTemplateId: string } interface Deceased { deceased?: boolean From 6a7050709b4f43859ca3ed669dec13724d5ccbf7 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 5 Nov 2024 20:14:54 +0600 Subject: [PATCH 41/58] storing certificates on query mappings only when PRINT_IN_ADVANCE --- packages/client/src/declarations/index.ts | 2 +- .../forms/certificate/fieldDefinitions/collectorSection.ts | 4 +++- .../birth/query/registration-mappings.ts | 6 ++++-- .../death/query/registration-mappings.ts | 6 ++++-- .../marriage/query/registration-mappings.ts | 6 ++++-- packages/client/src/views/PrintCertificate/Payment.tsx | 2 +- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index 1c29df7cb5e..de6de8fc410 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -268,7 +268,7 @@ type RelationForCertificateCorrection = export type ICertificate = { collector?: Partial<{ type: Relation | string - certificateTemplateId?: string + certificateTemplateId: string }> corrector?: Partial<{ type: RelationForCertificateCorrection | string }> hasShowedVerifiedDocument?: boolean diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index 1c7bffa7a50..a4c8a14e9a9 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -1045,7 +1045,9 @@ function getCertCollectorGroupForEvent( certificates .filter((x) => x.event === declaration.event) .map((x) => ({ label: x.label, value: x.id })) || [] - const certTemplateDefaultValue = certificates.find((x) => x.isDefault)?.id + const certTemplateDefaultValue = certificates.find( + (x) => x.isDefault && x.event === declaration.event + )?.id return { id: 'certCollector', title: certificateMessages.whoToCollect, diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts index 64465352b31..86e4f033d42 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts @@ -38,10 +38,12 @@ export function getBirthRegistrationSectionTransformer( Array.isArray(queryData[sectionId].certificates) && queryData[sectionId].certificates.length > 0 ) { - transformedData[sectionId].certificates = [ + const currentCertificate = queryData[sectionId].certificates[ queryData[sectionId].certificates.length - 1 ] - ] + if (currentCertificate?.collector?.relationship === 'PRINT_IN_ADVANCE') { + transformedData[sectionId].certificates = [currentCertificate] + } } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts index 276c9263c66..9193eaef7de 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts @@ -50,10 +50,12 @@ export function getDeathRegistrationSectionTransformer( Array.isArray(queryData[sectionId].certificates) && queryData[sectionId].certificates.length > 0 ) { - transformedData[sectionId].certificates = [ + const currentCertificate = queryData[sectionId].certificates[ queryData[sectionId].certificates.length - 1 ] - ] + if (currentCertificate?.collector?.relationship === 'PRINT_IN_ADVANCE') { + transformedData[sectionId].certificates = [currentCertificate] + } } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts index 9c398b96eda..d44f67babc4 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts @@ -47,11 +47,13 @@ export function getMarriageRegistrationSectionTransformer( Array.isArray(queryData[sectionId].certificates) && queryData[sectionId].certificates.length > 0 ) { - transformedData[sectionId].certificates = [ + const currentCertificate = queryData[sectionId].certificates[ queryData[sectionId].certificates.length - 1 ] - ] + if (currentCertificate?.collector?.relationship === 'PRINT_IN_ADVANCE') { + transformedData[sectionId].certificates = [currentCertificate] + } } } diff --git a/packages/client/src/views/PrintCertificate/Payment.tsx b/packages/client/src/views/PrintCertificate/Payment.tsx index 77c08f82071..bfcc04d5b17 100644 --- a/packages/client/src/views/PrintCertificate/Payment.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.tsx @@ -198,7 +198,7 @@ function mapStatetoProps( props: RouteComponentProps<{ registrationId: string; eventType: string }> ) { const { registrationId, eventType } = props.match.params - const event = getEvent(eventType) as Event + const event = getEvent(eventType) const declaration = state.declarationsState.declarations.find( (app) => app.id === registrationId && app.event === event ) as IPrintableDeclaration From 9838169893a5a812d6a42dce6bf8fa1d99b32bb2 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 14 Nov 2024 15:27:15 +0600 Subject: [PATCH 42/58] jpg|png|jpeg|svg urls in template replaced with base64 data before printing pdf --- .../usePrintableCertificate.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index a3345e2ab5c..771f831e9b1 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -48,6 +48,33 @@ import { printPDF } from '@client/pdfRenderer' import { useEffect, useState } from 'react' import { useParams } from 'react-router' import { ICertificateConfigData } from '@client/utils/referenceApi' +import { fetchImageAsBase64 } from '@client/utils/imageUtils' + +async function replaceMinioUrlWithBase64(template: Record) { + const regex = /\/[^\/?]+\.(jpg|png|jpeg|svg)(?=\?|$)/ + + async function recursiveTransform(obj: any) { + if (typeof obj !== 'object' || obj === null) { + return obj + } + + const transformedObject = Array.isArray(obj) ? [...obj] : { ...obj } + + for (const key in obj) { + const value = obj[key] + if (typeof value === 'string' && regex.test(value)) { + transformedObject[key] = await fetchImageAsBase64(value) + } else if (typeof value === 'object') { + transformedObject[key] = await recursiveTransform(value) + } else { + transformedObject[key] = value + } + } + + return transformedObject + } + return recursiveTransform(template) +} const withEnhancedTemplateVariables = ( declaration: IPrintableDeclaration | undefined, @@ -201,9 +228,13 @@ export const usePrintableCertificate = () => { certificateTemplateConfig.svgUrl ) + const base64ReplacedTemplate = await replaceMinioUrlWithBase64( + draft.data.template + ) + const svg = await compileSvg( svgTemplate, - { ...draft.data.template, preview: false }, + { ...base64ReplacedTemplate, preview: false }, state ) draft.data.registration = { From ce03d153d557e125be86a73f5db8a177ac5526ff Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 14 Nov 2024 16:55:39 +0600 Subject: [PATCH 43/58] stripTypename from certificate in mutation/registration-mappings --- packages/client/src/forms/register/mappings/mutation/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/forms/register/mappings/mutation/utils.ts b/packages/client/src/forms/register/mappings/mutation/utils.ts index ba12b7481ec..21676c80d69 100644 --- a/packages/client/src/forms/register/mappings/mutation/utils.ts +++ b/packages/client/src/forms/register/mappings/mutation/utils.ts @@ -28,7 +28,7 @@ export function stripTypename(obj: any): any { return obj } export function transformCertificateData(certificates: ICertificate[]) { - const certificateData = certificates[0] + const certificateData = stripTypename(certificates[0]) // Prepare the base certificate data const updatedCertificates: ICertificate[] = [ From 4159cc257bbff17cf8c41ad4be6ba9bc2b205c50 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 14 Nov 2024 20:56:44 +0600 Subject: [PATCH 44/58] saving certificate template in offlineData during startup --- .../fieldDefinitions/collectorSection.ts | 6 +- packages/client/src/offline/actions.ts | 25 ++++-- packages/client/src/offline/reducer.ts | 76 +++++++++++++++++-- packages/client/src/utils/referenceApi.ts | 6 ++ .../usePrintableCertificate.ts | 52 +++---------- 5 files changed, 110 insertions(+), 55 deletions(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index a4c8a14e9a9..a0d06c98b33 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -36,7 +36,7 @@ import { identityHelperTextMapper, identityNameMapper } from './messages' import { Event } from '@client/utils/gateway' import { IDeclaration } from '@client/declarations' import { issueMessages } from '@client/i18n/messages/issueCertificate' -import { ICertificateConfigData } from '@client/utils/referenceApi' +import { ICertificateData } from '@client/utils/referenceApi' interface INameField { firstNamesField: string @@ -1014,7 +1014,7 @@ const marriageIssueCollectorFormOptions = [ function getCertCollectorGroupForEvent( declaration: IDeclaration, - certificates: ICertificateConfigData[] + certificates: ICertificateData[] ): IFormSectionGroup { const informant = (declaration.data.informant.otherInformantType || declaration.data.informant.informantType) as string @@ -1079,7 +1079,7 @@ function getCertCollectorGroupForEvent( export function getCertificateCollectorFormSection( declaration: IDeclaration, - certificates: ICertificateConfigData[] + certificates: ICertificateData[] ): IFormSection { return { id: CertificateSection.Collector, diff --git a/packages/client/src/offline/actions.ts b/packages/client/src/offline/actions.ts index 5a3733ffd1d..9f25ea453b4 100644 --- a/packages/client/src/offline/actions.ts +++ b/packages/client/src/offline/actions.ts @@ -26,7 +26,8 @@ import { LoadFormsResponse, LoadValidatorsResponse, LoadConditionalsResponse, - LoadHandlebarHelpersResponse + LoadHandlebarHelpersResponse, + ICertificateData } from '@client/utils/referenceApi' import { System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' @@ -109,9 +110,15 @@ type ApplicationConfigLoadedAction = { payload: IApplicationConfigResponse } -const CERTIFICATE_LOAD_FAILED = 'OFFLINE/CERTIFICATE_LOAD_FAILED' -type CertificateLoadFailedAction = { - type: typeof CERTIFICATE_LOAD_FAILED +export const CERTIFICATES_LOADED = 'OFFLINE/CERTIFICATES_LOADED' +type CertificatesLoadedAction = { + type: typeof CERTIFICATES_LOADED + payload: ICertificateData[] +} + +export const CERTIFICATES_LOAD_FAILED = 'OFFLINE/CERTIFICATES_LOAD_FAILED' +type CertificatesLoadFailedAction = { + type: typeof CERTIFICATES_LOAD_FAILED payload: Error } @@ -252,6 +259,13 @@ export const configLoaded = ( payload: payload }) +export const certificatesLoaded = ( + payload: ICertificateData[] +): CertificatesLoadedAction => ({ + type: CERTIFICATES_LOADED, + payload +}) + export const configFailed = (error: Error): ApplicationConfigFailedAction => ({ type: APPLICATION_CONFIG_FAILED, payload: error @@ -316,10 +330,11 @@ export type Action = | ContentFailedAction | ContentLoadedAction | ApplicationConfigLoadedAction + | CertificatesLoadedAction + | CertificatesLoadFailedAction | ApplicationConfigAnonymousUserAction | ApplicationConfigFailedAction | ApplicationConfigUpdatedAction - | CertificateLoadFailedAction | UpdateOfflineSystemsAction | IFilterLocationsAction | ReturnType diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index 20af3927526..521e6eb1b02 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -28,7 +28,7 @@ import { CertificateConfiguration, IFacilitiesDataResponse, IOfficesDataResponse, - ICertificateConfigData + ICertificateData } from '@client/utils/referenceApi' import { ILanguage } from '@client/i18n/reducer' import { filterLocations } from '@client/utils/locationUtils' @@ -45,6 +45,7 @@ import { configurationErrorNotification } from '@client/notification/actions' import { initHandlebarHelpers } from '@client/forms/handlebarHelpers' +import { getToken } from '@client/utils/authUtils' export const OFFLINE_LOCATIONS_KEY = 'locations' export const OFFLINE_FACILITIES_KEY = 'facilities' @@ -103,9 +104,10 @@ export interface IOfflineData { languages: ILanguage[] templates: { fonts?: CertificateConfiguration['fonts'] - // Certificates might not be defined in the case of - // a field agent using the app. - certificates: ICertificateConfigData[] + // TODO: Certificates need to be restricted in the case of + // a user who does not have permission to print certificate + // (ex: a field agent using the app) + certificates: ICertificateData[] } assets: { logo: string @@ -134,6 +136,46 @@ async function saveOfflineData(offlineData: IOfflineData) { return storage.setItem('offline', JSON.stringify(offlineData)) } +async function loadSingleCertificate(certificate: ICertificateData) { + const { id } = certificate + const res = await fetch( + new URL(`/certificates/${id}`, window.config.COUNTRY_CONFIG_URL).toString(), + { + headers: { + Authorization: getToken(), + 'If-None-Match': certificate?.hash ?? '' + } + } + ) + if (res.status === 304) { + return { + ...certificate, + svg: certificate.svg, + hash: certificate!.hash! + } + } + if (!res.ok) { + return Promise.reject( + new Error(`Fetching certificate with id: "${id}" failed`) + ) + } + return res.text().then((svg) => { + return { + ...certificate, + svg, + hash: res.headers.get('etag')! + } + }) +} + +async function loadCertificates( + savedCertificates: IOfflineData['templates']['certificates'] +) { + return await Promise.all( + savedCertificates.map((cert) => loadSingleCertificate(cert)) + ) +} + function checkIfDone( oldState: IOfflineDataState, loopOrState: IOfflineDataState | Loop @@ -362,7 +404,7 @@ function reducer( systems, templates: { ...state.offlineData.templates, - certificates: (certificates as ICertificateConfigData[]).map((x) => { + certificates: (certificates as ICertificateData[]).map((x) => { const baseUrl = window.location.origin if (x.fonts) { x.fonts = Object.fromEntries( @@ -382,10 +424,30 @@ function reducer( } } + return loop( + { + ...state, + offlineDataLoaded: isOfflineDataLoaded(newOfflineData), + offlineData: newOfflineData + }, + Cmd.run(loadCertificates, { + successActionCreator: actions.certificatesLoaded, + args: [state.offlineData.templates?.certificates] + }) + ) + } + + case actions.CERTIFICATES_LOADED: { + const certificates = action.payload return { ...state, - offlineDataLoaded: isOfflineDataLoaded(newOfflineData), - offlineData: newOfflineData + offlineData: { + ...state.offlineData, + templates: { + ...state.offlineData.templates, + certificates + } + } } } diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index 5cfec26a14c..b9c8d4a36cc 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -94,6 +94,12 @@ export interface ICertificateConfigData { svgUrl: string fonts?: Record } + +export interface ICertificateData extends ICertificateConfigData { + hash?: string + svg: string +} + export interface ICurrency { isoCode: string languagesAndCountry: string[] diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index 771f831e9b1..7544170d34d 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -26,7 +26,6 @@ import { getOfflineData } from '@client/offline/selectors' import { getScope, getUserDetails } from '@client/profile/profileSelectors' import { IStoreState } from '@client/store' import { - getToken, hasRegisterScope, hasRegistrationClerkScope } from '@client/utils/authUtils' @@ -45,13 +44,12 @@ import { formatLongDate } from '@client/utils/date-formatting' import { AdminStructure, IOfflineData } from '@client/offline/reducer' import { getLocationHierarchy } from '@client/utils/locationUtils' import { printPDF } from '@client/pdfRenderer' -import { useEffect, useState } from 'react' import { useParams } from 'react-router' -import { ICertificateConfigData } from '@client/utils/referenceApi' +import { ICertificateData } from '@client/utils/referenceApi' import { fetchImageAsBase64 } from '@client/utils/imageUtils' async function replaceMinioUrlWithBase64(template: Record) { - const regex = /\/[^\/?]+\.(jpg|png|jpeg|svg)(?=\?|$)/ + const regex = /\/[^\/?]+\.(jpg|png|jpeg|svg)(?=\?|$)/i async function recursiveTransform(obj: any) { if (typeof obj !== 'object' || obj === null) { @@ -155,44 +153,22 @@ export const usePrintableCertificate = () => { declaration?.event !== Event.Marriage && (hasRegisterScope(scope) || hasRegistrationClerkScope(scope)) - const [svgCode, setSvgCode] = useState() - const certificateTemplateConfig: ICertificateConfigData | undefined = + const certificateTemplateConfig: ICertificateData | undefined = offlineData.templates.certificates.find( (x) => x.id === declaration?.data.registration.certificates[0].certificateTemplateId ) - const certificateFonts = certificateTemplateConfig?.fonts ?? {} - - const downloadCertificateSvg = async (certificateUrl: string) => { - const response = await fetch(certificateUrl, { - method: 'GET', - headers: { - Authorization: `Bearer ${getToken()}` - } - }) - const svg = await response.text() - return svg - } - - useEffect(() => { - const certificateUrl = - (declaration && certificateTemplateConfig?.svgUrl) || '' + if (!certificateTemplateConfig) return { svgCode: null } - if (certificateUrl && declaration) { - downloadCertificateSvg(certificateUrl).then((certificateTemplate) => { - if (!certificateTemplate) return - const svgWithoutFonts = compileSvg( - certificateTemplate, - { ...declaration.data.template, preview: true }, - state - ) - const svgWithFonts = addFontsToSvg(svgWithoutFonts, certificateFonts) - setSvgCode(svgWithFonts) - }) - } - // eslint-disable-next-line - }, [declaration]) + const certificateFonts = certificateTemplateConfig?.fonts ?? {} + const svgTemplate = certificateTemplateConfig?.svg + const svgWithoutFonts = compileSvg( + svgTemplate, + { ...declaration?.data.template, preview: true }, + state + ) + const svgCode = addFontsToSvg(svgWithoutFonts, certificateFonts) const handleCertify = async () => { if (!declaration || !certificateTemplateConfig) { @@ -224,10 +200,6 @@ export const usePrintableCertificate = () => { } } - const svgTemplate = await downloadCertificateSvg( - certificateTemplateConfig.svgUrl - ) - const base64ReplacedTemplate = await replaceMinioUrlWithBase64( draft.data.template ) From 92710491d9c0edc269c726fab9ada9f935fc4738 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 18 Nov 2024 15:37:00 +0600 Subject: [PATCH 45/58] Update regex in src-sw.ts to handle any minio bucket name containing 'ocrvs' in production caching --- packages/client/src/src-sw.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/src-sw.ts b/packages/client/src/src-sw.ts index 4c435b1a97d..e30e80f4dec 100644 --- a/packages/client/src/src-sw.ts +++ b/packages/client/src/src-sw.ts @@ -82,7 +82,7 @@ registerRoute(/http(.+)config$/, new NetworkFirst()) registerRoute( import.meta.env.DEV ? /http(.+)localhost:3535\/ocrvs\/.+/ - : /http(.+)minio\.(.+)\/ocrvs\/.+/, + : /http(.+)minio\.(.+)\/.*ocrvs.*\/.+/, new CacheFirst() ) From 697b59a172c5c8b2e775a1f86e4b18faeba883bb Mon Sep 17 00:00:00 2001 From: tareq89 Date: Mon, 18 Nov 2024 18:18:59 +0600 Subject: [PATCH 46/58] remove certificate from payload when correction request is made --- .../birth/mutation/registration-mappings.ts | 6 +++++- .../death/mutation/registration-mappings.ts | 6 +++++- .../marriage/mutation/registration-mappings.ts | 6 +++++- packages/client/src/offline/reducer.ts | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts index 0f76f464b65..13caa4efc6e 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts @@ -46,7 +46,11 @@ export function setBirthRegistrationSectionTransformer( const certificates: ICertificate[] = draftData[sectionId] .certificates as ICertificate[] - if (Array.isArray(certificates) && certificates.length) { + if ( + Array.isArray(certificates) && + certificates.length && + !draftData[sectionId].correction + ) { const updatedCertificates = transformCertificateData(certificates.slice(-1)) transformedData[sectionId].certificates = updatedCertificates.length > 0 && diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts index f005c340354..e61a9452f63 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts @@ -53,7 +53,11 @@ export function setDeathRegistrationSectionTransformer( const certificates: ICertificate[] = draftData[sectionId] .certificates as ICertificate[] - if (Array.isArray(certificates) && certificates.length) { + if ( + Array.isArray(certificates) && + certificates.length && + !draftData[sectionId].correction + ) { const updatedCertificates = transformCertificateData( certificates.slice(-1) ) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts index 46e2aed2251..e7c19c931b1 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts @@ -52,7 +52,11 @@ export function setMarriageRegistrationSectionTransformer( const certificates: ICertificate[] = draftData[sectionId] .certificates as ICertificate[] - if (Array.isArray(certificates) && certificates.length) { + if ( + Array.isArray(certificates) && + certificates.length && + !draftData[sectionId].correction + ) { const updatedCertificates = transformCertificateData( certificates.slice(-1) ) diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index 521e6eb1b02..178fb8023bf 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -432,7 +432,7 @@ function reducer( }, Cmd.run(loadCertificates, { successActionCreator: actions.certificatesLoaded, - args: [state.offlineData.templates?.certificates] + args: [newOfflineData.templates?.certificates] }) ) } From 580a0de8c0a66c18c26a31ba23bd035aa27ea8c9 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 29 Nov 2024 21:30:17 +0600 Subject: [PATCH 47/58] validation message added for both collector type and template type in collector form --- .../fieldDefinitions/collectorSection.ts | 23 ++++++-- .../src/i18n/messages/views/certificate.ts | 6 ++ .../collectorForm/CollectorForm.tsx | 58 +++++++------------ 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index 9e665d9eb43..f092d8378ba 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -14,6 +14,7 @@ import { FIELD_WITH_DYNAMIC_DEFINITIONS, identityTypeMapper, IFormField, + IFormFieldValue, IFormSection, IFormSectionGroup, IRadioGroupFormField, @@ -1051,15 +1052,21 @@ function getCertCollectorGroupForEvent( return { id: 'certCollector', title: certificateMessages.whoToCollect, - error: certificateMessages.certificateCollectorError, fields: [ { name: 'certificateTemplateId', type: 'SELECT_WITH_OPTIONS', label: certificateMessages.certificateTemplateSelectLabel, required: true, - initialValue: certTemplateDefaultValue, - validator: [], + validator: [ + (value: IFormFieldValue) => { + return !value + ? { + message: certificateMessages.certificateCollectorTemplateError + } + : undefined + } + ], options: certificateTemplateOptions }, { @@ -1070,7 +1077,15 @@ function getCertCollectorGroupForEvent( hideHeader: true, required: true, initialValue: '', - validator: [], + validator: [ + (value: IFormFieldValue) => { + return !value + ? { + message: certificateMessages.certificateCollectorError + } + : undefined + } + ], options: finalOptions } ] diff --git a/packages/client/src/i18n/messages/views/certificate.ts b/packages/client/src/i18n/messages/views/certificate.ts index fcd61124444..3002b0a496e 100644 --- a/packages/client/src/i18n/messages/views/certificate.ts +++ b/packages/client/src/i18n/messages/views/certificate.ts @@ -74,6 +74,7 @@ interface ICertificateMessages toastMessage: MessageDescriptor otherCollectorFormTitle: MessageDescriptor certificateCollectorError: MessageDescriptor + certificateCollectorTemplateError: MessageDescriptor certificateOtherCollectorInfoError: MessageDescriptor certificateOtherCollectorAffidavitFormTitle: MessageDescriptor certificateOtherCollectorAffidavitError: MessageDescriptor @@ -416,6 +417,11 @@ const messagesToDefine: ICertificateMessages = { description: 'Form level error for collector form', id: 'print.certificate.collector.form.error' }, + certificateCollectorTemplateError: { + defaultMessage: 'Please select certificate type', + description: 'Form level error for collector certificate template', + id: 'print.certificate.collector.form.error.template' + }, certificateOtherCollectorInfoError: { defaultMessage: 'Complete all the mandatory fields', description: 'Form level error for other collector information form', diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index 86ddc8cde52..ddb2a98e206 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -62,12 +62,14 @@ import { isCertificateForPrintInAdvance, filterPrintInAdvancedOption } from '@client/views/PrintCertificate/utils' -import { flatten } from 'lodash' import * as React from 'react' -import { WrappedComponentProps as IntlShapeProps, injectIntl } from 'react-intl' +import { + WrappedComponentProps as IntlShapeProps, + MessageDescriptor, + injectIntl +} from 'react-intl' import { connect } from 'react-redux' import { Redirect, RouteComponentProps } from 'react-router-dom' -import { IValidationResult } from '@client/utils/validate' import { getRegisterForm } from '@client/forms/register/declaration-selectors' import { getCertificateCollectorFormSection } from '@client/forms/certificate/fieldDefinitions/collectorSection' import { replaceInitialValues } from '@client/views/RegisterForm/RegisterForm' @@ -162,26 +164,13 @@ const getErrorsOnFieldsBySection = ( user ) - return { - [sectionId]: fields.reduce((fields, field) => { - const validationErrors: IValidationResult[] = ( - errors[field.name as keyof typeof errors] as IFieldErrors - ).errors - - const value = draft.data[sectionId] - ? draft.data[sectionId][field.name] - : null - - const informationMissing = - validationErrors.length > 0 || value === null ? validationErrors : [] - - return { ...fields, [field.name]: informationMissing } - }, {}) - } + return Object.values(errors) + .map((field) => field.errors[0]?.message) + .filter(Boolean) } interface IState { - showError: boolean + errorMessages: Array showModalForNoSignedAffidavit: boolean isFileUploading: boolean } @@ -190,7 +179,7 @@ class CollectorFormComponent extends React.Component { constructor(props: IProps) { super(props) this.state = { - showError: false, + errorMessages: [], showModalForNoSignedAffidavit: false, isFileUploading: false } @@ -246,10 +235,6 @@ class CollectorFormComponent extends React.Component { this.props.offlineCountryConfiguration, this.props.userDetails ) - const errorValues = Object.values(errors).map(Object.values) - const errLength = flatten(errorValues).filter( - (errs) => errs.length > 0 - ).length const certificates = draft.data.registration.certificates const certificate = (certificates && certificates[0]) || {} @@ -257,9 +242,9 @@ class CollectorFormComponent extends React.Component { sectionId as keyof typeof certificate ] as IFormSectionData - if (errLength > 0) { + if (errors.length > 0) { this.setState({ - showError: true + errorMessages: errors }) return @@ -282,7 +267,7 @@ class CollectorFormComponent extends React.Component { ) ) { this.setState({ - showError: true + errorMessages: [] }) return @@ -295,7 +280,7 @@ class CollectorFormComponent extends React.Component { } this.setState({ - showError: false, + errorMessages: [], showModalForNoSignedAffidavit: false }) if (!nextGroup) { @@ -349,7 +334,7 @@ class CollectorFormComponent extends React.Component { } render() { - const { showError, showModalForNoSignedAffidavit } = this.state + const { errorMessages, showModalForNoSignedAffidavit } = this.state const props = this.props const { declaration } = props @@ -411,12 +396,13 @@ class CollectorFormComponent extends React.Component { ]} > - {showError && ( + {errorMessages.length > 0 && ( - - {(formGroup.error && intl.formatMessage(formGroup.error)) || - ''} - + {errorMessages.map((err, key) => ( + + {intl.formatMessage(err)} + + ))} )} { onChange={(values) => { if (values && values.affidavitFile) { this.setState({ - showError: false + errorMessages: [] }) } this.modifyDeclaration(values, declarationToBeCertified) From 400c6e7ba6726fa5e4a4300100033920ffb6b9ee Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 3 Dec 2024 18:16:11 +0600 Subject: [PATCH 48/58] detailsExist field's conditional check bypassed in draftoToGqlTransformers --- packages/client/src/transformer/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/transformer/index.ts b/packages/client/src/transformer/index.ts index 9a1bb2d62d0..1f0733fa135 100644 --- a/packages/client/src/transformer/index.ts +++ b/packages/client/src/transformer/index.ts @@ -287,7 +287,8 @@ export const draftToGqlTransformer = ( draftData[section.id][fieldDef.name] !== null && draftData[section.id][fieldDef.name] !== undefined && draftData[section.id][fieldDef.name] !== '' && - !conditionalActions.includes('hide') + (!conditionalActions.includes('hide') || + fieldDef.name === 'detailsExist') // https://github.com/opencrvs/opencrvs-core/issues/7821#issuecomment-2514398986 ) { if (fieldDef.mapping && fieldDef.mapping.mutation) { fieldDef.mapping.mutation( From 616944d78a0b89e6ba19f76e13755bbdc13534ba Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 4 Dec 2024 18:33:09 +0600 Subject: [PATCH 49/58] inline validation added for collector form --- .../fieldDefinitions/collectorSection.ts | 25 ++++++++++-- .../collectorForm/CollectorForm.tsx | 38 +++++++------------ 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index f092d8378ba..a5010532813 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -13,9 +13,11 @@ import { CHECKBOX_GROUP, FIELD_WITH_DYNAMIC_DEFINITIONS, identityTypeMapper, + IFormData, IFormField, IFormFieldValue, IFormSection, + IFormSectionData, IFormSectionGroup, IRadioGroupFormField, IRadioOption, @@ -38,6 +40,7 @@ import { EventType } from '@client/utils/gateway' import { IDeclaration } from '@client/declarations' import { issueMessages } from '@client/i18n/messages/issueCertificate' import { ICertificateData } from '@client/utils/referenceApi' +import { IOfflineData } from '@client/offline/reducer' interface INameField { firstNamesField: string @@ -976,7 +979,24 @@ const affidavitCertCollectorGroup: IFormSectionGroup = { label: certificateMessages.noLabel, required: false, initialValue: [], - validator: [], + validator: [ + ( + value: IFormFieldValue, + drafts?: IFormData, + offlineCountryConfig?: IOfflineData, + form?: IFormSectionData + ) => + form && + !( + (form['noAffidavitAgreement'] as Array)?.length || + form['affidavitFile'] + ) + ? { + message: + certificateMessages.certificateOtherCollectorAffidavitError + } + : undefined + ], options: [ { value: 'AFFIDAVIT', @@ -1046,9 +1066,6 @@ function getCertCollectorGroupForEvent( certificates .filter((x) => x.event === declaration.event) .map((x) => ({ label: x.label, value: x.id })) || [] - const certTemplateDefaultValue = certificates.find( - (x) => x.isDefault && x.event === declaration.event - )?.id return { id: 'certCollector', title: certificateMessages.whoToCollect, diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index ddb2a98e206..22cb95a586b 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -79,6 +79,7 @@ import { WORKQUEUE_TABS } from '@client/components/interface/Navigation' import { getUserDetails } from '@client/profile/profileSelectors' import { getRegisteringOfficeId } from '@client/utils/draftUtils' import { UserDetails } from '@client/utils/userUtils' +import { FormikTouched, FormikValues } from 'formik' const ErrorWrapper = styled.div` margin-top: -3px; @@ -170,7 +171,6 @@ const getErrorsOnFieldsBySection = ( } interface IState { - errorMessages: Array showModalForNoSignedAffidavit: boolean isFileUploading: boolean } @@ -179,11 +179,11 @@ class CollectorFormComponent extends React.Component { constructor(props: IProps) { super(props) this.state = { - errorMessages: [], showModalForNoSignedAffidavit: false, isFileUploading: false } } + setAllFormFieldsTouched!: (touched: FormikTouched) => void onUploadingStateChanged = (isUploading: boolean) => { this.setState({ @@ -243,10 +243,14 @@ class CollectorFormComponent extends React.Component { ] as IFormSectionData if (errors.length > 0) { - this.setState({ - errorMessages: errors - }) + const formGroup = ( + this.props as PropsWhenDeclarationIsFound + ).formGroup.fields.reduce( + (acc, { name }) => ({ ...acc, [name]: true }), + {} + ) + this.setAllFormFieldsTouched(formGroup) return } @@ -266,10 +270,6 @@ class CollectorFormComponent extends React.Component { (collector.noAffidavitAgreement as string[]).length > 0 ) ) { - this.setState({ - errorMessages: [] - }) - return } @@ -280,7 +280,6 @@ class CollectorFormComponent extends React.Component { } this.setState({ - errorMessages: [], showModalForNoSignedAffidavit: false }) if (!nextGroup) { @@ -334,7 +333,7 @@ class CollectorFormComponent extends React.Component { } render() { - const { errorMessages, showModalForNoSignedAffidavit } = this.state + const { showModalForNoSignedAffidavit } = this.state const props = this.props const { declaration } = props @@ -396,30 +395,19 @@ class CollectorFormComponent extends React.Component { ]} > - {errorMessages.length > 0 && ( - - {errorMessages.map((err, key) => ( - - {intl.formatMessage(err)} - - ))} - - )} { - if (values && values.affidavitFile) { - this.setState({ - errorMessages: [] - }) - } this.modifyDeclaration(values, declarationToBeCertified) }} setAllFieldsDirty={false} fields={formGroup.fields} draftData={declarationToBeCertified.data} onUploadingStateChanged={this.onUploadingStateChanged} + onSetTouched={(setTouchedFunc) => + (this.setAllFormFieldsTouched = setTouchedFunc) + } /> From f3f0feacf6e38dc03c153d799f1d6cc5b7231be1 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 5 Dec 2024 17:54:51 +0600 Subject: [PATCH 50/58] condition added in getFormFieldValue > nestedFieldValue --- packages/client/src/views/CorrectionForm/utils.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/client/src/views/CorrectionForm/utils.ts b/packages/client/src/views/CorrectionForm/utils.ts index 9ecf98c0061..cdfa82efe87 100644 --- a/packages/client/src/views/CorrectionForm/utils.ts +++ b/packages/client/src/views/CorrectionForm/utils.ts @@ -412,9 +412,13 @@ const getFormFieldValue = ( let tempField: IFormField for (const key in sectionDraftData) { tempField = sectionDraftData[key] as IFormField - return (tempField && + if ( + tempField && tempField.nestedFields && - tempField.nestedFields[field.name]) as IFormFieldValue + field.name in tempField.nestedFields + ) { + return tempField.nestedFields[field.name] as IFormFieldValue + } } return '' } From 8d1b76b0b4b58fd2d819ef01c8c5ffe497ae3061 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Tue, 10 Dec 2024 17:25:56 +0600 Subject: [PATCH 51/58] using svgurl to load svg --- packages/client/src/offline/reducer.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index a021fc87c12..e042f0a9cb5 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -138,15 +138,12 @@ async function saveOfflineData(offlineData: IOfflineData) { async function loadSingleCertificate(certificate: ICertificateData) { const { id } = certificate - const res = await fetch( - new URL(`/certificates/${id}`, window.config.COUNTRY_CONFIG_URL).toString(), - { - headers: { - Authorization: getToken(), - 'If-None-Match': certificate?.hash ?? '' - } + const res = await fetch(certificate.svgUrl, { + headers: { + Authorization: getToken(), + 'If-None-Match': certificate?.hash ?? '' } - ) + }) if (res.status === 304) { return { ...certificate, From 8d103031c80a49e141f625034ff6eb8f62729731 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Wed, 11 Dec 2024 15:52:23 +0600 Subject: [PATCH 52/58] certificate preview keeps loading until svg code is downloaded --- .../src/views/PrintCertificate/usePrintableCertificate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index 5fa9b313665..0161772560f 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -160,6 +160,9 @@ export const usePrintableCertificate = (registrationId: string) => { const certificateFonts = certificateTemplateConfig?.fonts ?? {} const svgTemplate = certificateTemplateConfig?.svg + + if (!svgTemplate) return { svgCode: null } + const svgWithoutFonts = compileSvg( svgTemplate, { ...declaration?.data.template, preview: true }, @@ -201,7 +204,7 @@ export const usePrintableCertificate = (registrationId: string) => { draft.data.template ) - const svg = await compileSvg( + const svg = compileSvg( svgTemplate, { ...base64ReplacedTemplate, preview: false }, state From 5e36fa02a5ab2cde466a96520de57d52c06271d0 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 13 Dec 2024 16:45:54 +0600 Subject: [PATCH 53/58] informant type update from any to mother, father, spouse fixed --- packages/commons/src/fhir/transformers/utils.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/commons/src/fhir/transformers/utils.ts b/packages/commons/src/fhir/transformers/utils.ts index 15e62866d4b..8c23e6bc918 100644 --- a/packages/commons/src/fhir/transformers/utils.ts +++ b/packages/commons/src/fhir/transformers/utils.ts @@ -60,7 +60,7 @@ import { isObservation, isURLReference, urlReferenceToResourceIdentifier, - BundleEntryWithFullUrl, + // BundleEntryWithFullUrl, findEntryFromBundle } from '..' @@ -743,10 +743,14 @@ export function setInformantReference( throw new Error(`${sectionCode} not found in composition!`) } const personSectionEntry = section.entry[0] - const personEntry = fhirBundle.entry.find( - (entry): entry is BundleEntryWithFullUrl => - entry.fullUrl === personSectionEntry.reference + const personEntry = findEntryFromBundle( + fhirBundle, + personSectionEntry.reference ) + // const personEntry = fhirBundle.entry.find( + // (entry): entry is BundleEntryWithFullUrl => + // entry.fullUrl === personSectionEntry.reference + // ) if (!personEntry) { return } From dfab7ed53ca2e58247b0050feff1be23047f2e96 Mon Sep 17 00:00:00 2001 From: tareq89 Date: Fri, 20 Dec 2024 15:13:59 +0600 Subject: [PATCH 54/58] Merge branch 'develop' of github.com:opencrvs/opencrvs-core into ocrvs-7584 --- .../auth/src/features/authenticate/service.ts | 2 + packages/client/package.json | 5 +- packages/client/src/App.test.tsx | 26 +- packages/client/src/App.tsx | 841 ++++++--------- .../src/components/DateRangePicker.test.tsx | 13 +- .../src/components/GenericErrorToast.test.tsx | 7 +- .../client/src/components/Header/Header.tsx | 143 ++- .../components/Header/HistoryNavigator.tsx | 30 +- .../src/components/LocationPicker.test.tsx | 16 +- .../client/src/components/Notification.tsx | 28 +- packages/client/src/components/Page.tsx | 19 +- .../src/components/ProfileMenu.test.tsx | 26 +- .../client/src/components/ProfileMenu.tsx | 12 +- .../client/src/components/ProtectedPage.tsx | 37 +- .../client/src/components/ProtectedRoute.tsx | 30 +- .../client/src/components/ScrollToTop.tsx | 8 +- .../client/src/components/WithRouterProps.tsx | 57 + .../src/components/form/FetchButton.tsx | 4 +- .../form/FormFieldGenerator.test.tsx | 61 +- .../client/src/components/form/Redirect.tsx | 4 +- .../interface/DownloadButton.test.tsx | 19 +- .../components/interface/Navigation.test.tsx | 92 +- .../src/components/interface/Navigation.tsx | 246 +++-- .../review/RejectRegistrationForm.test.tsx | 7 +- .../review/RejectRegistrationForm.tsx | 51 +- packages/client/src/declarations/index.ts | 29 +- packages/client/src/declarations/selectors.ts | 15 +- .../forms/user/fieldDefinitions/createUser.ts | 1 + packages/client/src/index.tsx | 20 +- packages/client/src/navigation/index.ts | 931 +++++----------- packages/client/src/navigation/routes.ts | 1 - packages/client/src/profile/profileActions.ts | 2 - packages/client/src/setupTests.ts | 8 - packages/client/src/store.ts | 19 +- packages/client/src/tests/util.tsx | 193 ++-- .../src/transformer/transformer.test.tsx | 32 +- packages/client/src/user/userReducer.ts | 30 +- packages/client/src/utils.ts | 2 +- packages/client/src/utils/userUtils.ts | 7 + packages/client/src/v2-events/.eslintrc.js | 73 ++ .../src/v2-events/components/IconWithName.tsx | 98 ++ .../v2-events/components/LoadingIndicator.tsx | 125 +++ .../components/forms/FormFieldGenerator.tsx | 635 ++--------- .../src/v2-events/components/forms/utils.ts | 245 +---- .../v2-events/components/forms/validation.ts | 155 ++- .../src/v2-events/features/debug/debug.tsx | 37 +- .../features/events/EventSelection.tsx | 54 +- .../events/actions/declare/Declare.tsx | 184 +--- .../events/actions/declare/Review.tsx | 582 ++-------- .../events/actions/register/Register.tsx | 137 +++ .../features/events/components/FormHeader.tsx | 119 +++ .../features/events/components/Preview.tsx | 457 ++++++++ .../src/v2-events/features/events/fixtures.ts | 113 ++ .../events/registered-fields/DateField.tsx | 14 +- .../events/registered-fields/Paragraph.tsx | 4 +- .../events/registered-fields/RadioGroup.tsx | 6 +- .../events/registered-fields/TextField.tsx | 8 +- .../features/events/useEventConfiguration.ts | 2 +- .../features/events/useEventFormData.ts | 47 +- .../events/useEventFormNavigation.tsx | 40 +- .../events/useEvents/useEvents.test.tsx | 36 +- .../features/events/useEvents/useEvents.ts | 279 ++++- .../EventOverview/EventOverview.tsx | 95 ++ .../EventOverview/components/ActionMenu.tsx | 79 ++ .../EventOverview/components/EventHistory.tsx | 149 +++ .../EventOverview/components/EventSummary.tsx | 48 + .../features/workqueues/Workqueue.tsx | 297 ++++++ .../workqueues/components/ContentWrapper.tsx | 172 +++ .../v2-events/features/workqueues/index.tsx | 83 +- .../v2-events/features/workqueues/utils.tsx | 56 + .../client/src/v2-events/hooks/useModal.ts | 4 +- .../src/v2-events/hooks/usePagination.ts | 9 +- packages/client/src/v2-events/index.ts | 5 +- .../src/v2-events/messages/constants.ts | 990 ++++++++++++++++++ .../client/src/v2-events/messages/errors.ts | 26 + .../client/src/v2-events/messages/index.ts | 16 + .../src/v2-events/messages/navigation.ts | 40 + .../src/v2-events/messages/registrarHome.ts | 26 + .../src/v2-events/messages/workqueue.ts | 39 + .../client/src/v2-events/routes/config.tsx | 88 ++ packages/client/src/v2-events/routes/index.ts | 9 +- .../client/src/v2-events/routes/routes.ts | 62 ++ packages/client/src/v2-events/trpc.tsx | 29 +- .../AdvancedSearchResult.test.tsx | 75 +- .../AdvancedSearch/AdvancedSearchResult.tsx | 112 +- .../views/CorrectionForm/CorrectionForm.tsx | 29 +- .../CorrectionReasonForm.test.tsx | 77 +- .../CorrectionForm/CorrectionReasonForm.tsx | 30 +- .../CorrectionForm/CorrectionSummary.test.tsx | 80 +- .../CorrectionForm/CorrectionSummary.tsx | 124 ++- .../CorrectionForm/CorrectorForm.test.tsx | 167 ++- .../views/CorrectionForm/CorrectorForm.tsx | 56 +- .../views/CorrectionForm/ReviewForm.test.tsx | 29 +- .../src/views/CorrectionForm/ReviewForm.tsx | 27 +- .../SupportingDocumentsForm.test.tsx | 57 +- .../SupportingDocumentsForm.tsx | 30 +- .../CorrectionForm/VerifyCorrector.test.tsx | 219 ++-- .../views/CorrectionForm/VerifyCorrector.tsx | 73 +- .../client/src/views/CorrectionForm/utils.ts | 8 +- .../Duplicates/DuplicateWarning.test.tsx | 6 +- .../IssueCertificate/IssueCertificate.tsx | 26 +- .../IssueCollectorForm.test.tsx | 146 ++- .../IssueCollectorForm/IssueCollectorForm.tsx | 28 +- .../IssueCollectorForm/IssueFormForOthers.tsx | 19 +- .../IssueCollectorForm/IssuePayment.test.tsx | 89 +- .../IssueCollectorForm/IssuePayment.tsx | 29 +- packages/client/src/views/OfficeHome/Home.tsx | 8 +- .../OfficeHome/LoadingIndicator.test.tsx | 6 +- .../src/views/OfficeHome/OfficeHome.test.tsx | 556 ++++------ .../src/views/OfficeHome/OfficeHome.tsx | 78 +- .../InExternalValidationTab.tsx | 41 +- .../inExternalValidationTab.test.tsx | 18 +- .../OfficeHome/inProgress/InProgress.tsx | 94 +- .../OfficeHome/inProgress/inProgress.test.tsx | 70 +- .../views/OfficeHome/outbox/Outbox.test.tsx | 10 +- .../readyForReview/ReadyForReview.tsx | 56 +- .../readyForReview/readyForReview.test.tsx | 121 +-- .../OfficeHome/readyToIssue/ReadyToIssue.tsx | 25 +- .../OfficeHome/readyToPrint/ReadyToPrint.tsx | 40 +- .../readyToPrint/readyToPrint.test.tsx | 77 +- .../requiresUpdate/RequiresUpdate.tsx | 50 +- .../requiresUpdate/requiresUpdate.test.tsx | 76 +- .../sentForReview/SentForReview.test.tsx | 35 +- .../sentForReview/SentForReview.tsx | 32 +- .../Organisation/AdministrativeLevels.tsx | 53 +- .../client/src/views/PIN/CreatePin.test.tsx | 6 +- .../src/views/Performance/Dashboard.test.tsx | 22 +- .../src/views/Performance/Dashboard.tsx | 18 +- .../views/Performance/FieldAgentList.test.tsx | 26 +- .../src/views/Performance/FieldAgentList.tsx | 79 +- .../Performance/RegistrationsList.test.tsx | 12 +- .../views/Performance/RegistrationsList.tsx | 155 +-- .../views/PrintCertificate/Payment.test.tsx | 199 ++-- .../src/views/PrintCertificate/Payment.tsx | 73 +- .../ReviewCertificateAction.test.tsx | 440 ++++---- .../ReviewCertificateAction.tsx | 34 +- .../PrintCertificate/VerifyCollector.test.tsx | 371 +++---- .../PrintCertificate/VerifyCollector.tsx | 108 +- .../collectorForm/CollectorForm.test.tsx | 287 +++-- .../collectorForm/CollectorForm.tsx | 134 ++- .../usePrintableCertificate.ts | 29 +- .../client/src/views/PrintRecord/Body.tsx | 9 +- .../src/views/PrintRecord/PrintRecord.tsx | 14 +- .../src/views/RecordAudit/ActionButtons.tsx | 78 +- .../views/RecordAudit/ActionDetailsModal.tsx | 4 +- .../src/views/RecordAudit/ActionMenu.test.tsx | 711 +++++++------ .../src/views/RecordAudit/ActionMenu.tsx | 139 ++- .../client/src/views/RecordAudit/History.tsx | 24 +- .../views/RecordAudit/RecordAudit.test.tsx | 159 ++- .../src/views/RecordAudit/RecordAudit.tsx | 104 +- .../RegisterForm/DeclarationForm.test.tsx | 148 ++- .../views/RegisterForm/DeclarationForm.tsx | 28 +- .../views/RegisterForm/PreviewForm.test.tsx | 48 +- .../RegisterForm/RegisterForm.init.test.tsx | 26 +- .../views/RegisterForm/RegisterForm.test.tsx | 422 ++++---- .../src/views/RegisterForm/RegisterForm.tsx | 234 +++-- .../views/RegisterForm/ReviewForm.test.tsx | 514 ++++----- .../src/views/RegisterForm/ReviewForm.tsx | 22 +- .../duplicate/DuplicateForm.test.tsx | 8 +- .../RegisterForm/duplicate/DuplicateForm.tsx | 9 +- .../duplicate/DuplicateFormTabs.test.tsx | 58 +- .../review/EditConfirmation.test.tsx | 6 +- .../review/ReviewSection.test.tsx | 56 +- .../RegisterForm/review/ReviewSection.tsx | 88 +- .../review/ReviewSectionCorrection.tsx | 46 +- .../ReviewCorrection/ReviewCorrection.tsx | 25 +- .../SearchResult/AdvancedSearch.test.tsx | 44 +- .../src/views/SearchResult/AdvancedSearch.tsx | 10 +- .../views/SearchResult/SearchResult.test.tsx | 131 ++- .../src/views/SearchResult/SearchResult.tsx | 117 +-- .../SelectVitalEvent.test.tsx | 27 +- .../SelectVitalEvent/SelectVitalEvent.tsx | 77 +- .../ChangeEmailModal.test.tsx | 12 +- .../ChangePhoneModal.test.tsx | 13 +- .../views/Settings/PasswordChangeModal.tsx | 2 +- .../src/views/Settings/Settings.test.tsx | 6 +- .../SysAdmin/Config/Systems/Systems.test.tsx | 89 +- .../SysAdmin/Config/Systems/useSystems.ts | 1 - .../Performance/ApplicationSourcesReport.tsx | 35 +- .../Performance/CompletenessRates.test.tsx | 110 +- .../Performance/CompletenessRates.tsx | 84 +- .../Performance/PerformanceHome.test.tsx | 32 +- .../SysAdmin/Performance/PerformanceHome.tsx | 203 ++-- .../Performance/RegistrationsReport.tsx | 14 +- .../Performance/WorkflowStatus.test.tsx | 102 +- .../SysAdmin/Performance/WorkflowStatus.tsx | 94 +- .../CompletenessDataTable.test.tsx | 18 +- .../StatusWiseDeclarationCountView.test.tsx | 30 +- .../SysAdmin/SysAdminContentWrapper.test.tsx | 8 +- .../views/SysAdmin/SysAdminContentWrapper.tsx | 2 +- .../views/SysAdmin/Team/TeamSearch.test.tsx | 70 +- .../src/views/SysAdmin/Team/TeamSearch.tsx | 36 +- .../Team/user/UserAuditActionModal.test.tsx | 22 +- .../SysAdmin/Team/user/UserList.test.tsx | 130 ++- .../src/views/SysAdmin/Team/user/UserList.tsx | 135 ++- .../user/userCreation/CreateNewUser.test.tsx | 165 ++- .../Team/user/userCreation/CreateNewUser.tsx | 56 +- .../user/userCreation/SignatureForm.test.tsx | 58 +- .../Team/user/userCreation/UserForm.test.tsx | 6 +- .../Team/user/userCreation/UserForm.tsx | 91 +- .../Team/user/userCreation/UserReviewForm.tsx | 123 ++- .../src/views/SysAdmin/Vsexports/VSExport.tsx | 2 +- .../src/views/Unlock/ForgotPIN.test.tsx | 9 +- .../client/src/views/Unlock/Unlock.test.tsx | 31 +- .../src/views/UserAudit/UserAudit.test.tsx | 81 +- .../client/src/views/UserAudit/UserAudit.tsx | 42 +- .../views/UserAudit/UserAuditHistory.test.tsx | 68 +- .../src/views/UserAudit/UserAuditHistory.tsx | 27 +- .../views/UserSetup/CreatePassword.test.tsx | 6 +- .../UserSetup/SecurityQuestionView.test.tsx | 6 +- .../UserSetup/SetupConfirmationPage.test.tsx | 6 +- .../views/UserSetup/SetupReviewPage.test.tsx | 26 +- .../views/UserSetup/UserSetupPage.test.tsx | 18 +- .../VerifyCertificatePage.tsx | 12 +- .../src/views/ViewRecord/ViewRecord.test.tsx | 38 +- .../src/views/ViewRecord/ViewRecord.tsx | 35 +- packages/client/src/workqueue/reducer.ts | 2 +- packages/commons/package.json | 2 + packages/commons/src/client.ts | 2 + .../commons/src/conditionals/conditionals.ts | 36 + packages/commons/src/conditionals/validate.ts | 25 + packages/commons/src/events/ActionConfig.ts | 24 +- packages/commons/src/events/ActionDocument.ts | 25 +- packages/commons/src/events/ActionInput.ts | 26 +- packages/commons/src/events/EventConfig.ts | 15 +- packages/commons/src/events/EventDocument.ts | 4 +- packages/commons/src/events/EventIndex.ts | 23 +- packages/commons/src/events/EventMetadata.ts | 106 ++ packages/commons/src/events/FieldConfig.ts | 164 ++- packages/commons/src/events/FormConfig.ts | 8 +- packages/commons/src/events/SummaryConfig.ts | 29 + .../commons/src/events/WorkqueueConfig.ts | 47 + packages/commons/src/events/defineConfig.ts | 37 + packages/commons/src/events/index.ts | 6 + packages/commons/src/events/state/index.ts | 80 ++ packages/commons/src/events/utils.ts | 100 ++ .../fixtures/tennis-club-membership-event.ts | 55 +- packages/commons/tsconfig.esm.json | 2 +- packages/commons/tsconfig.json | 2 +- .../components/src/Workqueue/Workqueue.tsx | 2 +- packages/events/package.json | 1 + packages/events/src/index.test.ts | 88 -- .../__snapshots__/event.create.test.ts.snap | 3 + .../__snapshots__/event.get.test.ts.snap | 3 + .../__snapshots__/event.patch.test.ts.snap | 3 + .../events/src/router/event.actions.test.ts | 51 + .../events/src/router/event.create.test.ts | 46 + packages/events/src/router/event.get.test.ts | 29 + .../events/src/router/event.patch.test.ts | 41 + packages/events/src/router/events.get.test.ts | 62 ++ .../src/lib.ts => events/src/router/index.ts} | 4 +- packages/events/src/{ => router}/router.ts | 92 +- packages/events/src/service/events.ts | 24 +- .../src/service/indexing/indexing.test.ts | 36 +- .../events/src/service/indexing/indexing.ts | 91 +- .../src/storage/__mocks__/elasticsearch.ts | 1 - packages/events/src/tests/generators.ts | 57 + packages/events/src/tests/global-setup.ts | 1 + packages/events/src/tests/setup.ts | 6 +- .../events => events/src/tests}/utils.ts | 21 +- packages/login/package.json | 6 +- packages/login/src/App.tsx | 138 ++- packages/login/src/common/WithRouterProps.tsx | 57 + .../src/i18n/components/LanguageSelect.tsx | 17 +- packages/login/src/index.tsx | 9 +- packages/login/src/login/actions.ts | 68 +- packages/login/src/login/index.test.ts | 19 +- packages/login/src/login/reducer.ts | 30 +- packages/login/src/store.ts | 21 +- packages/login/src/tests/util.tsx | 57 +- .../AuthDetailsVerificationForm.test.tsx | 80 +- .../AuthDetailsVerificationForm.tsx | 69 +- .../ForgottenItemForm.test.tsx | 21 +- .../ForgottenItemForm.tsx | 29 +- .../RecoveryCodeEntryForm.tsx | 57 +- .../ResetCredentialsSuccessPage.test.tsx | 55 +- .../ResetCredentialsSuccessPage.tsx | 33 +- .../SecurityQuestionForm.tsx | 65 +- .../UpdatePasswordForm.test.tsx | 17 +- .../UpdatePasswordForm.tsx | 43 +- .../src/views/StepOne/StepOneContainer.tsx | 10 +- .../views/StepOne/stepOneContainer.test.tsx | 7 +- packages/login/vite.config.ts | 4 +- packages/toolkit/README.md | 1 + packages/toolkit/package.json | 15 +- packages/toolkit/src/conditionals/index.ts | 110 ++ packages/toolkit/tsconfig.json | 3 + yarn.lock | 156 +-- 288 files changed, 12663 insertions(+), 9377 deletions(-) create mode 100644 packages/client/src/components/WithRouterProps.tsx create mode 100644 packages/client/src/v2-events/components/IconWithName.tsx create mode 100644 packages/client/src/v2-events/components/LoadingIndicator.tsx create mode 100644 packages/client/src/v2-events/features/events/actions/register/Register.tsx create mode 100644 packages/client/src/v2-events/features/events/components/FormHeader.tsx create mode 100644 packages/client/src/v2-events/features/events/components/Preview.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/EventOverview/EventOverview.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/EventOverview/components/ActionMenu.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/EventOverview/components/EventHistory.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/EventOverview/components/EventSummary.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/Workqueue.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/components/ContentWrapper.tsx create mode 100644 packages/client/src/v2-events/features/workqueues/utils.tsx create mode 100644 packages/client/src/v2-events/messages/constants.ts create mode 100644 packages/client/src/v2-events/messages/errors.ts create mode 100644 packages/client/src/v2-events/messages/index.ts create mode 100644 packages/client/src/v2-events/messages/navigation.ts create mode 100644 packages/client/src/v2-events/messages/registrarHome.ts create mode 100644 packages/client/src/v2-events/messages/workqueue.ts create mode 100644 packages/client/src/v2-events/routes/config.tsx create mode 100644 packages/client/src/v2-events/routes/routes.ts create mode 100644 packages/commons/src/conditionals/conditionals.ts create mode 100644 packages/commons/src/conditionals/validate.ts create mode 100644 packages/commons/src/events/EventMetadata.ts create mode 100644 packages/commons/src/events/SummaryConfig.ts create mode 100644 packages/commons/src/events/WorkqueueConfig.ts create mode 100644 packages/commons/src/events/defineConfig.ts create mode 100644 packages/commons/src/events/state/index.ts create mode 100644 packages/commons/src/events/utils.ts delete mode 100644 packages/events/src/index.test.ts create mode 100644 packages/events/src/router/__snapshots__/event.create.test.ts.snap create mode 100644 packages/events/src/router/__snapshots__/event.get.test.ts.snap create mode 100644 packages/events/src/router/__snapshots__/event.patch.test.ts.snap create mode 100644 packages/events/src/router/event.actions.test.ts create mode 100644 packages/events/src/router/event.create.test.ts create mode 100644 packages/events/src/router/event.get.test.ts create mode 100644 packages/events/src/router/event.patch.test.ts create mode 100644 packages/events/src/router/events.get.test.ts rename packages/{toolkit/src/lib.ts => events/src/router/index.ts} (87%) rename packages/events/src/{ => router}/router.ts (52%) create mode 100644 packages/events/src/tests/generators.ts rename packages/{client/src/v2-events/features/events => events/src/tests}/utils.ts (56%) create mode 100644 packages/login/src/common/WithRouterProps.tsx create mode 100644 packages/toolkit/src/conditionals/index.ts diff --git a/packages/auth/src/features/authenticate/service.ts b/packages/auth/src/features/authenticate/service.ts index 51ed1062040..8b170a6d563 100644 --- a/packages/auth/src/features/authenticate/service.ts +++ b/packages/auth/src/features/authenticate/service.ts @@ -37,6 +37,7 @@ const sign = promisify< jwt.SignOptions, string >(jwt.sign) + export interface IUserName { use: string family: string @@ -213,6 +214,7 @@ export async function generateAndSendVerificationCode( } else { verificationCode = await generateVerificationCode(nonce) } + if (!env.isProd || env.QA_ENV) { logger.info( `Sending a verification to, diff --git a/packages/client/package.json b/packages/client/package.json index 5788232c5c3..982d4ea40b5 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -52,7 +52,6 @@ "@types/nock": "^10.0.3", "@types/pdfmake": "^0.2.0", "@types/react-redux": "^7.1.5", - "@types/react-router-dom": "^5.1.2", "@types/react-tooltip": "^4.2.4", "@types/redux-sentry-middleware": "^0.2.0", "@types/styled-components": "^5.1.3", @@ -62,7 +61,6 @@ "bcryptjs": "^2.4.3", "bowser": "^2.11.0", "browser-image-compression": "^1.0.6", - "connected-react-router": "^6.5.2", "core-js": "^3.2.1", "country-data": "^0.0.31", "date-fns": "^2.28.0", @@ -94,7 +92,8 @@ "react-intl": "5.25.1", "react-page-visibility": "^3.2.1", "react-redux": "^7.1.1", - "react-router-dom": "^5.1.2", + "react-router-dom": "^6.28.0", + "react-router-typesafe-routes": "1.2.2", "react-stickynode": "^2.0.1", "react-tooltip": "^4.2.21", "react-transition-group": "^4.2.1", diff --git a/packages/client/src/App.test.tsx b/packages/client/src/App.test.tsx index affae8a93b9..5d4aead51f2 100644 --- a/packages/client/src/App.test.tsx +++ b/packages/client/src/App.test.tsx @@ -59,10 +59,11 @@ describe('when user has a valid token in url but an expired one in localStorage' beforeEach(async () => { getItem.mockReturnValue(expiredToken) window.history.replaceState('', '', '?token=' + validToken) - await createTestApp() }) it("doesn't redirect user to SSO", async () => { + await createTestApp() + expect(assign.mock.calls).toHaveLength(0) }) @@ -72,7 +73,10 @@ describe('when user has a valid token in url but an expired one in localStorage' window.history.replaceState({}, '', '?token=' + token) - await createTestApp() + await createTestApp({ waitUntilOfflineCountryConfigLoaded: false }, [ + '/?token=' + token + ]) + expect(assign.mock.calls).toHaveLength(0) }) }) @@ -130,36 +134,36 @@ describe('when user has a valid token in local storage', () => { describe('it handles react errors', () => { it('displays react error page', async () => { - const { store, history } = createStore() + const { store } = createStore() function Problem(): JSX.Element { throw new Error('Error thrown.') } - const testComponent = await createTestComponent( + const { component } = await createTestComponent( , - { store, history } + { store } ) - expect(testComponent.find('#GoToHomepage').hostNodes()).toHaveLength(1) + expect(component.find('#GoToHomepage').hostNodes()).toHaveLength(1) }) }) describe('it handles react unauthorized errors', () => { it('displays react error page', async () => { - const { store, history } = createStore() + const { store } = createStore() function Problem(): JSX.Element { throw new Error('401') } - const testComponent = await createTestComponent( + const { component } = await createTestComponent( , - { store, history } + { store } ) - expect(testComponent.find('#GoToHomepage').hostNodes()).toHaveLength(1) + expect(component.find('#GoToHomepage').hostNodes()).toHaveLength(1) - testComponent.find('#GoToHomepage').hostNodes().simulate('click') + component.find('#GoToHomepage').hostNodes().simulate('click') }) }) diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 8433b7519f9..11a29a0afc9 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -9,17 +9,24 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ import { ErrorBoundary } from '@client/components/ErrorBoundary' +import { StyledErrorBoundary } from '@client/components/StyledErrorBoundary' +import { I18nContainer } from '@client/i18n/components/I18nContainer' +import { useApolloClient } from '@client/utils/apolloClient' +import { ApolloProvider } from '@client/utils/ApolloProvider' +import { getTheme } from '@opencrvs/components/lib/theme' +import { Provider } from 'react-redux' +import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom' +import styled, { createGlobalStyle, ThemeProvider } from 'styled-components' + +import * as React from 'react' + import { NotificationComponent } from '@client/components/Notification' import { Page } from '@client/components/Page' import { ProtectedPage } from '@client/components/ProtectedPage' import { ProtectedRoute } from '@client/components/ProtectedRoute' import ScrollToTop from '@client/components/ScrollToTop' import { SessionExpireConfirmation } from '@client/components/SessionExpireConfirmation' -import { StyledErrorBoundary } from '@client/components/StyledErrorBoundary' -import { I18nContainer } from '@client/i18n/components/I18nContainer' import * as routes from '@client/navigation/routes' -import styled, { createGlobalStyle, ThemeProvider } from 'styled-components' -import { useApolloClient } from '@client/utils/apolloClient' import { OfficeHome } from '@client/views/OfficeHome/OfficeHome' import { FieldAgentList } from '@client/views/Performance/FieldAgentList' import { CollectorForm } from '@client/views/PrintCertificate/collectorForm/CollectorForm' @@ -30,68 +37,40 @@ import { ReviewForm } from '@client/views/RegisterForm/ReviewForm' import { SearchResult } from '@client/views/SearchResult/SearchResult' import { SelectVitalEvent } from '@client/views/SelectVitalEvent/SelectVitalEvent' import { SettingsPage } from '@client/views/Settings/SettingsPage' -import { PerformanceHome } from '@client/views/SysAdmin/Performance/PerformanceHome' import { CompletenessRates } from '@client/views/SysAdmin/Performance/CompletenessRates' +import { PerformanceHome } from '@client/views/SysAdmin/Performance/PerformanceHome' import { WorkflowStatus } from '@client/views/SysAdmin/Performance/WorkflowStatus' -import { TeamSearch } from '@client/views/SysAdmin/Team/TeamSearch' import { CreateNewUser } from '@client/views/SysAdmin/Team/user/userCreation/CreateNewUser' -import { getTheme } from '@opencrvs/components/lib/theme' + +import { SystemRoleType } from '@client/utils/gateway' +import { AdvancedSearchResult } from '@client/views/AdvancedSearch/AdvancedSearchResult' +import { IssueCertificate } from '@client/views/IssueCertificate/IssueCertificate' +import { IssuePayment } from '@client/views/IssueCertificate/IssueCollectorForm/IssuePayment' +import { Home } from '@client/views/OfficeHome/Home' +import { AdministrativeLevels } from '@client/views/Organisation/AdministrativeLevels' +import { PerformanceDashboard } from '@client/views/Performance/Dashboard' +import { Leaderboards } from '@client/views/Performance/Leaderboards' +import { RegistrationList } from '@client/views/Performance/RegistrationsList' +import { PerformanceStatistics } from '@client/views/Performance/Statistics' +import { VerifyCertificatePage } from '@client/views/VerifyCertificate/VerifyCertificatePage' +import { ViewRecord } from '@client/views/ViewRecord/ViewRecord' + import { ApolloClient, NormalizedCacheObject } from '@apollo/client' -import { ConnectedRouter } from 'connected-react-router' -import { History, Location } from 'history' -import * as React from 'react' -import { Provider } from 'react-redux' -import { Route, Switch } from 'react-router-dom' import { AppStore } from './store' import { CorrectionForm, CorrectionReviewForm } from './views/CorrectionForm' import { VerifyCorrector } from './views/CorrectionForm/VerifyCorrector' +import { ReloadModal } from './views/Modals/ReloadModal' +import { ReviewCertificate } from './views/PrintCertificate/ReviewCertificateAction' +import { PrintRecord } from './views/PrintRecord/PrintRecord' import { RecordAudit } from './views/RecordAudit/RecordAudit' -import { UserList } from './views/SysAdmin/Team/user/UserList' +import { ReviewCorrection } from './views/ReviewCorrection/ReviewCorrection' +import { AdvancedSearchConfig } from './views/SearchResult/AdvancedSearch' +import AllUserEmail from './views/SysAdmin/Communications/AllUserEmail/AllUserEmail' import { SystemList } from './views/SysAdmin/Config/Systems/Systems' +import { UserList } from './views/SysAdmin/Team/user/UserList' import VSExport from './views/SysAdmin/Vsexports/VSExport' -import { AdvancedSearchConfig } from './views/SearchResult/AdvancedSearch' -import { ViewRecord } from '@client/views/ViewRecord/ViewRecord' import { UserAudit } from './views/UserAudit/UserAudit' -import { AdvancedSearchResult } from '@client/views/AdvancedSearch/AdvancedSearchResult' -import { RegistrationList } from '@client/views/Performance/RegistrationsList' -import { PerformanceStatistics } from '@client/views/Performance/Statistics' -import { Leaderboards } from '@client/views/Performance/Leaderboards' -import { PerformanceDashboard } from '@client/views/Performance/Dashboard' -import { SystemRoleType } from '@client/utils/gateway' -import { AdministrativeLevels } from '@client/views/Organisation/AdministrativeLevels' -import { VerifyCertificatePage } from '@client/views/VerifyCertificate/VerifyCertificatePage' -import { IssueCertificate } from '@client/views/IssueCertificate/IssueCertificate' -import { IssuePayment } from '@client/views/IssueCertificate/IssueCollectorForm/IssuePayment' -import { ApolloProvider } from '@client/utils/ApolloProvider' -import { Home } from '@client/views/OfficeHome/Home' -import { PrintRecord } from './views/PrintRecord/PrintRecord' -import { ReviewCorrection } from './views/ReviewCorrection/ReviewCorrection' -import { ReviewCertificate } from './views/PrintCertificate/ReviewCertificateAction' -import AllUserEmail from './views/SysAdmin/Communications/AllUserEmail/AllUserEmail' -import { ReloadModal } from './views/Modals/ReloadModal' -import { - V2_DECLARE_ACTION_REVIEW_ROUTE, - V2_DECLARE_ACTION_ROUTE, - V2_DECLARE_ACTION_ROUTE_WITH_PAGE, - V2_EVENTS_ROUTE, - V2_ROOT_ROUTE -} from './v2-events/routes' -import { Workqueues } from './v2-events/features/workqueues' -import { DeclareIndex } from './v2-events/features/events/actions/declare/Declare' -import { TRPCProvider } from './v2-events/trpc' -import { EventSelection } from './v2-events/features/events/EventSelection' -import { ReviewSection } from './v2-events/features/events/actions/declare/Review' - -interface IAppProps { - client?: ApolloClient - store: AppStore - history: History -} - -const MainSection = styled.section` - flex-grow: 8; - background: ${({ theme }) => theme.colors.background}; -` +import { routesConfig as v2RoutesConfig } from './v2-events/routes/config' // Injecting global styles for the body tag - used only once // eslint-disable-line @@ -103,481 +82,297 @@ const GlobalStyle = createGlobalStyle` } ` -export function App(props: IAppProps) { - const { client } = useApolloClient(props.store) +const MainSection = styled.section` + flex-grow: 8; + background: ${({ theme }) => theme.colors.background}; +` + +export const routesConfig = [ + { + path: '/', + element: ( + + + + + + + + + + + + + + ), + children: [ + { path: routes.HOME, element: }, + { path: routes.SELECT_VITAL_EVENT, element: }, + { path: routes.SELECT_DEATH_INFORMANT, element: }, + { path: routes.SELECT_MARRIAGE_INFORMANT, element: }, + { path: routes.DRAFT_BIRTH_PARENT_FORM, element: }, + { + path: routes.VIEW_VERIFY_CERTIFICATE, + element: + }, + { + path: routes.DRAFT_BIRTH_PARENT_FORM_PAGE, + element: + }, + { + path: routes.DRAFT_BIRTH_PARENT_FORM_PAGE_GROUP, + element: + }, + { path: routes.DRAFT_DEATH_FORM, element: }, + { path: routes.DRAFT_DEATH_FORM_PAGE, element: }, + { path: routes.DRAFT_MARRIAGE_FORM, element: }, + { + path: routes.DRAFT_DEATH_FORM_PAGE_GROUP, + element: + }, + { path: routes.DRAFT_MARRIAGE_FORM_PAGE, element: }, + { + path: routes.DRAFT_MARRIAGE_FORM_PAGE_GROUP, + element: + }, + { path: routes.REVIEW_EVENT_PARENT_FORM_PAGE, element: }, + { + path: routes.REVIEW_EVENT_PARENT_FORM_PAGE_GROUP, + element: + }, + { path: routes.REVIEW_CORRECTION, element: }, + { path: routes.REGISTRAR_HOME, element: }, + { path: routes.REGISTRAR_HOME_TAB, element: }, + { path: routes.REGISTRAR_HOME_TAB_PAGE, element: }, + { + path: routes.ALL_USER_EMAIL, + element: ( + + + + ) + }, + { + path: routes.ADVANCED_SEARCH, + element: ( + + + + ) + }, + { + path: routes.ADVANCED_SEARCH_RESULT, + element: ( + + + + ) + }, + { path: routes.DECLARATION_RECORD_AUDIT, element: }, + { path: routes.SEARCH, element: }, + { path: routes.SEARCH_RESULT, element: }, + { path: routes.CERTIFICATE_COLLECTOR, element: }, + { path: routes.VERIFY_COLLECTOR, element: }, + { path: routes.VERIFY_CORRECTOR, element: }, + { path: routes.REVIEW_CERTIFICATE, element: }, + { path: routes.PRINT_CERTIFICATE_PAYMENT, element: }, + { path: routes.CERTIFICATE_CORRECTION, element: }, + { + path: routes.CERTIFICATE_CORRECTION_REVIEW, + element: + }, + { path: routes.SETTINGS, element: }, + { + path: routes.TEAM_USER_LIST, + element: ( + + + + ) + }, + { + path: routes.SYSTEM_LIST, + element: ( + + + + ) + }, + { + path: routes.VS_EXPORTS, + element: ( + + + + ) + }, + { path: routes.USER_PROFILE, element: }, + { path: routes.VIEW_RECORD, element: }, + { + path: routes.PERFORMANCE_REGISTRATIONS_LIST, + element: + }, + { + path: routes.PERFORMANCE_STATISTICS, + element: ( + + + + ) + }, + { + path: routes.PERFORMANCE_LEADER_BOARDS, + element: ( + + + + ) + }, + { + path: routes.PERFORMANCE_DASHBOARD, + element: ( + + + + ) + }, + { + path: routes.ORGANISATIONS_INDEX, + element: ( + + + + ) + }, + { path: routes.ISSUE_COLLECTOR, element: }, + { path: routes.ISSUE_VERIFY_COLLECTOR, element: }, + { path: routes.ISSUE_CERTIFICATE_PAYMENT, element: }, + { path: routes.PRINT_RECORD, element: }, + { + path: routes.PERFORMANCE_FIELD_AGENT_LIST, + element: + }, + { + path: routes.PERFORMANCE_HOME, + element: ( + + + + ) + }, + { + path: routes.EVENT_COMPLETENESS_RATES, + element: + }, + { + path: routes.WORKFLOW_STATUS, + element: + }, + { + path: routes.CREATE_USER_ON_LOCATION, + element: + }, + { + path: routes.CREATE_USER_SECTION, + element: + }, + { + path: routes.REVIEW_USER_FORM, + element: + }, + { + path: routes.REVIEW_USER_DETAILS, + element: + }, + v2RoutesConfig + ] + } +] + +interface IAppProps { + client?: ApolloClient + store: AppStore + router: ReturnType +} +export function App({ client, store, router }: IAppProps) { + const { client: apolloClient } = useApolloClient(store) return ( - - + + - - - - - - - - - - - { - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) - }} - /> - - - - - - - - + diff --git a/packages/client/src/components/DateRangePicker.test.tsx b/packages/client/src/components/DateRangePicker.test.tsx index 639a84ae8ed..63a8cb9bf3a 100644 --- a/packages/client/src/components/DateRangePicker.test.tsx +++ b/packages/client/src/components/DateRangePicker.test.tsx @@ -18,30 +18,31 @@ import { import { AppStore } from '@client/store' import { DateRangePicker } from '@client/components/DateRangePicker' import { waitForElement } from '@client/tests/wait-for-element' -import { History } from 'history' import { Mock, vi } from 'vitest' import startOfMonth from 'date-fns/startOfMonth' describe('date picker tests', () => { - let component: ReactWrapper + let component: ReactWrapper<{}, {}> let store: AppStore - let history: History + let onDatesChangeMock: Mock beforeEach(async () => { Date.now = vi.fn(() => 1592233232409) const appStore = await createTestStore() store = appStore.store - history = appStore.history + onDatesChangeMock = vi.fn() - component = await createTestComponent( + const { component: testComponent } = await createTestComponent( , - { store, history } + { store } ) + + component = testComponent }) describe('on desktop', () => { diff --git a/packages/client/src/components/GenericErrorToast.test.tsx b/packages/client/src/components/GenericErrorToast.test.tsx index e7049b80e72..c702862fb63 100644 --- a/packages/client/src/components/GenericErrorToast.test.tsx +++ b/packages/client/src/components/GenericErrorToast.test.tsx @@ -14,12 +14,11 @@ import * as React from 'react' import { GenericErrorToast } from './GenericErrorToast' describe('Test toast notification', () => { - const { store, history } = createStore() + const { store } = createStore() it('checks if the appropriate toast is rendered', async () => { - const component = await createTestComponent(, { - store, - history + const { component } = await createTestComponent(, { + store }) expect(component.find('#error-toast').hostNodes()).toHaveLength(1) diff --git a/packages/client/src/components/Header/Header.tsx b/packages/client/src/components/Header/Header.tsx index 16cb7fde138..af9764fdf24 100644 --- a/packages/client/src/components/Header/Header.tsx +++ b/packages/client/src/components/Header/Header.tsx @@ -12,15 +12,7 @@ import { ProfileMenu } from '@client/components/ProfileMenu' import { constantsMessages } from '@client/i18n/messages' import { messages } from '@client/i18n/messages/views/header' import { Icon } from '@opencrvs/components/lib/Icon' -import { - goToEvents as goToEventsAction, - goToSearch, - goToSearchResult, - goToSettings, - goToCreateNewUserWithLocationId, - goToCreateNewUser, - goToAdvancedSearch -} from '@client/navigation' +import { formatUrl } from '@client/navigation' import { getUserDetails } from '@client/profile/profileSelectors' import { IStoreState } from '@client/store' import styled from 'styled-components' @@ -44,7 +36,7 @@ import { import * as React from 'react' import { injectIntl, WrappedComponentProps as IntlShapeProps } from 'react-intl' import { connect } from 'react-redux' -import { RouteComponentProps, withRouter } from 'react-router-dom' + import { TEAM_USER_LIST } from '@client/navigation/routes' import { setAdvancedSearchParam } from '@client/search/advancedSearch/actions' import { advancedSearchInitialState } from '@client/search/advancedSearch/reducer' @@ -53,6 +45,12 @@ import { getRegisterForm } from '@client/forms/register/declaration-selectors' import { getOfflineData } from '@client/offline/selectors' import { IOfflineData } from '@client/offline/reducer' import { SearchCriteria } from '@client/utils/referenceApi' +import { + RouteComponentProps, + withRouter +} from '@client/components/WithRouterProps' +import * as routes from '@client/navigation/routes' +import { parse, stringify } from 'query-string' type IStateProps = { userDetails: UserDetails | null @@ -62,16 +60,10 @@ type IStateProps = { } type IDispatchProps = { - goToEvents: typeof goToEventsAction - goToSearch: typeof goToSearch - goToCreateNewUserWithLocationId: typeof goToCreateNewUserWithLocationId - goToCreateNewUser: typeof goToCreateNewUser - goToAdvancedSearch: typeof goToAdvancedSearch - goToSearchResult: typeof goToSearchResult setAdvancedSearchParam: typeof setAdvancedSearchParam } -interface IProps extends RouteComponentProps { +type IProps = { activeMenuItem: ACTIVE_MENU_ITEM title?: string searchText?: string @@ -90,7 +82,7 @@ interface IProps extends RouteComponentProps { type IFullProps = IntlShapeProps & IStateProps & IDispatchProps & - IProps & + RouteComponentProps & IDomProps enum ACTIVE_MENU_ITEM { @@ -133,7 +125,7 @@ const USERS_WITHOUT_SEARCH = SYS_ADMIN_ROLES.concat( const HeaderComponent = (props: IFullProps) => { const { - location, + router, userDetails, mobileSearchBar, offlineData, @@ -142,19 +134,14 @@ const HeaderComponent = (props: IFullProps) => { activeMenuItem, title, mobileRight, - goToSearch, - goToEvents, - goToCreateNewUserWithLocationId, - goToCreateNewUser, - goToAdvancedSearch, - goToSearchResult, setAdvancedSearchParam, mapPerformanceClickHandler, changeTeamLocation } = props const getMobileHeaderActionProps = (activeMenuItem: ACTIVE_MENU_ITEM) => { - const locationId = new URLSearchParams(location.search).get('locationId') + const locationId = parse(router.location.search).locationId as string + if (activeMenuItem === ACTIVE_MENU_ITEM.PERFORMANCE) { return { mobileLeft: [ @@ -191,9 +178,10 @@ const HeaderComponent = (props: IFullProps) => { icon: () => , handler: () => { if (locationId) { - return goToCreateNewUserWithLocationId(locationId) + router.navigate( + formatUrl(routes.CREATE_USER_ON_LOCATION, { locationId }) + ) } - goToCreateNewUser() } } ] @@ -216,9 +204,10 @@ const HeaderComponent = (props: IFullProps) => { icon: () => , handler: () => { if (locationId) { - return goToCreateNewUserWithLocationId(locationId) + router.navigate( + formatUrl(routes.CREATE_USER_ON_LOCATION, { locationId }) + ) } - goToCreateNewUser() } } ] @@ -270,7 +259,7 @@ const HeaderComponent = (props: IFullProps) => { icon: () => ( ), - handler: () => goToSearch() + handler: () => router.navigate(routes.SEARCH) } ] } @@ -336,7 +325,7 @@ const HeaderComponent = (props: IFullProps) => { id: ADVANCED_SEARCH_TEXT, onClick: () => { setAdvancedSearchParam(advancedSearchInitialState) - goToAdvancedSearch() + router.navigate(routes.ADVANCED_SEARCH) } } ] @@ -355,7 +344,20 @@ const HeaderComponent = (props: IFullProps) => { ? undefined : navigationList } - searchHandler={(text, type) => goToSearchResult(text, type, isMobile)} + searchHandler={(text, type) => + router.navigate( + { + pathname: routes.SEARCH_RESULT, + search: stringify({ + searchText: text, + searchType: type + }) + }, + { + replace: isMobile + } + ) + } /> ) } @@ -398,7 +400,7 @@ const HeaderComponent = (props: IFullProps) => { size="medium" key="newEvent" id="header_new_event" - onClick={goToEvents} + onClick={() => router.navigate(routes.SELECT_VITAL_EVENT)} > @@ -451,45 +453,40 @@ const HeaderComponent = (props: IFullProps) => { ) } -export const Header = connect( - (store: IStoreState) => ({ - activeMenuItem: window.location.href.includes('performance') - ? ACTIVE_MENU_ITEM.PERFORMANCE - : window.location.href.includes(TEAM_USER_LIST) - ? ACTIVE_MENU_ITEM.USERS - : window.location.href.includes('team') - ? ACTIVE_MENU_ITEM.TEAM - : window.location.href.includes('config/certificate') - ? ACTIVE_MENU_ITEM.CERTIFICATE - : window.location.href.includes('config/application') - ? ACTIVE_MENU_ITEM.APPLICATION - : window.location.href.includes('config/form') - ? ACTIVE_MENU_ITEM.FORM - : window.location.href.includes('config/integration') - ? ACTIVE_MENU_ITEM.INTEGRATION - : window.location.href.includes('vsexports') - ? ACTIVE_MENU_ITEM.VSEXPORTS - : ACTIVE_MENU_ITEM.DECLARATIONS, - language: store.i18n.language, - userDetails: getUserDetails(store), - offlineData: getOfflineData(store), - fieldNames: Object.values(getRegisterForm(store)) - .flatMap((form) => form.sections) - .flatMap((section) => section.groups) - .flatMap((group) => group.fields) - .map((field) => field.name) - }), - { - goToSearch, - goToSettings, - goToEvents: goToEventsAction, - goToCreateNewUserWithLocationId, - goToCreateNewUser, - goToAdvancedSearch: goToAdvancedSearch, - setAdvancedSearchParam: setAdvancedSearchParam, - goToSearchResult - } -)(injectIntl(withRouter(HeaderComponent))) +export const Header = withRouter( + connect( + (store: IStoreState) => ({ + activeMenuItem: window.location.href.includes('performance') + ? ACTIVE_MENU_ITEM.PERFORMANCE + : window.location.href.includes(TEAM_USER_LIST) + ? ACTIVE_MENU_ITEM.USERS + : window.location.href.includes('team') + ? ACTIVE_MENU_ITEM.TEAM + : window.location.href.includes('config/certificate') + ? ACTIVE_MENU_ITEM.CERTIFICATE + : window.location.href.includes('config/application') + ? ACTIVE_MENU_ITEM.APPLICATION + : window.location.href.includes('config/form') + ? ACTIVE_MENU_ITEM.FORM + : window.location.href.includes('config/integration') + ? ACTIVE_MENU_ITEM.INTEGRATION + : window.location.href.includes('vsexports') + ? ACTIVE_MENU_ITEM.VSEXPORTS + : ACTIVE_MENU_ITEM.DECLARATIONS, + language: store.i18n.language, + userDetails: getUserDetails(store), + offlineData: getOfflineData(store), + fieldNames: Object.values(getRegisterForm(store)) + .flatMap((form) => form.sections) + .flatMap((section) => section.groups) + .flatMap((group) => group.fields) + .map((field) => field.name) + }), + { + setAdvancedSearchParam: setAdvancedSearchParam + } + )(injectIntl(HeaderComponent)) +) /** @deprecated since the introduction of `` */ export const MarginedHeader = styled(Header)` diff --git a/packages/client/src/components/Header/HistoryNavigator.tsx b/packages/client/src/components/Header/HistoryNavigator.tsx index dbfbb623963..479de1463d2 100644 --- a/packages/client/src/components/Header/HistoryNavigator.tsx +++ b/packages/client/src/components/Header/HistoryNavigator.tsx @@ -10,8 +10,8 @@ */ import React from 'react' import { Button } from '@opencrvs/components/lib/Button' -import { useHistory } from 'react-router-dom' -import { useDispatch, useSelector } from 'react-redux' +import { useLocation, useNavigate, useNavigationType } from 'react-router-dom' +import { useSelector } from 'react-redux' import { getUserDetails } from '@client/profile/profileSelectors' import { FIELD_AGENT_ROLES, @@ -24,7 +24,6 @@ import { PERFORMANCE_HOME, REGISTRAR_HOME } from '@client/navigation/routes' -import { goBack, goForward } from '@client/navigation' import { Icon } from '@opencrvs/components/lib/Icon' export function HistoryNavigator({ @@ -32,20 +31,23 @@ export function HistoryNavigator({ }: { hideForward?: boolean }) { - const history = useHistory() - const dispatch = useDispatch() const userDetails = useSelector(getUserDetails) const role = userDetails && userDetails.systemRole - const location = history.location.pathname + const location = useLocation() + const pathname = location.pathname + const navigate = useNavigate() + + const navigationType = useNavigationType() + const isLandingPage = () => { if ( - (FIELD_AGENT_ROLES.includes(role as string) && HOME.includes(location)) || + (FIELD_AGENT_ROLES.includes(role as string) && HOME.includes(pathname)) || (NATL_ADMIN_ROLES.includes(role as string) && - PERFORMANCE_HOME.includes(location)) || + PERFORMANCE_HOME.includes(pathname)) || (SYS_ADMIN_ROLES.includes(role as string) && - PERFORMANCE_HOME.includes(location)) || + PERFORMANCE_HOME.includes(pathname)) || (REGISTRAR_ROLES.includes(role as string) && - REGISTRAR_HOME.includes(location)) + REGISTRAR_HOME.includes(pathname)) ) { return true } else { @@ -59,10 +61,10 @@ export function HistoryNavigator({ type="icon" size="medium" disabled={ - (history.action === 'POP' || history.action === 'REPLACE') && + (navigationType === 'POP' || navigationType === 'REPLACE') && isLandingPage() } - onClick={() => dispatch(goBack())} + onClick={() => navigate(-1)} > @@ -70,8 +72,8 @@ export function HistoryNavigator({ diff --git a/packages/client/src/components/LocationPicker.test.tsx b/packages/client/src/components/LocationPicker.test.tsx index 31534076eb7..802a75682bd 100644 --- a/packages/client/src/components/LocationPicker.test.tsx +++ b/packages/client/src/components/LocationPicker.test.tsx @@ -8,36 +8,34 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import React from 'react' -import { ReactWrapper } from 'enzyme' -import { createTestComponent, flushPromises } from '@client/tests/util' -import { LocationPicker } from './LocationPicker' import { AppStore, createStore } from '@client/store' +import { createTestComponent, flushPromises } from '@client/tests/util' import { waitForElement } from '@client/tests/wait-for-element' -import { History } from 'history' +import { ReactWrapper } from 'enzyme' +import React from 'react' import { vi } from 'vitest' +import { LocationPicker } from './LocationPicker' describe('location picker tests', () => { let store: AppStore - let history: History let component: ReactWrapper const onChangeLocationMock = vi.fn() beforeAll(async () => { const appStore = createStore() store = appStore.store - history = appStore.history }) beforeEach(async () => { - component = await createTestComponent( + const { component: testComponent } = await createTestComponent( , - { store, history }, + { store }, { attachTo: document.body } ) + component = testComponent }) afterEach(() => { diff --git a/packages/client/src/components/Notification.tsx b/packages/client/src/components/Notification.tsx index d4640ea5075..ffed4e42818 100644 --- a/packages/client/src/components/Notification.tsx +++ b/packages/client/src/components/Notification.tsx @@ -10,7 +10,6 @@ */ import * as React from 'react' import { connect } from 'react-redux' -import { withRouter, RouteComponentProps } from 'react-router-dom' import { messages } from '@client/i18n/messages/views/notifications' import { userMessages } from '@client/i18n/messages/user' import { WrappedComponentProps as IntlShapeProps, injectIntl } from 'react-intl' @@ -35,8 +34,10 @@ import { toggleEmailAllUsersFeedbackToast } from '@client/notification/actions' import { TOAST_MESSAGES } from '@client/user/userReducer' -import { goToDeclarationRecordAudit } from '@client/navigation' +import * as routes from '@client/navigation/routes' import { withOnlineStatus } from '@client/views/OfficeHome/LoadingIndicator' +import { RouteComponentProps, withRouter } from './WithRouterProps' +import { formatUrl } from '@client/navigation' type NotificationProps = ReturnType & { children?: React.ReactNode @@ -56,14 +57,13 @@ type DispatchProps = { hideCreateUserFormDuplicateEmailErrorToast: typeof hideCreateUserFormDuplicateEmailErrorToast hideUnassignedDeclarationsToast: typeof hideUnassignedDeclarationsToast hideUserReconnectedToast: typeof hideUserReconnectedToast - goToDeclarationRecordAudit: typeof goToDeclarationRecordAudit toggleEmailAllUsersFeedbackToast: typeof toggleEmailAllUsersFeedbackToast } type Props = NotificationProps & DispatchProps & IntlShapeProps & - RouteComponentProps<{}> & { isOnline: boolean } + RouteComponentProps & { isOnline: boolean } const Component = ({ hideConfigurationErrorNotification, @@ -79,7 +79,6 @@ const Component = ({ hideCreateUserFormDuplicateEmailErrorToast, hideUnassignedDeclarationsToast, hideUserReconnectedToast, - goToDeclarationRecordAudit, toggleEmailAllUsersFeedbackToast, children, configurationError, @@ -100,7 +99,8 @@ const Component = ({ userReconnectedToast, isOnline, unassignedDeclarations, - emailAllUsers + emailAllUsers, + router }: Props) => { const hideEmailAllUsersFeedbackToast = () => { toggleEmailAllUsersFeedbackToast({ visible: false }) @@ -201,13 +201,16 @@ const Component = ({ underline color="white" element="button" - onClick={() => - hideDuplicateRecordsToast() && - goToDeclarationRecordAudit( - 'reviewTab', - duplicateCompositionId + onClick={() => { + hideDuplicateRecordsToast() + + router.navigate( + formatUrl(routes.DECLARATION_RECORD_AUDIT, { + tab: 'reviewTab', + declarationId: duplicateCompositionId + }) ) - } + }} > {duplicateTrackingId} @@ -329,7 +332,6 @@ export const NotificationComponent = withRouter( hideCreateUserErrorToast, hideCreateUserFormDuplicateEmailErrorToast, hideUserReconnectedToast, - goToDeclarationRecordAudit, hideUnassignedDeclarationsToast, toggleEmailAllUsersFeedbackToast })(injectIntl(withOnlineStatus(Component))) diff --git a/packages/client/src/components/Page.tsx b/packages/client/src/components/Page.tsx index afbf65783b8..6cdee91cf76 100644 --- a/packages/client/src/components/Page.tsx +++ b/packages/client/src/components/Page.tsx @@ -10,7 +10,6 @@ */ import * as React from 'react' import styled from 'styled-components' -import { RouteComponentProps, withRouter } from 'react-router-dom' import { connect } from 'react-redux' import { IStoreState } from '@opencrvs/client/src/store' import { setInitialDeclarations } from '@client/declarations' @@ -32,6 +31,7 @@ import { isRegisterFormReady } from '@client/forms/register/declaration-selector import { IOfflineData } from '@client/offline/reducer' import { isNavigatorOnline } from '@client/utils' import { LoadingBar } from '@opencrvs/components/src/LoadingBar/LoadingBar' +import { RouteComponentProps, withRouter } from './WithRouterProps' const StyledPage = styled.div` background: ${({ theme }) => theme.colors.background}; @@ -51,15 +51,16 @@ const StyledPage = styled.div` box-sizing: border-box; } ` -interface IPageProps { +type IPageProps = RouteComponentProps<{ children?: React.ReactNode }> +interface IStateToProps { initialDeclarationsLoaded: boolean offlineDataLoaded: boolean registerFormLoaded: boolean loadingError: boolean offlineData: IOfflineData | undefined - children?: React.ReactNode } +type IFullProps = IPageProps & IDispatchProps & IStateToProps interface IDispatchProps { setInitialDeclarations: () => void checkAuth: typeof checkAuth @@ -68,14 +69,10 @@ interface IDispatchProps { changeLanguage: (values: Ii18n) => void } -class Component extends React.Component< - RouteComponentProps<{}> & IPageProps & IDispatchProps -> { - componentDidUpdate( - prevProps: RouteComponentProps<{}> & IPageProps & IDispatchProps - ) { - const { hash } = this.props.location - const hashChanged = hash && hash !== prevProps.location.hash +class Component extends React.Component { + componentDidUpdate(prevProps: IFullProps) { + const hash = this.props.router.location?.hash + const hashChanged = hash && hash !== prevProps.router.location.hash const appName = this.props.offlineData ? this.props.offlineData.config.APPLICATION_NAME : '' diff --git a/packages/client/src/components/ProfileMenu.test.tsx b/packages/client/src/components/ProfileMenu.test.tsx index f99af6da039..7945fe2db1f 100644 --- a/packages/client/src/components/ProfileMenu.test.tsx +++ b/packages/client/src/components/ProfileMenu.test.tsx @@ -19,11 +19,14 @@ import { getStorageUserDetailsSuccess } from '@opencrvs/client/src/profile/profi describe('when user opens profile menu without user details', () => { let component: ReactWrapper<{}, {}> beforeEach(async () => { - const { store, history } = createStore() - component = await createTestComponent(, { - store, - history - }) + const { store } = createStore() + const { component: testComponent } = await createTestComponent( + , + { + store + } + ) + component = testComponent }) it('open menu', () => { @@ -38,7 +41,7 @@ describe('when user opens profile menu without user details', () => { describe('when user opens profile menu with user details', () => { let component: ReactWrapper<{}, {}> beforeEach(async () => { - const { store, history } = createStore() + const { store } = createStore() const details = userDetails details.name = [ { @@ -48,10 +51,13 @@ describe('when user opens profile menu with user details', () => { } ] store.dispatch(getStorageUserDetailsSuccess(JSON.stringify(details))) - component = await createTestComponent(, { - store, - history - }) + const { component: testComponent } = await createTestComponent( + , + { + store + } + ) + component = testComponent }) it('open menu', () => { diff --git a/packages/client/src/components/ProfileMenu.tsx b/packages/client/src/components/ProfileMenu.tsx index 65d443314fb..66cd08b40f5 100644 --- a/packages/client/src/components/ProfileMenu.tsx +++ b/packages/client/src/components/ProfileMenu.tsx @@ -27,9 +27,10 @@ import { UserDetails, getIndividualNameObj } from '@client/utils/userUtils' import { getLanguage } from '@client/i18n/selectors' import { getUserDetails } from '@client/profile/profileSelectors' import { redirectToAuthentication } from '@client/profile/profileActions' -import { goToSettings } from '@client/navigation' import { buttonMessages } from '@client/i18n/messages' import { getUserRole } from '@client/utils' +import { useNavigate } from 'react-router-dom' +import * as routes from '@client/navigation/routes' const UserName = styled.div` color: ${({ theme }) => theme.colors.copy}; @@ -47,7 +48,6 @@ interface IProps { language: string userDetails: UserDetails | null redirectToAuthentication: typeof redirectToAuthentication - goToSettings: typeof goToSettings } type FullProps = IProps & IntlShapeProps @@ -56,15 +56,16 @@ const ProfileMenuComponent = ({ intl, language, userDetails, - goToSettings, redirectToAuthentication }: FullProps) => { + const navigate = useNavigate() + const getMenuItems = (intl: IntlShape): IToggleMenuItem[] => { const items = [] as IToggleMenuItem[] items.push({ icon: , label: intl.formatMessage(buttonMessages.settings), - handler: goToSettings + handler: () => navigate(routes.SETTINGS) }) items.push({ icon: , @@ -133,6 +134,5 @@ const mapStateToProps = (store: IStoreState) => { } export const ProfileMenu = connect(mapStateToProps, { - redirectToAuthentication, - goToSettings + redirectToAuthentication })(injectIntl(ProfileMenuComponent)) diff --git a/packages/client/src/components/ProtectedPage.tsx b/packages/client/src/components/ProtectedPage.tsx index 914597302a3..0a77512f3f3 100644 --- a/packages/client/src/components/ProtectedPage.tsx +++ b/packages/client/src/components/ProtectedPage.tsx @@ -12,7 +12,6 @@ import * as React from 'react' import PageVisibility from 'react-page-visibility' import { Unlock } from '@client/views/Unlock/Unlock' import { storage } from '@client/storage' -import { withRouter, RouteComponentProps } from 'react-router-dom' import { isMobileDevice } from '@client/utils/commonUtils' import IdleTimer from 'react-idle-timer' import { USER_DETAILS, UserDetails } from '@client/utils/userUtils' @@ -31,6 +30,9 @@ import { showPINUpdateSuccessToast } from '@client/notification/actions' import { CreatePin } from '@client/views/PIN/CreatePin' import { redirectToAuthentication } from '@client/profile/profileActions' import { LoadingBar } from '@opencrvs/components/src/LoadingBar/LoadingBar' +import { RouteComponentProps, withRouter } from './WithRouterProps' +import { getAuthenticated } from '@client/profile/profileSelectors' +import { IStoreState } from '@client/store' export const SCREEN_LOCK = 'screenLock' type OwnProps = PropsWithChildren<{ @@ -51,7 +53,12 @@ interface IProtectPageState { passwordVerified: boolean } -type Props = OwnProps & DispatchProps & RouteComponentProps<{}> +type Props = OwnProps & + DispatchProps & + RouteComponentProps<{ + authenticated: boolean + userDetailsFetched: boolean + }> class ProtectedPageComponent extends React.Component { constructor(props: Props) { @@ -109,8 +116,8 @@ class ProtectedPageComponent extends React.Component { async handleVisibilityChange(isVisible: boolean) { const alreadyLocked = isVisible || (await storage.getItem(SCREEN_LOCK)) - const onUnprotectedPage = this.props.unprotectedRouteElements.some( - (route) => this.props.location.pathname.includes(route) + const onUnprotectedPage = this.props.unprotectedRouteElements?.some( + (route) => this.props.router.location.pathname.includes(route) ) const newState = { ...this.state } @@ -172,12 +179,14 @@ class ProtectedPageComponent extends React.Component { conditionalRenderUponSecuredState() { const { secured, loading, forgotPin } = this.state - if (loading) { + const { children, authenticated, userDetailsFetched } = this.props + + if (loading || (!authenticated && !userDetailsFetched)) { return this.renderLoadingScreen() } if (secured) { - return this.props.children + return children } if (!secured) { @@ -238,8 +247,20 @@ class ProtectedPageComponent extends React.Component { ) } } -export const ProtectedPage = connect<{}, DispatchProps, OwnProps>(null, { + +const mapStateToProps = (store: IStoreState) => { + return { + authenticated: getAuthenticated(store), + userDetailsFetched: store.profile.userDetailsFetched + } +} + +const mapDispatchToProps = { onNumPadVisible: refreshOfflineData, showPINUpdateSuccessToast, redirectToAuthentication -})(withRouter(ProtectedPageComponent)) +} + +export const ProtectedPage = withRouter( + connect(mapStateToProps, mapDispatchToProps)(ProtectedPageComponent) +) diff --git a/packages/client/src/components/ProtectedRoute.tsx b/packages/client/src/components/ProtectedRoute.tsx index a56e37ea806..77eae97c31a 100644 --- a/packages/client/src/components/ProtectedRoute.tsx +++ b/packages/client/src/components/ProtectedRoute.tsx @@ -9,7 +9,7 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ import * as React from 'react' -import { Redirect, Route } from 'react-router-dom' +import { Navigate, RouteProps } from 'react-router-dom' import { connect } from 'react-redux' import { IStoreState } from '@client/store' import { getAuthenticated } from '@client/profile/profileSelectors' @@ -21,22 +21,22 @@ interface IProps { roles?: SystemRoleType[] } -class ProtectedRouteWrapper extends Route< - IProps & ReturnType -> { - public render() { - const { authenticated, userDetailsFetched, userDetails, roles, ...rest } = - this.props - if (!authenticated && !userDetailsFetched) { - return
- } - if (roles && userDetails) { - if (!hasAccessToRoute(roles, userDetails)) { - return - } +/** + * Higher order component that wraps a route and checks if the user has access to it. + * If the user does not have access, they are redirected to the home page. + */ +const ProtectedRouteWrapper = ( + props: IProps & ReturnType & RouteProps +) => { + const { children, userDetails, roles } = props + + if (roles && userDetails) { + if (!hasAccessToRoute(roles, userDetails)) { + return } - return } + + return <>{children} } const mapStateToProps = (store: IStoreState, props: IProps) => { diff --git a/packages/client/src/components/ScrollToTop.tsx b/packages/client/src/components/ScrollToTop.tsx index e481616e582..8c5ed72437f 100644 --- a/packages/client/src/components/ScrollToTop.tsx +++ b/packages/client/src/components/ScrollToTop.tsx @@ -9,14 +9,14 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ import React, { useEffect, ReactNode } from 'react' -import { withRouter, RouteComponentProps } from 'react-router-dom' +import { RouteComponentProps, withRouter } from './WithRouterProps' -type ScrollToTopProps = RouteComponentProps & { children?: ReactNode } +type ScrollToTopProps = RouteComponentProps<{ children?: ReactNode }> -const ScrollToTop = ({ children, location }: ScrollToTopProps) => { +const ScrollToTop = ({ children, router }: ScrollToTopProps) => { useEffect(() => { window.scrollTo(0, 0) - }, [location]) + }, [router.location]) return <>{children} } diff --git a/packages/client/src/components/WithRouterProps.tsx b/packages/client/src/components/WithRouterProps.tsx new file mode 100644 index 00000000000..2a9745a1c81 --- /dev/null +++ b/packages/client/src/components/WithRouterProps.tsx @@ -0,0 +1,57 @@ +/* + * 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 { + useLocation, + useNavigate, + useParams, + Location, + NavigateFunction, + Params +} from 'react-router-dom' +import * as React from 'react' + +export type RouteComponentProps = { + router: { + location: Location + navigate: NavigateFunction + params: Readonly> + match: { params: Readonly> } + } +} & T + +/** + * Higher order component to pass router props to a component. + * withRouter was deprecated in react-router-dom v6. + * This implementation introduces the similar functionality using react-router-dom hooks. + */ +export function withRouter( + Component: React.ComponentType +) { + function ComponentWithRouterProp(props: Omit) { + const location = useLocation() + const navigate = useNavigate() + const params = useParams() + + /** + * For backwards compatibility, match is added to the router prop. + */ + const match = { params } + return ( + + ) + } + + return ComponentWithRouterProp +} diff --git a/packages/client/src/components/form/FetchButton.tsx b/packages/client/src/components/form/FetchButton.tsx index 7c25ea9ae18..8ed87e3c20c 100644 --- a/packages/client/src/components/form/FetchButton.tsx +++ b/packages/client/src/components/form/FetchButton.tsx @@ -25,7 +25,7 @@ import { Success, Error } from '@opencrvs/components/lib/icons' import { IQuery } from '@opencrvs/client/src/forms' import { useOnlineStatus } from '@client/utils' -interface IFetchButtonProps { +interface IFetchButtonProps { id: string queryData?: IQuery label: string @@ -33,7 +33,7 @@ interface IFetchButtonProps { modalTitle: string successTitle: string errorTitle: string - onFetch?: (response: any) => void + onFetch?: (response: T) => void isDisabled?: boolean } diff --git a/packages/client/src/components/form/FormFieldGenerator.test.tsx b/packages/client/src/components/form/FormFieldGenerator.test.tsx index b15f46ad29e..9593b94721c 100644 --- a/packages/client/src/components/form/FormFieldGenerator.test.tsx +++ b/packages/client/src/components/form/FormFieldGenerator.test.tsx @@ -41,11 +41,11 @@ describe('form component', () => { let component: ReactWrapper<{}, {}> beforeEach(async () => { - const { store, history } = createStore() + const { store } = createStore() const draft = createDeclaration(EventType.Birth) store.dispatch(storeDeclaration(draft)) const modifyDraft = vi.fn() - component = await createTestComponent( + const { component: testComponent } = await createTestComponent( { ]} />, { - store, - history + store } ) + + component = testComponent }) describe('when user is in the moth​​er section', () => { it('renders the page', async () => { @@ -150,8 +151,8 @@ describe('when field definition has location search input', () => { const modifyDraft = vi.fn() beforeEach(async () => { - const { store, history } = createStore() - component = await createTestComponent( + const { store } = createStore() + const { component: testComponent } = await createTestComponent( { } ]} />, - { store, history } + { store } ) + + component = testComponent }) it('renders location search input without crashing', async () => { @@ -215,11 +218,11 @@ describe('when field definition has location search input', () => { describe('when user is in the register section', () => { let component: ReactWrapper<{}, {}> beforeEach(async () => { - const { store, history } = createStore() + const { store } = createStore() const draft = createDeclaration(EventType.Birth) store.dispatch(storeDeclaration(draft)) const modifyDraft = vi.fn() - component = await createTestComponent( + const { component: testComponent } = await createTestComponent( { ]} />, { - store, - history + store } ) + + component = testComponent }) it('renders registration phone type as tel', () => { expect( @@ -257,11 +261,11 @@ describe('when field definition has nested fields', () => { let component: ReactWrapper<{}, {}> beforeEach(async () => { - const { store, history } = createStore() + const { store } = createStore() const draft = createDeclaration(EventType.Birth) store.dispatch(storeDeclaration(draft)) const modifyDraft = vi.fn() - component = await createTestComponent( + const { component: testComponent } = await createTestComponent( { ]} />, { - store, - history + store } ) + + component = testComponent }) it('renders radio group with nested fields', () => { @@ -436,8 +441,8 @@ describe('when field definition has date field', () => { describe('in case of static date field', () => { beforeEach(async () => { - const { store, history } = createStore() - component = await createTestComponent( + const { store } = createStore() + const { component: testComponent } = await createTestComponent( { } ]} />, - { store, history } + { store } ) + + component = testComponent }) it('shows validation errors for invalid date', async () => { @@ -473,8 +480,8 @@ describe('when field definition has number field', () => { const modifyDraftMock = vi.fn() beforeEach(async () => { - const { store, history } = createStore() - component = await createTestComponent( + const { store } = createStore() + const { component: testComponent } = await createTestComponent( { ]} />, { - store, - history + store } ) + + component = testComponent }) it('field does not take input of non numeric characters', async () => { @@ -524,8 +532,8 @@ describe('when field definition has select field on mobile device', () => { beforeEach(async () => { window.HTMLElement.prototype.scrollIntoView = scrollMock - const { store, history } = createStore() - component = await createTestComponent( + const { store } = createStore() + const { component: testComponent } = await createTestComponent( { ]} />, { - store, - history + store }, { attachTo: document.body } ) + + component = testComponent }) it('triggers scroll up when focus so that soft keyboard does not block options', async () => { diff --git a/packages/client/src/components/form/Redirect.tsx b/packages/client/src/components/form/Redirect.tsx index 10af834fe22..ac4cd3dc51f 100644 --- a/packages/client/src/components/form/Redirect.tsx +++ b/packages/client/src/components/form/Redirect.tsx @@ -14,7 +14,7 @@ import { getOfflineData } from '@client/offline/selectors' import { getUserDetails } from '@client/profile/profileSelectors' import React from 'react' import { useSelector } from 'react-redux' -import { Redirect } from 'react-router-dom' +import { Navigate } from 'react-router-dom' export const RedirectField = ({ to, @@ -28,7 +28,7 @@ export const RedirectField = ({ const config = useSelector(getOfflineData) const user = useSelector(getUserDetails) return ( - { let store: AppStore - let history: History + let testComponent: ReactWrapper<{}, {}> let client: ApolloClient<{}> @@ -32,9 +31,9 @@ describe('download button tests', () => { beforeEach(async () => { const testStore = await createTestStore() store = testStore.store - history = testStore.history + client = createClient(store) - testComponent = await createTestComponent( + const { component } = await createTestComponent( { }} status={DOWNLOAD_STATUS.DOWNLOADED} />, - { store, history, apolloClient: client } + { store, apolloClient: client } ) + + testComponent = component }) it('download button renders', () => { @@ -57,9 +58,9 @@ describe('download button tests', () => { beforeEach(async () => { const testStore = await createTestStore() store = testStore.store - history = testStore.history + client = createClient(store) - testComponent = await createTestComponent( + const { component } = await createTestComponent( { }} status={DOWNLOAD_STATUS.DOWNLOADED} />, - { store, history, apolloClient: client } + { store, apolloClient: client } ) + + testComponent = component }) it('download button renders', () => { diff --git a/packages/client/src/components/interface/Navigation.test.tsx b/packages/client/src/components/interface/Navigation.test.tsx index 1a08cd527f7..f348c72690e 100644 --- a/packages/client/src/components/interface/Navigation.test.tsx +++ b/packages/client/src/components/interface/Navigation.test.tsx @@ -23,12 +23,10 @@ import { createClient } from '@client/utils/apolloClient' import { OfficeHome } from '@client/views/OfficeHome/OfficeHome' import { merge } from 'lodash' import * as React from 'react' -import { - WORKQUEUE_TABS, - Navigation -} from '@client/components/interface/Navigation' +import { Navigation } from '@client/components/interface/Navigation' import { ReactWrapper } from 'enzyme' import { Mock, vi } from 'vitest' +import { createMemoryRouter } from 'react-router-dom' const getItem = window.localStorage.getItem as Mock const mockFetchUserDetails = vi.fn() @@ -70,7 +68,7 @@ const nameObjNatlSysAdmin = { storage.getItem = vi.fn() storage.setItem = vi.fn() -let { store, history } = createStore() +let { store } = createStore() let client = createClient(store) describe('Navigation for national system admin related tests', () => { @@ -80,28 +78,15 @@ describe('Navigation for national system admin related tests', () => { merge(mockUserResponse, nameObjNatlSysAdmin) mockFetchUserDetails.mockReturnValue(mockUserResponse) queries.fetchUserDetails = mockFetchUserDetails - ;({ store, history } = createStore()) + ;({ store } = createStore()) client = createClient(store) getItem.mockReturnValue(natlSysAdminToken) await store.dispatch(checkAuth()) await flushPromises() - testComponent = await createTestComponent( - , - { store, history } - ) + const { component } = await createTestComponent(, { store }) + + testComponent = component }) it('Tabs loaded successfully including config tab', async () => { @@ -123,52 +108,32 @@ describe('Navigation for national system admin related tests', () => { describe('Navigation for Registration agent related tests', () => { let testComponent: ReactWrapper<{}, {}> - + let router: ReturnType beforeEach(async () => { merge(mockUserResponse, nameObj) mockFetchUserDetails.mockReturnValue(mockUserResponse) queries.fetchUserDetails = mockFetchUserDetails - ;({ store, history } = createStore()) + ;({ store } = createStore()) client = createClient(store) getItem.mockReturnValue(registerScopeToken) await store.dispatch(checkAuth()) await flushPromises() - testComponent = await createTestComponent( - , - { store, history } + const { component, router: testRouter } = await createTestComponent( + , + { store } ) + router = testRouter + testComponent = component }) it('renders page with team and performance tab for registration agent', async () => { - const testComponent = await createTestComponent( - , - { store, history, apolloClient: client } - ) - expect(testComponent.exists('#navigation_team')).toBeTruthy() - expect(testComponent.exists('#navigation_performance')).toBeTruthy() - expect(testComponent.exists('#navigation_config_main')).toBeFalsy() + const { component } = await createTestComponent(, { + store, + apolloClient: client + }) + expect(component.exists('#navigation_team')).toBeTruthy() + expect(component.exists('#navigation_performance')).toBeTruthy() + expect(component.exists('#navigation_config_main')).toBeFalsy() }) it('5 application tabs exists for registration agent', async () => { @@ -187,18 +152,19 @@ describe('Navigation for Registration agent related tests', () => { .hostNodes() .simulate('click') await flushPromises() - expect(window.location.href).toContain('readyForReview') + + expect(router.state.location.pathname).toContain('readyForReview') testComponent .find('#navigation_requiresUpdate') .hostNodes() .simulate('click') await flushPromises() - expect(window.location.href).toContain('requiresUpdate') + expect(router.state.location.pathname).toContain('requiresUpdate') testComponent.find('#navigation_approvals').hostNodes().simulate('click') await flushPromises() - expect(window.location.href).toContain('approvals') + expect(router.state.location.pathname).toContain('approvals') }) }) @@ -209,16 +175,18 @@ describe('Navigation for District Registrar related tests', () => { merge(mockUserResponse, nameObj) mockFetchUserDetails.mockReturnValue(mockUserResponse) queries.fetchUserDetails = mockFetchUserDetails - ;({ store, history } = createStore()) + ;({ store } = createStore()) client = createClient(store) getItem.mockReturnValue(registerScopeToken) await store.dispatch(checkAuth()) await flushPromises() - testComponent = await createTestComponent( + const { component } = await createTestComponent( {}} />, - { store, history } + { store } ) + + testComponent = component }) it('settings and logout exists on navigation mobile view', async () => { expect(testComponent.exists('#navigation_settings')).toBeTruthy() diff --git a/packages/client/src/components/interface/Navigation.tsx b/packages/client/src/components/interface/Navigation.tsx index 962cab318f9..e0aefd52d3d 100644 --- a/packages/client/src/components/interface/Navigation.tsx +++ b/packages/client/src/components/interface/Navigation.tsx @@ -17,18 +17,10 @@ import { import { buttonMessages } from '@client/i18n/messages' import { navigationMessages } from '@client/i18n/messages/views/navigation' import { - goToAdvancedSearchResult, - goToAllUserEmail, - goToDashboardView, - goToHomeTab, - goToLeaderBoardsView, - goToOrganisationView, - goToPerformanceStatistics, - goToPerformanceView, - goToSettings, - goToSystemList, - goToTeamView, - goToVSExport + formatUrl, + generateGoToHomeTabUrl, + generatePerformanceHomeUrl, + getDefaultPerformanceLocationId } from '@client/navigation' import { ADVANCED_SEARCH_RESULT } from '@client/navigation/routes' import { IOfflineData } from '@client/offline/reducer' @@ -62,8 +54,13 @@ import { omit } from 'lodash' import * as React from 'react' import { injectIntl, WrappedComponentProps as IntlShapeProps } from 'react-intl' import { connect } from 'react-redux' -import { RouteComponentProps, withRouter } from 'react-router-dom' import { IS_PROD_ENVIRONMENT } from '@client/utils/constants' +import { + RouteComponentProps, + withRouter +} from '@client/components/WithRouterProps' +import * as routes from '@client/navigation/routes' +import { stringify } from 'query-string' const SCREEN_LOCK = 'screenLock' @@ -218,21 +215,9 @@ interface IProps { } interface IDispatchProps { - goToHomeTab: typeof goToHomeTab - goToVSExportsAction: typeof goToVSExport - goToAdvancedSearchResultAction: typeof goToAdvancedSearchResult redirectToAuthentication: typeof redirectToAuthentication - goToPerformanceViewAction: typeof goToPerformanceView - goToTeamViewAction: typeof goToTeamView - goToOrganisationViewAction: typeof goToOrganisationView - goToSystemViewAction: typeof goToSystemList - goToSettings: typeof goToSettings - goToLeaderBoardsView: typeof goToLeaderBoardsView - goToDashboardView: typeof goToDashboardView - goToPerformanceStatistics: typeof goToPerformanceStatistics updateRegistrarWorkqueue: typeof updateRegistrarWorkqueue setAdvancedSearchParam: typeof setAdvancedSearchParam - goToAllUserEmail: typeof goToAllUserEmail } interface IStateProps { @@ -250,16 +235,10 @@ type IFullProps = IProps & IStateProps & IDispatchProps & IntlShapeProps & - RouteComponentProps<{ tabId: string }> & { className?: string } + RouteComponentProps<{ className?: string }> const getSettingsAndLogout = (props: IFullProps) => { - const { - intl, - menuCollapse, - activeMenuItem, - redirectToAuthentication, - goToSettings - } = props + const { intl, menuCollapse, activeMenuItem, redirectToAuthentication } = props return ( <> { id={`navigation_${WORKQUEUE_TABS.settings}`} label={intl.formatMessage(buttonMessages[WORKQUEUE_TABS.settings])} onClick={() => { - goToSettings() + props.router.navigate(routes.SETTINGS) + menuCollapse && menuCollapse() }} isSelected={activeMenuItem === WORKQUEUE_TABS.settings} @@ -288,16 +268,13 @@ const getSettingsAndLogout = (props: IFullProps) => { const NavigationView = (props: IFullProps) => { const { intl, - match, + router, userDetails, advancedSearchParams, deselectAllTabs, enableMenuSelection = true, loadWorkqueueStatuses = true, activeMenuItem, - goToVSExportsAction, - goToSystemViewAction, - goToAdvancedSearchResultAction, navigationWidth, workqueue, storedDeclarations, @@ -307,16 +284,12 @@ const NavigationView = (props: IFullProps) => { offlineCountryConfiguration, updateRegistrarWorkqueue, setAdvancedSearchParam, - goToPerformanceStatistics, - goToDashboardView, - goToLeaderBoardsView, - goToAllUserEmail, className } = props const tabId = deselectAllTabs ? '' - : match.params.tabId - ? match.params.tabId + : router?.match?.params?.tabId + ? router.match.params.tabId : activeMenuItem ? activeMenuItem : 'review' @@ -422,7 +395,12 @@ const NavigationView = (props: IFullProps) => { count={props.draftDeclarations.length} isSelected={tabId === WORKQUEUE_TABS.inProgress} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.inProgress) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.inProgress + }) + ) + menuCollapse && menuCollapse() }} /> @@ -435,7 +413,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.readyForReview} isSelected={tabId === WORKQUEUE_TABS.sentForReview} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.sentForReview) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.sentForReview + }) + ) + menuCollapse && menuCollapse() }} /> @@ -448,7 +431,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.requiresUpdate} isSelected={tabId === WORKQUEUE_TABS.requiresUpdate} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.requiresUpdate) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.requiresUpdate + }) + ) + menuCollapse && menuCollapse() }} /> @@ -461,7 +449,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.outbox} isSelected={tabId === WORKQUEUE_TABS.outbox} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.outbox) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.outbox + }) + ) + menuCollapse && menuCollapse() }} /> @@ -487,7 +480,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.inProgress} isSelected={tabId === WORKQUEUE_TABS.inProgress} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.inProgress) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.inProgress + }) + ) + menuCollapse && menuCollapse() }} /> @@ -505,7 +503,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.readyForReview} isSelected={tabId === WORKQUEUE_TABS.readyForReview} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.readyForReview) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.readyForReview + }) + ) + menuCollapse && menuCollapse() }} /> @@ -523,7 +526,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.requiresUpdate} isSelected={tabId === WORKQUEUE_TABS.requiresUpdate} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.requiresUpdate) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.requiresUpdate + }) + ) + menuCollapse && menuCollapse() }} /> @@ -541,7 +549,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.sentForApproval} isSelected={tabId === WORKQUEUE_TABS.sentForApproval} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.sentForApproval) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.sentForApproval + }) + ) + menuCollapse && menuCollapse() }} /> @@ -556,7 +569,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.externalValidation} isSelected={tabId === WORKQUEUE_TABS.externalValidation} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.externalValidation) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.externalValidation + }) + ) + menuCollapse && menuCollapse() }} /> @@ -574,7 +592,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.readyToPrint} isSelected={tabId === WORKQUEUE_TABS.readyToPrint} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.readyToPrint) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.readyToPrint + }) + ) + menuCollapse && menuCollapse() }} /> @@ -594,7 +617,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.readyToIssue} isSelected={tabId === WORKQUEUE_TABS.readyToIssue} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.readyToIssue) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.readyToIssue + }) + ) + menuCollapse && menuCollapse() }} /> @@ -613,7 +641,12 @@ const NavigationView = (props: IFullProps) => { count={declarationCount.outbox} isSelected={tabId === WORKQUEUE_TABS.outbox} onClick={() => { - props.goToHomeTab(WORKQUEUE_TABS.outbox) + props.router.navigate( + generateGoToHomeTabUrl({ + tabId: WORKQUEUE_TABS.outbox + }) + ) + menuCollapse && menuCollapse() }} /> @@ -634,7 +667,12 @@ const NavigationView = (props: IFullProps) => { navigationMessages[WORKQUEUE_TABS.performance] )} onClick={() => { - props.goToPerformanceViewAction(userDetails) + props.router.navigate( + generatePerformanceHomeUrl({ + locationId: + getDefaultPerformanceLocationId(userDetails) + }) + ) }} isSelected={ enableMenuSelection && @@ -653,7 +691,11 @@ const NavigationView = (props: IFullProps) => { navigationMessages[WORKQUEUE_TABS.organisation] )} onClick={() => - props.goToOrganisationViewAction(userDetails) + router.navigate( + formatUrl(routes.ORGANISATIONS_INDEX, { + locationId: '' // NOTE: Empty string is required + }) + ) } isSelected={ enableMenuSelection && @@ -671,7 +713,20 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages[WORKQUEUE_TABS.team] )} - onClick={() => props.goToTeamViewAction(userDetails)} + onClick={() => { + if ( + userDetails && + userDetails.systemRole && + userDetails.primaryOffice + ) { + props.router.navigate({ + pathname: routes.TEAM_USER_LIST, + search: stringify({ + locationId: userDetails.primaryOffice.id + }) + }) + } + }} isSelected={ enableMenuSelection && activeMenuItem === WORKQUEUE_TABS.team @@ -712,7 +767,7 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages[WORKQUEUE_TABS.systems] )} - onClick={goToSystemViewAction} + onClick={() => router.navigate(routes.SYSTEM_LIST)} isSelected={ enableMenuSelection && activeMenuItem === WORKQUEUE_TABS.systems @@ -758,7 +813,9 @@ const NavigationView = (props: IFullProps) => { navigationMessages[WORKQUEUE_TABS.emailAllUsers] )} id={`navigation_${WORKQUEUE_TABS.emailAllUsers}`} - onClick={goToAllUserEmail} + onClick={() => + router.navigate(routes.ALL_USER_EMAIL) + } isSelected={ enableMenuSelection && activeMenuItem === WORKQUEUE_TABS.emailAllUsers @@ -784,7 +841,11 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages['dashboard'] )} - onClick={goToDashboardView} + onClick={() => + router.navigate(routes.PERFORMANCE_DASHBOARD, { + state: { isNavigatedInsideApp: true } + }) + } id="navigation_dashboard" isSelected={ enableMenuSelection && @@ -798,7 +859,11 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages['statistics'] )} - onClick={goToPerformanceStatistics} + onClick={() => + router.navigate(routes.PERFORMANCE_STATISTICS, { + state: { isNavigatedInsideApp: true } + }) + } id="navigation_statistics" isSelected={ enableMenuSelection && @@ -812,7 +877,11 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages['leaderboards'] )} - onClick={goToLeaderBoardsView} + onClick={() => + router.navigate(routes.PERFORMANCE_LEADER_BOARDS, { + state: { isNavigatedInsideApp: true } + }) + } id="navigation_leaderboards" isSelected={ enableMenuSelection && @@ -825,9 +894,14 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages['performance'] )} - onClick={() => - props.goToPerformanceViewAction(userDetails) - } + onClick={() => { + props.router.navigate( + generatePerformanceHomeUrl({ + locationId: + getDefaultPerformanceLocationId(userDetails) + }) + ) + }} id="navigation_report" isSelected={ enableMenuSelection && @@ -846,7 +920,7 @@ const NavigationView = (props: IFullProps) => { label={intl.formatMessage( navigationMessages[WORKQUEUE_TABS.vsexports] )} - onClick={goToVSExportsAction} + onClick={() => router.navigate(routes.VS_EXPORTS)} isSelected={ enableMenuSelection && activeMenuItem === WORKQUEUE_TABS.vsexports @@ -876,7 +950,7 @@ const NavigationView = (props: IFullProps) => { label={bookmarkResult.name} disabled={ advancedSearchParams.searchId === bookmarkResult.searchId && - props.location.pathname === ADVANCED_SEARCH_RESULT + props.router.location.pathname === ADVANCED_SEARCH_RESULT } onClick={() => { const filteredParam = omit( @@ -888,11 +962,12 @@ const NavigationView = (props: IFullProps) => { searchId: bookmarkResult?.searchId, bookmarkName: bookmarkResult?.name }) - goToAdvancedSearchResultAction() + + router.navigate(routes.ADVANCED_SEARCH_RESULT) }} isSelected={ advancedSearchParams.searchId === bookmarkResult.searchId && - props.location.pathname === ADVANCED_SEARCH_RESULT + props.router.location.pathname === ADVANCED_SEARCH_RESULT } /> ) @@ -957,28 +1032,13 @@ const mapStateToProps: (state: IStoreState) => IStateProps = (state) => { } } -export const Navigation = connect< - IStateProps, - IDispatchProps, - IProps, - IStoreState ->(mapStateToProps, { - goToHomeTab, - goToAdvancedSearchResultAction: goToAdvancedSearchResult, - goToVSExportsAction: goToVSExport, - goToPerformanceViewAction: goToPerformanceView, - goToOrganisationViewAction: goToOrganisationView, - goToTeamViewAction: goToTeamView, - goToSystemViewAction: goToSystemList, - redirectToAuthentication, - goToSettings, - updateRegistrarWorkqueue, - setAdvancedSearchParam, - goToPerformanceStatistics, - goToLeaderBoardsView, - goToDashboardView, - goToAllUserEmail -})(injectIntl(withRouter(NavigationView))) +export const Navigation = withRouter( + connect(mapStateToProps, { + redirectToAuthentication, + updateRegistrarWorkqueue, + setAdvancedSearchParam + })(injectIntl(NavigationView)) +) /** @deprecated since the introduction of `` */ export const FixedNavigation = styled(Navigation)` diff --git a/packages/client/src/components/review/RejectRegistrationForm.test.tsx b/packages/client/src/components/review/RejectRegistrationForm.test.tsx index ceb832787db..6f30d8c7357 100644 --- a/packages/client/src/components/review/RejectRegistrationForm.test.tsx +++ b/packages/client/src/components/review/RejectRegistrationForm.test.tsx @@ -17,14 +17,14 @@ import { EventType } from '@client/utils/gateway' import { createDeclaration } from '@client/declarations' import { vi } from 'vitest' -const { store, history } = createStore() +const { store } = createStore() const mockHandler = vi.fn() describe('reject registration form', () => { let component: ReactWrapper<{}, {}> const draftDeclaration = createDeclaration(EventType.Birth) beforeEach(async () => { - component = await createTestComponent( + const { component: testComponent } = await createTestComponent( { draftId="04ba2b0e-ba38-4049-ad74-332e4ee9fbfe" event={EventType.Birth} />, - { store, history } + { store } ) + component = testComponent }) it('renders form', () => { diff --git a/packages/client/src/components/review/RejectRegistrationForm.tsx b/packages/client/src/components/review/RejectRegistrationForm.tsx index b7904b83958..c0a86ed6503 100644 --- a/packages/client/src/components/review/RejectRegistrationForm.tsx +++ b/packages/client/src/components/review/RejectRegistrationForm.tsx @@ -19,7 +19,6 @@ import { IFormSectionData, SubmissionAction } from '@client/forms' import { hasFormError } from '@client/forms/utils' import { buttonMessages } from '@client/i18n/messages' import { messages } from '@client/i18n/messages/views/reject' -import { goToHome } from '@client/navigation' import { IOfflineData } from '@client/offline/reducer' import { getOfflineData } from '@client/offline/selectors' import { getUserDetails } from '@client/profile/profileSelectors' @@ -36,6 +35,11 @@ import { isEmpty } from 'lodash' import * as React from 'react' import { injectIntl, WrappedComponentProps as IntlShapeProps } from 'react-intl' import { connect } from 'react-redux' +import { + RouteComponentProps, + withRouter +} from '@client/components/WithRouterProps' +import * as routes from '@client/navigation/routes' import styled from 'styled-components' const Instruction = styled.div` @@ -48,14 +52,10 @@ interface IState { } interface IProps { draftId: string - config: IOfflineData - user: UserDetails | null declaration: IDeclaration event: EventType duplicate?: boolean onClose: () => void - archiveDeclaration: typeof archiveDeclaration - goToHome: typeof goToHome confirmRejectionEvent: ( declaration: IDeclaration, status: string, @@ -64,7 +64,20 @@ interface IProps { ) => void } -type IFullProps = IntlShapeProps & IProps & { form: IRejectRegistrationForm } +type IDispatchProps = { + archiveDeclaration: typeof archiveDeclaration +} + +type StateProps = { + config: IOfflineData + user: UserDetails | null + form: IRejectRegistrationForm +} + +type IFullProps = IntlShapeProps & + RouteComponentProps & + StateProps & + IDispatchProps class RejectRegistrationView extends React.Component { constructor(props: IFullProps) { @@ -167,7 +180,8 @@ class RejectRegistrationView extends React.Component { payload.reason as string, payload.comment as string ) - this.props.goToHome() + + this.props.router.navigate(routes.HOME) }} disabled={!this.state.enableArchiveBtn} > @@ -208,14 +222,15 @@ class RejectRegistrationView extends React.Component { } } -export const RejectRegistrationForm = connect( - (state: IStoreState) => ({ - form: rejectRegistration, - config: getOfflineData(state), - user: getUserDetails(state) - }), - { - archiveDeclaration, - goToHome - } -)(injectIntl(RejectRegistrationView)) +export const RejectRegistrationForm = withRouter( + connect, IStoreState>( + (state: IStoreState) => ({ + form: rejectRegistration, + config: getOfflineData(state), + user: getUserDetails(state) + }), + { + archiveDeclaration + } + )(injectIntl(RejectRegistrationView)) +) diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index b8ea12aefc7..90212662d6b 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -28,11 +28,6 @@ import { SystemRoleType } from '@client/utils/gateway' import { getRegisterForm } from '@client/forms/register/declaration-selectors' -import { - Action as NavigationAction, - GO_TO_PAGE, - IDynamicValues -} from '@client/navigation' import { UserDetailsAvailable, USER_DETAILS_AVAILABLE @@ -91,6 +86,7 @@ import { getReviewForm } from '@client/forms/register/review-selectors' import { getBirthQueryMappings } from '@client/views/DataProvider/birth/queries' import { getDeathQueryMappings } from '@client/views/DataProvider/death/queries' import { getMarriageQueryMappings } from '@client/views/DataProvider/marriage/queries' +import { IDynamicValues } from '@client/navigation' const ARCHIVE_DECLARATION = 'DECLARATION/ARCHIVE' const SET_INITIAL_DECLARATION = 'DECLARATION/SET_INITIAL_DECLARATION' @@ -466,7 +462,6 @@ export type Action = | IWriteDeclarationAction | IWriteDeclarationSuccessAction | IWriteDeclarationFailedAction - | NavigationAction | IDeleteDeclarationAction | IDeleteDeclarationSuccessAction | IDeleteDeclarationFailedAction @@ -1287,28 +1282,6 @@ export const declarationsReducer: LoopReducer = ( action: Action ): IDeclarationsState | Loop => { switch (action.type) { - case GO_TO_PAGE: { - const declaration = state.declarations.find( - ({ id }) => id === action.payload.declarationId - ) - - if ( - !declaration || - declaration.data[action.payload.pageId] || - action.payload.pageId === 'preview' || - action.payload.pageId === 'review' - ) { - return state - } - const modifiedDeclaration = { - ...declaration, - data: { - ...declaration.data, - [action.payload.pageId]: {} - } - } - return loop(state, Cmd.action(modifyDeclaration(modifiedDeclaration))) - } case STORE_DECLARATION: return { ...state, diff --git a/packages/client/src/declarations/selectors.ts b/packages/client/src/declarations/selectors.ts index ff0913ba840..1f69d0018f5 100644 --- a/packages/client/src/declarations/selectors.ts +++ b/packages/client/src/declarations/selectors.ts @@ -28,15 +28,16 @@ export const getInitialDeclarationsLoaded = ( getKey(store, 'initialDeclarationsLoaded') const selectDeclaration = - (declarationId: string) => - (store: IStoreState) => { - return getKey(store, 'declarations').find( - ({ id }) => declarationId === id - ) as T - } + (declarationId?: string) => + (store: IStoreState) => + declarationId + ? (getKey(store, 'declarations').find( + ({ id }) => declarationId === id + ) as T) + : (undefined as T) export const useDeclaration = ( - declarationId: string + declarationId?: string ) => { return useSelector(selectDeclaration(declarationId)) } diff --git a/packages/client/src/forms/user/fieldDefinitions/createUser.ts b/packages/client/src/forms/user/fieldDefinitions/createUser.ts index bffeb68e9c8..93e1baaaddc 100644 --- a/packages/client/src/forms/user/fieldDefinitions/createUser.ts +++ b/packages/client/src/forms/user/fieldDefinitions/createUser.ts @@ -28,6 +28,7 @@ function userSectionFormType(): ISerializedFormSection { groups: [ { id: 'registration-office', + preventContinueIfError: true, title: userFormMessages.assignedRegistrationOffice, conditionals: [ { diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx index da873fa6b32..be169a8a920 100644 --- a/packages/client/src/index.tsx +++ b/packages/client/src/index.tsx @@ -10,17 +10,18 @@ */ // eslint-disable-next-line import/no-unassigned-import import 'focus-visible/dist/focus-visible.js' -import * as React from 'react' -import { createRoot } from 'react-dom/client' -import { App } from '@client/App' -import { createStore } from '@client/store' import * as actions from '@client/notification/actions' import { storage } from '@client/storage' +import { createStore } from '@client/store' +import * as React from 'react' +import { createRoot } from 'react-dom/client' // eslint-disable-next-line no-restricted-imports -import * as Sentry from '@sentry/react' import { SubmissionController } from '@client/SubmissionController' -import WebFont from 'webfontloader' +import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' +import { createBrowserRouter } from 'react-router-dom' +import WebFont from 'webfontloader' +import { App, routesConfig } from './App' import { APPLICATION_VERSION } from './utils/constants' WebFont.load({ @@ -31,7 +32,7 @@ WebFont.load({ storage.configStorage('OpenCRVS') -const { store, history } = createStore() +const { store } = createStore() if ( window.location.hostname !== 'localhost' && @@ -61,6 +62,9 @@ window.addEventListener('online', userReconnectedToast) const container = document.getElementById('root') const root = createRoot(container!) -root.render() + +const router = createBrowserRouter(routesConfig) + +root.render() new SubmissionController(store).start() diff --git a/packages/client/src/navigation/index.ts b/packages/client/src/navigation/index.ts index dc9dcc248dd..b2d73cbe6f8 100644 --- a/packages/client/src/navigation/index.ts +++ b/packages/client/src/navigation/index.ts @@ -10,51 +10,24 @@ */ import { IWORKQUEUE_TABS } from '@client/components/interface/Navigation' -import { CorrectionSection, UserSection } from '@client/forms' +import { CorrectionSection } from '@client/forms' import { - ADVANCED_SEARCH, - ADVANCED_SEARCH_RESULT, - ALL_USER_EMAIL, CERTIFICATE_COLLECTOR, CERTIFICATE_CORRECTION, - CREATE_USER, - CREATE_USER_ON_LOCATION, CREATE_USER_SECTION, - DECLARATION_RECORD_AUDIT, - DRAFT_BIRTH_PARENT_FORM, - DRAFT_DEATH_FORM, - DRAFT_MARRIAGE_FORM, EVENT_COMPLETENESS_RATES, - HOME, ISSUE_CERTIFICATE_PAYMENT, ISSUE_COLLECTOR, ISSUE_VERIFY_COLLECTOR, - ORGANISATIONS_INDEX, - PERFORMANCE_DASHBOARD, - PERFORMANCE_FIELD_AGENT_LIST, PERFORMANCE_HOME, - PERFORMANCE_LEADER_BOARDS, PERFORMANCE_REGISTRATIONS_LIST, - PERFORMANCE_STATISTICS, PRINT_CERTIFICATE_PAYMENT, - PRINT_RECORD, REGISTRAR_HOME_TAB, REGISTRAR_HOME_TAB_PAGE, REVIEW_CERTIFICATE, - REVIEW_USER_DETAILS, REVIEW_USER_FORM, - SEARCH, - SEARCH_RESULT, - SELECT_VITAL_EVENT, - SETTINGS, - SYSTEM_LIST, - TEAM_SEARCH, - TEAM_USER_LIST, - USER_PROFILE, VERIFY_COLLECTOR, VERIFY_CORRECTOR, - VIEW_RECORD, - VS_EXPORTS, WORKFLOW_STATUS } from '@client/navigation/routes' import { @@ -64,22 +37,14 @@ import { REGISTRAR_ROLES, SYS_ADMIN_ROLES } from '@client/utils/constants' -import { EventType } from '@client/utils/gateway' -import { UserDetails } from '@client/utils/userUtils' -import { IRecordAuditTabs } from '@client/views/RecordAudit/RecordAudit' -import { IStatusMapping } from '@client/views/SysAdmin/Performance/reports/operational/StatusWiseDeclarationCountView' import { CompletenessRateTime } from '@client/views/SysAdmin/Performance/utils' -import { ISearchLocation } from '@opencrvs/components/lib/LocationSearch' -import { - goBack as back, - goForward as forward, - push, - replace -} from 'connected-react-router' + +import { stringify } from 'query-string' import startOfMonth from 'date-fns/startOfMonth' import subMonths from 'date-fns/subMonths' -import { stringify } from 'query-string' -import { Cmd, loop } from 'redux-loop' +import { UserDetails } from '@client/utils/userUtils' +import { IStatusMapping } from '@client/views/SysAdmin/Performance/reports/operational/StatusWiseDeclarationCountView' +import { EventType } from '@client/utils/gateway' export interface IDynamicValues { [key: string]: any @@ -92,133 +57,44 @@ export function formatUrl(url: string, props: { [key: string]: string }) { ) return formattedUrl.endsWith('?') ? formattedUrl.slice(0, -1) : formattedUrl } -export const GO_TO_PAGE = 'navigation/GO_TO_PAGE' -type GoToPageAction = { - type: typeof GO_TO_PAGE - payload: { - pageRoute: string - declarationId: string - pageId: string - groupId?: string - event: string - fieldNameHash?: string - historyState?: IDynamicValues - } -} - -const GO_TO_REVIEW_USER_DETAILS = 'navigation/GO_TO_REVIEW_USER_DETAILS' -type GoToReviewUserDetails = { - type: typeof GO_TO_REVIEW_USER_DETAILS - payload: { - userId: string - } -} - -const GO_TO_USER_PROFILE = 'navigation/GO_TO_USER_PROFILE' -type GoToUserProfile = { - type: typeof GO_TO_USER_PROFILE - payload: { - userId: string - } -} - -export type Action = GoToPageAction | GoToReviewUserDetails | GoToUserProfile - -export function goToDeathInformant(declarationId: string) { - return push( - formatUrl(DRAFT_DEATH_FORM, { - declarationId: declarationId.toString() - }) - ) -} - -export function goToMarriageInformant(declarationId: string) { - return push( - formatUrl(DRAFT_MARRIAGE_FORM, { - declarationId: declarationId.toString() - }) - ) -} - -export function goToEvents() { - return push(SELECT_VITAL_EVENT) -} - -export function goBack() { - return back() -} - -export function goForward() { - return forward() -} - -export function goToHome() { - return push(HOME) -} - -export function goToAllUserEmail() { - return push(ALL_USER_EMAIL) -} - -export function goToVSExport() { - return push(VS_EXPORTS) -} -export function goToPerformanceStatistics() { - return push(PERFORMANCE_STATISTICS, { isNavigatedInsideApp: true }) -} -export function goToLeaderBoardsView() { - return push(PERFORMANCE_LEADER_BOARDS, { isNavigatedInsideApp: true }) -} -export function goToDashboardView() { - return push(PERFORMANCE_DASHBOARD, { isNavigatedInsideApp: true }) -} - -export function goToAdvancedSearch() { - return push(ADVANCED_SEARCH) -} - -export function goToHomeTab( - tabId: IWORKQUEUE_TABS, +export const generateGoToHomeTabUrl = ({ + tabId, selectorId = '', pageId = 1 -) { +}: { + tabId: IWORKQUEUE_TABS + selectorId?: string + pageId?: number +}) => { if (tabId === 'progress') { if (selectorId) { - return push( - formatUrl(REGISTRAR_HOME_TAB_PAGE, { - tabId, - selectorId, - pageId: String(pageId) - }) - ) + return formatUrl(REGISTRAR_HOME_TAB_PAGE, { + tabId, + selectorId, + pageId: String(pageId) + }) } - return push(formatUrl(REGISTRAR_HOME_TAB, { tabId, selectorId })) + return formatUrl(REGISTRAR_HOME_TAB, { tabId, selectorId }) } - return push( - formatUrl(REGISTRAR_HOME_TAB, { tabId, selectorId: String(pageId) }) - ) -} - -type searchedLocation = { - selectedLocation: ISearchLocation -} -export function goToTeamSearch(searchedLocation?: searchedLocation) { - return searchedLocation && searchedLocation.selectedLocation - ? push(TEAM_SEARCH, { selectedLocation: searchedLocation.selectedLocation }) - : push(TEAM_SEARCH) + return formatUrl(REGISTRAR_HOME_TAB, { tabId, selectorId: String(pageId) }) } -export function goToPerformanceHome( - timeStart: Date = new Date( +export function generatePerformanceHomeUrl({ + timeStart = new Date( startOfMonth(subMonths(new Date(Date.now()), 11)).setHours(0, 0, 0, 0) ), - timeEnd: Date = new Date(new Date(Date.now()).setHours(23, 59, 59, 999)), - event?: EventType, + timeEnd = new Date(new Date(Date.now()).setHours(23, 59, 59, 999)), + event, + locationId +}: { + timeStart?: Date + timeEnd?: Date + event?: EventType locationId?: string -) { - return push({ +}) { + return { pathname: PERFORMANCE_HOME, search: stringify({ locationId, @@ -226,407 +102,278 @@ export function goToPerformanceHome( timeStart: timeStart.toISOString(), timeEnd: timeEnd.toISOString() }) - }) -} - -export function goToTeamUserList(id: string) { - return push({ - pathname: TEAM_USER_LIST, - search: stringify({ - locationId: id - }) - }) -} - -export function goToOrganizationList(locationId?: string | undefined | null) { - return push(formatUrl(ORGANISATIONS_INDEX, { locationId: locationId ?? '' })) -} - -export function goToSystemList() { - return push(SYSTEM_LIST) -} - -export function goToSearchResult( - searchText: string, - searchType: string, - mobile?: boolean -) { - return mobile - ? replace({ - pathname: SEARCH_RESULT, - search: stringify({ - searchText, - searchType - }) - }) - : push({ - pathname: SEARCH_RESULT, - search: stringify({ - searchText, - searchType - }) - }) -} - -export function goToAdvancedSearchResult(mobile?: boolean) { - return push(formatUrl(ADVANCED_SEARCH_RESULT, {})) -} - -export function goToSearch() { - return push(SEARCH) -} - -export function goToDeclarationRecordAudit( - tab: IRecordAuditTabs, - declarationId: string -) { - return push(formatUrl(DECLARATION_RECORD_AUDIT, { tab, declarationId })) -} - -export function goToBirthRegistrationAsParent(declarationId: string) { - return push( - formatUrl(DRAFT_BIRTH_PARENT_FORM, { - declarationId: declarationId.toString() - }) - ) + } } -export function goToPrintCertificate( - registrationId: string, - eventType: string, +export const generatePrintCertificateUrl = ({ + registrationId, + event, + groupId +}: { + registrationId: string + event: string groupId?: string -) { - return push( - formatUrl(CERTIFICATE_COLLECTOR, { - registrationId, - eventType, - groupId: groupId || 'certCollector' - }) - ) -} +}) => + formatUrl(CERTIFICATE_COLLECTOR, { + registrationId: registrationId.toString(), + eventType: event.toLowerCase().toString(), + groupId: groupId || 'certCollector' + }) -export function goToIssueCertificate( - registrationId: string, - pageId = 'collector' -) { - return push( - formatUrl(ISSUE_COLLECTOR, { - registrationId: registrationId.toString(), - pageId: pageId - }) - ) -} +export const generateIssueCertificateUrl = ({ + registrationId, + pageId +}: { + registrationId: string + pageId?: string +}) => + formatUrl(ISSUE_COLLECTOR, { + registrationId: registrationId.toString(), + pageId: pageId ?? 'collector' + }) -export function goToVerifyIssueCollector( - registrationId: string, - eventType: string, +export const generateVerifyIssueCollectorUrl = ({ + registrationId, + event, + collector +}: { + registrationId: string + event: EventType collector: string -) { - return push( - formatUrl(ISSUE_VERIFY_COLLECTOR, { - registrationId: registrationId.toString(), - eventType, - collector: collector.toLowerCase().toString() - }) - ) -} - -export function goToViewRecordPage(declarationId: string) { - return push( - formatUrl(VIEW_RECORD, { - declarationId - }) - ) -} +}) => + formatUrl(ISSUE_VERIFY_COLLECTOR, { + registrationId: registrationId.toString(), + eventType: event.toLowerCase().toString(), + collector: collector.toLowerCase().toString() + }) -export function goToCertificateCorrection( - declarationId: string, +export const generateCertificateCorrectionUrl = ({ + declarationId, + pageId +}: { + declarationId: string pageId: CorrectionSection -) { - return push( - formatUrl(CERTIFICATE_CORRECTION, { - declarationId: declarationId.toString(), - pageId: pageId.toString() - }) - ) -} +}) => + formatUrl(CERTIFICATE_CORRECTION, { + declarationId: declarationId.toString(), + pageId: pageId.toString() + }) -export function goToVerifyCorrector(declarationId: string, corrector: string) { - return push( - formatUrl(VERIFY_CORRECTOR, { - declarationId: declarationId.toString(), - corrector: corrector.toLowerCase().toString() - }) - ) -} +export const generateVerifyCorrectorUrl = ({ + declarationId, + corrector +}: { + declarationId: string + corrector: string +}) => + formatUrl(VERIFY_CORRECTOR, { + declarationId: declarationId.toString(), + corrector: corrector.toLowerCase().toString() + }) -export function goToReviewCertificate( - registrationId: string, - eventType: string -) { - return push( - formatUrl(REVIEW_CERTIFICATE, { - registrationId, - eventType - }), - { isNavigatedInsideApp: true } - ) -} +export const generateReviewCertificateUrl = ({ + registrationId, + event +}: { + registrationId: string + event: EventType +}) => + formatUrl(REVIEW_CERTIFICATE, { + registrationId: registrationId.toString(), + eventType: event + }) -export function goToVerifyCollector( - registrationId: string, - eventType: string, +export const generateVerifyCollectorUrl = ({ + registrationId, + event, + collector +}: { + registrationId: string + event: string collector: string -) { - return push( - formatUrl(VERIFY_COLLECTOR, { - registrationId: registrationId.toString(), - eventType: eventType.toLowerCase().toString(), - collector: collector.toLowerCase().toString() - }) - ) -} - -export function goToPrintCertificatePayment( - registrationId: string, - eventType: string -) { - return push( - formatUrl(PRINT_CERTIFICATE_PAYMENT, { - registrationId, - eventType - }) - ) -} - -export function goToIssueCertificatePayment( - registrationId: string, - eventType: string -) { - return push( - formatUrl(ISSUE_CERTIFICATE_PAYMENT, { - registrationId, - eventType - }) - ) -} - -export function goToSettings() { - return push(SETTINGS) -} +}) => + formatUrl(VERIFY_COLLECTOR, { + registrationId: registrationId.toString(), + eventType: event.toLowerCase().toString(), + collector: collector.toLowerCase().toString() + }) -export function goToCreateNewUser() { - return push(CREATE_USER) -} +export const generatePrintCertificatePaymentUrl = ({ + registrationId, + event +}: { + registrationId: string + event: EventType +}) => + formatUrl(PRINT_CERTIFICATE_PAYMENT, { + registrationId: registrationId.toString(), + eventType: event + }) -export function goToCreateNewUserWithLocationId(locationId: string) { - return push(formatUrl(CREATE_USER_ON_LOCATION, { locationId })) -} +export const generateIssueCertificatePaymentUrl = ({ + registrationId, + event +}: { + registrationId: string + event: EventType +}) => + formatUrl(ISSUE_CERTIFICATE_PAYMENT, { + registrationId: registrationId.toString(), + eventType: event + }) -export function goToCompletenessRates( - eventType: EventType, - locationId: string | undefined, - timeStart: Date, - timeEnd: Date, +export const generateCompletenessRatesUrl = ({ + eventType, + locationId, + timeStart, + timeEnd, time = CompletenessRateTime.WithinTarget -) { - return push({ - pathname: formatUrl(EVENT_COMPLETENESS_RATES, { eventType }), - search: stringify( - locationId - ? { - locationId, - timeStart: timeStart.toISOString(), - timeEnd: timeEnd.toISOString(), - time - } - : { - timeStart: timeStart.toISOString(), - timeEnd: timeEnd.toISOString(), - time - } - ) - }) -} +}: { + eventType: EventType + locationId?: string + timeStart: Date + timeEnd: Date + time?: CompletenessRateTime +}) => + formatUrl(EVENT_COMPLETENESS_RATES, { eventType }) + + '?' + + stringify( + locationId + ? { + locationId, + timeStart: timeStart.toISOString(), + timeEnd: timeEnd.toISOString(), + time + } + : { + timeStart: timeStart.toISOString(), + timeEnd: timeEnd.toISOString(), + time + } + ) -export function goToFieldAgentList( - timeStart: string, - timeEnd: string, - locationId?: string, +export const generateRegistrationsListUrlConfig = ({ + timeStart, + timeEnd, + locationId, + event, + filterBy, + currentPageNumber +}: { + timeStart: string + timeEnd: string + locationId?: string event?: string -) { - return push({ - pathname: PERFORMANCE_FIELD_AGENT_LIST, - search: stringify({ - event, - locationId, - timeStart, - timeEnd - }) - }) -} - -export function goToRegistrationsList( - timeStart: string, - timeEnd: string, - locationId?: string, - event?: string, - filterBy?: string, + filterBy?: string currentPageNumber?: number -) { - return push({ - pathname: PERFORMANCE_REGISTRATIONS_LIST, - search: stringify({ - locationId, - timeStart, - timeEnd, - event, - filterBy, - currentPageNumber - }) +}) => ({ + pathname: PERFORMANCE_REGISTRATIONS_LIST, + search: stringify({ + locationId, + timeStart, + timeEnd, + event, + filterBy, + currentPageNumber }) -} - -export function goToWorkflowStatus( - locationId: string, - timeStart: Date, - timeEnd: Date, - status?: keyof IStatusMapping, +}) + +export const generateWorkflowStatusUrl = ({ + locationId, + timeStart, + timeEnd, + status, + event +}: { + locationId: string + timeStart: Date + timeEnd: Date + status?: keyof IStatusMapping event?: EventType -) { - return push({ - pathname: WORKFLOW_STATUS, - search: stringify({ - locationId, - status, - event - }), - state: { - timeStart, - timeEnd - } - }) -} -export function goToReviewUserDetails(userId: string): GoToReviewUserDetails { - return { - type: GO_TO_REVIEW_USER_DETAILS, - payload: { - userId - } - } -} - -export function goToUserProfile(userId: string): GoToUserProfile { - return { - type: GO_TO_USER_PROFILE, - payload: { - userId - } - } -} - -const GO_TO_CREATE_USER_SECTION = 'navigation/GO_TO_CREATE_USER_SECTION' -type GoToCreateUserSection = { - type: typeof GO_TO_CREATE_USER_SECTION - payload: { - sectionId: string - nextGroupId: string - userFormFieldNameHash?: string - formHistoryState?: IDynamicValues - } -} - -const GO_TO_USER_REVIEW_FORM = 'navigation/GO_TO_USER_REVIEW_FORM' -type GoToUserReviewForm = { - type: typeof GO_TO_USER_REVIEW_FORM - payload: { - userId: string - sectionId: string - nextGroupId: string - userFormFieldNameHash?: string - formHistoryState?: IDynamicValues - } -} - -export function goToCreateUserSection( - sectionId: string, - nextGroupId: string, - fieldNameHash?: string, - historyState?: IDynamicValues -): GoToCreateUserSection { - return { - type: GO_TO_CREATE_USER_SECTION, - payload: { - sectionId, - nextGroupId, - userFormFieldNameHash: fieldNameHash, - formHistoryState: historyState - } - } -} - -export function goToUserReviewForm( - userId: string, - sectionId: string, - nextGroupId: string, - fieldNameHash?: string, - historyState?: IDynamicValues -): GoToUserReviewForm { - return { - type: GO_TO_USER_REVIEW_FORM, - payload: { - userId, - sectionId, - nextGroupId, - userFormFieldNameHash: fieldNameHash, - formHistoryState: historyState - } - } -} - -export function goToPageGroup( - pageRoute: string, - declarationId: string, - pageId: string, - groupId: string, - event: string, - fieldNameHash?: string, - historyState?: IDynamicValues -) { - return { - type: GO_TO_PAGE, - payload: { - declarationId, - pageId, - groupId, - event, - fieldNameHash, - pageRoute, - historyState - } +}) => ({ + pathname: WORKFLOW_STATUS, + search: stringify({ + locationId, + status, + event + }), + state: { + timeStart, + timeEnd } -} - -export function goToPage( - pageRoute: string, - declarationId: string, - pageId: string, - event: string, - fieldNameHash?: string, +}) + +export const generateUserReviewFormUrl = ({ + userId, + sectionId, + groupId, + userFormFieldNameHash +}: { + userId: string + sectionId: string + groupId: string + userFormFieldNameHash?: string +}) => + formatUrl(REVIEW_USER_FORM, { + userId, + sectionId, + groupId + }) + (userFormFieldNameHash ? `#${userFormFieldNameHash}` : '') + +export const generateCreateUserSectionUrl = ({ + sectionId, + groupId, + userFormFieldNameHash +}: { + sectionId: string + groupId: string + userFormFieldNameHash?: string +}) => + formatUrl(CREATE_USER_SECTION, { + sectionId, + groupId + }) + (userFormFieldNameHash ? `#${userFormFieldNameHash}` : '') + +export const generateGoToPageGroupUrl = ({ + pageRoute, + declarationId, + pageId, + groupId, + event, + fieldNameHash, + historyState +}: { + pageRoute: string + declarationId: string + pageId: string + groupId: string + event: string + fieldNameHash?: string historyState?: IDynamicValues -) { - return { - type: GO_TO_PAGE, - payload: { - declarationId, - pageId, - event, - fieldNameHash, - pageRoute, - historyState - } - } -} +}) => + formatUrl(pageRoute, { + declarationId: declarationId.toString(), + pageId, + groupId, + event + }) + (fieldNameHash ? `#${fieldNameHash}` : '') + +export const generateGoToPageUrl = ({ + pageRoute, + declarationId, + pageId, + event +}: { + pageRoute: string + declarationId: string + pageId: string + event: string +}) => + formatUrl(pageRoute, { + declarationId: declarationId.toString(), + pageId, + event + }) export function getDefaultPerformanceLocationId(userDetails: UserDetails) { const role = userDetails?.systemRole @@ -646,121 +393,3 @@ export function getDefaultPerformanceLocationId(userDetails: UserDetails) { `Performance view no default location selected for role: ${role}` ) } - -export function goToPerformanceView(userDetails: UserDetails) { - return goToPerformanceHome( - undefined, - undefined, - undefined, - getDefaultPerformanceLocationId(userDetails) - ) -} - -export function goToTeamView(userDetails: UserDetails) { - if (userDetails && userDetails.systemRole) { - return goToTeamUserList( - (userDetails.primaryOffice && userDetails.primaryOffice.id) || '' - ) - } -} - -export function goToOrganisationView(userDetails: UserDetails) { - return goToOrganizationList() -} - -export function goToPrintRecordView(declarationId: string) { - return push( - formatUrl(PRINT_RECORD, { - declarationId - }) - ) -} -export type INavigationState = undefined - -export function navigationReducer(state: INavigationState, action: any) { - switch (action.type) { - case GO_TO_PAGE: - const { - fieldNameHash, - declarationId, - pageId, - groupId, - event, - pageRoute, - historyState - } = action.payload - return loop( - state, - Cmd.action( - push( - formatUrl(pageRoute, { - declarationId: declarationId.toString(), - pageId, - groupId, - event - }) + (fieldNameHash ? `#${fieldNameHash}` : ''), - historyState - ) - ) - ) - case GO_TO_CREATE_USER_SECTION: - const { - sectionId, - nextGroupId, - userFormFieldNameHash, - formHistoryState - } = action.payload - return loop( - state, - Cmd.action( - push( - formatUrl(CREATE_USER_SECTION, { - sectionId, - groupId: nextGroupId - }) + (userFormFieldNameHash ? `#${userFormFieldNameHash}` : ''), - formHistoryState - ) - ) - ) - case GO_TO_USER_REVIEW_FORM: - return loop( - state, - Cmd.action( - push( - formatUrl(REVIEW_USER_FORM, { - userId: action.payload.userId, - sectionId: action.payload.sectionId, - groupId: action.payload.nextGroupId - }) + - (action.payload.userFormFieldNameHash - ? `#${action.payload.userFormFieldNameHash}` - : ''), - action.payload.formHistoryState - ) - ) - ) - case GO_TO_REVIEW_USER_DETAILS: - return loop( - state, - Cmd.action( - push( - formatUrl(REVIEW_USER_DETAILS, { - userId: action.payload.userId, - sectionId: UserSection.Preview - }) - ) - ) - ) - case GO_TO_USER_PROFILE: - return loop( - state, - Cmd.action( - push( - formatUrl(USER_PROFILE, { - userId: action.payload.userId - }) - ) - ) - ) - } -} diff --git a/packages/client/src/navigation/routes.ts b/packages/client/src/navigation/routes.ts index de16c90b131..823b2a24f36 100644 --- a/packages/client/src/navigation/routes.ts +++ b/packages/client/src/navigation/routes.ts @@ -86,7 +86,6 @@ export const PERFORMANCE_STATISTICS = '/performance/statistics' export const TEAM_SEARCH = '/team/search' export const TEAM_USER_LIST = '/team/users' -export const CREATE_USER = '/createUser' export const CREATE_USER_ON_LOCATION = '/createUserInLocation/:locationId' export const CREATE_USER_SECTION = '/createUser/:sectionId/:groupId' export const REVIEW_USER_FORM = '/user/:userId/:sectionId/:groupId' diff --git a/packages/client/src/profile/profileActions.ts b/packages/client/src/profile/profileActions.ts index 8dcd8291d00..4d0a4b1dc73 100644 --- a/packages/client/src/profile/profileActions.ts +++ b/packages/client/src/profile/profileActions.ts @@ -9,7 +9,6 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { RouterAction } from 'connected-react-router' import { ApolloQueryResult } from '@apollo/client' import { FetchUserQuery } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' @@ -178,7 +177,6 @@ export type Action = | CheckAuthAction | SetUserDetailsAction | RedirectToAuthenticationAction - | RouterAction | ISetInitialUserDetails | IGetStorageUserDetailsSuccessAction | IGetStorageUserDetailsFailedAction diff --git a/packages/client/src/setupTests.ts b/packages/client/src/setupTests.ts index 5391ecac46c..d3e92326211 100644 --- a/packages/client/src/setupTests.ts +++ b/packages/client/src/setupTests.ts @@ -270,11 +270,3 @@ vi.mock('./utils', async () => ({ isNavigatorOnline: () => true, getUserRole: vi.fn().mockImplementation((lang, role) => 'ENTREPENEUR') })) - -vi.mock('react-router-dom', async () => ({ - ...((await vi.importActual('react-router-dom')) as any), - useParams: vi.fn().mockImplementation(() => ({ - event: 'birth', - section: 'child' - })) -})) diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index 57af8df5b04..ecec7f7e67b 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -8,12 +8,6 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { - connectRouter, - routerMiddleware, - RouterState -} from 'connected-react-router' -import { createBrowserHistory, History } from 'history' import { AnyAction, applyMiddleware, @@ -29,7 +23,6 @@ import { registerFormReducer } from '@client/forms/register/reducer' import { intlReducer, IntlState } from '@client/i18n/reducer' -import { INavigationState, navigationReducer } from '@client/navigation' import { notificationReducer, NotificationState @@ -58,11 +51,9 @@ import { export interface IStoreState { profile: ProfileState - router: RouterState i18n: IntlState declarationsState: IDeclarationsState registerForm: IRegisterFormState - navigation: INavigationState notification: NotificationState reviewForm: IReviewFormState offline: IOfflineDataState @@ -78,17 +69,12 @@ export type AppStore = Store const config = { DONT_LOG_ERRORS_ON_HANDLED_FAILURES: true } -export const createStore = ( - existingHistory?: History -): { store: AppStore; history: History } => { - const history = existingHistory || createBrowserHistory() +export const createStore = (): { store: AppStore } => { const reducers = combineReducers({ profile: profileReducer, - router: connectRouter(history) as any, // @todo i18n: intlReducer, declarationsState: declarationsReducer, registerForm: registerFormReducer, - navigation: navigationReducer, notification: notificationReducer, reviewForm: reviewReducer, offline: offlineDataReducer, @@ -102,7 +88,6 @@ export const createStore = ( applyMiddleware(submissionMiddleware), install(config), applyMiddleware(persistenceMiddleware), - applyMiddleware(routerMiddleware(history)), // @ts-ignore types are not correct for this module yet applyMiddleware(createSentryMiddleware(Sentry)), typeof (window as any).__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined' @@ -115,5 +100,5 @@ export const createStore = ( getModel(reducers(undefined, { type: 'NOOP' })), enhancer ) - return { store, history } + return { store } } diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index b144ff1de4e..5c076976bd8 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -8,57 +8,57 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { App } from '@client/App' -import { EventType, SystemRoleType, Status } from '@client/utils/gateway' -import { UserDetails } from '@client/utils/userUtils' +import { + ApolloClient, + ApolloLink, + ApolloProvider, + InMemoryCache, + NetworkStatus, + Observable +} from '@apollo/client' +import { MockedProvider } from '@apollo/client/testing' +import { App, routesConfig } from '@client/App' import { getRegisterForm } from '@client/forms/register/declaration-selectors' import { getReviewForm } from '@client/forms/register/review-selectors' import { offlineDataReady, setOfflineData } from '@client/offline/actions' +import { setUserDetails } from '@client/profile/profileActions' import { AppStore, createStore, IStoreState } from '@client/store' -import { ThemeProvider } from 'styled-components' import { getSchema } from '@client/tests/graphql-schema-mock' +import { EventType, Status, SystemRoleType } from '@client/utils/gateway' +import { UserDetails } from '@client/utils/userUtils' import { I18nContainer } from '@opencrvs/client/src/i18n/components/I18nContainer' import { getTheme } from '@opencrvs/components/lib/theme' -import { join } from 'path' +import Adapter from '@wojtekmaj/enzyme-adapter-react-17' import { configure, mount, + MountRendererProps, ReactWrapper, - shallow, - MountRendererProps + shallow } from 'enzyme' -import Adapter from '@wojtekmaj/enzyme-adapter-react-17' import { readFileSync } from 'fs' import { graphql, print } from 'graphql' +import { createLocation, createMemoryHistory } from 'history' import * as jwt from 'jsonwebtoken' +import { join } from 'path' +import { stringify } from 'query-string' import * as React from 'react' -import { - ApolloProvider, - NetworkStatus, - ApolloClient, - InMemoryCache, - ApolloLink, - Observable -} from '@apollo/client' -import { MockedProvider } from '@apollo/client/testing' import { IntlShape } from 'react-intl' import { Provider } from 'react-redux' import { AnyAction, Store } from 'redux' +import { ThemeProvider } from 'styled-components' import { waitForElement } from './wait-for-element' -import { setUserDetails } from '@client/profile/profileActions' -import { createLocation, createMemoryHistory, History } from 'history' -import { stringify } from 'query-string' -import { match as Match } from 'react-router-dom' -import { ConnectedRouter } from 'connected-react-router' -import { mockOfflineData } from './mock-offline-data' -import { Section, SubmissionAction } from '@client/forms' + import { SUBMISSION_STATUS } from '@client/declarations' -import { vi } from 'vitest' -import { getSystemRolesQuery } from '@client/forms/user/query/queries' +import { Section, SubmissionAction } from '@client/forms' +import { deserializeFormSection } from '@client/forms/deserializer/deserializer' import { createOrUpdateUserMutation } from '@client/forms/user/mutation/mutations' +import { getSystemRolesQuery } from '@client/forms/user/query/queries' import { draftToGqlTransformer } from '@client/transformer' -import { deserializeFormSection } from '@client/forms/deserializer/deserializer' import * as builtInValidators from '@client/utils/validate' +import { createMemoryRouter, RouterProvider } from 'react-router-dom' +import { vi } from 'vitest' +import { mockOfflineData } from './mock-offline-data' export const registerScopeToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWdpc3RlciIsImNlcnRpZnkiLCJkZW1vIl0sImlhdCI6MTU0MjY4ODc3MCwiZXhwIjoxNTQzMjkzNTcwLCJhdWQiOlsib3BlbmNydnM6YXV0aC11c2VyIiwib3BlbmNydnM6dXNlci1tZ250LXVzZXIiLCJvcGVuY3J2czpoZWFydGgtdXNlciIsIm9wZW5jcnZzOmdhdGV3YXktdXNlciIsIm9wZW5jcnZzOm5vdGlmaWNhdGlvbi11c2VyIiwib3BlbmNydnM6d29ya2Zsb3ctdXNlciJdLCJpc3MiOiJvcGVuY3J2czphdXRoLXNlcnZpY2UiLCJzdWIiOiI1YmVhYWY2MDg0ZmRjNDc5MTA3ZjI5OGMifQ.ElQd99Lu7WFX3L_0RecU_Q7-WZClztdNpepo7deNHqzro-Cog4WLN7RW3ZS5PuQtMaiOq1tCb-Fm3h7t4l4KDJgvC11OyT7jD6R2s2OleoRVm3Mcw5LPYuUVHt64lR_moex0x_bCqS72iZmjrjS-fNlnWK5zHfYAjF2PWKceMTGk6wnI9N49f6VwwkinJcwJi6ylsjVkylNbutQZO0qTc7HRP-cBfAzNcKD37FqTRNpVSvHdzQSNcs7oiv3kInDN5aNa2536XSd3H-RiKR9hm9eID9bSIJgFIGzkWRd5jnoYxT70G0t03_mTVnDnqPXDtyI-lmerx24Ost0rQLUNIg' @@ -144,17 +144,20 @@ export function waitForReady(app: ReactWrapper) { } export async function createTestApp( - config = { waitUntilOfflineCountryConfigLoaded: true } + config = { waitUntilOfflineCountryConfigLoaded: true }, + initialEntries?: string[] ) { - const { store, history } = await createTestStore() + const { store } = await createTestStore() + const router = createMemoryRouter(routesConfig, { initialEntries }) + const app = mount( - + ) if (config.waitUntilOfflineCountryConfigLoaded) { await waitForReady(app) } - return { history, app, store } + return { app, store, router } } interface ITestView { @@ -538,6 +541,7 @@ export const mockDeclarationData = { }, registration: { informantsSignature: 'data:image/png;base64,abcd', + registrationNumber: '201908122365BDSS0SE1', regStatus: { type: 'REGISTERED', @@ -546,32 +550,7 @@ export const mockDeclarationData = { officeAddressLevel3: 'Gazipur', officeAddressLevel4: 'Dhaka' }, - certificates: [ - { - collector: { - relationship: 'OTHER', - affidavitFile: { - type: 'image/jpg', - data: 'data:image/png;base64,2324256' - }, - firstName: 'Doe', - lastName: 'Jane', - iD: '123456', - iDType: 'PASSPORT' - }, - certificateTemplateId: 'birth-certificate', - hasShowedVerifiedDocument: true, - payments: [ - { - paymentId: '1234', - type: 'MANUAL', - amount: 50, - outcome: 'COMPLETED', - date: '2018-10-22' - } - ] - } - ] + certificates: [{}] }, documents: {} } @@ -669,10 +648,9 @@ export const mockDeathDeclarationData = { certificates: [ { collector: { - relationship: 'MOTHER' + type: 'MOTHER' }, - hasShowedVerifiedDocument: true, - certificateTemplateId: 'death-certificate' + hasShowedVerifiedDocument: true } ] } @@ -783,27 +761,19 @@ export const mockBirthRegistrationSectionData = { certificates: [ { collector: { - relationship: 'OTHER', + type: 'OTHER', + relationship: 'Uncle', + firstName: 'Mushraful', + lastName: 'Hoque', + iDType: 'PASSPORT', + iD: '123456789', affidavitFile: { - type: 'image/jpg', - data: 'data:image/png;base64,2324256' - }, - firstName: 'Doe', - lastName: 'Jane', - iD: '123456', - iDType: 'PASSPORT' + type: 'abc', + data: 'BASE64 data' + } }, certificateTemplateId: 'birth-certificate', - hasShowedVerifiedDocument: true, - payments: [ - { - paymentId: '1234', - type: 'MANUAL', - amount: 50, - outcome: 'COMPLETED', - date: '2018-10-22' - } - ] + hasShowedVerifiedDocument: true } ] } @@ -961,24 +931,37 @@ export const mockOfflineDataDispatch = { } export async function createTestStore() { - const { store, history } = createStore() + const { store } = createStore() store.dispatch(offlineDataReady(mockOfflineDataDispatch)) await flushPromises() // This is to resolve the `referenceApi.importValidators()` promise - return { store, history } + return { store } } export async function createTestComponent( node: React.ReactElement, { store, - history, graphqlMocks, - apolloClient + apolloClient, + initialEntries, + path = '*' }: { store: AppStore - history: History graphqlMocks?: MockedProvider['props']['mocks'] apolloClient?: ApolloClient + initialEntries?: + | string[] + | { + pathname: string + state: Record< + string, + | string + | boolean + | number + | Record + > + }[] + path?: string }, options?: MountRendererProps ) { @@ -1003,22 +986,38 @@ export async function createTestComponent( ) } + const router = createMemoryRouter( + [ + { + path, + element: node + } + ], + { initialEntries } + ) - function PropProxy(props: Record) { + function PropProxy() { return withGraphQL( - - - - - - - + + + + + ) } - return mount(, options) + return { component: mount(, options), router } +} + +/** + * Create a test component with the given node and store. + * Returns component route + */ +export type TestComponentWithRouteMock = { + component: ReactWrapper<{}, {}> + router: Awaited>['router'] } export const getFileFromBase64String = ( @@ -1048,9 +1047,10 @@ export const getFileFromBase64String = ( export async function goToSection(component: ReactWrapper, nth: number) { for (let i = 0; i < nth; i++) { + await flushPromises() await waitForElement(component, '#next_section') component.find('#next_section').hostNodes().simulate('click') - await flushPromises() + await component.update() } } @@ -1075,6 +1075,11 @@ export async function goToMotherSection(component: ReactWrapper) { await waitForElement(component, '#form_section_id_mother-view-group') } +export async function goToChildSection(component: ReactWrapper) { + await goToSection(component, 1) + await waitForElement(component, '#form_section_id_child-view-group') +} + export async function getRegisterFormFromStore( store: Store, event: EventType @@ -1179,7 +1184,7 @@ export function createRouterProps< if (search) { location.search = stringify(search) } - const match: Match = { + const match = { isExact: false, path, url: path, diff --git a/packages/client/src/transformer/transformer.test.tsx b/packages/client/src/transformer/transformer.test.tsx index 6d293c251df..17d03bfe1e8 100644 --- a/packages/client/src/transformer/transformer.test.tsx +++ b/packages/client/src/transformer/transformer.test.tsx @@ -9,22 +9,19 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ import { - createTestApp, mockOfflineData, validToken, getItem, flushPromises, setItem, - userDetails + userDetails, + createTestStore } from '@client/tests/util' -import { DRAFT_BIRTH_PARENT_FORM } from '@client/navigation/routes' import { storeDeclaration, IDeclaration, SUBMISSION_STATUS } from '@client/declarations' -import { ReactWrapper } from 'enzyme' -import { History } from 'history' import { Store } from 'redux' import { v4 as uuid } from 'uuid' import { draftToGqlTransformer } from '@client/transformer' @@ -41,8 +38,6 @@ const fetch = createFetchMock(vi) fetch.enableMocks() describe('when draft data is transformed to graphql', () => { - let app: ReactWrapper - let history: History let store: Store let customDraft: IDeclaration let form: IForm @@ -55,13 +50,6 @@ describe('when draft data is transformed to graphql', () => { [JSON.stringify({ data: mockOfflineData.locations }), { status: 200 }], [JSON.stringify({ data: mockOfflineData.facilities }), { status: 200 }] ) - const testApp = await createTestApp() - app = testApp.app - await flushPromises() - app.update() - history = testApp.history - store = testApp.store - store.dispatch(getOfflineDataSuccess(JSON.stringify(mockOfflineData))) customDraft = { id: uuid(), @@ -69,16 +57,13 @@ describe('when draft data is transformed to graphql', () => { event: EventType.Birth, submissionStatus: SUBMISSION_STATUS[SUBMISSION_STATUS.DRAFT] } + ;({ store } = await createTestStore()) + await flushPromises() + + store.dispatch(getOfflineDataSuccess(JSON.stringify(mockOfflineData))) + store.dispatch(storeDeclaration(customDraft)) form = getRegisterForm(store.getState())[EventType.Birth] - history.replace( - DRAFT_BIRTH_PARENT_FORM.replace( - ':declarationId', - customDraft.id.toString() - ) - ) - - app.update() }) describe('when user is in birth registration by parent informant view', () => { @@ -106,6 +91,7 @@ describe('when draft data is transformed to graphql', () => { ).eventLocation.type ).toBe('PRIVATE_HOME') }) + it('Pass false as detailsExist on father section', () => { const data = { child: birthDraftData.child, @@ -138,6 +124,7 @@ describe('when draft data is transformed to graphql', () => { ).registration.inCompleteFields ).toContain('father/father-view-group/reasonNotApplying') }) + it('Sends inCompleteFields if in-complete data is given', () => { const data = { child: {}, @@ -156,6 +143,7 @@ describe('when draft data is transformed to graphql', () => { ).registration.inCompleteFields ).toContain('child/child-view-group/placeOfBirth') }) + it('Sends inCompleteFields when registration data is also missing', () => { const data = { child: {}, diff --git a/packages/client/src/user/userReducer.ts b/packages/client/src/user/userReducer.ts index 6b520204dc4..d79c8ef4039 100644 --- a/packages/client/src/user/userReducer.ts +++ b/packages/client/src/user/userReducer.ts @@ -17,7 +17,6 @@ import { UserSection } from '@client/forms' import { deserializeForm } from '@client/forms/deserializer/deserializer' -import { goToTeamUserList } from '@client/navigation' import { ShowCreateUserDuplicateEmailErrorToast, ShowCreateUserErrorToast, @@ -106,6 +105,7 @@ interface IUserFormDataSubmitAction { variables: { [key: string]: any } isUpdate: boolean officeLocationId: string + onSuccess: () => void } } @@ -114,7 +114,8 @@ export function submitUserFormData( mutation: any, variables: { [key: string]: any }, officeLocationId: string, - isUpdate = false + isUpdate = false, + onSuccess: () => void ): IUserFormDataSubmitAction { return { type: SUBMIT_USER_FORM_DATA, @@ -123,7 +124,8 @@ export function submitUserFormData( mutation, variables, officeLocationId, - isUpdate + isUpdate, + onSuccess } } } @@ -137,20 +139,20 @@ export function clearUserFormData() { interface ISubmitSuccessAction { type: typeof SUBMIT_USER_FORM_DATA_SUCCESS payload: { - locationId: string isUpdate: boolean + onSuccess: () => void } } function submitSuccess( - locationId: string, - isUpdate = false + isUpdate: boolean, + onSuccess: () => void ): ISubmitSuccessAction { return { type: SUBMIT_USER_FORM_DATA_SUCCESS, payload: { - locationId, - isUpdate + isUpdate, + onSuccess } } } @@ -344,7 +346,7 @@ export const userFormReducer: LoopReducer = ( } case SUBMIT_USER_FORM_DATA: - const { client, mutation, variables, officeLocationId, isUpdate } = ( + const { client, mutation, variables, isUpdate } = ( action as IUserFormDataSubmitAction ).payload const token = getToken() @@ -363,7 +365,7 @@ export const userFormReducer: LoopReducer = ( }), { successActionCreator: () => - submitSuccess(officeLocationId, isUpdate), + submitSuccess(isUpdate, action.payload.onSuccess), failActionCreator: submitFail } ) @@ -381,19 +383,19 @@ export const userFormReducer: LoopReducer = ( case SUBMIT_USER_FORM_DATA_SUCCESS: const list = Cmd.list< | ReturnType - | ReturnType | ReturnType >([ - Cmd.action(clearUserFormData()), - Cmd.action(goToTeamUserList(action.payload.locationId)), Cmd.action( showSubmitFormSuccessToast( action.payload.isUpdate ? TOAST_MESSAGES.UPDATE_SUCCESS : TOAST_MESSAGES.SUCCESS ) - ) + ), + Cmd.run(action.payload.onSuccess), + Cmd.action(clearUserFormData()) ]) + return loop({ ...state, submitting: false, submissionError: false }, list) case SUBMIT_USER_FORM_DATA_FAIL: diff --git a/packages/client/src/utils.ts b/packages/client/src/utils.ts index 5cf8c421449..7dddcd52620 100644 --- a/packages/client/src/utils.ts +++ b/packages/client/src/utils.ts @@ -11,7 +11,7 @@ import { storage } from '@client/storage' import { APPLICATION_VERSION, LANG_EN } from '@client/utils/constants' import { IUserData } from '@client/declarations' -import React, { useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { GetSystemRolesQuery, Role } from '@client/utils/gateway' export async function validateApplicationVersion() { diff --git a/packages/client/src/utils/userUtils.ts b/packages/client/src/utils/userUtils.ts index 1054db87214..74bd48f3b60 100644 --- a/packages/client/src/utils/userUtils.ts +++ b/packages/client/src/utils/userUtils.ts @@ -14,6 +14,7 @@ import { createNamesMap } from './data-formatting' import { LANG_EN } from './constants' import { useSelector } from 'react-redux' import { IStoreState } from '@client/store' +import { ITokenPayload } from './authUtils' export const USER_DETAILS = 'USER_DETAILS' @@ -54,6 +55,12 @@ export function getUserName(userDetails: UserDetails | null) { ) } +export function useAuthentication() { + return useSelector( + (state) => state.profile.tokenPayload + ) +} + export function useUserName() { return useSelector((state) => { const { userDetails } = state.profile diff --git a/packages/client/src/v2-events/.eslintrc.js b/packages/client/src/v2-events/.eslintrc.js index 556e99e91d0..a8fc18abd6f 100644 --- a/packages/client/src/v2-events/.eslintrc.js +++ b/packages/client/src/v2-events/.eslintrc.js @@ -10,6 +10,79 @@ */ module.exports = { rules: { + '@typescript-eslint/await-thenable': 2, + '@typescript-eslint/no-empty-function': 'warn', + '@typescript-eslint/consistent-type-definitions': 1, + '@typescript-eslint/default-param-last': 2, + '@typescript-eslint/no-dynamic-delete': 2, + '@typescript-eslint/no-explicit-any': 1, + '@typescript-eslint/no-floating-promises': 2, + '@typescript-eslint/no-misused-promises': 2, + '@typescript-eslint/no-unnecessary-boolean-literal-compare': 2, + '@typescript-eslint/no-non-null-assertion': 2, + '@typescript-eslint/no-unnecessary-condition': 1, + '@typescript-eslint/no-unsafe-argument': 1, + '@typescript-eslint/no-unsafe-return': 1, + '@typescript-eslint/prefer-includes': 1, + '@typescript-eslint/promise-function-async': 2, + '@typescript-eslint/require-await': 2, + '@typescript-eslint/return-await': 2, + '@typescript-eslint/switch-exhaustiveness-check': 2, + 'func-style': [ + 'error', + 'declaration', + { + allowArrowFunctions: true + } + ], + 'no-restricted-syntax': [ + 'error', + { + selector: + ':matches(Program > VariableDeclaration) > VariableDeclarator > ArrowFunctionExpression', + message: 'Top-level arrow functions are not allowed.' + } + ], + 'react/jsx-no-literals': 1, + 'no-shadow': 1, + 'no-undef-init': 2, + 'no-return-assign': 2, + 'vars-on-top': 1, + 'block-spacing': ['warn', 'always'], + curly: ['warn', 'all'], + 'no-nested-ternary': 'warn', + 'react/destructuring-assignment': 1, + 'no-multiple-empty-lines': 1, + 'prefer-const': 1, + 'block-scoped-var': 1, + 'import/order': [ + 'error', + { + pathGroups: [ + { + pattern: '@opencrvs/**', + group: 'external', + position: 'after' + }, + { + pattern: '@client/**', + group: 'external', + position: 'after' + } + ], + pathGroupsExcludedImportTypes: ['builtin'] + } + ], + 'import/no-cycle': 1, + 'max-lines': ['warn', 600], + 'react/jsx-sort-props': [ + 1, + { + reservedFirst: true, + callbacksLast: true, + shorthandFirst: true + } + ], 'no-restricted-imports': [ 'error', { diff --git a/packages/client/src/v2-events/components/IconWithName.tsx b/packages/client/src/v2-events/components/IconWithName.tsx new file mode 100644 index 00000000000..dbe625005d2 --- /dev/null +++ b/packages/client/src/v2-events/components/IconWithName.tsx @@ -0,0 +1,98 @@ +/* + * 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 * as React from 'react' +import styled from 'styled-components' +import { DeclarationIcon } from '@opencrvs/components/lib/icons' + +const Flex = styled.div` + display: flex; + align-items: center; + gap: 16px; + @media (max-width: ${({ theme }) => theme.grid.breakpoints.lg}px) { + align-items: flex-start; + } +` + +interface IIconWith { + status?: string + name: React.ReactNode + event?: string + isDuplicate?: boolean + isValidatedOnReview?: boolean + isArchived?: boolean +} + +const STATUS_TO_COLOR_MAP: { [key: string]: string } = { + OUTBOX: 'grey', + ARCHIVED: 'grey', + DRAFT: 'purple', + IN_PROGRESS: 'purple', + DECLARED: 'orange', + REJECTED: 'red', + VALIDATED: 'grey', + REGISTERED: 'green', + CERTIFIED: 'teal', + CORRECTION_REQUESTED: 'blue', + WAITING_VALIDATION: 'teal', + SUBMITTED: 'orange', + SUBMITTING: 'orange', + ISSUED: 'blue' +} + +const Icon = styled.div` + flex-shrink: 0; + display: flex; + @media (min-width: ${({ theme }) => theme.grid.breakpoints.lg}px) { + align-items: flex-end; + } + width: 24px; +` + +function IconComp({ + status, + isValidatedOnReview, + isArchived +}: { + status: string + isValidatedOnReview?: boolean + isArchived?: boolean +}) { + return ( + + + + ) +} + +export function IconWithName({ + status, + name, + isValidatedOnReview, + isArchived +}: IIconWith) { + return ( + + {status && ( + + )} + {name} + + ) +} diff --git a/packages/client/src/v2-events/components/LoadingIndicator.tsx b/packages/client/src/v2-events/components/LoadingIndicator.tsx new file mode 100644 index 00000000000..f1cec0a1be3 --- /dev/null +++ b/packages/client/src/v2-events/components/LoadingIndicator.tsx @@ -0,0 +1,125 @@ +/* + * 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 * as React from 'react' +import { injectIntl, WrappedComponentProps as IntlShapeProps } from 'react-intl' +import styled from 'styled-components' +import { ConnectionError } from '@opencrvs/components/lib/icons' +import { Spinner } from '@opencrvs/components/lib/Spinner' +import { errorMessages, constantsMessages } from '@client/v2-events/messages' +import { useOnlineStatus } from '@client/utils' + +const ErrorText = styled.div` + color: ${({ theme }) => theme.colors.negative}; + ${({ theme }) => theme.fonts.reg16}; + text-align: center; + margin-top: 100px; +` + +const ConnectivityContainer = styled.div` + width: 100%; + display: flex; + justify-content: center; +` +const NoConnectivity = styled(ConnectionError)` + width: 24px; + margin-right: 8px; +` +const Wrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; +` + +const LoadingContainer = styled.div` + width: 100%; + padding-left: 8px; + @media (max-width: ${({ theme }) => theme.grid.breakpoints.lg}px) { + display: flex; + padding-left: 0px; + margin: auto; + align-items: center; + justify-content: center; + } +` +const Text = styled.div` + ${({ theme }) => theme.fonts.reg16}; + text-align: center; +` + +const MobileViewContainer = styled.div<{ noDeclaration?: boolean }>` + padding-top: 16px; + @media (max-width: ${({ theme }) => theme.grid.breakpoints.lg}px) { + position: ${({ noDeclaration }) => (noDeclaration ? `fixed` : `relative`)}; + left: 0; + right: 0; + padding-top: 0; + padding-bottom: 16px; + ${({ noDeclaration }) => (noDeclaration ? `top:55%; padding: 0;` : ``)} + } +` + +interface IBaseLoadingProps { + loading: boolean + hasError?: boolean + noDeclaration?: boolean +} + +type IProps = IBaseLoadingProps & IntlShapeProps & IOnlineStatusProps + +function LoadingIndicatorComp({ + loading, + noDeclaration, + hasError, + intl, + isOnline +}: IProps) { + return ( + + {isOnline && loading && ( + + + + )} + + {isOnline && hasError && ( + + {intl.formatMessage(errorMessages.queryError)} + + )} + {!isOnline && ( + + + + {intl.formatMessage(constantsMessages.noConnection)} + + + )} + + + ) +} + +export function withOnlineStatus( + WrappedComponent: React.ComponentType +) { + return function WithOnlineStatus(props: T) { + const isOnline = useOnlineStatus() + return + } +} + +export interface IOnlineStatusProps { + isOnline: boolean +} + +export const LoadingIndicator = injectIntl( + withOnlineStatus(LoadingIndicatorComp) +) diff --git a/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx b/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx index 6f7e8ac3185..47d0c29c4c8 100644 --- a/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx +++ b/packages/client/src/v2-events/components/forms/FormFieldGenerator.tsx @@ -8,79 +8,10 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { FetchButtonField } from '@client/components/form/FetchButton' -import { InputField } from '@client/components/form/InputField' -import { - BIG_NUMBER, - BULLET_LIST, - BUTTON, - CHECKBOX, - CHECKBOX_GROUP, - DATE, - DATE_RANGE_PICKER, - DependencyInfo, - DIVIDER, - FETCH_BUTTON, - FIELD_GROUP_TITLE, - FIELD_WITH_DYNAMIC_DEFINITIONS, - HEADING3, - HIDDEN, - HTTP, - IDateRangePickerValue, - IDynamicFormField, - IFormField, - IFormFieldValue, - IFormSectionData, - Ii18nFormField, - Ii18nTextFormField, - InitialValue, - LINK, - LOCATION_SEARCH_INPUT, - NUMBER, - PARAGRAPH, - RADIO_GROUP, - SELECT_WITH_DYNAMIC_OPTIONS, - SELECT_WITH_OPTIONS, - SUBSECTION_HEADER, - TEL, - TEXT, - TEXTAREA, - TIME, - WARNING -} from '@client/forms' -import { buttonMessages } from '@client/i18n/messages/buttons' -import { IAdvancedSearchFormState } from '@client/search/advancedSearch/utils' -import { Checkbox, CheckboxGroup } from '@opencrvs/components/lib/Checkbox' -import { DateField } from '@opencrvs/components/lib/DateField' -import { ErrorText } from '@opencrvs/components/lib/ErrorText' -import { Link } from '@opencrvs/components/lib/Link' -import { RadioGroup } from '@opencrvs/components/lib/Radio' -import { Select } from '@opencrvs/components/lib/Select' -import { Text } from '@opencrvs/components/lib/Text' -import { TextArea } from '@opencrvs/components/lib/TextArea' -import { TextInput } from '@opencrvs/components/lib/TextInput' -import { TimeField } from '@opencrvs/components/lib/TimeField' +/* eslint-disable */ import * as React from 'react' import styled, { keyframes } from 'styled-components' -import { - evalExpressionInFieldDefinition, - flatten, - getConditionalActionsForField, - getDependentFields, - getFieldType, - handleInitialValue, - internationaliseFieldObject, - unflatten -} from './utils' -import { Errors, getValidationErrorsForForm } from './validation' - -import { DateRangePickerForFormField } from '@client/components/DateRangePickerForFormField' -import { isMobileDevice } from '@client/utils/commonUtils' -import { REGEXP_NUMBER_INPUT_NON_NUMERIC } from '@client/utils/constants' -import { BulletList, Divider } from '@opencrvs/components' -import { Heading2, Heading3 } from '@opencrvs/components/lib/Headings/Headings' -import { LocationSearch } from '@opencrvs/components/lib/LocationSearch' import { Field, FieldProps, @@ -95,6 +26,30 @@ import { MessageDescriptor, useIntl } from 'react-intl' +import { FieldConfig } from '@opencrvs/commons' +import { TextInput } from '@opencrvs/components/lib/TextInput' +import { Text } from '@opencrvs/components/lib/Text' +import { DateField } from '@opencrvs/components/lib/DateField' +import { IAdvancedSearchFormState } from '@client/search/advancedSearch/utils' +import { + DATE, + HIDDEN, + IFormFieldValue, + IFormSectionData, + PARAGRAPH, + TEXT +} from '@client/forms' +import { InputField } from '@client/components/form/InputField' +import { + evalExpressionInFieldDefinition, + flatten, + getConditionalActionsForField, + getDependentFields, + handleInitialValue, + hasInitialValueDependencyInfo, + unflatten +} from './utils' +import { Errors, getValidationErrorsForForm } from './validation' const fadeIn = keyframes` from { opacity: 0; } @@ -109,23 +64,9 @@ const FormItem = styled.div<{ ignoreBottomMargin ? '0px' : '22px'}; ` -function handleSelectFocus(id: string, isSearchable: boolean) { - if (isMobileDevice() && isSearchable) { - setTimeout(() => { - const inputElement = document.getElementById(`${id}-form-input`) - - if (inputElement) { - inputElement.scrollIntoView({ - behavior: 'smooth' - }) - } - }, 20) - } -} - -type GeneratedInputFieldProps = { - fieldDefinition: Ii18nFormField - fields: IFormField[] +interface GeneratedInputFieldProps { + fieldDefinition: FieldConfig + fields: FieldConfig[] values: IFormSectionData setFieldValue: (name: string, value: IFormFieldValue) => void onClick?: () => void @@ -159,119 +100,37 @@ const GeneratedInputField = React.memo( fields, values }) => { + const intl = useIntl() + const inputFieldProps = { - id: fieldDefinition.name, - label: fieldDefinition.label, - helperText: fieldDefinition.helperText, - tooltip: fieldDefinition.tooltip, - description: fieldDefinition.description, + id: fieldDefinition.id, + label: intl.formatMessage(fieldDefinition.label), + // helperText: fieldDefinition.helperText, + // tooltip: fieldDefinition.tooltip, + // description: fieldDefinition.description, required: fieldDefinition.required, disabled: fieldDefinition.disabled, - prefix: fieldDefinition.prefix, - postfix: fieldDefinition.postfix, - unit: fieldDefinition.unit, - hideAsterisk: fieldDefinition.hideAsterisk, - hideInputHeader: fieldDefinition.hideHeader, + // prefix: fieldDefinition.prefix, + // postfix: fieldDefinition.postfix, + // unit: fieldDefinition.unit, + // hideAsterisk: fieldDefinition.hideAsterisk, + // hideInputHeader: fieldDefinition.hideHeader, error, touched } - const intl = useIntl() - const onChangeGroupInput = React.useCallback( - (val: string) => setFieldValue(fieldDefinition.name, val), - [fieldDefinition.name, setFieldValue] - ) - const inputProps = { - id: fieldDefinition.name, + id: fieldDefinition.id, + name: fieldDefinition.id, onChange, onBlur, value, disabled: fieldDefinition.disabled ?? disabled, error: Boolean(error), touched: Boolean(touched), - placeholder: fieldDefinition.placeholder - } - if (fieldDefinition.type === SELECT_WITH_OPTIONS) { - return ( - -