@solidstarters/solid-core-ui 1.1.61 → 1.1.62

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 (35) hide show
  1. package/dist/components/core/common/SolidGlobalSearchElement.d.ts +1 -0
  2. package/dist/components/core/common/SolidGlobalSearchElement.d.ts.map +1 -1
  3. package/dist/components/core/common/SolidGlobalSearchElement.js +292 -56
  4. package/dist/components/core/common/SolidGlobalSearchElement.js.map +1 -1
  5. package/dist/components/core/common/SolidSaveCustomFilterForm.d.ts +9 -0
  6. package/dist/components/core/common/SolidSaveCustomFilterForm.d.ts.map +1 -0
  7. package/dist/components/core/common/SolidSaveCustomFilterForm.js +37 -0
  8. package/dist/components/core/common/SolidSaveCustomFilterForm.js.map +1 -0
  9. package/dist/components/core/filter/fields/SolidBooleanField.js +1 -1
  10. package/dist/components/core/filter/fields/SolidBooleanField.js.map +1 -1
  11. package/dist/components/core/kanban/SolidKanbanView.d.ts.map +1 -1
  12. package/dist/components/core/kanban/SolidKanbanView.js +130 -98
  13. package/dist/components/core/kanban/SolidKanbanView.js.map +1 -1
  14. package/dist/components/core/model/CreateModel.js +3 -3
  15. package/dist/components/core/model/CreateModel.js.map +1 -1
  16. package/dist/components/core/model/FieldMetaDataForm.d.ts.map +1 -1
  17. package/dist/components/core/model/FieldMetaDataForm.js.map +1 -1
  18. package/dist/components/core/model/ModelMetaData.d.ts.map +1 -1
  19. package/dist/components/core/model/ModelMetaData.js +68 -15
  20. package/dist/components/core/model/ModelMetaData.js.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/resources/globals.css +8 -0
  26. package/package.json +1 -1
  27. package/src/components/core/common/SolidGlobalSearchElement.tsx +425 -123
  28. package/src/components/core/common/SolidSaveCustomFilterForm.tsx +74 -0
  29. package/src/components/core/filter/fields/SolidBooleanField.tsx +1 -1
  30. package/src/components/core/kanban/SolidKanbanView.tsx +175 -157
  31. package/src/components/core/model/CreateModel.tsx +2 -2
  32. package/src/components/core/model/FieldMetaDataForm.tsx +0 -2
  33. package/src/components/core/model/ModelMetaData.tsx +204 -121
  34. package/src/index.ts +1 -0
  35. package/src/resources/globals.css +8 -0
@@ -6,9 +6,13 @@ import FilterComponent, { FilterOperator, FilterRule, FilterRuleType } from "@/c
6
6
  import { Button } from "primereact/button";
7
7
  import { OverlayPanel } from "primereact/overlaypanel";
8
8
  import { Divider } from "primereact/divider";
9
- import { useSearchParams } from "next/navigation";
9
+ import { useRouter, useSearchParams } from "next/navigation";
10
10
  import { queryStringToQueryObject } from "../list/SolidListView";
11
11
  import { InputText } from "primereact/inputtext";
12
+ import { createSolidEntityApi } from "@/redux/api/solidEntityApi";
13
+ import qs from "qs";
14
+ import { useSelector } from "react-redux";
15
+ import { SolidSaveCustomFilterForm } from "./SolidSaveCustomFilterForm";
12
16
 
