Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d1007bd
AMP-31026: Output / outcome manager
brianbrix Aug 21, 2025
24df9de
AMP-31025: ADD / Edit indicator
brianbrix Aug 23, 2025
2948b4f
AMP-31022:Indicators module changes
brianbrix Sep 2, 2025
7d10cab
AMP-31022:Indicators module changes
brianbrix Sep 3, 2025
8c3fb35
AMP-31044: break down target and original value by dissagregation.
brianbrix Sep 14, 2025
b5320b6
AMP-30988: Add log entry for sorting.
ginchauspe Aug 28, 2025
f263dd4
AMP-31050: Fix displaying the indicator value 2 times in the column.
ginchauspe Sep 4, 2025
6be8da1
AMP-31050: Modify the indicator per sector view to track Pillar/NPO/T…
ginchauspe Sep 4, 2025
66fa163
AMP-31050: Rollback changes over the sectors x indicator and implemen…
ginchauspe Sep 5, 2025
f61d28a
AMP-31050: Add behavior.
ginchauspe Sep 5, 2025
ab7bd2f
AMP-31050: Testing multi level hierarchy reports.
ginchauspe Sep 5, 2025
b497cb2
AMP-31050: Replace strings with constants.
ginchauspe Sep 8, 2025
a4ccb82
AMP-31050: Improve speed.
ginchauspe Sep 8, 2025
f9cb32e
AMP-31050: Update 2 more views.
ginchauspe Sep 10, 2025
6bf9807
AMP-31050: Checking some options for the Donor Group hierarchies.
ginchauspe Sep 11, 2025
c242c3d
AMP-31022: Force recreate views.
ginchauspe Sep 16, 2025
06393a8
AMP-31053: Fix M&E Indicator tabs issue
brianbrix Sep 21, 2025
9f35173
AMP-31053: Aggregation of values for M&E
brianbrix Sep 21, 2025
82dd25c
AMP-31053: Aggregation of values for M&E
brianbrix Sep 22, 2025
7010340
AMP-31053: Get target/base value supplied by admin
brianbrix Sep 24, 2025
1b1c67e
Merge pull request #4436 from devgateway/task/AMP-31053/M&E-Adjustements
jdeanquin-dg Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {lazy} from 'react';
import {Route, Routes } from 'react-router-dom';
import OutcomeOutputManagementPage from "./indicator_manager/pages/OutcomeOutputManagementPage";
import DisaggregationManagerPage from "./indicator_manager/pages/DisaggregationManagerPage";

const AdminNDDApp = lazy(() => import('./ndd'));
const IndicatorManagerApp = lazy(() => import('./indicator_manager'));
Expand All @@ -9,6 +11,8 @@ const AdminRoutes = () => {
<Routes>
<Route path="/ndd" element={<AdminNDDApp/>} />
<Route path="/indicator_manager" element={<IndicatorManagerApp/>} />
<Route path="/indicator_manager/outcome-output-management" element={<OutcomeOutputManagementPage/>} />
<Route path="/indicator_manager/disaggregation-manager" element={<DisaggregationManagerPage/>} />
</Routes>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { getPrograms } from '../reducers/fetchProgramsReducer';
import { getSettings } from '../reducers/fetchSettingsReducer';
import { resetSizePerPage } from '../reducers/fetchIndicatorsReducer';
import {getAmpCategories} from "../reducers/fetchAmpCategoryReducer";
import {getOutputs} from "../reducers/fetchOutputsReducer";
import {getOutcomes} from "../reducers/fetchOutcomesReducer";

export const AdminIndicatorManagerContext = React.createContext({});

Expand Down Expand Up @@ -43,6 +45,8 @@ const Startup: React.FC<StartupProps> = (props: any) => {
dispatch(getSectors());
dispatch(getPrograms());
dispatch(getAmpCategories());
dispatch(getOutputs());
dispatch(getOutcomes());
dispatch(resetSizePerPage());
// eslint-disable-next-line
}, []);
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useRef } from 'react';
import { Modal, Button, Form, Row, Col } from 'react-bootstrap';
import { Formik, FormikProps, Form as FormikForm, Field } from 'formik';
import * as Yup from 'yup';
import styles from './css/IndicatorModal.module.css';

