Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private Expense getExpense(SettlementUpdateRequest request) {
@Transactional
public SettlementResponse settleSettlement(Long id) {
Settlement settlement = findSettlementOrThrow(id);
settlement.setSettled();
settlement.convertSettled();
return SettlementMapper.toSettlementResponse(settlementRepository.save(settlement));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ public record SettlementResponse(
BigDecimal amount,
Boolean isSettled,
Long settlerId,
String settlerNickname,
Long payerId,
Long expenseId
String payerNickname,
Long expenseId,
String expenseDescription,
Long teamId
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void update(BigDecimal amount, Member settler, Member payer, Expense expe
}
}

public void setSettled() {
this.isSettled = true;
public void convertSettled() {
this.isSettled = !this.isSettled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ public static SettlementResponse toSettlementResponse(Settlement settlement) {
.updatedAt(settlement.getUpdatedAt())
.isSettled(settlement.getIsSettled())
.settlerId(settlement.getSettler().getId())
.settlerNickname(settlement.getSettler().getNickname())
.payerId(settlement.getPayer().getId())
.payerNickname(settlement.getPayer().getNickname())
.expenseId(settlement.getExpense().getId())
.expenseDescription(settlement.getExpense().getDescription())
.teamId(settlement.getExpense().getTeam().getId())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void createSettlement_ShouldSaveSettlement() {

@Test
@DisplayName("정산롼료")
void setSettled_ShouldUpdateSettlementStatus() {
void convertSettled_ShouldUpdateSettlementStatus() {
//given
when(settlementRepository.findWithSettlerAndPayerById(anyLong())).thenReturn(
Optional.of(settlement));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ void setUp() {
.expense(i < 15 ? expense1 : expense2)
.build();
if (i % 2 == 0) {
settlement.setSettled();
settlement.convertSettled();
}
settlementRepository.save(settlement);
}
Expand Down
17 changes: 3 additions & 14 deletions Frontend/luckeyseven/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import Login from "./pages/Login/Login"
import Signup from "./pages/Login/Signup"
import Home from "./pages/Home"
import {HomePage as SettlementHomePage} from "./pages/Settlement/HomePage"
import {TeamSettlementsPage} from "./pages/Settlement/TeamSettlementsPage"
import {SettlementNewPage} from "./pages/Settlement/SettlementNewPage"
import {SettlementEditPage} from "./pages/Settlement/SettlementEditPage"
Expand All @@ -19,8 +18,6 @@ import {getCurrentUser} from "./service/AuthService"
import TeamDashBoard from "./pages/TeamDashBoard";
import TeamSetup from "./pages/TeamSetup"
import {ToastProvider} from "./context/ToastContext"
import SettlementPage from "./pages/SettlementPage";
import ExpensesPage from "./pages/ExpensesPage";

// 보호된 라우트 컴포넌트
const ProtectedRoute = ({children}) => {
Expand Down Expand Up @@ -51,16 +48,8 @@ function App() {
}
/>
<Route path="/team-setup" element={<TeamSetup/>}/>

{/* Settlement 관련 라우트 */}
<Route
path="/settlement"
element={
<ProtectedRoute>
<SettlementHomePage/>
</ProtectedRoute>
}
/>
<Route
path="/teams/:teamId/settlements"
element={
Expand All @@ -70,7 +59,7 @@ function App() {
}
/>
<Route
path="/settlements/new"
path="/teams/:teamId/settlements/new"
element={
<ProtectedRoute>
<SettlementNewPage/>
Expand All @@ -86,7 +75,7 @@ function App() {
}
/>
<Route
path="/settlements/:settlementId/edit"
path="/teams/:teamId/settlements/:settlementId/edit"
element={
<ProtectedRoute>
<SettlementEditPage/>
Expand Down
45 changes: 26 additions & 19 deletions Frontend/luckeyseven/src/components/common/SettlementActions.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client"

import { useState } from "react"
import { updateSettlement } from "../../service/settlementService"
import { useToast } from "../../context/ToastContext"
import {useState} from "react"
import {updateSettlement} from "../../service/settlementService"
import {useToast} from "../../context/ToastContext"

export function SettlementActions({ settlement, onUpdate, inline = false }) {
const { addToast } = useToast()
export function SettlementActions({settlement, onUpdate, inline = false}) {
const {addToast} = useToast()
const [isLoading, setIsLoading] = useState(false)

const handleMarkAsSettled = async (e) => {
Expand All @@ -14,7 +14,9 @@ export function SettlementActions({ settlement, onUpdate, inline = false }) {
e.stopPropagation()
}

if (settlement.isSettled) return
if (settlement.isSettled) {
return
}

try {
setIsLoading(true)
Expand Down Expand Up @@ -50,11 +52,14 @@ export function SettlementActions({ settlement, onUpdate, inline = false }) {
e.stopPropagation()
}

if (!settlement.isSettled) return
if (!settlement.isSettled) {
return
}

try {
setIsLoading(true)
const updatedSettlement = await updateSettlement(settlement.id, { isSettled: false })
const updatedSettlement = await updateSettlement(settlement.id,
{}, true)

addToast({
title: "정산 완료 취소",
Expand Down Expand Up @@ -83,16 +88,18 @@ export function SettlementActions({ settlement, onUpdate, inline = false }) {
const btnClass = inline ? "btn-sm" : ""

return (
<div className="flex space-x-2">
{settlement.isSettled ? (
<button className={`btn btn-outline ${btnClass}`} onClick={handleCancelSettlement} disabled={isLoading}>
{isLoading ? "처리 중..." : "정산 완료 취소"}
</button>
) : (
<button className={`btn btn-primary ${btnClass}`} onClick={handleMarkAsSettled} disabled={isLoading}>
{isLoading ? "처리 중..." : "정산 완료 처리"}
</button>
)}
</div>
<div className="flex space-x-2">
{settlement.isSettled ? (
<button className={`btn btn-outline ${btnClass}`}
onClick={handleCancelSettlement} disabled={isLoading}>
{isLoading ? "처리 중..." : "정산 완료 취소"}
</button>
) : (
<button className={`btn btn-primary ${btnClass}`}
onClick={handleMarkAsSettled} disabled={isLoading}>
{isLoading ? "처리 중..." : "정산 완료 처리"}
</button>
)}
</div>
)
}
28 changes: 15 additions & 13 deletions Frontend/luckeyseven/src/components/common/UserProfile.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
export function UserProfile({ user, label }) {
export function UserProfile({nickname, label}) {
return (
<div>
<p className="text-sm text-muted">{label}</p>
<div className="flex items-center mt-1 space-x-2">
{user?.avatar ? (
<img src={user.avatar || "/placeholder.svg"} alt={user.name} className="w-8 h-8 rounded-full" />
) : (
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
{user?.name.charAt(0) || "?"}
</div>
)}
<span className="font-medium">{user?.name || "알 수 없음"}</span>
<div>
<p className="text-sm text-muted">{label}</p>
<div className="flex items-center mt-1 space-x-2">
{nickname ? (
<img src={"/placeholder.svg"} alt={nickname}
className="w-8 h-8 rounded-full"/>
) : (
<div
className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
{nickname.charAt(0) || "?"}

Copilot AI May 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure 'nickname' is defined before calling charAt(0) in the fallback rendering to avoid potential runtime errors. Use a conditional check or default value.

Suggested change
{nickname.charAt(0) || "?"}
{nickname && typeof nickname === "string" ? nickname.charAt(0) : "?"}

Copilot uses AI. Check for mistakes.
</div>
)}
<span className="font-medium">{nickname || "알 수 없음"}</span>
</div>
</div>
</div>
)
}
114 changes: 60 additions & 54 deletions Frontend/luckeyseven/src/components/settlement/settlement-detail.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client"

import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { formatDate, formatCurrency } from "../../lib/utils"
import { StatusBadge } from "../common/StatusBadge"
import { UserProfile } from "../common/UserProfile"
import { SettlementActions } from "../common/SettlementActions"
import {useState} from "react"
import {useNavigate} from "react-router-dom"
import {formatCurrency, formatDate} from "../../lib/utils"
import {StatusBadge} from "../common/StatusBadge"
import {UserProfile} from "../common/UserProfile"
import {SettlementActions} from "../common/SettlementActions"

export function SettlementDetail({ settlement: initialSettlement }) {
export function SettlementDetail({settlement: initialSettlement}) {
const navigate = useNavigate()
const [settlement, setSettlement] = useState(initialSettlement)

Expand All @@ -16,67 +16,73 @@ export function SettlementDetail({ settlement: initialSettlement }) {
}

const handleEdit = () => {
navigate(`/settlements/${settlement.id}/edit`)
navigate(`/teams/${settlement.teamId}/settlements/${settlement.id}/edit`)
}
console.info("팀아이디", settlement.teamId)

return (
<div className="card">
<div className="card-header">
<div className="flex justify-between items-center">
<h3 className="card-title">정산 #{settlement.id.substring(0, 8)}</h3>
<StatusBadge isSettled={settlement.isSettled} />
</div>
</div>
<div className="card-content space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<UserProfile user={settlement.payer} label="결제자 정보" />
<UserProfile user={settlement.settler} label="정산자 정보" />
<div className="card">
<div className="card-header">
<div className="flex justify-between items-center">
<h3 className="card-title">정산 #{String(settlement.id).substring(0,
8)}</h3>
<StatusBadge isSettled={settlement.isSettled}/>
</div>

<div className="space-y-4">
<div>
<h3 className="text-sm text-muted">연관 지출</h3>
<p className="mt-1 font-medium">{settlement.expense?.title || "알 수 없음"}</p>
{settlement.expense && (
<p className="text-sm text-muted">
{formatDate(settlement.expense.date)} · {formatCurrency(settlement.expense.amount)}
</p>
)}
</div>
<div className="card-content space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<UserProfile nickname={settlement.payerNickname} label="결제자 정보"/>
<UserProfile nickname={settlement.settlerNickname}
label="정산자 정보"/>
</div>

<div>
<h3 className="text-sm text-muted">정산 금액</h3>
<p className="mt-1 text-xl font-bold">{formatCurrency(settlement.amount)}</p>
<div className="space-y-4">
<div>
<h3 className="text-sm text-muted">연관 지출</h3>
<p className="mt-1 font-medium">{settlement.expenseDescription
|| "알 수 없음"}</p>
{settlement.expense && (
<p className="text-sm text-muted">
{formatDate(settlement.expense.date)} · {formatCurrency(
settlement.expense.amount)}
</p>
)}
</div>

<div>
<h3 className="text-sm text-muted">정산 금액</h3>
<p className="mt-1 text-xl font-bold">{formatCurrency(
settlement.amount)}</p>
</div>
</div>
</div>
</div>

<div className="pt-4 border-t">
<h3 className="text-sm text-muted mb-2">정산 정보</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p className="text-sm text-muted">생성일</p>
<p>{formatDate(settlement.createdAt)}</p>
</div>
<div>
<p className="text-sm text-muted">수정일</p>
<p>{formatDate(settlement.updatedAt)}</p>
<div className="pt-4 border-t">
<h3 className="text-sm text-muted mb-2">정산 정보</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p className="text-sm text-muted">생성일</p>
<p>{formatDate(settlement.createdAt)}</p>
</div>
<div>
<p className="text-sm text-muted">수정일</p>
<p>{formatDate(settlement.updatedAt)}</p>
</div>
</div>
</div>
</div>
</div>
<div className="card-footer">
<button className="btn btn-outline" onClick={() => navigate(-1)}>
뒤로 가기
</button>
<div className="flex space-x-2">
<button className="btn btn-outline" onClick={handleEdit}>
수정하기
<div className="card-footer">
<button className="btn btn-outline" onClick={() => navigate(-1)}>
뒤로 가기
</button>
<SettlementActions settlement={settlement} onUpdate={handleUpdate} />
<div className="flex space-x-2">
<button className="btn btn-outline" onClick={handleEdit}>
수정하기
</button>
<SettlementActions settlement={settlement} onUpdate={handleUpdate}/>
</div>
</div>
</div>
</div>
)
}
Loading