Skip to content

Commit 84b14c9

Browse files
committed
feat: payer-inquiry 조회, 추가 api 연결
1 parent 87efb24 commit 84b14c9

File tree

4 files changed

+103
-121
lines changed

4 files changed

+103
-121
lines changed

src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,12 @@ import {
1010
import { Button } from '@/components/ui/button';
1111
import { Checkbox } from '@/components/ui/checkbox';
1212
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
13-
14-
interface Invoice {
15-
name: string;
16-
student_id: string;
17-
admin?: boolean;
18-
}
19-
20-
interface TableComponentProps {
21-
data: Invoice[];
22-
showCheckboxes?: boolean; // 고민이에요... ESLint: propType "handleDelete" is not required, but has no corresponding defaultProps declaration 에러가 뜸
23-
headers?: string[];
24-
selected: string[];
25-
setSelected: (selectedIds: (prev: string[]) => string[]) => void;
26-
handleDelete?: (selectedIds: string[]) => void; // 22
27-
}
13+
import { Payer, TableComponentProps } from '@/types/payerInquiry';
2814

2915
export default function TableComponent({
30-
data,
16+
payers,
3117
showCheckboxes = true,
32-
headers = ['이름', '학번', '관리자 여부'], // 기본값을 설정
18+
headers = ['이름', '학번', '회원 여부'], // 기본값을 설정
3319
selected,
3420
setSelected,
3521
handleDelete = () => {}, // 기본값으로 빈 함수 설정
@@ -45,13 +31,15 @@ export default function TableComponent({
4531
);
4632
};
4733

48-
const paginatedData = data.slice(
49-
(currentPage - 1) * rowsPerPage,
50-
currentPage * rowsPerPage,
51-
);
34+
// payers가 배열이 아닐 경우 기본 빈 배열로 대체
35+
const paginatedData = Array.isArray(payers)
36+
? payers.slice((currentPage - 1) * rowsPerPage, currentPage * rowsPerPage)
37+
: [];
5238

5339
const handleSelectAll = () => {
54-
const visibleIds = paginatedData.map((item) => item.student_id);
40+
const visibleIds = paginatedData.map(
41+
(item: { studentId: string }) => item.studentId,
42+
);
5543
setSelected((prev: string[]) =>
5644
prev.length === visibleIds.length ? [] : visibleIds,
5745
);
@@ -78,25 +66,23 @@ export default function TableComponent({
7866
</TableRow>
7967
</TableHeader>
8068
<TableBody>
81-
{paginatedData.map((item) => (
82-
<TableRow key={item.student_id}>
69+
{paginatedData.map((item: Payer) => (
70+
<TableRow key={item.studentId}>
8371
{showCheckboxes && (
8472
<TableCell className="w-10 text-center">
8573
<Checkbox
86-
checked={selected.includes(item.student_id)}
87-
onCheckedChange={() => handleSelect(item.student_id)}
74+
checked={selected.includes(item.studentId)}
75+
onCheckedChange={() => handleSelect(item.studentId)}
8876
/>
8977
</TableCell>
9078
)}
9179
<TableCell className="w-30 text-center">{item.name}</TableCell>
9280
<TableCell className="w-30 text-center">
93-
{item.student_id}
81+
{item.studentId}
82+
</TableCell>
83+
<TableCell className="w-30 text-center">
84+
{item.registered !== undefined && (item.registered ? 'o' : 'x')}
9485
</TableCell>
95-
{headers.includes('관리자 여부') && (
96-
<TableCell className="w-30 text-center">
97-
{item.admin !== undefined && (item.admin ? 'o' : 'x')}
98-
</TableCell>
99-
)}
10086
</TableRow>
10187
))}
10288
</TableBody>
@@ -110,12 +96,12 @@ export default function TableComponent({
11096
<ChevronLeftIcon className="h-10 w-10 cursor-pointer text-black-primary" />
11197
</Button>
11298
<span>
113-
{currentPage} / {Math.ceil(data.length / rowsPerPage)}
99+
{currentPage} / {Math.ceil(payers.length / rowsPerPage)}
114100
</span>
115101
<Button
116102
className="bg-transparent shadow-transparent hover:bg-transparent"
117103
size="chevron"
118-
disabled={currentPage === Math.ceil(data.length / rowsPerPage)}
104+
disabled={currentPage === Math.ceil(payers.length / rowsPerPage)}
119105
onClick={() => setCurrentPage((prev) => prev + 1)}
120106
>
121107
<ChevronRightIcon className="h-6 w-6 cursor-pointer text-black-primary" />
@@ -124,8 +110,3 @@ export default function TableComponent({
124110
</div>
125111
);
126112
}
127-
128-
// defaultProps를 사용하여 headers에 기본값 설정
129-
TableComponent.defaultProps = {
130-
headers: ['이름', '학번', '관리자 여부'],
131-
};

src/app/desktop/payer-inquiry/page.tsx

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,106 +5,107 @@ import Search from '@/components/desktop/Search';
55
import { useState, useEffect } from 'react';
66
import AddStudentId from '@/components/desktop/AddStudentId';
77
import { Button } from '@/components/ui/button';
8+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
9+
import { getPayer, addPayer } from '@/services/payer-inquiry';
810
import TableComponent from './_components/TableComponent';
911
import AddInput from '../../../components/desktop/AddInput';
1012

1113
export default function PayerInquiryPage() {
12-
const [data, setData] = useState<any[]>([]); // 기존 데이터
13-
const [addedData, setAddedData] = useState<any[]>([]); // 추가된 데이터
14-
const [isDeleteModeOriginal, setIsDeleteModeOriginal] = useState(false); // 기존 데이터 삭제 모드
15-
const [isDeleteModeAdded, setIsDeleteModeAdded] = useState(false); // 추가된 데이터 삭제 모드
16-
const [selectedOriginal, setSelectedOriginal] = useState<string[]>([]); // 기존 데이터에서 선택된 항목
17-
const [selectedAdded, setSelectedAdded] = useState<string[]>([]); // 추가된 데이터에서 선택된 항목
14+
const queryClient = useQueryClient();
15+
const [addedData, setAddedData] = useState<any[]>([]);
16+
const [isDeleteModeOriginal, setIsDeleteModeOriginal] = useState(false);
17+
const [isDeleteModeAdded, setIsDeleteModeAdded] = useState(false);
18+
const [selectedOriginal, setSelectedOriginal] = useState<string[]>([]);
19+
const [selectedAdded, setSelectedAdded] = useState<string[]>([]);
1820

1921
const [newStudentId, setNewStudentId] = useState('');
2022
const [newStudentName, setNewStudentName] = useState('');
2123

22-
// API에서 데이터 가져오기
23-
// useEffect(() => {
24-
// const fetchMembers = async () => {
25-
// try {
26-
// const response = await fetch(
27-
// `${process.env.REACT_APP_API_URL}/admin/members`,
28-
// );
29-
// const result = await response.json();
30-
// const members = result.members.map((member: any) => ({
31-
// name: member.name,
32-
// student_id: member.studentId,
33-
// admin: member.role === 'ADMIN',
34-
// }));
35-
// setData(members); // 받아온 데이터로 기존 데이터 업데이트
36-
// } catch (error) {
37-
// console.error('API 호출 실패:', error);
38-
// }
39-
// };
40-
//
41-
// fetchMembers();
42-
// }, []);
43-
44-
// 학번 입력 핸들러
24+
const mutation = useMutation({
25+
mutationFn: addPayer,
26+
onSuccess: () => {
27+
alert('추가된 납부자 정보가 성공적으로 저장되었습니다.');
28+
setAddedData([]);
29+
},
30+
onError: () => {
31+
alert('추가된 납부자 정보 저장에 실패했습니다.');
32+
},
33+
});
34+
35+
const {
36+
data: originalData = [],
37+
isError: originalDataError,
38+
isLoading,
39+
} = useQuery({
40+
queryKey: ['payers'],
41+
queryFn: getPayer,
42+
});
43+
44+
useEffect(() => {
45+
console.log('Updated originalData:', originalData);
46+
}, [originalData]);
47+
48+
if (isLoading) {
49+
return <p>데이터를 불러오는 중...</p>;
50+
}
51+
52+
if (originalDataError) {
53+
console.error('기존 데이터 로드 중 오류 발생');
54+
}
55+
4556
const handleStudentIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
4657
setNewStudentId(e.target.value);
4758
};
4859

49-
// 이름 입력 핸들러
5060
const handleStudentNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
5161
setNewStudentName(e.target.value);
5262
};
5363

54-
// 추가 버튼 클릭 시 실행될 함수
5564
const handleAddStudent = () => {
5665
if (!newStudentId || !newStudentName) {
5766
alert('이름과 학번을 입력해주세요.');
5867
return;
5968
}
6069

61-
// 학번이 8자리 숫자인지 검증
6270
const studentIdPattern = /^\d{8}$/;
6371
if (!studentIdPattern.test(newStudentId)) {
6472
alert('학번은 8자리 숫자로 입력해야 합니다.');
6573
return;
6674
}
6775

68-
const newEntry = { name: newStudentName, student_id: newStudentId };
69-
setAddedData([...addedData, newEntry]); // 추가된 데이터 업데이트
76+
const newEntry = { name: newStudentName, studentId: newStudentId };
77+
setAddedData((prev) => [...prev, newEntry]);
7078
setNewStudentId('');
7179
setNewStudentName('');
7280
};
7381

74-
// 기존 데이터 삭제
75-
const handleDeleteOriginal = () => {
76-
const updatedData = data.filter(
77-
(item) => !selectedOriginal.includes(item.student_id),
78-
);
79-
setData(updatedData);
80-
setIsDeleteModeOriginal(false);
81-
setSelectedOriginal([]);
82-
};
83-
84-
// 추가된 데이터 삭제
85-
const handleDeleteAdded = () => {
86-
const updatedData = addedData.filter(
87-
(item) => !selectedAdded.includes(item.student_id),
88-
);
89-
setAddedData(updatedData);
90-
setIsDeleteModeAdded(false);
91-
setSelectedAdded([]);
92-
};
93-
94-
// 기존 데이터 삭제 모드 토글
95-
const toggleDeleteModeOriginal = () => {
96-
setIsDeleteModeOriginal((prev) => !prev);
97-
setSelectedOriginal([]);
82+
const handleDeleteData = (mode: 'original' | 'added') => {
83+
if (mode === 'original') {
84+
setSelectedOriginal([]);
85+
setIsDeleteModeOriginal(false);
86+
} else {
87+
const updatedData = addedData.filter(
88+
(item) => !selectedAdded.includes(item.studentId),
89+
);
90+
setAddedData(updatedData);
91+
setSelectedAdded([]);
92+
setIsDeleteModeAdded(false);
93+
}
9894
};
9995

100-
// 추가된 데이터 삭제 모드 토글
101-
const toggleDeleteModeAdded = () => {
102-
setIsDeleteModeAdded((prev) => !prev);
103-
setSelectedAdded([]);
96+
const toggleDeleteMode = (mode: 'original' | 'added') => {
97+
if (mode === 'original') {
98+
setIsDeleteModeOriginal((prev) => !prev);
99+
setSelectedOriginal([]);
100+
} else {
101+
setIsDeleteModeAdded((prev) => !prev);
102+
setSelectedAdded([]);
103+
}
104104
};
105105

106-
const api = () => {
107-
console.log('api 적용할 곳입니다.');
106+
const handleApply = () => {
107+
if (addedData.length === 0) return alert('추가된 데이터가 없습니다.');
108+
mutation.mutate({ payers: addedData });
108109
};
109110

110111
return (
@@ -133,7 +134,7 @@ export default function PayerInquiryPage() {
133134
</div>
134135
<div className="flex w-full flex-col">
135136
<TableComponent
136-
data={addedData}
137+
payers={addedData}
137138
headers={['추가된 이름', '추가된 학번']}
138139
showCheckboxes={isDeleteModeAdded}
139140
selected={selectedAdded}
@@ -144,7 +145,7 @@ export default function PayerInquiryPage() {
144145
size="sm"
145146
type="button"
146147
variant="deleteSecondary"
147-
onClick={toggleDeleteModeAdded}
148+
onClick={() => toggleDeleteMode('added')}
148149
>
149150
{isDeleteModeAdded ? '취소' : '삭제'}
150151
</Button>
@@ -153,7 +154,7 @@ export default function PayerInquiryPage() {
153154
size="sm"
154155
variant="deletePrimary"
155156
type="button"
156-
onClick={handleDeleteAdded}
157+
onClick={() => handleDeleteData('added')}
157158
>
158159
완료
159160
</Button>
@@ -162,15 +163,15 @@ export default function PayerInquiryPage() {
162163
</div>
163164
</div>
164165
<div className="bottom-0 flex justify-center gap-2 pt-10">
165-
<Button type="button" variant="primary" onClick={api}>
166+
<Button type="button" variant="primary" onClick={handleApply}>
166167
적용하기
167168
</Button>
168169
</div>
169170
</Sidebar>
170171
</div>
171172
<div className="flex flex-col justify-between">
172173
<TableComponent
173-
data={data}
174+
payers={originalData.payers}
174175
showCheckboxes={isDeleteModeOriginal}
175176
selected={selectedOriginal}
176177
setSelected={setSelectedOriginal}
@@ -180,7 +181,7 @@ export default function PayerInquiryPage() {
180181
size="sm"
181182
type="button"
182183
variant="deleteSecondary"
183-
onClick={toggleDeleteModeOriginal}
184+
onClick={() => toggleDeleteMode('original')}
184185
>
185186
{isDeleteModeOriginal ? '취소' : '삭제'}
186187
</Button>
@@ -189,9 +190,8 @@ export default function PayerInquiryPage() {
189190
<Button
190191
type="button"
191192
size="sm"
192-
variant="deleteSecondary"
193-
className={`btn whitespace-nowrap rounded-md px-3 py-2 text-sm text-white-primary ${isDeleteModeOriginal ? 'bg-gray-primary' : 'bg-gray-secondary'}`}
194-
onClick={handleDeleteOriginal}
193+
variant="deletePrimary"
194+
onClick={() => handleDeleteData('original')}
195195
>
196196
완료
197197
</Button>

src/services/payer-inquiry.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import PublicAxiosInstance from './publicAxiosInstance';
22
import PrivateAxiosInstance from './privateAxiosInstance';
33

4-
export const fetchOriginalData = async () => {
4+
export const getPayer = async () => {
55
const response = await PrivateAxiosInstance.get('/admin/members/payers');
6-
return response.data; // 반환 값은 필요에 따라 수정하세요.
6+
return response.data;
77
};
88

9-
export const addStudent = async (studentId: string, studentName: string) => {
10-
// API 요청
11-
const response = await PrivateAxiosInstance.post('/admin/members/payers', {
12-
studentId,
13-
name: studentName,
14-
});
9+
export const addPayer = async (data: {
10+
payers: { studentId: string; name: string }[];
11+
}) => {
12+
const response = await PrivateAxiosInstance.post(
13+
'/admin/members/payers',
14+
data,
15+
);
1516
return response.data;
1617
};

src/types/payerInquiry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface Payer {
66
}
77

88
export interface TableComponentProps {
9-
data: Payer[];
9+
payers: Payer[];
1010
showCheckboxes?: boolean;
1111
headers?: string[];
1212
selected: string[];

0 commit comments

Comments
 (0)