Skip to content
Snippets Groups Projects
Commit b785e114 authored by 유 채린's avatar 유 채린
Browse files

Merge conflict resolution

parent fc3484b3
Branches
No related tags found
No related merge requests found
Showing
with 1098 additions and 165 deletions
......@@ -14,7 +14,7 @@ function Comment(){
</div>
<div className={commentStyles.bottom}>
<div className={commentStyles.commentList}>
댓글리스트
아직 댓글이 없습니다.
</div>
</div>
......
import React, { useState, useEffect } from 'react';
import {PageItem} from "./recruit/recruitList";
//import {PageItem} from "./subscribe/subscribeList"
import InfiniteScrollStyles from './infiniteScroll.module.css'
function InfiniteScroll(props){
......
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useContext } from 'react';
import styles from './mainAside.module.css';
import { AppContext } from '../pages/main';
function NextSchedule(props) {
const { nextSchedules } = props.data;
if (!nextSchedules || !Array.isArray(nextSchedules)) {
if (!nextSchedules || !Array.isArray(nextSchedules) || nextSchedules.length === 0) {
return <p className={styles.noContent}>다음 일정이 없습니다.</p>;
}
const nextComponents = nextSchedules.map((nextSchedule) => (
<p className={styles.content}>
<span id={styles.title}>{nextSchedule.title}</span>
<span id={styles.content}>{nextSchedule.content}</span>
<span id={styles.time}>{nextSchedule.startTime}-{nextSchedule.endTime}</span>
console.log('ns:', !nextSchedules, !Array.isArray(nextSchedules));
const sortedSchedules = nextSchedules.slice().sort((a, b) => {
return a.startTime.localeCompare(b.startTime);
});
const nextComponents = sortedSchedules.map((nextSchedule) => (
<p className={styles.nextContent}>
<span className={styles.title}>{nextSchedule.title}</span>
<span className={styles.time}>{nextSchedule.startTime}-{nextSchedule.endTime}</span>
</p>
));
......@@ -26,15 +29,16 @@ function NextSchedule(props) {
function Notice(props){
const { subscribeNotices } = props.data;
if (!subscribeNotices || !Array.isArray(subscribeNotices)) {
if (!subscribeNotices || !Array.isArray(subscribeNotices) || subscribeNotices.length === 0) {
return <p className={styles.noContent}>최근 공지가 없습니다.</p>;
}
const noticeComponents = subscribeNotices.map((notice) => (
const sortedNotice = subscribeNotices.sort((a, b) => a.date - b.date);
const noticeComponents = sortedNotice.map((notice) => (
<p className={styles.noticeContent}>
<span id={styles.channel}>{notice.channel}</span>
<span id={styles.date}>{notice.date}</span>
<span id={styles.title}>{notice.title}</span>
<span className={styles.channel}>{notice.channel}</span>
<span className={styles.date}>{notice.date}</span>
<span className={styles.content}>{notice.title}</span>
</p>
));
......@@ -47,6 +51,7 @@ function Notice(props){
function Schedule(){
const [fulldata, setFulldata] = useState(null);
const { count } = useContext(AppContext);
const getFulldata = async ()=>{
try{
......@@ -66,26 +71,23 @@ function Schedule(){
useEffect(() => {
getFulldata();
}, []);
}, [count]);
return (
<div className={styles.aside_container}>
<div className={styles.header}>
<span className={styles.title}>다음 일정</span>
<span className={styles.sign}>다음 일정</span>
</div>
<div className={styles.nextSchedule}>
<NextSchedule data={fulldata || {}}/>
</div>
<div className={styles.header}>
<span className={styles.title}>최근 공지</span>
<span className={styles.sign}>최근 공지</span>
</div>
<div className={styles.notice}>
<Notice data={fulldata || {}}/>
</div>
</div>
)
}
......
.aside_container{
display: flex;
......@@ -13,45 +10,13 @@
justify-content: flex-end;
}
.nextSchedule, .notice{
display: flex;
flex: 1;
height: 50vh;
overflow-y: auto;
padding: 10px;
margin: 10px;
width:100%;
}
.nextSchedule::-webkit-scrollbar, .notice::-webkit-scrollbar{
width: 5px;
}
.nextSchedule::-webkit-scrollbar-thumb, .notice::-webkit-scrollbar-thumb {
background-color: #2f3542;
border-radius: 10px;
}
.nextSchedule::-webkit-scrollbar-track, .nextSchedule::-webkit-scrollbar-track {
background-color: grey;
border-radius: 10px;
box-shadow: inset 0px 0px 5px white;
}
.nextSchedule{
border-bottom: 2px solid #D9D9D9;
}
.header{
margin-left: 10px;
margin-top: 20px;
margin-bottom: 10px;
}
.title{
.sign{
float: left;
margin-left: 5%;
height: 25px;
......@@ -62,12 +27,21 @@
text-align: center;
}
.subscribe{
float: center;
font-weight: bold;
.nextSchedule, .notice{
display: flex;
flex: 1;
height: 50vh;
overflow-y: auto;
padding: 10px;
margin: 10px;
width:100%;
}
.content{
.nextSchedule{
border-bottom: 2px solid #D9D9D9;
}
.nextContent{
display: grid;
grid-template-columns: 1fr; /* 한 개의 열 */
height: 50px;
......@@ -84,8 +58,38 @@
border-radius: 3px;
}
#title{
.title{
font-weight: bold;
float: left;
}
.time{
}
.channel{
}
.date{
}
.content{
}
.nextSchedule::-webkit-scrollbar, .notice::-webkit-scrollbar{
width: 5px;
}
.nextSchedule::-webkit-scrollbar-thumb, .notice::-webkit-scrollbar-thumb {
background-color: #2f3542;
border-radius: 10px;
}
.nextSchedule::-webkit-scrollbar-track, .nextSchedule::-webkit-scrollbar-track {
background-color: grey;
border-radius: 10px;
box-shadow: inset 0px 0px 5px white;
}
\ No newline at end of file
......@@ -38,16 +38,6 @@ function CreateRecruit({ isOpen, onClose }){
}));
};
const handleColorChange = (color) => {
setFormData((prevData) => ({
...prevData,
data: {
...prevData.data,
color,
},
}));
};
const handleImageChange = (e) => {
const imageFile = e.target.files[0];
setFormData((prevDate) => ({
......@@ -64,8 +54,19 @@ function CreateRecruit({ isOpen, onClose }){
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
const handleSave = () => {
handleSubmit({
...formData,
data: {
...formData.data,
startTime: `${timeData.startHour|| '00'}:${timeData.startMinute || '00'}`,
endTime: `${timeData.endHour|| '00'}:${timeData.endMinute || '00'}`,
}
});
};
const handleSubmit = async (formData) => {
if(formData.data.title === ''){
alert('제목을 입력해주세요.');
......@@ -85,35 +86,26 @@ function CreateRecruit({ isOpen, onClose }){
} else if(formData.data.startDate === ''){
alert('시작 날짜를 입력해주세요.');
return;
} else if(formData.data.endDate === ''){
alert('종료 날짜를 입력해주세요.');
return;
} else if(formData.data.timeCategory === ''){
alert('시간 지정 여부를 선택해주세요.');
return;
} else if(formData.data.content === ''){
alert('내용을 입력해주세요.');
return;
} else if(formData.data.color === ''){
alert('색깔을 선택해주세요.');
return;
}
if(formData.data.timeCategory === "D"){
if(formData.data.title === '' || formData.data.title === ''){
if(timeData.startHour === null || timeData.endHour === null || timeData.startMinute === null || timeData.endMinute === null){
alert('시간을 선택해주세요.');
return;
}
setFormData((prevData) => ({
...prevData,
data: {
...prevData.data,
startTime: `${timeData.startHour}:${timeData.startMinute}`,
endTime: `${timeData.endHour}:${timeData.endMinute}`,
}
}));
}
const jsonDataString = JSON.stringify(formData.data);
try {
const jsonDataString = JSON.stringify(formData.data);
const formDataToSend = new FormData();
formDataToSend.append('data', jsonDataString);
formDataToSend.append('image', formData.image);
......@@ -126,12 +118,12 @@ function CreateRecruit({ isOpen, onClose }){
if (response.ok) {
// 성공적으로 모집글이 생성됨
alert('모집글이 성공적으로 생성되었습니다.');
window.location.reload();
} else {
// 오류 처리
alert('모집글 생성에 실패하였습니다.');
}
onClose();
} catch (error) {
console.error('Error:', error);
}
......@@ -166,7 +158,7 @@ function CreateRecruit({ isOpen, onClose }){
<div>모집글 생성</div>
</div>
<form onSubmit={handleSubmit}>
<form onSubmit={handleSave}>
<div className={createRecruitStyles.title}>
<label>제목*</label>
<input type="text" name="title" value={formData.data.title} onChange={handleInputChange} placeholder="제목을 입력하세요.(50자 이내)"/>
......@@ -175,7 +167,7 @@ function CreateRecruit({ isOpen, onClose }){
<div className={createRecruitStyles.owner}>
<label>작성자*</label>
<input type="radio" name="owner" value="User" checked={formData.data.owner === 'User'} onClick={handleInputChange}/>User
<input type="radio" name="owner" value="Cannel" checked={formData.data.owner === 'Cannel'} onClick={handleInputChange}/>Cannel
<input type="radio" name="owner" value="Channel" checked={formData.data.owner === 'Channel'} onClick={handleInputChange}/>Channel
</div>
<div className={createRecruitStyles.region}>
......@@ -208,7 +200,7 @@ function CreateRecruit({ isOpen, onClose }){
</div>
<div className={createRecruitStyles.endDate}>
<label>종료 날짜</label>
<label>종료 날짜*</label>
<input type="date" name="endDate" value={formData.data.endDate} onChange={handleInputChange}/>
</div>
......@@ -233,8 +225,8 @@ function CreateRecruit({ isOpen, onClose }){
</select>
<span>:</span>
<select name="startMinute" value={timeData.startMinute} onChange={handleTimeChange}>
<option name="minute">00</option>
<option name="minute">30</option>
<option name="minute" value='00'>00</option>
<option name="minute" value='30'>30</option>
</select>
</div>
</div>
......@@ -270,11 +262,6 @@ function CreateRecruit({ isOpen, onClose }){
<input type="file" name="image" value={formData.data.image} onChange={handleImageChange} />
</div>
<div className={createRecruitStyles.color}>
<label>색상 선택*</label>
<input type="color" name="color" value={formData.data.color} onChange={(e) => handleColorChange(e.target.value)}/>
</div>
<div className={createRecruitStyles.button}>
<button type="submit" className={createRecruitStyles.save}>저장</button>
<button type="button" className={createRecruitStyles.cancel} onClick={onClose}>취소</button>
......
......@@ -14,8 +14,8 @@
flex-direction: column;
position: fixed;
width: 400px;
height: 500px;
width: 450px;
height: 600px;
top: 50%;
left: 50%;
......
import React from 'react';
import createVoteStyles from './createVote.module.css';
function CreateVote({ id, data, create, onClose }){
const createVote = async ()=>{
try{
const response = await fetch(`/api/recruits/${id}/times/save`,{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok){
alert('투표가 등록되었습니다.');
onClose();
create();
}else{
throw new Error('Data loading error');
}
} catch(error){
console.log('Error during fetch:', error);
}
}
return (
<div>
<div>
<button type='button' className={createVoteStyles.createVote} onClick={createVote}>투표 생성</button>
</div>
</div>
)
}
export default CreateVote;
\ No newline at end of file
.createVote{
width: 90px;
height:35px;
border:none;
border-radius: 5px;
cursor: pointer;
color: white;
background-color:#8393BE;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 그림자 추가 */
transition: background-color 0.3s, color 0.3s, transform 0.3s;
}
\ No newline at end of file
......@@ -23,16 +23,16 @@ const RecruitDetail = ({ isOpen, onClose, data }) => {
const applyInfo = async ()=>{
try{
const response = await fetch(`/api/recruit/${data.id}`,{
const response = await fetch(`/api/recruits/${data.id}`,{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
console.log(data.id);
if (response.ok){
alert('신청되었습니다.');
window.location.reload();
}else{
alert('이미 신청되었거나 신청이 불가능합니다.');
}
......@@ -41,6 +41,46 @@ const RecruitDetail = ({ isOpen, onClose, data }) => {
}
}
const deleteInfo = async ()=>{
try{
const response = await fetch(`/api/recruits/${data.id}`,{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok){
alert('삭제되었습니다.');
window.location.reload();
}else{
alert('삭제를 실패했습니다.');
}
} catch(error){
console.log('Error during fetch:', error);
}
}
const stateChangeInfo = async ()=>{
try{
const response = await fetch(`/api/recruits/${data.id}`,{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok){
alert('상태가 변경되었습니다..');
window.location.reload();
}else{
alert('상태 변경을 실패하였습니다.');
}
} catch(error){
console.log('Error during fetch:', error);
}
}
return (
<div>
{isOpen && (
......@@ -49,12 +89,12 @@ const RecruitDetail = ({ isOpen, onClose, data }) => {
<button className={recruitDetailStyles.x} onClick={onClose}>X</button>
<div className={recruitDetailStyles.info}>
<div className={recruitDetailStyles.imgContainer}>
{data.imagePath ? (<img className={recruitDetailStyles.img} src={data.imagePath} alt='recruit img' />) : (<img src={data.imagePath}/>)}
<img className={recruitDetailStyles.img} src={data.imagePath} alt='recruit img' />
</div>
<div className={recruitDetailStyles.notImgContainer}>
<div className={recruitDetailStyles.top}>
<div className={recruitDetailStyles.title}>{data.title}</div>
<div className={recruitDetailStyles.title}>{data.title} ({data.Writer})</div>
{data.timeCategory === 'D' ? (null) : (<div className={recruitDetailStyles.tbd}>시간 미정</div>)}
<div className={`${state === '모집중' ?
recruitDetailStyles.Recruiting:
......@@ -83,12 +123,12 @@ const RecruitDetail = ({ isOpen, onClose, data }) => {
<svg className={recruitDetailStyles.dateIcon} width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 2H17V0H15V2H5V0H3V2H2C0.9 2 0 2.9 0 4V20C0 21.1 0.9 22 2 22H18C19.1 22 20 21.1 20 20V4C20 2.9 19.1 2 18 2ZM18 20H2V9H18V20ZM18 7H2V4H18V7Z" fill="#FF5959"/>
</svg>
<span className={recruitDetailStyles.date}>{data.startDate}</span>
{data.endDate ? (<span className={recruitDetailStyles.date}>~{data.endDate}</span>) : (null)}
<span className={recruitDetailStyles.date}>{data.startDate}~{data.endDate}</span>
</div>
{data.timeCategory === 'D' ? (
<div className={recruitDetailStyles.timeContainer}>
{data.timeCategory === 'D' ? (
<div>
<svg className={recruitDetailStyles.timeIcon} width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.8333 4.19668L16.85 0.0150146L15.4525 1.67251L20.4358 5.85418L21.8333 4.19668ZM6.53667 1.67251L5.15 0.0150146L0.166668 4.18585L1.56417 5.84335L6.53667 1.67251ZM11.5417 6.66668H9.91667V13.1667L15.0625 16.2542L15.875 14.9217L11.5417 12.3542V6.66668ZM11 2.33335C5.61583 2.33335 1.25 6.69918 1.25 12.0833C1.25 17.4675 5.605 21.8333 11 21.8333C16.3842 21.8333 20.75 17.4675 20.75 12.0833C20.75 6.69918 16.3842 2.33335 11 2.33335ZM11 19.6667C6.8075 19.6667 3.41667 16.2758 3.41667 12.0833C3.41667 7.89085 6.8075 4.50001 11 4.50001C15.1925 4.50001 18.5833 7.89085 18.5833 12.0833C18.5833 16.2758 15.1925 19.6667 11 19.6667Z" fill="#FF5959"/>
</svg>
......@@ -96,9 +136,16 @@ const RecruitDetail = ({ isOpen, onClose, data }) => {
</div>
) : (null)}
</div>
</div>
<button className={recruitDetailStyles.scheduleCheck} onClick={handleClick}>일정 확인</button>
{isCalendarVisible && <div className={recruitDetailStyles.calendarOverlay} onClick={handleClick}><div className={recruitDetailStyles.calendarModal} onClick={(e) => e.stopPropagation()}><MyScheduleApp/></div></div>}
{isCalendarVisible && (
<div className={recruitDetailStyles.calendarOverlay} onClick={handleClick}>
<div className={recruitDetailStyles.calendarModal} onClick={(e) => e.stopPropagation()}>
<MyScheduleApp/>
</div>
</div>
)}
<button type="button" className={recruitDetailStyles.apply} onClick={applyInfo}>신청</button>
</div>
</div>
......
......@@ -46,6 +46,7 @@
display: flex;
width: 900px;
height: 340px;
margin-bottom: 50px;
}
.comment{
......@@ -115,7 +116,6 @@
font-weight: bolder;
font-size: 20px;
line-height: 20px;
color: #FFFFFF;
......@@ -136,7 +136,6 @@
font-weight: bolder;
font-size: 20px;
line-height: 20px;
color: #FFFFFF;
......@@ -157,7 +156,6 @@
font-weight: bolder;
font-size: 20px;
line-height: 20px;
color: #FFFFFF;
......@@ -206,7 +204,7 @@
.dateTimeContainer{
display: flex;
width: 440px;
width: 470px;
}
.dateIcon{
......@@ -219,7 +217,6 @@
.date, .time{
font-weight: 600;
font-size: 20px;
line-height: 33px;
color: #000000;
}
......@@ -245,7 +242,6 @@
border: none;
font-size: 22px;
line-height: 24px;
font-weight: bolder;
background: #808DC2;
......@@ -290,7 +286,6 @@
border: none;
font-size: 22px;
line-height: 26px;
font-weight: bolder;
background: #808DC2;
......
......@@ -2,9 +2,13 @@ import React, { useState } from 'react';
import recruitListStyles from './recruitList.module.css';
import InfiniteScroll from '../infiniteScroll';
import RecruitDetail from './recruitDetail';
import TimeInquiry from './timeInquiry';
import Vote from './vote';
function PageItem(props){
const [isModalOpen, setIsModalOpen] = useState(false);
const [isInquiryOpen, setIsInquiryOpen] = useState(false);
const [isVoteOpen, setIsVoteOpen] = useState(false);
const openModal = () => {
setIsModalOpen(true);
......@@ -14,6 +18,24 @@ function PageItem(props){
setIsModalOpen(false);
};
const openInquiry = (e) => {
e.stopPropagation();
setIsInquiryOpen(true);
};
const closeInquiry = () => {
setIsInquiryOpen(false);
};
const openVote = (e) => {
e.stopPropagation();
setIsVoteOpen(true);
};
const closeVote = () => {
setIsVoteOpen(false);
};
let state = '';
if(props.data.state === 'Recruiting'){
......@@ -28,7 +50,7 @@ function PageItem(props){
<div className={recruitListStyles.recruit}>
<div className={recruitListStyles.container} onClick={openModal}>
<div className={recruitListStyles.imgContainer}>
{props.data.imagePath ? (<img className={recruitListStyles.img} src={props.data.imagePath} alt='recruit img' />) : (<img src={props.data.imagePath}/>)}
<img className={recruitListStyles.img} src={props.data.imagePath} alt='recruit img' />
</div>
<div className={recruitListStyles.notImgContainer}>
<div className={recruitListStyles.top}>
......@@ -56,12 +78,12 @@ function PageItem(props){
<svg className={recruitListStyles.dateIcon} width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 2H17V0H15V2H5V0H3V2H2C0.9 2 0 2.9 0 4V20C0 21.1 0.9 22 2 22H18C19.1 22 20 21.1 20 20V4C20 2.9 19.1 2 18 2ZM18 20H2V9H18V20ZM18 7H2V4H18V7Z" fill="#FF5959"/>
</svg>
<span className={recruitListStyles.date}>{props.data.startDate}</span>
{props.data.endDate ? (<span className={recruitListStyles.date}>~{props.data.endDate}</span>) : (null)}
<span className={recruitListStyles.date}>{props.data.startDate}~{props.data.endDate}</span>
</div>
{props.data.timeCategory === 'D' ? (
<div className={recruitListStyles.timeContainer}>
{props.data.timeCategory === 'D' ? (
<div>
<svg className={recruitListStyles.timeIcon} width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.8333 4.19668L16.85 0.0150146L15.4525 1.67251L20.4358 5.85418L21.8333 4.19668ZM6.53667 1.67251L5.15 0.0150146L0.166668 4.18585L1.56417 5.84335L6.53667 1.67251ZM11.5417 6.66668H9.91667V13.1667L15.0625 16.2542L15.875 14.9217L11.5417 12.3542V6.66668ZM11 2.33335C5.61583 2.33335 1.25 6.69918 1.25 12.0833C1.25 17.4675 5.605 21.8333 11 21.8333C16.3842 21.8333 20.75 17.4675 20.75 12.0833C20.75 6.69918 16.3842 2.33335 11 2.33335ZM11 19.6667C6.8075 19.6667 3.41667 16.2758 3.41667 12.0833C3.41667 7.89085 6.8075 4.50001 11 4.50001C15.1925 4.50001 18.5833 7.89085 18.5833 12.0833C18.5833 16.2758 15.1925 19.6667 11 19.6667Z" fill="#FF5959"/>
</svg>
......@@ -69,9 +91,26 @@ function PageItem(props){
</div>
) : (null)}
</div>
{state === '모집중' ? (
<div>
<div className={recruitListStyles.inquiryContainer}>
<button type="button" className={recruitListStyles.inquiry} onClick={openInquiry}>시간 조회</button>
<button type="button" className={recruitListStyles.vote} onClick={openVote}>투표하기</button>
</div>
</div>
) : (null)}
</div>
</div>
</div>
<RecruitDetail isOpen={isModalOpen} onClose={closeModal} data={props.data}/>
<div className={recruitListStyles.timeInquiry}>
<TimeInquiry isOpen={isInquiryOpen} onClose={closeInquiry} data={props.data}/>
</div>
<div className={recruitListStyles.voteInquiry}>
<Vote isOpen={isVoteOpen} onClose={closeVote} data={props.data}/>
</div>
</div>
)
}
......
......@@ -36,7 +36,6 @@
.bottom{
display: flex;
width: 440px;
}
.img{
......@@ -83,7 +82,6 @@
font-weight: bolder;
font-size: 20px;
line-height: 20px;
color: #FFFFFF;
......@@ -94,8 +92,6 @@
align-items: center; /* 세로 방향에서 가운데 정렬. */
justify-content: center;
margin-right: auto;
width: 90px;
height: 33px;
margin-left: auto;
......@@ -104,7 +100,6 @@
font-weight: bolder;
font-size: 20px;
line-height: 20px;
color: #FFFFFF;
......@@ -115,8 +110,6 @@
align-items: center; /* 세로 방향에서 가운데 정렬. */
justify-content: center;
margin-right: auto;
width: 90px;
height: 33px;
margin-left: auto;
......@@ -125,7 +118,6 @@
font-weight: bolder;
font-size: 20px;
line-height: 20px;
color: #FFFFFF;
......@@ -162,6 +154,14 @@
line-height: 40px;
}
.dateContainer{
width: 250px;
}
.timeContainer{
width: 240px;
}
.date, .time{
font-weight: 600;
font-size: 20px;
......@@ -182,3 +182,43 @@
margin-left: 10px;
margin-right: 5px;
}
.inquiryContainer{
display: flex;
width: 180px;
}
.inquiry{
display: flex; /* Flexbox를 사용하여 자식 요소들을 가로로 배열합니다. */
align-items: center; /* 세로 방향에서 가운데 정렬. */
justify-content: center;
width: 90px;
height: 33px;
background: #808DC2;
font-weight: bolder;
font-size: 20px;
color: #FFFFFF;
border: none;
}
.vote{
display: flex; /* Flexbox를 사용하여 자식 요소들을 가로로 배열합니다. */
align-items: center; /* 세로 방향에서 가운데 정렬. */
justify-content: center;
width: 90px;
height: 33px;
margin-left: 5px;
background: #808DC2;
font-weight: bolder;
font-size: 20px;
color: #FFFFFF;
border: none;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import timeInquiryStyles from './timeInquiry.module.css';
import CreateVote from './createVote';
//import MyScheduleApp from '../calendar/calendar';
function TimeItem({ item, selectedTimes, setSelectedTimes }){
const isChecked = selectedTimes.includes(item);
const onChangeCheck = () => {
if (!isChecked) {
setSelectedTimes((prev) => [...prev, item]);
} else {
setSelectedTimes((prev) =>
prev.filter((data) => data !== item)
);
}
setSelectedTimes((prev) =>
[...prev].sort((a, b) => new Date(b) - new Date(a))
);
};
return(
<li className={isChecked ? "checked" : ""}>
<span>{item.date} {item.startTime}-{item.endTime}</span>
<label>
<input
type='checkbox'
onChange={onChangeCheck}
checked={isChecked}
/>
</label>
</li>
);
}
function TimeInquiry({ isOpen, onClose, data }){
const savedIsVote = localStorage.getItem('isVote');
const [isVote, setIsVote] = useState(savedIsVote ? JSON.parse(savedIsVote) : false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [inquirydata, setInquirydata] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedTimes, setSelectedTimes] = useState([]);
const optionsArray = Array.from({ length: 1441 / 30 }, (_, index) => index * 30 + 30);
const [bodyData, setBodyData] = useState({
startDate: '',
endDate: '',
startTime: '',
endTime: '',
interval: '30',
});
const [timeData, setTimeData] = useState({
startHour: '',
startMinute: '',
endHour: '',
endMinute: ''
});
const openModal = () => {
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
const handleInputChange = (e) => {
const { name, value } = e.target;
setBodyData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleTimeChange = (e) => {
const { name, value } = e.target;
setTimeData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSave = () => {
timeInquiry({
...bodyData,
startTime: `${timeData.startHour|| '00'}:${timeData.startMinute || '00'}`,
endTime: `${timeData.endHour|| '00'}:${timeData.endMinute || '00'}`,
});
};
const handleButtonClick = () => {
if(!isVote){
setIsVote(true);
}
};
const timeInquiry = async (body)=>{
setIsLoading(true);
if(body.startDate === ''){
alert('시작 날짜를 입력해주세요.');
return;
} else if(body.endDate === ''){
alert('종료 날짜를 선택해주세요.');
return;
} else if(body.startTime === ''){
alert('시작 시간을 선택해주세요.');
return;
} else if(body.endTime === ''){
alert('종료 시간을 선택해주세요.');
return;
}else if(body.interval === ''){
alert('요구 시간을 선택해주세요.');
return;
}
try{
const response = await fetch(`/api/recruits/${data.id}/times`,{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (response.ok){
const jsonData = await response.json();
if(jsonData.length > 0){
const components = jsonData.map((item) => (
<TimeItem item={item} selectedTimes={selectedTimes} setSelectedTimes={setSelectedTimes}/>
));
setInquirydata(components);
}
}else{
throw new Error('Data loading error');
}
} catch(error){
console.log('Error during fetch:', error);
} finally{
setIsLoading(false);
}
}
useEffect(() => {
if(isVote){
localStorage.setItem('isVote', JSON.stringify(isVote));
}
}, [isVote]);
return(
<div className={timeInquiryStyles.timeInquiry}>
{isOpen && (
<div className={timeInquiryStyles.overlay} onClick={onClose}>
<div className={timeInquiryStyles.modal} onClick={(e)=>e.stopPropagation()}>
<button className={timeInquiryStyles.x} onClick={onClose}>X</button>
<div className={timeInquiryStyles.top}>
<div>참여 가능 시간 조회</div>
</div>
<form onSubmit={handleSave}>
<div className={timeInquiryStyles.startDate}>
<label>시작 날짜*</label>
<input type="date" name="startDate" value={bodyData.startDate} onChange={handleInputChange}/>
</div>
<div className={timeInquiryStyles.endDate}>
<label>종료 날짜*</label>
<input type="date" name="endDate" value={bodyData.endDate} onChange={handleInputChange}/>
</div>
<div className={timeInquiryStyles.time_select}>
<label>시작 시간*</label>
<div>
<select name="startHour" value={timeData.startHour} onChange={handleTimeChange}>
{[...Array(24).keys()].map((hour) => (
<option key={hour} value={hour < 10 ? `0${hour}` : `${hour}`}>
{hour < 10 ? `0${hour}` : `${hour}`}
</option>
))}
</select>
<span>:</span>
<select name="startMinute" value={timeData.startMinute} onChange={handleTimeChange}>
<option name="minute" value='00'>00</option>
<option name="minute" value='30'>30</option>
</select>
</div>
</div>
<div className={timeInquiryStyles.time_select}>
<label>종료 시간*</label>
<div>
<select name="endHour" value={timeData.endHour} onChange={handleTimeChange}>
{[...Array(24).keys()].map((hour) => (
<option key={hour} value={hour < 10 ? `0${hour}` : `${hour}`}>
{hour < 10 ? `0${hour}` : `${hour}`}
</option>
))}
</select>
<span>:</span>
<select name="endMinute" value={timeData.endMinute} onChange={handleTimeChange}>
<option name="minute">00</option>
<option name="minute">30</option>
</select>
</div>
</div>
<div className={timeInquiryStyles.time_select}>
<label>요구 시간*</label>
<div>
<select name="interval" value={bodyData.interval} onChange={handleInputChange}>
{optionsArray.map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</select>
</div>
</div>
{/* <button type="button" className={timeInquiryStyles.scheduleCheck} onClick={handleClick}>일정 확인</button>
{isCalendarVisible && (
<div className={timeInquiryStyles.calendarOverlay} onClick={handleClick}>
<div className={timeInquiryStyles.calendarModal} onClick={(e) => e.stopPropagation()}>
<MyScheduleApp/>
</div>
</div>
)} */}
<div className={timeInquiryStyles.button}>
<button type="button" onClick={handleSave} className={timeInquiryStyles.inquiry}>조회</button>
</div>
</form>
{!isLoading &&
<div>
<ul>
{inquirydata}
</ul>
</div>
}
<div className={timeInquiryStyles.voteButton}>
<div className={timeInquiryStyles.createVote}>
<CreateVote id={data.id} data={selectedTimes} create={handleButtonClick} onClose={onClose}/>
</div>
</div>
</div>
</div>
)}
</div>
)
}
export default TimeInquiry;
\ No newline at end of file
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.modal {
display: flex;
flex-direction: column;
position: fixed;
width: 450px;
height: 500px;
overflow: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
background: #FFFFFF;
border-radius: 5px;
}
.modal::-webkit-scrollbar {
width: 5px;
}
.modal::-webkit-scrollbar-thumb {
background-color: #2f3542;
border-radius: 10px;
}
.modal::-webkit-scrollbar-track {
background-color: grey;
border-radius: 10px;
box-shadow: inset 0px 0px 5px white;
}
.x{
width: 35px;
height: 35px;
margin-left: auto;
margin-top: -10px;
margin-right: -10px;
margin-bottom: 10px;
font-size: 20px;
background-color: none;
border: none;
color: #000000;
}
.top{
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
font-weight: 600;
font-size: 20px;
}
.time_select {
display: flex;
margin-bottom: 5px;
}
.time_select select {
width: 70px;
height: 27px;
margin-left:17px;
margin-right: 10px;
margin-bottom: 10px;
}
.button{
display: flex;
align-items: center;
justify-content: center;
margin-top: 30px;
margin-bottom: 50px;
}
.inquiry{
width: 100%;
height:35px;
border:none;
border-radius: 5px;
cursor: pointer;
color: white;
background-color:#8393BE;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 그림자 추가 */
transition: background-color 0.3s, color 0.3s, transform 0.3s;
}
.voteButton{
display: flex;
align-items: center;
justify-content: center;
border-top: 3px solid #E7E7E7;
}
.createVote, .seeVote{
margin-top: 20px;
}
.calendarOverlay{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
}
.calendarModal{
display: flex;
position: fixed;
width: 900px;
height: 600px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1002;
background: #FFFFFF;
border-radius: 5px;
}
\ No newline at end of file
import React, { useState, useEffect } from "react";
import voteStyles from './vote.module.css'
function VoteItem({ data, checkVote, setCheckVote }){
const isChecked = checkVote.includes(data);
if(data.length === 0){
return <div>아직 투표가 생성되지 않았습니다.</div>
}
const onChangeCheck = () => {
if (!isChecked) {
setCheckVote((prev) => [...prev, data]);
} else {
setCheckVote((prev) =>
prev.filter((item) => item !== data)
);
}
setCheckVote((prev) =>
[...prev].sort((a, b) => new Date(b) - new Date(a))
);
};
return(
<div>
<li>
<label>
<input
type='checkbox'
onChange={onChangeCheck}
checked={isChecked}
/>
<span>{data.date} {data.startTime}-{data.endTime}</span>
<span>{data.num}</span>
<span>{data.voteState}</span>
</label>
</li>
</div>
)
}
function Vote({ isOpen, onClose, data }) {
const [votedata, setVotedata] = useState([]);
const [checkVote, setCheckVote] = useState([]);
const [count, setCount] = useState(0);
const voteFetchData = async ()=>{
try{
const response = await fetch(`/api/recruits/${data.id}/times/vote`);
const jsonData = await response.json();
if(jsonData.length > 0) {
const components = jsonData.map((data) => (
<VoteItem data={data} checkVote={checkVote} setCheckVote={setCheckVote}/>
));
setVotedata(components);
}
} catch(error){
console.log('Error during fetch:', error);
}
}
const vote = async()=>{
try{
const response = await fetch(`/api/recruits/${data.id}/times/vote`,{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(checkVote),
});
if (response.ok){
alert("투표되었습니다.");
setCount((prev) => (prev + 1));
}else{
throw new Error('Data loading error');
}
} catch(error){
console.log('Error during fetch:', error);
}
}
const closedVote = ()=>{
}
useEffect(() => {
voteFetchData();
}, [count]);
return(
<div>
{isOpen && (
<div className={voteStyles.overlay} onClick={onClose}>
<div className={voteStyles.modal} onClick={(e) => e.stopPropagation()}>
<button type="button" className={voteStyles.x} onClick={onClose}>X</button>
<div className={voteStyles.top}>
<div>{data.title}</div>
</div>
<div>
<ul>
{votedata}
</ul>
</div>
<div className={voteStyles.voteContainer}>
<button className={voteStyles.goVote} onClick={vote}>투표하기</button>
<button className={voteStyles.closedVote} onClick={closedVote}>투표 마감</button>
</div>
</div>
</div>
)}
</div>
)
}
export default Vote;
\ No newline at end of file
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.modal {
display: flex;
flex-direction: column;
position: fixed;
width: 400px;
height: 400px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
background: #FFFFFF;
border-radius: 5px;
}
.x{
width: 35px;
height: 35px;
margin-left: auto;
margin-top: -10px;
margin-right: -10px;
margin-bottom: 10px;
font-size: 20px;
background-color: none;
border: none;
color: #000000;
}
.top{
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
font-weight: 600;
font-size: 20px;
}
.voteContainer{
width: 140px;
}
.inquiry{
display: flex; /* Flexbox를 사용하여 자식 요소들을 가로로 배열합니다. */
align-items: center; /* 세로 방향에서 가운데 정렬. */
justify-content: center;
width: 90px;
height: 33px;
margin-left: auto;
background: #808DC2;
font-weight: bolder;
font-size: 20px;
color: #FFFFFF;
border: none;
}
.voteContainer{
display: flex;
align-items: center; /* 세로 방향에서 가운데 정렬. */
justify-content: center;
margin-left: auto;
}
.goVote, .closedVote{
width: 90px;
height:35px;
border:none;
border-radius: 5px;
cursor: pointer;
color: white;
background-color:#8393BE;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); /* 그림자 추가 */
transition: background-color 0.3s, color 0.3s, transform 0.3s;
}
import React, { useState } from "react";
//import createSubscribeStyles from './createSubscribe.module.css';
function CreateSubscribe({ isOpen, onClose }){
const [formData, setFormData] = useState({
data: {
},
image: null
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
data: {
...prevData.data,
[name]: value,
},
}));
};
const handleImageChange = (e) => {
const imageFile = e.target.files[0];
setFormData((prevDate) => ({
...prevDate,
image: imageFile
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
if(formData.data.title === ''){
alert('제목을 입력해주세요.');
return;
} else if(formData.data.content === ''){
alert('내용을 입력해주세요.');
return;
}
const jsonDataString = JSON.stringify(formData.data);
try {
const formDataToSend = new FormData();
formDataToSend.append('data', jsonDataString);
formDataToSend.append('image', formData.image);
const response = await fetch('/api/subscribes', {
method: 'POST',
body: formDataToSend,
});
if (response.ok) {
alert('채널이 성공적으로 생성되었습니다.');
window.location.reload();
} else {
alert('채널 생성에 실패하였습니다.');
}
} catch (error) {
console.error('Error:', error);
}
};
return(
<div>
{isOpen && (
<div className={createRecruitStyles.overlay}>
<div className={createRecruitStyles.modal}>
<button type="button" className={createRecruitStyles.x} onClick={onClose}>X</button>
<div className={createRecruitStyles.top}>
<div>채널 생성</div>
</div>
<form onSubmit={handleSubmit}>
<div className={createRecruitStyles.title}>
<label>제목*</label>
<input type="text" name="title" value={formData.data.title} onChange={handleInputChange} placeholder="제목을 입력하세요.(50자 이내)"/>
</div>
<div className={createRecruitStyles.content}>
<label>내용*</label>
<textarea name="content" value={formData.data.content} onChange={handleInputChange} placeholder="내용을 입력하세요."/>
</div>
<div className={createRecruitStyles.image}>
<label>이미지 첨부</label>
<input type="file" name="image" value={formData.data.image} onChange={handleImageChange} />
</div>
<div className={createRecruitStyles.button}>
<button type="submit" className={createRecruitStyles.save}>저장</button>
<button type="button" className={createRecruitStyles.cancel} onClick={onClose}>취소</button>
</div>
</form>
</div>
</div>
)}
</div>
)
}
export default CreateSubscribe;
\ No newline at end of file
import React, { useState } from 'react';
//import subscribeListStyles from '.subscribeList.module.css';
import InfiniteScroll from '../infiniteScroll';
function PageItem(props){
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => {
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
return (
<div className='{subscribeListStyles.subscribe}'>
</div>
)
}
function SubscribeList(){
return(
<div className='{subscribeListStyles.SubscribeList}'>
<InfiniteScroll pagename='subscribes' />
</div>
)
}
export {SubscribeList, PageItem};
\ No newline at end of file
import React ,{useState, useContext} from 'react';
import React, { useState, createContext } from 'react';
import MySchedulerApp from '../components/calendar/calendar';
import Schedule from '../components/mainAside';
import main_styles from './main.module.css';
import Header from '../components/Header';
import { AuthContext } from '../App';
export const AppContext = createContext();
const Main=()=>{
const{ userData } = useContext(AuthContext);
const [count, setCount] = useState(0);
const contextValue = {
count,
setCount,
};
return(
<AppContext.Provider value={contextValue}>
<div className={main_styles.main}>
<div className={main_styles.main_header}>
<Header userData={userData}/></div>
......@@ -23,7 +32,7 @@ const Main=()=>{
</div>
</div>
</div>
</AppContext.Provider>
)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment