@ledvance/ui-biz-bundle 1.1.167 → 1.1.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/navigation/Routers.ts +1 -0
- package/src/newModules/biorhythm/BiorhythmPage.tsx +1 -0
- package/src/newModules/energyConsumption/EditPriceSegmentContract.tsx +89 -0
- package/src/newModules/energyConsumption/EditPriceSegmentScreen.tsx +89 -0
- package/src/newModules/energyConsumption/EnergyConsumptionActions.ts +5 -1
- package/src/newModules/energyConsumption/EnergyConsumptionChart/EnergyChartSection.tsx +33 -5
- package/src/newModules/energyConsumption/EnergyConsumptionPage.tsx +228 -117
- package/src/newModules/energyConsumption/Router.ts +9 -0
- package/src/newModules/energyConsumption/SetSegmentPricesContract.tsx +423 -0
- package/src/newModules/energyConsumption/SetSegmentedPricesScreen.tsx +189 -0
- package/src/newModules/energyConsumption/component/DateSelectedItem.tsx +3 -1
- package/src/newModules/energyConsumption/component/DateSwitch.tsx +9 -3
- package/src/newModules/energyConsumption/component/EnergyModal.tsx +66 -5
- package/src/newModules/energyConsumption/component/NewBarChart.tsx +63 -5
- package/src/newModules/energyConsumption/component/VerticalDailyPricesIndicator.tsx +175 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react"
|
|
2
|
+
import { parseMinutes, PriceSegment, UnitList } from "./component/EnergyModal"
|
|
3
|
+
import { SetSegmentedPricesParams } from "./SetSegmentedPricesScreen"
|
|
4
|
+
import { useReactive } from "ahooks"
|
|
5
|
+
import { showDialog } from "@ledvance/base/src/utils/common";
|
|
6
|
+
import I18n from "@ledvance/base/src/i18n";
|
|
7
|
+
import { StyleSheet, FlatList, Image, TouchableOpacity, View, Text } from "react-native";
|
|
8
|
+
import { Utils, Popup } from "tuya-panel-kit";
|
|
9
|
+
import ThemeType from "@ledvance/base/src/config/themeType";
|
|
10
|
+
import res from "@ledvance/base/src/res";
|
|
11
|
+
import Card from "@ledvance/base/src/components/Card";
|
|
12
|
+
import Spacer from "@ledvance/base/src/components/Spacer";
|
|
13
|
+
import React from "react";
|
|
14
|
+
|
|
15
|
+
interface EditingPopupState {
|
|
16
|
+
isShow: boolean
|
|
17
|
+
editingSegmentPrice: PriceSegment | undefined
|
|
18
|
+
}
|
|
19
|
+
export interface SetSegmentedPricesUiState {
|
|
20
|
+
editingSegmentPrice: EditingPopupState,
|
|
21
|
+
segments: PriceSegment[],
|
|
22
|
+
priceUnit: string,
|
|
23
|
+
selectedSegmentprice?: PriceSegment,
|
|
24
|
+
isLoading: boolean,
|
|
25
|
+
isDataChanged: boolean,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useSetSegmentPrice = (params: SetSegmentedPricesParams, theme?: ThemeType) => {
|
|
29
|
+
const cx = Utils.RatioUtils.convertX
|
|
30
|
+
const initialSegments = useMemo(() => {
|
|
31
|
+
return (params.isGenerate ? params.dynamicData.generatePrices : params.dynamicData.intervalPrices) ?? []
|
|
32
|
+
}, [params]);
|
|
33
|
+
|
|
34
|
+
const uiState = useReactive<SetSegmentedPricesUiState>({
|
|
35
|
+
editingSegmentPrice: {
|
|
36
|
+
isShow: false,
|
|
37
|
+
editingSegmentPrice: undefined,
|
|
38
|
+
},
|
|
39
|
+
segments: initialSegments,
|
|
40
|
+
priceUnit: params.priceUnit,
|
|
41
|
+
selectedSegmentprice: undefined,
|
|
42
|
+
isLoading: false,
|
|
43
|
+
isDataChanged: false,
|
|
44
|
+
});
|
|
45
|
+
const showAddPopup = useCallback(() => {
|
|
46
|
+
uiState.editingSegmentPrice = {
|
|
47
|
+
isShow: true,
|
|
48
|
+
editingSegmentPrice: undefined,
|
|
49
|
+
}
|
|
50
|
+
}, [uiState]);
|
|
51
|
+
const showEditPopup = useCallback((item: PriceSegment) => {
|
|
52
|
+
uiState.editingSegmentPrice = {
|
|
53
|
+
isShow: true,
|
|
54
|
+
editingSegmentPrice: item,
|
|
55
|
+
};
|
|
56
|
+
}, [uiState]);
|
|
57
|
+
const hidePopup = useCallback(() => {
|
|
58
|
+
uiState.editingSegmentPrice = {
|
|
59
|
+
isShow: false,
|
|
60
|
+
editingSegmentPrice: undefined,
|
|
61
|
+
};
|
|
62
|
+
}, [uiState]);
|
|
63
|
+
const insertPriceSegment = useCallback((newInterval: PriceSegment) => {
|
|
64
|
+
const result: PriceSegment[] = [];
|
|
65
|
+
const intervals = uiState.segments;
|
|
66
|
+
for (const old of intervals) {
|
|
67
|
+
// 无交集
|
|
68
|
+
if (
|
|
69
|
+
old.endMinute <= newInterval.startMinute ||
|
|
70
|
+
old.startMinute >= newInterval.endMinute
|
|
71
|
+
) {
|
|
72
|
+
result.push(old)
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 保留左边未覆盖部分
|
|
77
|
+
if (old.startMinute < newInterval.startMinute) {
|
|
78
|
+
result.push({
|
|
79
|
+
startMinute: old.startMinute,
|
|
80
|
+
endMinute: newInterval.startMinute,
|
|
81
|
+
price: old.price,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 保留右边未覆盖部分
|
|
86
|
+
if (old.endMinute > newInterval.endMinute) {
|
|
87
|
+
result.push({
|
|
88
|
+
startMinute: newInterval.endMinute,
|
|
89
|
+
endMinute: old.endMinute,
|
|
90
|
+
price: old.price,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 插入新区间
|
|
96
|
+
result.push(newInterval)
|
|
97
|
+
|
|
98
|
+
// 排序
|
|
99
|
+
result.sort((a, b) => a.startMinute - b.startMinute)
|
|
100
|
+
|
|
101
|
+
// 合并相邻且价格相同区间
|
|
102
|
+
const merged: PriceSegment[] = []
|
|
103
|
+
|
|
104
|
+
for (const seg of result) {
|
|
105
|
+
const last = merged[merged.length - 1]
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
last &&
|
|
109
|
+
last.price === seg.price &&
|
|
110
|
+
last.endMinute === seg.startMinute
|
|
111
|
+
) {
|
|
112
|
+
last.endMinute = seg.endMinute
|
|
113
|
+
} else {
|
|
114
|
+
merged.push({ ...seg })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
uiState.segments = merged
|
|
118
|
+
uiState.isDataChanged = true
|
|
119
|
+
}, [uiState])
|
|
120
|
+
const parsePriceSegmentText = useCallback((startMinutes: number, endMinutes: number, price: number, currency: string) => {
|
|
121
|
+
const { hour: sh, minute: sm } = parseMinutes(startMinutes)
|
|
122
|
+
const { hour: eh, minute: em } = parseMinutes(endMinutes)
|
|
123
|
+
return `${sh}:${sm} - ${eh}:${em} ${price}${currency}/kWh`;
|
|
124
|
+
}, [])
|
|
125
|
+
const showAlerDialog = useCallback((content: string) => {
|
|
126
|
+
showDialog({
|
|
127
|
+
method: 'alert',
|
|
128
|
+
title: I18n.getLang('title_tips'),
|
|
129
|
+
subTitle: content,
|
|
130
|
+
onConfirm: async (_, { close }) => {
|
|
131
|
+
close()
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}, [])
|
|
135
|
+
|
|
136
|
+
const verifyEditingPriceSegment = useCallback((item: PriceSegment | undefined) => {
|
|
137
|
+
if (!item) {
|
|
138
|
+
showAlerDialog(I18n.getLang('error_end_timer_must_greater'))
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
const { startMinute, endMinute, price } = item
|
|
142
|
+
if (price == undefined || price == 0) {
|
|
143
|
+
//toast price must be greater than 0
|
|
144
|
+
// uiState.toastMessage = "price must be greater than 0"
|
|
145
|
+
showAlerDialog(I18n.getLang('error_price_must_greater_than_z'))
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
if (endMinute <= startMinute) {
|
|
149
|
+
//toast end timer must be greater than the start timer
|
|
150
|
+
// uiState.toastMessage = "end timer must be greater than the start timer"
|
|
151
|
+
showAlerDialog(I18n.getLang('error_end_timer_must_greater'))
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
return true
|
|
155
|
+
}, [])
|
|
156
|
+
const checkFullDayCoverage = (segments: PriceSegment[]): boolean => {
|
|
157
|
+
if (!segments || segments.length === 0) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const sorted = [...segments].sort((a, b) => a.startMinute - b.startMinute);
|
|
162
|
+
|
|
163
|
+
if (sorted[0].startMinute !== 0) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let currentEnd = sorted[0].endMinute;
|
|
168
|
+
|
|
169
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
170
|
+
const nextSegment = sorted[i];
|
|
171
|
+
|
|
172
|
+
if (nextSegment.startMinute !== currentEnd) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
currentEnd = nextSegment.endMinute;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return currentEnd >= 1439;
|
|
180
|
+
}
|
|
181
|
+
const verifySegmentsResult = useCallback(() => {
|
|
182
|
+
const segments = uiState.segments
|
|
183
|
+
if (!checkFullDayCoverage(segments)) {
|
|
184
|
+
showAlerDialog(I18n.getLang('error_time_period_not_full_day'))
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
if (segments.length < 2) {
|
|
188
|
+
showAlerDialog(I18n.getLang('error_time_period_single'))
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
return true
|
|
192
|
+
}, [])
|
|
193
|
+
const handleSaveClick = async (onSuccess: () => void) => {
|
|
194
|
+
if (!verifySegmentsResult()) {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
uiState.isLoading = true
|
|
198
|
+
const isSuccess = await params.onSetPriceSements(uiState.segments, uiState.priceUnit)
|
|
199
|
+
uiState.isLoading = false
|
|
200
|
+
if (isSuccess) {
|
|
201
|
+
onSuccess()
|
|
202
|
+
} else {
|
|
203
|
+
showAlerDialog(I18n.getLang('common_server_api_error'))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const colorMapRef = React.useRef<Map<number, string>>(new Map())
|
|
207
|
+
const colors = [
|
|
208
|
+
'#00D084', // green
|
|
209
|
+
'#4A90E2', // blue
|
|
210
|
+
'#F5A623', // orange
|
|
211
|
+
'#BD10E0', // purple
|
|
212
|
+
'#7ED321', // light green
|
|
213
|
+
'#F8E71C', // yellow
|
|
214
|
+
'#FF6B6B', // red
|
|
215
|
+
'#50E3C2', // teal
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
useMemo(() => {
|
|
219
|
+
initialSegments.forEach((seg, index) => {
|
|
220
|
+
if (!colorMapRef.current.has(seg.startMinute)) {
|
|
221
|
+
colorMapRef.current.set(seg.startMinute, colors[index % colors.length]);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}, [initialSegments]);
|
|
225
|
+
|
|
226
|
+
//取颜色 使用map 确保颜色的一致性与连续性
|
|
227
|
+
const getSegmentColor = useCallback((startMinute: number, _price: number): string => {
|
|
228
|
+
let color = colorMapRef.current.get(startMinute);
|
|
229
|
+
if (!color) {
|
|
230
|
+
//找出当前未被使用的颜色,保证颜色列表充分利用且不重复
|
|
231
|
+
const usedColors = new Set(
|
|
232
|
+
uiState.segments
|
|
233
|
+
.map(seg => colorMapRef.current.get(seg.startMinute))
|
|
234
|
+
.filter(Boolean)
|
|
235
|
+
);
|
|
236
|
+
color = colors.find(c => !usedColors.has(c)) || colors[colorMapRef.current.size % colors.length];
|
|
237
|
+
colorMapRef.current.set(startMinute, color);
|
|
238
|
+
}
|
|
239
|
+
return color;
|
|
240
|
+
}, [uiState.segments]);
|
|
241
|
+
|
|
242
|
+
const showCurrencyPopup = () => {
|
|
243
|
+
const { height, statusBarHeight } = Utils.RatioUtils
|
|
244
|
+
Popup.custom({
|
|
245
|
+
title: (
|
|
246
|
+
<View style={{ backgroundColor: theme?.card.head, flexDirection: 'row', height: cx(60), justifyContent: 'space-between', alignItems: 'center', borderTopLeftRadius: cx(10), borderTopRightRadius: cx(10), paddingHorizontal: cx(8) }}>
|
|
247
|
+
<TouchableOpacity onPress={() => Popup.close()}>
|
|
248
|
+
<Text style={{ color: theme?.global.secondBrand, fontSize: cx(16) }}>{I18n.getLang('auto_scan_system_cancel')}</Text>
|
|
249
|
+
</TouchableOpacity>
|
|
250
|
+
</View>
|
|
251
|
+
),
|
|
252
|
+
wrapperStyle: {
|
|
253
|
+
height: height - statusBarHeight - cx(40),
|
|
254
|
+
backgroundColor: theme?.global.background
|
|
255
|
+
},
|
|
256
|
+
footer: (<View style={{ backgroundColor: theme?.global.background }}></View>),
|
|
257
|
+
onMaskPress: () => { },
|
|
258
|
+
motionType: 'none',
|
|
259
|
+
useKeyboardView: true,
|
|
260
|
+
content: (
|
|
261
|
+
<View
|
|
262
|
+
style={{ backgroundColor: theme?.global.background, paddingHorizontal: cx(16) }}>
|
|
263
|
+
<Spacer />
|
|
264
|
+
<Card>
|
|
265
|
+
<FlatList
|
|
266
|
+
data={UnitList}
|
|
267
|
+
initialNumToRender={15}
|
|
268
|
+
renderItem={({ item }) => (
|
|
269
|
+
<TouchableOpacity
|
|
270
|
+
onPress={() => {
|
|
271
|
+
uiState.priceUnit = item
|
|
272
|
+
uiState.isDataChanged = true
|
|
273
|
+
Popup.close()
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<View style={styles.unitItem}>
|
|
277
|
+
<Text style={{ fontSize: cx(16), color: theme?.global.fontColor }}>{item}</Text>
|
|
278
|
+
{uiState.priceUnit === item && <Image
|
|
279
|
+
style={{ width: cx(16), height: cx(16) }}
|
|
280
|
+
source={{ uri: res.app_music_check }}
|
|
281
|
+
resizeMode="contain"
|
|
282
|
+
/>}
|
|
283
|
+
</View>
|
|
284
|
+
</TouchableOpacity>
|
|
285
|
+
)}
|
|
286
|
+
ItemSeparatorComponent={() => (
|
|
287
|
+
<View style={{ flex: 1, height: 1, backgroundColor: theme?.card.background }}></View>
|
|
288
|
+
)}
|
|
289
|
+
keyExtractor={item => item}
|
|
290
|
+
/>
|
|
291
|
+
</Card>
|
|
292
|
+
<Spacer />
|
|
293
|
+
</View>
|
|
294
|
+
)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const deletePriceSegment = useCallback((itemToDelete: PriceSegment) => {
|
|
299
|
+
showDialog({
|
|
300
|
+
method: 'confirm',
|
|
301
|
+
title: I18n.getLang('title_tips'),
|
|
302
|
+
subTitle: I18n.getLang('consumption_confirm_delete_period'),
|
|
303
|
+
onConfirm: (_, { close }) => {
|
|
304
|
+
close()
|
|
305
|
+
uiState.isDataChanged = true
|
|
306
|
+
uiState.segments = uiState.segments.filter(item =>
|
|
307
|
+
!(item.startMinute === itemToDelete.startMinute && item.endMinute === itemToDelete.endMinute)
|
|
308
|
+
)
|
|
309
|
+
if (uiState.selectedSegmentprice?.startMinute === itemToDelete.startMinute &&
|
|
310
|
+
uiState.selectedSegmentprice?.endMinute === itemToDelete.endMinute) {
|
|
311
|
+
uiState.selectedSegmentprice = undefined;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
}, [uiState])
|
|
316
|
+
|
|
317
|
+
const styles = StyleSheet.create(
|
|
318
|
+
{
|
|
319
|
+
cardStyle: {
|
|
320
|
+
paddingHorizontal: cx(16),
|
|
321
|
+
paddingVertical: cx(8),
|
|
322
|
+
elevation: 1
|
|
323
|
+
},
|
|
324
|
+
addPeriodCircle: {
|
|
325
|
+
justifyContent: 'center',
|
|
326
|
+
alignItems: 'center',
|
|
327
|
+
width: cx(16),
|
|
328
|
+
height: cx(16),
|
|
329
|
+
borderRadius: cx(8),
|
|
330
|
+
backgroundColor: theme?.global.brand
|
|
331
|
+
},
|
|
332
|
+
addPeriodImg: {
|
|
333
|
+
width: cx(14),
|
|
334
|
+
height: cx(14),
|
|
335
|
+
tintColor: theme?.container.background
|
|
336
|
+
},
|
|
337
|
+
scrollContainer: {
|
|
338
|
+
paddingHorizontal: cx(16),
|
|
339
|
+
},
|
|
340
|
+
currencyCardContainer: {
|
|
341
|
+
flexDirection: 'row',
|
|
342
|
+
paddingHorizontal: cx(16),
|
|
343
|
+
paddingVertical: cx(8),
|
|
344
|
+
alignItems: 'center',
|
|
345
|
+
justifyContent: 'space-between',
|
|
346
|
+
},
|
|
347
|
+
cardTitle: {
|
|
348
|
+
fontSize: cx(16),
|
|
349
|
+
fontWeight: 'bold',
|
|
350
|
+
color: theme?.global.fontColor,
|
|
351
|
+
},
|
|
352
|
+
line: {
|
|
353
|
+
height: 1,
|
|
354
|
+
position: 'absolute',
|
|
355
|
+
start: cx(4),
|
|
356
|
+
end: cx(4),
|
|
357
|
+
bottom: 0,
|
|
358
|
+
backgroundColor: theme?.textInput.line,
|
|
359
|
+
},
|
|
360
|
+
textGroup: {
|
|
361
|
+
flexDirection: 'row',
|
|
362
|
+
borderRadius: cx(4),
|
|
363
|
+
backgroundColor: theme?.textInput.background,
|
|
364
|
+
alignItems: 'center',
|
|
365
|
+
flex: 1,
|
|
366
|
+
},
|
|
367
|
+
textParent: {
|
|
368
|
+
flex: 1,
|
|
369
|
+
height: cx(44),
|
|
370
|
+
justifyContent: 'center',
|
|
371
|
+
},
|
|
372
|
+
text: {
|
|
373
|
+
marginStart: cx(16),
|
|
374
|
+
marginEnd: cx(6),
|
|
375
|
+
fontSize: cx(16),
|
|
376
|
+
color: theme?.textInput.fontColor,
|
|
377
|
+
fontFamily: 'helvetica_neue_lt_std_roman',
|
|
378
|
+
},
|
|
379
|
+
unitItem: {
|
|
380
|
+
flexDirection: 'row',
|
|
381
|
+
justifyContent: 'space-between',
|
|
382
|
+
paddingHorizontal: cx(10),
|
|
383
|
+
alignItems: 'center',
|
|
384
|
+
height: cx(40),
|
|
385
|
+
},
|
|
386
|
+
verticalIndicator: {
|
|
387
|
+
position: 'absolute',
|
|
388
|
+
top: cx(8),
|
|
389
|
+
bottom: 0,
|
|
390
|
+
left: 0,
|
|
391
|
+
width: cx(40),
|
|
392
|
+
},
|
|
393
|
+
segmentItem: {
|
|
394
|
+
position: 'relative',
|
|
395
|
+
marginTop: cx(8),
|
|
396
|
+
marginLeft: cx(24),
|
|
397
|
+
marginRight: cx(24),
|
|
398
|
+
},
|
|
399
|
+
editSegmentIndicator: {
|
|
400
|
+
width: cx(18),
|
|
401
|
+
height: cx(18),
|
|
402
|
+
marginHorizontal: cx(6),
|
|
403
|
+
},
|
|
404
|
+
deleteSegmentIndicator: {
|
|
405
|
+
position: 'absolute',
|
|
406
|
+
top: -cx(6),
|
|
407
|
+
right: -cx(6),
|
|
408
|
+
width: cx(14),
|
|
409
|
+
height: cx(14),
|
|
410
|
+
zIndex: 99,
|
|
411
|
+
elevation: 5,
|
|
412
|
+
backgroundColor: '#ffffff',
|
|
413
|
+
borderRadius: cx(12),
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
uiState, showAddPopup, showEditPopup, hidePopup, insertPriceSegment, parsePriceSegmentText,
|
|
420
|
+
verifyEditingPriceSegment, handleSaveClick, getSegmentColor, styles, showCurrencyPopup, deletePriceSegment
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, {} from "react";
|
|
2
|
+
import {Image, ScrollView, StyleSheet, Text, TouchableOpacity, View} from "react-native";
|
|
3
|
+
import {Utils} from 'tuya-panel-kit'
|
|
4
|
+
import Card from "@ledvance/base/src/components/Card";
|
|
5
|
+
import ThemeType from "@ledvance/base/src/config/themeType";
|
|
6
|
+
import res from "@ledvance/base/src/res";
|
|
7
|
+
import Spacer from "@ledvance/base/src/components/Spacer";
|
|
8
|
+
import Page from "@ledvance/base/src/components/Page";
|
|
9
|
+
import I18n from "@ledvance/base/src/i18n";
|
|
10
|
+
import {useRoute} from '@react-navigation/core';
|
|
11
|
+
import EnergyPopup, {DynamicEnergyData, PriceSegment} from './component/EnergyModal';
|
|
12
|
+
import {useSetSegmentPrice} from "./SetSegmentPricesContract";
|
|
13
|
+
import VerticalDailyPricesIndicator from "./component/VerticalDailyPricesIndicator";
|
|
14
|
+
import {useNavigation} from '@react-navigation/core'
|
|
15
|
+
|
|
16
|
+
const {withTheme} = Utils.ThemeUtils
|
|
17
|
+
const {convertX: cx} = Utils.RatioUtils
|
|
18
|
+
|
|
19
|
+
const SetSegmentedPricesScreen = (props: { theme?: ThemeType }) => {
|
|
20
|
+
const params = useRoute().params as SetSegmentedPricesParams
|
|
21
|
+
const navigation = useNavigation()
|
|
22
|
+
const {
|
|
23
|
+
uiState, showAddPopup, showEditPopup, hidePopup, insertPriceSegment, parsePriceSegmentText,
|
|
24
|
+
verifyEditingPriceSegment, handleSaveClick, getSegmentColor, styles, showCurrencyPopup, deletePriceSegment
|
|
25
|
+
} = useSetSegmentPrice(params, props.theme)
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Page
|
|
29
|
+
style={{position: 'relative'}}
|
|
30
|
+
backText={params.backTitle}
|
|
31
|
+
headlineText={I18n.getLang('consumption_data_field_dynamic_money')}
|
|
32
|
+
rightButtonIcon={res.ic_check}
|
|
33
|
+
rightButtonIconClick={() => {
|
|
34
|
+
handleSaveClick(() => {
|
|
35
|
+
navigation.goBack()
|
|
36
|
+
})
|
|
37
|
+
}}
|
|
38
|
+
loading={uiState.isLoading}
|
|
39
|
+
showBackDialog={uiState.isDataChanged}
|
|
40
|
+
backDialogTitle={I18n.getLang('manage_user_unsaved_changes_dialog_headline')}
|
|
41
|
+
backDialogContent={I18n.getLang('motion_detection_change_activity_dialog_description_text')}
|
|
42
|
+
>
|
|
43
|
+
<ScrollView style={styles.scrollContainer}
|
|
44
|
+
contentContainerStyle={{flexGrow: 1, paddingBottom: cx(20)}}>
|
|
45
|
+
{/* currency field*/}
|
|
46
|
+
<Card style={{elevation: cx(2)}}
|
|
47
|
+
containerStyle={styles.currencyCardContainer}>
|
|
48
|
+
<Text
|
|
49
|
+
style={styles.cardTitle}>{I18n.getLang('consumption_data_price_per_kwh_currency_headline_text')}</Text>
|
|
50
|
+
<TouchableOpacity style={{flex: 0.4}} accessibilityLabel={"TextFieldStyleButton"}
|
|
51
|
+
onPress={showCurrencyPopup}>
|
|
52
|
+
<View style={styles.textGroup}>
|
|
53
|
+
<View style={styles.textParent}>
|
|
54
|
+
<Text style={styles.text}>{uiState.priceUnit}</Text>
|
|
55
|
+
</View>
|
|
56
|
+
<View style={styles.line}/>
|
|
57
|
+
</View>
|
|
58
|
+
</TouchableOpacity>
|
|
59
|
+
</Card>
|
|
60
|
+
{/* segmented prices field*/}
|
|
61
|
+
<View style={{width: '100%', marginTop: cx(10)}}>
|
|
62
|
+
<View style={{flexDirection: 'row', width: '100%', position: 'relative', marginTop: cx(10)}}>
|
|
63
|
+
{/*Vertical indicator*/}
|
|
64
|
+
<View style={styles.verticalIndicator}>
|
|
65
|
+
<VerticalDailyPricesIndicator
|
|
66
|
+
style={{height: '100%'}}
|
|
67
|
+
data={uiState.segments}
|
|
68
|
+
selectingData={uiState.selectedSegmentprice}
|
|
69
|
+
getSegmentColor={getSegmentColor}
|
|
70
|
+
/>
|
|
71
|
+
</View>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
<View style={{flex: 1, marginLeft: cx(45)}}>
|
|
75
|
+
{/* Add Time period */}
|
|
76
|
+
<Card
|
|
77
|
+
style={[styles.cardStyle, {marginTop: cx(8), marginLeft: cx(24), marginRight: cx(24)}]}
|
|
78
|
+
onPress={showAddPopup}>
|
|
79
|
+
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
|
80
|
+
<View style={styles.addPeriodCircle}>
|
|
81
|
+
<Image source={{uri: res.ic_plus}} style={styles.addPeriodImg}></Image>
|
|
82
|
+
</View>
|
|
83
|
+
<Spacer style={{width: cx(8)}}></Spacer>
|
|
84
|
+
<Text
|
|
85
|
+
style={{color: props.theme?.global.fontColor}}>{I18n.getLang('consumption_add_time_period')}</Text>
|
|
86
|
+
</View>
|
|
87
|
+
</Card>
|
|
88
|
+
{/** Time and Price segments */}
|
|
89
|
+
<Spacer/>
|
|
90
|
+
{
|
|
91
|
+
(uiState.segments.map((item) => {
|
|
92
|
+
const {startMinute, endMinute, price} = item
|
|
93
|
+
const content = parsePriceSegmentText(startMinute, endMinute, price, uiState.priceUnit)
|
|
94
|
+
const backgroundColor = getSegmentColor(item.startMinute, item.price)
|
|
95
|
+
return (
|
|
96
|
+
<View
|
|
97
|
+
key={`${item.startMinute}-${item.endMinute}`}
|
|
98
|
+
style={styles.segmentItem}
|
|
99
|
+
>
|
|
100
|
+
{/* Segment Card*/}
|
|
101
|
+
<Card
|
|
102
|
+
style={[styles.cardStyle, {
|
|
103
|
+
backgroundColor: backgroundColor,
|
|
104
|
+
marginTop: 0,
|
|
105
|
+
marginLeft: 0,
|
|
106
|
+
marginRight: 0,
|
|
107
|
+
width: '100%',
|
|
108
|
+
}]}
|
|
109
|
+
onPress={() => {
|
|
110
|
+
console.log("outside set selectedItem ->", JSON.stringify(item))
|
|
111
|
+
uiState.selectedSegmentprice = item
|
|
112
|
+
}}>
|
|
113
|
+
<View
|
|
114
|
+
style={{flexDirection: 'row', justifyContent: 'space-between',}}>
|
|
115
|
+
<Text
|
|
116
|
+
style={{color: props.theme?.button.fontColor}}>{content}</Text>
|
|
117
|
+
<TouchableOpacity
|
|
118
|
+
style={styles.editSegmentIndicator}
|
|
119
|
+
onPress={() => {
|
|
120
|
+
showEditPopup(item)
|
|
121
|
+
}}>
|
|
122
|
+
<Image
|
|
123
|
+
style={{
|
|
124
|
+
width: '100%',
|
|
125
|
+
height: '100%',
|
|
126
|
+
tintColor: props.theme?.button.fontColor
|
|
127
|
+
}} source={{uri: res.ic_more}}/>
|
|
128
|
+
</TouchableOpacity>
|
|
129
|
+
</View>
|
|
130
|
+
|
|
131
|
+
</Card>
|
|
132
|
+
{/* Delete icon*/}
|
|
133
|
+
{uiState.selectedSegmentprice?.startMinute === item.startMinute &&
|
|
134
|
+
uiState.selectedSegmentprice?.endMinute === item.endMinute && (
|
|
135
|
+
<TouchableOpacity
|
|
136
|
+
style={styles.deleteSegmentIndicator}
|
|
137
|
+
onPress={() => {
|
|
138
|
+
deletePriceSegment(item)
|
|
139
|
+
}}>
|
|
140
|
+
<Image style={{
|
|
141
|
+
width: '100%',
|
|
142
|
+
height: '100%',
|
|
143
|
+
}} source={res.icon_delete}/>
|
|
144
|
+
</TouchableOpacity>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
</View>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
))
|
|
151
|
+
}
|
|
152
|
+
</View>
|
|
153
|
+
</View>
|
|
154
|
+
|
|
155
|
+
</View>
|
|
156
|
+
</ScrollView>
|
|
157
|
+
<EnergyPopup
|
|
158
|
+
visible={uiState.editingSegmentPrice.isShow}
|
|
159
|
+
popupType={'segment_price'}
|
|
160
|
+
title={I18n.getLang('contact_sensor_specific_settings')}
|
|
161
|
+
cancelText={I18n.getLang('auto_scan_system_cancel')}
|
|
162
|
+
confirmText={I18n.getLang('auto_scan_system_wifi_confirm')}
|
|
163
|
+
editingPriceSegment={uiState.editingSegmentPrice.editingSegmentPrice}
|
|
164
|
+
onCancel={() => {
|
|
165
|
+
hidePopup()
|
|
166
|
+
}}
|
|
167
|
+
onPriceSegmentConfirm={(segment) => {
|
|
168
|
+
if (verifyEditingPriceSegment(segment)) {
|
|
169
|
+
hidePopup()
|
|
170
|
+
if (segment) {
|
|
171
|
+
console.log("wayne_check onPriceSegmentConfirm ->", JSON.stringify(segment))
|
|
172
|
+
insertPriceSegment(segment)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
</Page>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface SetSegmentedPricesParams {
|
|
182
|
+
backTitle: string,
|
|
183
|
+
dynamicData: DynamicEnergyData,
|
|
184
|
+
priceUnit: string,
|
|
185
|
+
isGenerate: boolean,
|
|
186
|
+
onSetPriceSements: (segments: PriceSegment[], currency: string) => Promise<boolean>,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default withTheme(SetSegmentedPricesScreen)
|
|
@@ -14,6 +14,7 @@ interface DateSelectedItemProps extends PropsWithChildren<ViewProps> {
|
|
|
14
14
|
theme?: ThemeType
|
|
15
15
|
date: string,
|
|
16
16
|
dateType: DateType,
|
|
17
|
+
minDate?: Date
|
|
17
18
|
onDateChange: (string) => void
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -43,7 +44,7 @@ const getDateForDateType = (year, month, day, dateType: DateType) => {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export default withTheme(function DateSelectedItem(props: DateSelectedItemProps) {
|
|
46
|
-
const {dateType, date, onDateChange, theme} = props;
|
|
47
|
+
const {dateType, date, minDate, onDateChange, theme} = props;
|
|
47
48
|
const state = useReactive({
|
|
48
49
|
showDateModal: false,
|
|
49
50
|
date: date,
|
|
@@ -129,6 +130,7 @@ export default withTheme(function DateSelectedItem(props: DateSelectedItemProps)
|
|
|
129
130
|
mode={state.modalMode}
|
|
130
131
|
dateSortKeys={['day', 'month', 'year']}
|
|
131
132
|
maxDate={new Date()}
|
|
133
|
+
minDate={minDate}
|
|
132
134
|
style={{backgroundColor: theme?.popup.cellBg}}
|
|
133
135
|
pickerFontColor={theme?.global.fontColor}
|
|
134
136
|
onDateChange={date => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ThemeType from "@ledvance/base/src/config/themeType";
|
|
2
2
|
import DateSelectedItem from './DateSelectedItem'
|
|
3
|
-
import React, {PropsWithChildren, useCallback, useEffect} from "react";
|
|
3
|
+
import React, {PropsWithChildren, useCallback, useEffect, useMemo} from "react";
|
|
4
4
|
import {Image, TouchableOpacity, View, ViewProps} from "react-native";
|
|
5
5
|
import {Utils} from "tuya-panel-kit";
|
|
6
6
|
import {useReactive} from "ahooks";
|
|
@@ -16,11 +16,12 @@ interface DateSwitchProps extends PropsWithChildren<ViewProps> {
|
|
|
16
16
|
headlineText: string,
|
|
17
17
|
date: string,
|
|
18
18
|
dateType: DateType,
|
|
19
|
+
minDate?: string,
|
|
19
20
|
onDateChange: (string) => void
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export default withTheme(function DateSwitch(props: DateSwitchProps) {
|
|
23
|
-
const {dateType, date, onDateChange, theme, headlineText} = props;
|
|
24
|
+
const {dateType, date, minDate, onDateChange, theme, headlineText} = props;
|
|
24
25
|
const state = useReactive({
|
|
25
26
|
date: date,
|
|
26
27
|
dateType: dateType,
|
|
@@ -29,6 +30,10 @@ export default withTheme(function DateSwitch(props: DateSwitchProps) {
|
|
|
29
30
|
disableArrowRight: false,
|
|
30
31
|
});
|
|
31
32
|
|
|
33
|
+
const minDateObj = useMemo(
|
|
34
|
+
() => (minDate ? new Date(minDate) : undefined),
|
|
35
|
+
[minDate]);
|
|
36
|
+
|
|
32
37
|
useEffect(() => {
|
|
33
38
|
state.date = date;
|
|
34
39
|
state.headlineText = headlineText;
|
|
@@ -39,7 +44,7 @@ export default withTheme(function DateSwitch(props: DateSwitchProps) {
|
|
|
39
44
|
}, [date, headlineText, dateType]);
|
|
40
45
|
|
|
41
46
|
const canPreOrNextChangeDate = useCallback((datejs: dayjs.Dayjs) => {
|
|
42
|
-
const minDatejs = dayjs('2000-1-1');
|
|
47
|
+
const minDatejs = dayjs(minDateObj || '2000-1-1');
|
|
43
48
|
const nowDatejs = dayjs();
|
|
44
49
|
const year = datejs.year();
|
|
45
50
|
const month = (datejs.month() + 1).toString().padStart(2, '0');
|
|
@@ -94,6 +99,7 @@ export default withTheme(function DateSwitch(props: DateSwitchProps) {
|
|
|
94
99
|
style={{flex: 1}}
|
|
95
100
|
dateType={state.dateType}
|
|
96
101
|
date={state.date}
|
|
102
|
+
minDate={minDateObj}
|
|
97
103
|
onDateChange={date => {
|
|
98
104
|
props.onDateChange(date)
|
|
99
105
|
}}
|