From b8ea94e37b36acc0965953b70a0d2a58430a91aa Mon Sep 17 00:00:00 2001 From: lorentz Date: Sat, 18 Jul 2026 13:05:12 +0000 Subject: [PATCH] feat(tasks): add per-client mode and preview-before-generate to Generate & Assign --- .../(dashboard)/tasks/assign/page-client.tsx | 216 ++++++++++++++---- 1 file changed, 172 insertions(+), 44 deletions(-) diff --git a/ondeck/src/app/(dashboard)/tasks/assign/page-client.tsx b/ondeck/src/app/(dashboard)/tasks/assign/page-client.tsx index 04218f2..78eea06 100644 --- a/ondeck/src/app/(dashboard)/tasks/assign/page-client.tsx +++ b/ondeck/src/app/(dashboard)/tasks/assign/page-client.tsx @@ -22,12 +22,23 @@ import { TableHeader, TableRow, } from '@/components/ui/table' +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog' import { Users, Filter, CheckSquare, Wand2, MessageSquare, Pencil } from 'lucide-react' import { formatDate, formatRenewalDate } from '@/lib/utils' import { TaskEditModal, type EditableTask } from '@/components/tasks/task-edit-modal' interface SimpleUser { id: string; displayName: string | null; email: string; department: string | null } -interface SimpleClient { id: string; name: string } +interface SimpleClient { id: string; name: string; claimsAdvocateId: string | null } interface SimpleDesignation { id: string; name: string } interface BulkAssignClientProps { @@ -52,13 +63,20 @@ const DEPT_OPTIONS = [ { value: 'OTHER', label: 'Other' }, ] +interface ClientTaskSummary { + id: string + name: string + estimatedTasks: number +} + interface GenResult { + dryRun: boolean + clientsFound: number + totalEstimatedTasks: number + advocateName: string | null + clients: ClientTaskSummary[] tasksCreated: number tasksAssigned: number - clientsFound: number - groupsProcessed: number - advocateName: string | null - message?: string } export function BulkAssignClient({ users, clients, designations }: BulkAssignClientProps) { @@ -70,9 +88,13 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli const [editingTask, setEditingTask] = useState(null) // Generate & assign state + const [genMode, setGenMode] = useState<'advocate-designation' | 'client'>('advocate-designation') const [genAdvocate, setGenAdvocate] = useState('') const [genDesignation, setGenDesignation] = useState('') + const [genClientId, setGenClientId] = useState('') + const [previewing, setPreviewing] = useState(false) const [generating, setGenerating] = useState(false) + const [preview, setPreview] = useState(null) const [genResult, setGenResult] = useState(null) // Filters @@ -140,26 +162,55 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli } } - const handleGenerateAndAssign = async () => { - if (!genAdvocate || !genDesignation) return - setGenerating(true) + const buildGenBody = (dryRun: boolean) => + genMode === 'client' + ? { clientId: genClientId, dryRun } + : { advocateId: genAdvocate, designationId: genDesignation, dryRun } + + const handlePreview = async () => { + setPreviewing(true) setGenResult(null) try { const res = await fetch('/api/tasks/generate-and-assign', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ advocateId: genAdvocate, designationId: genDesignation }), + body: JSON.stringify(buildGenBody(true)), + }) + const data = await res.json() + if (!res.ok) throw new Error(data.error) + if (data.totalEstimatedTasks === 0) { + toast.info( + data.clientsFound === 0 + ? 'No clients found matching this selection' + : 'No new tasks to generate — all tasks may already exist' + ) + return + } + setPreview(data) + } catch (err: any) { + toast.error(err.message || 'Failed to preview task generation') + } finally { + setPreviewing(false) + } + } + + const handleConfirmGenerate = async () => { + setGenerating(true) + try { + const res = await fetch('/api/tasks/generate-and-assign', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(buildGenBody(false)), }) const data = await res.json() if (!res.ok) throw new Error(data.error) setGenResult(data) + setPreview(null) if (data.tasksCreated > 0) { toast.success(`Generated ${data.tasksCreated} task(s) and assigned to ${data.advocateName}`) fetchTasks() - } else if (data.clientsFound === 0) { - toast.info('No clients found with this advocate and designation') } else { - toast.info(data.message || 'No new tasks to generate') + toast.info('No new tasks to generate') } } catch (err: any) { toast.error(err.message || 'Failed to generate tasks') @@ -194,49 +245,126 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli -
-
-

Claims Advocate

- + { setGenMode(v as 'advocate-designation' | 'client'); setGenResult(null) }} + > + + By Advocate + Designation + By Client + + + + {genMode === 'advocate-designation' ? ( +
+
+

Claims Advocate

+ +
+
+

Designation

+ +
+
-
-

Designation

- + ) : ( +
+
+

Client

+ + {genClientId && !clients.find((c) => c.id === genClientId)?.claimsAdvocateId && ( +

+ This client has no claims advocate assigned. Set one before generating tasks. +

+ )} +
+
- -
+ )} + {genResult && (

{genResult.tasksCreated > 0 ? `Created ${genResult.tasksCreated} task(s) across ${genResult.clientsFound} client(s) and assigned to ${genResult.advocateName}.` - : genResult.clientsFound === 0 - ? `No clients found assigned to this advocate with that designation.` - : genResult.message || 'No new tasks to generate — all tasks may already exist.'} + : 'No new tasks to generate — all tasks may already exist.'}

)} + { if (!open) setPreview(null) }}> + + + + Generate {preview?.totalEstimatedTasks} task{preview && preview.totalEstimatedTasks !== 1 ? 's' : ''}? + + + This will create {preview?.totalEstimatedTasks} task(s) across {preview?.clientsFound} client(s) + and assign them to {preview?.advocateName}. + + +
+ + + + Client + Tasks + + + + {preview?.clients.map((c) => ( + + {c.name} + {c.estimatedTasks} + + ))} + +
+
+ + Cancel + + {generating ? 'Generating...' : 'Confirm'} + + +
+
+ {/* Filter bar */}