diff --git a/src/api/@types/Groups.ts b/src/api/@types/Groups.ts index 6ed308b..0421213 100644 --- a/src/api/@types/Groups.ts +++ b/src/api/@types/Groups.ts @@ -118,3 +118,25 @@ export interface GetRetrospectiveGroupNodes { createdDate: Date; updatedDate: Date; } + +// put - update group boards +export interface PutRetrospectiveGroupBoardRequest { + retrospectiveGroupId: number; + retrospectiveIds: number[]; +} + +export interface PutRetrospectiveGroupBoardResponse { + code: number; + message: string; + data: { + id: number; + title: string; + userId: number; + userName: string; + status: string; + isBookmarked: boolean; + thumbnail: string | null; + description: string; + updateDate: Date; + }; +} diff --git a/src/api/@types/NoticeBoard.ts b/src/api/@types/NoticeBoard.ts new file mode 100644 index 0000000..b46f258 --- /dev/null +++ b/src/api/@types/NoticeBoard.ts @@ -0,0 +1,147 @@ +// get +// 게시글 목록 조회 +export interface GetNoticeListRequest { + page: number; + size: number; +} + +export interface GetNoticeListResponse { + code: number; + message: string; + data: GetNoticeListData; +} + +export interface GetNoticeListData { + posts: GetNoticeListPosts[]; + totalPages: number; +} + +export interface GetNoticeListPosts { + id: number; + title: string; + content: string; + status: 'PUBLISHED'; + createdDate: string; + modifiedDate: string; + views: number; +} + +// 개별 게시글 조회 +export interface GetNoticePostsRequest { + id: number; +} + +export interface GetNoticePostsResponse { + code: number; + message: string; + data: GetNoticePostsData; +} + +export interface GetNoticePostsData { + title: string; + content: string; + status: string; + createdDate: string; + modifiedDate: string; + views: number; +} + +//post +export interface PostNoticeRequest { + title: string; + content: string; + status: 'PUBLISHED' | 'TEMP'; +} + +export interface PostNoticeResponse { + code: number; + message: string; + data: PostNoticeData; +} + +export interface PostNoticeData { + title: string; + content: string; + status: string; + createdDate: string; + modifiedDate: string; + views: number; +} + +export interface PostNoticeTempPostsRequest { + title: string; + content: string; +} + +export interface PostNoticeTempPostsResponse { + code: number; + message: string; + data: PostNoticeTempPostsData; +} + +export interface PostNoticeTempPostsData { + title: string; + content: string; + status: string; + createdDate: string; + modifiedDate: string; + views: number; +} + +export interface PostNoticePresignedURLRequest { + filename: string; + method: 'GET' | 'PUT'; +} + +export interface PostNoticePresignedURLResponse { + code: number; + message: string; + data: PostNoticePresignedURLData; +} + +export interface PostNoticePresignedURLData { + filename: string; + preSignedUrl: string; +} + +// put +export interface PutNoticeRequest { + id: number; + title: string; + content: string; + status: string; +} + +export interface PutNoticeResponse { + code: number; + message: string; + data: PutNoticeData; +} + +export interface PutNoticeData { + title: string; + content: string; + status: string; + createdDate: string; + modifiedDate: string; + views: number; +} + +// delete +export interface DeleteNoticeRequest { + id: number; +} + +// export interface DeleteNoticeResponse { +// code: number; +// } + +export interface NoticeBoardClient { + listGet(request: GetNoticeListRequest): Promise; + postsGet(request: GetNoticePostsRequest): Promise; + create(request: PostNoticeRequest): Promise; + tempSave(request: PostNoticeTempPostsRequest): Promise; + Img(request: PostNoticePresignedURLRequest): Promise; + revise(request: PutNoticeRequest): Promise; + delete(request: DeleteNoticeRequest): Promise; +} diff --git a/src/api/@types/Survey.ts b/src/api/@types/Survey.ts index 3d1b2aa..045189a 100644 --- a/src/api/@types/Survey.ts +++ b/src/api/@types/Survey.ts @@ -6,6 +6,7 @@ export interface PostSurveyRequest { region: string; source: string; purposes: string[] | undefined; + emailConsents: boolean; } export interface PostSurveyResponse { diff --git a/src/api/@types/User.ts b/src/api/@types/User.ts index 2c7a8da..d4c2d26 100644 --- a/src/api/@types/User.ts +++ b/src/api/@types/User.ts @@ -28,3 +28,9 @@ export interface PutUsersResponse { username: string; }; } + +// // post +// export interface PostUserRequest { +// email: string; +// admin: boolean; +// } diff --git a/src/api/@types/Users.ts b/src/api/@types/Users.ts index 2ccf7bf..0629f26 100644 --- a/src/api/@types/Users.ts +++ b/src/api/@types/Users.ts @@ -1,3 +1,4 @@ +// get export interface GetUserResponse { code: number; message: string; @@ -12,8 +13,10 @@ export interface UserData { phone: string; createdDate: string; updatedDate: string; + administrator: boolean; } +// post export interface PostAdminRequest { email: string; admin: boolean; diff --git a/src/api/retroGroupsApi/putBoard.tsx b/src/api/retroGroupsApi/putBoard.tsx new file mode 100644 index 0000000..35aec2c --- /dev/null +++ b/src/api/retroGroupsApi/putBoard.tsx @@ -0,0 +1,14 @@ +import { PutRetrospectiveGroupBoardRequest, PutRetrospectiveGroupBoardResponse } from '../@types/Groups'; +import axiosInstance from '@/api/axiosConfig'; + +export const putBoard = async ({ + retrospectiveGroupId, + ...request +}: PutRetrospectiveGroupBoardRequest): Promise => { + try { + const response = await axiosInstance.put(`/retrospectiveGroups/${retrospectiveGroupId}/boards`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } +}; diff --git a/src/api/retrospectivesApi/getRetrospective.tsx b/src/api/retrospectivesApi/getRetrospective.tsx index c0d027d..3805892 100644 --- a/src/api/retrospectivesApi/getRetrospective.tsx +++ b/src/api/retrospectivesApi/getRetrospective.tsx @@ -17,14 +17,11 @@ export const queryGetRetrospective = async (requestData: GetRetrospectiveRequest params.keyword = keyword; } - console.log(params); const response = await axiosInstance.get('/retrospectives', { params, }); - // console.log('회고 get 성공', response.data); return response.data; } catch (error) { - // console.log('회고 get 실패', error); throw new Error('회고 get 실패'); } }; diff --git a/src/api/services/NoticeBoard.ts b/src/api/services/NoticeBoard.ts new file mode 100644 index 0000000..9b15b4c --- /dev/null +++ b/src/api/services/NoticeBoard.ts @@ -0,0 +1,80 @@ +import { + GetNoticeListRequest, + GetNoticeListResponse, + NoticeBoardClient, + PostNoticeRequest, + PostNoticeResponse, + PostNoticeTempPostsRequest, + PostNoticeTempPostsResponse, + PostNoticePresignedURLRequest, + PostNoticePresignedURLResponse, + PutNoticeResponse, + DeleteNoticeRequest, + GetNoticePostsRequest, + GetNoticePostsResponse, +} from '../@types/NoticeBoard'; +import axiosInstance from '../axiosConfig'; + +const ROUTE = 'admin/notices'; + +export const NoticeServices: NoticeBoardClient = { + listGet: async (request: GetNoticeListRequest): Promise => { + try { + const response = await axiosInstance.get(`/posts`, { params: request }); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + postsGet: async ({ id }: GetNoticePostsRequest): Promise => { + try { + const response = await axiosInstance.get(`/posts/${id}`); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + create: async (request: PostNoticeRequest): Promise => { + try { + const response = await axiosInstance.post(`${ROUTE}/posts`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + tempSave: async (request: PostNoticeTempPostsRequest): Promise => { + try { + const response = await axiosInstance.post(`${ROUTE}/temp-posts`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + Img: async (request: PostNoticePresignedURLRequest): Promise => { + try { + const response = await axiosInstance.post( + `${ROUTE}/files/presigned-url`, + request, + ); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + revise: async ({ id, ...request }): Promise => { + try { + const response = await axiosInstance.put(`${ROUTE}/posts/${id}`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + delete: async ({ id }: DeleteNoticeRequest): Promise => { + try { + const response = await axiosInstance.delete(`${ROUTE}/posts/${id}`); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, +}; diff --git a/src/api/services/User.ts b/src/api/services/User.ts index bb75abd..94aaf24 100644 --- a/src/api/services/User.ts +++ b/src/api/services/User.ts @@ -12,7 +12,7 @@ export const UserServices: UserClient = { throw new Error(error as string); } }, - adminPost: async (request: PostAdminRequest) => { + adminPost: async (request: PostAdminRequest): Promise => { try { const response = await axiosInstance.post(`/${ROUTE}/me/admin-status`, request); return response.data; diff --git a/src/components/Main/Contact.tsx b/src/components/Main/Contact.tsx index 670998a..6a95285 100644 --- a/src/components/Main/Contact.tsx +++ b/src/components/Main/Contact.tsx @@ -69,7 +69,11 @@ const Contact: React.FC = () => { Email - + diff --git a/src/components/RetroList/ContentsList.tsx b/src/components/RetroList/ContentsList.tsx index 04ef870..d361f8f 100644 --- a/src/components/RetroList/ContentsList.tsx +++ b/src/components/RetroList/ContentsList.tsx @@ -73,11 +73,9 @@ const ContentList: React.FC = ({ data, viewMode, searchData, s const requestData: PatchRetrospectiveRequest = { retrospectiveId: itemId, }; - const response = await patchRetrospective(requestData); - console.log('북마크 patch 요청 완료', response); + await patchRetrospective(requestData); setBookmarkUpdate(prev => !prev); } catch (error) { - // console.error('북마크 patch 요청 실패:', error); toast.error(error); } }; diff --git a/src/components/notice/NoticeBoard.tsx b/src/components/notice/NoticeBoard.tsx index 477dd03..644b7f2 100644 --- a/src/components/notice/NoticeBoard.tsx +++ b/src/components/notice/NoticeBoard.tsx @@ -1,33 +1,78 @@ -import { IoIosArrowBack } from 'react-icons/io'; -import { IoIosArrowForward } from 'react-icons/io'; +import { useEffect, useState } from 'react'; +// import getUser from '@/api/imageApi/getUser'; +// import { GetUsersResponse } from '@/api/@types/User'; +import { useNavigate } from 'react-router-dom'; +import { NoticeBoardContents } from './NoticeBoardContents'; +import { NoticePagination } from './NoticePagination'; +import { GetNoticeListPosts } from '@/api/@types/NoticeBoard'; +import { UserData } from '@/api/@types/Users'; +import { NoticeServices } from '@/api/services/NoticeBoard'; +import { UserServices } from '@/api/services/User'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/notice/noticeBoard.style'; -export const NoticeBoardContents = () => { - return ( - <> - -

