@etsoo/react 1.4.0 → 1.4.4

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.
@@ -11,4 +11,4 @@ export interface IServiceUser extends IUser {
11
11
  /**
12
12
  * Service user login result
13
13
  */
14
- export declare type ServiceLoginResult = IActionResult<IServiceUser>;
14
+ export declare type ServiceLoginResult<U extends IServiceUser = IServiceUser> = IActionResult<U>;
@@ -11,7 +11,7 @@ import { RefreshTokenRQ } from './RefreshTokenRQ';
11
11
  * Use the acess token to the service api, get a service access token
12
12
  * Use the new acess token and refresh token to login
13
13
  */
14
- export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends IServicePageData = IServicePageData, S extends IServiceAppSettings = IServiceAppSettings> extends ReactApp<S, U, P> {
14
+ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends IServicePageData = IServicePageData, S extends IServiceAppSettings = IServiceAppSettings> extends ReactApp<S, ISmartERPUser, P> {
15
15
  /**
16
16
  * Service API
17
17
  */
@@ -20,8 +20,8 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
20
20
  /**
21
21
  * Service user
22
22
  */
23
- get serviceUser(): IServiceUser | undefined;
24
- protected set serviceUser(value: IServiceUser | undefined);
23
+ get serviceUser(): U | undefined;
24
+ protected set serviceUser(value: U | undefined);
25
25
  /**
26
26
  * Service passphrase
27
27
  */
@@ -67,5 +67,5 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
67
67
  * @param refreshToken Refresh token
68
68
  * @param serviceUser Service user
69
69
  */
70
- userLoginEx(user: ISmartERPUser, refreshToken: string, serviceUser: IServiceUser): void;
70
+ userLoginEx(user: ISmartERPUser, refreshToken: string, serviceUser: U): void;
71
71
  }
@@ -6,7 +6,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
6
6
  /**
7
7
  * Item renderer
8
8
  */
9
- children: (item: D, index: number, deleteItem: (index: number) => void, editItem: (newItem: D, indexOrLabel: number | string) => void) => React.ReactNode;
9
+ children: (item: D, index: number, deleteItem: (index: number) => void, editItem: (newItem: D, index: number) => boolean) => React.ReactNode;
10
10
  /**
11
11
  * List container component
12
12
  * https://javascript.plainenglish.io/building-a-polymorphic-component-in-react-and-typescript-d9f236950af4
@@ -32,6 +32,10 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
32
32
  * Load data
33
33
  */
34
34
  loadData: (name: string) => PromiseLike<D[]>;
35
+ /**
36
+ * Data change handler
37
+ */
38
+ onChange?: (items: D[]) => void;
35
39
  /**
36
40
  * Top and bottom sides renderer
37
41
  */
package/lib/mu/DnDList.js CHANGED
@@ -9,9 +9,16 @@ import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
9
9
  */
10
10
  export function DnDList(props) {
11
11
  // Destruct
12
- const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, sideRenderer } = props;
12
+ const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, onChange, sideRenderer } = props;
13
13
  // State
14
14
  const [items, setItems] = React.useState([]);
15
+ const changeItems = (items) => {
16
+ // Possible to alter items with the handler
17
+ if (onChange)
18
+ onChange(items);
19
+ // Update state
20
+ setItems(items);
21
+ };
15
22
  // Drag end handler
16
23
  const onDragEnd = (result) => {
17
24
  // Dropped outside the list
@@ -25,34 +32,33 @@ export function DnDList(props) {
25
32
  // Insert to the destination index
26
33
  newItems.splice(result.destination.index, 0, removed);
27
34
  // Update the state
28
- setItems(newItems);
35
+ changeItems(newItems);
29
36
  };
30
37
  // Add item
31
38
  const addItem = (newItem) => {
32
39
  // Existence check
33
- if (items.some((item) => item[labelField] == newItem[labelField])) {
40
+ if (items.some((item) => item[labelField] === newItem[labelField])) {
34
41
  return false;
35
42
  }
36
43
  // Clone
37
44
  const newItems = [newItem, ...items];
38
45
  // Update the state
39
- setItems(newItems);
46
+ changeItems(newItems);
40
47
  return true;
41
48
  };
42
49
  // Edit item
43
- const editItem = (newItem, indexOrLabel) => {
44
- const index = typeof indexOrLabel === 'number'
45
- ? indexOrLabel
46
- : items.findIndex((item) => DataTypes.convert(item[labelField], 'string') ==
47
- indexOrLabel);
48
- if (index === -1)
49
- return;
50
+ const editItem = (newItem, index) => {
51
+ // Existence check
52
+ if (items.some((item) => item[labelField] === newItem[labelField])) {
53
+ return false;
54
+ }
50
55
  // Clone
51
56
  const newItems = [...items];
52
57
  // Remove the item
53
58
  newItems.splice(index, 1, newItem);
54
59
  // Update the state
55
- setItems(newItems);
60
+ changeItems(newItems);
61
+ return true;
56
62
  };
57
63
  // Add items
58
64
  const addItems = (inputItems) => {
@@ -67,7 +73,7 @@ export function DnDList(props) {
67
73
  newItems.push(newItem);
68
74
  });
69
75
  // Update the state
70
- setItems(newItems);
76
+ changeItems(newItems);
71
77
  return newItems.length - items.length;
72
78
  };
73
79
  // Delete item
@@ -77,9 +83,10 @@ export function DnDList(props) {
77
83
  // Remove the item
78
84
  newItems.splice(index, 1);
79
85
  // Update the state
80
- setItems(newItems);
86
+ changeItems(newItems);
81
87
  };
82
88
  React.useEffect(() => {
89
+ // Load data
83
90
  loadData(name).then((items) => setItems(items));
84
91
  }, [name]);
85
92
  // Layout
@@ -1,5 +1,5 @@
1
1
  import { NotificationAlign, NotificationMessageType, NotificationType } from '@etsoo/notificationbase';
2
- import { Alert, AlertTitle, Backdrop, Box, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Fade, Slider, Snackbar, styled, Switch, TextField } from '@mui/material';
2
+ import { Alert, AlertTitle, Backdrop, Box, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Fade, Slider, Snackbar, styled, Switch, TextField, Typography } from '@mui/material';
3
3
  import { Error, Info, Help, Warning, Done } from '@mui/icons-material';
4
4
  import React from 'react';
5
5
  import { Labels } from '../app/Labels';
@@ -120,14 +120,21 @@ export class NotificationMU extends NotificationReact {
120
120
  const labels = Labels.NotificationMU;
121
121
  const title = (_a = this.title) !== null && _a !== void 0 ? _a : labels.promptTitle;
122
122
  const { cancelLabel = labels.promptCancel, okLabel = labels.promptOK, inputs, type, fullScreen, fullWidth = true, maxWidth, primaryButton, ...rest } = (_b = this.inputProps) !== null && _b !== void 0 ? _b : {};
123
+ const inputRef = React.createRef();
124
+ const errorRef = React.createRef();
125
+ const setError = (error) => {
126
+ if (errorRef.current == null)
127
+ return;
128
+ errorRef.current.innerText = error !== null && error !== void 0 ? error : '';
129
+ };
123
130
  const handleSubmit = async (event) => {
124
131
  // Result
125
132
  let result = undefined;
133
+ const input = inputRef.current;
126
134
  if (this.onReturn) {
127
135
  // Inputs case, no HTMLForm set to value, set the current form
128
136
  if (inputs && value == null)
129
137
  value = event.currentTarget.form;
130
- const input = inputRef.current;
131
138
  if (input) {
132
139
  if (type === 'date') {
133
140
  const dateValue = input.valueAsDate;
@@ -165,12 +172,18 @@ export class NotificationMU extends NotificationReact {
165
172
  // Get the value
166
173
  // returns false to prevent default dismiss
167
174
  const v = await result;
168
- if (v === false)
175
+ if (v === false) {
176
+ input === null || input === void 0 ? void 0 : input.focus();
169
177
  return;
178
+ }
179
+ if (typeof v === 'string') {
180
+ setError(v);
181
+ input === null || input === void 0 ? void 0 : input.focus();
182
+ return;
183
+ }
170
184
  this.dismiss();
171
185
  };
172
186
  let localInputs;
173
- let inputRef = React.createRef();
174
187
  let value = undefined;
175
188
  if (inputs == null) {
176
189
  if (type === 'switch') {
@@ -180,7 +193,7 @@ export class NotificationMU extends NotificationReact {
180
193
  localInputs = React.createElement(Slider, { onChange: (_e, v) => (value = v) });
181
194
  }
182
195
  else {
183
- localInputs = (React.createElement(TextField, { inputRef: inputRef, autoFocus: true, margin: "dense", fullWidth: true, type: type, required: true, ...rest }));
196
+ localInputs = (React.createElement(TextField, { inputRef: inputRef, onChange: () => setError(undefined), autoFocus: true, margin: "dense", fullWidth: true, type: type, required: true, ...rest }));
184
197
  }
185
198
  }
186
199
  else {
@@ -193,7 +206,8 @@ export class NotificationMU extends NotificationReact {
193
206
  React.createElement("span", { className: "dialogTitle" }, title)),
194
207
  React.createElement(DialogContent, null,
195
208
  React.createElement(DialogContentText, null, this.content),
196
- localInputs),
209
+ localInputs,
210
+ React.createElement(Typography, { variant: "caption", display: "block", ref: errorRef, color: (theme) => theme.palette.error.main })),
197
211
  React.createElement(DialogActions, null,
198
212
  React.createElement(Button, { color: "secondary", onClick: () => {
199
213
  if (this.onReturn)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.4.0",
3
+ "version": "1.4.4",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,8 +50,8 @@
50
50
  "@emotion/react": "^11.7.1",
51
51
  "@emotion/style": "^0.8.0",
52
52
  "@emotion/styled": "^11.6.0",
53
- "@etsoo/appscript": "^1.2.11",
54
- "@etsoo/notificationbase": "^1.0.95",
53
+ "@etsoo/appscript": "^1.2.15",
54
+ "@etsoo/notificationbase": "^1.1.0",
55
55
  "@etsoo/shared": "^1.0.97",
56
56
  "@mui/icons-material": "^5.2.5",
57
57
  "@mui/material": "^5.2.6",
@@ -76,12 +76,12 @@
76
76
  "react-window": "^1.8.6"
77
77
  },
78
78
  "devDependencies": {
79
- "@babel/cli": "^7.16.0",
80
- "@babel/core": "^7.16.5",
81
- "@babel/plugin-transform-runtime": "^7.16.5",
82
- "@babel/preset-env": "^7.16.5",
83
- "@babel/runtime-corejs3": "^7.16.5",
84
- "@types/jest": "^27.0.3",
79
+ "@babel/cli": "^7.16.7",
80
+ "@babel/core": "^7.16.7",
81
+ "@babel/plugin-transform-runtime": "^7.16.7",
82
+ "@babel/preset-env": "^7.16.7",
83
+ "@babel/runtime-corejs3": "^7.16.7",
84
+ "@types/jest": "^27.4.0",
85
85
  "@types/react-test-renderer": "^17.0.1",
86
86
  "@typescript-eslint/eslint-plugin": "^5.8.1",
87
87
  "@typescript-eslint/parser": "^5.8.1",
@@ -13,4 +13,5 @@ export interface IServiceUser extends IUser {
13
13
  /**
14
14
  * Service user login result
15
15
  */
16
- export type ServiceLoginResult = IActionResult<IServiceUser>;
16
+ export type ServiceLoginResult<U extends IServiceUser = IServiceUser> =
17
+ IActionResult<U>;
@@ -24,20 +24,20 @@ export class ServiceApp<
24
24
  U extends IServiceUser = IServiceUser,
25
25
  P extends IServicePageData = IServicePageData,
26
26
  S extends IServiceAppSettings = IServiceAppSettings
27
- > extends ReactApp<S, U, P> {
27
+ > extends ReactApp<S, ISmartERPUser, P> {
28
28
  /**
29
29
  * Service API
30
30
  */
31
31
  readonly serviceApi: IApi;
32
32
 
33
- private _serviceUser?: IServiceUser;
33
+ private _serviceUser?: U;
34
34
  /**
35
35
  * Service user
36
36
  */
37
37
  get serviceUser() {
38
38
  return this._serviceUser;
39
39
  }
40
- protected set serviceUser(value: IServiceUser | undefined) {
40
+ protected set serviceUser(value: U | undefined) {
41
41
  this._serviceUser = value;
42
42
  }
43
43
 
@@ -140,7 +140,9 @@ export class ServiceApp<
140
140
  const userData = result.data;
141
141
 
142
142
  // Use core system access token to service api to exchange service access token
143
- const serviceResult = await this.serviceApi.put<ServiceLoginResult>(
143
+ const serviceResult = await this.serviceApi.put<
144
+ ServiceLoginResult<U>
145
+ >(
144
146
  'Auth/ExchangeToken',
145
147
  {
146
148
  token: this.encryptEnhanced(
@@ -322,7 +324,7 @@ export class ServiceApp<
322
324
  userLoginEx(
323
325
  user: ISmartERPUser,
324
326
  refreshToken: string,
325
- serviceUser: IServiceUser
327
+ serviceUser: U
326
328
  ): void {
327
329
  // Service user login
328
330
  this.servicePassphrase =
@@ -18,7 +18,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
18
18
  item: D,
19
19
  index: number,
20
20
  deleteItem: (index: number) => void,
21
- editItem: (newItem: D, indexOrLabel: number | string) => void
21
+ editItem: (newItem: D, index: number) => boolean
22
22
  ) => React.ReactNode;
23
23
 
24
24
  /**
@@ -52,6 +52,11 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
52
52
  */
53
53
  loadData: (name: string) => PromiseLike<D[]>;
54
54
 
55
+ /**
56
+ * Data change handler
57
+ */
58
+ onChange?: (items: D[]) => void;
59
+
55
60
  /**
56
61
  * Top and bottom sides renderer
57
62
  */
@@ -87,12 +92,21 @@ export function DnDList<
87
92
  labelField,
88
93
  loadData,
89
94
  name,
95
+ onChange,
90
96
  sideRenderer
91
97
  } = props;
92
98
 
93
99
  // State
94
100
  const [items, setItems] = React.useState<D[]>([]);
95
101
 
102
+ const changeItems = (items: D[]) => {
103
+ // Possible to alter items with the handler
104
+ if (onChange) onChange(items);
105
+
106
+ // Update state
107
+ setItems(items);
108
+ };
109
+
96
110
  // Drag end handler
97
111
  const onDragEnd = (result: DropResult) => {
98
112
  // Dropped outside the list
@@ -110,13 +124,13 @@ export function DnDList<
110
124
  newItems.splice(result.destination.index, 0, removed);
111
125
 
112
126
  // Update the state
113
- setItems(newItems);
127
+ changeItems(newItems);
114
128
  };
115
129
 
116
130
  // Add item
117
131
  const addItem = (newItem: D) => {
118
132
  // Existence check
119
- if (items.some((item) => item[labelField] == newItem[labelField])) {
133
+ if (items.some((item) => item[labelField] === newItem[labelField])) {
120
134
  return false;
121
135
  }
122
136
 
@@ -124,22 +138,17 @@ export function DnDList<
124
138
  const newItems = [newItem, ...items];
125
139
 
126
140
  // Update the state
127
- setItems(newItems);
141
+ changeItems(newItems);
128
142
 
129
143
  return true;
130
144
  };
131
145
 
132
146
  // Edit item
133
- const editItem = (newItem: D, indexOrLabel: number | string) => {
134
- const index =
135
- typeof indexOrLabel === 'number'
136
- ? indexOrLabel
137
- : items.findIndex(
138
- (item) =>
139
- DataTypes.convert(item[labelField], 'string') ==
140
- indexOrLabel
141
- );
142
- if (index === -1) return;
147
+ const editItem = (newItem: D, index: number) => {
148
+ // Existence check
149
+ if (items.some((item) => item[labelField] === newItem[labelField])) {
150
+ return false;
151
+ }
143
152
 
144
153
  // Clone
145
154
  const newItems = [...items];
@@ -148,7 +157,9 @@ export function DnDList<
148
157
  newItems.splice(index, 1, newItem);
149
158
 
150
159
  // Update the state
151
- setItems(newItems);
160
+ changeItems(newItems);
161
+
162
+ return true;
152
163
  };
153
164
 
154
165
  // Add items
@@ -169,7 +180,7 @@ export function DnDList<
169
180
  });
170
181
 
171
182
  // Update the state
172
- setItems(newItems);
183
+ changeItems(newItems);
173
184
 
174
185
  return newItems.length - items.length;
175
186
  };
@@ -183,10 +194,11 @@ export function DnDList<
183
194
  newItems.splice(index, 1);
184
195
 
185
196
  // Update the state
186
- setItems(newItems);
197
+ changeItems(newItems);
187
198
  };
188
199
 
189
200
  React.useEffect(() => {
201
+ // Load data
190
202
  loadData(name).then((items) => setItems(items));
191
203
  }, [name]);
192
204
 
@@ -25,7 +25,8 @@ import {
25
25
  Snackbar,
26
26
  styled,
27
27
  Switch,
28
- TextField
28
+ TextField,
29
+ Typography
29
30
  } from '@mui/material';
30
31
  import { Error, Info, Help, Warning, Done } from '@mui/icons-material';
31
32
  import React from 'react';
@@ -238,18 +239,30 @@ export class NotificationMU extends NotificationReact {
238
239
  ...rest
239
240
  } = this.inputProps ?? {};
240
241
 
242
+ const inputRef = React.createRef<HTMLInputElement>();
243
+ const errorRef = React.createRef<HTMLSpanElement>();
244
+
245
+ const setError = (error?: string) => {
246
+ if (errorRef.current == null) return;
247
+ errorRef.current.innerText = error ?? '';
248
+ };
249
+
241
250
  const handleSubmit = async (
242
251
  event: React.MouseEvent<HTMLButtonElement>
243
252
  ) => {
244
253
  // Result
245
- let result: boolean | void | PromiseLike<boolean | void> =
246
- undefined;
254
+ let result:
255
+ | boolean
256
+ | string
257
+ | void
258
+ | PromiseLike<boolean | string | void> = undefined;
259
+
260
+ const input = inputRef.current;
247
261
 
248
262
  if (this.onReturn) {
249
263
  // Inputs case, no HTMLForm set to value, set the current form
250
264
  if (inputs && value == null) value = event.currentTarget.form;
251
265
 
252
- const input = inputRef.current;
253
266
  if (input) {
254
267
  if (type === 'date') {
255
268
  const dateValue = input.valueAsDate;
@@ -284,13 +297,20 @@ export class NotificationMU extends NotificationReact {
284
297
  // Get the value
285
298
  // returns false to prevent default dismiss
286
299
  const v = await result;
287
- if (v === false) return;
300
+ if (v === false) {
301
+ input?.focus();
302
+ return;
303
+ }
304
+ if (typeof v === 'string') {
305
+ setError(v);
306
+ input?.focus();
307
+ return;
308
+ }
288
309
 
289
310
  this.dismiss();
290
311
  };
291
312
 
292
313
  let localInputs: React.ReactNode;
293
- let inputRef = React.createRef<HTMLInputElement>();
294
314
  let value: any = undefined;
295
315
 
296
316
  if (inputs == null) {
@@ -310,6 +330,7 @@ export class NotificationMU extends NotificationReact {
310
330
  localInputs = (
311
331
  <TextField
312
332
  inputRef={inputRef}
333
+ onChange={() => setError(undefined)}
313
334
  autoFocus
314
335
  margin="dense"
315
336
  fullWidth
@@ -341,6 +362,12 @@ export class NotificationMU extends NotificationReact {
341
362
  <DialogContent>
342
363
  <DialogContentText>{this.content}</DialogContentText>
343
364
  {localInputs}
365
+ <Typography
366
+ variant="caption"
367
+ display="block"
368
+ ref={errorRef}
369
+ color={(theme) => theme.palette.error.main}
370
+ />
344
371
  </DialogContent>
345
372
  <DialogActions>
346
373
  <Button