relative-dates.ts2.8 KBView on GitHub /**
* Relative date utilities for CRM filters.
*
* When a date filter has `dateRelative: true`, the stored `dateFromDaysOffset` / `dateToDaysOffset`
* represent a day count relative to today (0 = today, -7 = 7 days ago, +3 = 3 days from now).
* These offsets are resolved to absolute ISO dates at query time so the server receives
* normal dates — keeping filters automatically up-to-date as days pass.
*/
import { startOfDay, differenceInCalendarDays, addDays, format } from 'date-fns';
/**
* Compute the day offset between a date and today.
* Returns 0 for today, -1 for yesterday, +1 for tomorrow, etc.
*/
export function computeDaysOffset(date: Date): number {
const today = startOfDay(new Date());
const target = startOfDay(date);
return differenceInCalendarDays(target, today);
}
/**
* Resolve a day offset to an absolute Date, normalized to start of day (local midnight).
* The returned Date is stable within a calendar day.
*/
export function resolveOffsetToDate(offsetDays: number): Date {
return startOfDay(addDays(new Date(), offsetDays));
}
/**
* Resolve a day offset to an ISO date string suitable for filter queries.
*/
export function resolveOffsetToISO(offsetDays: number): string {
return resolveOffsetToDate(offsetDays).toISOString();
}
/**
* Format a day offset for badge/label display.
* Examples: "Today", "Yesterday", "Tomorrow", "7d ago", "3d from now"
*/
export function formatDaysOffset(offsetDays: number): string {
if (offsetDays === 0) return 'Today';
if (offsetDays === -1) return 'Yesterday';
if (offsetDays === 1) return 'Tomorrow';
if (offsetDays < 0) return `${Math.abs(offsetDays)}d ago`;
return `${offsetDays}d from now`;
}
/**
* Format a day offset with the resolved date for popover display.
* Examples: "Today (Feb 28)", "7d ago (Feb 21)"
*/
export function formatDaysOffsetWithDate(offsetDays: number): string {
const label = formatDaysOffset(offsetDays);
const resolved = resolveOffsetToDate(offsetDays);
return `${label} (${format(resolved, 'MMM d')})`;
}
/**
* Get a stable string representing "today" for use as a cache key epoch.
* Changes once per calendar day, causing React Query cache invalidation
* for queries that use relative date filters.
*/
export function getTodayEpoch(): string {
return new Date().toDateString();
}
/**
* Due-date cutoff that defines a conversation's "Suggested Action".
*
* The Suggested Action cell shows the most overdue todo task due on or before the end of
* today (ConversationItem/VirtualizedTableRow), falling back to last activity when there is
* none. Filters on that column must use the same cutoff, or "has an action" would include
* rows whose cell shows no action at all.
*/
export function currentActionDueCutoff(): string {
const endOfToday = new Date();
endOfToday.setHours(23, 59, 59, 999);
return endOfToday.toISOString();
}