From 7b9d1a3f4fd0d06d1c733b161530a8f2957f3509 Mon Sep 17 00:00:00 2001 From: Gertjan van Oosten Date: Thu, 4 Jul 2024 14:10:04 +0200 Subject: [PATCH] AB#1163 Add merge buttons to the show pages for mergeable objects --- client/src/components/Model.js | 8 +++++ client/src/models/Location.js | 5 +++ client/src/models/Person.js | 9 +++++ .../src/pages/admin/merge/MergeLocations.js | 36 +++++++++++-------- .../pages/admin/merge/MergeOrganizations.js | 30 +++++++++++----- client/src/pages/admin/merge/MergePeople.js | 36 ++++++++++++------- .../src/pages/admin/merge/MergePositions.js | 31 +++++++++++----- client/src/pages/locations/Show.js | 14 ++++++-- client/src/pages/organizations/Show.js | 15 ++++++-- client/src/pages/people/Show.js | 15 ++++++-- client/src/pages/positions/Show.js | 22 +++++++----- 11 files changed, 161 insertions(+), 60 deletions(-) diff --git a/client/src/components/Model.js b/client/src/components/Model.js index 071bcfd87c..fd2ff2d96e 100644 --- a/client/src/components/Model.js +++ b/client/src/components/Model.js @@ -971,6 +971,14 @@ export default class Model { return Model.filterClientSideFields(this, ...additionalFields) } + fixupFields() { + if (this.customFields) { + this[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + this.customFields + ) + } + } + static isAuthorized(user, customSensitiveInformationField) { // Admins are always allowed if (user?.isAdmin()) { diff --git a/client/src/models/Location.js b/client/src/models/Location.js index 86828dd206..7fd87e2924 100644 --- a/client/src/models/Location.js +++ b/client/src/models/Location.js @@ -308,4 +308,9 @@ export default class Location extends Model { filterClientSideFields(...additionalFields) { return Location.filterClientSideFields(this, ...additionalFields) } + + fixupFields() { + super.fixupFields() + this.displayedCoordinate = convertLatLngToMGRS(this.lat, this.lng) + } } diff --git a/client/src/models/Person.js b/client/src/models/Person.js index 0daedd9ffb..65c6bb8962 100644 --- a/client/src/models/Person.js +++ b/client/src/models/Person.js @@ -484,6 +484,15 @@ export default class Person extends Model { return Person.filterClientSideFields(this, ...additionalFields) } + fixupFields() { + super.fixupFields() + if (this.customSensitiveInformation) { + this[SENSITIVE_CUSTOM_FIELDS_PARENT] = utils.parseSensitiveFields( + this.customSensitiveInformation + ) + } + } + static isAuthorized(user, customSensitiveInformationField, position) { if (Model.isAuthorized(user, customSensitiveInformationField)) { return true diff --git a/client/src/pages/admin/merge/MergeLocations.js b/client/src/pages/admin/merge/MergeLocations.js index 30e7d4d9e0..3cba8489eb 100644 --- a/client/src/pages/admin/merge/MergeLocations.js +++ b/client/src/pages/admin/merge/MergeLocations.js @@ -40,10 +40,17 @@ import PropTypes from "prop-types" import React, { useEffect, useState } from "react" import { Button, Col, Container, Form, Row } from "react-bootstrap" import { connect } from "react-redux" -import { useNavigate } from "react-router-dom" +import { useLocation, useNavigate } from "react-router-dom" import LOCATIONS_ICON from "resources/locations.png" import Settings from "settings" -import utils from "utils" + +const GQL_GET_LOCATION = gql` + query ($uuid: String!) { + location(uuid: $uuid) { + ${Location.allFieldsQuery} + } + } +` const GQL_MERGE_LOCATION = gql` mutation ($loserUuid: String!, $winnerLocation: LocationInput!) { @@ -53,6 +60,8 @@ const GQL_MERGE_LOCATION = gql` const MergeLocations = ({ pageDispatchers }) => { const navigate = useNavigate() + const { state } = useLocation() + const initialLeftUuid = state?.initialLeftUuid const [saveError, setSaveError] = useState(null) const [saveWarning, setSaveWarning] = useState(null) const [locationFormat, setLocationFormat] = useState(Location.locationFormat) @@ -68,6 +77,15 @@ const MergeLocations = ({ pageDispatchers }) => { }) usePageTitle("Merge Locations") + if (!mergeState[MERGE_SIDES.LEFT] && initialLeftUuid) { + API.query(GQL_GET_LOCATION, { + uuid: initialLeftUuid + }).then(data => { + const location = new Location(data.location) + location.fixupFields() + dispatchMergeActions(setMergeable(location, MERGE_SIDES.LEFT)) + }) + } const location1 = mergeState[MERGE_SIDES.LEFT] const location2 = mergeState[MERGE_SIDES.RIGHT] const mergedLocation = mergeState.merged @@ -392,17 +410,7 @@ const LocationColumn = ({ overlayRenderRow={LocationOverlayRow} filterDefs={getLocationFilters()} onChange={value => { - if (value?.customFields) { - value[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( - value.customFields - ) - } - if (value) { - value.displayedCoordinate = convertLatLngToMGRS( - value.lat, - value.lng - ) - } + value?.fixupFields() dispatchMergeActions(setMergeable(value, align)) }} objectType={Location} @@ -603,7 +611,7 @@ const LocationColumn = ({ Object.entries(Settings.fields.location.customFields).map( ([fieldName, fieldConfig]) => { const fieldValue = - location[DEFAULT_CUSTOM_FIELDS_PARENT][fieldName] + location?.[DEFAULT_CUSTOM_FIELDS_PARENT]?.[fieldName] return ( { const navigate = useNavigate() + const { state } = useLocation() + const initialLeftUuid = state?.initialLeftUuid const [saveError, setSaveError] = useState(null) const [mergeState, dispatchMergeActions] = useMergeObjects( MODEL_TO_OBJECT_TYPE.Organization @@ -69,6 +79,15 @@ const MergeOrganizations = ({ pageDispatchers }) => { }) usePageTitle("Merge Organizations") + if (!mergeState[MERGE_SIDES.LEFT] && initialLeftUuid) { + API.query(GQL_GET_ORGANIZATION, { + uuid: initialLeftUuid + }).then(data => { + const organization = new Organization(data.organization) + organization.fixupFields() + dispatchMergeActions(setMergeable(organization, MERGE_SIDES.LEFT)) + }) + } const organization1 = mergeState[MERGE_SIDES.LEFT] const organization2 = mergeState[MERGE_SIDES.RIGHT] const mergedOrganization = mergeState.merged @@ -461,12 +480,7 @@ const OrganizationColumn = ({ overlayRenderRow={OrganizationSimpleOverlayRow} filterDefs={organizationsFilters} onChange={value => { - const newValue = value - if (newValue?.customFields) { - newValue[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( - value.customFields - ) - } + value?.fixupFields() dispatchMergeActions(setMergeable(value, align)) }} objectType={Organization} @@ -802,7 +816,7 @@ const OrganizationColumn = ({ Object.entries(Settings.fields.organization.customFields).map( ([fieldName, fieldConfig]) => { const fieldValue = - organization[DEFAULT_CUSTOM_FIELDS_PARENT][fieldName] + organization?.[DEFAULT_CUSTOM_FIELDS_PARENT]?.[fieldName] return ( { const navigate = useNavigate() + const { state } = useLocation() + const initialLeftUuid = state?.initialLeftUuid const [saveError, setSaveError] = useState(null) const [showHistoryModal, setShowHistoryModal] = useState(false) const [mergeState, dispatchMergeActions] = useMergeObjects( @@ -71,6 +81,15 @@ const MergePeople = ({ pageDispatchers }) => { }) usePageTitle("Merge People") + if (!mergeState[MERGE_SIDES.LEFT] && initialLeftUuid) { + API.query(GQL_GET_PERSON, { + uuid: initialLeftUuid + }).then(data => { + const person = new Person(data.person) + person.fixupFields() + dispatchMergeActions(setMergeable(person, MERGE_SIDES.LEFT)) + }) + } const person1 = mergeState[MERGE_SIDES.LEFT] const person2 = mergeState[MERGE_SIDES.RIGHT] const mergedPerson = mergeState.merged @@ -469,16 +488,7 @@ const PersonColumn = ({ align, label, mergeState, dispatchMergeActions }) => { overlayRenderRow={PersonSimpleOverlayRow} filterDefs={peopleFilters} onChange={value => { - const newValue = value - if (newValue?.customFields) { - newValue[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( - value.customFields - ) - } - if (newValue?.customSensitiveInformation) { - newValue[SENSITIVE_CUSTOM_FIELDS_PARENT] = - utils.parseSensitiveFields(value.customSensitiveInformation) - } + value?.fixupFields() dispatchMergeActions(setMergeable(value, align)) }} objectType={Person} @@ -765,7 +775,7 @@ const PersonColumn = ({ align, label, mergeState, dispatchMergeActions }) => { Object.entries(Settings.fields.person.customFields).map( ([fieldName, fieldConfig]) => { const fieldValue = - person[DEFAULT_CUSTOM_FIELDS_PARENT][fieldName] + person?.[DEFAULT_CUSTOM_FIELDS_PARENT]?.[fieldName] return ( { Settings.fields.person.customSensitiveInformation ).map(([fieldName, fieldConfig]) => { const fieldValue = - person[SENSITIVE_CUSTOM_FIELDS_PARENT][fieldName] + person?.[SENSITIVE_CUSTOM_FIELDS_PARENT]?.[fieldName] return ( { const navigate = useNavigate() + const { state } = useLocation() + const initialLeftUuid = state?.initialLeftUuid const [saveError, setSaveError] = useState(null) const [showHistoryModal, setShowHistoryModal] = useState(false) const [mergeState, dispatchMergeActions] = useMergeObjects( @@ -70,6 +79,15 @@ const MergePositions = ({ pageDispatchers }) => { }) usePageTitle("Merge Positions") + if (!mergeState[MERGE_SIDES.LEFT] && initialLeftUuid) { + API.query(GQL_GET_POSITION, { + uuid: initialLeftUuid + }).then(data => { + const position = new Position(data.position) + position.fixupFields() + dispatchMergeActions(setMergeable(position, MERGE_SIDES.LEFT)) + }) + } const position1 = mergeState[MERGE_SIDES.LEFT] const position2 = mergeState[MERGE_SIDES.RIGHT] const mergedPosition = mergeState.merged @@ -432,12 +450,7 @@ const PositionColumn = ({ align, label, mergeState, dispatchMergeActions }) => { overlayRenderRow={PositionOverlayRow} filterDefs={getPositionFilters(mergeState, align)} onChange={value => { - const newValue = value - if (newValue?.customFields) { - newValue[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( - value.customFields - ) - } + value?.fixupFields() dispatchMergeActions(setMergeable(value, align)) }} objectType={Position} @@ -636,7 +649,7 @@ const PositionColumn = ({ align, label, mergeState, dispatchMergeActions }) => { Object.entries(Settings.fields.position.customFields).map( ([fieldName, fieldConfig]) => { const fieldValue = - position[DEFAULT_CUSTOM_FIELDS_PARENT][fieldName] + position?.[DEFAULT_CUSTOM_FIELDS_PARENT]?.[fieldName] return ( { ) } const location = new Location(data ? data.location : {}) - const canEdit = currentUser.isSuperuser() + const isAdmin = currentUser?.isAdmin() + const canEdit = currentUser?.isSuperuser() const attachmentsEnabled = !Settings.fields.attachment.featureDisabled return ( @@ -95,6 +96,15 @@ const LocationShow = ({ pageDispatchers }) => { } const action = ( <> + {isAdmin && ( + + Merge with other location + + )} {canEdit && ( { } const organization = new Organization(data ? data.organization : {}) + const isAdmin = currentUser && currentUser.isAdmin() const canAdministrateOrg = - currentUser && - currentUser.hasAdministrativePermissionsForOrganization(organization) + currentUser?.hasAdministrativePermissionsForOrganization(organization) const attachmentsEnabled = !Settings.fields.attachment.featureDisabled const { parentContext, parentStandardIdentity } = Organization.getApp6ParentFields(organization, organization) @@ -287,6 +287,15 @@ const OrganizationShow = ({ pageDispatchers }) => { {({ values }) => { const action = ( <> + {isAdmin && ( + + Merge with other organization + + )} {canAdministrateOrg && ( { // User can always edit themselves // Admins can always edit anybody // Superusers can edit people in their org, their descendant orgs, or un-positioned people. - const isAdmin = currentUser && currentUser.isAdmin() - const hasPosition = position && position.uuid + const isAdmin = currentUser?.isAdmin() + const hasPosition = position?.uuid const canEdit = Person.isEqual(currentUser, person) || isAdmin || @@ -220,6 +220,15 @@ const PersonShow = ({ pageDispatchers }) => { const action = ( <> + {isAdmin && ( + + Merge with other person + + )} diff --git a/client/src/pages/positions/Show.js b/client/src/pages/positions/Show.js index 5d83f89340..bee6239484 100644 --- a/client/src/pages/positions/Show.js +++ b/client/src/pages/positions/Show.js @@ -34,7 +34,7 @@ import { positionTour } from "pages/HopscotchTour" import React, { useContext, useState } from "react" import { Badge, Button } from "react-bootstrap" import { connect } from "react-redux" -import { useLocation, useNavigate, useParams } from "react-router-dom" +import { Link, useLocation, useNavigate, useParams } from "react-router-dom" import Settings from "settings" import utils from "utils" import PreviousPeople from "./PreviousPeople" @@ -43,10 +43,6 @@ const GQL_GET_POSITION = gql` query($uuid: String!) { position(uuid: $uuid) { ${Position.allFieldsQuery} - emailAddresses { - network - address - } authorizationGroups { uuid name @@ -110,16 +106,17 @@ const PositionShow = ({ pageDispatchers }) => { const position = new Position(data ? data.position : {}) const isSuperuser = position.type === Position.TYPE.SUPERUSER + const isAdmin = currentUser?.isAdmin() const canEdit = // Admins can edit anybody - currentUser.isAdmin() || + isAdmin || // Superusers can edit positions if they have administrative permissions for the organization of the position (position?.organization?.uuid && currentUser.hasAdministrativePermissionsForOrganization( position.organization )) const canDelete = - currentUser.isAdmin() && + isAdmin && position.status === Model.STATUS.INACTIVE && position.uuid && (!position.person || !position.person.uuid) @@ -129,6 +126,15 @@ const PositionShow = ({ pageDispatchers }) => { {({ values }) => { const action = ( <> + {isAdmin && ( + + Merge with other position + + )} {canEdit && ( { Settings.fields.position.organizationsAdministrated.label )} action={ - currentUser.isAdmin() && ( + isAdmin && (