Skip to content

Commit f669b96

Browse files
committed
#885 | Code changes to show Glific messages for Subject
1 parent f9e22f0 commit f669b96

File tree

7 files changed

+494
-1
lines changed

7 files changed

+494
-1
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import UserInfoService from "../../service/UserInfoService";
2+
import {orderBy, size} from 'lodash';
3+
4+
class GlificActions {
5+
6+
static TAB_TYPE_SENT_MSGS = "Sent Messages";
7+
static TAB_TYPE_SCHEDULED_MSGS = "Scheduled Messages";
8+
9+
static getInitialState(context) {
10+
return {
11+
showSpinnerWhileLoadingSentMessages: true,
12+
showSpinnerWhileLoadingScheduledMessages: true,
13+
tabType: "Sent Messages",
14+
sentMessages: [],
15+
scheduledMessages: [],
16+
userInfo: {},
17+
individualUUID: null,
18+
failedToFetchSentMessages: false,
19+
setMsgsSentAvailable: false,
20+
failedToFetchScheduledMessages: false,
21+
setMsgsScheduledAvailable: false,
22+
};
23+
}
24+
25+
static onLoadScheduledAndSentMsgs(state, action, context) {
26+
const {newState, individualUUID} = action;
27+
newState.userInfo = context.get(UserInfoService).getUserInfo();
28+
newState.individualUUID = individualUUID;
29+
return newState;
30+
}
31+
32+
33+
static onSentMsgsClick(state, action, context) {
34+
const newState = {...state};
35+
newState.tabType = "Sent Messages";
36+
return newState;
37+
}
38+
39+
static onScheduledMsgsClick = (state, action, context) => {
40+
const newState = {...state};
41+
newState.tabType = "Scheduled Messages";
42+
return newState;
43+
}
44+
45+
static onFetchOfSentMsgs(state, action, context) {
46+
const newState = {...state};
47+
newState.sentMessages = action.sentMessages;
48+
newState.failedToFetchSentMessages = action.failedToFetchSentMessages;
49+
newState.setMsgsSentAvailable = (size(newState.sentMessages) > 0);
50+
newState.showSpinnerWhileLoadingSentMessages = false;
51+
newState.sentMessages = orderBy(newState.sentMessages, "insertedAt", "desc");
52+
return newState;
53+
}
54+
55+
static onFetchOfScheduledMsgs(state, action, context) {
56+
const newState = {...state};
57+
newState.scheduledMessages = action.scheduledMessages;
58+
newState.failedToFetchScheduledMessages = action.failedToFetchScheduledMessages;
59+
newState.setMsgsScheduledAvailable = (size(newState.scheduledMessages) > 0);
60+
newState.showSpinnerWhileLoadingScheduledMessages = false;
61+
newState.scheduledMessages = orderBy(newState.scheduledMessages, "insertedAt", "desc");
62+
return newState;
63+
}
64+
}
65+
66+
const ActionPrefix = 'Glific';
67+
68+
const GlificActionNames = {
69+
ON_LOAD_SCHEDULED_AND_SENT_MSGS: `${ActionPrefix}.ON_LOAD_SCHEDULED_AND_SENT_MSGS`,
70+
ON_SENT_MSGS_CLICK: `${ActionPrefix}.ON_SENT_MSGS_CLICK`,
71+
ON_SCHEDULED_MSGS_CLICK: `${ActionPrefix}.ON_SCHEDULED_MSGS_CLICK`,
72+
ON_FETCH_OF_SENT_MSGS: `${ActionPrefix}.ON_FETCH_OF_SENT_MSGS`,
73+
ON_FETCH_OF_SCHEDULED_MSGS: `${ActionPrefix}.ON_FETCH_OF_SCHEDULED_MSGS`,
74+
};
75+
76+
const GlificActionMap = new Map([
77+
[GlificActionNames.ON_LOAD_SCHEDULED_AND_SENT_MSGS, GlificActions.onLoadScheduledAndSentMsgs],
78+
[GlificActionNames.ON_SENT_MSGS_CLICK, GlificActions.onSentMsgsClick],
79+
[GlificActionNames.ON_SCHEDULED_MSGS_CLICK, GlificActions.onScheduledMsgsClick],
80+
[GlificActionNames.ON_FETCH_OF_SENT_MSGS, GlificActions.onFetchOfSentMsgs],
81+
[GlificActionNames.ON_FETCH_OF_SCHEDULED_MSGS, GlificActions.onFetchOfScheduledMsgs],
82+
]);
83+
84+
export {GlificActions, GlificActionNames, GlificActionMap}