1

-

- 일반공지, 공결 신청시 유의사항(공결 신청 시 반드시 확인) -

-

2024-02-28

-

26

-
- - -

2

-

- 회고, 어떻게 해야 잘할 수 있을까? -

-

2024-02-28

-

15

-
- - - ); -}; - export const NoticeBoard = () => { + const toast = useCustomToast(); + const navigate = useNavigate(); + const [user, setUser] = useState(); + const [NoticeList, setNoticeList] = useState([]); + + // 관리자 권한 부여 api + // import { UserServices } from '@/api/services/User'; + // const handleNoticeAdmin = async () => { + // try { + // await UserServices.adminPost({ + // email: 'binny1204@naver.com', + // admin: true, + // }); + // toast.success('관리자 권한이 부여되었습니다.'); + // } catch (e) { + // toast.error(e); + // } + // }; + + // 유저 정보 조회 + // const fetchUserData = async () => { + // try { + // const response = await getUser(); + // console.log('유저 정보', response); + // setUserData(response); + // } catch (error) { + // console.error('에러', error); + // toast.error(error); + // } + // }; + const fetchUser = async () => { + try { + const data = await UserServices.get(); + setUser(data.data); + console.log(data.data); + } catch (error) { + toast.error(error); + } + }; + + // 게시글 목록 조회 api + // const [page, setPage] = useState(1); + // const [size, setSize] = useState(10); + const fetchNotice = async () => { + try { + const data = await NoticeServices.listGet({ page: 1, size: 10 }); + setNoticeList(data.data.posts); + } catch (error) { + toast.error(error); + } + }; + + useEffect(() => { + fetchUser(); + fetchNotice(); + }, []); + if (!user) return; + + const handleNoticeWriteButton = () => { + navigate('/noticeWrite'); + }; return (
{ height: 'auto', display: 'grid', gridTemplateColumns: '1fr 9fr 1fr', - marginTop: '800px', + margin: ' 300px 0', }} > 게시판 + + {/* 게시판 제목 */} -

번호

-

제목

-

작성일

-

조회수

+ 번호 + 제목 + 작성일 + 조회수 - {/* 게시판 내용 */}
- + {/* 게시판 내용: NoticeList ExData.data.posts*/} + {NoticeList.map((posts, index) => ( + + ))}
+ + {/* 글쓰기 버튼 */}
- 글쓰기 + 글쓰기
- - -

1

