Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/codegen/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()),
Expand All @@ -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')
Expand Down
5 changes: 5 additions & 0 deletions src/features/form/layoutSets/LayoutSetsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
24 changes: 22 additions & 2 deletions src/features/form/layoutSettings/LayoutSettingsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProcessedLayoutSettings> {
Expand Down Expand Up @@ -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,
};
}
Expand All @@ -96,6 +105,7 @@ interface ProcessedLayoutSettings {
order: string[];
groups?: NavigationPageGroup[];
pageSettings: GlobalPageSettings;
validationOnNavigation?: PageValidation;
pdfLayoutName?: string;
}

Expand Down Expand Up @@ -131,6 +141,16 @@ const defaults: Required<GlobalPageSettings> = {
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<GlobalPageSettings> => {
const globalUISettings = useLaxGlobalUISettings();
const layoutSettings = useLaxCtx();
Expand Down
34 changes: 21 additions & 13 deletions src/features/navigation/components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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;
}
}
}

Expand Down
24 changes: 17 additions & 7 deletions src/hooks/usePageValidation.ts
Original file line number Diff line number Diff line change
@@ -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 effectivePageValidation = 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 ?? effectivePageValidation;

const getValidation = (direction: string) => {
const prevent = validationOnNavigation?.preventNavigation;
if (!prevent || prevent === 'all' || prevent === direction) {
Expand All @@ -32,5 +36,11 @@ export function usePageValidationConfig(componentId: string): {
getValidationOnNext: () => getValidation('forward'),
getValidationOnPrevious: () => getValidation('previous'),
};
}, [componentId, layoutLookups, layoutCollection]);
}, [pageKey, layoutCollection, effectivePageValidation]);
}

export function usePageValidationConfig(componentId: string): PageValidationConfig {
const layoutLookups = useLayoutLookups();
const pageKey = layoutLookups.componentToPage[componentId];
return usePageValidationConfigForPage(pageKey);
}
Loading