@eohjsc/react-native-smart-city 0.7.24 → 0.7.26

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 (54) hide show
  1. package/index.js +2 -0
  2. package/package.json +1 -1
  3. package/src/Images/Common/search-menu.svg +7 -0
  4. package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplateStyle.js +2 -1
  5. package/src/commons/ActionGroup/OneBigButtonTemplateStyle.js +1 -2
  6. package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -3
  7. package/src/commons/ActionGroup/TerminalBoxTemplate.js +1 -4
  8. package/src/commons/ActionGroup/TextBoxTemplate.js +1 -5
  9. package/src/commons/ActionGroup/ThreeButtonTemplate/components/ThreeButtonDefaultStyles.js +1 -1
  10. package/src/commons/ActionGroup/__test__/index.test.js +51 -0
  11. package/src/commons/ActionGroup/index.js +4 -0
  12. package/src/commons/Dashboard/MyDashboardDevice/__test__/index.test.js +171 -0
  13. package/src/commons/Dashboard/MyDashboardDevice/index.js +218 -0
  14. package/src/commons/Dashboard/MyDashboardDevice/styles.js +60 -0
  15. package/src/commons/Dashboard/MyPinnedSharedUnit/index.js +0 -10
  16. package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +114 -48
  17. package/src/commons/Dashboard/MyUnit/index.js +74 -27
  18. package/src/commons/Dashboard/MyUnit/styles.js +16 -1
  19. package/src/commons/DateTimeRangeChange/index.js +1 -1
  20. package/src/commons/Device/ItemDevice.js +12 -3
  21. package/src/commons/Device/ItemDeviceWrapper.js +10 -2
  22. package/src/commons/SelectUnit/index.js +19 -5
  23. package/src/commons/SelectUnit/styles.js +0 -1
  24. package/src/commons/SubUnit/DeviceTemplate/DeviceTemplate.js +8 -3
  25. package/src/commons/Widgets/IFrame/IFrameStyles.js +1 -0
  26. package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfigStyles.js +1 -0
  27. package/src/configs/API.js +11 -0
  28. package/src/configs/AccessibilityLabel.js +2 -0
  29. package/src/configs/Constants.js +3 -0
  30. package/src/context/actionType.ts +4 -0
  31. package/src/context/mockStore.ts +5 -0
  32. package/src/context/reducer.ts +30 -5
  33. package/src/iot/mqtt.js +163 -47
  34. package/src/navigations/UnitStack.js +22 -0
  35. package/src/screens/Device/__test__/detail.test.js +42 -1
  36. package/src/screens/Device/__test__/mqttDetail.test.js +411 -190
  37. package/src/screens/Device/__test__/sensorDisplayItem.test.js +27 -0
  38. package/src/screens/Device/components/DonutChart.js +5 -14
  39. package/src/screens/Device/components/SensorDisplayItem.js +92 -61
  40. package/src/screens/Device/components/VisualChart.js +0 -12
  41. package/src/screens/Device/detail.js +50 -14
  42. package/src/screens/Device/hooks/useDashboardDevice.js +34 -0
  43. package/src/screens/Device/styles.js +16 -0
  44. package/src/screens/SubUnit/AddSubUnit.js +18 -8
  45. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +5 -3
  46. package/src/screens/Unit/GoToDetailUnit.js +30 -0
  47. package/src/screens/Unit/__test__/GoToDetailUnit.test.js +103 -0
  48. package/src/utils/FactoryGateway.js +105 -0
  49. package/src/utils/I18n/translations/en.js +4 -0
  50. package/src/utils/I18n/translations/vi.js +4 -0
  51. package/src/utils/Route/index.js +1 -0
  52. package/src/utils/Storage.js +18 -4
  53. package/src/utils/Utils.js +2 -1
  54. package/src/utils/Functions/preloadImages.js +0 -38
@@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
2
2
  import React from 'react';
3
3
  import renderer, { act } from 'react-test-renderer';
4
4
 
5
- import FastImage from 'react-native-fast-image';
5
+ import Carousel from 'react-native-new-snap-carousel';
6
6
  import MyUnit from '..';
