- {{ note.title || "Untitled" }}
+
+
{{ note.title || "Untitled" }}
+
+
@@ -78,6 +85,35 @@ function isOverdue(): boolean {
.task-title {
margin: 0;
font-size: 1.1rem;
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.btn-edit {
+ flex-shrink: 0;
+ padding: 0.2rem 0.5rem;
+ font-size: 0.75rem;
+ background: transparent;
+ color: var(--color-text-muted);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.15s, color 0.15s, border-color 0.15s;
+}
+.task-card:hover .btn-edit {
+ opacity: 1;
+}
+.btn-edit:hover {
+ color: var(--color-primary);
+ border-color: var(--color-primary);
+}
+@media (hover: none) {
+ .btn-edit {
+ opacity: 1;
+ }
}
.task-preview {
margin: 0 0 0.5rem;
diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue
index 7fbd06b..41a28f7 100644
--- a/frontend/src/views/HomeView.vue
+++ b/frontend/src/views/HomeView.vue
@@ -15,37 +15,63 @@ const router = useRouter();
const recentNotes = ref([]);
const overdueTasks = ref([]);
const dueTodayTasks = ref([]);
+const dueThisWeekTasks = ref([]);
+const highPriorityTasks = ref([]);
const inProgressTasks = ref([]);
const recentChats = ref([]);
const loading = ref(true);
const tasksStore = useTasksStore();
-function todayStr(): string {
+const PRIORITY_ORDER: Record = {
+ high: 3,
+ medium: 2,
+ low: 1,
+ none: 0,
+};
+
+function dateStr(offsetDays: number = 0): string {
const d = new Date();
- return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
+ d.setDate(d.getDate() + offsetDays);
+ return (
+ d.getFullYear() +
+ "-" +
+ String(d.getMonth() + 1).padStart(2, "0") +
+ "-" +
+ String(d.getDate()).padStart(2, "0")
+ );
}
-function tomorrowStr(): string {
- const d = new Date();
- d.setDate(d.getDate() + 1);
- return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
+function sortByPriorityThenDate(tasks: Task[]): Task[] {
+ return tasks.slice().sort((a, b) => {
+ const pa = PRIORITY_ORDER[a.priority ?? "none"] ?? 0;
+ const pb = PRIORITY_ORDER[b.priority ?? "none"] ?? 0;
+ if (pb !== pa) return pb - pa;
+ // Earlier due dates first; null due dates last
+ if (a.due_date && b.due_date) return a.due_date.localeCompare(b.due_date);
+ if (a.due_date) return -1;
+ if (b.due_date) return 1;
+ return 0;
+ });
}
onMounted(async () => {
- const today = todayStr();
- const tomorrow = tomorrowStr();
+ const today = dateStr(0);
+ const nextWeek = dateStr(7);
- const [notesRes, overdueRes, dueTodayRes, inProgressRes, chatsRes] =
+ const [notesRes, overdueRes, dueSoonRes, highPriRes, inProgressRes, chatsRes] =
await Promise.allSettled([
apiGet("/api/notes?sort=updated_at&order=desc&limit=5"),
apiGet(
- `/api/tasks?due_before=${today}&sort=due_date&order=asc&limit=15`
+ `/api/tasks?due_before=${today}&sort=due_date&order=asc&limit=20`
),
apiGet(
- `/api/tasks?due_after=${today}&due_before=${tomorrow}&sort=priority&order=desc&limit=10`
+ `/api/tasks?due_after=${today}&due_before=${nextWeek}&sort=due_date&order=asc&limit=20`
),
apiGet(
- `/api/tasks?status=in_progress&sort=updated_at&order=desc&limit=5`
+ `/api/tasks?priority=high&sort=updated_at&order=desc&limit=10`
+ ),
+ apiGet(
+ `/api/tasks?status=in_progress&sort=updated_at&order=desc&limit=10`
),
apiGet<{ conversations: Conversation[]; total: number }>(
"/api/chat/conversations?limit=3&offset=0"
@@ -59,23 +85,47 @@ onMounted(async () => {
recentChats.value = chatsRes.value.conversations;
}
- // Filter out done tasks from overdue and due-today
+ // Build seen-set incrementally so each section deduplicates against all above it
+ const seen = new Set();
+
+ // Overdue: past due, not done
if (overdueRes.status === "fulfilled") {
- overdueTasks.value = overdueRes.value.tasks.filter(
- (t) => t.status !== "done"
- );
- }
- if (dueTodayRes.status === "fulfilled") {
- dueTodayTasks.value = dueTodayRes.value.tasks.filter(
- (t) => t.status !== "done"
+ overdueTasks.value = sortByPriorityThenDate(
+ overdueRes.value.tasks.filter((t) => t.status !== "done")
);
+ for (const t of overdueTasks.value) seen.add(t.id);
}
- // Deduplicate in-progress against overdue and due-today
- if (inProgressRes.status === "fulfilled") {
- const seen = new Set();
- for (const t of overdueTasks.value) seen.add(t.id);
+ // Due soon: split into "today" and "this week", exclude done
+ if (dueSoonRes.status === "fulfilled") {
+ const notDone = dueSoonRes.value.tasks.filter(
+ (t) => t.status !== "done" && !seen.has(t.id)
+ );
+ const todayList: Task[] = [];
+ const weekList: Task[] = [];
+ for (const t of notDone) {
+ if (t.due_date === today) {
+ todayList.push(t);
+ } else {
+ weekList.push(t);
+ }
+ }
+ dueTodayTasks.value = sortByPriorityThenDate(todayList);
+ dueThisWeekTasks.value = sortByPriorityThenDate(weekList);
for (const t of dueTodayTasks.value) seen.add(t.id);
+ for (const t of dueThisWeekTasks.value) seen.add(t.id);
+ }
+
+ // High priority: not done, deduplicated
+ if (highPriRes.status === "fulfilled") {
+ highPriorityTasks.value = highPriRes.value.tasks.filter(
+ (t) => t.status !== "done" && !seen.has(t.id)
+ );
+ for (const t of highPriorityTasks.value) seen.add(t.id);
+ }
+
+ // In progress: deduplicated against all above
+ if (inProgressRes.status === "fulfilled") {
inProgressTasks.value = inProgressRes.value.tasks.filter(
(t) => !seen.has(t.id)
);
@@ -84,10 +134,19 @@ onMounted(async () => {
loading.value = false;
});
+const allTaskLists = [
+ () => overdueTasks,
+ () => dueTodayTasks,
+ () => dueThisWeekTasks,
+ () => highPriorityTasks,
+ () => inProgressTasks,
+];
+
function removeFromAllLists(id: number) {
- overdueTasks.value = overdueTasks.value.filter((t) => t.id !== id);
- dueTodayTasks.value = dueTodayTasks.value.filter((t) => t.id !== id);
- inProgressTasks.value = inProgressTasks.value.filter((t) => t.id !== id);
+ for (const getList of allTaskLists) {
+ const list = getList();
+ list.value = list.value.filter((t) => t.id !== id);
+ }
}
function onStatusToggle(id: number, status: TaskStatus) {
@@ -95,8 +154,8 @@ function onStatusToggle(id: number, status: TaskStatus) {
if (updated.status === "done") {
removeFromAllLists(updated.id);
} else {
- // Update in whichever list contains it
- for (const list of [overdueTasks, dueTodayTasks, inProgressTasks]) {
+ for (const getList of allTaskLists) {
+ const list = getList();
const idx = list.value.findIndex((t) => t.id === updated.id);
if (idx !== -1) {
list.value[idx] = updated;
@@ -152,6 +211,36 @@ async function newChat() {
+
+
+
+