diff --git a/anet-dictionary.yml b/anet-dictionary.yml
index f651fd3635..945ebae7af 100644
--- a/anet-dictionary.yml
+++ b/anet-dictionary.yml
@@ -476,13 +476,247 @@ fields:
location:
format: LAT_LON
+ customFields:
+ multipleButtons:
+ type: enumset
+ label: Choose one or more of the options
+ helpText: Help text for choosing multiple values
+ choices:
+ opt1:
+ label: Option 1
+ opt2:
+ label: Option 2
+ opt3:
+ label: Option 3
+ inputFieldName:
+ type: text
+ label: Text field
+ placeholder: Placeholder text for input field
+ helpText: Help text for text field
+ colourOptions:
+ type: enum
+ label: Choose one of the colours
+ helpText: Help text for choosing colours
+ choices:
+ GREEN:
+ label: Green
+ color: '#c2ffb3'
+ AMBER:
+ label: Amber
+ color: '#ffe396'
+ RED:
+ label: Red
+ color: '#ff8279'
+ textareaFieldName:
+ type: text
+ label: Textarea field
+ placeholder: Placeholder text for textarea field
+ helpText: Help text for textarea field
+ componentClass: textarea
+ style:
+ height: 200px
+ visibleWhen: $[?(@.colourOptions === 'GREEN')]
+ numberFieldName:
+ type: number
+ typeError: Number field must be a number
+ label: Number field
+ placeholder: Placeholder text for number field
+ helpText: Help text for number field
+ validations:
+ - type: integer
+ - type: min
+ params: [5]
+ - type: max
+ params: [100]
+ visibleWhen: $[?((@.colourOptions === 'GREEN')||(@.colourOptions === 'RED'))]
+ nlt:
+ type: date
+ label: Not later than date
+ helpText: Help text for date field
+ nlt_dt:
+ type: datetime
+ label: Not later than datetime
+ helpText: Help text for datetime field
+ arrayFieldName:
+ type: array_of_objects
+ label: Array of objects
+ helpText: Here you can add as many objects as needed
+ addButtonLabel: Add an object
+ objectLabel: Object
+ objectFields:
+ textF:
+ type: text
+ label: Object text
+ placeholder: Placeholder text for object text field
+ helpText: Help text for object text field
+ dateF:
+ type: date
+ label: Object date
+ helpText: Help text for object date field
+ visibleWhen: $[?(@.colourOptions === 'GREEN')]
position:
name: 'Position Name'
+ customFields:
+ multipleButtons:
+ type: enumset
+ label: Choose one or more of the options
+ helpText: Help text for choosing multiple values
+ choices:
+ opt1:
+ label: Option 1
+ opt2:
+ label: Option 2
+ opt3:
+ label: Option 3
+ inputFieldName:
+ type: text
+ label: Text field
+ placeholder: Placeholder text for input field
+ helpText: Help text for text field
+ colourOptions:
+ type: enum
+ label: Choose one of the colours
+ helpText: Help text for choosing colours
+ choices:
+ GREEN:
+ label: Green
+ color: '#c2ffb3'
+ AMBER:
+ label: Amber
+ color: '#ffe396'
+ RED:
+ label: Red
+ color: '#ff8279'
+ textareaFieldName:
+ type: text
+ label: Textarea field
+ placeholder: Placeholder text for textarea field
+ helpText: Help text for textarea field
+ componentClass: textarea
+ style:
+ height: 200px
+ visibleWhen: $[?(@.colourOptions === 'GREEN')]
+ numberFieldName:
+ type: number
+ typeError: Number field must be a number
+ label: Number field
+ placeholder: Placeholder text for number field
+ helpText: Help text for number field
+ validations:
+ - type: integer
+ - type: min
+ params: [5]
+ - type: max
+ params: [100]
+ visibleWhen: $[?((@.colourOptions === 'GREEN')||(@.colourOptions === 'RED'))]
+ nlt:
+ type: date
+ label: Not later than date
+ helpText: Help text for date field
+ nlt_dt:
+ type: datetime
+ label: Not later than datetime
+ helpText: Help text for datetime field
+ arrayFieldName:
+ type: array_of_objects
+ label: Array of objects
+ helpText: Here you can add as many objects as needed
+ addButtonLabel: Add an object
+ objectLabel: Object
+ objectFields:
+ textF:
+ type: text
+ label: Object text
+ placeholder: Placeholder text for object text field
+ helpText: Help text for object text field
+ dateF:
+ type: date
+ label: Object date
+ helpText: Help text for object date field
+ visibleWhen: $[?(@.colourOptions === 'GREEN')]
organization:
shortName: Name
parentOrg: Parent Organization
+ customFields:
+ multipleButtons:
+ type: enumset
+ label: Choose one or more of the options
+ helpText: Help text for choosing multiple values
+ choices:
+ opt1:
+ label: Option 1
+ opt2:
+ label: Option 2
+ opt3:
+ label: Option 3
+ inputFieldName:
+ type: text
+ label: Text field
+ placeholder: Placeholder text for input field
+ helpText: Help text for text field
+ colourOptions:
+ type: enum
+ label: Choose one of the colours
+ helpText: Help text for choosing colours
+ choices:
+ GREEN:
+ label: Green
+ color: '#c2ffb3'
+ AMBER:
+ label: Amber
+ color: '#ffe396'
+ RED:
+ label: Red
+ color: '#ff8279'
+ textareaFieldName:
+ type: text
+ label: Textarea field
+ placeholder: Placeholder text for textarea field
+ helpText: Help text for textarea field
+ componentClass: textarea
+ style:
+ height: 200px
+ visibleWhen: $[?(@.colourOptions === 'GREEN')]
+ numberFieldName:
+ type: number
+ typeError: Number field must be a number
+ label: Number field
+ placeholder: Placeholder text for number field
+ helpText: Help text for number field
+ validations:
+ - type: integer
+ - type: min
+ params: [5]
+ - type: max
+ params: [100]
+ visibleWhen: $[?((@.colourOptions === 'GREEN')||(@.colourOptions === 'RED'))]
+ nlt:
+ type: date
+ label: Not later than date
+ helpText: Help text for date field
+ nlt_dt:
+ type: datetime
+ label: Not later than datetime
+ helpText: Help text for datetime field
+ arrayFieldName:
+ type: array_of_objects
+ label: Array of objects
+ helpText: Here you can add as many objects as needed
+ addButtonLabel: Add an object
+ objectLabel: Object
+ objectFields:
+ textF:
+ type: text
+ label: Object text
+ placeholder: Placeholder text for object text field
+ helpText: Help text for object text field
+ dateF:
+ type: date
+ label: Object date
+ helpText: Help text for object date field
+ visibleWhen: $[?(@.colourOptions === 'GREEN')]
advisor:
diff --git a/client/src/components/CustomFields.js b/client/src/components/CustomFields.js
index 1d4d4aa94d..3a7434d2d3 100644
--- a/client/src/components/CustomFields.js
+++ b/client/src/components/CustomFields.js
@@ -660,6 +660,20 @@ const FIELD_COMPONENTS = {
[CUSTOM_FIELD_TYPE.ARRAY_OF_ANET_OBJECTS]: ArrayOfAnetObjectsField
}
+// mutates the object
+export function initInvisibleFields(
+ anetObj,
+ config,
+ parentFieldName = DEFAULT_CUSTOM_FIELDS_PARENT
+) {
+ if (anetObj[parentFieldName]) {
+ // set initial invisible custom fields
+ anetObj[parentFieldName][
+ INVISIBLE_CUSTOM_FIELDS_FIELD
+ ] = getInvisibleFields(config, parentFieldName, anetObj)
+ }
+}
+
export function getInvisibleFields(
fieldsConfig = {},
parentFieldName,
diff --git a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js
index 709dc84c53..6ce20ebec6 100644
--- a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js
+++ b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js
@@ -74,7 +74,7 @@ const widgetPropsOrganization = {
overlayColumns: ["Name"],
filterDefs: entityFilters,
queryParams: { status: Model.STATUS.ACTIVE },
- fields: Models.Organization.autocompleteQuery,
+ fields: Models.Organization.autocompleteQueryWithNotes,
addon: ORGANIZATIONS_ICON
}
@@ -84,7 +84,7 @@ const widgetPropsPosition = {
overlayColumns: ["Position", "Organization", "Current Occupant"],
filterDefs: entityFilters,
queryParams: { status: Model.STATUS.ACTIVE },
- fields: Models.Position.autocompleteQuery,
+ fields: Models.Position.autocompleteQueryWithNotes,
addon: POSITIONS_ICON
}
@@ -94,7 +94,7 @@ const widgetPropsLocation = {
overlayColumns: ["Name"],
filterDefs: entityFilters,
queryParams: { status: Model.STATUS.ACTIVE },
- fields: Models.Location.autocompleteQuery,
+ fields: Models.Location.autocompleteQueryWithNotes,
addon: LOCATIONS_ICON
}
diff --git a/client/src/models/Location.js b/client/src/models/Location.js
index 47361adf1f..98bce1ee4c 100644
--- a/client/src/models/Location.js
+++ b/client/src/models/Location.js
@@ -1,4 +1,7 @@
-import Model from "components/Model"
+import Model, {
+ createCustomFieldsSchema,
+ GRAPHQL_NOTES_FIELDS
+} from "components/Model"
import { convertLatLngToMGRS, convertMGRSToLatLng } from "geoUtils"
import _isEmpty from "lodash/isEmpty"
import LOCATIONS_ICON from "resources/locations.png"
@@ -17,6 +20,11 @@ export default class Location extends Model {
REPORT_APPROVAL: "REPORT_APPROVAL"
}
+ // create yup schema for the customFields, based on the customFields config
+ static customFieldsSchema = createCustomFieldsSchema(
+ Settings.fields.location.customFields
+ )
+
static yupSchema = yup
.object()
.shape({
@@ -118,10 +126,14 @@ export default class Location extends Model {
.nullable()
.default([])
})
+ // not actually in the database, the database contains the JSON customFields
+ .concat(Location.customFieldsSchema)
.concat(Model.yupSchema)
static autocompleteQuery = "uuid, name"
+ static autocompleteQueryWithNotes = `${this.autocompleteQuery} ${GRAPHQL_NOTES_FIELDS}`
+
static hasCoordinates(location) {
return (
location &&
diff --git a/client/src/models/Organization.js b/client/src/models/Organization.js
index 8255246e24..2872bc1787 100644
--- a/client/src/models/Organization.js
+++ b/client/src/models/Organization.js
@@ -1,4 +1,7 @@
-import Model from "components/Model"
+import Model, {
+ createCustomFieldsSchema,
+ GRAPHQL_NOTES_FIELDS
+} from "components/Model"
import ORGANIZATIONS_ICON from "resources/organizations.png"
import Settings from "settings"
import utils from "utils"
@@ -20,6 +23,11 @@ export default class Organization extends Model {
REPORT_APPROVAL: "REPORT_APPROVAL"
}
+ // create yup schema for the customFields, based on the customFields config
+ static customFieldsSchema = createCustomFieldsSchema(
+ Settings.fields.organization.customFields
+ )
+
static yupSchema = yup
.object()
.shape({
@@ -89,11 +97,15 @@ export default class Organization extends Model {
positions: yup.array().nullable().default([]),
tasks: yup.array().nullable().default([])
})
+ // not actually in the database, the database contains the JSON customFields
+ .concat(Organization.customFieldsSchema)
.concat(Model.yupSchema)
static autocompleteQuery =
"uuid, shortName, longName, identificationCode, type"
+ static autocompleteQueryWithNotes = `${this.autocompleteQuery} ${GRAPHQL_NOTES_FIELDS}`
+
static humanNameOfStatus(status) {
return utils.sentenceCase(status)
}
diff --git a/client/src/models/Position.js b/client/src/models/Position.js
index b656a500f7..09ebb23c21 100644
--- a/client/src/models/Position.js
+++ b/client/src/models/Position.js
@@ -1,4 +1,7 @@
-import Model from "components/Model"
+import Model, {
+ createCustomFieldsSchema,
+ GRAPHQL_NOTES_FIELDS
+} from "components/Model"
import AFG_ICON from "resources/afg_small.png"
import POSITIONS_ICON from "resources/positions.png"
import RS_ICON from "resources/rs_small.png"
@@ -24,6 +27,11 @@ export default class Position extends Model {
ADMINISTRATOR: "ADMINISTRATOR"
}
+ // create yup schema for the customFields, based on the customFields config
+ static customFieldsSchema = createCustomFieldsSchema(
+ Settings.fields.position.customFields
+ )
+
static yupSchema = yup
.object()
.shape({
@@ -57,11 +65,15 @@ export default class Position extends Model {
person: yup.object().nullable().default({}),
location: yup.object().nullable().default({})
})
+ // not actually in the database, the database contains the JSON customFields
+ .concat(Position.customFieldsSchema)
.concat(Model.yupSchema)
static autocompleteQuery =
"uuid, name, code, type, status, organization { uuid, shortName}, person { uuid, name, rank, role, avatar(size: 32) }"
+ static autocompleteQueryWithNotes = `${this.autocompleteQuery} ${GRAPHQL_NOTES_FIELDS}`
+
static humanNameOfStatus(status) {
return utils.sentenceCase(status)
}
diff --git a/client/src/pages/admin/authorizationgroup/Form.js b/client/src/pages/admin/authorizationgroup/Form.js
index 6d32357adc..8d6df87ea6 100644
--- a/client/src/pages/admin/authorizationgroup/Form.js
+++ b/client/src/pages/admin/authorizationgroup/Form.js
@@ -5,7 +5,7 @@ import { PositionOverlayRow } from "components/advancedSelectWidget/AdvancedSele
import * as FieldHelper from "components/FieldHelper"
import Fieldset from "components/Fieldset"
import Messages from "components/Messages"
-import Model from "components/Model"
+import Model, { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model"
import NavigationWarning from "components/NavigationWarning"
import { jumpToTop } from "components/Page"
import PositionTable from "components/PositionTable"
@@ -233,6 +233,14 @@ const AuthorizationGroupForm = ({ edit, title, initialValues }) => {
new AuthorizationGroup(values),
"notes"
)
+ authorizationGroup.positions = values.positions.map(pos => {
+ const p = Object.without(
+ pos,
+ "customFields",
+ DEFAULT_CUSTOM_FIELDS_PARENT
+ )
+ return p
+ })
return API.mutation(
edit ? GQL_UPDATE_AUTHORIZATION_GROUP : GQL_CREATE_AUTHORIZATION_GROUP,
{ authorizationGroup }
diff --git a/client/src/pages/locations/Edit.js b/client/src/pages/locations/Edit.js
index d65e4052bb..701c70a384 100644
--- a/client/src/pages/locations/Edit.js
+++ b/client/src/pages/locations/Edit.js
@@ -1,9 +1,11 @@
import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions"
import API from "api"
import { gql } from "apollo-boost"
+import { initInvisibleFields } from "components/CustomFields"
+import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model"
import {
- PageDispatchersPropType,
mapPageDispatchersToProps,
+ PageDispatchersPropType,
useBoilerplate
} from "components/Page"
import RelatedObjectNotes, {
@@ -13,6 +15,8 @@ import { Location } from "models"
import React from "react"
import { connect } from "react-redux"
import { useParams } from "react-router-dom"
+import Settings from "settings"
+import utils from "utils"
import LocationForm from "./Form"
const GQL_GET_LOCATION = gql`
@@ -52,7 +56,8 @@ const GQL_GET_LOCATION = gql`
avatar(size: 32)
}
}
- }
+ }
+ customFields
${GRAPHQL_NOTES_FIELDS}
}
}
@@ -75,8 +80,14 @@ const LocationEdit = ({ pageDispatchers }) => {
if (done) {
return result
}
-
+ if (data) {
+ data.location[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe(
+ data.location.customFields
+ )
+ }
const location = new Location(data ? data.location : {})
+ // mutates the object
+ initInvisibleFields(location, Settings.fields.location.customFields)
return (
diff --git a/client/src/pages/locations/Form.js b/client/src/pages/locations/Form.js
index 8eeada0f6c..568b2b1933 100644
--- a/client/src/pages/locations/Form.js
+++ b/client/src/pages/locations/Form.js
@@ -2,11 +2,15 @@ import API from "api"
import { gql } from "apollo-boost"
import AppContext from "components/AppContext"
import ApprovalsDefinition from "components/approvals/ApprovalsDefinition"
+import {
+ CustomFieldsContainer,
+ customFieldsJSONString
+} from "components/CustomFields"
import * as FieldHelper from "components/FieldHelper"
import Fieldset from "components/Fieldset"
import Leaflet from "components/Leaflet"
import Messages from "components/Messages"
-import Model from "components/Model"
+import Model, { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model"
import NavigationWarning from "components/NavigationWarning"
import { jumpToTop } from "components/Page"
import { FastField, Form, Formik } from "formik"
@@ -17,6 +21,7 @@ import PropTypes from "prop-types"
import React, { useContext, useState } from "react"
import { Button } from "react-bootstrap"
import { useHistory } from "react-router-dom"
+import Settings from "settings"
import GeoLocation from "./GeoLocation"
const GQL_CREATE_LOCATION = gql`
@@ -94,6 +99,7 @@ const LocationForm = ({ edit, title, initialValues }) => {
setFieldValue,
setValues,
values,
+ validateForm,
submitForm
}) => {
const marker = {
@@ -189,7 +195,19 @@ const LocationForm = ({ edit, title, initialValues }) => {
setFieldValue={setFieldValue}
approversFilters={approversFilters}
/>
-
+ {Settings.fields.location.customFields && (
+
+ )}
@@ -260,8 +278,11 @@ const LocationForm = ({ edit, title, initialValues }) => {
const location = Object.without(
new Location(values),
"notes",
- "displayedCoordinate"
+ "displayedCoordinate",
+ "customFields", // initial JSON from the db
+ DEFAULT_CUSTOM_FIELDS_PARENT
)
+ location.customFields = customFieldsJSONString(values)
return API.mutation(edit ? GQL_UPDATE_LOCATION : GQL_CREATE_LOCATION, {
location
})
diff --git a/client/src/pages/locations/New.js b/client/src/pages/locations/New.js
index 45adf8e392..784e860256 100644
--- a/client/src/pages/locations/New.js
+++ b/client/src/pages/locations/New.js
@@ -1,12 +1,14 @@
import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions"
+import { initInvisibleFields } from "components/CustomFields"
import {
- PageDispatchersPropType,
mapPageDispatchersToProps,
+ PageDispatchersPropType,
useBoilerplate
} from "components/Page"
import { Location } from "models"
import React from "react"
import { connect } from "react-redux"
+import Settings from "settings"
import LocationForm from "./Form"
const LocationNew = ({ pageDispatchers }) => {
@@ -17,7 +19,8 @@ const LocationNew = ({ pageDispatchers }) => {
})
const location = new Location()
-
+ // mutates the object
+ initInvisibleFields(location, Settings.fields.location.customFields)
return
}
diff --git a/client/src/pages/locations/Show.js b/client/src/pages/locations/Show.js
index ed79a1edaa..5e03fbedd7 100644
--- a/client/src/pages/locations/Show.js
+++ b/client/src/pages/locations/Show.js
@@ -3,11 +3,13 @@ import API from "api"
import { gql } from "apollo-boost"
import AppContext from "components/AppContext"
import Approvals from "components/approvals/Approvals"
+import { ReadonlyCustomFields } from "components/CustomFields"
import * as FieldHelper from "components/FieldHelper"
import Fieldset from "components/Fieldset"
import Leaflet from "components/Leaflet"
import LinkTo from "components/LinkTo"
import Messages from "components/Messages"
+import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model"
import {
mapPageDispatchersToProps,
PageDispatchersPropType,
@@ -24,6 +26,8 @@ import { Location } from "models"
import React, { useContext } from "react"
import { connect } from "react-redux"
import { useLocation, useParams } from "react-router-dom"
+import Settings from "settings"
+import utils from "utils"
import GeoLocation, { GEO_LOCATION_DISPLAY_TYPE } from "./GeoLocation"
const GQL_GET_LOCATION = gql`
@@ -64,6 +68,7 @@ const GQL_GET_LOCATION = gql`
}
}
}
+ customFields
${GRAPHQL_NOTES_FIELDS}
}
}
@@ -88,7 +93,11 @@ const LocationShow = ({ pageDispatchers }) => {
if (done) {
return result
}
-
+ if (data) {
+ data.location[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe(
+ data.location.customFields
+ )
+ }
const location = new Location(data ? data.location : {})
const stateSuccess = routerLocation.state && routerLocation.state.success
const stateError = routerLocation.state && routerLocation.state.error
@@ -156,6 +165,14 @@ const LocationShow = ({ pageDispatchers }) => {
+ {Settings.fields.location.customFields && (
+
+ )}
diff --git a/client/src/pages/organizations/Edit.js b/client/src/pages/organizations/Edit.js
index c29b0a647c..c7fb4b6929 100644
--- a/client/src/pages/organizations/Edit.js
+++ b/client/src/pages/organizations/Edit.js
@@ -1,9 +1,11 @@
import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions"
import API from "api"
import { gql } from "apollo-boost"
+import { initInvisibleFields } from "components/CustomFields"
+import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model"
import {
- PageDispatchersPropType,
mapPageDispatchersToProps,
+ PageDispatchersPropType,
useBoilerplate
} from "components/Page"
import RelatedObjectNotes, {
@@ -13,6 +15,8 @@ import { Organization } from "models"
import React from "react"
import { connect } from "react-redux"
import { useParams } from "react-router-dom"
+import Settings from "settings"
+import utils from "utils"
import OrganizationForm from "./Form"
const GQL_GET_ORGANIZATION = gql`
@@ -65,6 +69,7 @@ const GQL_GET_ORGANIZATION = gql`
shortName
longName
}
+ customFields
${GRAPHQL_NOTES_FIELDS}
}
}
@@ -87,9 +92,14 @@ const OrganizationEdit = ({ pageDispatchers }) => {
if (done) {
return result
}
-
+ if (data) {
+ data.organization[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe(
+ data.organization.customFields
+ )
+ }
const organization = new Organization(data ? data.organization : {})
-
+ // mutates the object
+ initInvisibleFields(organization, Settings.fields.organization.customFields)
return (