diff --git a/src/app/home/home.js b/src/app/home/home.js index ebb4b92..68457f8 100644 --- a/src/app/home/home.js +++ b/src/app/home/home.js @@ -40,6 +40,7 @@ export default function Home() { const fetchProfile = async () => { try { + console.log(localStorage.getItem("accessToken")) const res = await axiosWithAuthorization.get("/members/me"); console.log(res) setProfile({ diff --git a/src/app/message/message.js b/src/app/message/message.js index 8926c40..df5560c 100644 --- a/src/app/message/message.js +++ b/src/app/message/message.js @@ -49,7 +49,7 @@ export default function Message({}) { } }; - // 개선된 메시지 전송(API: /feedbacks/sent) 요청 핸들러 + const handleSendFeedback = async () => { if (!gptMessage.trim()) { showAlert("error", "먼저 개선된 메시지를 작성해주세요."); @@ -62,7 +62,7 @@ export default function Message({}) { message: gptMessage, }); showAlert("success", "피드백 전송 완료"); - // 전송 후 이전 페이지로 이동하거나 원하는 동작을 수행 + router.back(); } catch (error) { showAlert( diff --git a/src/app/mypage/leader_modal.js b/src/app/mypage/leader_modal.js index 4439b2f..b762f12 100644 --- a/src/app/mypage/leader_modal.js +++ b/src/app/mypage/leader_modal.js @@ -1,9 +1,12 @@ -import styled from "styled-components"; -import * as m from '../../components/modal_s'; -import { IoClose } from 'react-icons/io5'; +"use client"; +import styled from "styled-components"; +import * as m from "../../components/modal_s"; +import { IoClose } from "react-icons/io5"; import Image from "next/image"; import { useEffect, useState, useRef } from "react"; +import axiosWithAuthorization from "@/context/axiosWithAuthorization"; +import { useAlert } from "@/context/AlertContext"; const Header = styled.div` display: flex; @@ -58,13 +61,6 @@ const MemberInfo = styled.div` flex-grow: 1; `; -const ProfileImage = styled(Image)` - width: 35px; - height: 35px; - border-radius: 50%; - object-fit: cover; -`; - const RadioButton = styled.input` transform: scale(1.2); cursor: pointer; @@ -82,151 +78,99 @@ const ButtonGroup = styled.div` gap: 20px; `; -const projectUserData = { - "success": true, - "status": 200, - "data": { - "projectParticipantId": 4, - "projectNickname": "최현태", - "profileImageUrl": "https://k.kakaocdn.net/dn/ceTrU6/btsL0V0mhKO/DAXjn1URCKkIOTBGqAZKAK/img_110x110.jpg", - "role": "ADMIN" - }, - "timestamp": "2025-03-19T21:42:40.59254" -} - -const projectMemberData = [ - { - "success": true, - "status": 200, - "data": { - "content": [ - { - "projectParticipantId": 1, - "projectNickname": "최현태", - "profileImageUrl": "https://k.kakaocdn.net/dn/ceTrU6/btsL0V0mhKO/DAXjn1URCKkIOTBGqAZKAK/img_110x110.jpg", - "role": "ADMIN" - }, - { - "projectParticipantId": 2, - "projectNickname": "최현태", - "profileImageUrl": "https://lh3.googleusercontent.com/a/ACg8ocIby_kbsDmHckQur6UKlkn1a4Ul89JdAf82TvYSGwehu-oVRA=s96-c", - "role": "MEMBER" - }, - { - "projectParticipantId": 3, - "projectNickname": "최현태", - "profileImageUrl": "https://lh3.googleusercontent.com/a/ACg8ocI6E5Q0hhiXNht5-76cyGpZNLbaO21GIgkmyF43ywYhSqTmZg=s96-c", - "role": "MEMBER" - }, - { - "projectParticipantId": 4, - "projectNickname": "최현태", - "profileImageUrl": "https://lh3.googleusercontent.com/a/ACg8ocI6E5Q0hhiXNht5-76cyGpZNLbaO21GIgkmyF43ywYhSqTmZg=s96-c", - "role": "MEMBER" - } - ], - "pageable": { - "pageNumber": 0, - "pageSize": 3, - "sort": [], - "offset": 0, - "paged": true, - "unpaged": false - }, - "first": true, - "last": true, - "size": 3, - "number": 0, - "sort": [], - "numberOfElements": 3, - "empty": false - }, - "timestamp": "2025-03-19T21:49:27.631511" - }, - { - "success": true, - "status": 200, - "data": { - "content": [ - { - "projectParticipantId": 5, - "projectNickname": "최현태", - "profileImageUrl": "https://k.kakaocdn.net/dn/ceTrU6/btsL0V0mhKO/DAXjn1URCKkIOTBGqAZKAK/img_110x110.jpg", - "role": "ADMIN" - }, - { - "projectParticipantId": 6, - "projectNickname": "최현태", - "profileImageUrl": "https://lh3.googleusercontent.com/a/ACg8ocIby_kbsDmHckQur6UKlkn1a4Ul89JdAf82TvYSGwehu-oVRA=s96-c", - "role": "MEMBER" - }, - { - "projectParticipantId": 7, - "projectNickname": "최현태", - "profileImageUrl": "https://lh3.googleusercontent.com/a/ACg8ocI6E5Q0hhiXNht5-76cyGpZNLbaO21GIgkmyF43ywYhSqTmZg=s96-c", - "role": "MEMBER" - }, - { - "projectParticipantId": 8, - "projectNickname": "최현태", - "profileImageUrl": "https://lh3.googleusercontent.com/a/ACg8ocI6E5Q0hhiXNht5-76cyGpZNLbaO21GIgkmyF43ywYhSqTmZg=s96-c", - "role": "MEMBER" - } - ], - "pageable": { - "pageNumber": 0, - "pageSize": 3, - "sort": [], - "offset": 0, - "paged": true, - "unpaged": false - }, - "first": true, - "last": true, - "size": 3, - "number": 0, - "sort": [], - "numberOfElements": 3, - "empty": false - }, - "timestamp": "2025-03-19T21:49:27.631511" - } -] -export default function LeaderModal({ isOpen, onClose, }) { - // <----------------------------------API 연결시 필요하면 수정 --------------------------------------> - // <--------------------------------------------여기 아래부터 시작------------------------------------> +export default function LeaderModal({ isOpen, onClose, projectId, currentMemberId, onAdminChanged }) { + const { showAlert } = useAlert(); const [selectedMember, setSelectedMember] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [members, setMembers] = useState([]); - const listRef = useRef(); + // 무한스크롤 관련 상태 + const [participants, setParticipants] = useState([]); + const [lastParticipantId, setLastParticipantId] = useState(null); + const [hasMoreParticipants, setHasMoreParticipants] = useState(true); + const [isFetchingParticipants, setIsFetchingParticipants] = useState(false); - const currentUserId = projectUserData.data.projectParticipantId; + // 리스트 스크롤 참조 + const listRef = useRef(); + // 모달이 열릴 때 최초 참가자 목록 불러오기 useEffect(() => { - const pageData = projectMemberData[currentPage]; - if (pageData?.data?.content) { - const filtered = pageData.data.content.filter( - (member) => member.projectParticipantId !== currentUserId + if (isOpen) { + // 상태 초기화 + setParticipants([]); + setLastParticipantId(null); + setHasMoreParticipants(true); + fetchParticipants(); + } + }, [isOpen, projectId]); + + // 참가자 API 호출 함수 (무한스크롤) + const fetchParticipants = async () => { + if (isFetchingParticipants || !hasMoreParticipants) return; + setIsFetchingParticipants(true); + try { + console.log(projectId); + console.log(currentMemberId); + const res = await axiosWithAuthorization.get( + `/projects/${projectId}/participants`, + { + params: { + lastProjectParticipantId: lastParticipantId, + size: 5, + }, + } ); - setMembers((prev) => [...prev, ...filtered]); + console.log("참가자 목록 응답:", res.data); + // 응답 구조: { data: { content: [...], last: true/false, ... } } + const newParticipants = res.data.data.content || []; + // 기존 목록에 합치기 + setParticipants((prev) => [...prev, ...newParticipants]); + if (newParticipants.length > 0) { + const nextLastId = newParticipants[newParticipants.length - 1].projectParticipantId; + setLastParticipantId(nextLastId); + } + if (res.data.data.last) { + setHasMoreParticipants(false); + } + } catch (error) { + showAlert("error", error.response?.data?.message || "참가자 조회 실패"); + console.log(error.response?.data); + } finally { + setIsFetchingParticipants(false); } - }, [currentPage]); + }; - // 스크롤 핸들러 + // 스크롤 이벤트 핸들러: 리스트 하단에 도달하면 추가 데이터 요청 const handleScroll = () => { const container = listRef.current; if (!container) return; - const isBottom = - container.scrollTop + container.clientHeight >= - container.scrollHeight - 10; + if (container.scrollTop + container.clientHeight >= container.scrollHeight - 10) { + fetchParticipants(); + } + }; - if (isBottom && currentPage + 1 < projectMemberData.length) { - setCurrentPage((prev) => prev + 1); + // 팀장 권한 양도 API 호출 함수 + const handleChangeAdmin = async () => { + if (!selectedMember) { + showAlert("error", "양도할 팀원을 선택해주세요."); + return; + } + try { + console.log(selectedMember) + await axiosWithAuthorization.post(`/projects/${projectId}/admin/change`, { + newAdminId: selectedMember, + }); + showAlert("success", "팀장 양도 완료"); + if (onAdminChanged){ + onAdminChanged(); + } + else{ + onClose(); + } + // 팀원 목록 새로고침 등 필요시 상위 컴포넌트 재조회 함수 호출 가능 + } catch (error) { + console.log(error); + showAlert("error", error.response?.data?.message || "팀장 양도 실패"); } }; - // <----------------------------------API 연결시 필요하면 수정 --------------------------------------> - // <--------------------------------------------여기 위까지 끝--------------------------------------> if (!isOpen) return null; @@ -241,31 +185,31 @@ export default function LeaderModal({ isOpen, onClose, }) { 양도할 팀원을 선택해주세요. - {/* <----------------------------------API 연결시 필요하면 수정 --------------------------------------> - <--------------------------------------map 함수 부분--------------------------------------> */} - {members.map((member) => ( + {participants + .filter((member) => member.projectParticipantId!=currentMemberId) + .map((member) => ( - - {member.projectNickname} + + + {member.nickname || member.projectNickname} + setSelectedMember(member.projectParticipantId)} + onChange={() => setSelectedMember(member.projectParticipantId)} /> ))} @@ -274,11 +218,10 @@ export default function LeaderModal({ isOpen, onClose, }) { 팀장을 양도하시겠습니까? - 예 + 예 아니오 ); - -} \ No newline at end of file +} diff --git a/src/app/mypage/mypage.js b/src/app/mypage/mypage.js index c228e13..ceb2012 100644 --- a/src/app/mypage/mypage.js +++ b/src/app/mypage/mypage.js @@ -40,24 +40,23 @@ export default function My({ projectId }) { const [clickCountMap, setClickCountMap] = useState({}); - useEffect(() => { - const getProjectInfo = async () => { - try { - const userData = await fetchProjectUser(ProjectId); - setProjectUserData(userData); - console.log("프로필 이미지:", userData?.profileImageUrl); - } catch (err) { - console.error("프로젝트 유저 정보 에러:", err.message); - } - try { - const data = await fetchProjectData(ProjectId); - setProjectData(data); - } catch (err) { - console.error("프로젝트 데이터 에러:", err.message); - } - }; + const getProjectInfo = async () => { + try { + const project = await fetchProjectData(projectId); + setProjectData(project); + } catch (error) { + showAlert("error", "프로젝트 데이터 조회 실패"); + } + try { + const userData = await fetchProjectUser(projectId); + setProjectUserData(userData); + } catch (error) { + showAlert("error", "프로젝트 유저 데이터 조회 실패"); + } + }; + useEffect(() => { getProjectInfo(); }, []); @@ -110,6 +109,7 @@ export default function My({ projectId }) { } }, [ProjectId]); + const handlePrevSprint = async () => { const currentBaseSprint = mySprints[currentSprintIndex]; @@ -187,6 +187,7 @@ export default function My({ projectId }) { } }; + useEffect(() => { const loadMyContribution = async () => { if (!ProjectId || !currentSprint?.id) return; @@ -202,6 +203,12 @@ export default function My({ projectId }) { loadMyContribution(); }, [ProjectId, currentSprint?.id]); + const handleAdminChanged = () => { + setIsLeaderModalOpen(false) + getProjectInfo(); // 프로젝트 데이터 재조회 + }; + + // 모달 상태에 따라 스크롤 제어 useEffect(() => { const disableScroll = () => { @@ -357,6 +364,9 @@ export default function My({ projectId }) { setIsLeaderModalOpen(false)} + projectId={ProjectId} + currentMemberId={projectUserData.projectParticipantId} + onAdminChanged={handleAdminChanged} />; > ); diff --git a/src/app/team/leave_modal.js b/src/app/team/leave_modal.js index 5b12b80..579a810 100644 --- a/src/app/team/leave_modal.js +++ b/src/app/team/leave_modal.js @@ -1,24 +1,51 @@ +"use client"; + import * as m from '@/components/modal_s'; +import { useAlert } from "@/context/AlertContext"; +import axiosWithAuthorization from "@/context/axiosWithAuthorization"; export default function LeaveModal({ selectedProject, onClose }) { - if (!selectedProject) return null; + const { showAlert } = useAlert(); + + if (!selectedProject) return null; + + // "예" 버튼을 눌렀을 때 호출하는 함수 + const handleLeaveProject = async () => { + try { + console.log(selectedProject); + await axiosWithAuthorization.delete(`/projects/${selectedProject.projectInfo.projectId}/leave`); + showAlert("success", "프로젝트 나가기가 완료되었습니다."); + onClose(); // 모달 닫기 후 추가 작업(예: 페이지 리디렉션) 필요 시 추가 + } catch (error) { + console.error(error); + showAlert("error", error.response?.data?.message || "프로젝트 나가기 실패"); + } + }; - return ( - - - - - {selectedProject.title} 프로젝트를 - - - 나가시겠습니까? - - - - 예 - 아니오 - - - - ); -}; + return ( + + + + + {selectedProject.title} 프로젝트를 + + + 나가시겠습니까? + + + + {/* "예" 버튼에 API 호출 핸들러 연결 */} + 예 + 아니오 + + + + ); +} diff --git a/src/app/team/team.js b/src/app/team/team.js index 3967fad..5fe988a 100644 --- a/src/app/team/team.js +++ b/src/app/team/team.js @@ -234,9 +234,18 @@ export default function Team({ teamId }) { const newOtherProjects = []; content.forEach(project => { - if (project.isParticipant) newMyProjects.push(project); - else newOtherProjects.push(project); - }); + //유저가 프로젝트에 참여 중 + if (project.isParticipant) { + //상태 INACTIVE -> others + if (project.currentUserParticipation && project.currentUserParticipation.status === "INACTIVE") { + newOtherProjects.push(project); + } else { + newMyProjects.push(project); + } + } else { + newOtherProjects.push(project); + } + }); setMyProjects(prev => [...prev, ...newMyProjects]); setOtherProjects(prev => [...prev, ...newOtherProjects]);
- {selectedProject.title} 프로젝트를 -
- 나가시겠습니까? -
+ {selectedProject.title} 프로젝트를 +
+ 나가시겠습니까? +