Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/survey form header #3136

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
12 changes: 12 additions & 0 deletions core/i18n/resources/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,16 @@ E.g. this.region = region_attribute_name
other: 'Other',
},
},
formHeaderProps: {
headerColorLabel: 'Header color',
headerColor: {
blue: 'Blue',
green: 'Green',
orange: 'Orange',
red: 'Red',
yellow: 'Yellow',
},
},
textProps: {
textInputType: 'Text input type',
textInputTypes: {
Expand Down Expand Up @@ -1236,6 +1246,8 @@ E.g. this.region = region_attribute_name
taxon: 'Taxon',
text: 'Text',
time: 'Time',
// layout elments
formHeader: 'Form Header',
},
clone: `Clone '{{nodeDefLabel}}'`,
compressFormItems: `Compress form items for '{{nodeDefLabel}}'`,
Expand Down
17 changes: 11 additions & 6 deletions core/survey/_survey/surveyNodeDefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,31 @@ const filterNodeDefsWithoutSiblings = (nodeDefs) =>
)

export const getNodeDefChildren =
(nodeDef, includeAnalysis = true) =>
(nodeDef, includeAnalysis = true, includeLayoutElements = false) =>
(survey) => {
const surveyIndexed = survey.nodeDefsIndex ? survey : SurveyNodeDefsIndex.initAndAssocNodeDefsIndex(survey)
let childDefs = Surveys.getNodeDefChildren({ survey: surveyIndexed, nodeDef, includeAnalysis })
let childDefs = Surveys.getNodeDefChildren({
survey: surveyIndexed,
nodeDef,
includeAnalysis,
includeLayoutElements,
})
childDefs = filterNodeDefsWithoutSiblings(childDefs)
return childDefs
}

export const getNodeDefChildrenSorted =
({ nodeDef, includeAnalysis = false, cycle = null }) =>
({ nodeDef, includeAnalysis = false, cycle = null, includeLayoutElements = false }) =>
(survey) => {
let childDefs = Surveys.getNodeDefChildrenSorted({ survey, nodeDef, includeAnalysis, cycle })
let childDefs = Surveys.getNodeDefChildrenSorted({ survey, cycle, nodeDef, includeAnalysis, includeLayoutElements })
childDefs = filterNodeDefsWithoutSiblings(childDefs)
return childDefs
}