packages/openchs-android/src/reducer/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {TaskActionMap, TaskActions} from "../action/task/TaskActions";
4545
import {TaskFilterActionMap, TaskFilterActions} from "../action/task/TaskFilterActions";
4646
import {TaskListActionMap, TaskListActions} from "../action/task/TaskListActions";
4747
import {ManualProgramEligibilityActionMap, ManualProgramEligibilityActions} from "../action/program/ManualProgramEligibilityActions";
48+
import {GlificActionMap, GlificActions} from '../action/glific/GlificActions';
4849

4950
export default class Reducers {
5051
static reducerKeys = {
@@ -95,6 +96,7 @@ export default class Reducers {
9596
taskFilter: "TaskFilter",
9697
taskList: "TaskList",
9798
manualProgramEligibility: "manualProgramEligibility",
99+
glific: "glific",
98100
};
99101

100102
static createReducers(beanStore) {
@@ -145,6 +147,7 @@ export default class Reducers {
145147
reducerMap[Reducers.reducerKeys.taskList] = Reducers._add(TaskListActionMap, TaskListActions, beanStore);
146148
reducerMap[Reducers.reducerKeys.taskFilter] = Reducers._add(TaskFilterActionMap, TaskFilterActions, beanStore);
147149
reducerMap[Reducers.reducerKeys.manualProgramEligibility] = Reducers._add(ManualProgramEligibilityActionMap, ManualProgramEligibilityActions, beanStore);
150+
reducerMap[Reducers.reducerKeys.glific] = Reducers._add(GlificActionMap, GlificActions, beanStore);
148151
return reducerMap;
149152
};
150153

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import BaseService from './BaseService';
2+
import Service from '../framework/bean/Service';
3+
import SettingsService from './SettingsService';
4+
import {getJSON} from '../framework/http/requests';
5+
6+
@Service("glificService")
7+
class GlificService extends BaseService {
8+
constructor(db, context) {
9+
super(db, context);
10+
}
11+
12+
init() {
13+
this.settingsService = this.getService(SettingsService);
14+
this.serverUrl = this.settingsService.getSettings().serverURL;
15+
}
16+
17+
getAllMessagesForSubject(individualUUID) {
18+
return getJSON(`${this.serverUrl}/web/contact/subject/${individualUUID}/msgs`);
19+
}
20+
21+
getAllMessagesForUser(userID) {
22+
return getJSON(`${this.serverUrl}/web/contact/user/${userID}/msgs`);
23+
}
24+
25+
getAllMessagesNotYetSentForSubject(individualUUID) {
26+
return getJSON(`${this.serverUrl}/web/message/subject/${individualUUID}/msgsNotYetSent`);
27+
}
28+
29+
getAllMessagesNotYetSentForUser(userID) {
30+
return getJSON(`${this.serverUrl}/web/message/user/${userID}/msgsNotYetSent`);
31+
}
32+
}
33+
34+
export default GlificService;

packages/openchs-android/src/views/common/IndividualProfile.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
22
import {TouchableNativeFeedback, TouchableOpacity, View} from "react-native";
33
import React from "react";
44
import AbstractComponent from "../../framework/view/AbstractComponent";
5-
import {Icon, Text} from "native-base";
5+
import {Text} from "native-base";
66
import {Actions} from "../../action/individual/IndividualProfileActions";
77
import Reducers from "../../reducer";
88
import Colors from "../primitives/Colors";
@@ -28,6 +28,7 @@ import SubjectProfilePicture from "./SubjectProfilePicture";
2828
import PhoneCall from "../../model/PhoneCall";
2929
import CustomActivityIndicator from "../CustomActivityIndicator";
3030
import AvniIcon from "../common/AvniIcon";
31+
import GlificScheduledAndSentMsgsView from '../glific/GlificScheduledAndSentMsgsView';
3132

3233
class IndividualProfile extends AbstractComponent {
3334
static propTypes = {
@@ -70,6 +71,30 @@ class IndividualProfile extends AbstractComponent {
7071
}
7172
}
7273

74+
renderWhatsappButton(individualUUID) {
75+
const number = this.getMobileNoFromObservation();
76+
const {enableMessaging} = this.getService(OrganisationConfigService).getSettings();
77+
78+
if (number && enableMessaging) {
79+
return (<View>
80+
<TouchableNativeFeedback onPress={() => this.showWhatsappMessages(individualUUID)}>
81+
<View>
82+
<AvniIcon type="MaterialCommunityIcons" name="whatsapp"
83+
style={{fontSize: 30}} color={Colors.TextOnPrimaryColor}/>
84+
</View>
85+
</TouchableNativeFeedback>
86+
</View>);
87+
} else {
88+
return (
89+
<View/>
90+
);
91+
}
92+
}
93+
94+
showWhatsappMessages(individualUUID) {
95+
TypedTransition.from(this).with({individualUUID}).to(GlificScheduledAndSentMsgsView, true);
96+
}
97+
7398
makeCall(number) {
7499
PhoneCall.makeCall(number, this,
75100
(displayProgressIndicator) => this.dispatchAction(Actions.TOGGLE_PROGRESS_INDICATOR, {displayProgressIndicator}));
@@ -245,6 +270,7 @@ class IndividualProfile extends AbstractComponent {
245270
<View style={{flexDirection: 'column'}}>
246271
{this.renderCommentIcon()}
247272
{this.renderCallButton()}
273+
{this.renderWhatsappButton(this.props.individual.uuid)}
248274
</View>
249275
</View>
250276
<View
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import AbstractComponent from '../../framework/view/AbstractComponent';
2+
import PropTypes from 'prop-types';
3+
import {SectionList, StyleSheet, View} from 'react-native';
4+
import Colors from '../primitives/Colors';
5+
import React from 'react';
6+
import Distances from '../primitives/Distances';
7+
import {Text} from 'native-base';
8+
import Fonts from '../primitives/Fonts';
9+
import General from '../../utility/General';
10+
import _ from 'lodash';
11+
12+
class GlificMessagesTab extends AbstractComponent {
13+
static propTypes = {
14+
dataLoaded: PropTypes.bool.isRequired,
15+
failedToFetchMessages: PropTypes.bool.isRequired,
16+
msgList: PropTypes.array.isRequired,
17+
tabType: PropTypes.string.isRequired,
18+
};
19+
20+
constructor(props, context) {
21+
super(props, context);
22+
}
23+
24+
shouldComponentUpdate(nextProps, state) {
25+
return state === null || nextProps === null ||
26+
nextProps.failedToFetchMessages !== state.failedToFetchMessages ||
27+
nextProps.dataLoaded !== state.dataLoaded ||
28+
nextProps.tabType !== state.tabType ||
29+
nextProps.msgList !== state.msgList;
30+
}
31+
32+
renderMessageView(msg) {
33+
const primaryDate = msg.insertedAt;
34+
const msgBody = msg.body;
35+
return <View style={styles.container}>
36+
<View
37+
style={styles.message}>
38+
<Text style={{fontSize: Fonts.Medium, color: Colors.DefaultPrimaryColor,}}>{msgBody}</Text>
39+
<Text style={{fontSize: Fonts.Small, color: Colors.SecondaryText}}>{General.toDisplayTime(primaryDate)}</Text>
40+
</View>
41+
</View>;
42+
}
43+
44+
renderScheduledMessageView(msg) {
45+
const primaryDate = msg.scheduledDateTime;
46+
const messageRule = msg.messageRule.messageRule;
47+
const messageTemplateName = msg.messageRule.name;
48+
const paramsSearchResult = messageRule.match(/parameters: \[.*\]/);
49+
const msgBody = messageTemplateName + (paramsSearchResult ? '\' with ' + paramsSearchResult[0] : '\'');
50+
51+
return <View style={styles.container}>
52+
<View
53+
style={styles.message}>
54+
<Text style={{fontSize: Fonts.Medium, color: Colors.DefaultPrimaryColor,}}>Scheduled to send message using template '{msgBody}</Text>
55+
<Text style={{fontSize: Fonts.Small, color: Colors.SecondaryText}}>{General.toDisplayTime(primaryDate)}</Text>
56+
</View>
57+
</View>;
58+
}
59+
60+
renderSentMessagesList(tabTypeSentMessages) {
61+
const sectionWiseList = _
62+
.chain(this.props.msgList)
63+
.groupBy((msg) => General.toDisplayDate(tabTypeSentMessages ? msg.insertedAt : msg.scheduledDateTime))
64+
.map((value, key) => ({title: key, data: value}))
65+
.value();
66+
return (<SectionList
67+
contentContainerStyle={{
68+
margin: Distances.ScaledContentDistanceFromEdge
69+
}}
70+
inverted={true}
71+
sections={sectionWiseList}
72+
ListEmptyComponent={this.status(
73+
this.props.failedToFetchMessages ? "Could not retrieve messages" :
74+
tabTypeSentMessages ? "No Messages Sent" : "No Messages Scheduled", this.props.failedToFetchMessages)}
75+
renderSectionFooter={({section: {title}}) => (
76+
<View style={{flex: 1, flexDirection: 'row'}}>
77+
<View style={{flex: 1}}>
78+
</View>
79+
<Text style={styles.heading}>{title}</Text>
80+
<View style={{flex: 1}}>
81+
</View>
82+
</View>
83+
)}
84+
renderItem={(msg) =>
85+
tabTypeSentMessages ? this.renderMessageView(msg.item) : this.renderScheduledMessageView(msg.item)}
86+
keyExtractor={(item, index) => item + index}
87+
/>);
88+
}
89+
90+
status(text, isErrorMsg = false) {
91+
return (<View style={styles.errorScreen}>
92+
<Text style={[styles.statusMessage, isErrorMsg ? styles.errorMessageColor: {}]}>{this.I18n.t(text)}</Text>
93+
</View>);
94+
}
95+
96+
render() {
97+
const tabTypeSentMsgs = this.props.tabType === "Sent Messages";
98+
return (
99+
<View style={styles.screen}>
100+
{this.renderSentMessagesList(tabTypeSentMsgs)}
101+
</View>
102+
);
103+
}
104+
105+
}
106+
107+
const styles = StyleSheet.create({
108+
container: {
109+
padding: Distances.ScaledContentDistanceFromEdge,
110+
margin: 4,
111+
elevation: 2,
112+
backgroundColor: Colors.cardBackgroundColor,
113+
marginVertical: 3,
114+
borderRadius: 10,
115+
display: 'flex',
116+
width: 'auto',
117+
maxWidth: '80%',
118+
alignSelf: 'flex-end',
119+
},
120+
errorScreen: {
121+
padding: Distances.ScaledContentDistanceFromEdge,
122+
marginVertical: "70%",
123+
display: 'flex',
124+
alignSelf: 'center',
125+
},
126+
message: {
127+
flexDirection: 'column',
128+
width: 'auto',
129+
justifyContent: 'space-around',
130+
alignItems: 'flex-end',
131+
flexWrap: 'wrap',
132+
fontSize: 20,
133+
},
134+
statusMessage: {
135+
alignItems: 'center',
136+
justifyContent: 'center',
137+
padding: Distances.ScaledContentDistanceFromEdge,
138+
margin: Distances.ScaledContentDistanceFromEdge,
139+
alignSelf: 'center',
140+
fontSize: Fonts.Large,
141+
color: Colors.DefaultPrimaryColor,
142+
textAlign: 'center',
143+
},
144+
errorMessageColor: {
145+
color: Colors.RejectionMessageColor,
146+
},
147+
screen: {
148+
flex: 1,
149+
backgroundColor: Colors.ChatBackground,
150+
paddingTop: 10,
151+
paddingBottom: 10
152+
},
153+
heading: {
154+
borderRadius: 10,
155+
marginVertical: 3,
156+
alignItems: 'center',
157+
justifyContent: 'center',
158+
padding: 2,
159+
fontSize: Fonts.Large,
160+
textAlign: 'center',
161+
color: Colors.ChatSectionHeaderFontColor,
162+
backgroundColor: Colors.ChatSectionHeaderBackground,
163+
}
164+
});
165+
166+
export default GlificMessagesTab;

0 commit comments

Comments
 (0)