Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/mypage/approve_modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export default function ApproveModal({ isOpen, onClose, projectId }) {
{request.projectRegistrationStatus === "REJECTED" && (
<ApproveButton onClick={() => handleApprove(request.registrationId)}>승인</ApproveButton>
)}

</ButtonGroup>
</RequestItem>
))
Expand Down
318 changes: 190 additions & 128 deletions src/app/project/meeting_modal.js
Original file line number Diff line number Diff line change
@@ -1,141 +1,203 @@
import React, { useState } from "react";
import styled from "styled-components";
import * as m from "../../components/modal_s";
import { IoClose } from "react-icons/io5";
import { IoClose } from "react-icons/io5";
import axiosWithAuthorization from "@/context/axiosWithAuthorization";
import { useAlert } from "@/context/AlertContext";

const ModalContent = styled.div`
background: white;
padding: 30px;
border-radius: 12px;
text-align: center;
width: 500px;
height: auto;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
border: 2px solid #796AD9;
position: relative;
background: white;
padding: 30px;
border-radius: 12px;
text-align: center;
width: 500px;
height: auto;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
border: 2px solid #796ad9;
position: relative;
`;

export default function MeetingModal({ isOpen, onClose, meetingTitle, setMeetingTitle, meetingDate, setMeetingDate, startTime, setStartTime, endTime, setEndTime, sprintNum, isEditing }) {
if (!isOpen) return null;
export default function MeetingModal({
isOpen,
onClose,
meetingId,
meetingTitle,
setMeetingTitle,
meetingDate,
setMeetingDate,
startTime,
setStartTime,
endTime,
setEndTime,
sprintId,
isEditing,
onMeetingSaved,
onMeetingDeleted
}) {
const { showAlert } = useAlert();
const [isTitleChanged, setIsTitleChanged] = useState(false);
const [isDateChanged, setIsDateChanged] = useState(false);

const formatMeetingData = (meetingDate, startTime, endTime) => {
if (!meetingDate || !startTime || !endTime) return null;
// λ‚ μ§œμ™€ μ‹œκ°„μ„ ν•©μ³μ„œ ISO ν˜•μ‹μœΌλ‘œ λ³€ν™˜
const meetingStart = `${meetingDate}T${startTime}:00`;
const meetingEnd = `${meetingDate}T${endTime}:00`;

return { meetingStart, meetingEnd };
if (!isOpen) return null;

// meetingDate/μ‹œκ°„-> "2025-04-14T09:00:00"
const formatMeetingData = (date, start, end) => {
if (!date || !start || !end) return null;
return {
meetingStart: `${date}T${start}:00`,
meetingEnd: `${date}T${end}:00`,
};
};

const handleSaveMeeting = () => {
const formattedMeeting = formatMeetingData(meetingDate, startTime, endTime);

if (!formattedMeeting) {
alert("λͺ¨λ“  ν•„λ“œλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.");
const handleSaveMeeting = async () => {
try {
if (isEditing && meetingId) {
// νŽΈμ§‘ λͺ¨λ“œλΌλ©΄ λ³€κ²½λœ ν•­λͺ©λ§Œ 각각 μ—…λ°μ΄νŠΈ μ§„ν–‰
if (isTitleChanged) {
// λ―ΈνŒ… 제λͺ© μ—…λ°μ΄νŠΈ API 호좜 (/meetings/{meetingId}/title)
await axiosWithAuthorization.post(`/meetings/${meetingId}/title`, {
title: meetingTitle,
});
showAlert("success", "λ―ΈνŒ… 제λͺ© μˆ˜μ • μ™„λ£Œ");
}
if (isDateChanged) {
// λ‚ μ§œ/μ‹œκ°„ 값이 λ³€κ²½λ˜μ—ˆμœΌλ©΄, ν•„μˆ˜ 값이 λͺ¨λ‘ μžˆλŠ”μ§€ 체크
const formattedMeeting = formatMeetingData(meetingDate, startTime, endTime);
if (!formattedMeeting) {
showAlert("error","λ‚ μ§œ 및 μ‹œκ°„μ„ λͺ¨λ‘ μž…λ ₯ν•΄μ£Όμ„Έμš”.");
return;
}
// λ―ΈνŒ… μΌμ‹œ μ—…λ°μ΄νŠΈ API 호좜 (/meetings/{meetingId}/date)
await axiosWithAuthorization.post(`/meetings/${meetingId}/date`, formattedMeeting);
showAlert("success", "λ―ΈνŒ… μΌμ‹œ μˆ˜μ • μ™„λ£Œ");
}

console.log("μ €μž₯ν•  λ―ΈνŒ… 데이터:", formattedMeeting);

// 여기에 API κ΄€λ ¨ μ½”λ“œ μΆ”κ°€ν•˜λ©΄ 될듯?
onClose();
};

return (
<m.ModalOverlay>
<ModalContent onClick={(e) => e.stopPropagation()}>
<m.Title>Sprint {sprintNum} νŒ€ λ―ΈνŒ…</m.Title>

<m.CloseButton onClick={onClose}>
<IoClose size={24} />
</m.CloseButton>

{/* λ―ΈνŒ… 제λͺ© μž…λ ₯ */}
<m.InputWrapper>
<m.Label>회의λͺ…</m.Label>
<m.Input
type="text"
value={meetingTitle}
onChange={(e) => setMeetingTitle(e.target.value)}
/>
</m.InputWrapper>

{/* λ‚ μ§œ μž…λ ₯ */}
<m.InputWrapper>
<m.Label>λ‚ μ§œ</m.Label>
<m.DateInput
type="date"
value={meetingDate}
onChange={(e) => setMeetingDate(e.target.value)}
/>
</m.InputWrapper>

{/* μ‹œκ°„ μž…λ ₯ */}
<m.InputWrapper>
<m.Label>μ‹œκ°„</m.Label>
<m.DateInputContainer>
<m.DateInput
type="time"
value={startTime}
onChange={(e) => {
const selectedTime = e.target.value;
const hour = parseInt(selectedTime.split(":")[0], 10);

// μ˜€μ „ 12μ‹œ ~ 7μ‹œλ₯Ό 선택할 경우, 8μ‹œλ‘œ μžλ™ λ³€κ²½
if ((hour >= 1 && hour < 8 ) || hour === 0) {
setStartTime("08:00");
} else {
setStartTime(selectedTime);
}
}}
/>
<span> ~ </span>
<m.DateInput
type="time"
value={endTime}
onChange={(e) => {
const selectedTime = e.target.value;

const [startHour, startMin] = startTime.split(":").map(Number);
const [endHour, endMin] = selectedTime.split(":").map(Number);

const start = new Date();
const end = new Date();

start.setHours(startHour, startMin, 0);
end.setHours(endHour, endMin, 0);

// λ§Œμ•½ λ§ˆκ°μ‹œκ°„μ΄ μ‹œμž‘μ‹œκ°„λ³΄λ‹€ λΉ λ₯΄κ±°λ‚˜ κ°™μœΌλ©΄ λ¬΄μ‹œ
if (end <= start) {
alert("마감 μ‹œκ°„μ€ μ‹œμž‘ μ‹œκ°„λ³΄λ‹€ 이후여야 ν•©λ‹ˆλ‹€.");
return;
}

// μ˜€μ „ 12μ‹œ ~ 7μ‹œλ₯Ό 선택할 경우, 8μ‹œλ‘œ μžλ™ λ³€κ²½
if ((endHour >= 1 && endHour < 8) || endHour === 0) {
setEndTime("08:00");
} else {
setEndTime(selectedTime);
}
}}
/>
</m.DateInputContainer>
</m.InputWrapper>
// 아무 값도 λ³€κ²½λ˜μ§€ μ•Šμ•˜λ‹€λ©΄ λ³„λ„λ‘œ μ•Œλ¦¬κ±°λ‚˜ κ·ΈλŒ€λ‘œ μ§„ν–‰
if (!isTitleChanged && !isDateChanged) {
showAlert("info", "λ³€κ²½λœ 사항이 μ—†μŠ΅λ‹ˆλ‹€.");
}
} else {
// μƒˆ λ―ΈνŒ… 생성 λͺ¨λ“œμ΄λ©΄ λͺ¨λ‘ ν•¨κ»˜ 생성
const formattedMeeting = formatMeetingData(meetingDate, startTime, endTime);
if (!meetingTitle || !formattedMeeting) {
showAlert("error","λͺ¨λ“  ν•„λ“œλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.");
return;
}
await axiosWithAuthorization.post(`/meetings/create`, {
sprintId: sprintId,
title: meetingTitle,
...formattedMeeting,
});
showAlert("success", "λ―ΈνŒ… 생성 μ™„λ£Œ");
}
if (onMeetingSaved) onMeetingSaved();
onClose();
} catch (error) {
showAlert("error", error.response?.data?.message || "였λ₯˜ λ°œμƒ");
}
};

<m.ButtonContainer
style={{ gap: "10px"}}
>
<m.SubmitButton onClick={handleSaveMeeting}>
{isEditing ? "μˆ˜μ • μ™„λ£Œ" : "λ―ΈνŒ… 생성"}
</m.SubmitButton>
const handleDeleteMeeting = async () => {
if (!meetingId) {
showAlert("error","μ‚­μ œν•  λ―ΈνŒ… 정보가 μ—†μŠ΅λ‹ˆλ‹€.");
return;
}
const confirmed = window.confirm("λ―ΈνŒ…μ„ μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?");
if (!confirmed) return;
try {
await axiosWithAuthorization.delete(`/meetings/${meetingId}`);
showAlert("success", "λ―ΈνŒ… μ‚­μ œ μ™„λ£Œ");
if (onMeetingDeleted) onMeetingDeleted();
onClose();
} catch (error) {
showAlert("error", error.response?.data?.message || "μ‚­μ œ 였λ₯˜");
}
};

{isEditing && (
<m.DeleteButton onClick={onClose}>
μ‚­μ œ
</m.DeleteButton>
)}
</m.ButtonContainer>
</ModalContent>
</m.ModalOverlay>
);
}
return (
<m.ModalOverlay>
<ModalContent onClick={(e) => e.stopPropagation()}>
<m.Title>Sprint {sprintId} νŒ€ λ―ΈνŒ…</m.Title>
<m.CloseButton onClick={onClose}>
<IoClose size={24} />
</m.CloseButton>
{/* λ―ΈνŒ… 제λͺ© μž…λ ₯ */}
<m.InputWrapper>
<m.Label>회의λͺ…</m.Label>
<m.Input
type="text"
value={meetingTitle}
onChange={(e) => {
setMeetingTitle(e.target.value);
setIsTitleChanged(true);
}}
/>
</m.InputWrapper>
{/* λ‚ μ§œ μž…λ ₯ */}
<m.InputWrapper>
<m.Label>λ‚ μ§œ</m.Label>
<m.DateInput
type="date"
value={meetingDate}
onChange={(e) => {
setMeetingDate(e.target.value);
setIsDateChanged(true);
}}
/>
</m.InputWrapper>
{/* μ‹œκ°„ μž…λ ₯ */}
<m.InputWrapper>
<m.Label>μ‹œκ°„</m.Label>
<m.DateInputContainer>
<m.DateInput
type="time"
value={startTime}
onChange={(e) => {
const selectedTime = e.target.value;
const hour = parseInt(selectedTime.split(":")[0], 10);
// μ˜€μ „ 12μ‹œ~7μ‹œ 선택 μ‹œ μžλ™ "08:00"
if ((hour >= 1 && hour < 8) || hour === 0) {
setStartTime("08:00");
} else {
setStartTime(selectedTime);
}
setIsDateChanged(true);
}}
/>
<span> ~ </span>
<m.DateInput
type="time"
value={endTime}
onChange={(e) => {
const selectedTime = e.target.value;
const [startHour, startMin] = startTime.split(":").map(Number);
const [endHour, endMin] = selectedTime.split(":").map(Number);
const startDateObj = new Date();
const endDateObj = new Date();
startDateObj.setHours(startHour, startMin, 0);
endDateObj.setHours(endHour, endMin, 0);
if (endDateObj <= startDateObj) {
showAlert("error","마감 μ‹œκ°„μ€ μ‹œμž‘ μ‹œκ°„λ³΄λ‹€ 이후여야 ν•©λ‹ˆλ‹€.");
return;
}
if ((endHour >= 1 && endHour < 8) || endHour === 0) {
setEndTime("08:00");
} else {
setEndTime(selectedTime);
}
setIsDateChanged(true);
}}
/>
</m.DateInputContainer>
</m.InputWrapper>
<m.ButtonContainer style={{ gap: "10px" }}>
<m.SubmitButton onClick={handleSaveMeeting}>
{isEditing ? "μˆ˜μ • μ™„λ£Œ" : "λ―ΈνŒ… 생성"}
</m.SubmitButton>
{isEditing && (
<m.DeleteButton onClick={handleDeleteMeeting}>μ‚­μ œ</m.DeleteButton>
)}
</m.ButtonContainer>
</ModalContent>
</m.ModalOverlay>
);
}
Loading