Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,16 @@ public class Budget extends BaseEntity {
@Column(nullable = false)
private Long setBy;

@Setter
@Column(nullable = false)
private BigDecimal balance;
@Setter

private BigDecimal foreignBalance;

@Setter
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 3)
private CurrencyCode foreignCurrency;

@Setter
private BigDecimal avgExchangeRate;

@Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.luckyseven.backend.sharedkernel.exception.ExceptionCode;
import com.luckyseven.backend.sharedkernel.jwt.entity.BlackListToken;
import com.luckyseven.backend.sharedkernel.jwt.repository.BlackListTokenRepository;
import com.luckyseven.backend.sharedkernel.jwt.repository.RefreshTokenRepository;
import com.luckyseven.backend.sharedkernel.jwt.utill.JwtTokenizer;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -69,13 +70,7 @@ public void logout(
String refreshToken,
HttpServletResponse resp
) {
blackListTokenRepository.save(
BlackListToken.builder()
.tokenValue(refreshToken)
.expirationTime(
jwtTokenizer.parseRefreshToken(refreshToken).getExpiration().toInstant())
.build()
);
jwtTokenizer.LogoutRefreshToken(refreshToken);

//TODO: 올바른 삭제 방법인가?
Cookie expired = new Cookie("refreshToken", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ public interface RefreshTokenRepository extends JpaRepository<RefreshToken,Long>


Optional<RefreshToken> findByUserId(Long id);


void deleteByTokenValue(String refreshToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.transaction.Transactional;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
Expand All @@ -28,6 +29,7 @@

@Slf4j
@Component
@Transactional
public class JwtTokenizer {

private final RefreshTokenRepository refreshTokenRepository;
Expand Down Expand Up @@ -141,10 +143,12 @@ private SecretKey getSigningKey(byte[] signingKey) {
}


@Transactional
public String validateRefreshToken(String refreshToken, HttpServletResponse response) {
if (blackListTokenRepository.existsByTokenValue(refreshToken)) {
throw new CustomLogicException(ExceptionCode.JWT_BLACKLISTED_TOKEN);
}
refreshTokenRepository.deleteByTokenValue(refreshToken);
Claims claims = parseRefreshToken(refreshToken);
Long memberId = Long.parseLong(claims.getSubject());
MemberDetails user = customMemberDetailsService.loadUserById(memberId);
Expand All @@ -154,7 +158,20 @@ public String validateRefreshToken(String refreshToken, HttpServletResponse resp
.expirationTime(claims.getExpiration().toInstant())
.build()
);

return reissueTokenPair(response, user);
}

public void LogoutRefreshToken(String refreshToken) {
blackListTokenRepository.save(
BlackListToken.builder()
.tokenValue(refreshToken)
.expirationTime(
parseRefreshToken(refreshToken).getExpiration().toInstant())
.build()
);
refreshTokenRepository.deleteByTokenValue(refreshToken);

}

}
5 changes: 1 addition & 4 deletions Frontend/luckeyseven/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ function App() {
}
/>
<Route path="/team-setup" element={<TeamSetup/>}/>
{/*settlement 임시 페이지 import 삭제로 인한 warning. 병합된 페이지로 변경 예정*/}
<Route path="/team/:teamId/settlement" element={<SettlementPage/>}/>
{/*expenses 임시 페이지 import 삭제로 인한 warning. 병합된 페이지로 변경 예정*/}
<Route path="/team/:teamId/expenses" element={<ExpensesPage/>}/>

{/* Settlement 관련 라우트 */}
<Route
path="/settlement"
Expand Down
65 changes: 65 additions & 0 deletions Frontend/luckeyseven/src/components/OverviewTabContent-back.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import styles from '../styles/App.module.css'; // Keep App.module.css for general layout styles
import SummaryCard from '../components/SummaryCard';
import BudgetBreakdown from '../components/BudgetBreakdown';
import RecentExpensesTable from '../components/RecentExpenseTable';

const OverviewTabContent = ({ dashboardData }) => {
if (!dashboardData) {
return <div>Loading overview...</div>; // Or some other loading indicator
}

// Adapt data for child components
const {
team_id,
totalAmount,
balance,
foreignBalance,
foreignCurrency,
expenseList, // This is List<ExpenseDto>
avgExchangeRate
} = dashboardData;

// Calculate percentages for SummaryCard if needed
const totalExpense = totalAmount - balance;
const totalExpensePercentage = totalAmount > 0 ? (totalExpense / totalAmount) * 100 : 0;
const remainingBudgetPercentage = totalAmount > 0 ? (balance / totalAmount) * 100 : 0;
const transformedExpenses = expenseList.map(expense => ({
id: expense.id,
title: `Expense by ${expense.payerNickname}`,
amount: parseFloat(expense.amount),
category: expense.category,
description: expense.description,
date: expense.date,
paidBy: expense.payerNickname,
paymentMethod: expense.paymentMethod,
currency: expense.paymentMethod == "CASH" ? '₩': foreignCurrency, // Assuming KRW for now, adjust if dynamic
}));


return (
<div>
<div className={styles.summaryCardContainer}>
<SummaryCard title="총 예산" amount={totalAmount} currency="₩"/>
<SummaryCard title="총 지출" amount={totalExpense} currency="₩" percentage={totalExpensePercentage.toFixed(1)} of="of budget" />
<SummaryCard title="남은 예산" amount={balance} currency="₩" percentage={remainingBudgetPercentage.toFixed(1)} of="of budget" />
{foreignCurrency && foreignBalance !== undefined && (
<SummaryCard
title={`남은 외화 (${foreignCurrency})`}
amount={foreignBalance}
currency={foreignCurrency} // Use the currency code (e.g., USD, JPY)
// percentage={...} // Add percentage if applicable
// of="of foreign budget"
/>
)}
</div>
<div className={styles.detailsContainer}>
{/* BudgetBreakdown might need significant changes based on how categories are handled now */}
<BudgetBreakdown expenses={transformedExpenses} />
<RecentExpensesTable expenses={transformedExpenses} />
</div>
</div>
);
};

export default OverviewTabContent;
45 changes: 21 additions & 24 deletions Frontend/luckeyseven/src/components/OverviewTabContent.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,62 @@
import React from 'react';
import styles from '../styles/App.module.css'; // Keep App.module.css for general layout styles
import styles from '../styles/App.module.css';
import SummaryCard from '../components/SummaryCard';
import BudgetBreakdown from '../components/BudgetBreakdown';
import RecentExpensesTable from '../components/RecentExpenseTable';

const OverviewTabContent = ({ dashboardData }) => {
if (!dashboardData) {
return <div>Loading overview...</div>; // Or some other loading indicator
return <div>Loading overview...</div>;
}

// Adapt data for child components
const {
team_id,
totalAmount,
balance,
foreignBalance,
foreignCurrency,
expenseList, // This is List<ExpenseDto>
avgExchangeRate
teamName,
totalAmount = 0, // 기본값 설정
balance = 0, // 기본값 설정
foreignBalance = 0, // 기본값 설정
foreignCurrency = 'KRW', // 기본값 설정
expenseList = [], // 기본값 설정
avgExchangeRate = 0, // 기본값 설정
} = dashboardData;

// Calculate percentages for SummaryCard if needed
const totalExpense = totalAmount - balance;
const totalExpensePercentage = totalAmount > 0 ? (totalExpense / totalAmount) * 100 : 0;
const remainingBudgetPercentage = totalAmount > 0 ? (balance / totalAmount) * 100 : 0;
const transformedExpenses = expenseList.map(expense => ({

// 지출 목록이 없는 경우 빈 배열로 처리
const transformedExpenses = Array.isArray(expenseList) ? expenseList.map(expense => ({
id: expense.id,
title: `Expense by ${expense.payerNickname}`,
amount: parseFloat(expense.amount),
title: `Expense by ${expense.payerNickname}`,
amount: parseFloat(expense.amount),
category: expense.category,
description: expense.description,
date: expense.date,
date: expense.date,
paidBy: expense.payerNickname,
paymentMethod: expense.paymentMethod,
currency: expense.paymentMethod == "CASH" ? '₩': foreignCurrency, // Assuming KRW for now, adjust if dynamic
}));

paymentMethod: expense.paymentMethod,
currency: expense.paymentMethod === "CASH" ? '₩' : foreignCurrency,
})) : [];

return (
<div>
<div className={styles.summaryCardContainer}>
<SummaryCard title="총 예산" amount={totalAmount} currency="₩"/>
<SummaryCard title="총 지출" amount={totalExpense} currency="₩" percentage={totalExpensePercentage.toFixed(1)} of="of budget" />
<SummaryCard title="남은 예산" amount={balance} currency="₩" percentage={remainingBudgetPercentage.toFixed(1)} of="of budget" />
{foreignCurrency && foreignBalance !== undefined && (
{foreignCurrency && foreignBalance !== undefined && foreignCurrency !== 'KRW' && (
<SummaryCard
title={`남은 외화 (${foreignCurrency})`}
amount={foreignBalance}
currency={foreignCurrency} // Use the currency code (e.g., USD, JPY)
// percentage={...} // Add percentage if applicable
// of="of foreign budget"
currency={foreignCurrency}
/>
)}
</div>
<div className={styles.detailsContainer}>
{/* BudgetBreakdown might need significant changes based on how categories are handled now */}
<BudgetBreakdown expenses={transformedExpenses} />
<RecentExpensesTable expenses={transformedExpenses} />
</div>
</div>
);
};

export default OverviewTabContent;
export default OverviewTabContent;
21 changes: 21 additions & 0 deletions Frontend/luckeyseven/src/components/PageHeaderControls-back.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import styles from '../styles/PageHeaderControls.module.css';

const PageHeaderControls = ({pageHeaderData}) => {
console.log('PageHeaderControls props ->', { pageHeaderData });
const { teamName } = pageHeaderData;
return (
<div className={styles.pageHeaderControls}>
<div>
<h2 className={styles.pageTitle}>{teamName}</h2>
<p className={styles.pageSubtitle}>Manage your team's expenses and budget</p>
</div>
<div className={styles.pageActions}>
<button className={styles.buttonSecondary}>예산 추가</button>
<button className={styles.buttonPrimary}>예산 수정</button>
</div>
</div>
);
};

export default PageHeaderControls;
77 changes: 64 additions & 13 deletions Frontend/luckeyseven/src/components/PageHeaderControls.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,72 @@
import React from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import styles from '../styles/PageHeaderControls.module.css';

const PageHeaderControls = ({pageHeaderData}) => {
console.log('PageHeaderControls props ->', { pageHeaderData });
const { teamName } = pageHeaderData;
const PageHeaderControls = ({ pageHeaderData }) => {
const navigate = useNavigate();
const { teamName, teamId, openDialog } = pageHeaderData || {};

const handleSetBudget = () => {
if (typeof openDialog === 'function') {
openDialog('set');
} else {
console.error('openDialog is not a function');
}
};

const handleEditBudget = () => {
if (typeof openDialog === 'function') {
openDialog('edit');
} else {
console.error('openDialog is not a function');
}
};

const handleAddBudget = () => {
if (typeof openDialog === 'function') {
openDialog('add');
} else {
console.error('openDialog is not a function');
}
};

const handleDeleteBudget = async () => {
if (!teamId) {
console.error('teamId is missing');
return;
}

// 사용자에게 삭제 확인 요청
if (!window.confirm('정말로 예산을 삭제하시겠습니까?')) {
return;
}

try {
await axios.delete(`/api/teams/${teamId}/budget`);
console.log('예산 삭제 완료');
alert('예산이 성공적으로 삭제되었습니다.');
} catch (error) {
console.error('예산 삭제 실패:', error);
// 에러 메시지 표시
alert('예산 삭제 실패: ' + (error.response?.data?.message || error.message));
}
};

return (
<div className={styles.pageHeaderControls}>
<div>
<h2 className={styles.pageTitle}>{teamName}</h2>
<p className={styles.pageSubtitle}>Manage your team's expenses and budget</p>
</div>
<div className={styles.pageActions}>
<button className={styles.buttonSecondary}>예산 추가</button>
<button className={styles.buttonPrimary}>예산 수정</button>
</div>
<div className={styles.pageHeaderControls}>
<div>
<h2 className={styles.pageTitle}>{teamName || 'Team Dashboard'}</h2>
<p className={styles.pageSubtitle}>Manage your team's expenses and budget</p>
</div>
<div className={styles.pageActions}>
<button className={styles.buttonPrimary} onClick={handleSetBudget}>예산 설정</button>
<button className={styles.buttonSecondary} onClick={handleEditBudget}>예산 수정</button>
<button className={styles.buttonSecondary} onClick={handleAddBudget}>예산 추가</button>
<button onClick={handleDeleteBudget}>예산 삭제</button>
</div>
</div>
);
};

export default PageHeaderControls;
export default PageHeaderControls;
4 changes: 2 additions & 2 deletions Frontend/luckeyseven/src/components/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const Tabs = ({ activeTab, setActiveTab }) => {

switch (tab) {
case 'Expenses':
return `/team/${teamId}/expenses`;
return `/teams/${teamId}/expenses`;
case 'Settlement':
return `/team/${teamId}/settlement`;
return `/teams/${teamId}/settlement`;
default:
return '#'; // Overview and Members will still use setActiveTab
}
Expand Down
Loading