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

resourceadm: change available fields for resource type "MaskinportenSchema" #11933

Merged
merged 32 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
660aee0
add fields for resource references
mgunnerud Dec 22, 2023
238b9cd
simplify css
mgunnerud Dec 22, 2023
c17151b
refactor: create a common fieldset wrapper component to use for conta…
mgunnerud Dec 22, 2023
ae3b4fb
simplification
mgunnerud Dec 22, 2023
bbee52a
refactoring
mgunnerud Dec 22, 2023
c68b64b
add reference fields to about resource page
mgunnerud Dec 22, 2023
de7af10
fix spelling
mgunnerud Dec 22, 2023
f36cebd
Merge remote-tracking branch 'origin/master' into 11905-part2
mgunnerud Dec 30, 2023
6e5d67f
remove self_identified user, enterprise user and available for type f…
mgunnerud Dec 30, 2023
629332e
add broker service resource type, hide available for field for broker…
mgunnerud Dec 30, 2023
bff762c
add tests for hidden fields based on resourceType
mgunnerud Dec 30, 2023
b49c278
remove code for brokerService resource type (will be done in another PR)
mgunnerud Jan 2, 2024
e906e39
remove text string
mgunnerud Jan 2, 2024
6b15263
show reference fields only for resourceType MaskinportenSchema
mgunnerud Jan 2, 2024
8b1b911
Merge branch 'master' into 11905-part2
mgunnerud Jan 4, 2024
2b2d7ad
fix bad key causing too many re-renders
mgunnerud Jan 4, 2024
367deec
replace Select component with radio buttons
mgunnerud Jan 4, 2024
f3298e6
fix merge conflict
mgunnerud Jan 4, 2024
28ab20d
hide translation panel when any non-translateable field is focused
mgunnerud Jan 4, 2024
dfbe1e9
move code into translation fields
mgunnerud Jan 4, 2024
5e0bacd
remove reference to deleted file
mgunnerud Jan 4, 2024
eda89be
fix tests
mgunnerud Jan 4, 2024
a6ed873
improve test coverage
mgunnerud Jan 4, 2024
46edd75
Merge branch 'master' into 11905-part2
mgunnerud Jan 4, 2024
402b80f
Merge branch 'master' into 11905-part2
mgunnerud Jan 5, 2024
b0d61c7
small refactor
mgunnerud Jan 5, 2024
cde4d6e
Merge branch 'master' into 11905-part2
mgunnerud Jan 5, 2024
b4a5a85
remove comment
mgunnerud Jan 8, 2024
5f7004d
Merge branch 'master' into 11905-part2
mgunnerud Jan 8, 2024
d4f7e12
hide migrate tab unless resource has a reference with Altinn2 source
mgunnerud Jan 8, 2024
9110deb
replace resource reference field radio buttons with NativeSelect
mgunnerud Jan 8, 2024
5a493ac
Merge branch '11905-part2' of https://github.com/Altinn/altinn-studio…
mgunnerud Jan 8, 2024
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
12 changes: 11 additions & 1 deletion frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@
"resourceadm.about_resource_contact_label_email": "E-post",
"resourceadm.about_resource_contact_label_telephone": "Telefon",
"resourceadm.about_resource_contact_legend": "Kontakt informasjon",
"resourceadm.about_resource_contact_point_error": "Du mangler å legge til et av felte for kontakt informasjon.",
"resourceadm.about_resource_contact_point_error": "Du mangler å legge til et av feltene for kontakt informasjon.",
"resourceadm.about_resource_contact_remove_button": "Slett kontaktinformasjon",
"resourceadm.about_resource_delegable_label": "Delegerbar",
"resourceadm.about_resource_delegable_show_text": "Ressursen {{showText}} være delegerbar.",
Expand All @@ -735,6 +735,16 @@
"resourceadm.about_resource_public_service_show": "skal",
"resourceadm.about_resource_public_service_show_text": "Ressursen {{showText}} vises i offentlige kataloger.",
"resourceadm.about_resource_public_service_text": "Etter publisering blir ressursen tilgjengelig i kataloger, blant annet i altinn, på norge.no og data.norge.no.",
"resourceadm.about_resource_reference": "Referanse",
"resourceadm.about_resource_reference_add_reference": "Legg til ny referanse",
"resourceadm.about_resource_reference_confirm_delete": "Er du sikker på at du vil slette denne referansen?",
"resourceadm.about_resource_reference_confirm_delete_button": "Slett referansen",
"resourceadm.about_resource_reference_delete": "Slett referanse",
"resourceadm.about_resource_reference_error": "Du må fylle ut alle referanse-feltene",
"resourceadm.about_resource_reference_source": "Referansekilde",
"resourceadm.about_resource_reference_type": "Referansetype",
"resourceadm.about_resource_references": "Ressursreferanser",
"resourceadm.about_resource_references_description": "Brukes kun av maskinporten",
"resourceadm.about_resource_resource_description_label": "Beskrivelse (Bokmål)",
"resourceadm.about_resource_resource_description_text": "Her må du beskrive tjenesten. Teksten kan bli synlig på flere områder på tvers av offentlige nettløsninger.",
"resourceadm.about_resource_resource_title_label": "Navn på tjenesten (Bokmål)",
Expand Down
23 changes: 15 additions & 8 deletions frontend/packages/shared/src/types/ResourceAdm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,21 @@ export interface Validation {
errors: any;
}

