Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useMemo } from 'react';
import type { PropsWithChildren } from 'react';

import { createContext } from 'src/core/contexts/context';

interface RepGroupEditContextValue {
editableChildIds: Set<string>;
}

const { Provider, useCtx } = createContext<RepGroupEditContextValue | undefined>({
name: 'RepGroupSummaryEditable',
required: false,
default: undefined,
});

export function RepGroupSummaryEditableProvider({
editableChildIds,
children,
}: PropsWithChildren<{ editableChildIds: string[] }>) {
const value = useMemo(() => ({ editableChildIds: new Set(editableChildIds) }), [editableChildIds]);
return <Provider value={value}>{children}</Provider>;
}

/**
* Hook to check if a summary component is editable within a repeating group row context
*/
export function useIsEditableInRepGroup(baseId: string): boolean {
const ctx = useCtx();
if (!ctx) {
return true;
}
return ctx.editableChildIds.has(baseId);
}
75 changes: 54 additions & 21 deletions src/layout/RepeatingGroup/Summary2/RepeatingGroupSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useUnifiedValidationsForNode } from 'src/features/validation/selectors/
import { validationsOfSeverity } from 'src/features/validation/utils';
import classes from 'src/layout/RepeatingGroup/Summary2/RepeatingGroupSummary.module.css';
import { RepeatingGroupTableSummary } from 'src/layout/RepeatingGroup/Summary2/RepeatingGroupTableSummary/RepeatingGroupTableSummary';
import { RepGroupSummaryEditableProvider } from 'src/layout/RepeatingGroup/Summary2/RepGroupSummaryEditableContext';
import { RepGroupHooks } from 'src/layout/RepeatingGroup/utils';
import { SingleValueSummary } from 'src/layout/Summary2/CommonSummaryComponents/SingleValueSummary';
import {
Expand All @@ -22,6 +23,8 @@ import {
import { useSummaryOverrides, useSummaryProp } from 'src/layout/Summary2/summaryStoreContext';
import { DataModelLocationProvider } from 'src/utils/layout/DataModelLocation';
import { useItemWhenType } from 'src/utils/layout/useNodeItem';
import type { IDataModelReference } from 'src/layout/common.generated';
import type { RepGroupRow } from 'src/layout/RepeatingGroup/utils';
import type { Summary2Props } from 'src/layout/Summary2/SummaryComponent2/types';

export const RepeatingGroupSummary = ({ targetBaseComponentId }: Summary2Props) => {
Expand Down Expand Up @@ -96,32 +99,20 @@ export const RepeatingGroupSummary = ({ targetBaseComponentId }: Summary2Props)
<Lang id={title} />
</Heading>
<div className={cn(classes.contentWrapper, { [classes.nestedContentWrapper]: isNested })}>
{rows.map((row) => {
{rows.map((row, index) => {
if (!row) {
return null;
}

return (
<DataModelLocationProvider
key={row?.uuid}
groupBinding={dataModelBindings.group}
rowIndex={row.index}
>
{row.index != 0 && <hr className={classes.rowDivider} />}
<Flex
key={row?.uuid}
container
spacing={6}
alignItems='flex-start'
>
{visibleChildIds.map((baseId) => (
<ComponentSummary
key={baseId}
targetBaseComponentId={baseId}
/>
))}
</Flex>
</DataModelLocationProvider>
<RepGroupListRow
key={row.uuid}
row={row}
targetBaseComponentId={targetBaseComponentId}
visibleChildIds={visibleChildIds}
dataModelBindings={dataModelBindings}
showDivider={index !== 0}
/>
);
})}
</div>
Expand All @@ -140,3 +131,45 @@ export const RepeatingGroupSummary = ({ targetBaseComponentId }: Summary2Props)
</SummaryFlexForContainer>
);
};

interface RepGroupListRowProps {
row: RepGroupRow;
targetBaseComponentId: string;
visibleChildIds: string[];
dataModelBindings: { group: IDataModelReference };
showDivider: boolean;
}

function RepGroupListRow({
row,
targetBaseComponentId,
visibleChildIds,
dataModelBindings,
showDivider,
}: RepGroupListRowProps) {
const rowWithExpressions = RepGroupHooks.useRowWithExpressions(targetBaseComponentId, { uuid: row.uuid });
const editableChildIds = RepGroupHooks.useEditableChildren(targetBaseComponentId, rowWithExpressions);

return (
<DataModelLocationProvider
groupBinding={dataModelBindings.group}
rowIndex={row.index}
>
<RepGroupSummaryEditableProvider editableChildIds={editableChildIds}>
{showDivider && <hr className={classes.rowDivider} />}
<Flex
container
spacing={6}
alignItems='flex-start'
>
{visibleChildIds.map((baseId) => (
<ComponentSummary
key={baseId}
targetBaseComponentId={baseId}
/>
))}
</Flex>
</RepGroupSummaryEditableProvider>
</DataModelLocationProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('RepeatingGroupTableSummary', () => {
jest.restoreAllMocks();
});

const layoutWithHidden = (hidden: NodeId[]): ILayoutCollection => ({
const layoutWithHidden = (hidden: NodeId[], editButton?: boolean): ILayoutCollection => ({
FormPage1: {
data: {
layout: [
Expand All @@ -32,6 +32,11 @@ describe('RepeatingGroupTableSummary', () => {
children: ['input1', 'input2', 'input3'],
maxCount: 3,
hidden: hidden.includes('repeating-group'),
...(editButton !== undefined && {
edit: {
editButton,
},
}),
},
{
id: 'input1',
Expand Down Expand Up @@ -140,6 +145,16 @@ describe('RepeatingGroupTableSummary', () => {
);
});

test('should not render edit button when edit.editButton is false', async () => {
await render({ layout: layoutWithHidden([], false) });
expect(screen.queryByRole('button', { name: /endre/i })).not.toBeInTheDocument();
});

test('should render edit button when edit.editButton is true', async () => {
await render({ layout: layoutWithHidden([], true) });
expect(screen.getByRole('button', { name: /endre/i })).toBeInTheDocument();
});

type IRenderProps = {
navigate?: jest.Mock;
layout?: ILayoutCollection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import tableClasses from 'src/layout/RepeatingGroup/Summary2/RepeatingGroupTable
import { RepeatingGroupTableTitle, useTableTitle } from 'src/layout/RepeatingGroup/Table/RepeatingGroupTableTitle';
import { useTableComponentIds } from 'src/layout/RepeatingGroup/useTableComponentIds';
import { RepGroupHooks } from 'src/layout/RepeatingGroup/utils';
import { EditButtonFirstVisible } from 'src/layout/Summary2/CommonSummaryComponents/EditButton';
import { EditButtonFirstVisibleAndEditable } from 'src/layout/Summary2/CommonSummaryComponents/EditButton';
import { useReportSummaryRender } from 'src/layout/Summary2/isEmpty/EmptyChildrenContext';
import { ComponentSummary, SummaryContains } from 'src/layout/Summary2/SummaryComponent2/ComponentSummary';
import utilClasses from 'src/styles/utils.module.css';
Expand Down Expand Up @@ -138,9 +138,12 @@ type DataRowProps = {

function DataRow({ row, baseComponentId, pdfModeActive, columnSettings }: DataRowProps) {
const layoutLookups = useLayoutLookups();
const ids = useTableComponentIds(baseComponentId);
const children = RepGroupHooks.useChildIds(baseComponentId);
const ids = useTableComponentIds(baseComponentId);
const visibleIds = ids.filter((id) => columnSettings[id]?.hidden !== true);
const rowWithExpressions = RepGroupHooks.useRowWithExpressions(baseComponentId, { uuid: row?.uuid ?? '' });
const editableChildren = RepGroupHooks.useEditableChildren(baseComponentId, rowWithExpressions);
const editableIds = [...ids, ...children].filter((id) => editableChildren.includes(id));

if (!row) {
return null;
Expand All @@ -166,9 +169,10 @@ function DataRow({ row, baseComponentId, pdfModeActive, columnSettings }: DataRo
align='right'
className={tableClasses.buttonCell}
>
<EditButtonFirstVisible
ids={[...ids, ...children]}
fallback={baseComponentId}
<EditButtonFirstVisibleAndEditable
key={editableIds.join(',')}
ids={editableIds}
fallback={rowWithExpressions?.edit?.editButton !== false ? baseComponentId : undefined}
/>
</Table.Cell>
)}
Expand Down
44 changes: 44 additions & 0 deletions src/layout/RepeatingGroup/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import { useCallback, useMemo } from 'react';
import { evalExpr } from 'src/features/expressions';
import { ExprVal } from 'src/features/expressions/types';
import { ExprValidation } from 'src/features/expressions/validation';
import { useLayoutLookups } from 'src/features/form/layout/LayoutsContext';
import { FD } from 'src/features/formData/FormDataWrite';
import { useMemoDeepEqual } from 'src/hooks/useStateDeepEqual';
import { getComponentDef } from 'src/layout';
import { CompCategory } from 'src/layout/common';
import { useComponentIdMutator } from 'src/utils/layout/DataModelLocation';
import { useIsHiddenMulti } from 'src/utils/layout/hidden';
import { useDataModelBindingsFor, useExternalItem } from 'src/utils/layout/hooks';
import { useExpressionDataSources } from 'src/utils/layout/useExpressionDataSources';
import type { ExprValToActual, ExprValToActualOrExpr } from 'src/features/expressions/types';
import type { LayoutLookups } from 'src/features/form/layout/makeLayoutLookups';
import type { IDataModelReference } from 'src/layout/common.generated';
import type { CompExternal } from 'src/layout/layout';
import type { GroupExpressions } from 'src/layout/RepeatingGroup/types';
import type { BaseRow } from 'src/utils/layout/types';
import type { ExpressionDataSources } from 'src/utils/layout/useExpressionDataSources';
Expand Down Expand Up @@ -60,6 +65,35 @@ function evalBool({ expr, defaultValue = false, dataSources, groupBinding, rowIn
return evalExpr(expr, { ...dataSources, currentDataModelPath }, { returnType: ExprVal.Boolean, defaultValue });
}

/**
* Helper function to check if a child component is editable in a repeating group
*/
function isChildEditableCheck(
childBaseComponentId: string,
layoutLookups: LayoutLookups,
parentComponent: CompExternal<'RepeatingGroup'>,
rowWithExpressions: RepGroupRowWithExpressions | undefined,
): boolean {
const childComponent = layoutLookups.getComponent(childBaseComponentId);

const def = getComponentDef(childComponent.type);
if (def.category !== CompCategory.Form) {
return false; // Must be a form component
}

const columnSettings = parentComponent.tableColumns?.[childBaseComponentId];
const hiddenInTable = columnSettings?.hidden === true;
const editInTable = columnSettings?.editInTable ?? false;
const showInExpandedEdit = columnSettings?.showInExpandedEdit ?? true;
const editButtonVisible = rowWithExpressions?.edit?.editButton !== false;

if (editButtonVisible) {
return showInExpandedEdit;
}

return editInTable && !hiddenInTable;
}

export const RepGroupHooks = {
useAllBaseRows(baseComponentId: string) {
const groupBinding = useDataModelBindingsFor(baseComponentId, 'RepeatingGroup')?.group;
Expand Down Expand Up @@ -228,4 +262,14 @@ export const RepGroupHooks = {
hidden: hidden[baseId] ?? false,
}));
},

useEditableChildren(baseComponentId: string, rowWithExpressions: RepGroupRowWithExpressions | undefined): string[] {
const childrenBaseIds = RepGroupHooks.useChildIds(baseComponentId);
const layoutLookups = useLayoutLookups();
const component = layoutLookups.getComponent(baseComponentId, 'RepeatingGroup');

return childrenBaseIds.filter((childId) =>
isChildEditableCheck(childId, layoutLookups, component, rowWithExpressions),
);
},
};
22 changes: 16 additions & 6 deletions src/layout/Summary2/CommonSummaryComponents/EditButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useLanguage } from 'src/features/language/useLanguage';
import { usePdfModeActive } from 'src/features/pdf/PdfWrapper';
import { useIsMobile } from 'src/hooks/useDeviceWidths';
import { useCurrentView, useNavigateToComponent } from 'src/hooks/useNavigatePage';
import { useIsEditableInRepGroup } from 'src/layout/RepeatingGroup/Summary2/RepGroupSummaryEditableContext';
import { useSummaryProp } from 'src/layout/Summary2/summaryStoreContext';
import { useIndexedId } from 'src/utils/layout/DataModelLocation';
import { useIsHidden, useIsHiddenMulti } from 'src/utils/layout/hidden';
Expand All @@ -22,24 +23,26 @@ export type EditButtonProps = {
} & React.HTMLAttributes<HTMLButtonElement>;

/**
* Render an edit button for the first visible (non-hidden) node in a list of possible IDs
* Render an edit button for the first visible (non-hidden) and editable component in a list of possible IDs
*/
export function EditButtonFirstVisible({
export function EditButtonFirstVisibleAndEditable({
ids,
fallback,
...rest
}: { ids: string[]; fallback: string } & Omit<EditButtonProps, 'targetBaseComponentId'>) {
}: { ids: string[]; fallback: string | undefined } & Omit<EditButtonProps, 'targetBaseComponentId'>) {
const hiddenIds = useIsHiddenMulti(ids);
const first = ids.find((id) => hiddenIds[id] === false);
const isFallbackHidden = useIsHidden(fallback);
if (!first && isFallbackHidden) {
const target = first ?? (isFallbackHidden ? undefined : fallback);

if (!target) {
return null;
}

return (
<EditButton
targetBaseComponentId={first ?? fallback}
skipLastIdMutator={!first}
targetBaseComponentId={target}
skipLastIdMutator={target === fallback}
{...rest}
/>
);
Expand Down Expand Up @@ -72,6 +75,12 @@ export function EditButton({
const indexedId = useIndexedId(targetBaseComponentId, skipLastIdMutator);
const summary2Id = useSummaryProp('id');

// Check if we're in a repeating group row and if this component is editable
const editableInRepGroup = useIsEditableInRepGroup(targetBaseComponentId);
if (!editableInRepGroup) {
return null;
}

if (isReadOnly) {
return null;
}
Expand Down Expand Up @@ -104,6 +113,7 @@ export function EditButton({
onClick={onChangeClick}
variant='tertiary'
className={className}
data-target-id={indexedId}
>
{!isMobile && <Lang id='general.edit' />}
<PencilIcon
Expand Down
Loading