@fto-consult/expo-ui 1.5.13 → 2.0.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": "1.5.13",
3
+ "version": "2.0.0",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -61,43 +61,46 @@
61
61
  "@expo/html-elements": "^0.2.0",
62
62
  "@expo/metro-config": "^0.4.0",
63
63
  "@expo/webpack-config": "^0.17.2",
64
- "@fto-consult/common": "^1.9.7",
64
+ "@fto-consult/common": "^1.9.8",
65
65
  "@gorhom/portal": "^1.0.14",
66
66
  "@react-native-async-storage/async-storage": "~1.17.3",
67
- "@react-native-community/datetimepicker": "6.2.0",
68
- "@react-native-community/netinfo": "9.3.0",
67
+ "@react-native-community/datetimepicker": "6.5.2",
68
+ "@react-native-community/netinfo": "9.3.5",
69
69
  "@react-navigation/native": "^6.0.13",
70
70
  "@react-navigation/native-stack": "^6.9.1",
71
- "@shopify/flash-list": "1.1.0",
71
+ "@shopify/flash-list": "1.3.1",
72
72
  "babel-plugin-inline-dotenv": "^1.7.0",
73
73
  "babel-plugin-module-resolver": "^4.1.0",
74
74
  "babel-plugin-transform-inline-environment-variables": "^0.4.4",
75
- "expo": "^46.0.15",
76
- "expo-camera": "~12.3.0",
77
- "expo-clipboard": "~3.1.0",
78
- "expo-image-picker": "~13.3.1",
79
- "expo-linking": "~3.2.2",
80
- "expo-sqlite": "~10.3.0",
81
- "expo-status-bar": "~1.4.0",
82
- "expo-system-ui": "~1.3.0",
83
- "expo-web-browser": "~11.0.0",
75
+ "expo": "^47.0.0",
76
+ "expo-camera": "~13.0.0",
77
+ "expo-clipboard": "~4.0.1",
78
+ "expo-image-picker": "~14.0.1",
79
+ "expo-linking": "~3.2.3",
80
+ "expo-sqlite": "~11.0.0",
81
+ "expo-status-bar": "~1.4.2",
82
+ "expo-system-ui": "~2.0.1",
83
+ "expo-web-browser": "~12.0.0",
84
84
  "google-libphonenumber": "^3.2.31",
85
85
  "htmlparser2-without-node-native": "^3.9.2",
86
+ "mongo-parse": "^2.1.0",
86
87
  "parent-package-json": "^2.0.1",
87
88
  "prop-types": "^15.8.1",
88
89
  "react-apexcharts": "^1.4.0",
89
90
  "react-content-loader": "^6.2.0",
90
- "react-dom": "18.0.0",
91
+ "react-dom": "18.1.0",
91
92
  "react-native-big-list": "^1.5.5",
92
- "react-native-gesture-handler": "~2.5.0",
93
+ "react-native-gesture-handler": "~2.8.0",
93
94
  "react-native-paper": "^4.12.5",
94
95
  "react-native-paper-dates": "^0.9.2",
95
- "react-native-reanimated": "~2.9.1",
96
- "react-native-safe-area-context": "4.3.1",
97
- "react-native-svg": "12.3.0",
96
+ "react-native-reanimated": "~2.12.0",
97
+ "react-native-safe-area-context": "4.4.1",
98
+ "react-native-svg": "13.4.0",
98
99
  "react-native-web": "~0.18.7",
99
- "react-native-webview": "11.23.0",
100
+ "react-native-webview": "11.23.1",
100
101
  "sharp-cli": "^2.1.0",
101
- "tippy.js": "^6.3.7"
102
+ "tippy.js": "^6.3.7",
103
+ "react-native": "0.70.5",
104
+ "react": "18.1.0"
102
105
  }
103
106
  }
