Task card: due date urgency bg, clickable status dropdown, plain text level, consistent text-xs

This commit is contained in:
lorentz 2026-04-10 14:46:30 +00:00
parent a215d7ce44
commit 4852acafda

View file

@ -97,6 +97,7 @@ function TaskCard({ task: initial }: { task: Task }) {
const [naReason, setNaReason] = useState('')
const [naSubmitting, setNaSubmitting] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const [statusUpdating, setStatusUpdating] = useState(false)
const [completeDialogOpen, setCompleteDialogOpen] = useState(false)
const [imageRightFiled, setImageRightFiled] = useState<boolean | null>(null)
const [reminderDate, setReminderDate] = useState('')
@ -199,11 +200,41 @@ function TaskCard({ task: initial }: { task: Task }) {
}
const levelLabel = task.policyGroup ? 'Group' : task.policy ? 'Policy' : 'Client'
const levelColor = task.policyGroup
? 'bg-purple-500/10 text-purple-700 dark:text-purple-400'
: task.policy
? 'bg-blue-500/10 text-blue-700 dark:text-blue-400'
: 'bg-muted text-muted-foreground'
const handleQuickStatus = async (newStatus: string) => {
if (newStatus === task.status) return
if (newStatus === 'NA') { setNaDialogOpen(true); return }
if (newStatus === 'COMPLETED') { handleToggleStatus(); return }
setStatusUpdating(true)
try {
const res = await fetch(`/api/tasks/${task.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus }),
})
if (!res.ok) throw new Error((await res.json()).error)
setTask((t) => ({ ...t, status: newStatus }))
toast.success(`Status → ${newStatus.replace('_', ' ')}`)
} catch (err: any) {
toast.error(err.message)
} finally {
setStatusUpdating(false)
}
}
// Due date urgency background
const dueMs = new Date(task.dueDate).getTime()
const nowMs = now.getTime()
const daysUntilDue = Math.ceil((dueMs - nowMs) / 86400000)
const dueBg = isCompleted
? 'bg-muted/50'
: daysUntilDue < 0
? 'bg-red-500/15'
: daysUntilDue <= 7
? 'bg-red-500/10'
: daysUntilDue <= 14
? 'bg-orange-500/10'
: 'bg-muted/40'
return (
<div
@ -243,12 +274,23 @@ function TaskCard({ task: initial }: { task: Task }) {
<div className="shrink-0 border-l border-border flex flex-col text-[12px]">
<div className="flex-1 flex">
{/* Left col: Due / Status / Level (data only, fit to content) */}
<div className="flex flex-col justify-center gap-1.5 px-3 py-2.5 whitespace-nowrap">
<span className={`font-medium ${isOverdue ? 'text-red-500' : 'text-foreground'}`} suppressHydrationWarning>
<div className="flex flex-col justify-center gap-1 px-3 py-2.5 whitespace-nowrap text-xs">
<span className={`font-bold rounded px-1.5 py-0.5 w-fit ${dueBg}`} suppressHydrationWarning>
{formatDate(task.dueDate)}
</span>
<StatusBadge status={task.status} />
<span className={`inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium w-fit ${levelColor}`}>
<Select value={task.status} onValueChange={handleQuickStatus} disabled={statusUpdating}>
<SelectTrigger className="h-auto border-0 p-0 shadow-none text-xs font-medium hover:underline focus:ring-0 w-auto gap-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="NOT_STARTED">Not Started</SelectItem>
<SelectItem value="IN_PROGRESS">In Progress</SelectItem>
<SelectItem value="BLOCKED">Blocked</SelectItem>
<SelectItem value="COMPLETED">Completed</SelectItem>
<SelectItem value="NA">N/A</SelectItem>
</SelectContent>
</Select>
<span className="text-xs text-muted-foreground">
{levelLabel}
</span>
</div>