export type ResourceReferenceSource =
| 'Default'
| 'Altinn1'
| 'Altinn2'
| 'Altinn3'
| 'ExternalPlatform';
export type ResourceReferenceType =
| 'Default'
| 'Uri'
| 'DelegationSchemeId'
| 'MaskinportenScope'
| 'ServiceCode'
| 'ServiceEditionCode';
export interface ResourceReference {
referenceSource?: 'Default' | 'Altinn1' | 'Altinn2' | 'Altinn3' | 'ExternalPlatform';
referenceSource?: ResourceReferenceSource;
reference?: string;
referenceType?:
| 'Default'
| 'Uri'
| 'DelegationSchemeId'
| 'MaskinportenScope'
| 'ServiceCode'
| 'ServiceEditionCode';
referenceType?: ResourceReferenceType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
width: 32rem;
margin-top: var(--fds-spacing-2);
margin-bottom: var(--fds-spacing-3);
position: relative;
}

.buttonWrapper {
Expand Down
153 changes: 153 additions & 0 deletions frontend/resourceadm/components/FieldsetWrapper/FieldsetWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Modal } from '@digdir/design-system-react';
import classes from './FieldsetWrapper.module.css';

const DELETE_ID_NOT_SET = -1;

export type FieldsetWrapperProps<T> = {
/**
* The current list
*/
list: T[];
/**
* Function to be executed when list is changed
* @param list the updated list
* @returns void
*/
onListFieldChanged: (list: T[]) => void;
/**
* Translation keys for texts displayed by the wrapper
*/
translations: {
deleteButton: string;
deleteHeader: string;
deleteConfirmation: string;
deleteConfirmationButton: string;
addButton: string;
};
/**
* List object where all values are default values
*/
emptyItem: T;
/**
* Render function for rendering a list item.
* @param listItem the list item to render
* @param onChange function to call when item is changed. Call this function from child fieldset render on change
*/
renderItem: (listItem: T, onChange: (item: T) => void) => React.ReactNode;
};

