Skip to content

Commit

Permalink
Merge pull request #97 from softeerbootcamp4th/feature/32-admincomment
Browse files Browse the repository at this point in the history
[feat] 어드민 기대평 페이지 일부 구현
  • Loading branch information
lybell-art authored Aug 14, 2024
2 parents 602259b + 521d5d7 commit 9e72552
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 70 deletions.
3 changes: 2 additions & 1 deletion src/adminPage/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EventsCreatePage from "./pages/EventsCreatePage.jsx";
import ProtectedRoute from "./pages/ProtectedRoute.jsx";
import RootRoute from "./pages/RootRoute.jsx";
import CommentsPage from "./pages/CommentsPage.jsx";
import CommentsIDPage from "./pages/CommentsIDPage.jsx";
import ServerTimeInitializer from "./shared/serverTime/ServerTimeInitializer.jsx";

import Modal from "@common/modal/modal.jsx";
Expand All @@ -31,7 +32,7 @@ function App() {
/>
<Route path="/events/:id" element={<EventsDetailPage />} />
<Route path="/events" element={<EventsPage />} />
<Route path="/comments/:id" element={<div>기대평 화면</div>} />
<Route path="/comments/:eventId" element={<CommentsIDPage />} />
<Route path="/comments" element={<CommentsPage />} />
</Route>
<Route path="/login" element={<LoginPage />} />
Expand Down
50 changes: 50 additions & 0 deletions src/adminPage/features/comment/id/Comments.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useQuery } from "@common/dataFetch/getQuery.js";
import { fetchServer } from "@common/dataFetch/fetchServer.js";

export default function Comments({
eventId,
checkedComments,
setCheckedComments,
}) {
const data = useQuery(eventId, () =>
fetchServer(
`/api/v1/admin/comments?eventId=${eventId}&page=${0}&size=15`,
).then((res) => res.comments),
);

function onChangeCheckbox(e, id) {
if (e.target.checked) {
setCheckedComments((oldSet) => new Set([...oldSet, id]));
} else {
setCheckedComments((oldSet) => {
const newSet = new Set(oldSet);
newSet.delete(id);
return newSet;
});
}
}

return (
<div className="mt-3 flex flex-col gap-1">
{data.map((comment) => (
<div
key={comment.id}
className="py-1 grid grid-cols-[1fr_5fr_15fr] bg-neutral-50"
>
<input
type="checkbox"
checked={checkedComments.has(comment.id)}
onChange={(e) => onChangeCheckbox(e, comment.id)}
className="w-4 h-4 place-self-center"
/>

<span className="text-body-s place-self-center">
{comment.createdAt}
</span>

<span className="text-body-s">{comment.content}</span>
</div>
))}
</div>
);
}
9 changes: 9 additions & 0 deletions src/adminPage/features/comment/id/Loading.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Spinner from "@common/components/Spinner";

export default function Loading() {
return (
<div className="flex justify-center items-center w-full h-60 bg-slate-50">
<Spinner />
</div>
);
}
73 changes: 73 additions & 0 deletions src/adminPage/features/comment/id/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Suspense from "@common/components/Suspense";
import Loading from "./Loading.jsx";
import Comments from "./Comments.jsx";
import { useState } from "react";
import { fetchServer } from "@common/dataFetch/fetchServer.js";

export default function AdminCommentID({ eventId }) {
const [checkedComments, setCheckedComments] = useState(new Set());

function deleteComments() {
const num = checkedComments.size;
if (!num) return;

fetchServer("/api/v1/admin/comments", {
method: "DELETE",
body: {
commentIds: [...checkedComments],
},
})
.then(() => {
alert(num + "개의 기대평 삭제 완료.");
setCheckedComments(new Set());
})
.catch((e) => {
console.log(e);
});
}

function searchComment(e) {
e.preventDefault();
console.log(e.target.searchText.value + "검색");
}

return (
<div className="flex flex-col w-full">
<button
onClick={deleteComments}
className="self-end px-5 py-1 bg-red-300 text-white hover:bg-red-500 rounded-lg"
>
삭제
</button>

<form onSubmit={searchComment} className="mt-5 w-full relative">
<input
type="text"
name="searchText"
placeholder="검색 단어 입력"
className="bg-neutral-50 focus:bg-white w-full px-4 py-2 rounded-lg"
/>

<img
src="/icons/search.png"
alt="검색"
className="absolute top-1/2 -translate-y-1/2 right-4"
/>
</form>

<div className="mt-3 py-2 grid grid-cols-[1fr_5fr_15fr] bg-blue-50 place-items-center">
<span>선택</span>
<span>작성 시간</span>
<span>기대평 내용</span>
</div>

<Suspense fallback={<Loading />}>
<Comments
eventId={eventId}
checkedComments={checkedComments}
setCheckedComments={setCheckedComments}
/>
</Suspense>
</div>
);
}
91 changes: 91 additions & 0 deletions src/adminPage/features/comment/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useState } from "react";
import { fetchServer } from "@common/dataFetch/fetchServer";
import { useNavigate } from "react-router-dom";

