From 0df6d1c1c840a54a6542acb7c64a423ecbbe06fa Mon Sep 17 00:00:00 2001 From: Hermes Date: Sun, 2 Aug 2026 01:25:14 +0000 Subject: [PATCH] feat: add Edit/Delete dropdown menus to habits, projects, and sections - Habits page: Add DropdownMenu with Edit/Delete options, Edit dialog (name/description/frequency/difficulty), Delete confirmation - Projects page: Add DropdownMenu with Edit/Delete options, Edit dialog (name/description/status), Delete confirmation - Project detail page: Add DropdownMenu with Edit/Delete per section, Edit Section dialog (name/kind/status), Delete confirmation --- apps/web/app/(dashboard)/habits/page.tsx | 174 +++++++++++- .../app/(dashboard)/projects/[id]/page.tsx | 159 ++++++++++- apps/web/app/(dashboard)/projects/page.tsx | 263 ++++++++++++++---- 3 files changed, 523 insertions(+), 73 deletions(-) diff --git a/apps/web/app/(dashboard)/habits/page.tsx b/apps/web/app/(dashboard)/habits/page.tsx index 198ee8b..707c28a 100644 --- a/apps/web/app/(dashboard)/habits/page.tsx +++ b/apps/web/app/(dashboard)/habits/page.tsx @@ -1,9 +1,16 @@ "use client"; import { useState, useEffect, useCallback } from "react"; -import { Plus, CheckCircle2, Circle, MoreHorizontal, Flame, Filter } from "lucide-react"; +import { Plus, CheckCircle2, Circle, MoreHorizontal, Flame, Filter, Pencil, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +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 { HabitCreateDialog } from "@/components/habits/habit-create-dialog"; import { HabitCompletionDialog } from "@/components/habits/habit-completion-dialog"; import { HabitCalendarHeatmap } from "@/components/habits/habit-calendar-heatmap"; @@ -40,6 +47,15 @@ export default function HabitsPage() { const [expandedHabit, setExpandedHabit] = useState(null); const [filter, setFilter] = useState('all'); const [loading, setLoading] = useState(true); + const [editingHabit, setEditingHabit] = useState(null); + const [editName, setEditName] = useState(""); + const [editDescription, setEditDescription] = useState(""); + const [editFrequency, setEditFrequency] = useState<"daily" | "weekly" | "custom">("daily"); + const [editDifficulty, setEditDifficulty] = useState<"easy" | "medium" | "hard">("medium"); + const [editGoalPerPeriod, setEditGoalPerPeriod] = useState(1); + const [saving, setSaving] = useState(false); + const [deleteHabitId, setDeleteHabitId] = useState(null); + const [deleting, setDeleting] = useState(false); // Fetch domains useEffect(() => { @@ -92,6 +108,60 @@ export default function HabitsPage() { } }; + // Edit a habit + const handleEdit = async () => { + if (!editingHabit || !editName.trim()) return; + setSaving(true); + try { + const res = await fetch(`/api/habits/${editingHabit.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: editName.trim(), + description: editDescription || null, + frequency: editFrequency, + difficulty: editDifficulty, + goalPerPeriod: editGoalPerPeriod, + }), + }); + if (!res.ok) throw new Error('Failed to update'); + toast.success('Habit updated'); + setEditingHabit(null); + fetchHabits(); + } catch { + toast.error('Failed to update habit'); + } finally { + setSaving(false); + } + }; + + // Delete a habit + const handleDelete = async () => { + if (!deleteHabitId) return; + setDeleting(true); + try { + const res = await fetch(`/api/habits/${deleteHabitId}`, { method: 'DELETE' }); + if (!res.ok) throw new Error('Failed to delete'); + toast.success('Habit deleted'); + setDeleteHabitId(null); + fetchHabits(); + } catch { + toast.error('Failed to delete habit'); + } finally { + setDeleting(false); + } + }; + + // Open edit dialog with habit data + const openEdit = (habit: Habit) => { + setEditName(habit.name); + setEditDescription(habit.description || ""); + setEditFrequency(habit.frequency); + setEditDifficulty(habit.difficulty); + setEditGoalPerPeriod(habit.goalPerPeriod || 1); + setEditingHabit(habit); + }; + // Listen for custom event to open create dialog useEffect(() => { const handler = () => setCreateOpen(true); @@ -188,13 +258,27 @@ export default function HabitsPage() {