ui-soxo-bootstrap-core 2.6.23 → 2.6.25

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.
@@ -600,7 +600,7 @@ function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpda
600
600
 
601
601
  > */}
602
602
  {isVisible === true ? (
603
- <div className="form-item-element">
603
+ <div className={`form-item-element ${field.type === 'search' ? 'form-item-element-search' : ''}`}>
604
604
 
605
605
  {/* <Button
606
606
  size="small"
@@ -28,6 +28,11 @@
28
28
  }
29
29
  }
30
30
 
31
+ .form-item-element-search {
32
+ min-width: 170px;
33
+ flex: 0 1 200px;
34
+ }
35
+
31
36
  .submit-button {
32
37
  margin-top: 34px;
33
38
  }
@@ -5,6 +5,7 @@ import { CoreScripts } from "../../../../../models";
5
5
  import "./advance-search.scss";
6
6
 
7
7
  const { Search } = Input;
8
+ const advancedSearchCache = {};
8
9
 
9
10
  export default function AdvancedSearchSelect({ reportId, onReset, field, value, onChange, style, ...rest }) {
10
11
  // Normalize the configuration.
@@ -17,13 +18,31 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
17
18
  const isSearchQuery = !!config.search_query;
18
19
  const fieldName = isObjectMode ? field.field : field;
19
20
  const caption = config.caption;
21
+ const cacheKey = `${finalReportId || "no-report"}::${fieldName || "no-field"}`;
20
22
 
21
23
  const safeValue = Array.isArray(value) ? value : [];
24
+ const cachedState = advancedSearchCache[cacheKey] || { optionLabels: {}, displayOptions: [] };
22
25
 
23
26
  const [searchResults, setSearchResults] = useState([]);
24
- const [displayOptions, setDisplayOptions] = useState([]);
27
+ const [displayOptions, setDisplayOptions] = useState(cachedState.displayOptions);
28
+ const [optionLabels, setOptionLabels] = useState(cachedState.optionLabels);
25
29
  const [loading, setLoading] = useState(false);
26
30
  const debounceRef = useRef(null);
31
+ const missingLabelsFetchRef = useRef("");
32
+
33
+ const mergeDisplayOptions = (apiOptions = [], selectedValues = safeValue, labels = optionLabels) => {
34
+ const selectedOptions = selectedValues
35
+ .filter((item) => labels[item])
36
+ .map((item) => ({
37
+ search_value: item,
38
+ display_value: labels[item],
39
+ }));
40
+
41
+ return [...selectedOptions, ...apiOptions].filter(
42
+ (item, index, array) =>
43
+ array.findIndex((entry) => entry.search_value === item.search_value) === index
44
+ );
45
+ };
27
46
 
28
47
  const loadOptions = async (searchText = "") => {
29
48
  try {
@@ -37,14 +56,21 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
37
56
 
38
57
  const res = await CoreScripts.getQuerySeacch(formBody);
39
58
  const data = Array.isArray(res.data) ? res.data : [];
40
- const apiValues = data?.map((r) => r[fieldName]) || [];
41
-
42
- setSearchResults(apiValues);
43
- // Refresh display options only when new search results arrive to keep the list stable during interaction
44
- setDisplayOptions([
45
- ...safeValue,
46
- ...apiValues.filter((item) => !safeValue.includes(item)),
47
- ]);
59
+ const nextLabels = {
60
+ ...optionLabels,
61
+ ...data.reduce((acc, item) => {
62
+ acc[item.search_value] = item.display_value;
63
+ return acc;
64
+ }, {}),
65
+ };
66
+
67
+ setSearchResults(data.map((item) => item.search_value));
68
+ setOptionLabels(nextLabels);
69
+ setDisplayOptions(mergeDisplayOptions(data, safeValue, nextLabels));
70
+ advancedSearchCache[cacheKey] = {
71
+ optionLabels: nextLabels,
72
+ displayOptions: mergeDisplayOptions(data, safeValue, nextLabels),
73
+ };
48
74
  } catch (error) {
49
75
  console.error("Search API error", error);
50
76
  } finally {
@@ -53,16 +79,58 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
53
79
  };
54
80
 
55
81
  useEffect(() => {
56
- // Clear search results when the field or report context changes,
57
- // but do not load automatically on mount.
82
+ // Restore cached labels/options for the current field after remounts like submit refreshes.
58
83
  setSearchResults([]);
59
- }, [fieldName, finalReportId]);
84
+ setOptionLabels(cachedState.optionLabels || {});
85
+ setDisplayOptions(cachedState.displayOptions || []);
86
+ missingLabelsFetchRef.current = "";
87
+ }, [cacheKey]);
88
+
89
+ useEffect(() => {
90
+ // FormCreator can remount this field after submit with only search_value(s).
91
+ // If any selected value is missing a label, fetch the option list once to recover display_value.
92
+ const missingValues = safeValue.filter((item) => !optionLabels[item]);
93
+ const missingKey = missingValues.join("||");
94
+
95
+ if (!isSearchQuery || safeValue.length === 0 || missingValues.length === 0) {
96
+ missingLabelsFetchRef.current = "";
97
+ return;
98
+ }
99
+
100
+ if (missingLabelsFetchRef.current === missingKey) {
101
+ return;
102
+ }
103
+
104
+ missingLabelsFetchRef.current = missingKey;
105
+ if (isSearchQuery && safeValue.length > 0) {
106
+ loadOptions("");
107
+ }
108
+ }, [isSearchQuery, safeValue, optionLabels]);
109
+
110
+ useEffect(() => {
111
+ if (!isSearchQuery || safeValue.length === 0) return;
112
+
113
+ setDisplayOptions((prev) => {
114
+ const nextDisplayOptions = mergeDisplayOptions(prev, safeValue, optionLabels);
115
+ advancedSearchCache[cacheKey] = {
116
+ optionLabels,
117
+ displayOptions: nextDisplayOptions,
118
+ };
119
+ return nextDisplayOptions;
120
+ });
121
+ }, [cacheKey, isSearchQuery, safeValue, optionLabels]);
60
122
 
61
123
  const handleSearch = (text) => {
62
124
  if (debounceRef.current) clearTimeout(debounceRef.current);
63
125
  debounceRef.current = setTimeout(() => loadOptions(text), 400);
64
126
  };
65
127
 
128
+ useEffect(() => {
129
+ return () => {
130
+ if (debounceRef.current) clearTimeout(debounceRef.current);
131
+ };
132
+ }, []);
133
+
66
134
  const toggleValue = (checked, item) => {
67
135
  let newValues;
68
136
 
@@ -80,6 +148,18 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
80
148
  };
81
149
 
82
150
  const isActive = safeValue.length > 0;
151
+ const selectOptions = [
152
+ ...safeValue
153
+ .filter((item) => optionLabels[item])
154
+ .map((item) => ({
155
+ value: item,
156
+ label: optionLabels[item],
157
+ })),
158
+ ...displayOptions.map((item) => ({
159
+ value: item.search_value,
160
+ label: item.display_value,
161
+ })),
162
+ ].filter((item, index, array) => array.findIndex((entry) => entry.value === item.value) === index);
83
163
 
84
164
  // -------- INPUT MODE --------
85
165
  if (!isSearchQuery) {
@@ -110,18 +190,17 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
110
190
  className={`advanced-search-select ${isActive ? "advanced-search-active" : ""}`}
111
191
  style={style}
112
192
  mode="multiple"
193
+ showSearch={false}
113
194
  value={safeValue}
195
+ options={selectOptions}
114
196
  placeholder={caption}
115
197
  onDropdownVisibleChange={(open) => {
116
198
  if (open) {
117
199
  loadOptions("");
118
200
  } else {
119
201
  // When closing, re-sort selected items to the top so they are visible next time it opens
120
- const currentResults = searchResults.length > 0 ? searchResults : displayOptions;
121
- setDisplayOptions([
122
- ...safeValue,
123
- ...currentResults.filter((item) => !safeValue.includes(item)),
124
- ]);
202
+ const currentResults = displayOptions.length > 0 ? displayOptions : [];
203
+ setDisplayOptions(mergeDisplayOptions(currentResults, safeValue, optionLabels));
125
204
  }
126
205
  }}
127
206
  allowClear
@@ -137,6 +216,10 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
137
216
  <Search
138
217
  placeholder={`Search ${caption}`}
139
218
  onChange={(e) => handleSearch(e.target.value)}
219
+ onKeyDown={(e) => {
220
+ // Prevent the parent Select from treating backspace as "remove last selected tag".
221
+ e.stopPropagation();
222
+ }}
140
223
  />
141
224
 
142
225
  <div className="dropdown-options-list">
@@ -145,17 +228,17 @@ export default function AdvancedSearchSelect({ reportId, onReset, field, value,
145
228
  ) : (
146
229
  displayOptions.map((item, idx) => (
147
230
  <div
148
- key={`${item}-${idx}`}
231
+ key={`${item.search_value}-${idx}`}
149
232
  className="dropdown-option-item"
150
- onClick={() => toggleValue(!safeValue.includes(item), item)}
233
+ onClick={() => toggleValue(!safeValue.includes(item.search_value), item.search_value)}
151
234
  style={{ cursor: 'pointer' }}
152
235
  >
153
236
  <Checkbox
154
- checked={safeValue.includes(item)}
237
+ checked={safeValue.includes(item.search_value)}
155
238
  onClick={(e) => e.stopPropagation()} // Prevent double trigger when clicking the checkbox box specifically
156
- onChange={(e) => toggleValue(e.target.checked, item)}
239
+ onChange={(e) => toggleValue(e.target.checked, item.search_value)}
157
240
  >
158
- {item}
241
+ {item.display_value}
159
242
  </Checkbox>
160
243
  </div>
161
244
  ))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.23",
3
+ "version": "2.6.25",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"