@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "2.29.3",
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.9",
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
- if(typeof children ==='function'){
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, setCurrent] = React.useState(from);
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) return;
64
- let defVal = filter.defaultValue;
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 +=(content.length?",":"")+"\n"+defaultStr(filter.label,filter.text,filter.field)+" : "+defVal+""
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
- }/*,[filters])*/
106
+ }
105
107
  if(typeof children ==='function'){
106
- const ct = children({content,menus:colMenus});
108
+ const ct = children({});
107
109
  return React.isValidElement(ct)? ct : null;
108
110
  }
109
- const {content,mainFilterTitle,colMenus} = prepareContent(filters);
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)=><Icon
120
- name={hasFilters?"filter-menu":"filter-plus"}
121
- color = {hasFilters?theme.colors.primaryOnSurface:undefined}
122
- {...props}
123
- title = {mainFilterTitle}
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
- <View style={[styles.wrapper]}>
131
- <View style = {[styles.menuWrapper]}>
132
- <Expandable
133
- left = {(props)=><Icon {...props} icon={content.length?'filter-plus':'filter-menu'}/>}
134
- style = {styles.expandable}
135
- title = {<Label>{filterTitle}</Label>}
136
- >
137
- <View style={[styles.menuContent]}>
138
- {colMenus}
139
- </View>
140
- </Expandable>
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
- {content.length ? <View style={[theme.styles.w100]}>
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 = defaultStr(testID,"RN_DatagridAccordion");
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].originValue = defaultVal(this.filters[v].originValue,f.defaultValue,f.value)
276
+ this.filters[v].defaultValue = defaultVal(f.defaultValue,f.value)
268
277
  } else {
269
278
  this.filters[v] = {
270
- originValue : f,
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 = {(p)=><Icon name="material-functions" {...p}
1337
- 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"
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 <Icon
1505
- {...p}
1506
- name = {activeType.icon}
1507
- title = {"Les données s'affichent actuellement en {0}. Cliquez pour modifier le type d'affichage".sprintf(activeType.label)}
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 <Icon {...props} name='format-list-group' title={"Grouper les éléments du tableau"}></Icon>
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.originValue,
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,originValue: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,originValue:v,originValue: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.originValue = filter.defaultValue = arg.defaultValue;
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 == 'accordion' && canRenderAccordion){
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 = defaultStr(testID,'RN_DatagridTableComponent');
129
+ testID = this.getTestID();
127
130
  rest = defaultObj(rest);
128
131
  let showDataSourceSelector = false;
129
132
  if(dataSourceSelector === true){
@@ -6,4 +6,5 @@ export * from "./Common";
6
6
 
7
7
  export {default} from "./IndexComponent";
8
8
 
9
- export {default as SWRDatagrid} from "./SWRDatagrid";
9
+ export {default as SWRDatagrid} from "./SWRDatagrid";
10
+ export {default as Dashboard} from "./Dashboard";
@@ -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+=" [Période1]";
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,originValue:originValue,originValue,field:this.props.field,action,operator,selector,originAction,context:this});
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 ? {width:winW,maxWidth:null} : undefined;
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
- ellipsizeMode = {"tail"}
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) || React.isValidElement(item) || !filter({items,item,_index,index})) return null;
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 { ActivityIndicator } from "react-native-paper";
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 [isLoading,setIsLoading] = React.useState(true);
18
- let {table,fetchCount,index,testID,title,icon,onPress} = props;
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
- fetchCount().then((count)=>{
32
- setCount(count);
33
- setIsLoading(false);
34
- refreshingRef.current = false;
35
- }).catch((e)=>{
36
- setIsLoading(false);
37
- refreshingRef.current = false;
38
- });
39
- },100);
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
- APP.on(cActions.upsert(tableName),refresh);
44
- APP.on(cActions.remove(tableName),refresh);
45
- refresh();
67
+ if(!withDashboard){
68
+ APP.on(cActions.upsert(tableName),refresh);
69
+ APP.on(cActions.remove(tableName),refresh);
70
+ refresh();
71
+ }
46
72
  return ()=>{
47
- APP.off(cActions.upsert(tableName),refresh);
48
- APP.off(cActions.remove(tableName),refresh);
73
+ if(!withDashboard){
74
+ APP.off(cActions.upsert(tableName),refresh);
75
+ APP.off(cActions.remove(tableName),refresh);
76
+ }
49
77
  }
50
78
  },[]);
51
- return <Item
52
- testID = {defaultStr(testID,"RN_DatabaseStatistic_"+table)}
53
- onPress = {(args)=>{
54
- if(onPress && onPress(args) === false) return;
55
- navigateToTableDataList(tableName,{
56
- tableName
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
- left = {(aProps)=>{
60
- return <Avatar suffix={index} {...aProps} icon= {icon} size={40} label={title}/>
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 = {<Label splitText numberOfLines={1} color={theme.colors.primaryOnSurface} style={[{fontSize:15}]}>{title}</Label>}
63
- titleProps = {{primary : true}}
64
- description = {isLoading?<View style={[theme.styles.justifyContentFlexStart,theme.styles.alignItemsFlexStart]}>
65
- <ActivityIndicator color={theme.colors.primary}/>
66
- </View>:<CountUp
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 = {{fontSize:20,color:theme.colors.secondaryOnSurface}}
166
+ style = {counUpStyle}
70
167
  />}
71
- >
72
-
73
- </Item>
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)