7
7
  import { API } from '../../../../configs';
8
8
  import { AccessibilityLabel } from '../../../../configs/Constants';
@@ -12,13 +12,12 @@ import { flushPromises } from '../../../../screens/AllGateway/test-utils';
12
12
  import MyUnitDevice from '../../../../screens/Unit/components/MyUnitDevice';
13
13
  import api from '../../../../utils/Apis/axios';
14
14
  import Routes from '../../../../utils/Route';
15
- import { removeMultiple, STORAGE_KEY } from '../../../../utils/Storage';
16
15
 
17
16
  const mock = new MockAdapter(api.axiosInstance);
18
17
 
19
- const wrapComponent = (data = {}) => (
18
+ const wrapComponent = (data = {}, refreshing = true) => (
20
19
  <SCProvider initState={mockSCStore(data)}>
21
- <MyUnit refreshing={true} />
20
+ <MyUnit refreshing={refreshing} />
22
21
  </SCProvider>
23
22
  );
24
23
 
@@ -27,42 +26,45 @@ describe('Test MyUnit', () => {
27
26
  let stateData = {
28
27
  app: { isDeleteUnitSuccessFully: true, isNeedUpdateCache: true },
29
28
  };
30
- let data = [
31
- {
32
- id: 1,
33
- name: 'name',
34
- background: 'background',
35
- abstract_devices: [
36
- {
37
- id: 1,
38
- name: 'device',
39
- is_managed_by_backend: true,
40
- device_type: 'GOOGLE_HOME',
41
- station_name: 'name',
42
- },
43
- {
44
- id: 2,
45
- name: 'device',
46
- is_managed_by_backend: true,
47
- device_type: '',
48
- station_name: 'name',
49
- quick_action: {
50
- config_id: 1,
29
+ let data = {
30
+ count: 2,
31
+ next: null,
32
+ previous: null,
33
+ results: [
34
+ {
35
+ id: 1,
36
+ name: 'name',
37
+ background: 'background',
38
+ abstract_devices: [
39
+ {
40
+ id: 1,
41
+ name: 'device',
42
+ is_managed_by_backend: true,
43
+ device_type: 'GOOGLE_HOME',
44
+ station_name: 'name',
51
45
  },
52
- },
53
- ],
54
- },
55
- {
56
- id: 2,
57
- name: 'name2',
58
- background: 'background',
59
- },
60
- ];
46
+ {
47
+ id: 2,
48
+ name: 'device',
49
+ is_managed_by_backend: true,
50
+ device_type: '',
51
+ station_name: 'name',
52
+ quick_action: {
53
+ config_id: 1,
54
+ },
55
+ },
56
+ ],
57
+ },
58
+ {
59
+ id: 2,
60
+ name: 'name2',
61
+ background: 'background',
62
+ },
63
+ ],
64
+ };
61
65
 
62
66
  beforeEach(async () => {
63
67
  mock.resetHistory();
64
- FastImage.preload.mockClear();
65
- await removeMultiple([STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_UNITS]);
66
68
  });
67
69
 
68
70
  const getElement = (instance) => {
@@ -88,7 +90,7 @@ describe('Test MyUnit', () => {
88
90
  });
89
91
 
90
92
  it('MyUnit with unit', async () => {
91
- mock.onGet(API.UNIT.MY_UNITS()).replyOnce(200, data);
93
+ mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
92
94
  await act(async () => {
93
95
  tree = await renderer.create(wrapComponent(stateData));
94
96
  });
@@ -101,16 +103,18 @@ describe('Test MyUnit', () => {
101
103
  accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-0`,
102
104
  });
103
105
  await act(async () => {
104
- await button.props.onPress(data[0]);
106
+ await button.props.onPress(data.results[0]);
105
107
  });
106
108
  expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
107
109
  params: { unitId: 1 },
108
110
  screen: Routes.UnitDetail,
109
111
  });
112
+ expect(mock.history.get).toHaveLength(1);
113
+ expect(mock.history.get[0].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
110
114
  });
111
115
 
112
116
  it('Test isNeedUpdateCache = false', async () => {
113
- mock.onGet(API.UNIT.MY_UNITS()).replyOnce(200, [data[1]]);
117
+ mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
114
118
  await act(async () => {
115
119
  tree = await renderer.create(
116
120
  wrapComponent({ app: { isNeedUpdateCache: false } })
@@ -118,10 +122,10 @@ describe('Test MyUnit', () => {
118
122
  });
119
123
  const instance = tree.root;
120
124
  const button = instance.findByProps({
121
- accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-0`,
125
+ accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-1`,
122
126
  });
123
127
  await act(async () => {
124
- await button.props.onPress(data[1]);
128
+ await button.props.onPress(data.results[1]);
125
129
  });
126
130
  expect(global.mockedNavigate).toHaveBeenCalledWith('UnitStack', {
127
131
  params: { unitId: 2 },
@@ -129,18 +133,80 @@ describe('Test MyUnit', () => {
129
133
  });
130
134
  });
131
135
 
132
- it('Test preload image', async () => {
133
- mock.onGet(API.UNIT.MY_UNITS()).replyOnce(200, [data[1]]);
134
- mock.onGet(API.UNIT.UNIT_DETAIL(2)).replyOnce(200, {
135
- stations: [{ background: 'background' }],
136
+ it('Test fetch when refreshing = false', async () => {
137
+ mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
138
+ await act(async () => {
139
+ tree = await renderer.create(
140
+ wrapComponent({ app: { isNeedUpdateCache: false } }, false)
141
+ );
142
+ });
143
+ expect(mock.history.get).toHaveLength(1);
144
+ expect(mock.history.get[0].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
145
+ });
146
+
147
+ it('Test fetch unit data when snap to item', async () => {
148
+ const base = data.results[0];
149
+ const extraIds = Array.from({ length: 9 }, (_, i) => i + 3);
150
+ const extraResults = extraIds.map((id) => ({
151
+ ...base,
152
+ id,
153
+ }));
154
+ mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, {
155
+ ...data,
156
+ count: 11,
157
+ results: [...data.results, ...extraResults],
136
158
  });
159
+ await act(async () => {
160
+ tree = await renderer.create(
161
+ wrapComponent({ app: { isNeedUpdateCache: false } }, false)
162
+ );
163
+ });
164
+ const instance = tree.root;
165
+ const carousel = instance.findByType(Carousel);
166
+
167
+ await act(() => {
168
+ carousel.props.onSnapToItem(3);
169
+ });
170
+ await act(() => {
171
+ carousel.props.onSnapToItem(4);
172
+ });
173
+ expect(mock.history.get).toHaveLength(1);
174
+ expect(mock.history.get[0].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
175
+ await act(() => {
176
+ carousel.props.onSnapToItem(5);
177
+ });
178
+ await act(() => {
179
+ carousel.props.onSnapToItem(6);
180
+ });
181
+ expect(mock.history.get).toHaveLength(2);
182
+ expect(mock.history.get[1].url).toEqual(API.UNIT_V2.MY_UNITS(2, 10));
183
+ await act(() => {
184
+ carousel.props.onSnapToItem(4);
185
+ });
186
+ await act(() => {
187
+ carousel.props.onSnapToItem(3);
188
+ });
189
+ expect(mock.history.get).toHaveLength(3);
190
+ expect(mock.history.get[2].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
191
+ });
192
+
193
+ it('Test navigation select unit', async () => {
194
+ mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
137
195
  await act(async () => {
138
196
  tree = await renderer.create(
139
197
  wrapComponent({ app: { isNeedUpdateCache: false } })
140
198
  );
141
199
  });
142
- expect(FastImage.preload).toHaveBeenCalledWith([
143
- { uri: 'background', priority: FastImage.priority.high },
144
- ]);
200
+
201
+ const instance = tree.root;
202
+ const button = instance.findByProps({
203
+ accessibilityLabel: AccessibilityLabel.SELECT_UNIT,
204
+ });
205
+ await act(async () => {
206
+ await button.props.onPress();
207
+ });
208
+ expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
209
+ screen: Routes.GoToDetailUnit,
210
+ });
145
211
  });
146
212
  });
@@ -23,13 +23,13 @@ import { Action } from '../../../context/actionType';
23
23
  import { BleManager } from 'react-native-ble-plx';
24
24
  import Carousel from 'react-native-new-snap-carousel';
25
25
  import FImage from '../../FImage';
26
+ import SearchMenu from '../../../Images/Common/search-menu.svg';
26
27
  import MyUnitDevice from '../../../screens/Unit/components/MyUnitDevice';
27
28
  import NetInfo from '@react-native-community/netinfo';
29
+ import { PAGE_SIZE } from '../../../configs/Constants';
28
30
  import Routes from '../../../utils/Route';
29
- import { STORAGE_KEY } from '../../../utils/Storage';
30
31
  import { Section } from '../../Section';
31
32
  import Text from '../../Text';
32
- import { preloadImagesFromUnits } from '../../../utils/Functions/preloadImages';
33
33
  import styles from './styles';
34
34
  import { usePrevious } from '../../../hooks';
35
35
  import { useTranslations } from '../../../hooks/Common/useTranslations';
@@ -44,7 +44,9 @@ const MyUnit = ({ refreshing }) => {
44
44
  const t = useTranslations();
45
45
  const isFocused = useIsFocused();
46
46
  const navigation = useNavigation();
47
- const [myUnits, setMyUnits] = useState([]);
47
+ const [page, setPage] = useState(1);
48
+ const [maxPageUnit, setMaxPageUnit] = useState(1);
49
+ const [unitsByPage, setUnitsByPage] = useState(() => new Map());
48
50
  const previousMyUnits = usePrevious(myUnits);
49
51
  const [slideIndex, setSlideIndex] = useState(0);
50
52
  const { setAction } = useContext(SCContext);
@@ -54,6 +56,10 @@ const MyUnit = ({ refreshing }) => {
54
56
  const isNeedUpdateCache = useSCContextSelector(
55
57
  (state) => state?.app?.isNeedUpdateCache
56
58
  );
59
+ const myUnits = useMemo(() => {
60
+ const pages = [...unitsByPage.keys()];
61
+ return pages.flatMap((p) => unitsByPage.get(p));
62
+ }, [unitsByPage]);
57
63
 
58
64
  const {
59
65
  permissionsRequested: bluetoothPermRequested,
@@ -64,25 +70,51 @@ const MyUnit = ({ refreshing }) => {
64
70
  !bluetoothPermRequested && requestBluetoothPerm();
65
71
  }, [bluetoothPermRequested, requestBluetoothPerm]);
66
72
 
67
- const fetchMyUnitDashboard = useCallback(async () => {
73
+ const fetchMyUnitDashboard = useCallback(async (pageParam) => {
74
+ const handleSuccess = (data) => {
75
+ setUnitsByPage((prev) => {
76
+ const next = new Map(prev);
77
+ next.set(pageParam, data.results);
78
+ return next;
79
+ });
80
+ setMaxPageUnit(Math.ceil(data.count / PAGE_SIZE));
81
+ };
82
+
68
83
  if (isNeedUpdateCache) {
69
84
  setAction(Action.IS_CHECK_CLEAR_CACHE_UNITS, false);
70
- const { success, data } = await axiosGet(API.UNIT.MY_UNITS(), {}, true);
71
- if (success) {
72
- setMyUnits(data);
73
- setAction(Action.SET_MY_UNITS, data);
74
- }
85
+ const { success, data } = await axiosGet(
86
+ API.UNIT_V2.MY_UNITS(pageParam, PAGE_SIZE),
87
+ {},
88
+ true
89
+ );
90
+ success && handleSuccess(data);
75
91
  } else {
76
- await fetchWithCache(API.UNIT.MY_UNITS(), {}, (response) => {
77
- const { success, data } = response;
78
- if (success) {
79
- setMyUnits(data);
80
- setAction(Action.SET_MY_UNITS, data);
92
+ await fetchWithCache(
93
+ API.UNIT_V2.MY_UNITS(pageParam, PAGE_SIZE),
94
+ {},
95
+ (response) => {
96
+ const { success, data } = response;
97
+ success && handleSuccess(data);
81
98
  }
82
- });
99
+ );
83
100
  }
84
101
  // eslint-disable-next-line react-hooks/exhaustive-deps
85
- }, [setMyUnits]);
102
+ }, []);
103
+
104
+ const onSnapToItem = useCallback(
105
+ (index) => {
106
+ const isUp = index > slideIndex;
107
+ const mid = page * PAGE_SIZE - PAGE_SIZE / 2;
108
+ if (isUp && index >= mid && page < maxPageUnit) {
109
+ setPage((prev) => prev + 1);
110
+ }
111
+ if (!isUp && index < mid && page > 1) {
112
+ setPage((prev) => prev - 1);
113
+ }
114
+ setSlideIndex(index);
115
+ },
116
+ [page, slideIndex, maxPageUnit]
117
+ );
86
118
 
87
119
  useFocusEffect(
88
120
  useCallback(() => {
@@ -136,6 +168,12 @@ const MyUnit = ({ refreshing }) => {
136
168
  [navigation]
137
169
  );
138
170
 
171
+ const goToSelectUnit = useCallback(() => {
172
+ navigation.navigate(Routes.UnitStack, {
173
+ screen: Routes.GoToDetailUnit,
174
+ });
175
+ }, [navigation]);
176
+
139
177
  const _renderItem = useCallback(
140
178
  ({ item, index }) => {
141
179
  const paddingLeft = index === 0 ? 0 : 8;
@@ -176,15 +214,15 @@ const MyUnit = ({ refreshing }) => {
176
214
  );
177
215
 
178
216
  useEffect(() => {
179
- myUnits?.length &&
180
- preloadImagesFromUnits(myUnits, STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_UNITS);
181
- }, [myUnits]);
182
-
183
- useEffect(() => {
217
+ let pageParam = page;
218
+ if (refreshing) {
219
+ pageParam = Math.floor(slideIndex / PAGE_SIZE) + 1;
220
+ }
184
221
  if (isFocused || refreshing) {
185
- fetchMyUnitDashboard();
222
+ fetchMyUnitDashboard(pageParam);
186
223
  }
187
- }, [fetchMyUnitDashboard, isFocused, refreshing]);
224
+ // eslint-disable-next-line react-hooks/exhaustive-deps
225
+ }, [fetchMyUnitDashboard, page, isFocused, refreshing]);
188
226
 
189
227
  useEffect(() => {
190
228
  if (isDeleteUnitSuccessFully || previousMyUnits?.length > myUnits?.length) {
@@ -201,9 +239,18 @@ const MyUnit = ({ refreshing }) => {
201
239
  return (
202
240
  <>
203
241
  <Section style={styles.boxTxtMyUnit}>
204
- <Text style={styles.unitsHeading} type="H5" color={Colors.Gray8}>
205
- {t('text_my_units')}
206
- </Text>
242
+ <View style={styles.titleWrap}>
243
+ <Text style={styles.unitsHeading} type="H5" color={Colors.Gray8}>
244
+ {t('text_my_units')}
245
+ </Text>
246
+ <TouchableOpacity
247
+ onPress={goToSelectUnit}
248
+ style={styles.searchMenuButton}
249
+ accessibilityLabel={AccessibilityLabel.SELECT_UNIT}
250
+ >
251
+ <SearchMenu style={styles.searchMenuIcon} />
252
+ </TouchableOpacity>
253
+ </View>
207
254
  <View style={styles.container}>
208
255
  {myUnits.length ? (
209
256
  <Carousel
@@ -213,7 +260,7 @@ const MyUnit = ({ refreshing }) => {
213
260
  itemWidth={screenWidth - 32}
214
261
  renderItem={_renderItem}
215
262
  inactiveSlideScale={1}
216
- onSnapToItem={setSlideIndex}
263
+ onSnapToItem={onSnapToItem}
217
264
  />
218
265
  ) : (
219
266
  <View>
@@ -14,7 +14,7 @@ const styles = StyleSheet.create({
14
14
  alignItems: 'center',
15
15
  paddingHorizontal: 16,
16
16
  paddingBottom: 8,
17
- paddingTop: 16,
17
+ paddingTop: 8,
18
18
  },
19
19
  container: {
20
20
  flex: 1,
@@ -52,10 +52,25 @@ const styles = StyleSheet.create({
52
52
  bottom: 16,
53
53
  left: 16,
54
54
  },
55
+ titleWrap: {
56
+ flexDirection: 'row',
57
+ justifyContent: 'space-between',
58
+ alignItems: 'center',
59
+ },
55
60
  btnItem: {
56
61
  height: 121,
57
62
  marginBottom: 16,
58
63
  },
64
+ searchMenuButton: {
65
+ width: 40,
66
+ height: 40,
67
+ justifyContent: 'center',
68
+ alignItems: 'center',
69
+ },
70
+ searchMenuIcon: {
71
+ width: 22,
72
+ height: 22,
73
+ },
59
74
  });
60
75
 
61
76
  export default styles;
@@ -156,7 +156,7 @@ const styles = StyleSheet.create({
156
156
  width: '100%',
157
157
  flexDirection: 'row',
158
158
  alignItems: 'center',
159
- marginTop: 16,
159
+ marginTop: 8,
160
160
  },
161
161
  spaceBetween: {
162
162
  justifyContent: 'space-between',
@@ -18,7 +18,7 @@ import IconComponent from '../IconComponent';
18
18
  import ItemDeviceWrapper from './ItemDeviceWrapper';
19
19
 
20
20
  const ItemDevice = memo(
21
- ({ description, title, sensor, unit, station, wrapStyle }) => {
21
+ ({ description, title, sensor, unit, station, goToDetail, wrapStyle }) => {
22
22
  const t = useTranslations();
23
23
  const navigation = useNavigation();
24
24
  const { is_managed_by_backend, device_type, icon_kit, icon, quick_action } =
@@ -44,6 +44,14 @@ const ItemDevice = memo(
44
44
  });
45
45
  }, [navigation, sensor, station, title, unit]);
46
46
 
47
+ const onPress = useCallback(() => {
48
+ if (goToDetail) {
49
+ goToDetail();
50
+ } else {
51
+ goToSensorDisplay();
52
+ }
53
+ }, [goToDetail, goToSensorDisplay]);
54
+
47
55
  const textConnected = useMemo(() => {
48
56
  if (is_managed_by_backend) {
49
57
  if (
@@ -80,10 +88,11 @@ const ItemDevice = memo(
80
88
  device={sensor}
81
89
  unit={unit}
82
90
  station={station}
91
+ goToDetail={goToDetail}
83
92
  wrapStyle={wrapStyle}
84
93
  >
85
94
  <View style={styles.boxIcon}>
86
- <TouchableOpacity onPress={goToSensorDisplay}>
95
+ <TouchableOpacity onPress={onPress}>
87
96
  <IconComponent icon={icon_kit || icon} />
88
97
  </TouchableOpacity>
89
98
  {canRenderQuickAction && (
@@ -94,7 +103,7 @@ const ItemDevice = memo(
94
103
  />
95
104
  )}
96
105
  </View>
97
- <TouchableOpacity onPress={goToSensorDisplay}>
106
+ <TouchableOpacity onPress={onPress}>
98
107
  <Text
99
108
  numberOfLines={1}
100
109
  semibold
@@ -17,7 +17,7 @@ import {
17
17
  } from '../../configs/Constants';
18
18
 
19
19
  const ItemDeviceWrapper = memo(
20
- ({ device, unit, station, wrapStyle, children }) => {
20
+ ({ device, unit, station, goToDetail, wrapStyle, children }) => {
21
21
  const navigation = useNavigation();
22
22
 
23
23
  const {
@@ -42,6 +42,14 @@ const ItemDeviceWrapper = memo(
42
42
  });
43
43
  }, [navigation, device, station, unit]);
44
44
 
45
+ const onPress = useCallback(() => {
46
+ if (goToDetail) {
47
+ goToDetail();
48
+ } else {
49
+ goToSensorDisplay();
50
+ }
51
+ }, [goToDetail, goToSensorDisplay]);
52
+
45
53
  const borderColor = useMemo(() => {
46
54
  if (!!device && device?.is_managed_by_backend) {
47
55
  if (device?.device_type === DEVICE_TYPE.LG_THINQ) {
@@ -73,7 +81,7 @@ const ItemDeviceWrapper = memo(
73
81
 
74
82
  return (
75
83
  <TouchableWithoutFeedback
76
- onPress={goToSensorDisplay}
84
+ onPress={onPress}
77
85
  accessibilityLabel={`${AccessibilityLabel.SENSOR_NAME}-${device?.id}`}
78
86
  >
79
87
  <View
@@ -1,12 +1,14 @@
1
1
  import { useNavigation } from '@react-navigation/native';
2
- import { useTranslations } from '../../hooks/Common/useTranslations';
3
- import React, { memo, useCallback, useEffect, useState } from 'react';
2
+ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
4
3
  import { ScrollView, TouchableOpacity, View } from 'react-native';
5
4
 
6
5
  import { API } from '../../configs';
7
6
  import { RadioCircle, Section, ViewButtonBottom } from '../../commons';
7
+ import { useTranslations } from '../../hooks/Common/useTranslations';
8
8
  import { axiosGet } from '../../utils/Apis/axios';
9
+ import { convertToSlug } from '../../utils/Functions/Search';
9
10
  import Text from '../../commons/Text';
11
+ import { Search } from '../../commons/DevMode';
10
12
  import AccessibilityLabel from '../../configs/AccessibilityLabel';
11
13
  import styles from './styles';
12
14
  import { HeaderCustom } from '../Header';
@@ -48,11 +50,20 @@ const SelectUnit = ({ title, subTitle, onPressNext }) => {
48
50
  const t = useTranslations();
49
51
  const { goBack } = useNavigation();
50
52
  const [selectedIndex, setSelectedIndex] = useState(-1);
53
+ const [search, setSearch] = useState('');
51
54
  const [units, setUnits] = useState([]);
52
55
 
56
+ const filterUnits = useMemo(() => {
57
+ setSelectedIndex(-1);
58
+ return units.filter((unit) => {
59
+ const text = convertToSlug(search);
60
+ return convertToSlug(unit.name).includes(text);
61
+ });
62
+ }, [units, search]);
63
+
53
64
  const onRightClick = useCallback(() => {
54
- onPressNext(units[selectedIndex]);
55
- }, [onPressNext, selectedIndex, units]);
65
+ onPressNext(filterUnits[selectedIndex]);
66
+ }, [onPressNext, selectedIndex, filterUnits]);
56
67
 
57
68
  useEffect(() => {
58
69
  (async () => {
@@ -66,6 +77,7 @@ const SelectUnit = ({ title, subTitle, onPressNext }) => {
66
77
  }
67
78
  })();
68
79
  }, []);
80
+
69
81
  return (
70
82
  <View
71
83
  style={styles.container}
@@ -73,6 +85,7 @@ const SelectUnit = ({ title, subTitle, onPressNext }) => {
73
85
  >
74
86
  <HeaderCustom title={title} isShowSeparator />
75
87
  <Text style={styles.subtitle}>{subTitle}</Text>
88
+ <Search onSearch={setSearch} />
76
89
  <View style={styles.contentContainer}>
77
90
  <ScrollView
78
91
  style={styles.scrollContainer}
@@ -80,13 +93,14 @@ const SelectUnit = ({ title, subTitle, onPressNext }) => {
80
93
  scrollIndicatorInsets={{ right: 1 }}
81
94
  >
82
95
  <Section type={'border'}>
83
- {units.map((item, index) => (
96
+ {filterUnits.map((item, index) => (
84
97
  <UnitItem
85
98
  key={item?.id?.toString()}
86
99
  item={item}
87
100
  index={index}
88
101
  setSelectedIndex={setSelectedIndex}
89
102
  selectedIndex={selectedIndex}
103
+ accessibilityLabel={AccessibilityLabel.ITEM_UNIT}
90
104
  />
91
105
  ))}
92
106
  </Section>
@@ -28,7 +28,6 @@ const styles = StyleSheet.create({
28
28
  lineHeight: 22,
29
29
  marginTop: 16,
30
30
  marginLeft: 16,
31
- marginBottom: 16,
32
31
  },
33
32
  row: {
34
33
  flex: 1,
@@ -3,14 +3,19 @@ import { getTemplate } from './';
3
3
  import ItemDeviceWrapper from '../../Device/ItemDeviceWrapper';
4
4
 
5
5
  export const DeviceTemplate = memo(
6
- ({ device, stationItem, unit, station, key }) => {
6
+ ({ device, stationItem, unit, station, goToDetail }) => {
7
7
  const Template = getTemplate(stationItem);
8
8
  if (!Template) {
9
9
  return null;
10
10
  }
11
11
  return (
12
- <ItemDeviceWrapper device={device} unit={unit} station={station}>
13
- <Template.Display device={device} stationItem={stationItem} key={key} />
12
+ <ItemDeviceWrapper
13
+ device={device}
14
+ unit={unit}
15
+ station={station}
16
+ goToDetail={goToDetail}
17
+ >
18
+ <Template.Display device={device} stationItem={stationItem} />
14
19
  </ItemDeviceWrapper>
15
20
  );
16
21
  }
@@ -34,5 +34,6 @@ export const styles = StyleSheet.create({
34
34
  width: '100%',
35
35
  display: 'flex',
36
36
  height: 200,
37
+ marginBottom: 16,
37
38
  },
38
39
  });
@@ -5,5 +5,6 @@ export const styles = {
5
5
  wrapper: {
6
6
  width: '100%',
7
7
  display: 'flex',
8
+ marginBottom: 16,
8
9
  },
9
10
  };
@@ -27,6 +27,9 @@ const API = {
27
27
  END_DEVICES_STATUS: (id) =>
28
28
  `/property_manager/units/${id}/end_devices_status/`,
29
29
  CHANGE_OWNER: (id) => `/property_manager/units/${id}/change_owner/`,
30
+ MY_DASHBOARD_DEVICES: () => '/property_manager/units/my_dashboard_devices/',
31
+ DASHBOARD_DEVICES: (id) =>
32
+ `/property_manager/units/${id}/dashboard_devices/`,
30
33
  FAVOURITE_DEVICES: (id) =>
31
34
  `/property_manager/units/${id}/favourite_devices/`,
32
35
  DEVICES_NOT_FAVORITES: (id) =>
@@ -38,6 +41,10 @@ const API = {
38
41
  STAR_AUTOMATE_SCRIPTS: (id) =>
39
42
  `/property_manager/units/${id}/star_automate_scripts/`,
40
43
  },
44
+ UNIT_V2: {
45
+ MY_UNITS: (page, pageSize) =>
46
+ `/property_manager/units_v2/my_with_devices/?page=${page}&page_size=${pageSize}`,
47
+ },
41
48
  SUB_UNIT: {
42
49
  END_DEVICES_STATUS: (stationId) =>
43
50
  `/property_manager/iot_dashboard/stations_v2/${stationId}/end_devices_status/`,
@@ -75,6 +82,10 @@ const API = {
75
82
  DISPLAY_ACTIONS: (id) => `/property_manager/devices/${id}/display_actions/`,
76
83
  CHANGE_SUB_UNIT: (unit_id, station_id, id) =>
77
84
  `/property_manager/${unit_id}/sub_units/${station_id}/devices/${id}/change_sub_unit/`,
85
+ ADD_TO_DASHBOARD: (id) =>
86
+ `/property_manager/devices/${id}/add_to_dashboard/`,
87
+ REMOVE_FROM_DASHBOARD: (id) =>
88
+ `/property_manager/devices/${id}/remove_from_dashboard/`,
78
89
  ADD_TO_FAVOURITES: (id) =>
79
90
  `/property_manager/devices/${id}/add_to_favourites/`,
80
91
  REMOVE_FROM_FAVOURITES: (id) =>