feat: task time estimates and velocity chart on dashboard

- Added estimatedHours column to tasks schema
- Backend: create/update support for estimatedHours
- New /api/tasks/stats/velocity endpoint: daily completions, weekly velocity, estimate totals
- Dashboard: velocity chart with 7-day bar chart, this week count, avg/week, estimate summary
- TaskDetailPanel: estimated hours input field
- CreateTaskModal: estimated hours in advanced options
- TaskCard, KanbanBoard, TaskPage: estimate badge display
This commit is contained in:
2026-01-29 11:35:50 +00:00
parent 6459734bc7
commit dd401290c1
10 changed files with 254 additions and 5 deletions

View File

@@ -12,6 +12,7 @@ interface CreateTaskModalProps {
priority?: string;
projectId?: string;
dueDate?: string;
estimatedHours?: number;
}) => void;
}
@@ -22,6 +23,7 @@ export function CreateTaskModal({ open, onClose, onCreate }: CreateTaskModalProp
const [priority, setPriority] = useState("medium");
const [projectId, setProjectId] = useState("");
const [dueDate, setDueDate] = useState("");
const [estimatedHours, setEstimatedHours] = useState("");
const [projects, setProjects] = useState<Project[]>([]);
const [showAdvanced, setShowAdvanced] = useState(false);
@@ -53,6 +55,7 @@ export function CreateTaskModal({ open, onClose, onCreate }: CreateTaskModalProp
priority,
projectId: projectId || undefined,
dueDate: dueDate ? new Date(dueDate).toISOString() : undefined,
estimatedHours: estimatedHours ? Number(estimatedHours) : undefined,
});
// Reset form
setTitle("");
@@ -61,6 +64,7 @@ export function CreateTaskModal({ open, onClose, onCreate }: CreateTaskModalProp
setPriority("medium");
setProjectId("");
setDueDate("");
setEstimatedHours("");
setShowAdvanced(false);
onClose();
};
@@ -179,6 +183,23 @@ export function CreateTaskModal({ open, onClose, onCreate }: CreateTaskModalProp
</div>
</div>
{/* Estimated Hours */}
<div>
<label className="text-xs font-medium text-gray-500 block mb-1">Estimated Hours</label>
<div className="flex items-center gap-2">
<input
type="number"
min="0"
step="1"
value={estimatedHours}
onChange={(e) => setEstimatedHours(e.target.value)}
placeholder="0"
className="w-24 text-sm border border-gray-200 rounded-lg px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-amber-400 bg-white"
/>
<span className="text-sm text-gray-500">hours</span>
</div>
</div>
{/* Source */}
<div>
<label className="text-xs font-medium text-gray-500 block mb-1">Source</label>