@fto-consult/expo-ui 2.13.1 → 2.14.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 +1 -1
- package/src/App.js +1 -0
- package/src/components/Datagrid/Accordion/index.js +4 -0
- package/src/components/Datagrid/Common/Common.js +53 -9
- package/src/components/Datagrid/Common/TableData.js +2 -2
- package/src/components/Datagrid/SWRDatagrid.js +189 -3
- package/src/components/Datagrid/Table/index.js +16 -17
- package/src/components/TableLink/index.js +25 -6
- package/src/layouts/DatabaseStatistics/DatabaseStatistic.js +6 -13
- package/src/layouts/Screen/TableData.js +41 -12
package/package.json
CHANGED
package/src/App.js
CHANGED
|
@@ -325,6 +325,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
325
325
|
backToTopProps,
|
|
326
326
|
testID,
|
|
327
327
|
renderEmpty,
|
|
328
|
+
...rest
|
|
328
329
|
} = this.props
|
|
329
330
|
const hasData = this.state.data.length ? true : false;
|
|
330
331
|
testID = defaultStr(testID,"RN_DatagridAccordion");
|
|
@@ -433,6 +434,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
433
434
|
<View testID={testID+"_HeaderQueryLimit"} style={[styles.paginationItem]}>
|
|
434
435
|
{this.renderQueryLimit(this.state.data.length.formatNumber())}
|
|
435
436
|
</View>
|
|
437
|
+
{this.renderCustomPagination()}
|
|
436
438
|
{sortedColumnsLength ? <View testID={testID+"_HeaderSortedColumns"} style={[styles.sortableItems,styles.paginationItem,{paddingRight:10}]}>
|
|
437
439
|
<Icon
|
|
438
440
|
testID={testID+"_HeaderSortIcon"}
|
|
@@ -503,6 +505,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
503
505
|
,icon : showFooters?'view-column':'view-module'
|
|
504
506
|
,text : (showFooters?'Masquer/Ligne des totaux':'Afficher/Ligne des totaux')
|
|
505
507
|
}:null,
|
|
508
|
+
...this.renderCustomMenu(),
|
|
506
509
|
...restItems,
|
|
507
510
|
this.canScrollTo() && {
|
|
508
511
|
text : 'Retour en haut',
|
|
@@ -579,6 +582,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
579
582
|
{hasData ? <FlashList
|
|
580
583
|
estimatedItemSize = {150}
|
|
581
584
|
prepareItems = {false}
|
|
585
|
+
{...rest}
|
|
582
586
|
{...accordionProps}
|
|
583
587
|
testID = {testID}
|
|
584
588
|
extraData = {this.state.refresh}
|
|
@@ -308,6 +308,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
308
308
|
return ;
|
|
309
309
|
}
|
|
310
310
|
canPaginateData(){
|
|
311
|
+
if(this.props.handlePagination === false) return false;
|
|
311
312
|
return false;
|
|
312
313
|
}
|
|
313
314
|
getFiltersProps(){
|
|
@@ -752,7 +753,41 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
752
753
|
}
|
|
753
754
|
this.handlePagination(newStart, limit, nextPage);
|
|
754
755
|
}
|
|
755
|
-
|
|
756
|
+
/*** pour le rendu personalisé de la pagination */
|
|
757
|
+
renderCustomPagination(){
|
|
758
|
+
if(typeof this.props.renderCustomPagination ==='function'){
|
|
759
|
+
const r = this.props.renderCustomPagination({
|
|
760
|
+
context:this,refresh:this.refresh.bind(this)
|
|
761
|
+
});
|
|
762
|
+
return React.isValidElement(r)? r : null;
|
|
763
|
+
}
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
/*** permet de faire le rendu de certaines entête personalisés
|
|
767
|
+
* utile lorsque l'on veut par exemple afficher d'autres information au niveau de l'entête du tableau
|
|
768
|
+
*/
|
|
769
|
+
renderCustomMenu(){
|
|
770
|
+
const customMenu = []
|
|
771
|
+
Object.map(this.props.customMenu,(menu,i)=>{
|
|
772
|
+
if(isObj(menu)){
|
|
773
|
+
const {onPress,menu,label,text,children,...rest} = menu;
|
|
774
|
+
const args = {context:this,refresh:this.refresh.bind(this)};
|
|
775
|
+
const lCB = defaultVal(children,label,text);
|
|
776
|
+
const labelText = typeof lCB ==='function'? lCB(args) : lCB;
|
|
777
|
+
if(labelText === false || !React.isValidElement(labelText,true)) return;
|
|
778
|
+
customMenu.push({
|
|
779
|
+
...rest,
|
|
780
|
+
label : labelText,
|
|
781
|
+
onPress : (event)=>{
|
|
782
|
+
if(typeof onPress =='function'){
|
|
783
|
+
return onPress({...React.getOnPressArgs(event),...args})
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
})
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
return customMenu;
|
|
790
|
+
}
|
|
756
791
|
/*** aller à la dernière page */
|
|
757
792
|
_goToLastPage(){
|
|
758
793
|
let { start,limit} = this._pagination;
|
|
@@ -1320,7 +1355,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1320
1355
|
this._pagination.start = 0;
|
|
1321
1356
|
}
|
|
1322
1357
|
this.filtersSelectors = {selector:this.getFilters()};
|
|
1323
|
-
return this.fetchData({force:true,fetchOptions:this.filtersSelectors});
|
|
1358
|
+
return this.fetchData({force:true,isFiltering : true,fetchOptions:this.filtersSelectors});
|
|
1324
1359
|
}
|
|
1325
1360
|
onSetQueryLimit(){
|
|
1326
1361
|
if(!this.canSetQueryLimit()) return;
|
|
@@ -1334,7 +1369,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1334
1369
|
this.currentDatagridQueryLimit = this.getSessionData("dataSourceQueryLimit");
|
|
1335
1370
|
setQueryLimit(this.currentDatagridQueryLimit,(limit)=>{
|
|
1336
1371
|
this.setSessionData("dataSourceQueryLimit",limit)
|
|
1337
|
-
notify.success("Le nombre maximal d'élément a été définit à la valeur "+(limit==0?" infinit ":limit.formatNumber())+". Cette valeur sera prise en compte à la prochaine réactualisation du tableau")
|
|
1372
|
+
notify.success("Le nombre maximal d'élément à récuperer par page a été définit à la valeur "+(limit==0?" infinit ":limit.formatNumber())+". Cette valeur sera prise en compte à la prochaine réactualisation du tableau")
|
|
1338
1373
|
});
|
|
1339
1374
|
}
|
|
1340
1375
|
|
|
@@ -1348,6 +1383,9 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1348
1383
|
return 0;
|
|
1349
1384
|
}
|
|
1350
1385
|
canHandleQueryLimit(){
|
|
1386
|
+
if(typeof this.props.handleQueryLimit ==='boolean'){
|
|
1387
|
+
return this.props.handleQueryLimit;
|
|
1388
|
+
}
|
|
1351
1389
|
return true;
|
|
1352
1390
|
}
|
|
1353
1391
|
renderQueryLimit(content){
|
|
@@ -1357,7 +1395,7 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1357
1395
|
let s = "";
|
|
1358
1396
|
let canSetQ = this.canSetQueryLimit();
|
|
1359
1397
|
if(canSetQ){
|
|
1360
|
-
s = ".\nPressez pendent quelques secondes pour modifier cette valeur du nombre limite d'éléments de la liste";
|
|
1398
|
+
s = ".\nPressez pendent quelques secondes pour modifier cette valeur du nombre limite d'éléments de la liste par page";
|
|
1361
1399
|
}
|
|
1362
1400
|
else if(isDecimal(cLImit)){
|
|
1363
1401
|
cLImit = (" de "+cLImit.formatNumber())
|
|
@@ -1666,16 +1704,14 @@ export default class CommonDatagridComponent extends AppComponent {
|
|
|
1666
1704
|
else if(_type =='select_country' || _type =='selectcountry'){
|
|
1667
1705
|
_render = <Flag withCode {...columnDef} length={undefined} width={undefined} height={undefined} code={defaultValue}/>
|
|
1668
1706
|
}
|
|
1669
|
-
///le lien vers le table data se fait via
|
|
1670
|
-
else if(
|
|
1671
|
-
let tableName = defaultStr(columnDef.linkToTable && columnDef.linkToTable,columnDef.foreignKeyTable && columnDef.foreignKeyTable,columnDef.tableName,columnDef.table).toUpperCase();
|
|
1707
|
+
///le lien vers le table data se fait via la colonne ayant la propriété foreignKeyTable de type chaine de caractère non nulle
|
|
1708
|
+
else if(isNonNullString(columnDef.foreignKeyTable)){
|
|
1672
1709
|
const id = rowData[columnField]?.toString();
|
|
1673
1710
|
if(isNonNullString(id)){
|
|
1674
1711
|
_render = <TableLink
|
|
1675
|
-
tableName = {tableName}
|
|
1676
1712
|
id = {id}
|
|
1713
|
+
{...columnDef}
|
|
1677
1714
|
data = {rowData}
|
|
1678
|
-
columnDef = {columnDef}
|
|
1679
1715
|
columnField = {columnField}
|
|
1680
1716
|
>
|
|
1681
1717
|
{rowData[columnField]}
|
|
@@ -1950,6 +1986,14 @@ CommonDatagridComponent.propTypes = {
|
|
|
1950
1986
|
/*** pour le rendu du footer, pied de page en affichage accordion */
|
|
1951
1987
|
sessionName : PropTypes.string,
|
|
1952
1988
|
onFetchData : PropTypes.func,
|
|
1989
|
+
handleQueryLimit : PropTypes.bool, ///si le datagrid devra gérer les queryLimit
|
|
1990
|
+
/**** les menus customisés à ajouter au composant Datagrid */
|
|
1991
|
+
customMenu : PropTypes.oneOfType([
|
|
1992
|
+
PropTypes.arrayOf(PropTypes.object),
|
|
1993
|
+
PropTypes.objectOf(PropTypes.object),
|
|
1994
|
+
]),
|
|
1995
|
+
/**** la fonction permettant de faire le rendu dun contenu paginé, personalisé */
|
|
1996
|
+
renderCustomPagination : PropTypes.func,
|
|
1953
1997
|
getActionsArgs : PropTypes.func,//fonction permettant de récupérer les props supplémentaires à passer aux actions du datagrid
|
|
1954
1998
|
}
|
|
1955
1999
|
|
|
@@ -60,7 +60,7 @@ export default class CommonTableDatagrid extends CommonDatagrid{
|
|
|
60
60
|
la table dans la base common.
|
|
61
61
|
Elle pourra éventuellement passer directement la limite et les filtres à la fonction fetchdata
|
|
62
62
|
*/
|
|
63
|
-
fetchData ({cb,callback,force,fetchOptions}){
|
|
63
|
+
fetchData ({cb,callback,force,fetchOptions,...rest}){
|
|
64
64
|
if(!this._isMounted()) return Promise.resolve(this.state.data);
|
|
65
65
|
if(this.isFetchingData) {
|
|
66
66
|
if(!isPromise(this.fetchingPromiseData)){
|
|
@@ -92,7 +92,7 @@ export default class CommonTableDatagrid extends CommonDatagrid{
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
this.beforeFetchData(fetchOptions);
|
|
95
|
-
if(typeof this.props.beforeFetchData =='function' && this.props.beforeFetchData({context:this,force,fetchOptions,options:fetchOptions}) === false){
|
|
95
|
+
if(typeof this.props.beforeFetchData =='function' && this.props.beforeFetchData({...rest,context:this,force,fetchOptions,options:fetchOptions}) === false){
|
|
96
96
|
this.isFetchingData = false;
|
|
97
97
|
return resolve(this.state.data);
|
|
98
98
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Datagrid from "./IndexComponent";
|
|
9
9
|
import {defaultStr,defaultObj,defaultVal,isObjOrArray,isObj,extendObj} from "$utils";
|
|
10
|
+
import {Pressable} from "react-native";
|
|
11
|
+
import SimpleSelect from "$ecomponents/SimpleSelect";
|
|
10
12
|
import React from "$react";
|
|
11
13
|
import Auth from "$cauth";
|
|
12
14
|
import DateLib from "$lib/date";
|
|
@@ -22,6 +24,9 @@ import appConfig from "$capp/config";
|
|
|
22
24
|
import APP from "$capp/instance";
|
|
23
25
|
import cAction from "$cactions";
|
|
24
26
|
import PropTypes from "prop-types";
|
|
27
|
+
import {isDesktopMedia} from "$dimensions";
|
|
28
|
+
import ActivityIndicator from "$ecomponents/ActivityIndicator";
|
|
29
|
+
import {Menu} from "$ecomponents/BottomSheet";
|
|
25
30
|
|
|
26
31
|
const timeout = 5000*60*60;
|
|
27
32
|
export const swrOptions = {
|
|
@@ -31,7 +36,9 @@ export const swrOptions = {
|
|
|
31
36
|
errorRetryInterval : timeout*2,
|
|
32
37
|
errorRetryCount : 5,
|
|
33
38
|
}
|
|
34
|
-
|
|
39
|
+
const getDefaultPaginationRowsPerPageItems = ()=>{
|
|
40
|
+
return [5,10,15,20,25,30,40,50,60,80,100,500,1000,...(isDesktopMedia() ? [1500,2000,2500,3000,3500,4000,4500,5000,10000]:[])];
|
|
41
|
+
}
|
|
35
42
|
/****la fonction fetcher doit toujours retourner :
|
|
36
43
|
* 1. la liste des éléments fetchés dans la props data
|
|
37
44
|
* 2. le nombre total d'éléments de la liste obtenue en escluant les clause limit et offset correspondant à la même requête
|
|
@@ -53,10 +60,13 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
|
|
|
53
60
|
fetchData,
|
|
54
61
|
fetchPath,
|
|
55
62
|
fetcher,
|
|
63
|
+
ListFooterComponent,
|
|
64
|
+
testID,
|
|
56
65
|
...rest
|
|
57
66
|
} = props;
|
|
58
67
|
rest = defaultObj(rest);
|
|
59
68
|
rest.exportTableProps = defaultObj(rest.exportTableProps)
|
|
69
|
+
const firstPage = 1;
|
|
60
70
|
const tableName = defaultStr(table.tableName,table.table).trim().toUpperCase();
|
|
61
71
|
canMakePhoneCall = defaultBool(canMakePhoneCall,table.canMakePhoneCall);
|
|
62
72
|
makePhoneCallProps = defaultObj(makePhoneCallProps,rest.makePhoneCallProps,table.makePhoneCallProps);
|
|
@@ -98,13 +108,30 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
|
|
|
98
108
|
const hasResultRef = React.useRef(false);
|
|
99
109
|
const totalRef = React.useRef(0);
|
|
100
110
|
const isFetchingRef = React.useRef(false);
|
|
111
|
+
const pageRef = React.useRef(1);
|
|
112
|
+
const limitRef = React.useRef(500);
|
|
113
|
+
const isInitializedRef = React.useRef(false);
|
|
114
|
+
testID = defaultStr(testID,"RNSWRDatagridComponent")
|
|
101
115
|
const {error, isValidating,isLoading,refresh} = useSWR(fetchPath,{
|
|
102
116
|
fetcher : (url,opts)=>{
|
|
117
|
+
if(!isInitializedRef.current) {
|
|
118
|
+
isFetchingRef.current = false;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
103
121
|
opts = extendObj({},opts,fetchOptionsRef.current);
|
|
104
122
|
opts.queryParams = defaultObj(opts.queryParams);
|
|
105
123
|
opts.queryParams.withTotal = true;
|
|
124
|
+
opts.queryParams.limit = limitRef.current;
|
|
125
|
+
opts.queryParams.page = pageRef.current -1;
|
|
106
126
|
const fetchCB = ({data,total})=>{
|
|
107
127
|
totalRef.current = total;
|
|
128
|
+
/***
|
|
129
|
+
* if(pageRef.current ===firstPage){
|
|
130
|
+
dataRef.current = data;
|
|
131
|
+
} else {
|
|
132
|
+
dataRef.current = prevPage != pageRef.current ? (isObj(data)?{...dataRef.current,...data}:[...dataRef.current,...data]) : data;
|
|
133
|
+
}
|
|
134
|
+
*/
|
|
108
135
|
dataRef.current = data;
|
|
109
136
|
hasResultRef.current = true;
|
|
110
137
|
return data;
|
|
@@ -142,10 +169,41 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
|
|
|
142
169
|
showProgressRef.current = showProgress ? typeof showProgress ==='boolean' : false;
|
|
143
170
|
if(isFetchingRef.current) return;
|
|
144
171
|
refreshCBRef.current = ()=>{
|
|
145
|
-
showProgressRef.current = false;
|
|
172
|
+
//showProgressRef.current = false;
|
|
146
173
|
};
|
|
147
174
|
refresh();
|
|
148
175
|
}
|
|
176
|
+
const canPagninate = ()=>{
|
|
177
|
+
if(typeof totalRef.current !=='number' || typeof pageRef.current !='number' || typeof limitRef.current !='number') return false;
|
|
178
|
+
if(limitRef.current <= 0) return false;
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
const getTotalPages = ()=>{
|
|
182
|
+
if(!canPagninate()) return false;
|
|
183
|
+
return Math.ceil(totalRef.current / limitRef.current);;
|
|
184
|
+
};
|
|
185
|
+
const getNextPage = ()=>{
|
|
186
|
+
if(!canPagninate()) return false;
|
|
187
|
+
const totalPages = getTotalPages();
|
|
188
|
+
let nPage = pageRef.current+1;
|
|
189
|
+
if(nPage > totalPages){
|
|
190
|
+
nPage = totalPages;
|
|
191
|
+
}
|
|
192
|
+
if(nPage === pageRef.current){
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return nPage;
|
|
196
|
+
},getPrevPage = ()=>{
|
|
197
|
+
if(!canPagninate()) return false;
|
|
198
|
+
let pPage = pageRef.current - 1;
|
|
199
|
+
if(pPage < firstPage){
|
|
200
|
+
pPage = firstPage;
|
|
201
|
+
}
|
|
202
|
+
if(pPage === pageRef.current){
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return pPage;
|
|
206
|
+
}
|
|
149
207
|
React.useEffect(()=>{
|
|
150
208
|
const upsert = cAction.upsert(tableName);
|
|
151
209
|
const remove = cAction.remove(tableName);
|
|
@@ -159,15 +217,138 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
|
|
|
159
217
|
APP.off(remove,onUpsert);
|
|
160
218
|
}
|
|
161
219
|
},[]);
|
|
220
|
+
const loading = (isLoading|| isValidating);
|
|
221
|
+
const pointerEvents = loading ?"node" : "auto";
|
|
162
222
|
return (
|
|
163
223
|
<Datagrid
|
|
224
|
+
testID = {testID}
|
|
164
225
|
{...rest}
|
|
165
226
|
{...defaultObj(table.datagrid)}
|
|
166
|
-
|
|
227
|
+
renderCustomPagination = {({context})=>{
|
|
228
|
+
if(!canPagninate()) return null;
|
|
229
|
+
const page = pageRef.current, totalPages = getTotalPages(), prevPage = getPrevPage(),nextPage = getNextPage();
|
|
230
|
+
const iconProp = {
|
|
231
|
+
size : 25,
|
|
232
|
+
style : [theme.styles.noMargin,theme.styles.noPadding],
|
|
233
|
+
}
|
|
234
|
+
const sStyle = [styles.limitStyle1,theme.styles.noPadding,theme.styles.noMargin];
|
|
235
|
+
return <View testID={testID+"_PaginationContainer"} pointerEvents={pointerEvents}>
|
|
236
|
+
<View style={[theme.styles.row,theme.styles.w100]} pointerEvents={pointerEvents} testID={testID+"_PaginationContentContainer"}>
|
|
237
|
+
<Menu
|
|
238
|
+
testID={testID+"_SimpleSelect"}
|
|
239
|
+
style = {sStyle}
|
|
240
|
+
anchor = {(p)=>{
|
|
241
|
+
return <Pressable style={[theme.styles.noMargin,theme.styles.noPadding]} {...p} testID={testID+"MenuSelectLimit"}>
|
|
242
|
+
<Label primary testID={testID+"_Label"} style={[{fontSize:15}]}>
|
|
243
|
+
{limitRef.current.formatNumber()}
|
|
244
|
+
</Label>
|
|
245
|
+
</Pressable>
|
|
246
|
+
}}
|
|
247
|
+
title = {'Limite du nombre d\'éléments par page'}
|
|
248
|
+
items = {getDefaultPaginationRowsPerPageItems().map((item)=>{
|
|
249
|
+
return {
|
|
250
|
+
text : item.formatNumber(),
|
|
251
|
+
icon : limitRef.current == item ? 'check' : null,
|
|
252
|
+
primary : limitRef.current === item ? true : false,
|
|
253
|
+
onPress : ()=>{
|
|
254
|
+
if(item == limitRef.current) return;
|
|
255
|
+
limitRef.current = item;
|
|
256
|
+
pageRef.current = firstPage;
|
|
257
|
+
setTimeout(() => {
|
|
258
|
+
doRefresh(true);
|
|
259
|
+
}, (500));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
})}
|
|
263
|
+
/>
|
|
264
|
+
<Icon
|
|
265
|
+
///firstPage
|
|
266
|
+
{...iconProp}
|
|
267
|
+
title = {"Aller à la première page"}
|
|
268
|
+
name="material-first-page"
|
|
269
|
+
disabled = {pageRef.current <= 1 && true || false}
|
|
270
|
+
onPress = {()=>{
|
|
271
|
+
if(pageRef.current <= firstPage) return;
|
|
272
|
+
pageRef.current = firstPage;
|
|
273
|
+
doRefresh(true);
|
|
274
|
+
}}
|
|
275
|
+
/>
|
|
276
|
+
<Icon
|
|
277
|
+
//decrement
|
|
278
|
+
{...iconProp}
|
|
279
|
+
title = {"Aller à la page précédente {0}".sprintf(prevPage && prevPage.formatNumber()||undefined)}
|
|
280
|
+
name="material-keyboard-arrow-left"
|
|
281
|
+
disabled = {page === prevPage || getPrevPage() === false ? true : false}
|
|
282
|
+
onPress = {()=>{
|
|
283
|
+
const page = getPrevPage();
|
|
284
|
+
if(page === false) return;
|
|
285
|
+
if(pageRef.current === page) return;
|
|
286
|
+
pageRef.current = page;
|
|
287
|
+
doRefresh(true);
|
|
288
|
+
}}
|
|
289
|
+
|
|
290
|
+
/>
|
|
291
|
+
<View testID={testID+"_PaginationLabel"}>
|
|
292
|
+
<Label style={{fontSize:15}}>
|
|
293
|
+
{page.formatNumber()}-{totalPages.formatNumber()}{" / "}{totalRef.current.formatNumber()}
|
|
294
|
+
</Label>
|
|
295
|
+
</View>
|
|
296
|
+
<Icon
|
|
297
|
+
//increment
|
|
298
|
+
{...iconProp}
|
|
299
|
+
title = {"Aller à la page suivante {0}".sprintf(nextPage && nextPage.formatNumber()||undefined)}
|
|
300
|
+
name="material-keyboard-arrow-right"
|
|
301
|
+
disabled = {nextPage >= totalPages || getNextPage() === false ? true : false}
|
|
302
|
+
onPress = {()=>{
|
|
303
|
+
const page = getNextPage();
|
|
304
|
+
if(page === false) return;
|
|
305
|
+
if(pageRef.current === page) return;
|
|
306
|
+
pageRef.current = page;
|
|
307
|
+
doRefresh(true);
|
|
308
|
+
}}
|
|
309
|
+
/>
|
|
310
|
+
<Icon
|
|
311
|
+
//lastPage
|
|
312
|
+
{...iconProp}
|
|
313
|
+
name="material-last-page"
|
|
314
|
+
title = {"Aller à la dernière page {0}".sprintf(totalPages && totalPages.formatNumber()||undefined)}
|
|
315
|
+
disabled = {page >= totalPages ? true : false}
|
|
316
|
+
onPress = {()=>{
|
|
317
|
+
const page = getTotalPages();
|
|
318
|
+
if(pageRef.current >= page) return;
|
|
319
|
+
pageRef.current = page;
|
|
320
|
+
doRefresh(true);
|
|
321
|
+
}}
|
|
322
|
+
|
|
323
|
+
/>
|
|
324
|
+
</View>
|
|
325
|
+
</View>
|
|
326
|
+
}}
|
|
327
|
+
ListFooterComponent = {(props)=>{
|
|
328
|
+
const r = typeof ListFooterComponent =='function'? ListFooterComponent(props) : null;
|
|
329
|
+
if(!loading) return r;
|
|
330
|
+
const aContent = <View testID={testID+"_ListHeaderActivityIndicator"} style={[theme.styles.w100,theme.styles.justifyContentCenter]}>
|
|
331
|
+
<ActivityIndicator color={theme.colors.primary}/>
|
|
332
|
+
</View>;
|
|
333
|
+
if(r){
|
|
334
|
+
return <View testID={testID+"_ListHeaderContainer"} style={[theme.styles.w100]}>
|
|
335
|
+
{r}
|
|
336
|
+
{aContent}
|
|
337
|
+
</View>
|
|
338
|
+
}
|
|
339
|
+
return aContent;
|
|
340
|
+
}}
|
|
341
|
+
handleQueryLimit = {false}
|
|
342
|
+
handlePagination = {false}
|
|
343
|
+
isLoading = {loading && !error && showProgressRef.current && true || false}
|
|
167
344
|
beforeFetchData = {({fetchOptions:opts,force})=>{
|
|
168
345
|
opts.fields = fetchFields;
|
|
169
346
|
opts = getFetchOptions({showError:showProgressRef.current,...opts});
|
|
347
|
+
isInitializedRef.current = true;
|
|
170
348
|
fetchOptionsRef.current = opts;
|
|
349
|
+
if(force){
|
|
350
|
+
pageRef.current = firstPage;
|
|
351
|
+
}
|
|
171
352
|
doRefresh(force);
|
|
172
353
|
return false;
|
|
173
354
|
}}
|
|
@@ -218,6 +399,11 @@ const styles = StyleSheet.create({
|
|
|
218
399
|
labelTitle: {
|
|
219
400
|
fontSize : 18,
|
|
220
401
|
},
|
|
402
|
+
limitStyle : {
|
|
403
|
+
backgroundColor:'transparent',
|
|
404
|
+
width:50,
|
|
405
|
+
height : 35,
|
|
406
|
+
},
|
|
221
407
|
emptyText : {
|
|
222
408
|
fontSize : 16,
|
|
223
409
|
fontWeight : 'bold',
|
|
@@ -170,20 +170,21 @@ const DatagridFactory = (Factory)=>{
|
|
|
170
170
|
if(selectableMultiple && max && defaultBool(this.props.selectableMultiple,true)){
|
|
171
171
|
max = max.formatNumber();
|
|
172
172
|
restItems = [
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
...this.renderCustomMenu(),
|
|
174
|
+
...(selectableMultiple ? [{
|
|
175
|
+
label : "Sélect "+max,
|
|
175
176
|
icon : "select-all",
|
|
176
177
|
onPress : (x,event)=>{
|
|
177
178
|
this.handleAllRowsToggle(true);
|
|
178
179
|
}
|
|
179
180
|
},
|
|
180
181
|
{
|
|
181
|
-
|
|
182
|
+
label : "Tout désélec",
|
|
182
183
|
onPress : (x,event)=>{
|
|
183
184
|
this.handleAllRowsToggle(false);
|
|
184
185
|
},
|
|
185
186
|
icon : "select"
|
|
186
|
-
}
|
|
187
|
+
}] : [])
|
|
187
188
|
]
|
|
188
189
|
}
|
|
189
190
|
const rPagination = showPagination ? <View style={[styles.paginationContainer]}>
|
|
@@ -192,6 +193,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
192
193
|
<View testID={testID+"_HeaderQueryLimit"}>
|
|
193
194
|
{this.renderQueryLimit(this.state.data.length.formatNumber())}
|
|
194
195
|
</View>
|
|
196
|
+
{this.renderCustomPagination()}
|
|
195
197
|
{!isMobile && <>
|
|
196
198
|
<Button normal style={[styles.paginationItem]} icon = {"refresh"} onPress = {this.refresh.bind(this)}>
|
|
197
199
|
Rafraichir
|
|
@@ -214,19 +216,15 @@ const DatagridFactory = (Factory)=>{
|
|
|
214
216
|
>
|
|
215
217
|
{showFooters?'Masquer/Ligne des totaux':'Afficher/Ligne des totaux'}
|
|
216
218
|
</Button>:null}
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
{item.text}
|
|
227
|
-
</Button>
|
|
228
|
-
})}
|
|
229
|
-
</>)}
|
|
219
|
+
{restItems.map((item,index)=>{
|
|
220
|
+
return <Button
|
|
221
|
+
normal
|
|
222
|
+
key = {index}
|
|
223
|
+
{...item}
|
|
224
|
+
style={[styles.paginationItem,item.style]}
|
|
225
|
+
children = {item.children|| item.label}
|
|
226
|
+
/>
|
|
227
|
+
})}
|
|
230
228
|
</>}
|
|
231
229
|
{exportable && (
|
|
232
230
|
<>{/**
|
|
@@ -320,6 +318,7 @@ const DatagridFactory = (Factory)=>{
|
|
|
320
318
|
</View>
|
|
321
319
|
<Table
|
|
322
320
|
ref = {this.listRef}
|
|
321
|
+
{...rest}
|
|
323
322
|
hasFooters = {hasFooterFields}
|
|
324
323
|
showFilters = {showFilters}
|
|
325
324
|
showFooters = {showFooters}
|
|
@@ -11,15 +11,16 @@ import {defaultStr,isPromise,defaultObj} from "$utils";
|
|
|
11
11
|
import {open as openPreloader,close as closePreloader} from "$preloader";
|
|
12
12
|
import {styles as _styles} from "$theme";
|
|
13
13
|
import Tooltip from "$ecomponents/Tooltip";
|
|
14
|
+
import {navigateToTableData} from "$enavigation/utils";
|
|
14
15
|
|
|
15
16
|
const TableLinKComponent = React.forwardRef((props,ref)=>{
|
|
16
|
-
let {disabled,labelProps,server,containerProps,id,
|
|
17
|
+
let {disabled,labelProps,server,containerProps,id,fetchForeignData,foreignKeyTable,foreignKeyColumn,data,testID,Component,routeName,routeParams,component,primary,triggerProps,onPress,children, ...rest} = props;
|
|
17
18
|
testID = defaultStr(testID,"RN_TableDataLinkContainer")
|
|
18
|
-
|
|
19
|
+
foreignKeyTable = defaultStr(foreignKeyTable).trim();
|
|
20
|
+
foreignKeyColumn = defaultStr(foreignKeyColumn).trim();
|
|
19
21
|
rest = defaultObj(rest);
|
|
20
22
|
containerProps = defaultObj(containerProps)
|
|
21
23
|
labelProps = defaultObj(labelProps);
|
|
22
|
-
columnDef = defaultObj(columnDef);
|
|
23
24
|
data = defaultObj(data);
|
|
24
25
|
id = defaultStr(id);
|
|
25
26
|
if(!id){
|
|
@@ -28,10 +29,25 @@ const TableLinKComponent = React.forwardRef((props,ref)=>{
|
|
|
28
29
|
const pointerEvents = disabled || !id? 'none' : 'auto';
|
|
29
30
|
const onPressLink = (event)=>{
|
|
30
31
|
React.stopEventPropagation(event);
|
|
31
|
-
const
|
|
32
|
+
const args = {...React.getOnPressArgs(event),...rest,foreignKeyTable,foreignKeyColumn,data,id,value:id};
|
|
33
|
+
let r = typeof onPress =='function'? onPress(args) : undefined;
|
|
34
|
+
if(r === false) return;
|
|
35
|
+
const cb = (a)=>{
|
|
36
|
+
const r2 = typeof fetchForeignData === 'function'? fetchForeignData({...args,...defaultObj(a)}) : undefined;
|
|
37
|
+
if(isPromise(r2)){
|
|
38
|
+
return r2.then((data)=>{
|
|
39
|
+
if(isObj(data) && data[foreignKeyColumn] !== undefined){
|
|
40
|
+
navigateToTableData({tableName:foreignKeyTable,data});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return Promise.reject({msg:'type de données retournée par la fonction fetchForeignKeyData invalide'});
|
|
45
|
+
}
|
|
46
|
+
openPreloader("traitement de la requête...");
|
|
32
47
|
if(isPromise(r)){
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
r.then(cb).finally(closePreloader);
|
|
49
|
+
} else {
|
|
50
|
+
cb().finally(closePreloader);
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
rest.style = [rest.style,_styles.cursorPointer];
|
|
@@ -47,6 +63,9 @@ export default TableLinKComponent;
|
|
|
47
63
|
|
|
48
64
|
|
|
49
65
|
TableLinKComponent.propTypes = {
|
|
66
|
+
foreignKeyColumn : PropTypes.string.isRequired,//le nom de la colonne de la clé étrangère
|
|
67
|
+
foreignKeyTable : PropTypes.string.isRequired, //le nom de la table référencée
|
|
68
|
+
fetchForeignData : PropTypes.func, // la fonction permettant de chercher la données à distance
|
|
50
69
|
server : PropTypes.string,//le serveur sur lequel rechercher les données
|
|
51
70
|
primary : PropTypes.bool,//si le composant sera stylé en theme primary de react
|
|
52
71
|
///les props à utiliser pour afficher la table de données en cas de click sur le lien
|
|
@@ -13,10 +13,8 @@ import cActions from "$cactions";
|
|
|
13
13
|
import {View} from "react-native";
|
|
14
14
|
|
|
15
15
|
export default function DatabaseStatisticContainer (props){
|
|
16
|
-
const [
|
|
17
|
-
|
|
18
|
-
count : 0,
|
|
19
|
-
});
|
|
16
|
+
const [count,setCount] = React.useState(0);
|
|
17
|
+
const [isLoading,setIsLoading] = React.useState(true);
|
|
20
18
|
let {table,fetchCount,index,testID,title,icon,onPress} = props;
|
|
21
19
|
title = defaultStr(title)
|
|
22
20
|
table = defaultObj(table);
|
|
@@ -28,14 +26,14 @@ export default function DatabaseStatisticContainer (props){
|
|
|
28
26
|
const refresh = ()=>{
|
|
29
27
|
if(refreshingRef.current || !isMounted()) return;
|
|
30
28
|
refreshingRef.current = true;
|
|
29
|
+
setIsLoading(true);
|
|
31
30
|
setTimeout(()=>{
|
|
32
31
|
fetchCount().then((count)=>{
|
|
33
|
-
|
|
32
|
+
setCount(count);
|
|
33
|
+
setIsLoading(false);
|
|
34
34
|
refreshingRef.current = false;
|
|
35
35
|
}).catch((e)=>{
|
|
36
|
-
|
|
37
|
-
isLoading : false, count : 0,
|
|
38
|
-
});
|
|
36
|
+
setIsLoading(false);
|
|
39
37
|
refreshingRef.current = false;
|
|
40
38
|
});
|
|
41
39
|
},100);
|
|
@@ -50,10 +48,6 @@ export default function DatabaseStatisticContainer (props){
|
|
|
50
48
|
APP.off(cActions.remove(tableName),refresh);
|
|
51
49
|
}
|
|
52
50
|
},[]);
|
|
53
|
-
React.useEffect(()=>{
|
|
54
|
-
refresh();
|
|
55
|
-
},[props])
|
|
56
|
-
const {isLoading,count} = state;
|
|
57
51
|
return <Item
|
|
58
52
|
testID = {defaultStr(testID,"RN_DatabaseStatistic_"+table)}
|
|
59
53
|
onPress = {(args)=>{
|
|
@@ -65,7 +59,6 @@ export default function DatabaseStatisticContainer (props){
|
|
|
65
59
|
left = {(aProps)=>{
|
|
66
60
|
return <Avatar suffix={index} {...aProps} icon= {icon} size={40} label={title}/>
|
|
67
61
|
}}
|
|
68
|
-
//right = {(rP)=><Icon {...rP} name='refresh' onPress={refresh}/>}
|
|
69
62
|
title = {<Label splitText numberOfLines={1} primary style={[{fontSize:15}]}>{title}</Label>}
|
|
70
63
|
titleProps = {{primary : true}}
|
|
71
64
|
description = {isLoading?<View style={[theme.styles.justifyContentFlexStart,theme.styles.alignItemsFlexStart]}>
|
|
@@ -29,6 +29,43 @@ const DEFAULT_TABS_KEYS = "main-tabs";
|
|
|
29
29
|
|
|
30
30
|
const TIMEOUT = 50;
|
|
31
31
|
|
|
32
|
+
const checkPrimary = (data,f)=>{
|
|
33
|
+
return !(!(f in data) || (data[f] == null) || (!data[f] && typeof data !=='number'));
|
|
34
|
+
}
|
|
35
|
+
/*** vérifie si le document passé en paramètre est éditable
|
|
36
|
+
* @param {object} data la données à vérifier
|
|
37
|
+
* @param {object| array} les champs sur lesquels se baser pour vérifier si la donénes est une mise à jour
|
|
38
|
+
* @param {func} checkPrimaryKey la foncition permettant de vérifier s'il s'agit d'une clé primaire pour la données courante
|
|
39
|
+
*/
|
|
40
|
+
export const isDocEditing = (data,fields,checkPrimaryKey)=>{
|
|
41
|
+
if(!isObj(data) || !isObjOrArray(fields)) return false;
|
|
42
|
+
|
|
43
|
+
let hasPrimaryFields = false;
|
|
44
|
+
let hasValidated = true;
|
|
45
|
+
for(let i in fields){
|
|
46
|
+
const field = fields[i];
|
|
47
|
+
if(typeof checkPrimaryKey =='function') {
|
|
48
|
+
hasPrimaryFields = true;
|
|
49
|
+
if(checkPrimaryKey({field,i,index:i,data}) === false){
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if(!isObj(field)) continue;
|
|
55
|
+
hasPrimaryFields = true;
|
|
56
|
+
const f = defaultStr(field.field,i);
|
|
57
|
+
if(field.primaryKey === true){
|
|
58
|
+
if(!checkPrimary(data,f)){
|
|
59
|
+
hasValidated = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if(hasPrimaryFields){
|
|
64
|
+
return hasValidated;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
32
69
|
export default class TableDataScreenComponent extends FormDataScreen{
|
|
33
70
|
constructor(props){
|
|
34
71
|
super(props);
|
|
@@ -49,7 +86,7 @@ export default class TableDataScreenComponent extends FormDataScreen{
|
|
|
49
86
|
Object.map(table.fields,(field,i)=>{
|
|
50
87
|
if(isObj(field) && field.form !== false){
|
|
51
88
|
fields[i] = Object.clone(field);
|
|
52
|
-
if(field.
|
|
89
|
+
if(field.primaryKey === true){
|
|
53
90
|
primaryKeyFields[field.field || i] = true;
|
|
54
91
|
}
|
|
55
92
|
} else {
|
|
@@ -420,17 +457,9 @@ export default class TableDataScreenComponent extends FormDataScreen{
|
|
|
420
457
|
isDocEditing(data){
|
|
421
458
|
data = defaultObj(data);
|
|
422
459
|
if(!this.isDocEditingProp){
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
hasPrimaryFields = true;
|
|
427
|
-
if(!(f in data) || (data[f] == null) || (!data[f] && typeof data !=='number')){
|
|
428
|
-
hasValidated = false;
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
if(hasPrimaryFields){
|
|
432
|
-
return hasValidated;
|
|
433
|
-
}
|
|
460
|
+
if(isDocEditing(data,this.primaryKeyFields,({index:field,data})=>{
|
|
461
|
+
return checkPrimary(data,field);
|
|
462
|
+
})) return true;
|
|
434
463
|
}
|
|
435
464
|
return super.isDocEditing(data);
|
|
436
465
|
}
|