@manuscripts/body-editor 3.12.33 → 3.12.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/cjs/components/ChangeHandlingForm.js +3 -2
  2. package/dist/cjs/components/affiliations/AffiliationForm.js +77 -44
  3. package/dist/cjs/components/affiliations/AffiliationsModal.js +69 -58
  4. package/dist/cjs/components/affiliations/AffiliationsPanel.js +1 -1
  5. package/dist/cjs/components/affiliations/CreateAffiliationModal.js +96 -0
  6. package/dist/cjs/components/authors/AuthorDetailsForm.js +110 -54
  7. package/dist/cjs/components/authors/AuthorsModal.js +71 -48
  8. package/dist/cjs/components/authors/AuthorsPanel.js +2 -2
  9. package/dist/cjs/components/authors/CreateAuthorModal.js +121 -0
  10. package/dist/cjs/components/authors/CreditDrawer.js +14 -12
  11. package/dist/cjs/components/authors-affiliations/AuthorsAndAffiliationsModals.js +8 -24
  12. package/dist/cjs/components/authors-affiliations/GenericPanel.js +2 -2
  13. package/dist/cjs/components/authors-affiliations/ModalTabs.js +80 -0
  14. package/dist/cjs/components/dialog/ConfirmationDialog.js +3 -2
  15. package/dist/cjs/components/form/CreateModalStyles.js +24 -0
  16. package/dist/cjs/components/form/FormFooter.js +8 -3
  17. package/dist/cjs/components/form/ModalFormActions.js +19 -42
  18. package/dist/cjs/components/form/UnsavedLabel.js +25 -0
  19. package/dist/cjs/components/hooks/useAffiliationShowsErrorIndicator.js +27 -0
  20. package/dist/cjs/components/hooks/useAuthorShowsErrorIndicator.js +40 -0
  21. package/dist/cjs/lib/authors-and-affiliations.js +51 -0
  22. package/dist/cjs/lib/helpers.js +28 -1
  23. package/dist/cjs/lib/normalize.js +17 -5
  24. package/dist/cjs/useEditor.js +7 -4
  25. package/dist/cjs/versions.js +1 -1
  26. package/dist/es/components/ChangeHandlingForm.js +3 -2
  27. package/dist/es/components/affiliations/AffiliationForm.js +75 -46
  28. package/dist/es/components/affiliations/AffiliationsModal.js +71 -60
  29. package/dist/es/components/affiliations/AffiliationsPanel.js +2 -2
  30. package/dist/es/components/affiliations/CreateAffiliationModal.js +56 -0
  31. package/dist/es/components/authors/AuthorDetailsForm.js +111 -56
  32. package/dist/es/components/authors/AuthorsModal.js +74 -51
  33. package/dist/es/components/authors/AuthorsPanel.js +3 -3
  34. package/dist/es/components/authors/CreateAuthorModal.js +81 -0
  35. package/dist/es/components/authors/CreditDrawer.js +13 -11
  36. package/dist/es/components/authors-affiliations/AuthorsAndAffiliationsModals.js +8 -24
  37. package/dist/es/components/authors-affiliations/GenericPanel.js +2 -2
  38. package/dist/es/components/authors-affiliations/ModalTabs.js +73 -0
  39. package/dist/es/components/dialog/ConfirmationDialog.js +3 -2
  40. package/dist/es/components/form/CreateModalStyles.js +18 -0
  41. package/dist/es/components/form/FormFooter.js +9 -4
  42. package/dist/es/components/form/ModalFormActions.js +18 -42
  43. package/dist/es/components/form/UnsavedLabel.js +18 -0
  44. package/dist/es/components/hooks/useAffiliationShowsErrorIndicator.js +21 -0
  45. package/dist/es/components/hooks/useAuthorShowsErrorIndicator.js +34 -0
  46. package/dist/es/lib/authors-and-affiliations.js +48 -0
  47. package/dist/es/lib/helpers.js +27 -1
  48. package/dist/es/lib/normalize.js +15 -4
  49. package/dist/es/useEditor.js +8 -5
  50. package/dist/es/versions.js +1 -1
  51. package/dist/es/views/affiliations.js +1 -1
  52. package/dist/types/components/affiliations/AffiliationForm.d.ts +6 -2
  53. package/dist/types/components/affiliations/AffiliationsModal.d.ts +1 -1
  54. package/dist/types/components/affiliations/CreateAffiliationModal.d.ts +8 -0
  55. package/dist/types/components/authors/AuthorDetailsForm.d.ts +11 -1
  56. package/dist/types/components/authors/AuthorsPanel.d.ts +1 -1
  57. package/dist/types/components/authors/CreateAuthorModal.d.ts +9 -0
  58. package/dist/types/components/authors/CreditDrawer.d.ts +2 -4
  59. package/dist/types/components/authors-affiliations/ModalTabs.d.ts +22 -0
  60. package/dist/types/components/form/CreateModalStyles.d.ts +4 -0
  61. package/dist/types/components/form/FormFooter.d.ts +2 -1
  62. package/dist/types/components/form/ModalFormActions.d.ts +12 -6
  63. package/dist/types/components/form/UnsavedLabel.d.ts +10 -0
  64. package/dist/types/components/hooks/useAffiliationShowsErrorIndicator.d.ts +5 -0
  65. package/dist/types/components/hooks/useAuthorShowsErrorIndicator.d.ts +5 -0
  66. package/dist/types/lib/authors-and-affiliations.d.ts +19 -0
  67. package/dist/types/lib/helpers.d.ts +2 -0
  68. package/dist/types/lib/normalize.d.ts +2 -1
  69. package/dist/types/versions.d.ts +1 -1
  70. package/package.json +2 -2
