diff --git a/FE/package.json b/FE/package.json
index 3cf090e9..1eab693f 100644
--- a/FE/package.json
+++ b/FE/package.json
@@ -64,5 +64,6 @@
"tailwindcss": "latest",
"ts-node": "^10.9.2",
"typescript": "latest"
- }
+ },
+ "packageManager": "pnpm@8.15.1+sha1.8adba2d20330c02d3856e18c4eb3819d1d3ca6aa"
}
diff --git a/FE/src/apis/__test__/program.test.ts b/FE/src/apis/__test__/program.test.ts
index d7b54aee..37d8bc23 100644
--- a/FE/src/apis/__test__/program.test.ts
+++ b/FE/src/apis/__test__/program.test.ts
@@ -301,7 +301,7 @@ describe("sendSlackMessage", () => {
// 내부 구현사항
expect(https).toHaveBeenCalledWith({
- data: { programUrl: "https://econo.eeos.store/detail/1" },
+ data: { programUrl: "https://eeos.co.kr/detail/1" },
method: "POST",
url: "/programs/1/slack/notification",
});
@@ -381,6 +381,7 @@ describe("patchProgram", () => {
},
],
teams: [{ teamId: 1 }],
+ programGithubUrl: "",
};
const programId = 1;
diff --git a/FE/src/apis/__test__/question.test.ts b/FE/src/apis/__test__/question.test.ts
index b03be2c5..248fc55a 100644
--- a/FE/src/apis/__test__/question.test.ts
+++ b/FE/src/apis/__test__/question.test.ts
@@ -56,9 +56,10 @@ describe("postQuestion", () => {
const programId = 1;
const teamId = 1;
const questionContent = "질문 내용";
+ const isAnonymous = 0;
// act
- await postQuestion({ programId, teamId, questionContent });
+ await postQuestion({ programId, teamId, questionContent, isAnonymous });
// assert
expect(mockHttps).toHaveBeenCalledWith({
@@ -67,6 +68,30 @@ describe("postQuestion", () => {
data: {
programId,
teamId,
+ isAnonymous: 0,
+ content: questionContent,
+ parentsCommentId: -1,
+ },
+ });
+ });
+ it("익명 질문을 등록한다", async () => {
+ // arrange
+ const programId = 1;
+ const teamId = 1;
+ const questionContent = "질문 내용";
+ const isAnonymous = 1;
+
+ // act
+ await postQuestion({ programId, teamId, questionContent, isAnonymous });
+
+ // assert
+ expect(mockHttps).toHaveBeenCalledWith({
+ url: "comments",
+ method: "POST",
+ data: {
+ programId,
+ teamId,
+ isAnonymous: 1,
content: questionContent,
parentsCommentId: -1,
},
@@ -78,6 +103,7 @@ describe("postQuestion", () => {
const teamId = 1;
const questionContent = "답변 내용";
const parentsCommentId = 1;
+ const isAnonymous = 0;
// act
await postQuestion({
@@ -85,6 +111,7 @@ describe("postQuestion", () => {
teamId,
questionContent,
parentsCommentId,
+ isAnonymous,
});
// assert
@@ -94,6 +121,7 @@ describe("postQuestion", () => {
data: {
programId,
teamId,
+ isAnonymous: 0,
content: questionContent,
parentsCommentId,
},
diff --git a/FE/src/apis/question.ts b/FE/src/apis/question.ts
index af74d3dd..e5ca7b1d 100644
--- a/FE/src/apis/question.ts
+++ b/FE/src/apis/question.ts
@@ -11,22 +11,34 @@ export const getQuestionsByTeam = async (programId: number, teamId: number) => {
return new QuestionListDto(data?.data);
};
+/**
+ * 질문을 등록합니다.
+ * - isAnonymous : 질문을 등록할 때 체크박스를 체크했는지 여부. 체크가 되었다면 1, 아니라면 0
+ */
export interface PostQuestionParams {
programId: number;
teamId: number;
questionContent: string;
parentsCommentId?: number;
+ commentType: "ANONYMOUS" | "NON_ANONYMOUS";
}
export const postQuestion = async ({
programId,
teamId,
questionContent,
parentsCommentId = -1,
+ commentType = "NON_ANONYMOUS",
}: PostQuestionParams) => {
return await https({
url: API.QUESTION.CREATE,
method: "POST",
- data: { programId, teamId, content: questionContent, parentsCommentId },
+ data: {
+ programId,
+ teamId,
+ content: questionContent,
+ parentsCommentId,
+ commentType,
+ },
});
};
diff --git a/FE/src/app/(admin)/admin/detail/[programId]/page.tsx b/FE/src/app/(admin)/admin/detail/[programId]/page.tsx
index 4eb29799..3cce624d 100644
--- a/FE/src/app/(admin)/admin/detail/[programId]/page.tsx
+++ b/FE/src/app/(admin)/admin/detail/[programId]/page.tsx
@@ -1,5 +1,9 @@
-import AttendeeInfoContainer from "@/components/programDetail/attendee/AttendeeInfo.container";
-import ProgramInfo from "@/components/programDetail/program/ProgramInfo";
+import AttendeeInfoContainer from "@/components/feature/detail/attendee/AttendeeInfo.container";
+import ProgramHeaderSection from "@/components/feature/detail/program/ProgramHeaderSection";
+import ProgramDetailSection from "@/components/feature/detail/program/ProgramDetailSection";
+import ProgramattendModeManageSection from "@/components/feature/detail/program/ProgramAttendStatusManageSection";
+import ProgramPresentationsSection from "@/components/feature/detail/presentation/ProgramPresentationsSection";
+import ProgramDashboardSection from "@/components/feature/detail/Dashboard/ProgramDashboardSection";
interface ProgramDetailPageProps {
params: {
@@ -12,7 +16,15 @@ const ProgramDetailPage = ({ params }: ProgramDetailPageProps) => {
return (
);
diff --git a/FE/src/app/(guest)/guest/detail/[programId]/page.tsx b/FE/src/app/(guest)/guest/detail/[programId]/page.tsx
index 0701336d..822373a3 100644
--- a/FE/src/app/(guest)/guest/detail/[programId]/page.tsx
+++ b/FE/src/app/(guest)/guest/detail/[programId]/page.tsx
@@ -1,6 +1,9 @@
-import AttendeeInfoContainer from "@/components/programDetail/attendee/AttendeeInfo.container";
-import ProgramInfo from "@/components/programDetail/program/ProgramInfo";
-import UserAttendModalContainer from "@/components/programDetail/userAttendModal/UserAttendModal.container";
+import AttendeeInfoContainer from "@/components/feature/detail/attendee/AttendeeInfo.container";
+import ProgramHeaderSection from "@/components/feature/detail/program/ProgramHeaderSection";
+import ProgramDetailSection from "@/components/feature/detail/program/ProgramDetailSection";
+import UserAttendModalContainer from "@/components/feature/detail/userAttendModal/UserAttendModal.container";
+import ProgramPresentationsSection from "@/components/feature/detail/presentation/ProgramPresentationsSection";
+import BlurDashboard from "@/components/feature/detail/Dashboard/BlurDashboard";
interface ProgramDetailPageProps {
params: {
@@ -13,10 +16,18 @@ const ProgramDetailPage = ({ params }: ProgramDetailPageProps) => {
return (
);
};
+
export default ProgramDetailPage;
diff --git a/FE/src/app/(private)/(program)/detail/[programId]/page.tsx b/FE/src/app/(private)/(program)/detail/[programId]/page.tsx
index 08621ea5..8ead99d2 100644
--- a/FE/src/app/(private)/(program)/detail/[programId]/page.tsx
+++ b/FE/src/app/(private)/(program)/detail/[programId]/page.tsx
@@ -1,6 +1,9 @@
-import AttendeeInfoContainer from "@/components/programDetail/attendee/AttendeeInfo.container";
-import ProgramInfo from "@/components/programDetail/program/ProgramInfo";
-import UserAttendModalContainer from "@/components/programDetail/userAttendModal/UserAttendModal.container";
+import AttendeeInfoContainer from "@/components/feature/detail/attendee/AttendeeInfo.container";
+import ProgramHeaderSection from "@/components/feature/detail/program/ProgramHeaderSection";
+import ProgramDetailSection from "@/components/feature/detail/program/ProgramDetailSection";
+import UserAttendModalContainer from "@/components/feature/detail/userAttendModal/UserAttendModal.container";
+import ProgramPresentationsSection from "@/components/feature/detail/presentation/ProgramPresentationsSection";
+import ProgramDashboardSection from "@/components/feature/detail/Dashboard/ProgramDashboardSection";
interface ProgramDetailPageProps {
params: {
@@ -13,10 +16,18 @@ const ProgramDetailPage = ({ params }: ProgramDetailPageProps) => {
return (
);
};
+
export default ProgramDetailPage;
diff --git a/FE/src/components/common/CheckBox/CheckBox.tsx b/FE/src/components/common/CheckBox/CheckBox.tsx
index 2da6b92a..253ab649 100644
--- a/FE/src/components/common/CheckBox/CheckBox.tsx
+++ b/FE/src/components/common/CheckBox/CheckBox.tsx
@@ -1,23 +1,32 @@
+"use client";
+
import classNames from "classnames";
import Image from "next/image";
interface CheckBoxProps {
checked: boolean;
- onClick: () => void;
+ onClick?: () => void;
disabled?: boolean;
+ className?: string;
}
-const CheckBox = ({ checked, onClick, disabled = false }: CheckBoxProps) => {
+const CheckBox = ({
+ checked,
+ onClick,
+ disabled = false,
+ className,
+}: CheckBoxProps) => {
const checkboxClass = classNames(
"flex h-6 w-6 items-center justify-center rounded border-2 transition duration-100",
checked ? "border-blue-500 bg-blue-500" : "border-gray-20 bg-background",
{
"cursor-not-allowed opacity-0": disabled,
},
+ className,
);
const handleCheckBoxClick = () => {
- !disabled && onClick();
+ !disabled && onClick && onClick();
};
return (
diff --git a/FE/src/components/common/dashboard/ChatBox.tsx b/FE/src/components/common/dashboard/ChatBox.tsx
new file mode 100644
index 00000000..ac709fa9
--- /dev/null
+++ b/FE/src/components/common/dashboard/ChatBox.tsx
@@ -0,0 +1,190 @@
+"use client";
+import { useState } from "react";
+import MarkdownViewer from "../markdown/MarkdownViewer";
+import { useGetAccessType } from "@/hooks/useAccess";
+
+export interface ChatBoxInnerData {
+ commentId: number;
+ defaultContent: string;
+ time: string;
+ markdownStyle: string;
+ showReplyButton: boolean;
+ writer: string;
+ userInputToModify: string;
+ setUserInputToModify: (content: string) => void;
+ isGuest: boolean;
+ hasUpdateRight: boolean;
+ toggleIsModify: () => void;
+}
+export interface UpdateComment extends ChatBoxInnerData {
+ newContents: string;
+}
+
+interface ChatBoxProps {
+ writer: string;
+ defaultContent: string;
+ time: string;
+ markdownStyle?: string;
+ showReplyButton?: boolean;
+ accessRight: "edit" | "read_only";
+ updateComment: (question: UpdateComment) => void;
+ deleteComment: (question: ChatBoxInnerData) => void;
+ commentId: number;
+ handleReply: () => void;
+}
+
+const ChatBox = ({
+ writer,
+ defaultContent,
+ time,
+ markdownStyle,
+ showReplyButton,
+ accessRight,
+ updateComment,
+ commentId, //TODO: 필요 없는지 확인 필요
+ deleteComment,
+ handleReply,
+}: ChatBoxProps) => {
+ const [userInputToModify, setUserInputToModify] = useState(defaultContent);
+ const [isModifyMode, setIsModify] = useState(false);
+
+ const accessType = useGetAccessType();
+
+ const isGuest = accessType === "public";
+ const hasUpdateRight = accessRight === "edit" && !isGuest;
+
+ const toggleIsModify = () => {
+ if (!hasUpdateRight) return;
+ setIsModify((prev) => !prev);
+ setUserInputToModify(defaultContent);
+ };
+
+ // const handleReply = () => {
+ // setParentsCommentId(commentId);
+ // changeSelectedCommentContent(content);
+ // };
+
+ const handleUpdateComment = () => {
+ if (!isModifyMode) return;
+
+ const newContents = userInputToModify;
+ if (!newContents) return;
+
+ if (defaultContent === newContents) {
+ setIsModify((prev) => !prev);
+ return;
+ }
+
+ // updateComment({ commentId, contents: newContents });
+ // isUpdateSuccess && setUserInputToModify(newContents);
+
+ updateComment({
+ commentId,
+ defaultContent,
+ hasUpdateRight,
+ isGuest,
+ markdownStyle,
+ showReplyButton,
+ time,
+ writer,
+ toggleIsModify,
+ newContents,
+ userInputToModify,
+ setUserInputToModify,
+ });
+
+ setIsModify((prev) => !prev);
+ };
+
+ const handleDeleteComment = () => {
+ if (!hasUpdateRight) return;
+
+ const isOkToDelete = confirm("정말 삭제하시겠습니까?");
+ if (!isOkToDelete) return;
+
+ deleteComment({
+ commentId,
+ defaultContent,
+ time,
+ markdownStyle,
+ showReplyButton,
+ writer,
+ userInputToModify,
+ setUserInputToModify,
+ isGuest,
+ hasUpdateRight,
+ toggleIsModify,
+ });
+ // deleteComment({ commentId });
+ // isDeleteSuccess && setUserInputToModify("");
+ };
+
+ return (
+ <>
+
+
+ {!isModifyMode && (
+
+ )}
+
+ {isModifyMode && (
+