refactor: 프로젝트 상세 AI 검토 문서를 버튼+모달 리스트로 변경
인라인 wrap 나열 제거, 'AI 검토 문서' 버튼 클릭 시 모달에서 파일종류 배지·AI상태·크기를 세로 리스트로 표시 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
// 프로젝트 AI 검토 문서 모달 — 연동 드라이브 폴더 내 문서를 리스트로 표시(다운로드 전용)
|
||||
import BaseModal from '@/components/common/BaseModal.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import AttachmentAiBadge from '@/components/common/AttachmentAiBadge.vue'
|
||||
import { useDrive } from '@/composables/useDrive'
|
||||
import { fileBadgeOf, formatBytes } from '@/shared/utils/format'
|
||||
import type { ApiProjectDocument, ApiProjectDriveLink } from '@/types/project'
|
||||
|
||||
defineProps<{
|
||||
documents: ApiProjectDocument[]
|
||||
driveLinks: ApiProjectDriveLink[]
|
||||
}>()
|
||||
defineEmits<{ (e: 'close'): void }>()
|
||||
|
||||
const drive = useDrive()
|
||||
|
||||
// 드라이브 단기 presigned URL 발급 후 다운로드
|
||||
async function download(fileId: string): Promise<void> {
|
||||
const url = await drive.getDownloadUrl(fileId)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.rel = 'noopener'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseModal
|
||||
title="AI 검토 문서"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<template #subtitle>
|
||||
연동된 드라이브 폴더 <b>{{ driveLinks.length }}</b>개의 문서를 AI 업무 추천에 활용합니다.
|
||||
</template>
|
||||
|
||||
<EmptyState v-if="documents.length === 0">
|
||||
연동 폴더에 문서가 없습니다. 설정에서 드라이브 폴더를 연동해 보세요.
|
||||
</EmptyState>
|
||||
<div
|
||||
v-else
|
||||
class="pdm-list"
|
||||
>
|
||||
<button
|
||||
v-for="d in documents"
|
||||
:key="d.id"
|
||||
class="pdm-row"
|
||||
type="button"
|
||||
title="다운로드"
|
||||
@click="download(d.id)"
|
||||
>
|
||||
<span
|
||||
class="pdm-ico"
|
||||
:class="fileBadgeOf(d.name).cls"
|
||||
>{{ fileBadgeOf(d.name).label }}</span>
|
||||
<span class="pdm-main">
|
||||
<span class="pdm-name">{{ d.name }}</span>
|
||||
<span class="pdm-size">{{ formatBytes(d.size) }}</span>
|
||||
</span>
|
||||
<AttachmentAiBadge :status="d.aiStatus" />
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pdm-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.625rem;
|
||||
overflow: hidden;
|
||||
max-height: 24rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.pdm-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
.pdm-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.pdm-row:hover {
|
||||
background: #fafbfc;
|
||||
}
|
||||
.pdm-ico {
|
||||
flex: 0 0 auto;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
color: #fff;
|
||||
}
|
||||
.pdm-main {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
.pdm-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pdm-size {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-3);
|
||||
}
|
||||
/* 파일 종류 배지 색상(format.fileBadgeOf 와 동일) */
|
||||
.fi-img {
|
||||
background: #2aa775;
|
||||
}
|
||||
.fi-pdf {
|
||||
background: #e25950;
|
||||
}
|
||||
.fi-doc {
|
||||
background: #3d8bf2;
|
||||
}
|
||||
.fi-xls {
|
||||
background: #1f9d57;
|
||||
}
|
||||
.fi-ppt {
|
||||
background: #e0823d;
|
||||
}
|
||||
.fi-zip {
|
||||
background: #8a64d6;
|
||||
}
|
||||
.fi-txt {
|
||||
background: #5b6470;
|
||||
}
|
||||
.fi-media {
|
||||
background: #c0469d;
|
||||
}
|
||||
</style>
|
||||
@@ -12,12 +12,11 @@ import DropdownSelect from '@/components/DropdownSelect.vue'
|
||||
import SegmentedTabs from '@/components/common/SegmentedTabs.vue'
|
||||
import LoadMore from '@/components/common/LoadMore.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import AttachmentAiBadge from '@/components/common/AttachmentAiBadge.vue'
|
||||
import ProjectDocumentsModal from '@/components/project/ProjectDocumentsModal.vue'
|
||||
import { useProjectStore } from '@/stores/project.store'
|
||||
import { useTaskStore } from '@/stores/task.store'
|
||||
import { useDrive } from '@/composables/useDrive'
|
||||
import type { TaskListQuery, TaskSegment, TaskSort } from '@/types/task'
|
||||
import { taskStatusLabel, formatBytes } from '@/shared/utils/format'
|
||||
import { taskStatusLabel } from '@/shared/utils/format'
|
||||
import type { Project } from '@/mock/relay.mock'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -36,17 +35,8 @@ async function loadProject() {
|
||||
}
|
||||
}
|
||||
|
||||
// 연동 문서 다운로드 — 드라이브 단기 presigned URL 발급 후 다운로드
|
||||
const driveApi = useDrive()
|
||||
async function downloadDoc(fileId: string): Promise<void> {
|
||||
const url = await driveApi.getDownloadUrl(fileId)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.rel = 'noopener'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
}
|
||||
// AI 검토 문서 모달 표시 여부
|
||||
const docsModalOpen = ref(false)
|
||||
|
||||
const keyword = ref('')
|
||||
const segment = ref<TaskSegment>('all')
|
||||
@@ -187,12 +177,16 @@ const overdueCount = computed(() => tasks.value.filter((t) => t.overdue).length)
|
||||
:can-manage="repo.canManage"
|
||||
/>
|
||||
|
||||
<!-- AI 검토 문서(연동 드라이브 폴더 내 파일, 다운로드 전용) -->
|
||||
<!-- AI 검토 문서 — 버튼으로 모달 열기(연동 폴더가 있을 때만 노출) -->
|
||||
<div
|
||||
v-if="(repo.documents && repo.documents.length) || (repo.driveLinks && repo.driveLinks.length)"
|
||||
class="proj-docs"
|
||||
class="docs-bar"
|
||||
>
|
||||
<div class="pd-head">
|
||||
<button
|
||||
type="button"
|
||||
class="btn docs-btn"
|
||||
@click="docsModalOpen = true"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
@@ -201,39 +195,18 @@ const overdueCount = computed(() => tasks.value.filter((t) => t.overdue).length)
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6" /></svg>
|
||||
AI 검토 문서 <span class="pd-ct">{{ repo.documents?.length ?? 0 }}</span>
|
||||
</div>
|
||||
<p
|
||||
v-if="repo.driveLinks && repo.driveLinks.length"
|
||||
class="pd-sub"
|
||||
>
|
||||
연동된 드라이브 폴더 {{ repo.driveLinks.length }}개의 문서를 AI 업무 추천에 활용합니다.
|
||||
</p>
|
||||
<div
|
||||
v-if="repo.documents && repo.documents.length"
|
||||
class="pd-list"
|
||||
>
|
||||
<button
|
||||
v-for="d in repo.documents"
|
||||
:key="d.id"
|
||||
class="pd-item"
|
||||
type="button"
|
||||
title="다운로드"
|
||||
@click="downloadDoc(d.id)"
|
||||
>
|
||||
<span class="pd-name">{{ d.name }}</span>
|
||||
<AttachmentAiBadge :status="d.aiStatus" />
|
||||
<span class="pd-size">{{ formatBytes(d.size) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
v-else
|
||||
class="pd-empty"
|
||||
>
|
||||
연동 폴더에 문서가 없습니다.
|
||||
</p>
|
||||
AI 검토 문서
|
||||
<span class="count-pill">{{ repo.documents?.length ?? 0 }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ProjectDocumentsModal
|
||||
v-if="docsModalOpen"
|
||||
:documents="repo.documents ?? []"
|
||||
:drive-links="repo.driveLinks ?? []"
|
||||
@close="docsModalOpen = false"
|
||||
/>
|
||||
|
||||
<!-- 툴바 -->
|
||||
<div class="toolbar">
|
||||
<div class="search">
|
||||
@@ -518,76 +491,12 @@ const overdueCount = computed(() => tasks.value.filter((t) => t.overdue).length)
|
||||
}
|
||||
|
||||
/* 프로젝트 문서 */
|
||||
.proj-docs {
|
||||
/* AI 검토 문서 — 모달 열기 버튼 */
|
||||
.docs-bar {
|
||||
margin: 0.25rem 0 1rem;
|
||||
padding: 0.875rem 1.125rem;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
.pd-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4375rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-2);
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
.pd-head svg {
|
||||
width: 0.9375rem;
|
||||
height: 0.9375rem;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.pd-head .pd-ct {
|
||||
font-size: 0.719rem;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.pd-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.pd-sub {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-3);
|
||||
margin: -0.25rem 0 0.625rem;
|
||||
}
|
||||
.pd-empty {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.pd-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
max-width: 100%;
|
||||
padding: 0.375rem 0.6875rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
.pd-item:hover {
|
||||
background: #fafbfc;
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
.pd-name {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pd-size {
|
||||
font-size: 0.719rem;
|
||||
color: var(--text-3);
|
||||
flex-shrink: 0;
|
||||
.docs-btn .count-pill {
|
||||
margin-left: 0.125rem;
|
||||
}
|
||||
.toolbar .search {
|
||||
width: 15rem;
|
||||
|
||||
Reference in New Issue
Block a user