@eohjsc/react-native-smart-city 0.2.79 → 0.2.83

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 (60) hide show
  1. package/package.json +4 -4
  2. package/src/commons/ActionGroup/CurtainButtonTemplate.js +32 -21
  3. package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +8 -6
  4. package/src/commons/ActionGroup/OnOffTemplate/index.js +11 -3
  5. package/src/commons/ActionGroup/OneBigButtonTemplate.js +10 -7
  6. package/src/commons/ActionGroup/OptionsDropdownActionTemplate.js +5 -2
  7. package/src/commons/ActionGroup/StatesGridActionTemplate.js +7 -3
  8. package/src/commons/ActionGroup/ThreeButtonTemplate.js +33 -24
  9. package/src/commons/ActionGroup/__test__/OnOffTemplate.test.js +18 -6
  10. package/src/commons/ActionGroup/__test__/OneBigButtonTemplate.test.js +9 -1
  11. package/src/commons/ActionGroup/__test__/OptionsDropdownTemplate.test.js +25 -13
  12. package/src/commons/ActionGroup/__test__/index.test.js +48 -14
  13. package/src/commons/Device/HorizontalBarChart.js +8 -2
  14. package/src/commons/Device/ItemDevice.js +8 -5
  15. package/src/commons/Device/LinearChart.js +1 -1
  16. package/src/commons/EmergencyButton/AlertSendConfirm.js +2 -2
  17. package/src/commons/EmergencyButton/AlertSent.js +2 -2
  18. package/src/commons/ImagePicker/__test__/ImagePicker.test.js +24 -3
  19. package/src/commons/MediaPlayerDetail/index.js +1 -0
  20. package/src/commons/SubUnit/Favorites/index.js +2 -0
  21. package/src/commons/SubUnit/ShortDetail.js +7 -1
  22. package/src/configs/API.js +2 -4
  23. package/src/configs/Constants.js +5 -0
  24. package/src/iot/RemoteControl/Internet.js +8 -1
  25. package/src/iot/RemoteControl/index.js +4 -2
  26. package/src/screens/AddCommon/SelectSubUnit.js +6 -0
  27. package/src/screens/AddNewGateway/__test__/SetupGateway.test.js +52 -0
  28. package/src/screens/AllCamera/index.js +76 -44
  29. package/src/screens/Device/EditDevice/__test__/EditDevice.test.js +2 -2
  30. package/src/screens/Device/EditDevice/index.js +2 -2
  31. package/src/screens/Device/__test__/detail.test.js +18 -11
  32. package/src/screens/Device/components/SensorConnectStatusViewHeader.js +2 -2
  33. package/src/screens/Device/components/SensorDisplayItem.js +2 -2
  34. package/src/screens/Device/detail.js +58 -20
  35. package/src/screens/Notification/__test__/NotificationItem.test.js +186 -14
  36. package/src/screens/Notification/components/NotificationItem.js +147 -3
  37. package/src/screens/Notification/index.js +4 -1
  38. package/src/screens/Notification/styles/NotificationItemStyles.js +3 -3
  39. package/src/screens/Sharing/SelectUser.js +17 -10
  40. package/src/screens/Sharing/__test__/SelectUser.test.js +73 -0
  41. package/src/screens/SubUnit/AddSubUnit.js +4 -1
  42. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +148 -0
  43. package/src/screens/Unit/Detail.js +28 -4
  44. package/src/screens/Unit/ManageUnit.js +88 -32
  45. package/src/screens/Unit/ManageUnitStyles.js +20 -0
  46. package/src/screens/Unit/SmartAccount.js +6 -2
  47. package/src/screens/Unit/Summaries.js +2 -2
  48. package/src/screens/Unit/__test__/CheckSendEmail.test.js +10 -0
  49. package/src/screens/Unit/__test__/Detail.test.js +53 -0
  50. package/src/screens/Unit/__test__/ManageUnit.test.js +69 -0
  51. package/src/screens/Unit/__test__/SmartAccount.test.js +37 -8
  52. package/src/screens/Unit/components/__test__/SharedUnit.test.js +21 -2
  53. package/src/screens/Unit/hook/useStateAlertRemove.js +1 -1
  54. package/src/screens/UnitSummary/__test__/index.test.js +3 -3
  55. package/src/screens/UnitSummary/components/RunningDevices/__test__/index.test.js +2 -2
  56. package/src/screens/UnitSummary/index.js +52 -9
  57. package/src/utils/Apis/axios.js +7 -2
  58. package/src/utils/I18n/translations/en.json +10 -1
  59. package/src/utils/I18n/translations/vi.json +10 -1
  60. package/src/{screens/Notification → utils}/Monitor.js +1 -1
