feat(07-05): DocumentCard classification_failed badge + Re-analyze button + Vitest coverage

- Add red "Classification failed" pill badge when doc.status === 'classification_failed'
- Re-analyze button calls classifyDocument(doc.id) via @click.stop, emits 'reclassified'
- Spinner text "Re-analyzing…" while in flight; resets after 500ms (via setTimeout)
- Existing is_shared badge block preserved unchanged
- Create frontend/tests/DocumentCard.spec.js: 4 Vitest tests covering badge render,
  no-badge for ready/processing, and reanalyze() calling classifyDocument + emit
- 131 frontend tests pass, build exits 0 (D-11 frontend coverage per CLAUDE.md)
This commit is contained in:
curo1305
2026-06-04 23:23:12 +02:00
parent 0db412d66c
commit c45d9e470d
2 changed files with 151 additions and 1 deletions
@@ -31,6 +31,19 @@
<div v-if="doc.is_shared" class="mt-2">
<span class="bg-indigo-50 text-indigo-600 text-xs font-medium px-2 py-1 rounded-full">Shared</span>
</div>
<!-- Classification failed badge + Re-analyze button -->
<div v-if="doc.status === 'classification_failed'" class="mt-2 flex items-center gap-2">
<span class="bg-red-50 text-red-600 text-xs font-medium px-2 py-1 rounded-full">Classification failed</span>
<button
@click.stop="reanalyze"
:disabled="reanalyzing"
class="text-xs text-indigo-600 hover:text-indigo-700 font-semibold disabled:opacity-50"
>
<span v-if="reanalyzing">Re-analyzing</span>
<span v-else>Re-analyze</span>
</button>
</div>
</div>
<!-- Action buttons (hover-reveal) -->
@@ -94,7 +107,7 @@
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useTopicsStore } from '../../stores/topics.js'
import { useFoldersStore } from '../../stores/folders.js'
import { moveDocument } from '../../api/client.js'
import { moveDocument, classifyDocument } from '../../api/client.js'
import TopicBadge from '../topics/TopicBadge.vue'
import ShareModal from '../sharing/ShareModal.vue'
import { formatDate, formatSize } from '../../utils/formatters.js'
@@ -103,10 +116,13 @@ const props = defineProps({
doc: Object,
})
const emit = defineEmits(['reclassified'])
const topicsStore = useTopicsStore()
const foldersStore = useFoldersStore()
const showShareModal = ref(false)
const showFolderPicker = ref(false)
const reanalyzing = ref(false)
const allFolders = computed(() => foldersStore.rootFolders)
@@ -138,4 +154,16 @@ function topicColor(name) {
return topicsStore.topics.find(t => t.name === name)?.color ?? '#6366f1'
}
async function reanalyze() {
reanalyzing.value = true
try {
await classifyDocument(props.doc.id)
emit('reclassified', props.doc.id)
} catch (e) {
console.error('Re-analyze failed:', e.message)
} finally {
setTimeout(() => { reanalyzing.value = false }, 500)
}
}
</script>