import * as React from 'react'; import { useState, useEffect, useRef } from 'react'; import { IBaseOption } from './interfaces/CustomSearchInterfaces'; import { TextInput, inputType } from '../../core'; import { Icon } from '../../core' import { DynamicCustomResultRenderer } from './DynamicCustomResultRenderer'; import { createQueryArray, getQueryArrayByQuery, countGivenChar } from './Utility' import { SearchResultType, logicalOperators, conditionalOperators, notOperator, inOperator, orderOperator, byOperator, sortingOperators, datePickerDefaultLeftMargin } from './enums/CustomSearchEnums'; import { faCheckCircle, faTimesCircle, faTimes } from '@fortawesome/free-solid-svg-icons'; import { isValidSearchQuery } from './SearchQueryValidator'; // import { getWidthByText } from '../../utility/utils'; import { Button } from '../../core'; import { keyCode } from '../../utility/Constants'; import './customSearch.scss'; interface ICustomSearchProps { customSearchData: any; onSearch: any; onChange: any; planeQuery: string; onEnterButtonClick: () => void; onCheckBoxChecked: boolean; isLoading: boolean; onClearSearch: boolean; onSearchTextChange: string; showEmptyOption: boolean; onClearButton?:any; } export const CustomSearch = (props: ICustomSearchProps) => { const { customSearchData, onSearch, onChange, onEnterButtonClick, planeQuery, onCheckBoxChecked, isLoading, onClearSearch, onSearchTextChange, showEmptyOption, onClearButton } = props; const { columns, searchResults } = customSearchData; const [showResults, setShowResults] = useState(false); const [options, setOptions] = useState([]); const [queryArray, setQueryArray] = useState(planeQuery ? planeQuery.split(' ') : []); const [lastColumn, setLastColumn] = useState(null); const [currentResultType, setCurrentResultType] = useState(SearchResultType.singleSelect); const [cursorElement, setCursorElement]: any = useState(null); const [inputCaret, setInputCaret] = useState({ status: true, position: 0 }); const [datePickerPosition, setDatePickerPosition]: any = useState({ left: 0 }); const queryRef = useRef(); const searchQuery = queryArray.join(' '); const [activeSuggestion, setActiveSuggestion] = useState(0); useEffect(() => { document.addEventListener('mousedown', outSideClick); return () => document.removeEventListener('mousedown', outSideClick); }, []); useEffect(() => { if (onClearSearch) { setQueryArray([]); } }, [onClearSearch]); useEffect(() => { if (onSearchTextChange.length) { setQueryArray(onSearchTextChange.split(' ')); } }, [onSearchTextChange]); useEffect(() => { const query = getSearchQueryReplacedByValues(); onChange(query ? query : searchQuery, searchQuery, isValidSearchQuery(searchQuery)); }, [onCheckBoxChecked]); useEffect(() => { const query = getSearchQueryReplacedByValues(); onChange(query ? query : searchQuery, searchQuery, isValidSearchQuery(searchQuery)); }, [queryArray]); useEffect(() => { if (inputCaret && inputCaret.position && queryArray.length) { //code needs to be validated const thresholdQueryArray = [...queryArray]; if (thresholdQueryArray && thresholdQueryArray.length) { thresholdQueryArray.splice(inputCaret.position + 1, queryArray.length - inputCaret.position); setCaretPosition(thresholdQueryArray.join(' ').length); } } }, [inputCaret]); const getSearchQueryReplacedByValues = () => { const wordsWithQuotesAndSpace: any = searchQuery.match(/'.*?'/g); const columnsWithQuotes: any = wordsWithQuotesAndSpace && wordsWithQuotesAndSpace.filter( (wordWithSpace: string) => columns && columns.find( (column: any) => column.label.toLowerCase() === wordWithSpace .split("'") .join('') .toLowerCase() ) ); let query = searchQuery.replace(/'.*?'/g, '$'); const remainingColumns: any = []; if (customSearchData.columns) customSearchData.columns.forEach((element: IBaseOption) => { let replaceCount = 0; query = query.replace(new RegExp(element.label, 'gi'), () => { replaceCount++; return element.value.toString(); }); if (!replaceCount) { remainingColumns.push(element); } }); const finalQueryArray: any = []; let spaceWordIndex = 0; query.split(' ').forEach((element: any) => { let currentW = ''; for (const char of element) { if (char === '$') { // TO DO Handle It Using Regex if ( columnsWithQuotes.find( (columnWithQuote: string) => columnWithQuote.toLowerCase() === wordsWithQuotesAndSpace[spaceWordIndex].toLowerCase() ) ) { const remainingColumnValue = remainingColumns.find( (remainingColumn: any) => wordsWithQuotesAndSpace[spaceWordIndex] .split("'") .join('') .toLowerCase() === remainingColumn.label.toLowerCase() ); currentW += remainingColumnValue ? remainingColumnValue.value : ''; } else currentW += wordsWithQuotesAndSpace[spaceWordIndex].toUpperCase(); spaceWordIndex++; } else currentW += char; } finalQueryArray.push(currentW); }); const result = finalQueryArray.join(' '); const finalResult = result.replace('parent_primary_product_name_name', 'parent_product_name'); return finalResult; }; useEffect(() => { if (lastColumn && searchResults) { setOptions(searchResults); setShowResults(true); } }, [JSON.stringify(customSearchData)]); const getAutoCompleteSuggestions = ( lastWord: any, currentColumn: any, secondLastExpression: any ) => { const isLogicalOperator = logicalOperators.find( (searchOption: any) => lastWord && searchOption.label.toLowerCase() === lastWord.toLowerCase() ); if (!lastWord || isLogicalOperator) return [...columns, notOperator]; if ((lastWord === notOperator.label || lastWord === notOperator.value) && currentColumn) return [inOperator]; const isSortingOperator = sortingOperators.find( (searchOption: any) => lastWord && searchOption.label.toLowerCase() === lastWord.toLowerCase() ); if (isSortingOperator) return []; if (lastWord === orderOperator.label || lastWord === orderOperator.value) return [byOperator]; if (lastWord === byOperator.label || lastWord === byOperator.value) return columns; if (lastWord === notOperator.label || lastWord === notOperator.value) return [...columns, notOperator]; const isColumnValue = columns.find( (searchOption: any) => searchOption.label.toLowerCase() === removeCharactersFromString(lastWord, ["'", '(']).toLowerCase() ); if ( isColumnValue && (secondLastExpression === byOperator.label || secondLastExpression === byOperator.value) ) return sortingOperators; if (isColumnValue && isColumnValue.type === 'string') return [...conditionalOperators.filter((operator: any) => operator.type === 'any')]; else if (isColumnValue && isColumnValue.type === 'date') return conditionalOperators; const isConditionalOperator = conditionalOperators.find( (searchOption: any) => searchOption.label.toLowerCase() === lastWord.toLowerCase() ); if (isConditionalOperator) return []; return logicalOperators; }; const setLastColumnValue = (newQueryArray: string[]) => { const modifiedWordIndex = newQueryArray.findIndex( (newElement: any, index: number) => newElement !== getQueryArrayByQuery(queryArray.join(' ').replace(/ +/g, ' '))[index] ); const lastExpression = newQueryArray[modifiedWordIndex - 1]; let newColumnValue = newQueryArray[modifiedWordIndex - 2]; if (newColumnValue === notOperator.label || newColumnValue === notOperator.value) newColumnValue = newQueryArray[modifiedWordIndex - 3]; const currentColumn = columns.find( (columnData: any) => newColumnValue && columnData.label.toLowerCase() === removeCharactersFromString(newColumnValue, ['(']) .toLowerCase() .replace(/\'/gi, '') ); if (currentColumn) setLastColumn(currentColumn); return { lastExpression, modifiedWord: newQueryArray[modifiedWordIndex], currentColumn, modifiedWordIndex, newColumnValue }; }; const getMultipleColumnValues = (currentWord: string, newWord: string) => { if (currentWord.substr(0, 1) === '(') { const multipleValues = currentWord.replace('(', '').split(','); if (multipleValues.length) { multipleValues[multipleValues.length - 1] = `'${newWord}'`; return '(' + multipleValues; } else return '(' + [newWord]; } else return ''; }; const isQueryValidByQuotes = (newQueryArray: string[]) => { let incompleteWordIndex = -1; let quotesCount = 0; // newQueryArray.forEach((queryElement: any, index: number) => { // for (const char of queryElement) { // if (char === "'") { // incompleteWordIndex = index; // quotesCount++; // } // } // }); return { newQueryArray, incompleteWordIndex, status: quotesCount % 2 === 0 }; }; const getIncompleteQuoteQueryArray = (newQueryArray: string[]) => { const queryValidByQuotes = isQueryValidByQuotes(newQueryArray); if (queryValidByQuotes.status) return newQueryArray; else { const { incompleteWordIndex } = queryValidByQuotes; const allIncompleteElements = newQueryArray.slice(incompleteWordIndex); newQueryArray.splice( incompleteWordIndex, newQueryArray.length - incompleteWordIndex, allIncompleteElements.join(' ') ); return newQueryArray; } }; const removeCharactersFromString = (targetString: string, characters: string[]) => { let finalString = ''; characters.forEach((characterToRemove: string) => { if (!finalString) finalString = targetString.split(characterToRemove).join(''); else finalString = finalString.split(characterToRemove).join(''); }); return finalString; }; const setCaretPosition = (pos: number) => { if (queryRef.current.setSelectionRange) { queryRef.current.focus(); queryRef.current.setSelectionRange(pos, pos); } }; const suggestOptions = (newQuery: string) => { if (newQuery === ' ') { setOptions(getAutoCompleteSuggestions(null, null, null)); setShowResults(true); } let newQueryArray: any = getQueryArrayByQuery(newQuery); newQueryArray = getIncompleteQuoteQueryArray(newQueryArray); const res = createQueryArray(newQueryArray); const { resultQueryArray, // lastSpace, startWithDoubleQuotes, endWithDoubleQuotes, startWithSingleQuotes, endWithSingleQuotes, flag, lastString } = res; newQueryArray = resultQueryArray; // .replace(/ +/g, ' ') const { lastExpression, modifiedWord, currentColumn, modifiedWordIndex, newColumnValue } = setLastColumnValue(newQueryArray); if (currentColumn && currentColumn.type === 'date') { setCurrentResultType(SearchResultType.datePicker); setCursorElement({ index: modifiedWordIndex, word: modifiedWord }); const changedWordArray = newQueryArray; changedWordArray.splice( modifiedWordIndex + 1, newQueryArray.length - (modifiedWordIndex + 1) ); setQueryArray(newQueryArray); setDatePickerPosition({ left: /** the below comment has to be uncommented once the angular canvas issue is resolved */ // getWidthByText(changedWordArray.join(' '), '12px system-ui') + datePickerDefaultLeftMargin, inputLeft: parseInt(queryRef.current.getBoundingClientRect().left, 10) + parseInt(queryRef.current.clientWidth, 10) }); if ( modifiedWord.substr(-1) !== ')' && conditionalOperators.find( (operator: any) => operator.label.toLowerCase() === lastExpression.toLowerCase() ) ) { setShowResults(true); } else setShowResults(false); return; } const numberOfSpaces = newQueryArray.filter((element: string) => element === ''); setCurrentResultType(SearchResultType.singleSelect); let suggestions = []; if (flag === "VALUE") { if ((!lastString.includes("IN")) && (/"/.test(lastString) || /'/.test(lastString)) && (countGivenChar(lastString, "'") % 2 === 0 || countGivenChar(lastString, '"') % 2 === 0) && ((startWithDoubleQuotes && endWithDoubleQuotes) || (startWithSingleQuotes && endWithSingleQuotes)) && isValidSearchQuery(searchQuery) ) { suggestions = numberOfSpaces.length > 1 ? [] : getAutoCompleteSuggestions(lastExpression, currentColumn, newColumnValue); } } else { suggestions = numberOfSpaces.length > 1 ? [] : getAutoCompleteSuggestions(lastExpression, currentColumn, newColumnValue); } if ( !newQueryArray[modifiedWord] && newQueryArray[modifiedWordIndex - 1] && newQueryArray[modifiedWordIndex - 1].substr(0, 1) === '(' && newQueryArray[modifiedWordIndex - 2] && newQueryArray[modifiedWordIndex - 2].toLowerCase() === inOperator.label.toLowerCase() && newQueryArray[modifiedWordIndex - 1].substr(-1) !== ')' ) { newQueryArray.pop(); setOptions([]); } else if ( currentColumn && !suggestions.length && modifiedWord && removeCharactersFromString(modifiedWord, ["'"]).length >= 2 && modifiedWord.substr(-1) !== "'" ) { if (modifiedWord.substr(0, 1) === '(') { const allColumnValues = modifiedWord.replace('(', '').split(','); let modifiedColumnValue = ''; const modifiedIncompleteStatus = isQueryValidByQuotes(allColumnValues); if (modifiedIncompleteStatus.status) modifiedColumnValue = allColumnValues[allColumnValues.length - 1]; else modifiedColumnValue = allColumnValues[modifiedIncompleteStatus.incompleteWordIndex]; if ( modifiedWord.substr(-1) !== ',' && modifiedWord.substr(-1) !== ')' && modifiedColumnValue && modifiedColumnValue.split("'").join('').length >= 2 ) onSearch({ // searchText: btoa(modifiedColumnValue.split("'").join('')), searchText: modifiedColumnValue.split("'").join(''), id: currentColumn.id, currentValueKey: currentColumn.value }); } else { if (/[a-zA-z-&~%:;?_@#^]/.test(modifiedWord.substr(-1))) onSearch({ // searchText: btoa(modifiedWord.split("'").join('')), searchText: modifiedWord.split("'").join(''), id: currentColumn.id, currentValueKey: currentColumn.value }); } } else { setOptions( suggestions.filter((option: IBaseOption) => option.label.toLowerCase().includes( modifiedWord && modifiedWord.split('(').join('') && modifiedWord .split('(') .join('') .toLowerCase() ) ) ); setShowResults(true); } setCursorElement({ index: modifiedWordIndex, word: modifiedWord }); setQueryArray(newQueryArray); }; const onInputChange = (event: any) => { const userInput = event.target.value; setActiveSuggestion(0); suggestOptions(userInput); }; const getOptionValue = (lastConditionalOperator: string, label: string) => { if ( [...logicalOperators, ...conditionalOperators, notOperator].find( (operator: any) => operator.label.toLowerCase() === label.toLowerCase() ) ) { return label; } else if ( lastConditionalOperator && lastConditionalOperator.toLowerCase() === inOperator.label.toLowerCase() ) { return label.includes("'") ? `"${label}"` : `'${label}'`; } else if ( conditionalOperators.find((operator: any) => operator.label === lastConditionalOperator) ) { return label.includes("'") ? `"${label}"` : `'${label}'`; } else return null; }; const setOptionForDateTypeColumn = (lastConditionalOperator: string, option: any | string[], caretPosition: number | null) => { setQueryArray( queryArray.map((queryElement: any, index: number) => { if (index === cursorElement.index) { caretPosition = index; const optionValue = getOptionValue(lastConditionalOperator, option); if (optionValue) return optionValue; else return `${option.includes(' ') ? `'${option}'` : `${option}`}`; } else return queryElement; }) ); return caretPosition; }; const setOptionForStringTypeColumn = (lastConditionalOperator: string, option: { label: string; }, caretPosition: number | null) => { let isReplaced: boolean = false; const newQueryArray = queryArray.map((queryElement: any, index: number) => { if (index === cursorElement.index) { caretPosition = index; const optionValue = getOptionValue(lastConditionalOperator, option.label); // options if it's an operator or a dropdown selecton from search if (optionValue) { return optionValue; } else if ( columns.find( (operator: any) => operator.label.toLowerCase() === option.label.toLowerCase() ) ) { //this conditon matches if it's a column isReplaced = true; return option.label.includes(' ') ? `'${option.label}'` : option.label; } else { return `${option.label.includes(' ') ? `'${option.label}'` : `${option.label}`}`; } } else if (!isReplaced && index > cursorElement.index) { if (queryElement === 'AND' || queryElement === 'OR' || queryElement === 'ORDER BY') { isReplaced = true; return queryElement; } else if (queryElement !== 'AND' && queryElement !== 'OR' && queryElement !== 'ORDER BY') { return ''; } isReplaced = true; return queryElement; } else { // if no condition matches return queryElement; } }).filter(item => item !== ''); setQueryArray(newQueryArray); return caretPosition; }; const onOptionClick = (option: any) => { onOptionSelect(option); }; const onOptionSelect = (option: any) => { const lastConditionalOperator = queryArray[queryArray.length - 2]; let caretPosition: any = null; if (cursorElement) { if ( lastConditionalOperator && lastConditionalOperator.toLowerCase() === inOperator.label.toLowerCase() && cursorElement.word.includes('(') ) { setQueryArray( queryArray.map((queryElement: any, index: number) => { if (index === cursorElement.index) { caretPosition = index; return `${getMultipleColumnValues( cursorElement.word, currentResultType === SearchResultType.datePicker ? option : option.label )}`; } else return queryElement; }) ); } else { if (currentResultType === SearchResultType.datePicker) { caretPosition = setOptionForDateTypeColumn( lastConditionalOperator, option, caretPosition ); } else if (currentResultType === SearchResultType.singleSelect) { caretPosition = setOptionForStringTypeColumn( lastConditionalOperator, option, caretPosition ); } } } if (caretPosition) { setInputCaret({ status: !inputCaret.status, position: caretPosition }); } setShowResults(false); queryRef.current.focus(); }; const outSideClick = (event: any) => { const selectedElement = document.getElementById('dynamic-results'); const datePickers = document.getElementsByClassName('flatpickr-calendar') as HTMLCollection; if (selectedElement && selectedElement.contains(event.target)) return; let elementInDatePicker = false; Array.from(datePickers).forEach(datePicker => { if (datePicker.contains(event.target)) elementInDatePicker = true; }); if (elementInDatePicker) return; return setShowResults(false); }; const onInputClick = () => { setOptions([]); setShowResults(false); }; const onCrossButtonClick = () => { setQueryArray([]); onClearButton(); }; const onEnterClick = (event: any) => { if (event.keyCode === keyCode.Enter && isValidSearchQuery(searchQuery) && searchQuery.length) onEnterButtonClick(); }; const onKeyDown = (e: any) => { // User pressed the enter key if (e.keyCode === 13) { onOptionSelect(options[activeSuggestion]); } // User pressed the up arrow else if (e.keyCode === 38) { if (activeSuggestion === 0) { return; } setActiveSuggestion(prev => prev - 1) } // User pressed the down arrow else if (e.keyCode === 40) { if (options.length === activeSuggestion + 1) { return; } setActiveSuggestion(prev => prev + 1) } }; return (
{searchQuery.length ? ( ) : null} {isValidSearchQuery(searchQuery) ? ( ) : ( )} {showResults && (
0 ? options : []} onOptionClick={onOptionClick} setShowResults={setShowResults} datePickerPosition={datePickerPosition} isLoading={isLoading} showEmptyOption={showEmptyOption} />
)} {/* {isValidSearchQuery(searchQuery) ? (
{planeQuery}
) : null} */}
); };