export const getNodeDefChildrenInOwnPage =
({ nodeDef, cycle }) =>
({ nodeDef, cycle, includeAnalysis = true, includeLayoutElements = false }) =>
(survey) => {
const children = getNodeDefChildren(nodeDef)(survey)
const children = getNodeDefChildren(nodeDef, includeAnalysis, includeLayoutElements)(survey)
const childrenInOwnPage = children.filter(NodeDefLayout.hasPage(cycle))
const childrenIndex = NodeDefLayout.getIndexChildren(cycle)(nodeDef)
if (childrenIndex.length === 0) return childrenInOwnPage
Expand Down
20 changes: 17 additions & 3 deletions core/survey/nodeDef.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { valuePropsTaxon } from './nodeValueProps'

export { nodeDefType }

export const NodeDefLayoutElementTypes = [nodeDefType.formHeader]

export const keys = {
id: ObjectUtils.keys.id,
uuid: ObjectUtils.keys.uuid,
Expand Down Expand Up @@ -87,6 +89,9 @@ export const propKeys = {
includeAccuracy: 'includeAccuracy',
includeAltitude: 'includeAltitude',
includeAltitudeAccuracy: 'includeAltitudeAccuracy',

// layout elements
headerColor: 'headerColor',
}

export const textInputTypes = {
Expand Down Expand Up @@ -203,6 +208,8 @@ export const isInteger = isType(nodeDefType.integer)
export const isTaxon = isType(nodeDefType.taxon)
export const isText = isType(nodeDefType.text)
export const isTime = isType(nodeDefType.time)
// layout elments
export const isFormHeader = isType(nodeDefType.formHeader)

export const isReadOnly = getProp(propKeys.readOnly, false)
export const isHidden = getProp(propKeys.hidden, false)
Expand Down Expand Up @@ -250,6 +257,11 @@ export const getTextTransform = getProp(propKeys.textTransform, textTransformVal
export const getTextTransformFunction = (nodeDef) =>
TextUtils.transform({ transformFunction: getTextTransform(nodeDef) })

// layout elements

export const getHeaderColor = getProp(propKeys.headerColor)
export const isLayoutElement = isFormHeader

// ==== READ meta
export const getMeta = R.propOr({}, keys.meta)
export const getMetaHierarchy = R.pathOr([], [keys.meta, metaKeys.h])
Expand Down Expand Up @@ -502,8 +514,9 @@ export const keepOnlyOneCycle =
export const canNodeDefBeMultiple = (nodeDef) =>
// Entity def but not root
(isEntity(nodeDef) && !isRoot(nodeDef)) ||
// Attribute def but not analysis
(!isAnalysis(nodeDef) &&
// Attribute def but not layout element and not analysis
(!isLayoutElement(nodeDef) &&
!isAnalysis(nodeDef) &&
R.includes(getType(nodeDef), [
nodeDefType.decimal,
nodeDefType.code,
Expand All @@ -523,7 +536,8 @@ export const canNodeDefTypeBeKey = (type) =>
nodeDefType.time,
])

export const canNodeDefBeKey = (nodeDef) => !isAnalysis(nodeDef) && canNodeDefTypeBeKey(getType(nodeDef))
export const canNodeDefBeKey = (nodeDef) =>
!isLayoutElement(nodeDef) && !isAnalysis(nodeDef) && canNodeDefTypeBeKey(getType(nodeDef))

export const canHaveDefaultValue = (nodeDef) =>
isSingleAttribute(nodeDef) &&
Expand Down
2 changes: 2 additions & 0 deletions core/survey/nodeDefType.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export const nodeDefType = {
taxon: 'taxon',
file: 'file',
entity: 'entity',
// layout elements
formHeader: 'formHeader',
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"@mui/material": "^5.14.18",
"@mui/x-data-grid": "^6.18.1",
"@mui/x-date-pickers": "^6.18.1",
"@openforis/arena-core": "^0.0.175",
"@openforis/arena-core": "^0.0.177",
"@openforis/arena-server": "^0.1.30",
"@sendgrid/mail": "^7.7.0",
"@shopify/draggable": "^1.1.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,11 @@ class RecordsUpdateThread extends Thread {

const { survey, recordsCache } = await this.getOrFetchSurveyData(msg)

console.log('===init record', recordUuid)
let record = await RecordManager.fetchRecordAndNodesByUuid({ surveyId, recordUuid })

console.log('===record1', record)

record = await RecordManager.initNewRecord({
user,
survey,
Expand All @@ -170,6 +173,7 @@ class RecordsUpdateThread extends Thread {
nodesUpdateListener: (updatedNodes) => this.handleNodesUpdated.bind(this)({ record, updatedNodes }),
nodesValidationListener: (validations) => this.handleNodesValidationUpdated.bind(this)({ record, validations }),
})
console.log('===record2', record)
recordsCache.set(recordUuid, record)
}

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/tests/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default () =>
page.click('text="Login"'),
])

const header = await page.$('.header')
const header = await page.$('.app-header')
await expect(header).not.toBe(null)
})
})
2 changes: 1 addition & 1 deletion test/e2e/tests/utils/formUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const expectDropdownValue = async ({ testId = null, parentSelector = '', value }

const waitForLoaderToDisappear = async () => page.waitForSelector('.loader', { state: 'hidden', timeout: 5000 })
const waitForHeaderLoaderToDisappear = async () =>
page.waitForSelector('.header__loader-wrapper', { state: 'hidden', timeout: 5000 })
page.waitForSelector('.app-header__loader-wrapper', { state: 'hidden', timeout: 5000 })

export const FormUtils = {
selectDropdownItem,
Expand Down
34 changes: 26 additions & 8 deletions webapp/components/form/buttonGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,54 @@ import PropTypes from 'prop-types'

import * as R from 'ramda'

const ButtonGroup = ({ items, groupName, multiple, selectedItemKey, onChange, disabled, deselectable, className }) => (
const ButtonGroup = ({
items,
groupName,
multiple,
selectedItemKey,
onChange,
disabled: disabledProp,
deselectable,
className,
}) => (
<div className={`btn-group${className ? ` ${className}` : ''}`}>
{items.map((item) => {
const selected = selectedItemKey === item.key || (multiple && R.includes(item.key, selectedItemKey))
const { key, disabled, icon, label } = item
const selected = selectedItemKey === key || (multiple && R.includes(key, selectedItemKey))
return (
<button
data-testid={groupName ? `${groupName}_${item.key}` : null}
key={item.key}
data-testid={groupName ? `${groupName}_${key}` : null}
key={key}
type="button"
className={`btn btn-s${selected ? ' active' : ''}${deselectable ? ' deselectable' : ''}`}
onClick={() => {
let value
if (multiple) {
value = R.ifElse(R.always(selected), R.without(item.key), R.append(item.key))(selectedItemKey)
value = R.ifElse(R.always(selected), R.without(key), R.append(key))(selectedItemKey)
} else if (selected) {
value = null
} else {
value = item.key
value = key
}
onChange(value)
}}
aria-disabled={Boolean(item.disabled) || disabled}
aria-disabled={Boolean(disabled) || disabledProp}
>
{item.label}
{icon?.({ key })}
{label}
</button>
)
})}
</div>
)

export const toButtonGroupItems = ({ i18n, object, labelPrefix, icon = null }) =>
Object.keys(object).map((key) => ({
key,
label: i18n.t(`${labelPrefix}${key}`),
icon,
}))

ButtonGroup.propTypes = {
items: PropTypes.array,
groupName: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import CodeProps from '../CodeProps'
import CoordinateProps from '../CoordinateProps'
import DecimalProps from '../DecimalProps'
import FileProps from '../FileProps'
import FormHeaderProps from '../FormHeaderProps'
import TaxonProps from '../TaxonProps'
import TextProps from '../TextProps'
import AnalysisProps from '../AnalysisProps'
Expand All @@ -37,6 +38,7 @@ const basicPropsComponentByType = {
[NodeDef.nodeDefType.coordinate]: CoordinateProps,
[NodeDef.nodeDefType.decimal]: DecimalProps,
[NodeDef.nodeDefType.file]: FileProps,
[NodeDef.nodeDefType.formHeader]: FormHeaderProps,
[NodeDef.nodeDefType.taxon]: TaxonProps,
[NodeDef.nodeDefType.text]: TextProps,
}
Expand Down
61 changes: 61 additions & 0 deletions webapp/components/survey/NodeDefDetails/FormHeaderProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import './FormHeaderProps.scss'

import React, { useCallback } from 'react'
import PropTypes from 'prop-types'

import { FormHeaderColor } from '@openforis/arena-core'

import * as NodeDef from '@core/survey/nodeDef'

import { useI18n } from '@webapp/store/system'
import { FormItem } from '@webapp/components/form/Input'
import ButtonGroup, { toButtonGroupItems } from '@webapp/components/form/buttonGroup'

import { headerColorRgbCodesByColor } from '@webapp/components/survey/SurveyForm/nodeDefs/nodeDefUIProps'

import { State } from './store'

const headerColorItems = ({ i18n }) =>
toButtonGroupItems({
i18n,
object: FormHeaderColor,
labelPrefix: 'nodeDefEdit.formHeaderProps.headerColor.',
icon: ({ key }) => (
<span className="form-header-color-icon" style={{ backgroundColor: headerColorRgbCodesByColor[key] }} />
),
})

const FormHeaderProps = (props) => {
const { state, Actions } = props

const i18n = useI18n()

const nodeDef = State.getNodeDef(state)

const headerColor = NodeDef.getHeaderColor(nodeDef)

const onHeaderColorChange = useCallback(
(value) => {
Actions.setProp({ state, key: NodeDef.propKeys.headerColor, value: FormHeaderColor[value] })
},
[Actions, state]
)

return (
<FormItem label={i18n.t('nodeDefEdit.formHeaderProps.headerColorLabel')}>
<ButtonGroup
className="form-header-color-btn-group"
selectedItemKey={headerColor}
onChange={onHeaderColorChange}
items={headerColorItems({ i18n })}
/>
</FormItem>
)
}

FormHeaderProps.propTypes = {
state: PropTypes.object.isRequired,
Actions: PropTypes.object.isRequired,
}

export default FormHeaderProps
11 changes: 11 additions & 0 deletions webapp/components/survey/NodeDefDetails/FormHeaderProps.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.btn-group.form-header-color-btn-group {
button {
display: flex;
gap: 1rem;

.form-header-color-icon {
height: 1rem;
width: 1rem;
}
}
}
4 changes: 2 additions & 2 deletions webapp/components/survey/NodeDefDetails/NodeDefDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ const NodeDefDetails = () => {
}
/>
<div className="attribute-selector">
{nodeDefType} {NodeDefUIProps.getIconByType(nodeDefType)}
{i18n.t(`surveyForm.addChildToTypes.${nodeDefType}`)} {NodeDefUIProps.getIconByType(nodeDefType)}
</div>
</FormItem>

<TabBar
showTabs={!NodeDef.isAnalysis(nodeDef) && !NodeDef.isRoot(nodeDef)}
showTabs={!NodeDef.isAnalysis(nodeDef) && !NodeDef.isRoot(nodeDef) && !NodeDef.isLayoutElement(nodeDef)}
tabs={[
{
label: i18n.t('nodeDefEdit.basic'),
Expand Down
Loading
Loading