interface AddNewOutcomeModalProps {
show: boolean;
setShow: (show: boolean) => void;
onSubmit?: (outcome: { name: string; description?: string }) => void;
initialName?: string;
initialDescription?: string;
translations?: Record<string, string>;
}

const OutcomeModal: React.FC<AddNewOutcomeModalProps> = ({ show, setShow, onSubmit, initialName = '', initialDescription = '', translations = {} }) => {
const nodeRef = useRef(null);
const validationSchema = Yup.object().shape({
name: Yup.string().required(translations['amp.outcomeoutput:errors-name-required'] || 'Name is required'),
description: Yup.string()
});

const initialValues = {
name: initialName,
description: initialDescription
};

const handleClose = () => setShow(false);

return (
<Modal
show={show}
onHide={handleClose}
centered
ref={nodeRef}
animation={false}
backdropClassName={styles.modal_backdrop}
backdrop="static"
keyboard={false}
size='lg'
>
<Modal.Header closeButton>
<Modal.Title>{translations['amp.outcomeoutput:modal-title-outcome'] || 'Add New Outcome'}</Modal.Title>
</Modal.Header>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
enableReinitialize
onSubmit={(values, { resetForm }) => {
if (onSubmit) onSubmit(values);
setShow(false);
resetForm();
}}
>
{(props: FormikProps<{ name: string; description?: string }>) => (
<Form noValidate onSubmit={props.handleSubmit}>
<Modal.Body>
<div className={styles.viewmodal_wrapper}>
<Row className={styles.view_row}>
<Form.Group as={Col} className={styles.view_item} controlId="formOutcomeName">
<Form.Label>{translations['amp.outcomeoutput:outcome-name'] || 'Outcome Name'}</Form.Label>
<Form.Control
defaultValue={props.values.name}
onChange={props.handleChange}
onBlur={props.handleBlur}
name="name"
className={`${styles.input_field} ${(props.errors.name && props.touched.name) && styles.text_is_invalid}`}
isInvalid={!!props.errors.name}
required
aria-required type="text"
placeholder={translations['amp.outcomeoutput:outcome-name'] || 'Outcome Name'}
/>
<Form.Control.Feedback type="invalid" className={styles.text_is_invalid}>
{props.errors.name}
</Form.Control.Feedback>
</Form.Group>
</Row>
<Row className={styles.view_row}>
<Form.Group as={Col} className={styles.view_item} controlId="formOutcomeDescription">
<Form.Label>{translations['amp.outcomeoutput:outcome-description'] || 'Outcome Description'}</Form.Label>
<Form.Control
defaultValue={props.values.description}
onChange={props.handleChange}
onBlur={props.handleBlur}
name="description"
type="text"
className={`${styles.input_field} ${(props.errors.description && props.touched.description) && styles.text_is_invalid}`}
placeholder={translations['amp.outcomeoutput:outcome-description'] || 'Outcome Description'}
/>
<Form.Control.Feedback type="invalid" className={styles.text_is_invalid}>
{props.errors.description}
</Form.Control.Feedback>
</Form.Group>
</Row>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
{translations['amp.outcomeoutput:cancel'] || 'Cancel'}
</Button>
<Button type="submit" variant="success">
{translations['amp.outcomeoutput:save-outcome'] || 'Save Outcome'}
</Button>
</Modal.Footer>
</Form>
)}
</Formik>
</Modal>
);
};

export default OutcomeModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useRef } from 'react';
import { Modal, Button, Form, Row, Col } from 'react-bootstrap';
import { Formik, FormikProps } from 'formik';
import * as Yup from 'yup';
import styles from './css/IndicatorModal.module.css';

interface Outcome {
id: number;
name: string;
}

