From 9ddab4e5a51407a1b6c0e3ba2f2a01c4ac144228 Mon Sep 17 00:00:00 2001 From: bot-hermes Date: Tue, 28 Jul 2026 15:02:01 +0000 Subject: [PATCH] fix: round 3 UI gaps - habit edit dialog, domain edit, empty states, domain name resolution --- .../app/(dashboard)/projects/[id]/page.tsx | 39 +++- apps/web/app/(dashboard)/reports/page.tsx | 18 +- apps/web/components/habits/habit-card.tsx | 214 ++++++++++++++---- .../components/settings/settings-domains.tsx | 117 +++++++++- .../components/tasks/tasks-kanban-view.tsx | 8 + apps/web/components/tasks/tasks-list-view.tsx | 8 + 6 files changed, 341 insertions(+), 63 deletions(-) diff --git a/apps/web/app/(dashboard)/projects/[id]/page.tsx b/apps/web/app/(dashboard)/projects/[id]/page.tsx index a9def7d..de3cfe2 100644 --- a/apps/web/app/(dashboard)/projects/[id]/page.tsx +++ b/apps/web/app/(dashboard)/projects/[id]/page.tsx @@ -48,9 +48,11 @@ export default function ProjectDetailPage() { const [tasks, setTasks] = useState([]); const [milestones, setMilestones] = useState([]); const [loading, setLoading] = useState(true); + const [domainMap, setDomainMap] = useState>(new Map()); useEffect(() => { if (projectId) { + fetchDomains(); fetchProject(); fetchTasks(); fetchMilestones(); @@ -58,6 +60,18 @@ export default function ProjectDetailPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectId]); + async function fetchDomains() { + try { + const res = await fetch('/api/domains?sort=sort_order'); + if (res.ok) { + const data = await res.json(); + const map = new Map(); + for (const d of data.items || []) map.set(d.id, d.name); + setDomainMap(map); + } + } catch {} + } + async function fetchProject() { try { const response = await fetch(`/api/projects/${projectId}`); @@ -138,17 +152,20 @@ export default function ProjectDetailPage() {

{project.description}

)} - - {project.status} - +
+ + {project.status} + + {domainMap.get(project.domain) || project.domain} +
{/* Project stats */} diff --git a/apps/web/app/(dashboard)/reports/page.tsx b/apps/web/app/(dashboard)/reports/page.tsx index 098bd10..28e0ee3 100644 --- a/apps/web/app/(dashboard)/reports/page.tsx +++ b/apps/web/app/(dashboard)/reports/page.tsx @@ -64,6 +64,7 @@ export default function ReportsPage() { const [showTemplates, setShowTemplates] = useState(false); const [error, setError] = useState(null); const [saveStatus, setSaveStatus] = useState<'Saved' | 'Saving' | 'Failed'>('Saved'); + const [domainMap, setDomainMap] = useState>(new Map()); const [reportToDelete, setReportToDelete] = useState(null); const [deleting, setDeleting] = useState(false); const pendingSave = useRef<{ id: string; updates: Partial } | null>(null); @@ -72,11 +73,24 @@ export default function ReportsPage() { useEffect(() => { fetchReports(); + fetchDomains(); return () => { if (saveTimer.current) clearTimeout(saveTimer.current); }; }, []); + async function fetchDomains() { + try { + const res = await fetch('/api/domains?sort=sort_order'); + if (res.ok) { + const data = await res.json(); + const map = new Map(); + for (const d of data.items || []) map.set(d.id, d.name); + setDomainMap(map); + } + } catch {} + } + async function fetchReports() { setLoading(true); setError(null); @@ -284,7 +298,7 @@ export default function ReportsPage() { {report.report_type} - {report.domain} + {domainMap.get(report.domain) || report.domain} @@ -328,7 +342,7 @@ export default function ReportsPage() {
{selectedReport.report_type} - {selectedReport.domain} + {domainMap.get(selectedReport.domain) || selectedReport.domain} {selectedReport.date_range_start && selectedReport.date_range_end && ( diff --git a/apps/web/components/habits/habit-card.tsx b/apps/web/components/habits/habit-card.tsx index 509299b..224e09e 100644 --- a/apps/web/components/habits/habit-card.tsx +++ b/apps/web/components/habits/habit-card.tsx @@ -4,8 +4,13 @@ import { useEffect, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { MoreHorizontal, Pencil, Trash2, Flame } from "lucide-react"; import { toast } from "sonner"; @@ -19,16 +24,39 @@ interface Habit { streak?: number; } +interface Domain { + id: string; + name: string; + color: string; +} + export function HabitCard() { const [habits, setHabits] = useState([]); const [domainMap, setDomainMap] = useState>(new Map()); + const [domains, setDomains] = useState([]); const [loading, setLoading] = useState(true); const [deleteId, setDeleteId] = useState(null); const [deleting, setDeleting] = useState(false); const [editing, setEditing] = useState(null); + const [editName, setEditName] = useState(""); + const [editDescription, setEditDescription] = useState(""); + const [editDomain, setEditDomain] = useState(""); + const [editFrequency, setEditFrequency] = useState("daily"); + const [editDifficulty, setEditDifficulty] = useState("medium"); + const [saving, setSaving] = useState(false); useEffect(() => { fetchHabits(); fetchDomains(); }, []); + useEffect(() => { + if (editing) { + setEditName(editing.name); + setEditDescription(editing.description || ""); + setEditDomain(editing.domain); + setEditFrequency(editing.frequency || "daily"); + setEditDifficulty(editing.difficulty || "medium"); + } + }, [editing]); + async function fetchHabits() { try { const res = await fetch("/api/habits?sort=-created"); @@ -42,16 +70,18 @@ export function HabitCard() { try { const res = await fetch("/api/domains?sort=sort_order"); const data = await res.json(); + const items = data.items || []; const map = new Map(); - for (const d of data.items || []) map.set(d.id, d.name); + for (const d of items) map.set(d.id, d.name); setDomainMap(map); + setDomains(items); } catch {} } async function handleDelete(id: string) { setDeleting(true); try { - const res = await fetch(`/api/habits/${id}`, { method: "DELETE" }); + const res = await fetch("/api/habits/" + id, { method: "DELETE" }); if (!res.ok) throw new Error(); toast.success("Habit deleted"); setHabits((h) => h.filter((x) => x.id !== id)); @@ -59,50 +89,156 @@ export function HabitCard() { finally { setDeleting(false); setDeleteId(null); } } + async function handleSave() { + if (!editing || !editName.trim()) return; + setSaving(true); + try { + const res = await fetch("/api/habits/" + editing.id, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: editName.trim(), + description: editDescription || undefined, + domain: editDomain, + frequency: editFrequency, + difficulty: editDifficulty, + }), + }); + if (!res.ok) throw new Error(); + toast.success("Habit updated"); + setEditing(null); + await fetchHabits(); + } catch { + toast.error("Unable to update habit"); + } finally { + setSaving(false); + } + } + if (loading) return

Loading habits...

; return ( <> -
- {habits.map((habit) => ( - - -
-
-
- {habit.name} - {habit.streak && habit.streak > 0 && ( - - {habit.streak} - - )} -
-
- {domainMap.get(habit.domain) || habit.domain} - {habit.frequency && {habit.frequency}} - {habit.difficulty && {habit.difficulty}} + {habits.length === 0 ? ( +
+

No habits yet. Create your first habit to get started!

+
+ ) : ( +
+ {habits.map((habit) => ( + + +
+
+
+ {habit.name} + {habit.streak && habit.streak > 0 && ( + + {habit.streak} + + )} +
+
+ {domainMap.get(habit.domain) || habit.domain} + {habit.frequency && {habit.frequency}} + {habit.difficulty && {habit.difficulty}} +
+ + + + + + setEditing(habit)}> + Edit + + setDeleteId(habit.id)}> + Delete + + +
- - - - - - setEditing(habit)}> - Edit - - setDeleteId(habit.id)}> - Delete - - - +
+
+ ))} +
+ )} + + {/* Edit Habit Dialog */} + !o && setEditing(null)}> + + + Edit habit + Update your habit details. + +
+
+ + setEditName(e.target.value)} autoFocus required /> +
+
+ +