@ramesesinc/platform-core 0.1.9 → 0.1.11

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.
Files changed (76) hide show
  1. package/dist/components/action/DeleteData.d.ts +1 -0
  2. package/dist/components/action/DeleteData.js +16 -5
  3. package/dist/components/action/LookupPage.d.ts +2 -1
  4. package/dist/components/action/LookupPage.js +4 -3
  5. package/dist/components/action/Play.d.ts +6 -0
  6. package/dist/components/action/Play.js +40 -0
  7. package/dist/components/action/ProgressBar.d.ts +8 -0
  8. package/dist/components/action/ProgressBar.js +146 -0
  9. package/dist/components/action/ViewPage.d.ts +3 -1
  10. package/dist/components/action/ViewPage.js +22 -10
  11. package/dist/components/common/UIMenu.d.ts +1 -0
  12. package/dist/components/common/UIMenu.js +5 -3
  13. package/dist/components/index.d.ts +4 -1
  14. package/dist/components/index.js +4 -1
  15. package/dist/components/input/Combo.d.ts +21 -0
  16. package/dist/components/input/Combo.js +137 -0
  17. package/dist/components/input/DateField.js +7 -14
  18. package/dist/components/input/Text.d.ts +5 -0
  19. package/dist/components/input/Text.js +42 -7
  20. package/dist/components/input/YearPicker.js +3 -2
  21. package/dist/components/list/EditableMenu.d.ts +2 -0
  22. package/dist/components/list/EditableMenu.js +128 -0
  23. package/dist/components/list/TabMenu.js +2 -2
  24. package/dist/components/list/TreeMenu.js +35 -13
  25. package/dist/components/table/DataList.d.ts +1 -1
  26. package/dist/components/table/DataList.js +56 -29
  27. package/dist/components/table/DataTable.d.ts +2 -0
  28. package/dist/components/table/DataTable.js +31 -22
  29. package/dist/components/view/FilterView.js +1 -1
  30. package/dist/components/view/HtmlForm.js +12 -9
  31. package/dist/components/view/HtmlView.js +1 -1
  32. package/dist/components/view/PageView.d.ts +1 -0
  33. package/dist/components/view/PageView.js +38 -11
  34. package/dist/components/view/PopupView.d.ts +1 -0
  35. package/dist/components/view/PopupView.js +4 -4
  36. package/dist/components/view/RootView.d.ts +1 -0
  37. package/dist/components/view/RootView.js +18 -18
  38. package/dist/components/view/WizardView.d.ts +1 -1
  39. package/dist/components/view/WizardView.js +7 -25
  40. package/dist/core/AuthContext.js +1 -1
  41. package/dist/core/DynamicComponent.d.ts +2 -1
  42. package/dist/core/DynamicComponent.js +24 -2
  43. package/dist/core/Page.d.ts +1 -0
  44. package/dist/core/Page.js +6 -5
  45. package/dist/core/PageCache.d.ts +0 -2
  46. package/dist/core/PageCache.js +3 -8
  47. package/dist/core/PageContext.d.ts +1 -0
  48. package/dist/core/PageContext.js +16 -2
  49. package/dist/core/PageViewContext.d.ts +8 -2
  50. package/dist/core/PageViewContext.js +155 -86
  51. package/dist/core/Panel.js +34 -12
  52. package/dist/core/StepHandler.d.ts +1 -1
  53. package/dist/core/StepHandler.js +58 -21
  54. package/dist/index.css +98 -0
  55. package/dist/layouts/CardLayout.d.ts +2 -2
  56. package/dist/layouts/CardLayout.js +3 -4
  57. package/dist/layouts/HPanel.d.ts +2 -2
  58. package/dist/layouts/HPanel.js +1 -2
  59. package/dist/layouts/VPanel.d.ts +2 -2
  60. package/dist/layouts/VPanel.js +1 -2
  61. package/dist/layouts/index.d.ts +2 -3
  62. package/dist/layouts/index.js +2 -3
  63. package/dist/lib/utils/ExprUtil.js +18 -29
  64. package/dist/lib/utils/ResourceLoader.js +19 -7
  65. package/dist/lib/utils/SectionProvider.js +1 -1
  66. package/dist/lib/utils/initResourceLoader.d.ts +2 -0
  67. package/dist/lib/utils/initResourceLoader.js +64 -95
  68. package/dist/lib/utils/nunjucks.d.ts +2 -0
  69. package/dist/lib/utils/nunjucks.js +8 -0
  70. package/dist/templates/CrudFormTemplate.js +2 -3
  71. package/dist/templates/DataListTemplate.js +1 -1
  72. package/dist/templates/WizardTemplate.d.ts +3 -0
  73. package/dist/templates/WizardTemplate.js +17 -10
  74. package/package.json +1 -1
  75. package/dist/components/input/Select.d.ts +0 -14
  76. package/dist/components/input/Select.js +0 -40
