|
2 | 2 | import { |
3 | 3 | ArrowLeft, ArrowRight, Briefcase, Clock, Hash, UserRound, Mail, MessageSquare, |
4 | 4 | FileText, Paperclip, Download, Eye, Phone, Search, ExternalLink, |
5 | | - UserPlus, Pencil, Trash2, MoreHorizontal, Globe, ChevronDown, |
| 5 | + UserPlus, Pencil, Trash2, MoreHorizontal, Globe, ChevronDown, X, |
6 | 6 | } from 'lucide-vue-next' |
7 | 7 | import { z } from 'zod' |
8 | 8 | import { usePreviewReadOnly } from '~/composables/usePreviewReadOnly' |
@@ -353,6 +353,11 @@ function goToNextCard() { |
353 | 353 | } |
354 | 354 |
|
355 | 355 | function handleKeyNavigation(event: KeyboardEvent) { |
| 356 | + if (event.key === 'Escape' && showDocPreview.value) { |
| 357 | + closeDocPreview() |
| 358 | + return |
| 359 | + } |
| 360 | +
|
356 | 361 | if ((event.target as HTMLElement)?.tagName === 'INPUT' || (event.target as HTMLElement)?.tagName === 'TEXTAREA' || (event.target as HTMLElement)?.tagName === 'SELECT') return |
357 | 362 |
|
358 | 363 | if (event.key === 'ArrowUp') { |
@@ -557,6 +562,41 @@ onBeforeUnmount(() => { |
557 | 562 | const isLoading = computed(() => { |
558 | 563 | return jobFetchStatus.value === 'pending' || appFetchStatus.value === 'pending' |
559 | 564 | }) |
| 565 | +
|
| 566 | +// ───────────────────────────────────────────── |
| 567 | +// Document preview |
| 568 | +// ───────────────────────────────────────────── |
| 569 | +
|
| 570 | +const { getPreviewUrl } = useDocuments() |
| 571 | +
|
| 572 | +const showDocPreview = ref(false) |
| 573 | +const docPreviewUrl = ref<string | null>(null) |
| 574 | +const docPreviewFilename = ref('') |
| 575 | +const docPreviewMimeType = ref('') |
| 576 | +const docPreviewDocId = ref<string | null>(null) |
| 577 | +
|
| 578 | +const isDocPreviewPdf = computed(() => docPreviewMimeType.value === 'application/pdf') |
| 579 | +
|
| 580 | +function handleDocPreview(doc: SwipeDocument) { |
| 581 | + if (doc.mimeType !== 'application/pdf') { |
| 582 | + // Non-PDFs: fall back to download |
| 583 | + window.open(`/api/documents/${doc.id}/download`, '_blank') |
| 584 | + return |
| 585 | + } |
| 586 | + docPreviewDocId.value = doc.id |
| 587 | + docPreviewFilename.value = doc.originalFilename |
| 588 | + docPreviewMimeType.value = doc.mimeType |
| 589 | + docPreviewUrl.value = getPreviewUrl(doc.id) |
| 590 | + showDocPreview.value = true |
| 591 | +} |
| 592 | +
|
| 593 | +function closeDocPreview() { |
| 594 | + showDocPreview.value = false |
| 595 | + docPreviewUrl.value = null |
| 596 | + docPreviewFilename.value = '' |
| 597 | + docPreviewMimeType.value = '' |
| 598 | + docPreviewDocId.value = null |
| 599 | +} |
560 | 600 | </script> |
561 | 601 |
|
562 | 602 | <template> |
@@ -1068,15 +1108,13 @@ const isLoading = computed(() => { |
1068 | 1108 | </div> |
1069 | 1109 | </div> |
1070 | 1110 | <div class="flex items-center gap-2"> |
1071 | | - <a |
1072 | | - :href="`/api/documents/${doc.id}/preview`" |
1073 | | - target="_blank" |
1074 | | - rel="noopener noreferrer" |
| 1111 | + <button |
1075 | 1112 | class="inline-flex items-center gap-1.5 rounded-lg border border-surface-200 px-3 py-1.5 text-xs font-medium text-surface-600 hover:bg-surface-50 hover:border-surface-300 dark:border-surface-700 dark:text-surface-300 dark:hover:bg-surface-800 dark:hover:border-surface-600 transition-all duration-150" |
| 1113 | + @click="handleDocPreview(doc)" |
1076 | 1114 | > |
1077 | 1115 | <Eye class="size-3.5" /> |
1078 | 1116 | Preview |
1079 | | - </a> |
| 1117 | + </button> |
1080 | 1118 | <a |
1081 | 1119 | :href="`/api/documents/${doc.id}/download`" |
1082 | 1120 | class="inline-flex items-center gap-1.5 rounded-lg border border-surface-200 px-3 py-1.5 text-xs font-medium text-surface-600 hover:bg-surface-50 hover:border-surface-300 dark:border-surface-700 dark:text-surface-300 dark:hover:bg-surface-800 dark:hover:border-surface-600 transition-all duration-150" |
@@ -1250,5 +1288,60 @@ const isLoading = computed(() => { |
1250 | 1288 | @close="showApplyModal = false" |
1251 | 1289 | @created="handleCandidateApplied" |
1252 | 1290 | /> |
| 1291 | + |
| 1292 | + <!-- Document Preview Modal --> |
| 1293 | + <Teleport to="body"> |
| 1294 | + <div v-if="showDocPreview" class="fixed inset-0 z-50 flex items-center justify-center px-4 py-6"> |
| 1295 | + <div class="absolute inset-0 bg-black/60 backdrop-blur-sm" @click="closeDocPreview" /> |
| 1296 | + <div class="relative flex flex-col bg-white dark:bg-surface-900 rounded-2xl shadow-2xl shadow-surface-900/10 dark:shadow-black/30 ring-1 ring-surface-200/80 dark:ring-surface-700/60 w-full max-w-4xl" style="height: calc(100vh - 3rem);"> |
| 1297 | + <!-- Header --> |
| 1298 | + <div class="flex items-center justify-between px-5 py-3 border-b border-surface-200/80 dark:border-surface-800/60 shrink-0"> |
| 1299 | + <div class="flex items-center gap-2.5 min-w-0"> |
| 1300 | + <FileText class="size-4 text-surface-400 shrink-0" /> |
| 1301 | + <span class="text-sm font-medium text-surface-800 dark:text-surface-100 truncate">{{ docPreviewFilename }}</span> |
| 1302 | + </div> |
| 1303 | + <div class="flex items-center gap-2 shrink-0 ml-4"> |
| 1304 | + <a |
| 1305 | + v-if="docPreviewDocId" |
| 1306 | + :href="`/api/documents/${docPreviewDocId}/download`" |
| 1307 | + class="inline-flex items-center gap-1.5 rounded-lg border border-surface-200 px-2.5 py-1.5 text-xs font-medium text-surface-600 hover:bg-surface-50 hover:border-surface-300 dark:border-surface-700 dark:text-surface-300 dark:hover:bg-surface-800 transition-all duration-150" |
| 1308 | + > |
| 1309 | + <Download class="size-3.5" /> |
| 1310 | + Download |
| 1311 | + </a> |
| 1312 | + <button |
| 1313 | + class="rounded-lg p-1.5 text-surface-500 hover:text-surface-700 hover:bg-surface-100 dark:hover:text-surface-300 dark:hover:bg-surface-800 transition-colors" |
| 1314 | + title="Close" |
| 1315 | + @click="closeDocPreview" |
| 1316 | + > |
| 1317 | + <X class="size-4" /> |
| 1318 | + </button> |
| 1319 | + </div> |
| 1320 | + </div> |
| 1321 | + <!-- PDF viewer --> |
| 1322 | + <iframe |
| 1323 | + v-if="docPreviewUrl && isDocPreviewPdf" |
| 1324 | + :src="docPreviewUrl" |
| 1325 | + class="flex-1 w-full rounded-b-2xl min-h-0" |
| 1326 | + title="Document preview" |
| 1327 | + /> |
| 1328 | + <!-- Non-PDF fallback --> |
| 1329 | + <div v-else class="flex-1 flex items-center justify-center p-8 text-center"> |
| 1330 | + <div> |
| 1331 | + <FileText class="size-12 text-surface-300 dark:text-surface-600 mx-auto mb-3" /> |
| 1332 | + <p class="text-sm font-medium text-surface-600 dark:text-surface-300">Preview not available for this file type</p> |
| 1333 | + <a |
| 1334 | + v-if="docPreviewDocId" |
| 1335 | + :href="`/api/documents/${docPreviewDocId}/download`" |
| 1336 | + class="mt-3 inline-flex items-center gap-1.5 text-sm text-brand-600 hover:text-brand-700 dark:text-brand-400 font-medium" |
| 1337 | + > |
| 1338 | + <Download class="size-3.5" /> |
| 1339 | + Download instead |
| 1340 | + </a> |
| 1341 | + </div> |
| 1342 | + </div> |
| 1343 | + </div> |
| 1344 | + </div> |
| 1345 | + </Teleport> |
1253 | 1346 | </div> |
1254 | 1347 | </template> |
0 commit comments