Tasks always show Policy #, Type, Renewal Date, and clickable notes icon on all surfaces

This commit is contained in:
lorentz 2026-04-10 14:00:54 +00:00
parent 02d1fcb73d
commit 8c5516f353
4 changed files with 75 additions and 17 deletions

View file

@ -78,6 +78,14 @@ export default async function ClientDetailPage({
}
})(),
include: {
policy: {
select: {
id: true,
policyNumber: true,
policyType: true,
expirationDate: true,
},
},
assignments: {
include: {
user: {
@ -88,6 +96,12 @@ export default async function ClientDetailPage({
},
},
},
taskNotes: {
include: {
user: { select: { id: true, displayName: true, email: true } },
},
orderBy: { createdAt: 'asc' },
},
},
orderBy: { dueDate: 'asc' },
},

View file

@ -23,7 +23,7 @@ import {
TableRow,
} from '@/components/ui/table'
import { Users, Filter, CheckSquare, Wand2, MessageSquare } from 'lucide-react'
import { formatDate } from '@/lib/utils'
import { formatDate, formatRenewalDate } from '@/lib/utils'
interface SimpleUser { id: string; displayName: string | null; email: string; department: string | null }
interface SimpleClient { id: string; name: string }
@ -327,6 +327,9 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli
<TableHead>Task</TableHead>
<TableHead>Client</TableHead>
<TableHead>Department</TableHead>
<TableHead>Policy #</TableHead>
<TableHead>Type</TableHead>
<TableHead>Renewal</TableHead>
<TableHead>Due Date</TableHead>
<TableHead>Status</TableHead>
<TableHead>Assigned To</TableHead>
@ -360,6 +363,11 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli
</TableCell>
<TableCell className="text-sm">{task.client?.name || '—'}</TableCell>
<TableCell className="text-sm">{task.department?.replace('_', ' ') || '—'}</TableCell>
<TableCell className="text-sm">{task.policy?.policyNumber || '—'}</TableCell>
<TableCell className="text-sm">{task.policy?.policyType || '—'}</TableCell>
<TableCell className="text-sm" suppressHydrationWarning>
{task.policy?.expirationDate ? formatRenewalDate(task.policy.expirationDate) : '—'}
</TableCell>
<TableCell className="text-sm">
<span suppressHydrationWarning>{formatDate(task.dueDate)}</span>
</TableCell>

View file

@ -23,7 +23,7 @@ import {
import { UserSelectContent } from '@/components/ui/user-select-content'
import { CheckSquare, Clock, AlertCircle, MessageSquare, CheckCircle2, RotateCcw, Eye, CalendarRange, ArrowRightLeft, Search, X, Building2, Plus, Ban, ArrowUpDown, ArrowUp, ArrowDown, Filter } from 'lucide-react'
import { Input } from '@/components/ui/input'
import { formatDate } from '@/lib/utils'
import { formatDate, formatRenewalDate } from '@/lib/utils'
import { AdditionalServiceModal } from '@/components/tasks/additional-service-modal'
interface TaskUser { displayName: string | null; email: string }
@ -222,18 +222,23 @@ function TaskCard({ task: initial }: { task: Task }) {
) : (
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground">Client</span>
)}
{notes.length > 0 && (
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<MessageSquare className="h-3 w-3" />{notes.length}
</span>
)}
<button
type="button"
onClick={() => setNoteOpen((o) => !o)}
className={`inline-flex items-center gap-1 text-xs rounded px-1 py-0.5 transition-colors ${
noteOpen ? 'bg-secondary text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'
}`}
title="Toggle notes"
>
<MessageSquare className="h-3 w-3" />{notes.length > 0 ? notes.length : '+'}
</button>
</div>
{task.description && (
<p className="text-sm text-muted-foreground mb-2 line-clamp-2">{task.description}</p>
)}
{/* Meta row */}
{/* Meta row — always show policy number, type, renewal date */}
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
{task.client && (
<span>
@ -249,14 +254,20 @@ function TaskCard({ task: initial }: { task: Task }) {
</span>
</span>
)}
{task.policy && (
<span>
<span className="text-muted-foreground">Policy:</span>{' '}
<span className="font-medium">
{[task.policy.policyNumber, task.policy.policyType].filter(Boolean).join(' · ')}
</span>
<span>
<span className="text-muted-foreground">Policy #:</span>{' '}
<span className="font-medium">{task.policy?.policyNumber || '—'}</span>
</span>
<span>
<span className="text-muted-foreground">Type:</span>{' '}
<span className="font-medium">{task.policy?.policyType || '—'}</span>
</span>
<span>
<span className="text-muted-foreground">Renewal:</span>{' '}
<span className="font-medium" suppressHydrationWarning>
{task.policy?.expirationDate ? formatRenewalDate(task.policy.expirationDate) : '—'}
</span>
)}
</span>
<span>
<span className="text-muted-foreground">Due:</span>{' '}
<span className={`font-medium ${isOverdue ? 'text-red-500' : ''}`}>

View file

@ -20,7 +20,7 @@ import {
SelectValue,
} from '@/components/ui/select'
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 } from 'lucide-react'
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 { PolicyGroupManager } from '@/components/clients/policy-group-manager'
@ -676,9 +676,34 @@ export function ClientDetail({ client, designations, policyGroups = [], allPolic
}`}>
{task.priority}
</span>
<button
type="button"
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground hover:bg-muted rounded px-1 py-0.5 transition-colors"
title="View notes"
>
<MessageSquare className="h-3 w-3" />
{(task.taskNotes?.length || 0) > 0 ? task.taskNotes.length : '+'}
</button>
</div>
{/* Always show Policy #, Type, Renewal Date */}
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm mt-1">
<span>
<span className="text-muted-foreground">Policy #:</span>{' '}
<span className="font-medium">{task.policy?.policyNumber || '—'}</span>
</span>
<span>
<span className="text-muted-foreground">Type:</span>{' '}
<span className="font-medium">{task.policy?.policyType || '—'}</span>
</span>
<span>
<span className="text-muted-foreground">Renewal:</span>{' '}
<span className="font-medium" suppressHydrationWarning>
{task.policy?.expirationDate ? formatRenewalDate(task.policy.expirationDate) : '—'}
</span>
</span>
</div>
{task.description && (
<p className="text-sm text-muted-foreground line-clamp-2">{task.description}</p>
<p className="text-sm text-muted-foreground line-clamp-2 mt-1">{task.description}</p>
)}
{task.assignments?.length > 0 && (
<p className="mt-1 text-sm text-muted-foreground">