Skip to content

Commit

Permalink
[#5080] Added active state to fix indicator approval workflow
Browse files Browse the repository at this point in the history
Active status helps MnE managers to identify updates of indicators that have an unlocked period still in progress.
the following what has to be done in order to finalize  this feature :
1. Update status description.
2. Add new icon history.
3. Create audit trail modal
4. Call /indicator_period_data_framework/ by period
   Pulling data audit trails from endpoint /indicator_period_data_framework/ by period ID of selected update.
  • Loading branch information
ifirmawan committed Aug 26, 2022
1 parent c9d073b commit 6e9e9f5
Show file tree
Hide file tree
Showing 12 changed files with 318 additions and 125 deletions.
15 changes: 15 additions & 0 deletions akvo/rsr/spa/app/components/Icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'
import { Icon as AntIcon } from 'antd'
import SVGInline from 'react-svg-inline'
import get from 'lodash/get'
import { icons } from '../utils/images'


const Icon = ({ type, ...props }) => {
const customIcon = get(icons, type)
return customIcon
? <SVGInline svg={customIcon} {...props} />
: <AntIcon type={type} {...props} />
}

export default Icon
21 changes: 5 additions & 16 deletions akvo/rsr/spa/app/components/StatusIndicator.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import React from 'react'
import { Col, Row, Typography } from 'antd'
import { statusDescription } from '../utils/constants'

const { Text } = Typography

const StatusIndicator = ({ status }) => {
let description = 'No status yet'
if (status === 'D') {
description = 'Draft update created'
}
if (status === 'P') {
description = 'Update submitted'
}
if (status === 'R') {
description = 'Update declined'
}
if (status === 'A') {
description = 'Approved update reported'
}
const StatusIndicator = ({ status, updateClass }) => {
const description = statusDescription[status] || statusDescription[updateClass] || statusDescription.NO_STATUS
return (
<Row>
<Col style={{ display: 'flex', gap: 10 }}>
<Row className="header-status">
<Col style={{ display: 'flex', gap: 10 }} className={updateClass}>
<Text strong>Status</Text>
<Text>:&nbsp;{description}</Text>
</Col>
Expand Down
1 change: 1 addition & 0 deletions akvo/rsr/spa/app/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { MobileSlider } from './MobileSlider'
export { PrevUpdate } from './PrevUpdate'
export { DeclinePopup } from './DeclinePopup'
export { IndicatorItem } from './IndicatorItem'
export { default as Icon } from './Icon'
3 changes: 3 additions & 0 deletions akvo/rsr/spa/app/images/clock-history.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions akvo/rsr/spa/app/images/edit-pencil.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
216 changes: 135 additions & 81 deletions akvo/rsr/spa/app/modules/results-admin/TobeReported.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,31 @@ import {
Row,
Col,
Modal,
Icon,
message
} from 'antd'
import { useTranslation } from 'react-i18next'
import SimpleMarkdown from 'simple-markdown'
import SVGInline from 'react-svg-inline'
import classNames from 'classnames'
import moment from 'moment'
import { isEmpty } from 'lodash'
import { isEmpty, kebabCase } from 'lodash'
import { connect } from 'react-redux'

import './TobeReported.scss'
import editButton from '../../images/edit-button.svg'
import api from '../../utils/api'
import ReportedEdit from './components/ReportedEdit'
import { isPeriodNeedsReportingForAdmin } from '../results/filters'
import Highlighted from '../../components/Highlighted'
import StatusIndicator from '../../components/StatusIndicator'
import ResultType from '../../components/ResultType'
import * as actions from '../results/actions'
import { ACTIVE_PERIOD } from '../../utils/constants'
import { Icon } from '../../components'
import AuditTrailModal from './components/AuditTrailModal'

const { Text } = Typography
const { Text, Title } = Typography

const TobeReported = ({
resultRdr,
keyword,
results,
updates,
Expand All @@ -51,6 +52,12 @@ const TobeReported = ({
const [activeKey, setActiveKey] = useState(null)
const [deletion, setDeletion] = useState([])
const [errors, setErrors] = useState([])
const [openHistory, setOpenHistory] = useState({
scores: [],
results: [],
fetched: false,
visible: false
})
const formRef = useRef()

const mdParse = SimpleMarkdown.defaultBlockParse
Expand Down Expand Up @@ -147,50 +154,97 @@ const TobeReported = ({
formRef.current.form.setConfig('keepDirtyOnReinitialize', true)
}

const handleOnShowHistory = item => {
setOpenHistory({
...openHistory,
fetched: false,
visible: !openHistory.visible
})
api
.get(`/indicator_period_data_framework/?period=${item?.period?.id}&format=json`)
.then(({ data }) => {
const { results: dataResults } = data
setOpenHistory({
scores: item?.indicator?.scores,
results: dataResults,
fetched: true,
visible: !openHistory.visible
})
})
.catch(() => message.error('Something went wrong'))
}

const handleOnCloseModal = () => {
setOpenHistory({
scores: [],
results: [],
fetched: false,
visible: false
})
}

return (
<List
grid={{ column: 1 }}
itemLayout="vertical"
className="tobe-reported"
dataSource={updates}
renderItem={(item, ix) => {
const iKey = item?.id || `${item?.indicator?.id}0${ix}`
const updateClass = item?.statusDisplay?.toLowerCase()?.replace(/\s+/g, '-')
return (
<List.Item className="tobe-reported-item">
<Card className={classNames(updateClass, { active: (activeKey === iKey) })}>
<Row type="flex" justify="space-between" align="middle">
<Col lg={22} md={22} sm={24} xs={24}>
{isEmpty(period) && (
<div className="period-caption">
{moment(item?.period?.periodStart, 'DD/MM/YYYY').format('DD MMM YYYY')} - {moment(item?.period?.periodEnd, 'DD/MM/YYYY').format('DD MMM YYYY')}
</div>
)}
<StatusIndicator status={item?.status} />
<ResultType {...item?.indicator?.result} />
<br />
<Text strong>Title : </Text>
<Highlighted text={item?.indicator?.title} highlight={keyword} />
<br />
{((!isEmpty(item?.indicator?.description.trim())) && item?.indicator?.description?.trim().length > 5) && (
<details>
<summary>{t('Description')}</summary>
<p className="desc hide-for-mobile">{mdOutput(mdParse(item?.indicator?.description))}</p>
</details>
)}
</Col>
<Col lg={2} md={2} sm={24} xs={24} className="action">
{
(activeKey === iKey)
? (
<div className="action-close">
<Button onClick={handleCancel}>
<Icon type="close" />
<span className="action-text">Close</span>
<>
<AuditTrailModal {...openHistory} onClose={handleOnCloseModal} />
<List
grid={{ column: 1 }}
itemLayout="vertical"
className="tobe-reported"
dataSource={updates}
renderItem={(item, ix) => {
const iKey = item?.id || `${item?.indicator?.id}0${ix}`
const allSubmissions = resultRdr
?.filter((r) => r.id === item.result?.id)
?.flatMap((r) => r.indicators)
?.filter((i) => i.id === item.indicator?.id)
?.flatMap((i) => i.periods)
?.filter((p) => (
p.id === item.period?.id &&
p.updates.length &&
!p.locked
))
?.flatMap((p) => p.updates)
const updateClass = (!item.status && allSubmissions.length) ? ACTIVE_PERIOD : kebabCase(item?.statusDisplay)
return (
<List.Item className="tobe-reported-item">
<Card className={classNames(updateClass, { active: (activeKey === iKey) })}>
<Row type="flex" justify="space-between" align="top">
<Col lg={22} md={22} sm={24} xs={24}>
{isEmpty(period) && (
<div className="period-caption">
{moment(item?.period?.periodStart, 'DD/MM/YYYY').format('DD MMM YYYY')} - {moment(item?.period?.periodEnd, 'DD/MM/YYYY').format('DD MMM YYYY')}
</div>
)}
<StatusIndicator status={item?.status} updateClass={updateClass} />
<ResultType {...item?.indicator?.result} />
<br />
<Text strong>Title : </Text>
<Highlighted text={item?.indicator?.title} highlight={keyword} />
<br />
{((!isEmpty(item?.indicator?.description.trim())) && item?.indicator?.description?.trim().length > 5) && (
<details>
<summary>{t('Description')}</summary>
<span className="desc">{mdOutput(mdParse(item?.indicator?.description))}</span>
</details>
)}
</Col>
<Col lg={2} md={2} sm={24} xs={24} className="action">
{(activeKey === iKey) && (
<div className="action-close">
<Button onClick={handleCancel}>
<Icon type="close" />
<span className="action-text">Close</span>
</Button>
</div>
)}
{(activeKey !== iKey) && (
<>
{(allSubmissions.length > 0) && (
<Button type="link" onClick={() => handleOnShowHistory(item)}>
<Icon type="clock.history" />
<span className="action-text">Audit Trail</span>
</Button>
</div>
)
: (
)}
<Button
type="link"
onClick={() => {
Expand All @@ -200,43 +254,43 @@ const TobeReported = ({
handleOnEdit(item)
setActiveKey(iKey)
}}
block
>
<SVGInline svg={editButton} className="edit-button" />
<Icon type="edit.pencil" />
<span className="action-text">Edit Value</span>
</Button>
)
}
</Col>
</Row>
</Card>
{(editing && activeKey) && (
<Collapse activeKey={activeKey} bordered={false} accordion>
<Collapse.Panel key={iKey} showArrow={false}>
<ReportedEdit
{...{
activeKey,
formRef,
project,
editing,
editPeriod,
deleteFile,
deletion,
errors,
setErrors,
setActiveKey,
handleOnUpdate,
mneView: true,
deletePendingUpdate: deleteOnUpdate
}}
/>
</Collapse.Panel>
</Collapse>
)}
</List.Item>
)
}}
/>
</>
)}
</Col>
</Row>
</Card>
{(editing && activeKey) && (
<Collapse activeKey={activeKey} bordered={false} accordion>
<Collapse.Panel key={iKey} showArrow={false}>
<ReportedEdit
{...{
activeKey,
formRef,
project,
editing,
editPeriod,
deleteFile,
deletion,
errors,
setErrors,
setActiveKey,
handleOnUpdate,
mneView: true,
deletePendingUpdate: deleteOnUpdate
}}
/>
</Collapse.Panel>
</Collapse>
)}
</List.Item>
)
}}
/>
</>
)
}

Expand Down
Loading

0 comments on commit 6e9e9f5

Please sign in to comment.