Last active
February 21, 2020 01:39
-
-
Save johnpaulmanoza/d9846d0f6a0ca4742ae5ca962f74db40 to your computer and use it in GitHub Desktop.
features/resto/resto-stats
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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