export default function AdminComment() {
const navigate = useNavigate();
const [formString, setFormString] = useState("");
const [isSpread, setIsSpread] = useState(false);
const [searchList, setSearchList] = useState([]);

function autoCorrect() {
fetchServer(`/api/v1/admin/events/hints?search=${formString}`)
.then((res) => {
setSearchList(res);
setIsSpread(true);
})
.catch((e) => {
console.log(e);
});
}

function onChangeForm(e) {
const filteredString = e.target.value.replace(/[^0-9]/g, "");

if (!filteredString) {
setFormString("");
} else if (filteredString.length <= 6) {
setFormString("HD_" + filteredString);
} else if (filteredString.length <= 9) {
setFormString(
"HD_" + filteredString.slice(0, 6) + "_" + filteredString.slice(6),
);
} else return;

if (filteredString.length >= 6) {
autoCorrect();
} else setIsSpread(false);
}

function searchEvent(e, eventId) {
e.preventDefault();

const eventIDRegex = /^HD_\d{6}_\d{3}$/;
const searchID = eventId ? eventId : formString;

if (eventIDRegex.test(searchID)) {
navigate(`/comments/${searchID}`);
}
}

return (
<form onSubmit={searchEvent} className="relative flex">
<input
type="text"
inputMode="numeric"
onChange={onChangeForm}
value={formString}
placeholder="ID (숫자 9자리)"
className={`z-10 bg-neutral-50 focus:bg-white px-4 py-2 w-full ${isSpread ? "rounded-t-md" : "rounded-md"}`}
/>

<div
className={`absolute max-h-40 top-full border overflow-y-auto w-full rounded-b-md px-3 py-2 flex flex-col gap-2 ${!isSpread && "hidden"}`}
>
{searchList.map((evt) => (
<li
key={evt.eventId}
onClick={(e) => searchEvent(e, evt.eventId)}
className={`cursor-pointer list-none w-full rounded px-1 flex hover:bg-blue-200`}
>
<span className="w-40">{evt.eventId}</span>
<span>{evt.name}</span>
</li>
))}

<span
className={`${searchList.length && "hidden"} text-neutral-300 text-body-s`}
>
일치하는 검색 결과가 없습니다.
</span>
</div>

<img
onClick={searchEvent}
src="/icons/search.png"
alt="검색"
className="z-10 absolute top-1/2 -translate-y-1/2 right-4 cursor-pointer"
/>
</form>
);
}
62 changes: 62 additions & 0 deletions src/adminPage/features/comment/mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { http, HttpResponse } from "msw";

function getRandomString(len) {
// const startCode = 0xac00;
// const endCode = 0xd7a3;

const startCode = 0x0750;
const endCode = 0x077f;

let str = "";
for (let i = 0; i < len; i++) {
const randomCode =
Math.floor(Math.random() * (endCode - startCode + 1)) + startCode;
str += String.fromCharCode(randomCode);
}

return str;
}

function getSampleEventList() {
const len = 10;
let eventList = [];
for (let i = 0; i < len; i++) {
eventList = [
...eventList,
{
eventId: "HD_240909_00" + i,
name: getRandomString(10),
},
];
}
return eventList;
}

function getSampleCommentList() {
const len = 15;
let commentList = [];
for (let i = 0; i < len; i++) {
commentList = [
...commentList,
{
id: i,
content: getRandomString(50),
userName: getRandomString(5),
createdAt: "2024-08-14T07:11:27.244Z",
},
];
}
return { comments: commentList };
}

const handlers = [
http.get("/api/v1/admin/events/hints", () =>
HttpResponse.json(getSampleEventList()),
),
http.get("/api/v1/admin/comments", () =>
HttpResponse.json(getSampleCommentList()),
),
http.delete("/api/v1/admin/comments", () => HttpResponse.json(true)),
];

export default handlers;
2 changes: 2 additions & 0 deletions src/adminPage/mock.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { setupWorker } from "msw/browser";
import authHandler from "@admin/auth/mock.js";
import commentHandler from "./features/comment/mock.js";
import serverTimeHandler from "@admin/serverTime/mock.js";
import eventSearchHandler from "./features/eventList/mock.js";

Expand All @@ -10,4 +11,5 @@ export default setupWorker(
...authHandler,
...eventSearchHandler,
...serverTimeHandler,
...commentHandler,
);
17 changes: 17 additions & 0 deletions src/adminPage/pages/CommentsIDPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Container from "@admin/components/Container.jsx";
import AdminCommentID from "../features/comment/id";
import { useParams } from "react-router-dom";

export default function CommentsPage() {
const { eventId } = useParams();

return (
<Container>
<div className="flex flex-col w-full p-20">
<span className="text-title-l pb-10">기대평</span>

<AdminCommentID eventId={eventId} />
</div>
</Container>
);
}
Loading

0 comments on commit 9e72552

Please sign in to comment.