Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Subform table - enable editing of column title #14318

Merged
merged 23 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
49cfe9d
small refactor
lassopicasso Dec 11, 2024
3fc0448
typo fix
lassopicasso Dec 11, 2024
528dd12
toggleable column title
lassopicasso Dec 13, 2024
1d00459
let StudioActionCloseButton also handle submit
lassopicasso Dec 13, 2024
b2e8717
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Dec 18, 2024
8d9ab9f
some refactor and display title input field only when component is se…
lassopicasso Dec 18, 2024
61847ad
reverse changes in studioactionclosebutton
lassopicasso Dec 18, 2024
459ba71
Last logic implementation
lassopicasso Dec 19, 2024
b35ad16
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Dec 19, 2024
dd8766b
small test fix
lassopicasso Dec 19, 2024
b6f67d1
fix tests
lassopicasso Dec 19, 2024
82df9b6
remove unused waitFor
lassopicasso Dec 19, 2024
77de724
fix test
lassopicasso Dec 19, 2024
456d1e4
update utils tests
lassopicasso Dec 19, 2024
d1547ce
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Jan 2, 2025
7daeeed
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Jan 7, 2025
c6b7e2a
Merge branch 'subform-table-manually-editing-column-name' of https://…
lassopicasso Jan 7, 2025
17b7c05
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Jan 13, 2025
b1f5544
feedback + update tests
lassopicasso Jan 14, 2025
010b76f
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Jan 14, 2025
9379497
rabbit feedback
lassopicasso Jan 14, 2025
a5119e8
add `subformId`
lassopicasso Jan 14, 2025
403ce43
Merge branch 'main' into subform-table-manually-editing-column-name
lassopicasso Jan 14, 2025
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
4 changes: 4 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1836,7 +1836,11 @@
"ux_editor.properties_panel.subform_table_columns.cell_content_query_label": "Query",
"ux_editor.properties_panel.subform_table_columns.choose_component": "Velg komponenten fra underskjemaet som skal vises her",
"ux_editor.properties_panel.subform_table_columns.choose_component_description": "Listen viser bare de komponentene som har minst én datamodellbinding og en ledetekst",
"ux_editor.properties_panel.subform_table_columns.column_cell_content": "Celleinnhold (query):",
"ux_editor.properties_panel.subform_table_columns.column_header": "Kolonne {{columnNumber}}",
"ux_editor.properties_panel.subform_table_columns.column_title_edit": "Kolonnetittel",
"ux_editor.properties_panel.subform_table_columns.column_title_error": "Kolonnetittel er påkrevd",
"ux_editor.properties_panel.subform_table_columns.column_title_unedit": "Kolonnetittel:",
"ux_editor.properties_panel.subform_table_columns.delete_column": "Slett kolonne {{columnNumber}}",
"ux_editor.properties_panel.subform_table_columns.header_content_label": "Header Content",
"ux_editor.properties_panel.subform_table_columns.heading": "Valg for tabell",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { ColumnElement, type ColumnElementProps } from './ColumnElement';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { renderWithProviders } from 'dashboard/testing/mocks';
Expand Down Expand Up @@ -31,15 +31,15 @@ const defaultProps: ColumnElementProps = {
isInitialOpenForEdit: false,
onDeleteColumn: jest.fn(),
onEdit: jest.fn(),
layoutSetName: layoutSet3SubformNameMock,
subformLayout: layoutSet3SubformNameMock,
};

