@widergy/utilitygo-smart-bill-mobile 2.5.2 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/lib/components/SmartBillSummary/components/AIDrawer/index.js +10 -5
- package/src/lib/components/SmartBillSummary/index.js +48 -5
- package/src/lib/components/SmartBillSummary/tabs/Billing/components/RateCard/index.js +24 -4
- package/src/lib/components/SmartBillSummary/tabs/Billing/index.js +3 -1
- package/src/lib/components/SmartBillSummary/tabs/Glossary/index.js +28 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.6.0](https://github.com/widergy/UtilityGO-Smart-Bill-Mobile/compare/v2.5.2...v2.6.0) (2025-06-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* [CX-834, CX-852] smartbill analytics ([#41](https://github.com/widergy/UtilityGO-Smart-Bill-Mobile/issues/41)) ([3b02c37](https://github.com/widergy/UtilityGO-Smart-Bill-Mobile/commit/3b02c37355c36c4251ea1fbaab185d6336d78152))
|
|
7
|
+
|
|
1
8
|
## [2.5.2](https://github.com/widergy/UtilityGO-Smart-Bill-Mobile/compare/v2.5.1...v2.5.2) (2025-06-18)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ const AIDrawer = ({
|
|
|
17
17
|
components = {},
|
|
18
18
|
error,
|
|
19
19
|
getAnswer,
|
|
20
|
+
handlers,
|
|
20
21
|
isOpen,
|
|
21
22
|
loading,
|
|
22
23
|
notEnoughBillsErrorType,
|
|
@@ -24,17 +25,20 @@ const AIDrawer = ({
|
|
|
24
25
|
translations
|
|
25
26
|
}) => {
|
|
26
27
|
const styles = createStyles(colors);
|
|
28
|
+
const { trackOpenAIQuestion } = handlers;
|
|
27
29
|
const { CollapsibleCard, SkeletonLoader } = components;
|
|
28
30
|
const { generatedByLabel, title: drawerTitle } = translations?.AIDrawer || {};
|
|
29
31
|
const [openCards, setOpenCards] = useState([]);
|
|
30
32
|
|
|
31
|
-
const toggleCard = id => {
|
|
33
|
+
const toggleCard = (id, question) => {
|
|
32
34
|
if (!answer?.[id] && !openCards.includes(id)) {
|
|
33
35
|
getAnswer(id);
|
|
34
36
|
}
|
|
35
|
-
setOpenCards(prevOpenCards =>
|
|
36
|
-
prevOpenCards.includes(id)
|
|
37
|
-
|
|
37
|
+
setOpenCards(prevOpenCards => {
|
|
38
|
+
if (prevOpenCards.includes(id)) return prevOpenCards.filter(openId => openId !== id);
|
|
39
|
+
trackOpenAIQuestion?.(question);
|
|
40
|
+
return [...prevOpenCards, id];
|
|
41
|
+
});
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
const handleClose = () => {
|
|
@@ -115,7 +119,7 @@ const AIDrawer = ({
|
|
|
115
119
|
CustomContent={AnswerContentComponent}
|
|
116
120
|
labelProps={{ withMarkdown: true }}
|
|
117
121
|
loading={isLoading}
|
|
118
|
-
onPress={() => toggleCard(id)}
|
|
122
|
+
onPress={() => toggleCard(id, question)}
|
|
119
123
|
preventRipple
|
|
120
124
|
style={{
|
|
121
125
|
content: styles.collapsibleCardContent,
|
|
@@ -161,6 +165,7 @@ AIDrawer.propTypes = {
|
|
|
161
165
|
components: object,
|
|
162
166
|
error: object,
|
|
163
167
|
getAnswer: func,
|
|
168
|
+
handlers: object,
|
|
164
169
|
isOpen: bool,
|
|
165
170
|
loading: object,
|
|
166
171
|
notEnoughBillsErrorType: string,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* eslint-disable react/forbid-prop-types */
|
|
2
2
|
/* eslint-disable react/jsx-fragments */
|
|
3
|
-
import React, { Fragment, useEffect, useState } from 'react';
|
|
3
|
+
import React, { Fragment, useEffect, useState, useMemo } from 'react';
|
|
4
4
|
import { UTButton, UTLoading, UTTabs } from '@widergy/mobile-ui';
|
|
5
5
|
import { View } from 'react-native';
|
|
6
6
|
import { arrayOf, bool, func, object, shape, string } from 'prop-types';
|
|
7
7
|
import isEmpty from 'lodash/isEmpty';
|
|
8
|
+
import debounce from 'lodash/debounce';
|
|
8
9
|
|
|
9
10
|
import { TAB_COMPONENT_MAPPER } from './constants';
|
|
10
11
|
import { getDefaultCurrentTab, getTabOptions } from './utils';
|
|
@@ -35,15 +36,41 @@ const SmartBillSummary = ({
|
|
|
35
36
|
smartBillAIAnswerError,
|
|
36
37
|
smartBillAIAnswerLoading
|
|
37
38
|
} = constants;
|
|
38
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
getSmartBillAIAnswer,
|
|
41
|
+
getSmartBillAIQuestions,
|
|
42
|
+
resetSmartBillAIAnswers,
|
|
43
|
+
trackAIFloatingButtonClick,
|
|
44
|
+
trackTabChange
|
|
45
|
+
} = handlers;
|
|
39
46
|
const filteredTabOptions = getTabOptions(tabOptions, smartBill) || [];
|
|
40
47
|
const defaultCurrentTab = getDefaultCurrentTab(filteredTabOptions);
|
|
41
48
|
const [currentTab, setCurrentTab] = useState(defaultCurrentTab);
|
|
42
49
|
const [tabsKey, setTabsKey] = useState(0);
|
|
43
50
|
const [aiDrawerIsOpen, setAiDrawerIsOpen] = useState(false);
|
|
51
|
+
const [lastTrackedTab, setLastTrackedTab] = useState(defaultCurrentTab);
|
|
44
52
|
|
|
45
53
|
const changeCurrentTab = newTab => setCurrentTab(newTab?.value || newTab);
|
|
54
|
+
|
|
55
|
+
const debouncedTrackTabChange = useMemo(
|
|
56
|
+
() =>
|
|
57
|
+
debounce(newTab => {
|
|
58
|
+
const tabValue = newTab?.value || newTab;
|
|
59
|
+
if (tabValue !== lastTrackedTab) {
|
|
60
|
+
trackTabChange(tabValue);
|
|
61
|
+
setLastTrackedTab(tabValue);
|
|
62
|
+
}
|
|
63
|
+
}, 300),
|
|
64
|
+
[trackTabChange, lastTrackedTab]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const onUserTabChange = newTab => {
|
|
68
|
+
debouncedTrackTabChange(newTab);
|
|
69
|
+
changeCurrentTab(newTab);
|
|
70
|
+
};
|
|
71
|
+
|
|
46
72
|
const openAIDrawer = () => {
|
|
73
|
+
trackAIFloatingButtonClick?.();
|
|
47
74
|
if (isEmpty(aiQuestionsList)) getSmartBillAIQuestions();
|
|
48
75
|
setAiDrawerIsOpen(true);
|
|
49
76
|
};
|
|
@@ -53,8 +80,17 @@ const SmartBillSummary = ({
|
|
|
53
80
|
};
|
|
54
81
|
|
|
55
82
|
useEffect(() => {
|
|
56
|
-
if (defaultCurrentTab
|
|
57
|
-
|
|
83
|
+
if (defaultCurrentTab && defaultCurrentTab !== currentTab) {
|
|
84
|
+
changeCurrentTab(defaultCurrentTab);
|
|
85
|
+
}
|
|
86
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
87
|
+
}, [defaultCurrentTab]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (defaultCurrentTab && !lastTrackedTab) {
|
|
91
|
+
setLastTrackedTab(defaultCurrentTab);
|
|
92
|
+
}
|
|
93
|
+
}, [defaultCurrentTab, lastTrackedTab]);
|
|
58
94
|
|
|
59
95
|
useEffect(() => {
|
|
60
96
|
if (glossaryError && !glossaryLoading && defaultCurrentTab) {
|
|
@@ -63,12 +99,18 @@ const SmartBillSummary = ({
|
|
|
63
99
|
}
|
|
64
100
|
}, [defaultCurrentTab, glossaryError, glossaryLoading]);
|
|
65
101
|
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
return () => {
|
|
104
|
+
debouncedTrackTabChange.cancel();
|
|
105
|
+
};
|
|
106
|
+
}, [debouncedTrackTabChange]);
|
|
107
|
+
|
|
66
108
|
return (
|
|
67
109
|
<UTLoading loading={loading} style={styles.loading}>
|
|
68
110
|
<UTTabs
|
|
69
111
|
key={tabsKey}
|
|
70
112
|
labelProps={{ weight: 'semibold' }}
|
|
71
|
-
onChange={
|
|
113
|
+
onChange={onUserTabChange}
|
|
72
114
|
tabs={filteredTabOptions}
|
|
73
115
|
value={currentTab}
|
|
74
116
|
/>
|
|
@@ -105,6 +147,7 @@ const SmartBillSummary = ({
|
|
|
105
147
|
components={components}
|
|
106
148
|
error={smartBillAIAnswerError}
|
|
107
149
|
getAnswer={getSmartBillAIAnswer}
|
|
150
|
+
handlers={handlers}
|
|
108
151
|
isOpen={aiDrawerIsOpen}
|
|
109
152
|
loading={smartBillAIAnswerLoading}
|
|
110
153
|
notEnoughBillsErrorType={notEnoughBillsErrorType}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable react/forbid-prop-types */
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { useEffect, useMemo } from 'react';
|
|
3
3
|
import { Linking, View } from 'react-native';
|
|
4
4
|
import { Surface, UTButton, UTLabel } from '@widergy/mobile-ui';
|
|
5
5
|
import { object, string, func, objectOf, array } from 'prop-types';
|
|
6
6
|
import isEmpty from 'lodash/isEmpty';
|
|
7
|
+
import debounce from 'lodash/debounce';
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
getConsumptionLabels,
|
|
@@ -24,7 +25,8 @@ const RateCard = ({
|
|
|
24
25
|
ratesTableLink,
|
|
25
26
|
smartBill = {},
|
|
26
27
|
subsidy = '',
|
|
27
|
-
subsidyLevels
|
|
28
|
+
subsidyLevels,
|
|
29
|
+
trackRedirectionToExternalLink
|
|
28
30
|
}) => {
|
|
29
31
|
const styles = createStyles(colors);
|
|
30
32
|
const {
|
|
@@ -60,7 +62,24 @@ const RateCard = ({
|
|
|
60
62
|
const noLevels = isEmpty(consumptionLevels);
|
|
61
63
|
const showRateLabel = !isT1 || startRateValue === false || limitRateValue === false;
|
|
62
64
|
|
|
63
|
-
const
|
|
65
|
+
const debouncedTrackRedirection = useMemo(
|
|
66
|
+
() =>
|
|
67
|
+
debounce(link => {
|
|
68
|
+
trackRedirectionToExternalLink(link);
|
|
69
|
+
}, 300),
|
|
70
|
+
[trackRedirectionToExternalLink]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
return () => {
|
|
75
|
+
debouncedTrackRedirection.cancel();
|
|
76
|
+
};
|
|
77
|
+
}, [debouncedTrackRedirection]);
|
|
78
|
+
|
|
79
|
+
const openExternalLink = () => {
|
|
80
|
+
debouncedTrackRedirection(ratesTableLink);
|
|
81
|
+
if (ratesTableLink) Linking.openURL(ratesTableLink);
|
|
82
|
+
};
|
|
64
83
|
|
|
65
84
|
return (
|
|
66
85
|
<Surface elevation={1} style={styles.container}>
|
|
@@ -153,7 +172,8 @@ RateCard.propTypes = {
|
|
|
153
172
|
ratesTableLink: string,
|
|
154
173
|
smartBill: object,
|
|
155
174
|
subsidy: string,
|
|
156
|
-
subsidyLevels: objectOf(string)
|
|
175
|
+
subsidyLevels: objectOf(string),
|
|
176
|
+
trackRedirectionToExternalLink: func
|
|
157
177
|
};
|
|
158
178
|
|
|
159
179
|
export default RateCard;
|
|
@@ -68,7 +68,8 @@ const Billing = ({
|
|
|
68
68
|
const {
|
|
69
69
|
handleAutomaticDebitAdherence = () => {},
|
|
70
70
|
handleDownloadBill = () => {},
|
|
71
|
-
handleGoToDigitalBill = () => {}
|
|
71
|
+
handleGoToDigitalBill = () => {},
|
|
72
|
+
trackRedirectionToExternalLink = () => {}
|
|
72
73
|
} = handlers;
|
|
73
74
|
const {
|
|
74
75
|
adherence_to_automatic_debit: adherenceToAutomaticDebit,
|
|
@@ -173,6 +174,7 @@ const Billing = ({
|
|
|
173
174
|
smartBill,
|
|
174
175
|
subsidy,
|
|
175
176
|
subsidyLevels,
|
|
177
|
+
trackRedirectionToExternalLink,
|
|
176
178
|
translations
|
|
177
179
|
}}
|
|
178
180
|
/>
|
|
@@ -3,15 +3,17 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { Image, ScrollView, View } from 'react-native';
|
|
4
4
|
import { UTButton, UTLabel, UTLoading } from '@widergy/mobile-ui';
|
|
5
5
|
import { func, object, shape, string } from 'prop-types';
|
|
6
|
+
import debounce from 'lodash/debounce';
|
|
6
7
|
|
|
7
8
|
import styles from './styles';
|
|
8
9
|
|
|
9
10
|
const Glossary = ({ constants = {}, handlers = {}, translations = {} }) => {
|
|
10
|
-
const { getGlossaryData } = handlers;
|
|
11
|
+
const { getGlossaryData, trackClickGlossarySection = () => {} } = handlers;
|
|
11
12
|
const { glossaryError: error, glossaryData, glossaryLoading: loading } = constants;
|
|
12
13
|
const { title: glossaryTitle } = translations;
|
|
13
14
|
const { pages = [], sections = [] } = glossaryData || {};
|
|
14
15
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
16
|
+
const [lastTrackedSection, setLastTrackedSection] = useState(null);
|
|
15
17
|
const selectedSection = useMemo(() => sections?.[selectedIndex] || {}, [sections, selectedIndex]);
|
|
16
18
|
const currentPage = pages.find(p => p.id === selectedSection.page_id) || {};
|
|
17
19
|
const { image } = currentPage;
|
|
@@ -21,15 +23,28 @@ const Glossary = ({ constants = {}, handlers = {}, translations = {} }) => {
|
|
|
21
23
|
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
|
|
22
24
|
const aspectRatio = imageSize.width / imageSize.height || 1;
|
|
23
25
|
|
|
26
|
+
const debouncedTrackSection = useMemo(
|
|
27
|
+
() =>
|
|
28
|
+
debounce(sectionTitle => {
|
|
29
|
+
if (sectionTitle && sectionTitle !== lastTrackedSection) {
|
|
30
|
+
trackClickGlossarySection(sectionTitle);
|
|
31
|
+
setLastTrackedSection(sectionTitle);
|
|
32
|
+
}
|
|
33
|
+
}, 300),
|
|
34
|
+
[trackClickGlossarySection, lastTrackedSection]
|
|
35
|
+
);
|
|
36
|
+
|
|
24
37
|
const handleNext = () => {
|
|
25
38
|
if (selectedIndex < sections.length - 1) {
|
|
26
39
|
setSelectedIndex(prev => prev + 1);
|
|
40
|
+
debouncedTrackSection(sections[selectedIndex + 1]?.title);
|
|
27
41
|
}
|
|
28
42
|
};
|
|
29
43
|
|
|
30
44
|
const handlePrev = () => {
|
|
31
45
|
if (selectedIndex > 0) {
|
|
32
46
|
setSelectedIndex(prev => prev - 1);
|
|
47
|
+
debouncedTrackSection(sections[selectedIndex - 1]?.title);
|
|
33
48
|
}
|
|
34
49
|
};
|
|
35
50
|
|
|
@@ -41,11 +56,23 @@ const Glossary = ({ constants = {}, handlers = {}, translations = {} }) => {
|
|
|
41
56
|
if (image) Image.getSize(image, (width, height) => setImageSize({ width, height }));
|
|
42
57
|
}, [image, selectedSection]);
|
|
43
58
|
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (selectedSection?.title && !lastTrackedSection) {
|
|
61
|
+
setLastTrackedSection(selectedSection.title);
|
|
62
|
+
}
|
|
63
|
+
}, [selectedSection, lastTrackedSection]);
|
|
64
|
+
|
|
44
65
|
useEffect(() => {
|
|
45
66
|
getGlossaryData?.();
|
|
46
67
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
68
|
}, []);
|
|
48
69
|
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
return () => {
|
|
72
|
+
debouncedTrackSection.cancel();
|
|
73
|
+
};
|
|
74
|
+
}, [debouncedTrackSection]);
|
|
75
|
+
|
|
49
76
|
return error && !loading ? null : (
|
|
50
77
|
<GeneralContainer>
|
|
51
78
|
<View style={styles.generalContainer}>
|