@eohjsc/react-native-smart-city 0.7.42 → 0.7.44

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.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/src/commons/Dashboard/MyDashboardDevice/index.js +1 -0
  3. package/src/commons/Dashboard/MyUnit/index.js +1 -0
  4. package/src/commons/Device/PowerConsumptionChart.js +63 -31
  5. package/src/configs/AccessibilityLabel.js +1 -0
  6. package/src/iot/mqtt.js +4 -2
  7. package/src/navigations/UnitStack.js +2 -2
  8. package/src/screens/ActivityLog/ItemLog.js +69 -16
  9. package/src/screens/ActivityLog/__test__/ItemLog.test.js +169 -2
  10. package/src/screens/ActivityLog/index.js +2 -2
  11. package/src/screens/ActivityLog/styles/itemLogStyles.js +35 -0
  12. package/src/screens/AddNewGateway/RenameNewDevices.js +1 -1
  13. package/src/screens/AddNewGateway/__test__/RenameNewDevices.test.js +1 -1
  14. package/src/screens/Automate/AddNewAction/NewActionWrapper.js +24 -4
  15. package/src/screens/Automate/AddNewAction/__test__/SelectControlDevices.test.js +75 -11
  16. package/src/screens/Automate/AddNewAction/__test__/SelectMonitorDevices.test.js +5 -1
  17. package/src/screens/Automate/AddNewAutoSmart/AddTypeSmart.js +3 -2
  18. package/src/screens/Automate/EditActionsList/__tests__/index.test.js +20 -1
  19. package/src/screens/Automate/EditActionsList/index.js +18 -9
  20. package/src/screens/Automate/MultiUnits.js +2 -0
  21. package/src/screens/Automate/OneTap/__test__/AddNewOneTap.test.js +5 -0
  22. package/src/screens/Automate/Scenario/__test__/AddNewOneTap.test.js +5 -0
  23. package/src/screens/Automate/ScriptDetail/Styles/indexStyles.js +3 -0
  24. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +41 -12
  25. package/src/screens/Automate/ScriptDetail/index.js +3 -3
  26. package/src/screens/Automate/SetSchedule/__test__/AddEditConditionSchedule.test.js +4 -0
  27. package/src/screens/Automate/SetSchedule/__test__/index.test.js +4 -0
  28. package/src/screens/Automate/__test__/MultiUnits.test.js +6 -4
  29. package/src/screens/Automate/__test__/index.test.js +1 -0
  30. package/src/screens/Automate/index.js +1 -0
  31. package/src/screens/ConfirmUnitDeletion/__test__/ConfirmUnitDeletion.test.js +1 -1
  32. package/src/screens/ConfirmUnitDeletion/index.js +1 -1
  33. package/src/screens/CreatePassword/__test__/index.test.js +11 -2
  34. package/src/screens/CreatePassword/index.js +12 -1
  35. package/src/screens/Device/components/SensorDisplayItem.js +1 -0
  36. package/src/screens/Device/styles.js +1 -1
  37. package/src/screens/EnterPassword/index.js +1 -1
  38. package/src/screens/Notification/components/NotificationItem.js +1 -4
  39. package/src/screens/Sharing/__test__/UnitMemberList.test.js +1 -1
  40. package/src/screens/Sharing/hooks/__test__/index.test.js +2 -2
  41. package/src/screens/Sharing/hooks/index.js +1 -1
  42. package/src/screens/SmartAccount/ListDevice/index.js +1 -1
  43. package/src/screens/SubUnit/AddSubUnit.js +2 -2
  44. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +1 -1
  45. package/src/screens/SyncLGDevice/AddLGDevice.js +3 -3
  46. package/src/screens/SyncLGDevice/__test__/AddLGDevice.test.js +5 -5
  47. package/src/screens/Unit/Detail.js +1 -3
  48. package/src/screens/Unit/__test__/Detail.test.js +4 -4
  49. package/src/screens/UnitSummary/components/PowerConsumption/__test__/PowerConsumption.test.js +32 -0
  50. package/src/utils/I18n/translations/en.js +1 -0
  51. package/src/utils/I18n/translations/vi.js +1 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.7.42",
