From 02a44f3c97183c5335297af1aab4df42fe90801b Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:57:31 +0000 Subject: [PATCH 1/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/lib/format.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/format.ts b/src/lib/format.ts index 943d822..c5392a9 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -3,6 +3,24 @@ export function formatCurrency(amount: number | string | null | undefined) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(n || 0); } +/** + * Parse a date-only string ("YYYY-MM-DD") as a LOCAL date (midnight in the + * user's timezone). Using `new Date("2026-04-24")` parses as UTC midnight, + * which renders as the previous day in negative-offset timezones — that's + * the source of the "due date is one day earlier" bug. + * + * Returns null for falsy/invalid input. If a non date-only string or Date is + * passed, returns a Date built from it as-is. + */ +export function parseDateOnly(value: string | Date | null | undefined): Date | null { + if (!value) return null; + if (value instanceof Date) return value; + const m = value.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (m) return new Date(+m[1], +m[2] - 1, +m[3]); + const d = new Date(value); + return isNaN(d.getTime()) ? null : d; +} + export function formatDate(value: string | Date | null | undefined) { if (!value) return "—"; // Date-only strings ("YYYY-MM-DD") must be rendered in UTC, otherwise From 186f5eeb9c78b6efcd94e4873248a78d1a6948e6 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:57:45 +0000 Subject: [PATCH 2/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/tasks/task-detail-panel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/tasks/task-detail-panel.tsx b/src/components/tasks/task-detail-panel.tsx index 6315bbb..ae5f88e 100644 --- a/src/components/tasks/task-detail-panel.tsx +++ b/src/components/tasks/task-detail-panel.tsx @@ -38,6 +38,7 @@ import { DialogFooter, } from "@/components/ui/dialog"; import { format } from "date-fns"; +import { parseDateOnly } from "@/lib/format"; import type { Database } from "@/integrations/supabase/types"; import { cn } from "@/lib/utils"; import { createNotifications } from "@/lib/notifications"; From 8e9086af576ccbe5f4f4c4e55e48444c27377084 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:57:54 +0000 Subject: [PATCH 3/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/tasks/task-detail-panel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/tasks/task-detail-panel.tsx b/src/components/tasks/task-detail-panel.tsx index ae5f88e..1c5bc46 100644 --- a/src/components/tasks/task-detail-panel.tsx +++ b/src/components/tasks/task-detail-panel.tsx @@ -454,13 +454,13 @@ export function TaskDetailPanel({ {task.due_date && ( From 37587e4e10c61e9b5f3703c94945076a64f588a6 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:58:03 +0000 Subject: [PATCH 4/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/tasks.index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/tasks.index.tsx b/src/routes/tasks.index.tsx index 75f1ca6..79b8a0a 100644 --- a/src/routes/tasks.index.tsx +++ b/src/routes/tasks.index.tsx @@ -24,6 +24,7 @@ import { NewTaskDialog } from "@/components/tasks/new-task-dialog"; import { ApplyWorkflowPickerDialog } from "@/components/tasks/apply-workflow-picker-dialog"; import { cn } from "@/lib/utils"; import { spawnNextWorkflowTask } from "@/lib/workflow-chain"; +import { parseDateOnly } from "@/lib/format"; export const Route = createFileRoute("/tasks/")({ component: TasksPage, From 6bbef9c50c0a362f8a5eb88ab3fc9b3cda89381f Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:58:17 +0000 Subject: [PATCH 5/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/tasks.index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/tasks.index.tsx b/src/routes/tasks.index.tsx index 79b8a0a..c826086 100644 --- a/src/routes/tasks.index.tsx +++ b/src/routes/tasks.index.tsx @@ -167,7 +167,7 @@ function TasksPage() { groups["No due date"].push(t); continue; } - const d = startOfDay(new Date(t.due_date)); + const d = startOfDay(parseDateOnly(t.due_date)!); if (isBefore(d, today)) groups.Overdue.push(t); else if (isSameDay(d, today)) groups.Today.push(t); else if (isSameDay(d, tomorrow)) groups.Tomorrow.push(t); @@ -298,7 +298,7 @@ function TasksPage() { .map((p) => p.full_name || p.email); let countdown: { label: string; tone: string } | null = null; if (t.due_date && t.status !== "complete") { - const days = differenceInCalendarDays(startOfDay(new Date(t.due_date)), startOfDay(new Date())); + const days = differenceInCalendarDays(startOfDay(parseDateOnly(t.due_date)!), startOfDay(new Date())); if (days < 0) countdown = { label: `${Math.abs(days)}d overdue`, @@ -343,7 +343,7 @@ function TasksPage() {
- {format(new Date(t.due_date), "MMM d, yyyy")} + {format(parseDateOnly(t.due_date)!, "MMM d, yyyy")}
{countdown && (
From d4be91ff54bef2a4031d653cbbf0e6f2c7dc6826 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:58:28 +0000 Subject: [PATCH 6/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/cases/tasks-tab.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/cases/tasks-tab.tsx b/src/components/cases/tasks-tab.tsx index 024aa9c..e2d2dd7 100644 --- a/src/components/cases/tasks-tab.tsx +++ b/src/components/cases/tasks-tab.tsx @@ -12,6 +12,7 @@ import { NewTaskDialog } from "@/components/tasks/new-task-dialog"; import { ApplyWorkflowDialog } from "@/components/tasks/apply-workflow-dialog"; import { cn } from "@/lib/utils"; import { spawnNextWorkflowTask } from "@/lib/workflow-chain"; +import { parseDateOnly } from "@/lib/format"; interface TaskRow { id: string; @@ -130,7 +131,10 @@ export function CaseTasksTab({ caseId }: { caseId: string }) { )} {tasks.map((t) => { const ids = assigneesByTask[t.id] || []; - const overdue = t.due_date && t.status === "incomplete" && isBefore(startOfDay(new Date(t.due_date)), today); + const overdue = + t.due_date && + t.status === "incomplete" && + isBefore(startOfDay(parseDateOnly(t.due_date)!), today); return (
- {format(new Date(t.due_date), "MMM d")} + {format(parseDateOnly(t.due_date)!, "MMM d")} )}
From ae821d015e8dd4c92cbb51108cc184e847699263 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:58:42 +0000 Subject: [PATCH 7/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/collections.$collectionId.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/collections.$collectionId.tsx b/src/routes/collections.$collectionId.tsx index 7e62441..5d0fcc4 100644 --- a/src/routes/collections.$collectionId.tsx +++ b/src/routes/collections.$collectionId.tsx @@ -38,7 +38,7 @@ import { CaseInvoicesTab } from "@/components/cases/invoices-tab"; import { CaseLitigationTab } from "@/components/cases/litigation-tab"; import { CaseCustomFieldsTab } from "@/components/cases/custom-fields-tab"; -import { formatDate } from "@/lib/format"; +import { formatDate, parseDateOnly } from "@/lib/format"; import { ArrowLeft, ArrowRight, @@ -526,7 +526,7 @@ function CollectionDetailRoute() { const overdue = !t.done && t.due_date && - new Date(t.due_date) < new Date(new Date().toDateString()); + parseDateOnly(t.due_date)! < new Date(new Date().toDateString()); return (
Date: Fri, 24 Apr 2026 13:58:59 +0000 Subject: [PATCH 8/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/collections/payment-plans-panel.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/collections/payment-plans-panel.tsx b/src/components/collections/payment-plans-panel.tsx index ec2ff75..cef9093 100644 --- a/src/components/collections/payment-plans-panel.tsx +++ b/src/components/collections/payment-plans-panel.tsx @@ -25,7 +25,7 @@ import { } from "@/components/ui/select"; import { CalendarClock, Loader2, Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; -import { formatCurrency, formatDate } from "@/lib/format"; +import { formatCurrency, formatDate, parseDateOnly } from "@/lib/format"; type Frequency = "weekly" | "biweekly" | "monthly"; type PlanStatus = "active" | "completed" | "defaulted" | "cancelled"; @@ -200,7 +200,8 @@ export function PaymentPlansPanel({ collectionId }: { collectionId: string }) { {ins.length > 0 && (
{ins.map((i) => { - const overdue = !i.paid && new Date(i.due_date) < new Date(new Date().toDateString()); + const overdue = + !i.paid && parseDateOnly(i.due_date)! < new Date(new Date().toDateString()); return (
Date: Fri, 24 Apr 2026 13:59:15 +0000 Subject: [PATCH 9/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/lib/workflow-chain.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib/workflow-chain.ts b/src/lib/workflow-chain.ts index 660dedd..ea015b8 100644 --- a/src/lib/workflow-chain.ts +++ b/src/lib/workflow-chain.ts @@ -1,5 +1,17 @@ import { supabase } from "@/integrations/supabase/client"; -import { addDays, format } from "date-fns"; +import { addDays } from "date-fns"; + +/** + * Format a Date as "YYYY-MM-DD" using its UTC components. We do this so that + * a `completed_at` UTC timestamp like 2026-04-24T02:00:00Z (which is still + * Apr 23 in US local time) doesn't shift the computed due_date back a day. + */ +function toUTCDateString(d: Date): string { + const y = d.getUTCFullYear(); + const m = String(d.getUTCMonth() + 1).padStart(2, "0"); + const day = String(d.getUTCDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} /** * After a task is marked complete, spawn the next task from its workflow @@ -48,7 +60,7 @@ export async function spawnNextWorkflowTask(taskId: string): Promise