diff --git a/src/actions.js b/src/actions.js
index 5538f01..1eea91e 100644
--- a/src/actions.js
+++ b/src/actions.js
@@ -22,6 +22,7 @@ const ENROLLMENT_SUMMARY_FULL_PROJECTION = () => [
'numberOfIndividualsAssignedToProgramme',
'numberOfIndividualsNotAssignedToProgramme',
'numberOfIndividualsAssignedToSelectedProgramme',
+ 'numberOfIndividualsToUpload',
];
export function fetchWorkflows() {
@@ -72,6 +73,13 @@ const GROUP_HISTORY_FULL_PROJECTION = GROUP_FULL_PROJECTION.filter(
(item) => item !== 'head {firstName, lastName}',
);
+const UPLOAD_HISTORY_FULL_PROJECTION = () => [
+ 'id',
+ 'uuid',
+ 'workflow',
+ 'dataUpload {uuid, dateCreated, dateUpdated, sourceName, sourceType, status, error }',
+];
+
export function fetchIndividualEnrollmentSummary(params) {
const payload = formatQuery(
'individualEnrollmentSummary',
@@ -116,6 +124,11 @@ export function fetchGroupHistory(params) {
return graphql(payload, ACTION_TYPE.SEARCH_GROUP_HISTORY);
}
+export function fetchUploadHistory(params) {
+ const payload = formatPageQueryWithCount('individualDataUploadHistory', params, UPLOAD_HISTORY_FULL_PROJECTION());
+ return graphql(payload, ACTION_TYPE.GET_INDIVIDUAL_UPLOAD_HISTORY);
+}
+
export function deleteIndividual(individual, clientMutationLabel) {
const individualUuids = `ids: ["${individual?.id}"]`;
const mutation = formatMutation('deleteIndividual', individualUuids, clientMutationLabel);
diff --git a/src/components/CollapsableErrorList.js b/src/components/CollapsableErrorList.js
new file mode 100644
index 0000000..4309079
--- /dev/null
+++ b/src/components/CollapsableErrorList.js
@@ -0,0 +1,64 @@
+import React, { useState } from 'react';
+import { injectIntl } from 'react-intl';
+import ExpandLess from '@material-ui/icons/ExpandLess';
+import ExpandMore from '@material-ui/icons/ExpandMore';
+import {
+ formatMessage,
+} from '@openimis/fe-core';
+import {
+ ListItem,
+ ListItemText,
+ Collapse,
+} from '@material-ui/core';
+import { withTheme, withStyles } from '@material-ui/core/styles';
+
+const styles = (theme) => ({
+ item: theme.paper.item,
+});
+
+function CollapsableErrorList({
+ intl,
+ errors,
+}) {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const handleOpen = () => {
+ setIsExpanded(!isExpanded);
+ };
+
+ if (!errors || !Object.keys(errors).length) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+
+ {isExpanded ? : }
+
+
+ { JSON.stringify(errors) }
+
+ >
+ );
+}
+
+export default injectIntl(
+ withTheme(
+ withStyles(styles)(CollapsableErrorList),
+ ),
+);
diff --git a/src/components/dialogs/AdvancedCriteriaForm.js b/src/components/dialogs/AdvancedCriteriaForm.js
index 213b4cd..55a0722 100644
--- a/src/components/dialogs/AdvancedCriteriaForm.js
+++ b/src/components/dialogs/AdvancedCriteriaForm.js
@@ -264,7 +264,7 @@ function AdvancedCriteriaForm({
-
+
{formatMessage(intl, 'individual', 'individual.enrollment.totalNumberOfIndividuals')}
@@ -274,7 +274,7 @@ function AdvancedCriteriaForm({
-
+
{formatMessage(intl, 'individual', 'individual.enrollment.numberOfSelectedIndividuals')}
@@ -284,7 +284,7 @@ function AdvancedCriteriaForm({
-
+
{formatMessage(intl, 'individual', 'individual.enrollment.numberOfIndividualsAssignedToProgramme')}
@@ -294,7 +294,7 @@ function AdvancedCriteriaForm({
-
+
{formatMessage(intl, 'individual', 'individual.enrollment.numberOfIndividualsNotAssignedToProgramme')}
@@ -304,7 +304,7 @@ function AdvancedCriteriaForm({
-
+
{/* eslint-disable-next-line max-len */}
@@ -315,6 +315,17 @@ function AdvancedCriteriaForm({
+
+
+
+ {/* eslint-disable-next-line max-len */}
+ {formatMessage(intl, 'individual', 'individual.enrollment.numberOfIndividualsToBeUploaded')}
+
+
+ {enrollmentSummary.numberOfIndividualsToUpload}
+
+
+
diff --git a/src/components/dialogs/IndividualsHistoryUploadDialog.js b/src/components/dialogs/IndividualsHistoryUploadDialog.js
new file mode 100644
index 0000000..79dffc9
--- /dev/null
+++ b/src/components/dialogs/IndividualsHistoryUploadDialog.js
@@ -0,0 +1,248 @@
+import React, { useEffect, useState } from 'react';
+import { injectIntl } from 'react-intl';
+import Button from '@material-ui/core/Button';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogTitle from '@material-ui/core/DialogTitle';
+import {
+ formatMessage,
+ formatDateFromISO,
+ ProgressOrError,
+ withModulesManager,
+} from '@openimis/fe-core';
+import {
+ TableHead,
+ TableBody,
+ Table,
+ TableCell,
+ TableRow,
+ TableFooter,
+ TableContainer,
+ Paper,
+ MenuItem,
+} from '@material-ui/core';
+import { withTheme, withStyles } from '@material-ui/core/styles';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import CollapsableErrorList from '../CollapsableErrorList';
+import { fetchUploadHistory } from '../../actions';
+import { downloadInvalidItems } from '../../utils';
+import { UPLOAD_STATUS } from '../../constants';
+
+const styles = (theme) => ({
+ item: theme.paper.item,
+});
+
+function IndividualsUploadHistoryDialog({
+ modulesManager,
+ intl,
+ classes,
+ fetchUploadHistory,
+ history,
+ fetchedHistory,
+ fetchingHistory,
+}) {
+ const [isOpen, setIsOpen] = useState(false);
+ const [records, setRecords] = useState([]);
+
+ const handleOpen = () => {
+ setIsOpen(true);
+ };
+
+ const handleClose = () => {
+ setIsOpen(false);
+ };
+
+ const downloadInvalidItemsFromUpload = (uploadId) => {
+ downloadInvalidItems(uploadId);
+ };
+
+ useEffect(() => {
+ if (isOpen) {
+ const params = [];
+ fetchUploadHistory(params);
+ }
+ }, [isOpen]);
+
+ useEffect(() => {
+ setRecords(history);
+ }, [fetchedHistory]);
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+const mapStateToProps = (state) => ({
+ rights: !!state.core && !!state.core.user && !!state.core.user.i_user ? state.core.user.i_user.rights : [],
+ confirmed: state.core.confirmed,
+ history: state.individual.individualDataUploadHistory,
+ fetchedHistory: state.individual.fetchedIndividualDataUploadHistory,
+ fetchingHistory: state.individual.fetchingIndividualDataUploadHistory,
+});
+
+const mapDispatchToProps = (dispatch) => bindActionCreators({
+ fetchUploadHistory,
+}, dispatch);
+
+export default injectIntl(
+ withModulesManager(withTheme(
+ withStyles(styles)(
+ connect(mapStateToProps, mapDispatchToProps)(IndividualsUploadHistoryDialog),
+ ),
+ )),
+);
diff --git a/src/components/dialogs/IndividualsUploadDialog.js b/src/components/dialogs/IndividualsUploadDialog.js
index ca2f320..8751db2 100644
--- a/src/components/dialogs/IndividualsUploadDialog.js
+++ b/src/components/dialogs/IndividualsUploadDialog.js
@@ -17,6 +17,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import WorkflowsPicker from '../../pickers/WorkflowsPicker';
import { fetchWorkflows } from '../../actions';
+import IndividualsHistoryUploadDialog from './IndividualsHistoryUploadDialog';
const styles = (theme) => ({
item: theme.paper.item,
@@ -105,6 +106,7 @@ function IndividualsUploadDialog({
>
{formatMessage(intl, 'individual', 'individual.upload.buttonLabel')}
+