diff --git a/src/App.tsx b/src/App.tsx index 31464dc7ec..235dabde84 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,12 +6,14 @@ import { useAppSelector } from 'src/common/hooks/useAppSelector'; import { Entrypoint } from 'src/features/entrypoint/Entrypoint'; import { PartySelection } from 'src/features/instantiate/containers/PartySelection'; import { UnknownError } from 'src/features/instantiate/containers/UnknownError'; +import { PdfActions } from 'src/features/pdf/data/pdfSlice'; import { makeGetAllowAnonymousSelector } from 'src/selectors/getAllowAnonymous'; import { makeGetHasErrorsSelector } from 'src/selectors/getErrors'; import { selectAppName, selectAppOwner } from 'src/selectors/language'; import { ProcessWrapper } from 'src/shared/containers/ProcessWrapper'; import { QueueActions } from 'src/shared/resources/queue/queueSlice'; import { httpGet } from 'src/utils/network/networking'; +import { shouldGeneratePdf } from 'src/utils/pdf'; import { getEnvironmentLoginUrl, refreshJwtTokenUrl } from 'src/utils/urls/appUrlHelper'; // 1 minute = 60.000ms @@ -47,6 +49,7 @@ export const App = () => { window.addEventListener('scroll', refreshJwtToken); window.addEventListener('onfocus', refreshJwtToken); window.addEventListener('keydown', refreshJwtToken); + window.addEventListener('hashchange', setPdfState); } function removeEventListeners() { @@ -54,6 +57,13 @@ export const App = () => { window.removeEventListener('scroll', refreshJwtToken); window.removeEventListener('onfocus', refreshJwtToken); window.removeEventListener('keydown', refreshJwtToken); + window.removeEventListener('hashchange', setPdfState); + } + + function setPdfState() { + if (shouldGeneratePdf()) { + dispatch(PdfActions.pdfStateChanged()); + } } function refreshJwtToken() { diff --git a/src/__mocks__/initialStateMock.ts b/src/__mocks__/initialStateMock.ts index d4e0ee917c..4886f91a7a 100644 --- a/src/__mocks__/initialStateMock.ts +++ b/src/__mocks__/initialStateMock.ts @@ -80,6 +80,12 @@ export function getInitialStateMock(customStates?: Partial): IRun parties: [partyMock], selectedParty: partyMock, }, + pdf: { + readyForPrint: false, + pdfFormat: null, + method: null, + error: null, + }, process: { error: null, taskType: null, @@ -135,8 +141,6 @@ export function getInitialStateMock(customStates?: Partial): IRun optionState: { options: {}, error: null, - optionsCount: 0, - optionsLoadedCount: 0, loading: false, }, dataListState: { diff --git a/src/components/summary/SummaryComponent.tsx b/src/components/summary/SummaryComponent.tsx index 19a5a1910d..7dae0e8396 100644 --- a/src/components/summary/SummaryComponent.tsx +++ b/src/components/summary/SummaryComponent.tsx @@ -207,7 +207,7 @@ export function SummaryComponent(_props: ISummaryComponent) { lg={displayGrid?.lg || false} xl={displayGrid?.xl || false} data-testid={`summary-${id}`} - className={cn(pageBreakStyles(summaryComponent ?? formComponent))} + className={cn(pageBreakStyles(summaryComponent?.pageBreak ?? formComponent?.pageBreak))} > (false); + const classes = useStyles(); const handlePopoverClick = (event: React.MouseEvent): void => { event.stopPropagation(); @@ -39,6 +50,7 @@ export function HelpTextContainer({ language, helpText }: IHelpTextContainerProp ({ + reducer: (state, action) => { + state.layouts = { ...state.layouts, ...action.payload }; + }, + }), }, })); diff --git a/src/features/form/layout/update/updateFormLayoutSagas.ts b/src/features/form/layout/update/updateFormLayoutSagas.ts index 0946a9d842..b8430b6b48 100644 --- a/src/features/form/layout/update/updateFormLayoutSagas.ts +++ b/src/features/form/layout/update/updateFormLayoutSagas.ts @@ -549,6 +549,8 @@ export function* watchInitialCalculatePageOrderAndMoveToNextPageSaga(): SagaIter skipMoveToNext: true, }), ); + } else { + yield put(FormLayoutActions.calculatePageOrderAndMoveToNextPageRejected({ error: null })); } } } diff --git a/src/features/pdf/AutomaticPDFLayout.tsx b/src/features/pdf/AutomaticPDFLayout.tsx deleted file mode 100644 index f0b370569b..0000000000 --- a/src/features/pdf/AutomaticPDFLayout.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; - -import type { IPdfFormat } from '.'; - -import { SummaryComponent } from 'src/components/summary/SummaryComponent'; -import css from 'src/features/pdf/PDFView.module.css'; -import { ComponentType } from 'src/layout'; -import { GenericComponent } from 'src/layout/GenericComponent'; -import { getLayoutComponentObject } from 'src/layout/LayoutComponent'; -import type { ILayoutCompInstanceInformation } from 'src/layout/InstanceInformation/types'; -import type { ComponentExceptGroupAndSummary, RenderableGenericComponent } from 'src/layout/layout'; -import type { LayoutNode, LayoutRootNodeCollection } from 'src/utils/layout/hierarchy'; - -interface IAutomaticPDFLayout { - layouts: LayoutRootNodeCollection<'resolved'>; - pdfFormat: IPdfFormat | null; - pageOrder: string[]; - hidden: string[]; -} - -const AutomaticPDFSummaryComponent = ({ - node, - pageRef, - excludedChildren, -}: { - node: LayoutNode<'resolved'>; - pageRef: string; - excludedChildren: string[]; -}) => { - const layoutComponent = getLayoutComponentObject(node.item.type as ComponentExceptGroupAndSummary); - - if (node.item.type === 'Group' || layoutComponent?.getComponentType() === ComponentType.Form) { - return ( - - ); - } - if (layoutComponent?.getComponentType() === ComponentType.Presentation) { - return ( - - ); - } - return null; -}; - -export const AutomaticPDFLayout = ({ layouts, pdfFormat, pageOrder, hidden }: IAutomaticPDFLayout) => { - const excludedPages = new Set(pdfFormat?.excludedPages); - const excludedComponents = new Set(pdfFormat?.excludedComponents); - const hiddenPages = new Set(hidden); - - const pdfLayouts = Object.entries(layouts.all()) - .filter(([pageRef]) => !excludedPages.has(pageRef)) - .filter(([pageRef]) => !hiddenPages.has(pageRef)) - .filter(([pageRef]) => pageOrder.includes(pageRef)) - .sort(([pA], [pB]) => pageOrder.indexOf(pA) - pageOrder.indexOf(pB)); - - const instanceInformationProps: ILayoutCompInstanceInformation = { - id: '__pdf__instance-information', - type: 'InstanceInformation', - elements: { - dateSent: true, - sender: true, - receiver: true, - referenceNumber: true, - }, - pageBreak: { - breakAfter: 'always', - }, - }; - - return ( - <> -
- -
- {pdfLayouts.map(([pageRef, layout]) => { - return layout.children().map((node) => { - if (excludedComponents.has(node.item.id)) { - return null; - } - - return ( -
- -
- ); - }); - })} - - ); -}; diff --git a/src/features/pdf/CustomPDFLayout.tsx b/src/features/pdf/CustomPDFLayout.tsx deleted file mode 100644 index 8f39225fbf..0000000000 --- a/src/features/pdf/CustomPDFLayout.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; - -import { SummaryComponent } from 'src/components/summary/SummaryComponent'; -import { DisplayGroupContainer } from 'src/features/form/containers/DisplayGroupContainer'; -import { mapGroupComponents } from 'src/features/form/containers/formUtils'; -import css from 'src/features/pdf/PDFView.module.css'; -import { ComponentType } from 'src/layout'; -import { GenericComponent } from 'src/layout/GenericComponent'; -import { getLayoutComponentObject } from 'src/layout/LayoutComponent'; -import { topLevelComponents } from 'src/utils/formLayout'; -import type { ComponentExceptGroupAndSummary, ILayout, ILayoutComponentOrGroup } from 'src/layout/layout'; - -interface ICustomPDFLayout { - layout: ILayout; -} - -const CustomPDFSummaryComponent = ({ component, layout }: { component: ILayoutComponentOrGroup; layout: ILayout }) => { - const layoutComponent = getLayoutComponentObject(component.type as ComponentExceptGroupAndSummary); - - if (component.type === 'Group') { - return ( - ( - - )} - /> - ); - } else if (component.type === 'Summary') { - return ( - - ); - } else if (layoutComponent?.getComponentType() === ComponentType.Presentation) { - return ( - - ); - } else { - console.warn(`Type: "${component.type}" is not allowed in PDF.`); - return null; - } -}; - -export const CustomPDFLayout = ({ layout }: ICustomPDFLayout) => ( - <> - {topLevelComponents(layout).map((component) => ( -
- -
- ))} - -); diff --git a/src/features/pdf/PDFView.tsx b/src/features/pdf/PDFView.tsx index d0962f2eed..d368ffb0e6 100644 --- a/src/features/pdf/PDFView.tsx +++ b/src/features/pdf/PDFView.tsx @@ -2,92 +2,70 @@ import React from 'react'; import cn from 'classnames'; -import type { IPdfFormat } from '.'; - import { useAppSelector } from 'src/common/hooks/useAppSelector'; -import { AutomaticPDFLayout } from 'src/features/pdf/AutomaticPDFLayout'; -import { CustomPDFLayout } from 'src/features/pdf/CustomPDFLayout'; +import { SummaryComponent } from 'src/components/summary/SummaryComponent'; +import { DisplayGroupContainer } from 'src/features/form/containers/DisplayGroupContainer'; +import { mapGroupComponents } from 'src/features/form/containers/formUtils'; +import { PDF_LAYOUT_NAME } from 'src/features/pdf/data/pdfSlice'; import css from 'src/features/pdf/PDFView.module.css'; +import { ComponentType } from 'src/layout'; +import { GenericComponent } from 'src/layout/GenericComponent'; +import { getLayoutComponentObject } from 'src/layout/LayoutComponent'; import { ReadyForPrint } from 'src/shared/components/ReadyForPrint'; -import { getCurrentTaskDataElementId } from 'src/utils/appMetadata'; -import { useExprContext } from 'src/utils/layout/ExprContext'; -import { httpGet } from 'src/utils/network/networking'; -import { getPdfFormatUrl } from 'src/utils/urls/appUrlHelper'; +import { topLevelComponents } from 'src/utils/formLayout'; +import type { ComponentExceptGroupAndSummary, ILayout, ILayoutComponentOrGroup } from 'src/layout/layout'; interface PDFViewProps { appName: string; appOwner?: string; } -export const PDFView = ({ appName, appOwner }: PDFViewProps) => { - const layouts = useAppSelector((state) => state.formLayout.layouts); - const layoutSets = useAppSelector((state) => state.formLayout.layoutsets); - const excludedPages = useAppSelector((state) => state.formLayout.uiConfig.excludePageFromPdf); - const excludedComponents = useAppSelector((state) => state.formLayout.uiConfig.excludeComponentFromPdf); - const pageOrder = useAppSelector((state) => state.formLayout.uiConfig.tracks.order); - const hidden = useAppSelector((state) => state.formLayout.uiConfig.tracks.hidden); - const pdfLayoutName = useAppSelector((state) => state.formLayout.uiConfig.pdfLayoutName); - const optionsLoading = useAppSelector((state) => state.optionState.loading); - const dataListLoading = useAppSelector((state) => state.dataListState.loading); - const repeatingGroups = useAppSelector((state) => state.formLayout.uiConfig.repeatingGroups); - const applicationSettings = useAppSelector((state) => state.applicationSettings.applicationSettings); - const applicationMetadata = useAppSelector((state) => state.applicationMetadata.applicationMetadata); - const formData = useAppSelector((state) => state.formData.formData); - const unsavedChanges = useAppSelector((state) => state.formData.unsavedChanges); - const hiddenFields = useAppSelector((state) => state.formLayout.uiConfig.hiddenFields); - const parties = useAppSelector((state) => state.party.parties); - const language = useAppSelector((state) => state.language.language); - const textResources = useAppSelector((state) => state.textResources.resources); - const instance = useAppSelector((state) => state.instanceData.instance); - const allOrgs = useAppSelector((state) => state.organisationMetaData.allOrgs); - const profile = useAppSelector((state) => state.profile.profile); - const nodeLayouts = useExprContext(); +const PDFComponent = ({ component, layout }: { component: ILayoutComponentOrGroup; layout: ILayout }) => { + const layoutComponent = getLayoutComponentObject(component.type as ComponentExceptGroupAndSummary); - // Custom pdf layout - const pdfLayout = pdfLayoutName && layouts ? layouts[pdfLayoutName] : undefined; + if (component.type === 'Group') { + return ( + ( + + )} + /> + ); + } else if (component.type === 'Summary') { + return ( + + ); + } else if (layoutComponent?.getComponentType() === ComponentType.Presentation) { + return ( + + ); + } else { + console.warn(`Type: "${component.type}" is not allowed in PDF.`); + return null; + } +}; - // Fetch PdfFormat from backend - const [pdfFormat, setPdfFormat] = React.useState(null); - React.useEffect(() => { - if ( - applicationMetadata && - instance && - (instance?.data?.length === 1 || layoutSets) && - excludedPages && - excludedComponents && - !pdfLayout && - !unsavedChanges - ) { - const dataGuid = getCurrentTaskDataElementId(applicationMetadata, instance, layoutSets); - if (typeof dataGuid === 'string') { - const url = getPdfFormatUrl(instance.id, dataGuid); - httpGet(url) - .then((pdfFormat: IPdfFormat) => setPdfFormat(pdfFormat)) - .catch(() => setPdfFormat({ excludedPages, excludedComponents })); - } else { - setPdfFormat({ excludedPages, excludedComponents }); - } - } - }, [applicationMetadata, excludedComponents, excludedPages, instance, layoutSets, pdfLayout, unsavedChanges]); +export const PDFView = ({ appName, appOwner }: PDFViewProps) => { + const { readyForPrint, method } = useAppSelector((state) => state.pdf); + const { layouts, uiConfig } = useAppSelector((state) => state.formLayout); + + const pdfLayoutName = method === 'custom' ? uiConfig.pdfLayoutName : method === 'auto' ? PDF_LAYOUT_NAME : undefined; + const pdfLayout = pdfLayoutName && layouts?.[pdfLayoutName]; - if ( - optionsLoading || - dataListLoading || - !layouts || - !hidden || - !repeatingGroups || - !applicationSettings || - !formData || - !hiddenFields || - !parties || - !language || - !textResources || - !allOrgs || - !profile || - !pageOrder || - (!pdfFormat && !pdfLayout) || - !nodeLayouts - ) { + if (!readyForPrint || !pdfLayout) { return null; } @@ -102,16 +80,17 @@ export const PDFView = ({ appName, appOwner }: PDFViewProps) => { {appOwner}

)} - {typeof pdfLayout !== 'undefined' ? ( - - ) : ( -