diff --git a/mocks/mocks/data/formio-api/attachments.json b/mocks/mocks/data/formio-api/attachments.json new file mode 100644 index 000000000..337075470 --- /dev/null +++ b/mocks/mocks/data/formio-api/attachments.json @@ -0,0 +1,246 @@ +{ + "_id": "61e7be0045916800030aee6d", + "type": "form", + "tags": ["nav-skjema", ""], + "owner": "606ea4ab852cf50003ac20d3", + "components": [ + { + "title": "Vedlegg", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "vedlegg", + "isAttachmentPanel": true, + "type": "panel", + "label": "Vedlegg", + "input": false, + "components": [ + { + "validate": { + "required": true + }, + "attachmentValues": { + "leggerVedNaa": { + "enabled": true, + "additionalDocumentation": { + "enabled": true, + "label": "Ledetekst tilleggsinformasjon", + "description": "Beskrivelse av krav til tilleggsinformasjon" + } + }, + "ettersender": { + "enabled": true, + "showDeadline": true, + "additionalDocumentation": { + "enabled": false + } + }, + "nei": { + "enabled": true, + "additionalDocumentation": { + "enabled": false + } + } + }, + "label": "Ny vedleggskomponent", + "key": "vedlegg1", + "properties": { + "vedleggstittel": "key", + "vedleggskode": "test" + }, + "input": true, + "tableView": false, + "validateOn": "blur", + "fieldSize": "input--xxl", + "type": "attachment", + "dataSrc": "values", + "attachmentType": "other", + "navId": "el6gm88" + }, + { + "label": "Gammel radio komponent", + "values": [ + { + "value": "leggerVedNaa", + "label": "Jeg legger det ved denne søknaden (anbefalt)", + "shortcut": "" + }, + { + "value": "levertTidligere", + "label": "Jeg har levert denne dokumentasjonen tidligere", + "shortcut": "" + } + ], + "validate": { + "required": true, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "onlyAvailableItems": false + }, + "key": "oldAttachment", + "properties": { + "vedleggstittel": "Test", + "vedleggskode": "O9" + }, + "type": "radiopanel", + "input": true, + "validateOn": "blur", + "tableView": false, + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "allowMultipleMasks": false, + "addons": [], + "inputType": "radio", + "fieldSet": false, + "id": "erg3rgt" + } + ], + "tableView": false, + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": false, + "hidden": false, + "clearOnHide": false, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "tree": false, + "lazyLoad": false, + "theme": "default", + "breadcrumb": "default", + "id": "eun8oti" + } + ], + "display": "wizard", + "name": "attachments", + "title": "Attachments test", + "path": "attachments", + "properties": { + "skjemanummer": "ATT-123", + "tema": "CYP", + "innsending": "PAPIR_OG_DIGITAL", + "hasLabeledSignatures": false, + "modified": "2022-12-08T12:09:17.108Z", + "enhetMaVelgesVedPapirInnsending": false, + "publishedLanguages": [], + "modifiedBy": "fornavn.etternavn@nav.no", + "published": "2022-11-22T10:00:47.211Z", + "publishedBy": "fornavn.etternavn@nav.no", + "ettersendelsesfrist": "14" + }, + "access": [ + { + "type": "read_all", + "roles": ["628ca77305690db58c974d04", "628ca77305690db58c974d09", "628ca77305690db58c974d0e"] + } + ], + "submissionAccess": [], + "created": "2022-01-19T07:30:08.345Z", + "modified": "2022-12-08T11:46:32.068Z", + "machineName": "customComponents", + "project": "628ca77305690db58c974cfd", + "_vid": 0, + "revisions": "" +} diff --git a/mocks/mocks/routes/formio-api.js b/mocks/mocks/routes/formio-api.js index 6727bb171..f42673ce6 100644 --- a/mocks/mocks/routes/formio-api.js +++ b/mocks/mocks/routes/formio-api.js @@ -24,6 +24,7 @@ const formActivities = require('../data/formio-api/activities.json'); const formDatagridConditional = require('../data/formio-api/datagrid-conditional.json'); const nav083501 = require('../data/formio-api/nav083501.json'); const formDrivingList = require('../data/formio-api/driving-list.json'); +const formAttachment = require('../data/formio-api/attachments.json'); const allForms = [ { form: formCypress101, translations: translationsCypress101 }, @@ -45,6 +46,7 @@ const allForms = [ { form: formDatagridConditional, translations: undefined }, { form: nav083501, translations: undefined }, { form: formDrivingList, translations: undefined }, + { form: formAttachment, translations: undefined }, ]; const findTestdata = (formPath) => allForms.find((testdata) => testdata.form.path === formPath); diff --git a/packages/bygger/src/Forms/NewFormPage.tsx b/packages/bygger/src/Forms/NewFormPage.tsx index 2a783d1fa..c22f9bcef 100644 --- a/packages/bygger/src/Forms/NewFormPage.tsx +++ b/packages/bygger/src/Forms/NewFormPage.tsx @@ -78,6 +78,7 @@ const NewFormPage: React.FC = ({ formio }): React.ReactElement => { innsending: 'PAPIR_OG_DIGITAL', ettersending: 'PAPIR_OG_DIGITAL', signatures: [{ label: '', description: '', key: uuidv4() }], + ettersendelsesfrist: '14', }, components: defaultFormFields() as Component[], }, diff --git a/packages/bygger/src/Forms/publish/PublishModalComponents.test.tsx b/packages/bygger/src/Forms/publish/PublishModalComponents.test.tsx index 0456c639e..4ad099bb7 100644 --- a/packages/bygger/src/Forms/publish/PublishModalComponents.test.tsx +++ b/packages/bygger/src/Forms/publish/PublishModalComponents.test.tsx @@ -35,7 +35,6 @@ describe('PublishModalComponents', () => { const form = createFormWithAttachment({ vedleggstittel: 'Bekreftelse fra skole', vedleggskode: 'B1', - vedleggErValgfritt: 'ja', }); renderPublishSettingsModal(form); }); @@ -66,7 +65,6 @@ describe('PublishModalComponents', () => { const form = createFormWithAttachment({ vedleggstittel: 'Bekreftelse fra skole', vedleggskode: '', - vedleggErValgfritt: 'ja', }); renderPublishSettingsModal(form); }); @@ -87,7 +85,6 @@ describe('PublishModalComponents', () => { const form = createFormWithAttachment({ vedleggstittel: '', vedleggskode: 'B1', - vedleggErValgfritt: 'ja', }); renderPublishSettingsModal(form); }); @@ -108,7 +105,6 @@ describe('PublishModalComponents', () => { const form = createFormWithAttachment({ vedleggstittel: '', vedleggskode: '', - vedleggErValgfritt: 'ja', }); renderPublishSettingsModal(form); }); diff --git a/packages/bygger/src/translations/utils.test.ts b/packages/bygger/src/translations/utils.test.ts index 8aa9acd50..7bdff5ba5 100644 --- a/packages/bygger/src/translations/utils.test.ts +++ b/packages/bygger/src/translations/utils.test.ts @@ -200,58 +200,6 @@ describe('testGetAllTextsAndTypeForForm', () => { { text: 'RyiX3OuRGRdTT1AIoP6qK2MLGPkXdij36yFs0NiTY1WfptfYkuY0cBZOIk4mLLMJWgEEt0SpaQUojObrM', type: 'textarea' }, ]); }); - it('Test form with panel and text field with suffix and prefix', () => { - const actual = getFormTexts( - createFormObject( - [ - createPanelObject( - 'Introduksjon', - [ - { ...createDummyTextfield('TestFieldWithSuffix'), suffix: 'centimeter' }, - { ...createDummyTextfield('TestFieldWithprefix'), prefix: '+47' }, - createDummyTextfield('wktcZylADGp1ewUpfHa6f0DSAhCWjNzDW7b1RJkiigXise0QQaw92SJoMpGvlt8BEL8vAcXRset4KjAIV'), - ], - 'Introduksjon', - ), - ], - 'title', - ), - true, - ); - expect(actual).toEqual([ - { text: 'title', type: 'text' }, - { text: 'Introduksjon', type: 'text' }, - { text: 'TestFieldWithSuffix', type: 'text' }, - { text: 'centimeter', type: 'text' }, - { text: 'TestFieldWithprefix', type: 'text' }, - { text: '+47', type: 'text' }, - { text: 'wktcZylADGp1ewUpfHa6f0DSAhCWjNzDW7b1RJkiigXise0QQaw92SJoMpGvlt8BEL8vAcXRset4KjAIV', type: 'textarea' }, - ]); - }); - it('Test form with panel and text fields with special suffix', () => { - const actual = getFormTexts( - createFormObject( - [ - createPanelObject( - 'Introduksjon', - [ - { ...createDummyTextfield(), suffix: 'cm' }, - { ...createDummyTextfield(), suffix: 'kg' }, - { ...createDummyTextfield(), suffix: '%' }, - ], - 'Introduksjon', - ), - ], - 'title', - ), - true, - ); - expect(actual).toEqual([ - { text: 'title', type: 'text' }, - { text: 'Introduksjon', type: 'text' }, - { text: 'Tekstfelt', type: 'text' }, - ]); - }); it('Test form with duplicated text field', () => { const actual = getFormTexts( createFormObject( diff --git a/packages/bygger/src/translations/utils.ts b/packages/bygger/src/translations/utils.ts index 78f63c109..f8d6fa54b 100644 --- a/packages/bygger/src/translations/utils.ts +++ b/packages/bygger/src/translations/utils.ts @@ -1,5 +1,6 @@ import { NavFormioJs } from '@navikt/skjemadigitalisering-shared-components'; import { + AttachmentSettingValues, Component, FormioTranslationMap, Language, @@ -120,14 +121,13 @@ const getTranslatablePropertiesFromForm = (form: NavFormType) => description, additionalDescriptionLabel, additionalDescriptionText, - suffix, - prefix, data, contentForPdf, altText, buttonText, addAnother, removeAnother, + attachmentValues, }) => ({ title, label: getLabel(label, type, !!hideLabel), @@ -138,14 +138,13 @@ const getTranslatablePropertiesFromForm = (form: NavFormType) => description: getTextFromComponentProperty(description), additionalDescriptionLabel: getTextFromComponentProperty(additionalDescriptionLabel), additionalDescriptionText: getTextFromComponentProperty(additionalDescriptionText), - suffix: getTextFromComponentProperty(filterSpecialSuffix(suffix || '')), - prefix: getTextFromComponentProperty(prefix), data: data?.values ? data.values.map((value) => value.label) : undefined, contentForPdf: getTextFromComponentProperty(contentForPdf), altText: getTextFromComponentProperty(altText), buttonText: getTextFromComponentProperty(buttonText), addAnother: getTextFromComponentProperty(addAnother), removeAnother: getTextFromComponentProperty(removeAnother), + attachmentValues: getAttachmentTexts(attachmentValues), }), ); @@ -165,10 +164,23 @@ const textObject = (withInputType: boolean, value: string) => { } }; +const getAttachmentTexts = (attachmentValues?: AttachmentSettingValues) => { + if (!attachmentValues) { + return undefined; + } + + return Object.entries(attachmentValues) + .flatMap(([key, values]) => { + return [values?.additionalDocumentation?.label, values?.additionalDocumentation?.description]; + }) + .filter((values) => !!values); +}; + const getFormTexts = (form?: NavFormType, withInputType = false) => { if (!form) { return []; } + const simplifiedComponentObject = [ { title: form.title, @@ -181,7 +193,7 @@ const getFormTexts = (form?: NavFormType, withInputType = false) => { Object.keys(component) .filter((key) => component[key] !== undefined) .flatMap((key) => { - if (key === 'values' || key === 'data') { + if (key === 'values' || key === 'data' || key === 'attachmentValues') { return component[key] .filter((value) => value !== '') .map((value) => textObject(withInputType, value)) as TextObjectType; diff --git a/packages/fyllut-backend/src/routers/api/helpers/htmlBuilder.ts b/packages/fyllut-backend/src/routers/api/helpers/htmlBuilder.ts index 63670fa39..7d57e93a5 100644 --- a/packages/fyllut-backend/src/routers/api/helpers/htmlBuilder.ts +++ b/packages/fyllut-backend/src/routers/api/helpers/htmlBuilder.ts @@ -95,6 +95,8 @@ const sectionContent = (components: Summary.Component[], level: number): string return activity(component); case 'drivinglist': return drivingList(component); + case 'attachment': + return attachment(component as Summary.Attachment); default: return field(component); } @@ -121,6 +123,18 @@ ${sectionContent(component.components, level)} const field = (component: Summary.Field) => `
${component.label}
: ${component.value}
`; +const attachment = (component: Summary.Attachment) => { + let html = `
${component.label}
: ${component.value.description}
`; + if (component.value.additionalDocumentationLabel && component.value.additionalDocumentation) { + html += `
${component.value.additionalDocumentationLabel}
: ${component.value.additionalDocumentation}
`; + } + if (component.value.deadlineWarning) { + html += `
${component.value.deadlineWarning}
`; + } + + return html; +}; + const activity = (component: Summary.Activity) => `
${component.label}
: ${component.value.text}
`; diff --git a/packages/fyllut/cypress/e2e/components/attachment.cy.ts b/packages/fyllut/cypress/e2e/components/attachment.cy.ts new file mode 100644 index 000000000..b19bbdd21 --- /dev/null +++ b/packages/fyllut/cypress/e2e/components/attachment.cy.ts @@ -0,0 +1,86 @@ +/* + * Tests that the alert component (react) renders correctly + * Also tests that exisiting alerts in older form definitions renders correctly + */ + +import { TEXTS } from '@navikt/skjemadigitalisering-shared-domain'; + +describe('Attachment', () => { + beforeEach(() => { + cy.defaultIntercepts(); + cy.visit('/fyllut/attachments/vedlegg?sub=paper'); + cy.defaultWaits(); + }); + + const TITLE = { + attachment: 'Ny vedleggskomponent', + oldAttachment: 'Gammel radio komponent', + textarea: 'Ledetekst tilleggsinformasjon', + }; + + it('check different attachment settings', () => { + cy.findByRole('textbox', { name: TITLE.textarea }).should('not.exist'); + + cy.findByRole('group', { name: TITLE.attachment }) + .should('exist') + .within(() => { + cy.findByLabelText(TEXTS.statiske.attachment.leggerVedNaa).should('exist').check({ force: true }); + cy.findByLabelText(TEXTS.statiske.attachment.leggerVedNaa).should('be.checked'); + }); + + cy.findByRole('textbox', { name: TITLE.textarea }).should('exist'); + cy.findByRole('textbox', { name: TITLE.textarea }).type('Dette er en test'); + + cy.findByRole('group', { name: TITLE.oldAttachment }) + .should('exist') + .within(() => { + cy.findByLabelText(TEXTS.statiske.attachment.levertTidligere).should('exist').check({ force: true }); + cy.findByLabelText(TEXTS.statiske.attachment.levertTidligere).should('be.checked'); + }); + + cy.clickNextStep(); + + cy.get('dl') + .first() + .within(() => { + cy.get('dt').eq(0).should('contain.text', TITLE.attachment); + cy.get('dd').eq(0).should('contain.text', TEXTS.statiske.attachment.leggerVedNaa); + }); + + cy.get('dl') + .first() + .within(() => { + cy.get('dt').eq(1).should('contain.text', TITLE.textarea); + cy.get('dd').eq(1).should('contain.text', 'Dette er en test'); + }); + + cy.get('dl') + .first() + .within(() => { + cy.get('dt').eq(2).should('contain.text', TITLE.oldAttachment); + cy.get('dd').eq(2).should('contain.text', TEXTS.statiske.attachment.levertTidligere); + }); + + cy.findByRoleWhenAttached('link', { name: TEXTS.grensesnitt.summaryPage.editAnswers }).should('exist').click(); + + cy.findByRole('textbox', { name: TITLE.textarea }).should('exist'); + + cy.findByRole('group', { name: TITLE.attachment }).within(() => { + cy.findByLabelText(TEXTS.statiske.attachment.ettersender).should('exist').check({ force: true }); + cy.findByLabelText(TEXTS.statiske.attachment.ettersender).should('be.checked'); + }); + + cy.findByRole('textbox', { name: TITLE.textarea }).should('not.exist'); + cy.get('.navds-alert') + .contains(TEXTS.statiske.attachment.deadline.replace(/{{deadline}}/, 14)) + .should('exist'); + + cy.findByRole('group', { name: TITLE.attachment }).within(() => { + cy.findByLabelText(TEXTS.statiske.attachment.nei).should('exist').check({ force: true }); + cy.findByLabelText(TEXTS.statiske.attachment.nei).should('be.checked'); + }); + + cy.findByRole('textbox', { name: TITLE.textarea }).should('not.exist'); + cy.get('.navds-alert').should('not.exist'); + }); +}); diff --git a/packages/fyllut/cypress/e2e/form/basic-form.cy.ts b/packages/fyllut/cypress/e2e/form/basic-form.cy.ts index fd814beeb..830a254ca 100644 --- a/packages/fyllut/cypress/e2e/form/basic-form.cy.ts +++ b/packages/fyllut/cypress/e2e/form/basic-form.cy.ts @@ -57,7 +57,7 @@ describe('Basic form', () => { // Gå tilbake til skjema fra oppsummering, og naviger til oppsummering på nytt // for å verifisere at ingen valideringsfeil oppstår grunnet manglende verdier. - cy.findByRoleWhenAttached('link', { name: 'Fortsett utfylling' }).should('exist').click(); + cy.findByRoleWhenAttached('link', { name: TEXTS.grensesnitt.summaryPage.editAnswers }).should('exist').click(); // There is a weird re-render happening after navigating back to the form, // where the first panel will be rendered for a time before redirecting to the intended panel. diff --git a/packages/fyllut/cypress/e2e/other/event-logging-amplitude.cy.ts b/packages/fyllut/cypress/e2e/other/event-logging-amplitude.cy.ts index c0321ae4f..afb52f24f 100644 --- a/packages/fyllut/cypress/e2e/other/event-logging-amplitude.cy.ts +++ b/packages/fyllut/cypress/e2e/other/event-logging-amplitude.cy.ts @@ -6,6 +6,8 @@ * TODO: Maybe we should also have tests for opening sub=digital or sub=paper directly, to see that the "skjema startet" logging is handled correctly. */ +import { TEXTS } from '@navikt/skjemadigitalisering-shared-domain'; + describe('Amplitude', () => { before(() => { cy.configMocksServer(); @@ -106,7 +108,7 @@ describe('Amplitude', () => { // Gå tilbake til skjema fra oppsummering, og naviger til oppsummering på nytt // for å verifisere at ingen valideringsfeil oppstår grunnet manglende verdier. - cy.findByRoleWhenAttached('link', { name: 'Fortsett utfylling' }).should('exist').click(); + cy.findByRoleWhenAttached('link', { name: TEXTS.grensesnitt.summaryPage.editAnswers }).should('exist').click(); // There is a weird re-render happening after navigating back to the form, // where the first panel will be rendered for a time before redirecting to the intended panel. // If the user navigates during this time period, the navigation is ignored. @@ -114,7 +116,7 @@ describe('Amplitude', () => { cy.wait(500); cy.checkLogToAmplitude('navigere', { - lenkeTekst: 'Fortsett utfylling', + lenkeTekst: TEXTS.grensesnitt.summaryPage.editAnswers, destinasjon: '/cypress101/veiledning', }); cy.findByRole('heading', { level: 2, name: 'Oppsummering' }).should('not.exist'); diff --git a/packages/shared-components/src/components/attachment/Attachment.tsx b/packages/shared-components/src/components/attachment/Attachment.tsx new file mode 100644 index 000000000..26dc199a4 --- /dev/null +++ b/packages/shared-components/src/components/attachment/Attachment.tsx @@ -0,0 +1,100 @@ +import { Alert, Textarea } from '@navikt/ds-react'; +import { + AttachmentSettingValues, + AttachmentValue, + ComponentValue, + TEXTS, +} from '@navikt/skjemadigitalisering-shared-domain'; +import { ChangeEvent, ReactNode } from 'react'; +import SingleSelect from '../single-select/SingleSelect'; + +interface Props { + title: ReactNode; + description: ReactNode; + error: ReactNode; + value?: any; + attachmentValues?: AttachmentSettingValues | ComponentValue[]; + onChange: (value: AttachmentValue) => void; + translate: (text: string, params?: any) => string; + deadline?: string; +} + +const Attachment = ({ attachmentValues, value, title, description, error, onChange, translate, deadline }: Props) => { + const additionalDocumentation = attachmentValues?.[value?.key]?.additionalDocumentation; + const showDeadline = !!attachmentValues?.[value?.key]?.showDeadline; + + const additionalDocumentationMaxLength = 200; + + const getValues = (): ComponentValue[] => { + if (attachmentValues) { + if (Array.isArray(attachmentValues)) { + return attachmentValues; + } else if (typeof attachmentValues === 'object') { + return Object.entries(attachmentValues) + .map(([key, values]) => { + if (!values.enabled) { + return undefined; + } else { + return { + value: key, + label: translate(TEXTS.statiske.attachment[key]), + }; + } + }) + .filter((values) => !!values) as ComponentValue[]; + } + } + + return []; + }; + + const handleAttachmentChange = (key: string) => { + onChange({ + ...value, + key, + additionalDocumentation: attachmentValues?.[key]?.additionalDocumentation?.enabled + ? value?.additionalDocumentation + : undefined, + }); + }; + + const handleAdditionalDocumentationChange = (event: ChangeEvent) => { + const additionalDocumentation = event.currentTarget.value ?? ''; + if (additionalDocumentation.length <= additionalDocumentationMaxLength) { + onChange({ + ...value, + additionalDocumentation, + }); + } + }; + + return ( +
+ + {additionalDocumentation?.enabled && ( +