mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(freight-backoffice): add DateRangePicker, replace ad-hoc from/to date filters
Adds a shadcn Popover+Calendar range picker with presets (Today, Last 7/30 days, this/last month, YTD) and Apply/Cancel. Swaps it into every shadcn-family from-to date filter: activity log, sector reports, incoming records (EN branch), CombinedFilterBar (13 consumers), user records, audit log — also fixes an uncontrolled-input bug on the audit log date fields. Mantine-based ListControls/ReportFilters left untouched (different UI kit).
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Input } from "@/shared/common/ui/input";
|
||||
import { DateRangePicker, parseDay, formatDay } from "@/shared/common/ui/date-range-picker";
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
@@ -134,21 +135,12 @@ const buildQuery = (): CollectionQueryDTO => {
|
||||
className="w-64"
|
||||
/>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
type="date"
|
||||
onChange={(e) =>
|
||||
setDateRange({ ...dateRange, start: e.target.value })
|
||||
}
|
||||
/>
|
||||
<span className="text-muted-foreground text-sm">to</span>
|
||||
<Input
|
||||
type="date"
|
||||
onChange={(e) =>
|
||||
setDateRange({ ...dateRange, end: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<DateRangePicker
|
||||
value={{ from: parseDay(dateRange.start), to: parseDay(dateRange.end) }}
|
||||
onChange={(range) =>
|
||||
setDateRange({ start: formatDay(range.from), end: formatDay(range.to) })
|
||||
}
|
||||
/>
|
||||
|
||||
<Select value={sort} onValueChange={setSort}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/common/ui/select";
|
||||
import { DateRangePicker, parseDay, formatDay } from "@/shared/common/ui/date-range-picker";
|
||||
|
||||
export interface ReportFilterOption {
|
||||
id: string;
|
||||
@@ -167,33 +168,18 @@ export function SectorReportFilters({
|
||||
</Select>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1.5 text-xs font-medium">
|
||||
<label className="space-y-1.5 text-xs font-medium sm:col-span-2">
|
||||
<span className="flex items-center gap-2">
|
||||
<CalendarDays className="size-4 text-primary" />
|
||||
From date
|
||||
Date range
|
||||
</span>
|
||||
<input
|
||||
type="date"
|
||||
value={fromDate}
|
||||
max={toDate || undefined}
|
||||
<DateRangePicker
|
||||
value={{ from: parseDay(fromDate), to: parseDay(toDate) }}
|
||||
onChange={(range) => {
|
||||
onFromDateChange(formatDay(range.from));
|
||||
onToDateChange(formatDay(range.to));
|
||||
}}
|
||||
disabled={!unitId || loading}
|
||||
onChange={(event) => onFromDateChange(event.target.value)}
|
||||
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1.5 text-xs font-medium">
|
||||
<span className="flex items-center gap-2">
|
||||
<CalendarDays className="size-4 text-primary" />
|
||||
To date
|
||||
</span>
|
||||
<input
|
||||
type="date"
|
||||
value={toDate}
|
||||
min={fromDate || undefined}
|
||||
disabled={!unitId || loading}
|
||||
onChange={(event) => onToDateChange(event.target.value)}
|
||||
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { DataTable } from "@/record-management/common/DataTable";
|
||||
import { Button } from "@/shared/common/ui/button";
|
||||
import { Input } from "@/shared/common/ui/input";
|
||||
import { DateRangePicker, parseDay, formatDay } from "@/shared/common/ui/date-range-picker";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -383,23 +384,13 @@ const ExternalIncomingRecords = () => {
|
||||
) : (
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
{lang.startsWith("en") ? (
|
||||
<>
|
||||
<label className="mt-2">From: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={fromDispatchedDate}
|
||||
onChange={(e) => setFromDispatchedDate(e.target.value)}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
<label className="mt-2">To: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={toDispatchedDate}
|
||||
onChange={(e) => setToDispatchedDate(e.target.value)}
|
||||
min={fromDispatchedDate}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
</>
|
||||
<DateRangePicker
|
||||
value={{ from: parseDay(fromDispatchedDate), to: parseDay(toDispatchedDate) }}
|
||||
onChange={(range) => {
|
||||
setFromDispatchedDate(formatDay(range.from));
|
||||
setToDispatchedDate(formatDay(range.to));
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{/* From Date */}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { DataTable } from "@/record-management/common/DataTable";
|
||||
import { Button } from "@/shared/common/ui/button";
|
||||
import { Input } from "@/shared/common/ui/input";
|
||||
import { DateRangePicker, parseDay, formatDay } from "@/shared/common/ui/date-range-picker";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -326,23 +327,13 @@ const InternalIncomingRecordsV2 = () => {
|
||||
) : (
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
{lang.startsWith("en") ? (
|
||||
<>
|
||||
<label className="mt-2">From: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={fromDispatchedDate}
|
||||
onChange={(e) => setFromDispatchedDate(e.target.value)}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
<label className="mt-2">To: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={toDispatchedDate}
|
||||
onChange={(e) => setToDispatchedDate(e.target.value)}
|
||||
min={fromDispatchedDate}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
</>
|
||||
<DateRangePicker
|
||||
value={{ from: parseDay(fromDispatchedDate), to: parseDay(toDispatchedDate) }}
|
||||
onChange={(range) => {
|
||||
setFromDispatchedDate(formatDay(range.from));
|
||||
setToDispatchedDate(formatDay(range.to));
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{/* From Date */}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@/shared/common/ui/select";
|
||||
import { Input } from "@/shared/common/ui/input";
|
||||
import { Button } from "@/shared/common/ui/button";
|
||||
import { DateRangePicker, parseDay, formatDay } from "@/shared/common/ui/date-range-picker";
|
||||
import { Badge } from "@/shared/common/ui/badge";
|
||||
import { AdvancedTable } from "@/shared/common/ui/table/AdvancedTable";
|
||||
import { useLocalizedName } from "@/shared/common/localizedName";
|
||||
@@ -521,29 +522,13 @@ const UserRecords = () => {
|
||||
{selectedFilter?.type === "dateRange" ? (
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
{lang.startsWith("en") ? (
|
||||
<>
|
||||
<label className="mt-2">From: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={fromDate}
|
||||
onChange={(e) => setFromDate(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleAddFilter();
|
||||
}}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
<label className="mt-2">To: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={toDate}
|
||||
onChange={(e) => setToDate(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleAddFilter();
|
||||
}}
|
||||
min={fromDate}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
</>
|
||||
<DateRangePicker
|
||||
value={{ from: parseDay(fromDate), to: parseDay(toDate) }}
|
||||
onChange={(range) => {
|
||||
setFromDate(formatDay(range.from));
|
||||
setToDate(formatDay(range.to));
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="relative">
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import * as React from "react";
|
||||
import { CalendarIcon, ChevronDown } from "lucide-react";
|
||||
import {
|
||||
format,
|
||||
isSameDay,
|
||||
isSameYear,
|
||||
startOfDay,
|
||||
endOfDay,
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
startOfYear,
|
||||
subDays,
|
||||
subMonths,
|
||||
} from "date-fns";
|
||||
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
import { Button } from "@/shared/common/ui/button";
|
||||
import { Calendar } from "@/shared/common/ui/calendar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/shared/common/ui/popover";
|
||||
|
||||
export interface DateRangeValue {
|
||||
from?: Date;
|
||||
to?: Date;
|
||||
}
|
||||
|
||||
/** Parse a `YYYY-MM-DD` string as a local calendar day (avoids the UTC-midnight timezone shift `new Date(str)` has). */
|
||||
export function parseDay(value?: string | null): Date | undefined {
|
||||
if (!value) return undefined;
|
||||
const [y, m, d] = value.split("-").map(Number);
|
||||
if (!y || !m || !d) return undefined;
|
||||
return new Date(y, m - 1, d);
|
||||
}
|
||||
|
||||
/** Format a `Date` back to `YYYY-MM-DD`, for call sites still storing plain date strings. */
|
||||
export function formatDay(date?: Date): string {
|
||||
return date ? format(date, "yyyy-MM-dd") : "";
|
||||
}
|
||||
|
||||
interface DateRangePreset {
|
||||
label: string;
|
||||
range: () => DateRangeValue;
|
||||
}
|
||||
|
||||
const DEFAULT_PRESETS: DateRangePreset[] = [
|
||||
{ label: "Today", range: () => ({ from: startOfDay(new Date()), to: endOfDay(new Date()) }) },
|
||||
{
|
||||
label: "Yesterday",
|
||||
range: () => ({ from: startOfDay(subDays(new Date(), 1)), to: endOfDay(subDays(new Date(), 1)) }),
|
||||
},
|
||||
{ label: "Last 7 days", range: () => ({ from: startOfDay(subDays(new Date(), 6)), to: endOfDay(new Date()) }) },
|
||||
{ label: "Last 30 days", range: () => ({ from: startOfDay(subDays(new Date(), 29)), to: endOfDay(new Date()) }) },
|
||||
{ label: "This month", range: () => ({ from: startOfMonth(new Date()), to: endOfDay(new Date()) }) },
|
||||
{
|
||||
label: "Last month",
|
||||
range: () => ({
|
||||
from: startOfMonth(subMonths(new Date(), 1)),
|
||||
to: endOfMonth(subMonths(new Date(), 1)),
|
||||
}),
|
||||
},
|
||||
{ label: "Year to date", range: () => ({ from: startOfYear(new Date()), to: endOfDay(new Date()) }) },
|
||||
];
|
||||
|
||||
function formatRangeLabel(value?: DateRangeValue, placeholder = "Select date range") {
|
||||
if (!value?.from) return placeholder;
|
||||
if (!value.to || isSameDay(value.from, value.to)) {
|
||||
return format(value.from, "MMM d, y");
|
||||
}
|
||||
if (isSameYear(value.from, value.to)) {
|
||||
return `${format(value.from, "MMM d")} – ${format(value.to, "MMM d, y")}`;
|
||||
}
|
||||
return `${format(value.from, "MMM d, y")} – ${format(value.to, "MMM d, y")}`;
|
||||
}
|
||||
|
||||
interface DateRangePickerProps {
|
||||
value?: DateRangeValue;
|
||||
onChange?: (range: DateRangeValue) => void;
|
||||
presets?: DateRangePreset[];
|
||||
placeholder?: string;
|
||||
numberOfMonths?: number;
|
||||
className?: string;
|
||||
align?: "start" | "center" | "end";
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function DateRangePicker({
|
||||
value,
|
||||
onChange,
|
||||
presets = DEFAULT_PRESETS,
|
||||
placeholder = "Select date range",
|
||||
numberOfMonths = 2,
|
||||
className,
|
||||
align = "start",
|
||||
disabled = false,
|
||||
}: DateRangePickerProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [draft, setDraft] = React.useState<DateRangeValue | undefined>(value);
|
||||
|
||||
// Reset the draft to the committed value each time the popover opens
|
||||
React.useEffect(() => {
|
||||
if (open) setDraft(value);
|
||||
}, [open, value]);
|
||||
|
||||
const activePresetLabel = React.useMemo(() => {
|
||||
if (!draft?.from || !draft?.to) return null;
|
||||
const match = presets.find((preset) => {
|
||||
const r = preset.range();
|
||||
return r.from && r.to && isSameDay(r.from, draft.from!) && isSameDay(r.to, draft.to!);
|
||||
});
|
||||
return match?.label ?? null;
|
||||
}, [draft, presets]);
|
||||
|
||||
function applyPreset(preset: DateRangePreset) {
|
||||
const range = preset.range();
|
||||
setDraft(range);
|
||||
onChange?.(range);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function handleApply() {
|
||||
if (draft?.from) onChange?.({ from: draft.from, to: draft.to ?? draft.from });
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setDraft(value);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open && !disabled} onOpenChange={(next: boolean) => setOpen(next && !disabled)}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"min-w-[240px] justify-start gap-2 font-normal text-slate-700 dark:text-slate-200",
|
||||
!value?.from && "text-slate-500 dark:text-slate-400",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="size-4 text-slate-500 dark:text-slate-400" />
|
||||
<span className="flex-1 text-left">{formatRangeLabel(value, placeholder)}</span>
|
||||
<ChevronDown className="size-4 text-slate-400" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align={align} className="w-auto p-0">
|
||||
<div className="flex flex-col sm:flex-row">
|
||||
<div className="flex shrink-0 flex-col gap-0.5 border-b p-2 sm:border-b-0 sm:border-r sm:w-[160px]">
|
||||
{presets.map((preset) => (
|
||||
<button
|
||||
key={preset.label}
|
||||
type="button"
|
||||
onClick={() => applyPreset(preset)}
|
||||
className={cn(
|
||||
"rounded-md px-2.5 py-1.5 text-left text-sm font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800",
|
||||
activePresetLabel === preset.label &&
|
||||
"bg-primary-50 text-primary-700 hover:bg-primary-50 dark:bg-primary-900/30 dark:text-primary-300",
|
||||
)}
|
||||
>
|
||||
{preset.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<Calendar
|
||||
mode="range"
|
||||
selected={draft}
|
||||
onSelect={(range: DateRangeValue | undefined) => setDraft(range ?? undefined)}
|
||||
numberOfMonths={numberOfMonths}
|
||||
defaultMonth={draft?.from ?? value?.from}
|
||||
className="border-none shadow-none"
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-3 border-t p-3">
|
||||
<span className="text-sm text-slate-500 dark:text-slate-400">
|
||||
{formatRangeLabel(draft, "No dates selected")}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" disabled={!draft?.from} onClick={handleApply}>
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Input } from '@/shared/common/ui/input'
|
||||
import {
|
||||
@@ -11,14 +10,8 @@ import {
|
||||
SelectValue,
|
||||
} from '@/shared/common/ui/select'
|
||||
import { Button } from '@/shared/common/ui/button'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/shared/common/ui/popover'
|
||||
import { Calendar } from '@/shared/common/ui/calendar'
|
||||
import { Search, Filter, CalendarIcon, X } from 'lucide-react'
|
||||
import { format } from 'date-fns'
|
||||
import { DateRangePicker } from '@/shared/common/ui/date-range-picker'
|
||||
import { Search, Filter, X } from 'lucide-react'
|
||||
import { cn } from '@/shared/lib/utils'
|
||||
|
||||
interface ActivityFiltersProps {
|
||||
@@ -53,7 +46,6 @@ export function ActivityFilters({
|
||||
onClearFilters,
|
||||
}: ActivityFiltersProps) {
|
||||
const { t } = useTranslation()
|
||||
const [datePickerOpen, setDatePickerOpen] = useState(false)
|
||||
|
||||
const textStrong = 'text-gray-900 dark:text-gray-100'
|
||||
const textMuted = 'text-gray-500 dark:text-gray-400'
|
||||
@@ -168,92 +160,11 @@ export function ActivityFilters({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Popover open={datePickerOpen} onOpenChange={setDatePickerOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'w-full md:w-auto justify-start text-left font-normal',
|
||||
border,
|
||||
surface,
|
||||
textStrong,
|
||||
)}
|
||||
>
|
||||
<CalendarIcon
|
||||
className={cn('mr-2 h-4 w-4', textMuted)}
|
||||
/>
|
||||
{dateRange.from ? (
|
||||
dateRange.to ? (
|
||||
<>
|
||||
{format(dateRange.from, 'LLL dd, y')} -{' '}
|
||||
{format(dateRange.to, 'LLL dd, y')}
|
||||
</>
|
||||
) : (
|
||||
format(dateRange.from, 'LLL dd, y')
|
||||
)
|
||||
) : (
|
||||
<span className={textMuted}>
|
||||
{t('auditLog.filters.dateRange')}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={cn('w-auto p-0', border, surface)}
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={dateRange.from}
|
||||
selected={{
|
||||
from: dateRange.from,
|
||||
to: dateRange.to,
|
||||
}}
|
||||
onSelect={(range) => {
|
||||
onDateRangeChange({
|
||||
from: range?.from,
|
||||
to: range?.to,
|
||||
})
|
||||
}}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const today = new Date()
|
||||
const last7Days = new Date(today)
|
||||
last7Days.setDate(last7Days.getDate() - 7)
|
||||
|
||||
onDateRangeChange({
|
||||
from: last7Days,
|
||||
to: today,
|
||||
})
|
||||
}}
|
||||
className={cn(border, surface, textStrong, hoverText)}
|
||||
>
|
||||
{t('auditLog.filters.last7Days')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const today = new Date()
|
||||
const last30Days = new Date(today)
|
||||
last30Days.setDate(last30Days.getDate() - 30)
|
||||
|
||||
onDateRangeChange({
|
||||
from: last30Days,
|
||||
to: today,
|
||||
})
|
||||
}}
|
||||
className={cn(border, surface, textStrong, hoverText)}
|
||||
>
|
||||
{t('auditLog.filters.last30Days')}
|
||||
</Button>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onChange={onDateRangeChange}
|
||||
placeholder={t('auditLog.filters.dateRange')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import EthiopianDatePickerModal, {
|
||||
import { Badge } from "@/shared/common/ui/badge";
|
||||
import { Button } from "@/shared/common/ui/button";
|
||||
import { Input } from "@/shared/common/ui/input";
|
||||
import { DateRangePicker, parseDay, formatDay } from "@/shared/common/ui/date-range-picker";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -225,29 +226,13 @@ export function CombinedFilterBar({
|
||||
{selectedFilter?.type === "dateRange" ? (
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
{lang.startsWith("en") ? (
|
||||
<>
|
||||
<label className="mt-2">From: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={fromDate}
|
||||
onChange={(event) => setFromDate(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") addFilter();
|
||||
}}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
<label className="mt-2">To: </label>
|
||||
<Input
|
||||
type="date"
|
||||
value={toDate}
|
||||
onChange={(event) => setToDate(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") addFilter();
|
||||
}}
|
||||
min={fromDate}
|
||||
className="w-[150px]"
|
||||
/>
|
||||
</>
|
||||
<DateRangePicker
|
||||
value={{ from: parseDay(fromDate), to: parseDay(toDate) }}
|
||||
onChange={(range) => {
|
||||
setFromDate(formatDay(range.from));
|
||||
setToDate(formatDay(range.to));
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="relative">
|
||||
|
||||
Reference in New Issue
Block a user