Skip to content

Commit

Permalink
Merge pull request #1313 from openmsupply/#507-badge-highlight-number…
Browse files Browse the repository at this point in the history
…-with-multi-row

#507 Badge highlight number with multi-row
  • Loading branch information
josh-griffin authored Nov 4, 2019
2 parents 0ab43ab + a6fbf84 commit b0fecd2
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 50 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@
"react-native-database": "^0.3.3",
"react-native-device-info": "4.0.1",
"react-native-fs": "2.14.1",
"react-native-generic-table-page": "^0.4.1",
"react-native-gesture-handler": "1.4.1",
"react-native-localization": "2.1.6",
"react-native-modalbox": "^2.0.0",
"react-native-popover-view": "^2.0.5",
"react-native-screens": "1.0.0-alpha.22",
"react-native-ui-components": "^0.5.0",
"react-native-vector-icons": "6.6.0",
Expand Down
6 changes: 6 additions & 0 deletions src/localization/generalStrings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export const generalStrings = new LocalizedStrings({
submitted: 'Submitted',
stocktake: 'Stocktake',
start_typing_to_search: 'Start typing to search',
unfinalised: 'Un-finalised',
customerRequisitions: 'Customer requisitions',
supplierRequisitions: 'Supplier requisitions',
supplierInvoices: ' Supplier invoices',
customerInvoices: 'Customer invoices',
stocktakes: 'Stocktakes',
not_available: 'N/A',
},
fr: {
Expand Down
17 changes: 15 additions & 2 deletions src/mSupplyMobileApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class MSupplyMobileAppContainer extends React.Component {
this.scheduler.schedule(() => {
const { currentUser } = this.state;
if (currentUser !== null) {
// Only reauthenticate if currently logged in.
// Only re-authenticate if currently logged in.
this.userAuthenticator.reauthenticate(this.onAuthentication);
}
}, AUTHENTICATION_INTERVAL);
Expand Down Expand Up @@ -92,6 +92,18 @@ class MSupplyMobileAppContainer extends React.Component {
return navigationState.index !== 0;
};

// eslint-disable-next-line class-methods-use-this
getCurrentRouteName(navigationState) {
if (!navigationState) return null;

const route = navigationState.routes[navigationState.index];

// dive into nested navigators
if (route.routes) return getCurrentRouteName(route);

return route.routeName;
}

handleBackEvent = () => {
const { dispatch } = this.props;
const { confirmFinalise, syncModalIsOpen } = this.state;
Expand All @@ -109,7 +121,7 @@ class MSupplyMobileAppContainer extends React.Component {

runWithLoadingIndicator = async functionToRun => {
UIDatabase.isLoading = true;
// We here set up an asyncronous promise that will be resolved after a timeout
// We here set up an asynchronous promise that will be resolved after a timeout
// of 1 millisecond. This allows a fraction of a delay for the javascript thread
// to unblock and allow the spinner animation to start up. The |functionToRun| should
// not be run inside a |setTimeout| as that relegates to a lower priority, resulting
Expand Down Expand Up @@ -216,6 +228,7 @@ class MSupplyMobileAppContainer extends React.Component {
return (
<View style={globalStyles.appBackground}>
<NavigationBar
routeName={this.getCurrentRouteName(navigationState)}
onPressBack={this.getCanNavigateBack() ? this.handleBackEvent : null}
LeftComponent={this.getCanNavigateBack() ? this.renderPageTitle : null}
CentreComponent={this.renderLogo}
Expand Down
13 changes: 11 additions & 2 deletions src/navigation/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
* mSupply Mobile
* Sustainable Solutions (NZ) Ltd. 2019
*/
const routeList = {
customerRequisitions: 'ResponseRequisition',
supplierRequisitions: 'RequestRequisition',
supplierInvoices: 'SupplierInvoice',
stocktakes: 'Stocktake',
customerInvoices: 'CustomerInvoice',
};

export const getCurrentRouteName = state =>
const getCurrentRouteName = state =>
state.routes[state.index] ? state.routes[state.index].routeName : undefined;

export const getCurrentParams = state =>
const getCurrentParams = state =>
state.routes[state.index] ? state.routes[state.index].params : undefined;

export { routeList, getCurrentRouteName, getCurrentParams };
93 changes: 63 additions & 30 deletions src/pages/MenuPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { connect } from 'react-redux';
import { Image, StyleSheet, Text, View, ToastAndroid } from 'react-native';
import { Button } from 'react-native-ui-components';
import Icon from 'react-native-vector-icons/FontAwesome';
import { InfoBadge } from '../widgets';

import { navStrings } from '../localization';

import { SETTINGS_KEYS } from '../settings';

import globalStyles, { APP_FONT_FAMILY, SHADOW_BORDER, GREY, WARMER_GREY } from '../globalStyles';
Expand All @@ -31,9 +33,24 @@ const { SYNC_SITE_NAME } = SETTINGS_KEYS;
class Menu extends React.Component {
constructor(props) {
super(props);

this.databaseListenerId = null;
}

componentWillMount() {
const { database } = this.props;

this.databaseListenerId = database.addListener(
// Ensure that language changes in login modal are re-rendered onto the MenuPage.
(_, recordType) => recordType === 'Setting' && this.forceUpdate()
);
}

componentWillUnmount() {
const { database } = this.props;
database.removeListener(this.databaseListenerId);
}

exportData = async () => {
const { settings, database } = this.props;
const syncSiteName = settings.get(SYNC_SITE_NAME);
Expand Down Expand Up @@ -71,18 +88,25 @@ class Menu extends React.Component {
// eslint-disable-next-line global-require
source={require('../images/menu_people.png')}
/>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.customer_invoices}
onPress={navigateToCustomerInvoices}
/>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.customer_requisitions}
onPress={navigateToCustomerRequisitions}
/>
<InfoBadge routeName="customerInvoices" mainWrapperStyle={localStyles.InfoBadgeWrapper}>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.customer_invoices}
onPress={navigateToCustomerInvoices}
/>
</InfoBadge>
<InfoBadge
routeName="customerRequisitions"
mainWrapperStyle={localStyles.InfoBadgeWrapper}
>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.customer_requisitions}
onPress={navigateToCustomerRequisitions}
/>
</InfoBadge>
</View>

<View style={[localStyles.container, localStyles.centralContainer]}>
Expand All @@ -92,18 +116,25 @@ class Menu extends React.Component {
// eslint-disable-next-line global-require
source={require('../images/menu_truck.png')}
/>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.supplier_invoices}
onPress={navigateToSupplierInvoices}
/>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.supplier_requisitions}
onPress={navigateToSupplierRequisitions}
/>
<InfoBadge routeName="supplierInvoices" mainWrapperStyle={localStyles.InfoBadgeWrapper}>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.supplier_invoices}
onPress={navigateToSupplierInvoices}
/>
</InfoBadge>
<InfoBadge
routeName="supplierRequisitions"
mainWrapperStyle={localStyles.InfoBadgeWrapper}
>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.supplier_requisitions}
onPress={navigateToSupplierRequisitions}
/>
</InfoBadge>
{isInAdminMode && (
<Button
style={globalStyles.menuButton}
Expand All @@ -127,12 +158,14 @@ class Menu extends React.Component {
text={navStrings.current_stock}
onPress={navigateToStock}
/>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.stocktakes}
onPress={navigateToStocktakes}
/>
<InfoBadge routeName="stocktakes" mainWrapperStyle={localStyles.InfoBadgeWrapper}>
<Button
style={globalStyles.menuButton}
textStyle={globalStyles.menuButtonText}
text={navStrings.stocktakes}
onPress={navigateToStocktakes}
/>
</InfoBadge>
{isInAdminMode && (
<Button
style={globalStyles.menuButton}
Expand Down
18 changes: 18 additions & 0 deletions src/utilities/getBadgeData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { routeList } from '../navigation/selectors';
import { generalStrings } from '../localization';
import { UIDatabase } from '../database';

const getBadgeData = routeName => {
const dataType = routeName in routeList ? routeList[routeName] : '';

return [
{
count:
dataType !== '' ? UIDatabase.objects(dataType).filtered('status != "finalised"').length : 0,
title: `${generalStrings.unfinalised} ${generalStrings[routeName]}`,
},
];
};

export default getBadgeData;
export { getBadgeData };
1 change: 1 addition & 0 deletions src/utilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export {
} from './finalisation';

export { formatErrorItemNames } from './formatters';
export { renderNode } from './renderNode';
29 changes: 29 additions & 0 deletions src/utilities/renderNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Taken from https://github.com/react-native-training/react-native-elements
* since we only need badge component. Tweaked the component class since we do not
* need extra logic present in the code.
*/

import React from 'react';

const renderNode = (Component, content, defaultProps) => {
if (content == null || content === false) {
return null;
}
if (React.isValidElement(content)) {
return content;
}
if (typeof content === 'function') {
return content();
}
// Just in case
if (content === true) {
return <Component {...defaultProps} />;
}
if (typeof content === 'string' || typeof content === 'number') {
return <Component {...defaultProps}>{content}</Component>;
}
return <Component {...defaultProps} {...content} />;
};

// eslint-disable-next-line import/prefer-default-export
export { renderNode };
88 changes: 88 additions & 0 deletions src/widgets/Badge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* Taken from https://github.com/react-native-training/react-native-elements
* since we only need badge component. Tweaked the component class since we do not
* need extra logic present in the code.
*/

import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, TouchableOpacity, ViewPropTypes } from 'react-native';
import { renderNode } from '../utilities';

const Badge = props => {
const {
containerStyle,
textStyle,
badgeStyle,
onPress,
Component = onPress ? TouchableOpacity : View,
value,
...attributes
} = props;

const element = renderNode(Text, value, {
style: StyleSheet.flatten([styles.text, textStyle && textStyle]),
});

return (
<View style={StyleSheet.flatten([containerStyle && containerStyle])}>
<Component
{...attributes}
style={StyleSheet.flatten([
styles.badge(),
!element && styles.miniBadge,
badgeStyle && badgeStyle,
])}
onPress={onPress}
>
{element}
</Component>
</View>
);
};

/* eslint-disable react/require-default-props */
Badge.propTypes = {
containerStyle: ViewPropTypes.style,
badgeStyle: ViewPropTypes.style,
textStyle: Text.propTypes.style,
value: PropTypes.node,
onPress: PropTypes.func,
Component: PropTypes.elementType,
};

Badge.defaultProps = {
containerStyle: {},
textStyle: {},
badgeStyle: {},
};

const size = 18;
const miniSize = 8;

const styles = {
badge: () => ({
alignSelf: 'center',
minWidth: size,
height: size,
borderRadius: size / 2,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#FFF',
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#fff',
}),
miniBadge: {
paddingHorizontal: 0,
paddingVertical: 0,
minWidth: miniSize,
height: miniSize,
borderRadius: miniSize / 2,
},
text: {
fontSize: 12,
color: 'white',
paddingHorizontal: 4,
},
};

export default Badge;
Loading

0 comments on commit b0fecd2

Please sign in to comment.