13
17
  const getRandomInt = (min: number, max: number) => {
14
18
  return Math.floor(Math.random() * (max - min + 1)) + min;
@@ -17,7 +21,6 @@ const getRandomInt = (min: number, max: number) => {
17
21
 
18
22
  const transformFiltersToRules = (filter: any, parentRule: number | null = null): FilterRule => {
19
23
  const currentId = idCounter++;
20
-
21
24
  if (filter["$or"]) {
22
25
  return {
23
26
  id: currentId,
@@ -173,6 +176,50 @@ const tranformSearchToFilters = (input: any) => {
173
176
  };
174
177
  }
175
178
 
179
+ export const mergeSearchAndCustomFilters = (transformedFilter: any, newFilter: any, transformedFilterName: string, newFilterName: string) => {
180
+ const filters: any = {};
181
+
182
+ // Add only non-null filters
183
+ if (transformedFilter && Object.keys(transformedFilter).length > 0) {
184
+ filters[transformedFilterName] = transformedFilter;
185
+ }
186
+ if (newFilter && Object.keys(newFilter).length > 0) {
187
+ filters[newFilterName] = newFilter;
188
+ }
189
+
190
+ // Return the combined filters object
191
+ return filters;
192
+ }
193
+
194
+
195
+ const SavedFilterList = ({ savedfilter, activeSavedFilter, applySavedFilter, openSavedCustomFilter, setSavedFilterTobeDeleted, setIsDeleteSQDialogVisible }: any) => {
196
+ return (
197
+ <div className="flex align-items-center justify-content-between gap-2">
198
+ <Button text size="small" className="text-base py-1 w-full" severity={Number(activeSavedFilter) == savedfilter.id ? "secondary" : "contrast" } onClick={() => applySavedFilter(savedfilter)}>{savedfilter.name}</Button>
199
+ <div className="flex align-items-center gap-2">
200
+ <Button
201
+ icon="pi pi-pencil"
202
+ style={{ fontSize: 10 }}
203
+ severity="secondary"
204
+ outlined size="small"
205
+ onClick={() => openSavedCustomFilter(savedfilter)}
206
+ />
207
+ <Button
208
+ icon="pi pi-trash"
209
+ style={{ fontSize: 10 }}
210
+ severity="secondary"
211
+ outlined size="small"
212
+ onClick={() => {
213
+ setSavedFilterTobeDeleted(savedfilter.id),
214
+ setIsDeleteSQDialogVisible(true);
215
+
216
+ }}
217
+ />
218
+ </div>
219
+ </div>
220
+ )
221
+ }
222
+
176
223
  export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCustomFilter, filters, clearFilter }: any, ref) => {
177
224
  const defaultState: FilterRule[] = [
178
225
  {
@@ -204,8 +251,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
204
251
  ];
205
252
  const [initialState, setInitialState] = useState(defaultState);
206
253
 
207
- const searchParams = useSearchParams().toString(); // Converts the query params to a string
254
+ const searchParams = useSearchParams() // Converts the query params to a string
255
+ const activeSavedFilter = searchParams?.get("savedQuery");
208
256
 
257
+ const router = useRouter();
209
258
 
210
259
  const chipsRef = useRef<HTMLDivElement | null | any>(null);
211
260
 
@@ -219,6 +268,85 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
219
268
  const [searchFilter, setSearchFilter] = useState<any | null>(null);
220
269
  const [customFilter, setCustomFilter] = useState<any | null>(null);
221
270
  const [hasSearched, setHasSearched] = useState<boolean>(false);
271
+ const [showSaveFilterPopup, setShowSaveFilterPopup] = useState<boolean>(false);
272
+ const [currentSavedFilterData, setCurrentSavedFilterData] = useState<any>();
273
+ const [savedFilterTobeDeleted, setSavedFilterTobeDeleted] = useState<any>();
274
+ const [isDeleteSQDialogVisible, setIsDeleteSQDialogVisible] = useState<boolean>(false);
275
+ const [savedFilterQueryString, setSavedFilterQueryString] = useState<string>();
276
+ const { user } = useSelector((state: any) => state.auth);
277
+
278
+
279
+ const [savedFilters, setSavedFilters] = useState([]);
280
+
281
+ const entityApi = createSolidEntityApi("savedFilters");
282
+ const {
283
+ useCreateSolidEntityMutation,
284
+ useDeleteSolidEntityMutation,
285
+ useGetSolidEntityByIdQuery,
286
+ useUpdateSolidEntityMutation,
287
+ useLazyGetSolidEntitiesQuery
288
+ } = entityApi;
289
+
290
+ const [
291
+ createEntity,
292
+ { isSuccess: isEntityCreateSuccess, isError: isEntityCreateError, error: entityCreateError },
293
+ ] = useCreateSolidEntityMutation();
294
+
295
+ const [
296
+ updateEntity,
297
+ { isSuccess: isEntityUpdateSuceess, isError: isEntityUpdateError, error: entityUpdateError },
298
+ ] = useUpdateSolidEntityMutation();
299
+
300
+ const [
301
+ deleteEntity,
302
+ { isSuccess: isEntityDeleteSuceess, isError: isEntityDeleteError, error: entityDeleteError },
303
+ ] = useDeleteSolidEntityMutation();
304
+
305
+ const [triggerGetSolidEntities, { data: solidEntityListViewData, isLoading, error }] = useLazyGetSolidEntitiesQuery();
306
+
307
+
308
+ useEffect(() => {
309
+
310
+ const filters = {
311
+ $or:[
312
+ {
313
+ $and: [
314
+ { model: { $in: [viewData?.data?.solidView?.model?.id] } },
315
+ { view: { $in: [viewData?.solidView?.id] } },
316
+ { user: { $in: [user?.user?.id] } }
317
+ ]
318
+ },
319
+ {
320
+ $and: [
321
+ { model: { $in: [viewData?.data?.solidView?.model?.id] } },
322
+ { view: { $in: [viewData?.solidView?.id] } },
323
+ { isPrivate: { $eq: true } }
324
+ ]
325
+ }
326
+
327
+ ]
328
+
329
+ }
330
+
331
+ const queryData: any = {
332
+ offset: 0,
333
+ limit: 10,
334
+ filters: filters,
335
+ populate: ["model", "view", "user"],
336
+ sort: ["id:desc"],
337
+ };
338
+ const queryString = qs.stringify(queryData, { encodeValuesOnly: true });
339
+ setSavedFilterQueryString(queryString)
340
+ triggerGetSolidEntities(queryString);
341
+
342
+ }, [searchParams])
343
+
344
+ useEffect(() => {
345
+ if (solidEntityListViewData) {
346
+ setSavedFilters(solidEntityListViewData.records)
347
+ }
348
+ }, [solidEntityListViewData])
349
+
222
350
  useImperativeHandle(ref, () => ({
223
351
  clearFilter: () => {
224
352
  setFilterRules(initialState);
@@ -226,43 +354,112 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
226
354
  }));
227
355
 
228
356
  useEffect(() => {
229
- const queryObject = queryStringToQueryObject();
230
- if (queryObject) {
231
- const searchChips: any = queryObject?.s_filter || null;
232
- const customChips = queryObject?.c_filter || null;
233
- if (searchChips) {
234
- const formattedChips = searchChips?.$and.map((chip: any, key: any) => {
235
- const chipKey = Object.keys(chip)[0]; // Get the key, e.g., "displayName"
236
- const chipValue = chip[chipKey]?.$containsi; // Get the value of "$containsi"
237
- const chipdata = {
238
- columnName: chipKey,
239
- value: chipValue
240
- };
241
- return chipdata
357
+ let searchChips: any;
358
+ let customChips: any;
359
+ let parsedSearchParams = searchParams;
360
+
361
+
362
+ const savedQuery = parsedSearchParams?.get("savedQuery");
363
+ if (savedQuery && savedFilters.length > 0) {
364
+ const currentSavedFilterId = Number(savedQuery);
365
+ const currentSavedFilterData: any = savedFilters.find((savedFilter: any) => savedFilter.id === currentSavedFilterId);
366
+ setCurrentSavedFilterData(currentSavedFilterData)
367
+ if (currentSavedFilterData) {
368
+ const filterJson = JSON.parse(currentSavedFilterData?.filterQueryJson);
369
+ if (filterJson) {
370
+ searchChips = filterJson?.s_filter || null;
371
+ customChips = filterJson?.c_filter || null;
242
372
  }
243
- );
244
- setSearchChips(formattedChips);
245
373
  }
246
- if (customChips) {
247
- setCustomFilter(customChips);
248
- const formatedCustomChips: FilterRule = transformFiltersToRules(customChips);
249
- console.log("formatedCustomChips", formatedCustomChips);
250
- setFilterRules([formatedCustomChips]);
374
+ } else {
375
+ const queryObject = queryStringToQueryObject();
376
+ if (queryObject) {
377
+ searchChips = queryObject?.s_filter || null;
378
+ customChips = queryObject?.c_filter || null;
379
+ }
380
+ }
381
+ if (searchChips) {
382
+ const formattedChips = searchChips?.$and.map((chip: any, key: any) => {
383
+ const chipKey = Object.keys(chip)[0]; // Get the key, e.g., "displayName"
384
+ const chipValue = chip[chipKey]?.$containsi; // Get the value of "$containsi"
385
+ const chipdata = {
386
+ columnName: chipKey,
387
+ value: chipValue
388
+ };
389
+ return chipdata
390
+ }
391
+ );
392
+ setSearchChips(formattedChips);
393
+ setSearchFilter(searchChips);
394
+ setHasSearched(true);
395
+ }
396
+ if (customChips) {
397
+ setCustomFilter(customChips);
398
+ const formatedCustomChips: FilterRule = transformFiltersToRules(customChips);
399
+ setFilterRules([formatedCustomChips]);
400
+ setHasSearched(true);
401
+ }
402
+ }, [searchParams, savedFilters])
403
+
404
+ function findSearchableField(node: any, targetName: string): boolean {
405
+ if (
406
+ node?.type === 'field' &&
407
+ node?.attrs?.name === targetName &&
408
+ node?.attrs?.isSearchable
409
+ ) {
410
+ return true;
411
+ }
412
+
413
+ if (Array.isArray(node?.children)) {
414
+ return node.children.some((child: any) => findSearchableField(child, targetName));
415
+ }
416
+
417
+ return false;
418
+ }
419
+
420
+
421
+ function collectLeafFieldTypes(layoutArray: any) {
422
+ const result: any = [];
423
+
424
+ function recurse(node: any) {
425
+ if (!node || typeof node !== 'object') return;
426
+
427
+ // If it's a field and has no children (leaf field node)
428
+ if (node.type === 'field' && !node.children) {
429
+ result.push(node);
430
+ }
251
431
 
432
+ // If it has children, recurse into them
433
+ if (Array.isArray(node.children)) {
434
+ node.children.forEach(recurse);
252
435
  }
253
436
  }
254
- }, [searchParams])
437
+
438
+ // Input is an array of nodes
439
+ layoutArray.forEach(recurse);
440
+
441
+ return result;
442
+ }
443
+
255
444
 
256
445
 
257
446
  useEffect(() => {
258
447
  if (viewData?.data?.solidFieldsMetadata) {
259
- if (searchParams && (searchParams.includes("list") || searchParams.includes("kanban"))) {
260
- }
261
- const fieldsData = viewData?.data?.solidFieldsMetadata;
448
+ let fieldsData = viewData?.data?.solidFieldsMetadata;
262
449
  const fieldsList = Object.entries(fieldsData).map(([key, value]: any) => ({ name: value.displayName, value: key, type: value.type }));
263
450
  setFields(fieldsList);
264
451
  const searchableFieldsList = fieldsList.filter((field: any) => field.type === "longText" || field.type === "shortText");
265
- const finalsearchableFieldsList = searchableFieldsList.filter((field: any) => field.value && viewData?.data?.solidView?.layout?.children?.some((child: any) => child?.attrs?.name === field.value && child?.attrs?.isSearchable)).map((field: any) => field.value);
452
+ let finalsearchableFieldsList: any = [];
453
+ if (typeof window !== "undefined" && window.location.href.includes("list")) {
454
+ finalsearchableFieldsList = searchableFieldsList.filter((field: any) => field.value && viewData?.data?.solidView?.layout?.children?.some((child: any) => child?.attrs?.name === field.value && child?.attrs?.isSearchable)).map((field: any) => field.value);
455
+ }
456
+
457
+ if (typeof window !== "undefined" && window.location.href.includes("kanban")) {
458
+ const result = collectLeafFieldTypes(viewData?.data?.solidView?.layout?.children);
459
+ finalsearchableFieldsList = searchableFieldsList.filter((field: any) => field.value && result?.some((child: any) => child?.attrs?.name === field.value && child?.attrs?.isSearchable)).map((field: any) => field.value);
460
+
461
+ }
462
+
266
463
  setSearchableFields(finalsearchableFieldsList);
267
464
  }
268
465
  }, [])
@@ -278,15 +475,15 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
278
475
  }
279
476
  }, []);
280
477
 
281
- useEffect(() => {
282
- // Get the last valid filter
283
- const validFilters = filters?.$or?.filter((filter: any) => filter !== undefined) || [];
284
- if (validFilters.length > 0) {
285
- setCustomChip(validFilters.length.toString()); // Store only the number
286
- } else {
287
- setCustomChip(""); // Reset when no filters are present
288
- }
289
- }, [filters]);
478
+ // useEffect(() => {
479
+ // // Get the last valid filter
480
+ // const validFilters = filters?.$or?.filter((filter: any) => filter !== undefined) || [];
481
+ // if (validFilters.length > 0) {
482
+ // setCustomChip(validFilters.length.toString()); // Store only the number
483
+ // } else {
484
+ // setCustomChip(""); // Reset when no filters are present
485
+ // }
486
+ // }, [filters]);
290
487
 
291
488
  const firstFilterableFieldName = searchableFields[0]; // First searchable field
292
489
 
@@ -374,6 +571,67 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
374
571
  }
375
572
  }, [searchChips]);
376
573
 
574
+
575
+ // Saved Filter related
576
+
577
+ const applySavedFilter = (savedfilter: any) => {
578
+ // push the savedQuery=1 in url
579
+ if (savedfilter?.id) {
580
+ router.push(`?savedQuery=${savedfilter.id}`);
581
+ } else {
582
+ console.error("Saved filter ID is undefined or null.");
583
+ }
584
+
585
+ }
586
+ const openSavedCustomFilter = (savedfilter: any) => {
587
+ //Open custom filter popup
588
+ router.push(`?savedQuery=${savedfilter.id}`);
589
+ setShowGlobalSearchElement(true);
590
+ // dont refetch the data yet
591
+ const customFilter = JSON.parse(savedfilter.filterQueryJson).c_filter;
592
+ setCustomFilter(customFilter ? customFilter : null);
593
+ if (customFilter) {
594
+ const formatedCustomChips: FilterRule = transformFiltersToRules(customFilter ? customFilter : null);
595
+ setFilterRules(formatedCustomChips ? [formatedCustomChips] : initialState);
596
+ }
597
+ }
598
+ const deleteSavedFilter = () => {
599
+ // delte the saved filter with id
600
+ deleteEntity(savedFilterTobeDeleted);
601
+ triggerGetSolidEntities(savedFilterQueryString);
602
+ setIsDeleteSQDialogVisible(false);
603
+ let parsedSearchParams = searchParams;
604
+ const savedQuery = parsedSearchParams?.get("savedQuery");
605
+ if (savedFilterTobeDeleted === savedQuery) {
606
+ const urlParams = new URLSearchParams(window.location.search);
607
+ urlParams.delete("savedQuery");
608
+ router.push(`?${urlParams.toString()}`);
609
+ }
610
+ }
611
+
612
+ const handleSaveFilter = async (formValues: any) => {
613
+ const filterJson = mergeSearchAndCustomFilters(customFilter, searchFilter, "c_filter", "s_filter");
614
+ setShowSaveFilterPopup(false)
615
+
616
+ const formData = new FormData();
617
+ formData.append("name", formValues.name);
618
+ formData.append("filterQueryJson", JSON.stringify(filterJson));
619
+ formData.append("modelId", viewData?.data?.solidView?.model?.id);
620
+ formData.append("viewId", viewData?.data?.solidView?.id);
621
+ formData.append("isPrivate", formValues.isPrivate);
622
+ formData.append("userId", user?.user?.id);
623
+ if (formValues.id) {
624
+ await updateEntity({ id: +formValues.id, data: formData }).unwrap();
625
+ router.push(`?savedQuery=${formValues.id}`);
626
+
627
+ } else {
628
+ const result = await createEntity(formData).unwrap();
629
+ router.push(`?savedQuery=${result.data.id}`);
630
+
631
+ }
632
+ }
633
+
634
+
377
635
  const groupedSearchChips = searchChips.reduce((acc, chip) => {
378
636
  const key = chip.columnName || firstFilterableFieldName;
379
637
  if (!acc[key]) {
@@ -400,7 +658,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
400
658
  <path d="M8.66667 15V13.3333H11.3333V15H8.66667ZM6 10.8333V9.16667H14V10.8333H6ZM4 6.66667V5H16V6.66667H4Z"
401
659
  fill="white" />
402
660
  </svg>
403
- <span><strong>{customFilter.$or.length > 0 ? `${customFilter.$or.length}` : customFilter.$and.length}</strong> rules applied</span>
661
+ <span><strong>{customFilter?.$or && customFilter?.$or?.length > 0 ? `${customFilter?.$or?.length}` : customFilter.$and.length}</strong> rules applied</span>
404
662
  </div>
405
663
 
406
664
  {/* button to clear filter */}
@@ -433,102 +691,146 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, handleApplyCusto
433
691
  <i className="pi pi-times ml-1"
434
692
  style={{ cursor: "pointer" }}
435
693
  onClick={() => handleRemoveChipGroup(column)}
436
- > </i>
694
+ >
695
+ </i>
437
696
  </div>
438
697
  </li>
439
698
  ))}
440
699
  </>
441
700
  );