- -
+ + {/* 게시판 목록 리모컨 */} +
); diff --git a/src/components/notice/NoticeBoardContents.tsx b/src/components/notice/NoticeBoardContents.tsx new file mode 100644 index 0000000..e376f80 --- /dev/null +++ b/src/components/notice/NoticeBoardContents.tsx @@ -0,0 +1,51 @@ +import { useNavigate } from 'react-router-dom'; +import { GetNoticeListPosts } from '@/api/@types/NoticeBoard'; +import * as S from '@/styles/notice/noticeBoard.style'; + +interface NoticeBoardContentsProps { + posts: GetNoticeListPosts; + index: number; +} + +// 게시글 시간 포멧 만들기 +export const convertToLocalTime = (dateString: string | number | Date) => { + const date = new Date(dateString); + const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + + const year = localTime.getFullYear(); + const month = String(localTime.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 1을 더해줌 + const day = String(localTime.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; // "YY-MM-DD" 형식으로 반환 +}; + +export const NoticeBoardContents = ({ posts, index }: NoticeBoardContentsProps) => { + const navigate = useNavigate(); + + // 개별 게시글 페이지로 이동 + const handleMoveNoticeShow = () => { + navigate(`/noticeShow?id=${posts.id}`); + }; + return ( + <> + + {/* 번호 */} +

{index}

+ + {/* 제목 */} +
+

+ {posts.title} +

+
+ + {/* 작성일 */} +

{convertToLocalTime(posts.createdDate)}

+ + {/* 조회수 */} +

{posts.views}

