@@ -123,6 +123,12 @@ export function TaskDetailClient({
|
||||
const { toast } = useToast();
|
||||
const [showUpload, setShowUpload] = useState(false);
|
||||
const [updatingStatus, setUpdatingStatus] = useState(false);
|
||||
const [dueDateValue, setDueDateValue] = useState(
|
||||
task.dueDate ? format(new Date(task.dueDate), "yyyy-MM-dd") : ""
|
||||
);
|
||||
const [estimatedHoursValue, setEstimatedHoursValue] = useState(
|
||||
task.estimatedHours != null ? String(task.estimatedHours) : ""
|
||||
);
|
||||
|
||||
const statusCfg = TASK_STATUS_CONFIG[task.status];
|
||||
const StatusIcon = statusCfg.icon;
|
||||
@@ -147,6 +153,27 @@ export function TaskDetailClient({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDueDateChange = async (value: string) => {
|
||||
setDueDateValue(value);
|
||||
try {
|
||||
await updateTask(task.id, { dueDate: value || null });
|
||||
router.refresh();
|
||||
} catch {
|
||||
toast({ title: "Failed to update due date", variant: "destructive" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleEstimatedHoursBlur = async () => {
|
||||
const parsed = estimatedHoursValue === "" ? null : parseFloat(estimatedHoursValue);
|
||||
if (parsed !== null && (isNaN(parsed) || parsed <= 0)) return;
|
||||
try {
|
||||
await updateTask(task.id, { estimatedHours: parsed });
|
||||
router.refresh();
|
||||
} catch {
|
||||
toast({ title: "Failed to update estimated hours", variant: "destructive" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssigneeChange = async (artistId: string) => {
|
||||
try {
|
||||
await updateTask(task.id, { assignedArtistId: artistId === "__none__" ? null : artistId });
|
||||
@@ -410,26 +437,51 @@ export function TaskDetailClient({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{task.dueDate && (
|
||||
<div>
|
||||
<p className="text-xs text-zinc-500 mb-1">Due Date</p>
|
||||
<div>
|
||||
<p className="text-xs text-zinc-500 mb-1">Due Date</p>
|
||||
{canManage ? (
|
||||
<input
|
||||
type="date"
|
||||
value={dueDateValue}
|
||||
onChange={(e) => handleDueDateChange(e.target.value)}
|
||||
className="w-full text-sm bg-transparent border border-border rounded-md px-2 py-1 text-zinc-300 focus:outline-none focus:ring-1 focus:ring-amber-500/50 [color-scheme:dark]"
|
||||
/>
|
||||
) : task.dueDate ? (
|
||||
<p className={cn("text-sm flex items-center gap-1.5", isOverdue ? "text-red-400" : "text-zinc-300")}>
|
||||
<CalendarDays className="h-3.5 w-3.5" />
|
||||
{format(new Date(task.dueDate), "MMM d, yyyy")}
|
||||
{isOverdue && <span className="text-xs">(Overdue)</span>}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
) : (
|
||||
<p className="text-sm text-zinc-600">—</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{task.estimatedHours && (
|
||||
<div>
|
||||
<p className="text-xs text-zinc-500 mb-1">Estimated</p>
|
||||
<div>
|
||||
<p className="text-xs text-zinc-500 mb-1">Estimated Hours</p>
|
||||
{canManage ? (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.5"
|
||||
value={estimatedHoursValue}
|
||||
onChange={(e) => setEstimatedHoursValue(e.target.value)}
|
||||
onBlur={handleEstimatedHoursBlur}
|
||||
placeholder="—"
|
||||
className="w-full text-sm bg-transparent border border-border rounded-md px-2 py-1 text-zinc-300 focus:outline-none focus:ring-1 focus:ring-amber-500/50 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
|
||||
/>
|
||||
<span className="text-sm text-zinc-500 shrink-0">h</span>
|
||||
</div>
|
||||
) : task.estimatedHours ? (
|
||||
<p className="text-sm flex items-center gap-1.5">
|
||||
<Clock className="h-3.5 w-3.5 text-zinc-400" />
|
||||
{task.estimatedHours}h
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
) : (
|
||||
<p className="text-sm text-zinc-600">—</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user