@@ -17,7 +17,7 @@ import {sortBy,isDecimal,extendObj,isObjOrArray,defaultNumber,defaultStr,isFunct
17
17
  import {Datagrid as DatagridContentLoader} from "$ecomponents/ContentLoader";
18
18
  import React from "$react";
19
19
  import DateLib from "$lib/date";
20
- import Filter,{canHandleFilter} from "$ecomponents/Filter";
20
+ import Filter,{canHandleFilter,prepareFilters} from "$ecomponents/Filter";
21
21
  import {CHECKED_ICON_NAME} from "$ecomponents/Checkbox";
22
22
  import { COLUMN_WIDTH,DATE_COLUMN_WIDTH } from "../utils";
23
23
  import { StyleSheet,Dimensions,useWindowDimensions} from "react-native";
@@ -29,6 +29,7 @@ import i18n from "$i18n";
29
29
  import { makePhoneCall,canMakePhoneCall as canMakeCall} from "$makePhoneCall";
30
30
  import copyToClipboard from "$capp/clipboard";
31
31
  import { Pressable } from "react-native";
32
+ import appConfig from "$capp/config";
32
33
 
33
34
  export const arrayValueSeparator = ", ";
34
35
 
@@ -94,13 +95,13 @@ export default class CommonDatagridComponent extends AppComponent {
94
95
  value : uniqid(footerFieldName),override:false, writable: false
95
96
  },
96
97
  })
97
- if(!isNonNullString(this.state.sort.column)){
98
- this.state.sort.column = "date";
99
- }
98
+
100
99
  this.state.sort.dir = defaultStr(this.state.sort.dir,this.state.sort.column == "date"?"desc":'asc')
101
100
  this.hasColumnsHalreadyInitialized = false;
102
101
  this.initColumns(props.columns);
103
-
102
+ if(!isNonNullString(this.state.sort.column) && "date" in this.state.columns){
103
+ this.state.sort.column = "date";
104
+ }
104
105
  this.INITIAL_STATE = {
105
106
  data,
106
107
  sort : this.state.sort
@@ -1171,41 +1172,14 @@ export default class CommonDatagridComponent extends AppComponent {
1171
1172
  }
1172
1173
  return this.doFilter(arg);
1173
1174
  }
1175
+ ///si les filtres devront être convertis au format SQL
1176
+ willConvertFiltersToSQL(){
1177
+ return !!defaultVal(this.props.convertFiltersToSQL,appConfig.get("convertDatagridFiltersToSQL"));;
1178
+ }
1174
1179
  getFilters(){
1175
- let filters = {}
1176
1180
  this.filters = extendObj(true,{},this.filteredValues,this.filters)
1177
- for(let i in this.filters){
1178
- let f = this.filters[i];
1179
- if(!this.canHandleFilterVal(f)) {
1180
- continue;
1181
- }
1182
- f.field = defaultStr(f.field,i);
1183
- if(f.action =="$today"){
1184
- f.operator = "$and";
1185
- f.action = "$eq"
1186
- }
1187
- filters[f.operator] = defaultArray(filters[f.operator]);
1188
- let ob = {};
1189
- ob[f.field] = {}
1190
- if(f.action == "$period"){
1191
- let sp = defaultStr(f.value);
1192
- if(sp){
1193
- sp = sp.split("=>");
1194
- if(DateLib.isValidSQLDate(sp[0]) && DateLib.isValidSQLDate(sp[1])){
1195
- filters[f.operator].push({
1196
- [f.field] : {$gte:sp[0]}
1197
- })
1198
- filters[f.operator].push({
1199
- [f.field] : {$lte:sp[1]}
1200
- })
1201
- }
1202
- }
1203
- } else {
1204
- ob[f.field][f.action] = f.value;
1205
- filters[f.operator].push(ob)
1206
- }
1207
- }
1208
- return filters;
1181
+ const preparedFilters = prepareFilters(this.filters,{filter:this.canHandleFilterVal.bind(this),convertToSQL:this.willConvertFiltersToSQL()});
1182
+ return preparedFilters;
1209
1183
  }
1210
1184
  fetchData(){}
1211
1185
  /**** Filtre le tableau */
@@ -1665,6 +1639,8 @@ CommonDatagridComponent.propTypes = {
1665
1639
  PropTypes.object,
1666
1640
  PropTypes.func,
1667
1641
  ]),
1642
+ /*** si les filtres de données seront convertis au format SQL avant d'effectuer la requête distante */
1643
+ convertFiltersToSQL : PropTypes.bool,
1668
1644
  isLoading : PropTypes.bool,///si les données sont en train d'être chargées
