robobyte-front-builder 1.0.16 → 1.0.19

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.
@@ -0,0 +1,245 @@
1
+ import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react';
2
+ import Autocomplete from '@mui/material/Autocomplete';
3
+ import TextField from '@mui/material/TextField';
4
+ import Chip from '@mui/material/Chip';
5
+ import {createRoot} from 'react-dom/client';
6
+ import {SettingsConsumer} from 'src/@core/context/settingsContext';
7
+ import ThemeComponent from 'src/@core/theme/ThemeComponent';
8
+ import moment from 'moment';
9
+ import Typography from "@mui/material/Typography";
10
+ import {darken, lighten, styled} from "@mui/material/styles";
11
+ import izColors from "src/configs/izColors.json"
12
+
13
+ const AutocompleteEditor = React.memo(
14
+ forwardRef((props, ref) => {
15
+ // Initialize as an array for multi-selection.
16
+ const [selectedValue, setSelectedValue] = useState(props.isSingle ? null : []);
17
+ // Initialize options from props.
18
+ const [options, setOptions] = useState(props.options || []);
19
+ const [searchTerm, setSearchTerm] = useState('');
20
+ const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
21
+ const [loading, setLoading] = useState(false);
22
+ const inputRef = useRef(null);
23
+
24
+ // If an asyncFetchOptions function is provided via cellEditorParams, call it immediately on mount.
25
+ useEffect(() => {
26
+ if (props.asyncFetchOptions) {
27
+ setLoading(true);
28
+ props.asyncFetchOptions()
29
+ .then(fetchedOptions => {
30
+ setOptions(fetchedOptions);
31
+ })
32
+ .finally(() => {
33
+ setLoading(false);
34
+ });
35
+ }
36
+ }, [props.asyncFetchOptions]);
37
+
38
+ // Debounce searchTerm changes.
39
+ useEffect(() => {
40
+ const handler = setTimeout(() => {
41
+ setDebouncedSearchTerm(searchTerm);
42
+ }, 300);
43
+ return () => clearTimeout(handler);
44
+ }, [searchTerm]);
45
+
46
+ // Optionally, if you want to refetch options on debounced search term changes,
47
+ // call the asyncFetchOptions function with the search term as argument.
48
+ useEffect(() => {
49
+ if (props.asyncFetchOptions) {
50
+ setLoading(true);
51
+ // If your asyncFetchOptions function accepts a search term, pass debouncedSearchTerm.
52
+ props.asyncFetchOptions(debouncedSearchTerm)
53
+ .then(fetchedOptions => {
54
+ setOptions(fetchedOptions);
55
+ })
56
+ .finally(() => {
57
+ setLoading(false);
58
+ });
59
+ }
60
+ }, [debouncedSearchTerm, props.asyncFetchOptions]);
61
+
62
+ const handleChange = (event, value) => {
63
+ setSelectedValue(value);
64
+ };
65
+
66
+ const handleBlur = () => {
67
+ props.api.stopEditing();
68
+ };
69
+
70
+ // Utility: Get nested property if needed.
71
+ function getNestedProperty(obj, path) {
72
+ if (!Array.isArray(path)) {
73
+ return path.split('.').reduce((acc, part) => acc && acc[part], obj);
74
+ } else {
75
+ let result;
76
+ let i = 0;
77
+ path.forEach((p) => {
78
+ if (i === 0) result = p.split('.').reduce((acc, part) => acc && acc[part], obj);
79
+ else result = result + ' - ' + p.split('.').reduce((acc, part) => acc && acc[part], obj);
80
+ i++;
81
+ });
82
+ return result;
83
+ }
84
+ }
85
+
86
+ const formatValueByType = (value, type) => {
87
+ let result = value;
88
+ if (type === 'datetime') result = moment(result).format('L');
89
+ return result;
90
+ };
91
+
92
+ // Update selected value when props.value changes.
93
+ useEffect(() => {
94
+ if (props.value) {
95
+ setSelectedValue(Array.isArray(props.value) ? props.value : []);
96
+ }
97
+ }, [props.value]);
98
+
99
+
100
+ useImperativeHandle(ref, () => {
101
+ try {
102
+
103
+ return ({
104
+ getValue: () => props?.targetValue != null ? getNestedProperty(selectedValue, props?.targetValue) : selectedValue,
105
+ })
106
+ } catch (e) {
107
+ console.log(e);
108
+ }
109
+
110
+ });
111
+
112
+ useEffect(() => {
113
+ if (inputRef.current) {
114
+ inputRef.current.focus();
115
+ }
116
+ }, []);
117
+
118
+ const GroupHeader = styled('div')(({theme}) => ({
119
+ position: 'sticky',
120
+ top: '-8px',
121
+ padding: '4px 10px',
122
+ color: theme.palette.primary.main,
123
+ backgroundColor: lighten(izColors.grey, 0.70),
124
+ }));
125
+
126
+ const GroupItems = styled('ul')({
127
+ padding: 0,
128
+ });
129
+
130
+ return (
131
+ <SettingsConsumer>
132
+ {({settings}) => (
133
+ <ThemeComponent settings={settings}>
134
+ <Autocomplete
135
+ multiple={!props.isSingle === true}
136
+ size="small"
137
+ loading={loading}
138
+ options={options}
139
+ groupBy={option => {
140
+ return props.groupByField == null ? null : getNestedProperty(option, props.groupByField);
141
+ }}
142
+ renderGroup={(params) => (
143
+ <li key={params.key}>
144
+ {props.groupByField && <GroupHeader>{params.group}</GroupHeader>}
145
+ <GroupItems>{params.children}</GroupItems>
146
+ </li>
147
+ )}
148
+ onBlur={handleBlur}
149
+ disableCloseOnSelect={!props.isSingle === true}
150
+ isOptionEqualToValue={(option, value) => option?.id === value?.id}
151
+ getOptionLabel={(option) => {
152
+ let result;
153
+
154
+ if (Array.isArray(props.fieldName)) {
155
+ result = props.fieldName
156
+ .map((field) => getNestedProperty(option, field))
157
+ .filter(Boolean)
158
+ .join(" - ");
159
+ } else {
160
+ result = props.fieldName == null
161
+ ? option?.name
162
+ : getNestedProperty(option, props.fieldName);
163
+ }
164
+
165
+ if (props.type != null) {
166
+ result = formatValueByType(result, props.type);
167
+ }
168
+
169
+ return result;
170
+ }}
171
+ value={selectedValue}
172
+ onChange={handleChange}
173
+ renderInput={(params) => {
174
+ params.InputProps.disableUnderline = true;
175
+ return (
176
+ <TextField
177
+ autoFocus
178
+ {...params}
179
+ variant="standard"
180
+ size="small"
181
+ hiddenLabel
182
+ onChange={(e) => setSearchTerm(e.target.value)}
183
+ inputRef={inputRef}
184
+ />
185
+ );
186
+ }}
187
+ renderTags={(value, getTagProps) =>
188
+ value.map((option, index) => (
189
+ <Chip
190
+ key={option?.id}
191
+ label={option?.name}
192
+ {...getTagProps({index})}
193
+ onMouseDown={(e) => {
194
+ e.stopPropagation();
195
+ e.preventDefault();
196
+ }}
197
+ />
198
+ ))
199
+ }
200
+ />
201
+ </ThemeComponent>
202
+ )}
203
+ </SettingsConsumer>
204
+ );
205
+ })
206
+ );
207
+
208
+ export class MultiSelectEditorWrapper {
209
+ constructor() {
210
+ this.eInput = document.createElement('div');
211
+ this.eInput.style = 'width: 100%; height: 100%;';
212
+ }
213
+
214
+ init(params) {
215
+ this.params = params;
216
+ this.root = createRoot(this.eInput);
217
+ this.root.render(
218
+ <AutocompleteEditor {...params} ref={(input) => (this.reactComponent = input)}/>
219
+ );
220
+ }
221
+
222
+ getGui() {
223
+ return this.eInput;
224
+ }
225
+
226
+ afterGuiAttached() {
227
+ // Optionally focus after attachment.
228
+ }
229
+
230
+ getValue() {
231
+ return this.reactComponent.getValue();
232
+ }
233
+
234
+ destroy() {
235
+ if (this.root) {
236
+ setTimeout(() => {
237
+ this.root.unmount();
238
+ }, 0);
239
+ }
240
+ }
241
+
242
+ isPopup() {
243
+ return false;
244
+ }
245
+ }
@@ -0,0 +1,83 @@
1
+
2
+ const STORAGE_KEY_PREFIX = 'RV_SESSION_';
3
+ const EXPIRATION_DAYS = 7;
4
+
5
+ export const generateSessionId = () => {
6
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
7
+ let result = '';
8
+ for (let i = 0; i < 6; i++) {
9
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
10
+ }
11
+ return result;
12
+ };
13
+
14
+ export const saveReportSession = (payload) => {
15
+ if (typeof window === 'undefined') return null;
16
+
17
+ const sessionId = generateSessionId();
18
+ const data = {
19
+ payload,
20
+ timestamp: Date.now()
21
+ };
22
+
23
+ try {
24
+ localStorage.setItem(`${STORAGE_KEY_PREFIX}${sessionId}`, JSON.stringify(data));
25
+ return sessionId;
26
+ } catch (e) {
27
+ console.error('Failed to save report session to localStorage', e);
28
+ return null;
29
+ }
30
+ };
31
+
32
+ export const getReportSession = (sessionId) => {
33
+ if (typeof window === 'undefined' || !sessionId) return null;
34
+
35
+ try {
36
+ const stored = localStorage.getItem(`${STORAGE_KEY_PREFIX}${sessionId}`);
37
+ if (!stored) return null;
38
+
39
+ const { payload, timestamp } = JSON.parse(stored);
40
+
41
+ // Check if expired
42
+ const now = Date.now();
43
+ const sevenDaysInMs = EXPIRATION_DAYS * 24 * 60 * 60 * 1000;
44
+ if (now - timestamp > sevenDaysInMs) {
45
+ localStorage.removeItem(`${STORAGE_KEY_PREFIX}${sessionId}`);
46
+ return null;
47
+ }
48
+
49
+ return payload;
50
+ } catch (e) {
51
+ console.error('Failed to get report session from localStorage', e);
52
+ return null;
53
+ }
54
+ };
55
+
56
+ export const cleanupOldSessions = () => {
57
+ if (typeof window === 'undefined') return;
58
+
59
+ try {
60
+ const now = Date.now();
61
+ const sevenDaysInMs = EXPIRATION_DAYS * 24 * 60 * 60 * 1000;
62
+
63
+ for (let i = 0; i < localStorage.length; i++) {
64
+ const key = localStorage.key(i);
65
+ if (key && key.startsWith(STORAGE_KEY_PREFIX)) {
66
+ try {
67
+ const stored = localStorage.getItem(key);
68
+ if (stored) {
69
+ const { timestamp } = JSON.parse(stored);
70
+ if (now - timestamp > sevenDaysInMs) {
71
+ localStorage.removeItem(key);
72
+ }
73
+ }
74
+ } catch (e) {
75
+ // If parsing fails, it's probably corrupt, might as well remove it
76
+ localStorage.removeItem(key);
77
+ }
78
+ }
79
+ }
80
+ } catch (e) {
81
+ console.error('Failed to cleanup old report sessions', e);
82
+ }
83
+ };