Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b0bcb47
feat: SettlementGroup 타입 및 getSettlementList API 함수 추가
yeoeun-ex Apr 12, 2026
3b155aa
feat: useGetSettlementList hook 추가
yeoeun-ex Apr 12, 2026
fc1c4ba
feat: 홈 정산 목록 API 연결 및 mock 데이터 제거
yeoeun-ex Apr 12, 2026
6c9ebde
feat: 입금 승인/거절 API 함수 및 타입 추가
yeoeun-ex Apr 12, 2026
ce17f2d
feat: useApprovePayment, useRejectPayment hook 추가
yeoeun-ex Apr 12, 2026
f609888
feat: 입금 관리 페이지 승인/거절 API 연결
yeoeun-ex Apr 12, 2026
c80da53
chore: prettier 적용 안된 부분 수정
yeoeun-ex Apr 12, 2026
90a6807
refactor: sortToggle boolean 상태를 SettlementSort 리터럴 타입으로 변경
yeoeun-ex Apr 12, 2026
494ac07
Merge remote-tracking branch 'origin/develop' into MD-28
yeoeun-ex Apr 18, 2026
6e535aa
chore: 불필요한 주석 해제
yeoeun-ex Apr 18, 2026
8f2f02f
fix: useGetSettlementList mutation에서 isLoading, isError UI 처리 추가
yeoeun-ex Apr 18, 2026
3139f4c
chore: mutation 훅 호출하는 부분을 컴포넌트 최상단으로 올리기
yeoeun-ex Apr 18, 2026
626da22
chore: settlementType에 따라 다른 문구를 보여주도록 변경
yeoeun-ex Apr 18, 2026
6971d20
chore: husky 동작테스트1
yeoeun-ex Apr 18, 2026
3f14ed7
chore: husky 테스트용 파일 삭제
yeoeun-ex Apr 18, 2026
041f548
chore: lint 에러 해결
yeoeun-ex Apr 18, 2026
51e11df
chore: 렌더 함수를 컴포넌트로 분리
yeoeun-ex Apr 18, 2026
496a5f3
chore: nvmrc 노드버전 변경
yeoeun-ex Apr 18, 2026
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
9 changes: 9 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)"
]
}
}
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.17.0
20.19.0
13 changes: 13 additions & 0 deletions src/entities/group/api/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {
CreateGroupData,
Group,
GroupHeaderResponse,
SettlementGroup,
SettlementSort,
SettlementStatus,
} from '@/entities/group/model/group.type';

