feat(date-picker): add clearable option and enhance date handling
- Introduced a clearable option for the DatePicker component, allowing users to easily reset the selected date. - Enhanced date handling logic to support conversion from Unix timestamps and improved value management for single and range selections. - Updated TenderListFilters to utilize the clearable feature in multiple DatePicker instances for better user experience. - Refactored useTenderListPresenter to manage date range filters more effectively, ensuring proper cleanup of cleared values.
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
import DatePicker from "@/components/FormElements/DatePicker/DatePicker";
|
||||
import InputGroup from "@/components/FormElements/InputGroup";
|
||||
import { Select } from "@/components/FormElements/select";
|
||||
import { Button } from "@/components/ui-elements/button";
|
||||
import { Countries } from "@/constants/countries";
|
||||
import { TenderStatus } from "@/constants/enums";
|
||||
import { Languages } from "@/constants/languages";
|
||||
import { getEnumAsArray } from "@/utils/shared";
|
||||
import {
|
||||
Control,
|
||||
FieldValues,
|
||||
@@ -59,30 +54,14 @@ const TenderListFilters = ({
|
||||
label="description"
|
||||
type="text"
|
||||
/>
|
||||
<Select
|
||||
{...filterRegister("status")}
|
||||
items={getEnumAsArray(TenderStatus)}
|
||||
label="status"
|
||||
name="status"
|
||||
clearable
|
||||
value={watch("status")}
|
||||
onClear={() => setValue("status", undefined)}
|
||||
/>
|
||||
|
||||
<InputGroup
|
||||
{...filterRegister("country")}
|
||||
name="country"
|
||||
label="country"
|
||||
type="text"
|
||||
/>
|
||||
<Select
|
||||
{...filterRegister("country_code")}
|
||||
items={Countries}
|
||||
label="country code"
|
||||
name="country_code"
|
||||
clearable
|
||||
value={watch("country_code")}
|
||||
onClear={() => setValue("country_code", undefined)}
|
||||
/>
|
||||
|
||||
<InputGroup
|
||||
{...filterRegister("country_codes")}
|
||||
name="country_codes"
|
||||
@@ -131,6 +110,7 @@ const TenderListFilters = ({
|
||||
label="created at range"
|
||||
range
|
||||
timePicker
|
||||
clearable
|
||||
/>
|
||||
<DatePicker
|
||||
control={control}
|
||||
@@ -138,6 +118,7 @@ const TenderListFilters = ({
|
||||
label="tender deadline range"
|
||||
range
|
||||
timePicker
|
||||
clearable
|
||||
/>
|
||||
<DatePicker
|
||||
control={control}
|
||||
@@ -145,6 +126,7 @@ const TenderListFilters = ({
|
||||
label="publication date range"
|
||||
range
|
||||
timePicker
|
||||
clearable
|
||||
/>
|
||||
<DatePicker
|
||||
control={control}
|
||||
@@ -152,15 +134,7 @@ const TenderListFilters = ({
|
||||
label="submission deadline range"
|
||||
range
|
||||
timePicker
|
||||
/>
|
||||
<Select
|
||||
{...filterRegister("lang")}
|
||||
items={Languages}
|
||||
label="language"
|
||||
name="lang"
|
||||
clearable
|
||||
value={watch("lang")}
|
||||
onClear={() => setValue("lang", undefined)}
|
||||
/>
|
||||
|
||||
<div className="col-span-2 flex gap-4">
|
||||
|
||||
@@ -50,6 +50,56 @@ const normalizeFilterValues = (values: Record<string, any>) => {
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const parseUnixParam = (v: unknown): number | undefined => {
|
||||
if (v === undefined || v === null || v === "") return undefined;
|
||||
const n = typeof v === "number" ? v : Number(v);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
};
|
||||
|
||||
const buildRangeFormValue = (
|
||||
params: Record<string, any>,
|
||||
fromKey: string,
|
||||
toKey: string,
|
||||
legacy?: { from: string; to: string },
|
||||
): number[] | undefined => {
|
||||
let from = parseUnixParam(params[fromKey]);
|
||||
let to = parseUnixParam(params[toKey]);
|
||||
if (legacy) {
|
||||
if (from === undefined) from = parseUnixParam(params[legacy.from]);
|
||||
if (to === undefined) to = parseUnixParam(params[legacy.to]);
|
||||
}
|
||||
if (from !== undefined && to !== undefined) return [from, to];
|
||||
if (from !== undefined) return [from];
|
||||
if (to !== undefined) return [to];
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const buildTenderFilterFormValues = (params: Record<string, any>) => ({
|
||||
...params,
|
||||
created_at_range: buildRangeFormValue(
|
||||
params,
|
||||
"created_at_from",
|
||||
"created_at_to",
|
||||
),
|
||||
tender_deadline_range: buildRangeFormValue(
|
||||
params,
|
||||
"tender_deadline_from",
|
||||
"tender_deadline_to",
|
||||
{ from: "deadline_from", to: "deadline_to" },
|
||||
),
|
||||
publication_date_range: buildRangeFormValue(
|
||||
params,
|
||||
"publication_date_from",
|
||||
"publication_date_to",
|
||||
),
|
||||
submission_deadline_range: buildRangeFormValue(
|
||||
params,
|
||||
"submission_deadline_from",
|
||||
"submission_deadline_to",
|
||||
{ from: "submission_date_from", to: "submission_date_to" },
|
||||
),
|
||||
});
|
||||
|
||||
const mapDateRangeFilters = (values: Record<string, any>) => {
|
||||
const mappedValues = { ...values };
|
||||
|
||||
@@ -65,6 +115,22 @@ const mapDateRangeFilters = (values: Record<string, any>) => {
|
||||
aliases?: Array<{ from: string; to: string }>;
|
||||
}) => {
|
||||
const rangeValue = mappedValues[key];
|
||||
const isCleared =
|
||||
rangeValue === "" ||
|
||||
rangeValue === null ||
|
||||
(Array.isArray(rangeValue) && rangeValue.length === 0);
|
||||
|
||||
if (isCleared) {
|
||||
delete mappedValues[key];
|
||||
delete mappedValues[fromKey];
|
||||
delete mappedValues[toKey];
|
||||
aliases.forEach((alias) => {
|
||||
delete mappedValues[alias.from];
|
||||
delete mappedValues[alias.to];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(rangeValue)) {
|
||||
return;
|
||||
}
|
||||
@@ -112,6 +178,42 @@ const mapDateRangeFilters = (values: Record<string, any>) => {
|
||||
|
||||
return mappedValues;
|
||||
};
|
||||
|
||||
const TENDER_RANGE_PARAM_KEYS = [
|
||||
"created_at_from",
|
||||
"created_at_to",
|
||||
"tender_deadline_from",
|
||||
"tender_deadline_to",
|
||||
"deadline_from",
|
||||
"deadline_to",
|
||||
"publication_date_from",
|
||||
"publication_date_to",
|
||||
"submission_deadline_from",
|
||||
"submission_deadline_to",
|
||||
"submission_date_from",
|
||||
"submission_date_to",
|
||||
"created_at_range",
|
||||
"tender_deadline_range",
|
||||
"publication_date_range",
|
||||
"submission_deadline_range",
|
||||
] as const;
|
||||
|
||||
/** Query keys owned by TenderListFilters — drop from URL when cleared (not in normalized submit). */
|
||||
const TENDER_FILTER_MODAL_KEYS = new Set<string>([
|
||||
"q",
|
||||
"title",
|
||||
"description",
|
||||
"country",
|
||||
"country_codes",
|
||||
"notice_type",
|
||||
"notice_types",
|
||||
"form_types",
|
||||
"main_classification",
|
||||
"classifications",
|
||||
"cpv_codes",
|
||||
...TENDER_RANGE_PARAM_KEYS,
|
||||
]);
|
||||
|
||||
const useTenderListPresenter = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
|
||||
@@ -139,13 +241,12 @@ const useTenderListPresenter = () => {
|
||||
...apiDefaultParams,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
lang: "en",
|
||||
};
|
||||
for (const [key, value] of searchParams.entries()) {
|
||||
newParams[key] = value;
|
||||
}
|
||||
setParams(newParams);
|
||||
filterFormReset(newParams);
|
||||
filterFormReset(buildTenderFilterFormValues(newParams));
|
||||
}, [searchParams, filterFormReset]);
|
||||
|
||||
const { data, error, isPending } = useTendersQuery(params);
|
||||
@@ -155,7 +256,13 @@ const useTenderListPresenter = () => {
|
||||
const search = (data: any) => {
|
||||
const mappedDateRanges = mapDateRangeFilters(data);
|
||||
const normalizedData = normalizeFilterValues(mappedDateRanges);
|
||||
const newParams = { ...params, ...normalizedData, offset: 0 };
|
||||
const merged: Record<string, any> = { ...params };
|
||||
for (const key of TENDER_FILTER_MODAL_KEYS) {
|
||||
if (!(key in normalizedData)) {
|
||||
delete merged[key];
|
||||
}
|
||||
}
|
||||
const newParams = { ...merged, ...normalizedData, offset: 0 };
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||
router.push(`${pathName}?${queryString}`);
|
||||
|
||||
Reference in New Issue
Block a user