From e7489b376c314b2f43d5a4282d542d4ff7a7f6b9 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 03:36:29 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/types/auth.ts | 0 frontend/src/types/common.ts | 6 ------ 2 files changed, 6 deletions(-) delete mode 100644 frontend/src/types/auth.ts delete mode 100644 frontend/src/types/common.ts diff --git a/frontend/src/types/auth.ts b/frontend/src/types/auth.ts deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts deleted file mode 100644 index 76b2b14..0000000 --- a/frontend/src/types/common.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type CommonResponse = { - status:boolean; - statusCode:number; - message:string; - data: T; -} \ No newline at end of file From 4664a92ef63c56a619e1e93fcc2e75dae66476db Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 03:37:03 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=EC=98=81=EC=88=98=EC=A6=9D=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ReceiptDetail.tsx | 115 ++++++++++++++++++++++ frontend/src/types/receipt.ts | 9 ++ 2 files changed, 124 insertions(+) create mode 100644 frontend/src/components/ReceiptDetail.tsx create mode 100644 frontend/src/types/receipt.ts diff --git a/frontend/src/components/ReceiptDetail.tsx b/frontend/src/components/ReceiptDetail.tsx new file mode 100644 index 0000000..b286a32 --- /dev/null +++ b/frontend/src/components/ReceiptDetail.tsx @@ -0,0 +1,115 @@ +import React, { useState } from 'react'; +import type { Receipt } from '../types/receipt'; // types/receipt 파일 경로 확인 필요 + +interface ReceiptDetailProps { + receiptData: Receipt[]; // 영수증 품목 데이터 배열 +} + +const ReceiptDetail: React.FC = ({ receiptData }) => { + // 영수증 데이터가 없거나 비어있으면 처리 + if (!receiptData || receiptData.length === 0) { + return
영수증 내역이 없습니다.
; + } + + // 상호명은 첫 번째 품목 데이터에서 가져옴 + const storeName = receiptData[0].store_name; + + // 각 품목별 참여자를 관리하기 위한 상태 (품목 index -> 참여자 이름 배열) + // 초기 상태는 각 품목에 대해 빈 참여자 배열로 설정 + const [itemParticipants, setItemParticipants] = useState( + receiptData.map(() => []) + ); + + // 특정 품목에 참여자 태그 추가 핸들러 + const handleAddParticipant = (itemIndex: number, participantName: string) => { + if (participantName.trim() === '') return; + + setItemParticipants(prevParticipants => { + const newParticipants = [...prevParticipants]; + // 해당 품목의 참여자 목록에 추가 (중복 방지) + if (!newParticipants[itemIndex].includes(participantName.trim())) { + newParticipants[itemIndex] = [...newParticipants[itemIndex], participantName.trim()]; + } + return newParticipants; + }); + }; + + // 특정 품목의 참여자 태그 삭제 핸들러 + const handleRemoveParticipant = (itemIndex: number, participantToRemove: string) => { + setItemParticipants(prevParticipants => { + const newParticipants = [...prevParticipants]; + newParticipants[itemIndex] = newParticipants[itemIndex].filter( + participant => participant !== participantToRemove + ); + return newParticipants; + }); + }; + + + return ( +
{/* 전체 컨테이너 너비 꽉 채우기 */} + {/* 상호명 및 날짜 */} +

{storeName}

