From 3b8560993dd5537c12e909ac4bf45578a60737ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maaria=20Wahlstr=C3=B6m?= Date: Fri, 7 Jun 2024 16:18:57 +0300 Subject: [PATCH 1/3] MSCR-491 Refactor content addition modals into one component --- mscr-ui/public/locales/en/admin.json | 24 - mscr-ui/public/locales/en/common.json | 64 +- mscr-ui/public/locales/fi/admin.json | 24 - mscr-ui/public/locales/fi/common.json | 62 +- mscr-ui/public/locales/sv/admin.json | 24 - mscr-ui/public/locales/sv/common.json | 62 +- .../components/crosswalk/crosswalk.slice.tsx | 4 +- .../schema-and-crosswalk-actionmenu/index.tsx | 38 +- .../interfaces/notifications.interface.ts | 7 +- mscr-ui/src/common/utils/get-errors.ts | 6 +- .../common/utils/hooks/use-initial-form.tsx | 53 ++ .../src/common/utils/translation-helpers.ts | 8 + .../crosswalk-form/crosswalk-form-fields.tsx | 56 +- .../crosswalk-form/crosswalk-form-modal.tsx | 307 --------- .../generate-crosswalk-payload.tsx | 55 -- .../target-and-source-schema-selector.tsx | 71 +- .../validate-crosswalk-form.tsx | 60 -- mscr-ui/src/modules/form/form.styles.tsx | 23 - mscr-ui/src/modules/form/generate-payload.tsx | 104 +++ mscr-ui/src/modules/form/index.tsx | 638 ++++++++++++++++++ .../form/modal-visibility-button/index.tsx | 20 + .../form/revision-form-modal/index.tsx | 512 -------------- .../schema-form/generate-schema-payload.tsx | 57 -- .../form/schema-form/schema-form-fields.tsx | 38 +- .../form/schema-form/schema-form-modal.tsx | 328 --------- .../form/schema-form/validate-schema-form.tsx | 61 -- mscr-ui/src/modules/form/validate-form.tsx | 105 +++ mscr-ui/src/modules/schema-view/index.tsx | 1 - .../modules/workspace/group-home/index.tsx | 62 +- .../modules/workspace/personal-home/index.tsx | 54 +- 30 files changed, 1294 insertions(+), 1634 deletions(-) create mode 100644 mscr-ui/src/common/utils/hooks/use-initial-form.tsx delete mode 100644 mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-modal.tsx delete mode 100644 mscr-ui/src/modules/form/crosswalk-form/generate-crosswalk-payload.tsx delete mode 100644 mscr-ui/src/modules/form/crosswalk-form/validate-crosswalk-form.tsx create mode 100644 mscr-ui/src/modules/form/generate-payload.tsx create mode 100644 mscr-ui/src/modules/form/index.tsx create mode 100644 mscr-ui/src/modules/form/modal-visibility-button/index.tsx delete mode 100644 mscr-ui/src/modules/form/revision-form-modal/index.tsx delete mode 100644 mscr-ui/src/modules/form/schema-form/generate-schema-payload.tsx delete mode 100644 mscr-ui/src/modules/form/schema-form/schema-form-modal.tsx delete mode 100644 mscr-ui/src/modules/form/schema-form/validate-schema-form.tsx create mode 100644 mscr-ui/src/modules/form/validate-form.tsx diff --git a/mscr-ui/public/locales/en/admin.json b/mscr-ui/public/locales/en/admin.json index 64ee53238..a967f482e 100644 --- a/mscr-ui/public/locales/en/admin.json +++ b/mscr-ui/public/locales/en/admin.json @@ -3,14 +3,12 @@ "add-file": "Add File", "add-or-drag-a-new-file-here": "Add or drag a file here", "allowed-file-formats": "Allowed file formats:", - "and": "and", "association-missing-general": "Association is missing information", "association-missing-identifier": "Association identifier is missing", "association-missing-label": "Association name is missing", "attribute-missing-general": "Attribute is missing information", "attribute-missing-identifier": "Attribute identifier is missing", "attribute-missing-label": "Attribute name is missing", - "cancel": "Cancel", "cancel-variant": "Cancel", "class-missing-general": "Class is missing information", "class-missing-identifier": "Class identifier is undefined", @@ -50,18 +48,6 @@ "content-language": "Content language", "create-new-class": "Create new class", "create-subclass-for-selected": "Create subclass for selected", - "crosswalk-form": { - "create": "Create Crosswalk", - "createTitle": "Create and register new crosswalk", - "file-required": "You must provide a crosswalk file to register", - "format-note": "NOTE: Public schemas in following formats are available for crosswalk creation: ", - "register": "Register Crosswalk", - "register-revision": "Register revision of crosswalk", - "registerTitle": "Register existing crosswalk", - "revision": "Register revision", - "supported-file-formats": "Supported crosswalk file formats are ", - "version-label": "Version label" - }, "data-model": "Data model", "data-models-added-to-this-model": "Data models added to this model", "delete-modal": { @@ -82,7 +68,6 @@ "model-success": "Model {{targetName}} removed", "model-title": "Remove model" }, - "description": "Description", "download-failed": "", "drag-files-to-upload": "Drag files to upload", "edit-concept-error": { @@ -119,14 +104,11 @@ "concept-import": "" }, "import-incorrect-excel-file-link": "", - "information-description-languages": "Information description languages", - "information-description-languages-hint-text": "Select information description languages in which the content of the data model is described.", "information-domain": "Information domain", "information-domains-all": "All information domains", "information-domains-available": "Information domains available", "language-english-with-suffix": "English EN", "language-finnish-with-suffix": "Finnish FI", - "language-input-text": "Crosswalk name", "language-swedish-with-suffix": "Swedish SV", "missing-description": "Description hasn't been set", "missing-file": "You need to add file or URI of the file to register", @@ -141,13 +123,8 @@ "missing-source-schema": "Source schema hasn't been selected", "missing-target-schema": "Target schema hasn't been selected", "modified": "Modified", - "optional": "optional", "register-crosswalk-file-uri-reference": "Provide crosswalk file URI reference", - "register-schema": "Register Schema", - "register-schema-file-required": "You must provide a schema file to register a schema.", "register-schema-file-uri-reference": "Provide schema file URI reference", - "register-schema-revision": "Register schema revision", - "register-schema-supported-file-formats": "Supported schema file formats are ", "remove": "Remove", "remove-modal": { "collection-confirmation": "", @@ -170,7 +147,6 @@ "terminology-warning": "" }, "select-class": "Select class", - "select-information-description-languages": "Select languages for information description", "site-open-link-new-window": "", "status": "Status", "status-all": "All statuses", diff --git a/mscr-ui/public/locales/en/common.json b/mscr-ui/public/locales/en/common.json index 49d7b9482..071a9095a 100644 --- a/mscr-ui/public/locales/en/common.json +++ b/mscr-ui/public/locales/en/common.json @@ -27,6 +27,7 @@ "publish-schema": "Publish schema", "revision": "Add new revision" }, + "and": "and", "association": "", "attribute": "", "breadcrumb": "", @@ -70,10 +71,55 @@ "save": "Are you sure you want to save changes?" }, "confirm-page-leave": "Are you sure you want to leave the page? Changes you made won't be saved.", + "content-form": { + "button": { + "crosswalk-create": "Create Crosswalk", + "crosswalk-register": "Register Crosswalk", + "crosswalk-revision": "Register revision", + "mscr-copy": "Register MSCR Copy", + "schema-register": "Register Schema", + "schema-revision": "Register revision" + }, + "description": "Description", + "format-label": "Format", + "format-placeholder": "Select file format", + "information-description-languages": "Information description languages", + "information-description-languages-hint-text": "Select information description languages in which the content of the data model is described.", + "information-description-languages-placeholder": "Select languages for information description", + "optional": "optional", + "state": "State", + "state-select": "Select state", + "title": { + "crosswalk-create": "Create and register new crosswalk", + "crosswalk-mscr-copy": "Register MSCR copy of crosswalk", + "crosswalk-register": "Register existing crosswalk", + "crosswalk-revision": "Register revision of crosswalk", + "schema-mscr-copy": "Register MSCR copy of schema", + "schema-register": "Register schema", + "schema-revision": "Register revision of schema" + }, + "version-label": "Version label" + }, "crosswalk-editor": { "search-from-source-schema": "Search from source schema", "search-from-target-schema": "Search from target schema" }, + "crosswalk-form": { + "all": "All", + "clear-selection": "Clear selection", + "file-required": "You must provide a crosswalk file to register.", + "format-note": "NOTE: Public schemas in following formats are available for crosswalk creation: ", + "group-workspace": "Group workspace", + "name": "Crosswalk name (required)", + "no-items": "No items", + "option-available": "option available", + "options-available": "options available", + "personal-workspace": "Personal workspace", + "search-or-select": "Search or select", + "source-schema-workspace": "Source schema workspace", + "supported-file-formats": "Supported crosswalk file formats are ", + "target-schema-workspace": "Target schema workspace" + }, "datamodel-title": "", "datamodels-all": "", "download": "Download", @@ -87,6 +133,7 @@ "not-found": "Not found" }, "error-occured": "", + "error-unauthenticated": "An error occured with login information verification. Please login to the service again.", "feedback-terminologies": "", "filter-by-language": "", "filter-character-not-allowed": "", @@ -122,6 +169,7 @@ "title": "Metadata Schema and Crosswalk Registry (MSCR)", "what-can-do": "What can you do in MSCR?" }, + "language-english-with-suffix": "English EN", "languages": { "en": "English", "fi": "Finnish", @@ -184,8 +232,12 @@ }, "not-recommended-synonym": "", "notifications": { + "add-crosswalk": "New crosswalk added", "add-crosswalk-revision": "New crosswalk version added", + "add-schema": "New schema added", "add-schema-revision": "New schema version added", + "copy-crosswalk": "MSCR copy of crosswalk added", + "copy-schema": "MSCR copy of schema added", "crosswalk-deleted": "Crosswalk was removed", "crosswalk-deprecated": "Crosswalk deprecated", "crosswalk-invalidated": "Crosswalk invalidated", @@ -229,15 +281,9 @@ "terminology-editor": "" }, "schema-form": { - "description": "Description", - "format-label": "Format", - "format-placeholder": "Select file format", - "information-description-languages": "Languages", - "information-description-languages-hint-text": "Select languages in which the content of the schema is described", - "name": "Name (required)", - "status": "Status", - "status-select": "Select status", - "version-label": "Version label" + "file-required": "You must provide a schema file to register.", + "name": "Schema name (required)", + "supported-file-formats": "Supported schema file formats are " }, "schema-tree": { "clear": "Clear", diff --git a/mscr-ui/public/locales/fi/admin.json b/mscr-ui/public/locales/fi/admin.json index 8f113d1db..d5baed5e4 100644 --- a/mscr-ui/public/locales/fi/admin.json +++ b/mscr-ui/public/locales/fi/admin.json @@ -3,14 +3,12 @@ "add-file": "", "add-or-drag-a-new-file-here": "", "allowed-file-formats": "", - "and": "", "association-missing-general": "Assosisaation kaikkia tietoja ei ole määritelty", "association-missing-identifier": "Assosisaation yksilöivää tunnusta ei ole määritelty", "association-missing-label": "Assosisaation nimeä ei ole määritelty", "attribute-missing-general": "Attribuutin kaikkia tietoja ei ole määritelty", "attribute-missing-identifier": "Attribuutin yksilöivää tunnusta ei ole määritelty", "attribute-missing-label": "Attribuutin nimeä ei ole määritelty", - "cancel": "Keskeytä", "cancel-variant": "Peruuta", "class-missing-general": "Luokan kaikkia tietoja ei ole määritelty", "class-missing-identifier": "Luokan tunnusta ei ole määritetty", @@ -50,18 +48,6 @@ "content-language": "Sisällön kieli", "create-new-class": "Luo uusi luokka", "create-subclass-for-selected": "Luo valitulle alaluokka", - "crosswalk-form": { - "create": "", - "createTitle": "", - "file-required": "", - "format-note": "", - "register": "", - "register-revision": "", - "registerTitle": "", - "revision": "", - "supported-file-formats": "", - "version-label": "" - }, "data-model": "Tietomalli", "data-models-added-to-this-model": "Tähän tietomalliin lisätyt tietomallit", "delete-modal": { @@ -82,7 +68,6 @@ "model-success": "Tietomalli {{targetName}} poistettu", "model-title": "Poista tietomalli" }, - "description": "Kuvaus", "download-failed": "", "drag-files-to-upload": "", "edit-concept-error": { @@ -119,14 +104,11 @@ "concept-import": "" }, "import-incorrect-excel-file-link": "", - "information-description-languages": "Tietosisällön kielet", - "information-description-languages-hint-text": "Valitse tietomallille kielet, joilla tietomallin sisältö on kuvattu.", "information-domain": "Tietoalue", "information-domains-all": "Kaikki tietoalueet", "information-domains-available": "Saatavilla olevat tietoalueet", "language-english-with-suffix": "englanti EN", "language-finnish-with-suffix": "suomi FI", - "language-input-text": "Tietomallin nimi", "language-swedish-with-suffix": "ruotsi SV", "missing-description": "Kuvausta ei ole määritelty", "missing-file": "", @@ -141,13 +123,8 @@ "missing-source-schema": "Lähdeskeemaa ei ole määritelty", "missing-target-schema": "Kohdeskeemaa ei ole määritelty", "modified": "Muokattu", - "optional": "valinnainen", "register-crosswalk-file-uri-reference": "", - "register-schema": "", - "register-schema-file-required": "", "register-schema-file-uri-reference": "", - "register-schema-revision": "", - "register-schema-supported-file-formats": "", "remove": "Poista", "remove-modal": { "collection-confirmation": "", @@ -170,7 +147,6 @@ "terminology-warning": "" }, "select-class": "Valitse luokka", - "select-information-description-languages": "Valitse tietomallin kielet", "site-open-link-new-window": "", "status": "Tila", "status-all": "Kaikki tilat", diff --git a/mscr-ui/public/locales/fi/common.json b/mscr-ui/public/locales/fi/common.json index 9af365aa4..38803cfee 100644 --- a/mscr-ui/public/locales/fi/common.json +++ b/mscr-ui/public/locales/fi/common.json @@ -27,6 +27,7 @@ "publish-schema": "", "revision": "" }, + "and": "", "association": "", "attribute": "", "breadcrumb": "", @@ -70,10 +71,55 @@ "save": "" }, "confirm-page-leave": "", + "content-form": { + "button": { + "crosswalk-create": "", + "crosswalk-register": "", + "crosswalk-revision": "", + "mscr-copy": "", + "schema-register": "", + "schema-revision": "" + }, + "description": "", + "format-label": "", + "format-placeholder": "", + "information-description-languages": "", + "information-description-languages-hint-text": "", + "information-description-languages-placeholder": "", + "optional": "", + "state": "", + "state-select": "", + "title": { + "crosswalk-create": "", + "crosswalk-mscr-copy": "", + "crosswalk-register": "", + "crosswalk-revision": "", + "schema-mscr-copy": "", + "schema-register": "", + "schema-revision": "" + }, + "version-label": "" + }, "crosswalk-editor": { "search-from-source-schema": "", "search-from-target-schema": "" }, + "crosswalk-form": { + "all": "", + "clear-selection": "", + "file-required": "", + "format-note": "", + "group-workspace": "", + "name": "", + "no-items": "", + "option-available": "", + "options-available": "", + "personal-workspace": "", + "search-or-select": "", + "source-schema-workspace": "", + "supported-file-formats": "", + "target-schema-workspace": "" + }, "datamodel-title": "", "datamodels-all": "", "download": "", @@ -87,6 +133,7 @@ "not-found": "" }, "error-occured": "", + "error-unauthenticated": "", "feedback-terminologies": "", "filter-by-language": "", "filter-character-not-allowed": "", @@ -122,6 +169,7 @@ "title": "", "what-can-do": "" }, + "language-english-with-suffix": "", "languages": { "en": "", "fi": "", @@ -184,8 +232,12 @@ }, "not-recommended-synonym": "", "notifications": { + "add-crosswalk": "", "add-crosswalk-revision": "", + "add-schema": "", "add-schema-revision": "", + "copy-crosswalk": "", + "copy-schema": "", "crosswalk-deleted": "", "crosswalk-deprecated": "", "crosswalk-invalidated": "", @@ -229,15 +281,9 @@ "terminology-editor": "" }, "schema-form": { - "description": "", - "format-label": "", - "format-placeholder": "", - "information-description-languages": "", - "information-description-languages-hint-text": "", + "file-required": "", "name": "", - "status": "", - "status-select": "", - "version-label": "" + "supported-file-formats": "" }, "schema-tree": { "clear": "", diff --git a/mscr-ui/public/locales/sv/admin.json b/mscr-ui/public/locales/sv/admin.json index f50fe58ba..8e8367b4f 100644 --- a/mscr-ui/public/locales/sv/admin.json +++ b/mscr-ui/public/locales/sv/admin.json @@ -3,14 +3,12 @@ "add-file": "", "add-or-drag-a-new-file-here": "", "allowed-file-formats": "", - "and": "", "association-missing-general": "", "association-missing-identifier": "", "association-missing-label": "", "attribute-missing-general": "", "attribute-missing-identifier": "", "attribute-missing-label": "", - "cancel": "", "cancel-variant": "", "class-missing-general": "", "class-missing-identifier": "", @@ -50,18 +48,6 @@ "content-language": "", "create-new-class": "", "create-subclass-for-selected": "", - "crosswalk-form": { - "create": "", - "createTitle": "", - "file-required": "", - "format-note": "", - "register": "", - "register-revision": "", - "registerTitle": "", - "revision": "", - "supported-file-formats": "", - "version-label": "" - }, "data-model": "", "data-models-added-to-this-model": "", "delete-modal": { @@ -82,7 +68,6 @@ "model-success": "", "model-title": "" }, - "description": "", "download-failed": "", "drag-files-to-upload": "", "edit-concept-error": { @@ -119,14 +104,11 @@ "concept-import": "" }, "import-incorrect-excel-file-link": "", - "information-description-languages": "", - "information-description-languages-hint-text": "", "information-domain": "", "information-domains-all": "", "information-domains-available": "", "language-english-with-suffix": "", "language-finnish-with-suffix": "", - "language-input-text": "", "language-swedish-with-suffix": "", "missing-description": "", "missing-file": "", @@ -141,13 +123,8 @@ "missing-source-schema": "", "missing-target-schema": "", "modified": "", - "optional": "", "register-crosswalk-file-uri-reference": "", - "register-schema": "", - "register-schema-file-required": "", "register-schema-file-uri-reference": "", - "register-schema-revision": "", - "register-schema-supported-file-formats": "", "remove": "", "remove-modal": { "collection-confirmation": "", @@ -170,7 +147,6 @@ "terminology-warning": "" }, "select-class": "", - "select-information-description-languages": "", "site-open-link-new-window": "", "status": "", "status-all": "", diff --git a/mscr-ui/public/locales/sv/common.json b/mscr-ui/public/locales/sv/common.json index f6f22f794..f16985a3b 100644 --- a/mscr-ui/public/locales/sv/common.json +++ b/mscr-ui/public/locales/sv/common.json @@ -27,6 +27,7 @@ "publish-schema": "", "revision": "" }, + "and": "", "association": "", "attribute": "", "breadcrumb": "", @@ -70,10 +71,55 @@ "save": "" }, "confirm-page-leave": "", + "content-form": { + "button": { + "crosswalk-create": "", + "crosswalk-register": "", + "crosswalk-revision": "", + "mscr-copy": "", + "schema-register": "", + "schema-revision": "" + }, + "description": "", + "format-label": "", + "format-placeholder": "", + "information-description-languages": "", + "information-description-languages-hint-text": "", + "information-description-languages-placeholder": "", + "optional": "", + "state": "", + "state-select": "", + "title": { + "crosswalk-create": "", + "crosswalk-mscr-copy": "", + "crosswalk-register": "", + "crosswalk-revision": "", + "schema-mscr-copy": "", + "schema-register": "", + "schema-revision": "" + }, + "version-label": "" + }, "crosswalk-editor": { "search-from-source-schema": "", "search-from-target-schema": "" }, + "crosswalk-form": { + "all": "", + "clear-selection": "", + "file-required": "", + "format-note": "", + "group-workspace": "", + "name": "", + "no-items": "", + "option-available": "", + "options-available": "", + "personal-workspace": "", + "search-or-select": "", + "source-schema-workspace": "", + "supported-file-formats": "", + "target-schema-workspace": "" + }, "datamodel-title": "", "datamodels-all": "", "download": "", @@ -87,6 +133,7 @@ "not-found": "" }, "error-occured": "", + "error-unauthenticated": "", "feedback-terminologies": "", "filter-by-language": "", "filter-character-not-allowed": "", @@ -122,6 +169,7 @@ "title": "", "what-can-do": "" }, + "language-english-with-suffix": "", "languages": { "en": "", "fi": "", @@ -184,8 +232,12 @@ }, "not-recommended-synonym": "", "notifications": { + "add-crosswalk": "", "add-crosswalk-revision": "", + "add-schema": "", "add-schema-revision": "", + "copy-crosswalk": "", + "copy-schema": "", "crosswalk-deleted": "", "crosswalk-deprecated": "", "crosswalk-invalidated": "", @@ -229,15 +281,9 @@ "terminology-editor": "" }, "schema-form": { - "description": "", - "format-label": "", - "format-placeholder": "", - "information-description-languages": "", - "information-description-languages-hint-text": "", + "file-required": "", "name": "", - "status": "", - "status-select": "", - "version-label": "" + "supported-file-formats": "" }, "schema-tree": { "clear": "", diff --git a/mscr-ui/src/common/components/crosswalk/crosswalk.slice.tsx b/mscr-ui/src/common/components/crosswalk/crosswalk.slice.tsx index ee21879a5..41fa0dae4 100644 --- a/mscr-ui/src/common/components/crosswalk/crosswalk.slice.tsx +++ b/mscr-ui/src/common/components/crosswalk/crosswalk.slice.tsx @@ -21,7 +21,7 @@ export const crosswalkApi = createApi({ } }, endpoints: (builder) => ({ - putCrosswalk: builder.mutation({ + putCrosswalk: builder.mutation>({ query: (value) => ({ url: '/crosswalk', method: 'PUT', @@ -29,7 +29,7 @@ export const crosswalkApi = createApi({ }), }), //Register Crosswalk with file - putCrosswalkFull: builder.mutation({ + putCrosswalkFull: builder.mutation({ query: (file) => ({ url: '/crosswalkFull', method: 'PUT', diff --git a/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx b/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx index 44de22518..ea83dd439 100644 --- a/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx +++ b/mscr-ui/src/common/components/schema-and-crosswalk-actionmenu/index.tsx @@ -14,17 +14,13 @@ import { ActionMenuTypes, Type } from '@app/common/interfaces/search.interface'; import { State } from '@app/common/interfaces/state.interface'; import ConfirmModal from '@app/common/components/confirmation-modal'; import { useStoreDispatch } from '@app/store'; -import { - clearNotification, - setNotification, -} from '@app/common/components/notifications/notifications.slice'; +import { setNotification } from '@app/common/components/notifications/notifications.slice'; import { mscrSearchApi } from '@app/common/components/mscr-search/mscr-search.slice'; import { CrosswalkWithVersionInfo } from '@app/common/interfaces/crosswalk.interface'; import { SchemaWithVersionInfo } from '@app/common/interfaces/schema.interface'; -import RevisionFormModal from '@app/modules/form/revision-form-modal'; -import { - ActionMenuWrapper, -} from '@app/common/components/schema-and-crosswalk-actionmenu/schema-and-crosswalk-actionmenu.styles'; +import { ActionMenuWrapper } from '@app/common/components/schema-and-crosswalk-actionmenu/schema-and-crosswalk-actionmenu.styles'; +import FormModal, { ModalType } from '@app/modules/form'; +import { Format } from '@app/common/interfaces/format.interface'; interface SchemaAndCrosswalkActionmenuProps { type: ActionMenuTypes; @@ -235,7 +231,9 @@ export default function SchemaAndCrosswalkActionMenu({ {t('actionmenu.edit-metadata')} setPublishConfirmModalOpen(true)} > {type === ActionMenuTypes.Schema || @@ -266,7 +264,9 @@ export default function SchemaAndCrosswalkActionMenu({ : t('actionmenu.deprecate-crosswalk')} setDeleteConfirmModalOpen(true)} > {t('actionmenu.delete-draft')} @@ -289,7 +289,8 @@ export default function SchemaAndCrosswalkActionMenu({ setRevisionModalOpen(true)}> + onClick={() => setRevisionModalOpen(true)} + > {t('actionmenu.revision')} @@ -389,16 +390,21 @@ export default function SchemaAndCrosswalkActionMenu({ text2={undefined} /> - ); diff --git a/mscr-ui/src/common/interfaces/notifications.interface.ts b/mscr-ui/src/common/interfaces/notifications.interface.ts index f479560e1..d68713aa3 100644 --- a/mscr-ui/src/common/interfaces/notifications.interface.ts +++ b/mscr-ui/src/common/interfaces/notifications.interface.ts @@ -18,8 +18,11 @@ export type NotificationKeys = | 'EDIT_MAPPINGS' | 'FINISH_EDITING_MAPPINGS' | 'CROSSWALK_REVISION' - | 'SCHEMA_REVISION'; + | 'SCHEMA_REVISION' + | 'CROSSWALK_ADD' + | 'SCHEMA_ADD' + | 'CROSSWALK_COPY' + | 'SCHEMA_COPY'; // ToDo: Add more notifications, like these below: // | 'MAPPING_ADD' - // | 'MAPPING_EDIT' // | 'MAPPING_DELETE' diff --git a/mscr-ui/src/common/utils/get-errors.ts b/mscr-ui/src/common/utils/get-errors.ts index 614860e1e..06862b637 100644 --- a/mscr-ui/src/common/utils/get-errors.ts +++ b/mscr-ui/src/common/utils/get-errors.ts @@ -1,20 +1,18 @@ -import { FormErrors as CrosswalkFormErrors } from '@app/modules/form/crosswalk-form/validate-crosswalk-form'; -import { FormErrors as SchemaFormErrors } from '@app/modules/form/schema-form/validate-schema-form'; import { translateLanguage, translateModelFormErrors, } from '@app/common/utils/translation-helpers'; import { TFunction } from 'next-i18next'; +import { InputErrors } from '@app/modules/form/validate-form'; export default function getErrors( t: TFunction, - errors?: CrosswalkFormErrors | SchemaFormErrors + errors?: InputErrors ): string[] { if (!errors) { return []; } - const langsWithError = Object.entries(errors) .filter(([_, value]) => Array.isArray(value)) ?.flatMap(([key, value]) => diff --git a/mscr-ui/src/common/utils/hooks/use-initial-form.tsx b/mscr-ui/src/common/utils/hooks/use-initial-form.tsx new file mode 100644 index 000000000..da537913d --- /dev/null +++ b/mscr-ui/src/common/utils/hooks/use-initial-form.tsx @@ -0,0 +1,53 @@ +import { useTranslation } from 'next-i18next'; +import { Format } from '@app/common/interfaces/format.interface'; +import { State } from '@app/common/interfaces/state.interface'; +import { Type } from '@app/common/interfaces/search.interface'; +import { LanguageBlockType } from 'yti-common-ui/components/form/language-selector'; + +export interface FormType { + format: Format; + state: State; + languages: (LanguageBlockType & { selected: boolean })[]; + versionLabel: string; + sourceSchema?: string; + targetSchema?: string; +} + +export function useInitialForm(type: Type): FormType { + const { t } = useTranslation('admin'); + const initialForm : FormType = { + format: type == Type.Crosswalk ? Format.Mscr : Format.Csv, + state: State.Draft, + languages: [ + // Hiding Swedish and Finnish for now + // { + // labelText: t('language-finnish-with-suffix'), + // uniqueItemId: 'fi', + // title: '', + // description: '', + // selected: false, + // }, + // { + // labelText: t('language-swedish-with-suffix'), + // uniqueItemId: 'sv', + // title: '', + // description: '', + // selected: false, + // }, + { + labelText: t('language-english-with-suffix'), + uniqueItemId: 'en', + title: '', + description: '', + selected: true, + }, + ], + versionLabel: '1', + }; + if (type == Type.Crosswalk) { + initialForm.sourceSchema = ''; + initialForm.targetSchema = ''; + } + + return initialForm; +} diff --git a/mscr-ui/src/common/utils/translation-helpers.ts b/mscr-ui/src/common/utils/translation-helpers.ts index 5761c8a43..eb2cfd61a 100644 --- a/mscr-ui/src/common/utils/translation-helpers.ts +++ b/mscr-ui/src/common/utils/translation-helpers.ts @@ -340,6 +340,14 @@ export function translateNotification( return t('notifications.add-crosswalk-revision'); case 'SCHEMA_REVISION': return t('notifications.add-schema-revision'); + case 'CROSSWALK_ADD': + return t('notifications.add-crosswalk'); + case 'SCHEMA_ADD': + return t('notifications.add-schema'); + case 'CROSSWALK_COPY': + return t('notifications.copy-crosswalk'); + case 'SCHEMA_COPY': + return t('notifications.copy-schema'); default: return ''; } diff --git a/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-fields.tsx b/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-fields.tsx index 08e5aa240..63fca3605 100644 --- a/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-fields.tsx +++ b/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-fields.tsx @@ -1,7 +1,5 @@ import { useTranslation } from 'next-i18next'; import { DropdownItem, Text } from 'suomifi-ui-components'; -import { FormErrors } from './validate-crosswalk-form'; -import { CrosswalkFormType } from '@app/common/interfaces/crosswalk.interface'; import TargetAndSourceSchemaSelector from './target-and-source-schema-selector'; import { possibleStatesAtRegistration, @@ -18,15 +16,17 @@ import { formatsAvailableForCrosswalkRegistration, } from '@app/common/interfaces/format.interface'; import { WideDropdown } from '@app/modules/form/crosswalk-form/crosswalk-form.styles'; +import { FormType } from '@app/common/utils/hooks/use-initial-form'; +import { InputErrors } from '@app/modules/form/validate-form'; interface RegisterCrosswalkFormProps { - formData: CrosswalkFormType; - setFormData: (value: CrosswalkFormType) => void; + formData: FormType; + setFormData: (value: FormType) => void; createNew: boolean; - isRevision?: boolean; + hasInitialData?: boolean; userPosted: boolean; disabled?: boolean; - errors?: FormErrors; + errors?: InputErrors; editMode?: boolean; groupWorkspacePid: string | undefined; } @@ -35,13 +35,13 @@ export default function CrosswalkFormFields({ formData, setFormData, createNew, - isRevision, + hasInitialData, userPosted, disabled, - errors, groupWorkspacePid - + errors, + groupWorkspacePid, }: RegisterCrosswalkFormProps) { - const { t } = useTranslation('admin'); + const { t } = useTranslation(); return ( @@ -49,7 +49,7 @@ export default function CrosswalkFormFields({ formData={formData} setFormData={setFormData} createNew={createNew} - schemaSelectorDisabled={isRevision} + schemaSelectorDisabled={hasInitialData} groupWorkspacePid={groupWorkspacePid} > {createNew && ( @@ -70,10 +70,14 @@ export default function CrosswalkFormFields({
setFormData({ ...formData, @@ -90,9 +94,9 @@ export default function CrosswalkFormFields({
setFormData({ @@ -120,9 +124,13 @@ export default function CrosswalkFormFields({
setFormData({ @@ -130,14 +138,14 @@ export default function CrosswalkFormFields({ languages: e, }) } - versionLabelCaption={t('crosswalk-form.version-label')} + versionLabelCaption={t('content-form.version-label')} versionLabel={formData.versionLabel ?? '1'} setVersionLabel={(e) => setVersionLabel(e)} userPosted={userPosted} translations={{ - textInput: t('language-input-text'), - textDescription: t('description'), - optionalText: t('optional'), + textInput: t('crosswalk-form.name'), + textDescription: t('content-form.description'), + optionalText: t('content-form.optional'), }} allowItemAddition={false} ariaChipActionLabel={''} diff --git a/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-modal.tsx b/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-modal.tsx deleted file mode 100644 index 0cb773b22..000000000 --- a/mscr-ui/src/modules/form/crosswalk-form/crosswalk-form-modal.tsx +++ /dev/null @@ -1,307 +0,0 @@ -import { useGetAuthenticatedUserQuery } from '@app/common/components/login/login.slice'; -import { useCallback, useEffect, useState } from 'react'; -import { - Button, - IconPlus, - InlineAlert, - Modal, - ModalContent, - ModalFooter, - ModalTitle, -} from 'suomifi-ui-components'; -import { useBreakpoints } from 'yti-common-ui/components/media-query'; -import { FormErrors, validateCrosswalkForm } from './validate-crosswalk-form'; -import FormFooterAlert from 'yti-common-ui/components/form-footer-alert'; -import { translateFileUploadError } from '@app/common/utils/translation-helpers'; -import { useTranslation } from 'next-i18next'; -import generateCrosswalkPayload from './generate-crosswalk-payload'; -import getApiError from '@app/common/utils/getApiErrors'; -import { useRouter } from 'next/router'; -import HasPermission from '@app/common/utils/has-permission'; -import { useInitialCrosswalkForm } from '@app/common/utils/hooks/use-initial-crosswalk-form'; -import { - usePutCrosswalkFullMutation, - usePutCrosswalkMutation, -} from '@app/common/components/crosswalk/crosswalk.slice'; -import CrosswalkForm from './crosswalk-form-fields'; -import getErrors from '@app/common/utils/get-errors'; -import FileDropAreaMscr from '@app/common/components/file-drop-area-mscr'; -import { fileExtensionsAvailableForCrosswalkRegistrationAttachments } from '@app/common/interfaces/format.interface'; -import SpinnerOverlay, { - delay, - SpinnerType, -} from '@app/common/components/spinner-overlay'; - -interface CrosswalkFormModalProps { - refetch: () => void; - createNew?: boolean; - groupContent: boolean; - pid?: string; -} - -// For the time being, using as schema metadata form, Need to update the props accordingly - -export default function CrosswalkFormModal({ - refetch, - createNew = false, - groupContent, - pid, -}: CrosswalkFormModalProps) { - const { t } = useTranslation('admin'); - const { isSmall } = useBreakpoints(); - const router = useRouter(); - const [visible, setVisible] = useState(false); - const [crosswalkFormInitialData] = useState(useInitialCrosswalkForm()); - const [formData, setFormData] = useState(crosswalkFormInitialData); - const [errors, setErrors] = useState(); - const [userPosted, setUserPosted] = useState(false); - const [skip, setSkip] = useState(true); - const { data: authenticatedUser } = useGetAuthenticatedUserQuery(undefined, { - skip, - }); - const [putCrosswalkFull, registerCrosswalkResult] = - usePutCrosswalkFullMutation(); - const [putCrosswalk, newCrosswalkResult] = usePutCrosswalkMutation(); - const [, setIsValid] = useState(false); - const [fileData, setFileData] = useState(); - const [fileUri, setFileUri] = useState(''); - const [submitAnimationVisible, setSubmitAnimationVisible] = - useState(false); - - const handleOpen = () => { - setSkip(false); - setVisible(true); - }; - - const handleClose = useCallback(() => { - setVisible(false); - setSkip(true); - setUserPosted(false); - setFormData(crosswalkFormInitialData); - setFileData(null); - setFileUri(null); - }, [crosswalkFormInitialData]); - - useEffect(() => { - const result = createNew ? newCrosswalkResult : registerCrosswalkResult; - if (userPosted && result.isSuccess && !submitAnimationVisible) { - refetch(); - handleClose(); - router.push(`/crosswalk/${result.data.pid}`); - } - }, [ - registerCrosswalkResult, - refetch, - userPosted, - handleClose, - router, - createNew, - newCrosswalkResult, - submitAnimationVisible, - ]); - - const spinnerDelay = async () => { - setSubmitAnimationVisible(true); - await delay(2000); - return Promise.resolve(); - }; - - function isFormValid() { - if (errors) { - return !Object.values(errors).includes(true); - } else { - return false; - } - } - - const handleSubmit = () => { - scrollToModalTop(); - setUserPosted(true); - if (!formData) { - return; - } - - const errors = validateCrosswalkForm(formData); - setErrors(errors); - if (Object.values(errors).includes(true)) { - return; - } - - const payload = generateCrosswalkPayload( - formData, - groupContent, - pid, - authenticatedUser - ); - const crosswalkFormData = new FormData(); - crosswalkFormData.append('metadata', JSON.stringify(payload)); - - if (isFormValid() && !createNew && fileUri && fileUri.length > 0) { - crosswalkFormData.append('contentURL', fileUri); - Promise.all([spinnerDelay(), putCrosswalkFull(crosswalkFormData)]).then( - (values) => { - setSubmitAnimationVisible(false); - } - ); - } else if (isFormValid() && !createNew && fileData) { - crosswalkFormData.append('file', fileData); - Promise.all([spinnerDelay(), putCrosswalkFull(crosswalkFormData)]).then( - (values) => { - setSubmitAnimationVisible(false); - } - ); - } else if (isFormValid() && createNew) { - Promise.all([spinnerDelay(), putCrosswalk(payload)]).then((values) => { - setSubmitAnimationVisible(false); - }); - } else { - return; - } - }; - - useEffect(() => { - if (!userPosted) { - return; - } - - const errors = validateCrosswalkForm(formData); - setErrors(errors); - }, [userPosted, formData]); - - function gatherErrorMessages() { - const inputErrors = getErrors(t, errors); - const result = createNew ? newCrosswalkResult : registerCrosswalkResult; - if (result.isError) { - const errorObject = getApiError(result.error); - if (errorObject.status && errorObject.message) { - inputErrors.push(`${errorObject.status}: ${errorObject.message}`); - } - const errorMessage = getApiError(result.error).message; - if (errorMessage) return [...inputErrors, errorMessage]; - } - return inputErrors; - } - - function renderButton() { - return ( - - ); - } - - function scrollToModalTop() { - const modalTop = document.getElementById('modalTop'); - if (modalTop) { - modalTop.scrollIntoView(); - } - } - - return ( - <> - {groupContent && - HasPermission({ - actions: ['CREATE_CROSSWALK'], - targetOrganization: pid, - }) ? ( -
{renderButton()}
- ) : !groupContent ? ( -
{renderButton()}
- ) : ( -
- )} - handleClose()} - variant={isSmall ? 'smallScreen' : 'default'} - > - - <> - - -
- - { - createNew - ? t('crosswalk-form.createTitle') - : t('crosswalk-form.registerTitle') /*'Add New Crosswalk'*/ - } - - {/**/} - {/* {'Add New Crosswalk Description'}*/} - {/**/} - - {!createNew && ( - - )} -
- - {authenticatedUser && - authenticatedUser.anonymous && - !submitAnimationVisible && ( - - {t('error-unauthenticated')} - - )} - {userPosted && !submitAnimationVisible && ( - - )} - - - - -
- - ); -} diff --git a/mscr-ui/src/modules/form/crosswalk-form/generate-crosswalk-payload.tsx b/mscr-ui/src/modules/form/crosswalk-form/generate-crosswalk-payload.tsx deleted file mode 100644 index ad98120e3..000000000 --- a/mscr-ui/src/modules/form/crosswalk-form/generate-crosswalk-payload.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { - Crosswalk, - CrosswalkFormType, -} from '@app/common/interfaces/crosswalk.interface'; -import { Visibility } from '@app/common/interfaces/search.interface'; -import { State } from '@app/common/interfaces/state.interface'; -import { MscrUser } from '@app/common/interfaces/mscr-user.interface'; -import { Organization } from '@app/common/interfaces/organizations.interface'; - -// here we are creating crosswalk payload by converting the form data to crosswalk type - -export default function generateCrosswalkPayload( - data: CrosswalkFormType, - groupContent: boolean, - pid?: string, - user?: MscrUser -): Partial { - const organizations: Organization[] = []; - if (user && groupContent && pid) { - const ownerOrg = user?.organizations.find((x) => x.id == pid); - if (ownerOrg) organizations.push(ownerOrg); - } - return { - format: data.format, - description: data.languages - .filter((l) => l.description !== '') - .reduce( - (obj, l) => ({ - ...obj, - [l.uniqueItemId]: l.description, - }), - {} - ), - label: data.languages - .filter((l) => l.title !== '') - .reduce( - (obj, l) => ({ - ...obj, - [l.uniqueItemId]: l.title, - }), - {} - ), - languages: data.languages - .filter((l) => l.title !== '') - .map((l) => l.uniqueItemId), - status: 'VALID', - state: data.state, - sourceSchema: data.sourceSchema, - targetSchema: data.targetSchema, - versionLabel: data.versionLabel ?? '1', - visibility: - data.state !== State.Draft ? Visibility.Public : Visibility.Private, - organizations: organizations.map((o: { id: any }) => o.id), - }; -} diff --git a/mscr-ui/src/modules/form/crosswalk-form/target-and-source-schema-selector.tsx b/mscr-ui/src/modules/form/crosswalk-form/target-and-source-schema-selector.tsx index 8c0fc5ad1..6f256e574 100644 --- a/mscr-ui/src/modules/form/crosswalk-form/target-and-source-schema-selector.tsx +++ b/mscr-ui/src/modules/form/crosswalk-form/target-and-source-schema-selector.tsx @@ -1,5 +1,4 @@ import { DropdownItem, TextInput } from 'suomifi-ui-components'; -import { CrosswalkFormType } from '@app/common/interfaces/crosswalk.interface'; import * as React from 'react'; import { useGetPublicSchemasQuery, @@ -18,15 +17,14 @@ import { WideSingleSelect, } from '@app/modules/form/crosswalk-form/crosswalk-form.styles'; -import { - selectLogin, -} from '@app/common/components/login/login.slice'; +import { selectLogin } from '@app/common/components/login/login.slice'; import { useSelector } from 'react-redux'; import { User } from 'yti-common-ui/interfaces/user.interface'; +import { FormType } from '@app/common/utils/hooks/use-initial-form'; interface CrosswalkFormProps { - formData: CrosswalkFormType; - setFormData: (value: CrosswalkFormType) => void; + formData: FormType; + setFormData: (value: FormType) => void; createNew: boolean; schemaSelectorDisabled?: boolean; groupWorkspacePid: string | undefined; @@ -48,7 +46,8 @@ export default function TargetAndSourceSchemaSelector({ formData, setFormData, createNew, - schemaSelectorDisabled, groupWorkspacePid + schemaSelectorDisabled, + groupWorkspacePid, }: CrosswalkFormProps) { const user: User = useSelector(selectLogin()); const formatRestrictions = createNew @@ -56,12 +55,18 @@ export default function TargetAndSourceSchemaSelector({ : []; const { data, isSuccess } = useGetPublicSchemasQuery(formatRestrictions); - const { data: sourceSchemaData } = useGetSchemaQuery(formData.sourceSchema, { - skip: !schemaSelectorDisabled, - }); - const { data: targetSchemaData } = useGetSchemaQuery(formData.targetSchema, { - skip: !schemaSelectorDisabled, - }); + const { data: sourceSchemaData } = useGetSchemaQuery( + formData.sourceSchema ?? '', + { + skip: !schemaSelectorDisabled, + } + ); + const { data: targetSchemaData } = useGetSchemaQuery( + formData.targetSchema ?? '', + { + skip: !schemaSelectorDisabled, + } + ); //defaultSchemas.push({ labelText: 'test', uniqueItemId: 'test'}); const [dataLoaded, setDataLoaded] = useState(false); const [defaultSchemas, setDefaultSchemas] = useState( @@ -104,22 +109,22 @@ export default function TargetAndSourceSchemaSelector({ const workspaceValuesPersonalCrosswalks: SelectableWorkspace[] = [ { - labelText: 'All', + labelText: t('crosswalk-form.all'), uniqueItemId: 'all', }, { - labelText: 'Personal workspace', + labelText: t('crosswalk-form.personal-workspace'), uniqueItemId: 'personalWorkspace', }, ]; const workspaceValuesGroupCrosswalks: SelectableWorkspace[] = [ { - labelText: 'All', + labelText: t('crosswalk-form.all'), uniqueItemId: 'all', }, { - labelText: 'Group workspace', + labelText: t('crosswalk-form.group-workspace'), uniqueItemId: 'groupWorkspace', }, ]; @@ -137,7 +142,7 @@ export default function TargetAndSourceSchemaSelector({ uniqueItemId: item._source.id, organizationIds: item._source.organizations.length > 0 - ? item._source.organizations.map(organization => organization.id) + ? item._source.organizations.map((organization) => organization.id) : [''], owner: item._source?.owner && item._source?.owner?.length > 0 @@ -171,7 +176,7 @@ export default function TargetAndSourceSchemaSelector({ setSourceSchemas( defaultSchemas.filter((item) => { if (groupWorkspacePid) { - return item.organizationIds.includes(groupWorkspacePid); + return item.organizationIds.includes(groupWorkspacePid); } }) ); @@ -225,7 +230,7 @@ export default function TargetAndSourceSchemaSelector({ <>
{ setSelectedSourceWorkspace(e); @@ -243,14 +248,16 @@ export default function TargetAndSourceSchemaSelector({
- amount === 1 ? 'option available' : 'options available' + amount === 1 + ? t('crosswalk-form.option-available') + : t('crosswalk-form.options-available') } onItemSelect={setSource} /> @@ -278,7 +285,7 @@ export default function TargetAndSourceSchemaSelector({ <>
{ setSelectedTargetWorkspace(e); @@ -296,14 +303,16 @@ export default function TargetAndSourceSchemaSelector({
- amount === 1 ? 'option available' : 'options available' + amount === 1 + ? t('crosswalk-form.option-available') + : t('crosswalk-form.options-available') } onItemSelect={setTarget} /> diff --git a/mscr-ui/src/modules/form/crosswalk-form/validate-crosswalk-form.tsx b/mscr-ui/src/modules/form/crosswalk-form/validate-crosswalk-form.tsx deleted file mode 100644 index 0737e94af..000000000 --- a/mscr-ui/src/modules/form/crosswalk-form/validate-crosswalk-form.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { CrosswalkFormType } from '@app/common/interfaces/crosswalk.interface'; - -export interface FormErrors { - languageAmount: boolean; - titleAmount: string[]; - prefix: boolean; - serviceCategories: boolean; - organizations?: boolean; - fileData?: boolean; - sourceSchema?: boolean; - targetSchema?: boolean; -} - -export function validateCrosswalkForm(data: CrosswalkFormType) { - const errors: FormErrors = { - languageAmount: false, - titleAmount: [], - prefix: false, - serviceCategories: false, - organizations: false, - sourceSchema: false, - targetSchema: false, - }; - - const selectedLanguages = data.languages.filter((lang) => lang.selected); - - // At least one language should be selected - if (selectedLanguages.length < 1) { - errors.languageAmount = true; - } - - // All selected languages should have a title - if ( - selectedLanguages.filter( - (lang) => !lang.title || lang.title === '' || lang.title.length < 1 - ).length > 0 - ) { - const langsWithError = selectedLanguages - .filter( - (lang) => !lang.title || lang.title === '' || lang.title.length < 1 - ) - .map((lang) => lang.uniqueItemId); - - errors.titleAmount = langsWithError ?? []; - } - - // Source schema should be selected - if (!data.sourceSchema || data.sourceSchema.length < 1) { - errors.sourceSchema = true; - } - - // Target schema should be selected - if (!data.targetSchema || data.targetSchema.length < 1) { - errors.targetSchema = true; - } - - // Currently crosswalk is valif withou any organization also - - return errors; -} diff --git a/mscr-ui/src/modules/form/form.styles.tsx b/mscr-ui/src/modules/form/form.styles.tsx index a01e9dad9..97f8b48f2 100644 --- a/mscr-ui/src/modules/form/form.styles.tsx +++ b/mscr-ui/src/modules/form/form.styles.tsx @@ -11,29 +11,6 @@ export const ModelFormContainer = styled(Block)` } `; -export const BlockContainer = styled.div` - display: flex; - flex-direction: column; - gap: ${(props) => props.theme.suomifi.spacing.m}; -`; - export const WideMultiSelect = styled(MultiSelect)` min-width: 100%; `; - -export const AddBlockWrapper = styled(Block)` - display: flex; - flex-direction: column; - - > .fi-label { - margin-bottom: 6px; - } - - > button { - width: max-content; - - :not(:last-child) { - margin-bottom: ${(props) => props.theme.suomifi.spacing.m}; - } - } -`; diff --git a/mscr-ui/src/modules/form/generate-payload.tsx b/mscr-ui/src/modules/form/generate-payload.tsx new file mode 100644 index 000000000..e5060bf59 --- /dev/null +++ b/mscr-ui/src/modules/form/generate-payload.tsx @@ -0,0 +1,104 @@ +import { FormType } from '@app/common/utils/hooks/use-initial-form'; +import { MscrUser } from '@app/common/interfaces/mscr-user.interface'; +import { ModalType } from '@app/modules/form/index'; +import { Organization } from '@app/common/interfaces/organizations.interface'; +import { Type, Visibility } from '@app/common/interfaces/search.interface'; +import { State } from '@app/common/interfaces/state.interface'; +import { Format } from '@app/common/interfaces/format.interface'; +import { Metadata } from '@app/common/interfaces/metadata.interface'; + +export default function generatePayload( + data: FormType, + contentType: Type, + user: MscrUser, + modalType: ModalType, + organizationPid?: string +): Partial { + const organizations: Organization[] = []; + if (user && organizationPid && organizationPid !== '') { + const ownerOrg = user?.organizations.find((x) => x.id == organizationPid); + if (ownerOrg) organizations.push(ownerOrg); + } + const payload = { + description: data.languages + .filter((l: { description: string }) => l.description !== '') + .reduce( + ( + obj: { [lang: string]: string }, + l: { uniqueItemId: string; description: string } + ) => ({ + ...obj, + [l.uniqueItemId]: l.description, + }), + {} + ), + label: data.languages + .filter((l: { title: string }) => l.title !== '') + .reduce( + ( + obj: { [lang: string]: string }, + l: { uniqueItemId: string; title: string } + ) => ({ + ...obj, + [l.uniqueItemId]: l.title, + }), + {} + ), + languages: data.languages + .filter((l: { title: string }) => l.title !== '') + .map((l: { uniqueItemId: string }) => l.uniqueItemId), + organizations: organizations.map((o: { id: string }) => o.id), + status: 'DRAFT', + format: data.format, + state: data.state, + versionLabel: data.versionLabel, + visibility: + data.state !== State.Draft ? Visibility.Public : Visibility.Private, + namespace: 'http://test.com', // Schema + sourceSchema: data.sourceSchema, // Crosswalk + targetSchema: data.targetSchema, // Crosswalk + }; + + const { sourceSchema, targetSchema, ...schemaPayload } = payload; + const { namespace, ...crosswalkPayload } = payload; + + if (modalType == ModalType.RevisionFull) { + if (contentType == Type.Schema) { + const { namespace, organizations, ...revisionPayload } = schemaPayload; + return revisionPayload; + } else if (contentType == Type.Crosswalk) { + const { organizations, ...revisionPayload } = crosswalkPayload; + console.log('crosswalkfullpayload: ', crosswalkPayload); + console.log('revisionfullpayload: ', revisionPayload); + return revisionPayload; + } + } else if (modalType == ModalType.RevisionMscr) { + if (contentType == Type.Schema) { + const { namespace, organizations, format, ...revisionPayload } = + schemaPayload; + return revisionPayload; + } else if (contentType == Type.Crosswalk) { + const { organizations, format, ...revisionPayload } = crosswalkPayload; + console.log('crosswalkmscrpayload: ', crosswalkPayload); + console.log('revisionmscrpayload: ', revisionPayload); + return revisionPayload; + } + } else if ( + modalType == ModalType.RegisterNewFull || + modalType == ModalType.RegisterNewMscr + ) { + if (contentType == Type.Schema) { + return schemaPayload; + } else if (contentType == Type.Crosswalk) { + return crosswalkPayload; + } + } + + const { format, state, visibility, ...mscrCopyPayload } = schemaPayload; + return { + ...mscrCopyPayload, + format: Format.Mscr, + state: State.Draft, + visibility: Visibility.Private, + }; +} diff --git a/mscr-ui/src/modules/form/index.tsx b/mscr-ui/src/modules/form/index.tsx new file mode 100644 index 000000000..688540b29 --- /dev/null +++ b/mscr-ui/src/modules/form/index.tsx @@ -0,0 +1,638 @@ +import { useGetAuthenticatedUserQuery } from '@app/common/components/login/login.slice'; +import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { + Button, + InlineAlert, + Modal, + ModalContent, + ModalFooter, + ModalTitle, + Text, +} from 'suomifi-ui-components'; +import { useBreakpoints } from 'yti-common-ui/components/media-query'; +import FormFooterAlert from 'yti-common-ui/components/form-footer-alert'; +import { translateFileUploadError } from '@app/common/utils/translation-helpers'; +import { useTranslation } from 'next-i18next'; +import getApiError from '@app/common/utils/getApiErrors'; +import { useRouter } from 'next/router'; +import { + usePutSchemaFullMutation, + usePutSchemaRevisionMutation, +} from '@app/common/components/schema/schema.slice'; +import Separator from 'yti-common-ui/components/separator'; +import getErrors from '@app/common/utils/get-errors'; +import { + fileExtensionsAvailableForCrosswalkRegistrationAttachments, + fileExtensionsAvailableForSchemaRegistration, + Format, +} from '@app/common/interfaces/format.interface'; +import FileDropAreaMscr from '@app/common/components/file-drop-area-mscr'; +import SpinnerOverlay, { + delay, + SpinnerType, +} from '@app/common/components/spinner-overlay'; +import { Schema } from '@app/common/interfaces/schema.interface'; +import { mscrSearchApi } from '@app/common/components/mscr-search/mscr-search.slice'; +import { useStoreDispatch } from '@app/store'; +import SchemaFormFields from '@app/modules/form/schema-form/schema-form-fields'; +import { Crosswalk } from '@app/common/interfaces/crosswalk.interface'; +import { Type } from '@app/common/interfaces/search.interface'; +import { getLanguageVersion } from '@app/common/utils/get-language-version'; +import { State } from '@app/common/interfaces/state.interface'; +import { + usePutCrosswalkFullMutation, + usePutCrosswalkFullRevisionMutation, + usePutCrosswalkMutation, + usePutCrosswalkRevisionMutation, +} from '@app/common/components/crosswalk/crosswalk.slice'; +import CrosswalkForm from '@app/modules/form/crosswalk-form/crosswalk-form-fields'; +import { setNotification } from '@app/common/components/notifications/notifications.slice'; +import { + FormType, + useInitialForm, +} from '@app/common/utils/hooks/use-initial-form'; +import { NotificationKeys } from '@app/common/interfaces/notifications.interface'; +import { InputErrors, validateForm } from '@app/modules/form/validate-form'; +import generatePayload from '@app/modules/form/generate-payload'; + +export enum ModalType { + RegisterNewFull = 'REGISTER_NEW_FULL', + RegisterNewMscr = 'REGISTER_NEW_MSCR', + RevisionFull = 'REVISION_FULL', + RevisionMscr = 'REVISION_MSCR', + McsrCopy = 'MSCR_COPY', +} + +interface FormModalProps { + modalType: ModalType; + contentType: Type; + visible: boolean; + setVisible: (visible: boolean) => void; + initialData?: Schema | Crosswalk; + organizationPid?: string; +} + +export default function FormModal({ + modalType, + contentType, + visible, + setVisible, + initialData, + organizationPid, +}: FormModalProps) { + const { t } = useTranslation('common'); + const { isSmall } = useBreakpoints(); + const router = useRouter(); + const lang = router.locale ?? ''; + const dispatch = useStoreDispatch(); + const emptyForm = useInitialForm(contentType); + const [formData, setFormData] = useState({ ...emptyForm }); + const [fileData, setFileData] = useState(); + const [fileUri, setFileUri] = useState(); + const [errors, setErrors] = useState(); + const { data: authenticatedUser } = useGetAuthenticatedUserQuery(); + const [userPosted, setUserPosted] = useState(false); + + const [putSchemaFull, resultSchemaFull] = usePutSchemaFullMutation(); + const [putCrosswalk, resultCrosswalk] = usePutCrosswalkMutation(); + const [putCrosswalkFull, resultCrosswalkFull] = usePutCrosswalkFullMutation(); + const [putSchemaRevision, resultSchemaRevision] = + usePutSchemaRevisionMutation(); + const [putCrosswalkRevision, resultCrosswalkRevision] = + usePutCrosswalkRevisionMutation(); + const [putCrosswalkFullRevision, resultCrosswalkFullRevision] = + usePutCrosswalkFullRevisionMutation(); + const [submitAnimationVisible, setSubmitAnimationVisible] = + useState(false); + + const formDataFromInitialData = useCallback(() => { + if (!initialData) return; + const existingData: FormType = { + format: initialData.format, + languages: [ + { + labelText: t('language-english-with-suffix'), + uniqueItemId: 'en', + title: + initialData.label['en'] ?? + getLanguageVersion({ + data: initialData.label, + lang: lang, + }), + description: + initialData.description['en'] ?? + getLanguageVersion({ + data: initialData.description, + lang: lang, + }), + selected: true, + }, + ], + state: State.Draft, + versionLabel: initialData.versionLabel, + }; + if ( + contentType == Type.Crosswalk && + 'sourceSchema' in initialData && + 'targetSchema' in initialData + ) { + existingData.sourceSchema = initialData.sourceSchema ?? ''; + existingData.targetSchema = initialData.targetSchema ?? ''; + } + return existingData; + }, [contentType, initialData, lang, t]); + + useEffect(() => { + if ( + modalType == ModalType.RegisterNewMscr || + modalType == ModalType.RegisterNewFull + ) { + return; + } + const formDataFromExisting = formDataFromInitialData(); + if (formDataFromExisting) setFormData({ ...formDataFromExisting }); + }, [formDataFromInitialData, modalType, initialData, visible]); + + const handleClose = useCallback(() => { + setVisible(false); + setUserPosted(false); + setFormData(emptyForm); + setFileData(null); + setFileUri(null); + }, [setVisible, emptyForm]); + + const getNewPidFromApi = useCallback( + (contentType: Type, modalType: ModalType) => { + let pid; + switch (modalType) { + case ModalType.RegisterNewMscr: + if ( + contentType == Type.Crosswalk && + resultCrosswalk.isSuccess && + resultCrosswalk.data + ) { + pid = resultCrosswalk.data.pid; + } + break; + case ModalType.RegisterNewFull: + if ( + contentType == Type.Crosswalk && + resultCrosswalkFull.isSuccess && + resultCrosswalkFull.data + ) { + pid = resultCrosswalkFull.data.pid; + } else if ( + contentType == Type.Schema && + resultSchemaFull.isSuccess && + resultSchemaFull.data + ) { + pid = resultSchemaFull.data.pid; + } + break; + case ModalType.RevisionMscr: + if ( + contentType == Type.Crosswalk && + resultCrosswalkRevision.isSuccess && + resultCrosswalkRevision.data + ) { + pid = resultCrosswalkRevision.data.pid; + } + // TODO: Api slice for mscr schema revision, then the pid retrieval here + break; + case ModalType.RevisionFull: + if ( + contentType == Type.Crosswalk && + resultCrosswalkFullRevision.isSuccess && + resultCrosswalkFullRevision.data + ) { + pid = resultCrosswalkFullRevision.data.pid; + } else if ( + contentType == Type.Schema && + resultSchemaRevision.isSuccess && + resultSchemaRevision.data + ) { + pid = resultSchemaRevision.data.pid; + } + break; + case ModalType.McsrCopy: + // TODO: MscrCopy API slice and then pid retrieval for schema and crosswalk here + } + return pid; + }, + [ + resultCrosswalk.data, + resultCrosswalk.isSuccess, + resultCrosswalkFull.data, + resultCrosswalkFull.isSuccess, + resultCrosswalkFullRevision.data, + resultCrosswalkFullRevision.isSuccess, + resultCrosswalkRevision.data, + resultCrosswalkRevision.isSuccess, + resultSchemaFull.data, + resultSchemaFull.isSuccess, + resultSchemaRevision.data, + resultSchemaRevision.isSuccess, + ] + ); + + useEffect(() => { + const newPid = getNewPidFromApi(contentType, modalType); + if (userPosted && newPid && !submitAnimationVisible) { + dispatch( + mscrSearchApi.util.invalidateTags([ + 'PersonalContent', + 'OrgContent', + 'MscrSearch', + ]) + ); + //Get the pid from the result + handleClose(); + let notificationKey: NotificationKeys; + if (contentType == Type.Schema) { + router.push(`/schema/${newPid}`); + switch (modalType) { + case ModalType.RegisterNewFull: + case ModalType.RegisterNewMscr: + notificationKey = 'SCHEMA_ADD'; + break; + case ModalType.RevisionFull: + case ModalType.RevisionMscr: + notificationKey = 'SCHEMA_REVISION'; + break; + case ModalType.McsrCopy: + notificationKey = 'SCHEMA_COPY'; + } + } else { + router.push(`/crosswalk/${newPid}`); + switch (modalType) { + case ModalType.RegisterNewFull: + case ModalType.RegisterNewMscr: + notificationKey = 'CROSSWALK_ADD'; + break; + case ModalType.RevisionFull: + case ModalType.RevisionMscr: + notificationKey = 'CROSSWALK_REVISION'; + break; + case ModalType.McsrCopy: + notificationKey = 'CROSSWALK_COPY'; + } + } + dispatch(setNotification(notificationKey)); + } + }, [ + userPosted, + submitAnimationVisible, + getNewPidFromApi, + contentType, + modalType, + dispatch, + handleClose, + router, + ]); + + const spinnerDelay = async () => { + setSubmitAnimationVisible(true); + if (!(errors && Object.values(errors).includes(true))) { + await delay(2000); + } + return Promise.resolve(); + }; + + const handleSubmit = () => { + scrollToModalTop(); + setUserPosted(true); + if (!formData) { + console.log('no formdata found'); + return; + } + const formErrors = validateForm( + formData, + contentType, + modalType, + fileData, + fileUri + ); + setErrors(formErrors); + + if (errors && Object.values(errors).includes(true)) { + console.log('errors found: ', errors); + return; + } + + if (authenticatedUser) { + console.log('creating payload'); + const payload = generatePayload( + formData, + contentType, + authenticatedUser, + modalType, + organizationPid + ); + console.log('payload: ', payload); + const newFormData = new FormData(); + newFormData.append('metadata', JSON.stringify(payload)); + if (fileUri && fileUri.length > 0) { + newFormData.append('contentURL', fileUri); + } else if (fileData) { + newFormData.append('file', fileData); + } else if (formData.format !== Format.Mscr) { + return; + } + console.log('payload assembled: ', newFormData); + + // Choose the api call and parameters according to content type and modal type + let makeApiCall; + if (modalType == ModalType.RegisterNewFull) { + makeApiCall = + contentType == Type.Schema ? putSchemaFull : putCrosswalkFull; + Promise.all([spinnerDelay(), makeApiCall(newFormData)]).then( + (_values) => { + setSubmitAnimationVisible(false); + } + ); + } else if (modalType == ModalType.RegisterNewMscr) { + Promise.all([spinnerDelay(), putCrosswalk(payload)]).then((_values) => { + setSubmitAnimationVisible(false); + }); + } else if (initialData && modalType == ModalType.RevisionFull) { + makeApiCall = + contentType == Type.Schema + ? putSchemaRevision + : putCrosswalkFullRevision; + Promise.all([ + spinnerDelay(), + makeApiCall({ pid: initialData.pid, data: newFormData }), + ]).then((_values) => { + setSubmitAnimationVisible(false); + }); + } else if (initialData) { + // Todo: Add mscrCopy option here + Promise.all([ + spinnerDelay(), + putCrosswalkRevision({ pid: initialData.pid, data: payload }), + ]).then((_values) => { + setSubmitAnimationVisible(false); + }); + } + } + }; + + // Continuously updating validation after submitting + useEffect(() => { + if (!userPosted || !formData) { + return; + } + const formErrors = validateForm( + formData, + contentType, + modalType, + fileData, + fileUri + ); + setErrors(formErrors); + }, [userPosted, formData, fileData, fileUri, contentType, modalType]); + + // This part was checking the user permission and based on that showing the button in every render + /* if (groupContent && !HasPermission({ actions: ['CREATE_SCHEMA'] })) { + console.log(HasPermission({actions:['CREATE_SCHEMA']})); + return null; + } */ + + function gatherInputError() { + return getErrors(t, errors); + } + + function gatherApiError() { + let errorObject; + if (resultSchemaRevision.isError) { + errorObject = getApiError(resultSchemaRevision.error); + } else if (resultCrosswalkRevision.isError) { + errorObject = getApiError(resultCrosswalkRevision.error); + } else if (resultCrosswalkFullRevision.isError) { + errorObject = getApiError(resultCrosswalkFullRevision.error); + } else if (resultCrosswalk.isError) { + errorObject = getApiError(resultCrosswalk.error); + } else if (resultCrosswalkFull.isError) { + errorObject = getApiError(resultCrosswalkFull.error); + } else if (resultSchemaFull.isError) { + errorObject = getApiError(resultSchemaFull.error); + } else { + // Todo: Add mscr copy error + return undefined; + } + return errorObject; + } + + function renderErrorAlerts() { + const inputErrors = gatherInputError(); + const errorObject = gatherApiError(); + let errorMessage; + let errorDetail; + if (errorObject && errorObject.status && errorObject.message) { + errorMessage = `${errorObject.status}: ${errorObject.message}`; + } + if (errorObject && errorObject.detail) { + errorDetail = errorObject.detail; + } + return ( + <> + {authenticatedUser && + authenticatedUser.anonymous && + !submitAnimationVisible && ( + + {t('error-unauthenticated')} + + )} + {userPosted && inputErrors && !submitAnimationVisible && ( + <> + + + )} + {/*Showing API Error if only input form error is not present*/} + {userPosted && + inputErrors.length < 1 && + errorObject && + !submitAnimationVisible && ( +
+ {errorMessage} + {errorDetail} +
+ )} + + ); + } + + function scrollToModalTop() { + const modalTop = document.getElementById('modalTop'); + if (modalTop) { + modalTop.scrollIntoView(); + } + } + + function renderFileDropArea() { + const fileExtensions = + contentType == Type.Schema + ? fileExtensionsAvailableForSchemaRegistration + : fileExtensionsAvailableForCrosswalkRegistrationAttachments; + const required = + contentType == Type.Schema + ? t('schema-form.file-required') + : t('crosswalk-form.file-required'); + const fileFormats = + contentType == Type.Schema + ? t('schema-form.supported-file-formats') + : t('crosswalk-form.supported-file-formats'); + + return ( + <> + {required + ' '} + + {fileFormats + + fileExtensions + .slice(0, fileExtensions.length - 1) + .join(', ') + .toUpperCase() + + ' ' + + t('and') + + ' ' + + fileExtensions + .slice(fileExtensions.length - 1) + .toString() + .toUpperCase() + + '.'} + + { + return; + }} + validFileTypes={fileExtensions} + translateFileUploadError={translateFileUploadError} + isSchemaUpload={contentType == Type.Schema} + setFileUri={setFileUri} + disabled={submitAnimationVisible} + /> +

+ + ); + } + + return ( + handleClose()} + variant={isSmall ? 'smallScreen' : 'default'} + > + + <> + {submitAnimationVisible && ( + + )} + +
+ + {contentType == Type.Schema + ? modalType == ModalType.RegisterNewFull + ? t('content-form.title.schema-register') + : modalType == ModalType.McsrCopy + ? t('content-form.title.schema-mscr-copy') + : t('content-form.title.schema-revision') + : modalType == ModalType.RegisterNewFull + ? t('content-form.title.crosswalk-register') + : modalType == ModalType.RegisterNewMscr + ? t('content-form.title.crosswalk-create') + : modalType == ModalType.McsrCopy + ? t('content-form.title.crosswalk-mscr-copy') + : t('content-form.title.crosswalk-revision')} + + + {(modalType == ModalType.RegisterNewFull || + modalType == ModalType.RevisionFull) && + renderFileDropArea()} + + {contentType == Type.Schema && ( + + )} + {contentType == Type.Crosswalk && ( + + )} + +
+ + {renderErrorAlerts()} + + + +
+ ); +} diff --git a/mscr-ui/src/modules/form/modal-visibility-button/index.tsx b/mscr-ui/src/modules/form/modal-visibility-button/index.tsx new file mode 100644 index 000000000..f451312e0 --- /dev/null +++ b/mscr-ui/src/modules/form/modal-visibility-button/index.tsx @@ -0,0 +1,20 @@ +import { Button, IconPlus } from 'suomifi-ui-components'; + +export function ModalVisibilityButton({ + setVisible, + label, +}: { + setVisible: (value: boolean) => void; + label: string; +}) { + return ( + + ); +} diff --git a/mscr-ui/src/modules/form/revision-form-modal/index.tsx b/mscr-ui/src/modules/form/revision-form-modal/index.tsx deleted file mode 100644 index 8974b8c56..000000000 --- a/mscr-ui/src/modules/form/revision-form-modal/index.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import { useGetAuthenticatedUserQuery } from '@app/common/components/login/login.slice'; -import * as React from 'react'; -import { useCallback, useEffect, useState } from 'react'; -import { - Button, - InlineAlert, - Modal, - ModalContent, - ModalFooter, - ModalTitle, - Text, -} from 'suomifi-ui-components'; -import { useBreakpoints } from 'yti-common-ui/components/media-query'; -import FormFooterAlert from 'yti-common-ui/components/form-footer-alert'; -import { translateFileUploadError } from '@app/common/utils/translation-helpers'; -import { useTranslation } from 'next-i18next'; -import getApiError from '@app/common/utils/getApiErrors'; -import { useRouter } from 'next/router'; -import { usePutSchemaRevisionMutation } from '@app/common/components/schema/schema.slice'; -import Separator from 'yti-common-ui/components/separator'; -import getErrors from '@app/common/utils/get-errors'; -import { - fileExtensionsAvailableForCrosswalkRegistrationAttachments, - fileExtensionsAvailableForSchemaRegistration, - Format, -} from '@app/common/interfaces/format.interface'; -import FileDropAreaMscr from '@app/common/components/file-drop-area-mscr'; -import SpinnerOverlay, { - delay, - SpinnerType, -} from '@app/common/components/spinner-overlay'; -import { - Schema, - SchemaFormType, -} from '@app/common/interfaces/schema.interface'; -import { mscrSearchApi } from '@app/common/components/mscr-search/mscr-search.slice'; -import { useStoreDispatch } from '@app/store'; -import { - FormErrors as SchemaErrors, - validateSchemaForm, -} from '@app/modules/form/schema-form/validate-schema-form'; -import generateSchemaPayload from '@app/modules/form/schema-form/generate-schema-payload'; -import SchemaFormFields from '@app/modules/form/schema-form/schema-form-fields'; -import { - Crosswalk, - CrosswalkFormType, -} from '@app/common/interfaces/crosswalk.interface'; -import { Type } from '@app/common/interfaces/search.interface'; -import { getLanguageVersion } from '@app/common/utils/get-language-version'; -import { State } from '@app/common/interfaces/state.interface'; -import { useInitialCrosswalkForm } from '@app/common/utils/hooks/use-initial-crosswalk-form'; -import { - usePutCrosswalkFullRevisionMutation, - usePutCrosswalkRevisionMutation, -} from '@app/common/components/crosswalk/crosswalk.slice'; -import { - FormErrors as CrosswalkErrors, - validateCrosswalkForm, -} from '@app/modules/form/crosswalk-form/validate-crosswalk-form'; -import CrosswalkForm from '@app/modules/form/crosswalk-form/crosswalk-form-fields'; -import { Metadata } from '@app/common/interfaces/metadata.interface'; -import { setNotification } from '@app/common/components/notifications/notifications.slice'; - -export default function RevisionFormModal({ - initialData, - visible, - setVisible, - type, -}: { - initialData: Schema | Crosswalk; - visible: boolean; - setVisible: (visible: boolean) => void; - type: Type; -}) { - const { t } = useTranslation('admin'); - const { isSmall } = useBreakpoints(); - const router = useRouter(); - const lang = router.locale ?? ''; - const dispatch = useStoreDispatch(); - const emptyForm = useInitialCrosswalkForm(); - const [, setIsValid] = useState(false); - const [formData, setFormData] = useState< - SchemaFormType | CrosswalkFormType - >(); - const [fileData, setFileData] = useState(); - const [fileUri, setFileUri] = useState(); - const [errors, setErrors] = useState(); - const { data: authenticatedUser } = useGetAuthenticatedUserQuery(); - const [userPosted, setUserPosted] = useState(false); - // Why are we using a mutation here? Why is that even implemented as a mutation, when the method is GET? - const [putSchemaRevision, resultSchemaRevision] = - usePutSchemaRevisionMutation(); - const [putCrosswalkRevision, resultCrosswalkRevision] = - usePutCrosswalkRevisionMutation(); - const [putCrosswalkFullRevision, resultCrosswalkFullRevision] = - usePutCrosswalkFullRevisionMutation(); - const [submitAnimationVisible, setSubmitAnimationVisible] = - useState(false); - - const formDataFromInitialData = useCallback(() => { - const formData: SchemaFormType | CrosswalkFormType = { - format: initialData.format, - languages: [ - { - labelText: t('language-english-with-suffix'), - uniqueItemId: 'en', - title: - initialData.label['en'] ?? - getLanguageVersion({ - data: initialData.label, - lang: lang, - }), - description: - initialData.description['en'] ?? - getLanguageVersion({ - data: initialData.description, - lang: lang, - }), - selected: true, - }, - ], - state: State.Draft, - versionLabel: initialData.versionLabel, - }; - if ( - type == Type.Crosswalk && - 'sourceSchema' in initialData && - 'targetSchema' in initialData - ) { - const crosswalkFormData = formData as CrosswalkFormType; - crosswalkFormData.sourceSchema = initialData.sourceSchema ?? ''; - crosswalkFormData.targetSchema = initialData.targetSchema ?? ''; - return crosswalkFormData; - } - return formData; - }, [initialData, lang, t, type]); - - useEffect(() => { - setFormData(formDataFromInitialData()); - }, [formDataFromInitialData, initialData, visible]); - - const handleClose = useCallback(() => { - setVisible(false); - setUserPosted(false); - setFormData(emptyForm); - setFileData(null); - setFileUri(null); - }, [setVisible, emptyForm]); - - useEffect(() => { - if ( - userPosted && - (resultSchemaRevision.isSuccess || - resultCrosswalkRevision.isSuccess || - resultCrosswalkFullRevision.isSuccess) && - !submitAnimationVisible - ) { - dispatch( - mscrSearchApi.util.invalidateTags([ - 'PersonalContent', - 'OrgContent', - 'MscrSearch', - ]) - ); - //Get the pid from the result - handleClose(); - if ( - resultSchemaRevision && - resultSchemaRevision.data && - resultSchemaRevision.data.pid - ) { - router.push(`/schema/${resultSchemaRevision.data.pid}`); - dispatch( - setNotification('SCHEMA_REVISION') - ); - } else if ( - resultCrosswalkRevision && - resultCrosswalkRevision.data && - resultCrosswalkRevision.data.pid - ) { - router.push(`/crosswalk/${resultCrosswalkRevision.data.pid}`); - dispatch( - setNotification('CROSSWALK_REVISION') - ); - } else if ( - resultCrosswalkFullRevision && - resultCrosswalkFullRevision.data && - resultCrosswalkFullRevision.data.pid - ) { - router.push(`/crosswalk/${resultCrosswalkFullRevision.data.pid}`); - dispatch( - setNotification('CROSSWALK_REVISION') - ); - } - } - }, [ - userPosted, - handleClose, - router, - submitAnimationVisible, - dispatch, - resultCrosswalkRevision, - resultSchemaRevision, - resultCrosswalkFullRevision, - ]); - - const spinnerDelay = async () => { - setSubmitAnimationVisible(true); - if (!(errors && Object.values(errors).includes(true))) { - await delay(2000); - } - return Promise.resolve(); - }; - - function isFormValid() { - if (errors && Object.values(errors).includes(true)) { - return false; - } else if (gatherInputError().length > 0) { - return false; - } else return true; - } - - function validateForm() { - let errors; - if (type == Type.Schema && formData) { - errors = validateSchemaForm(formData, fileData, fileUri); - setErrors(errors); - } else if (formData) { - errors = validateCrosswalkForm(formData as CrosswalkFormType); - setErrors(errors); - } - } - - const handleSubmit = () => { - scrollToModalTop(); - setUserPosted(true); - if (!formData) { - return; - } - validateForm(); - - if (errors && Object.values(errors).includes(true)) { - return; - } - const validatedFormData: SchemaFormType = { - format: formData.format, - languages: formData.languages, - versionLabel: formData.versionLabel, - state: State.Draft, - }; - - if (authenticatedUser) { - const payload = generateSchemaPayload( - validatedFormData, - false, - undefined, - undefined, - true - ) as Partial; - const newFormData = new FormData(); - newFormData.append('metadata', JSON.stringify(payload)); - if (fileUri && fileUri.length > 0) { - newFormData.append('contentURL', fileUri); - } else if (fileData) { - newFormData.append('file', fileData); - } else if (initialData.format !== Format.Mscr) { - return; - } - - if (initialData && type == Type.Schema) { - Promise.all([ - spinnerDelay(), - putSchemaRevision({ pid: initialData.pid, data: newFormData }), - ]).then((values) => { - setSubmitAnimationVisible(false); - }); - } - if (initialData && type == Type.Crosswalk) { - if (initialData.format == Format.Mscr) { - Promise.all([ - spinnerDelay(), - putCrosswalkRevision({ pid: initialData.pid, data: payload }), - ]).then((values) => { - setSubmitAnimationVisible(false); - }); - } else { - Promise.all([ - spinnerDelay(), - putCrosswalkFullRevision({ - pid: initialData.pid, - data: newFormData, - }), - ]).then((values) => { - setSubmitAnimationVisible(false); - }); - } - } - } - }; - - useEffect(() => { - if (!userPosted || !formData) { - return; - } - let errors; - if (type == Type.Schema) { - errors = validateSchemaForm(formData, fileData, fileUri); - setErrors(errors); - } else { - errors = validateCrosswalkForm(formData as CrosswalkFormType); - setErrors(errors); - } - }, [userPosted, formData, fileData, fileUri, type]); - - // This part was checking the user permission and based on that showing the button in every render - /* if (groupContent && !HasPermission({ actions: ['CREATE_SCHEMA'] })) { - console.log(HasPermission({actions:['CREATE_SCHEMA']})); - return null; - } */ - - function gatherInputError() { - return getErrors(t, errors); - } - - function gatherApiError() { - let errorObject; - let errorMessage = ''; - if (resultSchemaRevision.isError) { - errorObject = getApiError(resultSchemaRevision.error); - } else if (resultCrosswalkRevision.isError) { - errorObject = getApiError(resultCrosswalkRevision.error); - } else if (resultCrosswalkFullRevision.isError) { - errorObject = getApiError(resultCrosswalkFullRevision.error); - } else { - return ''; - } - if (errorObject.status && errorObject.message) { - errorMessage = `${errorObject.status}: ${errorObject.message}`; - return errorMessage; - } - return ''; - } - - function getErrorDetail() { - let errorObject; - if (resultSchemaRevision.isError) { - errorObject = getApiError(resultSchemaRevision.error); - } else if (resultCrosswalkRevision.isError) { - errorObject = getApiError(resultCrosswalkRevision.error); - } else if (resultCrosswalkFullRevision.isError) { - errorObject = getApiError(resultCrosswalkFullRevision.error); - } else { - return; - } - if (errorObject.detail) { - return errorObject.detail; - } - } - - function scrollToModalTop() { - const modalTop = document.getElementById('modalTop'); - if (modalTop) { - modalTop.scrollIntoView(); - } - } - - function renderFileDropArea() { - const fileExtensions = - type == Type.Schema - ? fileExtensionsAvailableForSchemaRegistration - : fileExtensionsAvailableForCrosswalkRegistrationAttachments; - const required = - type == Type.Schema - ? t('register-schema-file-required') - : t('crosswalk-form.file-required'); - const fileFormats = - type == Type.Schema - ? t('register-schema-supported-file-formats') - : t('crosswalk-form.supported-file-formats'); - - return ( - <> - {required + ' '} - - {fileFormats + - fileExtensions - .slice(0, fileExtensions.length - 1) - .join(', ') - .toUpperCase() + - ' ' + - t('and') + - ' ' + - fileExtensions - .slice(fileExtensions.length - 1) - .toString() - .toUpperCase() + - '.'} - - -

- - ); - } - - return ( - handleClose()} - variant={isSmall ? 'smallScreen' : 'default'} - > - - <> - {submitAnimationVisible && ( - - )} - -
- - {type == Type.Schema - ? t('register-schema-revision') - : t('crosswalk-form.register-revision')} - - - {formData?.format !== Format.Mscr && renderFileDropArea()} - - {type == Type.Schema && ( - - )} - {type == Type.Crosswalk && ( - - )} - -
- - {authenticatedUser && - authenticatedUser.anonymous && - !submitAnimationVisible && ( - - {t('error-unauthenticated')} - - )} - {userPosted && gatherInputError() && !submitAnimationVisible && ( - <> - - - )} - {/*Showing API Error if only input form error is not present*/} - {userPosted && - gatherInputError().length < 1 && - (resultSchemaRevision.error || resultCrosswalkRevision.error || resultCrosswalkFullRevision.error) && - !submitAnimationVisible && ( -
- {gatherApiError()} - {getErrorDetail()} -
- )} - - - -
-
- ); -} diff --git a/mscr-ui/src/modules/form/schema-form/generate-schema-payload.tsx b/mscr-ui/src/modules/form/schema-form/generate-schema-payload.tsx deleted file mode 100644 index 15ca4cc1a..000000000 --- a/mscr-ui/src/modules/form/schema-form/generate-schema-payload.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable */ -import { MscrUser } from '@app/common/interfaces/mscr-user.interface'; -import { Schema, SchemaFormType } from '@app/common/interfaces/schema.interface'; -import { Organization } from '@app/common/interfaces/organizations.interface'; - -// here we can create the schema payload - -export default function generateSchemaPayload( - data: SchemaFormType, - groupContent: boolean, - pid?: string, - user?: MscrUser, - isRevision?: boolean -): Partial { - const organizations: Organization[] = []; - if (user && groupContent && pid) { - const ownerOrg = user?.organizations.find((x) => x.id == pid); - if (ownerOrg) organizations.push(ownerOrg); - } - - const payload = { - namespace: 'http://test.com', - description: data.languages - .filter((l: { description: string }) => l.description !== '') - .reduce( - (obj: any, l: { uniqueItemId: any; description: any }) => ({ - ...obj, - [l.uniqueItemId]: l.description, - }), - {} - ), - label: data.languages - .filter((l: { title: string }) => l.title !== '') - .reduce( - (obj: any, l: { uniqueItemId: any; title: any }) => ({ - ...obj, - [l.uniqueItemId]: l.title, - }), - {} - ), - languages: data.languages - .filter((l: { title: string }) => l.title !== '') - .map((l: { uniqueItemId: any }) => l.uniqueItemId), - organizations: organizations.map((o: { id: any }) => o.id), - status: 'DRAFT', - format: data.format, - state: data.state, - versionLabel: data.versionLabel ?? '1', - }; - - if (isRevision) { - const { namespace, organizations, ...revisionPayload} = payload; - return revisionPayload; - } - - return payload; -} diff --git a/mscr-ui/src/modules/form/schema-form/schema-form-fields.tsx b/mscr-ui/src/modules/form/schema-form/schema-form-fields.tsx index 6686005b4..63620c49b 100644 --- a/mscr-ui/src/modules/form/schema-form/schema-form-fields.tsx +++ b/mscr-ui/src/modules/form/schema-form/schema-form-fields.tsx @@ -2,8 +2,6 @@ import { useTranslation } from 'next-i18next'; import { DropdownItem } from 'suomifi-ui-components'; import { ModelFormContainer } from '../form.styles'; -import { FormErrors } from './validate-schema-form'; -import { SchemaFormType } from '@app/common/interfaces/schema.interface'; import { Format, formatsAvailableForSchemaRegistration, @@ -11,15 +9,17 @@ import { import { State } from '@app/common/interfaces/state.interface'; import MscrLanguageSelector from '@app/common/components/language-selector/mscr-language-selector'; import { WideDropdown } from '@app/modules/form/crosswalk-form/crosswalk-form.styles'; +import { FormType } from '@app/common/utils/hooks/use-initial-form'; +import { InputErrors } from '@app/modules/form/validate-form'; interface SchemaFormProps { - formData: SchemaFormType; - setFormData: (value: SchemaFormType) => void; + formData: FormType; + setFormData: (value: FormType) => void; userPosted: boolean; disabled?: boolean; - errors?: FormErrors; + errors?: InputErrors; editMode?: boolean; - isRevision?: boolean; + hasInitialData?: boolean; } export default function SchemaFormFields({ @@ -28,7 +28,7 @@ export default function SchemaFormFields({ userPosted, disabled, errors, - isRevision, + hasInitialData, }: // editMode, SchemaFormProps) { const { t } = useTranslation(); @@ -49,14 +49,14 @@ SchemaFormProps) {
setFormData({ ...formData, @@ -76,10 +76,10 @@ SchemaFormProps) { style={{ display: 'flex', flexDirection: 'column', gap: '20px' }} > setFormData({ ...formData, @@ -105,9 +105,9 @@ SchemaFormProps) {
@@ -119,10 +119,10 @@ SchemaFormProps) { userPosted={userPosted} translations={{ textInput: t('schema-form.name'), - textDescription: t('schema-form.description'), - optionalText: '', + textDescription: t('content-form.description'), + optionalText: t('content-form.optional'), }} - versionLabelCaption={t('schema-form.version-label')} + versionLabelCaption={t('content-form.version-label')} versionLabel={formData.versionLabel ?? '1'} setVersionLabel={(e) => setVersionLabel(e)} allowItemAddition={false} diff --git a/mscr-ui/src/modules/form/schema-form/schema-form-modal.tsx b/mscr-ui/src/modules/form/schema-form/schema-form-modal.tsx deleted file mode 100644 index 2faf3de73..000000000 --- a/mscr-ui/src/modules/form/schema-form/schema-form-modal.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import { useGetAuthenticatedUserQuery } from '@app/common/components/login/login.slice'; -import { useCallback, useEffect, useState } from 'react'; -import { - Button, - IconPlus, - InlineAlert, - Modal, - ModalContent, - ModalFooter, - ModalTitle, - Text, -} from 'suomifi-ui-components'; -import { useBreakpoints } from 'yti-common-ui/components/media-query'; -import { FormErrors, validateSchemaForm } from './validate-schema-form'; -import FormFooterAlert from 'yti-common-ui/components/form-footer-alert'; -import { translateFileUploadError } from '@app/common/utils/translation-helpers'; -import { useTranslation } from 'next-i18next'; -import generateSchemaPayload from './generate-schema-payload'; -import getApiError from '@app/common/utils/getApiErrors'; -import { useRouter } from 'next/router'; -import HasPermission from '@app/common/utils/has-permission'; -import { useInitialSchemaForm } from '@app/common/utils/hooks/use-initial-schema-form'; -import { usePutSchemaFullMutation } from '@app/common/components/schema/schema.slice'; -import SchemaFormFields from './schema-form-fields'; -import Separator from 'yti-common-ui/components/separator'; -import getErrors from '@app/common/utils/get-errors'; -import { fileExtensionsAvailableForSchemaRegistration } from '@app/common/interfaces/format.interface'; -import FileDropAreaMscr from '@app/common/components/file-drop-area-mscr'; -import * as React from 'react'; -import SpinnerOverlay, { - SpinnerType, - delay, -} from '@app/common/components/spinner-overlay'; -import { Schema } from '@app/common/interfaces/schema.interface'; - -interface SchemaFormModalProps { - refetch: () => void; - groupContent: boolean; - pid?: string; -} - -// For the time being, using as schema metadata form, Need to update the props accordingly - -export default function SchemaFormModal({ - refetch, - groupContent, - pid, -}: SchemaFormModalProps) { - const { t } = useTranslation('admin'); - const { isSmall } = useBreakpoints(); - const router = useRouter(); - const [visible, setVisible] = useState(false); - const [, setIsValid] = useState(false); - const [schemaFormInitialData] = useState(useInitialSchemaForm()); - const [formData, setFormData] = useState(schemaFormInitialData); - const [fileData, setFileData] = useState(); - const [fileUri, setFileUri] = useState(); - const [errors, setErrors] = useState(); - const [skip, setSkip] = useState(true); - const { data: authenticatedUser } = useGetAuthenticatedUserQuery(undefined, { - skip, - }); - const [userPosted, setUserPosted] = useState(false); - // Why are we using a mutation here? Why is that even implemented as a mutation, when the method is GET? - const [putSchemaFull, resultSchemaFull] = usePutSchemaFullMutation(); - const [submitAnimationVisible, setSubmitAnimationVisible] = - useState(false); - - const handleOpen = () => { - setSkip(false); - setVisible(true); - }; - - const handleClose = useCallback(() => { - setVisible(false); - setSkip(true); - setUserPosted(false); - setFormData(schemaFormInitialData); - setFileData(null); - setFileUri(null); - }, [schemaFormInitialData]); - - useEffect(() => { - if (userPosted && resultSchemaFull.isSuccess && !submitAnimationVisible) { - refetch(); - //Get the pid from the result - handleClose(); - if ( - resultSchemaFull && - resultSchemaFull.data && - resultSchemaFull.data.pid - ) { - router.push(`/schema/${resultSchemaFull.data.pid}`); - } - - // After post route to saved schema get by PID - // Later we should show the created schema in the list - } - }, [ - resultSchemaFull, - refetch, - userPosted, - handleClose, - router, - formData, - submitAnimationVisible, - ]); - - const spinnerDelay = async () => { - setSubmitAnimationVisible(true); - await delay(2000); - return Promise.resolve(); - }; - - const handleSubmit = () => { - scrollToModalTop(); - setUserPosted(true); - if (!formData) { - return; - } - const errors = validateSchemaForm(formData, fileData, fileUri); - setErrors(errors); - - if (Object.values(errors).includes(true)) { - return; - } - - if (authenticatedUser) { - const payload = generateSchemaPayload( - formData, - groupContent, - pid, - authenticatedUser - ); - const schemaFormData = new FormData(); - schemaFormData.append('metadata', JSON.stringify(payload)); - if (fileUri && fileUri.length > 0) { - schemaFormData.append('contentURL', fileUri); - } else if (fileData) { - schemaFormData.append('file', fileData); - } else { - return; - } - Promise.all([spinnerDelay(), putSchemaFull(schemaFormData)]).then( - (values) => { - setSubmitAnimationVisible(false); - } - ); - } - }; - - useEffect(() => { - if (!userPosted) { - return; - } - const errors = validateSchemaForm(formData, fileData, fileUri); - setErrors(errors); - //console.log(errors); - }, [userPosted, formData, fileData, fileUri]); - - // This part was checking the user permission and based on that showing the button in every render - /* if (groupContent && !HasPermission({ actions: ['CREATE_SCHEMA'] })) { - console.log(HasPermission({actions:['CREATE_SCHEMA']})); - return null; - } */ - - function gatherInputError() { - return getErrors(t, errors); - } - - function gatherApiError() { - if (resultSchemaFull.isError) { - const errorObject = getApiError(resultSchemaFull.error); - let errorMessage = ''; - - if (errorObject.status && errorObject.message) { - errorMessage = `${errorObject.status}: ${errorObject.message}`; - return errorMessage; - } - } - } - - function getErrorDetail() { - if (resultSchemaFull.isError) { - const errorObject = getApiError(resultSchemaFull.error); - if (errorObject.detail) { - return errorObject.detail; - } - } - } - - function renderButton() { - return ( - - ); - } - - function scrollToModalTop() { - const modalTop = document.getElementById('modalTop'); - if (modalTop) { - modalTop.scrollIntoView(); - } - } - - return ( - <> - {/*Sending group pid as targetOrganization to check content creation right*/} - {groupContent && - HasPermission({ actions: ['CREATE_SCHEMA'], targetOrganization: pid }) ? ( -
{renderButton()}
- ) : !groupContent ? ( -
{renderButton()}
- ) : !groupContent ? ( -
{renderButton()}
- ) : ( -
- )} - - handleClose()} - variant={isSmall ? 'smallScreen' : 'default'} - > - - <> - {submitAnimationVisible && ( - - )} - -
- {t('register-schema')} - {t('register-schema-file-required') + ' '} - - {t('register-schema-supported-file-formats') + - fileExtensionsAvailableForSchemaRegistration - .slice( - 0, - fileExtensionsAvailableForSchemaRegistration.length - 1 - ) - .join(', ') - .toUpperCase() + - ' ' + - t('and') + - ' ' + - fileExtensionsAvailableForSchemaRegistration - .slice(fileExtensionsAvailableForSchemaRegistration.length - 1) - .toString() - .toUpperCase() + - '.'} - - -

- - - -
- - {authenticatedUser && - authenticatedUser.anonymous && - !submitAnimationVisible && ( - - {t('error-unauthenticated')} - - )} - {userPosted && gatherInputError() && !submitAnimationVisible && ( - - )} - {/*Showing API Error if only input form error is not present*/} - {userPosted && - gatherInputError().length < 1 && - resultSchemaFull.error && - !submitAnimationVisible && ( -
- {gatherApiError()} - {getErrorDetail()} -
- )} - - - -
-
- - ); -} diff --git a/mscr-ui/src/modules/form/schema-form/validate-schema-form.tsx b/mscr-ui/src/modules/form/schema-form/validate-schema-form.tsx deleted file mode 100644 index 3433ded2f..000000000 --- a/mscr-ui/src/modules/form/schema-form/validate-schema-form.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { SchemaFormType } from '@app/common/interfaces/schema.interface'; - -// Not yet modified according to mscr validation errors -// Right now checks if selected languages have missing titles, or there is no file data -export interface FormErrors { - languageAmount: boolean; - titleAmount: string[]; - prefix: boolean; - serviceCategories: boolean; - fileData: boolean; - format?: boolean; -} - -export function validateSchemaForm( - data: SchemaFormType, - fileData: File | null | undefined, - fileUri: string | null | undefined -) { - // console.log(FormData); - const errors: FormErrors = { - languageAmount: false, - titleAmount: [], - prefix: false, - serviceCategories: false, - fileData: false, - format:false - }; - - const selectedLanguages = data.languages.filter( - (lang: { selected: boolean }) => lang.selected - ); - - // At least one language should be selected - if (selectedLanguages.length < 1) { - errors.languageAmount = true; - } - - // All selected languages should have a title - const titleless = selectedLanguages.filter( - (lang: { title: string | undefined }) => - !lang.title || lang.title === '' || lang.title.length < 1 - ); - if (titleless.length > 0) { - const langsWithError = titleless.map( - (lang: { uniqueItemId: string }) => lang.uniqueItemId - ); - - errors.titleAmount = langsWithError ?? []; - } - - if (!fileData && !fileUri) { - errors.fileData = true; - } - - // Should check the selected format and file format - if (!data.format) { - errors.format = true; - } - - return errors; -} diff --git a/mscr-ui/src/modules/form/validate-form.tsx b/mscr-ui/src/modules/form/validate-form.tsx new file mode 100644 index 000000000..5c24bd3b1 --- /dev/null +++ b/mscr-ui/src/modules/form/validate-form.tsx @@ -0,0 +1,105 @@ +import { FormType } from '@app/common/utils/hooks/use-initial-form'; +import { Type } from '@app/common/interfaces/search.interface'; +import { ModalType } from '@app/modules/form/index'; +import { + Format, + formatsAvailableForCrosswalkRegistration, + formatsAvailableForSchemaRegistration, +} from '@app/common/interfaces/format.interface'; + +export interface InputErrors { + languageAmount: boolean; + titleAmount: string[]; + sourceSchema: boolean; + targetSchema: boolean; + fileData: boolean; + format: boolean; +} + +export function validateForm( + formData: FormType, + contentType: Type, + modalType: ModalType, + fileData: File | null | undefined, + fileUri: string | null | undefined +) { + const errors: InputErrors = { + languageAmount: false, + titleAmount: [], + sourceSchema: false, + targetSchema: false, + fileData: false, + format: false, + }; + + const selectedLanguages = formData.languages.filter((lang) => lang.selected); + + // At least one language should be selected + if (selectedLanguages.length < 1) { + errors.languageAmount = true; + } + + // All selected languages should have a title + if ( + selectedLanguages.filter( + (lang) => !lang.title || lang.title === '' || lang.title.length < 1 + ).length > 0 + ) { + const langsWithError = selectedLanguages + .filter( + (lang) => !lang.title || lang.title === '' || lang.title.length < 1 + ) + .map((lang) => lang.uniqueItemId); + + errors.titleAmount = langsWithError ?? []; + } + + // Crosswalk specific: + if (contentType == Type.Crosswalk) { + // Source schema should be selected + if (!formData.sourceSchema || formData.sourceSchema.length < 1) { + errors.sourceSchema = true; + } + + // Target schema should be selected + if (!formData.targetSchema || formData.targetSchema.length < 1) { + errors.targetSchema = true; + } + } + + // When registering existing schema or crosswalk or versioning content with file + if ( + modalType == ModalType.RegisterNewFull || + modalType == ModalType.RevisionFull + ) { + // File should be provided + if (!fileData && (!fileUri || fileUri == '')) { + errors.fileData = true; + } + // Format should be provided + if (!formData.format) { + errors.format = true; + } + // Crosswalk format should be acceptable + if ( + contentType == Type.Crosswalk && + !formatsAvailableForCrosswalkRegistration.includes(formData.format) + ) { + errors.format = true; + } + // Schema format should be acceptable + if ( + contentType == Type.Schema && + !formatsAvailableForSchemaRegistration.includes(formData.format) + ) { + errors.format = true; + } + // Todo: Check format against file format + + // If not registering with a file, format should be MSCR + } else if (formData.format !== Format.Mscr) { + errors.format = true; + } + + return errors; +} diff --git a/mscr-ui/src/modules/schema-view/index.tsx b/mscr-ui/src/modules/schema-view/index.tsx index 600fbdc28..a7d45fbf9 100644 --- a/mscr-ui/src/modules/schema-view/index.tsx +++ b/mscr-ui/src/modules/schema-view/index.tsx @@ -17,7 +17,6 @@ import { SchemaVisualizationWrapper, VersionsHeading, } from '@app/modules/schema-view/schema-view-styles'; -import SchemaFormModal from '@app/modules/form/schema-form/schema-form-modal'; export default function SchemaView({ schemaId }: { schemaId: string }) { const { t } = useTranslation('common'); diff --git a/mscr-ui/src/modules/workspace/group-home/index.tsx b/mscr-ui/src/modules/workspace/group-home/index.tsx index 3330851b1..3b23b0f48 100644 --- a/mscr-ui/src/modules/workspace/group-home/index.tsx +++ b/mscr-ui/src/modules/workspace/group-home/index.tsx @@ -12,8 +12,6 @@ import { import Separator from 'yti-common-ui/components/separator'; import { MscrUser } from '@app/common/interfaces/mscr-user.interface'; import Pagination from '@app/common/components/pagination'; -import CrosswalkFormModal from '@app/modules/form/crosswalk-form/crosswalk-form-modal'; -import SchemaFormModal from '@app/modules/form/schema-form/schema-form-modal'; import { ButtonBlock } from '../workspace.styles'; import useUrlState from '@app/common/utils/hooks/use-url-state'; import { @@ -24,6 +22,8 @@ import { useStoreDispatch } from '@app/store'; import { useEffect, useMemo, useState } from 'react'; import { getLanguageVersion } from '@app/common/utils/get-language-version'; import { useRouter } from 'next/router'; +import { ModalVisibilityButton } from '@app/modules/form/modal-visibility-button'; +import FormModal, { ModalType } from '@app/modules/form'; interface GroupHomeProps { user: MscrUser; @@ -42,6 +42,12 @@ export default function GroupWorkspace({ const { urlState } = useUrlState(); const dispatch = useStoreDispatch(); const pageSize = 20; + const [registerCrosswalkModalVisible, setRegisterCrosswalkModalVisible] = + useState(false); + const [createCrosswalkModalVisible, setCreateCrosswalkModalVisible] = + useState(false); + const [registerSchemaModalVisible, setRegisterSchemaModalVisible] = + useState(false); const [content, setContent] = useState(new Array()); const { data, isLoading } = useGetOrgContentQuery({ type: contentType, @@ -114,25 +120,43 @@ export default function GroupWorkspace({
{contentType == 'SCHEMA' ? ( - + <> + + + ) : ( <> - - + + + + )} diff --git a/mscr-ui/src/modules/workspace/personal-home/index.tsx b/mscr-ui/src/modules/workspace/personal-home/index.tsx index 1283cf5be..66963a769 100644 --- a/mscr-ui/src/modules/workspace/personal-home/index.tsx +++ b/mscr-ui/src/modules/workspace/personal-home/index.tsx @@ -6,9 +6,7 @@ import { TitleDescriptionWrapper, } from 'yti-common-ui/components/title/title.styles'; import Separator from 'yti-common-ui/components/separator'; -import SchemaFormModal from '@app/modules/form/schema-form/schema-form-modal'; import { useBreakpoints } from 'yti-common-ui/components/media-query'; -import CrosswalkFormModal from '@app/modules/form/crosswalk-form/crosswalk-form-modal'; import { ButtonBlock } from '@app/modules/workspace/workspace.styles'; import Pagination from '@app/common/components/pagination'; import useUrlState from '@app/common/utils/hooks/use-url-state'; @@ -23,6 +21,8 @@ import { useEffect, useMemo, useState } from 'react'; import WorkspaceTable, { ContentRow, } from '@app/modules/workspace/workspace-table'; +import { ModalVisibilityButton } from '@app/modules/form/modal-visibility-button'; +import FormModal, { ModalType } from '@app/modules/form'; export default function PersonalWorkspace({ contentType, @@ -36,6 +36,12 @@ export default function PersonalWorkspace({ const { urlState } = useUrlState(); const dispatch = useStoreDispatch(); const pageSize = 20; + const [registerCrosswalkModalVisible, setRegisterCrosswalkModalVisible] = + useState(false); + const [createCrosswalkModalVisible, setCreateCrosswalkModalVisible] = + useState(false); + const [registerSchemaModalVisible, setRegisterSchemaModalVisible] = + useState(false); const [content, setContent] = useState(new Array()); const { data, isLoading } = useGetPersonalContentQuery({ type: contentType, @@ -106,23 +112,39 @@ export default function PersonalWorkspace({ {contentType == 'SCHEMA' ? ( <> - + + ) : ( <> - - + + + + )} From 30ca6d6c04fb411c806deb61f6e581f67796794a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maaria=20Wahlstr=C3=B6m?= Date: Fri, 7 Jun 2024 16:40:06 +0300 Subject: [PATCH 2/3] MSCR-491 Remove debug logging --- mscr-ui/src/modules/form/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/mscr-ui/src/modules/form/index.tsx b/mscr-ui/src/modules/form/index.tsx index 688540b29..f71370886 100644 --- a/mscr-ui/src/modules/form/index.tsx +++ b/mscr-ui/src/modules/form/index.tsx @@ -303,7 +303,6 @@ export default function FormModal({ scrollToModalTop(); setUserPosted(true); if (!formData) { - console.log('no formdata found'); return; } const formErrors = validateForm( @@ -316,12 +315,10 @@ export default function FormModal({ setErrors(formErrors); if (errors && Object.values(errors).includes(true)) { - console.log('errors found: ', errors); return; } if (authenticatedUser) { - console.log('creating payload'); const payload = generatePayload( formData, contentType, @@ -329,7 +326,6 @@ export default function FormModal({ modalType, organizationPid ); - console.log('payload: ', payload); const newFormData = new FormData(); newFormData.append('metadata', JSON.stringify(payload)); if (fileUri && fileUri.length > 0) { @@ -339,7 +335,6 @@ export default function FormModal({ } else if (formData.format !== Format.Mscr) { return; } - console.log('payload assembled: ', newFormData); // Choose the api call and parameters according to content type and modal type let makeApiCall; @@ -393,12 +388,6 @@ export default function FormModal({ setErrors(formErrors); }, [userPosted, formData, fileData, fileUri, contentType, modalType]); - // This part was checking the user permission and based on that showing the button in every render - /* if (groupContent && !HasPermission({ actions: ['CREATE_SCHEMA'] })) { - console.log(HasPermission({actions:['CREATE_SCHEMA']})); - return null; - } */ - function gatherInputError() { return getErrors(t, errors); } From 52d86bc9dae4ac1943b8d69cde8c4180c35c8b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maaria=20Wahlstr=C3=B6m?= Date: Wed, 12 Jun 2024 11:11:50 +0300 Subject: [PATCH 3/3] MSCR-491 Prevent API call when form not valid --- mscr-ui/public/locales/en/admin.json | 2 +- mscr-ui/src/modules/form/generate-payload.tsx | 4 ---- mscr-ui/src/modules/form/index.tsx | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mscr-ui/public/locales/en/admin.json b/mscr-ui/public/locales/en/admin.json index a967f482e..eda65d9fe 100644 --- a/mscr-ui/public/locales/en/admin.json +++ b/mscr-ui/public/locales/en/admin.json @@ -112,7 +112,7 @@ "language-swedish-with-suffix": "Swedish SV", "missing-description": "Description hasn't been set", "missing-file": "You need to add file or URI of the file to register", - "missing-format": "", + "missing-format": "Format hasn't been selected", "missing-general": "Model is missing information", "missing-information-domain": "Information domain hasn't been set", "missing-language-title": "Title is missing in language", diff --git a/mscr-ui/src/modules/form/generate-payload.tsx b/mscr-ui/src/modules/form/generate-payload.tsx index e5060bf59..9336c5d5f 100644 --- a/mscr-ui/src/modules/form/generate-payload.tsx +++ b/mscr-ui/src/modules/form/generate-payload.tsx @@ -68,8 +68,6 @@ export default function generatePayload( return revisionPayload; } else if (contentType == Type.Crosswalk) { const { organizations, ...revisionPayload } = crosswalkPayload; - console.log('crosswalkfullpayload: ', crosswalkPayload); - console.log('revisionfullpayload: ', revisionPayload); return revisionPayload; } } else if (modalType == ModalType.RevisionMscr) { @@ -79,8 +77,6 @@ export default function generatePayload( return revisionPayload; } else if (contentType == Type.Crosswalk) { const { organizations, format, ...revisionPayload } = crosswalkPayload; - console.log('crosswalkmscrpayload: ', crosswalkPayload); - console.log('revisionmscrpayload: ', revisionPayload); return revisionPayload; } } else if ( diff --git a/mscr-ui/src/modules/form/index.tsx b/mscr-ui/src/modules/form/index.tsx index f71370886..cc1d9cfd1 100644 --- a/mscr-ui/src/modules/form/index.tsx +++ b/mscr-ui/src/modules/form/index.tsx @@ -314,7 +314,7 @@ export default function FormModal({ ); setErrors(formErrors); - if (errors && Object.values(errors).includes(true)) { + if (formErrors && (Object.values(formErrors).includes(true) || formErrors.titleAmount.length > 0)) { return; }