export const getGroupList = async (): Promise<Group[]> => {
Expand Down Expand Up @@ -47,3 +50,13 @@ export const getGroupHeader = (
.get(`/group/header?groupToken=${groupToken}`)
.then((res) => res.data);
};

export const getSettlementList = (
status: SettlementStatus,
sort: SettlementSort,
limit = 100
): Promise<SettlementGroup[]> => {
return axiosInstance
.get('/groups', { params: { status, sort, limit } })
.then((res) => res.data);
};
14 changes: 14 additions & 0 deletions src/entities/group/model/group.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,17 @@ export interface GroupHeaderResponse {
bank: string;
accountNumber: string;
}

export type SettlementStatus = 'ALL' | 'IN_PROGRESS' | 'COMPLETED';
export type SettlementSort = 'LATEST' | 'OLDEST';

export interface SettlementGroup {
groupId: number;
groupCode: string;
name: string;
totalAmount: number;
totalMemberCount: number;
completedMemberCount: number;
createdAt: string;
completedAt: string | null;
}
17 changes: 14 additions & 3 deletions src/entities/payment/api/payment.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import axiosInstance from '@/shared/api/axios';
import { PaymentList } from '@/entities/payment/model/payment.type';
import {
PaymentActionResult,
PaymentList,
} from '@/entities/payment/model/payment.type';

const payment = {
getAll: (): Promise<PaymentList> =>
// TODO: 모의 데이터 제거 후 실제 API 연동 시 삭제 useMock: true 옵션 제거
// axiosInstance.get('/payments', { useMock: true }).then((res) => res.data),
axiosInstance.get('/payments').then((res) => res.data),

approve: (paymentRequestId: number): Promise<PaymentActionResult> =>
axiosInstance
.patch(`/payments/${paymentRequestId}/approve`)
.then((res) => res.data),

reject: (paymentRequestId: number): Promise<PaymentActionResult> =>
axiosInstance
.patch(`/payments/${paymentRequestId}/reject`)
.then((res) => res.data),
};

export default payment;
12 changes: 12 additions & 0 deletions src/entities/payment/model/payment.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ export interface PaymentRequest {
export interface PaymentList {
paymentRequests: PaymentRequest[];
}

export type PaymentStatus = 'APPROVED' | 'REJECTED';

export interface PaymentActionResult {
id: number;
settlementId: number;
requestMemberId: number;
targetUserId: number;
requestedAt: string;
processedAt: string;
status: PaymentStatus;
}
18 changes: 18 additions & 0 deletions src/features/home/api/useGetSettlementList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from '@tanstack/react-query';
import { getSettlementList } from '@/entities/group/api/group';
import type {
SettlementSort,
SettlementStatus,
} from '@/entities/group/model/group.type';

const useGetSettlementList = (
status: SettlementStatus,
sort: SettlementSort
) => {
return useQuery({
queryKey: ['settlementList', status, sort],
queryFn: () => getSettlementList(status, sort),
});
};

export default useGetSettlementList;
14 changes: 14 additions & 0 deletions src/features/payment-management/api/useApprovePayment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import payment from '@/entities/payment/api/payment';

const useApprovePayment = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (paymentRequestId: number) => payment.approve(paymentRequestId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['payments'] });
},
});
};

export default useApprovePayment;
14 changes: 14 additions & 0 deletions src/features/payment-management/api/useRejectPayment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import payment from '@/entities/payment/api/payment';

const useRejectPayment = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (paymentRequestId: number) => payment.reject(paymentRequestId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['payments'] });
},
});
};

export default useRejectPayment;
173 changes: 105 additions & 68 deletions src/pages/home/ui/HomePageSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import { useState } from 'react';
import CoinImg from '@/shared/assets/pngs/CoinImg.png';
import LinkMain from '@/shared/assets/pngs/link_main.png';
import CardMain from '@/shared/assets/pngs/card_main.png';
import { format } from 'date-fns';
import { ko } from 'date-fns/locale/ko';
import useGetSettlementList from '@/features/home/api/useGetSettlementList';
import type {
SettlementSort,
SettlementStatus,
} from '@/entities/group/model/group.type';

import Flex from '@/shared/ui/Flex';
import Button from '@/shared/ui/Button';
Expand All @@ -17,38 +24,7 @@ import Chip from '@/shared/ui/Chip';
import * as S from './index.style';
import HomeExpenseItem from '../HomeExpenseItem';

type SettlementType = 'IN_PROGRESS' | 'COMPLETED';

interface HomeExpenseItemType {
date: string;
groupName: string;
totalAmount: number;
paidMember: number;
totalMember: number;
id: number;
}
/**
* @Todo 진행중인 정산 내역 조회 API 함수 호출
* 우선 mock data로 대체
* */
const settlementListMock: HomeExpenseItemType[] = [
{
id: 1,
date: '2026년 2월 22일',
groupName: 'DND 데모데이',
totalAmount: 120000,
paidMember: 3,
totalMember: 6,
},
{
id: 2,
date: '2026년 1월 14일',
groupName: 'DND 7조 첫모임',
totalAmount: 150000,
paidMember: 5,
totalMember: 6,
},
];
type SettlementType = SettlementStatus;

export function MainHeader() {
const theme = useTheme();
Expand Down Expand Up @@ -128,10 +104,92 @@ export function SettlementBanner() {
);
}

type SettlementContentProps = {
isLoading: boolean;
isError: boolean;
settlementList: NonNullable<ReturnType<typeof useGetSettlementList>['data']>;
settlementType: SettlementType;
};