4
+ "version": "0.7.44",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -201,6 +201,7 @@ const MyDashboardDevice = ({ refreshing }) => {
201
201
  {item.name}
202
202
  </Text>
203
203
  <TouchableOpacity
204
+ hitSlop={12}
204
205
  onPress={goToUnitDetail(item)}
205
206
  style={styles.arrowRightButton}
206
207
  accessibilityLabel={`${AccessibilityLabel.UNIT_GO_TO_DETAIL}-${index}`}
@@ -253,6 +253,7 @@ const MyUnit = ({ refreshing }) => {
253
253
  {t('text_my_units')}
254
254
  </Text>
255
255
  <TouchableOpacity
256
+ hitSlop={12}
256
257
  onPress={goToSelectUnit}
257
258
  style={styles.searchMenuButton}
258
259
  accessibilityLabel={AccessibilityLabel.SELECT_UNIT}
@@ -1,6 +1,6 @@
1
1
  import moment from 'moment';
2
2
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
- import { StyleSheet, View } from 'react-native';
3
+ import { ActivityIndicator, StyleSheet, View } from 'react-native';
4
4
  import { useTranslations } from '../../hooks/Common/useTranslations';
5
5
 
6
6
  import { API, Colors } from '../../configs';
@@ -19,6 +19,7 @@ const PowerConsumptionChart = ({
19
19
  powerConfigId,
20
20
  defaultPeriod = 'date',
21
21
  color = Colors.Primary,
22
+ isWidgetOrder = false,
22
23
  style,
23
24
  }) => {
24
25
  const t = useTranslations();
@@ -30,9 +31,11 @@ const PowerConsumptionChart = ({
30
31
  price: '',
31
32
  });
32
33
  const [value, setValue] = useState([moment().subtract(6, 'days'), moment()]);
34
+ const [isFetching, setIsFetching] = useState(false);
33
35
 
34
36
  const fetchData = useCallback(
35
37
  async (startDate, endDate) => {
38
+ setIsFetching(true);
36
39
  let params = new URLSearchParams();
37
40
  params.append('config', powerConfigId);
38
41
  params.append('group_by', groupBy);
@@ -50,6 +53,7 @@ const PowerConsumptionChart = ({
50
53
  if (success) {
51
54
  setDatas(data);
52
55
  }
56
+ setIsFetching(false);
53
57
  },
54
58
  [groupBy, powerConfigId]
55
59
  );
@@ -83,13 +87,20 @@ const PowerConsumptionChart = ({
83
87
  }, [datas, chartConfig]);
84
88
 
85
89
  const renderChart = useMemo(() => {
90
+ if (isFetching) {
91
+ return (
92
+ <View style={styles.loadingWrapper}>
93
+ <ActivityIndicator size="large" color={Colors.Primary} />
94
+ </View>
95
+ );
96
+ }
86
97
  if (!datas || datas.length === 0) {
87
98
  return null;
88
99
  }
89
100
  return (
90
101
  <HorizontalBarChart datas={datas} config={chartConfig} color={color} />
91
102
  );
92
- }, [chartConfig, datas, color]);
103
+ }, [chartConfig, datas, color, isFetching]);
93
104
 
94
105
  const selectStart = (date) => {
95
106
  setValue((state) => [date, state[1]]);
@@ -99,16 +110,23 @@ const PowerConsumptionChart = ({
99
110
  };
100
111
 
101
112
  return (
102
- <View style={style}>
113
+ <View style={[styles.wrapper, style]}>
103
114
  <View style={styles.historyView}>
104
115
  <View style={styles.titleHistory}>
105
- <Text style={styles.label} bold color={Colors.Gray9}>
116
+ <Text
117
+ style={styles.label}
118
+ bold
119
+ color={Colors.Gray9}
120
+ numberOfLines={1}
121
+ >
106
122
  {label || t('power_consumption_chart')}
107
123
  </Text>
108
124
 
109
- <ChartAggregationOption groupBy={groupBy} setGroupBy={setGroupBy} />
125
+ {!isWidgetOrder && (
126
+ <ChartAggregationOption groupBy={groupBy} setGroupBy={setGroupBy} />
127
+ )}
110
128
  </View>
111
- {groupBy === 'date' && (
129
+ {!isWidgetOrder && groupBy === 'date' && (
112
130
  <DateTimeRangeChange
113
131
  startTime={value[0]}
114
132
  endTime={value[1]}
@@ -119,34 +137,36 @@ const PowerConsumptionChart = ({
119
137
  )}
120
138
  </View>
121
139
 
122
- <View style={styles.calculateCostCard}>
123
- <Text style={styles.title}>
124
- {t('input_price_to_calculate_electricity_cost')}
125
- </Text>
126
- <View style={styles.inputRow}>
127
- <View style={styles.inputWrapper}>
128
- <CurrencyInput
129
- value={price}
130
- onChange={setPrice}
131
- minValue={0}
132
- maxValue={100000000}
133
- placeholder={'0'}
134
- onSubmitEditing={onCalculateCost}
135
- currency="đ/kWh"
136
- wrapperStyle={styles.currencyInputNoBorder}
140
+ {!isWidgetOrder && (
141
+ <View style={styles.calculateCostCard}>
142
+ <Text style={styles.title}>
143
+ {t('input_price_to_calculate_electricity_cost')}
144
+ </Text>
145
+ <View style={styles.inputRow}>
146
+ <View style={styles.inputWrapper}>
147
+ <CurrencyInput
148
+ value={price}
149
+ onChange={setPrice}
150
+ minValue={0}
151
+ maxValue={100000000}
152
+ placeholder={'0'}
153
+ onSubmitEditing={onCalculateCost}
154
+ currency="đ/kWh"
155
+ wrapperStyle={styles.currencyInputNoBorder}
156
+ />
157
+ </View>
158
+ <Button
159
+ type="primary"
160
+ title={t('calculate_cost')}
161
+ height={42}
162
+ onPress={onCalculateCost}
163
+ style={styles.buttonCalculate}
164
+ textSemiBold={true}
165
+ accessibilityLabel={AccessibilityLabel.BUTTON_CALCULATE_COST}
137
166
  />
138
167
  </View>
139
- <Button
140
- type="primary"
141
- title={t('calculate_cost')}
142
- height={42}
143
- onPress={onCalculateCost}
144
- style={styles.buttonCalculate}
145
- textSemiBold={true}
146
- accessibilityLabel={AccessibilityLabel.BUTTON_CALCULATE_COST}
147
- />
148
168
  </View>
149
- </View>
169
+ )}
150
170
 
151
171
  <View style={styles.chartCard}>
152
172
  {renderChart}
@@ -169,6 +189,10 @@ const PowerConsumptionChart = ({
169
189
  export default React.memo(PowerConsumptionChart);
170
190
 
171
191
  const styles = StyleSheet.create({
192
+ wrapper: {
193
+ flex: 1,
194
+ marginBottom: 8,
195
+ },
172
196
  historyView: {
173
197
  paddingTop: 0,
174
198
  },
@@ -181,6 +205,7 @@ const styles = StyleSheet.create({
181
205
  justifyContent: 'flex-end',
182
206
  marginTop: 12,
183
207
  marginLeft: 6,
208
+ flex: 1,
184
209
  },
185
210
  calculateCostCard: {
186
211
  backgroundColor: Colors.LightPrimary,
@@ -247,5 +272,12 @@ const styles = StyleSheet.create({
247
272
  label: {
248
273
  fontSize: 18,
249
274
  fontWeight: '700',
275
+ flex: 1,
276
+ marginRight: 8,
277
+ },
278
+ loadingWrapper: {
279
+ height: 150,
280
+ justifyContent: 'center',
281
+ alignItems: 'center',
250
282
  },
251
283
  });
@@ -138,6 +138,7 @@ export default {
138
138
  BOTTOM_BUTTON_RIGHT: 'BOTTOM_BUTTON_RIGHT',
139
139
  VIEW_BUTTON_BOTTOM_RIGHT_BUTTON: 'VIEW_BUTTON_BOTTOM_RIGHT_BUTTON',
140
140
  VIEW_BUTTON_BOTTOM_LEFT_BUTTON: 'VIEW_BUTTON_BOTTOM_LEFT_BUTTON',
141
+ TOUCHABLE_OPACITY: 'TOUCHABLE_OPACITY',
141
142
 
142
143
  // Smart parking drawer
143
144
  ROW_EXIT_SMARTPARKING_DRAWER: 'ROW_EXIT_SMARTPARKING_DRAWER',
package/src/iot/mqtt.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { Buffer } from 'buffer';
1
2
  import { getObject, storeObject } from '../utils/Storage';
2
3
  import { getLastUpdated, updateGlobalValue } from './UpdateStates';
3
- import { Buffer } from 'buffer';
4
4
 
5
5
  const struct = require('python-struct');
6
6
 
@@ -322,7 +322,9 @@ const get_offset_sample_value = async (value, config) => {
322
322
 
323
323
  const getValueConverter = (value, config) => {
324
324
  const converter = mappingValueConverter[config?.value_converter];
325
- return converter ? converter(value) : value;
325
+ return converter
326
+ ? Number(get_decimal_value(converter(value), config))
327
+ : value;
326
328
  };
327
329
 
328
330
  const check_filter = (value, config) => {
@@ -11,7 +11,7 @@ import { SCContext } from '../context';
11
11
  import { Action } from '../context/actionType';
12
12
  import { useTranslations } from '../hooks/Common/useTranslations';
13
13
  import { unwatchAllConfigs } from '../iot/Monitor';
14
- import ActivityLogScreen from '../screens/ActivityLog';
14
+ import ActivityLog from '../screens/ActivityLog';
15
15
  import Route from '../utils/Route';
16
16
  import { screenOptions } from './utils';
17
17
 
@@ -350,7 +350,7 @@ export const UnitStack = memo((props) => {
350
350
  />
351
351
  <Stack.Screen
352
352
  name={Route.ActivityLog}
353
- component={ActivityLogScreen}
353
+ component={ActivityLog}
354
354
  options={{
355
355
  headerShown: false,
356
356
  }}
@@ -1,10 +1,14 @@
1
- import React from 'react';
2
- import { View } from 'react-native';
1
+ import React, { useState, useCallback } from 'react';
2
+ import { View, TouchableOpacity, Modal } from 'react-native';
3
3
  import moment from 'moment';
4
4
  import Text from '../../commons/Text';
5
5
  import t from '../../hooks/Common/useTranslations';
6
6
  import styles from './styles/itemLogStyles';
7
- import { AUTOMATE_TYPE, ACTIVITY_LOG_TYPES } from '../../configs/Constants';
7
+ import {
8
+ AUTOMATE_TYPE,
9
+ ACTIVITY_LOG_TYPES,
10
+ AccessibilityLabel,
11
+ } from '../../configs/Constants';
8
12
 
9
13
  const DetailEmergencyEventLog = ({ item }) => (
10
14
  <Text style={styles.text}>
@@ -20,34 +24,83 @@ const DetailEmergencyEventLog = ({ item }) => (
20
24
  );
21
25
 
22
26
  const DetailLog = ({ item }) => {
23
- switch (item.content_code) {
27
+ const [showModal, setShowModal] = useState(false);
28
+ const { name, username, created_at, message, content_code, action_name } =
29
+ item;
30
+
31
+ const handlePress = useCallback(() => {
32
+ if (message) {
33
+ setShowModal(true);
34
+ }
35
+ }, [message]);
36
+
37
+ const handleClose = useCallback(() => {
38
+ setShowModal(false);
39
+ }, []);
40
+
41
+ switch (content_code) {
24
42
  case ACTIVITY_LOG_TYPES.AUTO_ACTIVATED:
25
43
  return <Text style={styles.text}>{t('auto_activated')}</Text>;
26
44
  case ACTIVITY_LOG_TYPES.ACTIVATED_BY:
27
45
  return (
28
46
  <Text style={styles.text}>
29
- {item.action_name
30
- ? `${item.action_name} ${t('by')} `
31
- : `${t('activated_by')} `}
32
- <Text style={styles.name}>{item.name || item.username}</Text>
47
+ {action_name ? `${action_name} ${t('by')} ` : `${t('activated_by')} `}
48
+ <Text style={styles.name}>{name || username}</Text>
33
49
  </Text>
34
50
  );
35
51
  case ACTIVITY_LOG_TYPES.SCRIPT_UPDATED_BY:
36
52
  return (
37
53
  <Text style={styles.text}>
38
54
  {`${t('script_updated_by')} `}
39
- <Text style={styles.name}>{item.name || item.username}</Text>
55
+ <Text style={styles.name}>{name || username}</Text>
40
56
  </Text>
41
57
  );
42
58
  default:
43
59
  return (
44
- <Text style={styles.text}>
45
- {item.action_name
46
- ? `${item.action_name} ${t('by')} `
47
- : `${t('activated_by')} `}
48
- <Text style={styles.name}>{item.name || item.username}</Text>
49
- <Text style={styles.text}>{'\n' + item.message}</Text>
50
- </Text>
60
+ <>
61
+ <TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
62
+ <Text style={styles.text}>
63
+ {action_name
64
+ ? `${action_name} ${t('by')} `
65
+ : `${t('activated_by')} `}
66
+ <Text style={styles.name}>{name || username}</Text>
67
+ </Text>
68
+ </TouchableOpacity>
69
+ <Modal
70
+ visible={showModal}
71
+ transparent
72
+ animationType="fade"
73
+ onRequestClose={handleClose}
74
+ accessibilityLabel={AccessibilityLabel.MODAL_BUTTON_POPUP}
75
+ >
76
+ <TouchableOpacity
77
+ style={styles.modalOverlay}
78
+ activeOpacity={1}
79
+ onPress={handleClose}
80
+ accessibilityLabel={AccessibilityLabel.TOUCHABLE_OPACITY}
81
+ >
82
+ <View style={styles.modalContent}>
83
+ <Text style={styles.modalTitle}>{t('detail')}</Text>
84
+ <Text style={styles.modalMessage}>
85
+ {action_name
86
+ ? `${action_name} ${t('by')} `
87
+ : `${t('activated_by')} `}
88
+ <Text style={styles.name}>{name || username}</Text>
89
+ </Text>
90
+ <Text style={styles.modalMessage}>{message}</Text>
91
+ <Text style={styles.modalMessage}>
92
+ {moment(created_at).format('DD/MM/YYYY HH:mm:ss')}
93
+ </Text>
94
+ <TouchableOpacity
95
+ style={styles.modalCloseButton}
96
+ onPress={handleClose}
97
+ >
98
+ <Text style={styles.modalCloseText}>{t('close')}</Text>
99
+ </TouchableOpacity>
100
+ </View>
101
+ </TouchableOpacity>
102
+ </Modal>
103
+ </>
51
104
  );
52
105
  }
53
106
  };
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { Text } from 'react-native';
2
+ import { Text, TouchableOpacity, Modal } from 'react-native';
3
3
  import { act, create } from 'react-test-renderer';
4
4
  import ItemLog from '../ItemLog';
5
5
  import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
- import { AUTOMATE_TYPE } from '../../../configs/Constants';
7
+ import { AUTOMATE_TYPE, AccessibilityLabel } from '../../../configs/Constants';
8
8
 
9
9
  const wrapComponent = (props) => (
10
10
  <SCProvider initState={mockSCStore({})}>
@@ -137,3 +137,170 @@ describe('test ItemLog emergency event', () => {
137
137
  await _test(props.item.user.email);
138
138
  });
139
139
  });
140
+
141
+ describe('test ItemLog Modal', () => {
142
+ const wrapComponentWithMessage = (message) => (
143
+ <SCProvider initState={mockSCStore({})}>
144
+ <ItemLog
145
+ item={{
146
+ username: 'testuser',
147
+ message: message,
148
+ created_at: '2021-07-02T15:48:24.917932Z',
149
+ }}
150
+ type="action"
151
+ length={2}
152
+ index={0}
153
+ />
154
+ </SCProvider>
155
+ );
156
+
157
+ const getItemLogModal = (tree) => {
158
+ const modals = tree.root.findAllByType(Modal);
159
+ return modals.find(
160
+ (m) =>
161
+ m.props.accessibilityLabel === AccessibilityLabel.MODAL_BUTTON_POPUP
162
+ );
163
+ };
164
+
165
+ const getOverlayTouchable = (tree) => {
166
+ const touchables = tree.root.findAllByType(TouchableOpacity);
167
+ return touchables.find(
168
+ (t) => t.props.accessibilityLabel === AccessibilityLabel.TOUCHABLE_OPACITY
169
+ );
170
+ };
171
+
172
+ it('should open modal when clicking item with message', async () => {
173
+ let tree;
174
+ await act(async () => {
175
+ tree = await create(wrapComponentWithMessage('Test message content'));
176
+ });
177
+
178
+ const modal = getItemLogModal(tree);
179
+ expect(modal.props.visible).toBe(false);
180
+
181
+ const touchables = tree.root.findAllByType(TouchableOpacity);
182
+ const itemTouchable = touchables[0];
183
+
184
+ await act(async () => {
185
+ itemTouchable.props.onPress();
186
+ });
187
+
188
+ expect(modal.props.visible).toBe(true);
189
+ });
190
+
191
+ it('should display correct message in modal', async () => {
192
+ let tree;
193
+ const testMessage = 'Test message content';
194
+ await act(async () => {
195
+ tree = await create(wrapComponentWithMessage(testMessage));
196
+ });
197
+
198
+ const touchables = tree.root.findAllByType(TouchableOpacity);
199
+ await act(async () => {
200
+ touchables[0].props.onPress();
201
+ });
202
+
203
+ const texts = tree.root.findAllByType(Text);
204
+ const messageText = texts.find((t) => t.props.children === testMessage);
205
+ expect(messageText).toBeTruthy();
206
+ });
207
+
208
+ it('should close modal when pressing close button', async () => {
209
+ let tree;
210
+ await act(async () => {
211
+ tree = await create(wrapComponentWithMessage('Test message'));
212
+ });
213
+
214
+ const modal = getItemLogModal(tree);
215
+ const touchables = tree.root.findAllByType(TouchableOpacity);
216
+
217
+ await act(async () => {
218
+ touchables[0].props.onPress();
219
+ });
220
+ expect(modal.props.visible).toBe(true);
221
+
222
+ // Re-find touchables after modal is open and find close button by style
223
+ const updatedTouchables = tree.root.findAllByType(TouchableOpacity);
224
+ const closeButton = updatedTouchables.find(
225
+ (t) => t.props.style?.alignItems === 'center'
226
+ );
227
+ await act(async () => {
228
+ closeButton.props.onPress();
229
+ });
230
+ expect(modal.props.visible).toBe(false);
231
+ });
232
+
233
+ it('should close modal when pressing overlay', async () => {
234
+ let tree;
235
+ await act(async () => {
236
+ tree = await create(wrapComponentWithMessage('Test message'));
237
+ });
238
+
239
+ const modal = getItemLogModal(tree);
240
+ const touchables = tree.root.findAllByType(TouchableOpacity);
241
+
242
+ await act(async () => {
243
+ touchables[0].props.onPress();
244
+ });
245
+ expect(modal.props.visible).toBe(true);
246
+
247
+ // Find overlay by accessibilityLabel
248
+ const overlayTouchable = getOverlayTouchable(tree);
249
+ await act(async () => {
250
+ overlayTouchable.props.onPress();
251
+ });
252
+ expect(modal.props.visible).toBe(false);
253
+ });
254
+
255
+ it('should close modal on requestClose (Android back button)', async () => {
256
+ let tree;
257
+ await act(async () => {
258
+ tree = await create(wrapComponentWithMessage('Test message'));
259
+ });
260
+
261
+ const modal = getItemLogModal(tree);
262
+ const touchables = tree.root.findAllByType(TouchableOpacity);
263
+
264
+ await act(async () => {
265
+ touchables[0].props.onPress();
266
+ });
267
+ expect(modal.props.visible).toBe(true);
268
+
269
+ await act(async () => {
270
+ modal.props.onRequestClose();
271
+ });
272
+ expect(modal.props.visible).toBe(false);
273
+ });
274
+
275
+ it('should not open modal when item has no message', async () => {
276
+ let tree;
277
+ await act(async () => {
278
+ tree = await create(wrapComponentWithMessage(null));
279
+ });
280
+
281
+ const modal = getItemLogModal(tree);
282
+ const touchables = tree.root.findAllByType(TouchableOpacity);
283
+
284
+ await act(async () => {
285
+ touchables[0].props.onPress();
286
+ });
287
+
288
+ expect(modal.props.visible).toBe(false);
289
+ });
290
+
291
+ it('should not open modal when message is empty string', async () => {
292
+ let tree;
293
+ await act(async () => {
294
+ tree = await create(wrapComponentWithMessage(''));
295
+ });
296
+
297
+ const modal = getItemLogModal(tree);
298
+ const touchables = tree.root.findAllByType(TouchableOpacity);
299
+
300
+ await act(async () => {
301
+ touchables[0].props.onPress();
302
+ });
303
+
304
+ expect(modal.props.visible).toBe(false);
305
+ });
306
+ });
@@ -20,7 +20,7 @@ import { AccessibilityLabel } from '../../configs/Constants';
20
20
 
21
21
  const keyExtractor = (item) => item.id;
22
22
 
23
- const ActivityLogScreen = ({ route }) => {
23
+ const ActivityLog = ({ route }) => {
24
24
  const t = useTranslations();
25
25
  const { id, type, share, filterEnabled } = route?.params || {};
26
26
  const {
@@ -131,4 +131,4 @@ const ActivityLogScreen = ({ route }) => {
131
131
  );
132
132
  };
133
133
 
134
- export default ActivityLogScreen;
134
+ export default ActivityLog;
@@ -1,5 +1,6 @@
1
1
  import { Colors } from '../../../configs';
2
2
  import { StyleSheet } from 'react-native';
3
+ import { colorOpacity } from '../../../utils/Converter/color';
3
4
 
4
5
  export default StyleSheet.create({
5
6
  wrapItem: {
@@ -41,4 +42,38 @@ export default StyleSheet.create({
41
42
  lineHeight: 22,
42
43
  flexShrink: 1,
43
44
  },
45
+ modalOverlay: {
46
+ flex: 1,
47
+ backgroundColor: colorOpacity(Colors.Black, 0.5),
48
+ justifyContent: 'center',
49
+ alignItems: 'center',
50
+ },
51
+ modalContent: {
52
+ backgroundColor: Colors.White,
53
+ borderRadius: 12,
54
+ padding: 20,
55
+ marginHorizontal: 20,
56
+ maxWidth: '90%',
57
+ minWidth: '80%',
58
+ },
59
+ modalTitle: {
60
+ fontSize: 18,
61
+ fontWeight: 'bold',
62
+ color: Colors.Gray9,
63
+ marginBottom: 12,
64
+ },
65
+ modalMessage: {
66
+ marginBottom: 15,
67
+ },
68
+ modalCloseButton: {
69
+ backgroundColor: Colors.Primary,
70
+ borderRadius: 8,
71
+ paddingVertical: 12,
72
+ alignItems: 'center',
73
+ },
74
+ modalCloseText: {
75
+ color: Colors.White,
76
+ fontSize: 16,
77
+ fontWeight: '600',
78
+ },
44
79
  });
@@ -80,7 +80,7 @@ const RenameNewDevices = memo(({ route }) => {
80
80
  unitId: unit?.id,
81
81
  stationId: subUnit?.id,
82
82
  isSuccessfullyConnected: true,
83
- routeName: Routes.DashboardStack,
83
+ routeName: Routes.Main,
84
84
  },
85
85
  },
86
86
  },
@@ -89,7 +89,7 @@ describe('Test rename new devices', () => {
89
89
  params: {
90
90
  params: {
91
91
  isSuccessfullyConnected: true,
92
- routeName: 'DashboardStack',
92
+ routeName: 'Main',
93
93
  stationId: undefined,
94
94
  unitId: undefined,
95
95
  },
@@ -8,6 +8,7 @@ import { FullLoading, HeaderCustom } from '../../../commons';
8
8
  import BottomButtonView from '../../../commons/BottomButtonView';
9
9
  import { Colors } from '../../../configs';
10
10
  import { AccessibilityLabel } from '../../../configs/Constants';
11
+ import Routes from '../../../utils/Route';
11
12
  import styles from './Styles/SelectActionStyles';
12
13
 
13
14
  const NewActionWrapper = ({
@@ -20,14 +21,33 @@ const NewActionWrapper = ({
20
21
  }) => {
21
22
  const hasNext =
22
23
  canNext !== undefined || onNext !== undefined || nextTitle !== undefined;
23
- const { navigate } = useNavigation();
24
+ const { navigate, getState, pop } = useNavigation();
24
25
  const { params = {} } = useRoute();
25
26
 
26
- const { closeScreen } = params;
27
+ const { closeScreen, closeStack } = params;
27
28
 
28
29
  const handleClose = useCallback(() => {
29
- navigate(closeScreen, params);
30
- }, [closeScreen, navigate, params]);
30
+ const state = getState();
31
+ const targetIndex = state.routes.findIndex((r) => r.name === closeScreen);
32
+
33
+ if (targetIndex >= 0) {
34
+ const popCount = state.index - targetIndex;
35
+ pop(popCount);
36
+ } else if (closeStack) {
37
+ navigate(Routes.Main, {
38
+ screen: Routes.DrawerMain,
39
+ params: {
40
+ screen: closeStack,
41
+ params: {
42
+ screen: closeScreen,
43
+ params: params,
44
+ },
45
+ },
46
+ });
47
+ } else {
48
+ navigate(closeScreen, params);
49
+ }
50
+ }, [closeScreen, closeStack, navigate, getState, params, pop]);
31
51
 
32
52
  const rightComponent = useMemo(
33
53
  () => (