feat: 업무 상세 화면을 첨부파일 보기 전용으로 변경

상세 화면에서 첨부 업로드·삭제 UI/로직을 제거하고 목록 표시만 유지.
업로드는 작성·편집 화면에서만 가능하도록 일원화(미사용 스토어 액션·CSS 정리).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 12:05:53 +09:00
parent fd2e3245b4
commit 1e6acff4f1
2 changed files with 1 additions and 146 deletions
+1 -124
View File
@@ -14,7 +14,6 @@ import { useRoute, useRouter, RouterLink } from 'vue-router'
import AppShell from '@/layouts/AppShell.vue'
import {
CHECKLIST_CATEGORIES,
type Attachment,
type ChecklistCategory,
type ChecklistItem,
type Comment,
@@ -332,39 +331,8 @@ const commentTotal = computed(
)
/* ============================================================
* 파일 첨부(5단계) — 업로드/삭제
* 파일 첨부 — 상세 화면은 목록 표시 전용(업로드/삭제는 작성·편집 화면에서만).
* ============================================================ */
const fileInput = ref<HTMLInputElement | null>(null)
const uploading = ref(false)
function pickFile() {
fileInput.value?.click()
}
async function onFileChange(e: Event) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
if (!file || !task.value) {
input.value = ''
return
}
uploading.value = true
try {
task.value = await taskStore.uploadFile(projectId.value, task.value.id, file)
} catch {
// 인터셉터가 알림 처리
} finally {
uploading.value = false
input.value = ''
}
}
async function removeFile(att: Attachment) {
if (!task.value || !att.id) return
try {
task.value = await taskStore.removeFile(projectId.value, task.value.id, att.id)
} catch {
// 인터셉터가 알림 처리
}
}
/* ============================================================
* 키보드 / 외부 클릭 처리
@@ -1134,29 +1102,7 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
<div class="side-field">
<div class="side-label files-head">
<span>첨부파일 · {{ task.files.length }}</span>
<button
class="file-add"
type="button"
:disabled="uploading"
@click="pickFile"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path d="M12 5v14M5 12h14" /></svg>
{{ uploading ? '업로드 중…' : '추가' }}
</button>
</div>
<input
ref="fileInput"
class="file-hidden"
type="file"
@change="onFileChange"
>
<div
v-if="task.files.length"
class="files"
@@ -1179,22 +1125,6 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
target="_blank"
rel="noopener"
><span class="file-name">{{ f.name }}</span><span class="file-size">{{ f.size }}</span></a>
<button
class="file-del"
type="button"
title="첨부 삭제"
aria-label="첨부 삭제"
@click="removeFile(f)"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path d="M18 6 6 18M6 6l12 12" /></svg>
</button>
</div>
</div>
<p
@@ -3018,34 +2948,6 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
align-items: center;
justify-content: space-between;
}
.file-add {
display: inline-flex;
align-items: center;
gap: 0.25rem;
border: 1px solid var(--border);
background: #fff;
font-family: inherit;
font-size: 0.719rem;
font-weight: 600;
color: var(--text-2);
padding: 0.1875rem 0.5rem;
border-radius: var(--radius-sm);
cursor: pointer;
}
.file-add:hover {
background: #f5f6f8;
}
.file-add:disabled {
opacity: 0.6;
cursor: default;
}
.file-add svg {
width: 0.75rem;
height: 0.75rem;
}
.file-hidden {
display: none;
}
a.file-ico,
a.file-info {
text-decoration: none;
@@ -3057,31 +2959,6 @@ a.file-ico {
a.file-info {
color: inherit;
}
.file-del {
width: 1.5rem;
height: 1.5rem;
flex-shrink: 0;
display: grid;
place-items: center;
border: none;
background: transparent;
color: var(--text-3);
border-radius: var(--radius-sm);
cursor: pointer;
opacity: 0;
transition: opacity 0.12s;
}
.file:hover .file-del {
opacity: 1;
}
.file-del:hover {
background: var(--red-weak);
color: var(--red);
}
.file-del svg {
width: 0.875rem;
height: 0.875rem;
}
.files-empty {
font-size: 0.781rem;
color: var(--text-3);
-22
View File
@@ -328,26 +328,6 @@ export const useTaskStore = defineStore('task', () => {
return refresh(projectId, taskId)
}
/* ===================== 파일 첨부 (5단계) ===================== */
async function uploadFile(
projectId: string,
taskId: number,
file: File,
): Promise<TaskDetailView> {
await taskApi.uploadFile(projectId, taskId, file)
return refresh(projectId, taskId)
}
async function removeFile(
projectId: string,
taskId: number,
fileId: string,
): Promise<TaskDetailView> {
await taskApi.removeFile(projectId, taskId, fileId)
return refresh(projectId, taskId)
}
/* ===================== 승인 워크플로우 (5단계) ===================== */
async function requestApproval(
@@ -396,8 +376,6 @@ export const useTaskStore = defineStore('task', () => {
changeStatus,
addComment,
addReply,
uploadFile,
removeFile,
requestApproval,
approve,
requestChanges,