@etsoo/react 1.4.80 → 1.4.81
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/lib/app/ReactApp.d.ts +8 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/mu/AuditDisplay.d.ts +39 -0
- package/lib/mu/AuditDisplay.js +77 -0
- package/lib/mu/ListMoreDisplay.d.ts +26 -0
- package/lib/mu/ListMoreDisplay.js +85 -0
- package/package.json +6 -6
- package/src/app/ReactApp.ts +10 -0
- package/src/index.ts +2 -0
- package/src/mu/AuditDisplay.tsx +176 -0
- package/src/mu/ListMoreDisplay.tsx +157 -0
package/lib/app/ReactApp.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface IReactApp<S extends IAppSettings, D extends IUser, P extends IP
|
|
|
30
30
|
* User state
|
|
31
31
|
*/
|
|
32
32
|
readonly userState: UserState<D>;
|
|
33
|
+
/**
|
|
34
|
+
* Is screen size down 'sm'
|
|
35
|
+
*/
|
|
36
|
+
smDown?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Is screen size up 'md'
|
|
39
|
+
*/
|
|
40
|
+
mdUp?: boolean;
|
|
33
41
|
/**
|
|
34
42
|
* Set page data
|
|
35
43
|
* @param data Page data
|
package/lib/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from './mu/pages/ViewPage';
|
|
|
30
30
|
export * from './mu/texts/DateText';
|
|
31
31
|
export * from './mu/texts/MoneyText';
|
|
32
32
|
export * from './mu/texts/NumberText';
|
|
33
|
+
export * from './mu/AuditDisplay';
|
|
33
34
|
export * from './mu/ButtonLink';
|
|
34
35
|
export * from './mu/ComboBox';
|
|
35
36
|
export * from './mu/CountdownButton';
|
|
@@ -47,6 +48,7 @@ export * from './mu/IconButtonLink';
|
|
|
47
48
|
export * from './mu/InputField';
|
|
48
49
|
export * from './mu/ItemList';
|
|
49
50
|
export * from './mu/ListItemRightIcon';
|
|
51
|
+
export * from './mu/ListMoreDisplay';
|
|
50
52
|
export * from './mu/LoadingButton';
|
|
51
53
|
export * from './mu/MaskInput';
|
|
52
54
|
export * from './mu/MobileListItemRenderer';
|
package/lib/index.js
CHANGED
|
@@ -33,6 +33,7 @@ export * from './mu/pages/ViewPage';
|
|
|
33
33
|
export * from './mu/texts/DateText';
|
|
34
34
|
export * from './mu/texts/MoneyText';
|
|
35
35
|
export * from './mu/texts/NumberText';
|
|
36
|
+
export * from './mu/AuditDisplay';
|
|
36
37
|
export * from './mu/ButtonLink';
|
|
37
38
|
export * from './mu/ComboBox';
|
|
38
39
|
export * from './mu/CountdownButton';
|
|
@@ -50,6 +51,7 @@ export * from './mu/IconButtonLink';
|
|
|
50
51
|
export * from './mu/InputField';
|
|
51
52
|
export * from './mu/ItemList';
|
|
52
53
|
export * from './mu/ListItemRightIcon';
|
|
54
|
+
export * from './mu/ListMoreDisplay';
|
|
53
55
|
export * from './mu/LoadingButton';
|
|
54
56
|
export * from './mu/MaskInput';
|
|
55
57
|
export * from './mu/MobileListItemRenderer';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Theme } from '@mui/material';
|
|
2
|
+
import React, { CSSProperties } from 'react';
|
|
3
|
+
import { ListMoreDisplayProps } from './ListMoreDisplay';
|
|
4
|
+
/**
|
|
5
|
+
* Audit line update data model
|
|
6
|
+
*/
|
|
7
|
+
export interface AuditLineUpdateData {
|
|
8
|
+
oldData: Record<string, unknown>;
|
|
9
|
+
newData: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Audit line data model
|
|
13
|
+
*/
|
|
14
|
+
export interface AuditLine {
|
|
15
|
+
id: number;
|
|
16
|
+
creation: Date;
|
|
17
|
+
user: string;
|
|
18
|
+
action: string;
|
|
19
|
+
changes?: AuditLineUpdateData;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Audit display props
|
|
23
|
+
*/
|
|
24
|
+
export interface AuditDisplayProps extends Omit<ListMoreDisplayProps<AuditLine>, 'children'> {
|
|
25
|
+
/**
|
|
26
|
+
* Get list item style callback
|
|
27
|
+
*/
|
|
28
|
+
getItemStyle?: (index: number, theme: Theme) => CSSProperties;
|
|
29
|
+
/**
|
|
30
|
+
* Item/line renderer
|
|
31
|
+
*/
|
|
32
|
+
itemRenderer?: (data: AuditLine, index: number) => React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Audit display
|
|
36
|
+
* @param props Props
|
|
37
|
+
* @returns Component
|
|
38
|
+
*/
|
|
39
|
+
export declare function AuditDisplay(props: AuditDisplayProps): JSX.Element;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Utils } from '@etsoo/shared';
|
|
2
|
+
import { Button, Table, TableBody, TableCell, TableHead, TableRow, useTheme } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { globalApp, NotificationMessageType } from '..';
|
|
5
|
+
import { ListMoreDisplay } from './ListMoreDisplay';
|
|
6
|
+
// Get label
|
|
7
|
+
const getLabel = (key) => {
|
|
8
|
+
var _a;
|
|
9
|
+
if (typeof globalApp === 'undefined')
|
|
10
|
+
return key;
|
|
11
|
+
return (_a = globalApp.get(Utils.formatInitial(key))) !== null && _a !== void 0 ? _a : key;
|
|
12
|
+
};
|
|
13
|
+
// Format date
|
|
14
|
+
const formatDate = (date) => {
|
|
15
|
+
if (typeof globalApp === 'undefined')
|
|
16
|
+
return date.toUTCString();
|
|
17
|
+
return globalApp.formatDate(date, 'ds');
|
|
18
|
+
};
|
|
19
|
+
// Format value
|
|
20
|
+
const formatValue = (value) => {
|
|
21
|
+
if (value == null)
|
|
22
|
+
return '';
|
|
23
|
+
if (value instanceof Date && typeof globalApp !== 'undefined')
|
|
24
|
+
return globalApp.formatDate(value, 'ds');
|
|
25
|
+
return `${value}`;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Audit display
|
|
29
|
+
* @param props Props
|
|
30
|
+
* @returns Component
|
|
31
|
+
*/
|
|
32
|
+
export function AuditDisplay(props) {
|
|
33
|
+
// Theme
|
|
34
|
+
const theme = useTheme();
|
|
35
|
+
// Show data comparison
|
|
36
|
+
const showDataComparison = (data) => {
|
|
37
|
+
if (typeof globalApp === 'undefined')
|
|
38
|
+
return;
|
|
39
|
+
const keys = new Set([
|
|
40
|
+
...Object.keys(data.oldData),
|
|
41
|
+
...Object.keys(data.newData)
|
|
42
|
+
]);
|
|
43
|
+
const rows = Array.from(keys).map((field) => ({
|
|
44
|
+
field,
|
|
45
|
+
oldValue: data.oldData[field],
|
|
46
|
+
newValue: data.newData[field]
|
|
47
|
+
}));
|
|
48
|
+
const inputs = (React.createElement(Table, null,
|
|
49
|
+
React.createElement(TableHead, null,
|
|
50
|
+
React.createElement(TableRow, null,
|
|
51
|
+
React.createElement(TableCell, null, getLabel('field')),
|
|
52
|
+
React.createElement(TableCell, { align: "right" }, getLabel('oldValue')),
|
|
53
|
+
React.createElement(TableCell, { align: "right" }, getLabel('newValue')))),
|
|
54
|
+
React.createElement(TableBody, null, rows.map((row) => (React.createElement(TableRow, { key: row.field },
|
|
55
|
+
React.createElement(TableCell, null, getLabel(row.field)),
|
|
56
|
+
React.createElement(TableCell, { align: "right" }, formatValue(row.oldValue)),
|
|
57
|
+
React.createElement(TableCell, { align: "right" }, formatValue(row.newValue))))))));
|
|
58
|
+
globalApp.notifier.alert(undefined, undefined, NotificationMessageType.Info, { fullScreen: globalApp.smDown, inputs });
|
|
59
|
+
};
|
|
60
|
+
// Destruct
|
|
61
|
+
const { getItemStyle = (index, theme) => ({
|
|
62
|
+
padding: theme.spacing(1),
|
|
63
|
+
background: index % 2 === 0
|
|
64
|
+
? theme.palette.grey[100]
|
|
65
|
+
: theme.palette.grey[50]
|
|
66
|
+
}), itemRenderer = (data) => {
|
|
67
|
+
return (React.createElement(React.Fragment, null,
|
|
68
|
+
formatDate(data.creation) +
|
|
69
|
+
', [' +
|
|
70
|
+
getLabel(data.action) +
|
|
71
|
+
'], ' +
|
|
72
|
+
data.user,
|
|
73
|
+
data.changes != null && (React.createElement(Button, { variant: "outlined", size: "small", onClick: () => showDataComparison(data.changes), sx: { marginLeft: theme.spacing(1) } }, getLabel('dataComparison')))));
|
|
74
|
+
}, ...rest } = props;
|
|
75
|
+
// Layout
|
|
76
|
+
return (React.createElement(ListMoreDisplay, { ...rest }, (data, index) => (React.createElement("div", { key: data.id, style: getItemStyle(index, theme) }, itemRenderer(data, index)))));
|
|
77
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CardProps } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { GridLoader } from '../components/GridLoader';
|
|
4
|
+
/**
|
|
5
|
+
* ListMoreDisplay props
|
|
6
|
+
*/
|
|
7
|
+
export interface ListMoreDisplayProps<T> extends CardProps, GridLoader<T> {
|
|
8
|
+
/**
|
|
9
|
+
* Children to display the list
|
|
10
|
+
*/
|
|
11
|
+
children: (data: T, index: number) => React.ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* More button label
|
|
14
|
+
*/
|
|
15
|
+
moreLabel?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Header title
|
|
18
|
+
*/
|
|
19
|
+
headerTitle?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* ListMoreDisplay
|
|
23
|
+
* @param props Props
|
|
24
|
+
* @returns Component
|
|
25
|
+
*/
|
|
26
|
+
export declare function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>): JSX.Element;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Card, CardActions, CardContent, CardHeader, CircularProgress } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { globalApp, LoadingButton } from '..';
|
|
4
|
+
/**
|
|
5
|
+
* ListMoreDisplay
|
|
6
|
+
* @param props Props
|
|
7
|
+
* @returns Component
|
|
8
|
+
*/
|
|
9
|
+
export function ListMoreDisplay(props) {
|
|
10
|
+
// Destruct
|
|
11
|
+
const { autoLoad = true, children, defaultOrderBy, headerTitle, loadBatchSize, loadData, moreLabel = typeof globalApp === 'undefined'
|
|
12
|
+
? undefined
|
|
13
|
+
: globalApp.get('more') + '...', threshold, ...rest } = props;
|
|
14
|
+
// Refs
|
|
15
|
+
const refs = React.useRef({
|
|
16
|
+
autoLoad,
|
|
17
|
+
currentPage: 0,
|
|
18
|
+
hasNextPage: true,
|
|
19
|
+
isNextPageLoading: false,
|
|
20
|
+
orderBy: defaultOrderBy,
|
|
21
|
+
batchSize: 10,
|
|
22
|
+
loadedItems: 0,
|
|
23
|
+
selectedItems: []
|
|
24
|
+
});
|
|
25
|
+
const ref = refs.current;
|
|
26
|
+
// States
|
|
27
|
+
const [states, setStates] = React.useReducer((currentStates, newStates) => {
|
|
28
|
+
return { ...currentStates, ...newStates };
|
|
29
|
+
}, { completed: false });
|
|
30
|
+
// Load data
|
|
31
|
+
const loadDataLocal = async () => {
|
|
32
|
+
// Prevent multiple loadings
|
|
33
|
+
if (!ref.hasNextPage || ref.isNextPageLoading)
|
|
34
|
+
return;
|
|
35
|
+
// Update state
|
|
36
|
+
ref.isNextPageLoading = true;
|
|
37
|
+
// Parameters
|
|
38
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } = ref;
|
|
39
|
+
const loadProps = {
|
|
40
|
+
currentPage,
|
|
41
|
+
batchSize,
|
|
42
|
+
orderBy,
|
|
43
|
+
orderByAsc,
|
|
44
|
+
data
|
|
45
|
+
};
|
|
46
|
+
const items = await loadData(loadProps);
|
|
47
|
+
if (items == null || ref.isMounted === false) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
ref.isMounted = true;
|
|
51
|
+
const newItems = items.length;
|
|
52
|
+
const hasNextPage = newItems >= batchSize;
|
|
53
|
+
ref.lastLoadedItems = newItems;
|
|
54
|
+
ref.isNextPageLoading = false;
|
|
55
|
+
ref.hasNextPage = hasNextPage;
|
|
56
|
+
// Update rows
|
|
57
|
+
if (states.items == null)
|
|
58
|
+
setStates({ items, completed: !hasNextPage });
|
|
59
|
+
else
|
|
60
|
+
setStates({
|
|
61
|
+
items: [...states.items, ...items],
|
|
62
|
+
completed: !hasNextPage
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
if (autoLoad)
|
|
67
|
+
loadDataLocal();
|
|
68
|
+
}, [autoLoad]);
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
return () => {
|
|
71
|
+
ref.isMounted = false;
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
// Loading
|
|
75
|
+
if (states.items == null)
|
|
76
|
+
return React.createElement(CircularProgress, { size: 20 });
|
|
77
|
+
return (React.createElement(Card, { ...rest },
|
|
78
|
+
React.createElement(CardHeader, { title: headerTitle }),
|
|
79
|
+
React.createElement(CardContent, { sx: {
|
|
80
|
+
paddingTop: 0,
|
|
81
|
+
paddingBottom: states.completed ? 0 : 'inherit'
|
|
82
|
+
} }, states.items.map((item, index) => children(item, index))),
|
|
83
|
+
!states.completed && (React.createElement(CardActions, { sx: { justifyContent: 'flex-end' } },
|
|
84
|
+
React.createElement(LoadingButton, { onClick: async () => await loadDataLocal() }, moreLabel)))));
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.81",
|
|
4
4
|
"description": "TypeScript ReactJs framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -50,11 +50,11 @@
|
|
|
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.
|
|
53
|
+
"@etsoo/appscript": "^1.2.31",
|
|
54
54
|
"@etsoo/notificationbase": "^1.1.0",
|
|
55
55
|
"@etsoo/shared": "^1.1.9",
|
|
56
56
|
"@mui/icons-material": "^5.3.1",
|
|
57
|
-
"@mui/material": "^5.
|
|
57
|
+
"@mui/material": "^5.4.0",
|
|
58
58
|
"@reach/router": "^1.3.4",
|
|
59
59
|
"@types/pica": "^5.1.3",
|
|
60
60
|
"@types/pulltorefreshjs": "^0.1.5",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"react-beautiful-dnd": "^13.1.0",
|
|
73
73
|
"react-dom": "^17.0.2",
|
|
74
74
|
"react-draggable": "^4.4.4",
|
|
75
|
-
"react-imask": "^6.
|
|
75
|
+
"react-imask": "^6.3.0",
|
|
76
76
|
"react-window": "^1.8.6"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"@babel/runtime-corejs3": "^7.16.8",
|
|
84
84
|
"@types/jest": "^27.4.0",
|
|
85
85
|
"@types/react-test-renderer": "^17.0.1",
|
|
86
|
-
"@typescript-eslint/eslint-plugin": "^5.10.
|
|
87
|
-
"@typescript-eslint/parser": "^5.10.
|
|
86
|
+
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
|
87
|
+
"@typescript-eslint/parser": "^5.10.2",
|
|
88
88
|
"eslint": "^8.8.0",
|
|
89
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
90
90
|
"eslint-plugin-import": "^2.25.4",
|
package/src/app/ReactApp.ts
CHANGED
|
@@ -96,6 +96,16 @@ export interface IReactApp<
|
|
|
96
96
|
*/
|
|
97
97
|
readonly userState: UserState<D>;
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Is screen size down 'sm'
|
|
101
|
+
*/
|
|
102
|
+
smDown?: boolean;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Is screen size up 'md'
|
|
106
|
+
*/
|
|
107
|
+
mdUp?: boolean;
|
|
108
|
+
|
|
99
109
|
/**
|
|
100
110
|
* Set page data
|
|
101
111
|
* @param data Page data
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from './mu/texts/DateText';
|
|
|
37
37
|
export * from './mu/texts/MoneyText';
|
|
38
38
|
export * from './mu/texts/NumberText';
|
|
39
39
|
|
|
40
|
+
export * from './mu/AuditDisplay';
|
|
40
41
|
export * from './mu/ButtonLink';
|
|
41
42
|
export * from './mu/ComboBox';
|
|
42
43
|
export * from './mu/CountdownButton';
|
|
@@ -54,6 +55,7 @@ export * from './mu/IconButtonLink';
|
|
|
54
55
|
export * from './mu/InputField';
|
|
55
56
|
export * from './mu/ItemList';
|
|
56
57
|
export * from './mu/ListItemRightIcon';
|
|
58
|
+
export * from './mu/ListMoreDisplay';
|
|
57
59
|
export * from './mu/LoadingButton';
|
|
58
60
|
export * from './mu/MaskInput';
|
|
59
61
|
export * from './mu/MobileListItemRenderer';
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Utils } from '@etsoo/shared';
|
|
2
|
+
import { HelpOutline } from '@mui/icons-material';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Table,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableRow,
|
|
10
|
+
Theme,
|
|
11
|
+
useTheme
|
|
12
|
+
} from '@mui/material';
|
|
13
|
+
import React, { CSSProperties } from 'react';
|
|
14
|
+
import { globalApp, NotificationMessageType } from '..';
|
|
15
|
+
import { ListMoreDisplay, ListMoreDisplayProps } from './ListMoreDisplay';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Audit line update data model
|
|
19
|
+
*/
|
|
20
|
+
export interface AuditLineUpdateData {
|
|
21
|
+
oldData: Record<string, unknown>;
|
|
22
|
+
newData: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Audit line data model
|
|
27
|
+
*/
|
|
28
|
+
export interface AuditLine {
|
|
29
|
+
id: number;
|
|
30
|
+
creation: Date;
|
|
31
|
+
user: string;
|
|
32
|
+
action: string;
|
|
33
|
+
changes?: AuditLineUpdateData;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Audit display props
|
|
38
|
+
*/
|
|
39
|
+
export interface AuditDisplayProps
|
|
40
|
+
extends Omit<ListMoreDisplayProps<AuditLine>, 'children'> {
|
|
41
|
+
/**
|
|
42
|
+
* Get list item style callback
|
|
43
|
+
*/
|
|
44
|
+
getItemStyle?: (index: number, theme: Theme) => CSSProperties;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Item/line renderer
|
|
48
|
+
*/
|
|
49
|
+
itemRenderer?: (data: AuditLine, index: number) => React.ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get label
|
|
53
|
+
const getLabel = (key: string) => {
|
|
54
|
+
if (typeof globalApp === 'undefined') return key;
|
|
55
|
+
return globalApp.get(Utils.formatInitial(key)) ?? key;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Format date
|
|
59
|
+
const formatDate = (date: Date) => {
|
|
60
|
+
if (typeof globalApp === 'undefined') return date.toUTCString();
|
|
61
|
+
return globalApp.formatDate(date, 'ds');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Format value
|
|
65
|
+
const formatValue = (value: unknown) => {
|
|
66
|
+
if (value == null) return '';
|
|
67
|
+
if (value instanceof Date && typeof globalApp !== 'undefined')
|
|
68
|
+
return globalApp.formatDate(value, 'ds');
|
|
69
|
+
return `${value}`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Audit display
|
|
74
|
+
* @param props Props
|
|
75
|
+
* @returns Component
|
|
76
|
+
*/
|
|
77
|
+
export function AuditDisplay(props: AuditDisplayProps) {
|
|
78
|
+
// Theme
|
|
79
|
+
const theme = useTheme();
|
|
80
|
+
|
|
81
|
+
// Show data comparison
|
|
82
|
+
const showDataComparison = (data: AuditLineUpdateData) => {
|
|
83
|
+
if (typeof globalApp === 'undefined') return;
|
|
84
|
+
|
|
85
|
+
const keys = new Set([
|
|
86
|
+
...Object.keys(data.oldData),
|
|
87
|
+
...Object.keys(data.newData)
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const rows = Array.from(keys).map((field) => ({
|
|
91
|
+
field,
|
|
92
|
+
oldValue: data.oldData[field],
|
|
93
|
+
newValue: data.newData[field]
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
const inputs = (
|
|
97
|
+
<Table>
|
|
98
|
+
<TableHead>
|
|
99
|
+
<TableRow>
|
|
100
|
+
<TableCell>{getLabel('field')}</TableCell>
|
|
101
|
+
<TableCell align="right">
|
|
102
|
+
{getLabel('oldValue')}
|
|
103
|
+
</TableCell>
|
|
104
|
+
<TableCell align="right">
|
|
105
|
+
{getLabel('newValue')}
|
|
106
|
+
</TableCell>
|
|
107
|
+
</TableRow>
|
|
108
|
+
</TableHead>
|
|
109
|
+
<TableBody>
|
|
110
|
+
{rows.map((row) => (
|
|
111
|
+
<TableRow key={row.field}>
|
|
112
|
+
<TableCell>{getLabel(row.field)}</TableCell>
|
|
113
|
+
<TableCell align="right">
|
|
114
|
+
{formatValue(row.oldValue)}
|
|
115
|
+
</TableCell>
|
|
116
|
+
<TableCell align="right">
|
|
117
|
+
{formatValue(row.newValue)}
|
|
118
|
+
</TableCell>
|
|
119
|
+
</TableRow>
|
|
120
|
+
))}
|
|
121
|
+
</TableBody>
|
|
122
|
+
</Table>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
globalApp.notifier.alert(
|
|
126
|
+
undefined,
|
|
127
|
+
undefined,
|
|
128
|
+
NotificationMessageType.Info,
|
|
129
|
+
{ fullScreen: globalApp.smDown, inputs }
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Destruct
|
|
134
|
+
const {
|
|
135
|
+
getItemStyle = (index, theme) => ({
|
|
136
|
+
padding: theme.spacing(1),
|
|
137
|
+
background:
|
|
138
|
+
index % 2 === 0
|
|
139
|
+
? theme.palette.grey[100]
|
|
140
|
+
: theme.palette.grey[50]
|
|
141
|
+
}),
|
|
142
|
+
itemRenderer = (data) => {
|
|
143
|
+
return (
|
|
144
|
+
<React.Fragment>
|
|
145
|
+
{formatDate(data.creation) +
|
|
146
|
+
', [' +
|
|
147
|
+
getLabel(data.action) +
|
|
148
|
+
'], ' +
|
|
149
|
+
data.user}
|
|
150
|
+
{data.changes != null && (
|
|
151
|
+
<Button
|
|
152
|
+
variant="outlined"
|
|
153
|
+
size="small"
|
|
154
|
+
onClick={() => showDataComparison(data.changes!)}
|
|
155
|
+
sx={{ marginLeft: theme.spacing(1) }}
|
|
156
|
+
>
|
|
157
|
+
{getLabel('dataComparison')}
|
|
158
|
+
</Button>
|
|
159
|
+
)}
|
|
160
|
+
</React.Fragment>
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
...rest
|
|
164
|
+
} = props;
|
|
165
|
+
|
|
166
|
+
// Layout
|
|
167
|
+
return (
|
|
168
|
+
<ListMoreDisplay {...rest}>
|
|
169
|
+
{(data, index) => (
|
|
170
|
+
<div key={data.id} style={getItemStyle(index, theme)}>
|
|
171
|
+
{itemRenderer(data, index)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</ListMoreDisplay>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardActions,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardProps,
|
|
7
|
+
CircularProgress
|
|
8
|
+
} from '@mui/material';
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { globalApp, LoadingButton } from '..';
|
|
11
|
+
import {
|
|
12
|
+
GridLoadDataProps,
|
|
13
|
+
GridLoader,
|
|
14
|
+
GridLoaderStates
|
|
15
|
+
} from '../components/GridLoader';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ListMoreDisplay props
|
|
19
|
+
*/
|
|
20
|
+
export interface ListMoreDisplayProps<T> extends CardProps, GridLoader<T> {
|
|
21
|
+
/**
|
|
22
|
+
* Children to display the list
|
|
23
|
+
*/
|
|
24
|
+
children: (data: T, index: number) => React.ReactNode;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* More button label
|
|
28
|
+
*/
|
|
29
|
+
moreLabel?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Header title
|
|
33
|
+
*/
|
|
34
|
+
headerTitle?: React.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type states<T> = {
|
|
38
|
+
items?: T[];
|
|
39
|
+
completed: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* ListMoreDisplay
|
|
44
|
+
* @param props Props
|
|
45
|
+
* @returns Component
|
|
46
|
+
*/
|
|
47
|
+
export function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>) {
|
|
48
|
+
// Destruct
|
|
49
|
+
const {
|
|
50
|
+
autoLoad = true,
|
|
51
|
+
children,
|
|
52
|
+
defaultOrderBy,
|
|
53
|
+
headerTitle,
|
|
54
|
+
loadBatchSize,
|
|
55
|
+
loadData,
|
|
56
|
+
moreLabel = typeof globalApp === 'undefined'
|
|
57
|
+
? undefined
|
|
58
|
+
: globalApp.get('more') + '...',
|
|
59
|
+
threshold,
|
|
60
|
+
...rest
|
|
61
|
+
} = props;
|
|
62
|
+
|
|
63
|
+
// Refs
|
|
64
|
+
const refs = React.useRef<GridLoaderStates<T>>({
|
|
65
|
+
autoLoad,
|
|
66
|
+
currentPage: 0,
|
|
67
|
+
hasNextPage: true,
|
|
68
|
+
isNextPageLoading: false,
|
|
69
|
+
orderBy: defaultOrderBy,
|
|
70
|
+
batchSize: 10,
|
|
71
|
+
loadedItems: 0,
|
|
72
|
+
selectedItems: []
|
|
73
|
+
});
|
|
74
|
+
const ref = refs.current;
|
|
75
|
+
|
|
76
|
+
// States
|
|
77
|
+
const [states, setStates] = React.useReducer(
|
|
78
|
+
(currentStates: states<T>, newStates: Partial<states<T>>) => {
|
|
79
|
+
return { ...currentStates, ...newStates };
|
|
80
|
+
},
|
|
81
|
+
{ completed: false }
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Load data
|
|
85
|
+
const loadDataLocal = async () => {
|
|
86
|
+
// Prevent multiple loadings
|
|
87
|
+
if (!ref.hasNextPage || ref.isNextPageLoading) return;
|
|
88
|
+
|
|
89
|
+
// Update state
|
|
90
|
+
ref.isNextPageLoading = true;
|
|
91
|
+
|
|
92
|
+
// Parameters
|
|
93
|
+
const { currentPage, batchSize, orderBy, orderByAsc, data } = ref;
|
|
94
|
+
|
|
95
|
+
const loadProps: GridLoadDataProps = {
|
|
96
|
+
currentPage,
|
|
97
|
+
batchSize,
|
|
98
|
+
orderBy,
|
|
99
|
+
orderByAsc,
|
|
100
|
+
data
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const items = await loadData(loadProps);
|
|
104
|
+
if (items == null || ref.isMounted === false) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
ref.isMounted = true;
|
|
108
|
+
|
|
109
|
+
const newItems = items.length;
|
|
110
|
+
const hasNextPage = newItems >= batchSize;
|
|
111
|
+
ref.lastLoadedItems = newItems;
|
|
112
|
+
ref.isNextPageLoading = false;
|
|
113
|
+
ref.hasNextPage = hasNextPage;
|
|
114
|
+
|
|
115
|
+
// Update rows
|
|
116
|
+
if (states.items == null) setStates({ items, completed: !hasNextPage });
|
|
117
|
+
else
|
|
118
|
+
setStates({
|
|
119
|
+
items: [...states.items, ...items],
|
|
120
|
+
completed: !hasNextPage
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
React.useEffect(() => {
|
|
125
|
+
if (autoLoad) loadDataLocal();
|
|
126
|
+
}, [autoLoad]);
|
|
127
|
+
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
return () => {
|
|
130
|
+
ref.isMounted = false;
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
// Loading
|
|
135
|
+
if (states.items == null) return <CircularProgress size={20} />;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<Card {...rest}>
|
|
139
|
+
<CardHeader title={headerTitle}></CardHeader>
|
|
140
|
+
<CardContent
|
|
141
|
+
sx={{
|
|
142
|
+
paddingTop: 0,
|
|
143
|
+
paddingBottom: states.completed ? 0 : 'inherit'
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{states.items.map((item, index) => children(item, index))}
|
|
147
|
+
</CardContent>
|
|
148
|
+
{!states.completed && (
|
|
149
|
+
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
|
150
|
+
<LoadingButton onClick={async () => await loadDataLocal()}>
|
|
151
|
+
{moreLabel}
|
|
152
|
+
</LoadingButton>
|
|
153
|
+
</CardActions>
|
|
154
|
+
)}
|
|
155
|
+
</Card>
|
|
156
|
+
);
|
|
157
|
+
}
|