feat: 프로젝트 드라이브 폴더 연동 + AI 검토 문서 전환
프로젝트 직접 업로드(project_attachments) 폐기, AI 검토 문서를 드라이브 폴더 연동으로 전환. 폴더 연동 CRUD·연동 폴더 문서에서 텍스트 지연 추출(NCP 다운로드, xlsx 포함)·AI 추천 주입. 폴더 선택 피커 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
// 프로젝트 문서의 AI 추천 반영 상태 배지
|
||||
// ready: 반영 가능 / failed: 추출 실패 / unsupported: 미지원 형식
|
||||
// ready: 반영됨 / pending: 추출 대기 / failed: 추출 실패 / unsupported: 미지원 형식
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
status: 'ready' | 'failed' | 'unsupported'
|
||||
status: 'ready' | 'pending' | 'failed' | 'unsupported'
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@@ -15,6 +15,11 @@ const META: Record<Props['status'], { label: string; title: string; cls: string
|
||||
title: 'AI 업무 추천에 이 문서 내용이 반영됩니다.',
|
||||
cls: 'ok',
|
||||
},
|
||||
pending: {
|
||||
label: '추출 대기',
|
||||
title: '다음 AI 업무 추천 시 문서 내용을 추출해 반영합니다.',
|
||||
cls: 'muted',
|
||||
},
|
||||
failed: {
|
||||
label: '추출 실패',
|
||||
title: '문서에서 텍스트를 추출하지 못해 AI 추천에 반영되지 않습니다.',
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
<script setup lang="ts">
|
||||
// 드라이브 폴더 선택 모달 — 내 드라이브를 탐색해 프로젝트에 연동할 폴더를 고른다.
|
||||
import { ref } from 'vue'
|
||||
import BaseModal from '@/components/common/BaseModal.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import { useDrive } from '@/composables/useDrive'
|
||||
import type { DriveCrumb, DriveFolder } from '@/types/drive'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', folder: { id: string; name: string }): void
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
const drive = useDrive()
|
||||
const loading = ref(false)
|
||||
const breadcrumb = ref<DriveCrumb[]>([])
|
||||
const folders = ref<DriveFolder[]>([])
|
||||
|
||||
async function load(id: string | null): Promise<void> {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await drive.list(id ?? undefined)
|
||||
breadcrumb.value = res.breadcrumb
|
||||
folders.value = res.folders
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
function pick(f: DriveFolder): void {
|
||||
emit('select', { id: f.id, name: f.name })
|
||||
}
|
||||
|
||||
void load(null)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseModal
|
||||
title="연동할 폴더 선택"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<template #subtitle>
|
||||
<b>내 드라이브</b>의 폴더(하위 포함)를 프로젝트의 AI 검토 문서로 연동합니다.
|
||||
</template>
|
||||
|
||||
<nav class="dfp-crumb">
|
||||
<a
|
||||
v-if="breadcrumb.length"
|
||||
href="#"
|
||||
@click.prevent="load(null)"
|
||||
>드라이브</a>
|
||||
<b v-else>드라이브</b>
|
||||
<template
|
||||
v-for="(c, i) in breadcrumb"
|
||||
:key="c.id"
|
||||
>
|
||||
<span class="dfp-sep">›</span>
|
||||
<a
|
||||
v-if="i < breadcrumb.length - 1"
|
||||
href="#"
|
||||
@click.prevent="load(c.id)"
|
||||
>{{ c.name }}</a>
|
||||
<b v-else>{{ c.name }}</b>
|
||||
</template>
|
||||
</nav>
|
||||
|
||||
<p
|
||||
v-if="loading"
|
||||
class="dfp-msg"
|
||||
>
|
||||
불러오는 중…
|
||||
</p>
|
||||
<EmptyState v-else-if="folders.length === 0">
|
||||
이 위치에 하위 폴더가 없습니다. 드라이브에서 폴더를 먼저 만들어 주세요.
|
||||
</EmptyState>
|
||||
<div
|
||||
v-else
|
||||
class="dfp-list"
|
||||
>
|
||||
<div
|
||||
v-for="f in folders"
|
||||
:key="f.id"
|
||||
class="dfp-row"
|
||||
>
|
||||
<button
|
||||
class="dfp-open"
|
||||
type="button"
|
||||
title="폴더 열기"
|
||||
@click="load(f.id)"
|
||||
>
|
||||
<span class="dfp-ico">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" /></svg>
|
||||
</span>
|
||||
<span class="dfp-name">{{ f.name }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn sm"
|
||||
type="button"
|
||||
@click="pick(f)"
|
||||
>
|
||||
연동
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #foot>
|
||||
<button
|
||||
class="mbtn"
|
||||
@click="emit('close')"
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
</template>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dfp-crumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-2);
|
||||
}
|
||||
.dfp-crumb a {
|
||||
color: var(--text-2);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dfp-crumb a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
.dfp-sep {
|
||||
color: var(--text-3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.dfp-msg {
|
||||
font-size: 0.844rem;
|
||||
color: var(--text-3);
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
.dfp-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.625rem;
|
||||
overflow: hidden;
|
||||
max-height: 18rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.dfp-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.dfp-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.dfp-row:hover {
|
||||
background: #fafbfc;
|
||||
}
|
||||
.dfp-open {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
padding: 0.125rem 0;
|
||||
}
|
||||
.dfp-ico {
|
||||
flex: 0 0 auto;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--accent);
|
||||
background: var(--accent-weak);
|
||||
}
|
||||
.dfp-ico svg {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
.dfp-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user