diff --git a/components/board/affiliate.cards.jsx b/components/board/benefit/affiliate/affiliate.cards.jsx
similarity index 98%
rename from components/board/affiliate.cards.jsx
rename to components/board/benefit/affiliate/affiliate.cards.jsx
index 4f74615..f0b4567 100644
--- a/components/board/affiliate.cards.jsx
+++ b/components/board/benefit/affiliate/affiliate.cards.jsx
@@ -1,5 +1,4 @@
import React, { useState } from 'react'
-import moment from 'moment'
import { Card, Modal, Grid, Header } from 'semantic-ui-react'
const AffiliateCards = ({affiliates}) => {
diff --git a/components/board/discount.cards.jsx b/components/board/benefit/discount/discount.cards.jsx
similarity index 98%
rename from components/board/discount.cards.jsx
rename to components/board/benefit/discount/discount.cards.jsx
index f200123..fa843c9 100644
--- a/components/board/discount.cards.jsx
+++ b/components/board/benefit/discount/discount.cards.jsx
@@ -1,5 +1,4 @@
import React, { useState } from 'react'
-import moment from 'moment'
import { Card, Modal, Grid, Header, Divider, Icon } from 'semantic-ui-react'
const DiscountOfferCards = ({discountOffers}) => {
diff --git a/components/board/board.menubar.jsx b/components/board/board.menubar.jsx
index 41001ee..12975cf 100644
--- a/components/board/board.menubar.jsx
+++ b/components/board/board.menubar.jsx
@@ -11,6 +11,11 @@ export default class BoardMenubar extends Component {
POPO 설정값
+
+
+ 공지사항
+
+
RC 사생 명단 업로드
diff --git a/components/board/notice/notice.table.jsx b/components/board/notice/notice.table.jsx
new file mode 100644
index 0000000..50c185c
--- /dev/null
+++ b/components/board/notice/notice.table.jsx
@@ -0,0 +1,63 @@
+import Link from 'next/link'
+import moment from 'moment'
+import { Table } from 'semantic-ui-react'
+
+const NoticeTable
+ = ({notices}) => {
+ return (
+
+
+
+ id.
+ 제목
+ 내용
+ {/* 이미지 */}
+ 게시 일자
+ 클릭수
+
+
+
+ {
+ notices.map((notice, idx) => {
+ const isActive = moment().isBetween(moment(notice.start_datetime), moment(notice.end_datetime));
+ const duration = moment(notice.end_datetime).diff(moment(notice.start_datetime), 'hours');
+ return (
+
+
+ {notice.id}
+
+ {
+ notice.link ? (
+
+ {notice.title}
+
+ ) : notice.title
+ }
+
+
+ {notice.content}
+
+ {/*
+
+ */}
+
+ {moment(notice.start_datetime).format('YYYY-MM-DD HH:mm')} ~ {moment(notice.end_datetime).format('YYYY-MM-DD HH:mm')}
+ ({Number(duration/24).toFixed(0)}일 {duration%24}시간)
+
+
+ {notice.click_count}
+
+
+
+ )
+ })
+ }
+
+
+ )
+}
+
+export default NoticeTable
diff --git a/components/board/whitebook.create.modal.jsx b/components/board/whitebook/whitebook.create.modal.jsx
similarity index 100%
rename from components/board/whitebook.create.modal.jsx
rename to components/board/whitebook/whitebook.create.modal.jsx
diff --git a/components/board/whitebook.table.jsx b/components/board/whitebook/whitebook.table.jsx
similarity index 100%
rename from components/board/whitebook.table.jsx
rename to components/board/whitebook/whitebook.table.jsx
diff --git a/components/board/whitebook.update.modal.jsx b/components/board/whitebook/whitebook.update.modal.jsx
similarity index 97%
rename from components/board/whitebook.update.modal.jsx
rename to components/board/whitebook/whitebook.update.modal.jsx
index 49e9527..452a2cd 100644
--- a/components/board/whitebook.update.modal.jsx
+++ b/components/board/whitebook/whitebook.update.modal.jsx
@@ -1,6 +1,6 @@
import { Button, Form, Icon, Modal } from 'semantic-ui-react'
import { useState } from 'react'
-import DeleteConfirmModal from '../common/delete.confirm.modal'
+import DeleteConfirmModal from '../../common/delete.confirm.modal'
import { PoPoAxios } from '@/utils/axios.instance';
const WhitebookUpdateModal = ({ trigger, whitebook }) => {
diff --git a/package-lock.json b/package-lock.json
index 369412f..8544ec4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"moment": "^2.29.4",
"next": "^12.3.4",
"react": "^17.0.2",
+ "react-datepicker": "^4.24.0",
"react-dom": "17.0.2",
"react-responsive": "^9.0.2",
"semantic-ui-css": "^2.5.0",
@@ -595,9 +596,9 @@
}
},
"node_modules/@popperjs/core": {
- "version": "2.11.6",
- "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
- "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@@ -1144,6 +1145,11 @@
}
]
},
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"node_modules/clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
@@ -1298,6 +1304,21 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -3702,6 +3723,23 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-datepicker": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz",
+ "integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==",
+ "dependencies": {
+ "@popperjs/core": "^2.11.8",
+ "classnames": "^2.2.6",
+ "date-fns": "^2.30.0",
+ "prop-types": "^15.7.2",
+ "react-onclickoutside": "^6.13.0",
+ "react-popper": "^2.3.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17 || ^18",
+ "react-dom": "^16.9.0 || ^17 || ^18"
+ }
+ },
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -3730,6 +3768,19 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "node_modules/react-onclickoutside": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
+ "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md"
+ },
+ "peerDependencies": {
+ "react": "^15.5.x || ^16.x || ^17.x || ^18.x",
+ "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x"
+ }
+ },
"node_modules/react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
@@ -4967,9 +5018,9 @@
}
},
"@popperjs/core": {
- "version": "2.11.6",
- "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
- "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
},
"@react-spring/animated": {
"version": "9.4.5",
@@ -5357,6 +5408,11 @@
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001521.tgz",
"integrity": "sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ=="
},
+ "classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
@@ -5501,6 +5557,14 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "requires": {
+ "@babel/runtime": "^7.21.0"
+ }
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -7249,6 +7313,19 @@
"object-assign": "^4.1.1"
}
},
+ "react-datepicker": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz",
+ "integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==",
+ "requires": {
+ "@popperjs/core": "^2.11.8",
+ "classnames": "^2.2.6",
+ "date-fns": "^2.30.0",
+ "prop-types": "^15.7.2",
+ "react-onclickoutside": "^6.13.0",
+ "react-popper": "^2.3.0"
+ }
+ },
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -7274,6 +7351,12 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "react-onclickoutside": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
+ "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==",
+ "requires": {}
+ },
"react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
diff --git a/package.json b/package.json
index 3b890f7..5028182 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"moment": "^2.29.4",
"next": "^12.3.4",
"react": "^17.0.2",
+ "react-datepicker": "^4.24.0",
"react-dom": "17.0.2",
"react-responsive": "^9.0.2",
"semantic-ui-css": "^2.5.0",
diff --git a/pages/board/notice/create.jsx b/pages/board/notice/create.jsx
new file mode 100644
index 0000000..20ddb6c
--- /dev/null
+++ b/pages/board/notice/create.jsx
@@ -0,0 +1,130 @@
+import { useState } from 'react'
+import { useRouter } from "next/router";
+import moment from 'moment'
+import { Form, Message } from "semantic-ui-react";
+import ReactDatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css'
+
+import { PoPoAxios } from "@/utils/axios.instance";
+import BoardLayout from '@/components/board/board.layout';
+
+const NoticeCreatePage = () => {
+ const router = useRouter();
+
+ const [title, setTitle] = useState('')
+ const [content, setContent] = useState()
+ const [link, setLink] = useState()
+ const [start_datetime, setStartDatetime] = useState()
+ const [end_datetime, setEndDatetime] = useState()
+
+ const duration = moment(end_datetime).diff(moment(start_datetime), 'hours');
+
+ const handleSubmit = async () => {
+ const body = {
+ 'title': title,
+ 'content': content,
+ 'link': link,
+ 'start_datetime': start_datetime,
+ 'end_datetime': end_datetime,
+ }
+
+ if (!start_datetime || !end_datetime) {
+ alert('시작 일자와 종료 일자를 입력해주세요.')
+ return;
+ }
+
+ if (start_datetime > end_datetime) {
+ alert('시작 일자가 종료 일자보다 늦을 수 없습니다.')
+ return;
+ }
+
+ PoPoAxios.post('/notice',
+ body,
+ { withCredentials: true },
+ ).then(() => {
+ alert('공지사항이 등록 되었습니다!')
+ router.push('/board/notice');
+ }).catch(err => {
+ const errMsg = err.response.data.message;
+ alert(`공지사항 등록에 실패했습니다.\n${errMsg}`);
+ })
+ }
+
+ return (
+
+ 공지사항 생성
+
+ setTitle(e.target.value)}
+ />
+
+
+ 공지사항 이미지 업로드는 공지사항 생성 후, 등록 할 수 있습니다.
+
+
+ setContent(e.target.value)}
+ />
+
+ setLink(e.target.value)}
+ />
+
+ 링크가 존재하는 공지사항일 경우 링크를 입력해주세요.
+
+
+
+
+ 시작 날짜
+ setStartDatetime(moment(date).format('YYYY-MM-DD HH:mm:ss'))}
+ onKeyDown={e => e.preventDefault()}
+ dateFormat="yyyy-MM-dd HH:mm"
+ timeIntervals={60}
+ minDate={new Date()}
+ showTimeSelect
+ />
+
+
+ 종료 날짜
+ setEndDatetime(moment(date).format('YYYY-MM-DD HH:mm:ss'))}
+ onKeyDown={e => e.preventDefault()}
+ dateFormat="yyyy-MM-dd HH:mm"
+ timeIntervals={60}
+ minDate={moment(start_datetime).toDate()}
+ showTimeSelect
+ />
+
+
+
+ {
+ (!start_datetime || !end_datetime) ? (
+ "게시 시작 날짜와 종료 날짜를 입력해주세요."
+ ) : (
+ start_datetime > end_datetime ? (
+ "시작 날짜가 종료 날짜보다 늦을 수 없습니다."
+ ) : (
+ `게시 기간: ${start_datetime} ~ ${end_datetime} (${Number(duration/24).toFixed(0)}일 ${duration%24}시간)`
+ )
+ )
+ }
+
+
+
+ 생성
+
+
+
+ )
+}
+
+export default NoticeCreatePage;
diff --git a/pages/board/notice/index.jsx b/pages/board/notice/index.jsx
new file mode 100644
index 0000000..a5acf56
--- /dev/null
+++ b/pages/board/notice/index.jsx
@@ -0,0 +1,42 @@
+import Link from 'next/link'
+import { Button, Message } from 'semantic-ui-react'
+
+import BoardLayout from '@/components/board/board.layout'
+import { PoPoAxios } from '@/utils/axios.instance';
+import NoticeTable from '@/components/board/notice/notice.table'
+
+const AnnouncementPage = ({ noticeList }) => {
+ return (
+
+ 공지사항
+
+
+ 공지사항 등록
+
+
+
+
+ 공지사항은 빠른 게시 시작 일자로 정렬되어 표시됩니다!
+
+
+
+ 기능 개발이 계속 이뤄지고 있습니다.
+
+
+
+
+
+
+ )
+}
+
+export default AnnouncementPage;
+
+export async function getServerSideProps() {
+ const res1 = await PoPoAxios.get('notice');
+ const noticeList = res1.data;
+
+ return { props: { noticeList } };
+}
diff --git a/pages/board/notice/update/[id].jsx b/pages/board/notice/update/[id].jsx
new file mode 100644
index 0000000..a274397
--- /dev/null
+++ b/pages/board/notice/update/[id].jsx
@@ -0,0 +1,165 @@
+import { useState } from 'react'
+import { useRouter } from "next/router";
+import moment from 'moment'
+import { Button, Form, Icon, Message } from "semantic-ui-react";
+import ReactDatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css'
+
+import { PoPoAxios } from "@/utils/axios.instance";
+import BoardLayout from '@/components/board/board.layout';
+import DeleteConfirmModal from "@/components/common/delete.confirm.modal";
+// import ImageUploadForm from '@/components/common/image-upload.form';
+
+const NoticeUpdatePage = ({ noticeInfo }) => {
+ const router = useRouter();
+
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false)
+
+ const id = noticeInfo.id;
+ const [title, setTitle] = useState(noticeInfo.title)
+ const [content, setContent] = useState(noticeInfo.content)
+ const [link, setLink] = useState(noticeInfo.link)
+ const [start_datetime, setStartDatetime] = useState(noticeInfo.start_datetime)
+ const [end_datetime, setEndDatetime] = useState(noticeInfo.end_datetime)
+
+ const duration = moment(end_datetime).diff(moment(start_datetime), 'hours');
+
+ const handleSubmit = async () => {
+ const body = {
+ 'title': title,
+ 'content': content,
+ 'link': link,
+ 'start_datetime': start_datetime,
+ 'end_datetime': end_datetime,
+ }
+
+ if (start_datetime > end_datetime) {
+ alert('시작 일자가 종료 일자보다 늦을 수 없습니다.')
+ return;
+ }
+
+ PoPoAxios.put(`/notice/${id}`,
+ body,
+ { withCredentials: true },
+ ).then(() => {
+ alert('공지사항이 업데이트 되었습니다!')
+ router.push('/board/notice');
+ }).catch(err => {
+ const errMsg = err.response.data.message;
+ alert(`공지사항 업데이트에 실패했습니다.\n${errMsg}`);
+ })
+ }
+
+ return (
+
+ 공지사항 수정
+
+ setTitle(e.target.value)}
+ />
+
+
+ 공지사항 이미지 업로드는 공지사항 생성 후, 수정 페이지에서 가능합니다.
+
+
+ setContent(e.target.value)}
+ />
+
+ setLink(e.target.value)}
+ />
+
+ 링크가 존재하는 공지사항일 경우 링크를 입력해주세요.
+
+
+ {/*
+
+ 권장 이미지 사이즈(가로 x 세로): 540 x 200
+ */}
+
+
+
+ 시작 날짜
+ setStartDatetime(moment(date).format('YYYY-MM-DD HH:mm:ss'))}
+ onKeyDown={e => e.preventDefault()}
+ dateFormat="yyyy-MM-dd HH:mm"
+ timeIntervals={60}
+ minDate={new Date()}
+ showTimeSelect
+ />
+
+
+ 종료 날짜
+ setEndDatetime(moment(date).format('YYYY-MM-DD HH:mm:ss'))}
+ onKeyDown={e => e.preventDefault()}
+ dateFormat="yyyy-MM-dd HH:mm"
+ timeIntervals={60}
+ minDate={moment(start_datetime).toDate()}
+ showTimeSelect
+ />
+
+
+
+ {
+ (!start_datetime || !end_datetime) ? (
+ "게시 시작 날짜와 종료 날짜를 입력해주세요."
+ ) : (
+ start_datetime > end_datetime ? (
+ "시작 날짜가 종료 날짜보다 늦을 수 없습니다."
+ ) : (
+ `게시 기간: ${start_datetime} ~ ${end_datetime} (${Number(duration/24).toFixed(0)}일 ${duration%24}시간)`
+ )
+ )
+ }
+
+
+
+
+
+ 수정
+
+ setDeleteModalOpen(true)}>
+ 삭제
+ )}
+ />
+
+
+
+
+ )
+}
+
+export default NoticeUpdatePage;
+
+export async function getServerSideProps(ctx) {
+ const { id } = ctx['params'];
+ const res = await PoPoAxios.get(`notice/${id}`);
+ const noticeInfo = res.data;
+
+ return { props: { noticeInfo } }
+}
diff --git a/pages/board/whitebook.jsx b/pages/board/whitebook.jsx
index d40ea36..122ded4 100644
--- a/pages/board/whitebook.jsx
+++ b/pages/board/whitebook.jsx
@@ -2,8 +2,8 @@ import { useState, useEffect } from 'react'
import { Button } from 'semantic-ui-react'
import BoardLayout from '@/components/board/board.layout'
-import WhitebookCreateModal from '@/components/board/whitebook.create.modal'
-import WhitebookTable from '@/components/board/whitebook.table'
+import WhitebookCreateModal from '@/components/board/whitebook/whitebook.create.modal'
+import WhitebookTable from '@/components/board/whitebook/whitebook.table'
import { PoPoAxios } from '@/utils/axios.instance';
const WhitebookPage = () => {