442
701
 
443
- console.log("custom chip", customFilter);
444
702
 
445
703
  const [showOverlay, setShowOverlay] = useState(false);
446
704
 
447
-
448
705
  return (
449
- <div className="flex justify-content-center solid-custom-filter-wrapper relative">
450
- <div className="solid-global-search-element">
451
- <ul className="">
452
- {customFilter && <CustomChip />}
453
- <SearchChip />
454
- <li ref={chipsRef}>
455
- <div className="relative">
456
- <InputText
457
- value={inputValue || ""}
458
- placeholder="Search..."
459
- onChange={(e) => {
460
- setInputValue(e.target.value);
461
- setShowOverlay(true);
462
- }}
463
- onFocus={() => {
464
- if (inputValue?.trim()) setShowOverlay(true);
465
- }}
466
- onBlur={() => {
467
- // Delay so you can click buttons inside overlay
468
- setTimeout(() => setShowOverlay(false), 150);
469
- }}
470
-
471
- onKeyDown={handleKeyDown}
472
- />
473
- <Button
474
- icon="pi pi-search"
475
- style={{ fontSize: 10 }}
476
- severity="secondary"
477
- outlined size="small"
478
- onClick={() => setShowGlobalSearchElement(true)}
479
- className="custom-filter-button"
480
- />
481
- </div>
482
- </li>
483
- </ul>
484
- </div>
485
- {showOverlay && inputValue?.trim() && (
486
- <div className="absolute w-full z-5 bg-white border-round border-1 border-300 shadow-2" style={{ top: 35 }}>
487
- {inputValue ? (
488
- <>
489
- <div className="custom-filter-search-options px-2 py-2 flex flex-column">
490
- {
491
- searchableFields.map((value: any, index: any) => (
492
- <Button
493
- key={index}
494
- className="p-2 flex gap-1 text-color"
495
- onClick={() => handleAddChip(value)}
496
- text
497
- severity="secondary"
498
- size="small"
499
- >
500
- Search <strong>{value}</strong> for :
501
- <span className="font-bold" style={{ color: '#000' }}>{inputValue}</span>
502
- </Button>
503
- ))
504
- }
706
+ <>
707
+ <div className="flex justify-content-center solid-custom-filter-wrapper relative">
708
+ <div className="solid-global-search-element">
709
+ <ul className="">
710
+ {customFilter && <CustomChip />}
711
+ <SearchChip />
712
+ <li ref={chipsRef}>
713
+ <div className="relative">
714
+ <InputText
715
+ value={inputValue || ""}
716
+ placeholder="Search..."
717
+ onChange={(e) => {
718
+ setInputValue(e.target.value);
719
+ setShowOverlay(true);
720
+ }}
721
+ onFocus={() => {
722
+ if (inputValue?.trim()) setShowOverlay(true);
723
+ }}
724
+ onBlur={() => {
725
+ // Delay so you can click buttons inside overlay
726
+ setTimeout(() => setShowOverlay(false), 150);
727
+ }}
728
+
729
+ onKeyDown={handleKeyDown}
730
+ />
731
+ <Button
732
+ icon="pi pi-search"
733
+ style={{ fontSize: 10 }}
734
+ severity="secondary"
735
+ outlined size="small"
736
+ onClick={() => setShowOverlay(true)}
737
+ className="custom-filter-button"
738
+ />
505
739
  </div>
506
- <Divider className="m-0" />
507
- </>
508
- ) :
509
- <>
510
- <div className="p-3 text-base">Search Here...</div>
511
- <Divider className="m-0" />
512
- </>
513
- }
514
- <div className="px-2 py-1">
515
- <Button text size="small" label="Custom Filter" iconPos="left" icon='pi pi-plus' onClick={() => setShowGlobalSearchElement(true)} className="font-bold" />
516
- </div>
740
+ </li>
741
+ </ul>
517
742
  </div>
518
- )
519
- }
520
- <Dialog header={false} className="solid-global-search-filter" showHeader={false} visible={showGlobalSearchElement} style={{ width: '65vw' }} onHide={() => { if (!showGlobalSearchElement) return; setShowGlobalSearchElement(false); }}>
521
- <div className="flex align-items-center justify-content-between px-3">
522
- <h5 className="solid-custom-title m-0">Add Custom Filter</h5>
523
- <Button icon="pi pi-times" rounded text aria-label="Cancel" type="reset" size="small" onClick={() => setShowGlobalSearchElement(false)} />
524
- </div>
525
- <Divider className="m-0" />
526
- <div className="p-4">
527
- {fields.length > 0 &&
528
- <FilterComponent viewData={viewData} fields={fields} filterRules={filterRules} setFilterRules={setFilterRules} transformFilterRules={transformFilterRules} closeDialog={() => setShowGlobalSearchElement(false)}></FilterComponent>
529
- }
530
- </div>
531
- </Dialog>
532
- </div>
743
+
744
+ {showOverlay && (
745
+ <div className="absolute w-full z-5 bg-white border-round border-1 border-300 shadow-2" style={{ top: 35 }}>
746
+ {inputValue ? (
747
+ <>
748
+ <div className="custom-filter-search-options px-2 py-2 flex flex-column">
749
+ {
750
+ searchableFields.map((value: any, index: any) => (
751
+ <Button
752
+ key={index}
753
+ className="p-2 flex gap-1 text-color"
754
+ onClick={() => handleAddChip(value)}
755
+ text
756
+ severity="secondary"
757
+ size="small"
758
+ >
759
+ Search <strong>{value}</strong> for :
760
+ <span className="font-bold" style={{ color: '#000' }}>{inputValue}</span>
761
+ </Button>
762
+ ))
763
+ }
764
+ </div>
765
+ <Divider className="m-0" />
766
+ </>
767
+ ) :
768
+ <>
769
+ <div className="p-3 text-base">Search Here...</div>
770
+ <Divider className="m-0" />
771
+ </>
772
+ }
773
+ {savedFilters.length > 0 &&
774
+ <>
775
+ <div className="p-3">
776
+ <p className="font-medium">Saved Filters</p>
777
+ <div className="flex flex-column gap-2">
778
+ {savedFilters.map((savedfilter: any) =>
779
+ <SavedFilterList savedfilter={savedfilter} activeSavedFilter={activeSavedFilter} applySavedFilter={applySavedFilter} openSavedCustomFilter={openSavedCustomFilter} setSavedFilterTobeDeleted={setSavedFilterTobeDeleted} setIsDeleteSQDialogVisible={setIsDeleteSQDialogVisible}></SavedFilterList>
780
+ )}
781
+ </div>
782
+ </div>
783
+ <Divider className="m-0" />
784
+ </>
785
+ }
786
+ <div className="px-2 py-1">
787
+ <Button text size="small" label="Custom Filter" iconPos="left" icon='pi pi-plus' onClick={() => setShowGlobalSearchElement(true)} className="font-bold" />
788
+ </div>
789
+ </div>
790
+ )
791
+ }
792
+ <Dialog header={false} className="solid-global-search-filter" showHeader={false} visible={showGlobalSearchElement} style={{ width: '65vw' }} onHide={() => { if (!showGlobalSearchElement) return; setShowGlobalSearchElement(false); }}>
793
+ <div className="flex align-items-center justify-content-between px-3">
794
+ <h5 className="solid-custom-title m-0">Add Custom Filter</h5>
795
+ <Button icon="pi pi-times" rounded text aria-label="Cancel" type="reset" size="small" onClick={() => setShowGlobalSearchElement(false)} />
796
+ </div>
797
+ <Divider className="m-0" />
798
+ <div className="p-4">
799
+ {fields.length > 0 &&
800
+ <FilterComponent viewData={viewData} fields={fields} filterRules={filterRules} setFilterRules={setFilterRules} transformFilterRules={transformFilterRules} closeDialog={() => setShowGlobalSearchElement(false)}></FilterComponent>
801
+ }
802
+ </div>
803
+ </Dialog>
804
+ <Dialog header="Add Custom Filter" visible={showSaveFilterPopup} style={{ width: 500 }} onHide={() => { if (!showSaveFilterPopup) return; setShowSaveFilterPopup(false); }}>
805
+ <SolidSaveCustomFilterForm currentSavedFilterData={currentSavedFilterData} handleSaveFilter={handleSaveFilter} closeDialog={setShowSaveFilterPopup}></SolidSaveCustomFilterForm>
806
+ </Dialog>
807
+
808
+ <Dialog
809
+ visible={isDeleteSQDialogVisible}
810
+ header="Confirm Delete"
811
+ modal
812
+ footer={() => (
813
+ <div className="flex justify-content-center">
814
+ <Button label="Yes" icon="pi pi-check" className='small-button' severity="danger" autoFocus onClick={deleteSavedFilter} />
815
+ <Button label="No" icon="pi pi-times" className='small-button' onClick={() => setIsDeleteSQDialogVisible(false)} />
816
+ </div>
817
+ )}
818
+ onHide={() => setIsDeleteSQDialogVisible(false)}
819
+ >
820
+ <p>Are you sure you want to delete the {currentSavedFilterData?.name} saved query?</p>
821
+ </Dialog>
822
+ </div>
823
+ <div>
824
+ <Button
825
+ icon="pi pi-save"
826
+ style={{ fontSize: 10 }}
827
+ severity="secondary"
828
+ outlined size="small"
829
+ onClick={() => {
830
+ setShowSaveFilterPopup(true)
831
+ }}
832
+ />
833
+ </div>
834
+ </>
533
835
  )
534
836
  });