WFDASH-WEBAPP/src/lib/working-days.ts
SJCoetz 2c6155abbb feat: add AI quote item rewriter and supporting UI/UX changes
- Add `user_edited` field to QuoteItem type to gate AI rewrites on user-edited content
- Add idempotent migration script to add and backfill the `user_edited` field across quote items
- Replace plain Select components with searchable SupplierSearch combobox across order and quote pages
- Add working days calculation utility and aged quote warning badges for overdue quotes
- Implement expandable row previews in the quotes list view
- Add full AI rewrite workflow: backend API endpoint, frontend modal, and n8n workflow definition
- Update package scripts for the new migration
2026-06-22 18:29:59 +02:00

57 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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;
}