Skip to content

Commit d80157b

Browse files
committed
fix(analysis): 특정 분석 결과 삭제 기능 수정
1 parent f949a6f commit d80157b

File tree

5 files changed

+169
-49
lines changed

5 files changed

+169
-49
lines changed

front/package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

front/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
},
1111
"dependencies": {
1212
"@microsoft/fetch-event-source": "^2.0.1",
13-
"@radix-ui/react-progress": "^1.1.7",
14-
"@radix-ui/react-select": "^2.2.6",
1513
"@radix-ui/react-avatar": "^1.1.10",
1614
"@radix-ui/react-label": "^2.1.7",
15+
"@radix-ui/react-progress": "^1.1.7",
16+
"@radix-ui/react-select": "^2.2.6",
1717
"@radix-ui/react-separator": "^1.1.7",
1818
"@radix-ui/react-slot": "^1.2.3",
1919
"@radix-ui/react-switch": "^1.2.6",
@@ -22,15 +22,16 @@
2222
"badges": "^4.40.0",
2323
"class-variance-authority": "^0.7.1",
2424
"clsx": "^2.1.1",
25-
"framer-motion": "^12.23.24",
2625
"date-fns": "^4.1.0",
26+
"framer-motion": "^12.23.24",
2727
"geist": "^1.5.1",
2828
"lucide-react": "^0.460.0",
2929
"next": "15.0.0",
3030
"next-themes": "^0.4.6",
3131
"react": "18.3.1",
3232
"react-dom": "18.3.1",
3333
"recharts": "^3.3.0",
34+
"sonner": "^2.0.7",
3435
"tailwind-merge": "^3.3.1",
3536
"tailwindcss-animate": "^1.0.7",
3637
"tw-animate-css": "^1.4.0",

front/src/app/(dashboard)/analysis/[id]/page.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { useParams } from "next/navigation"
3+
import { useParams, useRouter } from "next/navigation"
44
import { useAnalysisResult } from "@/hooks/analysis/useAnalysisResult"
55
import { AnalysisHeader } from "@/components/analysis/AnalysisHeader"
66
import { AnalysisRadarCard } from "@/components/analysis/AnalysisRadarCard"
@@ -10,11 +10,12 @@ import { RepositoryPublicSection } from "@/components/analysis/RepositoryPublicS
1010

1111
export default function ResultsPage() {
1212
const params = useParams()
13+
const router = useRouter()
1314
const repoId = Number(params.id)
1415
const user = typeof window !== "undefined" ? JSON.parse(localStorage.getItem("user") || "{}") : null
1516
const userId = user?.id
1617

17-
const { history, result, loading, selectedId, setSelectedId } = useAnalysisResult(userId, repoId)
18+
const { history, result, loading, selectedId, setSelectedId, reload } = useAnalysisResult(userId, repoId)
1819

1920
if (loading)
2021
return <div className="p-8 text-center text-muted-foreground">🕓 분석 결과를 불러오는 중...</div>
@@ -29,10 +30,24 @@ export default function ResultsPage() {
2930
{ category: "CI/CD", score: (result.cicdScore / 15) * 100 },
3031
]
3132

33+
const handleDeleted = () => {
34+
if (history.analysisVersions.length === 1) {
35+
router.push("/history")
36+
} else {
37+
reload?.()
38+
}
39+
}
40+
3241
return (
3342
<div className="flex justify-center">
3443
<div className="w-full max-w-5xl px-6 sm:px-8 lg:px-12 py-10">
35-
<AnalysisHeader history={history} selectedId={selectedId} onSelect={setSelectedId} />
44+
<AnalysisHeader
45+
history={history}
46+
selectedId={selectedId}
47+
onSelect={setSelectedId}
48+
userId={userId}
49+
repoId={repoId}
50+
onDeleted={handleDeleted} />
3651

3752
<AnalysisSummaryCard totalScore={result.totalScore} summary={result.summary} />
3853

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,96 @@
11
"use client"
22

3-
import { Clock } from "lucide-react"
3+
import { useToast } from "@/components/ui/Toast"
4+
import { Clock, Trash2 } from "lucide-react"
45
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
6+
import { Button } from "@/components/ui/Button"
7+
import { analysisApi } from "@/lib/api/analysis"
58
import type { HistoryResponseDto } from "@/types/analysis"
9+
import { useState } from "react"
610

711
interface Props {
812
history: HistoryResponseDto
913
selectedId: number | null
1014
onSelect: (val: number) => void
15+
userId: number | null
16+
repoId: number
17+
onDeleted?: () => void
1118
}
1219

13-
export function AnalysisHeader({ history, selectedId, onSelect }: Props) {
20+
export function AnalysisHeader({ history, selectedId, onSelect, userId, repoId, onDeleted }: Props) {
21+
const { push } = useToast()
22+
const [deleting, setDeleting] = useState(false)
23+
const isOwner = userId === history.repository.ownerId
24+
25+
console.log("userId: ", userId);
26+
console.log("selectedId: ", selectedId);
27+
console.log("repoId: ", repoId);
28+
29+
const handleDelete = async () => {
30+
if (!userId || !repoId || !selectedId) return
31+
32+
const confirmed = window.confirm("정말로 이 분석 결과를 삭제하시겠습니까?")
33+
if (!confirmed) return
34+
35+
try {
36+
setDeleting(true)
37+
38+
if (history.analysisVersions.length === 1) {
39+
await analysisApi.deleteRepository(userId, repoId)
40+
push("리포지토리가 삭제되었습니다.")
41+
} else {
42+
await analysisApi.deleteAnalysisResult(userId, repoId, selectedId)
43+
push("분석 결과가 삭제되었습니다.")
44+
}
45+
46+
onDeleted?.()
47+
} catch (error) {
48+
console.error("❌ 삭제 실패:", error)
49+
push("삭제 중 오류가 발생했습니다.") // ✅ 동일하게 push 사용
50+
} finally {
51+
setDeleting(false)
52+
}
53+
}
54+
1455
return (
1556
<div className="mb-8">
16-
<h1 className="text-2xl font-bold mb-2">{history.repository.name}</h1>
17-
<p className="text-muted-foreground mb-4">{history.repository.description}</p>
18-
19-
<Select value={selectedId?.toString() || ""} onValueChange={(val) => onSelect(Number(val))}>
20-
<SelectTrigger className="w-[260px]">
21-
<SelectValue placeholder="분석 버전 선택" />
22-
</SelectTrigger>
23-
<SelectContent>
24-
{history.analysisVersions.map((ver) => (
25-
<SelectItem key={ver.analysisId} value={ver.analysisId.toString()}>
26-
<div className="flex items-center gap-2">
27-
<Clock className="h-3 w-3" />
28-
<span>{ver.versionLabel}</span>
29-
</div>
30-
</SelectItem>
31-
))}
32-
</SelectContent>
33-
</Select>
57+
<div>
58+
<h1 className="text-2xl font-bold mb-2">{history.repository.name}</h1>
59+
<p className="text-muted-foreground mb-4">{history.repository.description}</p>
60+
</div>
61+
62+
<div className="flex items-center gap-3">
63+
<Select value={selectedId?.toString() || ""} onValueChange={(val) => onSelect(Number(val))}>
64+
<SelectTrigger className="w-[260px]">
65+
<SelectValue placeholder="분석 버전 선택" />
66+
</SelectTrigger>
67+
<SelectContent>
68+
{history.analysisVersions.map((ver) => (
69+
<SelectItem key={ver.analysisId} value={ver.analysisId.toString()}>
70+
<div className="flex items-center gap-2">
71+
<Clock className="h-3 w-3" />
72+
<span>{ver.versionLabel}</span>
73+
</div>
74+
</SelectItem>
75+
))}
76+
</SelectContent>
77+
</Select>
78+
79+
80+
{/* 🗑️ 삭제 버튼: 본인만 노출 */}
81+
{isOwner && (
82+
<Button
83+
variant="destructive"
84+
size="sm"
85+
onClick={handleDelete}
86+
disabled={deleting}
87+
className="flex items-center gap-1"
88+
>
89+
<Trash2 className="h-4 w-4" />
90+
{deleting ? "삭제 중..." : "삭제"}
91+
</Button>
92+
)}
93+
</div>
3494
</div>
3595
)
3696
}
Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { useEffect, useState } from "react"
3+
import { useEffect, useState, useCallback } from "react"
44
import { analysisApi } from "@/lib/api/analysis"
55
import type { HistoryResponseDto, AnalysisResultResponseDto } from "@/types/analysis"
66
import { formatDateTimeKST } from "@/lib/utils/formatDate"
@@ -11,30 +11,56 @@ export function useAnalysisResult(userId?: number, repoId?: number) {
1111
const [result, setResult] = useState<AnalysisResultResponseDto | null>(null)
1212
const [loading, setLoading] = useState(true)
1313

14-
// Repository 분석 히스토리 불러오기
15-
useEffect(() => {
14+
const load = useCallback(async () => {
1615
if (!userId || !repoId) return
17-
;(async () => {
18-
try {
19-
const data = await analysisApi.getRepositoryHistory(userId, repoId)
20-
const sorted = [...data.analysisVersions].sort(
21-
(a, b) => new Date(b.analysisDate).getTime() - new Date(a.analysisDate).getTime()
22-
)
23-
24-
const relabeled = sorted.map((ver, idx) => ({
25-
...ver,
26-
versionLabel: `v${sorted.length - idx} (${formatDateTimeKST(ver.analysisDate)})`,
27-
}))
28-
29-
setHistory({ ...data, analysisVersions: relabeled })
30-
if (relabeled.length > 0) setSelectedId(relabeled[0].analysisId)
31-
} finally {
32-
setLoading(false)
16+
setLoading(true)
17+
18+
try {
19+
// 1️⃣ 히스토리 불러오기
20+
const data = await analysisApi.getRepositoryHistory(userId, repoId)
21+
22+
// 최신순 정렬
23+
const sorted = [...data.analysisVersions].sort(
24+
(a, b) => new Date(b.analysisDate).getTime() - new Date(a.analysisDate).getTime()
25+
)
26+
27+
// 라벨링
28+
const relabeled = sorted.map((ver, idx) => ({
29+
...ver,
30+
versionLabel: `v${sorted.length - idx} (${formatDateTimeKST(ver.analysisDate)})`,
31+
}))
32+
33+
const updatedHistory = { ...data, analysisVersions: relabeled }
34+
setHistory(updatedHistory)
35+
36+
// 2️⃣ 최신 버전 자동 선택
37+
const latestId = relabeled[0]?.analysisId ?? null
38+
setSelectedId(latestId)
39+
40+
// 3️⃣ 선택된 분석 결과 불러오기
41+
if (latestId) {
42+
const detail = await analysisApi.getAnalysisDetail(userId, repoId, latestId)
43+
setResult(detail)
44+
} else {
45+
setResult(null)
3346
}
34-
})()
47+
} catch (err) {
48+
console.error("❌ useAnalysisResult load() error:", err)
49+
} finally {
50+
setLoading(false)
51+
}
3552
}, [userId, repoId])
3653

37-
// 선택된 분석 결과 불러오기
54+
/**
55+
* 🎬 최초 로드
56+
*/
57+
useEffect(() => {
58+
load()
59+
}, [load])
60+
61+
/**
62+
* 🔄 특정 버전 선택 시 분석 결과 다시 로드
63+
*/
3864
useEffect(() => {
3965
if (!selectedId || !userId || !repoId) return
4066
;(async () => {
@@ -43,5 +69,12 @@ export function useAnalysisResult(userId?: number, repoId?: number) {
4369
})()
4470
}, [selectedId, userId, repoId])
4571

46-
return { history, result, loading, selectedId, setSelectedId }
47-
}
72+
return {
73+
history,
74+
result,
75+
loading,
76+
selectedId,
77+
setSelectedId,
78+
reload: load, // ✅ 외부에서 다시 불러올 수 있도록 노출
79+
}
80+
}

0 commit comments

Comments
 (0)