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 29, 2022
1 parent 365654a commit 95596b0
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 140 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
5 changes: 4 additions & 1 deletion akvo/rsr/spa/app/components/StatusPeriod.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React from 'react'
import SVGInline from 'react-svg-inline'
import { Button } from 'antd'
import { useTranslation } from 'react-i18next'

import approvedSvg from '../images/status-approved.svg'
import pendingSvg from '../images/status-pending.svg'
import revisionSvg from '../images/status-revision.svg'
import { DeclinePopup } from './DeclinePopup'

const Aux = node => node.children

export const StatusPeriod = ({ update, pinned, index, handleUpdateStatus, t }) => {
export const StatusPeriod = ({ update, pinned, index, handleUpdateStatus }) => {
const { t } = useTranslation()
if (update.status === 'A') {
return (
<div className="status approved">
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.
255 changes: 161 additions & 94 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,32 @@ import {
Row,
Col,
Modal,
Icon,
message
message,
Tooltip
} 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 +53,12 @@ const TobeReported = ({
const [activeKey, setActiveKey] = useState(null)
const [deletion, setDeletion] = useState([])
const [errors, setErrors] = useState([])
const [openHistory, setOpenHistory] = useState({
scores: [],
results: [],
item: null,
visible: false
})
const formRef = useRef()

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

const handleOnShowHistory = item => {
setOpenHistory({
...openHistory,
item: {
...item,
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,
item: {
...item,
fetched: true
},
visible: !openHistory.visible
})
})
.catch(() => message.error('Something went wrong'))
}

const handleOnCloseModal = () => {
setOpenHistory({
scores: [],
results: [],
item: null,
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>
</Button>
</div>
)
: (
<Button
type="link"
onClick={() => {
if (errors.length) {
setErrors([])
}
handleOnEdit(item)
setActiveKey(iKey)
}}
block
>
<SVGInline svg={editButton} className="edit-button" />
<span className="action-text">Edit Value</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>
)
}
</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>
)
}}
/>
</div>
)}
{(activeKey !== iKey) && (
<>
{(allSubmissions.length > 0) && (
<Tooltip placement="top" title="Audit Trail">
<Button type="link" onClick={() => handleOnShowHistory(item)}>
<Icon type="clock.history" />
<span className="action-text">Audit Trail</span>
</Button>
</Tooltip>
)}
<Tooltip placement="top" title="Edit Value">
<Button
type="link"
onClick={() => {
if (errors.length) {
setErrors([])
}
handleOnEdit(item)
setActiveKey(iKey)
}}
>
<Icon type="edit.pencil" />
<span className="action-text">Edit Value</span>
</Button>
</Tooltip>
</>
)}
</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 95596b0

Please sign in to comment.