describe('ColumnElement', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should call onEdit with updated header content when header text field is blurred', async () => {
it('should call onEdit with updated header content when click on save button', async () => {
const onEditMock = jest.fn();

const user = userEvent.setup();
Expand All @@ -61,18 +61,25 @@ describe('ColumnElement', () => {
screen.getByRole('option', { name: new RegExp(`${subformLayoutMock.component1Id}`) }),
);

await waitFor(async () => {
await user.click(
screen.getByRole('button', {
name: textMock('general.save'),
}),
);
});
await user.click(
await screen.findByText(
textMock('ux_editor.properties_panel.subform_table_columns.column_title_unedit'),
),
);
await user.type(
screen.getByText(
textMock('ux_editor.properties_panel.subform_table_columns.column_title_edit'),
),
'New Title',
);

const saveButton = await screen.findByRole('button', { name: textMock('general.save') });
await user.click(saveButton);

expect(onEditMock).toHaveBeenCalledTimes(1);
expect(onEditMock).toHaveBeenCalledWith({
...mockTableColumn,
headerContent: subformLayoutMock.component1.textResourceBindings.title,
headerContent: expect.stringContaining('subform_table_column_title_'),
cellContent: { query: subformLayoutMock.component1.dataModelBindings.simpleBinding },
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmen
import { textResourceByLanguageAndIdSelector } from '../../../../selectors/textResourceSelectors';

export type ColumnElementProps = {
layoutSetName: string;
subformLayout: string;
tableColumn: TableColumn;
columnNumber: number;
isInitialOpenForEdit: boolean;
Expand All @@ -23,7 +23,7 @@ export const ColumnElement = ({
isInitialOpenForEdit,
onDeleteColumn,
onEdit,
layoutSetName,
subformLayout,
}: ColumnElementProps): ReactElement => {
const { t } = useTranslation();
const [editing, setEditing] = useState(isInitialOpenForEdit);
Expand All @@ -38,7 +38,7 @@ export const ColumnElement = ({
if (editing) {
return (
<EditColumnElement
layoutSetName={layoutSetName}
subformLayout={subformLayout}
sourceColumn={tableColumn}
columnNumber={columnNumber}
onDeleteColumn={onDeleteColumn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,92 +9,105 @@ import {
StudioDeleteButton,
StudioDivider,
StudioParagraph,
StudioTextfield,
} from '@studio/components';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { useFormLayoutsQuery } from '../../../../../hooks/queries/useFormLayoutsQuery';
import { getAllLayoutComponents } from '../../../../../utils/formLayoutUtils';
import type { FormItem } from '../../../../../types/FormItem';
import { PadlockLockedFillIcon } from '@studio/icons';
import { EditColumnElementContent } from './EditColumnElementContent';
import { useTextResourcesQuery } from 'app-shared/hooks/queries';
import { textResourceByLanguageAndIdSelector } from '../../../../../selectors/textResourceSelectors';
import { useUpsertTextResourceMutation } from 'app-shared/hooks/mutations';
import { useTextIdMutation } from 'app-development/hooks/mutations';
import {
getComponentsForSubformTable,
getTitleIdForColumn,
getValueOfTitleId,
} from '../../utils/editSubformTableColumnsUtils';
import { convertDataBindingToInternalFormat } from '../../../../../utils/dataModelUtils';

export type ColumnElementProps = {
sourceColumn: TableColumn;
columnNumber: number;
onDeleteColumn: () => void;
onEdit: (tableColumn: TableColumn) => void;
layoutSetName: string;
subformLayout: string;
};

export const EditColumnElement = ({
sourceColumn,
columnNumber,
onDeleteColumn,
onEdit,
layoutSetName,
subformLayout,
}: ColumnElementProps): ReactElement => {
const { t } = useTranslation();
const { org, app } = useStudioEnvironmentParams();
const subformLayout = layoutSetName;
const { data: textResources } = useTextResourcesQuery(org, app);
const [tableColumn, setTableColumn] = useState(sourceColumn);
const [title, setTitle] = useState<string>(
getValueOfTitleId(sourceColumn.headerContent, textResources),
);
const [uniqueTitleId, _] = useState(
getTitleIdForColumn({
titleId: tableColumn.headerContent,
subformId: subformLayout,
textResources,
}),
);
const { mutate: upsertTextResource } = useUpsertTextResourceMutation(org, app);
const { mutate: textIdMutation } = useTextIdMutation(org, app);
const { data: formLayouts } = useFormLayoutsQuery(org, app, subformLayout);
const { data: textResources } = useTextResourcesQuery(org, app);

const textKeyValue = textResourceByLanguageAndIdSelector(
'nb',
tableColumn.headerContent,
)(textResources)?.value;
const components = formLayouts
? Object.values(formLayouts).flatMap((layout) => {
return getAllLayoutComponents(layout);
})
: [];
const handleSave = () => {
upsertTextResource({ language: 'nb', textId: uniqueTitleId, translation: title });
onEdit({ ...tableColumn, headerContent: uniqueTitleId });
};

const componentsWithLabelAndDataModel = components.filter(
(comp) => comp.textResourceBindings?.title && comp.dataModelBindings?.simpleBinding,
);
const handleDelete = () => {
textIdMutation([{ oldId: uniqueTitleId }]);
onDeleteColumn();
};

const selectComponent = (values: string[]) => {
const selectedComponentId = values[0];
const selectedComponent = components.find((comp) => comp.id === selectedComponentId);
const selectedComponent = availableComponents.find((comp) => comp.id === selectedComponentId);

const binding = convertDataBindingToInternalFormat(selectedComponent, 'simpleBinding');
const updatedTableColumn = {
...sourceColumn,
headerContent: selectedComponent.textResourceBindings?.title,
cellContent: { query: binding.field },
};

setTitle(getValueOfTitleId(selectedComponent.textResourceBindings.title, textResources));
setTableColumn(updatedTableColumn);
};

const availableComponents = getComponentsForSubformTable(formLayouts);
const isSaveButtonDisabled = !tableColumn.headerContent || !title?.trim();

return (
<StudioCard className={classes.wrapper}>
<EditColumnElementHeader columnNumber={columnNumber} />
<StudioCard.Content className={classes.content}>
<EditColumnElementComponentSelect
components={componentsWithLabelAndDataModel}
components={availableComponents}
onSelectComponent={selectComponent}
/>
<StudioTextfield
label={
<>
<PadlockLockedFillIcon />
{t('ux_editor.modal_properties_textResourceBindings_title')}
</>
}
disabled={true}
size='sm'
value={textKeyValue}
/>
{tableColumn.headerContent && (
<EditColumnElementContent
cellContent={tableColumn.cellContent.query}
title={title}
setTitle={setTitle}
/>
)}
<div className={classes.buttons}>
<StudioActionCloseButton
variant='secondary'
onClick={() => onEdit(tableColumn)}
onClick={handleSave}
title={t('general.save')}
></StudioActionCloseButton>
<StudioDeleteButton title={t('general.delete')} onDelete={onDeleteColumn} />
disabled={isSaveButtonDisabled}
/>
<StudioDeleteButton title={t('general.delete')} onDelete={handleDelete} />
</div>
</StudioCard.Content>
</StudioCard>
Expand Down Expand Up @@ -128,19 +141,6 @@ export const EditColumnElementComponentSelect = ({
}: EditColumnElementComponentSelectProps) => {
const { t } = useTranslation();

const subformComponentOptions =
components.length > 0 ? (
components.map((comp: FormItem) => (
<StudioCombobox.Option key={comp.id} value={comp.id} description={comp.type}>
{comp.id}
</StudioCombobox.Option>
))
) : (
<StudioCombobox.Empty key={'noComponentsWithLabel'}>
{t('ux_editor.properties_panel.subform_table_columns.no_components_available_message')}
</StudioCombobox.Empty>
);

return (
<StudioCombobox
label={t('ux_editor.properties_panel.subform_table_columns.choose_component')}
Expand All @@ -151,7 +151,14 @@ export const EditColumnElementComponentSelect = ({
onValueChange={onSelectComponent}
id='columncomponentselect'
>
{subformComponentOptions}
{components.map((comp: FormItem) => (
<StudioCombobox.Option key={comp.id} value={comp.id} description={comp.type}>
{comp.id}
</StudioCombobox.Option>
))}
<StudioCombobox.Empty key={'noComponentsWithLabel'}>
{t('ux_editor.properties_panel.subform_table_columns.no_components_available_message')}
</StudioCombobox.Empty>
</StudioCombobox>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.componentTitleButton,
.componentCellContent {
padding-left: var(--fds-spacing-1);
}

.componentTitleButton > span:first-child {
display: flex;
gap: var(--fds-spacing-1);
}

.componentTitleButton {
margin-bottom: var(--fds-spacing-1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState } from 'react';

import { StudioDisplayTile, StudioProperty, StudioTextfield } from '@studio/components';
import { useTranslation } from 'react-i18next';
import classes from './EditColumnElementContent.module.css';

type EditColumnElementContentProps = {
title: string;
setTitle: (title: string) => void;
cellContent: string;
};

export const EditColumnElementContent = ({
title,
setTitle,
cellContent,
}: EditColumnElementContentProps) => {
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
const { t } = useTranslation();

const errorMessage = t('ux_editor.properties_panel.subform_table_columns.column_title_error');

return (
<div>
{isEditingTitle ? (
<StudioTextfield
label={t('ux_editor.properties_panel.subform_table_columns.column_title_edit')}
size='sm'
value={title}
autoFocus={true}
onChange={(e) => setTitle(e.target.value)}
error={!title?.trim() && errorMessage}
/>
) : (
<StudioProperty.Button
className={classes.componentTitleButton}
onClick={() => setIsEditingTitle(true)}
property={t('ux_editor.properties_panel.subform_table_columns.column_title_unedit')}
value={title}
/>
)}

<StudioDisplayTile
className={classes.componentCellContent}
label={t('ux_editor.properties_panel.subform_table_columns.column_cell_content')}
value={cellContent}
showPadlock={false}
/>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('EditSubformTableColumns', () => {
expect(handleComponentChangeMock).toHaveBeenCalledTimes(1);

const columnTitleKey = getUpdatedTableColumns(handleComponentChangeMock)[0].headerContent;
expect(columnTitleKey).toBe(componentTextKeyId);
expect(columnTitleKey).toEqual(expect.stringContaining('subform_table_column_title_'));
});

it('should call handleComponentChange when a column is deleted', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const EditSubformTableColumns = ({
{tableColumns.length > 0 &&
tableColumns.map((tableColumn: TableColumn, index: number) => (
<ColumnElement
layoutSetName={component.layoutSet}
subformLayout={component.layoutSet}
key={getUniqueKey(index)}
tableColumn={tableColumn}
columnNumber={index + 1}
Expand Down
Loading
Loading