@@ -1,53 +1,82 @@
1
- import { FormRow, InputErrorText, Label, TextField, } from '@manuscripts/style-guide';
2
- import { Field, Formik } from 'formik';
3
- import React, { useRef } from 'react';
1
+ import { FormGroup, FormRow, InputErrorText, RequiredIndicator, SelectField, TextField, } from '@manuscripts/style-guide';
2
+ import { Field, FormikProvider, getIn, useFormik, } from 'formik';
3
+ import React, { useEffect } from 'react';
4
+ import styled from 'styled-components';
5
+ import { COUNTRY_SELECT_OPTIONS } from '@manuscripts/style-guide';
6
+ import { normalizeAffiliation } from '../../lib/normalize';
4
7
  import { ChangeHandlingForm } from '../ChangeHandlingForm';
5
- export const AffiliationForm = ({ values, onSave, onChange, actionsRef, }) => {
6
- const formRef = useRef(null);
7
- if (actionsRef) {
8
- actionsRef.current = {
9
- reset: () => formRef.current?.resetForm(),
10
- submitForm: () => formRef.current?.submitForm(),
11
- };
12
- }
13
- const validateAffiliation = (values) => {
8
+ import { UnsavedLabel } from '../form/UnsavedLabel';
9
+ import { isInstitutionError } from '../hooks/useAffiliationShowsErrorIndicator';
10
+ function isAffiliationFieldChanged(formik, key) {
11
+ const v = normalizeAffiliation(formik.values);
12
+ const i = normalizeAffiliation(formik.initialValues);
13
+ return getIn(v, key) !== getIn(i, key);
14
+ }
15
+ export { affiliationShowsErrorIndicator } from '../hooks/useAffiliationShowsErrorIndicator';
16
+ export const AffiliationForm = ({ values, onSave, onChange, actionsRef, newEntity = false, onAffiliationErrorChange, unsavedContinueActive = false, }) => {
17
+ const validateAffiliation = (vals) => {
14
18
  const errors = {};
15
- if (!values.institution?.trim()) {
19
+ if (!vals.institution?.trim()) {
16
20
  errors.institution = 'Institution Name is required';
17
21
  }
18
22
  return errors;
19
23
  };
20
- return (React.createElement(Formik, { initialValues: values, onSubmit: (attrs) => onSave(attrs), innerRef: formRef, enableReinitialize: true, validate: validateAffiliation }, (formik) => (React.createElement(ChangeHandlingForm, { onChange: onChange, id: "affiliation-form", noValidate: true },
21
- React.createElement(FormRow, null,
22
- React.createElement(Field, { name: "institution" }, (props) => {
23
- const hasError = formik.touched.institution && formik.errors.institution;
24
- return (React.createElement(React.Fragment, null,
25
- React.createElement(Label, { htmlFor: "institution" }, "Institution Name*"),
26
- React.createElement(TextField, { id: "institution", ...props.field, error: hasError }),
27
- hasError && (React.createElement(InputErrorText, null, formik.errors.institution))));
28
- })),
29
- React.createElement(FormRow, null,
30
- React.createElement(Field, { name: "department" }, (props) => (React.createElement(React.Fragment, null,
31
- React.createElement(Label, { htmlFor: "department" }, "Department"),
32
- React.createElement(TextField, { id: "department", ...props.field }))))),
33
- React.createElement(FormRow, null,
34
- React.createElement(Field, { name: "addressLine1" }, (props) => (React.createElement(React.Fragment, null,
35
- React.createElement(Label, { htmlFor: "addressLine1" }, "Street Address"),
36
- React.createElement(TextField, { id: "addressLine1", ...props.field }))))),
37
- React.createElement(FormRow, null,
38
- React.createElement(Field, { name: "city" }, (props) => (React.createElement(React.Fragment, null,
39
- React.createElement(Label, { htmlFor: "city" }, "City"),
40
- React.createElement(TextField, { id: "city", ...props.field }))))),
41
- React.createElement(FormRow, null,
42
- React.createElement(Field, { name: "county" }, (props) => (React.createElement(React.Fragment, null,
43
- React.createElement(Label, { htmlFor: "county" }, "State / Province"),
44
- React.createElement(TextField, { id: "county", ...props.field }))))),
45
- React.createElement(FormRow, null,
46
- React.createElement(Field, { name: "postCode" }, (props) => (React.createElement(React.Fragment, null,
47
- React.createElement(Label, { htmlFor: "postCode" }, "Postal Code"),
48
- React.createElement(TextField, { id: "postCode", ...props.field }))))),
49
- React.createElement(FormRow, null,
50
- React.createElement(Field, { name: "country" }, ({ field }) => (React.createElement(React.Fragment, null,
51
- React.createElement(Label, { htmlFor: "country" }, "Country"),
52
- React.createElement(TextField, { id: "country", ...field })))))))));
24
+ const formik = useFormik({
25
+ initialValues: values,
26
+ onSubmit: (attrs) => onSave(attrs),
27
+ enableReinitialize: true,
28
+ validate: validateAffiliation,
29
+ validateOnChange: true,
30
+ });
31
+ if (actionsRef) {
32
+ actionsRef.current = {
33
+ reset: () => formik.resetForm(),
34
+ submitForm: () => formik.submitForm(),
35
+ };
36
+ }
37
+ const showInstitutionError = isInstitutionError(formik, newEntity);
38
+ useEffect(() => {
39
+ onAffiliationErrorChange?.(showInstitutionError);
40
+ }, [showInstitutionError, onAffiliationErrorChange]);
41
+ const showUnsavedDot = (key) => unsavedContinueActive && isAffiliationFieldChanged(formik, key);
42
+ return (React.createElement(FormikProvider, { value: formik },
43
+ React.createElement(ChangeHandlingForm, { onChange: onChange, id: "affiliation-form", noValidate: true },
44
+ React.createElement(FormRow, null,
45
+ React.createElement(Field, { name: "institution" }, (props) => (React.createElement(React.Fragment, null,
46
+ React.createElement(UnsavedLabel, { htmlFor: "institution", showDot: showUnsavedDot('institution') },
47
+ "Institution Name",
48
+ React.createElement(RequiredIndicator, null, "*")),
49
+ React.createElement(TextField, { id: "institution", ...props.field, error: showInstitutionError }),
50
+ showInstitutionError && (React.createElement(InputErrorText, null, formik.errors.institution)))))),
51
+ React.createElement(FormRow, null,
52
+ React.createElement(Field, { name: "department", type: "textarea" }, (props) => (React.createElement(React.Fragment, null,
53
+ React.createElement(UnsavedLabel, { htmlFor: "department", showDot: showUnsavedDot('department') }, "Department"),
54
+ React.createElement(TextField, { id: "department", ...props.field, as: "textarea" }))))),
55
+ React.createElement(FormRow, null,
56
+ React.createElement(Field, { name: "addressLine1" }, (props) => (React.createElement(React.Fragment, null,
57
+ React.createElement(UnsavedLabel, { htmlFor: "addressLine1", showDot: showUnsavedDot('addressLine1') }, "Street Address"),
58
+ React.createElement(TextField, { id: "addressLine1", ...props.field }))))),
59
+ React.createElement(StyledFormGroup, null,
60
+ React.createElement(FormRow, null,
61
+ React.createElement(UnsavedLabel, { htmlFor: "country", showDot: showUnsavedDot('country') }, "Country"),
62
+ React.createElement(Field, { id: "country", name: "country", component: SelectField, options: COUNTRY_SELECT_OPTIONS, isSearchable: true, variant: "large", listMaxHeight: "140px" })),
63
+ React.createElement(FormRow, null,
64
+ React.createElement(Field, { name: "city" }, (props) => (React.createElement(React.Fragment, null,
65
+ React.createElement(UnsavedLabel, { htmlFor: "city", showDot: showUnsavedDot('city') }, "City"),
66
+ React.createElement(TextField, { id: "city", ...props.field })))))),
67
+ React.createElement(StyledFormGroup, null,
68
+ React.createElement(FormRow, null,
69
+ React.createElement(Field, { name: "county" }, (props) => (React.createElement(React.Fragment, null,
70
+ React.createElement(UnsavedLabel, { htmlFor: "county", showDot: showUnsavedDot('county') }, "State / Province"),
71
+ React.createElement(TextField, { id: "county", ...props.field }))))),
72
+ React.createElement(FormRow, null,
73
+ React.createElement(Field, { name: "postCode" }, (props) => (React.createElement(React.Fragment, null,
74
+ React.createElement(UnsavedLabel, { htmlFor: "postCode", showDot: showUnsavedDot('postCode') }, "Postal Code"),
75
+ React.createElement(TextField, { id: "postCode", ...props.field })))))))));
53
76
  };
77
+ const StyledFormGroup = styled(FormGroup) `
78
+ > div {
79
+ flex: 1;
80
+ min-width: 0;
81
+ }
82
+ `;
@@ -1,4 +1,4 @@
1
- import { AddIcon, AffiliationPlaceholderIcon, CloseButton, InspectorTab, InspectorTabList, InspectorTabPanel, InspectorTabPanels, InspectorTabs, outlineStyle, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, } from '@manuscripts/style-guide';
1
+ import { AddIcon, AffiliationPlaceholderIcon, CloseButton, InspectorTabPanel, InspectorTabPanels, InspectorTabs, outlineStyle, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, } from '@manuscripts/style-guide';
2
2
  import { generateNodeID, schema } from '@manuscripts/transform';
3
3
  import { isEqual } from 'lodash';
4
4
  import React, { useCallback, useEffect, useReducer, useRef, useState, } from 'react';
@@ -10,17 +10,19 @@ import { affiliationsReducer } from '../authors/useManageAffiliations';
10
10
  import { ConfirmationDialog, DialogType } from '../dialog/ConfirmationDialog';
11
11
  import FormFooter from '../form/FormFooter';
12
12
  import { FormPlaceholder } from '../form/FormPlaceholder';
13
- import { ModalFormActions } from '../form/ModalFormActions';
13
+ import { ModalFormActions, ModalFormSaveButton } from '../form/ModalFormActions';
14
+ import { ModalTabs } from '../authors-affiliations/ModalTabs';
14
15
  import { AuthorsPanel } from '../authors/AuthorsPanel';
15
16
  import { AffiliationForm } from './AffiliationForm';
16
17
  import { AffiliationList } from './AffiliationList';
18
+ const MODAL_ON_CLOSE_NOTIFY_DELAY_MS = 220;
17
19
  function makeAuthorItems(authors) {
18
20
  return authors.map((author) => ({
19
21
  id: author.id,
20
22
  label: `${author.given} ${author.family}`,
21
23
  }));
22
24
  }
23
- export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliations, affiliation, onSaveAffiliation, onDeleteAffiliation, onUpdateAuthors, addNewAffiliation = false, onClose, onOpenAuthorsModal, }) => {
25
+ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliations, affiliation, onSaveAffiliation, onDeleteAffiliation, onUpdateAuthors, addNewAffiliation = false, onClose, openAuthorsModal, }) => {
24
26
  const [isOpen, setIsOpen] = useState(true);
25
27
  const [selection, setSelection] = useState(affiliation);
26
28
  const [showingDeleteDialog, setShowDeleteDialog] = useState(false);
@@ -43,13 +45,31 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
43
45
  const [pendingAction, setPendingAction] = useState(null);
44
46
  const [savedAffiliationId, setSavedAffiliationId] = useState(undefined);
45
47
  const [affiliationAuthorMap, setAffiliationAuthorMap] = useState(new Map());
46
- const prevIsOpenRef = useRef(isOpen);
48
+ const [affiliationTabHasError, setAffiliationTabHasError] = useState(false);
49
+ const [affiliationDetailsUnsavedContinue, setAffiliationDetailsUnsavedContinue,] = useState(false);
50
+ const [affiliationTabIndex, setAffiliationTabIndex] = useState(0);
51
+ const prevIsOpenRef = useRef(true);
47
52
  useEffect(() => {
48
53
  if (prevIsOpenRef.current && !isOpen) {
49
- onClose?.();
54
+ prevIsOpenRef.current = isOpen;
55
+ const id = window.setTimeout(() => {
56
+ onClose?.();
57
+ }, MODAL_ON_CLOSE_NOTIFY_DELAY_MS);
58
+ return () => window.clearTimeout(id);
50
59
  }
51
60
  prevIsOpenRef.current = isOpen;
52
61
  }, [isOpen, onClose]);
62
+ useEffect(() => {
63
+ if (isDisableSave) {
64
+ setAffiliationDetailsUnsavedContinue(false);
65
+ }
66
+ }, [isDisableSave]);
67
+ useEffect(() => {
68
+ setAffiliationDetailsUnsavedContinue(false);
69
+ if (selection?.id) {
70
+ setAffiliationTabIndex(0);
71
+ }
72
+ }, [selection?.id]);
53
73
  useEffect(() => {
54
74
  if (!selection) {
55
75
  return;
@@ -153,6 +173,7 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
153
173
  newMap.set(affiliation.id, selectedAuthorIds);
154
174
  return newMap;
155
175
  });
176
+ setAffiliationDetailsUnsavedContinue(false);
156
177
  setSavedAffiliationId(affiliation.id);
157
178
  setTimeout(() => {
158
179
  setSavedAffiliationId(undefined);
@@ -253,70 +274,49 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
253
274
  setNewAffiliation(true);
254
275
  }
255
276
  }, [addNewAffiliation]);
256
- const handleConfirmationSave = useCallback(() => {
257
- handleSaveAffiliation(valuesRef.current);
277
+ const handleConfirmationCancel = () => {
278
+ const action = pendingAction;
279
+ const pending = pendingSelection;
258
280
  setShowConfirmationDialog(false);
259
281
  setShowRequiredFieldConfirmationDialog(false);
260
- if (pendingAction === 'new') {
261
- setNewAffiliation(true);
262
- setSelection(affiliation);
263
- setSelectedAuthorIds([]);
264
- setIsDisableSave(true);
282
+ if (action === 'close') {
283
+ setIsOpen(false);
284
+ setSelection(undefined);
285
+ valuesRef.current = undefined;
286
+ setPendingSelection(null);
287
+ setPendingAction(null);
288
+ return;
265
289
  }
266
- else if (pendingAction === 'select' && pendingSelection) {
267
- setSelection(pendingSelection);
290
+ if (action === 'select' && pending) {
291
+ setSelection(pending);
268
292
  setNewAffiliation(false);
269
293
  const affiliatedAuthorIds = authors
270
- .filter((author) => author.affiliationIDs.some((aff) => aff === pendingSelection.id))
294
+ .filter((author) => author.affiliationIDs?.some((aff) => aff === pending.id))
271
295
  .map((author) => author.id);
272
296
  setSelectedAuthorIds(affiliatedAuthorIds);
273
- valuesRef.current = checkID(pendingSelection, 'affiliation');
274
- setIsDisableSave(true);
297
+ valuesRef.current = checkID(pending, 'affiliation');
275
298
  setAffiliationAuthorMap((prevMap) => {
276
299
  const newMap = new Map(prevMap);
277
- newMap.set(pendingSelection.id, affiliatedAuthorIds);
300
+ newMap.set(pending.id, affiliatedAuthorIds);
278
301
  return newMap;
279
302
  });
280
303
  }
281
- if (pendingAction === 'close') {
282
- setIsOpen(false);
283
- }
284
- setPendingSelection(null);
285
- setPendingAction(null);
286
- }, [
287
- authors,
288
- affiliation,
289
- pendingAction,
290
- pendingSelection,
291
- handleSaveAffiliation,
292
- ]);
293
- const handleConfirmationCancel = () => {
294
- setShowConfirmationDialog(false);
295
- setShowRequiredFieldConfirmationDialog(false);
296
- if (pendingAction === 'select' && pendingSelection) {
297
- setSelection(pendingSelection);
298
- setNewAffiliation(false);
299
- const affiliatedAuthorIds = authors
300
- .filter((author) => author.affiliationIDs?.some((aff) => aff === pendingSelection.id))
301
- .map((author) => author.id);
302
- setSelectedAuthorIds(affiliatedAuthorIds);
303
- }
304
- else if (pendingAction === 'new') {
304
+ else if (action === 'new') {
305
305
  setNewAffiliation(true);
306
306
  setSelection(affiliation);
307
307
  setSelectedAuthorIds([]);
308
+ valuesRef.current = affiliation
309
+ ? checkID(affiliation, 'affiliation')
310
+ : undefined;
308
311
  }
309
- if (pendingSelection) {
310
- valuesRef.current = checkID(pendingSelection, 'affiliation');
312
+ else if (pending) {
313
+ valuesRef.current = checkID(pending, 'affiliation');
311
314
  }
312
315
  else {
313
316
  valuesRef.current = undefined;
314
317
  }
315
318
  setPendingSelection(null);
316
319
  setPendingAction(null);
317
- if (pendingAction === 'close') {
318
- setIsOpen(false);
319
- }
320
320
  };
321
321
  return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: () => handleClose(), shouldCloseOnOverlayClick: true },
322
322
  React.createElement(ModalContainer, { "data-cy": "affiliations-modal" },
@@ -325,28 +325,35 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
325
325
  React.createElement(StyledModalBody, null,
326
326
  React.createElement(ModalSidebar, null,
327
327
  React.createElement(StyledModalSidebarHeader, null,
328
- React.createElement(ModalSidebarTitle, null, "Affiliations")),
328
+ React.createElement(ModalSidebarTitle, null, "Institutional Affiliations")),
329
329
  React.createElement(StyledSidebarContent, null,
330
330
  React.createElement(AddAffiliationButton, { "data-cy": "add-affiliation-button", onClick: handleAddAffiliation, "data-active": newAffiliation },
331
331
  React.createElement(AddIcon, { width: 18, height: 18 }),
332
332
  React.createElement(ActionTitle, null, "New Affiliation")),
333
333
  React.createElement(AffiliationList, { affiliation: selection, affiliations: affiliations, onSelect: handleSelect, onDelete: handleShowDeleteDialog, lastSavedAffiliationId: savedAffiliationId }))),
334
- React.createElement(ScrollableModalContent, { "data-cy": "affiliations-modal-content" }, selection ? (React.createElement(React.Fragment, null,
335
- React.createElement(AffiliationTabs, null,
336
- React.createElement(ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onSubmitForm: () => actionsRef.current?.submitForm?.(), onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog &&
334
+ React.createElement(StyledScrollableModalContent, { "data-cy": "affiliations-modal-content" }, selection ? (React.createElement(React.Fragment, null,
335
+ React.createElement(AffiliationTabs, { selectedIndex: affiliationTabIndex, onChange: setAffiliationTabIndex },
336
+ React.createElement(ModalFormActions, { type: "affiliation", onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog &&
337
337
  !(showConfirmationDialog ||
338
- showRequiredFieldConfirmationDialog), showDeleteDialog: handleShowDeleteDialog, newEntity: newAffiliation, isDisableSave: isDisableSave }),
339
- React.createElement(InspectorTabList, null,
340
- React.createElement(InspectorTab, null, "Details"),
341
- onOpenAuthorsModal && React.createElement(InspectorTab, null, "Authors")),
338
+ showRequiredFieldConfirmationDialog), showDeleteDialog: handleShowDeleteDialog }),
339
+ React.createElement(ModalTabs, { tabLabels: ['Affiliation Details', 'Authors'], tabErrorIndicators: [affiliationTabHasError, false], tabWarningIndicators: [
340
+ affiliationDetailsUnsavedContinue &&
341
+ !affiliationTabHasError,
342
+ false,
343
+ ] }),
342
344
  React.createElement(InspectorTabPanels, null,
343
345
  React.createElement(AffiliationTabPanel, null,
344
- React.createElement(AffiliationForm, { values: checkID(selection, 'affiliation'), onSave: (attrs) => handleSaveAffiliation(attrs), onChange: handleAffiliationChange, actionsRef: actionsRef })),
345
- onOpenAuthorsModal && (React.createElement(AffiliationTabPanel, null,
346
- React.createElement(AuthorsPanel, { items: makeAuthorItems(authors), selectedItems: selectedAuthors, onSelect: selectAuthor, onOpenAuthorsModal: onOpenAuthorsModal }))))),
346
+ React.createElement(AffiliationForm, { values: checkID(selection, 'affiliation'), onSave: (attrs) => handleSaveAffiliation(attrs), onChange: handleAffiliationChange, actionsRef: actionsRef, newEntity: newAffiliation, onAffiliationErrorChange: setAffiliationTabHasError, unsavedContinueActive: affiliationDetailsUnsavedContinue })),
347
+ openAuthorsModal && (React.createElement(AffiliationTabPanel, null,
348
+ React.createElement(AuthorsPanel, { items: makeAuthorItems(authors), selectedItems: selectedAuthors, onSelect: selectAuthor, openAuthorsModal: openAuthorsModal }))))),
347
349
  React.createElement(ConfirmationDialog, { isOpen: showRequiredFieldConfirmationDialog, onPrimary: () => setShowRequiredFieldConfirmationDialog(false), onSecondary: handleConfirmationCancel, type: DialogType.REQUIRED, entityType: "affiliation" }),
348
- React.createElement(ConfirmationDialog, { isOpen: showConfirmationDialog, onPrimary: handleConfirmationSave, onSecondary: handleConfirmationCancel, type: DialogType.SAVE, entityType: "affiliation" }))) : (React.createElement(FormPlaceholder, { type: "affiliation", title: "Affiliation Details", message: "Select an affiliation from the list to display it's details here.", placeholderIcon: React.createElement(AffiliationPlaceholderIcon, null) })))),
349
- React.createElement(FormFooter, { onCancel: handleClose }))));
350
+ React.createElement(ConfirmationDialog, { isOpen: showConfirmationDialog, onPrimary: () => {
351
+ setShowConfirmationDialog(false);
352
+ setPendingSelection(null);
353
+ setPendingAction(null);
354
+ setAffiliationDetailsUnsavedContinue(true);
355
+ }, onSecondary: handleConfirmationCancel, type: DialogType.SAVE, entityType: "affiliation" }))) : (React.createElement(FormPlaceholder, { type: "affiliation", title: "Affiliation Details", message: "Select an affiliation from the list to display it's details here.", placeholderIcon: React.createElement(AffiliationPlaceholderIcon, null) })))),
356
+ React.createElement(FormFooter, { onCancel: handleClose, primaryAction: selection ? (React.createElement(ModalFormSaveButton, { form: "affiliation-form", newEntity: newAffiliation, isDisableSave: isDisableSave, onSubmitForm: () => actionsRef.current?.submitForm?.() })) : undefined }))));
350
357
  };