+ + {/* 영수증 품목 테이블 */} + {/* 테이블 너비 꽉 채우기, 테두리 병합 */} + {/* 헤더 텍스트 좌측 정렬 */} + + {/* 품목 열 너비 설정 */} + {/* 수량 열 너비 설정 */} + {/* 금액 열 너비 설정 */} + + + + + {receiptData.map((item, index) => ( + + + + + + + + {itemParticipants[index].length > 0 && ( + + + + )} + + ))} + +
품목수량금액참여자
{item.item_name}{item.quantity}{item.total_amount} {/* 상대 위치 설정 */} + { + if (e.key === 'Enter') { + const inputElement = e.target as HTMLInputElement; + handleAddParticipant(index, inputElement.value); + inputElement.value = ''; + } + }} + /> +
+
+ {itemParticipants[index].map((participant, pIndex) => ( + + {participant} + + + ))} +
+
+
+ ); +}; + +export default ReceiptDetail; \ No newline at end of file diff --git a/frontend/src/types/receipt.ts b/frontend/src/types/receipt.ts new file mode 100644 index 0000000..3269462 --- /dev/null +++ b/frontend/src/types/receipt.ts @@ -0,0 +1,9 @@ +export type Receipt = { + id: number; + store_name: string; + item_name: string; + quantity: number; + unit_price: number; + total_amount: number; + receipt: number; +}; \ No newline at end of file From 70f97e2930b3b24bbf0fa6fd8d616591e3cb4825 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 03:37:36 +0900 Subject: [PATCH 03/13] =?UTF-8?q?navigation=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/layouts/Layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/layouts/Layout.tsx b/frontend/src/layouts/Layout.tsx index d216d3d..f70e920 100644 --- a/frontend/src/layouts/Layout.tsx +++ b/frontend/src/layouts/Layout.tsx @@ -23,10 +23,10 @@ const Layout = ({ children }: LayoutProps) => {
From 2b9fbe7898d22cfb3303ec5d2fab7452a61198c1 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 03:38:20 +0900 Subject: [PATCH 04/13] =?UTF-8?q?CheckPage=20UI=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=B0=EC=A0=9C=20=EB=82=B4=EC=97=AD=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/CheckPage.tsx | 112 ++++++++++++++++++++++++++++++- frontend/src/pages/MainPage.tsx | 1 - 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/CheckPage.tsx b/frontend/src/pages/CheckPage.tsx index 682e551..9543231 100644 --- a/frontend/src/pages/CheckPage.tsx +++ b/frontend/src/pages/CheckPage.tsx @@ -1,5 +1,115 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import ReceiptDetail from '../components/ReceiptDetail'; +import { axiosInstance } from '../apis/axios'; +import type { Receipt } from '../types/receipt'; + const CheckPage = () => { - return
+ const [rawReceiptItems, setRawReceiptItems] = useState([]); + const [groupedReceipts, setGroupedReceipts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [totalAmount, setTotalAmount] = useState(0); + + const navigate = useNavigate(); + + useEffect(() => { + const fetchReceipts = async () => { + try { + const response = await axiosInstance.get<{ success: boolean; results: Receipt[] }>('api/receiptinfo/analyze/'); + + const items = response.data.results; + + setRawReceiptItems(items); + console.log('API 응답 데이터:', response.data); + + const groupedData = groupReceiptItemsByReceiptId(items); + setGroupedReceipts(groupedData); + + const calculatedTotal = items.reduce((sum, item) => sum + item.total_amount, 0); + setTotalAmount(calculatedTotal); + + } catch (err: any) { + console.error('영수증 데이터 불러오기 실패:', err); + setError('영수증 데이터를 불러오는데 실패했습니다.'); + } finally { + setLoading(false); + } + }; + + fetchReceipts(); + }, []); + + const groupReceiptItemsByReceiptId = (items: Receipt[]): Receipt[][] => { + const receiptsMap = new Map(); + + items.forEach(item => { + if (!receiptsMap.has(item.receipt)) { + receiptsMap.set(item.receipt, []); + } + receiptsMap.get(item.receipt)!.push(item); + }); + + return Array.from(receiptsMap.values()); + }; + + if (loading) { + return
로딩 중...
; + } + + if (error) { + return
오류: {error}
; + } + + if (groupedReceipts.length === 0 && rawReceiptItems.length === 0) { + return
결제 내역이 없습니다.
; + } + + if (groupedReceipts.length === 0 && rawReceiptItems.length > 0) { + return
데이터는 있지만, 처리 중 문제가 발생했습니다.
; + } + + return ( +
+
+
+

결제 내역

+ +
+ {groupedReceipts.map((receiptItems, index) => ( + + ))} +
+ +

총액 {totalAmount}원

+
+ +
+

정산 결과

+ +
+ +
+
+ + + +
+ + +
+
+
+ ); }; export default CheckPage; \ No newline at end of file diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index 92f50e2..c5e3281 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -167,7 +167,6 @@ const MainPage = () => { className="ml-2 text-lg font-bold focus:outline-none flex items-center justify-center h-full text-[#0069CD] cursor-pointer" onClick={() => handleRemove(name)} aria-label="참여자 삭제" - type="button" > × From 61dd22854cead760bc100b740facb55d593d9775 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 04:00:07 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apis/auth.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frontend/src/apis/auth.ts diff --git a/frontend/src/apis/auth.ts b/frontend/src/apis/auth.ts deleted file mode 100644 index e69de29..0000000 From c3d2821a710d656253f97862f6d1ae4547194047 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 04:00:48 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/MainPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index c5e3281..bec35f1 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -160,13 +160,14 @@ const MainPage = () => { {participants.map(name => (
{name} From 1ef3a31da0b0cedb5b7d7cbf6d678efd6e540599 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Wed, 4 Jun 2025 04:01:21 +0900 Subject: [PATCH 07/13] =?UTF-8?q?LandingPage=20=EB=94=94=EC=9E=90=EC=9D=B8?= =?UTF-8?q?=20=EC=82=AC=EC=9D=B4=EC=A6=88=20=EC=A1=B0=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/LandingPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index 1a07c43..3f2fa8b 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -9,10 +9,10 @@ const LandingPage = () => { return (
-
- 나만의 회계 비서, +
+ 나만의 회계 비서, - logo + logo
- -
- - -
{/* 메인 컨텐츠 */}
{children}
From c9495f9e6694d2265fa702a2e3ffb0fb4e7cb8bf Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Thu, 5 Jun 2025 11:06:34 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 2 +- frontend/public/icon.svg | 10 +++++----- frontend/public/icon2.svg | 9 --------- frontend/public/icon3.svg | 9 --------- frontend/public/navigate_left.svg | 3 --- frontend/public/navigate_right.svg | 3 --- frontend/public/receipt.svg | 7 ------- 7 files changed, 6 insertions(+), 37 deletions(-) delete mode 100644 frontend/public/icon2.svg delete mode 100644 frontend/public/icon3.svg delete mode 100644 frontend/public/navigate_left.svg delete mode 100644 frontend/public/navigate_right.svg delete mode 100644 frontend/public/receipt.svg diff --git a/frontend/index.html b/frontend/index.html index 4d1b104..dc856dc 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + 나만의 회계 비서, PayCheck diff --git a/frontend/public/icon.svg b/frontend/public/icon.svg index 18e25f3..a60e494 100644 --- a/frontend/public/icon.svg +++ b/frontend/public/icon.svg @@ -1,9 +1,9 @@ - - - - - + + + + + diff --git a/frontend/public/icon2.svg b/frontend/public/icon2.svg deleted file mode 100644 index 872cbd3..0000000 --- a/frontend/public/icon2.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/frontend/public/icon3.svg b/frontend/public/icon3.svg deleted file mode 100644 index a60e494..0000000 --- a/frontend/public/icon3.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/frontend/public/navigate_left.svg b/frontend/public/navigate_left.svg deleted file mode 100644 index de4b7d3..0000000 --- a/frontend/public/navigate_left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/public/navigate_right.svg b/frontend/public/navigate_right.svg deleted file mode 100644 index cb44808..0000000 --- a/frontend/public/navigate_right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/public/receipt.svg b/frontend/public/receipt.svg deleted file mode 100644 index 44fc08d..0000000 --- a/frontend/public/receipt.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - From a4536349ff441665c64921a07095f2bdfd7b6724 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Fri, 6 Jun 2025 13:42:42 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=EC=B0=B8=EC=97=AC=EC=9E=90=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ReceiptDetail.tsx | 57 +++++++++++++++++++++-- frontend/src/pages/CheckPage.tsx | 20 +++++++- frontend/src/pages/MainPage.tsx | 2 +- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ReceiptDetail.tsx b/frontend/src/components/ReceiptDetail.tsx index b286a32..541be4b 100644 --- a/frontend/src/components/ReceiptDetail.tsx +++ b/frontend/src/components/ReceiptDetail.tsx @@ -3,9 +3,10 @@ import type { Receipt } from '../types/receipt'; // types/receipt 파일 경로 interface ReceiptDetailProps { receiptData: Receipt[]; // 영수증 품목 데이터 배열 + allowedParticipants: string[]; // 추가: 허용된 참여자 명단 } -const ReceiptDetail: React.FC = ({ receiptData }) => { +const ReceiptDetail: React.FC = ({ receiptData, allowedParticipants }) => { // 영수증 데이터가 없거나 비어있으면 처리 if (!receiptData || receiptData.length === 0) { return
영수증 내역이 없습니다.
; @@ -15,20 +16,52 @@ const ReceiptDetail: React.FC = ({ receiptData }) => { const storeName = receiptData[0].store_name; // 각 품목별 참여자를 관리하기 위한 상태 (품목 index -> 참여자 이름 배열) - // 초기 상태는 각 품목에 대해 빈 참여자 배열로 설정 const [itemParticipants, setItemParticipants] = useState( receiptData.map(() => []) ); + // 각 품목별 오류 메시지를 관리하기 위한 상태 (품목 index -> 오류 메시지 문자열) + const [itemErrors, setItemErrors] = useState( + receiptData.map(() => '') + ); + // 특정 품목에 참여자 태그 추가 핸들러 const handleAddParticipant = (itemIndex: number, participantName: string) => { - if (participantName.trim() === '') return; + const trimmedName = participantName.trim(); + + // 입력이 비어있으면 오류 메시지 제거 후 종료 + if (trimmedName === '') { + setItemErrors(prevErrors => { + const newErrors = [...prevErrors]; + newErrors[itemIndex] = ''; + return newErrors; + }); + return; + } + + // 허용된 참여자 명단에 있는지 확인 + if (!allowedParticipants.includes(trimmedName)) { + // 명단에 없으면 오류 메시지 설정 + setItemErrors(prevErrors => { + const newErrors = [...prevErrors]; + newErrors[itemIndex] = '리스트에 있는 이름이 아닙니다'; + return newErrors; + }); + return; // 참여자 추가 방지 + } + + // 명단에 있으면 오류 메시지 제거 및 참여자 추가 + setItemErrors(prevErrors => { + const newErrors = [...prevErrors]; + newErrors[itemIndex] = ''; // 성공 시 오류 메시지 초기화 + return newErrors; + }); setItemParticipants(prevParticipants => { const newParticipants = [...prevParticipants]; // 해당 품목의 참여자 목록에 추가 (중복 방지) - if (!newParticipants[itemIndex].includes(participantName.trim())) { - newParticipants[itemIndex] = [...newParticipants[itemIndex], participantName.trim()]; + if (!newParticipants[itemIndex].includes(trimmedName)) { + newParticipants[itemIndex] = [...newParticipants[itemIndex], trimmedName]; } return newParticipants; }); @@ -45,6 +78,14 @@ const ReceiptDetail: React.FC = ({ receiptData }) => { }); }; + // 입력 필드 내용 변경 시 해당 품목의 오류 메시지 제거 + const handleInputChange = (itemIndex: number) => { + setItemErrors(prevErrors => { + const newErrors = [...prevErrors]; + newErrors[itemIndex] = ''; + return newErrors; + }); + }; return (
{/* 전체 컨테이너 너비 꽉 채우기 */} @@ -69,6 +110,10 @@ const ReceiptDetail: React.FC = ({ receiptData }) => { {item.quantity} {item.total_amount} {/* 상대 위치 설정 */} + {/* 오류 메시지 표시 */} + {itemErrors[index] && ( +

{itemErrors[index]}

+ )} = ({ receiptData }) => { if (e.key === 'Enter') { const inputElement = e.target as HTMLInputElement; handleAddParticipant(index, inputElement.value); + // 참여자 추가 후 입력 필드 초기화 inputElement.value = ''; } }} + onChange={() => handleInputChange(index)} // 입력 변경 시 오류 메시지 제거 /> diff --git a/frontend/src/pages/CheckPage.tsx b/frontend/src/pages/CheckPage.tsx index 9543231..e89a41f 100644 --- a/frontend/src/pages/CheckPage.tsx +++ b/frontend/src/pages/CheckPage.tsx @@ -10,6 +10,7 @@ const CheckPage = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [totalAmount, setTotalAmount] = useState(0); + const [allowedParticipants, setAllowedParticipants] = useState([]); const navigate = useNavigate(); @@ -28,6 +29,23 @@ const CheckPage = () => { const calculatedTotal = items.reduce((sum, item) => sum + item.total_amount, 0); setTotalAmount(calculatedTotal); + + const fetchParticipants = async () => { + try { + const participantResponse = await axiosInstance.get<{ success: boolean; message: string; data: { id: number; name: string }[] }>('api/participant/members/'); + console.log('GET 요청 응답 데이터:', participantResponse.data); + if (participantResponse.data && Array.isArray(participantResponse.data.data)) { + setAllowedParticipants(participantResponse.data.data.map(member => member.name)); + } else { + console.error('참여자 명단 데이터 형식이 예상과 다릅니다.', participantResponse.data); + setAllowedParticipants([]); + } + } catch (participantError: any) { + console.error('GET 요청 실패:', participantError); + } + }; + + fetchParticipants(); } catch (err: any) { console.error('영수증 데이터 불러오기 실패:', err); @@ -77,7 +95,7 @@ const CheckPage = () => {
{groupedReceipts.map((receiptItems, index) => ( - + ))}
diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index bec35f1..42eab74 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -79,7 +79,7 @@ const MainPage = () => { }; // '/api/participant/' 엔드포인트로 JSON 형태의 본문 전송 - await axiosInstance.post('api/participant/', participantBody, { + await axiosInstance.post('api/participant/join/', participantBody, { headers: { 'Content-Type': 'application/json', } From e340222c4555817f730bd950fa28662e98c8e7b4 Mon Sep 17 00:00:00 2001 From: SeungyeonHa <0727ha@naver.com> Date: Fri, 6 Jun 2025 13:59:12 +0900 Subject: [PATCH 11/13] =?UTF-8?q?1/n=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ReceiptDetail.tsx | 18 ++++++++++++++---- frontend/src/pages/CheckPage.tsx | 5 ++++- frontend/src/pages/MainPage.tsx | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ReceiptDetail.tsx b/frontend/src/components/ReceiptDetail.tsx index 541be4b..c52a291 100644 --- a/frontend/src/components/ReceiptDetail.tsx +++ b/frontend/src/components/ReceiptDetail.tsx @@ -1,12 +1,13 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import type { Receipt } from '../types/receipt'; // types/receipt 파일 경로 확인 필요 interface ReceiptDetailProps { receiptData: Receipt[]; // 영수증 품목 데이터 배열 allowedParticipants: string[]; // 추가: 허용된 참여자 명단 + settleType: 'even' | 'item'; // 추가: 정산 방식 } -const ReceiptDetail: React.FC = ({ receiptData, allowedParticipants }) => { +const ReceiptDetail: React.FC = ({ receiptData, allowedParticipants, settleType }) => { // 영수증 데이터가 없거나 비어있으면 처리 if (!receiptData || receiptData.length === 0) { return
영수증 내역이 없습니다.
; @@ -17,9 +18,18 @@ const ReceiptDetail: React.FC = ({ receiptData, allowedParti // 각 품목별 참여자를 관리하기 위한 상태 (품목 index -> 참여자 이름 배열) const [itemParticipants, setItemParticipants] = useState( - receiptData.map(() => []) + receiptData.map(() => settleType === 'even' ? allowedParticipants : []) ); + // settleType 또는 allowedParticipants가 변경될 때 itemParticipants 상태를 업데이트 + useEffect(() => { + if (settleType === 'even') { + setItemParticipants(receiptData.map(() => allowedParticipants)); + } else { + setItemParticipants(receiptData.map(() => [])); + } + }, [settleType, allowedParticipants, receiptData]); // 의존성 배열에 settleType, allowedParticipants, receiptData 추가 + // 각 품목별 오류 메시지를 관리하기 위한 상태 (품목 index -> 오류 메시지 문자열) const [itemErrors, setItemErrors] = useState( receiptData.map(() => '') @@ -109,7 +119,7 @@ const ReceiptDetail: React.FC = ({ receiptData, allowedParti {item.item_name} {item.quantity} {item.total_amount} - {/* 상대 위치 설정 */} + {/* 오류 메시지 표시 */} {itemErrors[index] && (

{itemErrors[index]}

diff --git a/frontend/src/pages/CheckPage.tsx b/frontend/src/pages/CheckPage.tsx index e89a41f..289a1ff 100644 --- a/frontend/src/pages/CheckPage.tsx +++ b/frontend/src/pages/CheckPage.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import ReceiptDetail from '../components/ReceiptDetail'; import { axiosInstance } from '../apis/axios'; import type { Receipt } from '../types/receipt'; +import { useLocation } from 'react-router-dom'; const CheckPage = () => { const [rawReceiptItems, setRawReceiptItems] = useState([]); @@ -13,6 +14,8 @@ const CheckPage = () => { const [allowedParticipants, setAllowedParticipants] = useState([]); const navigate = useNavigate(); + const location = useLocation(); + const { settleType } = location.state as { settleType: 'even' | 'item' } || { settleType: 'even' }; // state에서 settleType 가져오기 (기본값 'even') useEffect(() => { const fetchReceipts = async () => { @@ -95,7 +98,7 @@ const CheckPage = () => {
{groupedReceipts.map((receiptItems, index) => ( - + ))}
diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index 42eab74..a4a75fe 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -87,7 +87,7 @@ const MainPage = () => { } // 업로드 성공 시 페이지 이동 - navigate('/check'); + navigate('/check', { state: { settleType } }); } catch (error: any) { // 에러 타입 명시 console.error('업로드 및 참여자 명단 전송 실패:', error); alert('업로드 및 참여자 명단 전송에 실패했습니다.\n' + (error.response?.data?.detail || error.message)); // 상세 에러 메시지 포함 From 70511361bb6500789ec8d5573094d98bebb0c363 Mon Sep 17 00:00:00 2001 From: April-37 <150885908+April-37@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:28:34 +0900 Subject: [PATCH 12/13] feat: kakao share button --- src/components/KakaoShareBtn.tsx | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/components/KakaoShareBtn.tsx diff --git a/src/components/KakaoShareBtn.tsx b/src/components/KakaoShareBtn.tsx new file mode 100644 index 0000000..062e056 --- /dev/null +++ b/src/components/KakaoShareBtn.tsx @@ -0,0 +1,72 @@ +import { useEffect } from 'react'; + +declare global { + interface Window { + Kakao: any; + } +} + +interface KakaoShareButtonProps { + title: string; + description: string; + imageUrl: string; + linkUrl: string; +} + +export default function KakaoShareBtn({ + title, + description, + imageUrl, + linkUrl, +}: KakaoShareButtonProps) { + useEffect(() => { + // Kakao SDK 스크립트 추가 + if (!window.Kakao && !document.getElementById('kakao-sdk')) { + const script = document.createElement('script'); + script.id = 'kakao-sdk'; + script.src = 'https://developers.kakao.com/sdk/js/kakao.js'; + script.onload = () => { + window.Kakao.init('c4913a27ee144670505405de9ee16631'); // 카카오 JavaScript 키 + console.log('Kakao SDK initialized:', window.Kakao.isInitialized()); + }; + document.head.appendChild(script); + } else if (window.Kakao && !window.Kakao.isInitialized()) { + window.Kakao.init('c4913a27ee144670505405de9ee16631'); // 이미 로드된 경우 초기화만 + } + }, []); + + const shareToKakao = () => { + if (!window.Kakao) return; + + window.Kakao.Link.sendDefault({ + objectType: 'feed', + content: { + title, + description, + imageUrl, + link: { + mobileWebUrl: linkUrl, + webUrl: linkUrl, + }, + }, + buttons: [ + { + title: '웹으로 보기', + link: { + mobileWebUrl: linkUrl, + webUrl: linkUrl, + }, + }, + ], + }); + }; + + return ( + + ); +} From f1ada2f4888617b1061565028de58abe8af65028 Mon Sep 17 00:00:00 2001 From: April-37 <150885908+April-37@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:32:45 +0900 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20checkpage=20=EA=B3=B5=EC=9C=A0?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/KakaoShareBtn.tsx | 2 +- src/pages/CheckPage.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/KakaoShareBtn.tsx b/src/components/KakaoShareBtn.tsx index 062e056..de6fd16 100644 --- a/src/components/KakaoShareBtn.tsx +++ b/src/components/KakaoShareBtn.tsx @@ -63,7 +63,7 @@ export default function KakaoShareBtn({ return ( - +