From 612d6c7de89089267cc7057beeba762ead82352a Mon Sep 17 00:00:00 2001 From: ttipo Date: Sun, 21 Jun 2026 11:36:58 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=97=85=EB=AC=B4=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=20=ED=95=84=EC=88=98=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20+=20=EC=A0=9C=EB=AA=A9=20=EC=97=90=EB=9F=AC=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 업무 내용(*)이 표기상 필수인데 검증이 없던 문제 → 필수로 강제 · 프론트: 인라인 검증(에디터 빨강 테두리 + '업무 내용을 입력해 주세요.'), 입력 시 즉시 해제 · 백엔드: CreateTaskDto.content 를 @ArrayNotEmpty 필수로(데이터 계약 일치) - 제목 에러 표기 정리: 1.5rem 큰 placeholder 가 빨갛게 변하며 아래 메시지와 이중으로 강조되던 것을 제거 → 작은 인라인 메시지만 표시(차분하게) 검증: 백엔드 build/lint 0·8 tests, 프론트 type-check/lint/build 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/modules/task/dto/create-task.dto.ts | 7 ++--- frontend/src/pages/relay/TaskCreatePage.vue | 30 ++++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/backend/src/modules/task/dto/create-task.dto.ts b/backend/src/modules/task/dto/create-task.dto.ts index a988972..d093ee0 100644 --- a/backend/src/modules/task/dto/create-task.dto.ts +++ b/backend/src/modules/task/dto/create-task.dto.ts @@ -37,17 +37,16 @@ export class CreateTaskDto { title!: string; @ApiProperty({ - description: '본문 문단 배열(선택)', + description: '본문 문단 배열(필수)', type: [String], - required: false, example: ['9월 신제품 캠페인 메인 배너를 제작합니다.'], }) - @IsOptional() @IsArray() + @ArrayNotEmpty({ message: '업무 내용을 입력해 주세요.' }) @ArrayMaxSize(200, { message: '본문 문단이 너무 많습니다.' }) @IsString({ each: true }) @MaxLength(5000, { each: true }) - content?: string[]; + content!: string[]; @ApiProperty({ description: '담당자 사용자 ID 배열(프로젝트 멤버만 가능)', diff --git a/frontend/src/pages/relay/TaskCreatePage.vue b/frontend/src/pages/relay/TaskCreatePage.vue index b23ef8c..db6a57c 100644 --- a/frontend/src/pages/relay/TaskCreatePage.vue +++ b/frontend/src/pages/relay/TaskCreatePage.vue @@ -257,12 +257,16 @@ async function uploadPendingFiles(tid: number): Promise { // --- 제출 --- const submitting = ref(false) -// 필드별 인라인 검증 — 제목·담당자 필수. 입력하면 즉시 해제. +// 필드별 인라인 검증 — 제목·내용·담당자 필수. 입력하면 즉시 해제. const titleError = ref(false) +const contentError = ref(false) const assigneeError = ref(false) watch(title, (v) => { if (v.trim().length > 0) titleError.value = false }) +watch(contentText, (v) => { + if (v.trim().length > 0) contentError.value = false +}) watch( assignees, (v) => { @@ -272,8 +276,9 @@ watch( ) function validateForm(): boolean { titleError.value = title.value.trim().length === 0 + contentError.value = contentText.value.trim().length === 0 assigneeError.value = assignees.value.length === 0 - return !titleError.value && !assigneeError.value + return !titleError.value && !contentError.value && !assigneeError.value } async function submitTask() { @@ -417,7 +422,6 @@ function goBack() {
* 업무 내용
-
+
+
+ 업무 내용을 입력해 주세요. +
@@ -981,9 +994,6 @@ function goBack() { .title-input::placeholder { color: #c4c8cf; } -.title-input.has-error::placeholder { - color: var(--red); -} .field-error { font-size: 0.75rem; color: var(--red); @@ -1023,6 +1033,12 @@ function goBack() { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak); } +.editor.has-error { + border-color: var(--red); +} +.editor.has-error:focus-within { + box-shadow: 0 0 0 3px var(--red-weak); +} .editor-toolbar { display: flex; align-items: center;