351
358
  function createEmptyAffiliation(priority) {
352
359
  return {
@@ -400,6 +407,7 @@ const AffiliationTabPanel = styled(InspectorTabPanel).attrs({
400
407
  unmount: false,
401
408
  }) `
402
409
  margin-top: ${(props) => props.theme.grid.unit * 4}px;
410
+ height: calc(100% - 16px);
403
411
  `;
404
412
  const StyledModalBody = styled(ModalBody) `
405
413
  position: relative;
@@ -409,3 +417,6 @@ const StyledModalSidebarHeader = styled(ModalSidebarHeader) `
409
417
  margin-top: 8px;
410
418
  margin-bottom: 16px;
411
419
  `;
420
+ const StyledScrollableModalContent = styled(ScrollableModalContent) `
421
+ padding: 45px 16px 16px;
422
+ `;
@@ -1,4 +1,4 @@
1
- import { AffiliationPlaceholderIcon } from '@manuscripts/style-guide';
1
+ import { InfoCircleIcon } from '@manuscripts/style-guide';
2
2
  import React from 'react';
3
3
  import { GenericPanel, ListItem, ListItems, useListSelectedIds, } from '../authors-affiliations/GenericPanel';
4
4
  function affiliationSecondaryLine(item) {
@@ -7,7 +7,7 @@ function affiliationSecondaryLine(item) {
7
7
  }
8
8
  export const AffiliationsPanel = ({ items, selectedItems = [], onSelect, onOpenAffiliationsModal, }) => {
9
9
  const selectedIds = useListSelectedIds(selectedItems);
10
- return (React.createElement(GenericPanel, { title: "Institutional Affiliations", createLabel: "Create New Affiliation", onCreate: onOpenAffiliationsModal, createDataCy: "add-affiliations-link", emptyDataCy: "affiliations-panel-empty", isEmpty: items.length === 0, emptyIcon: React.createElement(AffiliationPlaceholderIcon, null), emptyMessage: React.createElement(React.Fragment, null,
10
+ return (React.createElement(GenericPanel, { title: "Institutional Affiliations", createLabel: "Create New Affiliation", onCreate: onOpenAffiliationsModal, createDataCy: "add-affiliations-link", emptyDataCy: "affiliations-panel-empty", isEmpty: items.length === 0, emptyIcon: React.createElement(InfoCircleIcon, null), emptyMessage: React.createElement(React.Fragment, null,
11
11
  "There are no affiliations attributed yet!",
12
12
  React.createElement("br", null),
13
13
  "Click \u2018Create New Affiliation\u2019") },
@@ -0,0 +1,56 @@
1
+ import { CloseButton, ModalContainer, ModalHeader, StyledModal, } from '@manuscripts/style-guide';
2
+ import { generateNodeID, schema } from '@manuscripts/transform';
3
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { checkID } from '../../lib/normalize';
5
+ import { MODAL_ON_CLOSE_NOTIFY_DELAY_MS, FormTitle, StyledModalBody, StyledScrollableModalContent, } from '../form/CreateModalStyles';
6
+ import FormFooter from '../form/FormFooter';
7
+ import { ModalFormSaveButton } from '../form/ModalFormActions';
8
+ import { AffiliationForm } from './AffiliationForm';
9
+ export const CreateAffiliationModal = ({ affiliationsCount, onSave, onClose, }) => {
10
+ const [isOpen, setIsOpen] = useState(true);
11
+ const [hasError, setHasError] = useState(false);
12
+ const [isDisableSave, setIsDisableSave] = useState(true);
13
+ const actionsRef = useRef(undefined);
14
+ const prevIsOpenRef = useRef(true);
15
+ useEffect(() => {
16
+ if (prevIsOpenRef.current && !isOpen) {
17
+ prevIsOpenRef.current = isOpen;
18
+ const id = window.setTimeout(() => {
19
+ onClose();
20
+ }, MODAL_ON_CLOSE_NOTIFY_DELAY_MS);
21
+ return () => window.clearTimeout(id);
22
+ }
23
+ prevIsOpenRef.current = isOpen;
24
+ }, [isOpen, onClose]);
25
+ const selection = useMemo(() => ({
26
+ id: generateNodeID(schema.nodes.affiliation),
27
+ institution: '',
28
+ department: '',
29
+ addressLine1: '',
30
+ addressLine2: '',
31
+ addressLine3: '',
32
+ postCode: '',
33
+ country: '',
34
+ county: '',
35
+ city: '',
36
+ email: { href: '', text: '' },
37
+ priority: affiliationsCount,
38
+ }), []);
39
+ const handleSave = (values) => {
40
+ onSave({ ...checkID(selection, 'affiliation'), ...values });
41
+ setIsOpen(false);
42
+ };
43
+ const handleChange = (values) => {
44
+ const isInstitutionEmpty = !values.institution?.trim();
45
+ setIsDisableSave(isInstitutionEmpty);
46
+ };
47
+ return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: () => setIsOpen(false), shouldCloseOnOverlayClick: true },
48
+ React.createElement(ModalContainer, { "data-cy": "create-affiliation-modal" },
49
+ React.createElement(ModalHeader, null,
50
+ React.createElement(CloseButton, { onClick: () => setIsOpen(false), "data-cy": "modal-close-button" })),
51
+ React.createElement(StyledModalBody, null,
52
+ React.createElement(StyledScrollableModalContent, null,
53
+ React.createElement(FormTitle, null, "Create New Affiliation"),
54
+ React.createElement(AffiliationForm, { values: checkID(selection, 'affiliation'), onSave: handleSave, onChange: handleChange, actionsRef: actionsRef, newEntity: true, onAffiliationErrorChange: setHasError }))),
55
+ React.createElement(FormFooter, { onCancel: () => setIsOpen(false), primaryAction: React.createElement(ModalFormSaveButton, { form: "affiliation-form", newEntity: true, isDisableSave: isDisableSave || hasError, createLabel: "Create New Affiliation", onSubmitForm: () => actionsRef.current?.submitForm?.() }) }))));
56
+ };