+
+ + + ); +}; diff --git a/src/components/notice/NoticeMenuBar.tsx b/src/components/notice/NoticeMenuBar.tsx index b400968..6c07443 100644 --- a/src/components/notice/NoticeMenuBar.tsx +++ b/src/components/notice/NoticeMenuBar.tsx @@ -1,22 +1,104 @@ +import { RiImageAddLine } from 'react-icons/ri'; +import { useLocation } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import { NoticeServices } from '@/api/services/NoticeBoard'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/notice/noticeWrite.style'; -export const NoticeMenuBar = () => { - const TemporarySaveCount = 3; +interface NoticeMenuBarProps { + title: string; + content: string; +} + +export const NoticeMenuBar = ({ title, content }: NoticeMenuBarProps) => { + const TemporarySaveCount = 88; + + const toast = useCustomToast(); + const navigate = useNavigate(); + + // url의 쿼리 문자열에서 게시물 Id 값 가져오기 + const { search } = useLocation(); + const query = search.split(/[=,&]/); + const NoticeShowId = Number(query[1]); + + // 게시글 작성 api 연결 + const handleNoticeWrite = async () => { + try { + await NoticeServices.create({ + title: title, + content: content, + status: 'PUBLISHED', + }); + toast.success('공지사항이 추가되었습니다.'); + navigate('/'); + } catch (e) { + toast.error(e); + } + }; + + // 게시글 수정 api 연결 + const handleNoticeRevise = async () => { + try { + await NoticeServices.revise({ + id: NoticeShowId, + title: title, + content: content, + status: 'PUBLISHED', + }); + toast.success('공지사항이 수정되었습니다.'); + navigate(`/noticeShow?id=${NoticeShowId}`); + } catch (e) { + toast.error(e); + } + }; + + // 게시물 작성 취소 + const handleCancel = () => { + navigate('/'); + }; + + // 게시물 이미지 api 연결 + const handleNoticeImg = () => { + console.log('게시물 이미지를 등록합니다.'); + console.log(NoticeShowId); + }; + return ( <> {/* 메뉴바 */} - 공지사항 -
-
- - 저장 | - {TemporarySaveCount} - - 저장 - 취소 + 공지사항{' '} + + {isNaN(NoticeShowId) ? ( + <> + {/* 게시물 임시저장 */} + {`저장 | ${TemporarySaveCount}`} + + {/* 게시물 저장 */} + 저장 + + ) : ( + <> + {/* 게시물 수정 */} + 수정 + + )} + {/* 게시물 작성 취소 */} + 취소 + + {/* 게시물 객체 추가란*/} + + +
+ {/* 게시물 사진 추가하기 */} + + + + + 사진 +
-
+ ); diff --git a/src/components/notice/NoticePagination.tsx b/src/components/notice/NoticePagination.tsx new file mode 100644 index 0000000..388cd0f --- /dev/null +++ b/src/components/notice/NoticePagination.tsx @@ -0,0 +1,25 @@ +import { IoIosArrowBack } from 'react-icons/io'; +import { IoIosArrowForward } from 'react-icons/io'; +import * as S from '@/styles/notice/noticeBoard.style'; + +export const NoticePagination = () => { + const NoticePageNumber = [1, 2, 3]; + return ( + <> + + {/* 이전 화살표 */} + + + + + {/* 게시판 페이지 */} +

{NoticePageNumber}

+ + {/* 다음 화살표 */} + + + +
+ + ); +}; diff --git a/src/components/notice/NoticeShowFooter.tsx b/src/components/notice/NoticeShowFooter.tsx index bfdfdcc..334cdb5 100644 --- a/src/components/notice/NoticeShowFooter.tsx +++ b/src/components/notice/NoticeShowFooter.tsx @@ -1,26 +1,54 @@ import { IoIosArrowDown } from 'react-icons/io'; import { IoIosArrowUp } from 'react-icons/io'; +import { useNavigate } from 'react-router-dom'; import * as S from '@/styles/notice/noticeShow.style'; -export const NoticeShowFooter = () => { - const PreviousNotice = '이전 글'; - const NextNotice = '다음 글'; +interface NoticeShowFooterProps { + id: number; +} + +export const NoticeShowFooter = ({ id }: NoticeShowFooterProps) => { return ( <> + {/* 이전 글 */} - - - - {NextNotice} - - {PreviousNotice} + {/* 다음 글 */} - - - - {NextNotice} + ); }; + +interface NoticeShowMoveProps { + direction: 'up' | 'down'; + id: number; +} + +export const NoticeShowMove = ({ direction, id }: NoticeShowMoveProps) => { + const navigate = useNavigate(); + + const { icon, title } = + direction === 'up' + ? { icon: , title: '이전 글' } + : { icon: , title: '다음 글' }; + + // 게시물로 이동하기 + const noticeMove = direction === 'up' ? id - 1 : id + 1; + const handleNoticeShowMove = () => { + navigate(`/noticeShow?id=${noticeMove}`); + }; + return ( + <> + + {/* 게시물 이동 화살표 */} + {icon} + + {/* 이동할 게시물의 제목 */} + {title} + + + + ); +}; diff --git a/src/components/notice/NoticeShowHeader.tsx b/src/components/notice/NoticeShowHeader.tsx index 74df37a..cef816e 100644 --- a/src/components/notice/NoticeShowHeader.tsx +++ b/src/components/notice/NoticeShowHeader.tsx @@ -1,26 +1,72 @@ +import { useNavigate } from 'react-router-dom'; +import { NoticeServices } from '@/api/services/NoticeBoard'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/notice/noticeShow.style'; -export const NoticeShowHeader = () => { - const NoticeShowTitle = '회고, 어떻게 해야 잘할 수 있을까?'; - const NoticeWriter = '조용복'; - const NoticeDate = '2024.03.09'; - const NoticeView = 78; +interface NoticeShowHeaderProps { + id: number; + title: string; + date: string; + views: number; +} +export const NoticeShowHeader = ({ id, title, date, views }: NoticeShowHeaderProps) => { + const navigate = useNavigate(); + const toast = useCustomToast(); + const NoticeWriter = '관리자'; + + // 게시글 시간 포멧 만들기 + const convertToLocalTime = (dateString: string | number | Date) => { + const date = new Date(dateString); + const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + + const year = localTime.getFullYear(); + const month = String(localTime.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 1을 더해줌 + const day = String(localTime.getDate()).padStart(2, '0'); + + return `${year}.${month}.${day}`; // "YY-MM-DD" 형식으로 반환 + }; + + // 게시물 삭제 api 연결 + const handleDeleteNotice = async () => { + try { + await NoticeServices.delete({ id: id }); + setTimeout(() => { + navigate('/'); + toast.info('게시글이 삭제되었습니다.'); + }, 1000); // 1초(1000밀리초) 후에 함수를 실행 + } catch (e) { + toast.error(e); + } + }; + + // 게시물 수정 api 연결 + const handleReviseNotice = () => { + navigate(`/noticeWrite?id=${id}`); + }; return ( <> -
-

공지사항

- {NoticeShowTitle} - - {NoticeWriter} | - {NoticeDate} | - 조회수 {NoticeView} - -
- - +

공지사항

+ + {/* 게시물 제목 */} + {title} + + {/* 게시물 정보 */} + + {`${NoticeWriter} | ${convertToLocalTime(date)} | 조회수 ${views}`} + + + {/* 게시물 버튼 */} + + {/* 게시물 수정 버튼 */} + 수정 + + {/* 게시물 삭제 버튼 */} + 삭제 +
+ ); }; diff --git a/src/components/notice/NoticeShowMain.tsx b/src/components/notice/NoticeShowMain.tsx new file mode 100644 index 0000000..9418947 --- /dev/null +++ b/src/components/notice/NoticeShowMain.tsx @@ -0,0 +1,23 @@ +import * as S from '@/styles/notice/noticeShow.style'; + +interface NoticeShowMainProps { + content: string; +} + +export const NoticeShowMain = ({ content }: NoticeShowMainProps) => { + return ( + <> + +
+ {/* 게시물 텍스트 */} + {content} + + {/* 게시물 사진 */} + + + +
+
+ + ); +}; diff --git a/src/components/notice/NoticeWriteBox.tsx b/src/components/notice/NoticeWriteBox.tsx index 2d21a22..1459ac1 100644 --- a/src/components/notice/NoticeWriteBox.tsx +++ b/src/components/notice/NoticeWriteBox.tsx @@ -1,13 +1,40 @@ import * as S from '@/styles/notice/noticeWrite.style'; -export const NoticeWriteBox = () => { +interface NoticeWriteBoxProps { + setTitle: (title: string) => void; + setContent: (content: string) => void; +} + +export const NoticeWriteBox = ({ setTitle, setContent }: NoticeWriteBoxProps) => { + // api에 제목 전달하기 + const handleTitleChange = (event: React.ChangeEvent) => { + setTitle(event.target.value); + event.target.style.height = 'auto'; + event.target.style.height = `${event.target.scrollHeight}px`; + }; + + // api에 본문 전달하기 + const handleContentChange = (event: React.ChangeEvent) => { + setContent(event.target.value); + event.target.style.height = 'auto'; + event.target.style.height = `${event.target.scrollHeight}px`; + }; + return ( <> - + {/* 제목 */} + - + + {/* 본문 */} + + + {/* 이미지 */} + {/* + + */} diff --git a/src/components/projectRetro/BoardModal.tsx b/src/components/projectRetro/BoardModal.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/projectRetro/GroupBoardList.tsx b/src/components/projectRetro/GroupBoardList.tsx index 6299d43..6a7b361 100644 --- a/src/components/projectRetro/GroupBoardList.tsx +++ b/src/components/projectRetro/GroupBoardList.tsx @@ -1,14 +1,19 @@ import { useEffect, useState } from 'react'; import { CgTimelapse } from 'react-icons/cg'; // ing +import { CiStar } from 'react-icons/ci'; +import { FaStar } from 'react-icons/fa'; import { FaRegCircleCheck } from 'react-icons/fa6'; import { IoMdPerson } from 'react-icons/io'; import { MdPeople } from 'react-icons/md'; +import { RxCounterClockwiseClock } from 'react-icons/rx'; //before +import { useNavigate } from 'react-router-dom'; import { Spinner } from '@chakra-ui/react'; import { GetRetrospectiveGroupNodes } from '@/api/@types/Groups'; import { UserData } from '@/api/@types/Users'; import postImageToS3 from '@/api/imageApi/postImageToS3'; import { UserServices } from '@/api/services/User'; import Thumbnail from '@/assets/Thumbnail.png'; +import ReviseModal from '@/components/RetroList/Modal'; import { useCustomToast } from '@/hooks/useCustomToast'; import * as T from '@/styles/RetroList/ContentsList.styles'; import * as S from '@/styles/projectRetro/GroupBoardList.styles'; @@ -27,18 +32,28 @@ export const convertToLocalTime = (dateString: string | number | Date) => { hour: 'numeric', minute: 'numeric', }; - return localTime.toLocaleString(undefined, options); // 로컬 타임존으로 변환하여 문자열로 반환 + return localTime.toLocaleString(undefined, options); }; const GroupBoardList: React.FC = ({ data }) => { + const navigate = useNavigate(); const toast = useCustomToast(); const [user, setUser] = useState(); const [image, setImage] = useState<{ [key: number]: string }>({}); + const [openReviseModalId, setOpenReviseModalId] = useState(null); const [isLoading, setIsLoading] = useState(true); const handleImageLoad = () => { setIsLoading(false); }; + const openModalForItem = (itemId: number) => { + setOpenReviseModalId(itemId); + }; + + const closeModalForItem = () => { + setOpenReviseModalId(null); + }; + const fetchUser = async () => { try { const data = await UserServices.get(); @@ -49,7 +64,7 @@ const GroupBoardList: React.FC = ({ data }) => { }; useEffect(() => { if (data) { - const filtered = data.filter(item => item.thumbnail !== null); // thumbnail이 null인 항목 필터링 + const filtered = data.filter(item => item.thumbnail !== null); fetchUser(); const fetchThumbnailsData = async (item: GetRetrospectiveGroupNodes) => { @@ -80,7 +95,11 @@ const GroupBoardList: React.FC = ({ data }) => { return ( <> - {data && data.length !== 0 ? ( + {isLoading ? ( + + + + ) : data && data.length !== 0 ? ( {data?.map(item => ( @@ -95,7 +114,9 @@ const GroupBoardList: React.FC = ({ data }) => {
{item.teamId && } {!item.teamId && } - {item.title}{' '} + navigate(`/sections?retrospectiveId=${item.id}&teamId=${item.teamId}`)}> + {item.title} +
= ({ data }) => { justifyItems: 'center', }} > - {item.isBookmarked ? : } + {item.isBookmarked ? ( + + ) : ( + + )} + { + if (user && user.userId === item.userId) { + navigate(`/revise?retrospectiveId=${item.id}&teamId=${item.teamId}`); + } else { + openModalForItem(item.id); + } + }} + /> +
{item.username} @@ -117,6 +152,12 @@ const GroupBoardList: React.FC = ({ data }) => { ? `${convertToLocalTime(item.updatedDate)} 수정` : convertToLocalTime(item.createdDate)} + {item.status === 'NOT_STARTED' && ( + + )} {item.status === 'IN_PROGRESS' && ( )} diff --git a/src/components/projectRetro/ManageModal.tsx b/src/components/projectRetro/ManageModal.tsx new file mode 100644 index 0000000..387f701 --- /dev/null +++ b/src/components/projectRetro/ManageModal.tsx @@ -0,0 +1,165 @@ +import React, { useState, useEffect } from 'react'; +import { CgTimelapse } from 'react-icons/cg'; // ing +import { CiStar } from 'react-icons/ci'; +import { FaStar } from 'react-icons/fa'; +import { FaRegCircleCheck } from 'react-icons/fa6'; // done +import { IoMdClose } from 'react-icons/io'; +import { IoMdPerson } from 'react-icons/io'; +import { MdPeople } from 'react-icons/md'; +import { RxCounterClockwiseClock } from 'react-icons/rx'; //before +import { GetRetrospectiveGroupNodes } from '@/api/@types/Groups'; +import { GetRetrospectiveData } from '@/api/@types/Retrospectives'; +import { UserData } from '@/api/@types/Users'; +import { putBoard } from '@/api/retroGroupsApi/putBoard'; +import { queryGetRetrospective } from '@/api/retrospectivesApi/getRetrospective'; +import { UserServices } from '@/api/services/User'; +import { useCustomToast } from '@/hooks/useCustomToast'; +import * as S from '@/styles/projectRetro/ManageModal.styles'; +import * as T from '@/styles/projectRetro/Modal.styles'; + +interface ManageModalProps { + groupId: number; + data: GetRetrospectiveGroupNodes[] | null; + isClose: () => void; +} + +export const convertToLocalTime = (dateString: string | number | Date) => { + const date = new Date(dateString); + const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + const options: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }; + return localTime.toLocaleString(undefined, options); +}; + +const ManageModal: React.FC = ({ groupId, data, isClose }) => { + const toast = useCustomToast(); + const [user, setUser] = useState(); + const [retro, setRetro] = useState(); + const [requestData, setRequestData] = useState([]); + const [checkedRetroId, setCheckedRetroId] = useState([]); // 그룹에 추가되어 있는 회고 id + + const fetchUser = async () => { + try { + const data = await UserServices.get(); + setUser(data.data); + } catch (error) { + toast.error(error); + } + }; + + const fetchRetrospective = async () => { + try { + const data = await queryGetRetrospective({ + page: 0, + size: 999, + order: 'NEWEST', + status: 'ALL', + keyword: '', + isBookmarked: false, + }); + setRetro(data.data); + } catch (e) { + toast.error('회고 불러오기에 실패했습니다.'); + } + }; + + useEffect(() => { + fetchUser(); + fetchRetrospective(); + }, [retro?.totalCount]); + + const handlePutBoard = async () => { + if (requestData.length === 0) { + isClose(); + } else { + try { + await putBoard({ retrospectiveGroupId: groupId, retrospectiveIds: requestData }); + toast.info('그룹 회고가 정상적으로 추가되었습니다.'); + isClose(); + window.location.reload(); + } catch (e) { + toast.error('그룹 회고 추가에 실패했습니다.'); + } + } + }; + + const handleInputCheck = (id: number) => (event: React.ChangeEvent) => { + if (event.target.checked) { + setRequestData(prev => [...prev, id]); + } else { + setRequestData(prev => prev.filter(retroId => retroId !== id)); + } + }; + + useEffect(() => { + if (data) { + const ids = data.map(item => item.id); + setCheckedRetroId(ids); + } + }, [data]); + + return ( + <> + + + + + 프로젝트로 회고 관리하기 + + {retro && + retro.nodes.map(item => ( + + + {checkedRetroId.indexOf(item.id) < 0 ? ( // 이미 추가된 회고인지 판단 + + ) : ( + + )} + {/* */} + + {item.teamId ? : } + {item.title} + + {item.username} + {user?.userId === item.userId && 본인} + + {convertToLocalTime(item.updatedDate)} + + {item.isBookmarked ? : } + + + {item.status === 'NOT_STARTED' ? ( + + ) : item.status === 'IN_PROGRESS' ? ( + + ) : ( + + )} + + + ))} + + 적용하기 + + + + + ); +}; + +export default ManageModal; diff --git a/src/components/survey/EmailConsents.tsx b/src/components/survey/EmailConsents.tsx new file mode 100644 index 0000000..67980a4 --- /dev/null +++ b/src/components/survey/EmailConsents.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react'; +import { Radio, RadioGroup, Stack, Text } from '@chakra-ui/react'; +import * as S from '@/styles/survey/EmailConsents.style'; + +interface EmailConsentsProps { + onEmailConsentsChange: (emailConsents: boolean) => void; +} + +export const EmailConsents: React.FC = ({ onEmailConsentsChange }) => { + const [emailConsents, setEmailConsents] = useState(''); + const handleCityChange = (event: React.ChangeEvent) => { + const emailConsents = event.target.value; + const emailConsentsChoose = emailConsents === 'false' ? false : true; + setEmailConsents(emailConsents); + onEmailConsentsChange(emailConsentsChoose); + }; + + return ( + <> + + 이메일 마케팅 수신 동의 (선택) + Past Forward 관련 뉴스, 업데이트 및 회고 알림 등을 포함합니다. + + + + + 수신함 + + + 수신하지 않음 + + + + + + + ); +}; diff --git a/src/pages/GroupBoardPage.tsx b/src/pages/GroupBoardPage.tsx index 5a69fed..805e6b7 100644 --- a/src/pages/GroupBoardPage.tsx +++ b/src/pages/GroupBoardPage.tsx @@ -8,6 +8,7 @@ import { } from '@/api/@types/Groups'; import { GetRetrospectiveGroup } from '@/api/retroGroupsApi/getGroup'; import GroupBoardList from '@/components/projectRetro/GroupBoardList'; +import ManageModal from '@/components/projectRetro/ManageModal'; import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/projectRetro/GroupBoard.styles'; @@ -19,6 +20,7 @@ const GroupBoard = () => { const [groupData, setGroupData] = useState(null); const [groupBoardData, setGroupBoardData] = useState(null); + const [isOpen, setIsOpen] = useState(false); useEffect(() => { const fetchGroupBoard = async () => { @@ -28,7 +30,6 @@ const GroupBoard = () => { }; const responseData = await GetRetrospectiveGroup(requestData); setGroupData(responseData.data); - console.log('단일 그룹 내 회고 불러오기', responseData); } catch (error) { toast.error('단일 그룹 내 회고 불러오기에 실패했습니다.'); } @@ -43,18 +44,21 @@ const GroupBoard = () => { }, [groupData]); return ( - - - - - {groupData?.title} - - - - 회고 관리하기 - - - + <> + {isOpen && setIsOpen(false)} data={groupBoardData} />} + + + + + {groupData?.title} + + + + setIsOpen(true)}>회고 관리하기 + + + + ); }; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 1fd9348..292f310 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -22,12 +22,12 @@ const App = () => { - - - + + +
); }; diff --git a/src/pages/SectionPage.tsx b/src/pages/SectionPage.tsx index 2fa8795..9636272 100644 --- a/src/pages/SectionPage.tsx +++ b/src/pages/SectionPage.tsx @@ -81,7 +81,7 @@ const RetroTeamPage = () => { {retro && } - <S.SectionBox> + <S.SectionBox column={4}> {/* <Flex flexDirection="column" margin="0 auto"> <Flex> */} {template @@ -94,19 +94,21 @@ const RetroTeamPage = () => { taskCount={section.filter(data => data.sectionName === title.name).length} /> <AddTask template={title.id} retrospectiveId={retro?.retrospectiveId} setRendering={setRendering} /> - {section - .filter(key => key.sectionName === title.name) - .map(section => ( - <TeamTask - section={section} - setRendering={setRendering} - teamId={teamId ?? null} - imageURL={section.thumbnail} - user={user} - fetchSection={fetchSection} - key={section.sectionId} - /> - ))} + <div style={{ height: 'auto', display: 'flex', flexDirection: 'column-reverse' }}> + {section + .filter(key => key.sectionName === title.name) + .map(section => ( + <TeamTask + section={section} + setRendering={setRendering} + teamId={teamId ?? null} + imageURL={section.thumbnail} + user={user} + fetchSection={fetchSection} + key={section.sectionId} + /> + ))} + </div> </S.FrameStyle> </> )) diff --git a/src/pages/SurveyPage.tsx b/src/pages/SurveyPage.tsx index 7de2322..209f54f 100644 --- a/src/pages/SurveyPage.tsx +++ b/src/pages/SurveyPage.tsx @@ -1,16 +1,20 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Text, Button, Divider } from '@chakra-ui/react'; import { PostSurvey } from '@/api/survey/postSurvey'; import AgeInput from '@/components/survey/AgeInput'; import CityRadio from '@/components/survey/CityRadio'; +import { EmailConsents } from '@/components/survey/EmailConsents'; import GenderRadio from '@/components/survey/GenderRadio'; import JobSelect from '@/components/survey/JobSelect'; import PathRadio from '@/components/survey/PathRadio'; import PurposeCheckbox from '@/components/survey/PurposeCheckbox'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/survey/SurveyPage.style'; const SurveyPage: React.FC = () => { + const toast = useCustomToast(); + // Survey 페이지 작업 시 주석 처리하기 useEffect(() => { localStorage.setItem('surveyVisited', 'true'); @@ -37,6 +41,8 @@ const SurveyPage: React.FC = () => { path, '/목적은(복수선택):', purpose, + '이메일 수신 동의 여부: ', + emailConsents, ); const SurveyRequest = await PostSurvey({ age: numAge, @@ -45,12 +51,14 @@ const SurveyPage: React.FC = () => { region: city, source: path, purposes: purpose, + emailConsents: emailConsents, }); console.log('설문조사 전송 성공', SurveyRequest); alert('설문조사가 전송되었습니다.'); // 회고 작성 페이지로 이동 navigate('/create'); } catch (error) { + toast.error(error); console.error('실패입니다.', error); } }; @@ -61,6 +69,7 @@ const SurveyPage: React.FC = () => { const [city, setCity] = useState<string>('서울'); const [path, setPath] = useState<string>('블라인드'); const [purpose, setPurpose] = useState<string[]>(); + const [emailConsents, setEmailConsents] = useState<boolean>(false); const handleAgeChange = (age: string) => { setAge(age); @@ -82,6 +91,10 @@ const SurveyPage: React.FC = () => { setPurpose(purpose); }; + const handleEmailConsentsChange = (emailConsents: boolean) => { + setEmailConsents(emailConsents); + }; + return ( <> <S.Background> @@ -106,6 +119,7 @@ const SurveyPage: React.FC = () => { <PathRadio onPathChange={handlePathChange} /> <Divider /> <PurposeCheckbox onPurposeChange={handlePurposeChange} /> + <EmailConsents onEmailConsentsChange={handleEmailConsentsChange} /> <Button onClick={handleSurveyButtonClick} colorScheme="brand" diff --git a/src/pages/notice/NoticeShowPage.tsx b/src/pages/notice/NoticeShowPage.tsx index 64a9b12..f0061c1 100644 --- a/src/pages/notice/NoticeShowPage.tsx +++ b/src/pages/notice/NoticeShowPage.tsx @@ -1,14 +1,61 @@ +import { useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { GetNoticePostsData } from '@/api/@types/NoticeBoard'; +import { NoticeServices } from '@/api/services/NoticeBoard'; import { NoticeShowFooter } from '@/components/notice/NoticeShowFooter'; import { NoticeShowHeader } from '@/components/notice/NoticeShowHeader'; +import { NoticeShowMain } from '@/components/notice/NoticeShowMain'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/notice/noticeShow.style'; export const NoticeShowPage = () => { + const toast = useCustomToast(); + + // url의 쿼리 문자열에서 게시물 Id 값 가져오기 + const { search } = useLocation(); + const query = search.split(/[=,&]/); + const NoticeShowId = Number(query[1]); + + // 개별 게시물 조회 + const [notice, setNotice] = useState<GetNoticePostsData>(); + const fetchNoticeShow = async () => { + try { + const data = await NoticeServices.postsGet({ id: NoticeShowId }); + setNotice(data.data); + console.log(data.data); + } catch (e) { + console.log(NoticeShowId); + toast.error(e); + } + }; + useEffect(() => { + fetchNoticeShow(); + }, [NoticeShowId]); + + // useEffect(() => { + // if (notice) { + // console.log(notice.title); + // } + // }, [notice]); + return ( <> <S.NoticeShowContainer> <div style={{ gridColumn: '2' }}> - <NoticeShowHeader></NoticeShowHeader> - <NoticeShowFooter></NoticeShowFooter> + {notice ? ( + <> + {/* 게시물 헤더 */} + <NoticeShowHeader id={NoticeShowId} title={notice.title} date={notice.createdDate} views={notice.views} /> + + {/* 게시물 내용 */} + <NoticeShowMain content={notice.content} /> + + {/* 다른 게시물로 이동 */} + <NoticeShowFooter id={NoticeShowId} /> + </> + ) : ( + <p>Loading...</p> + )} </div> </S.NoticeShowContainer> </> diff --git a/src/pages/notice/NoticeWritePage.tsx b/src/pages/notice/NoticeWritePage.tsx index 466190b..a4c8df5 100644 --- a/src/pages/notice/NoticeWritePage.tsx +++ b/src/pages/notice/NoticeWritePage.tsx @@ -1,16 +1,20 @@ +import { useState } from 'react'; import { NoticeMenuBar } from '@/components/notice/NoticeMenuBar'; import { NoticeWriteBox } from '@/components/notice/NoticeWriteBox'; import * as S from '@/styles/notice/noticeWrite.style'; export const NoticeWritePage = () => { + const [title, setTitle] = useState<string>(''); + const [content, setContent] = useState<string>(''); + return ( <> {/* 메뉴바 */} - <NoticeMenuBar></NoticeMenuBar> + <NoticeMenuBar title={title} content={content}></NoticeMenuBar> {/* 바탕화면 */} <S.NoticeWriteContainer> {/* 입력창 */} - <NoticeWriteBox></NoticeWriteBox> + <NoticeWriteBox setTitle={setTitle} setContent={setContent}></NoticeWriteBox> </S.NoticeWriteContainer> ; </> diff --git a/src/styles/notice/noticeBoard.style.ts b/src/styles/notice/noticeBoard.style.ts index eebf67f..deb1b03 100644 --- a/src/styles/notice/noticeBoard.style.ts +++ b/src/styles/notice/noticeBoard.style.ts @@ -11,6 +11,7 @@ export const NoticeBoardTitle = styled.p` font-weight: 400; color: #000000; line-height: 48px; + margin-bottom: 114px; `; export const NoticeBoardBox = styled.div` @@ -24,8 +25,16 @@ export const NoticeBoardBox = styled.div` border-radius: 5px; display: grid; grid-template-columns: 1fr 5fr 2fr 1fr; - grid-template-rows: 1fr 5fr; + grid-template-rows: 1fr 10fr; grid-auto-rows: minmax(76px, auto); + margin-bottom: 38px; +`; + +export const NoticeBoardContentsTitle = styled.p` + height: auto; + min-height: 76px; + display: grid; + align-items: center; `; export const NoticeBoardContentsBox = styled.div` @@ -42,11 +51,13 @@ export const NoticeBoardContentsLine = styled.div` export const NoticeBoardContentsStyle = styled.div` width: 100%; - height: minmax(76px, auto); + min-height: 76px; + height: auto; font-size: 20px; font-weight: 500; color: #25213b; text-align: center; + align-items: center; display: grid; grid-template-columns: 1fr 5fr 2fr 1fr; `; @@ -58,14 +69,28 @@ export const NoticeWriteButton = styled.button` font-weight: 700; color: #486c8d; line-height: 22px; + margin-bottom: 152px; &:hover { cursor: pointer; } `; -export const NoticeMoveArrow = styled.div` +export const NoticePaginationContainer = styled.div` width: auto; height: 39px; display: flex; justify-content: center; `; + +export const NoticePaginationButton = styled.button` + width: 20px; + height: 20px; + line-height: 20px; + display: grid; + justify-content: center; + /* background-color: red; */ + &:hover { + cursor: pointer; + } + margin: 0px 20px; +`; diff --git a/src/styles/notice/noticeShow.style.ts b/src/styles/notice/noticeShow.style.ts index 9a9995e..4760d51 100644 --- a/src/styles/notice/noticeShow.style.ts +++ b/src/styles/notice/noticeShow.style.ts @@ -9,6 +9,9 @@ export const NoticeShowContainer = styled.div` export const NoticeShowHeaderStyle = styled.div` width: 100%; height: auto; + text-align: center; + display: grid; + justify-items: center; `; export const NoticeShowTitle = styled.p` @@ -25,25 +28,103 @@ export const NoticeShowInformation = styled.p` margin-bottom: 20px; `; +export const NoticeShowButtonContainer = styled.div` + width: 150px; + height: 50px; + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-items: center; + align-items: center; + margin-bottom: 15px; +`; + +export const NoticeDeleteButton = styled.button` + width: 50px; + height: 30px; + font-size: 20px; + font-weight: 500; + color: white; + border-radius: 5px; + background-color: red; +`; + +export const NoticeReviseButton = styled(NoticeDeleteButton)` + background-color: #cfcfcf; +`; + +export const NoticeShowMainStyle = styled.div` + width: 100%; + height: auto; + display: grid; + grid-template-columns: 1fr 15fr 1fr; +`; + +export const NoticeShowText = styled.div` + width: 100%; + height: auto; + margin-top: 30px; +`; + +export const NoticeShowImgContainer = styled.div` + width: 100%; + height: auto; +`; + +export const NoticeShowImg = styled.div` + width: auto; + height: auto; + min-width: 300px; + min-height: 300px; + border-radius: 5px; +`; + export const NoticeShowHeaderLine = styled.div` width: 100%; border-bottom: 1px solid rgba(0, 0, 0, 0.1); `; export const NoticeShowFooterStyle = styled.div` + width: auto; + height: auto; + margin-bottom: 50px; +`; + +export const NoticeShowFooterLine = styled.div` + width: 100%; + border-top: 1px solid rgba(0, 0, 0, 0.1); +`; + +export const NoticeShowMoveStyle = styled.div` + width: 100%; + height: auto; + min-height: 80px; display: grid; - grid-template-columns: 1fr 9fr; - grid-template-rows: repeat(2, 1fr); + grid-template-columns: 1fr 18fr; `; -export const OtherNoticeArrow = styled.div` - display: block; +export const NoticeMoveArrow = styled.div` + width: auto; + height: auto; + display: grid; + justify-content: center; + align-items: center; + &:hover { + cursor: pointer; + } `; -export const OtherNoticeTitle = styled.p` +export const NoticeMoveTitle = styled.p` + width: auto; + height: auto; font-size: 30px; font-weight: 500; color: #25213b; text-align: left; + display: grid; grid-column: 2; + align-items: center; + margin-left: 15px; + &:hover { + cursor: pointer; + } `; diff --git a/src/styles/notice/noticeWrite.style.ts b/src/styles/notice/noticeWrite.style.ts index 03c59e8..4e0c616 100644 --- a/src/styles/notice/noticeWrite.style.ts +++ b/src/styles/notice/noticeWrite.style.ts @@ -2,18 +2,17 @@ import styled from 'styled-components'; export const NoticeMenuBarStyle = styled.div` width: 100vw; - height: 90px; + height: 180px; background-color: #ffffff; /* border: 1px solid #cfcfcf; */ box-shadow: 0 -10px 10px 10px #cfcfcf; display: grid; - grid-template-columns: 7fr 3fr; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(2, 1fr); + align-items: center; position: fixed; top: 0px; z-index: 1; - @media (max-width: 580px) { - grid-template-columns: repeat(2, 5fr); - } `; export const NoticeMenuTitle = styled.p` @@ -23,11 +22,18 @@ export const NoticeMenuTitle = styled.p` font-weight: 500; color: #495364; line-height: 45px; - margin-top: 24px; margin-left: 34px; justify-content: left; `; +export const NoticeWriteButtonContainer = styled.div` + width: 250px; + display: flex; + justify-self: right; + justify-content: flex-end; + margin-right: 5vw; +`; + export const NoticeSaveButton = styled.button` width: 56px; height: 35px; @@ -39,21 +45,60 @@ export const NoticeSaveButton = styled.button` background-color: #00063f; border-radius: 6px; margin-left: 10px; - &:hover { cursor: pointer; } `; +export const NoticeTempSaveButton = styled(NoticeSaveButton)` + width: auto; + color: #8e8e8e; + background-color: #ffffff; + box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1); + padding: 0px 10px; +`; + export const NoticeCancelButton = styled(NoticeSaveButton)` background-color: #b3b3b3; `; -export const NoticeTemporarySaveButton = styled(NoticeSaveButton)` - width: 79px; - color: #8e8e8e; - background-color: #ffffff; - box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1); +export const NoticeReviseButton = styled(NoticeSaveButton)` + background-color: #00063f; +`; + +export const NoticeAddObjectsContainer = styled.div` + width: auto; + height: auto; + grid-column: 1/3; + grid-row: 2/3; +`; + +export const NoticeMenuLine = styled.div` + border-top: 1px solid #cfcfcf; +`; + +export const NoticeAddObjectsStyle = styled.div` + width: 70px; + height: 90px; + padding: 10px; + &:hover { + cursor: pointer; + } +`; + +export const NoticeAddObjectsIcon = styled.div` + width: 50px; + height: 50px; +`; + +export const NoticeAddObjectsText = styled.p` + width: auto; + height: 20px; + font-size: 16px; + font-weight: 400; + color: #cfcfcf; + line-height: 20px; + text-align: center; `; export const NoticeWriteContainer = styled.div` @@ -81,9 +126,7 @@ export const NoticeWriteMainTitle = styled.textarea` font-size: 36px; font-weight: 500; padding-bottom: 15px; - margin-top: 120px; - /* background-color: red; */ - + margin-top: 210px; &:focus { outline: none; /* 포커스 시 검은색 테두리 제거 */ } @@ -102,5 +145,18 @@ export const NoticeWriteMainLine = styled.div` export const NoticeWriteMainContents = styled(NoticeWriteMainTitle)` font-size: 24px; line-height: 34px; - margin-top: 40px; + margin: 40px 0px; +`; + +export const NoticeShowImgContainer = styled.div` + width: auto; + height: auto; + display: grid; +`; + +export const NoticeShowImgStyle = styled.div` + width: 300px; + height: 300px; + border-radius: 5px; + background-color: orange; `; diff --git a/src/styles/projectRetro/ManageModal.styles.ts b/src/styles/projectRetro/ManageModal.styles.ts new file mode 100644 index 0000000..d8b6f39 --- /dev/null +++ b/src/styles/projectRetro/ManageModal.styles.ts @@ -0,0 +1,90 @@ +import styled from 'styled-components'; + +export const Modal = styled.div` + display: flex; + flex-direction: column; + align-items: center; + background-color: white; + border-radius: 5px; + width: 1000px; + height: 600px; + padding: 15px 25px; +`; + +export const Title = styled.span` + background-color: #111b47; + color: white; + font-size: medium; + border-radius: 3px; + display: flex; + justify-content: center; + align-items: center; + padding: 5px 35px; + width: 300px; +`; + +export const Box = styled.div` + border: 1px solid rgba(0, 0, 0, 0.3); + margin: 20px 0px; + padding: 10px 20px; + overflow-y: scroll; + width: 90%; + height: 90%; +`; + +export const ListItem = styled.div` + margin: 25px; + display: grid; + grid-template-columns: 0.5fr 1fr 1.5fr 1fr 1.8fr 1fr 1fr; + align-items: center; +`; + +export const CheckBox = styled.input` + width: 15px; + height: 15px; + &:hover { + cursor: pointer; + } +`; + +export const RetroTitle = styled.span` + padding-left: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const RetroUserBox = styled.div` + padding-left: 5px; + display: flex; + align-items: center; +`; +export const RetroUser = styled.span` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const RetroLeader = styled.span` + background-color: #3360eb; + color: white; + padding: 2px; + margin-left: 5px; + border-radius: 2px; + font-size: 8px; +`; + +export const RetroBox = styled.div` + align-self: center; + padding-left: 5px; +`; + +export const Button = styled.button` + color: white; + background-color: #4972a8; + border-radius: 3px; + padding: 5px 20px; + margin-left: auto; + margin-top: auto; + width: 100px; +`; diff --git a/src/styles/survey/EmailConsents.style.ts b/src/styles/survey/EmailConsents.style.ts new file mode 100644 index 0000000..d550ee2 --- /dev/null +++ b/src/styles/survey/EmailConsents.style.ts @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +export const CustomContainer = styled.div` + margin-bottom: 5rem; + margin-top: 2rem; + display: flex; + flex-direction: column; + align-items: center; +`; + +export const RadioContainer = styled.div` + margin-top: 2rem; + display: flex; + justify-content: center; + width: 100%; +`; diff --git a/src/styles/writeRetroStyles/Layout.style.ts b/src/styles/writeRetroStyles/Layout.style.ts index 9f04ce5..0978912 100644 --- a/src/styles/writeRetroStyles/Layout.style.ts +++ b/src/styles/writeRetroStyles/Layout.style.ts @@ -115,22 +115,33 @@ export const SettingButton = styled.div` } `; -export const SectionBox = styled.div` +interface SectionBoxProps { + column: number; +} + +export const SectionBox = styled.div<SectionBoxProps>` width: 100vw; - display: flex; + display: grid; + grid-template-columns: repeat(column, 1fr); + grid-auto-flow: column; + gap: 10px; margin-top: 10px; - flex-direction: row; - flex-wrap: wrap; @media (max-width: 800px) { display: block; margin: 10px auto; } + @media (max-width: 1200px) { + grid-template-columns: repeat(Math.ceil(calc(column/2)), 1fr); + grid-template-rows: repeat(2, 1fr); + } `; export const FrameStyle = styled.div` + /* width: 345px; */ + /* min-width: 300px; */ + width: 100%; + height: auto; min-height: 100vh; - min-width: 300px; - width: 345px; background-color: #f8f8f8; box-shadow: -0.3px 0 0 0.3px #4d5e80, @@ -138,10 +149,9 @@ export const FrameStyle = styled.div` 0 -0.3px 0 0.3px #4d5e80; border-radius: 10px 10px 0px 0px; padding: 15px; - margin-left: 10px; padding-bottom: 80px; @media (max-width: 800px) { - width: 90vw; + width: 90%; /* box-shadow: 0.3px 0 0 0.3px #4d5e80, 0 0.3px 0 0.3px #4d5e80,