Index: CHANGELOG.md
===================================================================
diff -u -N -rd337a0ac836544c6a7201f924878e2fd5f88370e -r7f9ffae3458e2f9994249398a1d526a3cbe861d6
--- CHANGELOG.md (.../CHANGELOG.md) (revision d337a0ac836544c6a7201f924878e2fd5f88370e)
+++ CHANGELOG.md (.../CHANGELOG.md) (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -1,6 +1,8 @@
2.1.4-SNAPSHOT / WIP
==================
+mobile app
* [OLMIS-7654](https://openlmis.atlassian.net/browse/OLMIS-7654): Create template page for program/facility selection - mobile app
+* [OLMIS-7655](https://openlmis.atlassian.net/browse/OLMIS-7655): Enable selecting program and facility on program/facility selection page - mobile app
2.1.3 / 2022-10-07
==================
Index: src/stock-on-hand-mobile/pages/program-select.jsx
===================================================================
diff -u -N -rd337a0ac836544c6a7201f924878e2fd5f88370e -r7f9ffae3458e2f9994249398a1d526a3cbe861d6
--- src/stock-on-hand-mobile/pages/program-select.jsx (.../program-select.jsx) (revision d337a0ac836544c6a7201f924878e2fd5f88370e)
+++ src/stock-on-hand-mobile/pages/program-select.jsx (.../program-select.jsx) (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -17,24 +17,48 @@
import { useSelector } from 'react-redux';
import RadioButton from '../../react-components/buttons/radio-button';
import Select from '../../react-components/inputs/select';
+import InputWithSuggestions from '../../react-components/inputs/input-with-suggestions';
const ProgramSelect = ({ offlineService }) => {
- const facility = useSelector(state => state[`facilitiesStockOnHand`][`userHomeFacilityStockOnHand`]);
-
- const programs = facility.supportedPrograms.map(({ id, name }) => ({ value: id, name }));
+ const convertIntoSelectOptions = (values) => {
+ return values.map(({ id, name }) => ({ value: id, name }));
+ };
- const menu = document.getElementsByClassName('header ng-scope')[0];
+ const facility = useSelector(state => state['facilitiesStockOnHand']['userHomeFacilityStockOnHand']);
+ const supervisedPrograms = useSelector(state => convertIntoSelectOptions(state['programsStockOnHand']['supervisedProgramsStockOnHand']));
+ const supervisedFacilities = useSelector(state => state['facilitiesStockOnHand']['supervisedFacilitiesStockOnHand']);
+ const programs = convertIntoSelectOptions(facility.supportedPrograms);
const [facilityId, setFacilityId] = useState(null);
const [programId, setProgramId] = useState(null);
- const [programName, setProgramName] = useState(null);
const [facilityType, setFacilityType] = useState('MyFacility');
+ const [supervisedFacilitiesOptions, setSupervisedFacilitiesOptions] = useState([]);
- const radioChangeHandler = e => setFacilityType(e.target.value);
+ const radioChangeHandler = (e) => {
+ if (facilityType !== e.target.value) {
+ if (e.target.value == 'MyFacility') {
+ setFacilityId(facility.id);
+ } else {
+ setFacilityId(null);
+ }
+ setFacilityType(e.target.value);
+ setProgramId(null);
+ }
+ };
- useEffect(() => menu.style.display = '', [menu]);
+ const supervisedProgramsHandler = (value) => {
+ setProgramId(value);
+ setSupervisedFacilitiesOptions(supervisedFacilities[value]);
+ };
+ const menu = document.getElementsByClassName('header ng-scope')[0];
+
+ useEffect(() => {
+ setFacilityId(facility.id);
+ menu.style.display = '';
+ }, [menu, programId]);
+
return (
<>
@@ -64,6 +88,7 @@
isSelected={facilityType === 'SupervisedFacility'}
label='Supervised Facility'
value='SupervisedFacility'
+ disabled={!supervisedPrograms}
/>
@@ -72,33 +97,44 @@
Program
-
+ {facilityType !== 'SupervisedFacility' ?
+
+
+ :
+ <>
+
- {facilityType === 'SupervisedFacility' && (
- <>
+
-
-
>
- )}
+ }
Index: src/stock-on-hand-mobile/reducers/facilities.jsx
===================================================================
diff -u -N -rd337a0ac836544c6a7201f924878e2fd5f88370e -r7f9ffae3458e2f9994249398a1d526a3cbe861d6
--- src/stock-on-hand-mobile/reducers/facilities.jsx (.../facilities.jsx) (revision d337a0ac836544c6a7201f924878e2fd5f88370e)
+++ src/stock-on-hand-mobile/reducers/facilities.jsx (.../facilities.jsx) (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -18,15 +18,19 @@
export const facilitiesStockOnHandSlice = createSlice({
name: 'facilitiesStockOnHand',
initialState: {
- userHomeFacilityStockOnHand: null
+ userHomeFacilityStockOnHand: null,
+ supervisedFacilitiesStockOnHand: []
},
reducers: {
setUserHomeFacilityStockOnHand: (state, action) => {
state.userHomeFacilityStockOnHand = action.payload;
+ },
+ setSupervisedFacilitiesStockOnHand: (state, action) => {
+ state.supervisedFacilitiesStockOnHand = action.payload;
}
}
});
-export const {setUserHomeFacilityStockOnHand} = facilitiesStockOnHandSlice.actions;
+export const {setUserHomeFacilityStockOnHand, setSupervisedFacilitiesStockOnHand} = facilitiesStockOnHandSlice.actions;
export default facilitiesStockOnHandSlice.reducer;
Index: src/stock-on-hand-mobile/reducers/programs.jsx
===================================================================
diff -u -N
--- src/stock-on-hand-mobile/reducers/programs.jsx (revision 0)
+++ src/stock-on-hand-mobile/reducers/programs.jsx (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -0,0 +1,36 @@
+/*
+ * This program is part of the OpenLMIS logistics management information system platform software.
+ * Copyright © 2017 VillageReach
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Affero General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details. You should have received a copy of
+ * the GNU Affero General Public License along with this program. If not, see
+ * http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org.
+ */
+
+import { createSlice } from '@reduxjs/toolkit';
+
+export const programsStockOnHandSlice = createSlice({
+ name: 'programsStockOnHand',
+ initialState: {
+ programsStockOnHand: [],
+ supervisedProgramsStockOnHand: []
+ },
+ reducers: {
+ setProgramsStockOnHand: (state, action) => {
+ state.programsStockOnHand = action.payload;
+ },
+ setSupervisedProgramsStockOnHand: (state, action) => {
+ state.supervisedProgramsStockOnHand = action.payload;
+ }
+ }
+});
+
+export const {setProgramsStockOnHand, setSupervisedProgramsStockOnHand} = programsStockOnHandSlice.actions;
+
+export default programsStockOnHandSlice.reducer;
Index: src/stock-on-hand-mobile/stock-on-hand-app.jsx
===================================================================
diff -u -N -rd337a0ac836544c6a7201f924878e2fd5f88370e -r7f9ffae3458e2f9994249398a1d526a3cbe861d6
--- src/stock-on-hand-mobile/stock-on-hand-app.jsx (.../stock-on-hand-app.jsx) (revision d337a0ac836544c6a7201f924878e2fd5f88370e)
+++ src/stock-on-hand-mobile/stock-on-hand-app.jsx (.../stock-on-hand-app.jsx) (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -13,27 +13,129 @@
* http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org.
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
-import { setUserHomeFacilityStockOnHand } from './reducers/facilities';
+import { setSupervisedProgramsStockOnHand } from './reducers/programs';
+import { setUserHomeFacilityStockOnHand, setSupervisedFacilitiesStockOnHand } from './reducers/facilities';
import ProgramSelect from './pages/program-select';
const StockOnHandApp = ({
+ asynchronousService,
facilityFactory,
- offlineService
+ offlineService,
+ facilityService,
+ programService,
+ authorizationService,
+ currentUserService,
+ permissionService
}) => {
const dispatch = useDispatch();
- const userHomeFacility = useSelector(state => state[`facilitiesStockOnHand`][`userHomeFacilityStockOnHand`]);
+ const userHomeFacilityStore = useSelector(state => state[`facilitiesStockOnHand`][`userHomeFacilityStockOnHand`]);
- useEffect(() => facilityFactory.getUserHomeFacility().then(facility => dispatch(setUserHomeFacilityStockOnHand(facility))), [facilityFactory]);
+ const getUserPrograms = (isSupervised, userHomeFacility, permissions, programs) => {
- const menu = document.getElementsByClassName('header ng-scope')[0];
+ const programIds = [];
+ if (isSupervised) {
+ permissions.forEach((permission) => {
+ if (!userHomeFacility || (userHomeFacility && userHomeFacility.id !== permission.facilityId)) {
+ programIds.push(permission.programId);
+ }
+ });
+ } else {
+ if (!userHomeFacility) {
+ return [];
+ }
- useEffect(() => menu.style.display = '', [menu]);
+ permissions.forEach((permission) => {
+ if (userHomeFacility.id === permission.facilityId) {
+ programIds.push(permission.programId);
+ }
+ });
+ }
+ const result = [];
+ programs.forEach((program) => {
+ if (programIds.indexOf(program.id) !== -1) {
+ result.push(program);
+ }
+ });
+
+ return result;
+ }
+
+ const getFacilityById = (facilities, id) => {
+ return facilities.filter(function(facility) {
+ return facility.id === id;
+ })[0];
+ }
+
+ const getSupervisedFacilities = (programId, permissions, facilities) => {
+ const facilityIds = [];
+ permissions.forEach(function(permission) {
+ if (programId === permission.programId) {
+ facilityIds.push(permission.facilityId);
+ }
+ });
+
+ const result = [];
+ facilities.forEach(function(facility) {
+ if (facilityIds.indexOf(facility.id) !== -1) {
+ result.push(facility);
+ }
+ });
+ return result;
+ }
+
+ const getSupervisedFacilitiesForAllPrograms = (programs, permissions, facilities) => {
+ const result = {};
+ programs.forEach((program) => {
+ result[program.id] = getSupervisedFacilities(program.id, permissions, facilities);
+ });
+
+ return result;
+ }
+
+ const dispatchData = (actions) => {
+ actions.forEach((action) => {
+ dispatch(action);
+ })
+ }
+
+ useEffect(() => {
+ facilityFactory.getUserHomeFacility().then((facility) => {
+ const userId = authorizationService.getUser().user_id;
+ asynchronousService.all([
+ facilityService.getAllMinimal(),
+ programService.getUserPrograms(userId),
+ permissionService.load(userId),
+ currentUserService.getUserInfo()
+ ])
+ .then(function(responses) {
+ const [facilities, programs, permissions, currentUserDetails] = responses;
+
+ const homeFacility = getFacilityById(facilities, currentUserDetails.homeFacilityId);
+ const supervisedPrograms = getUserPrograms(true, homeFacility, permissions, programs);
+ const supervisedFacilities = getSupervisedFacilitiesForAllPrograms(programs, permissions, facilities);
+
+ dispatchData([
+ setUserHomeFacilityStockOnHand(facility),
+ setSupervisedProgramsStockOnHand(supervisedPrograms),
+ setSupervisedFacilitiesStockOnHand(supervisedFacilities)
+ ]);
+
+ }).catch((error) => {
+ console.log(error);
+ });
+ });
+ } ,[facilityFactory]);
+
+ const menu = document.getElementsByClassName("header ng-scope")[0];
+
+ useEffect(() => menu.style.display = "", [menu]);
+
return (
{
- userHomeFacility
- &&
}
Index: src/stock-on-hand-mobile/stock-on-hand.wrapper.jsx
===================================================================
diff -u -N -rd337a0ac836544c6a7201f924878e2fd5f88370e -r7f9ffae3458e2f9994249398a1d526a3cbe861d6
--- src/stock-on-hand-mobile/stock-on-hand.wrapper.jsx (.../stock-on-hand.wrapper.jsx) (revision d337a0ac836544c6a7201f924878e2fd5f88370e)
+++ src/stock-on-hand-mobile/stock-on-hand.wrapper.jsx (.../stock-on-hand.wrapper.jsx) (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -27,9 +27,12 @@
.module('stock-on-hand-mobile')
.directive('stockOnHandMobile', stockOnHandMobile);
- stockOnHandMobile.$inject = ['facilityFactory', 'offlineService'];
+ stockOnHandMobile.$inject = ['$q', 'facilityFactory', 'offlineService', 'facilityService', 'programService',
+ 'authorizationService', 'currentUserService', 'permissionService'];
- function stockOnHandMobile(facilityFactory, offlineService) {
+ function stockOnHandMobile($q, facilityFactory, offlineService, facilityService,
+ programService, authorizationService, currentUserService, permissionService) {
+
return {
template: '',
replace: true,
@@ -42,8 +45,14 @@
,
app
Index: src/stock-on-hand-mobile/store.jsx
===================================================================
diff -u -N -rb8425eb5ba768726641220a0e19de688e0bcc639 -r7f9ffae3458e2f9994249398a1d526a3cbe861d6
--- src/stock-on-hand-mobile/store.jsx (.../store.jsx) (revision b8425eb5ba768726641220a0e19de688e0bcc639)
+++ src/stock-on-hand-mobile/store.jsx (.../store.jsx) (revision 7f9ffae3458e2f9994249398a1d526a3cbe861d6)
@@ -14,11 +14,13 @@
*/
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
-import facilitiesStockOnHandReducer from "./reducers/facilities";
+import facilitiesStockOnHandReducer from './reducers/facilities';
+import programsStockOnHandReducer from './reducers/programs';
const store = configureStore({
reducer: {
- facilitiesStockOnHand: facilitiesStockOnHandReducer
+ facilitiesStockOnHand: facilitiesStockOnHandReducer,
+ programsStockOnHand: programsStockOnHandReducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false