From ee0de0504c74478f3ca1ab2e2900f4eaeedbb36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A8=B8=EB=8B=88=ED=8E=98=EB=8B=88?= Date: Wed, 18 Mar 2026 22:22:37 +0900 Subject: [PATCH] feat: add portfolio edit/delete UI with confirmation dialogs Add hover-visible edit (rename) and delete buttons to portfolio cards on the list page, with modal dialogs for name editing and delete confirmation. Uses existing PUT/DELETE API endpoints. Co-Authored-By: Claude Opus 4.6 --- frontend/src/app/portfolio/page.tsx | 156 ++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/portfolio/page.tsx b/frontend/src/app/portfolio/page.tsx index c42be21..29035f0 100644 --- a/frontend/src/app/portfolio/page.tsx +++ b/frontend/src/app/portfolio/page.tsx @@ -5,9 +5,21 @@ import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { DashboardLayout } from '@/components/layout/dashboard-layout'; import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; import { PortfolioCard } from '@/components/portfolio/portfolio-card'; import { Skeleton } from '@/components/ui/skeleton'; import { api } from '@/lib/api'; +import { toast } from 'sonner'; +import { Pencil, Trash2 } from 'lucide-react'; interface HoldingWithValue { ticker: string; @@ -33,6 +45,17 @@ export default function PortfolioListPage() { const [portfolios, setPortfolios] = useState([]); const [error, setError] = useState(null); + // Edit modal state + const [editModalOpen, setEditModalOpen] = useState(false); + const [editTarget, setEditTarget] = useState(null); + const [editName, setEditName] = useState(''); + const [editSaving, setEditSaving] = useState(false); + + // Delete modal state + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [deleteTarget, setDeleteTarget] = useState(null); + const [deleting, setDeleting] = useState(false); + useEffect(() => { const init = async () => { try { @@ -88,6 +111,53 @@ export default function PortfolioListPage() { return (portfolio.total_profit_loss / portfolio.total_invested) * 100; }; + const handleOpenEdit = (e: React.MouseEvent, portfolio: Portfolio) => { + e.preventDefault(); + e.stopPropagation(); + setEditTarget(portfolio); + setEditName(portfolio.name); + setEditModalOpen(true); + }; + + const handleSaveEdit = async () => { + if (!editTarget || !editName.trim()) return; + setEditSaving(true); + try { + await api.put(`/api/portfolios/${editTarget.id}`, { name: editName.trim() }); + toast.success('포트폴리오 이름이 변경되었습니다.'); + setEditModalOpen(false); + await fetchPortfolios(); + } catch (err) { + console.error('Failed to update portfolio:', err); + toast.error('포트폴리오 수정에 실패했습니다.'); + } finally { + setEditSaving(false); + } + }; + + const handleOpenDelete = (e: React.MouseEvent, portfolio: Portfolio) => { + e.preventDefault(); + e.stopPropagation(); + setDeleteTarget(portfolio); + setDeleteModalOpen(true); + }; + + const handleConfirmDelete = async () => { + if (!deleteTarget) return; + setDeleting(true); + try { + await api.delete(`/api/portfolios/${deleteTarget.id}`); + toast.success('포트폴리오가 삭제되었습니다.'); + setDeleteModalOpen(false); + setPortfolios((prev) => prev.filter((p) => p.id !== deleteTarget.id)); + } catch (err) { + console.error('Failed to delete portfolio:', err); + toast.error('포트폴리오 삭제에 실패했습니다.'); + } finally { + setDeleting(false); + } + }; + if (loading) { return ( @@ -121,15 +191,32 @@ export default function PortfolioListPage() {
{portfolios.map((portfolio) => ( - +
+ +
+ + +
+
))}
@@ -161,6 +248,57 @@ export default function PortfolioListPage() { )} + {/* Edit Modal */} + + + + 포트폴리오 이름 변경 + + 포트폴리오의 이름을 수정합니다. + + +
+
+ + setEditName(e.target.value)} + placeholder="포트폴리오 이름" + onKeyDown={(e) => e.key === 'Enter' && handleSaveEdit()} + /> +
+
+ + + + +
+
+ + {/* Delete Confirmation Modal */} + + + + 포트폴리오 삭제 + + "{deleteTarget?.name}" 포트폴리오를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. + + + + + + + +
); }