feat(phase-4-09): create new components — FolderRow, FolderBreadcrumb, FolderDeleteModal, ShareModal, DocumentPreviewModal, SearchBar, SortControls, AuditLogTab

- FolderRow: inline rename, three-dot menu, delete/rename callbacks, outside-click close
- FolderBreadcrumb: truncation at depth > 4, nav aria-label, ol structure
- FolderDeleteModal: role=dialog, warning icon, doc count in body, Keep/Delete buttons
- ShareModal: handle input, recipients list with revoke, 404/409 error handling
- DocumentPreviewModal: iframe with proxy URL only (never presigned), Escape/overlay close
- SearchBar: role=search, aria-label, Escape clears
- SortControls: aria-pressed, direction indicator, toggle vs switch logic
- AuditLogTab: filters, paginated table, CSV export via window.location.href
- api/client.js: add adminListAuditLog function
This commit is contained in:
curo1305
2026-05-25 22:10:23 +02:00
parent 437c9d134b
commit 36721575a5
9 changed files with 845 additions and 0 deletions
@@ -0,0 +1,71 @@
<template>
<!-- Overlay -->
<div
ref="overlayRef"
class="fixed inset-0 bg-black/60 z-50 flex flex-col"
role="dialog"
aria-modal="true"
aria-label="Document preview"
@click="handleOverlayClick"
>
<!-- Header bar -->
<div class="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between shrink-0">
<span class="text-sm font-medium text-gray-900 truncate max-w-xs">{{ doc.filename || doc.original_name }}</span>
<button
@click="emit('close')"
aria-label="Close preview"
class="text-gray-400 hover:text-gray-600 transition-colors ml-4 shrink-0"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- iframe content -->
<div class="flex-1 overflow-hidden">
<iframe
class="w-full h-full border-0"
:src="proxyUrl"
title="Document preview"
></iframe>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
doc: {
type: Object,
required: true,
},
})
const emit = defineEmits(['close'])
const overlayRef = ref(null)
const proxyUrl = computed(() => `/api/documents/${props.doc.id}/content`)
function handleOverlayClick(e) {
if (e.target === overlayRef.value) {
emit('close')
}
}
function handleKeydown(e) {
if (e.key === 'Escape') {
emit('close')
}
}
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
})
</script>