This commit is contained in:
2025-12-12 09:30:23 +09:00
parent 1b2430bb7a
commit e4f89a369f

View File

@@ -57,6 +57,39 @@
</div>
</div>
<!-- 필터/정렬 영역 -->
<div v-if="documents.length > 0" class="filter-section">
<div class="filter-group">
<span class="filter-label">상태:</span>
<label
v-for="status in statusOptions"
:key="status.value"
class="filter-checkbox"
>
<input
type="checkbox"
:value="status.value"
v-model="selectedStatuses"
/>
<span :class="['status-tag', status.value.toLowerCase()]">
{{ status.label }}
</span>
</label>
</div>
<div class="sort-group">
<span class="filter-label">정렬:</span>
<select v-model="sortOption" class="sort-select">
<option value="uploadDesc">업로드순 (최신)</option>
<option value="uploadAsc">업로드순 (오래된)</option>
<option value="nameAsc">파일명 (오름차순)</option>
<option value="nameDesc">파일명 (내림차순)</option>
</select>
</div>
<div class="filter-info">
{{ filteredDocuments.length }} / {{ documents.length }}
</div>
</div>
<!-- 문서 목록 -->
<div class="doc-list">
<div v-if="documents.length === 0" class="empty-docs">
@@ -64,8 +97,13 @@
<p class="hint">PDF, DOCX, TXT 파일을 업로드해보세요.</p>
</div>
<div v-else-if="filteredDocuments.length === 0" class="empty-docs">
<p>필터 조건에 맞는 문서가 없습니다.</p>
<button class="reset-filter-btn" @click="resetFilters">필터 초기화</button>
</div>
<div
v-for="doc in documents"
v-for="doc in filteredDocuments"
:key="doc.docId"
class="doc-item"
>
@@ -238,7 +276,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
import { topicApi, docApi } from '@/api'
// 상태
@@ -246,6 +284,52 @@ const topics = ref([])
const selectedTopic = ref(null)
const documents = ref([])
// 필터/정렬 상태
const selectedStatuses = ref(['PENDING', 'PROCESSING', 'INDEXED', 'FAILED']) // 기본 전체 선택
const sortOption = ref('uploadDesc') // 기본: 업로드순 (최신)
// 상태 옵션
const statusOptions = [
{ value: 'PENDING', label: '⏳ 대기중' },
{ value: 'PROCESSING', label: '⚙️ 처리중' },
{ value: 'INDEXED', label: '✅ 완료' },
{ value: 'FAILED', label: '❌ 실패' }
]
// 필터링 + 정렬된 문서 목록
const filteredDocuments = computed(() => {
let result = [...documents.value]
// 상태 필터
if (selectedStatuses.value.length > 0 && selectedStatuses.value.length < 4) {
result = result.filter(doc => selectedStatuses.value.includes(doc.docStatus))
}
// 정렬
switch (sortOption.value) {
case 'uploadDesc':
result.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
break
case 'uploadAsc':
result.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
break
case 'nameAsc':
result.sort((a, b) => (a.originalName || '').localeCompare(b.originalName || '', 'ko'))
break
case 'nameDesc':
result.sort((a, b) => (b.originalName || '').localeCompare(a.originalName || '', 'ko'))
break
}
return result
})
// 필터 초기화
const resetFilters = () => {
selectedStatuses.value = ['PENDING', 'PROCESSING', 'INDEXED', 'FAILED']
sortOption.value = 'uploadDesc'
}
// 폴링 관련
const pollingInterval = ref(null)
const processingDocIds = ref(new Set())
@@ -788,6 +872,79 @@ onUnmounted(() => {
}
}
// 필터/정렬 영역
.filter-section {
display: flex;
align-items: center;
gap: 24px;
padding: 12px 24px;
background: white;
border-bottom: 1px solid #e8e8e8;
.filter-label {
font-size: 13px;
font-weight: 500;
color: #666;
}
.filter-group {
display: flex;
align-items: center;
gap: 8px;
}
.filter-checkbox {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
input[type="checkbox"] {
width: 14px;
height: 14px;
cursor: pointer;
accent-color: #667eea;
}
.status-tag {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
&.pending { background: #fff3e0; color: #e65100; }
&.processing { background: #e3f2fd; color: #1565c0; }
&.indexed { background: #e8f5e9; color: #2e7d32; }
&.failed { background: #ffebee; color: #c62828; }
}
}
.sort-group {
display: flex;
align-items: center;
gap: 8px;
}
.sort-select {
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 13px;
background: white;
cursor: pointer;
&:focus {
border-color: #667eea;
outline: none;
}
}
.filter-info {
margin-left: auto;
font-size: 13px;
color: #888;
}
}
.doc-list {
flex: 1;
overflow-y: auto;
@@ -804,6 +961,19 @@ onUnmounted(() => {
font-size: 13px;
color: #aaa;
}
.reset-filter-btn {
margin-top: 16px;
padding: 8px 16px;
background: #667eea;
color: white;
border-radius: 6px;
font-size: 13px;
&:hover {
background: #5a6fd6;
}
}
}
.doc-item {