From 4960fe7352ddefc72170360c77d06c432729f9b3 Mon Sep 17 00:00:00 2001 From: EENE Dashboard Date: Fri, 5 Jun 2026 22:55:11 +0900 Subject: [PATCH] fix: feedback author display, sidebar scroll, stage sort by start date Co-authored-by: Cursor --- .../migration.sql | 1 + backend/prisma/schema.prisma | 1 + backend/src/routes/details.ts | 13 +++------- .../src/components/detail/FeedbackModal.tsx | 2 +- frontend/src/pages/DetailPage.tsx | 25 ++++++++++++++----- frontend/src/types/index.ts | 1 + 6 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 backend/prisma/migrations/20260606120000_task_detail_author_name/migration.sql diff --git a/backend/prisma/migrations/20260606120000_task_detail_author_name/migration.sql b/backend/prisma/migrations/20260606120000_task_detail_author_name/migration.sql new file mode 100644 index 0000000..c188e1d --- /dev/null +++ b/backend/prisma/migrations/20260606120000_task_detail_author_name/migration.sql @@ -0,0 +1 @@ +ALTER TABLE "task_details" ADD COLUMN IF NOT EXISTS "authorName" TEXT; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index cd6fe13..b2e83fe 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -99,6 +99,7 @@ model TaskDetail { taskId String milestoneId String? content String + authorName String? // 피드백 작성자 표시명 (자유 입력) updatedBy String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/backend/src/routes/details.ts b/backend/src/routes/details.ts index 10783ad..1bd2c61 100644 --- a/backend/src/routes/details.ts +++ b/backend/src/routes/details.ts @@ -5,15 +5,6 @@ import { AppError } from '../middleware/errorHandler'; const router = Router(); -async function resolveAuthorId(taskId: string, authorName?: string): Promise { - const name = authorName?.toString().trim(); - if (name) { - const user = await prisma.user.findFirst({ where: { name } }); - if (user) return user.id; - } - return resolveTaskActorId(taskId); -} - // POST /api/details/:taskId router.post('/:taskId', async (req, res, next) => { try { @@ -30,13 +21,15 @@ router.post('/:taskId', async (req, res, next) => { if (!ms) throw new AppError(400, '유효하지 않은 업무 단계입니다.'); } - const updatedBy = await resolveAuthorId(taskId, authorName); + const displayName = authorName?.toString().trim() || null; + const updatedBy = await resolveTaskActorId(taskId); const detail = await prisma.taskDetail.create({ data: { taskId, milestoneId: milestoneId || null, content: content.toString().trim(), + authorName: displayName, updatedBy, }, include: { author: { select: { id: true, name: true } } }, diff --git a/frontend/src/components/detail/FeedbackModal.tsx b/frontend/src/components/detail/FeedbackModal.tsx index 53f7cca..84470c5 100644 --- a/frontend/src/components/detail/FeedbackModal.tsx +++ b/frontend/src/components/detail/FeedbackModal.tsx @@ -26,7 +26,7 @@ export function FeedbackModal({ }: FeedbackModalProps) { const [form, setForm] = useState({ content: detail?.content ?? '', - authorName: detail?.author?.name ?? defaultAuthorName, + authorName: detail?.authorName ?? detail?.author?.name ?? defaultAuthorName, }); const handleSubmit = async (e: React.FormEvent) => { diff --git a/frontend/src/pages/DetailPage.tsx b/frontend/src/pages/DetailPage.tsx index 90bbd5b..d13cca4 100644 --- a/frontend/src/pages/DetailPage.tsx +++ b/frontend/src/pages/DetailPage.tsx @@ -58,6 +58,19 @@ function sortByIsoDesc(items: T[], pick: (item: T) => string) { return [...items].sort((a, b) => new Date(pick(b)).getTime() - new Date(pick(a)).getTime()); } +/** 시작일이 늦은 단계가 상단 (시작일 없으면 종료일 → 생성일 순) */ +function sortStagesByStartDesc(stages: Milestone[]) { + const pickStart = (m: Milestone) => + m.startDate ?? m.dueDate ?? m.createdAt; + return [...stages].sort( + (a, b) => new Date(pickStart(b)).getTime() - new Date(pickStart(a)).getTime(), + ); +} + +function feedbackAuthorName(detail: TaskDetail) { + return detail.authorName?.trim() || detail.author?.name || '—'; +} + function milestoneProgress(m: Milestone) { if (m.completedAt) return 100; const p = m.progress ?? 0; @@ -215,7 +228,7 @@ function DetailView({ task }: { task: TaskWithRelations }) { const details = task.details ?? []; const sortedStages = useMemo( - () => sortByIsoDesc(milestones, (m) => m.updatedAt), + () => sortStagesByStartDesc(milestones), [milestones], ); @@ -404,7 +417,7 @@ function DetailView({ task }: { task: TaskWithRelations }) { + -
+
{sortedStages.length === 0 && (

+ 버튼으로 단계를 추가하세요.

)} @@ -450,7 +463,7 @@ function DetailView({ task }: { task: TaskWithRelations }) { 업무내용
    { if (!selected) return; e.preventDefault(); @@ -495,7 +508,7 @@ function DetailView({ task }: { task: TaskWithRelations }) {
{ if (!selectedId) return; e.preventDefault(); @@ -505,7 +518,7 @@ function DetailView({ task }: { task: TaskWithRelations }) { {sortedFeedbacks.length === 0 ? (

우클릭 또는 + 버튼으로 피드백을 추가하세요.

) : ( -
+
{sortedFeedbacks.map((f) => (

{f.content} - — {f.author?.name ?? '—'} + — {feedbackAuthorName(f)}

))} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 95843f9..6c35d82 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -47,6 +47,7 @@ export interface TaskDetail { taskId: string; milestoneId: string | null; content: string; + authorName: string | null; updatedBy: string; createdAt: string; updatedAt: string;