1669
1645
  session : PropTypes.bool, /// si les données de sessions seront persistées
1670
1646
  exportTableProps : PropTypes.oneOfType([
@@ -135,12 +135,13 @@ export default class CommonTableDatagrid extends CommonDatagrid{
135
135
  return this.resolveFetchedDataPromise({cb,data:this.INITIAL_STATE.data}).then(resolve).catch(reject)
136
136
  }
137
137
  let fetchData = this.INITIAL_STATE.fetchData;
138
- fetchOptions = defaultObj(fetchOptions);
138
+ fetchOptions = isObj(fetchOptions)?Object.clone(fetchOptions):{};
139
139
  fetchOptions.selector = defaultObj(fetchOptions.selector);
140
- fetchOptions.selector.$and = defaultArray(fetchOptions.selector.$and);
141
140
  fetchOptions.dataSources = this.currentDataSources;
142
- fetchOptions = extendObj(true,true,{},{selector : this.getFilters()},fetchOptions,this.filtersSelectors);
141
+ const fetchFilters = this.getFilters();
142
+ fetchOptions = extendObj(true,true,{},fetchOptions,{selector : fetchFilters});
143
143
  fetchOptions.dataSources = this.currentDataSources;
144
+ fetchOptions.sort = this.state.sort;
144
145
  let limit = this.getQueryLimit();
145
146
  if(limit > 0 && !this.isPivotDatagrid()){
146
147
  fetchOptions.limit = limit;
@@ -1,5 +1,7 @@
1
1
 
2
- import {isNonNullString,defaultStr,isObjOrArray,isObj} from "$utils";
2
+ import {isNonNullString,defaultStr,defaultArray,isObjOrArray,isObj} from "$utils";
3
+ import DateLib from "$date";
4
+ import mangoParser from "mongo-parse";
3
5
 
4
6
  export const filterTextTypes = ['text','number','email','search','tel','url','password',"id","idfield",'piecefield','piece'];
5
7
 
@@ -49,33 +51,97 @@ export const canHandleFilter = (f)=>{
49
51
 
50
52
  }
51
53
 
54
+ export const regexExpressions = {
55
+ notcontains : {
56
+ left : "^((?!(",
57
+ right : ")).)*$",
58
+ /***@see : https://www.w3schools.com/sql/sql_like.asp */
59
+ sql : (string)=>{
60
+ return "%"+defaultStr(string).ltrim("%").rtrim("%")+"%";
61
+ }
62
+ },
63
+ notequals : {
64
+ left : "^(?!",
65
+ right : "$).*$",
66
+ },
67
+ equals : {
68
+ left : "/^",
69
+ right : '$/'
70
+ },
71
+ startswith : {
72
+ left : "/^",
73
+ right : '/',
74
+ sql : (string)=>{
75
+ return defaultStr(string).rtrim("%")+"%";
76
+ }
77
+ },
78
+ endswith : {
79
+ left : "",
80
+ right : '$/',
81
+ sql : (string)=>{
82
+ return "%"+defaultStr(string).ltrim("%");
83
+ }
84
+ },
85
+ contains : {
86
+ left : "/",
87
+ right : '/',
88
+ sql : (string)=>{
89
+ return "%"+defaultStr(string).ltrim("%").rtrim("%")+"%";
90
+ }
91
+ },
92
+ }
93
+ /**** prend en paramètre une chaine de caractère et un nom d'opérateur et génère
94
+ * la chaine de caracètre mango regex correspondant
95
+ */
96
+ const toMangoRegex = (val,operator)=>{
97
+ if(!isNonNullString(val) || !isNonNullString(operator) || !regexExpressions[operator]) return "";
98
+ const rOp = regexExpressions[operator];
99
+ return rOp.left+escapeRegexChars(val)+rOp.right;
100
+ }
52
101
  export const regexParser = {
53
102
  equals : (val)=> {
54
- if(!isNonNullString(val)) return '';
55
- return "/^"+escapeRegexChars(val)+'$/';
103
+ return toMangoRegex(val,"equals");
56
104
  },
57
105
  startswith : (val) => {
58
- if(!isNonNullString(val)) return "";
59
- return "/^"+escapeRegexChars(val)+'/'
106
+ return toMangoRegex(val,"startswith");
60
107
  },
61
108
  endswith : (val) => {
62
- if(!isNonNullString(val)) return "";
63
- return escapeRegexChars(val)+'$/'
109
+ return toMangoRegex(val,"endswith");
64
110
  },
65
111
  contains : (val) => {
66
- if(!isNonNullString(val)) return "";
67
- return "/"+escapeRegexChars(val)+'/'
112
+ return toMangoRegex(val,"contains");
68
113
  },
69
114
  notequals : (val) => {
70
- if(!isNonNullString(val)) return ""
71
- return "^(?!"+escapeRegexChars(val)+"$).*$"
115
+ return toMangoRegex(val,"notequals");
72
116
  },
73
117
  notcontains : (val) =>{
74
- if(!isNonNullString(val)) return "";
75
- return "^((?!("+escapeRegexChars(val)+")).)*$"
118
+ return toMangoRegex(val,"notcontains");
76
119
  }
77
- }
78
-
120
+ }
121
+ /*** prend une expression regulière et le convertis en operateur, valeur
122
+ * @param {string} la valeur otenue au format regex
123
+ */
124
+ export const regexDeparser = (regexValue)=>{
125
+ if(!regexValue) return null;
126
+ regexValue = regexValue.toString();
127
+ if(regexValue.endsWith("/i")){
128
+ regexValue = regexValue.rtrim("/i")+"/";
129
+ }
130
+ if(!isNonNullString(regexValue)) return null;
131
+ for(let i in regexExpressions){
132
+ const p = regexExpressions[i];
133
+ let rVal = regexValue.ltrim("/").rtrim("/");
134
+ const pLeft = p.left.ltrim("/"),pRight = p.right.rtrim("/");
135
+ if(rVal.startsWith(pLeft) && rVal.endsWith(pRight)){
136
+ return {
137
+ operator : i,
138
+ value : rVal.ltrim(pLeft).rtrim(pRight),
139
+ regexValue : rVal,
140
+ }
141
+ }
142
+ }
143
+ return null;
144
+ }
79
145
  export const matchPouchDBOperator = (operator,value)=>{
80
146
  if(!isNonNullString(operator)) return null;
81
147
  if(!(value) && !isDecimal(value)) return null;
@@ -168,7 +234,7 @@ let Operators = {
168
234
  regexpSupported: false
169
235
  }
170
236
  };
171
- let utils = {
237
+ const utils = {
172
238
  ALL: '#All#',
173
239
  NONE: '#None#',
174
240
  BLANK: '#Blank#"',
@@ -286,4 +352,140 @@ for(let i in utils.items){
286
352
  utils.constants[i] = i;
287
353
  }
288
354
 
289
- export default utils;
355
+ /*** prend en paramètre un objet de filtres et les prépare pour la requête distante
356
+ * @param {object} filtersToPrepare les filtres à exploiter pour la requête distante
357
+ * le tableau des filtres
358
+ * @param {function|object} filter|options, la méthode utilisée pour effectuer un filtre sur les les élément à prendre en compte
359
+ *
360
+ */
361
+ export const prepareFilters = (filtersToPrepare,opts)=>{
362
+ if(typeof opts =='function'){
363
+ opts = {filter:opts};
364
+ }
365
+ let {filter,convertToSQL:convToSQL} = opts;
366
+ const filters = {}
367
+ filter = typeof filter =='function'? filter : x=>true;
368
+ Object.map(filtersToPrepare,(f,i)=>{
369
+ if(!isObj(f) || !filter(f) || !isNonNullString(f.operator) || !isNonNullString(f.action)) {
370
+ return;
371
+ }
372
+ f.field = defaultStr(f.field,i).trim();
373
+ f.action = defaultStr(f.action).toLowerCase().trim();
374
+ if(f.action =="$today"){
375
+ f.operator = "$and";
376
+ f.action = "$eq"
377
+ }
378
+ filters[f.operator] = defaultArray(filters[f.operator]);
379
+ const ob = {};
380
+ ob[f.field] = {}
381
+ if(f.action == "$period"){
382
+ let sp = defaultStr(f.value);
383
+ if(sp){
384
+ sp = sp.split("=>");
385
+ if(DateLib.isValidSQLDate(sp[0]) && DateLib.isValidSQLDate(sp[1])){
386
+ filters[f.operator].push({
387
+ [f.field] : {$gte:sp[0]}
388
+ })
389
+ filters[f.operator].push({
390
+ [f.field] : {$lte:sp[1]}
391
+ })
392
+ }
393
+ }
394
+ } else {
395
+ ob[f.field][f.action] = f.value;
396
+ filters[f.operator].push(ob)
397
+ }
398
+ });
399
+ return convToSQL ? convertToSQL(filters) : filters;
400
+ }
401
+
402
+ /*** la liste des actions supportés par les filtres
403
+ * @see : https://github.com/cloudant/mango from mango query mapping
404
+ *
405
+ */
406
+ export const actions = ["$lt", "$gt", "$lte", "$gte", "$eq", "$exists", "$type", "$in", "$nin", "$all", "$size", "$or", "$nor", "$not", "$mod", "$regex", "$elemMatch"]
407
+
408
+ export default utils;
409
+
410
+ /*** convertis les filtres pris au format mangoesQuey, au format de sortie SQL
411
+ * @see : https://github.com/gordonBusyman/mongo-to-sql-converter
412
+ * @param {object} filters, les filtres au format mango query à convertir au format SQL
413
+ */
414
+ export const convertToSQL = (filters)=>{
415
+ if(!isObjOrArray(filters)) return [];
416
+ const whereParsed = mangoParser.parse(filters)
417
+ return whereParsed.parts.reduce((prev, curr) => {
418
+ const c = whereClauseToSQL(curr);
419
+ return c ? [...prev,c] : prev;
420
+ }, []).map((filter)=>{
421
+ if(filter.field === MANGO_QUERY_OPERATOR){
422
+ delete filter.field;
423
+ }
424
+ return filter;
425
+ });
426
+ }
427
+ export const MANGO_QUERY_OPERATOR = "MANGO_QUERY_OPERATOR";
428
+ const whereClauseToSQL = (currentMongoParserElement) => {
429
+ let { field, operator, operand } = currentMongoParserElement;
430
+ if(!isNonNullString(operator) || !operatorsMap[operator]) return null;
431
+ operator = operatorsMap[operator]
432
+ if(operator == 'LIKE'){
433
+ const deparsed = regexDeparser(operand);
434
+ if(!isObj(deparsed)){
435
+ return null;
436
+ }
437
+ operand = deparsed.value;
438
+ const deparsedOp = deparsed.operator;
439
+ if(deparsedOp =='notcontains'){
440
+ operator = "NOT LIKE";
441
+ }
442
+ if(deparsedOp =='equals'){
443
+ operator = operatorsMap.$eq;
444
+ } else if(deparsedOp =='notequals'){
445
+ operator = operatorsMap.$ne;
446
+ } else{
447
+ const opMap = regexExpressions[deparsedOp];
448
+ if(!opMap || typeof opMap.sql !=='function') return null;
449
+ operand = opMap.sql(operand);
450
+ }
451
+ }
452
+ // AND or OR operators with nested elements
453
+ if (typeof field === 'undefined') {
454
+ // parse nested elements
455
+ const nested = operand.reduce((prev, curr) => {
456
+ const parsed = mangoParser.parse(curr);
457
+ const prepared = whereClauseToSQL(parsed.parts[0]);
458
+ if(prepared){
459
+ return [...prev,prepared]
460
+ }
461
+ return prev;
462
+ }, [])
463
+
464
+ // nested WHERE element
465
+ return {
466
+ field: MANGO_QUERY_OPERATOR,
467
+ operator,
468
+ operand: nested
469
+ }
470
+ }
471
+ // simple WHERE element
472
+ return {
473
+ field,
474
+ operator,
475
+ operand
476
+ }
477
+ }
478
+
479
+ // map mongoDB -> SQL operators
480
+ const operatorsMap = {
481
+ $or: 'OR',
482
+ $and: 'AND',
483
+ $lt: '<',
484
+ $lte: '<=',
485
+ $gt: '>',
486
+ $gte: '>=',
487
+ $ne: '!=',
488
+ $in: 'IN',
489
+ $eq: '=',
490
+ $regex : "LIKE"
491
+ }