@fto-consult/expo-ui 2.29.3 → 2.31.0
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 +2 -2
- package/src/components/BackToTop/index.js +3 -2
- package/src/components/BottomSheet/Menu.js +7 -7
- package/src/components/CountUp/index.js +8 -1
- package/src/components/Datagrid/Accordion/FilterContent.js +24 -0
- package/src/components/Datagrid/Accordion/Filters.js +47 -40
- package/src/components/Datagrid/Accordion/index.js +4 -1
- package/src/components/Datagrid/Common/Common.js +118 -21
- package/src/components/Datagrid/Dashboard/index.js +143 -0
- package/src/components/Datagrid/Footer/Footer.js +2 -2
- package/src/components/Datagrid/IndexComponent.js +5 -1
- package/src/components/Datagrid/Table/index.js +4 -1
- package/src/components/Datagrid/index.js +2 -1
- package/src/components/Date/DateTime.js +0 -1
- package/src/components/Date/PeriodAction.js +1 -1
- package/src/components/Filter/index.js +3 -3
- package/src/components/Menu/Item.js +2 -2
- package/src/components/Menu/utils.js +5 -1
- package/src/layouts/DatabaseStatistics/DatabaseStatistic.js +151 -38
- package/src/layouts/DatabaseStatistics/index.js +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fto-consult/expo-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "Bibliothèque de composants UI Expo,react-native",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@emotion/native": "^11.10.0",
|
|
62
62
|
"@expo/html-elements": "^0.2.0",
|
|
63
63
|
"@expo/vector-icons": "^13.0.0",
|
|
64
|
-
"@fto-consult/common": "^1.25.
|
|
64
|
+
"@fto-consult/common": "^1.25.17",
|
|
65
65
|
"@gorhom/portal": "^1.0.14",
|
|
66
66
|
"@react-native-async-storage/async-storage": "~1.17.3",
|
|
67
67
|
"@react-native-community/datetimepicker": "6.5.2",
|
|
@@ -2,6 +2,7 @@ import Fab from "$ecomponents/Fab";
|
|
|
2
2
|
import {isObj,defaultStr,defaultVal,defaultObj} from "$utils";
|
|
3
3
|
import React from "$react";
|
|
4
4
|
import PropTypes from "prop-types";
|
|
5
|
+
import theme from "$theme";
|
|
5
6
|
|
|
6
7
|
const SCREEN_INDENT = 20;
|
|
7
8
|
export const isNativeScrollEvent = (nativeEvent)=>!isObj(nativeEvent) || !isObj(nativeEvent.layoutMeasurement) || !isObj(nativeEvent.contentOffset) ? false : true;
|
|
@@ -49,7 +50,7 @@ const BackToTopComponent = React.forwardRef((props,ref)=>{
|
|
|
49
50
|
}
|
|
50
51
|
},[visible])
|
|
51
52
|
const style = defaultStr(position).toLowerCase() =='right' ? {
|
|
52
|
-
right: 0
|
|
53
|
+
right: 0,
|
|
53
54
|
} : {left : 0};
|
|
54
55
|
React.setRef(ref,context);
|
|
55
56
|
React.useEffect(()=>{
|
|
@@ -67,7 +68,7 @@ const BackToTopComponent = React.forwardRef((props,ref)=>{
|
|
|
67
68
|
}
|
|
68
69
|
}}
|
|
69
70
|
icon = {defaultVal(icon,'arrow-up')}
|
|
70
|
-
style = {[rest.style,style]}
|
|
71
|
+
style = {[theme.styles.ph1,rest.style,style]}
|
|
71
72
|
/>
|
|
72
73
|
});
|
|
73
74
|
|
|
@@ -72,12 +72,7 @@ const BottomSheetMenuComponent = React.forwardRef((props,ref)=>{
|
|
|
72
72
|
open();
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
if(!visible) children = null;
|
|
77
|
-
else {
|
|
78
|
-
children = children({open,close});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
75
|
+
|
|
81
76
|
}
|
|
82
77
|
React.useEffect(()=>{
|
|
83
78
|
const closeModal = ()=>{
|
|
@@ -92,7 +87,12 @@ const BottomSheetMenuComponent = React.forwardRef((props,ref)=>{
|
|
|
92
87
|
APP.off(APP.EVENTS.RESIZE_PAGE,closeModal);
|
|
93
88
|
}
|
|
94
89
|
},[]);
|
|
95
|
-
|
|
90
|
+
if(typeof children ==='function'){
|
|
91
|
+
if(!visible) children = null;
|
|
92
|
+
else {
|
|
93
|
+
children = children({open,close});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
96
|
return <>
|
|
97
97
|
{isMob && React.isValidElement(anchor)? anchor : null}
|
|
98
98
|
<Component
|
|
@@ -18,7 +18,14 @@ const CountUpComponent = ({from,to,format,type,duration,step,formatter,interval,
|
|
|
18
18
|
const isNegative = end < 0 ? true : false;
|
|
19
19
|
const intervalRef = React.useRef(null);
|
|
20
20
|
const increment = (isNegative ? (-1):(1))* Math.abs(defaultNumber(step,getDefaultStep(to)));
|
|
21
|
-
const [current,
|
|
21
|
+
const [current, _setCurrent] = React.useState(from);
|
|
22
|
+
const setCurrent = (current)=>{
|
|
23
|
+
if(typeof current !=='number') return;
|
|
24
|
+
if(current > end){
|
|
25
|
+
current = end;
|
|
26
|
+
}
|
|
27
|
+
_setCurrent(current);
|
|
28
|
+
}
|
|
22
29
|
const isMounted = React.useIsMounted();
|
|
23
30
|
const formatValue = typeof formatter =="function"? formatter : (number)=>{
|
|
24
31
|
return isCurrency ? number.formatMoney() : number.formatNumber();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright 2022 @fto-consult/Boris Fouomene. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style
|
|
3
|
+
// license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import React from "$react";
|
|
6
|
+
import Filter from "$ecomponents/Filter";
|
|
7
|
+
|
|
8
|
+
export default function FilterAccordionComponent({onChange,...props}){
|
|
9
|
+
const valuesRefs = React.useRef({});
|
|
10
|
+
console.log("rendering ",valuesRefs);
|
|
11
|
+
return <Filter
|
|
12
|
+
{...props}
|
|
13
|
+
{...valuesRefs.current}
|
|
14
|
+
onChange = {(args)=>{
|
|
15
|
+
["action","operator","defaultValue","ignoreCase","manualRun"].map((k)=>{
|
|
16
|
+
valuesRefs.current[k] = args[k];
|
|
17
|
+
})
|
|
18
|
+
console.log(valuesRefs," is changeddd");
|
|
19
|
+
if(onChange){
|
|
20
|
+
onChange(args);
|
|
21
|
+
}
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
}
|
|
@@ -2,7 +2,7 @@ import {Content} from "$ecomponents/BottomSheet";
|
|
|
2
2
|
import Icon from "$ecomponents/Icon";
|
|
3
3
|
import React from "$react";
|
|
4
4
|
import {defaultStr,defaultBool,defaultObj} from "$utils";
|
|
5
|
-
import Filter,{canHandleFilter,getFilterStateValues} from "$ecomponents/Filter";
|
|
5
|
+
import Filter, {canHandleFilter,getFilterStateValues} from "$ecomponents/Filter";
|
|
6
6
|
import PropTypes from "prop-types";
|
|
7
7
|
import { StyleSheet,View } from "react-native";
|
|
8
8
|
import Menu from "$ecomponents/Menu";
|
|
@@ -11,17 +11,20 @@ import theme from "$theme"
|
|
|
11
11
|
import Expandable from "$ecomponents/Expandable";
|
|
12
12
|
import { Dimensions } from "react-native";
|
|
13
13
|
import Grid from "$components/Grid";
|
|
14
|
+
import { Pressable } from "react-native";
|
|
15
|
+
import Tooltip from "$ecomponents/Tooltip";
|
|
14
16
|
|
|
15
17
|
const MIN_WIDTH = 250;
|
|
16
18
|
let windowWidth = Dimensions.get("window").width;
|
|
17
19
|
|
|
18
20
|
const FiltersAccordionComponent = React.forwardRef((props,ref)=>{
|
|
19
|
-
const {filters,isLoading,filteredColumns,children,filterTitle:customFilterTitle,visible:customVisible,orOperator,andOperator,onToggleFilters,context:customContext,...restProps} = props;
|
|
21
|
+
const {filters,isLoading,filteredColumns,children,label,filterTitle:customFilterTitle,visible:customVisible,orOperator,andOperator,onToggleFilters,context:customContext,...restProps} = props;
|
|
20
22
|
const context = defaultObj(customContext);
|
|
21
23
|
const [state,setState] = React.useState({
|
|
22
24
|
visible : defaultBool(customVisible,false),
|
|
23
25
|
visibleColumns : defaultObj(filteredColumns),
|
|
24
26
|
});
|
|
27
|
+
const valuesRefs = React.useRef({});
|
|
25
28
|
windowWidth = Dimensions.get("window").width;
|
|
26
29
|
const innerRef = React.useRef(null);
|
|
27
30
|
const {visible,visibleColumns} = state;
|
|
@@ -35,10 +38,11 @@ const FiltersAccordionComponent = React.forwardRef((props,ref)=>{
|
|
|
35
38
|
phoneSize : 12,
|
|
36
39
|
style : [theme.styles.ph1],
|
|
37
40
|
}
|
|
38
|
-
const prepareContent = (filters)=>{
|
|
41
|
+
const prepareContent = (filters,renderMenusOnly)=>{
|
|
39
42
|
const content = []
|
|
40
43
|
const colMenus = [];
|
|
41
44
|
let mainFilterTitle = defaultStr(filterTitle);
|
|
45
|
+
let counter = 0;
|
|
42
46
|
const containerProps = {
|
|
43
47
|
style : [styles.filter,{minWidth : Math.min(windowWidth,MIN_WIDTH)}]
|
|
44
48
|
};
|
|
@@ -60,28 +64,26 @@ const FiltersAccordionComponent = React.forwardRef((props,ref)=>{
|
|
|
60
64
|
setState({...state,visibleColumns:{...visibleColumns,[key]:!visible}})
|
|
61
65
|
}}
|
|
62
66
|
/>)
|
|
63
|
-
if(!visible)
|
|
64
|
-
|
|
65
|
-
if(typeof defVal !== 'string' && typeof defVal !=='boolean'){
|
|
66
|
-
if(Array.isArray(defVal)){
|
|
67
|
-
defVal = "["+defVal.join(",")+"]";
|
|
68
|
-
} else {
|
|
69
|
-
if(defVal && defVal.toString){
|
|
70
|
-
defVal = defVal.toString();
|
|
71
|
-
} else defVal = "";
|
|
72
|
-
}
|
|
67
|
+
if(!visible) {
|
|
68
|
+
return null;
|
|
73
69
|
}
|
|
74
|
-
mainFilterTitle +=(
|
|
70
|
+
mainFilterTitle +=(counter?",":" :\n")+defaultStr(filter.label,filter.text,filter.field)//+" : "+defVal+""
|
|
71
|
+
counter++;
|
|
72
|
+
if(renderMenusOnly) return null;
|
|
75
73
|
content.push(
|
|
76
74
|
<Grid.Cell {...cellProps} key={key}>
|
|
77
75
|
<Filter
|
|
78
76
|
{...filter}
|
|
77
|
+
{...(isObj(valuesRefs.current[key]) ? valuesRefs.current[key] : {})}
|
|
79
78
|
dynamicRendered
|
|
80
79
|
isLoading = {isLoading && filteredRef.current[key] ? true : false}
|
|
81
80
|
orOperator = {defaultBool(orOperator,filter.orOperator,true)}
|
|
82
81
|
andOperator = {defaultBool(andOperator,filter.andOperator,true)}
|
|
83
82
|
onChange = {(arg)=>{
|
|
83
|
+
if(!arg.action && !arg.operator || !arg.field) return;
|
|
84
|
+
//console.log("calling on change dddd",arg);
|
|
84
85
|
const canHandle = canHandleFilter(arg);
|
|
86
|
+
valuesRefs.current[key] = arg;
|
|
85
87
|
if(filteredRef.current[key] !== canHandle){
|
|
86
88
|
if(canHandle){
|
|
87
89
|
canHandlerFilterRef.current++;
|
|
@@ -101,12 +103,12 @@ const FiltersAccordionComponent = React.forwardRef((props,ref)=>{
|
|
|
101
103
|
}
|
|
102
104
|
})
|
|
103
105
|
return {content,mainFilterTitle,colMenus}
|
|
104
|
-
}
|
|
106
|
+
}
|
|
105
107
|
if(typeof children ==='function'){
|
|
106
|
-
const ct = children({
|
|
108
|
+
const ct = children({});
|
|
107
109
|
return React.isValidElement(ct)? ct : null;
|
|
108
110
|
}
|
|
109
|
-
const {
|
|
111
|
+
const {mainFilterTitle} = prepareContent(filters,true);
|
|
110
112
|
const hasFilters = canHandlerFilterRef.current > 0 ? true : false;
|
|
111
113
|
return <Content
|
|
112
114
|
animateOnClose
|
|
@@ -116,35 +118,40 @@ const FiltersAccordionComponent = React.forwardRef((props,ref)=>{
|
|
|
116
118
|
onDissmiss = {()=>{
|
|
117
119
|
setState({...state,visible:false})
|
|
118
120
|
}}
|
|
119
|
-
anchor = {(props)=><
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
anchor = {(props)=><Tooltip title = {mainFilterTitle}Component={Pressable} {...props} style={[theme.styles.row]}>
|
|
122
|
+
<Icon
|
|
123
|
+
name={hasFilters?"filter-menu":"filter-plus"}
|
|
124
|
+
{...props}
|
|
125
|
+
color = {hasFilters?theme.colors.primaryOnSurface:undefined}
|
|
126
|
+
/>
|
|
127
|
+
{React.isValidElement(label,true)?<Label style={[hasFilters && {color:theme.colors.primaryOnSurface}]} fontSize={16} textBold>{label}</Label>:null}
|
|
128
|
+
</Tooltip>}
|
|
125
129
|
ref = {(el)=>{
|
|
126
130
|
innerRef.current = el;
|
|
127
131
|
React.setRef(ref,el);
|
|
128
132
|
}}
|
|
129
133
|
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
{({open,close})=>{
|
|
135
|
+
const {content,colMenus} = prepareContent(filters);
|
|
136
|
+
return <View style={[styles.wrapper]}>
|
|
137
|
+
<View style = {[styles.menuWrapper]}>
|
|
138
|
+
<Expandable
|
|
139
|
+
left = {(props)=><Icon {...props} icon={content.length?'filter-plus':'filter-menu'}/>}
|
|
140
|
+
style = {styles.expandable}
|
|
141
|
+
title = {<Label>{filterTitle}</Label>}
|
|
142
|
+
>
|
|
143
|
+
<View style={[styles.menuContent]}>
|
|
144
|
+
{colMenus}
|
|
145
|
+
</View>
|
|
146
|
+
</Expandable>
|
|
147
|
+
</View>
|
|
148
|
+
{content.length ? <View style={[theme.styles.w100]}>
|
|
149
|
+
<Grid style={theme.styles.w100}>
|
|
150
|
+
{content}
|
|
151
|
+
</Grid>
|
|
152
|
+
</View> : null}
|
|
141
153
|
</View>
|
|
142
|
-
|
|
143
|
-
<Grid style={theme.styles.w100}>
|
|
144
|
-
{content}
|
|
145
|
-
</Grid>
|
|
146
|
-
</View> : null}
|
|
147
|
-
</View>
|
|
154
|
+
}}
|
|
148
155
|
</Content>
|
|
149
156
|
})
|
|
150
157
|
|
|
@@ -303,6 +303,9 @@ const DatagridFactory = (Factory)=>{
|
|
|
303
303
|
}
|
|
304
304
|
return super.renderEmpty();
|
|
305
305
|
}
|
|
306
|
+
getTestID(){
|
|
307
|
+
return defaultStr(this.props.testID,"RN_DatagridAccordion");
|
|
308
|
+
}
|
|
306
309
|
render (){
|
|
307
310
|
let {
|
|
308
311
|
filters:customFilters,
|
|
@@ -333,7 +336,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
333
336
|
chartContainerProps = defaultObj(chartContainerProps);
|
|
334
337
|
const canRenderChart = this.canRenderChart();
|
|
335
338
|
const hasData = this.getStateDataSize(false) ? true : false;
|
|
336
|
-
testID =
|
|
339
|
+
testID = this.getTestID();
|
|
337
340
|
backToTopProps = defaultObj(backToTopProps);
|
|
338
341
|
accordionProps = defaultObj(accordionProps);
|
|
339
342
|
this.renderingItemsProps = {};
|
|
@@ -138,6 +138,14 @@ export const displayTypes = {
|
|
|
138
138
|
},*/
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
export const chartTypes = {};
|
|
142
|
+
|
|
143
|
+
Object.map(displayTypes,(c,k)=>{
|
|
144
|
+
if(c && c.isChart){
|
|
145
|
+
chartTypes[k] = c;
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
141
149
|
export const arrayValueSeparator = ", ";
|
|
142
150
|
|
|
143
151
|
const dataSourceArgs = {};
|
|
@@ -233,6 +241,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
233
241
|
aggregatorFunctions : {value : extendAggreagatorFunctions(this.props.aggregatorFunctions)},
|
|
234
242
|
///les types d'affichage
|
|
235
243
|
displayTypes : {value : hasFoundDisplayTypes ? disTypes : Object.clone(displayTypes)},
|
|
244
|
+
dateFields : {value : {}},
|
|
236
245
|
sectionListColumnsSize : {value : {current:0}}, //la taille du nombre d'éléments de section dans les colonnes
|
|
237
246
|
})
|
|
238
247
|
const sessionAggregator = defaultStr(this.getSessionData("aggregatorFunction")).trim();
|
|
@@ -262,16 +271,16 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
262
271
|
this.filters = {}
|
|
263
272
|
Object.map(this.getFiltersProps(),(f,v)=>{
|
|
264
273
|
if(isPlainObject(f)){
|
|
265
|
-
this.filters[v] = f;
|
|
274
|
+
this.filters[v] = {...f};
|
|
266
275
|
this.filters[v].field = defaultStr(f.field,v);
|
|
267
|
-
this.filters[v].
|
|
276
|
+
this.filters[v].defaultValue = defaultVal(f.defaultValue,f.value)
|
|
268
277
|
} else {
|
|
269
278
|
this.filters[v] = {
|
|
270
|
-
|
|
279
|
+
defaultValue : f,
|
|
271
280
|
field : v
|
|
272
281
|
}
|
|
273
282
|
}
|
|
274
|
-
})
|
|
283
|
+
});
|
|
275
284
|
this.state.filteredColumns = defaultObj(this.getSessionData("filteredColumns"+this.getSessionNameKey()),this.props.filters);
|
|
276
285
|
this.filtersSelectors = {selector:this.getFilters()};
|
|
277
286
|
const {sectionListColumns} = this.prepareColumns();
|
|
@@ -280,6 +289,9 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
280
289
|
this.state.columnsWidths = this.preparedColumns.widths;
|
|
281
290
|
}
|
|
282
291
|
this.state.chartConfig = extendObj({},this.getSessionData("chartConfig"),this.props.chartConfig);
|
|
292
|
+
if(!("sparkline" in this.state.chartConfig) && this.isDashboard()){
|
|
293
|
+
this.state.chartConfig.sparkline = true;
|
|
294
|
+
}
|
|
283
295
|
const dType = defaultStr(this.props.displayType,this.getSessionData("displayType"),"table");
|
|
284
296
|
this.state.displayType = this.displayTypes[dType] ? this.displayTypes[dType].code : "table" in this.displayTypes ? "table" : Object.keys(this.displayTypes)[0]?.code;
|
|
285
297
|
this.state.displayOnlySectionListHeaders = defaultBool(this.getSessionData("displayOnlySectionListHeaders"),this.props.displayOnlySectionListHeaders,false)
|
|
@@ -330,6 +342,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
330
342
|
} else {
|
|
331
343
|
this.currentDataSources = Object.toArray(this.currentDataSources);
|
|
332
344
|
}
|
|
345
|
+
this.persistDisplayType(this.state.displayType);
|
|
333
346
|
}
|
|
334
347
|
|
|
335
348
|
/*** si une ligne peut être selectionable */
|
|
@@ -342,6 +355,8 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
342
355
|
(Array.isArray(props.sectionListColumns) ? props.sectionListColumns : defaultArray(this.getSessionData("sectionListColumns"))).map((col)=>{
|
|
343
356
|
if(isNonNullString(col)){
|
|
344
357
|
l[col.trim()] = {};
|
|
358
|
+
} else if(isObj(col) && isNonNullString(col.field)){
|
|
359
|
+
l[col.field] = col;
|
|
345
360
|
}
|
|
346
361
|
})
|
|
347
362
|
return l;
|
|
@@ -648,6 +663,14 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
648
663
|
isIndexColumn(columnDef,columnField){
|
|
649
664
|
return isObj(columnDef) && defaultStr(columnDef.field,columnField) === this.getIndexColumnName();
|
|
650
665
|
}
|
|
666
|
+
isDateField(column){
|
|
667
|
+
const field = isObj(column)? defaultStr(column.field,column.name) : defaultStr(column);
|
|
668
|
+
return field && field in this.dateFields ? true : false;
|
|
669
|
+
}
|
|
670
|
+
isFooterField(column){
|
|
671
|
+
const field = isObj(column)? defaultStr(column.field,column.name) : defaultStr(column);
|
|
672
|
+
return field && isObj(this.state.footers) && field in this.state.footers ? true : false;
|
|
673
|
+
}
|
|
651
674
|
initColumnsCallback(){}
|
|
652
675
|
initColumns (columns){
|
|
653
676
|
this.state.columns = {};
|
|
@@ -689,6 +712,9 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
689
712
|
let header = {...headerCol};
|
|
690
713
|
header.field = defaultStr(header.field, headerIndex)
|
|
691
714
|
header.type = defaultStr(header.jsType,header.type,"text").toLowerCase();
|
|
715
|
+
if(header.type.contains("date")){
|
|
716
|
+
this.dateFields[header.field] = header;
|
|
717
|
+
}
|
|
692
718
|
/**** pour ignorer une colonne du datagrid, il suffit de passer le paramètre datagrid à false */
|
|
693
719
|
if(!isNonNullString(header.field) || header.datagrid === false) {
|
|
694
720
|
return;
|
|
@@ -714,6 +740,9 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
714
740
|
hasFootersFields(){
|
|
715
741
|
return Object.size(this.getFootersFields(),true) ? true : false;
|
|
716
742
|
}
|
|
743
|
+
getFooters(){
|
|
744
|
+
return this.getFootersFields();
|
|
745
|
+
}
|
|
717
746
|
getActionsArgs(selected){
|
|
718
747
|
const r = isObj(selected)? selected : {};
|
|
719
748
|
const ret = {
|
|
@@ -1333,9 +1362,12 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1333
1362
|
if(!m.length) return null;
|
|
1334
1363
|
return <Menu
|
|
1335
1364
|
items = {m}
|
|
1336
|
-
anchor = {(
|
|
1337
|
-
|
|
1338
|
-
|
|
1365
|
+
anchor = {(props)=>{
|
|
1366
|
+
return <Pressable {...props} style={[theme.styles.row]}>
|
|
1367
|
+
<Icon {...props} name="material-functions" title = "Fonctions d'aggrégations. Veuillez sélectionner la fonction à utiliser par défaut pour la totalisation des données des colonnes de type nombre"></Icon>
|
|
1368
|
+
{this.isDashboard() && <Label splitText numberOfLines={1} textBold>Fonction d'aggrégation</Label>||null}
|
|
1369
|
+
</Pressable>
|
|
1370
|
+
}}
|
|
1339
1371
|
/>
|
|
1340
1372
|
}
|
|
1341
1373
|
configureChart(refreshChart){
|
|
@@ -1404,6 +1436,14 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1404
1436
|
multiple : true,
|
|
1405
1437
|
},
|
|
1406
1438
|
stacked : stackSettings,
|
|
1439
|
+
sparkline : {
|
|
1440
|
+
type : 'switch',
|
|
1441
|
+
text : "Sparkline",
|
|
1442
|
+
checkedTooltip : "Le graphe a été définit pour être affiché dans un environnement petite surface",
|
|
1443
|
+
checkedValue : true,
|
|
1444
|
+
uncheckedValue : false,
|
|
1445
|
+
settingKey : "chart",
|
|
1446
|
+
},
|
|
1407
1447
|
title : {
|
|
1408
1448
|
text : "Titre du graphe",
|
|
1409
1449
|
format :'hashtag',
|
|
@@ -1443,6 +1483,9 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1443
1483
|
displayOnlySectionListHeaders : this.canDisplayOnlySectionListHeaders(),
|
|
1444
1484
|
};
|
|
1445
1485
|
}
|
|
1486
|
+
isDashboard(){
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1446
1489
|
renderDisplayTypes(){
|
|
1447
1490
|
const m = [];
|
|
1448
1491
|
let activeType = null,hasFoundChart = false,hasFoundTable = false;
|
|
@@ -1501,11 +1544,14 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1501
1544
|
title = "Type d'affichage"
|
|
1502
1545
|
items = {m}
|
|
1503
1546
|
anchor = {(p)=>{
|
|
1504
|
-
return <
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1547
|
+
return <Pressable {...p} style={[theme.styles.row]}>
|
|
1548
|
+
<Icon
|
|
1549
|
+
{...p}
|
|
1550
|
+
name = {activeType.icon}
|
|
1551
|
+
title = {"Les données s'affichent actuellement en {0}. Cliquez pour modifier le type d'affichage".sprintf(activeType.label)}
|
|
1552
|
+
/>
|
|
1553
|
+
{this.isDashboard() && <Label textBold>Type d'affichage</Label>||null}
|
|
1554
|
+
</Pressable>
|
|
1509
1555
|
}}
|
|
1510
1556
|
/>
|
|
1511
1557
|
}
|
|
@@ -1720,7 +1766,6 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1720
1766
|
chartProps[settingKey] = defaultObj(chartProps[settingKey]);
|
|
1721
1767
|
chartProps[settingKey][key] = config[key];
|
|
1722
1768
|
});
|
|
1723
|
-
|
|
1724
1769
|
const chartOptions = {
|
|
1725
1770
|
...chartProps,
|
|
1726
1771
|
title :extendObj(true,{}, {
|
|
@@ -1738,7 +1783,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1738
1783
|
},
|
|
1739
1784
|
},chartProps.title),
|
|
1740
1785
|
series,
|
|
1741
|
-
chart : extendObj(true,{},{height :350},chartProps.chart,{
|
|
1786
|
+
chart : extendObj(true,{},{height :this.isDashboard()?80:350},chartProps.chart,{
|
|
1742
1787
|
type : chartType.type,
|
|
1743
1788
|
})
|
|
1744
1789
|
}
|
|
@@ -1765,9 +1810,26 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1765
1810
|
if(!chartType.isDonut){
|
|
1766
1811
|
delete chartOptions.labels;
|
|
1767
1812
|
}
|
|
1813
|
+
if(this.isDashboard() && typeof chartOptions.chart.sparkline !=='boolean'){
|
|
1814
|
+
chartOptions.chart.sparkline = true;
|
|
1815
|
+
}
|
|
1816
|
+
if(chartOptions.chart.sparkline){
|
|
1817
|
+
chartOptions.chart.sparkline = {enabled: true}
|
|
1818
|
+
} else delete chartOptions.chart.sparkline;
|
|
1819
|
+
if(chartOptions.chart.sparkline && chartOptions.chart.sparkline.enabled){
|
|
1820
|
+
chartOptions.xaxis = defaultObj(chartOptions.xaxis);
|
|
1821
|
+
chartOptions.xaxis.labels = defaultObj(chartOptions.xaxis.labels);
|
|
1822
|
+
chartOptions.xaxis.labels.show = false;
|
|
1823
|
+
|
|
1824
|
+
chartOptions.yaxis = defaultObj(chartOptions.yaxis);
|
|
1825
|
+
chartOptions.yaxis.labels = defaultObj(chartOptions.yaxis.labels);
|
|
1826
|
+
chartOptions.yaxis.labels.show = false;
|
|
1827
|
+
chartOptions.legend = defaultObj(chartOptions.legend);
|
|
1828
|
+
chartOptions.legend.show = false;
|
|
1829
|
+
}
|
|
1768
1830
|
return <Chart
|
|
1769
1831
|
options = {chartOptions}
|
|
1770
|
-
key = {chartOptions.chart.id}
|
|
1832
|
+
key = {chartOptions.chart.id+"-"+this.state.displayType}
|
|
1771
1833
|
/>
|
|
1772
1834
|
}
|
|
1773
1835
|
canShowFooters(){
|
|
@@ -1819,7 +1881,10 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1819
1881
|
title = {"Grouper les données du tableau"}
|
|
1820
1882
|
testID = {"RN_DatagridSectionListMenu"}
|
|
1821
1883
|
anchor = {(props)=>{
|
|
1822
|
-
return <
|
|
1884
|
+
return <Pressable {...props} style={[theme.styles.row]}>
|
|
1885
|
+
<Icon {...props} color={hasList?theme.colors.primaryOnSurface:undefined} name='format-list-group' title={"Grouper les éléments du tableau"}></Icon>
|
|
1886
|
+
{this.isDashboard() && <Label style={[hasList && {color:theme.colors.primaryOnSurface}]} textBold>Grouper par</Label>||null}
|
|
1887
|
+
</Pressable>
|
|
1823
1888
|
}}
|
|
1824
1889
|
items = {[
|
|
1825
1890
|
{
|
|
@@ -1829,7 +1894,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1829
1894
|
divider : true,
|
|
1830
1895
|
style : theme.styles.bold,
|
|
1831
1896
|
},
|
|
1832
|
-
this.canDisplayOnlySectionListHeaders() && {
|
|
1897
|
+
this.canDisplayOnlySectionListHeaders() && this.state.displayType =='table' && {
|
|
1833
1898
|
text : "Afficher uniquement totaux",
|
|
1834
1899
|
icon : this.state.displayOnlySectionListHeaders?"check":null,
|
|
1835
1900
|
onPress : this.toggleDisplayOnlySectionListHeaders.bind(this)
|
|
@@ -1981,7 +2046,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1981
2046
|
andOperator : filterAndOperator,
|
|
1982
2047
|
searchIconTooltip : 'Filtre',
|
|
1983
2048
|
searchIcon : 'filter_list',
|
|
1984
|
-
defaultValue : fCol.
|
|
2049
|
+
defaultValue : defaultVal(fCol.defaultValue,restCol.defaultValue),
|
|
1985
2050
|
name : header.field,
|
|
1986
2051
|
onClearFilter : this.onClearFilter.bind(this),
|
|
1987
2052
|
onChange : this.onFilterChange.bind(this),
|
|
@@ -2190,6 +2255,10 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
2190
2255
|
this.sectionListData[k] = sectionListData[k];
|
|
2191
2256
|
return k;
|
|
2192
2257
|
});
|
|
2258
|
+
} else {
|
|
2259
|
+
Object.map(sectionListData,(v,k)=>{
|
|
2260
|
+
this.sectionListData[k] = v;
|
|
2261
|
+
})
|
|
2193
2262
|
}
|
|
2194
2263
|
data = newData;
|
|
2195
2264
|
}
|
|
@@ -2390,7 +2459,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
2390
2459
|
if(!isObj(f)) return;
|
|
2391
2460
|
defValue = undefined;
|
|
2392
2461
|
if(f.type =="select") defValue = [];
|
|
2393
|
-
filters[i] = {...f,value:defValue,defaultValue:defValue
|
|
2462
|
+
filters[i] = {...f,value:defValue,defaultValue:defValue}
|
|
2394
2463
|
})
|
|
2395
2464
|
this.filters = filters;
|
|
2396
2465
|
this.refresh(true);
|
|
@@ -2416,7 +2485,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
2416
2485
|
field = defaultStr(field,name);
|
|
2417
2486
|
if(field){
|
|
2418
2487
|
let v = defaultStr(type).toLowerCase() == "select"? []:undefined
|
|
2419
|
-
this.filters[field] = {value:v,defaultValue:v
|
|
2488
|
+
this.filters[field] = {value:v,defaultValue:v}
|
|
2420
2489
|
this.doFilter({value:v,field,force:true})
|
|
2421
2490
|
}
|
|
2422
2491
|
}
|
|
@@ -2433,7 +2502,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
2433
2502
|
this.filteredValues[field] = {
|
|
2434
2503
|
operator,action,value,field
|
|
2435
2504
|
}
|
|
2436
|
-
filter.
|
|
2505
|
+
filter.defaultValue = arg.defaultValue;
|
|
2437
2506
|
filter.operator = operator;
|
|
2438
2507
|
filter.action = defaultStr(originAction,action);
|
|
2439
2508
|
}
|
|
@@ -2829,6 +2898,23 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
2829
2898
|
/**** permet d'afficher le menu item lié aux champs triables */
|
|
2830
2899
|
getSortableMenuMenuItem(){
|
|
2831
2900
|
|
|
2901
|
+
}
|
|
2902
|
+
getTestID(){
|
|
2903
|
+
return defaultStr(this.props.testID,'RN_DatagridComponent');
|
|
2904
|
+
}
|
|
2905
|
+
renderTitle (){
|
|
2906
|
+
const testID = this.getTestID();
|
|
2907
|
+
const title = typeof this.props.title =="function"? this.props.title({context:this}) : this.props.title;
|
|
2908
|
+
const titleProps = defaultObj(this.props.titleProps);
|
|
2909
|
+
return React.isValidElement(title) ? <Label testID={testID+"_Title"} {...titleProps} style={[theme.styles.w100,titleProps.style]}>
|
|
2910
|
+
{title}
|
|
2911
|
+
</Label> : null
|
|
2912
|
+
}
|
|
2913
|
+
getInitialData (){
|
|
2914
|
+
return this.INITIAL_STATE.data;
|
|
2915
|
+
}
|
|
2916
|
+
getData(){
|
|
2917
|
+
return this.state.data;
|
|
2832
2918
|
}
|
|
2833
2919
|
renderSelectableCheckboxCell(props){
|
|
2834
2920
|
const {containerProps} = props;
|
|
@@ -3071,6 +3157,12 @@ const chartDisplayType = PropTypes.oneOf(Object.keys(displayTypes).filter(type=>
|
|
|
3071
3157
|
return typeof x =='object' && x && typeof x.disabled !== true && x.isChart === true && true || false;
|
|
3072
3158
|
}));
|
|
3073
3159
|
CommonDatagridComponent.propTypes = {
|
|
3160
|
+
title : PropTypes.oneOfType([
|
|
3161
|
+
PropTypes.func,
|
|
3162
|
+
PropTypes.string,
|
|
3163
|
+
PropTypes.node,
|
|
3164
|
+
PropTypes.element,
|
|
3165
|
+
]),
|
|
3074
3166
|
canMakePhoneCall : PropTypes.bool,//si l'on peut faire un appel sur la données sélectionnées
|
|
3075
3167
|
makePhoneCallProps : PropTypes.oneOfType([
|
|
3076
3168
|
PropTypes.object,
|
|
@@ -3227,6 +3319,11 @@ CommonDatagridComponent.propTypes = {
|
|
|
3227
3319
|
PropTypes.arrayOf(PropTypes.object),
|
|
3228
3320
|
PropTypes.objectOf(PropTypes.object),
|
|
3229
3321
|
]),
|
|
3322
|
+
/****les colonnes via lesquelles le tableau est groupé par defaut */
|
|
3323
|
+
sectionListColumns : PropTypes.oneOfType([
|
|
3324
|
+
PropTypes.arrayOf(PropTypes.object),
|
|
3325
|
+
PropTypes.arrayOf(PropTypes.string),
|
|
3326
|
+
]),
|
|
3230
3327
|
sectionListHeaderEmptyValue : PropTypes.string, //la valeur vide par défaut à afficher dans les entêtes du table
|
|
3231
3328
|
ignoreCaseOnSectionListHeader : PropTypes.bool,//si l'on ignorera la casse dans le sectionlISThEADER
|
|
3232
3329
|
sectoonListHeaderUpperCase : PropTypes.bool, //si le sectionListHeader sera en majuscule
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {TableData} from "../Common";
|
|
2
|
+
import {defaultObj,defaultArray,defaultStr,defaultDecimal,isNonNullString} from "$utils";
|
|
3
|
+
import View from "$ecomponents/View";
|
|
4
|
+
import { StyleSheet,Dimensions,Pressable } from "react-native";
|
|
5
|
+
import Icon,{MENU_ICON} from "$ecomponents/Icon";
|
|
6
|
+
import React from "$react";
|
|
7
|
+
import {Menu as BottomSheetMenu} from "$ecomponents/BottomSheet";
|
|
8
|
+
import { chartTypes } from "../Common/Common";
|
|
9
|
+
import theme from "$theme";
|
|
10
|
+
import FiltersAccordionComponent from "../Accordion/Filters";
|
|
11
|
+
|
|
12
|
+
export default class DatagridDashboard extends TableData {
|
|
13
|
+
constructor(props){
|
|
14
|
+
super(props);
|
|
15
|
+
Object.map(this.displayTypes,(t,i)=>{
|
|
16
|
+
if(!t || !t.isChart) delete this.displayTypes[i];
|
|
17
|
+
});
|
|
18
|
+
if(!this.state.displayType.toLowerCase().contains("chart")){
|
|
19
|
+
this.state.displayType = chartTypes[Object.keys(chartTypes)[0]].code
|
|
20
|
+
}
|
|
21
|
+
this.persistDisplayType(this.state.displayType);
|
|
22
|
+
}
|
|
23
|
+
isDatagrid(){
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
canPaginateData(){
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
bindResizeEvents(){
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
isDashboard(){
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
renderMenu(){
|
|
36
|
+
const testID = this.getTestID();
|
|
37
|
+
const {filterOrOperator,filterAndOperator,} = this.props;
|
|
38
|
+
const {
|
|
39
|
+
visibleColumnsNames,
|
|
40
|
+
filteredColumns,
|
|
41
|
+
filters :headerFilters,
|
|
42
|
+
} = this.preparedColumns;
|
|
43
|
+
const menus = [
|
|
44
|
+
{
|
|
45
|
+
text : 'Rafraichir',
|
|
46
|
+
icon : "refresh",
|
|
47
|
+
onPress : this.refresh.bind(this)
|
|
48
|
+
},
|
|
49
|
+
this.isFilterable() ?<FiltersAccordionComponent
|
|
50
|
+
testID={testID+"_HeaderFilters"}
|
|
51
|
+
isLoading = {this.isLoading()}
|
|
52
|
+
filters = {headerFilters}
|
|
53
|
+
visibleColumns = {visibleColumnsNames}
|
|
54
|
+
filteredColumns = {filteredColumns}
|
|
55
|
+
orOperator = {filterOrOperator}
|
|
56
|
+
andOperator = {filterAndOperator}
|
|
57
|
+
context = {this}
|
|
58
|
+
label = {"Filtres"}
|
|
59
|
+
/> : null,
|
|
60
|
+
...this.renderCustomMenu(),
|
|
61
|
+
this.renderDisplayTypes(),
|
|
62
|
+
this.renderSectionListMenu(),
|
|
63
|
+
this.renderAggregatorFunctionsMenu(),
|
|
64
|
+
]
|
|
65
|
+
return menus;
|
|
66
|
+
}
|
|
67
|
+
getTestID(){
|
|
68
|
+
return defaultStr(this.props.testID,"RN_DatagridDashboard");
|
|
69
|
+
}
|
|
70
|
+
canHandleQueryLimit(){
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
render(){
|
|
74
|
+
let {
|
|
75
|
+
title,
|
|
76
|
+
testID,actions,
|
|
77
|
+
selectableMultiple,
|
|
78
|
+
sortable,
|
|
79
|
+
titleProps,
|
|
80
|
+
autoSort,
|
|
81
|
+
exportable,
|
|
82
|
+
selectable,pagin,showPagination,
|
|
83
|
+
sessionName,onMount,onUnmount,onFetchData,dataSourceSelector,dataSourceSelectorProps,queryLimit,
|
|
84
|
+
filters,
|
|
85
|
+
filterOrOperator,
|
|
86
|
+
filterAndOperator,
|
|
87
|
+
chartContainerProps,
|
|
88
|
+
accordion, //pour le rendu du header en accordion
|
|
89
|
+
...rest
|
|
90
|
+
} = this.props;
|
|
91
|
+
const canRenderChart = this.canRenderChart();
|
|
92
|
+
if(!canRenderChart) return null;
|
|
93
|
+
chartContainerProps = defaultObj(chartContainerProps);
|
|
94
|
+
titleProps = Object.assign({},titleProps);
|
|
95
|
+
testID = this.getTestID();
|
|
96
|
+
rest = defaultObj(rest);
|
|
97
|
+
const _progressBar = this.getProgressBar();
|
|
98
|
+
const pointerEvents = this.getPointerEvents();
|
|
99
|
+
const maxHeight = 300;
|
|
100
|
+
return <View {...rest} testID={testID} style={[styles.container,{maxHeight},rest.style]} pointerEvents={pointerEvents}>
|
|
101
|
+
{this.renderTitle()}
|
|
102
|
+
{showPagination ? <View style={[styles.paginationContainer]}>
|
|
103
|
+
<BottomSheetMenu
|
|
104
|
+
anchor = {(props)=>{
|
|
105
|
+
return <Icon {...props} title={isMobile?"Actions":"Colonnes"} name={isMobile?MENU_ICON:'view-column'}></Icon>
|
|
106
|
+
}}
|
|
107
|
+
closeOnPress = {isMobile?undefined:false}
|
|
108
|
+
items = {this.renderMenu()}
|
|
109
|
+
/>
|
|
110
|
+
</View> : null}
|
|
111
|
+
{<View testID={testID+"_ChartContainer"} {...chartContainerProps} style={[theme.styles.w100,chartContainerProps.style]}>
|
|
112
|
+
{_progressBar}
|
|
113
|
+
{this.renderChart()}
|
|
114
|
+
</View>}
|
|
115
|
+
</View>
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
DatagridDashboard.displayName = "DatagridDashboardComponent";
|
|
120
|
+
|
|
121
|
+
DatagridDashboard.propTypes = {
|
|
122
|
+
...defaultObj(TableData.propTypes),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
const styles = StyleSheet.create({
|
|
127
|
+
paginationContainer : {
|
|
128
|
+
flexDirection : 'row',
|
|
129
|
+
width : '100%'
|
|
130
|
+
},
|
|
131
|
+
paginationContent : {
|
|
132
|
+
flex : 1,
|
|
133
|
+
flexDirection : 'row',
|
|
134
|
+
width : '100%',
|
|
135
|
+
justifyContent : 'flex-end',
|
|
136
|
+
alignItems : 'center',
|
|
137
|
+
paddingHorizontal : 10,
|
|
138
|
+
},
|
|
139
|
+
container : {
|
|
140
|
+
width : '100%',
|
|
141
|
+
flex:1,
|
|
142
|
+
},
|
|
143
|
+
})
|
|
@@ -83,13 +83,13 @@ const formatValue = ({value,format,aggregatorFunction})=>{
|
|
|
83
83
|
return (format === 'money' && aggregatorFunction != 'count')? value.formatMoney():value.formatNumber();
|
|
84
84
|
}
|
|
85
85
|
export default function DGGridFooterValue (props){
|
|
86
|
-
let {label,text,displayLabel,style,aggregatorFunctions,aggregatorFunction,format,testID,anchorProps} = props;
|
|
86
|
+
let {label,text,displayLabel,withLabel,style,aggregatorFunctions,aggregatorFunction,format,testID,anchorProps} = props;
|
|
87
87
|
aggregatorFunctions = defaultObj(aggregatorFunctions);
|
|
88
88
|
anchorProps = defaultObj(anchorProps);
|
|
89
89
|
testID = defaultStr(testID,"RN_DatagridFooterComponent");
|
|
90
90
|
label = defaultVal(label,text);
|
|
91
91
|
const defLabel = label;
|
|
92
|
-
if(displayLabel !== false){
|
|
92
|
+
if(displayLabel !== false && withLabel !== false){
|
|
93
93
|
if(!label || !React.isValidElement(label,true)) return null;
|
|
94
94
|
} else label = undefined;
|
|
95
95
|
const [active,setActive] = React.useState(isNonNullString(aggregatorFunction) && aggregatorFunction in aggregatorFunctions ? aggregatorFunction : aggregatorFunctions[Object.keys(aggregatorFunctions)[0]]?.code)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// license that can be found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import Accordion,{ TableData as TableDataAccordion} from "./Accordion";
|
|
6
|
+
import Dashboard from "./Dashboard";
|
|
6
7
|
import Table,{TableData as DatagridTableData} from "./Table";
|
|
7
8
|
import {isDesktopMedia,isMobileMedia} from "$cplatform/dimensions";
|
|
8
9
|
import {isFunction,defaultVal} from "$utils";
|
|
@@ -19,7 +20,10 @@ const DatagridMainComponent = React.forwardRef((props,ref)=>{
|
|
|
19
20
|
let Component = TableComponent;
|
|
20
21
|
const canRenderAccordion = (isFunction(props.accordion) || (isObj(props.accordionProps) && isFunction(props.accordionProps.accordion)) || props.accordion === true);
|
|
21
22
|
let renderType = defaultStr(getRenderType(),isDesk? "table":'accordion').trim().toLowerCase()
|
|
22
|
-
if(renderType
|
|
23
|
+
if(false && (renderType ==="dashboard" || props.dashobard === true)){
|
|
24
|
+
Component = Dashboard;
|
|
25
|
+
delete props.dashobard;
|
|
26
|
+
} else if(renderType == 'accordion' && canRenderAccordion){
|
|
23
27
|
Component = AccordionComponent;
|
|
24
28
|
} else if(renderType =='table'){
|
|
25
29
|
Component = TableComponent;
|
|
@@ -108,6 +108,9 @@ const DatagridFactory = (Factory)=>{
|
|
|
108
108
|
this.listRef.current.scrollToIndex({index});
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
getTestID(){
|
|
112
|
+
return defaultStr(this.props.testID,"RN_DatagridTable");
|
|
113
|
+
}
|
|
111
114
|
render(){
|
|
112
115
|
let {title,testID,actions,
|
|
113
116
|
selectableMultiple,
|
|
@@ -123,7 +126,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
123
126
|
} = this.props;
|
|
124
127
|
const canRenderChart = this.canRenderChart();
|
|
125
128
|
chartContainerProps = defaultObj(chartContainerProps);
|
|
126
|
-
testID =
|
|
129
|
+
testID = this.getTestID();
|
|
127
130
|
rest = defaultObj(rest);
|
|
128
131
|
let showDataSourceSelector = false;
|
|
129
132
|
if(dataSourceSelector === true){
|
|
@@ -10,7 +10,6 @@ import {defaultObj,isNumber,defaultNumber,defaultStr,defaultBool,isNonNullString
|
|
|
10
10
|
import theme,{flattenStyle} from "$theme";
|
|
11
11
|
import DateLib from "$date";
|
|
12
12
|
import { toDateObj } from "./utils";
|
|
13
|
-
import TextField from "$components/TextField";
|
|
14
13
|
import PeriodActionComponent from "./PeriodAction";
|
|
15
14
|
|
|
16
15
|
export default function DateTimePickerComponent({left,isPeriodAction,contentProps,withSeconds,right,format,dateFormat,timeFormat,defaultValue,onChange,testID,dateProps,disabled,readOnly,timeProps,...rest}){
|
|
@@ -8,7 +8,7 @@ import DateLib from "$date";
|
|
|
8
8
|
|
|
9
9
|
const PeriodActionComponent = React.forwardRef(({defaultValue,label,isDateTime,...props},ref)=>{
|
|
10
10
|
if(isNonNullString(label)){
|
|
11
|
-
label+=" [
|
|
11
|
+
label+=" [Période]";
|
|
12
12
|
}
|
|
13
13
|
const datePeriod= DateLib.formatPeriod(defaultValue,isDateTime);
|
|
14
14
|
if(datePeriod){
|
|
@@ -178,7 +178,6 @@ export default class Filter extends AppComponent {
|
|
|
178
178
|
if(!isObjOrArray(value) && (isNullOrEmpty(value,true) || value ==='undefined') ){
|
|
179
179
|
value = undefined;
|
|
180
180
|
}
|
|
181
|
-
let originValue = value;
|
|
182
181
|
if(action =="$today" || action =='$yesterday'){
|
|
183
182
|
force = true;
|
|
184
183
|
}
|
|
@@ -230,7 +229,7 @@ export default class Filter extends AppComponent {
|
|
|
230
229
|
}
|
|
231
230
|
}
|
|
232
231
|
}
|
|
233
|
-
this.props.onChange({...this.getStateValues(),value,
|
|
232
|
+
this.props.onChange({...this.getStateValues(),value,field:this.props.field,action,operator,selector,originAction,context:this});
|
|
234
233
|
}
|
|
235
234
|
}
|
|
236
235
|
componentDidUpdate (){
|
|
@@ -600,7 +599,6 @@ export default class Filter extends AppComponent {
|
|
|
600
599
|
</>
|
|
601
600
|
const containerProps = defaultObj(this.props.containerProps,rest.containerProps);
|
|
602
601
|
delete rest.containerProps;
|
|
603
|
-
rest.onValidate = this.onFilterValidate.bind(this);
|
|
604
602
|
const Component = isBetweenAction ? FilterBetweenComponent : this.Component;
|
|
605
603
|
const responsiveProps = Object.assign({},responsiveProps);
|
|
606
604
|
responsiveProps.style = [theme.styles.w100,responsiveProps.style]
|
|
@@ -615,6 +613,8 @@ export default class Filter extends AppComponent {
|
|
|
615
613
|
isFilter
|
|
616
614
|
name = {this.name}
|
|
617
615
|
testID = {testID}
|
|
616
|
+
onValidate = {this.onFilterValidate.bind(this)}
|
|
617
|
+
onChange = {x=>null}
|
|
618
618
|
ref = {React.mergeRefs(this.searchFilter,ref)}
|
|
619
619
|
/>
|
|
620
620
|
</View>
|
|
@@ -70,7 +70,7 @@ const MenuItemComponent = React.forwardRef(({
|
|
|
70
70
|
const pointerEvents = disabled ? 'none' : 'auto';
|
|
71
71
|
const disabledStyle = disabled ? {opacity:DISABLED_OPACITY} : null;
|
|
72
72
|
const winW = Dimensions.get("window").width-30;
|
|
73
|
-
const maxWidthStyle = isBottomSheetItem ? {
|
|
73
|
+
const maxWidthStyle = isBottomSheetItem ? {maxWidth:winW,paddingRight:10} : undefined;
|
|
74
74
|
const maxWidthTextStyle = isBottomSheetItem ? {width:winW-50} : null;
|
|
75
75
|
right = typeof right =='function'? right ({color:titleColor}) : right;
|
|
76
76
|
if(!React.isValidElement(right)) right = null;
|
|
@@ -113,7 +113,7 @@ const MenuItemComponent = React.forwardRef(({
|
|
|
113
113
|
testID={testID+"_Label"}
|
|
114
114
|
selectable={false}
|
|
115
115
|
numberOfLines={1}
|
|
116
|
-
|
|
116
|
+
splitText
|
|
117
117
|
{...labelProps}
|
|
118
118
|
style={[styles.title, { color: titleColor }, labelStyle,styles.noMargin,!right ? maxWidthTextStyle : null]}
|
|
119
119
|
>
|
|
@@ -16,7 +16,11 @@ export const renderItems = (props)=>{
|
|
|
16
16
|
testID = defaultStr(testID,"RN_MenuComponents.Items")
|
|
17
17
|
filter = defaultFunc(filter,x=> true);
|
|
18
18
|
Object.map(items,(item,index,_index)=>{
|
|
19
|
-
if(!isObj(item)
|
|
19
|
+
if(!isObj(item)|| !filter({items,item,_index,index})) return null;
|
|
20
|
+
if(React.isValidElement(item)) {
|
|
21
|
+
_items.push(<React.Fragment key={index+"_"+_index}>{item}</React.Fragment>);
|
|
22
|
+
return
|
|
23
|
+
}
|
|
20
24
|
if(item.divider === true && !item.title && !item.text && !item.label && !item.icon){
|
|
21
25
|
_items.push(<Divider testID={testID+"_Divider"+index} {...item} key={index}/>);
|
|
22
26
|
return
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {defaultStr,isNonNullString} from "$utils";
|
|
1
|
+
import {defaultStr,defaultVal,isNonNullString,defaultNumber,defaultObj} from "$utils";
|
|
2
2
|
import React from "$react";
|
|
3
3
|
import CountUp from "$ecomponents/CountUp";
|
|
4
4
|
import Avatar from "$ecomponents/Avatar";
|
|
5
|
-
import {
|
|
5
|
+
import { Pressable } from "react-native";
|
|
6
|
+
import {ActivityIndicator} from "react-native-paper";
|
|
6
7
|
import Item from "$ecomponents/Expandable/Item";
|
|
7
8
|
import {navigateToTableDataList} from "$enavigation/utils";
|
|
8
9
|
import theme from "$theme";
|
|
@@ -11,66 +12,174 @@ import PropTypes from "prop-types";
|
|
|
11
12
|
import APP from "$capp/instance"
|
|
12
13
|
import cActions from "$cactions";
|
|
13
14
|
import {View} from "react-native";
|
|
15
|
+
import {Menu} from "$ecomponents/BottomSheet";
|
|
16
|
+
import Dashboard from "$ecomponents/Datagrid/Dashboard";
|
|
17
|
+
import fetch from "$capi/fetch";
|
|
18
|
+
import Auth from "$auth";
|
|
19
|
+
import Footer from "$ecomponents/Datagrid/Footer/Footer";
|
|
14
20
|
|
|
15
|
-
export default function DatabaseStatisticContainer (props){
|
|
21
|
+
export default function DatabaseStatisticContainer ({dashboardProps,fetchDataProps,table,fetchCount,index,testID,title,icon,onPress:customOnPress,columns,fetchData,withDashboard,...props}){
|
|
22
|
+
dashboardProps = defaultObj(dashboardProps);
|
|
16
23
|
const [count,setCount] = React.useState(0);
|
|
17
|
-
const
|
|
18
|
-
let {
|
|
24
|
+
const datagridRef = React.useRef(null);
|
|
25
|
+
let {} = props;
|
|
19
26
|
title = defaultStr(title)
|
|
20
27
|
table = defaultObj(table);
|
|
28
|
+
const dbStatistics = defaultObj(table.databaseStatistics,table.databaseStatisticsProps);
|
|
29
|
+
const databaseStatisticsFields = defaultObj(table.databaseStatisticsFields);
|
|
30
|
+
const hasDFields = Object.size(databaseStatisticsFields,true);
|
|
31
|
+
if(!withDashboard && hasDFields){
|
|
32
|
+
withDashboard = true;
|
|
33
|
+
}
|
|
34
|
+
const dFields = hasDFields ? databaseStatisticsFields : defaultObj(dbStatistics.fields,dbStatistics.columns);
|
|
35
|
+
if(typeof fetchData !=='function'){
|
|
36
|
+
fetchData = typeof dbStatistics.fetchData =='function'? dbStatistics.fetchData : undefined;
|
|
37
|
+
}
|
|
38
|
+
withDashboard = withDashboard && typeof fetchData == 'function'? true : false;
|
|
39
|
+
columns = Object.size(columns,true)? columns : Object.size(dFields,true)? dFields : table.fields;
|
|
21
40
|
const tableName = defaultStr(table.tableName,table.table).toUpperCase();
|
|
22
41
|
fetchCount = typeof table.fetchCount =='function'? table.fetchCount : typeof fetchCount =='function'? fetchCount : undefined;
|
|
23
|
-
if(!fetchCount || !tableName) return null;
|
|
42
|
+
if((!fetchCount && !withDashboard) || !tableName) return null;
|
|
24
43
|
const refreshingRef = React.useRef(null);
|
|
25
44
|
const isMounted = React.useIsMounted();
|
|
45
|
+
|
|
46
|
+
const [isLoading,setIsLoading] = React.useState(withDashboard?false:true);
|
|
26
47
|
const refresh = ()=>{
|
|
27
48
|
if(refreshingRef.current || !isMounted()) return;
|
|
49
|
+
if(!fetchCount){
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
28
52
|
refreshingRef.current = true;
|
|
29
53
|
setIsLoading(true);
|
|
30
54
|
setTimeout(()=>{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
fetchCount({table,tableName}).then((count)=>{
|
|
56
|
+
setCount(count);
|
|
57
|
+
setIsLoading(false);
|
|
58
|
+
refreshingRef.current = false;
|
|
59
|
+
}).catch((e)=>{
|
|
60
|
+
setIsLoading(false);
|
|
61
|
+
refreshingRef.current = false;
|
|
62
|
+
});
|
|
63
|
+
},100);
|
|
40
64
|
}
|
|
41
65
|
|
|
42
66
|
React.useEffect(()=>{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
if(!withDashboard){
|
|
68
|
+
APP.on(cActions.upsert(tableName),refresh);
|
|
69
|
+
APP.on(cActions.remove(tableName),refresh);
|
|
70
|
+
refresh();
|
|
71
|
+
}
|
|
46
72
|
return ()=>{
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
if(!withDashboard){
|
|
74
|
+
APP.off(cActions.upsert(tableName),refresh);
|
|
75
|
+
APP.off(cActions.remove(tableName),refresh);
|
|
76
|
+
}
|
|
49
77
|
}
|
|
50
78
|
},[]);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
const progressBar = <View style={[theme.styles.justifyContentFlexStart,theme.styles.alignItemsFlexStart]}>
|
|
80
|
+
<ActivityIndicator color={theme.colors.primary}/>
|
|
81
|
+
</View>;
|
|
82
|
+
const isDatagridLoading = datagridRef.current && datagridRef.current.isLoading && datagridRef.current.isLoading();
|
|
83
|
+
testID = defaultStr(testID,"RN_DatabaseStatistic_"+table);
|
|
84
|
+
const onPress = (args)=>{
|
|
85
|
+
if(customOnPress && customOnPress(args) === false) return;
|
|
86
|
+
navigateToTableDataList(tableName,{
|
|
87
|
+
tableName
|
|
88
|
+
})
|
|
89
|
+
};
|
|
90
|
+
const fetchFields = React.useCallback(()=>{
|
|
91
|
+
const fetchFields = [];
|
|
92
|
+
Object.map(columns,(field,f)=>{
|
|
93
|
+
const ff = defaultStr(isObj(field)? field.field: undefined,f);
|
|
94
|
+
if(ff){
|
|
95
|
+
fetchFields.push(ff);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return fetchFields;
|
|
99
|
+
},[columns])();
|
|
100
|
+
const counUpStyle = {fontSize:20,fontWeight:'bold',color:theme.colors.secondaryOnSurface};
|
|
101
|
+
title = React.isValidElement(title,true)?<Label splitText numberOfLines={1} color={theme.colors.primaryOnSurface} style={[{fontSize:15}]}>{title}</Label>: null;
|
|
102
|
+
return withDashboard ? <Dashboard
|
|
103
|
+
chartProps = {{
|
|
104
|
+
stroke: {
|
|
105
|
+
curve: 'straight'
|
|
106
|
+
},
|
|
107
|
+
fill: {
|
|
108
|
+
type: 'solid',
|
|
109
|
+
opacity: 1,
|
|
110
|
+
},
|
|
58
111
|
}}
|
|
59
|
-
|
|
60
|
-
|
|
112
|
+
sessionName = {tableName+"-database-statistics"}
|
|
113
|
+
{...props}
|
|
114
|
+
{...dbStatistics}
|
|
115
|
+
style = {[theme.styles.ph1,props.style]}
|
|
116
|
+
columns = {columns}
|
|
117
|
+
ref = {datagridRef}
|
|
118
|
+
progressBar = {isLoading?<View/>:<View style={[theme.styles.w100,theme.styles.alignItemsCenter,theme.styles.justifyContentCenter]}>{progressBar}</View>}
|
|
119
|
+
tableName = {tableName}
|
|
120
|
+
table = {table}
|
|
121
|
+
fetchData = {(options)=>{
|
|
122
|
+
return fetchData({...defaultObj(fetchDataProps),fields:fetchFields,table,tableName,fetch,Auth,...options});
|
|
61
123
|
}}
|
|
62
|
-
title = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
124
|
+
title = {({context})=>{
|
|
125
|
+
if(!context || !context.state) return null;
|
|
126
|
+
const {state} = context;
|
|
127
|
+
const footers = context.getFooters && context.getFooters() || {};
|
|
128
|
+
const dataSize = context.getStateDataSize && context.getStateDataSize() || 0;
|
|
129
|
+
const footersValues = context.getFooterValues && context.getFooterValues() || {};
|
|
130
|
+
const y = defaultStr(state.chartConfig?.y);
|
|
131
|
+
const footerValue = y && isObj(footersValues) && footersValues[y] || null;
|
|
132
|
+
const format = defaultStr(isObj(footerValue) && isNonNullString(footerValue.format) && footerValue.format || undefined).toLowerCase();
|
|
133
|
+
const aggregatorFunction = isNonNullString(state.aggregatorFunction)? state.aggregatorFunction : undefined;
|
|
134
|
+
let value = isObj(footerValue) && aggregatorFunction && aggregatorFunction in footerValue ? footerValue[aggregatorFunction] : undefined;
|
|
135
|
+
if(typeof value !=='number'){
|
|
136
|
+
value = isObj(footerValue) && typeof footerValue.sum =='number'? footerValue.sum : 0;
|
|
137
|
+
}
|
|
138
|
+
return <Pressable onPress={onPress} testID={testID+"_TitleContainer"} style={[theme.styles.w100]}>
|
|
139
|
+
<View testID={testID+"_TitleCountUp"} style={[theme.styles.w100]}>
|
|
140
|
+
<View style={[theme.styles.w100,theme.styles.row,theme.styles.alignItemsCenter]}>
|
|
141
|
+
{title}
|
|
142
|
+
{typeof dataSize =='number' && dataSize && <Label>
|
|
143
|
+
{dataSize.formatNumber()}
|
|
144
|
+
</Label> || null}
|
|
145
|
+
</View>
|
|
146
|
+
<Menu
|
|
147
|
+
testID={testID+"_Menu"}
|
|
148
|
+
items = {context.renderMenu()}
|
|
149
|
+
anchor = {(p)=><Pressable {...p} testID={testID+"_MenuAnchor"}>
|
|
150
|
+
<Label
|
|
151
|
+
textCenter
|
|
152
|
+
style = {counUpStyle}
|
|
153
|
+
>{format =='money'?value.formatMoney():value.formatNumber()}</Label>
|
|
154
|
+
</Pressable>}
|
|
155
|
+
/>
|
|
156
|
+
</View>
|
|
157
|
+
</Pressable>
|
|
158
|
+
}}
|
|
159
|
+
/> : <Item
|
|
160
|
+
onPress = {onPress}
|
|
161
|
+
title = {title}
|
|
162
|
+
//style = {[theme.styles.pv1]}
|
|
163
|
+
description = {isLoading || isDatagridLoading?progressBar:<CountUp
|
|
67
164
|
from={0}
|
|
68
165
|
to={count}
|
|
69
|
-
style = {
|
|
166
|
+
style = {counUpStyle}
|
|
70
167
|
/>}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
168
|
+
titleProps ={{primary : true}}
|
|
169
|
+
left = {(aProps)=>{
|
|
170
|
+
return <Menu
|
|
171
|
+
testID={testID+"_Menu"}
|
|
172
|
+
items = {[
|
|
173
|
+
{
|
|
174
|
+
icon : "refresh",
|
|
175
|
+
onPress : refresh,
|
|
176
|
+
text : "Actualiser"
|
|
177
|
+
}
|
|
178
|
+
]}
|
|
179
|
+
anchor = {(p)=><Avatar suffix={index} {...aProps} {...p} icon= {icon} size={40} label={title}/>}
|
|
180
|
+
/>
|
|
181
|
+
}}
|
|
182
|
+
/>;
|
|
74
183
|
}
|
|
75
184
|
|
|
76
185
|
/*** DBSTAT, prend en paramètre le nom de la bd ainsi que celui de la table et affiche en statistic,:
|
|
@@ -78,6 +187,10 @@ export default function DatabaseStatisticContainer (props){
|
|
|
78
187
|
*/
|
|
79
188
|
DatabaseStatisticContainer.propTypes = {
|
|
80
189
|
...Item.propTypes,
|
|
190
|
+
/*** les props supplémentaires à passer à la fonction fetchData */
|
|
191
|
+
fetchDataProps : PropTypes.oneOfType([
|
|
192
|
+
PropTypes.object,
|
|
193
|
+
]),
|
|
81
194
|
/*** La méthode fetchCount doit retourner une promèsse qui lorsqu'elle est résolue, résoue le nombre d'éléments de la table de données en bd */
|
|
82
195
|
fetchCount : PropTypes.func,//la fonction permettant de counter les éléments de la table data
|
|
83
196
|
table : PropTypes.shape({
|
|
@@ -7,7 +7,7 @@ import theme from "$theme";
|
|
|
7
7
|
import PropTypes from "prop-types";
|
|
8
8
|
|
|
9
9
|
export const title = 'Statistiques en BD';
|
|
10
|
-
export default function DatabaseStatisticScreen ({withScreen,tableFilter,fetchCount,title:customTitle,contentProps,containerProps,tables,Component,...props}){
|
|
10
|
+
export default function DatabaseStatisticScreen ({withScreen,fetchDataProps,tableFilter,getTable,fetchCount,fetchData,title:customTitle,contentProps,containerProps,tables,Component,...props}){
|
|
11
11
|
Component = React.isComponent(Component)? Component : Grid;
|
|
12
12
|
containerProps = defaultObj(containerProps);
|
|
13
13
|
const title = containerProps.title = defaultStr(containerProps.title,DatabaseStatisticScreen.title);
|
|
@@ -20,6 +20,10 @@ export default function DatabaseStatisticScreen ({withScreen,tableFilter,fetchCo
|
|
|
20
20
|
let content = [];
|
|
21
21
|
tableFilter = typeof tableFilter ==="function"? tableFilter : x=>true;
|
|
22
22
|
Object.map(tables,(table,index,suffix)=>{
|
|
23
|
+
const t = typeof getTable =='function'?getTable(defaultStr(table?.tableName,table?.table,index)) : null;
|
|
24
|
+
if(isObj(t) && defaultStr(t.table,t.tableName)){
|
|
25
|
+
table = t;
|
|
26
|
+
}
|
|
23
27
|
if(isObj(table) && table.databaseStatistic !== false && table.databaseStatistics !== false && tableFilter(table) !== false){
|
|
24
28
|
content.push(
|
|
25
29
|
<Cell elevation = {5} withSurface mobileSize={12} desktopSize={3} tabletSize={4} {...contentProps} key = {index} >
|
|
@@ -27,6 +31,8 @@ export default function DatabaseStatisticScreen ({withScreen,tableFilter,fetchCo
|
|
|
27
31
|
icon = {table.icon}
|
|
28
32
|
key = {index}
|
|
29
33
|
table = {table}
|
|
34
|
+
fetchData = {fetchData}
|
|
35
|
+
fetchDataProps = {fetchDataProps}
|
|
30
36
|
index = {suffix}
|
|
31
37
|
title = {defaultStr(table.text,table.label,table.title)}
|
|
32
38
|
fetchCount = {table.fetchCount|| typeof fetchCount =='function'? (a,b)=>{
|
|
@@ -51,6 +57,11 @@ export const screenName = DatabaseStatisticScreen.screenName = "DatabaseStatisti
|
|
|
51
57
|
DatabaseStatisticScreen.title = title;
|
|
52
58
|
|
|
53
59
|
DatabaseStatisticScreen.propTypes = {
|
|
60
|
+
/*** les props supplémentaires à passer à la fonction fetchData */
|
|
61
|
+
fetchDataProps : PropTypes.oneOfType([
|
|
62
|
+
PropTypes.object,
|
|
63
|
+
]),
|
|
64
|
+
getTable : PropTypes.func,//la fonction permettant de récupérer la table à partir du nom
|
|
54
65
|
tables : PropTypes.oneOfType([
|
|
55
66
|
PropTypes.arrayOf(PropTypes.object),
|
|
56
67
|
PropTypes.objectOf(PropTypes.object)
|