diff --git a/Makefile b/Makefile index 6a8f07c17..43554155b 100644 --- a/Makefile +++ b/Makefile @@ -477,7 +477,6 @@ else $(call upload,platformTranslation,@packages/openchs-android/translations/ka_IN.json) endif - deploy_platform_translations_staging: make deploy_translations server=https://staging.avniproject.org port=443 username=admin password=$(OPENCHS_STAGING_ADMIN_PASSWORD) @@ -496,3 +495,6 @@ deploy_platform_translations_for_flavor_live: deploy_platform_translations_live_for_all_flavors: make deploy_platform_translations_for_flavor_live flavor='lfe' make deploy_platform_translations_for_flavor_live flavor='generic' + +deploy_platform_translations_local_live: + make deploy_translations server=http://localhost port=8021 username=$(username) password=$(password) diff --git a/packages/openchs-android/src/action/customDashboard/CustomDashboardActions.js b/packages/openchs-android/src/action/customDashboard/CustomDashboardActions.js index d5a7bda5b..dacc630c5 100644 --- a/packages/openchs-android/src/action/customDashboard/CustomDashboardActions.js +++ b/packages/openchs-android/src/action/customDashboard/CustomDashboardActions.js @@ -49,7 +49,7 @@ class CustomDashboardActions { loading: false, reportCardSectionMappings: [], cardToCountResultMap: {}, - countUpdateTime: null, + resultUpdatedAt: null, hasFilters: false, activeDashboardUUID: null, customDashboardFilters: [] @@ -108,7 +108,7 @@ class CustomDashboardActions { static setFilterApplied(state, action, context) { const customDashboardCacheService = context.get(CustomDashboardCacheService); customDashboardCacheService.clearResults(state.activeDashboardUUID); - return CustomDashboardActions.refreshCount(state, action, context); + return state; } static setFilterCleared(state, action, context) { @@ -132,7 +132,6 @@ class CustomDashboardActions { const newState = {...state}; newState.cardToCountResultMap = {}; - newState.countUpdateTime = new Date(); //Update this to ensure reportCard count change is reflected const ruleInputArray = context.get(DashboardFilterService).toRuleInputObjects(state.activeDashboardUUID, selectedFilterValues); reportCardSectionMappings.forEach(rcm => { @@ -168,7 +167,8 @@ class CustomDashboardActions { } General.logDebug('CustomDashboardActions', `${rcm.card.name} took ${new Date() - start} ms`); }); - customDashboardCacheService.setDashboardUpdateCompleted(state.activeDashboardUUID); + const {dashboardCache} = customDashboardCacheService.getDashboardCache(state.activeDashboardUUID); + newState.resultUpdatedAt = dashboardCache.updatedAt; return newState; } @@ -193,14 +193,33 @@ class CustomDashboardActions { } return state; } + + static forceRefresh(state, action, context) { + const customDashboardCacheService = context.get(CustomDashboardCacheService); + customDashboardCacheService.clearResults(state.activeDashboardUUID); + return state; + } + + static clearCounts(state, action, context) { + const newState = {...state}; + newState.cardToCountResultMap = {}; + return newState; + } } -// This is not a reducer, just a code reuse mechanism -export function performCustomDashboardActionAndRefresh(dispatcher, actionName, action) { - dispatcher.dispatchAction(actionName, action); +// These are not reducers, just a code reuse mechanism +export function performCustomDashboardActionAndRefresh(dispatcher, actionName, payload) { + dispatcher.dispatchAction(actionName, payload); setTimeout(() => dispatcher.dispatchAction(CustomDashboardActionNames.REFRESH_COUNT), 500); } +export function performCustomDashboardActionAndClearRefresh(dispatcher, actionName, payload) { + dispatcher.dispatchAction(actionName, payload); + dispatcher.dispatchAction(CustomDashboardActionNames.CLEAR_COUNTS, payload); + setTimeout(() => dispatcher.dispatchAction(CustomDashboardActionNames.REFRESH_COUNT), 500); +} + + const ActionPrefix = 'CustomDashboard'; const CustomDashboardActionNames = { @@ -212,7 +231,9 @@ const CustomDashboardActionNames = { SET_DASHBOARD_FILTERS: `${ActionPrefix}.SET_DASHBOARD_FILTERS`, FILTER_APPLIED: `${ActionPrefix}.FILTER_APPLIED`, FILTER_CLEARED: `${ActionPrefix}.FILTER_CLEARED`, - DISABLE_AUTO_REFRESH_VALUE_UPDATED: `${ActionPrefix}.DISABLE_AUTO_REFRESH_VALUE_UPDATED` + DISABLE_AUTO_REFRESH_VALUE_UPDATED: `${ActionPrefix}.DISABLE_AUTO_REFRESH_VALUE_UPDATED`, + FORCE_REFRESH: `${ActionPrefix}.FORCE_REFRESH`, + CLEAR_COUNTS: `${ActionPrefix}.CLEAR_COUNTS` }; const CustomDashboardActionMap = new Map([ @@ -224,7 +245,9 @@ const CustomDashboardActionMap = new Map([ [CustomDashboardActionNames.SET_DASHBOARD_FILTERS, CustomDashboardActions.setCustomDashboardFilters], [CustomDashboardActionNames.FILTER_APPLIED, CustomDashboardActions.setFilterApplied], [CustomDashboardActionNames.FILTER_CLEARED, CustomDashboardActions.setFilterCleared], - [CustomDashboardActionNames.DISABLE_AUTO_REFRESH_VALUE_UPDATED, CustomDashboardActions.disableAutoRefreshValueUpdated] + [CustomDashboardActionNames.DISABLE_AUTO_REFRESH_VALUE_UPDATED, CustomDashboardActions.disableAutoRefreshValueUpdated], + [CustomDashboardActionNames.FORCE_REFRESH, CustomDashboardActions.forceRefresh], + [CustomDashboardActionNames.CLEAR_COUNTS, CustomDashboardActions.clearCounts] ]); export {CustomDashboardActionNames, CustomDashboardActionMap, CustomDashboardActions} diff --git a/packages/openchs-android/src/service/CustomDashboardCacheService.js b/packages/openchs-android/src/service/CustomDashboardCacheService.js index b4477039b..b21fa36a2 100644 --- a/packages/openchs-android/src/service/CustomDashboardCacheService.js +++ b/packages/openchs-android/src/service/CustomDashboardCacheService.js @@ -1,9 +1,8 @@ import BaseService from "./BaseService"; import Service from "../framework/bean/Service"; -import {CustomDashboardCache, DashboardFilterConfig} from "openchs-models"; +import {CustomDashboardCache, Dashboard, DashboardFilterConfig, EncounterType, Program, Range, SubjectType} from "openchs-models"; import _ from "lodash"; import EntityService from "./EntityService"; -import {Dashboard, EncounterType, Range, Program, SubjectType} from "openchs-models"; import DashboardFilterService from "./reports/DashboardFilterService"; import General from "../utility/General"; import FormMetaDataSelection from "../model/FormMetaDataSelection"; @@ -138,6 +137,7 @@ class CustomDashboardCacheService extends BaseService { result.reportCard = reportCard.uuid; dashboardCache.nestedReportCardResults.push(result); }); + dashboardCache.updatedAt = new Date(); this.saveOrUpdate(dashboardCache); } @@ -145,6 +145,7 @@ class CustomDashboardCacheService extends BaseService { const dashboardCache = getDashboardCache(this, dashboardUUID); dashboardCache.reportCardResults = []; dashboardCache.nestedReportCardResults = []; + dashboardCache.updatedAt = null; this.saveOrUpdate(dashboardCache); } @@ -160,13 +161,8 @@ class CustomDashboardCacheService extends BaseService { _.remove(dashboardCache.reportCardResults, (x) => x.reportCard === reportCard.uuid && x.dashboard === dashboardCache.dashboard.uuid); reportCardResult.dashboard = dashboardUUID; reportCardResult.reportCard = reportCard.uuid; - dashboardCache.reportCardResults.push(reportCardResult); - this.saveOrUpdate(dashboardCache); - } - - setDashboardUpdateCompleted(dashboardUUID) { - const dashboardCache = getDashboardCache(this, dashboardUUID); dashboardCache.updatedAt = new Date(); + dashboardCache.reportCardResults.push(reportCardResult); this.saveOrUpdate(dashboardCache); } } diff --git a/packages/openchs-android/src/service/SyncService.js b/packages/openchs-android/src/service/SyncService.js index f872661a3..b43a36728 100644 --- a/packages/openchs-android/src/service/SyncService.js +++ b/packages/openchs-android/src/service/SyncService.js @@ -37,7 +37,10 @@ import AllSyncableEntityMetaData from "../model/AllSyncableEntityMetaData"; import {IndividualSearchActionNames as IndividualSearchActions} from '../action/individual/IndividualSearchActions'; import {LandingViewActionsNames as LandingViewActions} from '../action/LandingViewActions'; import {MyDashboardActionNames} from '../action/mydashboard/MyDashboardActions'; -import {CustomDashboardActionNames, performCustomDashboardActionAndRefresh} from '../action/customDashboard/CustomDashboardActions'; +import { + CustomDashboardActionNames, + performCustomDashboardActionAndClearRefresh, +} from '../action/customDashboard/CustomDashboardActions'; import LocalCacheService from "./LocalCacheService"; import CustomDashboardService, {CustomDashboardType} from './customDashboard/CustomDashboardService'; @@ -412,7 +415,7 @@ class SyncService extends BaseService { const customDashboardService = this.context.getService(CustomDashboardService); const renderCustomDashboard = customDashboardService.isCustomDashboardMarkedPrimary(); if (renderCustomDashboard) { - performCustomDashboardActionAndRefresh(this, CustomDashboardActionNames.ON_LOAD, {customDashboardType: CustomDashboardType.None}); + performCustomDashboardActionAndClearRefresh(this, CustomDashboardActionNames.ON_LOAD, {customDashboardType: CustomDashboardType.None}); } else { this.dispatchAction(MyDashboardActionNames.ON_LOAD); } diff --git a/packages/openchs-android/src/views/customDashboard/CustomDashboardView.js b/packages/openchs-android/src/views/customDashboard/CustomDashboardView.js index 9a2167932..9f6b49885 100644 --- a/packages/openchs-android/src/views/customDashboard/CustomDashboardView.js +++ b/packages/openchs-android/src/views/customDashboard/CustomDashboardView.js @@ -3,7 +3,11 @@ import CHSContainer from "../common/CHSContainer"; import AppHeader from "../common/AppHeader"; import React, {Fragment} from "react"; import Reducers from "../../reducer"; -import {CustomDashboardActionNames as Actions, performCustomDashboardActionAndRefresh} from "../../action/customDashboard/CustomDashboardActions"; +import { + CustomDashboardActionNames as Actions, + performCustomDashboardActionAndClearRefresh, + performCustomDashboardActionAndRefresh +} from "../../action/customDashboard/CustomDashboardActions"; import {SafeAreaView, ScrollView, StyleSheet, Text, TouchableNativeFeedback, View} from "react-native"; import _ from "lodash"; import CustomDashboardTab from "./CustomDashboardTab"; @@ -32,6 +36,7 @@ import MCIIcon from "react-native-vector-icons/MaterialCommunityIcons"; import Line from '../common/Line'; import {CardTileView} from './CardTileView'; import {CardListView} from './CardListView'; +import UserInfoService from "../../service/UserInfoService"; const viewNameMap = { 'ApprovalListingView': ApprovalListingView, @@ -41,15 +46,37 @@ const viewNameMap = { 'ChecklistListingView': ChecklistListingView }; +function RefreshSection({I18n, onRefreshPressed, lastUpdatedOn}) { + const refreshSectionStyle = { + paddingLeft: 15, + color: Styles.grey, + fontSize: 8, + fontWeight: 'bold', + textTransform: 'uppercase' + }; + return onRefreshPressed()}> + + {I18n.t('lastRefreshedMessage', {dateTime: General.formatDateTime(lastUpdatedOn)})} + + + ; +} + function SubHeader({I18n, onFilterPressed}) { const filterLabelStyle = { paddingLeft: 15, color: Styles.grey, fontSize: Styles.normalTextSize, fontWeight: 'bold', - textTransform: 'uppercase' + textTransform: 'uppercase', }; - return onFilterPressed()}> + return onFilterPressed()} style={{flex: 0.5}}> {section.viewType !== DashboardSection.viewTypeName.Default && this.renderSectionName(section.name, section.description, section.viewType, cards)} - + {_.map(cards, (card, index) => { return section.viewType === 'Tile' ? ); } - renderFilters() { - return this.state.filtersPresent && this.onFilterPressed()}/>; - } - onFilterPressed() { const {activeDashboardUUID} = this.state; TypedTransition.from(this) .with({ dashboardUUID: activeDashboardUUID, - onFilterChosen: (ruleInputArray) => this.dispatchAction(Actions.FILTER_APPLIED, {ruleInput: {ruleInputArray: ruleInputArray}, filterApplied: true}), + onFilterChosen: (ruleInputArray) => performCustomDashboardActionAndClearRefresh(this, Actions.FILTER_APPLIED, { + ruleInput: {ruleInputArray: ruleInputArray}, + filterApplied: true + }), loadFiltersData: (filters) => this.dispatchAction(Actions.SET_DASHBOARD_FILTERS, {customDashboardFilters: filters, filterApplied: true}), }).to(FiltersViewV2, true); } render() { + const settings = this.getService(UserInfoService).getUserSettingsObject(); General.logDebug("CustomDashboardView", "render"); const {hideBackButton, startSync, renderSync, icon, customDashboardType, onSearch, showSearch} = this.props; const title = this.props.title || 'dashboards'; @@ -271,7 +298,13 @@ class CustomDashboardView extends AbstractComponent { {this.renderZeroResultsMessageIfNeeded()} } - {this.renderFilters()} + + {settings.autoRefreshDisabled && !_.isNil(this.state.resultUpdatedAt) && + performCustomDashboardActionAndClearRefresh(this, Actions.FORCE_REFRESH)} + lastUpdatedOn={this.state.resultUpdatedAt}/>} + {this.state.filtersPresent && this.onFilterPressed()}/>} + {hasFilters && @@ -317,7 +350,7 @@ const styles = StyleSheet.create({ justifyContent: 'flex-start', }, listContainer: { - marginTop: 16 + marginTop: 16 } }); diff --git a/packages/openchs-android/translations/en.json b/packages/openchs-android/translations/en.json index f089b5806..ba442070b 100644 --- a/packages/openchs-android/translations/en.json +++ b/packages/openchs-android/translations/en.json @@ -501,6 +501,7 @@ "addressFilterImplicitBehaviorHint": "(Selection includes lower levels implicitly)", "copyErrorTryAgain": "Copy Error & Try Again", "copyErrorAndCancel": "Copy Error & Cancel", - "reportCopiedReportByPasting": "Report is copied. You can paste in a messaging app to report." + "reportCopiedReportByPasting": "Report is copied. You can paste in a messaging app to report.", + "lastRefreshedMessage": "Card numbers last updated on {{dateTime}" } }