EDMS/components/dms/CabinetNavigation.vue
2025-05-30 16:16:59 +08:00

558 lines
22 KiB
Vue

<script setup>
import { ref, computed } from 'vue';
import { useDmsStore } from '~/stores/dms';
const store = useDmsStore();
const expandedSections = ref({
'public-cabinet': true,
'my-cabinets': true,
'private-cabinets': true
});
const showAccessRequestDialog = ref(false);
const currentCabinetForAccess = ref(null);
const accessRequestReason = ref('');
// Toggle expanded state of a section
const toggleSection = (sectionId) => {
expandedSections.value[sectionId] = !expandedSections.value[sectionId];
};
// Navigate to a cabinet
const navigateToCabinet = (cabinetId) => {
// In a real implementation, this would navigate to the cabinet
console.log(`Navigating to cabinet: ${cabinetId}`);
};
// Request access to a private cabinet
const requestAccess = (cabinet) => {
currentCabinetForAccess.value = cabinet;
showAccessRequestDialog.value = true;
};
// Submit access request
const submitAccessRequest = async () => {
if (!accessRequestReason.value.trim()) {
alert('Please provide a reason for your request');
return;
}
try {
await store.requestCabinetAccess(
currentCabinetForAccess.value.id,
accessRequestReason.value
);
// Reset form
accessRequestReason.value = '';
currentCabinetForAccess.value = null;
showAccessRequestDialog.value = false;
// Show success message
alert('Access request submitted successfully');
} catch (error) {
console.error('Failed to submit access request:', error);
alert('Failed to submit access request. Please try again.');
}
};
// Get access status icon
const getAccessStatusIcon = (cabinet) => {
if (cabinet.accessType === 'public') {
return { name: 'check-circle', color: 'text-green-500' };
}
if (cabinet.accessType === 'personal') {
return { name: 'check-circle', color: 'text-green-500' };
}
if (cabinet.accessType === 'private') {
if (cabinet.hasAccess) {
return { name: 'check-circle', color: 'text-green-500' };
}
if (cabinet.isLocked) {
return { name: 'lock', color: 'text-red-500' };
}
return { name: 'clock', color: 'text-amber-500' };
}
return null;
};
// Render cabinet recursively
const renderCabinet = (cabinet, level = 0) => {
const paddingStyle = { paddingLeft: `${level * 16 + 8}px` };
const hasChildren = cabinet.children && cabinet.children.length > 0;
const isExpanded = expandedSections.value[cabinet.id];
return (
<div class="cabinet-item">
<div
class="cabinet-header flex items-center py-2 px-2 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
style={paddingStyle}
onClick={() => hasChildren ? toggleSection(cabinet.id) : navigateToCabinet(cabinet.id)}
>
{hasChildren && (
<svg
class={`mr-1 text-gray-500 transition-transform ${isExpanded ? 'transform rotate-90' : ''}`}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
)}
<span class={`ml-${hasChildren ? '0' : '4'} flex-1`}>
{cabinet.type === 'tag' ? (
<span class="flex items-center">
<span class="w-2 h-2 rounded-full bg-purple-500 mr-1"></span>
<span class="text-sm">{cabinet.name}</span>
</span>
) : (
<span class="flex items-center">
{cabinet.type === 'cabinet' && (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-1"
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
)}
<span class="text-sm">{cabinet.name}</span>
</span>
)}
</span>
{/* Status icon */}
{cabinet.accessType === 'private' && !cabinet.hasAccess && (
<button
class="text-blue-500 hover:text-blue-700 p-1"
onClick={(e) => {
e.stopPropagation();
requestAccess(cabinet);
}}
disabled={cabinet.accessRequestStatus === 'rejected'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</button>
)}
{getAccessStatusIcon(cabinet) && (
<span class={getAccessStatusIcon(cabinet).color}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
{getAccessStatusIcon(cabinet).name === 'check-circle' && (
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
)}
{getAccessStatusIcon(cabinet).name === 'clock' && (
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
)}
{getAccessStatusIcon(cabinet).name === 'lock' && (
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
)}
</svg>
</span>
)}
</div>
{hasChildren && isExpanded && (
<div class="cabinet-children">
{cabinet.children.map(child => renderCabinet(child, level + 1))}
</div>
)}
</div>
);
};
</script>
<template>
<div class="cabinet-navigation h-full overflow-y-auto">
<!-- Public cabinets -->
<div class="navigation-section">
<div
@click="toggleSection('public-cabinet')"
class="section-header flex items-center p-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
>
<svg
class="mr-1 text-gray-500"
:class="expandedSections['public-cabinet'] ? 'transform rotate-90' : ''"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<span class="font-medium text-sm">📁 Public Cabinet</span>
</div>
<div v-if="expandedSections['public-cabinet']" class="section-content">
<div v-for="cabinet in store.cabinets.filter(c => c.accessType === 'public')" :key="cabinet.id">
<div v-for="child in cabinet.children" :key="child.id" class="menu-item">
<div
class="flex items-center pl-8 pr-3 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
@click="navigateToCabinet(child.id)"
>
<svg class="mr-2 text-gray-500" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span class="text-sm">{{ child.name }}</span>
<span class="ml-auto text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</span>
</div>
</div>
</div>
</div>
</div>
<!-- My Cabinets -->
<div class="navigation-section">
<div
@click="toggleSection('my-cabinets')"
class="section-header flex items-center p-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
>
<svg
class="mr-1 text-gray-500"
:class="expandedSections['my-cabinets'] ? 'transform rotate-90' : ''"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<span class="font-medium text-sm">📁 My Cabinets</span>
</div>
<div v-if="expandedSections['my-cabinets']" class="section-content">
<template v-if="store.personalCabinets">
<div v-for="cabinet in store.personalCabinets.children" :key="cabinet.id">
<div
class="flex items-center pl-8 pr-3 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
@click="() => {
toggleSection(cabinet.id);
navigateToCabinet(cabinet.id);
}"
>
<svg
v-if="cabinet.children && cabinet.children.length > 0"
class="mr-2 text-gray-500"
:class="expandedSections[cabinet.id] ? 'transform rotate-90' : ''"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<svg v-else class="mr-2 text-gray-500" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span class="text-sm">{{ cabinet.name }}</span>
<span class="ml-auto text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</span>
</div>
<!-- Recursive children -->
<template v-if="expandedSections[cabinet.id] && cabinet.children && cabinet.children.length > 0">
<div
v-for="child in cabinet.children"
:key="child.id"
class="pl-4"
>
<div
class="flex items-center pl-8 pr-3 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
@click="() => {
if (child.children && child.children.length > 0) {
toggleSection(child.id);
}
navigateToCabinet(child.id);
}"
>
<svg
v-if="child.children && child.children.length > 0"
class="mr-2 text-gray-500"
:class="expandedSections[child.id] ? 'transform rotate-90' : ''"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<svg v-else class="mr-2 text-gray-500" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span class="text-sm">{{ child.name }}</span>
</div>
<!-- Third level -->
<template v-if="expandedSections[child.id] && child.children && child.children.length > 0">
<div
v-for="grandchild in child.children"
:key="grandchild.id"
class="pl-4"
>
<!-- Display tag groups -->
<template v-if="grandchild.type === 'cabinet'">
<div
class="flex items-center pl-12 pr-3 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
@click="() => {
if (grandchild.children && grandchild.children.length > 0) {
toggleSection(grandchild.id);
}
navigateToCabinet(grandchild.id);
}"
>
<svg
v-if="grandchild.children && grandchild.children.length > 0"
class="mr-2 text-gray-500"
:class="expandedSections[grandchild.id] ? 'transform rotate-90' : ''"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<svg v-else class="mr-2 text-gray-500" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span class="text-sm">{{ grandchild.name }}</span>
</div>
<!-- Tags -->
<template v-if="expandedSections[grandchild.id] && grandchild.children && grandchild.children.length > 0">
<div
v-for="tag in grandchild.children"
:key="tag.id"
@click="navigateToCabinet(tag.id)"
class="flex items-center pl-16 pr-3 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
>
<span class="w-2 h-2 rounded-full bg-purple-500 mr-2"></span>
<span class="text-sm">{{ tag.name }}</span>
</div>
</template>
</template>
</div>
</template>
</div>
</template>
</div>
</template>
</div>
</div>
<!-- Private Cabinets -->
<div class="navigation-section">
<div
@click="toggleSection('private-cabinets')"
class="section-header flex items-center p-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
>
<svg
class="mr-1 text-gray-500"
:class="expandedSections['private-cabinets'] ? 'transform rotate-90' : ''"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
<span class="font-medium text-sm">🔒 Private Cabinets</span>
</div>
<div v-if="expandedSections['private-cabinets']" class="section-content">
<template v-if="store.privateCabinets">
<div v-for="cabinet in store.privateCabinets.children" :key="cabinet.id">
<div
class="flex items-center pl-8 pr-3 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"
@click="cabinet.hasAccess ? navigateToCabinet(cabinet.id) : null"
>
<svg class="mr-2 text-gray-500" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span class="text-sm">{{ cabinet.name }}</span>
<!-- Status indicators -->
<div class="ml-auto flex items-center gap-1">
<!-- Request access button -->
<button
v-if="!cabinet.hasAccess && cabinet.accessRequestStatus !== 'rejected'"
@click.stop="requestAccess(cabinet)"
class="text-blue-500 hover:text-blue-700 p-1"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path>
</svg>
</button>
<!-- Access status indicator -->
<span
v-if="cabinet.accessRequestStatus === 'pending'"
class="text-amber-500"
title="Access request pending"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
</span>
<span
v-else-if="cabinet.isLocked"
class="text-red-500"
title="Access locked"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</span>
<span
v-else-if="cabinet.hasAccess"
class="text-green-500"
title="Access granted"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</span>
</div>
</div>
</div>
</template>
</div>
</div>
<!-- Access request dialog -->
<rs-dialog v-model="showAccessRequestDialog" title="Request Access">
<template v-if="currentCabinetForAccess">
<p class="mb-4">You are requesting access to: <strong>{{ currentCabinetForAccess.name }}</strong></p>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Reason for Access Request
</label>
<textarea
v-model="accessRequestReason"
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary focus:ring focus:ring-primary focus:ring-opacity-50"
placeholder="Please provide a reason for your request..."
></textarea>
</div>
</template>
<template #footer>
<div class="flex justify-end gap-2">
<rs-button color="secondary" @click="showAccessRequestDialog = false">
Cancel
</rs-button>
<rs-button color="primary" @click="submitAccessRequest">
Submit Request
</rs-button>
</div>
</template>
</rs-dialog>
</div>
</template>
<style scoped>
.cabinet-navigation {
width: 260px;
position: relative;
}
.section-header {
font-weight: 500;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.menu-item {
transition: all 0.2s ease;
}
</style>