generated from corrad-software/corrad-af-2024
716 lines
26 KiB
Vue
716 lines
26 KiB
Vue
<script setup>
|
|
import { ref, computed, watch, onMounted } from 'vue';
|
|
import { useNotifications } from '~/composables/useNotifications';
|
|
|
|
const props = defineProps({
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
initialPath: {
|
|
type: String,
|
|
default: '/'
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['update:visible', 'create-complete', 'cancel']);
|
|
|
|
// Notifications
|
|
const { success, error } = useNotifications();
|
|
|
|
// Creation state
|
|
const creationType = ref('folder'); // 'folder', 'subfolder', 'document'
|
|
const itemName = ref('');
|
|
const selectedPath = ref(props.initialPath);
|
|
const currentFolder = ref(null);
|
|
const showAdvancedOptions = ref(false);
|
|
|
|
// Navigation state
|
|
const expandedFolders = ref(new Set(['/']));
|
|
const breadcrumbs = ref([]);
|
|
const folderContents = ref([]);
|
|
const isLoading = ref(false);
|
|
|
|
// Advanced options
|
|
const accessPermissions = ref({
|
|
level: 'internal',
|
|
inheritFromParent: true
|
|
});
|
|
|
|
const templateOptions = ref([
|
|
{ value: 'blank', label: 'Blank Document', icon: 'mdi:file-document' },
|
|
{ value: 'template1', label: 'Report Template', icon: 'mdi:file-chart' },
|
|
{ value: 'template2', label: 'Memo Template', icon: 'mdi:file-document-edit' },
|
|
{ value: 'template3', label: 'Project Plan', icon: 'mdi:file-table' }
|
|
]);
|
|
|
|
const selectedTemplate = ref('blank');
|
|
|
|
// Creation types
|
|
const creationTypes = [
|
|
{
|
|
value: 'folder',
|
|
label: 'New Folder',
|
|
icon: 'mdi:folder-plus',
|
|
description: 'Create a new folder to organize documents'
|
|
},
|
|
{
|
|
value: 'subfolder',
|
|
label: 'New Subfolder',
|
|
icon: 'mdi:folder-multiple-plus',
|
|
description: 'Create a subfolder within the selected folder'
|
|
},
|
|
{
|
|
value: 'document',
|
|
label: 'New Document',
|
|
icon: 'mdi:file-plus',
|
|
description: 'Create a new document from template'
|
|
}
|
|
];
|
|
|
|
// Mock folder structure (in real implementation, this would come from an API)
|
|
const mockFolderStructure = ref([
|
|
{
|
|
id: '1',
|
|
name: 'JKR Documents',
|
|
path: '/jkr-documents',
|
|
type: 'folder',
|
|
hasChildren: true,
|
|
children: [
|
|
{
|
|
id: '1-1',
|
|
name: 'Kota Bharu Branch',
|
|
path: '/jkr-documents/kota-bharu',
|
|
type: 'folder',
|
|
hasChildren: true,
|
|
children: [
|
|
{ id: '1-1-1', name: 'Reports', path: '/jkr-documents/kota-bharu/reports', type: 'folder', hasChildren: false },
|
|
{ id: '1-1-2', name: 'Projects', path: '/jkr-documents/kota-bharu/projects', type: 'folder', hasChildren: false }
|
|
]
|
|
},
|
|
{
|
|
id: '1-2',
|
|
name: 'Headquarters',
|
|
path: '/jkr-documents/headquarters',
|
|
type: 'folder',
|
|
hasChildren: true,
|
|
children: [
|
|
{ id: '1-2-1', name: 'Admin', path: '/jkr-documents/headquarters/admin', type: 'folder', hasChildren: false },
|
|
{ id: '1-2-2', name: 'Finance', path: '/jkr-documents/headquarters/finance', type: 'folder', hasChildren: false }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Public Documents',
|
|
path: '/public-documents',
|
|
type: 'folder',
|
|
hasChildren: true,
|
|
children: [
|
|
{ id: '2-1', name: 'Announcements', path: '/public-documents/announcements', type: 'folder', hasChildren: false },
|
|
{ id: '2-2', name: 'Forms', path: '/public-documents/forms', type: 'folder', hasChildren: false },
|
|
{ id: '2-3', name: 'Policies', path: '/public-documents/policies', type: 'folder', hasChildren: false }
|
|
]
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'Archive',
|
|
path: '/archive',
|
|
type: 'folder',
|
|
hasChildren: true,
|
|
children: [
|
|
{ id: '3-1', name: '2023', path: '/archive/2023', type: 'folder', hasChildren: false },
|
|
{ id: '3-2', name: '2022', path: '/archive/2022', type: 'folder', hasChildren: false }
|
|
]
|
|
}
|
|
]);
|
|
|
|
// Computed
|
|
const canCreate = computed(() => {
|
|
return itemName.value.trim().length > 0 && selectedPath.value;
|
|
});
|
|
|
|
const currentPathName = computed(() => {
|
|
if (selectedPath.value === '/') return 'Root';
|
|
return selectedPath.value.split('/').pop();
|
|
});
|
|
|
|
// Navigation methods
|
|
const buildBreadcrumbs = (path) => {
|
|
if (path === '/') {
|
|
return [{ name: 'Root', path: '/' }];
|
|
}
|
|
|
|
const segments = path.split('/').filter(Boolean);
|
|
const crumbs = [{ name: 'Root', path: '/' }];
|
|
|
|
let currentPath = '';
|
|
segments.forEach(segment => {
|
|
currentPath += '/' + segment;
|
|
crumbs.push({
|
|
name: segment,
|
|
path: currentPath
|
|
});
|
|
});
|
|
|
|
return crumbs;
|
|
};
|
|
|
|
const navigateToPath = (path) => {
|
|
selectedPath.value = path;
|
|
breadcrumbs.value = buildBreadcrumbs(path);
|
|
loadFolderContents(path);
|
|
};
|
|
|
|
const loadFolderContents = async (path) => {
|
|
isLoading.value = true;
|
|
|
|
try {
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
// Mock folder contents
|
|
const contents = [
|
|
{
|
|
id: 'c1',
|
|
name: 'Annual Report 2024.pdf',
|
|
type: 'file',
|
|
size: '2.4 MB',
|
|
lastModified: '2024-01-15T10:30:00Z',
|
|
icon: 'mdi:file-pdf'
|
|
},
|
|
{
|
|
id: 'c2',
|
|
name: 'Budget Analysis',
|
|
type: 'folder',
|
|
itemCount: 12,
|
|
lastModified: '2024-01-14T15:45:00Z',
|
|
icon: 'mdi:folder'
|
|
},
|
|
{
|
|
id: 'c3',
|
|
name: 'Meeting Minutes.docx',
|
|
type: 'file',
|
|
size: '156 KB',
|
|
lastModified: '2024-01-13T09:15:00Z',
|
|
icon: 'mdi:file-word'
|
|
}
|
|
];
|
|
|
|
folderContents.value = contents;
|
|
} catch (err) {
|
|
error('Failed to load folder contents');
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// Tree navigation
|
|
const toggleFolderExpansion = (folder) => {
|
|
if (expandedFolders.value.has(folder.path)) {
|
|
expandedFolders.value.delete(folder.path);
|
|
} else {
|
|
expandedFolders.value.add(folder.path);
|
|
}
|
|
};
|
|
|
|
const selectFolder = (folder) => {
|
|
navigateToPath(folder.path);
|
|
currentFolder.value = folder;
|
|
};
|
|
|
|
// Creation process
|
|
const performCreation = async () => {
|
|
try {
|
|
const newItem = {
|
|
name: itemName.value.trim(),
|
|
type: creationType.value,
|
|
path: selectedPath.value,
|
|
permissions: accessPermissions.value,
|
|
template: creationType.value === 'document' ? selectedTemplate.value : null
|
|
};
|
|
|
|
// Here you would implement actual creation logic
|
|
console.log('Creating:', newItem);
|
|
|
|
success(`Successfully created ${creationType.value}: ${newItem.name}`);
|
|
emit('create-complete', newItem);
|
|
closeModal();
|
|
} catch (err) {
|
|
error('Failed to create item: ' + err.message);
|
|
}
|
|
};
|
|
|
|
// Modal controls
|
|
const closeModal = () => {
|
|
itemName.value = '';
|
|
creationType.value = 'folder';
|
|
selectedPath.value = props.initialPath;
|
|
showAdvancedOptions.value = false;
|
|
selectedTemplate.value = 'blank';
|
|
emit('update:visible', false);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
closeModal();
|
|
emit('cancel');
|
|
};
|
|
|
|
// Format file size
|
|
const formatFileSize = (size) => {
|
|
if (typeof size === 'string') return size;
|
|
// ... existing formatFileSize logic
|
|
return size;
|
|
};
|
|
|
|
// Format date
|
|
const formatDate = (dateString) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString();
|
|
};
|
|
|
|
// Initialize
|
|
watch(() => props.visible, (visible) => {
|
|
if (visible) {
|
|
navigateToPath(props.initialPath);
|
|
}
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (props.visible) {
|
|
navigateToPath(props.initialPath);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="visible"
|
|
class="fixed inset-0 z-50 overflow-y-auto"
|
|
@click.self="handleCancel"
|
|
>
|
|
<!-- Backdrop -->
|
|
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity"></div>
|
|
|
|
<!-- Modal -->
|
|
<div class="relative min-h-screen flex items-center justify-center p-4">
|
|
<div class="relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-6xl w-full max-h-[90vh] overflow-hidden">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center space-x-3">
|
|
<Icon name="mdi:plus-circle" class="w-6 h-6 text-green-600 dark:text-green-400" />
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
Create New Item
|
|
</h2>
|
|
</div>
|
|
<button
|
|
@click="handleCancel"
|
|
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
|
>
|
|
<Icon name="mdi:close" class="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="flex h-[70vh]">
|
|
<!-- Left Panel: Navigation Tree -->
|
|
<div class="w-1/3 border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
|
<!-- Tree Header -->
|
|
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
Choose Location
|
|
</h3>
|
|
</div>
|
|
|
|
<!-- Tree Navigation -->
|
|
<div class="flex-1 overflow-y-auto p-2">
|
|
<div class="space-y-1">
|
|
<!-- Root -->
|
|
<button
|
|
@click="selectFolder({ name: 'Root', path: '/' })"
|
|
:class="[
|
|
'w-full flex items-center px-3 py-2 text-sm rounded-md transition-colors',
|
|
selectedPath === '/'
|
|
? 'bg-blue-100 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
|
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
]"
|
|
>
|
|
<Icon name="mdi:folder-home" class="w-4 h-4 mr-2 text-blue-600 dark:text-blue-400" />
|
|
<span class="font-medium">Root</span>
|
|
</button>
|
|
|
|
<!-- Folder Tree -->
|
|
<div
|
|
v-for="folder in mockFolderStructure"
|
|
:key="folder.id"
|
|
class="ml-2"
|
|
>
|
|
<div class="flex items-center">
|
|
<!-- Expand/Collapse Button -->
|
|
<button
|
|
v-if="folder.hasChildren"
|
|
@click="toggleFolderExpansion(folder)"
|
|
class="flex-shrink-0 w-4 h-4 mr-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600"
|
|
>
|
|
<Icon
|
|
:name="expandedFolders.has(folder.path) ? 'mdi:chevron-down' : 'mdi:chevron-right'"
|
|
class="w-4 h-4"
|
|
/>
|
|
</button>
|
|
<div v-else class="w-5"></div>
|
|
|
|
<!-- Folder Button -->
|
|
<button
|
|
@click="selectFolder(folder)"
|
|
:class="[
|
|
'flex-1 flex items-center px-2 py-1 text-sm rounded-md transition-colors text-left',
|
|
selectedPath === folder.path
|
|
? 'bg-blue-100 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
|
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
]"
|
|
>
|
|
<Icon name="mdi:folder" class="w-4 h-4 mr-2 text-blue-600 dark:text-blue-400" />
|
|
<span class="truncate">{{ folder.name }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Nested Children -->
|
|
<div
|
|
v-if="folder.children && expandedFolders.has(folder.path)"
|
|
class="ml-4 mt-1 space-y-1"
|
|
>
|
|
<div
|
|
v-for="child in folder.children"
|
|
:key="child.id"
|
|
class="flex items-center"
|
|
>
|
|
<button
|
|
v-if="child.hasChildren"
|
|
@click="toggleFolderExpansion(child)"
|
|
class="flex-shrink-0 w-4 h-4 mr-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600"
|
|
>
|
|
<Icon
|
|
:name="expandedFolders.has(child.path) ? 'mdi:chevron-down' : 'mdi:chevron-right'"
|
|
class="w-4 h-4"
|
|
/>
|
|
</button>
|
|
<div v-else class="w-5"></div>
|
|
|
|
<button
|
|
@click="selectFolder(child)"
|
|
:class="[
|
|
'flex-1 flex items-center px-2 py-1 text-sm rounded-md transition-colors text-left',
|
|
selectedPath === child.path
|
|
? 'bg-blue-100 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
|
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
]"
|
|
>
|
|
<Icon name="mdi:folder" class="w-4 h-4 mr-2 text-blue-600 dark:text-blue-400" />
|
|
<span class="truncate">{{ child.name }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Nested Children's Children -->
|
|
<div
|
|
v-if="child.children && expandedFolders.has(child.path)"
|
|
class="ml-4 mt-1 space-y-1"
|
|
>
|
|
<button
|
|
v-for="grandchild in child.children"
|
|
:key="grandchild.id"
|
|
@click="selectFolder(grandchild)"
|
|
:class="[
|
|
'w-full flex items-center px-2 py-1 text-sm rounded-md transition-colors text-left',
|
|
selectedPath === grandchild.path
|
|
? 'bg-blue-100 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
|
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
]"
|
|
>
|
|
<div class="w-5"></div>
|
|
<Icon name="mdi:folder" class="w-4 h-4 mr-2 text-blue-600 dark:text-blue-400" />
|
|
<span class="truncate">{{ grandchild.name }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Panel: Folder Contents & Creation Form -->
|
|
<div class="flex-1 flex flex-col">
|
|
<!-- Breadcrumb Navigation -->
|
|
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
|
|
<nav class="flex items-center space-x-1 text-sm">
|
|
<button
|
|
v-for="(crumb, index) in breadcrumbs"
|
|
:key="index"
|
|
@click="navigateToPath(crumb.path)"
|
|
:class="[
|
|
'px-2 py-1 rounded-md transition-colors',
|
|
index === breadcrumbs.length - 1
|
|
? 'text-gray-900 dark:text-gray-100 font-medium'
|
|
: 'text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300'
|
|
]"
|
|
>
|
|
{{ crumb.name }}
|
|
</button>
|
|
<Icon
|
|
v-if="index < breadcrumbs.length - 1"
|
|
name="mdi:chevron-right"
|
|
class="w-4 h-4 text-gray-400"
|
|
/>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Creation Form -->
|
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
<!-- Creation Type Selection -->
|
|
<div class="mb-6">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
|
What would you like to create?
|
|
</label>
|
|
<div class="grid grid-cols-3 gap-3">
|
|
<button
|
|
v-for="type in creationTypes"
|
|
:key="type.value"
|
|
@click="creationType = type.value"
|
|
:class="[
|
|
'p-4 border-2 rounded-lg transition-all text-left',
|
|
creationType === type.value
|
|
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/10'
|
|
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'
|
|
]"
|
|
>
|
|
<Icon
|
|
:name="type.icon"
|
|
:class="[
|
|
'w-8 h-8 mb-2',
|
|
creationType === type.value
|
|
? 'text-blue-600 dark:text-blue-400'
|
|
: 'text-gray-400'
|
|
]"
|
|
/>
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ type.label }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
{{ type.description }}
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Name Input -->
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
{{ creationType === 'folder' ? 'Folder' : creationType === 'subfolder' ? 'Subfolder' : 'Document' }} Name
|
|
</label>
|
|
<input
|
|
v-model="itemName"
|
|
type="text"
|
|
:placeholder="`Enter ${creationType} name...`"
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md
|
|
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
|
|
focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
@keyup.enter="canCreate && performCreation()"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Template Selection (for documents) -->
|
|
<div v-if="creationType === 'document'" class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
Document Template
|
|
</label>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<button
|
|
v-for="template in templateOptions"
|
|
:key="template.value"
|
|
@click="selectedTemplate = template.value"
|
|
:class="[
|
|
'p-3 border rounded-lg transition-all text-left flex items-center space-x-3',
|
|
selectedTemplate === template.value
|
|
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/10'
|
|
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'
|
|
]"
|
|
>
|
|
<Icon
|
|
:name="template.icon"
|
|
:class="[
|
|
'w-6 h-6',
|
|
selectedTemplate === template.value
|
|
? 'text-blue-600 dark:text-blue-400'
|
|
: 'text-gray-400'
|
|
]"
|
|
/>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ template.label }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Options -->
|
|
<div class="mb-4">
|
|
<button
|
|
@click="showAdvancedOptions = !showAdvancedOptions"
|
|
class="flex items-center text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"
|
|
>
|
|
<Icon
|
|
:name="showAdvancedOptions ? 'mdi:chevron-up' : 'mdi:chevron-down'"
|
|
class="w-4 h-4 mr-1"
|
|
/>
|
|
Advanced Options
|
|
</button>
|
|
|
|
<div v-if="showAdvancedOptions" class="mt-3 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
|
<div class="mb-4">
|
|
<label class="flex items-center">
|
|
<input
|
|
v-model="accessPermissions.inheritFromParent"
|
|
type="checkbox"
|
|
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded"
|
|
/>
|
|
<span class="ml-2 text-sm text-gray-900 dark:text-gray-100">
|
|
Inherit permissions from parent folder
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div v-if="!accessPermissions.inheritFromParent">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
Access Level
|
|
</label>
|
|
<select
|
|
v-model="accessPermissions.level"
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md
|
|
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
|
>
|
|
<option value="public">Public - Accessible to everyone</option>
|
|
<option value="internal">Internal - Company employees only</option>
|
|
<option value="confidential">Confidential - Restricted access</option>
|
|
<option value="secret">Secret - Highly restricted</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Selected Location Display -->
|
|
<div class="p-3 bg-blue-50 dark:bg-blue-900/10 rounded-lg">
|
|
<div class="flex items-center text-sm">
|
|
<Icon name="mdi:map-marker" class="w-4 h-4 text-blue-600 dark:text-blue-400 mr-2" />
|
|
<span class="text-gray-700 dark:text-gray-300">
|
|
Creating in: <span class="font-medium text-blue-600 dark:text-blue-400">{{ currentPathName }}</span>
|
|
</span>
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
{{ selectedPath }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Folder Contents Preview -->
|
|
<div class="flex-1 overflow-y-auto p-4">
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
|
|
Current folder contents:
|
|
</h4>
|
|
|
|
<div v-if="isLoading" class="flex items-center justify-center py-8">
|
|
<Icon name="mdi:loading" class="w-6 h-6 animate-spin text-gray-400" />
|
|
<span class="ml-2 text-gray-500 dark:text-gray-400">Loading...</span>
|
|
</div>
|
|
|
|
<div v-else-if="folderContents.length === 0" class="text-center py-8">
|
|
<Icon name="mdi:folder-open" class="w-12 h-12 text-gray-300 mx-auto mb-2" />
|
|
<p class="text-gray-500 dark:text-gray-400">This folder is empty</p>
|
|
</div>
|
|
|
|
<div v-else class="space-y-2">
|
|
<div
|
|
v-for="item in folderContents"
|
|
:key="item.id"
|
|
class="flex items-center p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
>
|
|
<Icon
|
|
:name="item.icon"
|
|
:class="[
|
|
'w-5 h-5 mr-3',
|
|
item.type === 'folder' ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400'
|
|
]"
|
|
/>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
|
{{ item.name }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">
|
|
{{ item.type === 'folder' ? `${item.itemCount} items` : item.size }} •
|
|
{{ formatDate(item.lastModified) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="flex items-center justify-between px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
Location: {{ selectedPath }}
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-3">
|
|
<RsButton
|
|
variant="secondary-outline"
|
|
@click="handleCancel"
|
|
>
|
|
Cancel
|
|
</RsButton>
|
|
|
|
<RsButton
|
|
variant="primary"
|
|
@click="performCreation"
|
|
:disabled="!canCreate"
|
|
>
|
|
<Icon :name="creationTypes.find(t => t.value === creationType)?.icon" class="w-4 h-4 mr-2" />
|
|
Create {{ creationType === 'folder' ? 'Folder' : creationType === 'subfolder' ? 'Subfolder' : 'Document' }}
|
|
</RsButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Windows Explorer styling */
|
|
.tree-node {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
}
|
|
|
|
/* Smooth transitions */
|
|
.transition-all {
|
|
transition: all 0.2s ease-in-out;
|
|
}
|
|
|
|
/* Custom scrollbar for tree */
|
|
.overflow-y-auto::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-thumb {
|
|
background: #cbd5e1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
|
|
background: #94a3b8;
|
|
}
|
|
</style> |