function SettlementContent({
isLoading,
isError,
settlementList,
settlementType,
}: SettlementContentProps) {
if (isLoading) {
return (
<Flex
direction="column"
py={20}
justifyContent="center"
alignItems="center"
flexGrow={1}
gap={20}
>
<Text variant="body2R" color="semantic.text.subtle">
정산 내역을 불러오는 중이에요.
</Text>
</Flex>
);
}
if (isError) {
return (
<Flex
direction="column"
py={20}
justifyContent="center"
alignItems="center"
flexGrow={1}
gap={20}
>
<Text variant="body2R" color="semantic.text.subtle">
정산 내역을 불러오지 못했어요.
</Text>
</Flex>
);
}
if (settlementList.length > 0) {
return (
<S.SettlementListWrapper>
{settlementList.map((item) => (
<HomeExpenseItem
key={item.groupId}
date={format(new Date(item.createdAt), 'yyyy년 M월 d일', {
locale: ko,
})}
groupName={item.name}
totalAmount={item.totalAmount}
paidMember={item.completedMemberCount}
totalMember={item.totalMemberCount}
/>
))}
</S.SettlementListWrapper>
);
}
return (
<Flex
direction="column"
py={20}
justifyContent="center"
alignItems="center"
flexGrow={1}
gap={20}
>
<S.NoSettlementImg src={CoinImg} alt="" />
<Text variant="body2R" color="semantic.text.subtle">
{settlementType === 'IN_PROGRESS'
? '아직 진행중인 정산이 없어요.'
: '완료된 정산이 없어요.'}
</Text>
</Flex>
);
}

export function SettlementList() {
const [settlementType, setSettlementType] =
useState<SettlementType>('IN_PROGRESS');
const [sortToggle, setSortToggle] = useState<boolean>(false);
const [sort, setSort] = useState<SettlementSort>('LATEST');
const theme = useTheme();

const handleSettlementTypeButtonClick = (type: SettlementType) => {
Expand All @@ -142,12 +200,14 @@ export function SettlementList() {
};

const handleSortOptionClick = () => {
setSortToggle(!sortToggle);
setSort((prev) => (prev === 'LATEST' ? 'OLDEST' : 'LATEST'));
};

const settlementList = sortToggle
? [...settlementListMock].reverse()
: settlementListMock;
const { data, isLoading, isError } = useGetSettlementList(
settlementType,
sort
);
const settlementList = data ?? [];

return (
<Flex direction="column" pt={16} flexGrow={1}>
Expand Down Expand Up @@ -175,47 +235,24 @@ export function SettlementList() {
{/** @Todo Select 컴포넌트 개발 후 변경 */}
<Button variant="text" onClick={handleSortOptionClick}>
<Text variant="body2R" color="semantic.text.subtle">
{sortToggle ? '오래된순' : '최신순'}
{sort === 'OLDEST' ? '오래된순' : '최신순'}
</Text>
<Next
width={theme.unit[24]}
height={theme.unit[24]}
style={{
transform: `rotate(${sortToggle ? 180 : 0}deg)`,
transform: `rotate(${sort === 'OLDEST' ? 180 : 0}deg)`,
transition: 'transform 0.2s ease',
}}
/>
</Button>
</Flex>
{settlementList.length > 0 && settlementType === 'IN_PROGRESS' && (
<S.SettlementListWrapper>
{settlementList.map((data) => (
<HomeExpenseItem
key={data.id}
date={data.date}
groupName={data.groupName}
totalAmount={data.totalAmount}
paidMember={data.paidMember}
totalMember={data.totalMember}
/>
))}
</S.SettlementListWrapper>
)}
{settlementType === 'COMPLETED' && (
<Flex
direction="column"
py={20}
justifyContent="center"
alignItems="center"
flexGrow={1}
gap={20}
>
<S.NoSettlementImg src={CoinImg} alt="" />
<Text variant="body2R" color="semantic.text.subtle">
아직 진행중인 정산이 없어요.
</Text>
</Flex>
)}
<SettlementContent
isLoading={isLoading}
isError={isError}
settlementList={settlementList}
settlementType={settlementType}
/>
</Flex>
);
}
Loading
Loading