@etsoo/materialui 1.5.34 → 1.5.36
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/cjs/ButtonPopupCheckbox.d.ts +72 -0
- package/lib/cjs/ButtonPopupCheckbox.js +113 -0
- package/lib/cjs/DnDList.d.ts +5 -7
- package/lib/cjs/DnDList.js +2 -2
- package/lib/cjs/app/ReactApp.d.ts +5 -0
- package/lib/cjs/app/ReactApp.js +10 -1
- package/lib/cjs/app/ServiceApp.d.ts +5 -0
- package/lib/cjs/app/ServiceApp.js +11 -2
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/mjs/ButtonPopupCheckbox.d.ts +72 -0
- package/lib/mjs/ButtonPopupCheckbox.js +107 -0
- package/lib/mjs/DnDList.d.ts +5 -7
- package/lib/mjs/DnDList.js +2 -2
- package/lib/mjs/app/ReactApp.d.ts +5 -0
- package/lib/mjs/app/ReactApp.js +10 -1
- package/lib/mjs/app/ServiceApp.d.ts +5 -0
- package/lib/mjs/app/ServiceApp.js +11 -2
- package/lib/mjs/index.d.ts +1 -0
- package/lib/mjs/index.js +1 -0
- package/package.json +4 -4
- package/src/ButtonPopupCheckbox.tsx +342 -0
- package/src/DnDList.tsx +6 -16
- package/src/app/ReactApp.ts +11 -1
- package/src/app/ServiceApp.ts +14 -2
- package/src/index.ts +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ButtonProps } from "@mui/material/Button";
|
|
2
|
+
import { DataTypes, IdType } from "@etsoo/shared";
|
|
3
|
+
type DnDItemType = {
|
|
4
|
+
id: IdType;
|
|
5
|
+
};
|
|
6
|
+
export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick"> & {
|
|
7
|
+
/**
|
|
8
|
+
* Add items splitter
|
|
9
|
+
*/
|
|
10
|
+
addSplitter?: RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* Input field name
|
|
13
|
+
*/
|
|
14
|
+
inputName?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Label
|
|
17
|
+
*/
|
|
18
|
+
label?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Label in the end
|
|
21
|
+
*/
|
|
22
|
+
labelEnd?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Label field in items
|
|
25
|
+
*/
|
|
26
|
+
labelField: DataTypes.Keys<D>;
|
|
27
|
+
/**
|
|
28
|
+
* Label formatter
|
|
29
|
+
* @param item Item to be formatted
|
|
30
|
+
*/
|
|
31
|
+
labelFormatter?: (item: D) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Labels
|
|
34
|
+
*/
|
|
35
|
+
labels?: {
|
|
36
|
+
dragIndicator?: string;
|
|
37
|
+
add?: string;
|
|
38
|
+
more?: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Load data
|
|
42
|
+
*/
|
|
43
|
+
loadData: (ids?: D["id"][]) => Promise<D[]>;
|
|
44
|
+
/**
|
|
45
|
+
* On add handler
|
|
46
|
+
* @param ids Ids
|
|
47
|
+
*/
|
|
48
|
+
onAdd?: (ids: string[]) => Promise<false | D[]>;
|
|
49
|
+
/**
|
|
50
|
+
* On change handler
|
|
51
|
+
* @param ids Ids
|
|
52
|
+
*/
|
|
53
|
+
onChange?: (ids: D["id"][]) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Popup title
|
|
56
|
+
*/
|
|
57
|
+
popupTitle?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Popup message
|
|
60
|
+
*/
|
|
61
|
+
popupMessage?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The field is required or not
|
|
64
|
+
*/
|
|
65
|
+
required?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Ids
|
|
68
|
+
*/
|
|
69
|
+
ids?: D["id"][];
|
|
70
|
+
};
|
|
71
|
+
export declare function ButtonPopupCheckbox<D extends DnDItemType>(props: ButtonPopupCheckboxProps<D>): import("react/jsx-runtime").JSX.Element;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ButtonPopupCheckbox = ButtonPopupCheckbox;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const Button_1 = __importDefault(require("@mui/material/Button"));
|
|
9
|
+
const Chip_1 = __importDefault(require("@mui/material/Chip"));
|
|
10
|
+
const react_1 = __importDefault(require("react"));
|
|
11
|
+
const shared_1 = require("@etsoo/shared");
|
|
12
|
+
const FormGroup_1 = __importDefault(require("@mui/material/FormGroup"));
|
|
13
|
+
const Grid_1 = __importDefault(require("@mui/material/Grid"));
|
|
14
|
+
const Typography_1 = __importDefault(require("@mui/material/Typography"));
|
|
15
|
+
const DragIndicator_1 = __importDefault(require("@mui/icons-material/DragIndicator"));
|
|
16
|
+
const Add_1 = __importDefault(require("@mui/icons-material/Add"));
|
|
17
|
+
const IconButton_1 = __importDefault(require("@mui/material/IconButton"));
|
|
18
|
+
const FormControlLabel_1 = __importDefault(require("@mui/material/FormControlLabel"));
|
|
19
|
+
const Checkbox_1 = __importDefault(require("@mui/material/Checkbox"));
|
|
20
|
+
const TextField_1 = __importDefault(require("@mui/material/TextField"));
|
|
21
|
+
const DnDList_1 = require("./DnDList");
|
|
22
|
+
const FlexBox_1 = require("./FlexBox");
|
|
23
|
+
const ReactApp_1 = require("./app/ReactApp");
|
|
24
|
+
function ButtonPopupList(props) {
|
|
25
|
+
// Destruct
|
|
26
|
+
const { addSplitter = /\s*[,;]\s*/, ids, labelField, labelFormatter = (data) => {
|
|
27
|
+
console.log("data", data);
|
|
28
|
+
if (labelField in data) {
|
|
29
|
+
return data[labelField];
|
|
30
|
+
}
|
|
31
|
+
return data.id.toString();
|
|
32
|
+
}, labels, loadData, onAdd } = props;
|
|
33
|
+
// Methods
|
|
34
|
+
const dndRef = react_1.default.createRef();
|
|
35
|
+
// Ref
|
|
36
|
+
const inputRef = react_1.default.useRef(null);
|
|
37
|
+
react_1.default.useEffect(() => {
|
|
38
|
+
loadData(ids).then((data) => {
|
|
39
|
+
if (data == null || dndRef.current == null)
|
|
40
|
+
return;
|
|
41
|
+
dndRef.current.addItems(data);
|
|
42
|
+
});
|
|
43
|
+
}, [ids]);
|
|
44
|
+
return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(FormGroup_1.default, { children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: (0, jsx_runtime_1.jsx)(DnDList_1.DnDList, { items: [], labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => ((0, jsx_runtime_1.jsxs)(Grid_1.default, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: (0, jsx_runtime_1.jsx)(DragIndicator_1.default, {}) }), (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Checkbox_1.default, { name: "item", value: item.id, defaultChecked: ids?.includes(item.id) }), label: `${index + 1}. ${labelFormatter(item)}` })] })), height: 200, mRef: dndRef }) }) }), onAdd && ((0, jsx_runtime_1.jsxs)(FlexBox_1.HBox, { gap: 1, children: [(0, jsx_runtime_1.jsx)(TextField_1.default, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), (0, jsx_runtime_1.jsx)(Button_1.default, { sx: { width: "120px" }, variant: "contained", startIcon: (0, jsx_runtime_1.jsx)(Add_1.default, {}), size: "small", onClick: async () => {
|
|
45
|
+
if (inputRef.current == null)
|
|
46
|
+
return;
|
|
47
|
+
const input = inputRef.current.value.trim();
|
|
48
|
+
if (input === "") {
|
|
49
|
+
inputRef.current.focus();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const items = dndRef.current?.getItems() ?? [];
|
|
53
|
+
const inputIds = input
|
|
54
|
+
.split(addSplitter)
|
|
55
|
+
.filter((id) => !items.some((item) => item.id == id));
|
|
56
|
+
if (inputIds.length === 0) {
|
|
57
|
+
inputRef.current.focus();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const result = await onAdd(inputIds);
|
|
61
|
+
if (result === false) {
|
|
62
|
+
inputRef.current.focus();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
dndRef.current?.addItems(result);
|
|
66
|
+
inputRef.current.value = "";
|
|
67
|
+
inputRef.current.focus();
|
|
68
|
+
}, children: labels?.add })] }))] }));
|
|
69
|
+
}
|
|
70
|
+
function ButtonPopupCheckbox(props) {
|
|
71
|
+
// App
|
|
72
|
+
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
73
|
+
// Destruct
|
|
74
|
+
const { addSplitter, ids, inputName, label, labelEnd, labelFormatter, labelField, labels = {}, loadData, onAdd, onChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
|
|
75
|
+
// Default labels
|
|
76
|
+
if (!labels.add)
|
|
77
|
+
labels.add = app.get("add");
|
|
78
|
+
if (!labels.dragIndicator)
|
|
79
|
+
labels.dragIndicator = app.get("dragIndicator");
|
|
80
|
+
if (!labels.more)
|
|
81
|
+
labels.more = app.get("more");
|
|
82
|
+
// State
|
|
83
|
+
const [values, setValues] = react_1.default.useState([]);
|
|
84
|
+
react_1.default.useEffect(() => {
|
|
85
|
+
if (ids == null)
|
|
86
|
+
return;
|
|
87
|
+
setValues(ids);
|
|
88
|
+
}, [ids]);
|
|
89
|
+
// Click handler
|
|
90
|
+
const clickHandler = () => {
|
|
91
|
+
app.showInputDialog({
|
|
92
|
+
title: popupTitle,
|
|
93
|
+
message: popupMessage,
|
|
94
|
+
callback: (form) => {
|
|
95
|
+
if (form == null)
|
|
96
|
+
return;
|
|
97
|
+
// Form data
|
|
98
|
+
const { item = [] } = shared_1.DomUtils.dataAs(new FormData(form), {
|
|
99
|
+
item: "string[]"
|
|
100
|
+
});
|
|
101
|
+
if (required && item.length === 0) {
|
|
102
|
+
shared_1.DomUtils.setFocus("item", form);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
setValues(item);
|
|
106
|
+
onChange?.(item);
|
|
107
|
+
},
|
|
108
|
+
inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, ids: values, labelFormatter: labelFormatter, labelField: labelField, labels: labels, loadData: loadData, onAdd: onAdd })),
|
|
109
|
+
fullScreen: app.smDown
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: values.join(",") }), (0, jsx_runtime_1.jsxs)(Button_1.default, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, children: [label && (0, jsx_runtime_1.jsxs)(Typography_1.default, { variant: "body2", children: [label, ":"] }), values.map((id) => ((0, jsx_runtime_1.jsx)(Chip_1.default, { size: "small", label: id }, id))), labelEnd && (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "caption", children: labelEnd })] })] }));
|
|
113
|
+
}
|
package/lib/cjs/DnDList.d.ts
CHANGED
|
@@ -48,7 +48,9 @@ export interface DnDListRef<D extends object> {
|
|
|
48
48
|
/**
|
|
49
49
|
* DnD sortable list properties
|
|
50
50
|
*/
|
|
51
|
-
export interface DnDListPros<D extends
|
|
51
|
+
export interface DnDListPros<D extends {
|
|
52
|
+
id: UniqueIdentifier;
|
|
53
|
+
}> {
|
|
52
54
|
/**
|
|
53
55
|
* Get list item style callback
|
|
54
56
|
*/
|
|
@@ -65,14 +67,10 @@ export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
|
65
67
|
* List items
|
|
66
68
|
*/
|
|
67
69
|
items: D[];
|
|
68
|
-
/**
|
|
69
|
-
* Unique key field
|
|
70
|
-
*/
|
|
71
|
-
keyField: K;
|
|
72
70
|
/**
|
|
73
71
|
* Label field
|
|
74
72
|
*/
|
|
75
|
-
labelField:
|
|
73
|
+
labelField: DataTypes.Keys<D>;
|
|
76
74
|
/**
|
|
77
75
|
* Methods ref
|
|
78
76
|
*/
|
|
@@ -101,4 +99,4 @@ export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
|
101
99
|
*/
|
|
102
100
|
export declare function DnDList<D extends {
|
|
103
101
|
id: UniqueIdentifier;
|
|
104
|
-
}
|
|
102
|
+
}>(props: DnDListPros<D>): import("react/jsx-runtime").JSX.Element;
|
package/lib/cjs/DnDList.js
CHANGED
|
@@ -54,7 +54,7 @@ exports.DnDItemStyle = DnDItemStyle;
|
|
|
54
54
|
*/
|
|
55
55
|
function DnDList(props) {
|
|
56
56
|
// Destruct
|
|
57
|
-
const {
|
|
57
|
+
const { height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
|
|
58
58
|
// Theme
|
|
59
59
|
const theme = (0, styles_1.useTheme)();
|
|
60
60
|
// States
|
|
@@ -210,7 +210,7 @@ function DnDList(props) {
|
|
|
210
210
|
.forEach((input) => input.addEventListener("change", () => doFormChange()));
|
|
211
211
|
};
|
|
212
212
|
const children = ((0, jsx_runtime_1.jsx)(DndContextType, { onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: (0, jsx_runtime_1.jsx)(SortableContextType, { items: items, strategy: strategy, children: items.map((item, index) => {
|
|
213
|
-
const id = item
|
|
213
|
+
const id = item.id;
|
|
214
214
|
return ((0, jsx_runtime_1.jsx)(SortableItem, { id: id, useSortableType: useSortableType, CSSType: CSSType, style: getItemStyle(index, id === activeId), itemRenderer: (nodeRef, actionNodeRef) => itemRenderer(item, index, nodeRef, actionNodeRef) }, id));
|
|
215
215
|
}) }) }));
|
|
216
216
|
if (onFormChange) {
|
|
@@ -183,6 +183,11 @@ export declare class ReactApp<S extends IAppSettings, D extends IUser> extends C
|
|
|
183
183
|
* @param dispatch User state dispatch
|
|
184
184
|
*/
|
|
185
185
|
userLogin(user: D, refreshToken: string, dispatch?: boolean): void;
|
|
186
|
+
/**
|
|
187
|
+
* User login dispatch
|
|
188
|
+
* @param user New user
|
|
189
|
+
*/
|
|
190
|
+
protected doLoginDispatch(user: D): void;
|
|
186
191
|
/**
|
|
187
192
|
* User logout
|
|
188
193
|
* @param clearToken Clear refresh token or not
|
package/lib/cjs/app/ReactApp.js
CHANGED
|
@@ -311,7 +311,16 @@ class ReactApp extends appscript_1.CoreApp {
|
|
|
311
311
|
// Super call, set token
|
|
312
312
|
super.userLogin(user, refreshToken);
|
|
313
313
|
// Dispatch action
|
|
314
|
-
if (
|
|
314
|
+
if (dispatch !== false) {
|
|
315
|
+
this.doLoginDispatch(user);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* User login dispatch
|
|
320
|
+
* @param user New user
|
|
321
|
+
*/
|
|
322
|
+
doLoginDispatch(user) {
|
|
323
|
+
if (this.userStateDispatch != null)
|
|
315
324
|
this.userStateDispatch({
|
|
316
325
|
type: react_2.UserActionType.Login,
|
|
317
326
|
user
|
|
@@ -70,6 +70,11 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, S extends
|
|
|
70
70
|
* @param data Data
|
|
71
71
|
*/
|
|
72
72
|
protected saveCoreToken(data: ApiRefreshTokenDto): void;
|
|
73
|
+
/**
|
|
74
|
+
* On switch organization handler
|
|
75
|
+
* This method is called when the organization is switched successfully
|
|
76
|
+
*/
|
|
77
|
+
protected onSwitchOrg(): Promise<void> | void;
|
|
73
78
|
/**
|
|
74
79
|
* Switch organization
|
|
75
80
|
* @param organizationId Organization ID
|
|
@@ -157,6 +157,11 @@ class ServiceApp extends ReactApp_1.ReactApp {
|
|
|
157
157
|
// Exchange tokens
|
|
158
158
|
this.exchangeTokenAll(data);
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* On switch organization handler
|
|
162
|
+
* This method is called when the organization is switched successfully
|
|
163
|
+
*/
|
|
164
|
+
onSwitchOrg() { }
|
|
160
165
|
/**
|
|
161
166
|
* Switch organization
|
|
162
167
|
* @param organizationId Organization ID
|
|
@@ -183,8 +188,12 @@ class ServiceApp extends ReactApp_1.ReactApp {
|
|
|
183
188
|
}
|
|
184
189
|
// Override the user data's refresh token
|
|
185
190
|
const user = refreshToken ? { ...result.data, refreshToken } : result.data;
|
|
186
|
-
// User login
|
|
187
|
-
this.userLoginEx(user, core,
|
|
191
|
+
// User login without dispatch
|
|
192
|
+
this.userLoginEx(user, core, false);
|
|
193
|
+
// Handle the switch organization
|
|
194
|
+
await this.onSwitchOrg();
|
|
195
|
+
// Trigger the dispatch at last
|
|
196
|
+
this.doLoginDispatch(user);
|
|
188
197
|
return result;
|
|
189
198
|
}
|
|
190
199
|
async refreshTokenSucceed(user, token, callback) {
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from "./BackButton";
|
|
|
39
39
|
export * from "./BridgeCloseButton";
|
|
40
40
|
export * from "./ButtonLink";
|
|
41
41
|
export * from "./ButtonPopover";
|
|
42
|
+
export * from "./ButtonPopupCheckbox";
|
|
42
43
|
export * from "./ComboBox";
|
|
43
44
|
export * from "./ComboBoxMultiple";
|
|
44
45
|
export * from "./ComboBoxPro";
|
package/lib/cjs/index.js
CHANGED
|
@@ -55,6 +55,7 @@ __exportStar(require("./BackButton"), exports);
|
|
|
55
55
|
__exportStar(require("./BridgeCloseButton"), exports);
|
|
56
56
|
__exportStar(require("./ButtonLink"), exports);
|
|
57
57
|
__exportStar(require("./ButtonPopover"), exports);
|
|
58
|
+
__exportStar(require("./ButtonPopupCheckbox"), exports);
|
|
58
59
|
__exportStar(require("./ComboBox"), exports);
|
|
59
60
|
__exportStar(require("./ComboBoxMultiple"), exports);
|
|
60
61
|
__exportStar(require("./ComboBoxPro"), exports);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ButtonProps } from "@mui/material/Button";
|
|
2
|
+
import { DataTypes, IdType } from "@etsoo/shared";
|
|
3
|
+
type DnDItemType = {
|
|
4
|
+
id: IdType;
|
|
5
|
+
};
|
|
6
|
+
export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps, "chidren" | "onClick"> & {
|
|
7
|
+
/**
|
|
8
|
+
* Add items splitter
|
|
9
|
+
*/
|
|
10
|
+
addSplitter?: RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* Input field name
|
|
13
|
+
*/
|
|
14
|
+
inputName?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Label
|
|
17
|
+
*/
|
|
18
|
+
label?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Label in the end
|
|
21
|
+
*/
|
|
22
|
+
labelEnd?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Label field in items
|
|
25
|
+
*/
|
|
26
|
+
labelField: DataTypes.Keys<D>;
|
|
27
|
+
/**
|
|
28
|
+
* Label formatter
|
|
29
|
+
* @param item Item to be formatted
|
|
30
|
+
*/
|
|
31
|
+
labelFormatter?: (item: D) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Labels
|
|
34
|
+
*/
|
|
35
|
+
labels?: {
|
|
36
|
+
dragIndicator?: string;
|
|
37
|
+
add?: string;
|
|
38
|
+
more?: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Load data
|
|
42
|
+
*/
|
|
43
|
+
loadData: (ids?: D["id"][]) => Promise<D[]>;
|
|
44
|
+
/**
|
|
45
|
+
* On add handler
|
|
46
|
+
* @param ids Ids
|
|
47
|
+
*/
|
|
48
|
+
onAdd?: (ids: string[]) => Promise<false | D[]>;
|
|
49
|
+
/**
|
|
50
|
+
* On change handler
|
|
51
|
+
* @param ids Ids
|
|
52
|
+
*/
|
|
53
|
+
onChange?: (ids: D["id"][]) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Popup title
|
|
56
|
+
*/
|
|
57
|
+
popupTitle?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Popup message
|
|
60
|
+
*/
|
|
61
|
+
popupMessage?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The field is required or not
|
|
64
|
+
*/
|
|
65
|
+
required?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Ids
|
|
68
|
+
*/
|
|
69
|
+
ids?: D["id"][];
|
|
70
|
+
};
|
|
71
|
+
export declare function ButtonPopupCheckbox<D extends DnDItemType>(props: ButtonPopupCheckboxProps<D>): import("react/jsx-runtime").JSX.Element;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Button from "@mui/material/Button";
|
|
3
|
+
import Chip from "@mui/material/Chip";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { DomUtils } from "@etsoo/shared";
|
|
6
|
+
import FormGroup from "@mui/material/FormGroup";
|
|
7
|
+
import Grid from "@mui/material/Grid";
|
|
8
|
+
import Typography from "@mui/material/Typography";
|
|
9
|
+
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
10
|
+
import AddIcon from "@mui/icons-material/Add";
|
|
11
|
+
import IconButton from "@mui/material/IconButton";
|
|
12
|
+
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
13
|
+
import Checkbox from "@mui/material/Checkbox";
|
|
14
|
+
import TextField from "@mui/material/TextField";
|
|
15
|
+
import { DnDList } from "./DnDList";
|
|
16
|
+
import { HBox, VBox } from "./FlexBox";
|
|
17
|
+
import { useRequiredAppContext } from "./app/ReactApp";
|
|
18
|
+
function ButtonPopupList(props) {
|
|
19
|
+
// Destruct
|
|
20
|
+
const { addSplitter = /\s*[,;]\s*/, ids, labelField, labelFormatter = (data) => {
|
|
21
|
+
console.log("data", data);
|
|
22
|
+
if (labelField in data) {
|
|
23
|
+
return data[labelField];
|
|
24
|
+
}
|
|
25
|
+
return data.id.toString();
|
|
26
|
+
}, labels, loadData, onAdd } = props;
|
|
27
|
+
// Methods
|
|
28
|
+
const dndRef = React.createRef();
|
|
29
|
+
// Ref
|
|
30
|
+
const inputRef = React.useRef(null);
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
loadData(ids).then((data) => {
|
|
33
|
+
if (data == null || dndRef.current == null)
|
|
34
|
+
return;
|
|
35
|
+
dndRef.current.addItems(data);
|
|
36
|
+
});
|
|
37
|
+
}, [ids]);
|
|
38
|
+
return (_jsxs(VBox, { gap: 2, children: [_jsx(FormGroup, { children: _jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDList, { items: [], labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => (_jsxs(Grid, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [_jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: _jsx(DragIndicatorIcon, {}) }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "item", value: item.id, defaultChecked: ids?.includes(item.id) }), label: `${index + 1}. ${labelFormatter(item)}` })] })), height: 200, mRef: dndRef }) }) }), onAdd && (_jsxs(HBox, { gap: 1, children: [_jsx(TextField, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), _jsx(Button, { sx: { width: "120px" }, variant: "contained", startIcon: _jsx(AddIcon, {}), size: "small", onClick: async () => {
|
|
39
|
+
if (inputRef.current == null)
|
|
40
|
+
return;
|
|
41
|
+
const input = inputRef.current.value.trim();
|
|
42
|
+
if (input === "") {
|
|
43
|
+
inputRef.current.focus();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const items = dndRef.current?.getItems() ?? [];
|
|
47
|
+
const inputIds = input
|
|
48
|
+
.split(addSplitter)
|
|
49
|
+
.filter((id) => !items.some((item) => item.id == id));
|
|
50
|
+
if (inputIds.length === 0) {
|
|
51
|
+
inputRef.current.focus();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const result = await onAdd(inputIds);
|
|
55
|
+
if (result === false) {
|
|
56
|
+
inputRef.current.focus();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
dndRef.current?.addItems(result);
|
|
60
|
+
inputRef.current.value = "";
|
|
61
|
+
inputRef.current.focus();
|
|
62
|
+
}, children: labels?.add })] }))] }));
|
|
63
|
+
}
|
|
64
|
+
export function ButtonPopupCheckbox(props) {
|
|
65
|
+
// App
|
|
66
|
+
const app = useRequiredAppContext();
|
|
67
|
+
// Destruct
|
|
68
|
+
const { addSplitter, ids, inputName, label, labelEnd, labelFormatter, labelField, labels = {}, loadData, onAdd, onChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
|
|
69
|
+
// Default labels
|
|
70
|
+
if (!labels.add)
|
|
71
|
+
labels.add = app.get("add");
|
|
72
|
+
if (!labels.dragIndicator)
|
|
73
|
+
labels.dragIndicator = app.get("dragIndicator");
|
|
74
|
+
if (!labels.more)
|
|
75
|
+
labels.more = app.get("more");
|
|
76
|
+
// State
|
|
77
|
+
const [values, setValues] = React.useState([]);
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
if (ids == null)
|
|
80
|
+
return;
|
|
81
|
+
setValues(ids);
|
|
82
|
+
}, [ids]);
|
|
83
|
+
// Click handler
|
|
84
|
+
const clickHandler = () => {
|
|
85
|
+
app.showInputDialog({
|
|
86
|
+
title: popupTitle,
|
|
87
|
+
message: popupMessage,
|
|
88
|
+
callback: (form) => {
|
|
89
|
+
if (form == null)
|
|
90
|
+
return;
|
|
91
|
+
// Form data
|
|
92
|
+
const { item = [] } = DomUtils.dataAs(new FormData(form), {
|
|
93
|
+
item: "string[]"
|
|
94
|
+
});
|
|
95
|
+
if (required && item.length === 0) {
|
|
96
|
+
DomUtils.setFocus("item", form);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
setValues(item);
|
|
100
|
+
onChange?.(item);
|
|
101
|
+
},
|
|
102
|
+
inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, ids: values, labelFormatter: labelFormatter, labelField: labelField, labels: labels, loadData: loadData, onAdd: onAdd })),
|
|
103
|
+
fullScreen: app.smDown
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
return (_jsxs(React.Fragment, { children: [_jsx("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: values.join(",") }), _jsxs(Button, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, children: [label && _jsxs(Typography, { variant: "body2", children: [label, ":"] }), values.map((id) => (_jsx(Chip, { size: "small", label: id }, id))), labelEnd && _jsx(Typography, { variant: "caption", children: labelEnd })] })] }));
|
|
107
|
+
}
|
package/lib/mjs/DnDList.d.ts
CHANGED
|
@@ -48,7 +48,9 @@ export interface DnDListRef<D extends object> {
|
|
|
48
48
|
/**
|
|
49
49
|
* DnD sortable list properties
|
|
50
50
|
*/
|
|
51
|
-
export interface DnDListPros<D extends
|
|
51
|
+
export interface DnDListPros<D extends {
|
|
52
|
+
id: UniqueIdentifier;
|
|
53
|
+
}> {
|
|
52
54
|
/**
|
|
53
55
|
* Get list item style callback
|
|
54
56
|
*/
|
|
@@ -65,14 +67,10 @@ export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
|
65
67
|
* List items
|
|
66
68
|
*/
|
|
67
69
|
items: D[];
|
|
68
|
-
/**
|
|
69
|
-
* Unique key field
|
|
70
|
-
*/
|
|
71
|
-
keyField: K;
|
|
72
70
|
/**
|
|
73
71
|
* Label field
|
|
74
72
|
*/
|
|
75
|
-
labelField:
|
|
73
|
+
labelField: DataTypes.Keys<D>;
|
|
76
74
|
/**
|
|
77
75
|
* Methods ref
|
|
78
76
|
*/
|
|
@@ -101,4 +99,4 @@ export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
|
101
99
|
*/
|
|
102
100
|
export declare function DnDList<D extends {
|
|
103
101
|
id: UniqueIdentifier;
|
|
104
|
-
}
|
|
102
|
+
}>(props: DnDListPros<D>): import("react/jsx-runtime").JSX.Element;
|
package/lib/mjs/DnDList.js
CHANGED
|
@@ -46,7 +46,7 @@ export const DnDItemStyle = (index, isDragging, theme) => ({
|
|
|
46
46
|
*/
|
|
47
47
|
export function DnDList(props) {
|
|
48
48
|
// Destruct
|
|
49
|
-
const {
|
|
49
|
+
const { height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
|
|
50
50
|
// Theme
|
|
51
51
|
const theme = useTheme();
|
|
52
52
|
// States
|
|
@@ -202,7 +202,7 @@ export function DnDList(props) {
|
|
|
202
202
|
.forEach((input) => input.addEventListener("change", () => doFormChange()));
|
|
203
203
|
};
|
|
204
204
|
const children = (_jsx(DndContextType, { onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: _jsx(SortableContextType, { items: items, strategy: strategy, children: items.map((item, index) => {
|
|
205
|
-
const id = item
|
|
205
|
+
const id = item.id;
|
|
206
206
|
return (_jsx(SortableItem, { id: id, useSortableType: useSortableType, CSSType: CSSType, style: getItemStyle(index, id === activeId), itemRenderer: (nodeRef, actionNodeRef) => itemRenderer(item, index, nodeRef, actionNodeRef) }, id));
|
|
207
207
|
}) }) }));
|
|
208
208
|
if (onFormChange) {
|
|
@@ -183,6 +183,11 @@ export declare class ReactApp<S extends IAppSettings, D extends IUser> extends C
|
|
|
183
183
|
* @param dispatch User state dispatch
|
|
184
184
|
*/
|
|
185
185
|
userLogin(user: D, refreshToken: string, dispatch?: boolean): void;
|
|
186
|
+
/**
|
|
187
|
+
* User login dispatch
|
|
188
|
+
* @param user New user
|
|
189
|
+
*/
|
|
190
|
+
protected doLoginDispatch(user: D): void;
|
|
186
191
|
/**
|
|
187
192
|
* User logout
|
|
188
193
|
* @param clearToken Clear refresh token or not
|
package/lib/mjs/app/ReactApp.js
CHANGED
|
@@ -303,7 +303,16 @@ export class ReactApp extends CoreApp {
|
|
|
303
303
|
// Super call, set token
|
|
304
304
|
super.userLogin(user, refreshToken);
|
|
305
305
|
// Dispatch action
|
|
306
|
-
if (
|
|
306
|
+
if (dispatch !== false) {
|
|
307
|
+
this.doLoginDispatch(user);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* User login dispatch
|
|
312
|
+
* @param user New user
|
|
313
|
+
*/
|
|
314
|
+
doLoginDispatch(user) {
|
|
315
|
+
if (this.userStateDispatch != null)
|
|
307
316
|
this.userStateDispatch({
|
|
308
317
|
type: UserActionType.Login,
|
|
309
318
|
user
|
|
@@ -70,6 +70,11 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, S extends
|
|
|
70
70
|
* @param data Data
|
|
71
71
|
*/
|
|
72
72
|
protected saveCoreToken(data: ApiRefreshTokenDto): void;
|
|
73
|
+
/**
|
|
74
|
+
* On switch organization handler
|
|
75
|
+
* This method is called when the organization is switched successfully
|
|
76
|
+
*/
|
|
77
|
+
protected onSwitchOrg(): Promise<void> | void;
|
|
73
78
|
/**
|
|
74
79
|
* Switch organization
|
|
75
80
|
* @param organizationId Organization ID
|
|
@@ -154,6 +154,11 @@ export class ServiceApp extends ReactApp {
|
|
|
154
154
|
// Exchange tokens
|
|
155
155
|
this.exchangeTokenAll(data);
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* On switch organization handler
|
|
159
|
+
* This method is called when the organization is switched successfully
|
|
160
|
+
*/
|
|
161
|
+
onSwitchOrg() { }
|
|
157
162
|
/**
|
|
158
163
|
* Switch organization
|
|
159
164
|
* @param organizationId Organization ID
|
|
@@ -180,8 +185,12 @@ export class ServiceApp extends ReactApp {
|
|
|
180
185
|
}
|
|
181
186
|
// Override the user data's refresh token
|
|
182
187
|
const user = refreshToken ? { ...result.data, refreshToken } : result.data;
|
|
183
|
-
// User login
|
|
184
|
-
this.userLoginEx(user, core,
|
|
188
|
+
// User login without dispatch
|
|
189
|
+
this.userLoginEx(user, core, false);
|
|
190
|
+
// Handle the switch organization
|
|
191
|
+
await this.onSwitchOrg();
|
|
192
|
+
// Trigger the dispatch at last
|
|
193
|
+
this.doLoginDispatch(user);
|
|
185
194
|
return result;
|
|
186
195
|
}
|
|
187
196
|
async refreshTokenSucceed(user, token, callback) {
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from "./BackButton";
|
|
|
39
39
|
export * from "./BridgeCloseButton";
|
|
40
40
|
export * from "./ButtonLink";
|
|
41
41
|
export * from "./ButtonPopover";
|
|
42
|
+
export * from "./ButtonPopupCheckbox";
|
|
42
43
|
export * from "./ComboBox";
|
|
43
44
|
export * from "./ComboBoxMultiple";
|
|
44
45
|
export * from "./ComboBoxPro";
|
package/lib/mjs/index.js
CHANGED
|
@@ -39,6 +39,7 @@ export * from "./BackButton";
|
|
|
39
39
|
export * from "./BridgeCloseButton";
|
|
40
40
|
export * from "./ButtonLink";
|
|
41
41
|
export * from "./ButtonPopover";
|
|
42
|
+
export * from "./ButtonPopupCheckbox";
|
|
42
43
|
export * from "./ComboBox";
|
|
43
44
|
export * from "./ComboBoxMultiple";
|
|
44
45
|
export * from "./ComboBoxPro";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/materialui",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.36",
|
|
4
4
|
"description": "TypeScript Material-UI Implementation",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/mjs/index.js",
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"@dnd-kit/sortable": "^10.0.0",
|
|
41
41
|
"@emotion/react": "^11.14.0",
|
|
42
42
|
"@emotion/styled": "^11.14.0",
|
|
43
|
-
"@etsoo/appscript": "^1.6.
|
|
43
|
+
"@etsoo/appscript": "^1.6.29",
|
|
44
44
|
"@etsoo/notificationbase": "^1.1.60",
|
|
45
45
|
"@etsoo/react": "^1.8.40",
|
|
46
|
-
"@etsoo/shared": "^1.2.
|
|
46
|
+
"@etsoo/shared": "^1.2.70",
|
|
47
47
|
"@mui/icons-material": "^7.0.2",
|
|
48
48
|
"@mui/material": "^7.0.2",
|
|
49
|
-
"@mui/x-data-grid": "^8.
|
|
49
|
+
"@mui/x-data-grid": "^8.1.0",
|
|
50
50
|
"chart.js": "^4.4.9",
|
|
51
51
|
"chartjs-plugin-datalabels": "^2.2.0",
|
|
52
52
|
"dompurify": "^3.2.5",
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import Button, { ButtonProps } from "@mui/material/Button";
|
|
2
|
+
import Chip from "@mui/material/Chip";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { DataTypes, DomUtils, IdType } from "@etsoo/shared";
|
|
5
|
+
import FormGroup from "@mui/material/FormGroup";
|
|
6
|
+
import Grid from "@mui/material/Grid";
|
|
7
|
+
import Typography from "@mui/material/Typography";
|
|
8
|
+
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
9
|
+
import AddIcon from "@mui/icons-material/Add";
|
|
10
|
+
import IconButton from "@mui/material/IconButton";
|
|
11
|
+
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
12
|
+
import Checkbox from "@mui/material/Checkbox";
|
|
13
|
+
import TextField from "@mui/material/TextField";
|
|
14
|
+
import { DnDList, DnDListRef } from "./DnDList";
|
|
15
|
+
import { HBox, VBox } from "./FlexBox";
|
|
16
|
+
import { useRequiredAppContext } from "./app/ReactApp";
|
|
17
|
+
|
|
18
|
+
type DnDItemType = {
|
|
19
|
+
id: IdType;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
|
|
23
|
+
ButtonProps,
|
|
24
|
+
"chidren" | "onClick"
|
|
25
|
+
> & {
|
|
26
|
+
/**
|
|
27
|
+
* Add items splitter
|
|
28
|
+
*/
|
|
29
|
+
addSplitter?: RegExp;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Input field name
|
|
33
|
+
*/
|
|
34
|
+
inputName?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Label
|
|
38
|
+
*/
|
|
39
|
+
label?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Label in the end
|
|
43
|
+
*/
|
|
44
|
+
labelEnd?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Label field in items
|
|
48
|
+
*/
|
|
49
|
+
labelField: DataTypes.Keys<D>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Label formatter
|
|
53
|
+
* @param item Item to be formatted
|
|
54
|
+
*/
|
|
55
|
+
labelFormatter?: (item: D) => string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Labels
|
|
59
|
+
*/
|
|
60
|
+
labels?: {
|
|
61
|
+
dragIndicator?: string;
|
|
62
|
+
add?: string;
|
|
63
|
+
more?: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Load data
|
|
68
|
+
*/
|
|
69
|
+
loadData: (ids?: D["id"][]) => Promise<D[]>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* On add handler
|
|
73
|
+
* @param ids Ids
|
|
74
|
+
*/
|
|
75
|
+
onAdd?: (ids: string[]) => Promise<false | D[]>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* On change handler
|
|
79
|
+
* @param ids Ids
|
|
80
|
+
*/
|
|
81
|
+
onChange?: (ids: D["id"][]) => void;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Popup title
|
|
85
|
+
*/
|
|
86
|
+
popupTitle?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Popup message
|
|
90
|
+
*/
|
|
91
|
+
popupMessage?: string;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The field is required or not
|
|
95
|
+
*/
|
|
96
|
+
required?: boolean;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Ids
|
|
100
|
+
*/
|
|
101
|
+
ids?: D["id"][];
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type ButtonPopupListProps<D extends DnDItemType> = Pick<
|
|
105
|
+
ButtonPopupCheckboxProps<D>,
|
|
106
|
+
| "addSplitter"
|
|
107
|
+
| "labelField"
|
|
108
|
+
| "labelFormatter"
|
|
109
|
+
| "labels"
|
|
110
|
+
| "loadData"
|
|
111
|
+
| "onAdd"
|
|
112
|
+
| "ids"
|
|
113
|
+
>;
|
|
114
|
+
|
|
115
|
+
function ButtonPopupList<D extends DnDItemType>(
|
|
116
|
+
props: ButtonPopupListProps<D>
|
|
117
|
+
) {
|
|
118
|
+
// Destruct
|
|
119
|
+
const {
|
|
120
|
+
addSplitter = /\s*[,;]\s*/,
|
|
121
|
+
ids,
|
|
122
|
+
labelField,
|
|
123
|
+
labelFormatter = (data) => {
|
|
124
|
+
console.log("data", data);
|
|
125
|
+
if (labelField in data) {
|
|
126
|
+
return data[labelField] as string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return data.id.toString();
|
|
130
|
+
},
|
|
131
|
+
labels,
|
|
132
|
+
loadData,
|
|
133
|
+
onAdd
|
|
134
|
+
} = props;
|
|
135
|
+
|
|
136
|
+
// Methods
|
|
137
|
+
const dndRef = React.createRef<DnDListRef<D>>();
|
|
138
|
+
|
|
139
|
+
// Ref
|
|
140
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
141
|
+
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
loadData(ids).then((data) => {
|
|
144
|
+
if (data == null || dndRef.current == null) return;
|
|
145
|
+
dndRef.current.addItems(data);
|
|
146
|
+
});
|
|
147
|
+
}, [ids]);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<VBox gap={2}>
|
|
151
|
+
<FormGroup>
|
|
152
|
+
<Grid container spacing={0}>
|
|
153
|
+
<DnDList<D>
|
|
154
|
+
items={[]}
|
|
155
|
+
labelField={labelField}
|
|
156
|
+
itemRenderer={(item, index, nodeRef, actionNodeRef) => (
|
|
157
|
+
<Grid
|
|
158
|
+
size={{ xs: 12, md: 6, lg: 4 }}
|
|
159
|
+
display="flex"
|
|
160
|
+
justifyContent="flex-start"
|
|
161
|
+
alignItems="center"
|
|
162
|
+
gap={1}
|
|
163
|
+
{...nodeRef}
|
|
164
|
+
>
|
|
165
|
+
<IconButton
|
|
166
|
+
style={{ cursor: "move" }}
|
|
167
|
+
size="small"
|
|
168
|
+
title={labels?.dragIndicator}
|
|
169
|
+
{...actionNodeRef}
|
|
170
|
+
>
|
|
171
|
+
<DragIndicatorIcon />
|
|
172
|
+
</IconButton>
|
|
173
|
+
<FormControlLabel
|
|
174
|
+
control={
|
|
175
|
+
<Checkbox
|
|
176
|
+
name="item"
|
|
177
|
+
value={item.id}
|
|
178
|
+
defaultChecked={ids?.includes(item.id)}
|
|
179
|
+
/>
|
|
180
|
+
}
|
|
181
|
+
label={`${index + 1}. ${labelFormatter(item)}`}
|
|
182
|
+
/>
|
|
183
|
+
</Grid>
|
|
184
|
+
)}
|
|
185
|
+
height={200}
|
|
186
|
+
mRef={dndRef}
|
|
187
|
+
></DnDList>
|
|
188
|
+
</Grid>
|
|
189
|
+
</FormGroup>
|
|
190
|
+
{onAdd && (
|
|
191
|
+
<HBox gap={1}>
|
|
192
|
+
<TextField
|
|
193
|
+
variant="outlined"
|
|
194
|
+
label={labels?.more}
|
|
195
|
+
fullWidth
|
|
196
|
+
inputRef={inputRef}
|
|
197
|
+
/>
|
|
198
|
+
<Button
|
|
199
|
+
sx={{ width: "120px" }}
|
|
200
|
+
variant="contained"
|
|
201
|
+
startIcon={<AddIcon />}
|
|
202
|
+
size="small"
|
|
203
|
+
onClick={async () => {
|
|
204
|
+
if (inputRef.current == null) return;
|
|
205
|
+
|
|
206
|
+
const input = inputRef.current.value.trim();
|
|
207
|
+
if (input === "") {
|
|
208
|
+
inputRef.current.focus();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const items = dndRef.current?.getItems() ?? [];
|
|
213
|
+
|
|
214
|
+
const inputIds = input
|
|
215
|
+
.split(addSplitter)
|
|
216
|
+
.filter((id) => !items.some((item) => item.id == id));
|
|
217
|
+
|
|
218
|
+
if (inputIds.length === 0) {
|
|
219
|
+
inputRef.current.focus();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const result = await onAdd(inputIds);
|
|
224
|
+
if (result === false) {
|
|
225
|
+
inputRef.current.focus();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
dndRef.current?.addItems(result);
|
|
230
|
+
|
|
231
|
+
inputRef.current.value = "";
|
|
232
|
+
inputRef.current.focus();
|
|
233
|
+
}}
|
|
234
|
+
>
|
|
235
|
+
{labels?.add}
|
|
236
|
+
</Button>
|
|
237
|
+
</HBox>
|
|
238
|
+
)}
|
|
239
|
+
</VBox>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function ButtonPopupCheckbox<D extends DnDItemType>(
|
|
244
|
+
props: ButtonPopupCheckboxProps<D>
|
|
245
|
+
) {
|
|
246
|
+
// App
|
|
247
|
+
const app = useRequiredAppContext();
|
|
248
|
+
|
|
249
|
+
// Destruct
|
|
250
|
+
const {
|
|
251
|
+
addSplitter,
|
|
252
|
+
ids,
|
|
253
|
+
inputName,
|
|
254
|
+
label,
|
|
255
|
+
labelEnd,
|
|
256
|
+
labelFormatter,
|
|
257
|
+
labelField,
|
|
258
|
+
labels = {},
|
|
259
|
+
loadData,
|
|
260
|
+
onAdd,
|
|
261
|
+
onChange,
|
|
262
|
+
popupTitle = label,
|
|
263
|
+
popupMessage,
|
|
264
|
+
required = false,
|
|
265
|
+
sx = { gap: 1, justifyContent: "flex-start" },
|
|
266
|
+
variant = "outlined",
|
|
267
|
+
...rest
|
|
268
|
+
} = props;
|
|
269
|
+
|
|
270
|
+
// Default labels
|
|
271
|
+
if (!labels.add) labels.add = app.get("add");
|
|
272
|
+
if (!labels.dragIndicator) labels.dragIndicator = app.get("dragIndicator");
|
|
273
|
+
if (!labels.more) labels.more = app.get("more");
|
|
274
|
+
|
|
275
|
+
// State
|
|
276
|
+
const [values, setValues] = React.useState<D["id"][]>([]);
|
|
277
|
+
React.useEffect(() => {
|
|
278
|
+
if (ids == null) return;
|
|
279
|
+
setValues(ids);
|
|
280
|
+
}, [ids]);
|
|
281
|
+
|
|
282
|
+
// Click handler
|
|
283
|
+
const clickHandler = () => {
|
|
284
|
+
app.showInputDialog({
|
|
285
|
+
title: popupTitle,
|
|
286
|
+
message: popupMessage,
|
|
287
|
+
callback: (form) => {
|
|
288
|
+
if (form == null) return;
|
|
289
|
+
|
|
290
|
+
// Form data
|
|
291
|
+
const { item = [] } = DomUtils.dataAs(new FormData(form), {
|
|
292
|
+
item: "string[]"
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (required && item.length === 0) {
|
|
296
|
+
DomUtils.setFocus("item", form);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
setValues(item);
|
|
301
|
+
|
|
302
|
+
onChange?.(item);
|
|
303
|
+
},
|
|
304
|
+
inputs: (
|
|
305
|
+
<ButtonPopupList
|
|
306
|
+
addSplitter={addSplitter}
|
|
307
|
+
ids={values}
|
|
308
|
+
labelFormatter={labelFormatter}
|
|
309
|
+
labelField={labelField}
|
|
310
|
+
labels={labels}
|
|
311
|
+
loadData={loadData}
|
|
312
|
+
onAdd={onAdd}
|
|
313
|
+
/>
|
|
314
|
+
),
|
|
315
|
+
fullScreen: app.smDown
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<React.Fragment>
|
|
321
|
+
<input
|
|
322
|
+
type="text"
|
|
323
|
+
style={{ position: "absolute", opacity: 0, width: 0 }}
|
|
324
|
+
name={inputName}
|
|
325
|
+
required={required}
|
|
326
|
+
defaultValue={values.join(",")}
|
|
327
|
+
/>
|
|
328
|
+
<Button
|
|
329
|
+
variant={variant}
|
|
330
|
+
sx={sx}
|
|
331
|
+
onClick={() => clickHandler()}
|
|
332
|
+
{...rest}
|
|
333
|
+
>
|
|
334
|
+
{label && <Typography variant="body2">{label}:</Typography>}
|
|
335
|
+
{values.map((id) => (
|
|
336
|
+
<Chip key={id} size="small" label={id} />
|
|
337
|
+
))}
|
|
338
|
+
{labelEnd && <Typography variant="caption">{labelEnd}</Typography>}
|
|
339
|
+
</Button>
|
|
340
|
+
</React.Fragment>
|
|
341
|
+
);
|
|
342
|
+
}
|
package/src/DnDList.tsx
CHANGED
|
@@ -119,7 +119,7 @@ export interface DnDListRef<D extends object> {
|
|
|
119
119
|
/**
|
|
120
120
|
* DnD sortable list properties
|
|
121
121
|
*/
|
|
122
|
-
export interface DnDListPros<D extends
|
|
122
|
+
export interface DnDListPros<D extends { id: UniqueIdentifier }> {
|
|
123
123
|
/**
|
|
124
124
|
* Get list item style callback
|
|
125
125
|
*/
|
|
@@ -145,15 +145,10 @@ export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
|
145
145
|
*/
|
|
146
146
|
items: D[];
|
|
147
147
|
|
|
148
|
-
/**
|
|
149
|
-
* Unique key field
|
|
150
|
-
*/
|
|
151
|
-
keyField: K;
|
|
152
|
-
|
|
153
148
|
/**
|
|
154
149
|
* Label field
|
|
155
150
|
*/
|
|
156
|
-
labelField:
|
|
151
|
+
labelField: DataTypes.Keys<D>;
|
|
157
152
|
|
|
158
153
|
/**
|
|
159
154
|
* Methods ref
|
|
@@ -191,16 +186,11 @@ export interface DnDListPros<D extends object, K extends DataTypes.Keys<D>> {
|
|
|
191
186
|
* @param props Props
|
|
192
187
|
* @returns Component
|
|
193
188
|
*/
|
|
194
|
-
export function DnDList<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
D,
|
|
198
|
-
UniqueIdentifier
|
|
199
|
-
>
|
|
200
|
-
>(props: DnDListPros<D, K>) {
|
|
189
|
+
export function DnDList<D extends { id: UniqueIdentifier }>(
|
|
190
|
+
props: DnDListPros<D>
|
|
191
|
+
) {
|
|
201
192
|
// Destruct
|
|
202
193
|
const {
|
|
203
|
-
keyField,
|
|
204
194
|
height = 360,
|
|
205
195
|
itemRenderer,
|
|
206
196
|
labelField,
|
|
@@ -465,7 +455,7 @@ export function DnDList<
|
|
|
465
455
|
<DndContextType onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
|
466
456
|
<SortableContextType items={items} strategy={strategy}>
|
|
467
457
|
{items.map((item, index) => {
|
|
468
|
-
const id = item
|
|
458
|
+
const id = item.id;
|
|
469
459
|
return (
|
|
470
460
|
<SortableItem
|
|
471
461
|
id={id}
|
package/src/app/ReactApp.ts
CHANGED
|
@@ -483,7 +483,17 @@ export class ReactApp<S extends IAppSettings, D extends IUser>
|
|
|
483
483
|
super.userLogin(user, refreshToken);
|
|
484
484
|
|
|
485
485
|
// Dispatch action
|
|
486
|
-
if (
|
|
486
|
+
if (dispatch !== false) {
|
|
487
|
+
this.doLoginDispatch(user);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* User login dispatch
|
|
493
|
+
* @param user New user
|
|
494
|
+
*/
|
|
495
|
+
protected doLoginDispatch(user: D) {
|
|
496
|
+
if (this.userStateDispatch != null)
|
|
487
497
|
this.userStateDispatch({
|
|
488
498
|
type: UserActionType.Login,
|
|
489
499
|
user
|
package/src/app/ServiceApp.ts
CHANGED
|
@@ -209,6 +209,12 @@ export class ServiceApp<
|
|
|
209
209
|
this.exchangeTokenAll(data);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* On switch organization handler
|
|
214
|
+
* This method is called when the organization is switched successfully
|
|
215
|
+
*/
|
|
216
|
+
protected onSwitchOrg(): Promise<void> | void {}
|
|
217
|
+
|
|
212
218
|
/**
|
|
213
219
|
* Switch organization
|
|
214
220
|
* @param organizationId Organization ID
|
|
@@ -248,8 +254,14 @@ export class ServiceApp<
|
|
|
248
254
|
// Override the user data's refresh token
|
|
249
255
|
const user = refreshToken ? { ...result.data, refreshToken } : result.data;
|
|
250
256
|
|
|
251
|
-
// User login
|
|
252
|
-
this.userLoginEx(user, core,
|
|
257
|
+
// User login without dispatch
|
|
258
|
+
this.userLoginEx(user, core, false);
|
|
259
|
+
|
|
260
|
+
// Handle the switch organization
|
|
261
|
+
await this.onSwitchOrg();
|
|
262
|
+
|
|
263
|
+
// Trigger the dispatch at last
|
|
264
|
+
this.doLoginDispatch(user);
|
|
253
265
|
|
|
254
266
|
return result;
|
|
255
267
|
}
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ export * from "./BackButton";
|
|
|
45
45
|
export * from "./BridgeCloseButton";
|
|
46
46
|
export * from "./ButtonLink";
|
|
47
47
|
export * from "./ButtonPopover";
|
|
48
|
+
export * from "./ButtonPopupCheckbox";
|
|
48
49
|
export * from "./ComboBox";
|
|
49
50
|
export * from "./ComboBoxMultiple";
|
|
50
51
|
export * from "./ComboBoxPro";
|