interface AddNewOutputModalProps {
show: boolean;
setShow: (show: boolean) => void;
onSubmit?: (output: { name: string; description?: string; outcomeId: number }) => void;
initialName?: string;
initialDescription?: string;
selectedOutcome?: Outcome;
translations?: Record<string, string>;
loading?: boolean;
}

const OutputModal: React.FC<AddNewOutputModalProps> = ({
show,
setShow,
onSubmit,
initialName = '',
initialDescription = '',
selectedOutcome = undefined,
translations = {},
loading = false
}) => {
const nodeRef = useRef(null);

// Only render if selectedOutcome is provided
if (!selectedOutcome) return null;

const validationSchema = Yup.object().shape({
name: Yup.string().required(translations['amp.outcomeoutput:errors-name-required'] || 'Name is required'),
description: Yup.string(),
outcomeId: Yup.number().required('Outcome is required')
});

const initialValues = {
name: initialName,
description: initialDescription,
outcomeId: selectedOutcome.id
};

const handleClose = () => setShow(false);

return (
<Modal
show={show}
onHide={handleClose}
centered
ref={nodeRef}
animation={false}
backdropClassName={styles.modal_backdrop}
backdrop="static"
keyboard={false}
size='lg'
>
<Modal.Header closeButton>
<Modal.Title>{translations['amp.outcomeoutput:modal-title-output'] || 'Add New Output'}</Modal.Title>
</Modal.Header>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
enableReinitialize
onSubmit={(values, { resetForm }) => {
if (onSubmit) onSubmit(values);
setShow(false);
resetForm();
}}
>
{(props: FormikProps<{ name: string; description?: string; outcomeId: number }>) => (
<Form noValidate onSubmit={props.handleSubmit}>
<Modal.Body>
<div className={styles.viewmodal_wrapper}>
<Row className={styles.view_row}>
<Form.Group as={Col} className={styles.view_item} controlId="formOutputName">
<Form.Label>{translations['amp.outcomeoutput:output-name'] || 'Output Name'}</Form.Label>
<Form.Control
defaultValue={props.values.name}
onChange={props.handleChange}
onBlur={props.handleBlur}
name="name"
className={`${styles.input_field} ${(props.errors.name && props.touched.name) && styles.text_is_invalid}`}
isInvalid={!!props.errors.name}
required
aria-required type="text"
placeholder={translations['amp.outcomeoutput:output-name'] || 'Output Name'}
/>
<Form.Control.Feedback type="invalid" className={styles.text_is_invalid}>
{props.errors.name}
</Form.Control.Feedback>
</Form.Group>
</Row>
<Row className={styles.view_row}>
<Form.Group as={Col} className={styles.view_item} controlId="formOutputDescription">
<Form.Label>{translations['amp.outcomeoutput:output-description'] || 'Output Description'}</Form.Label>
<Form.Control
defaultValue={props.values.description}
onChange={props.handleChange}
onBlur={props.handleBlur}
name="description"
type="text"
className={`${styles.input_field} ${(props.errors.description && props.touched.description) && styles.text_is_invalid}`}
placeholder={translations['amp.outcomeoutput:output-description'] || 'Output Description'}
/>
<Form.Control.Feedback type="invalid" className={styles.text_is_invalid}>
{props.errors.description}
</Form.Control.Feedback>
</Form.Group>
</Row>
<Row className={styles.view_row}>
<Form.Group as={Col} className={styles.view_item} controlId="formOutputOutcome">
<Form.Label>{translations['amp.outcomeoutput:linked-outcome'] || 'Linked Outcome'}</Form.Label>
<Form.Control
type="text"
value={`${selectedOutcome.id}: ${selectedOutcome.name}`}
disabled
readOnly
className={styles.input_field}
/>
</Form.Group>
</Row>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose} disabled={loading}>
{translations['amp.outcomeoutput:cancel'] || 'Cancel'}
</Button>
<Button type="submit" variant="success" disabled={loading}>
{translations['amp.outcomeoutput:save-output'] || 'Save Output'}
</Button>
</Modal.Footer>
</Form>
)}
</Formik>
</Modal>
);
};

export default OutputModal;
Loading