/** * Working-days date math for the Quotes dashboard. * * "Working days" = weekdays only. Saturdays and Sundays are skipped. * Public holidays are intentionally NOT subtracted — the team can layer * that on later if it becomes a problem; for the current "show me the * aged quotes" indicator, weekday-only is the right baseline. * * All comparisons are done in the browser's local time. Quotes have * `created` timestamps in ISO UTC; we convert to local-day boundaries * before counting so "3 working days old" means "3 weekdays since the * day this row was created, in the user's timezone". */ /** Strip a Date down to its local-day midnight. */ function startOfLocalDay(d: Date): Date { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); } /** * Number of working days (Mon–Fri) between `then` and `now`, exclusive * of both endpoints' days. So a quote created today returns 0; a quote * created yesterday on a weekday returns 1; a quote created Friday * morning checked Monday morning returns 1 (Sat/Sun skipped). * * Returns 0 when `then` is in the future or on the same day as `now`. */ export function workingDaysBetween(then: Date | string, now: Date = new Date()): number { const thenDate = typeof then === "string" ? new Date(then) : then; if (Number.isNaN(thenDate.getTime())) return 0; const startThen = startOfLocalDay(thenDate); const startNow = startOfLocalDay(now); if (startNow <= startThen) return 0; let count = 0; const cursor = new Date(startThen); cursor.setDate(cursor.getDate() + 1); // start counting the day AFTER creation while (cursor <= startNow) { const dow = cursor.getDay(); // 0 = Sunday, 6 = Saturday if (dow !== 0 && dow !== 6) count++; cursor.setDate(cursor.getDate() + 1); } return count; } /** * True iff `then` is older than `days` working days before `now`. * Convenience wrapper around `workingDaysBetween` for the age warning. */ export function isOlderThanWorkingDays( then: Date | string, days: number, now: Date = new Date() ): boolean { return workingDaysBetween(then, now) >= days; }