Task card redesign: remove duplicate notes, cleaner layout with header/meta separation
This commit is contained in:
parent
04c87f111a
commit
bc240c79c1
1 changed files with 43 additions and 100 deletions
|
|
@ -200,7 +200,7 @@ function TaskCard({ task: initial }: { task: Task }) {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg border p-4 transition-shadow hover:shadow-md ${
|
||||
className={`rounded-lg border transition-shadow hover:shadow-md ${
|
||||
isCompleted
|
||||
? 'border-border/50 opacity-75'
|
||||
: isOverdue
|
||||
|
|
@ -208,93 +208,24 @@ function TaskCard({ task: initial }: { task: Task }) {
|
|||
: 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Title row */}
|
||||
<div className="flex flex-wrap items-center gap-2 mb-1.5">
|
||||
<h3 className={`font-semibold ${isCompleted ? 'line-through text-muted-foreground' : ''}`}>
|
||||
{task.title}
|
||||
</h3>
|
||||
<StatusBadge status={task.status} />
|
||||
<PriorityDot priority={task.priority} />
|
||||
{task.policyGroup ? (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-purple-500/10 text-purple-700 dark:text-purple-400">Group</span>
|
||||
) : task.policy ? (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-blue-500/10 text-blue-700 dark:text-blue-400">Policy</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground">Client</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 — always show policy number, type, renewal date */}
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||
{task.client && (
|
||||
<span>
|
||||
<span className="text-muted-foreground">Client:</span>{' '}
|
||||
<span className="font-medium">{task.client.name}</span>
|
||||
</span>
|
||||
)}
|
||||
{task.policyGroup && (
|
||||
<span>
|
||||
<span className="text-muted-foreground">Group:</span>{' '}
|
||||
<span className="font-medium">
|
||||
{task.policyGroup.name ?? (task.policyGroup.renewalDate ? formatDate(task.policyGroup.renewalDate) : task.policyGroup.id)}
|
||||
</span>
|
||||
</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' : ''}`}>
|
||||
<span suppressHydrationWarning>{formatDate(task.dueDate)}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{task.assignments.length > 0 && (
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Assigned to:{' '}
|
||||
{task.assignments.map((a) => a.user.displayName || a.user.email).join(', ')}
|
||||
</p>
|
||||
{/* Header: title + badges + actions */}
|
||||
<div className="flex items-center gap-3 px-4 pt-3 pb-2">
|
||||
<div className="flex-1 min-w-0 flex flex-wrap items-center gap-2">
|
||||
<h3 className={`font-semibold truncate ${isCompleted ? 'line-through text-muted-foreground' : ''}`}>
|
||||
{task.title}
|
||||
</h3>
|
||||
<StatusBadge status={task.status} />
|
||||
<PriorityDot priority={task.priority} />
|
||||
{task.policyGroup ? (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-purple-500/10 text-purple-700 dark:text-purple-400">Group</span>
|
||||
) : task.policy ? (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-blue-500/10 text-blue-700 dark:text-blue-400">Policy</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-muted text-muted-foreground">Client</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setEditOpen(true)}
|
||||
className="gap-1.5 text-muted-foreground hover:text-foreground"
|
||||
title="Edit task"
|
||||
>
|
||||
<Button size="icon" variant="ghost" onClick={() => setEditOpen(true)} className="h-7 w-7 text-muted-foreground hover:text-foreground" title="Edit task">
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -302,36 +233,48 @@ function TaskCard({ task: initial }: { task: Task }) {
|
|||
variant={isCompleted ? 'outline' : 'default'}
|
||||
onClick={handleToggleStatus}
|
||||
disabled={toggling}
|
||||
className="gap-1.5"
|
||||
className="h-7 gap-1 text-xs px-2.5"
|
||||
>
|
||||
{isCompleted
|
||||
? <><RotateCcw className="h-3.5 w-3.5" /> Reopen</>
|
||||
: <><CheckCircle2 className="h-3.5 w-3.5" /> Complete</>
|
||||
? <><RotateCcw className="h-3 w-3" /> Reopen</>
|
||||
: <><CheckCircle2 className="h-3 w-3" /> Complete</>
|
||||
}
|
||||
</Button>
|
||||
{task.status !== 'COMPLETED' && task.status !== 'NA' && task.status !== 'CANCELLED' && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setNaDialogOpen(true)}
|
||||
className="gap-1.5 text-muted-foreground hover:text-foreground"
|
||||
title="Mark as N/A"
|
||||
>
|
||||
<Ban className="h-3.5 w-3.5" /> N/A
|
||||
<Button size="sm" variant="ghost" onClick={() => setNaDialogOpen(true)} className="h-7 gap-1 text-xs px-2 text-muted-foreground hover:text-foreground" title="Mark as N/A">
|
||||
<Ban className="h-3 w-3" /> N/A
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant={noteOpen ? 'secondary' : 'ghost'}
|
||||
onClick={() => setNoteOpen((o) => !o)}
|
||||
className="gap-1.5"
|
||||
className="h-7 gap-1 text-xs px-2"
|
||||
>
|
||||
<MessageSquare className="h-3.5 w-3.5" />
|
||||
Notes{notes.length > 0 ? ` (${notes.length})` : ''}
|
||||
<MessageSquare className="h-3 w-3" />
|
||||
{notes.length > 0 ? notes.length : 'Notes'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Meta: structured two-row grid */}
|
||||
<div className="px-4 pb-3 text-[13px] text-muted-foreground space-y-1">
|
||||
<div className="flex items-center gap-x-6 gap-y-0.5 flex-wrap">
|
||||
{task.client && (
|
||||
<span><span className="text-muted-foreground/70">Client</span> <span className="text-foreground font-medium">{task.client.name}</span></span>
|
||||
)}
|
||||
<span><span className="text-muted-foreground/70">Policy #</span> <span className="text-foreground font-medium">{task.policy?.policyNumber || '—'}</span></span>
|
||||
<span><span className="text-muted-foreground/70">Type</span> <span className="text-foreground font-medium">{task.policy?.policyType || '—'}</span></span>
|
||||
<span><span className="text-muted-foreground/70">Renewal</span> <span className="text-foreground font-medium" suppressHydrationWarning>{task.policy?.expirationDate ? formatRenewalDate(task.policy.expirationDate) : '—'}</span></span>
|
||||
<span><span className="text-muted-foreground/70">Due</span> <span className={`font-medium ${isOverdue ? 'text-red-500' : 'text-foreground'}`} suppressHydrationWarning>{formatDate(task.dueDate)}</span></span>
|
||||
</div>
|
||||
{task.assignments.length > 0 && (
|
||||
<div className="text-[12px]">
|
||||
Assigned to {task.assignments.map((a) => a.user.displayName || a.user.email).join(', ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Completion Dialog */}
|
||||
<Dialog open={completeDialogOpen} onOpenChange={setCompleteDialogOpen}>
|
||||
<DialogContent>
|
||||
|
|
@ -413,7 +356,7 @@ function TaskCard({ task: initial }: { task: Task }) {
|
|||
|
||||
{/* Notes panel */}
|
||||
{noteOpen && (
|
||||
<div className="mt-3 pt-3 border-t border-border space-y-3">
|
||||
<div className="mx-4 mb-3 pt-3 border-t border-border space-y-3">
|
||||
{/* Existing notes feed */}
|
||||
{notes.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue