From b718e3291653634f1e64b5de1a0c30cf6a1f7185 Mon Sep 17 00:00:00 2001 From: walldenfilippa Date: Wed, 11 Feb 2026 16:08:19 +0100 Subject: [PATCH 1/2] added PageValidation on layoutSets and layoutSettings --- src/codegen/Common.ts | 3 +- .../form/layoutSets/LayoutSetsProvider.tsx | 5 +++ .../layoutSettings/LayoutSettingsContext.tsx | 24 +++++++++++-- src/features/navigation/components/Page.tsx | 34 ++++++++++++------- src/hooks/usePageValidation.ts | 24 +++++++++---- 5 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/codegen/Common.ts b/src/codegen/Common.ts index f7b8e2c8ef..dfa0c51be6 100644 --- a/src/codegen/Common.ts +++ b/src/codegen/Common.ts @@ -788,6 +788,7 @@ const common = { 'Name of a custom layout file to use for PDF creation instead of the automatically generated PDF.', ), ), + new CG.prop('validationOnNavigation', CG.common('PageValidation').optional()), ), INavigationBasePageGroup: () => new CG.obj( @@ -843,7 +844,6 @@ const common = { .setTitle('Layout settings') .setDescription('Settings regarding layout pages and components'), - // Layout sets: ILayoutSets: () => new CG.obj( new CG.prop('$schema', new CG.str().optional()), @@ -853,6 +853,7 @@ const common = { .setTitle('Layout sets') .setDescription('List of layout sets for different data types'), ), + new CG.prop('validationOnNavigation', CG.common('PageValidation').optional()), new CG.prop('uiSettings', CG.common('GlobalPageSettings').optional()), ) .setTitle('Layout sets') diff --git a/src/features/form/layoutSets/LayoutSetsProvider.tsx b/src/features/form/layoutSets/LayoutSetsProvider.tsx index 09c07fd042..9ae46446d2 100644 --- a/src/features/form/layoutSets/LayoutSetsProvider.tsx +++ b/src/features/form/layoutSets/LayoutSetsProvider.tsx @@ -64,3 +64,8 @@ export const useLaxGlobalUISettings = () => { const layoutSets = useLaxCtx(); return layoutSets !== ContextNotProvided ? layoutSets.uiSettings : ContextNotProvided; }; + +export const useLaxLayoutSetsPageValidation = () => { + const layoutSets = useLaxCtx(); + return layoutSets !== ContextNotProvided ? layoutSets.validationOnNavigation : ContextNotProvided; +}; diff --git a/src/features/form/layoutSettings/LayoutSettingsContext.tsx b/src/features/form/layoutSettings/LayoutSettingsContext.tsx index 3241600abe..d0d776ffbb 100644 --- a/src/features/form/layoutSettings/LayoutSettingsContext.tsx +++ b/src/features/form/layoutSettings/LayoutSettingsContext.tsx @@ -7,11 +7,19 @@ import { useAppQueries } from 'src/core/contexts/AppQueriesProvider'; import { ContextNotProvided } from 'src/core/contexts/context'; import { delayedContext } from 'src/core/contexts/delayedContext'; import { createQueryContext } from 'src/core/contexts/queryContext'; -import { useLaxGlobalUISettings } from 'src/features/form/layoutSets/LayoutSetsProvider'; +import { + useLaxGlobalUISettings, + useLaxLayoutSetsPageValidation, +} from 'src/features/form/layoutSets/LayoutSetsProvider'; import { useLayoutSetIdFromUrl } from 'src/features/form/layoutSets/useCurrentLayoutSet'; import { useShallowMemo } from 'src/hooks/useShallowMemo'; import type { QueryDefinition } from 'src/core/queries/usePrefetchQuery'; -import type { GlobalPageSettings, ILayoutSettings, NavigationPageGroup } from 'src/layout/common.generated'; +import type { + GlobalPageSettings, + ILayoutSettings, + NavigationPageGroup, + PageValidation, +} from 'src/layout/common.generated'; // Also used for prefetching @see formPrefetcher.ts export function useLayoutSettingsQueryDef(layoutSetId?: string): QueryDefinition { @@ -71,6 +79,7 @@ function processData(settings: ILayoutSettings | null): ProcessedLayoutSettings showProgress: settings.pages.showProgress, taskNavigation: settings.pages.taskNavigation?.map((g) => ({ ...g, id: uuidv4() })), }), + validationOnNavigation: settings.pages.validationOnNavigation, pdfLayoutName: settings.pages.pdfLayoutName, }; } @@ -96,6 +105,7 @@ interface ProcessedLayoutSettings { order: string[]; groups?: NavigationPageGroup[]; pageSettings: GlobalPageSettings; + validationOnNavigation?: PageValidation; pdfLayoutName?: string; } @@ -131,6 +141,16 @@ const defaults: Required = { taskNavigation: [], }; +export const useValidationOnNavigation = (): PageValidation | undefined => { + const layoutSettings = useLaxCtx(); + const layoutSetsPageValidation = useLaxLayoutSetsPageValidation(); + + const settingsValidation = layoutSettings !== ContextNotProvided ? layoutSettings.validationOnNavigation : undefined; + const layoutSetsValidation = layoutSetsPageValidation !== ContextNotProvided ? layoutSetsPageValidation : undefined; + + return settingsValidation ?? layoutSetsValidation; +}; + export const usePageSettings = (): Required => { const globalUISettings = useLaxGlobalUISettings(); const layoutSettings = useLaxCtx(); diff --git a/src/features/navigation/components/Page.tsx b/src/features/navigation/components/Page.tsx index e97a8a2fc3..f85221498f 100644 --- a/src/features/navigation/components/Page.tsx +++ b/src/features/navigation/components/Page.tsx @@ -5,7 +5,6 @@ import cn from 'classnames'; import { Spinner } from 'src/app-components/loading/Spinner/Spinner'; import { useIsProcessing } from 'src/core/contexts/processingContext'; -import { useLayoutCollection } from 'src/features/form/layout/LayoutsContext'; import { Lang } from 'src/features/language/Lang'; import { useLanguage } from 'src/features/language/useLanguage'; import classes from 'src/features/navigation/components/Page.module.css'; @@ -14,7 +13,7 @@ import { useNavigationIsPrevented } from 'src/features/navigation/utils'; import { useOnPageNavigationValidation } from 'src/features/validation/callbacks/onPageNavigationValidation'; import { useNavigationParam } from 'src/hooks/navigation'; import { useNavigatePage } from 'src/hooks/useNavigatePage'; -import type { ILayoutFile } from 'src/layout/common.generated'; +import { usePageValidationConfigForPage } from 'src/hooks/usePageValidation'; export function Page({ page, @@ -33,7 +32,7 @@ export function Page({ const { navigateToPage } = useNavigatePage(); const { performProcess, isAnyProcessing, isThisProcessing: isNavigating } = useIsProcessing(); const onPageNavigationValidation = useOnPageNavigationValidation(); - const layoutCollection = useLayoutCollection(); + const { getValidationOnNext, getValidationOnPrevious } = usePageValidationConfigForPage(currentPageId); const { order, maybeSaveOnPageChange } = useNavigatePage(); const navigationIsPrevented = useNavigationIsPrevented(page); @@ -44,21 +43,30 @@ export function Page({ return; } - const currentPageLayout = currentPageId ? layoutCollection[currentPageId] : undefined; - const validationOnNavigation = currentPageLayout?.data - ?.validationOnNavigation as ILayoutFile['data']['validationOnNavigation']; - await maybeSaveOnPageChange(); - if (validationOnNavigation && currentPageId) { + if (currentPageId) { const currentIndex = order.indexOf(currentPageId); const targetIndex = order.indexOf(page); - const direction = targetIndex > currentIndex ? 'forward' : 'previous'; - const hasValidationErrors = await onPageNavigationValidation(currentPageId, validationOnNavigation, direction); - if (hasValidationErrors) { - // Block navigation if validation fails - return; + if (currentIndex === -1 || targetIndex === -1) { + return false; + } + + const isForward = targetIndex > currentIndex; + const validationOnNavigation = isForward ? getValidationOnNext() : getValidationOnPrevious(); + + if (validationOnNavigation) { + const direction = isForward ? 'forward' : 'previous'; + const hasValidationErrors = await onPageNavigationValidation( + currentPageId, + validationOnNavigation, + direction, + ); + if (hasValidationErrors) { + // Block navigation if validation fails + return; + } } } diff --git a/src/hooks/usePageValidation.ts b/src/hooks/usePageValidation.ts index 3be81ec77c..d902affe60 100644 --- a/src/hooks/usePageValidation.ts +++ b/src/hooks/usePageValidation.ts @@ -1,25 +1,29 @@ import { useMemo } from 'react'; import { useLayoutCollection, useLayoutLookups } from 'src/features/form/layout/LayoutsContext'; +import { useValidationOnNavigation } from 'src/features/form/layoutSettings/LayoutSettingsContext'; import type { ILayoutFile, PageValidation } from 'src/layout/common.generated'; -export function usePageValidationConfig(componentId: string): { +type PageValidationConfig = { getValidationOnNext: () => PageValidation | undefined; getValidationOnPrevious: () => PageValidation | undefined; -} { - const layoutLookups = useLayoutLookups(); +}; + +export function usePageValidationConfigForPage(pageKey: string | undefined): PageValidationConfig { const layoutCollection = useLayoutCollection(); + const layoutSettingsValidation = useValidationOnNavigation(); return useMemo(() => { - const pageKey = layoutLookups.componentToPage[componentId]; if (!pageKey) { return { getValidationOnNext: () => undefined, getValidationOnPrevious: () => undefined }; } - const currentPageLayout = pageKey ? layoutCollection[pageKey] : undefined; - const validationOnNavigation = currentPageLayout?.data + const currentPageLayout = layoutCollection[pageKey]; + const pageValidation = currentPageLayout?.data ?.validationOnNavigation as ILayoutFile['data']['validationOnNavigation']; + const validationOnNavigation = pageValidation ?? layoutSettingsValidation; + const getValidation = (direction: string) => { const prevent = validationOnNavigation?.preventNavigation; if (!prevent || prevent === 'all' || prevent === direction) { @@ -32,5 +36,11 @@ export function usePageValidationConfig(componentId: string): { getValidationOnNext: () => getValidation('forward'), getValidationOnPrevious: () => getValidation('previous'), }; - }, [componentId, layoutLookups, layoutCollection]); + }, [pageKey, layoutCollection, layoutSettingsValidation]); +} + +export function usePageValidationConfig(componentId: string): PageValidationConfig { + const layoutLookups = useLayoutLookups(); + const pageKey = layoutLookups.componentToPage[componentId]; + return usePageValidationConfigForPage(pageKey); } From 9ad76d3bcf0b58847631e09a8a8821c0560ad3f5 Mon Sep 17 00:00:00 2001 From: walldenfilippa Date: Wed, 11 Feb 2026 16:38:58 +0100 Subject: [PATCH 2/2] rename minor edit --- src/hooks/usePageValidation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/usePageValidation.ts b/src/hooks/usePageValidation.ts index d902affe60..da0bd79693 100644 --- a/src/hooks/usePageValidation.ts +++ b/src/hooks/usePageValidation.ts @@ -11,7 +11,7 @@ type PageValidationConfig = { export function usePageValidationConfigForPage(pageKey: string | undefined): PageValidationConfig { const layoutCollection = useLayoutCollection(); - const layoutSettingsValidation = useValidationOnNavigation(); + const effectivePageValidation = useValidationOnNavigation(); return useMemo(() => { if (!pageKey) { @@ -22,7 +22,7 @@ export function usePageValidationConfigForPage(pageKey: string | undefined): Pag const pageValidation = currentPageLayout?.data ?.validationOnNavigation as ILayoutFile['data']['validationOnNavigation']; - const validationOnNavigation = pageValidation ?? layoutSettingsValidation; + const validationOnNavigation = pageValidation ?? effectivePageValidation; const getValidation = (direction: string) => { const prevent = validationOnNavigation?.preventNavigation; @@ -36,7 +36,7 @@ export function usePageValidationConfigForPage(pageKey: string | undefined): Pag getValidationOnNext: () => getValidation('forward'), getValidationOnPrevious: () => getValidation('previous'), }; - }, [pageKey, layoutCollection, layoutSettingsValidation]); + }, [pageKey, layoutCollection, effectivePageValidation]); } export function usePageValidationConfig(componentId: string): PageValidationConfig {