diff --git a/src/app/desktop/item-list/_components/ItemTable/index.tsx b/src/app/desktop/item-list/_components/ItemTable/index.tsx
index bda825e..7176f4d 100644
--- a/src/app/desktop/item-list/_components/ItemTable/index.tsx
+++ b/src/app/desktop/item-list/_components/ItemTable/index.tsx
@@ -10,28 +10,10 @@ import {
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
-
-interface Item {
- logo: string;
- name: string;
- isConsumable: boolean;
- totalQuantity: number;
- rentedQuantity: number;
- id: string;
- isAdmin?: boolean;
-}
-
-interface ItemTableProps {
- data: Item[];
- showCheckboxes?: boolean;
- headers?: string[];
- selected: string[];
- setSelected: (selectedIds: (prev: string[]) => string[]) => void;
- handleDelete?: (selectedIds: string[]) => void;
-}
+import { Item, ItemTableProps } from '@/types/items';
export default function ItemTable({
- data,
+ items = [],
showCheckboxes = true,
headers = ['로고', '물품명', '소모품', '총 수량', '대여 중'],
selected,
@@ -41,26 +23,25 @@ export default function ItemTable({
const [currentPage, setCurrentPage] = useState(1);
const rowsPerPage = 10;
- const handleSelect = (id: string) => {
- setSelected((prev: string[]) =>
- prev.includes(id)
- ? prev.filter((itemId) => itemId !== id)
- : [...prev, id],
- );
- };
+ const totalPages = Math.ceil((items?.length || 0) / rowsPerPage);
- const paginatedData = data.slice(
- (currentPage - 1) * rowsPerPage,
- currentPage * rowsPerPage,
- );
+ // 선택된 항목을 다루는 함수
+ const handleSelect = (id: number) => {
+ setSelected(id); // 단일 선택으로 변경
+ };
const handleSelectAll = () => {
- const visibleIds = paginatedData.map((item) => item.id);
- setSelected((prev: string[]) =>
- prev.length === visibleIds.length ? [] : visibleIds,
- );
+ if (selected === paginatedData[0]?.itemId) {
+ setSelected(0); // 전체 선택 해제
+ } else {
+ setSelected(paginatedData[0]?.itemId); // 첫 번째 항목을 선택(전체 선택)
+ }
};
+ const paginatedData = items
+ ? items.slice((currentPage - 1) * rowsPerPage, currentPage * rowsPerPage)
+ : [];
+
return (
@@ -69,7 +50,7 @@ export default function ItemTable({
{showCheckboxes && (
@@ -82,55 +63,68 @@ export default function ItemTable({
- {paginatedData.map((item) => (
-
- {showCheckboxes && (
-
- handleSelect(item.id)}
+ {paginatedData.length > 0 ? (
+ paginatedData.map((item: Item) => (
+
+ {showCheckboxes && (
+
+ handleSelect(item.itemId)}
+ />
+
+ )}
+
+
- )}
- {item.logo}
- {item.name}
-
- {item.isConsumable ? '소모품' : '대여 물품'}
-
-
- {item.totalQuantity}
-
-
- {item.rentedQuantity}
-
- {headers.includes('관리자 여부') && (
- {item.isAdmin !== undefined && (item.isAdmin ? 'o' : 'x')}
+ {item.itemName}
+
+
+ {item.itemType ? 'RENTAL' : 'CONSUMPTION'}
+
+ {item.count}
+
+ {item.renterCount}
- )}
+
+ ))
+ ) : (
+
+
+ 데이터가 없습니다.
+
- ))}
+ )}
-
-
-
- {currentPage} / {Math.ceil(data.length / rowsPerPage)}
-
-
+
+
+
+
+ {totalPages > 0 ? `${currentPage} / ${totalPages}` : '0 / 0'}
+
+
+
);
diff --git a/src/app/desktop/item-list/page.tsx b/src/app/desktop/item-list/page.tsx
index 69f6e74..0d557fb 100644
--- a/src/app/desktop/item-list/page.tsx
+++ b/src/app/desktop/item-list/page.tsx
@@ -2,109 +2,113 @@
import Sidebar from 'src/components/desktop/Sidebar';
import Search from '@/components/desktop/Search';
-import { useState } from 'react';
+import { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { getItems, addItems, deleteItems } from '@/services/items';
import TableComponent from './_components/ItemTable';
-const dummyData = [
- {
- id: 1,
- name: '물품1',
- isConsumable: false,
- totalQuantity: 100,
- rentedQuantity: 30,
- logo: 'logo1.png',
- },
- {
- id: 2,
- name: '물품2',
- isConsumable: false,
- totalQuantity: 200,
- rentedQuantity: 50,
- logo: 'logo2.png',
- },
- {
- id: 3,
- name: '물품3',
- isConsumable: false,
- totalQuantity: 300,
- rentedQuantity: 70,
- logo: 'logo3.png',
- },
-];
-
-const dummyData2 = [
- {
- id: 4,
- name: '물품4',
- isConsumable: false,
- totalQuantity: 400,
- rentedQuantity: 90,
- logo: 'logo4.png',
- },
- {
- id: 5,
- name: '물품5',
- isConsumable: false,
- totalQuantity: 500,
- rentedQuantity: 120,
- logo: 'logo5.png',
- },
-];
-
-export default function PayerInquiryPage() {
- const [data, setData] = useState(dummyData);
- const [, setAddedData] = useState(dummyData2);
- const [isDeleteModeOriginal, setIsDeleteModeOriginal] = useState(false);
- const [selectedOriginal, setSelectedOriginal] = useState
([]);
-
- const [selectedImage, setSelectedImage] = useState(null);
- const [itemName, setItemName] = useState('');
- const [isConsumable, setIsConsumable] = useState(false);
- const [quantity, setQuantity] = useState('');
-
- const handleDeleteOriginal = () => {
- const hasRentedItems = data.some(
- (item) =>
- selectedOriginal.includes(String(item.id)) && item.rentedQuantity > 0,
- );
-
- if (hasRentedItems) {
- alert('대여 중인 물품은 삭제할 수 없습니다.');
- return;
- }
+export default function ItemListPage() {
+ const queryClient = useQueryClient();
- setData(data.filter((item) => !selectedOriginal.includes(String(item.id))));
- setIsDeleteModeOriginal(false);
- setSelectedOriginal([]);
- };
+ const [formData, setFormData] = useState({
+ selectedImage: null as File | null,
+ itemName: '',
+ isConsumable: false,
+ quantity: '' as number | '',
+ });
+
+ const [isDeleteMode, setIsDeleteMode] = useState(false);
+ const [selectedItem, setSelectedItem] = useState(null);
+
+ const mutation = useMutation({
+ mutationFn: addItems,
+ onSuccess: () => {
+ alert('물품 등록이 완료되었습니다.');
+ queryClient.invalidateQueries({ queryKey: ['items'] });
+ },
+ onError: () => {
+ alert('물품 등록에 실패했습니다.');
+ },
+ });
+
+ const deleteMutation = useMutation({
+ mutationFn: deleteItems,
+ onSuccess: () => {
+ alert('선택된 물품이 삭제되었습니다.');
+ },
+ onError: () => {
+ alert('물품 삭제에 실패했습니다.');
+ },
+ });
+
+ const {
+ data: originalData = [],
+ isError: originalDataError,
+ isLoading,
+ } = useQuery({
+ queryKey: ['items'],
+ queryFn: getItems,
+ });
+
+ // 물품 추가 핸들러
+ const handleAddItem = useCallback(() => {
+ const { itemName, quantity, selectedImage, isConsumable } = formData;
- const handleAddItem = () => {
if (!itemName || quantity === '' || quantity <= 0) {
alert('모든 정보를 입력하세요.');
return;
}
- const newItem = {
- id: Date.now(),
+ const newFormData = new FormData();
+ if (selectedImage) newFormData.append('image', selectedImage);
+
+ const itemData = {
name: itemName,
- isConsumable,
- totalQuantity: Number(quantity),
- rentedQuantity: 0,
- logo: selectedImage || 'default.png',
+ type: isConsumable ? 'CONSUMPTION' : 'RENTAL',
+ count: Number(quantity),
};
- setAddedData((prev) => [...prev, newItem]);
+ newFormData.append(
+ 'itemRequest',
+ new Blob([JSON.stringify(itemData)], { type: 'application/json' }),
+ );
- setItemName('');
- setIsConsumable(false);
- setQuantity('');
- setSelectedImage(null);
+ mutation.mutate(newFormData);
+
+ setFormData({
+ selectedImage: null,
+ itemName: '',
+ isConsumable: false,
+ quantity: '',
+ });
+ }, [formData, mutation]);
+
+ // 이미지 파일 선택 핸들러
+ const handleImageChange = (event: React.ChangeEvent) => {
+ const file = event.target.files?.[0];
+ if (file) {
+ setFormData((prev) => ({ ...prev, selectedImage: file }));
+ }
+ };
- alert('물품 등록을 완료하였습니다.');
+ // 삭제 모드 토글
+ const toggleDeleteMode = () => {
+ setIsDeleteMode((prev) => !prev);
+ setSelectedItem(null); // 삭제 모드에서 선택된 항목 초기화
};
- const isAddButtonDisabled = !itemName || quantity === '' || quantity <= 0;
+ // 물품 삭제 핸들러
+ const handleDeleteItem = () => {
+ if (selectedItem === null) {
+ alert('삭제할 물품을 선택해 주세요.');
+ return;
+ }
+
+ // @ts-ignore
+ deleteMutation.mutate([selectedItem]); // selectedItem을 배열로 전달
+ setSelectedItem(null); // 삭제 후 선택 초기화
+ };
return (
@@ -118,90 +122,70 @@ export default function PayerInquiryPage() {
title="복지 물품 추가하기"
description="설명"
>
-
-
복지물품 이모티콘
-
{
- if (e.target.files && e.target.files[0]) {
- setSelectedImage(URL.createObjectURL(e.target.files[0]));
- }
- }}
- className="hidden"
- id="imageUpload"
- />
-
-
-
-
@@ -211,24 +195,26 @@ export default function PayerInquiryPage() {
- {isDeleteModeOriginal && (
+ {isDeleteMode && (
diff --git a/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx b/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
index 315c100..01f7745 100644
--- a/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
+++ b/src/app/desktop/payer-inquiry/_components/TableComponent/index.tsx
@@ -10,7 +10,7 @@ import {
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
-import { Payer, TableComponentProps } from '@/types/payerInquiry';
+import { Payer, TableComponentProps } from '@/types/payers';
export default function TableComponent({
payers,
@@ -18,16 +18,15 @@ export default function TableComponent({
headers = ['이름', '학번', '회원 여부'], // 기본값을 설정
selected,
setSelected,
- handleDelete = () => {}, // 기본값으로 빈 함수 설정
}: TableComponentProps) {
const [currentPage, setCurrentPage] = useState(1);
const rowsPerPage = 10;
- const handleSelect = (student_id: string) => {
- setSelected((prev: string[]) =>
- prev.includes(student_id)
- ? prev.filter((id) => id !== student_id)
- : [...prev, student_id],
+ const handleSelect = (payerId: number) => {
+ setSelected((prev: number[]) =>
+ prev.includes(payerId)
+ ? prev.filter((id) => id !== payerId)
+ : [...prev, payerId],
);
};
@@ -38,10 +37,10 @@ export default function TableComponent({
const handleSelectAll = () => {
const visibleIds = paginatedData.map(
- (item: { studentId: string }) => item.studentId,
+ (item: { payerId: number }) => item.payerId,
);
- setSelected((prev: string[]) =>
- prev.length === visibleIds.length ? [] : visibleIds,
+ setSelected(
+ (prev: number[]) => (prev.length === visibleIds.length ? [] : visibleIds), // 배열 길이를 비교하는 대신 선택된 아이디와 비교
);
};
@@ -67,12 +66,12 @@ export default function TableComponent({
{paginatedData.map((item: Payer) => (
-
+
{showCheckboxes && (
handleSelect(item.studentId)}
+ checked={selected.includes(item.payerId)}
+ onCheckedChange={() => handleSelect(item.payerId)}
/>
)}
diff --git a/src/app/desktop/payer-inquiry/page.tsx b/src/app/desktop/payer-inquiry/page.tsx
index ae9ca1c..ef6cead 100644
--- a/src/app/desktop/payer-inquiry/page.tsx
+++ b/src/app/desktop/payer-inquiry/page.tsx
@@ -6,7 +6,7 @@ import { useState, useEffect } from 'react';
import AddStudentId from '@/components/desktop/AddStudentId';
import { Button } from '@/components/ui/button';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { getPayer, addPayer } from '@/services/payer-inquiry';
+import { getPayer, addPayer, deletePayer } from '@/services/payers';
import TableComponent from './_components/TableComponent';
import AddInput from '../../../components/desktop/AddInput';
@@ -15,8 +15,8 @@ export default function PayerInquiryPage() {
const [addedData, setAddedData] = useState([]);
const [isDeleteModeOriginal, setIsDeleteModeOriginal] = useState(false);
const [isDeleteModeAdded, setIsDeleteModeAdded] = useState(false);
- const [selectedOriginal, setSelectedOriginal] = useState([]);
- const [selectedAdded, setSelectedAdded] = useState([]);
+ const [selectedOriginal, setSelectedOriginal] = useState([]); // 수정: payerId 배열로
+ const [selectedAdded, setSelectedAdded] = useState([]); // 수정: payerId 배열로
const [newStudentId, setNewStudentId] = useState('');
const [newStudentName, setNewStudentName] = useState('');
@@ -32,6 +32,17 @@ export default function PayerInquiryPage() {
},
});
+ const deletemutation = useMutation({
+ mutationFn: deletePayer,
+ onSuccess: () => {
+ alert('선택된 납부자 정보가 성공적으로 삭제되었습니다.');
+ setAddedData([]);
+ },
+ onError: () => {
+ alert('납부자 정보 삭제에 실패했습니다.');
+ },
+ });
+
const {
data: originalData = [],
isError: originalDataError,
@@ -81,11 +92,14 @@ export default function PayerInquiryPage() {
const handleDeleteData = (mode: 'original' | 'added') => {
if (mode === 'original') {
+ // payerId 배열을 직접 전달
+ deletemutation.mutate(selectedOriginal); // payerId 배열을 전달
setSelectedOriginal([]);
setIsDeleteModeOriginal(false);
} else {
+ // addedData에서 삭제할 때도 payerId로 삭제
const updatedData = addedData.filter(
- (item) => !selectedAdded.includes(item.studentId),
+ (item) => !selectedAdded.includes(item.studentId), // studentId로 비교 후 삭제
);
setAddedData(updatedData);
setSelectedAdded([]);
diff --git a/src/services/items.ts b/src/services/items.ts
new file mode 100644
index 0000000..88ee8b3
--- /dev/null
+++ b/src/services/items.ts
@@ -0,0 +1,16 @@
+import PrivateAxiosInstance from './privateAxiosInstance';
+
+export const getItems = async () => {
+ const response = await PrivateAxiosInstance.get('/admin/items');
+ return response.data;
+};
+
+export const addItems = async (data: FormData) => {
+ const response = await PrivateAxiosInstance.post('/admin/items', data);
+ return response.data;
+};
+
+export const deleteItems = async (id: number) => {
+ const response = await PrivateAxiosInstance.delete(`/admin/items/${id}`);
+ return response.data;
+};
diff --git a/src/services/payer-inquiry.ts b/src/services/payers.ts
similarity index 66%
rename from src/services/payer-inquiry.ts
rename to src/services/payers.ts
index f4943de..6ebfe4e 100644
--- a/src/services/payer-inquiry.ts
+++ b/src/services/payers.ts
@@ -15,3 +15,10 @@ export const addPayer = async (data: {
);
return response.data;
};
+
+export const deletePayer = async (payerIds: number[]) => {
+ const response = await PrivateAxiosInstance.delete('/admin/members/payers', {
+ data: { payerIds }, // DELETE 요청의 본문에 payerIds를 포함
+ });
+ return response.data;
+};
diff --git a/src/services/privateAxiosInstance.ts b/src/services/privateAxiosInstance.ts
index f027b58..9fdbe73 100644
--- a/src/services/privateAxiosInstance.ts
+++ b/src/services/privateAxiosInstance.ts
@@ -3,7 +3,6 @@ import axios, { InternalAxiosRequestConfig } from 'axios';
const PrivateAxiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URI,
headers: {
- 'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.NEXT_PUBLIC_AUTH_TOKEN}`,
},
});
diff --git a/src/types/items.ts b/src/types/items.ts
new file mode 100644
index 0000000..be05be6
--- /dev/null
+++ b/src/types/items.ts
@@ -0,0 +1,17 @@
+export interface Item {
+ itemId: number;
+ itemName: string;
+ itemType: string;
+ count: number;
+ renterCount: number;
+ imageUrl: string;
+}
+
+export interface ItemTableProps {
+ items: Item[];
+ showCheckboxes?: boolean;
+ headers?: string[];
+ selected: number;
+ setSelected: (selectedIds: number) => void;
+ handleDelete?: (selectedIds: string) => void;
+}
diff --git a/src/types/payerInquiry.ts b/src/types/payers.ts
similarity index 75%
rename from src/types/payerInquiry.ts
rename to src/types/payers.ts
index e2fd88e..4cf5b26 100644
--- a/src/types/payerInquiry.ts
+++ b/src/types/payers.ts
@@ -9,7 +9,7 @@ export interface TableComponentProps {
payers: Payer[];
showCheckboxes?: boolean;
headers?: string[];
- selected: string[];
- setSelected: (selectedIds: (prev: string[]) => string[]) => void;
+ selected: number[];
+ setSelected: (selectedIds: (prev: number[]) => number[]) => void;
handleDelete?: (selectedIds: string[]) => void;
}