@@ -11,6 +11,17 @@ import { mockSCStore } from '../../../context/mockStore';
11
11
 
12
12
  jest.mock('axios');
13
13
 
14
+ const mockNavigate = jest.fn();
15
+ jest.mock('@react-navigation/native', () => {
16
+ return {
17
+ ...jest.requireActual('@react-navigation/native'),
18
+ useRoute: jest.fn(),
19
+ useNavigation: () => ({
20
+ navigate: mockNavigate,
21
+ }),
22
+ };
23
+ });
24
+
14
25
  const mockedDispatch = jest.fn();
15
26
  jest.mock('react-redux', () => ({
16
27
  useSelector: jest.fn(),
@@ -42,6 +53,11 @@ describe('Test Manage Unit', () => {
42
53
  },
43
54
  };
44
55
 
56
+ beforeEach(() => {
57
+ mockNavigate.mockClear();
58
+ axios.get.mockClear();
59
+ });
60
+
45
61
  const getElement = (instance) => {
46
62
  const changeName = instance.findAll(
47
63
  (item) => item.props.testID === TESTID.MANAGE_UNIT_CHANGE_NAME
@@ -207,4 +223,57 @@ describe('Test Manage Unit', () => {
207
223
  spyToast.mockReset();
208
224
  spyToast.mockRestore();
209
225
  });
226
+ test('test onPress onUpdateImage', async () => {
227
+ await act(async () => {
228
+ tree = create(wrapComponent(route));
229
+ });
230
+ const instance = tree.root;
231
+
232
+ const imagePicker = instance.find(
233
+ (item) => item.props.testID === TESTID.MANAGE_UNIT_IMAGE_PICKER
234
+ );
235
+
236
+ const handleChoosePhoto = instance.findAll(
237
+ (item) => item.props.testID === TESTID.MANAGE_UNIT_CHANGE_PHOTO
238
+ );
239
+
240
+ act(() => {
241
+ handleChoosePhoto[0].props.onPress();
242
+ handleChoosePhoto[1].props.onPress();
243
+ imagePicker.props.setImageUrl('abc');
244
+ });
245
+
246
+ expect(axios.patch).toBeCalled();
247
+ expect(imagePicker.props.showImagePicker).toBeTruthy();
248
+ });
249
+ test('test onPress goSelectLocation', async () => {
250
+ await act(async () => {
251
+ tree = create(wrapComponent(route));
252
+ });
253
+ const instance = tree.root;
254
+
255
+ const buttonWrapper = instance.find(
256
+ (item) => item.props.testID === TESTID.MANAGE_UNIT_CHANGE_LOCATION
257
+ );
258
+
259
+ act(() => {
260
+ buttonWrapper.props.onPress();
261
+ });
262
+ expect(mockNavigate).toBeCalled();
263
+ });
264
+ test('test onPress goToManageSubUnit', async () => {
265
+ await act(async () => {
266
+ tree = create(wrapComponent(route));
267
+ });
268
+ const instance = tree.root;
269
+
270
+ const buttonWrapper = instance.find(
271
+ (item) => item.props.testID === TESTID.MANAGE_UNIT_GO_TO_SUBUNIT
272
+ );
273
+
274
+ act(() => {
275
+ buttonWrapper.props.onPress();
276
+ });
277
+ expect(mockNavigate).toBeCalled();
278
+ });
210
279
  });
@@ -6,21 +6,22 @@ import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
7
  import ListSmartAccount from '../SmartAccount';
8
8
  import { SmartAccountItem } from '../SmartAccountItem';
9
- import { MenuActionMore } from '../../../commons';
9
+ import { AlertAction, MenuActionMore } from '../../../commons';
10
+ import Routes from '../../../utils/Route';
10
11
 
11
12
  const wrapComponent = (route, navigation) => (
12
13
  <SCProvider initState={mockSCStore({})}>
13
- <ListSmartAccount />
14
+ <ListSmartAccount route={route} />
14
15
  </SCProvider>
15
16
  );
16
17
 
17
- const mockedNavigate = jest.fn();
18
-
18
+ const mockNavigate = jest.fn();
19
19
  jest.mock('@react-navigation/native', () => {
20
20
  return {
21
21
  ...jest.requireActual('@react-navigation/native'),
22
+ useRoute: jest.fn(),
22
23
  useNavigation: () => ({
23
- goBack: mockedNavigate,
24
+ navigate: mockNavigate,
24
25
  }),
25
26
  };
26
27
  });
@@ -35,8 +36,13 @@ jest.mock('react', () => {
35
36
  jest.mock('axios');
36
37
  describe('Test SmartAccount', () => {
37
38
  let tree;
39
+ let route = {
40
+ params: {
41
+ unitId: 1,
42
+ },
43
+ };
38
44
  test('test render SmartAccount', async () => {
39
- axios.get.mockImplementationOnce(() => ({
45
+ const reponse = {
40
46
  status: 200,
41
47
  data: [
42
48
  {
@@ -58,14 +64,25 @@ describe('Test SmartAccount', () => {
58
64
  username: 'loitest3',
59
65
  },
60
66
  ],
61
- }));
67
+ };
68
+ axios.get.mockImplementationOnce(() => reponse);
62
69
  await act(async () => {
63
- tree = await renderer.create(wrapComponent());
70
+ tree = await renderer.create(wrapComponent(route));
64
71
  });
65
72
  const instance = tree.root;
66
73
 
67
74
  const smartAccountItem = instance.findAllByType(SmartAccountItem);
68
75
  expect(smartAccountItem.length).toEqual(3);
76
+ act(() => {
77
+ smartAccountItem[0].props.gotoSmartAccountDetail(reponse.data[0]);
78
+ smartAccountItem[0].props.onShowMenuMore(reponse.data[0]);
79
+ });
80
+ expect(mockNavigate).toBeCalledWith(Routes.ListDeviceSmartAccount, {
81
+ username: reponse.data[0].username,
82
+ brand: reponse.data[0].brand,
83
+ smart_account_id: reponse.data[0].id,
84
+ unit_id: route.params.unitId,
85
+ });
69
86
  });
70
87
 
71
88
  test('test render SmartAccountItem', async () => {
@@ -79,4 +96,16 @@ describe('Test SmartAccount', () => {
79
96
  });
80
97
  expect(menuActionMore.props.isVisible).toEqual(false);
81
98
  });
99
+
100
+ test('test render AlertAction', async () => {
101
+ await act(async () => {
102
+ tree = await renderer.create(wrapComponent());
103
+ });
104
+ const instance = tree.root;
105
+ const alertAction = instance.findByType(AlertAction);
106
+ await act(async () => {
107
+ alertAction.props.leftButtonClick();
108
+ });
109
+ expect(alertAction).toBeDefined();
110
+ });
82
111
  });
@@ -12,10 +12,15 @@ import { mockSCStore } from '../../../../context/mockStore';
12
12
 
13
13
  jest.mock('axios');
14
14
 
15
+ const mockRenewItem = jest.fn();
15
16
  const wrapComponent = (item, navigation) => (
16
17
  <SCProvider initState={mockSCStore({})}>
17
- <SharedUnit item={item} navigation={navigation} />
18
- );
18
+ <SharedUnit
19
+ item={item}
20
+ navigation={navigation}
21
+ renewItem={mockRenewItem}
22
+ index={0}
23
+ />
19
24
  </SCProvider>
20
25
  );
21
26
 
@@ -55,6 +60,13 @@ describe('Test SharedUnit', () => {
55
60
 
56
61
  test('test create SharedUnit unit is not pin, not star', async () => {
57
62
  const navigation = useNavigation();
63
+ const response = {
64
+ status: 200,
65
+ success: true,
66
+ };
67
+ axios.post.mockImplementation(async () => {
68
+ return response;
69
+ });
58
70
 
59
71
  await act(async () => {
60
72
  tree = await create(wrapComponent(item, navigation));
@@ -100,6 +112,13 @@ describe('Test SharedUnit', () => {
100
112
  const navigation = useNavigation();
101
113
  item.is_pin = true;
102
114
  item.is_star = true;
115
+ const response = {
116
+ status: 200,
117
+ success: true,
118
+ };
119
+ axios.post.mockImplementation(async () => {
120
+ return response;
121
+ });
103
122
 
104
123
  await act(async () => {
105
124
  tree = await create(wrapComponent(item, navigation));
@@ -7,7 +7,7 @@ export const useStateAlertRemove = () => {
7
7
  visible: false,
8
8
  title: t('remove_account'),
9
9
  message: '',
10
- leftButton: t('yes_remove'),
10
+ leftButton: t('remove'),
11
11
  rightButton: t('cancel'),
12
12
  });
13
13
  const hideAlertAction = useCallback(() => {
@@ -43,10 +43,10 @@ describe('Test UnitSummary', () => {
43
43
  Date.now = jest.fn(() => new Date('2021-01-24T12:00:00.000Z'));
44
44
  route = {
45
45
  params: {
46
- unit: {
46
+ unitData: {
47
47
  id: 1,
48
48
  },
49
- summary: {
49
+ summaryData: {
50
50
  id: 1,
51
51
  name: '',
52
52
  screen: Routes.AirQuality,
@@ -155,7 +155,7 @@ describe('Test UnitSummary', () => {
155
155
 
156
156
  list_value.forEach((value, index) => {
157
157
  test(`create Unit Summarty Detail ${value}`, async () => {
158
- route.params.summary.screen = value;
158
+ route.params.summaryData.screen = value;
159
159
  await act(async () => {
160
160
  tree = await create(wrapComponent(route));
161
161
  });
@@ -113,10 +113,10 @@ describe('test RunningDevices', () => {
113
113
  });
114
114
  expect(mockedNavigate).toBeCalledWith('DeviceDetail', {
115
115
  isGGHomeConnected: false,
116
- sensor: summaryDetail.devices[0],
116
+ sensorData: summaryDetail.devices[0],
117
117
  station: 'station',
118
118
  title: undefined,
119
- unit: unit,
119
+ unitData: unit,
120
120
  });
121
121
  });
122
122
  });
@@ -23,15 +23,17 @@ import { timeDifference } from '../../utils/Converter/time';
23
23
 
24
24
  const UnitSummary = memo(({ route }) => {
25
25
  const t = useTranslations();
26
- const { unit, summary } = route.params;
26
+ const { unitData, unitId, summaryData, summaryId } = route.params;
27
27
  const [summaryDetail, setSummaryDetail] = useState({});
28
+ const [unit, setUnit] = useState(unitData || { id: unitId });
29
+ const [summary, setSummary] = useState(summaryData || { id: summaryId });
28
30
 
29
31
  const [loading, setLoading] = useState(false);
30
32
  const [lastUpdated, setLastUpdated] = useState();
31
33
  const navigation = useNavigation();
32
34
 
33
35
  const getComponentAndGuide = useCallback(() => {
34
- switch (summary.screen) {
36
+ switch (summary?.screen) {
35
37
  case Routes.AirQuality:
36
38
  return {
37
39
  guideName: Routes.AQIGuide,
@@ -66,12 +68,17 @@ const UnitSummary = memo(({ route }) => {
66
68
  return {
67
69
  componentName: ThreePhasePowerConsumption,
68
70
  };
71
+ default:
72
+ return {
73
+ guideName: null,
74
+ componentName: null,
75
+ };
69
76
  }
70
- }, [summary.screen]);
77
+ }, [summary?.screen]);
71
78
 
72
79
  const fetchSummaryDetail = useCallback(async () => {
73
80
  const { success, data } = await axiosGet(
74
- API.UNIT.UNIT_SUMMARY_DETAIL(unit.id, summary.id),
81
+ API.UNIT.UNIT_SUMMARY_DETAIL(unit?.id, summary?.id),
75
82
  {},
76
83
  true
77
84
  );
@@ -80,7 +87,41 @@ const UnitSummary = memo(({ route }) => {
80
87
  setSummaryDetail(data.data);
81
88
  setLastUpdated(data.data.last_updated);
82
89
  }
83
- }, [summary.id, unit.id]);
90
+ }, [summary?.id, unit?.id]);
91
+
92
+ const fetchUnitDetail = useCallback(async () => {
93
+ const { success, data } = await axiosGet(
94
+ API.UNIT.UNIT_DETAIL(unit?.id),
95
+ {},
96
+ true
97
+ );
98
+ if (success) {
99
+ setUnit(data);
100
+ }
101
+ }, [unit.id]);
102
+
103
+ const fetchUnitSummary = useCallback(async () => {
104
+ const { success, data } = await axiosGet(
105
+ API.UNIT.UNIT_SUMMARY(unit?.id),
106
+ {}
107
+ );
108
+ if (success) {
109
+ const newData = data.filter((item) => item.id === summaryId);
110
+ setSummary(newData[0] || {});
111
+ }
112
+ }, [summaryId, unit?.id]);
113
+
114
+ useEffect(() => {
115
+ if (!unitData && unitId) {
116
+ fetchUnitDetail();
117
+ }
118
+ }, [fetchUnitDetail, unitData, unitId]);
119
+
120
+ useEffect(() => {
121
+ if (!summaryData && summaryId) {
122
+ fetchUnitSummary();
123
+ }
124
+ }, [fetchUnitSummary, summaryData, summaryId]);
84
125
 
85
126
  useEffect(() => {
86
127
  setLoading(true);
@@ -99,8 +140,8 @@ const UnitSummary = memo(({ route }) => {
99
140
  }, [fetchSummaryDetail]);
100
141
 
101
142
  const UnitSummaryDetail = getComponentAndGuide();
102
- const ComponentName = UnitSummaryDetail.componentName;
103
- const GuideName = UnitSummaryDetail.guideName;
143
+ const ComponentName = UnitSummaryDetail?.componentName;
144
+ const GuideName = UnitSummaryDetail?.guideName;
104
145
 
105
146
  const lastUpdatedStr = lastUpdated
106
147
  ? `${t('last_updated')} ${timeDifference(moment(), moment(lastUpdated))}`
@@ -109,7 +150,7 @@ const UnitSummary = memo(({ route }) => {
109
150
  return (
110
151
  <View style={[styles.container]}>
111
152
  <WrapHeaderScrollable
112
- title={summary.name}
153
+ title={summary?.name}
113
154
  subTitle={lastUpdatedStr}
114
155
  rightComponent={
115
156
  GuideName ? (
@@ -130,7 +171,9 @@ const UnitSummary = memo(({ route }) => {
130
171
  loading={loading}
131
172
  onRefresh={onRefresh}
132
173
  >
133
- <ComponentName summaryDetail={summaryDetail} unit={unit} />
174
+ {ComponentName && (
175
+ <ComponentName summaryDetail={summaryDetail} unit={unit} />
176
+ )}
134
177
  </WrapHeaderScrollable>
135
178
  </View>
136
179
  );
@@ -151,7 +151,10 @@ export async function axiosPatch(...options) {
151
151
  export async function axiosDelete(...options) {
152
152
  return await axiosCall('delete', ...options);
153
153
  }
154
-
154
+ const convertFilenameImage = (filename) => {
155
+ const filenameConverted = filename?.replace(/HEIC/g, 'jpg');
156
+ return filename || filenameConverted;
157
+ };
155
158
  export function createFormData(data, list_file_field) {
156
159
  const formData = new FormData();
157
160
 
@@ -165,7 +168,9 @@ export function createFormData(data, list_file_field) {
165
168
  }
166
169
 
167
170
  formData.append(key, {
168
- name: item.filename || item.path?.split('/').pop(),
171
+ name: convertFilenameImage(
172
+ item.filename || item.path?.split('/').pop()
173
+ ),
169
174
  type: item.mime,
170
175
  uri: item.path,
171
176
  });
@@ -668,10 +668,18 @@
668
668
  "text_notification_content_pay_fine_successfully": "Your violation **%{booking}** has been paid successfully.",
669
669
  "text_notification_content_pay_fine_and_extend_successfully": "Your violation **%{booking_id_old}** has been paid successfully. The new parking session **%{booking_id_new}** is now extended until **%{leave_time}**.",
670
670
  "text_notification_content_stop_violation_free_parking_zone": "Free parking zone. You have an unpaid violation. Please complete your fine.",
671
+ "text_notification_content_air_quality_high": "The Air Quality index at **%{unit_name}** is **%{status}**. Wear a mask and reduce outdoor exercise.",
672
+ "text_notification_content_uv_index_high": "The UV index at **%{unit_name}** is **%{status}**. Reduce time in the sun and protect your skin, eye.",
673
+ "text_notification_content_pH_index_high": "The pH index at **%{unit_name}** is **%{status}**. Water is not safe to drink.",
674
+ "text_notification_content_clo_high": "The Chlorine residual at **%{unit_name}** is **%{status}**. Water is not safe to drink.",
675
+ "text_notification_content_turbility_high": "The Turbility at **%{unit_name}** is **%{status}**. Water is not safe to drink.",
676
+ "text_notification_content_smoke": "Smoke appears in **%{unit_name}**. Please check your home and call the rescue team if there is a fire.",
677
+ "text_notification_content_fire": "There is a fire at **%{unit_name}**, Please move out of the house immediately and call the rescue team.",
671
678
  "text_notification_content_remove_unit_to_owner": "Unit **%{unit_name}** has been removed successfully.",
672
679
  "text_notification_content_remove_unit_to_member": "Unit **%{unit_name}** has been removed by **%{unit_owner_name}**. You cannot access to this unit anymore.",
673
680
  "text_notification_content_remove_member": "You were remove from **%{unit_name}** by **%{unit_owner_name}**. You cannot access to this unit anymore.",
674
681
  "text_notification_content_member_leave_unit": "**%{member_name}** has left **%{unit_name}**.",
682
+ "text_notification_content_rename_unit": "Unit **%{old_unit_name}** has been renamed to **%{new_unit_name}** by **%{owner_name}**.",
675
683
  "this_spot_does_not_exsit": "This spot does not exist",
676
684
  "please_scan_again_or_contact_the_parking_manager": "Please scan again or contact the parking manager",
677
685
  "this_spot_does_not_support_to_scan": "This spot does not support to scan",
@@ -894,8 +902,9 @@
894
902
  "location_permission_required_wifi_message": "This app needs location permission as this is required to scan for wifi networks.",
895
903
  "deny": "DENY",
896
904
  "allow": "ALLOW",
905
+ "avatar" : "Avatar",
897
906
  "error_share_permission": "{text} {data} does not exist!",
898
907
  "text_phone_share_permission": "The phone",
899
908
  "text_email_share_permission": "Email",
900
- "yes_remove": "Yes,remove"
909
+ "icon_unit": "Icon unit"
901
910
  }
@@ -676,10 +676,18 @@
676
676
  "text_notification_content_pay_fine_successfully": "Đỗ xe vi phạm **%{booking}** của bạn đã được thanh toán.",
677
677
  "text_notification_content_pay_fine_and_extend_successfully": "Đỗ xe vi phạm **%{oldbooking}** của bạn đã được thanh toán thành công. Phiên đỗ xe mới **%{newbooking}** sẽ kết thúc vào lúc **%{time}**.",
678
678
  "text_notification_content_stop_violation_free_parking_zone": "Thời gian đậu xe miễn phí. Bạn có một vi phạm chưa được thanh toán. Vui lòng hoàn thành tiền phạt của bạn.",
679
+ "text_notification_content_air_quality_high": "Chỉ số AQI tại **%{unit_name}** đang ở mức **%{status}**. Đeo khẩu trang và giảm các hoạt động ngoài trời để bảo vệ sức khoẻ của bạn.",
680
+ "text_notification_content_uv_index_high": "Chỉ số UV tại **%{unit_name}** đang ở mức **%{status}**. Giảm thời gian ngoài trời và bảo vệ da, mắt khỏi ánh nắng mặt trời.",
681
+ "text_notification_content_pH_index_high": "Độ pH của nước tại **%{unit_name}** đang ở mức **%{status}**. Nước không đảm bảo an toàn để uống.",
682
+ "text_notification_content_clo_high": "Độ clo của nước tại **%{unit_name}** đang ở mức **%{status}**. Nước không đảm bảo an toàn để uống.",
683
+ "text_notification_content_turbility_high": "Độ đục của nước tại **%{unit_name}** đang ở mức **%{status}**. Nước không đảm bảo an toàn để uống.",
684
+ "text_notification_content_smoke": "Xuất hiện khói tại **%{unit_name}**. Vui lòng kiểm tra nhà của bạn và gọi cho đội cứu hộ nếu xảy ra hỏa hoạn.",
685
+ "text_notification_content_fire": "Có đám cháy tại **%{unit_name}**, Vui lòng di chuyển ra khỏi nhà ngay lập tức và gọi cho đội cứu hộ.",
679
686
  "text_notification_content_remove_unit_to_owner": "Địa điểm **%{unit_name}** vừa được xoá thành công.",
680
687
  "text_notification_content_remove_unit_to_member": "Địa điểm **%{unit_name}** vừa được xoá bởi **%{unit_owner_name}**. Bạn không thể truy cập vào địa điểm này được nữa.",
681
688
  "text_notification_content_remove_member": "Bạn vừa được xoá khỏi **%{unit_name}** bởi **%{unit_owner_name}**. Bạn không thể truy cập vào địa điểm này được nữa.",
682
689
  "text_notification_content_member_leave_unit": "**%{member_name}** vừa rời khỏi **%{unit_name}**.",
690
+ "text_notification_content_rename_unit": "Địa điểm **%{old_unit_name}** vừa được đổi tên thành **%{new_unit_name}** bởi **%{owner_name}**.",
683
691
  "this_spot_does_not_exsit": "Vị trí đỗ này không tồn tại",
684
692
  "please_scan_again_or_contact_the_parking_manager": "Vui lòng quét lại hoặc liên hệ với người quản lý bãi đậu xe",
685
693
  "this_spot_does_not_support_to_scan": "Vị trí đỗ này không hỗ trợ quét",
@@ -897,8 +905,9 @@
897
905
  "location_permission_required_wifi_message": "Ứng dụng này cần có quyền định vị vì ứng dụng này được yêu cầu để quét các mạng Wi-Fi.",
898
906
  "deny": "Từ chối",
899
907
  "allow": "Cho phép",
908
+ "avatar" : "Ảnh đại diện",
900
909
  "error_share_permission": "{text} {data} không tồn tại!",
901
910
  "text_phone_share_permission": "Số điện thoại",
902
911
  "text_email_share_permission": "Email",
903
- "yes_remove": "Có,loại bỏ"
912
+ "icon_unit": "Ảnh đại diện địa điểm"
904
913
  }
@@ -1,4 +1,4 @@
1
- import { getPusher, destroyPusher } from '../../utils/Pusher';
1
+ import { getPusher, destroyPusher } from './Pusher';
2
2
 
3
3
  // eslint-disable-next-line promise/prefer-await-to-callbacks
4
4
  export const watchNotificationData = (user, callback) => {