diff --git a/public/hyperswitch/icons/solid.svg b/public/hyperswitch/icons/solid.svg index 14c30ed75..bd244ce9d 100644 --- a/public/hyperswitch/icons/solid.svg +++ b/public/hyperswitch/icons/solid.svg @@ -237,11 +237,38 @@ License) d="M143 352.3L7 216.3c-9.4-9.4-9.4-24.6 0-33.9l22.6-22.6c9.4-9.4 24.6-9.4 33.9 0l96.4 96.4 96.4-96.4c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9l-136 136c-9.2 9.4-24.4 9.4-33.8 0z" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + className={`flex flex-1 flex-row justify-center overflow-auto bg-jp-gray-100 bg-opacity-20 rounded-md border select-none ${calendarContaierStyle}`}> {dummyRow ->Array.mapWithIndex((_item, i) => { let currDateTemp = Js.Date.fromFloat(Js.Date.valueOf(currDateIm)) @@ -121,7 +121,7 @@ let make = ( : React.null}
+ className="font-medium text-sm md:text-base text-jp-gray-900 dark:text-jp-gray-text_darktheme dark:text-opacity-75"> {React.string(monthAndYear)}
diff --git a/src/components/DateRangeField.res b/src/components/DateRangeField.res new file mode 100644 index 000000000..ff4f34d1a --- /dev/null +++ b/src/components/DateRangeField.res @@ -0,0 +1,907 @@ +let defaultCellHighlighter = (_): Calendar.highlighter => { + { + highlightSelf: false, + highlightLeft: false, + highlightRight: false, + } +} + +let useErroryValueResetter = (value: string, setValue: (string => string) => unit) => { + React.useEffect0(() => { + let isErroryTimeValue = _ => { + try { + false + } catch { + | _error => true + } + } + if value->isErroryTimeValue { + setValue(_ => "") + } + + None + }) +} + +let getDateStringForValue = ( + value, + isoStringToCustomTimeZone: string => TimeZoneHook.dateTimeString, +) => { + if value->LogicUtils.isEmptyString { + "" + } else { + try { + let check = TimeZoneHook.formattedISOString(value, "YYYY-MM-DDTHH:mm:ss.SSS[Z]") + let {year, month, date} = isoStringToCustomTimeZone(check) + `${year}-${month}-${date}` + } catch { + | _error => "" + } + } +} + +let getTimeStringForValue = ( + value, + isoStringToCustomTimeZone: string => TimeZoneHook.dateTimeString, +) => { + if value->LogicUtils.isEmptyString { + "" + } else { + try { + let check = TimeZoneHook.formattedISOString(value, "YYYY-MM-DDTHH:mm:ss.SSS[Z]") + let {hour, minute, second} = isoStringToCustomTimeZone(check) + `${hour}:${minute}:${second}` + } catch { + | _error => "" + } + } +} + +let getFormattedDate = (date, format) => { + date->Date.fromString->Date.toISOString->TimeZoneHook.formattedISOString(format) +} + +let isStartBeforeEndDate = (start, end) => { + let getDate = date => { + let datevalue = Js.Date.makeWithYMD( + ~year=Js.Float.fromString(date[0]->Option.getOr("")), + ~month=Js.Float.fromString( + String.make(Js.Float.fromString(date[1]->Option.getOr("")) -. 1.0), + ), + ~date=Js.Float.fromString(date[2]->Option.getOr("")), + (), + ) + datevalue + } + let startDate = getDate(String.split(start, "-")) + let endDate = getDate(String.split(end, "-")) + startDate < endDate +} + +let getStartEndDiff = (startDate, endDate) => { + let diffTime = Math.abs( + endDate->Date.fromString->Date.getTime -. startDate->Date.fromString->Date.getTime, + ) + diffTime +} + +module PredefinedOption = { + @react.component + let make = ( + ~predefinedOptionSelected, + ~value, + ~onClick, + ~disableFutureDates, + ~disablePastDates, + ~todayDayJsObj, + ~isoStringToCustomTimeZone, + ~isoStringToCustomTimezoneInFloat, + ~customTimezoneToISOString, + ~todayDate, + ~todayTime, + ~formatDateTime, + ~isTooltipVisible=true, + ) => { + let optionBG = if predefinedOptionSelected === Some(value) { + "bg-blue-100 dark:bg-jp-gray-850 py-2" + } else { + "bg-transparent md:bg-white md:dark:bg-jp-gray-lightgray_background py-2" + } + + let (stDate, enDate, stTime, enTime) = DateRangeUtils.getPredefinedStartAndEndDate( + todayDayJsObj, + isoStringToCustomTimeZone, + isoStringToCustomTimezoneInFloat, + customTimezoneToISOString, + value, + disableFutureDates, + disablePastDates, + todayDate, + todayTime, + ) + + let startDate = getFormattedDate(`${stDate}T${stTime}Z`, formatDateTime) + let endDate = getFormattedDate(`${enDate}T${enTime}Z`, formatDateTime) + let handleClick = _value => { + onClick(value, disableFutureDates) + } + let dateRangeDropdownVal = DateRangeUtils.datetext(value, disableFutureDates) + +
+
+ {React.string(dateRangeDropdownVal)} +
+
+ } + toolTipPosition=Right + contentAlign=Left + /> + } +} + +module Base = { + @react.component + let make = ( + ~startDateVal: string, + ~setStartDateVal: (string => string) => unit, + ~endDateVal: string, + ~setEndDateVal: (string => string) => unit, + ~showTime=false, + ~disable=false, + ~disablePastDates=true, + ~disableFutureDates=false, + ~predefinedDays=[], + ~format="YYYY-MM-DDTHH:mm:ss.SSS[Z]", + ~numMonths=1, + ~disableApply=true, + ~removeFilterOption=false, + ~dateRangeLimit=?, + ~optFieldKey as _=?, + ~textHideInMobileView=true, + ~showSeconds=true, + ~hideDate=false, + ~selectStandardTime=false, + ~buttonType=?, + ~buttonText="", + ~allowedDateRange=?, + ~textStyle=?, + ~standardTimeToday=false, + ~removeConversion=false, + ~customborderCSS="", + ~isTooltipVisible=true, + ) => { + open DateRangeUtils + open LogicUtils + let (isCustomSelected, setIsCustomSelected) = React.useState(_ => + predefinedDays->Array.length === 0 + ) + let formatDateTime = showSeconds ? "MMM DD, YYYY HH:mm:ss" : "MMM DD, YYYY HH:mm" + let (showOption, setShowOption) = React.useState(_ => false) + let customTimezoneToISOString = TimeZoneHook.useCustomTimeZoneToIsoString() + let isoStringToCustomTimeZone = TimeZoneHook.useIsoStringToCustomTimeZone() + let isoStringToCustomTimezoneInFloat = TimeZoneHook.useIsoStringToCustomTimeZoneInFloat() + + let (clickedDates, setClickedDates) = React.useState(_ => []) + + let (localStartDate, setLocalStartDate) = React.useState(_ => startDateVal) + let (localEndDate, setLocalEndDate) = React.useState(_ => endDateVal) + let (_localOpt, setLocalOpt) = React.useState(_ => "") + + let (isDropdownExpanded, setIsDropdownExpanded) = React.useState(_ => false) + let (calendarVisibility, setCalendarVisibility) = React.useState(_ => false) + let isMobileView = MatchMedia.useMobileChecker() + let isFilterSection = React.useContext(TableFilterSectionContext.filterSectionContext) + + let dropdownPosition = isFilterSection && !isMobileView && isCustomSelected ? "right-0" : "" + + let todayDayJsObj = React.useMemo1(() => { + Date.make()->Date.toString->DayJs.getDayJsForString + }, [isDropdownExpanded]) + + let currentTime = todayDayJsObj.format(. "HH:mm") + let todayDate = todayDayJsObj.format(. "YYYY-MM-DD") + let todayTime = React.useMemo1(() => { + todayDayJsObj.format(. "HH:mm:ss") + }, [currentTime]) + + let initialStartTime = disableFutureDates || selectStandardTime ? "00:00:00" : "23:59:59" + let initialEndTime = disableFutureDates || selectStandardTime ? "23:59:59" : "00:00:00" + React.useEffect2(() => { + setLocalStartDate(_ => startDateVal) + setLocalEndDate(_ => endDateVal) + setLocalOpt(_ => "") + None + }, (startDateVal, endDateVal)) + + let resetStartEndInput = () => { + setLocalStartDate(_ => "") + setLocalEndDate(_ => "") + setLocalOpt(_ => "") + } + + React.useEffect2(() => { + switch dateRangeLimit { + | Some(maxLen) => { + let diff = getStartEndDiff(localStartDate, localEndDate) + if diff > (maxLen->Int.toFloat *. 24. *. 60. *. 60. -. 1.) *. 1000. { + resetStartEndInput() + } + } + + | None => () + } + None + }, (localStartDate, localEndDate)) + + let dateRangeRef = React.useRef(Nullable.null) + let dropdownRef = React.useRef(Nullable.null) + + useErroryValueResetter(startDateVal, setStartDateVal) + useErroryValueResetter(endDateVal, setEndDateVal) + + let startDate = localStartDate->getDateStringForValue(isoStringToCustomTimeZone) + let endDate = localEndDate->getDateStringForValue(isoStringToCustomTimeZone) + + let isDropdownExpandedActual = isDropdownExpanded && calendarVisibility + + let dropdownVisibilityClass = if isDropdownExpandedActual { + "inline-block" + } else { + "hidden" + } + let saveDates = () => { + if localStartDate->isNonEmptyString && localEndDate->isNonEmptyString { + setStartDateVal(_ => localStartDate) + setEndDateVal(_ => localEndDate) + } + } + let resetToInitalValues = () => { + setLocalStartDate(_ => startDateVal) + setLocalEndDate(_ => endDateVal) + setLocalOpt(_ => "") + } + + OutsideClick.useOutsideClick( + ~refs=ArrayOfRef([dateRangeRef, dropdownRef]), + ~isActive=isDropdownExpanded || calendarVisibility, + ~callback=() => { + setIsDropdownExpanded(_ => false) + setCalendarVisibility(p => !p) + if isDropdownExpandedActual && isCustomSelected { + resetToInitalValues() + } + }, + (), + ) + + let changeEndDate = (ele, isFromCustomInput, time) => { + if disableApply { + setIsDropdownExpanded(_ => false) + } + if localEndDate == ele && isFromCustomInput { + setEndDateVal(_ => "") + } else { + let endDateSplit = String.split(ele, "-") + let endDateDate = endDateSplit[2]->Option.getOr("") + let endDateYear = endDateSplit[0]->Option.getOr("") + let endDateMonth = endDateSplit[1]->Option.getOr("") + let splitTime = switch time { + | Some(val) => val + | None => + if disableFutureDates && ele == todayDate { + todayTime + } else { + initialEndTime + } + } + + let timeSplit = String.split(splitTime, ":") + let timeHour = timeSplit->Array.get(0)->Option.getOr("00") + let timeMinute = timeSplit->Array.get(1)->Option.getOr("00") + let timeSecond = timeSplit->Array.get(2)->Option.getOr("00") + let endDateTimeCheck = customTimezoneToISOString( + endDateYear, + endDateMonth, + endDateDate, + timeHour, + timeMinute, + timeSecond, + ) + setLocalEndDate(_ => TimeZoneHook.formattedISOString(endDateTimeCheck, format)) + } + } + let changeStartDate = (ele, isFromCustomInput, time) => { + let setDate = str => { + let startDateSplit = String.split(str, "-") + let startDateDay = startDateSplit[2]->Option.getOr("") + let startDateYear = startDateSplit[0]->Option.getOr("") + let startDateMonth = startDateSplit[1]->Option.getOr("") + let splitTime = switch time { + | Some(val) => val + | None => + if !disableFutureDates && ele == todayDate && !standardTimeToday { + todayTime + } else { + initialStartTime + } + } + let timeSplit = String.split(splitTime, ":") + let timeHour = timeSplit->Array.get(0)->Option.getOr("00") + let timeMinute = timeSplit->Array.get(1)->Option.getOr("00") + let timeSecond = timeSplit->Array.get(2)->Option.getOr("00") + let startDateTimeCheck = customTimezoneToISOString( + startDateYear, + startDateMonth, + startDateDay, + timeHour, + timeMinute, + timeSecond, + ) + + setLocalStartDate(_ => TimeZoneHook.formattedISOString(startDateTimeCheck, format)) + } + let resetStartDate = () => { + resetStartEndInput() + setDate(ele) + } + if startDate->isNonEmptyString && startDate == ele && isFromCustomInput { + changeEndDate(ele, isFromCustomInput, None) + } else if startDate->isNonEmptyString && startDate > ele && isFromCustomInput { + resetStartDate() + } else if endDate->isNonEmptyString && startDate == ele && isFromCustomInput { + resetStartDate() + } else if ( + ele > startDate && + ele < endDate && + startDate->isNonEmptyString && + endDate->isNonEmptyString && + isFromCustomInput + ) { + resetStartDate() + } else if ( + startDate->isNonEmptyString && + endDate->isNonEmptyString && + ele > endDate && + isFromCustomInput + ) { + resetStartDate() + } else { + () + } + + if !isFromCustomInput || startDate->isEmptyString { + setDate(ele) + } + + if ( + (startDate->isNonEmptyString && endDate->isEmptyString && !isFromCustomInput) || + (startDate->isNonEmptyString && + endDate->isEmptyString && + isStartBeforeEndDate(startDate, ele) && + isFromCustomInput) + ) { + changeEndDate(ele, isFromCustomInput, None) + } + } + + let onDateClick = str => { + let data = switch Array.find(clickedDates, x => x == str) { + | Some(_d) => Belt.Array.keep(clickedDates, x => x != str) + | None => Array.concat(clickedDates, [str]) + } + let dat = data->Array.map(x => x) + setClickedDates(_ => dat) + changeStartDate(str, true, None) + } + + let handleApply = _ev => { + setShowOption(_ => false) + setCalendarVisibility(p => !p) + setIsDropdownExpanded(_ => false) + saveDates() + } + + let cancelButton = _ => { + resetToInitalValues() + setCalendarVisibility(p => !p) + setIsDropdownExpanded(_ => false) + } + + let selectedStartDate = if localStartDate->isNonEmptyString { + getFormattedDate( + localStartDate->getDateStringForValue(isoStringToCustomTimeZone), + "YYYY-MM-DD", + ) + } else { + "" + } + let selectedEndDate = if localEndDate->isNonEmptyString { + getFormattedDate(localEndDate->getDateStringForValue(isoStringToCustomTimeZone), "YYYY-MM-DD") + } else { + "" + } + let setStartDate = (~date, ~time) => { + if date->isNonEmptyString { + let timestamp = changeTimeFormat(~date, ~time, ~customTimezoneToISOString, ~format) + setLocalStartDate(_ => timestamp) + } + } + let setEndDate = (~date, ~time) => { + if date->isNonEmptyString { + let timestamp = changeTimeFormat(~date, ~time, ~customTimezoneToISOString, ~format) + setLocalEndDate(_ => timestamp) + } + } + let startTimeInput: ReactFinalForm.fieldRenderPropsInput = { + name: "string", + onBlur: _ev => (), + onChange: timeValEv => { + let startTimeVal = timeValEv->Identity.formReactEventToString + let endTime = localEndDate->getTimeStringForValue(isoStringToCustomTimeZone) + + if localStartDate->isNonEmptyString { + if disableFutureDates && selectedStartDate == todayDate && startTimeVal > todayTime { + setStartDate(~date=startDate, ~time=todayTime) + } else if ( + disableFutureDates && selectedStartDate == selectedEndDate && startTimeVal > endTime + ) { + () + } else { + setStartDate(~date=startDate, ~time=startTimeVal) + } + } + }, + onFocus: _ev => (), + value: localStartDate->getTimeStringForValue(isoStringToCustomTimeZone)->JSON.Encode.string, + checked: false, + } + let endTimeInput: ReactFinalForm.fieldRenderPropsInput = { + name: "string", + onBlur: _ev => (), + onChange: timeValEv => { + let endTimeVal = timeValEv->Identity.formReactEventToString + let startTime = localStartDate->getTimeStringForValue(isoStringToCustomTimeZone) + if localEndDate->isNonEmptyString { + if disableFutureDates && selectedEndDate == todayDate && endTimeVal > todayTime { + setEndDate(~date=startDate, ~time=todayTime) + } else if ( + disableFutureDates && selectedStartDate == selectedEndDate && endTimeVal < startTime + ) { + () + } else { + setEndDate(~date=endDate, ~time=endTimeVal) + } + } + }, + onFocus: _ev => (), + value: localEndDate->getTimeStringForValue(isoStringToCustomTimeZone)->JSON.Encode.string, + checked: false, + } + + let startDateStr = + startDateVal->isNonEmptyString + ? getFormattedDate( + startDateVal->getDateStringForValue(isoStringToCustomTimeZone), + "MMM DD, YYYY", + ) + : buttonText->isNonEmptyString + ? buttonText + : "[From-Date]" + let endDateStr = + endDateVal->isNonEmptyString + ? getFormattedDate( + endDateVal->getDateStringForValue(isoStringToCustomTimeZone), + "MMM DD, YYYY", + ) + : buttonText->isNonEmptyString + ? "" + : "[To-Date]" + let startTimeStr = + startDateVal->isNonEmptyString + ? startDateVal->getTimeStringForValue(isoStringToCustomTimeZone) + : "00:00:00" + let endTimeStr = + startDateVal->isNonEmptyString + ? endDateVal->getTimeStringForValue(isoStringToCustomTimeZone) + : "23:59:59" + + let endTimeStr = { + let timeArr = endTimeStr->String.split(":") + let endTimeTxt = `${timeArr[0]->Option.getOr("00")}:${timeArr[1]->Option.getOr("00")}` + showSeconds ? `${endTimeTxt}:${timeArr[2]->Option.getOr("00")}` : endTimeTxt + } + let startTimeStr = { + let timeArr = startTimeStr->String.split(":") + let startTimeTxt = `${timeArr[0]->Option.getOr("00")}:${timeArr[1]->Option.getOr("00")}` + showSeconds ? `${startTimeTxt}:${timeArr[2]->Option.getOr("00")}` : startTimeTxt + } + + let buttonText = { + startDateVal->isEmptyString && endDateVal->isEmptyString + ? `Select Date ${showTime ? "and Time" : ""}` + : showTime + ? `${startDateStr} ${startTimeStr} - ${endDateStr} ${endTimeStr}` + : `${startDateStr} ${startDateStr === buttonText ? "" : "-"} ${endDateStr}` + } + + let buttonIcon = isDropdownExpanded ? "angle-up" : "angle-down" + + let handlePredefinedOptionClick = (value, disableFutureDates) => { + setIsCustomSelected(_ => false) + setCalendarVisibility(_ => false) + setIsDropdownExpanded(_ => false) + setShowOption(_ => false) + let (stDate, enDate, stTime, enTime) = DateRangeUtils.getPredefinedStartAndEndDate( + todayDayJsObj, + isoStringToCustomTimeZone, + isoStringToCustomTimezoneInFloat, + customTimezoneToISOString, + value, + disableFutureDates, + disablePastDates, + todayDate, + todayTime, + ) + + resetStartEndInput() + + setStartDate(~date=startDate, ~time=stTime) + setEndDate(~date=endDate, ~time=enTime) + setLocalOpt(_ => + DateRangeUtils.datetext(value, disableFutureDates) + ->String.toLowerCase + ->String.split(" ") + ->Array.joinWith("_") + ) + changeStartDate(stDate, false, Some(stTime)) + changeEndDate(enDate, false, Some(enTime)) + } + + let handleDropdownClick = () => { + if predefinedDays->Array.length > 0 { + if calendarVisibility { + setCalendarVisibility(_ => false) + setShowOption(_ => !isDropdownExpanded) + setIsDropdownExpanded(_ => !isDropdownExpanded) + setShowOption(_ => !isCustomSelected) + } else { + setIsDropdownExpanded(_ => true) + setShowOption(_ => true) + setCalendarVisibility(_ => true) + } + } else { + setIsDropdownExpanded(_p => !isDropdownExpanded) + setCalendarVisibility(_ => !isDropdownExpanded) + } + } + + React.useEffect4(() => { + if startDate->isNonEmptyString && endDate->isNonEmptyString { + if ( + localStartDate->isNonEmptyString && + localEndDate->isNonEmptyString && + (disableApply || !isCustomSelected) + ) { + saveDates() + } + + if disableApply { + setShowOption(_ => false) + } + } + None + }, (startDate, endDate, localStartDate, localEndDate)) + + let customStyleForBtn = "rounded-lg bg-white" + + let timeVisibilityClass = showTime ? "block" : "hidden" + + let getDiffForPredefined = predefinedDay => { + let (stDate, enDate, stTime, enTime) = DateRangeUtils.getPredefinedStartAndEndDate( + todayDayJsObj, + isoStringToCustomTimeZone, + isoStringToCustomTimezoneInFloat, + customTimezoneToISOString, + predefinedDay, + disableFutureDates, + disablePastDates, + todayDate, + todayTime, + ) + let startTimestamp = changeTimeFormat( + ~date=stDate, + ~time=stTime, + ~customTimezoneToISOString, + ~format="YYYY-MM-DDTHH:mm:00[Z]", + ) + let endTimestamp = changeTimeFormat( + ~date=enDate, + ~time=enTime, + ~customTimezoneToISOString, + ~format="YYYY-MM-DDTHH:mm:00[Z]", + ) + getStartEndDiff(startTimestamp, endTimestamp) + } + + let predefinedOptionSelected = predefinedDays->Array.find(item => { + let startDate = convertTimeStamp( + ~isoStringToCustomTimeZone, + startDateVal, + "YYYY-MM-DDTHH:mm:00[Z]", + ) + let endDate = convertTimeStamp( + ~isoStringToCustomTimeZone, + endDateVal, + "YYYY-MM-DDTHH:mm:00[Z]", + ) + let difference = getStartEndDiff(startDate, endDate) + getDiffForPredefined(item) === difference + }) + + let filteredPredefinedDays = { + switch dateRangeLimit { + | Some(limit) => + predefinedDays->Array.filter(item => { + getDiffForPredefined(item) <= (limit->Float.fromInt *. 24. *. 60. *. 60. -. 1.) *. 1000. + }) + | None => predefinedDays + } + } + + let customeRangeBg = switch predefinedOptionSelected { + | Some(_) => "bg-white dark:bg-jp-gray-lightgray_background" + | None => "bg-jp-gray-100 dark:bg-jp-gray-850" + } + + let removeApplyFilter = ev => { + ev->ReactEvent.Mouse.stopPropagation + resetToInitalValues() + setStartDateVal(_ => "") + setEndDateVal(_ => "") + } + + let buttonType: option = buttonType + + let arrowIconSize = 14 + let strokeColor = if disable { + "stroke-jp-2-light-gray-600" + } else if isDropdownExpandedActual { + "stroke-jp-2-light-gray-1700" + } else { + "stroke-jp-2-light-gray-1100" + } + + let iconElement = { +
+ + {if removeFilterOption && startDateVal->isNonEmptyString && endDateVal->isNonEmptyString { + + } else { + React.null + }} +
+ } + + let calendarElement = +
+ {if predefinedDays->Array.length > 0 && showOption { + +
+ {filteredPredefinedDays + ->Array.mapWithIndex((value, i) => { +
Int.toString} + className="w-1/3 md:w-full md:min-w-max text-center md:text-start"> + +
+ }) + ->React.array} +
{ + setCalendarVisibility(_ => true) + setIsCustomSelected(_ => true) + }}> + {React.string("Custom Range")} +
+
+
+ } else { + React.null + }} + +
+ +
+ + +
+ {if disableApply { + React.null + } else { +
+
+ }} +
+
+
+ open HeadlessUI + <> +
+ +
ReactDOM.Ref.domRef}> +
+
+ {if isDropdownExpandedActual { + if isMobileView { + + calendarElement + + } else { + +
ReactDOM.Ref.domRef} + className={`${dropdownVisibilityClass} absolute ${dropdownPosition} z-20 max-h-min max-w-min overflow-auto bg-white dark:bg-jp-gray-950 rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none mt-2`}> + calendarElement +
+
+ } + } else { + React.null + }} +
+ + } +} + +let useStateForInput = (input: ReactFinalForm.fieldRenderPropsInput) => { + React.useMemo1(() => { + let val = input.value->JSON.Decode.string->Option.getOr("") + let onChange = fn => { + let newVal = fn(val) + input.onChange(newVal->Identity.stringToFormReactEvent) + } + + (val, onChange) + }, [input]) +} + +@react.component +let make = ( + ~startKey: string, + ~endKey: string, + ~showTime=false, + ~disable=false, + ~disablePastDates=true, + ~disableFutureDates=false, + ~predefinedDays=[], + ~format="YYYY-MM-DDTHH:mm:ss.SSS[Z]", + ~numMonths=1, + ~disableApply=true, + ~removeFilterOption=false, + ~dateRangeLimit=?, + ~optFieldKey=?, + ~textHideInMobileView=true, + ~showSeconds=true, + ~hideDate=false, + ~allowedDateRange=?, + ~selectStandardTime=false, + ~buttonText="", + ~textStyle=?, + ~standardTimeToday=false, + ~removeConversion=false, + ~isTooltipVisible=true, +) => { + let startInput = ReactFinalForm.useField(startKey).input + let endInput = ReactFinalForm.useField(endKey).input + let (startDateVal, setStartDateVal) = useStateForInput(startInput) + let (endDateVal, setEndDateVal) = useStateForInput(endInput) + + +} diff --git a/src/components/DynamicFilter.res b/src/components/DynamicFilter.res index 2dc89286e..0a1728951 100644 --- a/src/components/DynamicFilter.res +++ b/src/components/DynamicFilter.res @@ -155,15 +155,13 @@ let make = ( ~tabNames: array, ~updateUrlWith=?, ~showCustomFilter=true, - ~customViewTop=React.null, + ~customLeftView=React.null, ~filterButtonStyle="", ~moduleName="", ~customFilterKey="", ~filterFieldsPortalName="navbarSecondRow", ~filtersDisplayOption=true, ~showSelectFiltersSearch=false, - ~revampedFilter=false, - ~hideFiltersDefaultValue=?, ~refreshFilters=true, ) => { open LogicUtils @@ -218,7 +216,7 @@ let make = ( }
- isNonEmptyString ? 1 : 0} - showFiltersBtn=filtersDisplayOption showSelectFiltersSearch tableName=moduleName - revampedFilter - ?hideFiltersDefaultValue - disableURIdecode=true />
} diff --git a/src/components/DynamicTable.res b/src/components/DynamicTable.res index f6954ac93..9de15922b 100644 --- a/src/components/DynamicTable.res +++ b/src/components/DynamicTable.res @@ -58,7 +58,6 @@ let make = ( ~forcePreventConcatData=false, ~collapseTableRow=false, ~showRefreshFilter=true, - ~showRemoteOptions=false, ~filterButtonStyle="", ~getRowDetails=_ => React.null, ~onMouseEnter=?, @@ -341,13 +340,11 @@ let make = ( }} - { + let {updateExistingKeys} = React.useContext(FilterContext.filterContext) + let textStyle = "text-red-900" + let leftIcon: Button.iconType = CustomIcon() + + let formState: ReactFinalForm.formState = ReactFinalForm.useFormState( + ReactFinalForm.useFormSubscription(["values", "initialValues"])->Nullable.make, + ) + + let handleClearFilter = switch clearFilters { + | Some(fn) => + _ => { + fn() + + // fn() + } + | None => + _ => { + let searchStr = + formState.values + ->JSON.Decode.object + ->Option.getOr(Dict.make()) + ->Dict.toArray + ->Belt.Array.keepMap(entry => { + let (key, value) = entry + switch defaultFilterKeys->Array.includes(key) { + | true => + switch value->JSON.Classify.classify { + | String(str) => `${key}=${str}`->Some + | Number(num) => `${key}=${num->String.make}`->Some + | Array(arr) => `${key}=[${arr->String.make}]`->Some + | _ => None + } + | false => None + } + }) + ->Array.joinWith("&") + + searchStr->FilterUtils.parseFilterString->updateExistingKeys + } + } + + let hasExtraFilters = React.useMemo2(() => { + formState.initialValues + ->JSON.Decode.object + ->Option.getOr(Dict.make()) + ->Dict.toArray + ->Array.filter(entry => { + let (key, value) = entry + let isEmptyValue = switch value->JSON.Classify.classify { + | String(str) => str->LogicUtils.isEmptyString + | Array(arr) => arr->Array.length === 0 + | Null => true + | _ => false + } + + !(defaultFilterKeys->Array.includes(key)) && !isEmptyValue + }) + ->Array.length > 0 + }, (formState.initialValues, defaultFilterKeys)) + let text = "Clear All" + + + } + + ) + ->React.array} + + + }} + } + + } + + + + 0}> + 0} /> + + + + } + +} diff --git a/src/components/FilterSelectBox.res b/src/components/FilterSelectBox.res new file mode 100644 index 000000000..5c93fdced --- /dev/null +++ b/src/components/FilterSelectBox.res @@ -0,0 +1,2281 @@ +type retType = CheckBox(array) | Radiobox(string) + +external toDict: 'a => Dict.t<'t> = "%identity" +@send external getClientRects: Dom.element => Dom.domRect = "getClientRects" +@send external focus: Dom.element => unit = "focus" + +external ffInputToSelectInput: ReactFinalForm.fieldRenderPropsInput => ReactFinalForm.fieldRenderPropsCustomInput< + array, +> = "%identity" + +external ffInputToRadioInput: ReactFinalForm.fieldRenderPropsInput => ReactFinalForm.fieldRenderPropsCustomInput< + string, +> = "%identity" + +let regex = (a, searchString) => { + let searchStringNew = + searchString + ->String.replaceRegExp(%re("/[<>\[\]';|?*\\]/g"), "") + ->String.replaceRegExp(%re("/\(/g"), "\\(") + ->String.replaceRegExp(%re("/\+/g"), "\\+") + ->String.replaceRegExp(%re("/\)/g"), "\\)") + ->String.replaceRegExp(%re("/\./g"), "") + Js.Re.fromStringWithFlags("(.*)(" ++ a ++ "" ++ searchStringNew ++ ")(.*)", ~flags="i") +} + +module ListItem = { + @react.component + let make = ( + ~isDropDown, + ~searchString, + ~multiSelect, + ~optionSize: CheckBoxIcon.size=Small, + ~isSelectedStateMinus=false, + ~isSelected, + ~isPrevSelected=false, + ~isNextSelected=false, + ~onClick, + ~text, + ~fill="#0EB025", + ~labelValue="", + ~isDisabled=false, + ~icon: Button.iconType, + ~leftVacennt=false, + ~showToggle=false, + ~customStyle="", + ~serialNumber=None, + ~isMobileView=false, + ~description=None, + ~customLabelStyle=None, + ~customMarginStyle="mx-3 py-2 gap-2", + ~listFlexDirection="", + ~customSelectStyle="", + ~textOverflowClass=?, + ~dataId, + ~showDescriptionAsTool=true, + ~optionClass="", + ~selectClass="", + ~toggleProps="", + ~checkboxDimension="", + ~iconStroke="", + ~showToolTipOptions=false, + ~textEllipsisForDropDownOptions=false, + ~textColorClass="", + ) => { + let {globalUIConfig: {font}} = React.useContext(ConfigContext.configContext) + let labelText = switch labelValue->String.length { + | 0 => text + | _ => labelValue + } + let (toggleSelect, setToggleSelect) = React.useState(() => isSelected) + let listText = + searchString->LogicUtils.isEmptyString + ? [text] + : { + switch Js.String2.match_(text, regex("\\b", searchString)) { + | Some(r) => r->Array.sliceToEnd(~start=1)->Belt.Array.keepMap(x => x) + | None => + switch Js.String2.match_(text, regex("_", searchString)) { + | Some(a) => a->Array.sliceToEnd(~start=1)->Belt.Array.keepMap(x => x) + | None => [text] + } + } + } + + let bgClass = "md:bg-jp-gray-100 md:dark:bg-jp-gray-text_darktheme md:dark:bg-opacity-3 dark:hover:text-white dark:text-white" + + let hoverClass = "hover:bg-jp-gray-100 dark:hover:bg-jp-gray-text_darktheme dark:hover:bg-opacity-10 dark:hover:text-white dark:text-white" + + let customMarginStyle = if isMobileView { + "py-2 gap-2" + } else if !isDropDown { + "mr-3 py-2 gap-2" + } else { + customMarginStyle + } + let backgroundClass = if showToggle { + "" + } else if isSelected && customStyle->LogicUtils.isNonEmptyString { + customSelectStyle + } else if isDropDown && isSelected && !isDisabled { + `${bgClass} transition ease-[cubic-bezier(0.33, 1, 0.68, 1)]` + } else { + hoverClass + } + + let justifyClass = if isDropDown { + "justify-between" + } else { + "" + } + let selectedClass = if isSelected { + "text-opacity-100 dark:text-opacity-100" + } else if isDisabled { + "text-opacity-50 dark:text-opacity-50" + } else { + "text-opacity-75 dark:text-opacity-75" + } + let leftElementClass = if leftVacennt { + "px-4 " + } else { + "" + } + + let labelStyle = customLabelStyle->Option.isSome ? customLabelStyle->Option.getOr("") : "" + + let onToggleSelect = val => { + if !isDisabled { + setToggleSelect(_ => val) + } + } + React.useEffect1(() => { + setToggleSelect(_ => isSelected) + None + }, [isSelected]) + let cursorClass = if showToggle || !isDropDown { + "" + } else if isDisabled { + "cursor-not-allowed" + } else { + "cursor-pointer" + } + let paddingClass = showToggle ? "pr-6 mr-4" : "pr-2" + let onClickTemp = if showToggle { + _ => () + } else { + onClick + } + let parentRef = React.useRef(Nullable.null) + + let textColor = "text-jp-2-gray-300" + + let textColor = if textColorClass->LogicUtils.isNonEmptyString { + textColorClass + } else { + textColor + } + + let itemRoundedClass = "" + let toggleClass = if showToggle { + "" + } else if multiSelect { + "pr-2" + } else { + "pl-2" + } + let textGap = "" + + let selectedNoBadgeColor = "bg-blue-500" + let optionIconStroke = "" + + let optionTextSize = "text-fs-14" + + let searchMatchTextColor = `dark:${font.textColor.primaryNormal} ${font.textColor.primaryNormal}` + let optionDescPadding = if optionSize === Small { + showToggle ? "pl-12" : "pl-7" + } else if showToggle { + "pl-15" + } else { + "pl-9" + } + + let overFlowTextCustomClass = switch textOverflowClass { + | Some(val) => val + | None => "overflow-hidden" + } + + let customCss = + listFlexDirection->LogicUtils.isEmptyString ? `flex-row ${paddingClass}` : listFlexDirection + RippleEffectBackground.useLinearRippleHook(parentRef, isDropDown) + let comp = + Int.toString), + ("data-dropdown-value", labelText), + ("data-dropdown-value-selected", {isSelected} ? "True" : "False"), + ]> +
ReactDOM.Ref.domRef} + onClick=onClickTemp + className={`flex relative mx-2 md:mx-0 my-3 md:my-0 pr-2 md:pr-0 md:w-full items-center font-medium ${overFlowTextCustomClass} ${itemRoundedClass} ${textColor} ${justifyClass} ${cursorClass} ${backgroundClass} ${selectedClass} ${customStyle} ${customCss} `}> + {if !isDropDown { + if showToggle { +
+ +
+ } else if multiSelect { + + {checkboxDimension->LogicUtils.isNonEmptyString + ? + : } + + } else { +
+ +
+ } + } else if multiSelect && !isMobileView { + + + + } else { + React.null + }} +
+
+ {switch icon { + | FontAwesome(iconName) => + LogicUtils.isEmptyString + ? optionIconStroke + : iconStroke} `} + size={20} + name=iconName + /> + | CustomIcon(ele) => ele + | Euler(iconName) => + + | _ => React.null + }} +
+ {listText + ->Array.filter(str => str->LogicUtils.isNonEmptyString) + ->Array.mapWithIndex((item, i) => { + if ( + (String.toLowerCase(item) == String.toLowerCase(searchString) || + String.toLowerCase(item) == String.toLowerCase("_" ++ searchString)) && + String.length(searchString) > 0 + ) { + Int.toString} attributes=[("data-searched-text", item)]> + Int.toString} className={`${searchMatchTextColor} bg-transparent`}> + {item->React.string} + + + } else { + let className = isSelected ? `${selectClass}` : `${optionClass}` + + let textClass = if textEllipsisForDropDownOptions { + `${className} text-ellipsis overflow-hidden ` + } else { + className + } + + let selectOptions = + Int.toString}> + Int.toString} className=textClass value=labelText> + {item->React.string} + + + + { + if showToolTipOptions { + Int.toString} + description=item + toolTipFor=selectOptions + contentAlign=Default + justifyClass="justify-start" + /> + } else { + selectOptions + } + } + } + }) + ->React.array} +
+
+ {switch icon { + | CustomRightIcon(ele) => ele + | _ => React.null + }} +
+ {if isMobileView && isDropDown { + if multiSelect { + + } else { + + } + } else if isDropDown { +
+ +
+ } else { + React.null + }} + {switch serialNumber { + | Some(sn) => + +
+ {React.string(sn)} +
+
+ | None => React.null + }} +
+
+ <> + {switch description { + | Some(str) => + if isDropDown { + showDescriptionAsTool + ? { + + } + : { +
+ comp +
{React.string(str)}
+
+ } + } else { + <> + comp +
+ {str->React.string} +
+ + } + + | None => comp + }} + + } +} + +type dropdownOptionWithoutOptional = { + label: string, + value: string, + isDisabled: bool, + icon: Button.iconType, + description: option, + iconStroke: string, + textColor: string, + optGroup: string, +} +type dropdownOption = { + label: string, + value: string, + optGroup?: string, + isDisabled?: bool, + icon?: Button.iconType, + description?: string, + iconStroke?: string, + textColor?: string, +} + +let makeNonOptional = (dropdownOption: dropdownOption): dropdownOptionWithoutOptional => { + { + label: dropdownOption.label, + value: dropdownOption.value, + isDisabled: dropdownOption.isDisabled->Option.getOr(false), + icon: dropdownOption.icon->Option.getOr(NoIcon), + description: dropdownOption.description, + iconStroke: dropdownOption.iconStroke->Option.getOr(""), + textColor: dropdownOption.textColor->Option.getOr(""), + optGroup: dropdownOption.optGroup->Option.getOr("-"), + } +} + +let useTransformed = options => { + React.useMemo1(() => { + options->Array.map(makeNonOptional) + }, [options]) +} + +type allSelectType = Icon | Text + +type opt = {name_: string} + +let makeOptions = (options: array): array => { + options->Array.map(str => {label: str, value: str}) +} + +module BaseSelect = { + @react.component + let make = ( + ~showSelectAll=true, + ~showDropDown=false, + ~isDropDown=true, + ~options: array, + ~optionSize: CheckBoxIcon.size=Small, + ~isSelectedStateMinus=false, + ~onSelect: array => unit, + ~value as values: JSON.t, + ~onBlur=?, + ~showClearAll=true, + ~isHorizontal=false, + ~customLabelStyle=?, + ~showToggle=false, + ~showSerialNumber=false, + ~heading="Some heading", + ~showSelectionAsChips=true, + ~maxHeight="md:max-h-72", + ~searchable=?, + ~optionRigthElement=?, + ~searchInputPlaceHolder="", + ~showSearchIcon=true, + ~customStyle="", + ~customMargin="", + ~disableSelect=false, + ~deselectDisable=?, + ~hideBorder=false, + ~allSelectType=Icon, + ~isMobileView=false, + ~isModalView=false, + ~customSearchStyle="bg-jp-gray-100 dark:bg-jp-gray-950 p-2", + ~hasApplyButton=false, + ~setShowDropDown=?, + ~dropdownCustomWidth="w-full md:max-w-md min-w-[10rem]", + ~sortingBasedOnDisabled=true, + ~customMarginStyle="mx-3 py-2 gap-2", + ~listFlexDirection="", + ~onApply=?, + ~showAllSelectedOptions=true, + ~showDescriptionAsTool=true, + ~optionClass="", + ~selectClass="", + ~toggleProps="", + ~showSelectCountButton=false, + ~customSelectAllStyle="", + ~checkboxDimension="", + ~dropdownClassName="", + ~onItemSelect=(_, _) => (), + ~wrapBasis="", + ~preservedAppliedOptions=[], + ) => { + let customSearchStyle = "bg-white p-2 border-b-2" + let {globalUIConfig: {font}} = React.useContext(ConfigContext.configContext) + let (searchString, setSearchString) = React.useState(() => "") + let maxHeight = if maxHeight->String.includes("72") { + "md:max-h-66.5" + } else { + maxHeight + } + + let saneValue = React.useMemo1(() => + switch values->JSON.Decode.array { + | Some(jsonArr) => jsonArr->LogicUtils.getStrArrayFromJsonArray + | _ => [] + } + , [values]) + + let initialSelectedOptions = React.useMemo0(() => { + options->Array.filter(item => saneValue->Array.includes(item.value)) + }) + + options->Array.sort((item1, item2) => { + let item1Index = initialSelectedOptions->Array.findIndex(item => item.label === item1.label) + let item2Index = initialSelectedOptions->Array.findIndex(item => item.label === item2.label) + + item1Index <= item2Index ? 1. : -1. + }) + + let transformedOptions = useTransformed(options) + + let (filteredOptions, setFilteredOptions) = React.useState(() => transformedOptions) + React.useEffect1(() => { + setFilteredOptions(_ => transformedOptions) + None + }, [transformedOptions]) + React.useEffect1(() => { + let shouldDisplay = (option: dropdownOption) => { + switch Js.String2.match_(option.label, regex("\\b", searchString)) { + | Some(_) => true + | None => + switch Js.String2.match_(option.label, regex("_", searchString)) { + | Some(_) => true + | None => false + } + } + } + let filterOptions = options->Array.filter(shouldDisplay)->Array.map(makeNonOptional) + + setFilteredOptions(_ => filterOptions) + None + }, [searchString]) + + let onItemClick = (itemDataValue, isDisabled, e) => { + if !isDisabled { + let data = if Array.includes(saneValue, itemDataValue) { + let values = + deselectDisable->Option.getOr(false) + ? saneValue + : saneValue->Array.filter(x => x !== itemDataValue) + onItemSelect(e, itemDataValue)->ignore + values + } else { + Array.concat(saneValue, [itemDataValue]) + } + onSelect(data) + switch onBlur { + | Some(fn) => + "blur"->Webapi.Dom.FocusEvent.make->Identity.webAPIFocusEventToReactEventFocus->fn + | None => () + } + } + } + + let handleSearch = str => { + setSearchString(_ => str) + } + + let selectAll = (select, _ev) => { + let newValues = if select { + let newVal = + filteredOptions + ->Array.filter(x => !x.isDisabled && !(saneValue->Array.includes(x.value))) + ->Array.map(x => x.value) + Array.concat(saneValue, newVal) + } else { + [] + } + + onSelect(newValues) + switch onBlur { + | Some(fn) => + "blur"->Webapi.Dom.FocusEvent.make->Identity.webAPIFocusEventToReactEventFocus->fn + | None => () + } + } + + let borderClass = if !hideBorder { + if isDropDown { + `bg-white border dark:bg-jp-gray-lightgray_background border-jp-gray-lightmode_steelgray border-opacity-75 dark:border-jp-gray-960 + rounded-lg animate-textTransition transition duration-400` + } else if showToggle { + "bg-white border rounded dark:bg-jp-gray-darkgray_background border-jp-gray-lightmode_steelgray border-opacity-75 dark:border-jp-gray-960" + } else { + "" + } + } else { + "" + } + + let minWidth = isDropDown ? "min-w-65" : "" + let widthClass = if showToggle { + "" + } else if isMobileView { + "w-full" + } else { + `${minWidth} ${dropdownCustomWidth}` + } + let textIconPresent = options->Array.some(op => op.icon->Option.getOr(NoIcon) !== NoIcon) + + let _ = if sortingBasedOnDisabled { + options->Array.toSorted((m1, m2) => { + let m1Disabled = m1.isDisabled->Option.getOr(false) + let m2Disabled = m2.isDisabled->Option.getOr(false) + if m1Disabled === m2Disabled { + 0. + } else if m1Disabled { + 1. + } else { + -1. + } + }) + } else { + options + } + + let noOfSelected = saneValue->Array.length + let applyBtnDisabled = + noOfSelected === preservedAppliedOptions->Array.length && + saneValue->Array.reduce(true, (acc, val) => { + preservedAppliedOptions->Array.includes(val) && acc + }) + + let searchRef = React.useRef(Nullable.null) + let (isChooseAllToggleSelected, setChooseAllToggleSelected) = React.useState(() => false) + let gapClass = switch optionRigthElement { + | Some(_) => "flex gap-4" + | None => "" + } + + let form = ReactFinalForm.useForm() + + let onClick = ev => { + form.submit()->ignore + + switch setShowDropDown { + | Some(fn) => fn(_ => false) + | None => () + } + + switch onApply { + | Some(fn) => fn(ev) + | None => () + } + } + + React.useEffect2(() => { + searchRef.current->Nullable.toOption->Option.forEach(input => input->focus) + None + }, (searchRef.current, showDropDown)) + + let listPadding = "" + + React.useEffect2(() => { + if noOfSelected === options->Array.length { + setChooseAllToggleSelected(_ => true) + } else { + setChooseAllToggleSelected(_ => false) + } + None + }, (noOfSelected, options)) + let toggleSelectAll = val => { + if !disableSelect { + selectAll(val, "") + + setChooseAllToggleSelected(_ => val) + } + } + let disabledClass = disableSelect ? "cursor-not-allowed" : "" + + let marginClass = if customMargin->LogicUtils.isEmptyString { + "mt-4" + } else { + customMargin + } + let dropdownAnimation = showDropDown + ? "animate-textTransition transition duration-400" + : "animate-textTransitionOff transition duration-400" + let searchInputUI = +
+
+ LogicUtils.isEmptyString + ? "Search..." + : searchInputPlaceHolder} + showSearchIcon + /> +
+
+ let animationClass = isModalView ? `` : dropdownAnimation + let outerClass = if isModalView { + "h-full" + } else if isDropDown { + "overflow-auto" + } else { + "" + } + +
+ {switch searchable { + | Some(val) => + if val { + searchInputUI + } else { + React.null + } + | None => + if isDropDown && options->Array.length > 5 { + searchInputUI + } else { + React.null + } + }} + {if showSelectAll && isDropDown { + if !isMobileView { + let clearAllCondition = noOfSelected > 0 + Array.length > 1 && + filteredOptions->Array.find(item => item.value === "Loading...")->Option.isNone}> +
+ + {{clearAllCondition ? "Clear All" : "Select All"}->React.string} +
+
+ } else { +
Array.length)} + className={`flex ${isHorizontal + ? "flex-col" + : "flex-row"} justify-between pr-4 pl-5 pt-6 pb-1 text-base font-semibold ${font.textColor.primaryNormal} cursor-pointer`}> + {"SELECT ALL"->React.string} + Array.length} /> +
+ } + } else { + React.null + }} + {if showToggle { +
+
+
+ {React.string(heading)} +
+ {if showSelectAll { +
+ {switch allSelectType { + | Icon => + + | Text => + +
{ + toggleSelectAll(!isChooseAllToggleSelected) + }}> + {if isChooseAllToggleSelected { + "Deselect All"->React.string + } else { + "Select All"->React.string + }} +
+
+ }} +
+ } else { + React.null + }} +
+ {if !hideBorder { +
+ } else { + React.null + }} +
+ } else { + React.null + }} +
LogicUtils.isEmptyString ? "" : " flex flex-wrap justify-between" + }}> + {if filteredOptions->Array.length === 0 { +
+ {React.string("No matching records found")} +
+ } else if filteredOptions->Array.find(item => item.value === "Loading...")->Option.isSome { + + } else { + { + filteredOptions + ->Array.mapWithIndex((item, indx) => { + let valueToConsider = item.value + let index = Array.findIndex(saneValue, sv => sv === valueToConsider) + let isPrevSelected = switch filteredOptions->Array.get(indx - 1) { + | Some(prevItem) => Array.findIndex(saneValue, sv => sv === prevItem.value) > -1 + | None => false + } + let isNextSelected = switch filteredOptions->Array.get(indx + 1) { + | Some(nextItem) => Array.findIndex(saneValue, sv => sv === nextItem.value) > -1 + | None => false + } + let isSelected = index > -1 + let serialNumber = + isSelected && showSerialNumber ? Some(Int.toString(index + 1)) : None + let leftVacennt = isDropDown && textIconPresent && item.icon === NoIcon +
+ + {switch optionRigthElement { + | Some(rightElement) => rightElement + | None => React.null + }} +
+ }) + ->React.array + } + }} +
+
+ } +} + +module BaseSelectButton = { + @react.component + let make = ( + ~showDropDown=false, + ~isDropDown=true, + ~isHorizontal=false, + ~options: array, + ~optionSize: CheckBoxIcon.size=Small, + ~isSelectedStateMinus=false, + ~onSelect: string => unit, + ~value: JSON.t, + ~deselectDisable=false, + ~onBlur=?, + ~setShowDropDown=?, + ~onAssignClick=?, + ~customSearchStyle, + ~disableSelect=false, + ~isMobileView=false, + ~hideAssignBtn=false, + ~searchInputPlaceHolder="", + ~showSearchIcon=true, + ~allowButtonTextMinWidth=?, + ) => { + let options = useTransformed(options) + let (searchString, setSearchString) = React.useState(() => "") + let (itemdata, setItemData) = React.useState(() => "") + let (assignButtonState, setAssignButtonState) = React.useState(_ => false) + let searchRef = React.useRef(Nullable.null) + let onItemClick = (itemData, _ev) => { + if !disableSelect { + let isSelected = value->JSON.Decode.string->Option.mapOr(false, str => itemData === str) + + if isSelected && !deselectDisable { + onSelect("") + } else { + setItemData(_ => itemData) + onSelect(itemData) + } + setAssignButtonState(_ => true) + + switch onBlur { + | Some(fn) => + "blur"->Webapi.Dom.FocusEvent.make->Identity.webAPIFocusEventToReactEventFocus->fn + | None => () + } + } + } + + React.useEffect2(() => { + searchRef.current->Nullable.toOption->Option.forEach(input => input->focus) + None + }, (searchRef.current, showDropDown)) + + let handleSearch = str => { + setSearchString(_ => str) + } + + let searchable = isDropDown && options->Array.length > 5 + + let width = isHorizontal ? "w-auto" : "w-full md:w-72" + let inlineClass = isHorizontal ? "inline-flex" : "" + + let textIconPresent = options->Array.some(op => op.icon !== NoIcon) + + let onButtonClick = itemdata => { + switch onAssignClick { + | Some(fn) => fn(itemdata) + | None => () + } + switch setShowDropDown { + | Some(fn) => fn(_ => false) + | None => () + } + } + + let listPadding = "" + let optionsOuterClass = !isDropDown ? "" : "md:max-h-72 overflow-auto" + let overflowClass = !isDropDown ? "" : "overflow-auto" + +
+ {if searchable { +
+
+ LogicUtils.isEmptyString + ? "Search..." + : searchInputPlaceHolder} + showSearchIcon + /> +
+
+ } else { + React.null + }} +
+ {options + ->Array.mapWithIndex((option, i) => { + let isSelected = switch value->JSON.Decode.string { + | Some(str) => option.value === str + | None => false + } + + let shouldDisplay = { + switch Js.String2.match_(option.label, regex("\\b", searchString)) { + | Some(_) => true + | None => + switch Js.String2.match_(option.label, regex("_", searchString)) { + | Some(_) => true + | None => false + } + } + } + + let leftVacennt = isDropDown && textIconPresent && option.icon === NoIcon + if shouldDisplay { + + } else { + React.null + } + }) + ->React.array} +
+ {if !hideAssignBtn { +
+
+ } else { + React.null + }} +
+ } +} + +module RenderListItemInBaseRadio = { + @react.component + let make = ( + ~newOptions: array, + ~value, + ~descriptionOnHover, + ~isDropDown, + ~textIconPresent, + ~searchString, + ~optionSize, + ~isSelectedStateMinus, + ~onItemClick, + ~fill, + ~customStyle, + ~isMobileView, + ~listFlexDirection, + ~customSelectStyle, + ~textOverflowClass, + ~showToolTipOptions, + ~textEllipsisForDropDownOptions, + ~isHorizontal, + ~customMarginStyleOfListItem="mx-3 py-2 gap-2", + ) => { + newOptions + ->Array.mapWithIndex((option, i) => { + let isSelected = switch value->JSON.Decode.string { + | Some(str) => option.value === str + | None => false + } + + let description = descriptionOnHover ? option.description : None + let leftVacennt = isDropDown && textIconPresent && option.icon === NoIcon + let listItemComponent = + + + if !descriptionOnHover { + switch option.description { + | Some(str) => +
Int.toString} className="flex flex-row"> + listItemComponent + + + +
} + /> + +
+ | None => listItemComponent + } + } else { + listItemComponent + } + }) + ->React.array + } +} + +let getHashMappedOptionValues = (options: array) => { + let hashMappedOptions = options->Array.reduce(Dict.make(), ( + acc, + ele: dropdownOptionWithoutOptional, + ) => { + if acc->Dict.get(ele.optGroup)->Option.isNone { + acc->Dict.set(ele.optGroup, [ele]) + } else { + acc->Dict.get(ele.optGroup)->Option.getOr([])->Array.push(ele)->ignore + } + acc + }) + + hashMappedOptions +} + +let getSortedKeys = hashMappedOptions => { + hashMappedOptions + ->Dict.keysToArray + ->Array.toSorted((a, b) => { + switch (a, b) { + | ("-", _) => 1. + | (_, "-") => -1. + | (_, _) => String.compare(a, b) + } + }) +} + +module BaseRadio = { + @react.component + let make = ( + ~showDropDown=false, + ~isDropDown=true, + ~isHorizontal=false, + ~options: array, + ~optionSize: CheckBoxIcon.size=Small, + ~isSelectedStateMinus=false, + ~onSelect: string => unit, + ~value: JSON.t, + ~deselectDisable=false, + ~onBlur=?, + ~fill="#0EB025", + ~customStyle="", + ~searchable=?, + ~isMobileView=false, + ~customSearchStyle="bg-jp-gray-100 dark:bg-jp-gray-950 p-2", + ~descriptionOnHover=false, + ~addDynamicValue=false, + ~dropdownCustomWidth="w-80", + ~dropdownRef=?, + ~showMatchingRecordsText=true, + ~fullLength=false, + ~selectedString="", + ~setSelectedString=_ => (), + ~setExtSearchString=_ => (), + ~listFlexDirection="", + ~baseComponentCustomStyle="", + ~customSelectStyle="", + ~maxHeight="md:max-h-72", + ~textOverflowClass=?, + ~searchInputPlaceHolder="", + ~showSearchIcon=true, + ~showToolTipOptions=false, + ~textEllipsisForDropDownOptions=false, + ) => { + let options = React.useMemo1(() => { + options->Array.map(makeNonOptional) + }, [options]) + + let hashMappedOptions = getHashMappedOptionValues(options) + + let isNonGrouped = + hashMappedOptions->Dict.get("-")->Option.getOr([])->Array.length === options->Array.length + + let (optgroupKeys, setOptgroupKeys) = React.useState(_ => getSortedKeys(hashMappedOptions)) + + let (searchString, setSearchString) = React.useState(() => "") + React.useEffect1(() => { + setExtSearchString(_ => searchString) + None + }, [searchString]) + + OutsideClick.useOutsideClick( + ~refs={ArrayOfRef([dropdownRef->Option.getOr(React.useRef(Nullable.null))])}, + ~isActive=showDropDown, + ~callback=() => { + setSearchString(_ => "") + }, + (), + ) + let onItemClick = (itemData, isDisabled, _ev) => { + if !isDisabled { + let isSelected = value->JSON.Decode.string->Option.mapOr(false, str => itemData === str) + + if isSelected && !deselectDisable { + setSelectedString(_ => "") + onSelect("") + } else { + if ( + addDynamicValue && !(options->Array.map(item => item.value)->Array.includes(itemData)) + ) { + setSelectedString(_ => itemData) + } else if selectedString->LogicUtils.isNonEmptyString { + setSelectedString(_ => "") + } + + onSelect(itemData) + } + setSearchString(_ => "") + switch onBlur { + | Some(fn) => + "blur"->Webapi.Dom.FocusEvent.make->Identity.webAPIFocusEventToReactEventFocus->fn + | None => () + } + } + } + let handleSearch = str => { + setSearchString(_ => str) + } + + let isSearchable = + isDropDown && + switch searchable { + | Some(isSearch) => isSearch + | None => options->Array.length > 5 + } + let widthClass = + isMobileView || !isSearchable ? "w-auto" : fullLength ? "w-full" : dropdownCustomWidth + + let searchRef = React.useRef(Nullable.null) + + let width = + isHorizontal || !isDropDown || customStyle->LogicUtils.isEmptyString + ? widthClass + : customStyle + + let inlineClass = isHorizontal ? "inline-flex" : "" + + let textIconPresent = options->Array.some(op => op.icon !== NoIcon) + + React.useEffect2(() => { + searchRef.current->Nullable.toOption->Option.forEach(input => input->focus) + None + }, (searchRef.current, showDropDown)) + + let roundedClass = "" + let listPadding = "" + + let dropDownbgClass = isDropDown ? "bg-white" : "" + let shouldDisplay = (option: dropdownOptionWithoutOptional) => { + switch Js.String2.match_(option.label, regex("\\b", searchString)) { + | Some(_) => true + | None => + switch Js.String2.match_(option.label, regex("_", searchString)) { + | Some(_) => true + | None => false + } + } + } + + let newOptions = React.useMemo3(() => { + let options = if selectedString->LogicUtils.isNonEmptyString { + options->Array.concat([selectedString]->makeOptions->Array.map(makeNonOptional)) + } else { + options + } + if searchString->String.length != 0 { + let options = options->Array.filter(option => { + shouldDisplay(option) + }) + if ( + addDynamicValue && !(options->Array.map(item => item.value)->Array.includes(searchString)) + ) { + if isNonGrouped { + options->Array.concat([searchString]->makeOptions->Array.map(makeNonOptional)) + } else { + options + } + } else { + let hashMappedSearchedOptions = getHashMappedOptionValues(options) + let optgroupKeysForSearch = getSortedKeys(hashMappedSearchedOptions) + setOptgroupKeys(_ => optgroupKeysForSearch) + options + } + } else { + setOptgroupKeys(_ => getSortedKeys(hashMappedOptions)) + options + } + }, (searchString, options, selectedString)) + let overflowClass = !isDropDown ? "" : "overflow-auto" + + let searchInputUI = +
+
+ LogicUtils.isEmptyString + ? "Search..." + : searchInputPlaceHolder} + showSearchIcon + /> +
+
+
+ {switch searchable { + | Some(val) => searchInputUI + | None => + Array.length > 5 || addDynamicValue)}> + searchInputUI + + }} +
+ {if newOptions->Array.length === 0 && showMatchingRecordsText { +
+ {React.string("No matching records found")} +
+ } else if isNonGrouped { + + } else { + { + optgroupKeys + ->Array.mapWithIndex((ele, index) => { + Int.toString}> +

{ele->React.string}

+ Dict.get(ele) + ->Option.getOr([])} + value + descriptionOnHover + isDropDown + textIconPresent + searchString + optionSize + isSelectedStateMinus + onItemClick + fill + customStyle + isMobileView + listFlexDirection + customSelectStyle + textOverflowClass + showToolTipOptions + textEllipsisForDropDownOptions + isHorizontal + customMarginStyleOfListItem="ml-8 mx-3 py-2 gap-2" + /> +
+ }) + ->React.array + } + }} +
+
+ } +} + +module InfraSelectBox = { + @react.component + let make = ( + ~options: array, + ~input: ReactFinalForm.fieldRenderPropsInput, + ~deselectDisable=false, + ~allowMultiSelect=true, + ~borderRadius="rounded-full", + ~selectedClass="border-jp-gray-600 dark:border-jp-gray-800 text-jp-gray-850 dark:text-jp-gray-400", + ~nonSelectedClass="border-jp-gray-900 dark:border-jp-gray-300 text-jp-gray-900 dark:text-jp-gray-300 font-semibold", + ~showTickMark=true, + ) => { + let transformedOptions = useTransformed(options) + + let newInputSelect = input->ffInputToSelectInput + let values = newInputSelect.value + let saneValue = React.useMemo1(() => + switch values->JSON.Decode.array { + | Some(jsonArr) => jsonArr->LogicUtils.getStrArrayFromJsonArray + | _ => [] + } + , [values]) + + let onItemClick = (itemDataValue, isDisabled) => { + if !isDisabled { + if allowMultiSelect { + let data = if Array.includes(saneValue, itemDataValue) { + if deselectDisable { + saneValue + } else { + saneValue->Array.filter(x => x !== itemDataValue) + } + } else { + Array.concat(saneValue, [itemDataValue]) + } + newInputSelect.onChange(data) + } else { + newInputSelect.onChange([itemDataValue]) + } + } + } + +
+ {transformedOptions + ->Array.mapWithIndex((option, i) => { + let isSelected = saneValue->Array.includes(option.value) + let selectedClass = isSelected ? selectedClass : nonSelectedClass + +
onItemClick(option.value, option.isDisabled)} + className={`px-4 py-1 border ${borderRadius} flex flex-row gap-2 items-center cursor-pointer ${selectedClass}`}> + {if isSelected && showTickMark { + + } else { + React.null + }} + {React.string(option.label)} +
+ }) + ->React.array} +
+ } +} + +type direction = + | BottomLeft + | BottomMiddle + | BottomRight + | TopLeft + | TopMiddle + | TopRight + +module BaseDropdown = { + @react.component + let make = ( + ~buttonText, + ~buttonSize=Button.Small, + ~allowMultiSelect, + ~input, + ~showClearAll=true, + ~showSelectAll=true, + ~options: array, + ~optionSize: CheckBoxIcon.size=Small, + ~isSelectedStateMinus=false, + ~hideMultiSelectButtons, + ~deselectDisable=?, + ~buttonType=Button.SecondaryFilled, + ~baseComponent=?, + ~baseComponentMethod=?, + ~disableSelect=false, + ~textStyle=?, + ~buttonTextWeight=?, + ~defaultLeftIcon: Button.iconType=NoIcon, + ~autoApply=true, + ~fullLength=false, + ~customButtonStyle="", + ~onAssignClick=?, + ~fixedDropDownDirection=?, + ~addButton=false, + ~marginTop="mt-12", //to position dropdown below the button, + ~customStyle="", + ~customSearchStyle="bg-jp-gray-100 dark:bg-jp-gray-950 p-2", + ~showSelectionAsChips=true, + ~showToolTip=false, + ~showNameAsToolTip=false, + ~searchable=?, + ~showBorder=?, + ~dropDownCustomBtnClick=false, + ~showCustomBtnAtEnd=false, + ~customButton=React.null, + ~descriptionOnHover=false, + ~addDynamicValue=false, + ~showMatchingRecordsText=true, + ~hasApplyButton=false, + ~dropdownCustomWidth=?, + ~customMarginStyle=?, + ~customButtonLeftIcon: option=?, + ~customTextPaddingClass=?, + ~customButtonPaddingClass=?, + ~customButtonIconMargin=?, + ~buttonStyleOnDropDownOpened="", + ~selectedString="", + ~setSelectedString=_ => (), + ~setExtSearchString=_ => (), + ~listFlexDirection="", + ~ellipsisOnly=false, + ~isPhoneDropdown=false, + ~onApply=?, + ~showAllSelectedOptions=true, + ~buttonClickFn=?, + ~showSelectCountButton=false, + ~maxHeight=?, + ~customBackColor=?, + ~showToolTipOptions=false, + ~textEllipsisForDropDownOptions=false, + ~showBtnTextToolTip=false, + ~dropdownClassName="", + ~searchInputPlaceHolder="", + ~showSearchIcon=true, + ~sortingBasedOnDisabled=?, + ) => { + let transformedOptions = useTransformed(options) + let isMobileView = MatchMedia.useMobileChecker() + let isSelectTextDark = React.useContext( + DropdownTextWeighContextWrapper.selectedTextWeightContext, + ) + let isFilterSection = React.useContext(TableFilterSectionContext.filterSectionContext) + let {removeKeys, filterKeys, setfilterKeys} = React.useContext(FilterContext.filterContext) + let showBorder = isFilterSection && !isMobileView ? Some(false) : showBorder + + let dropdownOuterClass = "bg-white dark:bg-jp-gray-950 rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" + + let newInputSelect = input->ffInputToSelectInput + let newInputRadio = input->ffInputToRadioInput + let isMobileView = MatchMedia.useMobileChecker() + let (showDropDown, setShowDropDown) = React.useState(() => false) + let (isGrowDown, setIsGrowDown) = React.useState(_ => false) + let (isInitialRender, setIsInitialRender) = React.useState(_ => true) + let selectBoxRef = React.useRef(Nullable.null) + let dropdownRef = React.useRef(Nullable.null) + let selectBtnRef = React.useRef(Nullable.null) + let (preservedAppliedOptions, setPreservedAppliedOptions) = React.useState(_ => + newInputSelect.value->LogicUtils.getStrArryFromJson + ) + + let onApply = ev => { + switch onApply { + | Some(fn) => fn(ev) + | None => () + } + + setPreservedAppliedOptions(_ => newInputSelect.value->LogicUtils.getStrArryFromJson) + } + + let clearBtnRef = React.useRef(Nullable.null) + + React.useEffect1(() => { + setShowDropDown(_ => false) + None + }, [dropDownCustomBtnClick]) + + let refs = autoApply + ? [selectBoxRef, dropdownRef] + : [selectBoxRef, dropdownRef, selectBtnRef, clearBtnRef] + OutsideClick.useOutsideClick( + ~refs=ArrayOfRef(refs), + ~isActive=showDropDown, + ~callback=() => { + setShowDropDown(_ => false) + hasApplyButton ? newInputSelect.onChange(preservedAppliedOptions) : () + }, + (), + ) + let onClick = _ => { + switch buttonClickFn { + | Some(fn) => fn(input.name) + | None => () + } + setShowDropDown(_ => !showDropDown) + setIsGrowDown(_ => true) + let _id = setTimeout(() => setIsGrowDown(_ => false), 250) + if isInitialRender { + setIsInitialRender(_ => false) + } + } + + let removeOption = (text, _ev) => { + let actualValue = switch Array.find(transformedOptions, option => option.value == text) { + | Some(str) => str.value + | None => "" + } + newInputSelect.onChange( + switch newInputSelect.value->JSON.Decode.array { + | Some(jsonArr) => + jsonArr->LogicUtils.getStrArrayFromJsonArray->Array.filter(str => str !== actualValue) + | _ => [] + }, + ) + } + + let downArrowIcon = "angle-down-outline" + let arrowIconSize = 24 + + let dropDowntext = allowMultiSelect + ? buttonText + : switch newInputRadio.value->JSON.Decode.string { + | Some(str) => + switch transformedOptions->Array.find(x => x.value === str) { + | Some(x) => x.label + | None => buttonText + } + | None => buttonText + } + + let dropDirection = React.useMemo1(() => { + switch fixedDropDownDirection { + | Some(dropDownDirection) => dropDownDirection + | None => + selectBoxRef.current + ->Nullable.toOption + ->Option.flatMap(elem => elem->getClientRects->toDict->Dict.get("0")) + ->Option.flatMap(firstEl => { + let bottomVacent = Window.innerHeight - firstEl["bottom"]->Float.toInt > 375 + let topVacent = firstEl["top"]->Float.toInt > 470 + let rightVacent = Window.innerWidth - firstEl["left"]->Float.toInt > 270 + let leftVacent = firstEl["right"]->Float.toInt > 270 + + if bottomVacent { + rightVacent ? BottomRight : leftVacent ? BottomLeft : BottomMiddle + } else if topVacent { + rightVacent ? TopRight : leftVacent ? TopLeft : TopMiddle + } else if rightVacent { + BottomRight + } else if leftVacent { + BottomLeft + } else { + BottomMiddle + }->Some + }) + ->Option.getOr(BottomMiddle) + } + }, [showDropDown]) + + let flexWrapper = switch dropDirection { + | BottomLeft => "flex-row-reverse flex-wrap" + | BottomRight => "flex-row flex-wrap" + | BottomMiddle => "flex-row flex-wrap justify-center" + | TopLeft => "flex-row-reverse flex-wrap-reverse" + | TopRight => "flex-row flex-wrap-reverse" + | TopMiddle => "flex-row flex-wrap-reverse justify-center" + } + let marginBottom = switch dropDirection { + | BottomLeft | BottomRight | BottomMiddle | TopMiddle => "" + | TopLeft | TopRight => "mb-12" + } + + let onRadioOptionSelect = _ev => { + newInputRadio.onChange(_ev) + addButton ? setShowDropDown(_ => true) : setShowDropDown(_ => false) + } + + let allSellectedOptions = React.useMemo2(() => { + newInputSelect.value + ->JSON.Decode.array + ->Option.getOr([]) + ->Belt.Array.keepMap(JSON.Decode.string) + ->Belt.Array.keepMap(str => { + transformedOptions->Array.find(x => x.value == str)->Option.map(x => x.label) + }) + ->Array.joinWith(", ") + ->LogicUtils.getNonEmptyString + ->Option.getOr(buttonText) + }, (transformedOptions, newInputSelect.value)) + + let title = showAllSelectedOptions ? allSellectedOptions : buttonText + + let badgeForSelect = React.useMemo1((): Button.badge => { + let count = newInputSelect.value->JSON.Decode.array->Option.getOr([])->Array.length + let condition = count > 1 + + { + value: count->Int.toString, + color: condition ? BadgeBlue : NoBadge, + } + }, [newInputSelect.value]) + let widthClass = isMobileView ? "w-full" : dropdownCustomWidth->Option.getOr("") + + let optionsElement = if allowMultiSelect { + + } else if addButton { + + } else { + + } + + let selectButtonText = if !showSelectionAsChips { + title + } else if selectedString->LogicUtils.isNonEmptyString { + selectedString + } else { + dropDowntext + } + + let buttonIcon = + + + let textStyle = if isSelectTextDark && selectButtonText !== buttonText { + Some("text-black dark:text-white") + } else { + textStyle + } + + let onDeleteClick = name => { + [name]->removeKeys + + setfilterKeys(_ => filterKeys->Array.filter(item => item !== name)) + } + +
+
+
ReactDOM.Ref.domRef} + className={`text-opacity-50 ${fullLength ? "w-full" : ""}`}> + {switch baseComponent { + | Some(comp) => {comp} + | None => + switch baseComponentMethod { + | Some(compFn) => {compFn(showDropDown)} + | None => + switch buttonType { + | FilterAdd => +
+ {if showDropDown { + if !isMobileView { + +
ReactDOM.Ref.domRef}> + optionsElement + {showCustomBtnAtEnd ? customButton : React.null} +
+
+ } else { + + optionsElement + + } + } else if !isInitialRender && isGrowDown && !isMobileView { +
ReactDOM.Ref.domRef}> + optionsElement +
+ } else { + React.null + }} +
+ {if allowMultiSelect && !hideMultiSelectButtons && showSelectionAsChips { + switch newInputSelect.value->JSON.Decode.array { + | Some(jsonArr) => + jsonArr + ->LogicUtils.getStrArrayFromJsonArray + ->Array.mapWithIndex((str, i) => { + let actualValueIndex = Array.findIndex(options->Array.map(x => x.value), item => + item == str + ) + if actualValueIndex !== -1 { + let (text, leftIcon) = switch options[actualValueIndex] { + | Some(ele) => (ele.label, ele.icon->Option.getOr(NoIcon)) + | None => ("", NoIcon) + } + +
+
+ } else { + React.null + } + }) + ->React.array + | _ => React.null + } + } else { + React.null + }} +
+ } +} + +module ChipFilterSelectBox = { + @react.component + let make = ( + ~options: array, + ~input: ReactFinalForm.fieldRenderPropsInput, + ~deselectDisable=false, + ~allowMultiSelect=true, + ~isTickRequired=true, + ~customStyleForChips="", + ) => { + let transformedOptions = useTransformed(options) + + let initalClassName = " m-2 bg-gray-200 dark:text-gray-800 border-jp-gray-800 inline-block text-s px-2 py-1 rounded-2xl" + let passedClassName = "flex items-center m-2 bg-blue-400 dark:text-gray-800 border-gray-300 inline-block text-s px-2 py-1 rounded-2xl" + let newInputSelect = input->ffInputToSelectInput + let values = newInputSelect.value + let saneValue = React.useMemo1(() => { + values->LogicUtils.getArrayFromJson([])->LogicUtils.getStrArrayFromJsonArray + }, [values]) + + let onItemClick = (itemDataValue, isDisabled) => { + if !isDisabled { + if allowMultiSelect { + let data = if Array.includes(saneValue, itemDataValue) { + if deselectDisable { + saneValue + } else { + saneValue->Array.filter(x => x !== itemDataValue) + } + } else { + Array.concat(saneValue, [itemDataValue]) + } + newInputSelect.onChange(data) + } else { + newInputSelect.onChange([itemDataValue]) + } + } + } + +
+ {transformedOptions + ->Array.mapWithIndex((option, i) => { + let isSelected = saneValue->Array.includes(option.value) + let selectedClass = isSelected ? passedClassName : initalClassName + let chipsCss = + customStyleForChips->LogicUtils.isEmptyString ? selectedClass : customStyleForChips + +
onItemClick(option.value, option.isDisabled)} + className={`px-4 py-1 mr-1 mt-0.5 border rounded-full flex flex-row gap-2 items-center cursor-pointer ${chipsCss}`}> + {if isTickRequired { + if isSelected { + + } else { + + } + } else { + React.null + }} + {React.string(option.label)} +
+ }) + ->React.array} +
+ } +} + +@react.component +let make = ( + ~input: ReactFinalForm.fieldRenderPropsInput, + ~buttonText="Normal Selection", + ~buttonSize=?, + ~allowMultiSelect=false, + ~isDropDown=true, + ~hideMultiSelectButtons=false, + ~options: array<'a>, + ~optionSize: CheckBoxIcon.size=Small, + ~isSelectedStateMinus=false, + ~isHorizontal=false, + ~deselectDisable=false, + ~showClearAll=true, + ~showSelectAll=true, + ~buttonType=Button.SecondaryFilled, + ~disableSelect=false, + ~fullLength=false, + ~customButtonStyle="", + ~textStyle="", + ~marginTop="mt-12", + ~customStyle="", + ~showSelectionAsChips=true, + ~showToggle=false, + ~maxHeight=?, + ~searchable=?, + ~fill="#0EB025", + ~optionRigthElement=?, + ~hideBorder=false, + ~allSelectType=Icon, + ~customSearchStyle="bg-jp-gray-100 dark:bg-jp-gray-950 p-2", + ~searchInputPlaceHolder=?, + ~showSearchIcon=true, + ~customLabelStyle=?, + ~customMargin="", + ~showToolTip=false, + ~showNameAsToolTip=false, + ~showBorder=?, + ~showCustomBtnAtEnd=false, + ~dropDownCustomBtnClick=false, + ~addDynamicValue=false, + ~showMatchingRecordsText=true, + ~customButton=React.null, + ~descriptionOnHover=false, + ~fixedDropDownDirection=?, + ~dropdownCustomWidth=?, + ~baseComponent=?, + ~baseComponentMethod=?, + ~customMarginStyle=?, + ~buttonTextWeight=?, + ~customButtonLeftIcon=?, + ~customTextPaddingClass=?, + ~customButtonPaddingClass=?, + ~customButtonIconMargin=?, + ~setExtSearchString=_ => (), + ~buttonStyleOnDropDownOpened="", + ~listFlexDirection="", + ~baseComponentCustomStyle="", + ~ellipsisOnly=false, + ~customSelectStyle="", + ~isPhoneDropdown=false, + ~hasApplyButton=?, + ~onApply=?, + ~showAllSelectedOptions=?, + ~buttonClickFn=?, + ~showDescriptionAsTool=true, + ~optionClass="", + ~selectClass="", + ~toggleProps="", + ~showSelectCountButton=false, + ~leftIcon=?, + ~customBackColor=?, + ~customSelectAllStyle=?, + ~checkboxDimension="", + ~showToolTipOptions=false, + ~textEllipsisForDropDownOptions=false, + ~showBtnTextToolTip=false, + ~dropdownClassName="", + ~onItemSelect=(_, _) => (), + ~wrapBasis="", + (), +) => { + let isMobileView = MatchMedia.useMobileChecker() + let (selectedString, setSelectedString) = React.useState(_ => "") + let newInputSelect = input->ffInputToSelectInput + let newInputRadio = input->ffInputToRadioInput + + let customButtonStyle = "bg-white rounded-lg !px-4 !py-2 !h-10" + + if isDropDown { + + } else if allowMultiSelect { + + } else { + + } +} diff --git a/src/components/InputFields.res b/src/components/InputFields.res index 5760d049f..9d3fe9a52 100644 --- a/src/components/InputFields.res +++ b/src/components/InputFields.res @@ -102,6 +102,111 @@ let infraSelectInput = ( /> } +let filterMultiSelectInput = ( + ~input: ReactFinalForm.fieldRenderPropsInput, + ~options: array, + ~optionSize: CheckBoxIcon.size=Small, + ~placeholder as _, + ~buttonText, + ~buttonSize=?, + ~hideMultiSelectButtons=false, + ~showSelectionAsChips=true, + ~showToggle=false, + ~isDropDown=true, + ~searchable=false, + ~showBorder=?, + ~optionRigthElement=?, + ~customStyle="", + ~customMargin="", + ~customButtonStyle=?, + ~hideBorder=false, + ~allSelectType=FilterSelectBox.Icon, + ~showToolTip=false, + ~showNameAsToolTip=false, + ~buttonType=Button.SecondaryFilled, + ~showSelectAll=true, + ~isHorizontal=false, + ~fullLength=?, + ~fixedDropDownDirection=?, + ~dropdownCustomWidth=?, + ~customMarginStyle=?, + ~buttonTextWeight=?, + ~marginTop=?, + ~customButtonLeftIcon=?, + ~customButtonPaddingClass=?, + ~customButtonIconMargin=?, + ~customTextPaddingClass=?, + ~listFlexDirection="", + ~buttonClickFn=?, + ~showDescriptionAsTool=true, + ~optionClass="", + ~selectClass="", + ~toggleProps="", + ~showSelectCountButton=false, + ~showAllSelectedOptions=true, + ~leftIcon=?, + ~customBackColor=?, + ~customSelectAllStyle=?, + ~onItemSelect=(_, _) => (), + ~wrapBasis="", + ~dropdownClassName="", + ~baseComponentMethod=?, + ~disableSelect=false, + (), +) => { + +} + let multiSelectInput = ( ~input: ReactFinalForm.fieldRenderPropsInput, ~options: array, @@ -387,6 +492,49 @@ let singleDatePickerInput = ( /> } +let filterDateRangeField = ( + ~startKey: string, + ~endKey: string, + ~format, + ~disablePastDates=false, + ~disableFutureDates=false, + ~showTime=false, + ~predefinedDays=[], + ~disableApply=false, + ~numMonths=1, + ~dateRangeLimit=?, + ~removeFilterOption=?, + ~optFieldKey=?, + ~showSeconds=true, + ~hideDate=false, + ~selectStandardTime=false, + ~isTooltipVisible=true, + (), +): comboCustomInputRecord => { + let fn = (_fieldsArray: array) => { + + } + + {fn, names: [startKey, endKey]} +} + let dateRangeField = ( ~startKey: string, ~endKey: string, diff --git a/src/components/InputFields.resi b/src/components/InputFields.resi index 38a271238..d9f331c65 100644 --- a/src/components/InputFields.resi +++ b/src/components/InputFields.resi @@ -51,6 +51,58 @@ let infraSelectInput: ( ~allowMultiSelect: bool=?, unit, ) => React.element +let filterMultiSelectInput: ( + ~input: ReactFinalForm.fieldRenderPropsInput, + ~options: array, + ~optionSize: CheckBoxIcon.size=?, + ~placeholder: 'a, + ~buttonText: string, + ~buttonSize: Button.buttonSize=?, + ~hideMultiSelectButtons: bool=?, + ~showSelectionAsChips: bool=?, + ~showToggle: bool=?, + ~isDropDown: bool=?, + ~searchable: bool=?, + ~showBorder: bool=?, + ~optionRigthElement: React.element=?, + ~customStyle: string=?, + ~customMargin: string=?, + ~customButtonStyle: string=?, + ~hideBorder: bool=?, + ~allSelectType: FilterSelectBox.allSelectType=?, + ~showToolTip: bool=?, + ~showNameAsToolTip: bool=?, + ~buttonType: Button.buttonType=?, + ~showSelectAll: bool=?, + ~isHorizontal: bool=?, + ~fullLength: bool=?, + ~fixedDropDownDirection: FilterSelectBox.direction=?, + ~dropdownCustomWidth: string=?, + ~customMarginStyle: string=?, + ~buttonTextWeight: string=?, + ~marginTop: string=?, + ~customButtonLeftIcon: Button.iconType=?, + ~customButtonPaddingClass: string=?, + ~customButtonIconMargin: string=?, + ~customTextPaddingClass: string=?, + ~listFlexDirection: string=?, + ~buttonClickFn: string => unit=?, + ~showDescriptionAsTool: bool=?, + ~optionClass: string=?, + ~selectClass: string=?, + ~toggleProps: string=?, + ~showSelectCountButton: bool=?, + ~showAllSelectedOptions: bool=?, + ~leftIcon: Button.iconType=?, + ~customBackColor: string=?, + ~customSelectAllStyle: string=?, + ~onItemSelect: (JsxEvent.Mouse.t, Js_string.t) => unit=?, + ~wrapBasis: string=?, + ~dropdownClassName: string=?, + ~baseComponentMethod: bool => React.element=?, + ~disableSelect: bool=?, + unit, +) => React.element let multiSelectInput: ( ~input: ReactFinalForm.fieldRenderPropsInput, ~options: array, @@ -194,6 +246,25 @@ let singleDatePickerInput: ( ~fullLength: bool=?, unit, ) => React.element +let filterDateRangeField: ( + ~startKey: string, + ~endKey: string, + ~format: string, + ~disablePastDates: bool=?, + ~disableFutureDates: bool=?, + ~showTime: bool=?, + ~predefinedDays: array=?, + ~disableApply: bool=?, + ~numMonths: int=?, + ~dateRangeLimit: int=?, + ~removeFilterOption: bool=?, + ~optFieldKey: string=?, + ~showSeconds: bool=?, + ~hideDate: bool=?, + ~selectStandardTime: bool=?, + ~isTooltipVisible: bool=?, + unit, +) => comboCustomInputRecord let dateRangeField: ( ~startKey: string, ~endKey: string, diff --git a/src/components/LoadedTableWithCustomColumns.res b/src/components/LoadedTableWithCustomColumns.res index b6964e8ae..782878dc2 100644 --- a/src/components/LoadedTableWithCustomColumns.res +++ b/src/components/LoadedTableWithCustomColumns.res @@ -90,11 +90,12 @@ let make = (