@@ -0,0 +1,137 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronDown } from "lucide-react";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { usePageContext } from "../../core/PageContext";
5
+ import UIComponent from "../common/UIComponent";
6
+ import useUIInput from "../common/UIInput";
7
+ const RADIUS_STYLES = {
8
+ none: "rounded-none",
9
+ md: "rounded-md",
10
+ lg: "rounded-lg",
11
+ xl: "rounded-xl",
12
+ "2xl": "rounded-2xl",
13
+ "3xl": "rounded-3xl",
14
+ full: "rounded-full",
15
+ };
16
+ const variantStyles = {
17
+ primary: "border bg-blue-500 text-blue-50",
18
+ secondary: "border bg-white text-gray-700",
19
+ danger: "border bg-red-600 text-white",
20
+ text: "border bg-transparent text-blue-600",
21
+ contained: "border bg-blue-500 text-blue-50",
22
+ outlined: "border border-1 bg-transparent text-black",
23
+ };
24
+ const Combo = (props) => {
25
+ var _a, _b, _c, _d, _e, _f;
26
+ const { name, immediate = true, items = [], data = {}, placeholder, variant = "outlined", radius = "md", className = "", } = props !== null && props !== void 0 ? props : {};
27
+ const { params } = data !== null && data !== void 0 ? data : {};
28
+ const { projection } = params !== null && params !== void 0 ? params : {};
29
+ const selectRef = useRef(null);
30
+ const pageContext = usePageContext();
31
+ const [listItems, setListItems] = useState(items);
32
+ const [rawItems, setRawItems] = useState([]);
33
+ const [selectValue, setSelectValue] = useState("");
34
+ // derive label/value keys from projection
35
+ const projectedKeys = Object.keys(projection !== null && projection !== void 0 ? projection : {});
36
+ const labelKey = (_b = (_a = data.labelKey) !== null && _a !== void 0 ? _a : projectedKeys[0]) !== null && _b !== void 0 ? _b : "label";
37
+ const valueKey = (_d = (_c = data.valueKey) !== null && _c !== void 0 ? _c : projectedKeys[0]) !== null && _d !== void 0 ? _d : "value";
38
+ const isSpecificField = (_e = props.name) === null || _e === void 0 ? void 0 : _e.includes(".");
39
+ const buildValue = (rawItem) => {
40
+ if (isSpecificField) {
41
+ return rawItem[valueKey];
42
+ }
43
+ else {
44
+ return Object.keys(projection !== null && projection !== void 0 ? projection : {}).reduce((acc, key) => {
45
+ acc[key] = rawItem[key];
46
+ return acc;
47
+ }, {});
48
+ }
49
+ };
50
+ const onRefresh = () => {
51
+ var _a;
52
+ const val = getValue();
53
+ if (val != null && typeof val === "object") {
54
+ setSelectValue(String((_a = val[valueKey]) !== null && _a !== void 0 ? _a : ""));
55
+ }
56
+ else {
57
+ setSelectValue(val !== null && val !== void 0 ? val : "");
58
+ }
59
+ };
60
+ const { getValue, setValue } = useUIInput(Object.assign(Object.assign({}, props), { onRefresh }));
61
+ const fetchItems = async () => {
62
+ var _a, _b, _c, _d;
63
+ if (data === null || data === void 0 ? void 0 : data.api) {
64
+ const res = await (pageContext === null || pageContext === void 0 ? void 0 : pageContext.execService(data.api, (_a = data.params) !== null && _a !== void 0 ? _a : {}));
65
+ const raw = (_c = (_b = res === null || res === void 0 ? void 0 : res.data) !== null && _b !== void 0 ? _b : res) !== null && _c !== void 0 ? _c : [];
66
+ setRawItems(raw);
67
+ const mapped = raw
68
+ .map((item) => {
69
+ var _a, _b, _c;
70
+ return ({
71
+ label: String((_a = item[labelKey]) !== null && _a !== void 0 ? _a : ""),
72
+ value: String((_c = (_b = item[valueKey]) !== null && _b !== void 0 ? _b : item["_id"]) !== null && _c !== void 0 ? _c : ""),
73
+ });
74
+ })
75
+ .sort((a, b) => a.label.localeCompare(b.label));
76
+ setListItems(mapped);
77
+ // auto-select first item
78
+ if (mapped.length > 0) {
79
+ const currentVal = getValue();
80
+ if (currentVal != null && currentVal !== "" && currentVal !== undefined) {
81
+ // value already set, just sync display
82
+ if (typeof currentVal === "object") {
83
+ setSelectValue(String((_d = currentVal[valueKey]) !== null && _d !== void 0 ? _d : ""));
84
+ }
85
+ else {
86
+ setSelectValue(String(currentVal));
87
+ }
88
+ }
89
+ else {
90
+ // no value set, auto-select first
91
+ const firstMapped = mapped[0];
92
+ const firstItem = raw.find((item) => { var _a, _b; return String((_b = (_a = item[valueKey]) !== null && _a !== void 0 ? _a : item["_id"]) !== null && _b !== void 0 ? _b : "") === firstMapped.value; });
93
+ if (firstItem) {
94
+ setSelectValue(firstMapped.value);
95
+ setValue(buildValue(firstItem));
96
+ }
97
+ }
98
+ }
99
+ return;
100
+ }
101
+ setListItems([...items].sort((a, b) => a.label.localeCompare(b.label)));
102
+ };
103
+ useEffect(() => {
104
+ fetchItems();
105
+ }, []);
106
+ useEffect(() => {
107
+ if (!(data === null || data === void 0 ? void 0 : data.api)) {
108
+ setListItems([...items].sort((a, b) => a.label.localeCompare(b.label)));
109
+ }
110
+ }, [items]);
111
+ const handleBlur = () => {
112
+ if (!immediate) {
113
+ setValue(selectValue);
114
+ }
115
+ };
116
+ const onChange = (e) => {
117
+ var _a, _b;
118
+ const text = (_a = e.target.value) !== null && _a !== void 0 ? _a : "";
119
+ setSelectValue(text);
120
+ if (immediate) {
121
+ const rawItem = rawItems.find((item) => { var _a, _b; return String((_b = (_a = item[valueKey]) !== null && _a !== void 0 ? _a : item["_id"]) !== null && _b !== void 0 ? _b : "") === text; });
122
+ if (rawItem) {
123
+ setValue(buildValue(rawItem));
124
+ }
125
+ else {
126
+ setValue(text);
127
+ }
128
+ }
129
+ (_b = props.onChange) === null || _b === void 0 ? void 0 : _b.call(props, text);
130
+ };
131
+ const baseStyles = "w-full appearance-none cursor-pointer pl-4 pr-10 py-[4px] text-md font-medium transition-all duration-150 ease-in-out bg-transparent";
132
+ const focusStyles = "focus:outline-none focus:ring-0 focus:shadow-none";
133
+ const roundingStyle = (_f = RADIUS_STYLES[radius]) !== null && _f !== void 0 ? _f : RADIUS_STYLES["md"];
134
+ const finalClassName = [baseStyles, focusStyles, roundingStyle].filter(Boolean).join(" ");
135
+ return (_jsx(UIComponent, Object.assign({}, (props !== null && props !== void 0 ? props : {}), { children: _jsxs("div", { className: `relative inline-flex items-center overflow-hidden ${roundingStyle} ${variantStyles[variant]} ${className}`, children: [_jsxs("select", { ref: selectRef, onChange: onChange, value: selectValue, onBlur: handleBlur, className: finalClassName, children: [_jsx("option", { value: "", children: placeholder !== null && placeholder !== void 0 ? placeholder : "Select..." }), listItems.map((item) => (_jsx("option", { value: item.value, children: item.label }, item.value)))] }), _jsx("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none flex items-center", children: _jsx(ChevronDown, { size: 16 }) })] }) })));
136
+ };
137
+ export default Combo;
@@ -29,7 +29,7 @@ const DateField = (props) => {
29
29
  const getCurrentDate = () => {
30
30
  if (inputValue && inputValue.match(/^\d{4}-\d{2}-\d{2}$/)) {
31
31
  // Parse ISO date string as local date to avoid timezone issues
32
- const parts = inputValue.split('-');
32
+ const parts = inputValue.split("-");
33
33
  const year = parseInt(parts[0], 10);
34
34
  const month = parseInt(parts[1], 10) - 1; // Month is 0-indexed
35
35
  const day = parseInt(parts[2], 10);
@@ -46,7 +46,7 @@ const DateField = (props) => {
46
46
  setInputValue(text);
47
47
  // Update calendar date when user types a valid date
48
48
  if (text && text.match(/^\d{4}-\d{2}-\d{2}$/)) {
49
- const parts = text.split('-');
49
+ const parts = text.split("-");
50
50
  const year = parseInt(parts[0], 10);
51
51
  const month = parseInt(parts[1], 10) - 1;
52
52
  const day = parseInt(parts[2], 10);
@@ -103,27 +103,20 @@ const DateField = (props) => {
103
103
  if (!inputValue || !inputValue.match(/^\d{4}-\d{2}-\d{2}$/))
104
104
  return false;
105
105
  // Parse ISO date string as local date
106
- const parts = inputValue.split('-');
106
+ const parts = inputValue.split("-");
107
107
  const selectedYear = parseInt(parts[0], 10);
108
108
  const selectedMonth = parseInt(parts[1], 10) - 1;
109
109
  const selectedDay = parseInt(parts[2], 10);
110
- return (date.getDate() === selectedDay &&
111
- date.getMonth() === selectedMonth &&
112
- date.getFullYear() === selectedYear);
110
+ return date.getDate() === selectedDay && date.getMonth() === selectedMonth && date.getFullYear() === selectedYear;
113
111
  };
114
112
  const isToday = (date) => {
115
113
  const today = new globalThis.Date();
116
- return (date.getDate() === today.getDate() &&
117
- date.getMonth() === today.getMonth() &&
118
- date.getFullYear() === today.getFullYear());
114
+ return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
119
115
  };
120
- const monthNames = [
121
- "January", "February", "March", "April", "May", "June",
122
- "July", "August", "September", "October", "November", "December"
123
- ];
116
+ const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
124
117
  const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
125
118
  const calendarDays = generateCalendarDays();
126
- return (_jsx(UIComponent, Object.assign({}, (props !== null && props !== void 0 ? props : {}), { children: _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "flex gap-1", children: [_jsx("input", { type: "date", ref: inputRef, onChange: onChange, value: inputValue, onFocus: handleFocus, onBlur: handleBlur, className: className, min: min, max: max }), _jsx("button", { type: "button", onClick: toggleCalendar, className: "border rounded px-3 py-1 bg-white hover:bg-gray-50", children: "\uD83D\uDCC5" })] }), showCalendar && (_jsxs("div", { ref: calendarRef, className: "absolute z-10 mt-1 bg-white border rounded shadow-lg p-4", style: { minWidth: "280px" }, children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("button", { type: "button", onClick: previousMonth, className: "px-2 py-1 hover:bg-gray-100 rounded", children: "\u25C0" }), _jsxs("div", { className: "font-semibold", children: [monthNames[calendarDate.getMonth()], " ", calendarDate.getFullYear()] }), _jsx("button", { type: "button", onClick: nextMonth, className: "px-2 py-1 hover:bg-gray-100 rounded", children: "\u25B6" })] }), _jsx("div", { className: "grid grid-cols-7 gap-1 mb-2", children: dayNames.map((day) => (_jsx("div", { className: "text-center text-xs font-medium text-gray-600 py-1", children: day }, day))) }), _jsx("div", { className: "grid grid-cols-7 gap-1", children: calendarDays.map((date, index) => {
119
+ return (_jsx(UIComponent, Object.assign({}, (props !== null && props !== void 0 ? props : {}), { children: _jsxs("div", { className: "relative", children: [_jsx("div", { className: "flex gap-1", children: _jsx("input", { type: "date", ref: inputRef, onChange: onChange, value: inputValue, onFocus: handleFocus, onBlur: handleBlur, className: className, min: min, max: max }) }), showCalendar && (_jsxs("div", { ref: calendarRef, className: "absolute z-10 mt-1 bg-white border rounded shadow-lg p-4", style: { minWidth: "280px" }, children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("button", { type: "button", onClick: previousMonth, className: "px-2 py-1 hover:bg-gray-100 rounded", children: "\u25C0" }), _jsxs("div", { className: "font-semibold", children: [monthNames[calendarDate.getMonth()], " ", calendarDate.getFullYear()] }), _jsx("button", { type: "button", onClick: nextMonth, className: "px-2 py-1 hover:bg-gray-100 rounded", children: "\u25B6" })] }), _jsx("div", { className: "grid grid-cols-7 gap-1 mb-2", children: dayNames.map((day) => (_jsx("div", { className: "text-center text-xs font-medium text-gray-600 py-1", children: day }, day))) }), _jsx("div", { className: "grid grid-cols-7 gap-1", children: calendarDays.map((date, index) => {
127
120
  if (!date) {
128
121
  return _jsx("div", { className: "aspect-square" }, index);
129
122
  }
@@ -1,7 +1,12 @@
1
1
  import { UIInputProps } from "../common/UIInput";
2
+ export type TextCase = "upper" | "lower" | "capitalize" | "none";
3
+ export declare const applyTextCase: (value: string, mode: TextCase) => string;
2
4
  type TextFieldProps = UIInputProps & {
3
5
  required?: boolean;
4
6
  immediate?: boolean;
7
+ align?: "left" | "center" | "right";
8
+ noSpace?: boolean;
9
+ placeholder?: string;
5
10
  };
6
11
  declare const TextField: (props: TextFieldProps) => import("react/jsx-runtime").JSX.Element;
7
12
  export default TextField;
@@ -2,20 +2,38 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRef, useState } from "react";
3
3
  import UIComponent from "../common/UIComponent";
4
4
  import useUIInput from "../common/UIInput";
5
+ class TextCaseFormatter {
6
+ apply(value, mode) {
7
+ switch (mode) {
8
+ case "upper":
9
+ return value.toUpperCase();
10
+ case "lower":
11
+ return value.toLowerCase();
12
+ case "capitalize":
13
+ return value.replace(/\b\w/g, (char) => char.toUpperCase());
14
+ case "none":
15
+ default:
16
+ return value;
17
+ }
18
+ }
19
+ }
20
+ export const applyTextCase = (value, mode) => {
21
+ return new TextCaseFormatter().apply(value, mode);
22
+ };
5
23
  const TextField = (props) => {
6
- const { name, immediate = true } = props !== null && props !== void 0 ? props : {};
7
- const [focused, setFocused] = useState(false);
24
+ var _a, _b;
25
+ const { immediate = true, align = "left", noSpace = false, required = false, placeholder } = props !== null && props !== void 0 ? props : {};
26
+ // const [focused, setFocused] = useState(false);
8
27
  const inputRef = useRef(null);
9
28
  const valueRef = useRef("");
10
- const className = "border rounded px-2 py-1 w-full";
11
29
  const handleFocus = () => {
12
- setFocused(true);
30
+ // setFocused(true);
13
31
  };
14
32
  const handleBlur = () => {
15
33
  if (!immediate) {
16
34
  setValue(inputValue);
17
35
  }
18
- setFocused(false);
36
+ // setFocused(false);
19
37
  };
20
38
  const onRefresh = () => {
21
39
  setInputValue(getValue());
@@ -25,7 +43,10 @@ const TextField = (props) => {
25
43
  const [inputValue, setInputValue] = useState(valueRef.current);
26
44
  const onChange = (e) => {
27
45
  var _a;
28
- const text = (_a = e.target.value) !== null && _a !== void 0 ? _a : "";
46
+ let text = (_a = e.target.value) !== null && _a !== void 0 ? _a : "";
47
+ if (noSpace) {
48
+ text = text.replace(/\s+/g, "");
49
+ }
29
50
  if (text !== inputValue) {
30
51
  valueRef.current = text;
31
52
  setInputValue(valueRef.current);
@@ -34,6 +55,20 @@ const TextField = (props) => {
34
55
  setValue(text);
35
56
  }
36
57
  };
37
- return (_jsx(UIComponent, Object.assign({}, (props !== null && props !== void 0 ? props : {}), { children: _jsx("input", { type: "text", ref: inputRef, onChange: onChange, value: inputValue, onFocus: handleFocus, onBlur: handleBlur, className: className }) })));
58
+ const handleKeyDown = (e) => {
59
+ if (noSpace && e.key === " ") {
60
+ e.preventDefault();
61
+ }
62
+ };
63
+ const alignClass = {
64
+ left: "text-left",
65
+ center: "text-center",
66
+ right: "text-right",
67
+ }[align];
68
+ const inReadMode = (_b = (_a = binding === null || binding === void 0 ? void 0 : binding.isReadMode) === null || _a === void 0 ? void 0 : _a.call(binding)) !== null && _b !== void 0 ? _b : false;
69
+ const defaultStyleClass = "text-sm px-2 py-2 border rounded-md shadow-sm transition-colors duration-150 ease-in-out w-full";
70
+ const focusStyleClass = `focus:outline-none ${inReadMode ? "bg-gray-50" : "focus:bg-yellow-50"} focus:ring-1 focus:ring-blue-400`;
71
+ const borderClass = "border-gray-300";
72
+ return (_jsx(UIComponent, Object.assign({}, (props !== null && props !== void 0 ? props : {}), { children: _jsx("input", { type: "text", ref: inputRef, onChange: onChange, onKeyDown: handleKeyDown, value: inputValue, onFocus: handleFocus, onBlur: handleBlur, required: required, disabled: inReadMode, placeholder: inReadMode ? "" : placeholder, className: `${defaultStyleClass} ${focusStyleClass} ${alignClass} ${borderClass}` }) })));
38
73
  };
39
74
  export default TextField;
@@ -18,9 +18,10 @@ const YearPicker = (props) => {
18
18
  valueRef.current = initialValue !== null && initialValue !== void 0 ? initialValue : "";
19
19
  const [inputValue, setInputValue] = useState(valueRef.current);
20
20
  const handleChange = (e) => {
21
- const year = e.target.value;
21
+ const raw = e.target.value;
22
+ const year = raw !== "" ? parseInt(raw, 10) : "";
22
23
  if (year !== inputValue) {
23
- valueRef.current = year;
24
+ valueRef.current = String(year);
24
25
  setInputValue(valueRef.current);
25
26
  }
26
27
  if (immediate) {
@@ -0,0 +1,2 @@
1
+ declare const EditableMenu: (props: any) => import("react/jsx-runtime").JSX.Element;
2
+ export default EditableMenu;
@@ -0,0 +1,128 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Eye } from "lucide-react";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { DataBindingProvider } from "../../core/DataContext";
5
+ import DynamicIcon from "../../core/DynamicIcon";
6
+ import { usePageContext } from "../../core/PageContext";
7
+ import { usePageViewContext } from "../../core/PageViewContext";
8
+ import useDependHandler from "../../core/UIDependHandler";
9
+ import { replaceValues } from "../../lib/utils/BeanUtils";
10
+ import ViewPage from "../action/ViewPage";
11
+ import useUIMenu from "../common/UIMenu";
12
+ const EditableMenu = (props) => {
13
+ const { name, depends, title, menugroup, items: itemsProp, data, addPage, viewPage } = props !== null && props !== void 0 ? props : {};
14
+ const [activeItem, setActiveItem] = useState(null);
15
+ const [resolvedData, setResolvedData] = useState(data);
16
+ const [sortedItems, setSortedItems] = useState([]);
17
+ const pageContext = usePageContext();
18
+ const pageView = usePageViewContext();
19
+ const uuid = Math.random().toString(36).slice(2);
20
+ const dragHandler = useDragHandler({
21
+ items: sortedItems,
22
+ onSort: setSortedItems,
23
+ onDrop: async (draggedItem, updatedItems) => {
24
+ const reordered = updatedItems.map((item, idx) => ({
25
+ id: item.id,
26
+ sortorder: idx + 1, // ← 1-based sortorder
27
+ }));
28
+ console.log("resolvedData", JSON.stringify(resolvedData, null, 2));
29
+ console.log("reordered", JSON.stringify(reordered, null, 2));
30
+ // const status = await pageContext?.postMgmt("menus", "reorder", {
31
+ // type: resolvedData?.params?.type,
32
+ // groupid: resolvedData?.params?.groupid,
33
+ // items: reordered,
34
+ // });
35
+ // console.log("status", status);
36
+ },
37
+ });
38
+ const { items } = useUIMenu({ menugroup, items: itemsProp, data: resolvedData });
39
+ const handleItemClick = (item) => {
40
+ // console.log("Item clicked:", item);
41
+ pageContext === null || pageContext === void 0 ? void 0 : pageContext.set(name, item);
42
+ setActiveItem(item);
43
+ };
44
+ const handleAdd = () => {
45
+ console.log("Add clicked", addPage);
46
+ const { url, mode } = addPage !== null && addPage !== void 0 ? addPage : {};
47
+ const idx = ["window", "popup"].indexOf(String(mode).toLowerCase());
48
+ console.log(idx);
49
+ if (idx >= 0) {
50
+ // pageView.setPage(url, { mode });
51
+ }
52
+ else {
53
+ // pageView.setSelectedPage(url);
54
+ pageContext.set(name, url);
55
+ }
56
+ };
57
+ const handleView = (item) => {
58
+ const { url, mode } = viewPage !== null && viewPage !== void 0 ? viewPage : {};
59
+ console.log("View clicked", url, mode, item);
60
+ };
61
+ const handleDelete = (item) => console.log("Delete clicked", item);
62
+ useEffect(() => {
63
+ if (items.length === 0)
64
+ return;
65
+ const firstItem = items[0];
66
+ setActiveItem(firstItem);
67
+ setSortedItems(items);
68
+ pageContext === null || pageContext === void 0 ? void 0 : pageContext.set(name, items[0]);
69
+ }, [items]);
70
+ const resolveData = () => {
71
+ var _a;
72
+ if (!(data === null || data === void 0 ? void 0 : data.params))
73
+ return data;
74
+ const dependsValue = (_a = pageContext === null || pageContext === void 0 ? void 0 : pageContext.get(depends)) !== null && _a !== void 0 ? _a : {};
75
+ const resolvedParams = replaceValues(data.params, dependsValue);
76
+ return Object.assign(Object.assign({}, data), { params: resolvedParams });
77
+ };
78
+ const onRefresh = () => {
79
+ // when depends changes → re-resolve params
80
+ setResolvedData(resolveData());
81
+ // setActiveItem(null); // reset active item
82
+ };
83
+ useDependHandler({ name: depends, onRefresh });
84
+ return (_jsxs("div", { className: "p-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { className: "text-lg font-semibold", children: title }), _jsx(ViewPage, { url: addPage === null || addPage === void 0 ? void 0 : addPage.url, mode: addPage === null || addPage === void 0 ? void 0 : addPage.mode, iconOnly: true, icon: _jsx(DynamicIcon, { icon: "Plus", size: 18 }), title: title })] }), _jsx("div", { className: "border-b border-gray-200 my-2" }), _jsx("div", { className: "flex flex-col gap-2", children: sortedItems.map((item, index) => {
85
+ var _a;
86
+ const isActive = (activeItem === null || activeItem === void 0 ? void 0 : activeItem.id) === item.id; // ← use activeItem
87
+ return (_jsx(DataBindingProvider, { data: { data: item }, children: _jsxs("div", { draggable: true, onDragStart: () => dragHandler.handleDragStart(index), onDragOver: (e) => dragHandler.handleDragOver(e, index), onDrop: dragHandler.handleDrop, onDragEnd: dragHandler.handleDragEnd, className: `flex items-center justify-between border-b p-2 cursor-pointer transition-colors
88
+ ${isActive ? "bg-gray-200" : "bg-gray-50 border-gray-200 hover:bg-gray-200"}`, onClick: () => handleItemClick(item), children: [_jsx("span", { className: "text-gray-400 cursor-grab mr-2", children: _jsx(DynamicIcon, { icon: "GripVertical", size: 16 }) }), _jsx("span", { className: `text-base flex-1 ${isActive ? "font-medium" : ""}`, children: item.title }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ViewPage, { url: viewPage === null || viewPage === void 0 ? void 0 : viewPage.url, mode: viewPage === null || viewPage === void 0 ? void 0 : viewPage.mode, popupClassName: viewPage === null || viewPage === void 0 ? void 0 : viewPage.popupClassName, iconOnly: true, icon: _jsx(Eye, { size: 16, color: "blue" }) }), renderButton((_a = item.deleteIcon) !== null && _a !== void 0 ? _a : "Trash", () => handleDelete(item), "text-red-500")] })] }) }, `${uuid}-${index}`));
89
+ }) })] }));
90
+ };
91
+ export default EditableMenu;
92
+ /* ------------------------------------------------------------------ */
93
+ /* Render Button */
94
+ /* ------------------------------------------------------------------ */
95
+ const renderButton = (icon, onClick, color = "text-gray-500") => (_jsx("span", { className: `${color} cursor-pointer hover:opacity-70 transition-opacity`, onClick: (e) => {
96
+ e.stopPropagation();
97
+ onClick();
98
+ }, children: _jsx(DynamicIcon, { icon: icon, size: 18 }) }));
99
+ const useDragHandler = ({ items, onSort, onDrop }) => {
100
+ const dragIndexRef = useRef(null);
101
+ const dragOverIndexRef = useRef(null);
102
+ const handleDragStart = (index) => {
103
+ dragIndexRef.current = index;
104
+ };
105
+ const handleDragOver = (e, index) => {
106
+ e.preventDefault();
107
+ dragOverIndexRef.current = index;
108
+ };
109
+ const handleDrop = () => {
110
+ const from = dragIndexRef.current;
111
+ const to = dragOverIndexRef.current;
112
+ if (from == null || to == null || from === to)
113
+ return;
114
+ const draggedItem = items[from];
115
+ const updated = [...items];
116
+ const [moved] = updated.splice(from, 1);
117
+ updated.splice(to, 0, moved);
118
+ onSort(updated);
119
+ onDrop === null || onDrop === void 0 ? void 0 : onDrop(draggedItem, updated);
120
+ dragIndexRef.current = null;
121
+ dragOverIndexRef.current = null;
122
+ };
123
+ const handleDragEnd = () => {
124
+ dragIndexRef.current = null;
125
+ dragOverIndexRef.current = null;
126
+ };
127
+ return { handleDragStart, handleDragOver, handleDrop, handleDragEnd };
128
+ };
@@ -4,8 +4,8 @@ import { useEffect, useState } from "react";
4
4
  import { usePageContext } from "../../core/PageContext";
5
5
  import { usePageViewContext } from "../../core/PageViewContext";
6
6
  import useDependHandler from "../../core/UIDependHandler";
7
- import HPanel from "../../layouts/HPanel";
8
- import VPanel from "../../layouts/VPanel";
7
+ import { HPanel } from "../../layouts/HPanel";
8
+ import { VPanel } from "../../layouts/VPanel";
9
9
  const TabMenu = (props) => {
10
10
  var _a;
11
11
  const { name, depends, items = [], data, menugroup, orientation = "horizontal" } = props !== null && props !== void 0 ? props : {};
@@ -13,9 +13,26 @@ const buildItems = (raw) => {
13
13
  const mapped = [];
14
14
  for (const entry of raw) {
15
15
  if (Array.isArray(entry.items)) {
16
- mapped.push(Object.assign(Object.assign({}, entry), { isDropdown: (_a = entry.isDropdown) !== null && _a !== void 0 ? _a : true, items: buildItems(entry.items) }));
16
+ const visibleItems = entry.items.filter((it) => !it.hidden);
17
+ // category hidden + multiple items → skip entirely
18
+ if (entry.hidden && visibleItems.length !== 1)
19
+ continue;
20
+ // category hidden + only one visible item → show item flat without category header
21
+ if (entry.hidden && visibleItems.length === 1) {
22
+ const lastGroup = mapped[mapped.length - 1];
23
+ if (lastGroup && lastGroup.title === "" && !lastGroup.isDropdown) {
24
+ lastGroup.items.push(visibleItems[0]);
25
+ }
26
+ else {
27
+ mapped.push({ title: "", icon: undefined, isDropdown: false, items: [visibleItems[0]] });
28
+ }
29
+ continue;
30
+ }
31
+ mapped.push(Object.assign(Object.assign({}, entry), { isDropdown: (_a = entry.isDropdown) !== null && _a !== void 0 ? _a : true, items: buildItems(visibleItems) }));
17
32
  }
18
33
  else {
34
+ if (entry.hidden)
35
+ continue;
19
36
  const lastGroup = mapped[mapped.length - 1];
20
37
  if (lastGroup && lastGroup.title === "" && !lastGroup.isDropdown) {
21
38
  lastGroup.items.push(entry);
@@ -84,6 +101,7 @@ const TreeMenu = (props) => {
84
101
  const [openGroups, setOpenGroups] = useState([]);
85
102
  const [activeItem, setActiveItem] = useState({});
86
103
  const [items, setItems] = useState([]);
104
+ const [hashPage, setHashPage] = useState(null);
87
105
  const initializedRef = useRef(false);
88
106
  const { items: rawItems } = useUIMenu({
89
107
  items: itemsProp,
@@ -95,7 +113,9 @@ const TreeMenu = (props) => {
95
113
  const { path = "" } = (_a = pageView.getOriginalLocationInfo()) !== null && _a !== void 0 ? _a : {};
96
114
  const [, ...anchors] = path.split("#");
97
115
  const [selectedPath] = anchors;
98
- pageContext.set(contextKey, selectedPath);
116
+ if (selectedPath) {
117
+ pageContext.set(contextKey, selectedPath);
118
+ }
99
119
  }, []);
100
120
  /* ---------------------- Sync active item from page view ---------------------- */
101
121
  useLayoutEffect(() => {
@@ -115,23 +135,27 @@ const TreeMenu = (props) => {
115
135
  }, [rawItems]);
116
136
  /* ---------------------- Auto-select first item on init ---------------------- */
117
137
  useEffect(() => {
138
+ var _a;
118
139
  if (initializedRef.current || items.length === 0)
119
140
  return;
120
141
  const firstItem = findFirstItem(items);
121
142
  if (!(firstItem === null || firstItem === void 0 ? void 0 : firstItem.page))
122
143
  return;
123
144
  let selectedPage = pageContext.get(contextKey);
124
- // console.log("treemenu selected page from context", contextKey, selectedPage);
125
- if (selectedPage == null && contextKey === "selectedPage") {
126
- selectedPage = pageView.getSelectedPage();
127
- // console.log("treemenu selected page fallback from context", contextKey, selectedPage);
145
+ if (selectedPage == null) {
146
+ // fallback to window.location.hash
147
+ const hash = (_a = window.location.hash) !== null && _a !== void 0 ? _a : "";
148
+ const [, firstAnchor] = hash.split("#");
149
+ if (firstAnchor) {
150
+ selectedPage = firstAnchor; // strip query params
151
+ setHashPage(selectedPage);
152
+ }
128
153
  }
129
- // console.log("treemenu selected page final", contextKey, selectedPage);
130
154
  if (selectedPage != null) {
131
155
  const selectedItem = findItemByPage(items, selectedPage);
132
- // console.log("treemenu findItemByPage", selectedPage, selectedItem);
133
156
  if (selectedItem) {
134
- setOpenGroups(findGroupPath(items, selectedPage));
157
+ const groupPath = findGroupPath(items, selectedPage);
158
+ setOpenGroups(groupPath);
135
159
  setActiveItem(selectedItem);
136
160
  }
137
161
  else {
@@ -144,7 +168,7 @@ const TreeMenu = (props) => {
144
168
  setActiveItem(firstItem);
145
169
  }
146
170
  initializedRef.current = true;
147
- }, [items, contextKey]);
171
+ }, [items, contextKey, hashPage]);
148
172
  /* ---------------------- Sync page context on active item change ---------------------- */
149
173
  useEffect(() => {
150
174
  const { mode, page } = (activeItem !== null && activeItem !== void 0 ? activeItem : {});
@@ -155,7 +179,6 @@ const TreeMenu = (props) => {
155
179
  pageView.setPage(page, { mode });
156
180
  }
157
181
  else {
158
- pageView.setSelectedPage(page);
159
182
  pageContext.set(contextKey, page);
160
183
  }
161
184
  }, [activeItem]);
@@ -182,8 +205,7 @@ const TreeMenu = (props) => {
182
205
  };
183
206
  /* ---------------------- Events ---------------------- */
184
207
  const handleSubItemClick = (item) => {
185
- if (!item.page || item.page === activeItem.page)
186
- return;
208
+ // if (!item.page || item.page === activeItem.page) return;
187
209
  setActiveItem(item);
188
210
  };
189
211
  /* ---------------------- Recursive render ---------------------- */
@@ -50,7 +50,7 @@ export interface DataListAttr {
50
50
  searchDebounce?: number;
51
51
  onSearchChange?: (text: string) => void;
52
52
  filters?: FilterDefinition[] | FilterDefinition[][];
53
- showFilterPanel?: boolean;
53
+ filterPage?: Record<string, any>;
54
54
  onFilterChange?: (filters: Record<string, any>) => void;
55
55
  sortable?: boolean;
56
56
  defaultSort?: {