diff --git a/apps/edr-freight-web/backoffice/src/shared/common/ui/date-range-picker.tsx b/apps/edr-freight-web/backoffice/src/shared/common/ui/date-range-picker.tsx
new file mode 100644
index 000000000..07df09353
--- /dev/null
+++ b/apps/edr-freight-web/backoffice/src/shared/common/ui/date-range-picker.tsx
@@ -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
(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 (
+ setOpen(next && !disabled)}>
+
+
+
+
+
+
+ {presets.map((preset) => (
+
+ ))}
+
+
+
+
setDraft(range ?? undefined)}
+ numberOfMonths={numberOfMonths}
+ defaultMonth={draft?.from ?? value?.from}
+ className="border-none shadow-none"
+ />
+
+
+ {formatRangeLabel(draft, "No dates selected")}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/edr-freight-web/backoffice/src/shared/components/activity-log/activity-filters.tsx b/apps/edr-freight-web/backoffice/src/shared/components/activity-log/activity-filters.tsx
index f1dc6a849..61e4a2a3b 100644
--- a/apps/edr-freight-web/backoffice/src/shared/components/activity-log/activity-filters.tsx
+++ b/apps/edr-freight-web/backoffice/src/shared/components/activity-log/activity-filters.tsx
@@ -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({