From 20f7ec1188983dc521b8d0a61b119bfc9105da07 Mon Sep 17 00:00:00 2001 From: lorentz Date: Thu, 28 May 2026 23:33:54 +0000 Subject: [PATCH] Fix: 8 issues from Horizon Issues & Next Steps doc 1. Tasks disappearing (Mimi bug) - open tasks now always visible regardless of renewal group date; only terminal tasks filter by group age 2. Star date change recalculates task due dates - PATCH policy-group now updates dueDate on all open template tasks when renewalDate changes 3. Auto task generation on setup complete - wizard calls generate-tasks for each saved group immediately after Save & Complete 4. Manual sync date range - defaults are now dynamic (prev year to +2y) instead of hardcoded 2026-01-01 / 2026-12-31 5. Setup queue filters out clients with no active policies; shows skipped count so managers know what was excluded 6. Audit logging gap fixed - notes route now emits TASK_STATUS_CHANGED with old+new status when note submission also changes status 7. Renewal day-count warnings now shown on individual policy cards in client detail (<=30d destructive, <=90d secondary badge) 8. Client notes now show save timestamp after editing --- dev/Horizon issues and Next Steps.docx | Bin 0 -> 27123 bytes dev/Seubert-Claims-LukeBillman.md | 147 ++++++++++++++++++ dev/audit-logging-fix-plan.md | 108 +++++++++++++ .../src/app/(dashboard)/clients/[id]/page.tsx | 1 + .../app/(dashboard)/manager/setup/page.tsx | 59 ++++--- ondeck/src/app/(dashboard)/tasks/page.tsx | 3 +- .../src/app/api/clients/[id]/setup/route.ts | 16 +- .../src/app/api/clients/[id]/tasks/route.ts | 3 +- .../src/app/api/policy-groups/[id]/route.ts | 24 ++- ondeck/src/app/api/tasks/[id]/notes/route.ts | 11 ++ ondeck/src/components/admin/sync-trigger.tsx | 5 +- .../src/components/clients/client-detail.tsx | 32 +++- .../renewal-groups/setup-wizard.tsx | 12 +- ondeck/src/lib/auth.ts | 2 +- 14 files changed, 381 insertions(+), 42 deletions(-) create mode 100644 dev/Horizon issues and Next Steps.docx create mode 100644 dev/Seubert-Claims-LukeBillman.md create mode 100644 dev/audit-logging-fix-plan.md diff --git a/dev/Horizon issues and Next Steps.docx b/dev/Horizon issues and Next Steps.docx new file mode 100644 index 0000000000000000000000000000000000000000..2afc0bfc3315d6a83ccb4eb4ec545bc11fe3c812 GIT binary patch literal 27123 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 s4*>WNm+UV5&j;b}@Fc_E;6EOV@=^%jWi_Cq literal 0 HcmV?d00001 diff --git a/dev/Seubert-Claims-LukeBillman.md b/dev/Seubert-Claims-LukeBillman.md new file mode 100644 index 0000000..462ea24 --- /dev/null +++ b/dev/Seubert-Claims-LukeBillman.md @@ -0,0 +1,147 @@ +## Key Outcomes + +The meeting resolved critical issues with disappearing tasks and established a comprehensive solution for handling client renewal dates. All tasks prior to April 1, 2026 will be marked complete to clear historical backlog, and a new client-level renewal date field will be implemented to ensure consistent task generation across different renewal group configurations. +## Decisions Made + +### Task Visibility and Historical Data + +- **Mark all tasks before April 1, 2026 as complete** to eliminate the backlog of 599+ overdue tasks that were causing confusion across user dashboards +- **Standardize overdue task definition** across all system views (tasks page, manager page, and workload page) to mean: past the due date, not marked as N/A or completed, with no lower bound restriction + +### Client Renewal Date Logic + +- **Add a "Client Renewal" date field** at the client level that will be editable by managers and serve as the source of truth for client-level tasks +- **Implement automatic date suggestion logic** using the following priority: (1) default/starred group renewal date, (2) if only one group exists, use that group's date, (3) if only individual policies exist, use policy date +- **Allow manual override** of the suggested client renewal date during client setup to accommodate complex scenarios like transportation clients with multiple renewal groups + +### Meeting Cadence + +- **Establish weekly or bi-weekly recurring meetings** short-term to ensure system issues are addressed promptly +- **Explore adding in-app functionality** for submitting change requests or bug reports that can be quickly triaged as either bugs for immediate fixes or design issues requiring meetings + +## Issues Resolved + +### Disappearing Tasks Problem + +- **Root cause identified**: Original 7-day visibility window was removed to address initial backlog, but this caused legitimate overdue tasks to accumulate without being hidden +- **Dashboard inconsistencies**: Task counts differed between primary dashboard and task dashboard due to three different overdue definitions being applied across different system views +- **Example case**: Mimi showed 599 past due tasks in one view but only 2 in another view + +### JG Contracting Renewal Task Generation Failure + +- **Problem**: Client with January 1, 2027 renewal wasn't generating expected claim review tasks in April and August 2026 +- **Root cause**: System was following the June 30 renewal date of a single unassigned policy instead of the January 1 date of the main renewal group containing the bulk of policies +- **Why it happened**: System used "nearest to year start" logic (default date rule) which selected June 30 as closer to year start than January 1, applying this to client-level tasks +- **Evidence**: Client-level tasks appeared at 275 days before June 30 (September 28) and 90 days before June 30 (April 1), confirming they followed the wrong renewal date + +### Group Numbering Confusion + +- **Issue**: JG Contracting's single renewal group was labeled "Group 2" instead of "Group 1" +- **Explanation**: Group functionality and UI were built on different days, causing inconsistent labeling logic + +## Technical Implementation Details + +### Task Status Update Execution + +- **Bulk update completed**: 3,292 tasks with due date before April 1, 2026 and status "not started" were marked as completed +- **Logging implemented**: Bulk changes are now logged to track why large numbers of tasks changed status +- **Potential issue identified**: Some tasks that were previously marked complete after April 1 may have been reset to "not started" - requires investigation + +### Task Source Tracking + +- **New icon added**: Small settings icon appears next to policies on the right side of the screen to show where tasks originated from +- **Helps debugging**: Allows users to trace which renewal date or policy group generated specific tasks + +### Client Renewal Date Field Implementation + +- **Location**: Will appear in the client setup screen alongside existing renewal group configuration +- **Label**: "Client Renewal" to allow future reuse for other purposes beyond just tasking +- **Behavior**: Auto-populated by logic on client creation but editable by managers at any time +- **No automatic overwriting**: Once set, the date won't change automatically unless a specific triggering action occurs + +## Complex Scenarios Addressed + +### Transportation Clients with Multiple Renewal Groups + +- **Use case**: Clients with auto and motor truck cargo renewing 1/1, but AUCAC and truck broker liability renewing 3/1 +- **Challenge**: System must allow selection of which group's renewal date should drive client-level tasks +- **Solution**: Manager can manually select the appropriate renewal date during setup, typically following the "big line" (highest premium policy) + +### Single Policy vs. Multiple Groups + +- **Single policy scenario**: If client has only one policy with no groups, system will use that policy's renewal date +- **Multiple groups scenario**: System will follow the starred/default group's renewal date +- **Override capability**: Regardless of automatic selection, managers can change the client renewal date if business logic dictates otherwise + +## Pending Confirmation + +- **Task reset investigation needed**: Verify whether tasks completed after April 1, 2026 were incorrectly reset to "not started" during the bulk update +- **Sync date range behavior**: Confirm that policy syncs only capture policies within the specified date range (e.g., a policy starting 8/1/25 expiring 8/1/26 would be missed if sync runs for 1/1/26-12/31/26) + +## Technical Specifications + +### Overdue Task Definition (Standardized) + +- **Criteria**: Task is overdue if all of the following are true: + - Due date is before today + - Status is NOT "N/A" + - Status is NOT "Completed" + - No lower bound date restriction (perpetual visibility) + +### Task Generation Logic Hierarchy + +1. **Client-level tasks**: Follow the client renewal date field +2. **Group-level tasks**: Follow their specific group's renewal date +3. **Policy-level tasks**: Follow individual policy renewal dates + +### Default Date Rule Logic (Pre-Fix) + +- **Original behavior**: Selected renewal date nearest to start of year across all policies and groups +- **Problem**: Did not account for which policies/groups were primary or contained bulk of coverage +- **New behavior**: Will populate suggested client renewal date but allow override + +## Action Items + +- **Lawrence**: Implement client renewal date field in client setup screen with auto-population logic and manual override capability +- **Lawrence**: Investigate and resolve task reset issue where completed tasks after 4/1 may have been changed to "not started" +- **Lawrence**: Update sync date range to clearly indicate which policies will be captured based on effective/expiration dates +- **Luke**: Set up weekly or bi-weekly recurring meeting with Lawrence to review ongoing system issues +- **Luke**: Review task counts across all users to verify the April 1 bulk completion resolved dashboard inconsistencies + +## Open Questions + +### Task Visibility After Bulk Update + +- Why are some tasks showing different statuses than expected after the bulk completion of pre-April 1 tasks? +- Are there any completed tasks from April 2026 onwards that were incorrectly reset? + +### Sync Functionality + +- Does the sync date range need to be adjusted to capture policies with effective dates before the sync year but expiration dates within it? +- Should sync default dates be modified to be more inclusive of cross-year policies? + +## System Behavior Notes + +### Task Page vs. Dashboard Discrepancies (Pre-Fix) + +- **Tasks page**: Counted all overdue tasks with no lower bound +- **Manager page**: Used configurable overdue window (last 30 days only) +- **Workload KPI**: Counted all tasks including unassigned ones +- **Result**: Same user could see vastly different overdue counts depending on which view they accessed + +### Renewal Group Star Functionality + +- **Purpose**: Star indicates the default renewal group that should drive client-level task generation +- **Previous assumption**: System was following starred groups for client-level tasks +- **Actual behavior**: System was using "nearest to year start" logic instead, ignoring the star designation + +### Policy Sync Date Range Default + +- **Current behavior**: Sync defaults to current calendar year (1/1/26 - 12/31/26) +- **Limitation**: Policies with effective dates in previous year but expiration in current year will be missed unless date range is manually adjusted + +## Next Meeting + +- **Frequency**: Weekly or bi-weekly to be scheduled +- **Topics**: Continue working through system functionality issues and enhancements +- **Participants**: Luke, Lawrence, potentially Dawn and Christy as needed diff --git a/dev/audit-logging-fix-plan.md b/dev/audit-logging-fix-plan.md new file mode 100644 index 0000000..ea585cb --- /dev/null +++ b/dev/audit-logging-fix-plan.md @@ -0,0 +1,108 @@ +# Audit Logging Fix Plan +_Scheduled: after hours_ + +## Problem + +Two code paths can change a task's status without capturing the **previous** status in the audit log: + +1. **`POST /api/tasks/[id]/notes`** — When a user adds a note and simultaneously changes task status, the audit entry is `TASK_NOTE_ADDED` with only `statusChange: `. The old status is never recorded. +2. **`src/lib/shape-import/run-import.ts`** — SHAPE import directly calls `prisma.task.update()` and `prisma.task.create()` with `COMPLETED`/`NA` status. Zero per-task audit entries. + +The notes-route gap is the most operationally urgent because users are actively using it daily. The import gap is lower risk (import is a rare admin operation). + +--- + +## Fix 1 — Notes Route: Capture Old Status (Priority: High) + +**File:** `src/app/api/tasks/[id]/notes/route.ts` + +### What to change + +The task is already fetched before the note is written (to check assignment). Add the old status to the audit log, and emit a **separate** `TASK_STATUS_CHANGED` entry when a status change accompanies the note. + +```ts +// After fetching task (line ~42), old status is already available as task.status + +if (status && ['COMPLETED', 'NOT_STARTED', 'IN_PROGRESS'].includes(status)) { + await prisma.task.update({ ... }) // existing logic, unchanged + + // ADD: dedicated status-change audit entry + await prisma.auditLog.create({ + data: { + userId, + action: 'TASK_STATUS_CHANGED', + entityType: 'Task', + entityId: id, + oldValues: { status: task.status }, + newValues: { status, source: 'note' }, + }, + }) +} + +// Existing TASK_NOTE_ADDED entry — update to include oldStatus too +await prisma.auditLog.create({ + data: { + userId, + action: 'TASK_NOTE_ADDED', + entityType: 'Task', + entityId: id, + oldValues: { status: task.status }, // ADD this + newValues: { noteId: note.id, statusChange: status ?? null }, + }, +}) +``` + +### Verification after deploy +```sql +-- Confirm old status is now captured +SELECT old_values->>'status', new_values->>'status', new_values->>'source', created_at +FROM audit_logs +WHERE action = 'TASK_STATUS_CHANGED' +ORDER BY created_at DESC +LIMIT 10; +``` + +--- + +## Fix 2 — PATCH Route: Confirm old_values.status is always set (Priority: Medium) + +**File:** `src/app/api/tasks/[id]/route.ts` + +The `TASK_UPDATED` entries already capture `oldValues: { status: task.status }`, but only when a status field is present in the request body. Verify the task is always fetched before the update (it is — line ~30 fetches `task`). No code change needed; just confirm via spot-check. + +```sql +-- Spot-check: any TASK_UPDATED entries missing old_values.status +SELECT COUNT(*) FROM audit_logs +WHERE action = 'TASK_UPDATED' + AND old_values->>'status' IS NULL; +``` + +--- + +## Fix 3 — SHAPE Import: Per-task Audit Logging (Priority: Low) + +**File:** `src/lib/shape-import/run-import.ts` + +This is a bulk operation run rarely by admins. The `ShapeImportRun` table already records run-level stats. Acceptable to leave per-task audit logging out for now given the volume (hundreds of tasks per run would bloat the audit table). + +**Recommendation:** Add a summary `SHAPE_IMPORT_TASK_SUMMARY` audit entry per import run that records counts by status (created COMPLETED, created NOT_STARTED, updated, skipped). Already partially done via `ImportStats`. + +--- + +## Deployment Steps + +1. Apply Fix 1 to `notes/route.ts` +2. TypeScript check: `npx tsc --noEmit` +3. Commit: `git commit -m "Fix: capture old status in task notes audit log"` +4. Push + deploy: `docker compose -f /opt/stacks/horizon/docker-compose.yml up -d --build` +5. Run verification queries above to confirm +6. Optionally apply Fix 3 summary entry to shape import + +--- + +## Notes + +- No DB schema changes required — `old_values` and `new_values` are already `jsonb` columns. +- No migration needed. +- Zero downtime deploy (standard Next.js container rebuild). +- Fix 1 is safe to apply during off-hours with no user impact. diff --git a/ondeck/src/app/(dashboard)/clients/[id]/page.tsx b/ondeck/src/app/(dashboard)/clients/[id]/page.tsx index 05ff44c..d29b9d5 100644 --- a/ondeck/src/app/(dashboard)/clients/[id]/page.tsx +++ b/ondeck/src/app/(dashboard)/clients/[id]/page.tsx @@ -66,6 +66,7 @@ export default async function ClientDetailPage({ OR: [ { policyGroupId: null }, { policyGroup: { renewalDate: { gte: cutoff } } }, + { status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] } }, ], AND: [ { diff --git a/ondeck/src/app/(dashboard)/manager/setup/page.tsx b/ondeck/src/app/(dashboard)/manager/setup/page.tsx index 9030d9d..d18d618 100644 --- a/ondeck/src/app/(dashboard)/manager/setup/page.tsx +++ b/ondeck/src/app/(dashboard)/manager/setup/page.tsx @@ -30,28 +30,40 @@ export default async function SetupQueuePage() { redirect('/dashboard') } - const clients = await prisma.client.findMany({ - where: { - designation: { name: { in: ['Shape', 'Shape 2'] } }, - policies: { some: {} }, - OR: [ - { claimsAdvocateId: null }, - { setupCompletedAt: null }, - ], - }, - select: { - id: true, - name: true, - createdAt: true, - claimsAdvocateId: true, - setupCompletedAt: true, - policies: { - select: { expirationDate: true }, + const DEAD_STATUSES = ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] + + const [clients, skippedCount] = await Promise.all([ + prisma.client.findMany({ + where: { + designation: { name: { in: ['Shape', 'Shape 2'] } }, + policies: { some: { status: { notIn: DEAD_STATUSES } } }, + OR: [ + { claimsAdvocateId: null }, + { setupCompletedAt: null }, + ], }, - _count: { select: { policies: true } }, - }, - orderBy: { createdAt: 'asc' }, - }) + select: { + id: true, + name: true, + createdAt: true, + claimsAdvocateId: true, + setupCompletedAt: true, + policies: { + where: { status: { notIn: DEAD_STATUSES } }, + select: { expirationDate: true }, + }, + _count: { select: { policies: { where: { status: { notIn: DEAD_STATUSES } } } } }, + }, + orderBy: { createdAt: 'asc' }, + }), + prisma.client.count({ + where: { + designation: { name: { in: ['Shape', 'Shape 2'] } }, + OR: [{ claimsAdvocateId: null }, { setupCompletedAt: null }], + NOT: { policies: { some: { status: { notIn: DEAD_STATUSES } } } }, + }, + }), + ]) const now = new Date() @@ -85,6 +97,11 @@ export default async function SetupQueuePage() {

