@eohjsc/react-native-smart-city 0.7.20 → 0.7.21

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.
@@ -0,0 +1,173 @@
1
+ import { useNavigation } from '@react-navigation/native';
2
+ import moment from 'moment';
3
+ import React, { memo, useCallback, useState } from 'react';
4
+ import { ScrollView } from 'react-native';
5
+ import Calendar from '../../../commons/Calendar';
6
+ import WheelDateTimePicker from '../../../commons/WheelDateTimePicker';
7
+ import { useBoolean } from '../../../hooks/Common';
8
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
9
+ import Routes from '../../../utils/Route';
10
+ import NewActionWrapper from '../AddNewAction/NewActionWrapper';
11
+ import RepeatOptionsPopup, {
12
+ REPEAT_OPTIONS,
13
+ } from './components/RepeatOptionsPopup';
14
+ import RowItem from './components/RowItem';
15
+ import SelectWeekday from './components/SelectWeekday';
16
+ import styles from './styles/indexStyles';
17
+ import { axiosPost, axiosPut } from '../../../utils/Apis/axios';
18
+ import { API } from '../../../configs';
19
+ import { AUTOMATE_TYPE } from '../../../configs/Constants';
20
+
21
+ const getDateString = (date, t) => {
22
+ //Move outside component to prevent re-creation on every render.
23
+ const today = moment();
24
+ if (date.isSame(today, 'day')) {
25
+ return date.format(`[${t('today')}], D MMMM YYYY `);
26
+ }
27
+ return date.format('ddd, D MMMM YYYY');
28
+ };
29
+
30
+ const AddEditConditionSchedule = ({ route }) => {
31
+ const t = useTranslations();
32
+ const { automate, condition = {}, isUpdateCondition } = route.params;
33
+ const { navigate } = useNavigation();
34
+ const [repeat, setRepeat] = useState(
35
+ condition.repeat ? condition.repeat : REPEAT_OPTIONS.ONCE
36
+ );
37
+ const [time, setTime] = useState(
38
+ condition.time_repeat
39
+ ? moment(condition.time_repeat, 'HH:mm:ss')
40
+ : moment().second(0)
41
+ );
42
+ const [date, setDate] = useState(
43
+ condition.date_repeat
44
+ ? moment(condition.date_repeat, 'YYYY-MM-DD')
45
+ : moment()
46
+ );
47
+ const [weekday, setWeekday] = useState(
48
+ condition.weekday_repeat ? condition.weekday_repeat : []
49
+ );
50
+
51
+ const [showRepeatOptions, setShowRepeatOptions, setHideRepeatOptions] =
52
+ useBoolean();
53
+ const [showTimePicker, setShowTimePicker, setHideTimePicker] = useBoolean();
54
+ const [showCalendar, setShowCalendar, setHideCalendar] = useBoolean();
55
+
56
+ const handleOnSave = useCallback(async () => {
57
+ const data = {
58
+ type: AUTOMATE_TYPE.SCHEDULE,
59
+ repeat,
60
+ time_repeat: time.format('HH:mm:ss'),
61
+ date_repeat:
62
+ repeat === REPEAT_OPTIONS.ONCE ? date.format('YYYY-MM-DD') : null,
63
+ weekday_repeat: repeat === REPEAT_OPTIONS.EVERYWEEK ? weekday : null,
64
+ };
65
+
66
+ let response;
67
+ if (isUpdateCondition) {
68
+ response = await axiosPut(
69
+ API.AUTOMATE.UPDATE_CONDITION(automate.id, condition.id),
70
+ data
71
+ );
72
+ } else {
73
+ response = await axiosPost(API.AUTOMATE.ADD_CONDITION(automate.id), data);
74
+ }
75
+ const { success } = response;
76
+ if (success) {
77
+ navigate({
78
+ name: Routes.ScriptDetail,
79
+ params: { preAutomate: automate },
80
+ });
81
+ }
82
+ }, [
83
+ repeat,
84
+ time,
85
+ date,
86
+ weekday,
87
+ isUpdateCondition,
88
+ automate,
89
+ condition.id,
90
+ navigate,
91
+ ]);
92
+
93
+ const onSetRepeatOption = useCallback(
94
+ (value) => {
95
+ setRepeat(value);
96
+ setHideRepeatOptions();
97
+ },
98
+ [setRepeat, setHideRepeatOptions]
99
+ );
100
+
101
+ const onTimePicked = useCallback(
102
+ (timeData) => {
103
+ setTime(moment(timeData));
104
+ },
105
+ [setTime]
106
+ );
107
+
108
+ const onDatePicked = useCallback(
109
+ (datePicked) => {
110
+ setDate(datePicked);
111
+ },
112
+ [setDate]
113
+ );
114
+
115
+ return (
116
+ <NewActionWrapper
117
+ name={t('set_schedule')}
118
+ onNext={handleOnSave}
119
+ canNext={true}
120
+ nextTitle={t('save')}
121
+ >
122
+ <ScrollView
123
+ contentContainerStyle={styles.scollView}
124
+ scrollIndicatorInsets={{ right: 1 }}
125
+ >
126
+ <RowItem
127
+ title={t('set_time')}
128
+ value={time.format('HH:mm')}
129
+ icon="clock-circle"
130
+ onPress={setShowTimePicker}
131
+ />
132
+ {repeat === REPEAT_OPTIONS.ONCE && (
133
+ <RowItem
134
+ title={t('select_date')}
135
+ value={getDateString(date, t)}
136
+ icon="calendar"
137
+ onPress={setShowCalendar}
138
+ />
139
+ )}
140
+ {repeat === REPEAT_OPTIONS.EVERYWEEK && (
141
+ <SelectWeekday weekday={weekday} setWeekday={setWeekday} />
142
+ )}
143
+ <RowItem
144
+ title={t('repeat')}
145
+ value={t(`${repeat}`)}
146
+ arrow
147
+ onPress={setShowRepeatOptions}
148
+ />
149
+ </ScrollView>
150
+ <RepeatOptionsPopup
151
+ isVisible={showRepeatOptions}
152
+ onHide={setHideRepeatOptions}
153
+ onSetRepeat={onSetRepeatOption}
154
+ />
155
+ <WheelDateTimePicker
156
+ mode="time"
157
+ isVisible={showTimePicker}
158
+ defaultValue={time.valueOf()}
159
+ onCancel={setHideTimePicker}
160
+ onPicked={onTimePicked}
161
+ />
162
+ <Calendar
163
+ isVisible={showCalendar}
164
+ defaultDate={date}
165
+ minDate={moment()}
166
+ onCancel={setHideCalendar}
167
+ onConfirm={onDatePicked}
168
+ />
169
+ </NewActionWrapper>
170
+ );
171
+ };
172
+
173
+ export default memo(AddEditConditionSchedule);
@@ -0,0 +1,211 @@
1
+ import { useNavigation, useRoute } from '@react-navigation/native';
2
+ import moment from 'moment';
3
+ import React from 'react';
4
+ import { act, create } from 'react-test-renderer';
5
+ import Calendar from '../../../../commons/Calendar';
6
+ import WheelDateTimePicker from '../../../../commons/WheelDateTimePicker';
7
+ import AccessibilityLabel from '../../../../configs/AccessibilityLabel';
8
+ import { SCProvider } from '../../../../context';
9
+ import { mockSCStore } from '../../../../context/mockStore';
10
+ import Routes from '../../../../utils/Route';
11
+ import RepeatOptionsPopup, {
12
+ REPEAT_OPTIONS,
13
+ } from '../components/RepeatOptionsPopup';
14
+
15
+ import RowItem from '../components/RowItem';
16
+ import SelectWeekday from '../components/SelectWeekday';
17
+ import AddEditConditionSchedule from '../AddEditConditionSchedule';
18
+ import { AUTOMATE_TYPE } from '../../../../configs/Constants';
19
+ import NewActionWrapper from '../../AddNewAction/NewActionWrapper';
20
+ import { API } from '../../../../configs';
21
+ import MockAdapter from 'axios-mock-adapter';
22
+ import api from '../../../../utils/Apis/axios';
23
+
24
+ const wrapComponent = (route) => {
25
+ useRoute.mockReturnValue(route);
26
+ return (
27
+ <SCProvider initState={mockSCStore({})}>
28
+ <AddEditConditionSchedule route={route} />
29
+ </SCProvider>
30
+ );
31
+ };
32
+
33
+ const mock = new MockAdapter(api.axiosInstance);
34
+
35
+ describe('Test AddEditConditionSchedule', () => {
36
+ let tree;
37
+ let route = {
38
+ params: {
39
+ automate: { type: 'schedule', unit: 1, id: 1 },
40
+ condition: {
41
+ id: 1,
42
+ type: AUTOMATE_TYPE.SCHEDULE,
43
+ repeat: 'once',
44
+ time_repeat: '19:00:00',
45
+ date_repeat: '2025-01-01',
46
+ weekday_repeat: [],
47
+ },
48
+ isUpdateCondition: true,
49
+ closeScreen: 'ScriptDetail',
50
+ },
51
+ };
52
+
53
+ const mockedNavigate = useNavigation().navigate;
54
+
55
+ beforeEach(() => {
56
+ mockedNavigate.mockClear();
57
+ Date.now = jest.fn(() => new Date('2025-01-01T19:00:00.000Z'));
58
+ });
59
+
60
+ it('test render SetSchedule', async () => {
61
+ await act(async () => {
62
+ tree = await create(wrapComponent(route));
63
+ });
64
+ const instance = tree.root;
65
+ const rowItems = instance.findAllByType(RowItem);
66
+ const timePicker = instance.findByType(WheelDateTimePicker);
67
+ const popup = instance.findByType(RepeatOptionsPopup);
68
+ const calendar = instance.findByType(Calendar);
69
+ const header = instance.findByProps({
70
+ accessibilityLabel: AccessibilityLabel.ICON_CLOSE,
71
+ });
72
+
73
+ await act(async () => {
74
+ await rowItems[0].props.onPress();
75
+ });
76
+ expect(timePicker.props.isVisible).toBeTruthy();
77
+
78
+ await act(async () => {
79
+ await rowItems[2].props.onPress();
80
+ });
81
+ expect(popup.props.isVisible).toBeTruthy();
82
+
83
+ await act(async () => {
84
+ await rowItems[1].props.onPress();
85
+ });
86
+ expect(calendar.props.isVisible).toBeTruthy();
87
+
88
+ await act(async () => {
89
+ await header.props.onPress();
90
+ });
91
+ expect(global.mockedNavigate).toHaveBeenCalledWith('ScriptDetail', {
92
+ automate: { id: 1, type: 'schedule', unit: 1 },
93
+ closeScreen: 'ScriptDetail',
94
+ condition: {
95
+ id: 1,
96
+ repeat: 'once',
97
+ time_repeat: '19:00:00',
98
+ date_repeat: '2025-01-01',
99
+ type: 'schedule',
100
+ weekday_repeat: [],
101
+ },
102
+ isUpdateCondition: true,
103
+ });
104
+ });
105
+
106
+ it('test repeat options popup', async () => {
107
+ await act(async () => {
108
+ tree = await create(wrapComponent(route));
109
+ });
110
+ const instance = tree.root;
111
+ const popup = instance.findByType(RepeatOptionsPopup);
112
+ let rowItems = instance.findAllByType(RowItem);
113
+ expect(rowItems).toHaveLength(3);
114
+
115
+ await act(async () => {
116
+ await popup.props.onSetRepeat(REPEAT_OPTIONS.EVERYDAY);
117
+ });
118
+ rowItems = instance.findAllByType(RowItem);
119
+ expect(rowItems).toHaveLength(2);
120
+
121
+ await act(async () => {
122
+ await popup.props.onSetRepeat(REPEAT_OPTIONS.EVERYWEEK);
123
+ });
124
+ const selectWeekday = instance.findByType(SelectWeekday);
125
+ rowItems = instance.findAllByType(RowItem);
126
+ expect(rowItems).toHaveLength(2);
127
+ expect(selectWeekday).toBeDefined();
128
+ });
129
+
130
+ it('test pick date and time', async () => {
131
+ await act(async () => {
132
+ tree = await create(wrapComponent(route));
133
+ });
134
+ const instance = tree.root;
135
+ const timePicker = instance.findByType(WheelDateTimePicker);
136
+ const calendar = instance.findByType(Calendar);
137
+ const rowItems = instance.findAllByType(RowItem);
138
+
139
+ const time = moment();
140
+ const date = moment();
141
+ expect(rowItems[0].props.value).toBe('19:00');
142
+ expect(rowItems[1].props.value).toBe(date.format('[Today], D MMMM YYYY '));
143
+
144
+ time.hour(10).minute(30);
145
+ date.add(1, 'days');
146
+ await act(async () => {
147
+ await timePicker.props.onPicked(time);
148
+ await calendar.props.onConfirm(date);
149
+ });
150
+ expect(rowItems[0].props.value).toBe(time.format('HH:mm'));
151
+ expect(rowItems[1].props.value).toBe(date.format('ddd, D MMMM YYYY'));
152
+ });
153
+
154
+ it('test save isUpdateCondition', async () => {
155
+ mock.onPut(API.AUTOMATE.UPDATE_CONDITION(1, 1)).reply(200);
156
+ await act(async () => {
157
+ tree = await create(wrapComponent(route));
158
+ });
159
+ const instance = tree.root;
160
+ const button = instance.findByType(NewActionWrapper);
161
+
162
+ await act(async () => {
163
+ await button.props.onNext();
164
+ });
165
+ expect(global.mockedNavigate).toHaveBeenCalledWith({
166
+ name: 'ScriptDetail',
167
+ params: { preAutomate: { id: 1, type: 'schedule', unit: 1 } },
168
+ });
169
+ });
170
+
171
+ it('test call api isUpdateCondition fail', async () => {
172
+ mock.onPut(API.AUTOMATE.UPDATE_CONDITION(1, 1)).reply(400);
173
+ await act(async () => {
174
+ tree = await create(wrapComponent(route));
175
+ });
176
+ const instance = tree.root;
177
+ const popup = instance.findByType(RepeatOptionsPopup);
178
+
179
+ await act(async () => {
180
+ await popup.props.onSetRepeat(REPEAT_OPTIONS.EVERYWEEK);
181
+ });
182
+ const button = instance.findByType(NewActionWrapper);
183
+
184
+ await act(async () => {
185
+ await button.props.onNext();
186
+ });
187
+ expect(global.mockedNavigate).toBeDefined();
188
+ });
189
+
190
+ it('test save isAddCondition', async () => {
191
+ // route.params.condition = {};
192
+ route.params = {
193
+ automate: { id: 1 },
194
+ closeScreen: Routes.ScriptDetail,
195
+ };
196
+ mock.onPost(API.AUTOMATE.ADD_CONDITION(1)).reply(200);
197
+ await act(async () => {
198
+ tree = await create(wrapComponent(route));
199
+ });
200
+ const instance = tree.root;
201
+ const button = instance.findByType(NewActionWrapper);
202
+
203
+ await act(async () => {
204
+ await button.props.onNext();
205
+ });
206
+ expect(global.mockedNavigate).toHaveBeenCalledWith({
207
+ name: 'ScriptDetail',
208
+ params: { preAutomate: { id: 1 } },
209
+ });
210
+ });
211
+ });
@@ -499,6 +499,8 @@ export default {
499
499
  west: 'West',
500
500
  condition: 'Condition',
501
501
  no_condition: 'No condition',
502
+ available_condition: 'Available [{number}] condition',
503
+ available_action: 'Available [{number}] action',
502
504
  north_east: 'North East',
503
505
  south_east: 'South East',
504
506
  south_west: 'South West',
@@ -1030,8 +1032,21 @@ export default {
1030
1032
  multi_units_automate: 'Multi-Units Smart',
1031
1033
  tap_to_run: 'Tap to run',
1032
1034
  how_to_start: 'How to start',
1035
+ go_to_unit: 'Go to Unit',
1033
1036
  edit_condition: 'Edit condition',
1037
+ update_condition_success: 'Update condition success',
1038
+ edit_condition_success: 'Edit condition success',
1039
+ delete_condition: 'Delete condition',
1040
+ are_met: 'Are Met (All Condition)',
1041
+ is_met: 'Is Met (Any Condition)',
1042
+ add_condition: 'Add condition',
1043
+ add_condition_success: 'Add condition success',
1034
1044
  device_display: 'Device display',
1045
+ time_frame: 'Time frame',
1046
+ ex_time_frame: 'For example: 8:00 a.m. everyday.',
1047
+ when_value_change: 'When the value of the device changes',
1048
+ ex_when_value_change:
1049
+ 'For example: When opening the door, when the sensor value changes, etc.',
1035
1050
  'set_up {name}': 'Setup {name}',
1036
1051
  choose_action: 'Choose action',
1037
1052
  action: 'action',
@@ -1047,8 +1062,11 @@ export default {
1047
1062
  add_script: 'Add Script',
1048
1063
  delete_script: 'Delete Script',
1049
1064
  title_delete_script: 'Delete script "{scriptName}"?',
1065
+ title_delete_condition: 'Delete condition schedule?',
1050
1066
  message_delete_script:
1051
1067
  'Are you sure you want to delete script "{scriptName}"?',
1068
+ message_delete_condition:
1069
+ 'Are you sure you want to delete condition schedule?',
1052
1070
  rename_automate: 'Rename automate',
1053
1071
  edit_sub_unit: 'Edit Sub-unit',
1054
1072
  no_sub_unit_yet: 'No sub-unit yet.',
@@ -1072,6 +1090,7 @@ export default {
1072
1090
  smart: 'Smart',
1073
1091
  enable_this_script: 'Enable this script',
1074
1092
  local_control: 'Local control',
1093
+ local_control_update_success: 'Local control update successful',
1075
1094
  choose_gateway: 'Choose gateway',
1076
1095
  this_script_has_been_disabled: 'This script has been disabled',
1077
1096
  disable: 'Disable',
@@ -1501,6 +1520,8 @@ export default {
1501
1520
  'You have reached the maximum number of {length} chips per unit.',
1502
1521
  reach_max_configs_per_unit:
1503
1522
  'You have reached the maximum number of {length} configurations per unit.',
1523
+ reach_max_conditions_per_automation:
1524
+ 'You have reached the maximum number of {length} condition per scenario.',
1504
1525
  no_permission_plug_and_play_modbus:
1505
1526
  'You do not have permission to add Modbus devices. Please upgrade your service package at e-ra.io',
1506
1527
  no_permission_plug_and_play_zigbee:
@@ -491,6 +491,8 @@ export default {
491
491
  to: 'đến',
492
492
  condition: 'Điều kiện',
493
493
  no_condition: 'Không điều kiện',
494
+ available_condition: 'Số điều khiện còn lại [{number}]',
495
+ available_action: 'Số hành động còn lại [{number}]',
494
496
  shared_unit: 'Khu vực được chia sẻ',
495
497
  owner_unit: 'Chủ khu vực',
496
498
  sensor_device: 'Thiết bị cảm biến',
@@ -1042,8 +1044,21 @@ export default {
1042
1044
  continue: 'Tiếp tục',
1043
1045
  tap_to_run: 'Nhấn để chạy',
1044
1046
  how_to_start: 'Cách khởi động',
1047
+ go_to_unit: 'Đi đến địa điểm',
1045
1048
  edit_condition: 'Chỉnh sửa điều kiện',
1049
+ update_condition_success: 'Cập nhật điều kiện thành công',
1050
+ edit_condition_success: 'Chỉnh sửa điều kiện thành công',
1051
+ delete_condition: 'Xoá điều kiện',
1052
+ are_met: 'Tất cả điều khiện đều thỏa',
1053
+ is_met: 'Một trong các điều kiện được thỏa',
1054
+ add_condition: 'Thêm điều khiện',
1055
+ add_condition_success: 'Thêm điều khiện thành công',
1046
1056
  device_display: 'Thiết bị hiển thị',
1057
+ time_frame: 'Khung thời gian',
1058
+ ex_time_frame: 'Ví dụ: 8:00 a.m. mỗi ngày.',
1059
+ when_value_change: 'Khi giá trị của thiết bị có thay đổi',
1060
+ ex_when_value_change:
1061
+ 'Ví dụ: Khi mở cửa, khi giá trị cảm biến thay đổi, .v.v.',
1047
1062
  'set_up {name}': 'Thiết lập {name}',
1048
1063
  choose_action: 'Lựa chọn hành động',
1049
1064
  action: 'hành động',
@@ -1059,7 +1074,9 @@ export default {
1059
1074
  add_script: 'Thêm Kịch Bản',
1060
1075
  delete_script: 'Xóa kịch bản',
1061
1076
  title_delete_script: 'Xóa kịch bản "{scriptName}"?',
1077
+ title_delete_condition: 'Xoá điều kiện lịch trình?',
1062
1078
  message_delete_script: 'Bạn có chắc chắn muốn xóa kịch bản "{scriptName}"?',
1079
+ message_delete_condition: 'Bạn có chắc chắn muốn xóa điều kiện lịch trình?',
1063
1080
  rename_automate: 'Đổi tên tự động hóa',
1064
1081
  edit_sub_unit: 'Chỉnh sửa khu vực',
1065
1082
  no_sub_unit_yet: 'Chưa có khu vực nào.',
@@ -1082,6 +1099,7 @@ export default {
1082
1099
  smart: 'Thông minh',
1083
1100
  enable_this_script: 'Kích hoạt kịch bản này',
1084
1101
  local_control: 'Điều khiển tại biên',
1102
+ local_control_update_success: 'Cập nhật điều khiển tại biên thành công',
1085
1103
  choose_gateway: 'Chọn gateway',
1086
1104
  this_script_has_been_disabled: 'Kịch bản này đã bị vô hiệu hoá',
1087
1105
  disable: 'Vô hiệu hóa',
@@ -1508,7 +1526,9 @@ export default {
1508
1526
  reach_max_chips_per_unit:
1509
1527
  'Bạn đã đạt đến số lượng tối đa {length} chip cho mỗi địa điểm.',
1510
1528
  reach_max_configs_per_unit:
1511
- 'Bạn đã đạt đến số lượng tối đa {length} thông số cho mỗi địa điểm..',
1529
+ 'Bạn đã đạt đến số lượng tối đa {length} thông số cho mỗi địa điểm.',
1530
+ reach_max_conditions_per_automation:
1531
+ 'Bạn đã đạt đến số lượng tối đa {length} điều kiện cho mỗi kịch bản.',
1512
1532
  no_permission_plug_and_play_modbus:
1513
1533
  'Bạn không có quyền thêm thiết bị Modbus. Vui lòng nâng cấp gói dịch vụ tại e-ra.io',
1514
1534
  no_permission_plug_and_play_zigbee:
@@ -155,6 +155,7 @@ const Routes = {
155
155
  EditDevice: 'EditDevice',
156
156
  SetSchedule: 'SetSchedule',
157
157
  Notification: 'Notification',
158
+ AddEditConditionSchedule: 'AddEditConditionSchedule',
158
159
  PersonalHealthStack: 'PersonalHealthStack',
159
160
  ListSmartAccount: 'ListSmartAccount',
160
161
  ListDeviceSmartAccount: 'ListDeviceSmartAccount',