From 28d4b8ed1a4f7b7d9b12f85e5fd1cc5bb7675341 Mon Sep 17 00:00:00 2001
From: goodsmell <87801306+goodsmell@users.noreply.github.com>
Date: Sat, 19 Sep 2026 18:34:25 +0900
Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EB=A7=89=EA=B3=BC=20=EC=9E=A5=20/?=
=?UTF-8?q?=20=EB=AC=B4=EB=8C=80=20=EC=A0=95=EB=B3=B4=20=EC=82=AD=EC=A0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/assets/image/myPage/pupple_dot_line.svg | 3 +
src/assets/image/myPage/pupple_line.svg | 3 -
src/mocks/handlers.ts | 504 +++++++++++++++++-
.../myPage/PostManage/PostManageDetail.tsx | 148 +----
.../myPage/PostManage/PostMangeDetail.scss | 54 +-
src/pages/work/Detail.tsx | 2 -
6 files changed, 516 insertions(+), 198 deletions(-)
create mode 100644 src/assets/image/myPage/pupple_dot_line.svg
delete mode 100644 src/assets/image/myPage/pupple_line.svg
diff --git a/src/assets/image/myPage/pupple_dot_line.svg b/src/assets/image/myPage/pupple_dot_line.svg
new file mode 100644
index 00000000..d35d7f84
--- /dev/null
+++ b/src/assets/image/myPage/pupple_dot_line.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/image/myPage/pupple_line.svg b/src/assets/image/myPage/pupple_line.svg
deleted file mode 100644
index a7c1e71f..00000000
--- a/src/assets/image/myPage/pupple_line.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts
index a179034b..03958e90 100644
--- a/src/mocks/handlers.ts
+++ b/src/mocks/handlers.ts
@@ -1,5 +1,6 @@
import { delay, http, HttpResponse } from "msw";
import type { ScriptItem } from "@/api/user/postListApi";
+import type { Review, ReviewStatistics } from "@/types/review";
const BASE = "http://localhost:8080";
@@ -603,6 +604,313 @@ const sortList = (list: ScriptItem[], sortType: string) =>
const getLikedLongPlays = () => longPlays.filter((script) => script.like);
const getLikedShortPlays = () => shortPlays.filter((script) => script.like);
+type WorkReviewStatus = "WAIT" | "PASS" | "RE_WAIT" | "RE_PASS";
+
+interface ManagedWork {
+ id: string;
+ title: string;
+ writer: string;
+ imagePath: string;
+ script: boolean;
+ scriptPrice: number;
+ performance: boolean;
+ performancePrice: number;
+ checked: WorkReviewStatus;
+ createdDate: string;
+ descriptionPath: string;
+ playType: "SHORT" | "LONG";
+ plot: string;
+ intention: string;
+ buyStatus: number;
+ any: number;
+ male: number;
+ female: number;
+ stageComment: string;
+ runningTime: number;
+ scene: number;
+ act: number;
+}
+
+// 작품 관리 화면에서 심사/판매 상태별 UI를 한 번에 확인하기 위한 개발용 데이터입니다.
+let managedWorks: ManagedWork[] = [
+ {
+ id: "10000000-0000-4000-8000-000000000001",
+ title: "유리 온실의 밤",
+ writer: "테스트작가",
+ imagePath: makePlaceholderImage(101),
+ script: true,
+ scriptPrice: 5000,
+ performance: true,
+ performancePrice: 30000,
+ checked: "PASS",
+ createdDate: "2026-09-18",
+ descriptionPath: "/test.pdf",
+ playType: "LONG",
+ plot: "폐쇄를 앞둔 식물원에서 세 사람이 마지막 밤을 보내며 감춰 둔 진실을 마주한다.",
+ intention: "떠나보내는 과정에서 비로소 선명해지는 관계를 무대 위에 담고자 했습니다.",
+ buyStatus: 0,
+ any: 1,
+ male: 1,
+ female: 2,
+ stageComment: "온실을 연상시키는 투명한 소품과 푸른 조명을 권장합니다.",
+ runningTime: 90,
+ scene: 6,
+ act: 2,
+ },
+ {
+ id: "10000000-0000-4000-8000-000000000002",
+ title: "막차는 오지 않는다",
+ writer: "테스트작가",
+ imagePath: makePlaceholderImage(102),
+ script: true,
+ scriptPrice: 3500,
+ performance: false,
+ performancePrice: 0,
+ checked: "PASS",
+ createdDate: "2026-09-18",
+ descriptionPath: "/test.pdf",
+ playType: "SHORT",
+ plot: "막차가 끊긴 시골 정류장에서 낯선 두 사람이 서로의 목적지를 묻는다.",
+ intention: "기다림이라는 짧은 시간 안에 생기는 연대와 위로를 이야기합니다.",
+ buyStatus: 0,
+ any: 0,
+ male: 1,
+ female: 1,
+ stageComment: "벤치 하나만으로 공연할 수 있는 소극장용 작품입니다.",
+ runningTime: 35,
+ scene: 3,
+ act: 1,
+ },
+ {
+ id: "10000000-0000-4000-8000-000000000003",
+ title: "파도에게 보내는 편지",
+ writer: "테스트작가",
+ imagePath: makePlaceholderImage(103),
+ script: false,
+ scriptPrice: 4500,
+ performance: true,
+ performancePrice: 18000,
+ checked: "RE_WAIT",
+ createdDate: "2026-09-12",
+ descriptionPath: "/test.pdf",
+ playType: "LONG",
+ plot: "섬마을 우체국에 배달되지 못한 편지들이 쌓이면서 오래된 사건이 다시 떠오른다.",
+ intention: "전하지 못한 말이 사람과 공동체에 남기는 흔적을 그리고 싶었습니다.",
+ buyStatus: 0,
+ any: 1,
+ male: 2,
+ female: 3,
+ stageComment: "파도 소리를 장면 전환의 중심 요소로 사용합니다.",
+ runningTime: 105,
+ scene: 8,
+ act: 2,
+ },
+ {
+ id: "10000000-0000-4000-8000-000000000004",
+ title: "오늘의 분실물",
+ writer: "테스트작가",
+ imagePath: makePlaceholderImage(104),
+ script: false,
+ scriptPrice: 0,
+ performance: false,
+ performancePrice: 0,
+ checked: "WAIT",
+ createdDate: "2026-09-05",
+ descriptionPath: "/test.pdf",
+ playType: "SHORT",
+ plot: "분실물 보관소 직원이 주인 없는 물건들의 하루를 상상하며 기록한다.",
+ intention: "사라진 물건을 통해 평범한 하루의 소중함을 바라봅니다.",
+ buyStatus: 0,
+ any: 1,
+ male: 1,
+ female: 1,
+ stageComment: "여러 개의 상자와 작은 테이블이 필요합니다.",
+ runningTime: 45,
+ scene: 5,
+ act: 1,
+ },
+ {
+ id: "10000000-0000-4000-8000-000000000005",
+ title: "별이 내리는 세탁소",
+ writer: "테스트작가",
+ imagePath: makePlaceholderImage(105),
+ script: true,
+ scriptPrice: 6000,
+ performance: true,
+ performancePrice: 25000,
+ checked: "RE_PASS",
+ createdDate: "2026-08-27",
+ descriptionPath: "/test.pdf",
+ playType: "LONG",
+ plot: "밤에만 문을 여는 세탁소에서 손님들은 옷과 함께 잊고 싶은 기억을 맡긴다.",
+ intention: "기억을 지우는 대신 잘 개어 간직하는 방법에 관한 이야기입니다.",
+ buyStatus: 0,
+ any: 2,
+ male: 2,
+ female: 2,
+ stageComment: "회전하는 세탁기 조명을 별빛처럼 활용할 수 있습니다.",
+ runningTime: 80,
+ scene: 7,
+ act: 2,
+ },
+];
+
+const toManagedWorkList = () => {
+ const groups = new Map();
+ managedWorks.forEach((work) => {
+ groups.set(work.createdDate, [...(groups.get(work.createdDate) ?? []), work]);
+ });
+
+ return [...groups.entries()]
+ .sort(([dateA], [dateB]) => dateB.localeCompare(dateA))
+ .map(([date, works]) => ({
+ date,
+ works: works.map(({ createdDate: _createdDate, ...work }) => work),
+ }));
+};
+
+const detailReviews: Review[] = [
+ {
+ id: "20000000-0000-4000-8000-000000000001",
+ nickname: "포도독자",
+ stageType: "GRAPE_CLUSTER",
+ date: "2026-09-17T14:20:00",
+ isEdited: false,
+ myself: false,
+ rating: 5,
+ standardType: "STORY",
+ content:
+ "장면이 바뀔 때마다 앞에서 지나쳤던 대사가 새롭게 들렸어요. 마지막 장면까지 긴장감이 잘 이어지는 작품입니다.",
+ isLike: true,
+ likeCount: 18,
+ },
+ {
+ id: "20000000-0000-4000-8000-000000000002",
+ nickname: "무대위고양이",
+ stageType: "WINE",
+ date: "2026-09-15T09:10:00",
+ isEdited: true,
+ myself: false,
+ rating: 4,
+ standardType: "CHARACTER",
+ content:
+ "등장인물의 선택에 충분한 이유가 있어서 배우들과 인물을 분석하기 좋았습니다. 소극장 공연으로도 잘 어울릴 것 같아요.",
+ isLike: false,
+ likeCount: 11,
+ },
+ {
+ id: "20000000-0000-4000-8000-000000000003",
+ nickname: "오늘도연습",
+ stageType: "SINGLE_GRAPE",
+ date: "2026-09-12T18:45:00",
+ isEdited: false,
+ myself: false,
+ rating: 5,
+ standardType: "RELATION",
+ content: "인물 사이의 거리감이 대사와 동선으로 섬세하게 표현되어 있어 인상 깊었습니다.",
+ isLike: false,
+ likeCount: 7,
+ },
+ {
+ id: "20000000-0000-4000-8000-000000000004",
+ nickname: "극장앞자리",
+ stageType: "DEFAULT",
+ date: "2026-09-10T11:30:00",
+ isEdited: false,
+ myself: false,
+ rating: 3,
+ standardType: "STORY",
+ content: "중반부 전개는 조금 느렸지만 결말이 좋았고 읽고 난 뒤에도 여운이 남았습니다.",
+ isLike: false,
+ likeCount: 3,
+ },
+ {
+ id: "20000000-0000-4000-8000-000000000005",
+ nickname: "보라빛무대",
+ stageType: "GRAPE_CLUSTER",
+ date: "2026-09-08T20:05:00",
+ isEdited: false,
+ myself: false,
+ rating: 5,
+ standardType: "CHARACTER",
+ content: "각 인물의 말투가 뚜렷해서 대본만 읽어도 장면이 자연스럽게 그려졌습니다.",
+ isLike: true,
+ likeCount: 14,
+ },
+ {
+ id: "20000000-0000-4000-8000-000000000006",
+ nickname: "대학로산책",
+ stageType: "SINGLE_GRAPE",
+ date: "2026-09-05T16:40:00",
+ isEdited: false,
+ myself: false,
+ rating: 4,
+ standardType: "RELATION",
+ content: "적은 인원으로도 관계의 변화를 풍성하게 보여줄 수 있는 작품이라 좋았습니다.",
+ isLike: false,
+ likeCount: 5,
+ },
+];
+
+const toPercent = (count: number, total: number) =>
+ total === 0 ? 0 : Math.round((count / total) * 1000) / 10;
+
+const makeReviewStatistics = (reviews: Review[]): ReviewStatistics => {
+ const total = reviews.length;
+ const countRating = (rating: number) =>
+ reviews.filter((review) => review.rating === rating).length;
+ const countStandard = (standard: string) =>
+ reviews.filter((review) => review.standardType === standard).length;
+
+ return {
+ totalReviewCount: total,
+ reviewAverageRating:
+ total === 0
+ ? 0
+ : Math.round((reviews.reduce((sum, review) => sum + review.rating, 0) / total) * 10) / 10,
+ fiveStarPercent: toPercent(countRating(5), total),
+ fourStarPercent: toPercent(countRating(4), total),
+ threeStarPercent: toPercent(countRating(3), total),
+ twoStarPercent: toPercent(countRating(2), total),
+ oneStarPercent: toPercent(countRating(1), total),
+ characterPercent: toPercent(countStandard("CHARACTER"), total),
+ relationPercent: toPercent(countStandard("RELATION"), total),
+ storyPercent: toPercent(countStandard("STORY"), total),
+ };
+};
+
+const detailEngagement = new Map();
+
+const findDetailWork = (id: string) => {
+ const managed = managedWorks.find((work) => work.id === id);
+ if (managed) return managed;
+
+ const listed = [...longPlays, ...shortPlays].find((work) => work.id === id);
+ if (!listed) return undefined;
+
+ return {
+ ...listed,
+ createdDate: listed.date.slice(0, 10),
+ descriptionPath: "/test.pdf",
+ playType: longPlays.includes(listed) ? ("LONG" as const) : ("SHORT" as const),
+ plot: "서로 다른 비밀을 품은 인물들이 한 공간에 모이면서 예상하지 못한 진실과 마주한다.",
+ intention: "관계 속에서 쉽게 말하지 못했던 마음과 선택의 순간을 담아낸 작품입니다.",
+ buyStatus: 0,
+ any: 1,
+ male: 2,
+ female: 2,
+ stageComment: "간결한 무대와 장면의 분위기를 살리는 조명 연출을 권장합니다.",
+ runningTime: longPlays.includes(listed) ? 90 : 40,
+ scene: longPlays.includes(listed) ? 7 : 4,
+ act: longPlays.includes(listed) ? 2 : 1,
+ };
+};
+
+const getPdfResponse = async () => {
+ const pdf = await fetch(new URL("/test.pdf", globalThis.location.origin));
+ return pdf.arrayBuffer();
+};
+
export const handlers = [
// 로그인 (조건 없이 항상 성공)
http.post(`${BASE}/auth/signin`, async ({ request }) => {
@@ -623,6 +931,166 @@ export const handlers = [
});
}),
+ // 마이페이지 > 작품 관리 목록
+ http.get(`${BASE}/profile/work`, async () => {
+ await delay(250);
+ return HttpResponse.json({
+ nickname: "테스트작가",
+ dateWorks: toManagedWorkList(),
+ });
+ }),
+
+ // 작품 관리 상세
+ http.get(`${BASE}/profile/work/detail`, ({ request }) => {
+ const id = new URL(request.url).searchParams.get("script");
+ const work = managedWorks.find((item) => item.id === id);
+ if (!work) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+
+ const { createdDate, ...detail } = work;
+ return HttpResponse.json({ ...detail, date: `${createdDate}T12:00:00` });
+ }),
+
+ // 상세 수정, 삭제, 심사 취소 및 파일 변경 플로우
+ http.post(`${BASE}/profile/work/detail`, async ({ request }) => {
+ const form = await request.formData();
+ const id = String(form.get("id") ?? "");
+ const index = managedWorks.findIndex((item) => item.id === id);
+ if (index < 0) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+
+ const current = managedWorks[index];
+ managedWorks[index] = {
+ ...current,
+ title: String(form.get("title") ?? current.title),
+ plot: String(form.get("plot") ?? current.plot),
+ intention: String(form.get("intention") ?? current.intention),
+ script: form.get("script") === "true",
+ performance: form.get("performance") === "true",
+ scriptPrice: Number(form.get("scriptPrice") ?? current.scriptPrice),
+ performancePrice: Number(form.get("performancePrice") ?? current.performancePrice),
+ any: Number(form.get("any") ?? current.any),
+ male: Number(form.get("male") ?? current.male),
+ female: Number(form.get("female") ?? current.female),
+ stageComment: String(form.get("stageComment") ?? current.stageComment),
+ runningTime: Number(form.get("runningTime") ?? current.runningTime),
+ scene: Number(form.get("scene") ?? current.scene),
+ act: Number(form.get("act") ?? current.act),
+ };
+ return HttpResponse.json(true);
+ }),
+
+ http.delete(`${BASE}/profile/work/deleteScript/:id`, ({ params }) => {
+ const exists = managedWorks.some((work) => work.id === params.id);
+ if (!exists) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+ managedWorks = managedWorks.filter((work) => work.id !== params.id);
+ return HttpResponse.json(true);
+ }),
+
+ http.delete(`${BASE}/profile/work/cancel/:id`, ({ params }) => {
+ const exists = managedWorks.some((work) => work.id === params.id);
+ if (!exists) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+ managedWorks = managedWorks.filter((work) => work.id !== params.id);
+ return HttpResponse.json(true);
+ }),
+
+ http.post(`${BASE}/profile/work/changeScript`, async ({ request }) => {
+ const form = await request.formData();
+ const id = String(form.get("id") ?? "");
+ const work = managedWorks.find((item) => item.id === id);
+ if (!work) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+ work.checked = "RE_WAIT";
+ return HttpResponse.json(true);
+ }),
+
+ // 작품 상세: 정보, 후기 정렬 및 페이지네이션
+ http.get(`${BASE}/scripts/detail`, ({ request }) => {
+ const url = new URL(request.url);
+ const id = url.searchParams.get("script") ?? "";
+ const page = Number(url.searchParams.get("page") ?? 0);
+ const sortType = url.searchParams.get("sortType") ?? "LIKE_COUNT";
+ const work = findDetailWork(id);
+
+ if (!work) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+
+ const listed = [...longPlays, ...shortPlays].find((item) => item.id === id);
+ const engagement = detailEngagement.get(id) ?? {
+ like: listed?.like ?? false,
+ likeCount: listed?.likeCount ?? 24,
+ viewCount: listed?.viewCount ?? 187,
+ };
+ detailEngagement.set(id, engagement);
+
+ const sortedReviews = [...detailReviews].sort((a, b) =>
+ sortType === "LATEST"
+ ? new Date(b.date).getTime() - new Date(a.date).getTime()
+ : b.likeCount - a.likeCount
+ );
+ const reviews = sortedReviews.slice(page * 5, (page + 1) * 5);
+
+ return HttpResponse.json({
+ id: work.id,
+ title: work.title,
+ writer: work.writer,
+ imagePath: work.imagePath,
+ script: work.script,
+ scriptPrice: work.scriptPrice,
+ performance: work.performance,
+ performancePrice: work.performancePrice,
+ playType: work.playType,
+ checked: work.checked,
+ plot: work.plot,
+ date: `${work.createdDate}T12:00:00`,
+ buyOptions: [
+ ...(work.script ? ["SCRIPT"] : []),
+ ...(work.performance ? ["PERFORMANCE"] : []),
+ ],
+ like: engagement.like,
+ likeCount: engagement.likeCount,
+ viewCount: engagement.viewCount,
+ any: work.any,
+ male: work.male,
+ female: work.female,
+ stageComment: work.stageComment,
+ runningTime: work.runningTime,
+ scene: work.scene,
+ act: work.act,
+ intention: work.intention,
+ isReviewWritten: false,
+ isMine: managedWorks.some((managedWork) => managedWork.id === id),
+ reviewStatistics: makeReviewStatistics(detailReviews),
+ reviews,
+ });
+ }),
+
+ // 상세 화면에서 자동으로 불러오는 작품 설명 PDF와 대본 미리보기
+ http.get(`${BASE}/scripts/description`, async () => {
+ const pdf = await getPdfResponse();
+ return new HttpResponse(pdf, {
+ headers: { "Content-Type": "application/pdf" },
+ });
+ }),
+
+ http.get(`${BASE}/scripts/preview`, async ({ request }) => {
+ const id = new URL(request.url).searchParams.get("script") ?? "";
+ const work = findDetailWork(id);
+ if (!work) return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+
+ const pdf = await getPdfResponse();
+ return new HttpResponse(pdf, {
+ headers: {
+ "Content-Type": "application/pdf",
+ "X-Total-Pages": work.playType === "SHORT" ? "18" : "72",
+ },
+ });
+ }),
+
+ http.post(`${BASE}/scripts/review/like/:id`, ({ params }) => {
+ const review = detailReviews.find((item) => item.id === params.id);
+ if (!review) return HttpResponse.json({ error: "후기를 찾을 수 없습니다." }, { status: 404 });
+ review.isLike = !review.isLike;
+ review.likeCount += review.isLike ? 1 : -1;
+ return HttpResponse.json(true);
+ }),
+
// 좋아요한 작품 조회
http.get(`${BASE}/profile/like`, async () => {
await delay(350);
@@ -638,9 +1106,7 @@ export const handlers = [
const url = new URL(request.url);
const page = Number(url.searchParams.get("page") ?? 0);
const likedLongPlays = getLikedLongPlays();
- return HttpResponse.json(
- likedLongPlays.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
- );
+ return HttpResponse.json(likedLongPlays.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE));
}),
// 좋아요한 단편 작품 페이지네이션
@@ -649,9 +1115,7 @@ export const handlers = [
const url = new URL(request.url);
const page = Number(url.searchParams.get("page") ?? 0);
const likedShortPlays = getLikedShortPlays();
- return HttpResponse.json(
- likedShortPlays.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
- );
+ return HttpResponse.json(likedShortPlays.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE));
}),
// 전체 작품 조회 (페이지네이션, Spring Page 응답 형태)
@@ -753,10 +1217,28 @@ export const handlers = [
// 좋아요 토글
http.post(`${BASE}/scripts/like/:id`, ({ params }) => {
- const target = [...longPlays, ...shortPlays].find((s) => s.id === params.id);
- if (!target) return new HttpResponse(null, { status: 404 });
- target.like = !target.like;
- target.likeCount += target.like ? 1 : -1;
- return HttpResponse.json({ message: target.like ? "like" : "cancel like" });
+ const id = String(params.id);
+ const target = [...longPlays, ...shortPlays].find((script) => script.id === id);
+
+ if (target) {
+ target.like = !target.like;
+ target.likeCount += target.like ? 1 : -1;
+ const engagement = detailEngagement.get(id);
+ if (engagement) {
+ engagement.like = target.like;
+ engagement.likeCount = target.likeCount;
+ }
+ return HttpResponse.json({ message: target.like ? "like" : "cancel like" });
+ }
+
+ if (!managedWorks.some((work) => work.id === id)) {
+ return HttpResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
+ }
+
+ const engagement = detailEngagement.get(id) ?? { like: false, likeCount: 24, viewCount: 187 };
+ engagement.like = !engagement.like;
+ engagement.likeCount += engagement.like ? 1 : -1;
+ detailEngagement.set(id, engagement);
+ return HttpResponse.json({ message: engagement.like ? "like" : "cancel like" });
}),
];
diff --git a/src/pages/myPage/PostManage/PostManageDetail.tsx b/src/pages/myPage/PostManage/PostManageDetail.tsx
index 113c84e9..6756a4cc 100644
--- a/src/pages/myPage/PostManage/PostManageDetail.tsx
+++ b/src/pages/myPage/PostManage/PostManageDetail.tsx
@@ -21,7 +21,7 @@ import useWindowDimensions from "@/hooks/useWindowDimensions";
import grayCheckIcon from "@/assets/image/myPage/ic_gray_check.svg";
import puppleCheckIcon from "@/assets/image/myPage/ic_pupple_check.svg";
import stickIcon from "@/assets/image/myPage/ic_stick.svg";
-import puppleLine from "@/assets/image/myPage/pupple_line.svg";
+import puppleLine from "@/assets/image/myPage/pupple_dot_line.svg";
import circleInfoBtn from "@/assets/image/button/circleInfoBtn.svg";
import Cookies from "js-cookie";
@@ -279,7 +279,7 @@ const PostManageDetail: React.FC = () => {
isSmallMobile ? "p-small-bold mb-[8px]" : "p-medium-bold mb-[10px]"
}`}
>
- 작품 정보
+ *작품 정보
@@ -335,13 +335,13 @@ const PostManageDetail: React.FC = () => {
{/* --- 개요 --- */}
-
개요
+
+ *개요
+
{/* 등장인물 */}
-
-
+
+
{" "}

{
alt="입력 체크"
/>
@@ -360,7 +360,7 @@ const PostManageDetail: React.FC = () => {
@@ -545,7 +545,7 @@ const PostManageDetail: React.FC = () => {
alt="입력 체크"
/>
@@ -609,132 +609,6 @@ const PostManageDetail: React.FC = () => {
- {/* 무대 */}
-
-
- {" "}
-

-
- 무대
-
-
-

-
- {/* 장과 막 */}
-
-
- {" "}
-

-
- 막과 장
-
-
-

-
- {
- const value = e.target.value;
-
- setIsTouched((prev) => ({ ...prev, act: true }));
-
- // 빈 문자열 입력 시 → 상태를 빈 문자열로 유지
- if (value === "") {
- setForm((prev) => ({ ...prev, act: undefined })); // 또는 null
- } else if (/^\d{0,2}$/.test(value)) {
- setForm((prev) => ({
- ...prev,
- act: Number(value),
- }));
- }
- }}
- />
-
- 막
-
- {
- const value = e.target.value;
-
- setIsTouched((prev) => ({ ...prev, scene: true }));
-
- // 빈 문자열 입력 시 → 상태를 빈 문자열로 유지
- if (value === "") {
- setForm((prev) => ({ ...prev, scene: undefined })); // 또는 null
- } else if (/^\d{0,2}$/.test(value)) {
- setForm((prev) => ({
- ...prev,
- scene: Number(value),
- }));
- }
- }}
- />
-
- 장
-
-
-
@@ -742,7 +616,7 @@ const PostManageDetail: React.FC = () => {
- 작가 의도{" "}
+ *작가 의도{" "}
From ddb018a7a2e4a9bf8408d421254e0edd6e689941 Mon Sep 17 00:00:00 2001
From: goodsmell <87801306+goodsmell@users.noreply.github.com>
Date: Sat, 19 Sep 2026 19:02:23 +0900
Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EB=8C=80=EB=B3=B8=EB=AC=B4?=
=?UTF-8?q?=EB=A3=8C=EC=97=B4=EB=9E=8C=20=EB=B2=84=ED=8A=BC=20=ED=99=9C?=
=?UTF-8?q?=EC=84=B1=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/mocks/handlers.ts | 14 +++++++------
src/pages/work/Detail.tsx | 44 ++++++++++++++++++++++++++-------------
2 files changed, 37 insertions(+), 21 deletions(-)
diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts
index 03958e90..34a39494 100644
--- a/src/mocks/handlers.ts
+++ b/src/mocks/handlers.ts
@@ -11,7 +11,6 @@ const makeScript = (overrides: Partial
& { id: string }): ScriptItem
writer: "작가명",
imagePath: makePlaceholderImage(Number(overrides.id)),
script: false,
- scriptPrice: 0,
performance: false,
performancePrice: 0,
checked: "PASS",
@@ -20,6 +19,8 @@ const makeScript = (overrides: Partial & { id: string }): ScriptItem
likeCount: 0,
viewCount: 0,
...overrides,
+ // 현재 mock 작품은 포도알 스테이지 데이터이므로 대본 가격은 무료로 고정합니다.
+ scriptPrice: 0,
});
const daysAgo = (n: number) => new Date(Date.now() - n * 86400000).toISOString();
@@ -639,7 +640,7 @@ let managedWorks: ManagedWork[] = [
writer: "테스트작가",
imagePath: makePlaceholderImage(101),
script: true,
- scriptPrice: 5000,
+ scriptPrice: 0,
performance: true,
performancePrice: 30000,
checked: "PASS",
@@ -663,7 +664,7 @@ let managedWorks: ManagedWork[] = [
writer: "테스트작가",
imagePath: makePlaceholderImage(102),
script: true,
- scriptPrice: 3500,
+ scriptPrice: 0,
performance: false,
performancePrice: 0,
checked: "PASS",
@@ -687,7 +688,7 @@ let managedWorks: ManagedWork[] = [
writer: "테스트작가",
imagePath: makePlaceholderImage(103),
script: false,
- scriptPrice: 4500,
+ scriptPrice: 0,
performance: true,
performancePrice: 18000,
checked: "RE_WAIT",
@@ -735,7 +736,7 @@ let managedWorks: ManagedWork[] = [
writer: "테스트작가",
imagePath: makePlaceholderImage(105),
script: true,
- scriptPrice: 6000,
+ scriptPrice: 0,
performance: true,
performancePrice: 25000,
checked: "RE_PASS",
@@ -965,7 +966,8 @@ export const handlers = [
intention: String(form.get("intention") ?? current.intention),
script: form.get("script") === "true",
performance: form.get("performance") === "true",
- scriptPrice: Number(form.get("scriptPrice") ?? current.scriptPrice),
+ // 포도알 스테이지에서는 대본 가격을 변경할 수 없습니다.
+ scriptPrice: 0,
performancePrice: Number(form.get("performancePrice") ?? current.performancePrice),
any: Number(form.get("any") ?? current.any),
male: Number(form.get("male") ?? current.male),
diff --git a/src/pages/work/Detail.tsx b/src/pages/work/Detail.tsx
index dfe6bc54..9d38364e 100644
--- a/src/pages/work/Detail.tsx
+++ b/src/pages/work/Detail.tsx
@@ -2,7 +2,7 @@ import { authApi, api } from "@/api/api";
import Cookies from "js-cookie";
import { useEffect, useState, useRef, useContext, lazy, Suspense } from "react";
import { Document, Page, pdfjs } from "react-pdf";
-import { useNavigate, useParams } from "react-router-dom";
+import { useLocation, useNavigate, useParams } from "react-router-dom";
import Loading from "../Loading";
import AuthContext from "@/contexts/AuthContext";
import defaultImg from "../../assets/image/post/list/defaultProfile_noneBorder.png";
@@ -112,6 +112,7 @@ const Detail = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
+ const location = useLocation();
const [sort, setSort] = useState<"LIKE_COUNT" | "LATEST">("LIKE_COUNT");
const [openSort, setOpenSort] = useState(false);
@@ -129,8 +130,14 @@ const Detail = () => {
const inflightRef = useRef