From bf4e38b00d069e9398b449cb6c78366351db7a66 Mon Sep 17 00:00:00 2001 From: HidenLee Date: Tue, 1 Oct 2024 15:33:06 +0900 Subject: [PATCH] feat: settlement logic changed. one schedule room would be able to have multiple settlementList --- travelday-fe/src/App.js | 2 +- .../{settlement.js => ExpenseSettlement.js} | 0 .../components/schedulePage/scheduleTab.jsx | 7 +- .../components/schedulePage/settlement.jsx | 415 ++++++++++++++++++ .../schedulePage/settlementList.jsx | 149 +++++++ .../pages/schedulePage/scheduleDetailPage.jsx | 2 +- 6 files changed, 570 insertions(+), 5 deletions(-) rename travelday-fe/src/components/schedulePage/{settlement.js => ExpenseSettlement.js} (100%) create mode 100644 travelday-fe/src/components/schedulePage/settlement.jsx create mode 100644 travelday-fe/src/components/schedulePage/settlementList.jsx diff --git a/travelday-fe/src/App.js b/travelday-fe/src/App.js index daafc7b..fbfb5aa 100644 --- a/travelday-fe/src/App.js +++ b/travelday-fe/src/App.js @@ -28,7 +28,7 @@ import PrivacyPage from './pages/mainPage/privacyPage'; import ChatPage from './pages/chatPage/chatPage'; import ChatTest from './pages/chatPage/chatTest'; import ChatListPage from './pages/chatPage/chatListPage'; -import ExpenseSettlement from './components/schedulePage/settlement'; +import ExpenseSettlement from "./components/schedulePage/settlement"; import "./firebase.js"; diff --git a/travelday-fe/src/components/schedulePage/settlement.js b/travelday-fe/src/components/schedulePage/ExpenseSettlement.js similarity index 100% rename from travelday-fe/src/components/schedulePage/settlement.js rename to travelday-fe/src/components/schedulePage/ExpenseSettlement.js diff --git a/travelday-fe/src/components/schedulePage/scheduleTab.jsx b/travelday-fe/src/components/schedulePage/scheduleTab.jsx index d3b1393..1a96935 100644 --- a/travelday-fe/src/components/schedulePage/scheduleTab.jsx +++ b/travelday-fe/src/components/schedulePage/scheduleTab.jsx @@ -4,7 +4,7 @@ import ScheduleDetailList from "./scheduleDetailList"; import StickyBox from 'react-sticky-box'; import styled from "styled-components"; import ChatPage from "../../pages/chatPage/chatPage"; -import Settlement from "./settlement"; +import SettlementList from "./settlementList"; const items = (travelRoomId,startDate,endDate) => [ { label:"일정 보기", @@ -25,7 +25,8 @@ const items = (travelRoomId,startDate,endDate) => [ { label:"정산 하기", key:"정산", - children: + children: + } ] console.table(items) @@ -83,4 +84,4 @@ const StyledTabs = styled(Tabs)` .ant-tabs-ink-bar { background: #000000; } -`; \ No newline at end of file +`; diff --git a/travelday-fe/src/components/schedulePage/settlement.jsx b/travelday-fe/src/components/schedulePage/settlement.jsx new file mode 100644 index 0000000..11a7398 --- /dev/null +++ b/travelday-fe/src/components/schedulePage/settlement.jsx @@ -0,0 +1,415 @@ +import styled from "styled-components"; +import React, {useEffect, useState} from "react"; +import axiosInstance from "../../utils/axiosInstance"; + +const Settlement = ({settlementId, postSettlementList, travelRoomId}) => { + const [settlementDetailList,setSettlementDetailList] = useState([]); + const [newRowName, setNewRowName] = useState(''); + const [newRowAmount, setNewRowAmount] = useState(''); + const [isEditingRowId, setIsEditingRowId] = useState(null); + const [errorMessage, setErrorMessage] = useState(''); + const [totalAmount, setTotalAmount] = useState(0); + + + /** 서버에서 정산 리스트를 받아옴 단계 2 */ + function fetchSettlementDetails(settlementId) { + if(settlementId === undefined || settlementId === null) { + return + } + axiosInstance.get(`/api/settlement/${travelRoomId}/${settlementId}/detail`, {}) + .then(response=>{ + setSettlementDetailList(response.data.data); + }) + .catch(error=>{ + console.error(error) + }) + } + + // 1차, 2차 정산 추가 + const addRow = () => { + if (newRowAmount <= 0 || newRowAmount > 5000000) { + setErrorMessage('0원 이하의 금액은 입력할 수 없습니다.'); + return; + } + + if (settlementDetailList.length >= 10) { + setErrorMessage('정산 항목은 10개까지 입력 할 수 있습니다.'); + return; + } + + axiosInstance.post(`/api/settlement/${travelRoomId}/${settlementId}`, { + name: newRowName, + amount: newRowAmount + }) + .then(response => { + fetchSettlementDetails(settlementId); + }) + .catch(error => console.log(error)) + } + + + const handleNameChange = (e) => { + const value = e.target.value; + + if (value.length > 10) { + setErrorMessage('정산 내역 이름은 최대 10글자까지만 입력 가능합니다.'); + setNewRowName(value.slice(0, 10)); // 최대 10글자까지만 허용 + } else { + setErrorMessage(''); + setNewRowName(value); + } + }; + + + const handleAmountChange = (e) => { + const value = e.target.value === '' ? '' : parseInt(e.target.value); + + if (value === '') { + setErrorMessage('금액을 입력해 주세요.'); + setNewRowAmount(0); // 빈 값일 때 0으로 설정 + } else if (value <= 0) { + setErrorMessage('0원 이하의 금액은 입력할 수 없습니다.'); + } else if (value > 5000000) { + setErrorMessage('최대 500만원까지만 입력할 수 있습니다.'); + } else { + setErrorMessage(''); + setNewRowAmount(value); + } + }; + + const deleteRow = (settlementDetailId) => { + axiosInstance.delete(`/api/settlement/${travelRoomId}/${settlementId}/${settlementDetailId}`, {}) + .then(response=>{fetchSettlementDetails(settlementId)}) + .catch(error=>console.log(error)); + }; + + const editRow = (settlementDetailId) => { + axiosInstance.patch(`/api/settlement/${travelRoomId}/${settlementId}/${settlementDetailId}`,{ name: newRowName, amount: parseInt(newRowAmount) }) + .then(response=>{fetchSettlementDetails(settlementId)}) + .catch(error=>console.log(error)); + setIsEditingRowId(null) + } + + useEffect(() => { + const calculatedTotalAmount = settlementDetailList.reduce((sum, row) => sum + row.amount, 0); + setTotalAmount(calculatedTotalAmount); + }, [settlementDetailList]); + + + + useEffect(()=>{ + if(settlementDetailList.length > 0){ + return + } + fetchSettlementDetails(settlementId); + },[settlementId]) + + + + return ( + + { + settlementDetailList.length === 0 && ( + 정산 내역이 없습니다! + ) + } + {settlementId === null ? ( + <> + 정산 시작하기 + + ): + + 총 정산 금액: {totalAmount.toLocaleString()}원 + {errorMessage && ( + + {errorMessage} + + )} + + + + + + 차수 추가 + + + + {settlementDetailList.map((row, index) => ( + + {isEditingRowId === row.id ? ( + <> + setNewRowName(e.target.value)} + placeholder="정산 내역 이름" + /> + + + + ) : ( + <> + {row.name}: {row.amount.toLocaleString()}원 + + + deleteRow(row.id)} + >삭제 + + + )} + + ))} + + + + } + + ) +}; + +const Container = styled.div` + width: inherit; + //background-color: #000dff; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + +`; + +const ContentBox = styled.div` + min-width: 250px; + min-height: 500px; + //border: 1px solid #000; + margin: 10px; + +// background-color: lightgray; +` + +const InputContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + margin-bottom: 20px; + position: relative; +`; + + +const StyledInput = styled.input` + padding: 12px; + border: 1px solid #ddd; + border-radius: 8px; + margin-bottom: 10px; + font-size: 16px; + background-color: #f8f8f8; + transition: border-color 0.2s; + &:focus { + outline: none; + } +`; + +const Input = styled.input` + padding: 10px; + width: 60px; + border: 1px solid #ddd; + border-radius: 8px; + font-size: 16px; + background-color: #f8f8f8; + transition: border-color 0.2s; + &:focus { + outline: none; + } +`; + +const HelperText = styled.p` + color: #f12e5e; + font-size: 12px; + position: absolute; + width: 100%; + text-align: center; + top: 60px; + left: 0; + opacity: 0; + transition: opacity 0.3s ease; +`; + + + + +const TotalAmount = styled.p` + font-size: 22px; + font-weight: bold; + color: #333; + margin-bottom: 40px; + text-align: center; +`; + +const NoDataText = styled.p` + font-size: 18px; + color: #999; + text-align: center; + margin-bottom: 20px; +`; + +const MainButton = styled.button` + padding: 15px; + background-color: #f12e5e; + color: white; + font-size: 18px; + border: none; + border-radius: 8px; + margin-top: 10px; + //width: 100%; + cursor: pointer; + transition: background-color 0.3s; + &:hover { + background-color: #d3204a; + } + &:disabled { + background-color: #f58b9f; + cursor: not-allowed; + } +`; + +const BackButton = styled.button` + padding: 15px; + background-color: gray; + color: white; + font-size: 18px; + border: none; + border-radius: 8px; + margin-top: 10px; + width: 100%; + cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background-color: #515151; + } + + &:disabled { + background-color: #f58b9f; + cursor: not-allowed; + } +` + +const ButtonGroup = styled.div` + display: flex; + gap: 10px; +`; + +const Button = styled.button` + padding: 10px; + background-color: #f12e5e; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s, opacity 0.3s; + + &:hover { + background-color: #d3204a; + } + + &:disabled { + background-color: #d3d3d3; + color: #a9a9a9; + cursor: not-allowed; + opacity: 0.7; + } +`; + +const DeleteButton = styled(Button)` + background-color: #ff5f5f; + &:hover { + background-color: #d94444; + } +`; + +const RoundList = styled.ul` + list-style-type: none; + padding: 0; +`; + +const RoundItem = styled.li` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + font-size: 16px; + padding: 12px; + //background-color: #f12525; + border-radius: 8px; +`; + +const RoundDetails = styled.span` + font-size: 16px; + font-weight: 500; +`; + +const PeopleList = styled.ul` + list-style-type: none; + padding: 0; + margin-top: 20px; +`; + +const PeopleInput = styled.input` + padding: 12px; + width: 100px; + border: 1px solid #ddd; + border-radius: 8px; + font-size: 16px; + background-color: #f8f8f8; + transition: border-color 0.2s; + margin-right: 5px; + &:focus { + outline: none; + } +`; + +const PeopleItem = styled.li` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + font-size: 16px; + padding: 12px; +`; + +const AmountWrapper = styled.div` + display: flex; + align-items: center; +`; + + +export default Settlement; diff --git a/travelday-fe/src/components/schedulePage/settlementList.jsx b/travelday-fe/src/components/schedulePage/settlementList.jsx new file mode 100644 index 0000000..a89d592 --- /dev/null +++ b/travelday-fe/src/components/schedulePage/settlementList.jsx @@ -0,0 +1,149 @@ +import React, {useState, useRef, useEffect} from 'react'; +// import ExpenseSettlement from "./expenseSettlement"; +import Settlement from "./settlement"; +import axiosInstance from "../../utils/axiosInstance"; +import styled from "styled-components"; + +const SLIDER_WIDTH = 370; // Adjust to your desired width +const SLIDER_CONTENT_WIDTH = 310; +const SLIDER_HEIGHT = "80vh"; // Adjust to your desired height +// const imageList = ['red', 'blue', 'green','yellow','purple']; // Example images + +const SettlementList = ({travelRoomId}) => { + const [currentIndex, setCurrentIndex] = useState(0); + const [transX, setTransX] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const startXRef = useRef(0); + const [fetchedList,setFetchedList] = useState([]); + const onMouseDown = (e) => { + setIsDragging(true); + startXRef.current = e.clientX; + }; + + const onMouseMove = (e) => { + if (!isDragging) return; + const diff = e.clientX - startXRef.current; + setTransX(diff); + }; + + const onMouseUp = () => { + if (isDragging) { + setIsDragging(false); + if (transX > 100 && currentIndex > 0) { + setCurrentIndex((prev) => prev - 1); + } else if (transX < -100 ) { + if (currentIndex === fetchedList.length - 1) { + setCurrentIndex(0); + } + else { + setCurrentIndex((prev) => prev + 1); + } + } + setTransX(0); // Reset the translation + } + + }; + + function fetchSettlementList() { + + axiosInstance.get(`/api/settlement/${travelRoomId}`, { + }) + .then(response => { + console.table(response.data.data); + if (response.data.data.length > 0) { + const settlementList = [ + ...response.data.data, + { + id: null, + status: null, + totalAmount: null, + settledAt: null, + isLastPage: true, + } + ]; + setFetchedList(settlementList); + } + else { + setFetchedList([ + { + id: null, + status: null, + totalAmount: null, + settledAt: null, + isLastPage: true, + } + ]); + } + }) + .catch(error => { + console.error('정산 리스트 조회 중 오류 발생:', error); + + }); + } + + function postSettlementList() { + axiosInstance.post(`/api/settlement/${travelRoomId}`, { + }) + .then(response => { + console.table(response.data.data); + fetchSettlementList() + }) + .catch(error => { + console.error('정산 리스트 생성 중 오류 발생:', error); + }); + } + + function fetchSettlementDetails(settlementId) { + axiosInstance.get(`/api/settlement/${travelRoomId}`, {}) + } + + useEffect(() => { + fetchSettlementList(); + },[]) + + return ( +
+ {/* Slider */} +
+ {/* Slide */} + {fetchedList.map((i, index) => ( +
+
+ +
+
+ ))} +
+
+ ); +}; + +export default SettlementList; + diff --git a/travelday-fe/src/pages/schedulePage/scheduleDetailPage.jsx b/travelday-fe/src/pages/schedulePage/scheduleDetailPage.jsx index 450f3af..08e4a11 100644 --- a/travelday-fe/src/pages/schedulePage/scheduleDetailPage.jsx +++ b/travelday-fe/src/pages/schedulePage/scheduleDetailPage.jsx @@ -303,7 +303,7 @@ const ContentContainer = styled.div` display: flex; flex-direction: column; align-items: center; - padding-bottom: 20vh; + padding-bottom: 150px; `; const MapContainer = styled.div`