/* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect, useMemo } from "react"; import FilterPopupView from "./FilterPopupView"; import { Operator, OperatorCreiteria } from "../../enum"; import { FilterExpression, FilterExpressionViewModel, FilterTarget, } from "../../data/advancedSearch"; import { getAdvancedSearchIntitialFilterRow, isFilterValueEmpty, normalizeLogicOperators, } from "./helper/AdvancedSearchFunctions"; import _ from "lodash"; import { defaultCriteria } from "./constant"; interface FilterPopupProps { uiElementGroupData: Record; filterableFields: FilterTarget[]; config: { uiElementGroupId: string }; cache?: Record; loadSupportiveData: (propertyToFilter: FilterTarget) =>Promise; handlePopupShow: () => void; onModelUpdate: ( callBack: ((args: any) => void) | null, fieldName: string, value: any, ) => void; loadTemplateSupportiveData?: ( callBack: (args: any) => void, supportiveKeys: any, ) => Promise; } const FilterPopup = (props: FilterPopupProps) => { // If appliedQuery has data but filterRows doesn't, populate filterRows from appliedQuery.filters useEffect(() => { const advancedSearch = props.uiElementGroupData?.advancedSearch; const hasAppliedQuery = advancedSearch?.appliedQuery?.filters?.length > 0; const hasValidFilterRows = advancedSearch?.filterRows?.length > 0 && advancedSearch?.filterRows[0]?.selectedFilter?.propertyToFilter ?.apiPropertyName; if (hasAppliedQuery && !hasValidFilterRows) { const filterRows: FilterExpressionViewModel[] = []; let criteria = advancedSearch.appliedQuery.criteria; advancedSearch.appliedQuery.filters.forEach( (filter: FilterExpression, index: number) => { const filterRow: FilterExpressionViewModel = { id: filter.id, idLabel: `(${(index + 1).toString()})`, selectedFilter: _.cloneDeep(filter), allPropertieseToFilter: props.filterableFields, alloperators: [], allValues: undefined, }; criteria = criteria.replace(filter.id, filterRow.idLabel); filterRows.push(filterRow); }, ); const updatedAdvancedSearch = { ...advancedSearch, filterRows: _.cloneDeep(filterRows), criteria, }; const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: updatedAdvancedSearch, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); } }, []); const onAdvancedSearchFilterTargetChange = ( value: FilterTarget, id: string, ) => { const filterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); const filterRow = filterRows.find( (filterRow: FilterExpressionViewModel) => filterRow.id === id, ); if (filterRow) { filterRow.selectedFilter.propertyToFilter = value; filterRow.selectedFilter.value = undefined; filterRow.selectedFilter.operator = { label: "Select Operator...", value: null, }; } const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: filterRows, }, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchOperatorChange = ( value: { label: string; value: Operator | null; }, id: string, ) => { const filterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); const filterRow = filterRows.find( (filterRow: FilterExpressionViewModel) => filterRow.id === id, ); if (filterRow) { filterRow.selectedFilter.operator = value; filterRow.selectedFilter.value = undefined; } const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: filterRows, }, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchFilterValueChange = (value: any, id: string) => { const filterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); const filterRow = filterRows.find( (filterRow: FilterExpressionViewModel) => filterRow.id === id, ); if (filterRow) { filterRow.selectedFilter.value = value; } const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: filterRows, }, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchCriteriaToggle = (id: string) => { const filterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); const filterRow = filterRows.find( (filterRow: FilterExpressionViewModel) => filterRow.id === id, ); if (filterRow) { filterRow.selectedFilter.selectedCriteria = filterRow.selectedFilter.selectedCriteria === OperatorCreiteria.AND ? OperatorCreiteria.OR : OperatorCreiteria.AND; } let criteria = ""; filterRows.map((filterRow: any, index: number) => { criteria += `${filterRow.idLabel}`; if (filterRows?.length - 1 !== index) { criteria += ` ${filterRow?.selectedFilter?.selectedCriteria} `; } }); const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: filterRows, criteria, }, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchFilterAdd = (id: string) => { const idPosition = props.uiElementGroupData.advancedSearch.filterRows.findIndex( (filterRow: FilterExpressionViewModel) => filterRow.id === id, ); const initialFilterRow = _.cloneDeep(getAdvancedSearchIntitialFilterRow()); initialFilterRow.allPropertieseToFilter = props.filterableFields; let updatedFilterRows = [ ...props.uiElementGroupData.advancedSearch.filterRows.slice( 0, idPosition + 1, ), initialFilterRow, ...props.uiElementGroupData.advancedSearch.filterRows.slice( idPosition + 1, ), ]; let criteria = ""; updatedFilterRows = _.cloneDeep(updatedFilterRows).map((row, index) => { row.idLabel = `(${index + 1})`; criteria += `${row.idLabel}`; if (updatedFilterRows?.length - 1 !== index) { criteria += ` ${row?.selectedFilter?.selectedCriteria} `; } return row; }); const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: updatedFilterRows, criteria: criteria?.trim(), }, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchFilterDelete = (id: string) => { let filterRows = props.uiElementGroupData.advancedSearch.filterRows.filter( (filterRow: FilterExpressionViewModel) => filterRow.id !== id, ); let criteriaExpression = ""; filterRows = _.cloneDeep(filterRows)?.map( (filterRow: FilterExpressionViewModel, index: number) => { filterRow.idLabel = `(${index + 1})`; criteriaExpression += filterRow.idLabel; if (filterRows?.length - 1 !== index) { criteriaExpression += ` ${filterRow.selectedFilter.selectedCriteria} `; } return filterRow; }, ); const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: filterRows, criteria: criteriaExpression, }, pagination: { ...props.uiElementGroupData.pagination, skip: 0 }, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchClear = () => { const initialFilterRow = _.cloneDeep(getAdvancedSearchIntitialFilterRow()); initialFilterRow.allPropertieseToFilter = props.filterableFields; const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: { ...props.uiElementGroupData.advancedSearch, filterRows: [initialFilterRow], criteria: defaultCriteria, appliedQuery: { criteria: "", filters: [], }, }, }; props.handlePopupShow(); props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onCriteriaApplied = () => { let criteriaExpression = document.getElementById( "tmpl-criteria-input-" + (props.config.uiElementGroupId ?? ""), )?.innerText; criteriaExpression = normalizeLogicOperators(criteriaExpression ?? ""); const operators = [...(criteriaExpression?.matchAll(/AND|OR/gi) ?? [])].map( (match) => match[0], ); let filterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); filterRows = filterRows.map((filterRow: any, index: number) => { if (filterRows?.length - 1 !== index) { filterRow.selectedFilter.selectedCriteria = operators[index] ?? "AND"; } return filterRow; }); const updatedAdvancedSearch = { ...props.uiElementGroupData.advancedSearch, filterRows: filterRows, criteria: criteriaExpression ?? "", }; const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: updatedAdvancedSearch, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onCopyToClipboard = async () => { const criteria = document.getElementById( "tmpl-criteria-input-" + (props.config.uiElementGroupId ?? ""), )?.innerText; try { await navigator.clipboard.writeText(criteria || ""); } catch (err) { console.error("Unable to copy to clipboard", err); } }; const onAdvancedSearchCancel = () => { const clonedFilterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); const filterRows: FilterExpressionViewModel[] = []; let criteria = props.uiElementGroupData.advancedSearch.appliedQuery.criteria; props.uiElementGroupData.advancedSearch.appliedQuery?.filters?.map( (filter: any, index: number) => { const clonedFilterRow = clonedFilterRows?.find( (row: any) => row.id === filter.id, ); const filterRow: FilterExpressionViewModel = { id: filter.id, idLabel: `(${(index + 1).toString()})`, selectedFilter: _.cloneDeep(filter), allPropertieseToFilter: props.filterableFields, alloperators: clonedFilterRow?.alloperators ?? [], allValues: clonedFilterRow ? clonedFilterRow?.allValues : undefined, }; criteria = criteria.replace(filter.id, filterRow.idLabel); filterRows.push(filterRow); }, ); if (!filterRows.length) { const initialFilterRow = _.cloneDeep( getAdvancedSearchIntitialFilterRow(), ); initialFilterRow.allPropertieseToFilter = props.filterableFields; filterRows.push(initialFilterRow); criteria = defaultCriteria; } const updatedAdvancedSearch = { ...props.uiElementGroupData.advancedSearch, filterRows: _.cloneDeep(filterRows), criteria, }; const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: updatedAdvancedSearch, }; props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const onAdvancedSearchApply = () => { const filterRows = _.cloneDeep( props.uiElementGroupData.advancedSearch.filterRows, ); const selectedFilters: FilterExpression[] = []; let criteria = props.uiElementGroupData?.advancedSearch?.criteria ?? ""; filterRows?.map((filterRow: FilterExpressionViewModel) => { selectedFilters.push(filterRow.selectedFilter); criteria = criteria.replace(filterRow.idLabel, filterRow.id); }); const appliedQuery = { criteria, filters: selectedFilters, }; const updatedAdvancedSearch = { ...props.uiElementGroupData.advancedSearch, appliedQuery: _.cloneDeep(appliedQuery), }; const updatedUiElementGroupData = { ...props.uiElementGroupData, advancedSearch: updatedAdvancedSearch, pagination: { ...props.uiElementGroupData.pagination, skip: 0 }, }; props.handlePopupShow(); props.onModelUpdate( null, props.config.uiElementGroupId, updatedUiElementGroupData, ); }; const hasValidFilter = useMemo(() => { const filterRows: FilterExpressionViewModel[] = props.uiElementGroupData?.advancedSearch?.filterRows ?? []; return filterRows.some( (row) => row.selectedFilter?.propertyToFilter?.apiPropertyName && row.selectedFilter?.operator?.value != null && !isFilterValueEmpty(row.selectedFilter?.value), ); }, [props.uiElementGroupData?.advancedSearch?.filterRows]); const hasAppliedFilter = useMemo(() => { const appliedFilters = props.uiElementGroupData?.advancedSearch?.appliedQuery?.filters ?? []; return appliedFilters.length > 0; }, [props.uiElementGroupData?.advancedSearch?.appliedQuery?.filters]); const isDirty = useMemo(() => { const filterRows: FilterExpressionViewModel[] = props.uiElementGroupData?.advancedSearch?.filterRows ?? []; const appliedQuery = props.uiElementGroupData?.advancedSearch?.appliedQuery; const appliedFilters = appliedQuery?.filters ?? []; const currentCriteria = props.uiElementGroupData?.advancedSearch?.criteria ?? ""; // Build comparable current filters from filterRows const currentFilters = filterRows.map((row) => ({ apiPropertyName: row.selectedFilter?.propertyToFilter?.apiPropertyName, operator: row.selectedFilter?.operator?.value, value: row.selectedFilter?.value, selectedCriteria: row.selectedFilter?.selectedCriteria, })); // Build comparable applied filters const appliedComparable = appliedFilters.map( (filter: FilterExpression) => ({ apiPropertyName: filter.propertyToFilter?.apiPropertyName, operator: filter.operator?.value, value: filter.value, selectedCriteria: filter.selectedCriteria, }), ); // Also compare criteria expression const appliedCriteria = appliedQuery?.criteria ?? ""; // Reconstruct current criteria with IDs for comparison let currentCriteriaWithIds = currentCriteria; filterRows.forEach((row) => { currentCriteriaWithIds = currentCriteriaWithIds.replace( row.idLabel, row.id, ); }); return ( JSON.stringify(currentFilters) !== JSON.stringify(appliedComparable) || currentCriteriaWithIds !== appliedCriteria ); }, [ props.uiElementGroupData?.advancedSearch?.filterRows, props.uiElementGroupData?.advancedSearch?.appliedQuery, props.uiElementGroupData?.advancedSearch?.criteria, ]); return ( ); }; export default FilterPopup;