Skip to content
Snippets Groups Projects

Update SearchRanges component

Merged Lukas Budginas requested to merge lbudginas/#491_incorrect_filter_value_bug into develop
1 file
+ 136
122
Compare changes
  • Side-by-side
  • Inline
import React, { useEffect, useState } from "react";
import React, { useState, useEffect } from "react";
import moment from "moment";
import { SearchRangesResult } from "../../hooks/common/useSearchRanges";
import { Select, SelectContent, SelectTrigger, SelectItem } from "../ui/select";
import { Input } from "../ui/input";
import DateTimePicker from "../DateTimePicker";
import ErrorMessage from "../ErrorMessage"; // Import the ErrorMessage component
import ErrorMessage from "../ErrorMessage";
interface SearchRangesProps {
rangesProps: SearchRangesResult;
safeTimeRangeDisplay?: boolean;
}
const SearchRanges: React.FC<SearchRangesProps> = ({
rangesProps,
safeTimeRangeDisplay,
}) => {
const SearchRanges: React.FC<SearchRangesProps> = ({ rangesProps }) => {
const {
rangeSelectOptions,
timeSelectOptions,
@@ -38,79 +35,100 @@ const SearchRanges: React.FC<SearchRangesProps> = ({
const [rangeError, setRangeError] = useState<string | null>(null);
const handleOnBlur = (
e: React.FocusEvent<HTMLInputElement>,
fieldSetter: Function,
validateField: Function | null
) => {
const value = e.target.value;
const numericValue = value ? Number(value) : undefined;
const [localLastBlocks, setLocalLastBlocks] = useState(
lastBlocksValue !== undefined ? String(lastBlocksValue) : ""
);
const [localLastTimeUnit, setLocalLastTimeUnit] = useState(
lastTimeUnitValue !== undefined ? String(lastTimeUnitValue) : ""
);
const [localFromBlock, setLocalFromBlock] = useState(
fromBlock !== undefined ? String(fromBlock) : ""
);
const [localToBlock, setLocalToBlock] = useState(
toBlock !== undefined ? String(toBlock) : ""
);
// Fetch the latest block number dynamically
let validated = true;
if (validateField) {
validated = validateField(e, numericValue);
useEffect(() => {
setLocalLastBlocks(
lastBlocksValue !== undefined ? String(lastBlocksValue) : ""
);
setLocalLastTimeUnit(
lastTimeUnitValue !== undefined ? String(lastTimeUnitValue) : ""
);
setLocalFromBlock(fromBlock !== undefined ? String(fromBlock) : "");
setLocalToBlock(toBlock !== undefined ? String(toBlock) : "");
}, [lastBlocksValue, lastTimeUnitValue, fromBlock, toBlock]);
const sanitizeNumericInput = (value: string, allowDecimal = false) => {
let cleaned = allowDecimal
? value.replace(/[^0-9.]/g, "")
: value.replace(/[^0-9]/g, "");
if (allowDecimal && cleaned.split(".").length > 2) {
const parts = cleaned.split(".");
cleaned = parts.shift() + "." + parts.join("");
}
validated ? fieldSetter(numericValue) : fieldSetter(null);
if (cleaned.length > 15) {
cleaned = cleaned.slice(0, 15);
}
return cleaned;
};
const validateToBlock = (
e: React.FocusEvent<HTMLInputElement>,
value: number | undefined
) => {
if (value !== undefined && value <= 0) {
const validateFromBlock = (numVal: number | undefined) => {
if (numVal !== undefined && numVal <= 0) {
setRangeError("Block Number must be a positive number");
e.target.value = "";
return false;
}
if (value && fromBlock && !isNaN(value) && value < fromBlock) {
setRangeError("To block must be greater than From block");
e.target.value = "";
if (numVal && toBlock && !isNaN(numVal) && numVal > toBlock) {
setRangeError("From block must be less than To block");
return false;
}
return true;
};
const validateFromBlock = (
e: React.FocusEvent<HTMLInputElement>,
value: number | undefined
) => {
if (value !== undefined && value <= 0) {
const validateToBlock = (numVal: number | undefined) => {
if (numVal !== undefined && numVal <= 0) {
setRangeError("Block Number must be a positive number");
e.target.value = "";
return false;
}
if (value && toBlock && !isNaN(value) && value > toBlock) {
setRangeError("From block must be less than To block");
e.target.value = "";
if (numVal && fromBlock && !isNaN(numVal) && numVal < fromBlock) {
setRangeError("To block must be greater than From block");
return false;
}
return true;
};
const handleNumericInput = (
e: React.ChangeEvent<HTMLInputElement>,
allowDecimal: boolean = false
) => {
let cleanedValue = e.target.value;
const handleLastBlocksBlur = () => {
const val = localLastBlocks ? Number(localLastBlocks) : undefined;
setLastBlocksValue(val);
setRangeError(null);
};
// Clean the value based on the logic
cleanedValue = allowDecimal
? cleanedValue.replace(/[^0-9.]/g, "") // Allow numbers and decimal point
: cleanedValue.replace(/[^0-9]/g, ""); // Only allow numbers
const handleLastTimeUnitBlur = () => {
const val = localLastTimeUnit ? Number(localLastTimeUnit) : undefined;
setLastTimeUnitValue(val);
setRangeError(null);
};
if (allowDecimal && cleanedValue.split(".").length > 2) {
cleanedValue =
cleanedValue.slice(0, cleanedValue.indexOf(".") + 1) +
cleanedValue.split(".").slice(1).join(""); // Remove extra decimals
const handleFromBlockBlur = () => {
const val = localFromBlock ? Number(localFromBlock) : undefined;
if (!validateFromBlock(val)) {
setFromBlock(undefined);
return;
}
setFromBlock(val);
setRangeError(null);
};
if (cleanedValue.length > 15) {
cleanedValue = cleanedValue.slice(0, 15); // Limit to 15 digits
const handleToBlockBlur = () => {
const val = localToBlock ? Number(localToBlock) : undefined;
if (!validateToBlock(val)) {
setToBlock(undefined);
return;
}
e.target.value = cleanedValue;
setToBlock(val);
setRangeError(null);
};
useEffect(() => {
@@ -130,22 +148,17 @@ const SearchRanges: React.FC<SearchRangesProps> = ({
value={rangeSelectKey}
>
<SelectTrigger className="w-1/2 border-0 border-b-2 bg-theme text-text">
{
rangeSelectOptions.find(
(selectOption) => selectOption.key === rangeSelectKey
)?.name
}
{rangeSelectOptions.find((opt) => opt.key === rangeSelectKey)?.name}
</SelectTrigger>
<SelectContent className="bg-theme text-text rounded-sm max-h-[31rem]">
{rangeSelectOptions.map((selectOption, index) => (
{rangeSelectOptions.map((option, idx) => (
<SelectItem
className="text-center"
key={index}
value={selectOption.key}
defaultChecked={false}
key={idx}
value={option.key}
data-testid="search-select-option"
>
{selectOption.name}
{option.name}
</SelectItem>
))}
</SelectContent>
@@ -153,90 +166,91 @@ const SearchRanges: React.FC<SearchRangesProps> = ({
{rangeSelectKey === "lastBlocks" && (
<div className="flex items-center">
<div className="flex flex-col w-full">
<Input
className="w-1/2 border-0 border-b-2 bg-theme"
type="text" // Use type="text" to allow custom validation
defaultValue={lastBlocksValue || ""}
onChange={(e) => handleNumericInput(e)}
onBlur={(e) => handleOnBlur(e, setLastBlocksValue, null)}
placeholder={"Last"}
/>
</div>
<Input
className="w-1/2 border-0 border-b-2 bg-theme"
type="text"
value={localLastBlocks}
onChange={(e) =>
setLocalLastBlocks(sanitizeNumericInput(e.target.value))
}
onBlur={handleLastBlocksBlur}
placeholder="Last"
/>
</div>
)}
{rangeSelectKey === "lastTime" && (
<>
<div className="flex items-center justify-center">
<div className="flex flex-col w-full mr-2">
<Input
type="text"
className="bg-theme border-0 border-b-2 text-text"
defaultValue={lastTimeUnitValue || ""}
onChange={(e) => handleNumericInput(e, true)}
onBlur={(e) => handleOnBlur(e, setLastTimeUnitValue, null)}
placeholder={"Last"}
/>
</div>
<Select onValueChange={setTimeUnitSelectKey}>
<SelectTrigger className="pl-2 bg-theme border-0 border-b-2 text-text">
{
timeSelectOptions.find(
(selectOption) => selectOption.key === timeUnitSelectKey
)?.name
}
</SelectTrigger>
<SelectContent
className="bg-theme text-text rounded-sm max-h-[31rem]"
data-testid="select-time-option-units"
>
{timeSelectOptions.map((selectOption, index) => (
<SelectItem
className="text-center"
key={index}
value={selectOption.key}
defaultChecked={false}
>
{selectOption.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</>
<div className="flex items-center justify-center">
<Input
type="text"
className="bg-theme border-0 border-b-2 text-text mr-2"
value={localLastTimeUnit}
onChange={(e) =>
setLocalLastTimeUnit(sanitizeNumericInput(e.target.value, true))
}
onBlur={handleLastTimeUnitBlur}
placeholder="Last"
/>
<Select
onValueChange={setTimeUnitSelectKey}
value={timeUnitSelectKey}
>
<SelectTrigger className="pl-2 bg-theme border-0 border-b-2 text-text">
{
timeSelectOptions.find((opt) => opt.key === timeUnitSelectKey)
?.name
}
</SelectTrigger>
<SelectContent className="bg-theme text-text rounded-sm max-h-[31rem]">
{timeSelectOptions.map((option, index) => (
<SelectItem
className="text-center"
key={index}
value={option.key}
>
{option.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{rangeSelectKey === "blockRange" && (
<div className="flex items-center">
<div className="flex flex-col w-full mr-2">
<div className="mr-2 w-full">
<Input
type="text"
className="bg-theme border-0 border-b-2"
data-testid="from-block-input"
defaultValue={fromBlock || ""}
onChange={(e) => handleNumericInput(e)}
onBlur={(e) => handleOnBlur(e, setFromBlock, validateFromBlock)}
value={localFromBlock}
onChange={(e) =>
setLocalFromBlock(sanitizeNumericInput(e.target.value))
}
onBlur={handleFromBlockBlur}
placeholder="From"
/>
</div>
<div className="flex flex-col w-full">
<div className="w-full">
<Input
className="bg-theme border-0 border-b-2"
data-testid="headblock-number"
type="text"
defaultValue={toBlock || ""}
onChange={(e) => handleNumericInput(e)}
placeholder={"To"}
onBlur={(e) => handleOnBlur(e, setToBlock, validateToBlock)}
value={localToBlock}
onChange={(e) =>
setLocalToBlock(sanitizeNumericInput(e.target.value))
}
onBlur={handleToBlockBlur}
placeholder="To"
/>
</div>
</div>
)}
{rangeError && (
<ErrorMessage
message={rangeError}
onClose={() => setRangeError(null)} // Close the error message
onClose={() => setRangeError(null)}
timeout={3000}
/>
)}
Loading