feat: 업무 지시 필드별 인라인 검증 + 제출 버튼 상시 활성화
기존: 제목·담당자 미입력 시 '지시 보내기' 버튼이 비활성화(이유 안내 없음) 변경: - 버튼 비활성화 해제(제출 중 중복 클릭 방지용 submitting 만 유지) - 제출 시 validateForm 으로 검증 → 미입력 필드에 인라인 에러 표시 · 제목: 빨강 placeholder + '업무 제목을 입력해 주세요.' · 담당자: combo 빨강 테두리 + '담당자를 1명 이상 지정해 주세요.' - 해당 필드를 채우면 watch 로 에러 즉시 해제 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -257,12 +257,28 @@ async function uploadPendingFiles(tid: number): Promise<number> {
|
||||
|
||||
// --- 제출 ---
|
||||
const submitting = ref(false)
|
||||
const canSubmit = computed(
|
||||
() => !submitting.value && title.value.trim().length > 0 && assignees.value.length > 0,
|
||||
// 필드별 인라인 검증 — 제목·담당자 필수. 입력하면 즉시 해제.
|
||||
const titleError = ref(false)
|
||||
const assigneeError = ref(false)
|
||||
watch(title, (v) => {
|
||||
if (v.trim().length > 0) titleError.value = false
|
||||
})
|
||||
watch(
|
||||
assignees,
|
||||
(v) => {
|
||||
if (v.length > 0) assigneeError.value = false
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
function validateForm(): boolean {
|
||||
titleError.value = title.value.trim().length === 0
|
||||
assigneeError.value = assignees.value.length === 0
|
||||
return !titleError.value && !assigneeError.value
|
||||
}
|
||||
|
||||
async function submitTask() {
|
||||
if (!canSubmit.value) return
|
||||
if (submitting.value) return
|
||||
if (!validateForm()) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const content = contentText.value
|
||||
@@ -374,7 +390,7 @@ function goBack() {
|
||||
<button
|
||||
class="btn primary"
|
||||
type="button"
|
||||
:disabled="!canSubmit"
|
||||
:disabled="submitting"
|
||||
@click="submitTask"
|
||||
>
|
||||
<svg
|
||||
@@ -401,8 +417,15 @@ function goBack() {
|
||||
<input
|
||||
v-model="title"
|
||||
class="title-input"
|
||||
:class="{ 'has-error': titleError }"
|
||||
placeholder="업무 제목을 입력하세요"
|
||||
>
|
||||
<div
|
||||
v-if="titleError"
|
||||
class="field-error"
|
||||
>
|
||||
업무 제목을 입력해 주세요.
|
||||
</div>
|
||||
<div class="divider" />
|
||||
|
||||
<!-- 업무 내용 -->
|
||||
@@ -661,7 +684,10 @@ function goBack() {
|
||||
</div>
|
||||
<div class="combo">
|
||||
<!-- 입력칸은 고정 — 선택된 담당자는 아래에 표기 -->
|
||||
<div class="combo-field">
|
||||
<div
|
||||
class="combo-field"
|
||||
:class="{ 'has-error': assigneeError }"
|
||||
>
|
||||
<input
|
||||
v-model="assigneeQuery"
|
||||
class="combo-input"
|
||||
@@ -729,6 +755,12 @@ function goBack() {
|
||||
>×</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="assigneeError"
|
||||
class="field-error"
|
||||
>
|
||||
담당자를 1명 이상 지정해 주세요.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -949,6 +981,14 @@ function goBack() {
|
||||
.title-input::placeholder {
|
||||
color: #c4c8cf;
|
||||
}
|
||||
.title-input.has-error::placeholder {
|
||||
color: var(--red);
|
||||
}
|
||||
.field-error {
|
||||
font-size: 0.75rem;
|
||||
color: var(--red);
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
@@ -1429,6 +1469,12 @@ function goBack() {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-weak);
|
||||
}
|
||||
.combo-field.has-error {
|
||||
border-color: var(--red);
|
||||
}
|
||||
.combo-field.has-error:focus-within {
|
||||
box-shadow: 0 0 0 3px var(--red-weak);
|
||||
}
|
||||
.combo-caret {
|
||||
color: var(--text-3);
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user