New Client Setup

Clients that need a claims advocate or renewal group configuration. + {skippedCount > 0 && ( + + ({skippedCount} skipped — no active policies) + + )}

diff --git a/ondeck/src/app/(dashboard)/tasks/page.tsx b/ondeck/src/app/(dashboard)/tasks/page.tsx index b5f80b4..6176d1d 100644 --- a/ondeck/src/app/(dashboard)/tasks/page.tsx +++ b/ondeck/src/app/(dashboard)/tasks/page.tsx @@ -29,11 +29,12 @@ export default async function TasksPage() { policy: { status: { in: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] } }, }, }, - // Exclude tasks on expired renewal groups + // Exclude tasks on expired renewal groups — but NEVER hide open tasks { OR: [ { policyGroupId: null }, { policyGroup: { renewalDate: { gte: cutoff } } }, + { status: { notIn: TERMINAL_STATUSES } }, ], }, // Keep ALL open tasks regardless of age; only show terminal tasks completed/closed recently diff --git a/ondeck/src/app/api/clients/[id]/setup/route.ts b/ondeck/src/app/api/clients/[id]/setup/route.ts index 5435229..f395b2d 100644 --- a/ondeck/src/app/api/clients/[id]/setup/route.ts +++ b/ondeck/src/app/api/clients/[id]/setup/route.ts @@ -129,10 +129,16 @@ async function handleSave( }) }) - const updated = await prisma.client.findUnique({ - where: { id: clientId }, - select: { id: true, name: true, renewalDate: true, setupCompletedAt: true, claimsAdvocateId: true }, - }) + const [updated, savedGroups] = await Promise.all([ + prisma.client.findUnique({ + where: { id: clientId }, + select: { id: true, name: true, renewalDate: true, setupCompletedAt: true, claimsAdvocateId: true }, + }), + prisma.policyGroup.findMany({ + where: { clientId }, + select: { id: true, name: true, renewalDate: true }, + }), + ]) // If starred group changed, recalculate due dates on open client-level template tasks const renewalDateChanged = @@ -208,7 +214,7 @@ async function handleSave( await Promise.all(auditEntries) - return NextResponse.json({ client: updated }) + return NextResponse.json({ client: updated, groups: savedGroups }) } catch (error) { console.error('Setup save error:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) diff --git a/ondeck/src/app/api/clients/[id]/tasks/route.ts b/ondeck/src/app/api/clients/[id]/tasks/route.ts index b2c4cab..5d8460a 100644 --- a/ondeck/src/app/api/clients/[id]/tasks/route.ts +++ b/ondeck/src/app/api/clients/[id]/tasks/route.ts @@ -30,10 +30,11 @@ export async function GET( where.NOT = { policy: { status: { in: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] } }, } - // Exclude tasks on expired renewal groups + // Exclude tasks on expired renewal groups — but NEVER hide open tasks where.OR = [ { policyGroupId: null }, { policyGroup: { renewalDate: { gte: cutoff } } }, + { status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] } }, ] // Keep ALL open tasks regardless of age; only hide ancient terminal tasks where.AND = [ diff --git a/ondeck/src/app/api/policy-groups/[id]/route.ts b/ondeck/src/app/api/policy-groups/[id]/route.ts index b8d1ecd..f5eec68 100644 --- a/ondeck/src/app/api/policy-groups/[id]/route.ts +++ b/ondeck/src/app/api/policy-groups/[id]/route.ts @@ -153,6 +153,26 @@ export async function PATCH( }, }) + // Recalculate due dates on open template tasks when renewal date changes + let tasksRescheduled = 0 + if (renewalDate !== undefined) { + const newRenewalDate = new Date(renewalDate) + const openTasks = await prisma.task.findMany({ + where: { + policyGroupId: id, + templateId: { not: null }, + status: { notIn: ['COMPLETED', 'CANCELLED', 'NA'] }, + }, + select: { id: true, daysOffset: true }, + }) + for (const t of openTasks) { + const newDue = new Date(newRenewalDate) + newDue.setDate(newDue.getDate() + (t.daysOffset ?? 0)) + await prisma.task.update({ where: { id: t.id }, data: { dueDate: newDue } }) + } + tasksRescheduled = openTasks.length + } + await prisma.auditLog.create({ data: { userId: (session.user as any).id, @@ -160,11 +180,11 @@ export async function PATCH( entityType: 'PolicyGroup', entityId: group.id, oldValues: { name: existing.name, renewalDate: existing.renewalDate }, - newValues: { name, renewalDate, notes, policyIds }, + newValues: { name, renewalDate, notes, policyIds, tasksRescheduled }, }, }) - return NextResponse.json(group) + return NextResponse.json({ ...group, tasksRescheduled }) } catch (error) { console.error('Policy group PATCH error:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) diff --git a/ondeck/src/app/api/tasks/[id]/notes/route.ts b/ondeck/src/app/api/tasks/[id]/notes/route.ts index 94c0e87..1fbc067 100644 --- a/ondeck/src/app/api/tasks/[id]/notes/route.ts +++ b/ondeck/src/app/api/tasks/[id]/notes/route.ts @@ -72,6 +72,16 @@ export async function POST( : { completedAt: null, completedBy: null }), }, }) + await prisma.auditLog.create({ + data: { + userId, + action: 'TASK_STATUS_CHANGED', + entityType: 'Task', + entityId: id, + oldValues: { status: task.status }, + newValues: { status, source: 'note', noteId: note.id }, + }, + }) } await prisma.auditLog.create({ @@ -80,6 +90,7 @@ export async function POST( action: 'TASK_NOTE_ADDED', entityType: 'Task', entityId: id, + oldValues: { status: task.status }, newValues: { noteId: note.id, statusChange: status ?? null }, }, }) diff --git a/ondeck/src/components/admin/sync-trigger.tsx b/ondeck/src/components/admin/sync-trigger.tsx index 435ec5f..70f15c2 100644 --- a/ondeck/src/components/admin/sync-trigger.tsx +++ b/ondeck/src/components/admin/sync-trigger.tsx @@ -13,8 +13,9 @@ export function SyncTrigger() { const [progress, setProgress] = useState(0) const [status, setStatus] = useState<'idle' | 'syncing' | 'success' | 'error'>('idle') const [result, setResult] = useState(null) - const [startDate, setStartDate] = useState('2026-01-01') - const [endDate, setEndDate] = useState('2026-12-31') + const currentYear = new Date().getFullYear() + const [startDate, setStartDate] = useState(`${currentYear - 1}-01-01`) + const [endDate, setEndDate] = useState(`${currentYear + 2}-12-31`) const handleSync = async () => { setSyncing(true) diff --git a/ondeck/src/components/clients/client-detail.tsx b/ondeck/src/components/clients/client-detail.tsx index 052ca99..eb1d2f9 100644 --- a/ondeck/src/components/clients/client-detail.tsx +++ b/ondeck/src/components/clients/client-detail.tsx @@ -32,7 +32,7 @@ import { import { Textarea } from '@/components/ui/textarea' import { Building2, MapPin, Phone, Mail, FileText, CheckSquare, CalendarRange, Users, X, Plus, UserCheck, StickyNote, Pencil, Trash2, ChevronDown, ChevronUp, ArrowUp, ArrowDown, ArrowUpDown, GitBranch, ChevronRight, Settings2, MessageSquare } from 'lucide-react' import { Combobox, type ComboboxGroup } from '@/components/ui/combobox' -import { formatDate, formatRenewalDate } from '@/lib/utils' +import { formatDate, formatRenewalDate, daysUntil } from '@/lib/utils' import { PolicyGroupManager } from '@/components/clients/policy-group-manager' import { AdditionalServiceModal } from '@/components/tasks/additional-service-modal' import { TaskCard } from '@/components/tasks/task-card' @@ -61,6 +61,7 @@ export function ClientDetail({ client, designations, policyGroups = [], allPolic const [saving, setSaving] = useState(false) const [notes, setNotes] = useState(client.notes || '') const [notesSaving, setNotesSaving] = useState(false) + const [notesSavedAt, setNotesSavedAt] = useState(null) const [tasks, setTasks] = useState(client.tasks || []) const [contacts, setContacts] = useState([]) const [contactsLoading, setContactsLoading] = useState(false) @@ -296,6 +297,7 @@ export function ClientDetail({ client, designations, policyGroups = [], allPolic body: JSON.stringify({ notes: notes || null }), }) if (!res.ok) throw new Error() + setNotesSavedAt(new Date().toLocaleString()) toast.success('Notes saved') } catch { toast.error('Failed to save notes') @@ -461,9 +463,16 @@ export function ClientDetail({ client, designations, policyGroups = [], allPolic onChange={(e) => setNotes(e.target.value)} rows={4} /> - +
+ + {notesSavedAt && ( + + Saved {notesSavedAt} + + )} +
)} @@ -514,11 +523,18 @@ export function ClientDetail({ client, designations, policyGroups = [], allPolic Policy #: {policy.policyNumber || 'N/A'}

- - +
+ {(() => { + const d = daysUntil(policy.expirationDate) + if (d <= 0) return null + if (d <= 30) return {d}d + if (d <= 90) return {d}d + return null + })()} + Renewal {formatRenewalDate(policy.expirationDate)} - - + +
{/* Policy Details Grid */} diff --git a/ondeck/src/components/renewal-groups/setup-wizard.tsx b/ondeck/src/components/renewal-groups/setup-wizard.tsx index bcb06be..996b94d 100644 --- a/ondeck/src/components/renewal-groups/setup-wizard.tsx +++ b/ondeck/src/components/renewal-groups/setup-wizard.tsx @@ -399,7 +399,17 @@ export function SetupWizard({ toast.success('Draft saved') setWizardDirty(false) } else { - toast.success('Setup complete!') + const groups: { id: string }[] = data.groups ?? [] + if (groups.length > 0) { + toast.loading('Generating tasks…', { id: 'gen-tasks' }) + await Promise.allSettled( + groups.map((g) => + fetch(`/api/policy-groups/${g.id}/generate-tasks`, { method: 'POST' }) + ) + ) + toast.dismiss('gen-tasks') + } + toast.success('Setup complete! Tasks generated.') router.push(`/clients/${clientId}`) } } catch (err: any) { diff --git a/ondeck/src/lib/auth.ts b/ondeck/src/lib/auth.ts index 5400727..99937cd 100644 --- a/ondeck/src/lib/auth.ts +++ b/ondeck/src/lib/auth.ts @@ -36,7 +36,7 @@ export const authOptions: NextAuthOptions = { if (account) { // Create or update user in database const user = await prisma.user.upsert({ - where: { email: account.email }, + where: { entraOid: `local-${account.username}` }, update: { displayName: account.name, lastLoginAt: new Date(),