/**
* @component
* Renders the list and calls the renderItem prop function for each list item
*
* @property {T[]}[list] - The current list
* @property {function}[onListFieldChanged] - Function to be executed when list is changed
* @property {Object}[translations] - Translation keys for texts displayed by the wrapper
* @property {T}[emptyItem] - List object where all values are default values
* @property {function}[renderItem] - Render function for rendering a list item.
*
* @returns {React.ReactNode} - The rendered component
*/
export const FieldsetWrapper = <T,>({
list,
onListFieldChanged,
emptyItem,
translations,
renderItem,
}: FieldsetWrapperProps<T>): React.ReactNode => {
const { t } = useTranslation();
const deleteModalRef = useRef<HTMLDialogElement>(null);

const [deleteId, setDeleteId] = useState<number>(DELETE_ID_NOT_SET);
const [listItems, setListItems] = useState<T[]>(list ?? [emptyItem]);

/**
* Adds a new empty list item to the list
*/
const handleClickAddButton = () => {
const updatedList = [...listItems, emptyItem];
setListItems(updatedList);
onListFieldChanged(updatedList);
};

/**
* Removes a list item in the deleteId position from the list
*/
const handleClickRemoveButton = () => {
const updatedList = listItems.filter((_item, index) => index !== deleteId);
onCloseDeleteModal();
setListItems(updatedList);
onListFieldChanged(updatedList);
};

/**
* Updates the list item when a field is changed
* @param listItem
* @param pos
*/
const onChangeListItemField = (listItem: T, pos: number) => {
const updatedList = [...listItems];
updatedList[pos] = listItem;
setListItems(updatedList);
onListFieldChanged(updatedList);
};

/**
* Closes the delete list item modal
*/
const onCloseDeleteModal = (): void => {
deleteModalRef.current?.close();
setDeleteId(DELETE_ID_NOT_SET);
};
/**
* Render each list item with renderItem() and display a delete button for each
*/
const displayFields = listItems.map((listItem: T, pos: number) => (
<div key={`${pos}/${listItems.length}`} className={classes.fieldset}>
<div className={classes.divider} />
{renderItem(listItem, (item: T) => {
onChangeListItemField(item, pos);
})}
<div className={classes.buttonWrapper}>
<Button
size='small'
color='danger'
aria-disabled={listItems.length < 2}
onClick={() => {
if (listItems.length > 1) {
// TODO: can the last item be deleted??
deleteModalRef.current?.showModal();
setDeleteId(pos);
}
}}
>
{t(translations.deleteButton)}
</Button>
</div>
</div>
));

return (
<>
<Modal ref={deleteModalRef} onClose={onCloseDeleteModal}>
<Modal.Header>{t(translations.deleteHeader)}</Modal.Header>
<Modal.Content>{t(translations.deleteConfirmation)}</Modal.Content>
<Modal.Footer>
<Button color='danger' size='small' onClick={handleClickRemoveButton}>
{t(translations.deleteConfirmationButton)}
</Button>
<Button size='small' variant='tertiary' onClick={onCloseDeleteModal}>
{t('general.cancel')}
</Button>
</Modal.Footer>
</Modal>
<div className={classes.divider} />
{displayFields}
<Button size='small' onClick={handleClickAddButton}>
{t(translations.addButton)}
</Button>
</>
);
};
1 change: 1 addition & 0 deletions frontend/resourceadm/components/FieldsetWrapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FieldsetWrapper } from './FieldsetWrapper';
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('ResourceContactPointFields', () => {
const defaultProps: ResourceContactPointFieldsProps = {
contactPointList: mockContactPointList,
onContactPointsChanged: mockOnContactPointsChanged,
onFocus: jest.fn(),
showErrors: false,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import React, { useRef, useState } from 'react';
import classes from './ResourceContactPointFields.module.css';
import React from 'react';
import type { ResourceContactPoint } from 'app-shared/types/ResourceAdm';
import { Button, Modal } from '@digdir/design-system-react';
import { useTranslation } from 'react-i18next';
import { ResourceContactPointFieldset } from './ResourceContactPointFieldset';

const DELETE_ID_NOT_SET = -1;
import { FieldsetWrapper } from '../FieldsetWrapper';

// Empty value for when adding a new field
const emptyContactPoint: ResourceContactPoint = {
Expand All @@ -26,6 +22,11 @@ export type ResourceContactPointFieldsProps = {
* @returns void
*/
onContactPointsChanged: (contactPoints: ResourceContactPoint[]) => void;
/**
* Function to be executed when the field is focused
* @returns void
*/
onFocus: () => void;
/**
* If the error should be shown
*/
Expand All @@ -39,110 +40,42 @@ export type ResourceContactPointFieldsProps = {
*
* @property {ResourceContactPoint[]}[contactPointList] - The current contact point list
* @property {function}[onContactPointsChanged] - Function to be executed when contact points are changed
* @property {function}[onFocus] - Function to be executed when the field is focused
* @property {boolean}[showErrors] - If the error should be shown
*
* @returns {React.ReactNode} - The rendered component
*/
export const ResourceContactPointFields = ({
contactPointList,
onContactPointsChanged,
onFocus,
showErrors,
}: ResourceContactPointFieldsProps): React.ReactNode => {
const { t } = useTranslation();
const deleteModalRef = useRef<HTMLDialogElement>(null);

const [deleteId, setDeleteId] = useState<number>(DELETE_ID_NOT_SET);
const [contactPoints, setContactPoints] = useState<ResourceContactPoint[]>(
contactPointList ?? [emptyContactPoint],
);

/**
* Adds the contact point to the list
*/
const handleClickAddButton = () => {
const updatedList = [...contactPoints, emptyContactPoint];
setContactPoints(updatedList);
onContactPointsChanged(updatedList);
};

/**
* Removes the contact point from the list
*/
const handleClickRemoveButton = () => {
const updatedList = contactPoints.filter((_cp, index) => index !== deleteId);
onCloseDeleteModal();
setContactPoints(updatedList);
onContactPointsChanged(updatedList);
};

/**
* Updates the contact points when leaving a field
* @param contactPoint
* @param pos
*/
const handleLeaveTextFields = (contactPoint: ResourceContactPoint, pos: number) => {
const updatedList = [...contactPoints];
updatedList[pos] = contactPoint;
setContactPoints(updatedList);
onContactPointsChanged(updatedList);
};

/**
* Closes the delete contact point modal
*/
const onCloseDeleteModal = (): void => {
deleteModalRef.current?.close();
setDeleteId(DELETE_ID_NOT_SET);
};
/**
* Displays each contact point as a group of 4 elements
*/
const displayContactFields = contactPoints.map(
(contactPoint: ResourceContactPoint, pos: number) => (
<div key={JSON.stringify(contactPoint)}>
<ResourceContactPointFieldset
contactPoint={contactPoint}
onLeaveTextFields={(cp: ResourceContactPoint) => handleLeaveTextFields(cp, pos)}
showErrors={showErrors}
/>
<Button
size='small'
color='danger'
aria-disabled={contactPoints.length < 2}
onClick={() => {
if (contactPoints.length > 1) {
deleteModalRef.current?.showModal();
setDeleteId(pos);
}
}}
>
{t('resourceadm.about_resource_contact_remove_button')}
</Button>
</div>
),
);

return (
<>
<Modal ref={deleteModalRef} onClose={onCloseDeleteModal}>
<Modal.Header>{t('resourceadm.about_resource_contact_remove_button')}</Modal.Header>
<Modal.Content>{t('resourceadm.about_resource_contact_confirm_remove')}</Modal.Content>
<Modal.Footer>
<Button color='danger' size='small' onClick={handleClickRemoveButton}>
{t('resourceadm.about_resource_contact_confirm_remove_button')}
</Button>
<Button size='small' variant='tertiary' onClick={onCloseDeleteModal}>
{t('general.cancel')}
</Button>
</Modal.Footer>
</Modal>
<div className={classes.divider} />
{displayContactFields}
<div className={classes.buttonWrapper}>
<Button size='small' onClick={handleClickAddButton}>
{t('resourceadm.about_resource_contact_add_button')}
</Button>
</div>
</>
<FieldsetWrapper<ResourceContactPoint>
list={contactPointList}
onListFieldChanged={onContactPointsChanged}
translations={{
deleteButton: 'resourceadm.about_resource_contact_remove_button',
deleteHeader: 'resourceadm.about_resource_contact_remove_button',
deleteConfirmation: 'resourceadm.about_resource_contact_confirm_remove',
deleteConfirmationButton: 'resourceadm.about_resource_contact_confirm_remove_button',
addButton: 'resourceadm.about_resource_contact_add_button',
}}
emptyItem={emptyContactPoint}
renderItem={(
contactPoint: ResourceContactPoint,
onChange: (changedItem: ResourceContactPoint) => void,
) => {
return (
<ResourceContactPointFieldset
contactPoint={contactPoint}
onLeaveTextFields={onChange}
onFocus={onFocus}
showErrors={showErrors}
/>
);
}}
/>
);
};
Loading
Loading