Skip to content

Instantly share code, notes, and snippets.

@johnpaulmanoza
Last active February 21, 2020 01:39
Show Gist options
  • Save johnpaulmanoza/d9846d0f6a0ca4742ae5ca962f74db40 to your computer and use it in GitHub Desktop.
Save johnpaulmanoza/d9846d0f6a0ca4742ae5ca962f74db40 to your computer and use it in GitHub Desktop.
features/resto/resto-stats
/*
* Created by John Paul Manoza on Fri Nov 22 2019
*
* Copyright (c) 2019 _______. All rights reserved.
*/
import moment from 'moment';
import { AsyncStorage } from 'react-native';
// this is a custom sdk I created that is available in NPM, I can't give the name of the package.
import { loadRestoOverview, loadBranchOverview } from '________';
import { actSetDateFilter } from './../../resto-stats-date/actions/index';
export function actIsLoading(isLoading) {
return { type: 'RESTO_STAT_ISLOADING', payload: { isLoading } };
}
export function actIsOverviewLoading(isLoading) {
return { type: 'RESTO_STAT_OVERVIEW_ISLOADING', payload: { isLoading } };
}
export function actIsSuccess(data, isPrefetchGraph = false) {
return { type: 'RESTO_STAT_SUCCESS', payload: { data, isPrefetchGraph } };
}
export function actIsOverviewSuccess(overview, isPrefetchOverview = false) {
return { type: 'RESTO_STAT_OVERVIEW_SUCCESS', payload: { overview, isPrefetchOverview } };
}
export function actIsFailed(error) {
return { type: 'RESTO_STAT_FAILED', payload: { error } };
}
export function actIsFailedReset() {
return { type: 'RESTO_STAT_FAILED_RESET' };
}
export function actSetData(payload) {
return { type: 'RESTO_STAT_SET_DATA', payload };
}
export function actSetGraphMode(tabIndex, index) {
return { type: 'RESTO_STAT_TAB_CHANGE', payload: { tabIndex, index } };
}
export function actForceReload() {
return { type: 'RESTO_STAT_FORCE_RELOAD' };
}
/**
* Load Reservation Summary Required props
*/
export function actLoadOverview() {
return async (dispatch) => {
const userInfo = await AsyncStorage.getItem('userInfo');
const userRole = (JSON.parse(userInfo) || {}).role;
const payload = { userRole };
// set default data
dispatch(actSetData(payload));
// make sure to set a default date filter
dispatch(actSetDateFilter());
}
}
/**
* Load Resto Overview
* @param {String} restoId
* @param {String} branchId
* @param {String} start
* @param {String} end
*/
export function actLoadRestoOverview(restoId, branchId, start, end) {
return async (dispatch) => {
dispatch(actIsOverviewLoading(true));
try {
let startDate = start; let endDate = end;
let isPrefetchOverview = false;
// set a default mode (7 days) filter
if (startDate == undefined && endDate == undefined) {
startDate = moment().subtract(7, 'days').format('YYYY-MM-DD HH:mm:ss');
endDate = moment().format('YYYY-MM-DD HH:mm:ss');
isPrefetchOverview = true;
}
const response = await loadRestoOverview(restoId, branchId, startDate, endDate);
dispatch(actIsLoading(false));
if (response.error || response.message) {
const error = response.message || (response.error || {}).message;
dispatch(actIsFailed(error));
setTimeout(() => dispatch(actIsFailedReset()), 500);
} else if (response.average_advance_booking_time) {
dispatch(actIsOverviewSuccess(response, isPrefetchOverview));
}
} catch (error) {
if (error) {
dispatch(actIsFailed(error));
setTimeout(() => dispatch(actIsFailedReset()), 500);
}
}
}
}
/**
* Load Branch Overview
* @param {String} branchId
* @param {String} restoId
* @param {String} start
* @param {String} end
*/
export function actLoadBranchOverview(branchId, restoId, start, end) {
return async (dispatch) => {
dispatch(actIsLoading(true));
try {
let startDate = start; let endDate = end;
let isPrefetchGraph = false;
// set a default mode (7 days) filter
if (startDate == undefined && endDate == undefined) {
startDate = moment().subtract(7, 'days').format('YYYY-MM-DD HH:mm:ss');
endDate = moment().format('YYYY-MM-DD HH:mm:ss');
isPrefetchGraph = true;
}
const response = await loadBranchOverview(branchId, restoId, startDate, endDate);
dispatch(actIsLoading(false));
if (response.error || response.message) {
const error = response.message || (response.error || {}).message;
dispatch(actIsFailed(error));
setTimeout(() => dispatch(actIsFailedReset()), 500);
} else if (response.data) {
dispatch(actIsSuccess(response.data, isPrefetchGraph));
}
} catch (error) {
if (error) {
dispatch(actIsFailed(error));
setTimeout(() => dispatch(actIsFailedReset()), 500);
}
}
}
}
/*
* Created by John Paul Manoza on Fri Nov 22 2019
*
* Copyright (c) 2019 _______ Inc. All rights reserved.
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { View, Text, RefreshControl, ScrollView } from 'react-native';
import { IconButton } from 'react-native-paper';
import RestoStatsHeader from './header';
import RestoStatsOverview from './overview';
import RestoStatsGraph from './stats';
import RestoStatsUsers from './users';
import { style } from './style';
import { getActiveBranch } from './../../../settings/branch-picker/reducers/index';
import { actLoadOverview, actLoadRestoOverview, actLoadBranchOverview, actForceReload } from '../actions/index';
class RestoStatsComponent extends Component {
static navigationOptions = ({ navigation }) => {
return {
title: 'Restaurant Stats',
headerRight: (
// To avoid warning about undefined button props
<If condition={navigation.getParam('doSendViaEmail')}>
<IconButton icon="send" size={20} color="white" onPress={navigation.getParam('doSendViaEmail')} />
</If>
)
}
};
constructor(props) {
super(props); this.state = { refreshing: false };
this.loadBranchOverview = this.loadBranchOverview.bind(this);
this.onRefresh = this.onRefresh.bind(this);
this.doSendViaEmail = this.doSendViaEmail.bind(this);
// send the nav action to navigation
this.props.navigation.setParams({ doSendViaEmail: this.doSendViaEmail });
}
componentWillReceiveProps(nextProps) {
if (nextProps.isLoading && nextProps.isLoading == false) {
this.setState({ refreshing: false });
}
// TODO: Move this logic to getDerivedStateFromProps on Rct17
const shouldRequest =
// On initial load
nextProps.canStartFetch && nextProps.dateFilter
// if branch is changed
|| (nextProps.branchId != this.props.branchId) && nextProps.dateFilter;
if (shouldRequest) {
const { branchId, restoId } = nextProps;
const { start, end } = nextProps.dateFilter;
this.loadBranchOverview(branchId, restoId, start, end);
this.loadRestoOverview(restoId, branchId, start, end);
}
}
componentDidMount() {
this.props.doLoadOverview();
}
render() {
const { refreshing } = this.state;
return (
<ScrollView style={style.container}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} />
}>
<RestoStatsHeader {...this.props} />
<RestoStatsOverview {...this.props} />
<RestoStatsGraph {...this.props} />
<RestoStatsUsers {...this.props} />
</ScrollView>
);
}
loadBranchOverview(branchId, restoId, start, end) {
this.props.doLoadBranchOverview(branchId, restoId, start, end);
}
loadRestoOverview(restoId, branchId, start, end) {
this.props.doLoadRestoOverview(restoId, branchId, start, end);
}
onRefresh() {
this.props.doForceReload();
}
doSendViaEmail() {
const { branchId, restoId } = this.props;
if (branchId != undefined && restoId != undefined) {
this.props.navigation.navigate('RestoStatsEmailComponent', { branchId, restoId });
}
}
}
const mapStateToProps = (state) => {
const branchLocation = 'restostat';
const userRole = state.restoStats.userRole;
const branch = getActiveBranch(state, branchLocation, userRole);
const _state = {
branchId: branch.id, branch: branch,
restoId: (branch.restaurant || {}).id,
dateFilter: state.restoStats.dateFilter,
canStartFetch: state.restoStats.canStartFetch,
data: state.restoStats.data || []
};
return _state;
};
const mapDispatchToProps = (dispatch) => {
const _action = {
dispatch,
doForceReload: (payload) => dispatch(actForceReload()),
doLoadOverview: () => dispatch(actLoadOverview()),
doLoadRestoOverview: (restoId, branchId, start, end) => dispatch(actLoadRestoOverview(restoId, branchId, start, end)),
doLoadBranchOverview: (branchId, restoId, start, end) => dispatch(actLoadBranchOverview(branchId, restoId, start, end)),
};
return _action;
};
export default connect(mapStateToProps, mapDispatchToProps)(RestoStatsComponent);
/*
* Created by John Paul Manoza on Fri Nov 22 2019
*
* Copyright (c) 2019 _______ All rights reserved.
*/
import moment from 'moment';
import _ from 'lodash';
import * as randomColor from 'randomcolor';
export default function restoStats(state: any = {}, action: any) {
switch (action.type) {
case 'RESTO_STAT_ISLOADING': {
let isLoading = action.payload.isLoading || false;
// OVERRIDE: Meaning there is a prefetch data
// so, don't show the loader while loading an updated data
if (state.isPrefetchGraph) {
isLoading = false;
}
// always reset the state of error and success
return {
...state, isLoading, success: undefined,
error: undefined, canStartFetch: undefined
};
}
case 'RESTO_STAT_SUCCESS': {
const { data, isPrefetchGraph } = action.payload;
const legends = [
'Most popular discount slot (Bookings)',
'Most popular discount slot (Diners)',
'Most popular day (Bookings)',
'Most popular day (Diners)',
'Most popular time (Bookings)',
'Most popular time (Diners)',
];
const colors = [
'#1C77B4', // blue
'#FF7F08', // orange
'#2A9F2A', // green
'#D62525', // red
'#9466BD', // violet
'#8C564B', // brown
'#E376C2', // pink
'#FFC108', // yellow
];
let graphData = [];
data.map((value, index) => {
if (value.columns) {
let graphDataItem = [];
const legend = legends[index] || '';
value.columns.map((item, idx) => {
// get values
const status = item[0] || 'OTHERS';
const itemVl = item[1] || 0;
const pColor = colors[idx] || randomColor.randomColor();
const color = value.colors[status] || pColor;
// if graph has no data set, don't add items
if (status != 'No Data Set') {
const graphItem = {
name: status.toUpperCase(),
// IMPORTANT: needed 0.0001 to re-render graph
status: itemVl != 0 ? itemVl : 0.0001,
color,
legendFontColor: 'black',
legendFontSize: 10,
legendDesc: legend
};
graphDataItem.push(graphItem);
// If item only has 1 item, the graph is not rendering
// This is a fix to make the graph re-render
// add a fake item that has 0% and is not visible label
if (value.columns.length == 1) {
const defaultItem = {
name: '',
// IMPORTANT: needed 0.0001 to display 100% item
status: 0.0001,
color: 'white',
legendFontColor: 'white',
legendFontSize: 10,
legendDesc: ''
};
graphDataItem.push(defaultItem);
}
}
});
graphData.push({ legend, item: graphDataItem, visible: index % 2 == 0 });
}
});
graphData
return {
...state, data: graphData, isPrefetchGraph
}
}
case 'RESTO_STAT_FORCE_RELOAD': {
return {
...state, canStartFetch: true
}
}
case 'RESTO_STAT_TAB_CHANGE': {
const { index, tabIndex } = action.payload;
let data = state.data;
let changeIdx = undefined;
if (index % 2 == 0 && tabIndex == 1) {
changeIdx = index + 1;
} else {
changeIdx = index - 1;
}
if (data[changeIdx] != undefined) {
let visibleItem = data[changeIdx]; let hiddenItem = data[index];
visibleItem.visible = true; hiddenItem.visible = false;
}
return {
...state, data, updatedIndex: index
}
}
case 'RESTO_STAT_OVERVIEW_ISLOADING': {
let isLoading = action.payload.isLoading || false;
// OVERRIDE: Meaning there is a prefetch data
// so, don't show the loader while loading an updated data
if (state.isPrefetchOverview) {
isLoading = false;
}
return {
...state, overviewIsLoading: isLoading
}
}
case 'RESTO_STAT_OVERVIEW_SUCCESS': {
let { overview, isPrefetchOverview } = action.payload;
if (overview.most_popular_discount) {
const discount = overview.most_popular_discount;
if (discount == 241) {
overview.most_popular_discount_desc = '2 For 1';
} else {
overview.most_popular_discount_desc = discount + '%';
}
}
return {
...state, overview, isPrefetchOverview, overviewIsLoading: undefined
}
}
case 'RESTO_STAT_FAILED': {
const error = action.payload.error || 'Failed Loading Data';
return {
...state, isLoading: false, success: false, error
};
}
case 'RESTO_STAT_FAILED_RESET': {
return {
...state, isLoading: false, success: false, error: undefined
};
}
case 'RESTO_STAT_SET_DATE_FILTER_CUSTOM': {
const { type, date } = action.payload;
let filter = state.customFilter || {};
if (type == 'start') {
filter.customStart = date;
filter.customStartFmt = moment(date).format('DD MMM YYYY');
} else {
filter.customEnd = date;
filter.customEndFmt = moment(date).format('DD MMM YYYY');
}
return {
...state, customFilter: filter
}
}
case 'RESTO_STAT_SET_DATE_FILTER': {
const { start, end } = action.payload;
const mode = action.payload.mode || 'Last 7 days';
let startDate; let endDate;
if (mode == 'Last 7 days') {
startDate = moment().subtract(7, 'days');
endDate = moment();
} else if (mode == 'Last 30 days') {
startDate = moment().subtract(30, 'days');
endDate = moment();
} else if (mode == 'Last 90 days') {
startDate = moment().subtract(90, 'days');
endDate = moment();
} else if (mode == 'Last week') {
startDate = moment().startOf('week').isoWeekday('Monday');
endDate = moment().endOf('week').subtract(1, 'days').isoWeekday('Monday');
} else if (mode == 'Last month') {
startDate = moment().startOf('month').subtract(1, 'months');
endDate = moment().subtract(1, 'months').endOf('month');
} else if (mode == 'Custom') {
startDate = moment(start); endDate = moment(end);
}
const startDateFmt = startDate.format('DD MMM');
const endDateFmt = endDate.format('DD MMM');
const startDateObj = startDate.format('YYYY-MM-DD HH:mm:ss');
const endDateObj = endDate.format('YYYY-MM-DD HH:mm:ss');
let dateFilter = {
dateDesc: `${mode} (${startDateFmt} - ${endDateFmt})`,
start: startDateObj, end: endDateObj,
startObj: start, endObj: end
}
// IMPORTANT: needed to start the request, make sure to clean while loading
let canStartFetch = true;
// Do not force to call request if there is a prev date filter
if (action.payload.mode == undefined && state.dateFilter) {
if (state.dateFilter.start != undefined) {
canStartFetch = false; dateFilter = state.dateFilter;
}
}
return {
...state, dateFilter, canStartFetch,
};
}
case 'RESTO_STAT_SET_DATA': {
const { userRole } = action.payload;
return {
...state, userRole
};
}
default: {
return state;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment